diff --git a/AUTHORS.md b/AUTHORS.md index 8e9d35cf2..e968d1d01 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -19,6 +19,7 @@ Programmers Alexander Nadeau (wareya) Alexander Olofsson (Ace) Artem Kotsynyak (greye) + artemutin Arthur Moore (EmperorArthur) athile Bret Curtis (psi29a) diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index f9a73a09f..6af04e8fc 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -67,7 +67,7 @@ opencs_hdrs_noqt (view/doc opencs_units (view/world table tablesubview scriptsubview util regionmapsubview tablebottombox creator genericcreator - cellcreator referenceablecreator referencecreator scenesubview + cellcreator referenceablecreator startscriptcreator referencecreator scenesubview infocreator scriptedit dialoguesubview previewsubview regionmap dragrecordtable nestedtable dialoguespinbox recordbuttonbar tableeditidaction scripterrortable extendedcommandconfigurator ) @@ -85,12 +85,12 @@ opencs_units (view/widget opencs_units (view/render scenewidget worldspacewidget pagedworldspacewidget unpagedworldspacewidget - previewwidget editmode + previewwidget editmode instancemode ) opencs_units_noqt (view/render lighting lightingday lightingnight - lightingbright object cell terrainstorage + lightingbright object cell terrainstorage tagbase cellarrow ) opencs_hdrs_noqt (view/render diff --git a/apps/opencs/model/settings/usersettings.cpp b/apps/opencs/model/settings/usersettings.cpp index 24680ada3..3e5ab24d1 100644 --- a/apps/opencs/model/settings/usersettings.cpp +++ b/apps/opencs/model/settings/usersettings.cpp @@ -371,6 +371,57 @@ void CSMSettings::UserSettings::buildSettingModelDefaults() "list go to the first/last item"); } + declareSection ("scene-input", "3D Scene Input"); + { + QString left ("Left Mouse-Button"); + QString cLeft ("Ctrl-Left Mouse-Button"); + QString right ("Right Mouse-Button"); + QString cRight ("Ctrl-Right Mouse-Button"); + QString middle ("Middle Mouse-Button"); + QString cMiddle ("Ctrl-Middle Mouse-Button"); + + QStringList values; + values << left << cLeft << right << cRight << middle << cMiddle; + + Setting *primaryNavigation = createSetting (Type_ComboBox, "p-navi", "Primary Camera Navigation Button"); + primaryNavigation->setDeclaredValues (values); + primaryNavigation->setDefaultValue (left); + + Setting *secondaryNavigation = createSetting (Type_ComboBox, "s-navi", "Secondary Camera Navigation Button"); + secondaryNavigation->setDeclaredValues (values); + secondaryNavigation->setDefaultValue (cLeft); + + Setting *primaryEditing = createSetting (Type_ComboBox, "p-edit", "Primary Editing Button"); + primaryEditing->setDeclaredValues (values); + primaryEditing->setDefaultValue (right); + + Setting *secondaryEditing = createSetting (Type_ComboBox, "s-edit", "Secondary Editing Button"); + secondaryEditing->setDeclaredValues (values); + secondaryEditing->setDefaultValue (cRight); + + Setting *selection = createSetting (Type_ComboBox, "select", "Selection Button"); + selection->setDeclaredValues (values); + selection->setDefaultValue (middle); + + Setting *contextSensitive = createSetting (Type_CheckBox, "context-select", "Context Sensitive Selection"); + contextSensitive->setDefaultValue ("false"); + + Setting *dragMouseSensitivity = createSetting (Type_DoubleSpinBox, "drag-factor", + "Mouse sensitivity during drag operations"); + dragMouseSensitivity->setDefaultValue (1.0); + dragMouseSensitivity->setRange (0.001, 100.0); + + Setting *dragWheelSensitivity = createSetting (Type_DoubleSpinBox, "drag-wheel-factor", + "Mouse wheel sensitivity during drag operations"); + dragWheelSensitivity->setDefaultValue (1.0); + dragWheelSensitivity->setRange (0.001, 100.0); + + Setting *dragShiftFactor = createSetting (Type_DoubleSpinBox, "drag-shift-factor", + "Acceleration factor during drag operations while holding down shift"); + dragShiftFactor->setDefaultValue (4.0); + dragShiftFactor->setRange (0.001, 100.0); + } + { /****************************************************************** * There are three types of values: diff --git a/apps/opencs/model/world/cellcoordinates.cpp b/apps/opencs/model/world/cellcoordinates.cpp index 95e206e2d..3ef3e6c69 100644 --- a/apps/opencs/model/world/cellcoordinates.cpp +++ b/apps/opencs/model/world/cellcoordinates.cpp @@ -32,6 +32,23 @@ std::string CSMWorld::CellCoordinates::getId (const std::string& worldspace) con return stream.str(); } +std::pair CSMWorld::CellCoordinates::fromId ( + const std::string& id) +{ + // no worldspace for now, needs to be changed for 1.1 + if (!id.empty() && id[0]=='#') + { + int x, y; + char ignore; + + std::istringstream stream (id); + if (stream >> ignore >> x >> y) + return std::make_pair (CellCoordinates (x, y), true); + } + + return std::make_pair (CellCoordinates(), false); +} + bool CSMWorld::operator== (const CellCoordinates& left, const CellCoordinates& right) { return left.getX()==right.getX() && left.getY()==right.getY(); diff --git a/apps/opencs/model/world/cellcoordinates.hpp b/apps/opencs/model/world/cellcoordinates.hpp index ee395ea7e..63db60c29 100644 --- a/apps/opencs/model/world/cellcoordinates.hpp +++ b/apps/opencs/model/world/cellcoordinates.hpp @@ -28,6 +28,12 @@ namespace CSMWorld std::string getId (const std::string& worldspace) const; ///< Return the ID for the cell at these coordinates. + + /// \return first: CellCoordinates (or 0, 0 if cell does not have coordinates), + /// second: is cell paged? + /// + /// \note The worldspace part of \a id is ignored + static std::pair fromId (const std::string& id); }; bool operator== (const CellCoordinates& left, const CellCoordinates& right); diff --git a/apps/opencs/model/world/columnbase.cpp b/apps/opencs/model/world/columnbase.cpp index 2143ec730..39232d442 100644 --- a/apps/opencs/model/world/columnbase.cpp +++ b/apps/opencs/model/world/columnbase.cpp @@ -77,7 +77,7 @@ bool CSMWorld::ColumnBase::isId (Display display) Display_Video, Display_Id, - Display_SkillImpact, + Display_SkillId, Display_EffectRange, Display_EffectId, Display_PartRefType, @@ -85,7 +85,6 @@ bool CSMWorld::ColumnBase::isId (Display display) Display_InfoCondFunc, Display_InfoCondVar, Display_InfoCondComp, - Display_RaceSkill, Display_None }; diff --git a/apps/opencs/model/world/columnbase.hpp b/apps/opencs/model/world/columnbase.hpp index 400e31333..e2871d4d8 100644 --- a/apps/opencs/model/world/columnbase.hpp +++ b/apps/opencs/model/world/columnbase.hpp @@ -113,7 +113,7 @@ namespace CSMWorld Display_SoundGeneratorType, Display_School, Display_Id, - Display_SkillImpact, + Display_SkillId, Display_EffectRange, Display_EffectId, Display_PartRefType, @@ -121,7 +121,6 @@ namespace CSMWorld Display_InfoCondFunc, Display_InfoCondVar, Display_InfoCondComp, - Display_RaceSkill, Display_String32, Display_LongString256, diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index 2e0a697a5..d0d3a1671 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -249,7 +249,7 @@ namespace CSMWorld { ColumnId_AiWanderDist, "Wander Dist" }, { ColumnId_AiDuration, "Ai Duration" }, { ColumnId_AiWanderToD, "Wander ToD" }, - { ColumnId_AiWanderIdle, "Wander Idle" }, + //{ ColumnId_AiWanderIdle, "Wander Idle" }, { ColumnId_AiWanderRepeat, "Wander Repeat" }, { ColumnId_AiActivateName, "Activate" }, { ColumnId_AiTargetId, "Target ID" }, @@ -268,7 +268,7 @@ namespace CSMWorld { ColumnId_LevelledItemChanceNone, "Chance None" }, { ColumnId_PowerList, "Powers" }, - { ColumnId_SkillImpact, "Skill" }, + { ColumnId_Skill, "Skill" }, { ColumnId_InfoList, "Info List" }, { ColumnId_InfoCondition, "Info Conditions" }, @@ -293,8 +293,7 @@ namespace CSMWorld { ColumnId_NpcPersistence, "Persistent" }, { ColumnId_RaceAttributes, "Race Attributes" }, - { ColumnId_RaceMaleValue, "Male Attrib" }, - { ColumnId_RaceFemaleValue, "Female Attrib" }, + { ColumnId_Male, "Male" }, { ColumnId_RaceSkillBonus, "Skill Bonus" }, { ColumnId_RaceBonus, "Bonus" }, @@ -317,6 +316,15 @@ namespace CSMWorld { ColumnId_MaxAttack, "Max Attack" }, { ColumnId_CreatureMisc, "Creature Misc" }, + { ColumnId_Idle1, "Idle 1" }, + { ColumnId_Idle2, "Idle 2" }, + { ColumnId_Idle3, "Idle 3" }, + { ColumnId_Idle4, "Idle 4" }, + { ColumnId_Idle5, "Idle 5" }, + { ColumnId_Idle6, "Idle 6" }, + { ColumnId_Idle7, "Idle 7" }, + { ColumnId_Idle8, "Idle 8" }, + { ColumnId_UseValue1, "Use value 1" }, { ColumnId_UseValue2, "Use value 2" }, { ColumnId_UseValue3, "Use value 3" }, @@ -572,7 +580,7 @@ namespace case CSMWorld::Columns::ColumnId_MeshType: return sMeshTypes; case CSMWorld::Columns::ColumnId_SoundGeneratorType: return sSoundGeneratorType; case CSMWorld::Columns::ColumnId_School: return sSchools; - case CSMWorld::Columns::ColumnId_SkillImpact: return sSkills; + case CSMWorld::Columns::ColumnId_Skill: return sSkills; case CSMWorld::Columns::ColumnId_EffectRange: return sEffectRange; case CSMWorld::Columns::ColumnId_EffectId: return sEffectId; case CSMWorld::Columns::ColumnId_PartRefType: return sPartRefType; diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index fb25004ec..a504e5f65 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -241,7 +241,7 @@ namespace CSMWorld ColumnId_AiWanderDist = 221, ColumnId_AiDuration = 222, ColumnId_AiWanderToD = 223, - ColumnId_AiWanderIdle = 224, + // unused ColumnId_AiWanderRepeat = 225, ColumnId_AiActivateName = 226, // use ColumnId_PosX, etc for AI destinations @@ -261,7 +261,7 @@ namespace CSMWorld ColumnId_LevelledItemChanceNone = 238, ColumnId_PowerList = 239, - ColumnId_SkillImpact = 240, // impact from magic effects + ColumnId_Skill = 240, ColumnId_InfoList = 241, ColumnId_InfoCondition = 242, @@ -288,8 +288,8 @@ namespace CSMWorld ColumnId_NpcPersistence = 261, ColumnId_RaceAttributes = 262, - ColumnId_RaceMaleValue = 263, - ColumnId_RaceFemaleValue = 264, + ColumnId_Male = 263, + // unused ColumnId_RaceSkillBonus = 265, // unused ColumnId_RaceBonus = 267, @@ -316,6 +316,15 @@ namespace CSMWorld ColumnId_MaxAttack = 284, ColumnId_CreatureMisc = 285, + ColumnId_Idle1 = 286, + ColumnId_Idle2 = 287, + ColumnId_Idle3 = 288, + ColumnId_Idle4 = 289, + ColumnId_Idle5 = 290, + ColumnId_Idle6 = 291, + ColumnId_Idle7 = 292, + ColumnId_Idle8 = 293, + // Allocated to a separate value range, so we don't get a collision should we ever need // to extend the number of use values. ColumnId_UseValue1 = 0x10000, diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index a27ab5094..8acdac84f 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -143,15 +143,15 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_Attribute, ColumnBase::Flag_Dialogue, false)); mRaces.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_RaceMaleValue, ColumnBase::Display_Integer)); + new NestedChildColumn (Columns::ColumnId_Male, ColumnBase::Display_Integer)); mRaces.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_RaceFemaleValue, ColumnBase::Display_Integer)); + new NestedChildColumn (Columns::ColumnId_Female, ColumnBase::Display_Integer)); // Race skill bonus mRaces.addColumn (new NestedParentColumn (Columns::ColumnId_RaceSkillBonus)); index = mRaces.getColumns()-1; mRaces.addAdapter (std::make_pair(&mRaces.getColumn(index), new RaceSkillsBonusAdapter())); mRaces.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_SkillImpact, ColumnBase::Display_SkillImpact)); + new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_SkillId)); mRaces.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_RaceBonus, ColumnBase::Display_Integer)); @@ -213,7 +213,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mSpells.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_EffectId, ColumnBase::Display_EffectId)); mSpells.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_SkillImpact, ColumnBase::Display_SkillImpact)); + new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_SkillId)); mSpells.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_Attribute)); mSpells.getNestableColumn(index)->addColumn( @@ -329,7 +329,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc mEnchantments.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_EffectId, ColumnBase::Display_EffectId)); mEnchantments.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_SkillImpact, ColumnBase::Display_SkillImpact)); + new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_SkillId)); mEnchantments.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_Attribute)); mEnchantments.getNestableColumn(index)->addColumn( diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index 53da63806..4ac27b6e9 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -1532,6 +1532,8 @@ namespace CSMWorld virtual ~ActorAiRefIdAdapter() {} + // FIXME: should check if the AI package type is already in the list and use a default + // that wasn't used already (in extreme case do not add anything at all? virtual void addNestedRow (const RefIdColumn *column, RefIdData& data, int index, int position) const { @@ -1615,6 +1617,7 @@ namespace CSMWorld switch (subColIndex) { case 0: + // FIXME: should more than one AI package type be allowed? Check vanilla switch (content.mType) { case ESM::AI_Wander: return 0; @@ -1642,47 +1645,52 @@ namespace CSMWorld else return QVariant(); case 4: // wander idle + case 5: + case 6: + case 7: + case 8: + case 9: + case 10: + case 11: if (content.mType == ESM::AI_Wander) - { - return static_cast(content.mWander.mIdle[0]); // FIXME: - } + return static_cast(content.mWander.mIdle[subColIndex-4]); else return QVariant(); - case 5: // wander repeat + case 12: // wander repeat if (content.mType == ESM::AI_Wander) return content.mWander.mShouldRepeat != 0; else return QVariant(); - case 6: // activate name + case 13: // activate name if (content.mType == ESM::AI_Activate) return QString(content.mActivate.mName.toString().c_str()); else return QVariant(); - case 7: // target id + case 14: // target id if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) return QString(content.mTarget.mId.toString().c_str()); else return QVariant(); - case 8: // target cell + case 15: // target cell if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) return QString::fromUtf8(content.mCellName.c_str()); else return QVariant(); - case 9: + case 16: if (content.mType == ESM::AI_Travel) return content.mTravel.mX; else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) return content.mTarget.mX; else return QVariant(); - case 10: + case 17: if (content.mType == ESM::AI_Travel) return content.mTravel.mY; else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) return content.mTarget.mY; else return QVariant(); - case 11: + case 18: if (content.mType == ESM::AI_Travel) return content.mTravel.mZ; else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) @@ -1712,11 +1720,12 @@ namespace CSMWorld case 0: // ai package type switch (value.toInt()) { - case 0: content.mType = ESM::AI_Wander; - case 1: content.mType = ESM::AI_Travel; - case 2: content.mType = ESM::AI_Follow; - case 3: content.mType = ESM::AI_Escort; - case 4: content.mType = ESM::AI_Activate; + case 0: content.mType = ESM::AI_Wander; break; + case 1: content.mType = ESM::AI_Travel; break; + case 2: content.mType = ESM::AI_Follow; break; + case 3: content.mType = ESM::AI_Escort; break; + case 4: content.mType = ESM::AI_Activate; break; + default: return; // return without saving } break; // always save @@ -1725,6 +1734,8 @@ namespace CSMWorld content.mWander.mDistance = static_cast(value.toInt()); else return; // return without saving + + break; // always save case 2: if (content.mType == ESM::AI_Wander || content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) @@ -1736,62 +1747,77 @@ namespace CSMWorld content.mWander.mTimeOfDay = static_cast(value.toInt()); else return; // return without saving + + break; // always save case 4: + case 5: + case 6: + case 7: + case 8: + case 9: + case 10: + case 11: if (content.mType == ESM::AI_Wander) - break; // FIXME: idle + content.mWander.mIdle[subColIndex-4] = static_cast(value.toInt()); else return; // return without saving - case 5: + + break; // always save + case 12: if (content.mType == ESM::AI_Wander) - { content.mWander.mShouldRepeat = static_cast(value.toInt()); - break; - } - case 6: // NAME32 + else + return; // return without saving + + break; // always save + case 13: // NAME32 if (content.mType == ESM::AI_Activate) - { content.mActivate.mName.assign(value.toString().toUtf8().constData()); - break; - } else return; // return without saving - case 7: // NAME32 + + break; // always save + case 14: // NAME32 if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) - { content.mTarget.mId.assign(value.toString().toUtf8().constData()); - break; - } else return; // return without saving - case 8: + + break; // always save + case 15: if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) - { content.mCellName = std::string(value.toString().toUtf8().constData()); - break; - } else return; // return without saving - case 9: + + break; // always save + case 16: if (content.mType == ESM::AI_Travel) content.mTravel.mZ = value.toFloat(); else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) content.mTarget.mZ = value.toFloat(); else return; // return without saving - case 10: + + break; // always save + case 17: if (content.mType == ESM::AI_Travel) content.mTravel.mZ = value.toFloat(); else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) content.mTarget.mZ = value.toFloat(); else return; // return without saving - case 11: + + break; // always save + case 18: if (content.mType == ESM::AI_Travel) content.mTravel.mZ = value.toFloat(); else if (content.mType == ESM::AI_Follow || content.mType == ESM::AI_Escort) content.mTarget.mZ = value.toFloat(); else return; // return without saving + + break; // always save default: throw std::runtime_error("Trying to access non-existing column in the nested table!"); } @@ -1801,7 +1827,7 @@ namespace CSMWorld virtual int getNestedColumnsCount(const RefIdColumn *column, const RefIdData& data) const { - return 12; + return 19; } virtual int getNestedRowsCount(const RefIdColumn *column, const RefIdData& data, int index) const diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index d7b7cedfa..3b316bea3 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -83,7 +83,7 @@ CSMWorld::RefIdCollection::RefIdCollection() mColumns.back().addColumn( new NestedChildColumn (Columns::ColumnId_EffectId, ColumnBase::Display_EffectId)); mColumns.back().addColumn( - new NestedChildColumn (Columns::ColumnId_SkillImpact, ColumnBase::Display_SkillImpact)); + new NestedChildColumn (Columns::ColumnId_Skill, ColumnBase::Display_SkillId)); mColumns.back().addColumn( new NestedChildColumn (Columns::ColumnId_Attribute, ColumnBase::Display_Attribute)); mColumns.back().addColumn( @@ -193,8 +193,24 @@ CSMWorld::RefIdCollection::RefIdCollection() new RefIdColumn (Columns::ColumnId_AiDuration, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_AiWanderToD, CSMWorld::ColumnBase::Display_Integer)); + + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Idle1, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Idle2, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Idle3, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Idle4, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_AiWanderIdle, CSMWorld::ColumnBase::Display_Integer)); + new RefIdColumn (Columns::ColumnId_Idle5, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Idle6, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Idle7, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( + new RefIdColumn (Columns::ColumnId_Idle8, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_AiWanderRepeat, CSMWorld::ColumnBase::Display_Boolean)); mColumns.back().addColumn( @@ -491,7 +507,7 @@ CSMWorld::RefIdCollection::RefIdCollection() skillsMap.insert(std::make_pair(UniversalId::Type_Npc, new NpcSkillsRefIdAdapter())); mNestedAdapters.push_back (std::make_pair(&mColumns.back(), skillsMap)); mColumns.back().addColumn( - new RefIdColumn (Columns::ColumnId_SkillImpact, CSMWorld::ColumnBase::Display_SkillImpact, false, false)); + new RefIdColumn (Columns::ColumnId_Skill, CSMWorld::ColumnBase::Display_SkillId, false, false)); mColumns.back().addColumn( new RefIdColumn (Columns::ColumnId_UChar, CSMWorld::ColumnBase::Display_Integer)); diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index ea11bb0f9..38088c6d7 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -435,6 +435,8 @@ CSVDoc::View::View (ViewManager& viewManager, CSMDoc::Document *document, int to mOperations = new Operations; addDockWidget (Qt::BottomDockWidgetArea, mOperations); + setContextMenuPolicy(Qt::NoContextMenu); + updateTitle(); setupUi(); diff --git a/apps/opencs/view/doc/viewmanager.cpp b/apps/opencs/view/doc/viewmanager.cpp index ca4ad2d00..728e69a7a 100644 --- a/apps/opencs/view/doc/viewmanager.cpp +++ b/apps/opencs/view/doc/viewmanager.cpp @@ -97,7 +97,7 @@ CSVDoc::ViewManager::ViewManager (CSMDoc::DocumentManager& documentManager) { CSMWorld::ColumnBase::Display_Gender, CSMWorld::Columns::ColumnId_Gender, true }, { CSMWorld::ColumnBase::Display_SoundGeneratorType, CSMWorld::Columns::ColumnId_SoundGeneratorType, false }, { CSMWorld::ColumnBase::Display_School, CSMWorld::Columns::ColumnId_School, false }, - { CSMWorld::ColumnBase::Display_SkillImpact, CSMWorld::Columns::ColumnId_SkillImpact, true }, + { CSMWorld::ColumnBase::Display_SkillId, CSMWorld::Columns::ColumnId_Skill, true }, { CSMWorld::ColumnBase::Display_EffectRange, CSMWorld::Columns::ColumnId_EffectRange, false }, { CSMWorld::ColumnBase::Display_EffectId, CSMWorld::Columns::ColumnId_EffectId, false }, { CSMWorld::ColumnBase::Display_PartRefType, CSMWorld::Columns::ColumnId_PartRefType, false }, diff --git a/apps/opencs/view/filter/editwidget.cpp b/apps/opencs/view/filter/editwidget.cpp index 657a47750..600fa4f3b 100644 --- a/apps/opencs/view/filter/editwidget.cpp +++ b/apps/opencs/view/filter/editwidget.cpp @@ -5,14 +5,17 @@ #include #include "../../model/world/data.hpp" +#include "../../model/world/idtablebase.hpp" +#include "../../model/world/columns.hpp" CSVFilter::EditWidget::EditWidget (CSMWorld::Data& data, QWidget *parent) -: QLineEdit (parent), mParser (data) +: QLineEdit (parent), mParser (data), mIsEmpty(true) { mPalette = palette(); connect (this, SIGNAL (textChanged (const QString&)), this, SLOT (textChanged (const QString&))); - QAbstractItemModel *model = data.getTableModel (CSMWorld::UniversalId::Type_Filters); + const CSMWorld::IdTableBase *model = + static_cast (data.getTableModel (CSMWorld::UniversalId::Type_Filters)); connect (model, SIGNAL (dataChanged (const QModelIndex &, const QModelIndex&)), this, SLOT (filterDataChanged (const QModelIndex &, const QModelIndex&)), @@ -23,10 +26,23 @@ CSVFilter::EditWidget::EditWidget (CSMWorld::Data& data, QWidget *parent) connect (model, SIGNAL (rowsInserted (const QModelIndex&, int, int)), this, SLOT (filterRowsInserted (const QModelIndex&, int, int)), Qt::QueuedConnection); + + mStateColumnIndex = model->findColumnIndex(CSMWorld::Columns::ColumnId_Modification); + mDescColumnIndex = model->findColumnIndex(CSMWorld::Columns::ColumnId_Description); } void CSVFilter::EditWidget::textChanged (const QString& text) { + //no need to parse and apply filter if it was empty and now is empty too. + //e.g. - we modifiing content of filter with already opened some other (big) tables. + if (text.length() == 0){ + if (mIsEmpty) + return; + else + mIsEmpty = true; + }else + mIsEmpty = false; + if (mParser.parse (text.toUtf8().constData())) { setPalette (mPalette); @@ -45,7 +61,9 @@ void CSVFilter::EditWidget::textChanged (const QString& text) void CSVFilter::EditWidget::filterDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { - textChanged (text()); + for (int i = topLeft.column(); i <= bottomRight.column(); ++i) + if (i != mStateColumnIndex && i != mDescColumnIndex) + textChanged (text()); } void CSVFilter::EditWidget::filterRowsRemoved (const QModelIndex& parent, int start, int end) diff --git a/apps/opencs/view/filter/editwidget.hpp b/apps/opencs/view/filter/editwidget.hpp index a0f9f8919..f672877d9 100644 --- a/apps/opencs/view/filter/editwidget.hpp +++ b/apps/opencs/view/filter/editwidget.hpp @@ -25,6 +25,9 @@ namespace CSVFilter CSMFilter::Parser mParser; QPalette mPalette; + bool mIsEmpty; + int mStateColumnIndex; + int mDescColumnIndex; public: diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index 28a48cfbd..da40f7e7c 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -9,6 +9,7 @@ #include "../../model/world/columns.hpp" #include "../../model/world/data.hpp" #include "../../model/world/refcollection.hpp" +#include "../../model/world/cellcoordinates.hpp" #include "elements.hpp" #include "terrainstorage.hpp" @@ -50,33 +51,39 @@ bool CSVRender::Cell::addObjects (int start, int end) return modified; } -CSVRender::Cell::Cell (CSMWorld::Data& data, osg::Group* rootNode, const std::string& id) -: mData (data), mId (Misc::StringUtils::lowerCase (id)), mX(0), mY(0) +CSVRender::Cell::Cell (CSMWorld::Data& data, osg::Group* rootNode, const std::string& id, + bool deleted) +: mData (data), mId (Misc::StringUtils::lowerCase (id)), mDeleted (deleted) { + std::pair result = CSMWorld::CellCoordinates::fromId (id); + + if (result.second) + mCoordinates = result.first; + mCellNode = new osg::Group; rootNode->addChild(mCellNode); - CSMWorld::IdTable& references = dynamic_cast ( - *mData.getTableModel (CSMWorld::UniversalId::Type_References)); + if (!mDeleted) + { + CSMWorld::IdTable& references = dynamic_cast ( + *mData.getTableModel (CSMWorld::UniversalId::Type_References)); - int rows = references.rowCount(); + int rows = references.rowCount(); - addObjects (0, rows-1); + addObjects (0, rows-1); - const CSMWorld::IdCollection& land = mData.getLand(); - int landIndex = land.searchId(mId); - if (landIndex != -1) - { - const ESM::Land& esmLand = land.getRecord(mId).get(); - - if (esmLand.getLandData (ESM::Land::DATA_VHGT)) + const CSMWorld::IdCollection& land = mData.getLand(); + int landIndex = land.searchId(mId); + if (landIndex != -1) { - mTerrain.reset(new Terrain::TerrainGrid(mCellNode, data.getResourceSystem().get(), NULL, new TerrainStorage(mData), Element_Terrain<<1)); - mTerrain->loadCell(esmLand.mX, - esmLand.mY); + const ESM::Land& esmLand = land.getRecord(mId).get(); - mX = esmLand.mX; - mY = esmLand.mY; + if (esmLand.getLandData (ESM::Land::DATA_VHGT)) + { + mTerrain.reset(new Terrain::TerrainGrid(mCellNode, data.getResourceSystem().get(), NULL, new TerrainStorage(mData), Element_Terrain<<1)); + mTerrain->loadCell(esmLand.mX, + esmLand.mY); + } } } } @@ -122,6 +129,9 @@ bool CSVRender::Cell::referenceableAboutToBeRemoved (const QModelIndex& parent, bool CSVRender::Cell::referenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { + if (mDeleted) + return false; + CSMWorld::IdTable& references = dynamic_cast ( *mData.getTableModel (CSMWorld::UniversalId::Type_References)); @@ -189,6 +199,9 @@ bool CSVRender::Cell::referenceAboutToBeRemoved (const QModelIndex& parent, int if (parent.isValid()) return false; + if (mDeleted) + return false; + CSMWorld::IdTable& references = dynamic_cast ( *mData.getTableModel (CSMWorld::UniversalId::Type_References)); @@ -209,5 +222,57 @@ bool CSVRender::Cell::referenceAdded (const QModelIndex& parent, int start, int if (parent.isValid()) return false; + if (mDeleted) + return false; + return addObjects (start, end); } + +void CSVRender::Cell::setSelection (int elementMask, Selection mode) +{ + if (elementMask & Element_Reference) + { + for (std::map::const_iterator iter (mObjects.begin()); + iter!=mObjects.end(); ++iter) + { + bool selected = false; + + switch (mode) + { + case Selection_Clear: selected = false; break; + case Selection_All: selected = true; break; + case Selection_Invert: selected = !iter->second->getSelected(); break; + } + + iter->second->setSelected (selected); + } + } +} + +void CSVRender::Cell::setCellArrows (int mask) +{ + for (int i=0; i<4; ++i) + { + CellArrow::Direction direction = static_cast (1< mCellNode; std::map mObjects; std::auto_ptr mTerrain; - int mX; - int mY; + CSMWorld::CellCoordinates mCoordinates; + std::auto_ptr mCellArrows[4]; + bool mDeleted; /// Ignored if cell does not have an object with the given ID. /// @@ -51,7 +54,19 @@ namespace CSVRender public: - Cell (CSMWorld::Data& data, osg::Group* rootNode, const std::string& id); + enum Selection + { + Selection_Clear, + Selection_All, + Selection_Invert + }; + + public: + + /// \note Deleted covers both cells that are deleted and cells that don't exist in + /// the first place. + Cell (CSMWorld::Data& data, osg::Group* rootNode, const std::string& id, + bool deleted = false); ~Cell(); @@ -75,6 +90,15 @@ namespace CSVRender /// \return Did this call result in a modification of the visual representation of /// this cell? bool referenceAdded (const QModelIndex& parent, int start, int end); + + void setSelection (int elementMask, Selection mode); + + void setCellArrows (int mask); + + /// Returns 0, 0 in case of an unpaged cell. + CSMWorld::CellCoordinates getCoordinates() const; + + bool isDeleted() const; }; } diff --git a/apps/opencs/view/render/cellarrow.cpp b/apps/opencs/view/render/cellarrow.cpp new file mode 100644 index 000000000..fce5ffda5 --- /dev/null +++ b/apps/opencs/view/render/cellarrow.cpp @@ -0,0 +1,161 @@ + +#include "cellarrow.hpp" + +#include +#include +#include +#include +#include + +#include "elements.hpp" + +CSVRender::CellArrowTag::CellArrowTag (CellArrow *arrow) +: TagBase (Element_CellArrow), mArrow (arrow) +{} + +CSVRender::CellArrow *CSVRender::CellArrowTag::getCellArrow() const +{ + return mArrow; +} + + +void CSVRender::CellArrow::adjustTransform() +{ + // position + const int cellSize = 8192; + const int offset = cellSize / 2 + 800; + + int x = mCoordinates.getX()*cellSize + cellSize/2; + int y = mCoordinates.getY()*cellSize + cellSize/2; + + float xr = 0; + float yr = 0; + float zr = 0; + + float angle = osg::DegreesToRadians (90.0f); + + switch (mDirection) + { + case Direction_North: y += offset; xr = -angle; zr = angle; break; + case Direction_West: x -= offset; yr = -angle; break; + case Direction_South: y -= offset; xr = angle; zr = angle; break; + case Direction_East: x += offset; yr = angle; break; + }; + + mBaseNode->setPosition (osg::Vec3f (x, y, 0)); + + // orientation + osg::Quat xr2 (xr, osg::Vec3f (1,0,0)); + osg::Quat yr2 (yr, osg::Vec3f (0,1,0)); + osg::Quat zr2 (zr, osg::Vec3f (0,0,1)); + mBaseNode->setAttitude (zr2*yr2*xr2); +} + +void CSVRender::CellArrow::buildShape() +{ + osg::ref_ptr geometry (new osg::Geometry); + + const int arrowWidth = 4000; + const int arrowLength = 1500; + const int arrowHeight = 500; + + osg::Vec3Array *vertices = new osg::Vec3Array; + for (int i2=0; i2<2; ++i2) + for (int i=0; i<2; ++i) + { + float height = i ? -arrowHeight/2 : arrowHeight/2; + vertices->push_back (osg::Vec3f (height, -arrowWidth/2, 0)); + vertices->push_back (osg::Vec3f (height, arrowWidth/2, 0)); + vertices->push_back (osg::Vec3f (height, 0, arrowLength)); + } + + geometry->setVertexArray (vertices); + + osg::DrawElementsUShort *primitives = new osg::DrawElementsUShort (osg::PrimitiveSet::TRIANGLES, 0); + + // top + primitives->push_back (0); + primitives->push_back (1); + primitives->push_back (2); + + // bottom + primitives->push_back (5); + primitives->push_back (4); + primitives->push_back (3); + + // back + primitives->push_back (3+6); + primitives->push_back (4+6); + primitives->push_back (1+6); + + primitives->push_back (3+6); + primitives->push_back (1+6); + primitives->push_back (0+6); + + // sides + primitives->push_back (0+6); + primitives->push_back (2+6); + primitives->push_back (5+6); + + primitives->push_back (0+6); + primitives->push_back (5+6); + primitives->push_back (3+6); + + primitives->push_back (4+6); + primitives->push_back (5+6); + primitives->push_back (2+6); + + primitives->push_back (4+6); + primitives->push_back (2+6); + primitives->push_back (1+6); + + geometry->addPrimitiveSet (primitives); + + osg::Vec4Array *colours = new osg::Vec4Array; + + for (int i=0; i<6; ++i) + colours->push_back (osg::Vec4f (1.0f, 0.0f, 0.0f, 1.0f)); + for (int i=0; i<6; ++i) + colours->push_back (osg::Vec4f (0.8f, (i==2 || i==5) ? 0.6f : 0.4f, 0.0f, 1.0f)); + + geometry->setColorArray (colours, osg::Array::BIND_PER_VERTEX); + + geometry->getOrCreateStateSet()->setMode (GL_LIGHTING, osg::StateAttribute::OFF); + + osg::ref_ptr geode (new osg::Geode); + geode->addDrawable (geometry); + + mBaseNode->addChild (geode); +} + +CSVRender::CellArrow::CellArrow (osg::Group *cellNode, Direction direction, + const CSMWorld::CellCoordinates& coordinates) +: mDirection (direction), mParentNode (cellNode), mCoordinates (coordinates) +{ + mBaseNode = new osg::PositionAttitudeTransform; + + mBaseNode->setUserData (new CellArrowTag (this)); + + mParentNode->addChild (mBaseNode); + + // 0x1 reserved for separating cull and update visitors + mBaseNode->setNodeMask (Element_CellArrow<<1); + + adjustTransform(); + buildShape(); +} + +CSVRender::CellArrow::~CellArrow() +{ + mParentNode->removeChild (mBaseNode); +} + +CSMWorld::CellCoordinates CSVRender::CellArrow::getCoordinates() const +{ + return mCoordinates; +} + +CSVRender::CellArrow::Direction CSVRender::CellArrow::getDirection() const +{ + return mDirection; +} diff --git a/apps/opencs/view/render/cellarrow.hpp b/apps/opencs/view/render/cellarrow.hpp new file mode 100644 index 000000000..cbbcc9d5e --- /dev/null +++ b/apps/opencs/view/render/cellarrow.hpp @@ -0,0 +1,72 @@ +#ifndef OPENCS_VIEW_CELLARROW_H +#define OPENCS_VIEW_CELLARROW_H + +#include "tagbase.hpp" + +#include + +#include "../../model/world/cellcoordinates.hpp" + +namespace osg +{ + class PositionAttitudeTransform; + class Group; +} + +namespace CSVRender +{ + class CellArrow; + + class CellArrowTag : public TagBase + { + CellArrow *mArrow; + + public: + + CellArrowTag (CellArrow *arrow); + + CellArrow *getCellArrow() const; + }; + + + class CellArrow + { + public: + + enum Direction + { + Direction_North = 1, + Direction_West = 2, + Direction_South = 4, + Direction_East = 8 + }; + + private: + + // not implemented + CellArrow (const CellArrow&); + CellArrow& operator= (const CellArrow&); + + Direction mDirection; + osg::Group* mParentNode; + osg::ref_ptr mBaseNode; + CSMWorld::CellCoordinates mCoordinates; + + void adjustTransform(); + + void buildShape(); + + public: + + CellArrow (osg::Group *cellNode, Direction direction, + const CSMWorld::CellCoordinates& coordinates); + + ~CellArrow(); + + CSMWorld::CellCoordinates getCoordinates() const; + + Direction getDirection() const; + }; +} + +#endif diff --git a/apps/opencs/view/render/editmode.cpp b/apps/opencs/view/render/editmode.cpp index 8a99ba049..4235faf76 100644 --- a/apps/opencs/view/render/editmode.cpp +++ b/apps/opencs/view/render/editmode.cpp @@ -1,7 +1,13 @@ #include "editmode.hpp" +#include "tagbase.hpp" #include "worldspacewidget.hpp" +CSVRender::WorldspaceWidget& CSVRender::EditMode::getWorldspaceWidget() +{ + return *mWorldspaceWidget; +} + CSVRender::EditMode::EditMode (WorldspaceWidget *worldspaceWidget, const QIcon& icon, unsigned int mask, const QString& tooltip, QWidget *parent) : ModeButton (icon, tooltip, parent), mWorldspaceWidget (worldspaceWidget), mMask (mask) @@ -15,4 +21,44 @@ unsigned int CSVRender::EditMode::getInteractionMask() const void CSVRender::EditMode::activate (CSVWidget::SceneToolbar *toolbar) { mWorldspaceWidget->setInteractionMask (mMask); + mWorldspaceWidget->clearSelection (~mMask); +} + +void CSVRender::EditMode::updateUserSetting (const QString& name, const QStringList& value) +{ + } + +void CSVRender::EditMode::setEditLock (bool locked) +{ + +} + +void CSVRender::EditMode::primaryEditPressed (osg::ref_ptr tag) {} + +void CSVRender::EditMode::secondaryEditPressed (osg::ref_ptr tag) {} + +void CSVRender::EditMode::selectPressed (osg::ref_ptr tag) {} + +bool CSVRender::EditMode::primaryEditStartDrag (osg::ref_ptr tag) +{ + return false; +} + +bool CSVRender::EditMode::secondaryEditStartDrag (osg::ref_ptr tag) +{ + return false; +} + +bool CSVRender::EditMode::selectStartDrag (osg::ref_ptr tag) +{ + return false; +} + +void CSVRender::EditMode::drag (int diffX, int diffY, double speedFactor) {} + +void CSVRender::EditMode::dragCompleted() {} + +void CSVRender::EditMode::dragAborted() {} + +void CSVRender::EditMode::dragWheel (int diff, double speedFactor) {} diff --git a/apps/opencs/view/render/editmode.hpp b/apps/opencs/view/render/editmode.hpp index c3192f8ea..77676d6a3 100644 --- a/apps/opencs/view/render/editmode.hpp +++ b/apps/opencs/view/render/editmode.hpp @@ -1,11 +1,14 @@ #ifndef CSV_RENDER_EDITMODE_H #define CSV_RENDER_EDITMODE_H +#include + #include "../widget/modebutton.hpp" namespace CSVRender { class WorldspaceWidget; + class TagBase; class EditMode : public CSVWidget::ModeButton { @@ -14,6 +17,10 @@ namespace CSVRender WorldspaceWidget *mWorldspaceWidget; unsigned int mMask; + protected: + + WorldspaceWidget& getWorldspaceWidget(); + public: EditMode (WorldspaceWidget *worldspaceWidget, const QIcon& icon, unsigned int mask, @@ -22,6 +29,51 @@ namespace CSVRender unsigned int getInteractionMask() const; virtual void activate (CSVWidget::SceneToolbar *toolbar); + + /// Default-implementation: Do nothing. + virtual void updateUserSetting (const QString& name, const QStringList& value); + + /// Default-implementation: Ignored. + virtual void setEditLock (bool locked); + + /// Default-implementation: Ignored. + virtual void primaryEditPressed (osg::ref_ptr tag); + + /// Default-implementation: Ignored. + virtual void secondaryEditPressed (osg::ref_ptr tag); + + /// Default-implementation: Ignored. + virtual void selectPressed (osg::ref_ptr tag); + + /// Default-implementation: ignore and return false + /// + /// \return Drag accepted? + virtual bool primaryEditStartDrag (osg::ref_ptr tag); + + /// Default-implementation: ignore and return false + /// + /// \return Drag accepted? + virtual bool secondaryEditStartDrag (osg::ref_ptr tag); + + /// Default-implementation: ignore and return false + /// + /// \return Drag accepted? + virtual bool selectStartDrag (osg::ref_ptr tag); + + /// Default-implementation: ignored + virtual void drag (int diffX, int diffY, double speedFactor); + + /// Default-implementation: ignored + virtual void dragCompleted(); + + /// Default-implementation: ignored + /// + /// \note dragAborted will not be called, if the drag is aborted via changing + /// editing mode + virtual void dragAborted(); + + /// Default-implementation: ignored + virtual void dragWheel (int diff, double speedFactor); }; } diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp new file mode 100644 index 000000000..333d91656 --- /dev/null +++ b/apps/opencs/view/render/instancemode.cpp @@ -0,0 +1,56 @@ + +#include "instancemode.hpp" + +#include "../../model/settings/usersettings.hpp" + +#include "elements.hpp" +#include "object.hpp" +#include "worldspacewidget.hpp" + +CSVRender::InstanceMode::InstanceMode (WorldspaceWidget *worldspaceWidget, QWidget *parent) +: EditMode (worldspaceWidget, QIcon (":placeholder"), Element_Reference, "Instance editing", + parent), mContextSelect (false) +{ + +} + +void CSVRender::InstanceMode::activate (CSVWidget::SceneToolbar *toolbar) +{ + EditMode::activate (toolbar); + + mContextSelect = CSMSettings::UserSettings::instance().setting ("scene-input/context-select")=="true"; +} + +void CSVRender::InstanceMode::updateUserSetting (const QString& name, const QStringList& value) +{ + if (name=="scene-input/context-select") + mContextSelect = value.at (0)=="true"; +} + +void CSVRender::InstanceMode::primaryEditPressed (osg::ref_ptr tag) +{ + if (mContextSelect) + selectPressed (tag); +} + +void CSVRender::InstanceMode::secondaryEditPressed (osg::ref_ptr tag) +{ + if (mContextSelect) + selectPressed (tag); +} + +void CSVRender::InstanceMode::selectPressed (osg::ref_ptr tag) +{ + if (tag) + { + if (CSVRender::ObjectTag *objectTag = dynamic_cast (tag.get())) + { + // hit an Object, toggle its selection state + CSVRender::Object* object = objectTag->mObject; + object->setSelected (!object->getSelected()); + return; + } + } + + getWorldspaceWidget().clearSelection (Element_Reference); +} diff --git a/apps/opencs/view/render/instancemode.hpp b/apps/opencs/view/render/instancemode.hpp new file mode 100644 index 000000000..cc4fd5434 --- /dev/null +++ b/apps/opencs/view/render/instancemode.hpp @@ -0,0 +1,30 @@ +#ifndef CSV_RENDER_INSTANCEMODE_H +#define CSV_RENDER_INSTANCEMODE_H + +#include "editmode.hpp" + +namespace CSVRender +{ + class InstanceMode : public EditMode + { + Q_OBJECT + + bool mContextSelect; + + public: + + InstanceMode (WorldspaceWidget *worldspaceWidget, QWidget *parent = 0); + + virtual void activate (CSVWidget::SceneToolbar *toolbar); + + virtual void updateUserSetting (const QString& name, const QStringList& value); + + virtual void primaryEditPressed (osg::ref_ptr tag); + + virtual void secondaryEditPressed (osg::ref_ptr tag); + + virtual void selectPressed (osg::ref_ptr tag); + }; +} + +#endif diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index f05e311f9..ac96cb283 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -38,6 +38,11 @@ namespace } +CSVRender::ObjectTag::ObjectTag (Object* object) +: TagBase (Element_Reference), mObject (object) +{} + + void CSVRender::Object::clear() { } @@ -124,7 +129,7 @@ CSVRender::Object::Object (CSMWorld::Data& data, osg::Group* parentNode, mOutline = new osgFX::Scribe; mOutline->addChild(mBaseNode); - mBaseNode->setUserData(new ObjectHolder(this)); + mBaseNode->setUserData(new ObjectTag(this)); parentNode->addChild(mBaseNode); diff --git a/apps/opencs/view/render/object.hpp b/apps/opencs/view/render/object.hpp index 9b3749069..0858a2edb 100644 --- a/apps/opencs/view/render/object.hpp +++ b/apps/opencs/view/render/object.hpp @@ -8,8 +8,9 @@ #include #include -class QModelIndex; +#include "tagbase.hpp" +class QModelIndex; namespace osg { @@ -35,21 +36,19 @@ namespace CSMWorld namespace CSVRender { - class Object; // An object to attach as user data to the osg::Node, allows us to get an Object back from a Node when we are doing a ray query - class ObjectHolder : public osg::Referenced + class ObjectTag : public TagBase { - public: - ObjectHolder(Object* obj) - : mObject(obj) - { - } + public: + + ObjectTag (Object* object); - Object* mObject; + Object* mObject; }; + class Object { const CSMWorld::Data& mData; diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index b0c1e74a9..dae5990e6 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -1,8 +1,10 @@ #include "pagedworldspacewidget.hpp" +#include #include #include +#include #include @@ -21,20 +23,19 @@ bool CSVRender::PagedWorldspaceWidget::adjustCells() { bool modified = false; + bool wasEmpty = mCells.empty(); const CSMWorld::IdCollection& cells = mDocument.getData().getCells(); { - // remove (or name/region modified) + // remove/update std::map::iterator iter (mCells.begin()); while (iter!=mCells.end()) { - int index = cells.searchId (iter->first.getId (mWorldspace)); - - if (!mSelection.has (iter->first) || index==-1 || - cells.getRecord (index).mState==CSMWorld::RecordBase::State_Deleted) + if (!mSelection.has (iter->first)) { + // remove delete iter->second; mCells.erase (iter++); @@ -42,12 +43,33 @@ bool CSVRender::PagedWorldspaceWidget::adjustCells() } else { - // check if name or region field has changed - // FIXME: config setting - //std::string name = cells.getRecord(index).get().mName; - //std::string region = cells.getRecord(index).get().mRegion; + // update + int index = cells.searchId (iter->first.getId (mWorldspace)); + + bool deleted = index==-1 || + cells.getRecord (index).mState==CSMWorld::RecordBase::State_Deleted; + + if (deleted!=iter->second->isDeleted()) + { + modified = true; + + std::auto_ptr cell (new Cell (mDocument.getData(), mRootNode, + iter->first.getId (mWorldspace), deleted)); + + delete iter->second; + iter->second = cell.release(); + } + else if (!deleted) + { + // delete state has not changed -> just update - // cell marker update goes here + // TODO check if name or region field has changed (cell marker) + // FIXME: config setting + //std::string name = cells.getRecord(index).get().mName; + //std::string region = cells.getRecord(index).get().mRegion; + + modified = true; + } ++iter; } @@ -58,20 +80,43 @@ bool CSVRender::PagedWorldspaceWidget::adjustCells() for (CSMWorld::CellSelection::Iterator iter (mSelection.begin()); iter!=mSelection.end(); ++iter) { - int index = cells.searchId (iter->getId (mWorldspace)); - - if (index > 0 && cells.getRecord (index).mState!=CSMWorld::RecordBase::State_Deleted && - mCells.find (*iter)==mCells.end()) + if (mCells.find (*iter)==mCells.end()) { - Cell *cell = new Cell (mDocument.getData(), mRootNode, - iter->getId (mWorldspace)); - mCells.insert (std::make_pair (*iter, cell)); - + addCellToScene (*iter); modified = true; } } if (modified) + { + for (std::map::const_iterator iter (mCells.begin()); + iter!=mCells.end(); ++iter) + { + int mask = 0; + + for (int i=CellArrow::Direction_North; i<=CellArrow::Direction_East; i *= 2) + { + CSMWorld::CellCoordinates coordinates (iter->second->getCoordinates()); + + switch (i) + { + case CellArrow::Direction_North: coordinates = coordinates.move (0, 1); break; + case CellArrow::Direction_West: coordinates = coordinates.move (-1, 0); break; + case CellArrow::Direction_South: coordinates = coordinates.move (0, -1); break; + case CellArrow::Direction_East: coordinates = coordinates.move (1, 0); break; + } + + if (!mSelection.has (coordinates)) + mask |= i; + } + + iter->second->setCellArrows (mask); + } + } + + /// \todo do not overwrite manipulator object + /// \todo move code to useViewHint function + if (modified && wasEmpty) mView->setCameraManipulator(new osgGA::TrackballManipulator); return modified; @@ -105,6 +150,76 @@ void CSVRender::PagedWorldspaceWidget::addEditModeSelectorButtons ( "terrain-move"); } +void CSVRender::PagedWorldspaceWidget::handleMouseClick (osg::ref_ptr tag, const std::string& button, bool shift) +{ + if (tag && tag->getElement()==Element_CellArrow) + { + if (button=="p-edit" || button=="s-edit") + { + if (CellArrowTag *cellArrowTag = + dynamic_cast (tag.get())) + { + CellArrow *arrow = cellArrowTag->getCellArrow(); + + CSMWorld::CellCoordinates coordinates = arrow->getCoordinates(); + + CellArrow::Direction direction = arrow->getDirection(); + + int x = 0; + int y = 0; + + switch (direction) + { + case CellArrow::Direction_North: y = 1; break; + case CellArrow::Direction_West: x = -1; break; + case CellArrow::Direction_South: y = -1; break; + case CellArrow::Direction_East: x = 1; break; + } + + bool modified = false; + + if (shift) + { + if (button=="p-edit") + addCellSelection (x, y); + else + moveCellSelection (x, y); + + modified = true; + } + else + { + CSMWorld::CellCoordinates newCoordinates = coordinates.move (x, y); + + if (mCells.find (newCoordinates)==mCells.end()) + { + addCellToScene (newCoordinates); + mSelection.add (newCoordinates); + modified = true; + } + + if (button=="s-edit") + { + if (mCells.find (coordinates)!=mCells.end()) + { + removeCellFromScene (coordinates); + mSelection.remove (coordinates); + modified = true; + } + } + } + + if (modified) + adjustCells(); + + return; + } + } + } + + WorldspaceWidget::handleMouseClick (tag, button, shift); +} + void CSVRender::PagedWorldspaceWidget::referenceableDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { @@ -184,6 +299,72 @@ std::string CSVRender::PagedWorldspaceWidget::getStartupInstruction() return stream.str(); } +void CSVRender::PagedWorldspaceWidget::addCellToScene ( + const CSMWorld::CellCoordinates& coordinates) +{ + const CSMWorld::IdCollection& cells = mDocument.getData().getCells(); + + int index = cells.searchId (coordinates.getId (mWorldspace)); + + bool deleted = index==-1 || + cells.getRecord (index).mState==CSMWorld::RecordBase::State_Deleted; + + Cell *cell = new Cell (mDocument.getData(), mRootNode, coordinates.getId (mWorldspace), + deleted); + + mCells.insert (std::make_pair (coordinates, cell)); +} + +void CSVRender::PagedWorldspaceWidget::removeCellFromScene ( + const CSMWorld::CellCoordinates& coordinates) +{ + std::map::iterator iter = mCells.find (coordinates); + + if (iter!=mCells.end()) + { + delete iter->second; + mCells.erase (iter); + } +} + +void CSVRender::PagedWorldspaceWidget::addCellSelection (int x, int y) +{ + CSMWorld::CellSelection newSelection = mSelection; + newSelection.move (x, y); + + for (CSMWorld::CellSelection::Iterator iter (newSelection.begin()); iter!=newSelection.end(); + ++iter) + { + if (mCells.find (*iter)==mCells.end()) + { + addCellToScene (*iter); + mSelection.add (*iter); + } + } +} + +void CSVRender::PagedWorldspaceWidget::moveCellSelection (int x, int y) +{ + CSMWorld::CellSelection newSelection = mSelection; + newSelection.move (x, y); + + for (CSMWorld::CellSelection::Iterator iter (mSelection.begin()); iter!=mSelection.end(); + ++iter) + { + if (!newSelection.has (*iter)) + removeCellFromScene (*iter); + } + + for (CSMWorld::CellSelection::Iterator iter (newSelection.begin()); iter!=newSelection.end(); + ++iter) + { + if (!mSelection.has (*iter)) + addCellToScene (*iter); + } + + mSelection = newSelection; +} + CSVRender::PagedWorldspaceWidget::PagedWorldspaceWidget (QWidget* parent, CSMDoc::Document& document) : WorldspaceWidget (document, parent), mDocument (document), mWorldspace ("std::default"), mControlElements(NULL), mDisplayCellCoord(true) @@ -314,6 +495,15 @@ unsigned int CSVRender::PagedWorldspaceWidget::getVisibilityMask() const return WorldspaceWidget::getVisibilityMask() | mControlElements->getSelection(); } +void CSVRender::PagedWorldspaceWidget::clearSelection (int elementMask) +{ + for (std::map::iterator iter = mCells.begin(); + iter!=mCells.end(); ++iter) + iter->second->setSelection (elementMask, Cell::Selection_Clear); + + flagAsModified(); +} + CSVWidget::SceneToolToggle *CSVRender::PagedWorldspaceWidget::makeControlVisibilitySelector ( CSVWidget::SceneToolbar *parent) { diff --git a/apps/opencs/view/render/pagedworldspacewidget.hpp b/apps/opencs/view/render/pagedworldspacewidget.hpp index 64a0bccd1..e983fddd5 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.hpp +++ b/apps/opencs/view/render/pagedworldspacewidget.hpp @@ -53,6 +53,20 @@ namespace CSVRender virtual std::string getStartupInstruction(); + /// \note Does not update the view or any cell marker + void addCellToScene (const CSMWorld::CellCoordinates& coordinates); + + /// \note Does not update the view or any cell marker + /// + /// \note Calling this function for a cell that is not in the selection is a no-op. + void removeCellFromScene (const CSMWorld::CellCoordinates& coordinates); + + /// \note Does not update the view or any cell marker + void addCellSelection (int x, int y); + + /// \note Does not update the view or any cell marker + void moveCellSelection (int x, int y); + public: PagedWorldspaceWidget (QWidget *parent, CSMDoc::Document& document); @@ -79,12 +93,17 @@ namespace CSVRender virtual unsigned int getVisibilityMask() const; + /// \param elementMask Elements to be affected by the clear operation + virtual void clearSelection (int elementMask); + protected: virtual void addVisibilitySelectorButtons (CSVWidget::SceneToolToggle2 *tool); virtual void addEditModeSelectorButtons (CSVWidget::SceneToolMode *tool); + virtual void handleMouseClick (osg::ref_ptr tag, const std::string& button, bool shift); + signals: void cellSelectionChanged (const CSMWorld::CellSelection& selection); diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index fb3bcb3c3..76b3db348 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -110,6 +110,8 @@ bool RenderWidget::eventFilter(QObject* obj, QEvent* event) keyPressEvent(static_cast(event)); if (event->type() == QEvent::KeyRelease) keyReleaseEvent(static_cast(event)); + if (event->type() == QEvent::Wheel) + wheelEvent(static_cast(event)); // Always pass the event on to GLWidget, i.e. to OSG event queue return QObject::eventFilter(obj, event); diff --git a/apps/opencs/view/render/tagbase.cpp b/apps/opencs/view/render/tagbase.cpp new file mode 100644 index 000000000..af9a37624 --- /dev/null +++ b/apps/opencs/view/render/tagbase.cpp @@ -0,0 +1,9 @@ + +#include "tagbase.hpp" + +CSVRender::TagBase::TagBase (Elements element) : mElement (element) {} + +CSVRender::Elements CSVRender::TagBase::getElement() const +{ + return mElement; +} diff --git a/apps/opencs/view/render/tagbase.hpp b/apps/opencs/view/render/tagbase.hpp new file mode 100644 index 000000000..874b856c6 --- /dev/null +++ b/apps/opencs/view/render/tagbase.hpp @@ -0,0 +1,22 @@ +#ifndef OPENCS_VIEW_TAGBASE_H +#define OPENCS_VIEW_TAGBASE_H + +#include + +#include "elements.hpp" + +namespace CSVRender +{ + class TagBase : public osg::Referenced + { + Elements mElement; + + public: + + TagBase (Elements element); + + Elements getElement() const; + }; +} + +#endif diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index 3e1733c81..68f068dac 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -102,6 +102,12 @@ bool CSVRender::UnpagedWorldspaceWidget::handleDrop (const std::vectorsetSelection (elementMask, Cell::Selection_Clear); + flagAsModified(); +} + void CSVRender::UnpagedWorldspaceWidget::referenceableDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { diff --git a/apps/opencs/view/render/unpagedworldspacewidget.hpp b/apps/opencs/view/render/unpagedworldspacewidget.hpp index d01c3e766..9e6fa97f4 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.hpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.hpp @@ -43,6 +43,9 @@ namespace CSVRender virtual bool handleDrop (const std::vector& data, DropType type); + /// \param elementMask Elements to be affected by the clear operation + virtual void clearSelection (int elementMask); + private: virtual void referenceableDataChanged (const QModelIndex& topLeft, diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index a1f0f6bf2..46c5867eb 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -18,6 +19,8 @@ #include "../../model/world/universalid.hpp" #include "../../model/world/idtable.hpp" +#include "../../model/settings/usersettings.hpp" + #include "../widget/scenetoolmode.hpp" #include "../widget/scenetooltoggle2.hpp" #include "../widget/scenetoolrun.hpp" @@ -25,10 +28,22 @@ #include "object.hpp" #include "elements.hpp" #include "editmode.hpp" +#include "instancemode.hpp" + +namespace +{ + static const char * const sMappingSettings[] = + { + "p-navi", "s-navi", + "p-edit", "s-edit", + "select", + 0 + }; +} CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidget* parent) : SceneWidget (document.getData().getResourceSystem(), parent), mSceneElements(0), mRun(0), mDocument(document), - mInteractionMask (0) + mInteractionMask (0), mEditMode (0), mLocked (false), mDragging (false) { setAcceptDrops(true); @@ -59,6 +74,17 @@ CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidg this, SLOT (debugProfileDataChanged (const QModelIndex&, const QModelIndex&))); connect (debugProfiles, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), this, SLOT (debugProfileAboutToBeRemoved (const QModelIndex&, int, int))); + + for (int i=0; sMappingSettings[i]; ++i) + { + QString key ("scene-input/"); + key += sMappingSettings[i]; + storeMappingSetting (key, CSMSettings::UserSettings::instance().settingValue (key)); + } + + mDragFactor = CSMSettings::UserSettings::instance().settingValue ("scene-input/drag-factor").toDouble(); + mDragWheelFactor = CSMSettings::UserSettings::instance().settingValue ("scene-input/drag-wheel-factor").toDouble(); + mDragShiftFactor = CSMSettings::UserSettings::instance().settingValue ("scene-input/drag-shift-factor").toDouble(); } CSVRender::WorldspaceWidget::~WorldspaceWidget () @@ -178,11 +204,14 @@ CSVWidget::SceneToolRun *CSVRender::WorldspaceWidget::makeRunTool ( CSVWidget::SceneToolMode *CSVRender::WorldspaceWidget::makeEditModeSelector ( CSVWidget::SceneToolbar *parent) { - CSVWidget::SceneToolMode *tool = new CSVWidget::SceneToolMode (parent, "Edit Mode"); + mEditMode = new CSVWidget::SceneToolMode (parent, "Edit Mode"); - addEditModeSelectorButtons (tool); + addEditModeSelectorButtons (mEditMode); - return tool; + connect (mEditMode, SIGNAL (modeChanged (const std::string&)), + this, SLOT (editModeChanged (const std::string&))); + + return mEditMode; } CSVRender::WorldspaceWidget::DropType CSVRender::WorldspaceWidget::getDropType ( @@ -254,6 +283,26 @@ unsigned int CSVRender::WorldspaceWidget::getInteractionMask() const return mInteractionMask & getVisibilityMask(); } +void CSVRender::WorldspaceWidget::updateUserSetting (const QString& name, const QStringList& value) +{ + if (!value.isEmpty() && storeMappingSetting (name, value.first())) + return; + + if (name=="scene-input/drag-factor") + mDragFactor = value.at (0).toDouble(); + else if (name=="scene-input/drag-wheel-factor") + mDragWheelFactor = value.at (0).toDouble(); + else if (name=="scene-input/drag-shift-factor") + mDragShiftFactor = value.at (0).toDouble(); + else + dynamic_cast (*mEditMode->getCurrent()).updateUserSetting (name, value); +} + +void CSVRender::WorldspaceWidget::setEditLock (bool locked) +{ + dynamic_cast (*mEditMode->getCurrent()).setEditLock (locked); +} + void CSVRender::WorldspaceWidget::addVisibilitySelectorButtons ( CSVWidget::SceneToolToggle2 *tool) { @@ -265,9 +314,7 @@ void CSVRender::WorldspaceWidget::addVisibilitySelectorButtons ( void CSVRender::WorldspaceWidget::addEditModeSelectorButtons (CSVWidget::SceneToolMode *tool) { /// \todo replace EditMode with suitable subclasses - tool->addButton ( - new EditMode (this, QIcon (":placeholder"), Element_Reference, "Instance editing"), - "object"); + tool->addButton (new InstanceMode (this, tool), "object"); tool->addButton ( new EditMode (this, QIcon (":placeholder"), Element_Pathgrid, "Pathgrid editing"), "pathgrid"); @@ -288,6 +335,95 @@ void CSVRender::WorldspaceWidget::dragMoveEvent(QDragMoveEvent *event) event->accept(); } + +bool CSVRender::WorldspaceWidget::storeMappingSetting (const QString& key, const QString& value) +{ + const QString prefix = "scene-input/"; + + if (key.startsWith (prefix)) + { + QString key2 (key.mid (prefix.length())); + + for (int i=0; sMappingSettings[i]; ++i) + if (key2==sMappingSettings[i]) + { + Qt::MouseButton button = Qt::NoButton; + + if (value.endsWith ("Left Mouse-Button")) + button = Qt::LeftButton; + else if (value.endsWith ("Right Mouse-Button")) + button = Qt::RightButton; + else if (value.endsWith ("Middle Mouse-Button")) + button = Qt::MiddleButton; + else + return false; + + bool ctrl = value.startsWith ("Ctrl-"); + + mButtonMapping[std::make_pair (button, ctrl)] = sMappingSettings[i]; + return true; + } + } + + return false; +} + +osg::ref_ptr CSVRender::WorldspaceWidget::mousePick (QMouseEvent *event) +{ + // (0,0) is considered the lower left corner of an OpenGL window + int x = event->x(); + int y = height() - event->y(); + + osg::ref_ptr intersector (new osgUtil::LineSegmentIntersector(osgUtil::Intersector::WINDOW, x, y)); + + intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::NO_LIMIT); + osgUtil::IntersectionVisitor visitor(intersector); + + visitor.setTraversalMask(getInteractionMask() << 1); + + mView->getCamera()->accept(visitor); + + for (osgUtil::LineSegmentIntersector::Intersections::iterator it = intersector->getIntersections().begin(); + it != intersector->getIntersections().end(); ++it) + { + osgUtil::LineSegmentIntersector::Intersection intersection = *it; + + // reject back-facing polygons + osg::Vec3f normal = intersection.getWorldIntersectNormal(); + normal = osg::Matrix::transform3x3(normal, mView->getCamera()->getViewMatrix()); + if (normal.z() < 0) + continue; + + for (std::vector::iterator it = intersection.nodePath.begin(); it != intersection.nodePath.end(); ++it) + { + osg::Node* node = *it; + if (osg::ref_ptr tag = dynamic_cast(node->getUserData())) + return tag; + } + +// ignoring terrain for now + // must be terrain, report coordinates +// std::cout << "Terrain hit at " << intersection.getWorldIntersectPoint().x() << " " << intersection.getWorldIntersectPoint().y() << std::endl; +// return; + } + + return osg::ref_ptr(); +} + +std::string CSVRender::WorldspaceWidget::mapButton (QMouseEvent *event) +{ + std::pair phyiscal ( + event->button(), event->modifiers() & Qt::ControlModifier); + + std::map, std::string>::const_iterator iter = + mButtonMapping.find (phyiscal); + + if (iter!=mButtonMapping.end()) + return iter->second; + + return ""; +} + void CSVRender::WorldspaceWidget::dropEvent (QDropEvent* event) { const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); @@ -352,6 +488,12 @@ void CSVRender::WorldspaceWidget::debugProfileAboutToBeRemoved (const QModelInde } } +void CSVRender::WorldspaceWidget::editModeChanged (const std::string& id) +{ + dynamic_cast (*mEditMode->getCurrent()).setEditLock (mLocked); + mDragging = false; +} + void CSVRender::WorldspaceWidget::elementSelectionChanged() { setVisibilityMask (getVisibilityMask()); @@ -365,74 +507,97 @@ void CSVRender::WorldspaceWidget::updateOverlay() void CSVRender::WorldspaceWidget::mouseMoveEvent (QMouseEvent *event) { - if(event->buttons() & Qt::RightButton) + if (!mDragging) { - //mMouse->mouseMoveEvent(event); - } - RenderWidget::mouseMoveEvent(event); -} - -void CSVRender::WorldspaceWidget::mousePressEvent (QMouseEvent *event) -{ - if (event->button() != Qt::RightButton) - return; + if (mDragMode=="p-navi" || mDragMode=="s-navi") + { - // (0,0) is considered the lower left corner of an OpenGL window - int x = event->x(); - int y = height() - event->y(); + } + else if (mDragMode=="p-edit" || mDragMode=="s-edit" || mDragMode=="select") + { + osg::ref_ptr tag = mousePick (event); - osg::ref_ptr intersector (new osgUtil::LineSegmentIntersector(osgUtil::Intersector::WINDOW, x, y)); + EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); - intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::NO_LIMIT); - osgUtil::IntersectionVisitor visitor(intersector); + if (mDragMode=="p-edit") + mDragging = editMode.primaryEditStartDrag (tag); + else if (mDragMode=="s-edit") + mDragging = editMode.secondaryEditStartDrag (tag); + else if (mDragMode=="select") + mDragging = editMode.selectStartDrag (tag); - visitor.setTraversalMask(getInteractionMask() << 1); + if (mDragging) + { +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + mDragX = event->localPos().x(); + mDragY = height() - event->localPos().y(); +#else + mDragX = event->posF().x(); + mDragY = height() - event->posF().y(); +#endif + } + } + } + else + { + int diffX = event->x() - mDragX; + int diffY = (height() - event->y()) - mDragY; - mView->getCamera()->accept(visitor); + mDragX = event->x(); + mDragY = height() - event->y(); - for (osgUtil::LineSegmentIntersector::Intersections::iterator it = intersector->getIntersections().begin(); - it != intersector->getIntersections().end(); ++it) - { - osgUtil::LineSegmentIntersector::Intersection intersection = *it; + double factor = mDragFactor; - // reject back-facing polygons - osg::Vec3f normal = intersection.getWorldIntersectNormal(); - normal = osg::Matrix::transform3x3(normal, mView->getCamera()->getViewMatrix()); - if (normal.z() < 0) - continue; + if (event->modifiers() & Qt::ShiftModifier) + factor *= mDragShiftFactor; - for (std::vector::iterator it = intersection.nodePath.begin(); it != intersection.nodePath.end(); ++it) - { - osg::Node* node = *it; - if (CSVRender::ObjectHolder* holder = dynamic_cast(node->getUserData())) - { - // hit an Object, toggle its selection state - CSVRender::Object* obj = holder->mObject; - obj->setSelected(!obj->getSelected()); - return; - } - } + EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); - // must be terrain, report coordinates - std::cout << "Terrain hit at " << intersection.getWorldIntersectPoint().x() << " " << intersection.getWorldIntersectPoint().y() << std::endl; - return; + editMode.drag (diffX, diffY, factor); } } +void CSVRender::WorldspaceWidget::mousePressEvent (QMouseEvent *event) +{ + std::string button = mapButton (event); + + if (!mDragging) + mDragMode = button; +} + void CSVRender::WorldspaceWidget::mouseReleaseEvent (QMouseEvent *event) { - if(event->button() == Qt::RightButton) + std::string button = mapButton (event); + + if (mDragging) + { + if (mDragMode=="p-navi" || mDragMode=="s-navi") + { + + } + else if (mDragMode=="p-edit" || mDragMode=="s-edit" || mDragMode=="select") + { + EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); + + editMode.dragCompleted(); + mDragging = false; + } + } + else { - /* - if(!getViewport()) + if (button=="p-navi" || button=="s-navi") + { + + } + else if (button=="p-edit" || button=="s-edit" || button=="select") { - SceneWidget::mouseReleaseEvent(event); - return; + osg::ref_ptr tag = mousePick (event); + + handleMouseClick (tag, button, event->modifiers() & Qt::ShiftModifier); } - */ - //mMouse->mouseReleaseEvent(event); } - RenderWidget::mouseReleaseEvent(event); + + mDragMode.clear(); } void CSVRender::WorldspaceWidget::mouseDoubleClickEvent (QMouseEvent *event) @@ -441,21 +606,47 @@ void CSVRender::WorldspaceWidget::mouseDoubleClickEvent (QMouseEvent *event) { //mMouse->mouseDoubleClickEvent(event); } - //SceneWidget::mouseDoubleClickEvent(event); } void CSVRender::WorldspaceWidget::wheelEvent (QWheelEvent *event) { - //if(!mMouse->wheelEvent(event)) - RenderWidget::wheelEvent(event); + if (mDragging) + { + double factor = mDragWheelFactor; + + if (event->modifiers() & Qt::ShiftModifier) + factor *= mDragShiftFactor; + + EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); + + editMode.dragWheel (event->delta(), factor); + } } void CSVRender::WorldspaceWidget::keyPressEvent (QKeyEvent *event) { if(event->key() == Qt::Key_Escape) { - //mMouse->cancelDrag(); + if (mDragging) + { + EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); + + editMode.dragAborted(); + mDragging = false; + } } else RenderWidget::keyPressEvent(event); } + +void CSVRender::WorldspaceWidget::handleMouseClick (osg::ref_ptr tag, const std::string& button, bool shift) +{ + EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); + + if (button=="p-edit") + editMode.primaryEditPressed (tag); + else if (button=="s-edit") + editMode.secondaryEditPressed (tag); + else if (button=="select") + editMode.selectPressed (tag); +} diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index fe4555820..c2d61e75b 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -1,12 +1,15 @@ #ifndef OPENCS_VIEW_WORLDSPACEWIDGET_H #define OPENCS_VIEW_WORLDSPACEWIDGET_H +#include + #include -#include "scenewidget.hpp" +#include "../../model/doc/document.hpp" +#include "../../model/world/tablemimedata.hpp" -#include -#include +#include "scenewidget.hpp" +#include "elements.hpp" namespace CSMWorld { @@ -23,6 +26,9 @@ namespace CSVWidget namespace CSVRender { + class TagBase; + class CellArrow; + class WorldspaceWidget : public SceneWidget { Q_OBJECT @@ -31,6 +37,16 @@ namespace CSVRender CSVWidget::SceneToolRun *mRun; CSMDoc::Document& mDocument; unsigned int mInteractionMask; + std::map, std::string> mButtonMapping; + CSVWidget::SceneToolMode *mEditMode; + bool mLocked; + std::string mDragMode; + bool mDragging; + int mDragX; + int mDragY; + double mDragFactor; + double mDragWheelFactor; + double mDragShiftFactor; public: @@ -93,14 +109,21 @@ namespace CSVRender /// marked for interaction. unsigned int getInteractionMask() const; + virtual void updateUserSetting (const QString& name, const QStringList& value); + + virtual void setEditLock (bool locked); + + CSMDoc::Document& getDocument(); + + /// \param elementMask Elements to be affected by the clear operation + virtual void clearSelection (int elementMask) = 0; + protected: virtual void addVisibilitySelectorButtons (CSVWidget::SceneToolToggle2 *tool); virtual void addEditModeSelectorButtons (CSVWidget::SceneToolMode *tool); - CSMDoc::Document& getDocument(); - virtual void updateOverlay(); virtual void mouseMoveEvent (QMouseEvent *event); @@ -110,6 +133,9 @@ namespace CSVRender virtual void wheelEvent (QWheelEvent *event); virtual void keyPressEvent (QKeyEvent *event); + virtual void handleMouseClick (osg::ref_ptr tag, const std::string& button, + bool shift); + private: void dragEnterEvent(QDragEnterEvent *event); @@ -118,6 +144,13 @@ namespace CSVRender void dragMoveEvent(QDragMoveEvent *event); + /// \return Is \a key a button mapping setting? (ignored otherwise) + bool storeMappingSetting (const QString& key, const QString& value); + + osg::ref_ptr mousePick (QMouseEvent *event); + + std::string mapButton (QMouseEvent *event); + virtual std::string getStartupInstruction() = 0; private slots: @@ -144,6 +177,7 @@ namespace CSVRender void debugProfileAboutToBeRemoved (const QModelIndex& parent, int start, int end); + void editModeChanged (const std::string& id); protected slots: diff --git a/apps/opencs/view/widget/scenetoolmode.cpp b/apps/opencs/view/widget/scenetoolmode.cpp index 9f963873c..a93bb0556 100644 --- a/apps/opencs/view/widget/scenetoolmode.cpp +++ b/apps/opencs/view/widget/scenetoolmode.cpp @@ -71,6 +71,11 @@ void CSVWidget::SceneToolMode::addButton (ModeButton *button, const std::string& } } +CSVWidget::ModeButton *CSVWidget::SceneToolMode::getCurrent() +{ + return mCurrent; +} + void CSVWidget::SceneToolMode::selected() { std::map::const_iterator iter = diff --git a/apps/opencs/view/widget/scenetoolmode.hpp b/apps/opencs/view/widget/scenetoolmode.hpp index e6f462cf8..6828a2269 100644 --- a/apps/opencs/view/widget/scenetoolmode.hpp +++ b/apps/opencs/view/widget/scenetoolmode.hpp @@ -41,6 +41,9 @@ namespace CSVWidget /// The ownership of \a button is transferred to *this. void addButton (ModeButton *button, const std::string& id); + /// Will return a 0-pointer only if the mode does not have any buttons yet. + ModeButton *getCurrent(); + signals: void modeChanged (const std::string& id); diff --git a/apps/opencs/view/world/genericcreator.cpp b/apps/opencs/view/world/genericcreator.cpp index 8ed52bf80..df7739941 100644 --- a/apps/opencs/view/world/genericcreator.cpp +++ b/apps/opencs/view/world/genericcreator.cpp @@ -47,6 +47,16 @@ std::string CSVWorld::GenericCreator::getId() const return mId->text().toUtf8().constData(); } +std::string CSVWorld::GenericCreator::getIdValidatorResult() const +{ + std::string errors; + + if (!mId->hasAcceptableInput()) + errors = mValidator->getError(); + + return errors; +} + void CSVWorld::GenericCreator::configureCreateCommand (CSMWorld::CreateCommand& command) const {} void CSVWorld::GenericCreator::pushCommand (std::auto_ptr command, diff --git a/apps/opencs/view/world/genericcreator.hpp b/apps/opencs/view/world/genericcreator.hpp index 471d0622e..f63c45109 100644 --- a/apps/opencs/view/world/genericcreator.hpp +++ b/apps/opencs/view/world/genericcreator.hpp @@ -60,6 +60,8 @@ namespace CSVWorld virtual std::string getId() const; + virtual std::string getIdValidatorResult() const; + /// Allow subclasses to add additional data to \a command. virtual void configureCreateCommand (CSMWorld::CreateCommand& command) const; diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index c2f3442f8..753d791c0 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -15,6 +15,7 @@ #include "../render/pagedworldspacewidget.hpp" #include "../render/unpagedworldspacewidget.hpp" +#include "../render/editmode.hpp" #include "../widget/scenetoolbar.hpp" #include "../widget/scenetoolmode.hpp" @@ -121,15 +122,14 @@ CSVWidget::SceneToolbar* CSVWorld::SceneSubView::makeToolbar (CSVRender::Worldsp CSVWidget::SceneToolRun *runTool = widget->makeRunTool (toolbar); toolbar->addTool (runTool); - CSVWidget::SceneToolMode *editModeTool = widget->makeEditModeSelector (toolbar); - toolbar->addTool (editModeTool); + toolbar->addTool (widget->makeEditModeSelector (toolbar)); return toolbar; } void CSVWorld::SceneSubView::setEditLock (bool locked) { - + mScene->setEditLock (locked); } void CSVWorld::SceneSubView::setStatusBar (bool show) @@ -147,6 +147,12 @@ std::string CSVWorld::SceneSubView::getTitle() const return mTitle; } +void CSVWorld::SceneSubView::updateUserSetting (const QString& name, const QStringList& value) +{ + mScene->updateUserSetting (name, value); + CSVDoc::SubView::updateUserSetting (name, value); +} + void CSVWorld::SceneSubView::cellSelectionChanged (const CSMWorld::UniversalId& id) { setUniversalId(id); diff --git a/apps/opencs/view/world/scenesubview.hpp b/apps/opencs/view/world/scenesubview.hpp index a34d71901..2458d58f4 100644 --- a/apps/opencs/view/world/scenesubview.hpp +++ b/apps/opencs/view/world/scenesubview.hpp @@ -27,6 +27,7 @@ namespace CSVRender namespace CSVWidget { class SceneToolbar; + class SceneToolMode; } namespace CSVWorld @@ -58,6 +59,8 @@ namespace CSVWorld virtual std::string getTitle() const; + virtual void updateUserSetting (const QString& name, const QStringList& value); + private: void makeConnections(CSVRender::PagedWorldspaceWidget* widget); diff --git a/apps/opencs/view/world/scriptsubview.cpp b/apps/opencs/view/world/scriptsubview.cpp index d99f789b3..eb0c70656 100644 --- a/apps/opencs/view/world/scriptsubview.cpp +++ b/apps/opencs/view/world/scriptsubview.cpp @@ -198,26 +198,41 @@ void CSVWorld::ScriptSubView::useHint (const std::string& hint) if (hint.empty()) return; - if (hint[0]=='l') - { - std::istringstream stream (hint.c_str()+1); - - char ignore; - int line; - int column; - - if (stream >> ignore >> line >> column) + unsigned line = 0, column = 0; + char c; + std::istringstream stream (hint.c_str()+1); + switch(hint[0]){ + case 'R': + case 'r': { - QTextCursor cursor = mEditor->textCursor(); - - cursor.movePosition (QTextCursor::Start); - if (cursor.movePosition (QTextCursor::Down, QTextCursor::MoveAnchor, line)) - cursor.movePosition (QTextCursor::Right, QTextCursor::MoveAnchor, column); - - mEditor->setFocus(); - mEditor->setTextCursor (cursor); + QModelIndex index = mModel->getModelIndex (getUniversalId().getId(), mColumn); + QString source = mModel->data (index).toString(); + unsigned pos, dummy; + if (!(stream >> c >> dummy >> pos) ) + return; + + for (unsigned i = 0; i <= pos; ++i){ + if (source[i] == '\n'){ + ++line; + column = i+1; + } + } + column = pos - column; + break; } + case 'l': + if (!(stream >> c >> line >> column)) + return; } + + QTextCursor cursor = mEditor->textCursor(); + + cursor.movePosition (QTextCursor::Start); + if (cursor.movePosition (QTextCursor::Down, QTextCursor::MoveAnchor, line)) + cursor.movePosition (QTextCursor::Right, QTextCursor::MoveAnchor, column); + + mEditor->setFocus(); + mEditor->setTextCursor (cursor); } void CSVWorld::ScriptSubView::textChanged() diff --git a/apps/opencs/view/world/startscriptcreator.cpp b/apps/opencs/view/world/startscriptcreator.cpp new file mode 100644 index 000000000..69b1b3ff1 --- /dev/null +++ b/apps/opencs/view/world/startscriptcreator.cpp @@ -0,0 +1,20 @@ +#include "startscriptcreator.hpp" + +CSVWorld::StartScriptCreator::StartScriptCreator(CSMWorld::Data &data, QUndoStack &undoStack, const CSMWorld::UniversalId &id, bool relaxedIdRules): + GenericCreator (data, undoStack, id, true) +{} + +std::string CSVWorld::StartScriptCreator::getErrors() const +{ + std::string errors; + + errors = getIdValidatorResult(); + if (errors.length() > 0) + return errors; + else if (getData().getScripts().searchId(getId()) == -1) + errors = "Script ID not found"; + else if (getData().getStartScripts().searchId(getId()) > -1 ) + errors = "Script with this ID already registered as Start Script"; + + return errors; +} diff --git a/apps/opencs/view/world/startscriptcreator.hpp b/apps/opencs/view/world/startscriptcreator.hpp new file mode 100644 index 000000000..07fe8ff3d --- /dev/null +++ b/apps/opencs/view/world/startscriptcreator.hpp @@ -0,0 +1,25 @@ +#ifndef STARTSCRIPTCREATOR_HPP +#define STARTSCRIPTCREATOR_HPP + +#include "genericcreator.hpp" + +namespace CSVWorld { + + class StartScriptCreator : public GenericCreator + { + Q_OBJECT + + public: + StartScriptCreator(CSMWorld::Data& data, QUndoStack& undoStack, + const CSMWorld::UniversalId& id, bool relaxedIdRules = false); + + 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. + }; + +} + + + +#endif // STARTSCRIPTCREATOR_HPP diff --git a/apps/opencs/view/world/subviews.cpp b/apps/opencs/view/world/subviews.cpp index 299ac6ebe..88375caaf 100644 --- a/apps/opencs/view/world/subviews.cpp +++ b/apps/opencs/view/world/subviews.cpp @@ -10,6 +10,7 @@ #include "cellcreator.hpp" #include "referenceablecreator.hpp" #include "referencecreator.hpp" +#include "startscriptcreator.hpp" #include "scenesubview.hpp" #include "dialoguecreator.hpp" #include "infocreator.hpp" @@ -42,7 +43,6 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) CSMWorld::UniversalId::Type_BodyParts, CSMWorld::UniversalId::Type_SoundGens, CSMWorld::UniversalId::Type_Pathgrids, - CSMWorld::UniversalId::Type_StartScripts, CSMWorld::UniversalId::Type_None // end marker }; @@ -51,6 +51,9 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) manager.add (sTableTypes[i], new CSVDoc::SubViewFactoryWithCreator >); + manager.add (CSMWorld::UniversalId::Type_StartScripts, + new CSVDoc::SubViewFactoryWithCreator >); + manager.add (CSMWorld::UniversalId::Type_Cells, new CSVDoc::SubViewFactoryWithCreator >); @@ -123,7 +126,6 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) CSMWorld::UniversalId::Type_BodyPart, CSMWorld::UniversalId::Type_SoundGen, CSMWorld::UniversalId::Type_Pathgrid, - CSMWorld::UniversalId::Type_StartScript, CSMWorld::UniversalId::Type_None // end marker }; @@ -133,6 +135,10 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) new CSVDoc::SubViewFactoryWithCreator > (false)); + manager.add (CSMWorld::UniversalId::Type_StartScript, + new CSVDoc::SubViewFactoryWithCreator > (false)); + manager.add (CSMWorld::UniversalId::Type_Skill, new CSVDoc::SubViewFactoryWithCreator (false)); diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index f1dd18acc..34d93da67 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -234,6 +234,11 @@ namespace MWClass std::pair Light::canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const { + MWWorld::LiveCellRef *ref = + ptr.get(); + if (!(ref->mBase->mData.mFlags & ESM::Light::Carry)) + return std::make_pair(0,""); + MWWorld::InventoryStore& invStore = npc.getClass().getInventoryStore(npc); MWWorld::ContainerStoreIterator weapon = invStore.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 993dde6e4..f8c9bb38e 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -139,9 +139,6 @@ namespace MWDialogue win->startDialogue(actor, actor.getClass().getName (actor), resetHistory); - //setup the list of topics known by the actor. Topics who are also on the knownTopics list will be added to the GUI - updateTopics(); - //greeting const MWWorld::Store &dialogs = MWBase::Environment::get().getWorld()->getStore().get(); @@ -165,12 +162,19 @@ namespace MWDialogue // TODO play sound } + // first topics update so that parseText knows the keywords to highlight + updateTopics(); + parseText (info->mResponse); MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor); win->addResponse (Interpreter::fixDefinesDialog(info->mResponse, interpreterContext)); executeScript (info->mResultScript); mLastTopic = Misc::StringUtils::lowerCase(it->mId); + + // update topics again to accomodate changes resulting from executeScript + updateTopics(); + return; } } @@ -420,7 +424,6 @@ namespace MWDialogue { if(mDialogueMap.find(keyword) != mDialogueMap.end()) { - ESM::Dialogue ndialogue = mDialogueMap[keyword]; if (mDialogueMap[keyword].mType == ESM::Dialogue::Topic) { executeTopic (keyword); diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp index 083dd32b0..761f7d164 100644 --- a/apps/openmw/mwgui/console.cpp +++ b/apps/openmw/mwgui/console.cpp @@ -170,25 +170,25 @@ namespace MWGui mCommandLine->setFontName(fntName); } - void Console::print(const std::string &msg) + void Console::print(const std::string &msg, const std::string& color) { - mHistory->addText(msg); + mHistory->addText(color + MyGUI::TextIterator::toTagsString(msg)); } void Console::printOK(const std::string &msg) { - print("#FF00FF" + msg + "\n"); + print(msg + "\n", "#FF00FF"); } void Console::printError(const std::string &msg) { - print("#FF2222" + msg + "\n"); + print(msg + "\n", "#FF2222"); } void Console::execute (const std::string& command) { // Log the command - print("#FFFFFF> " + command + "\n"); + print("> " + command + "\n"); Compiler::Locals locals; Compiler::Output output (locals); diff --git a/apps/openmw/mwgui/console.hpp b/apps/openmw/mwgui/console.hpp index 09a05be48..98e46a559 100644 --- a/apps/openmw/mwgui/console.hpp +++ b/apps/openmw/mwgui/console.hpp @@ -48,9 +48,8 @@ namespace MWGui void onResChange(int width, int height); - // Print a message to the console. Messages may contain color - // code, eg. "#FFFFFF this is white". - void print(const std::string &msg); + // Print a message to the console, in specified color. + void print(const std::string &msg, const std::string& color = "#FFFFFF"); // These are pre-colored versions that you should use. diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index f5a5f023f..c1e202bc0 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -167,11 +167,12 @@ namespace MWGui MyGUI::IntSize size(static_cast(Settings::Manager::getFloat(setting + " w", "Windows") * viewSize.width), static_cast(Settings::Manager::getFloat(setting + " h", "Windows") * viewSize.height)); + mMainWidget->setPosition(pos); + mMainWidget->setSize(size); + if (size.width != mMainWidget->getWidth() || size.height != mMainWidget->getHeight()) updatePreviewSize(); - mMainWidget->setPosition(pos); - mMainWidget->setSize(size); adjustPanes(); } diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 9b99ad7bf..9afce6873 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -235,40 +235,64 @@ namespace MWGui draw(); } - void LoadingScreen::draw() + bool LoadingScreen::needToDrawLoadingScreen() { - if (mTimer.time_m() > mLastRenderTime + (1.0/mTargetFrameRate) * 1000.0) + if ( mTimer.time_m() <= mLastRenderTime + (1.0/mTargetFrameRate) * 1000.0) + return false; + + // the minimal delay before a loading screen shows + const float initialDelay = 0.05; + + bool alreadyShown = (mLastRenderTime > mLoadingOnTime); + float diff = (mTimer.time_m() - mLoadingOnTime); + + if (!alreadyShown) { - bool showWallpaper = (MWBase::Environment::get().getStateManager()->getState() - == MWBase::StateManager::State_NoGame); + // bump the delay by the current progress - i.e. if during the initial delay the loading + // has almost finished, no point showing the loading screen now + diff -= mProgress / static_cast(mProgressBar->getScrollRange()) * 100.f; + } - if (showWallpaper && mTimer.time_m() > mLastWallpaperChangeTime + 5000*1) - { - mLastWallpaperChangeTime = mTimer.time_m(); - changeWallpaper(); - } + bool showWallpaper = (MWBase::Environment::get().getStateManager()->getState() + == MWBase::StateManager::State_NoGame); + if (!showWallpaper && diff < initialDelay*1000) + return false; + return true; + } - // Turn off rendering except the GUI - int oldUpdateMask = mViewer->getUpdateVisitor()->getTraversalMask(); - int oldCullMask = mViewer->getCamera()->getCullMask(); - mViewer->getUpdateVisitor()->setTraversalMask(MWRender::Mask_GUI); - mViewer->getCamera()->setCullMask(MWRender::Mask_GUI); + void LoadingScreen::draw() + { + if (!needToDrawLoadingScreen()) + return; + + bool showWallpaper = (MWBase::Environment::get().getStateManager()->getState() + == MWBase::StateManager::State_NoGame); + if (showWallpaper && mTimer.time_m() > mLastWallpaperChangeTime + 5000*1) + { + mLastWallpaperChangeTime = mTimer.time_m(); + changeWallpaper(); + } - MWBase::Environment::get().getInputManager()->update(0, true, true); + // Turn off rendering except the GUI + int oldUpdateMask = mViewer->getUpdateVisitor()->getTraversalMask(); + int oldCullMask = mViewer->getCamera()->getCullMask(); + mViewer->getUpdateVisitor()->setTraversalMask(MWRender::Mask_GUI); + mViewer->getCamera()->setCullMask(MWRender::Mask_GUI); - //osg::Timer timer; - mViewer->frame(mViewer->getFrameStamp()->getSimulationTime()); - //std::cout << "frame took " << timer.time_m() << std::endl; + MWBase::Environment::get().getInputManager()->update(0, true, true); - //if (mViewer->getIncrementalCompileOperation()) - //std::cout << "num to compile " << mViewer->getIncrementalCompileOperation()->getToCompile().size() << std::endl; + //osg::Timer timer; + mViewer->frame(mViewer->getFrameStamp()->getSimulationTime()); + //std::cout << "frame took " << timer.time_m() << std::endl; - // resume 3d rendering - mViewer->getUpdateVisitor()->setTraversalMask(oldUpdateMask); - mViewer->getCamera()->setCullMask(oldCullMask); + //if (mViewer->getIncrementalCompileOperation()) + //std::cout << "num to compile " << mViewer->getIncrementalCompileOperation()->getToCompile().size() << std::endl; - mLastRenderTime = mTimer.time_m(); - } + // resume 3d rendering + mViewer->getUpdateVisitor()->setTraversalMask(oldUpdateMask); + mViewer->getCamera()->setCullMask(oldCullMask); + + mLastRenderTime = mTimer.time_m(); } } diff --git a/apps/openmw/mwgui/loadingscreen.hpp b/apps/openmw/mwgui/loadingscreen.hpp index 194535eee..f0f354223 100644 --- a/apps/openmw/mwgui/loadingscreen.hpp +++ b/apps/openmw/mwgui/loadingscreen.hpp @@ -52,6 +52,7 @@ namespace MWGui private: void findSplashScreens(); + bool needToDrawLoadingScreen(); const VFS::Manager* mVFS; osg::ref_ptr mViewer; diff --git a/apps/openmw/mwgui/spellmodel.cpp b/apps/openmw/mwgui/spellmodel.cpp index 91512a011..58ec05794 100644 --- a/apps/openmw/mwgui/spellmodel.cpp +++ b/apps/openmw/mwgui/spellmodel.cpp @@ -1,5 +1,7 @@ #include "spellmodel.hpp" +#include + #include #include "../mwbase/environment.hpp" @@ -79,8 +81,13 @@ namespace MWGui const std::string enchantId = item.getClass().getEnchantment(item); if (enchantId.empty()) continue; - const ESM::Enchantment* enchant = - esmStore.get().find(item.getClass().getEnchantment(item)); + const ESM::Enchantment* enchant = esmStore.get().search(enchantId); + if (!enchant) + { + std::cerr << "Can't find enchantment '" << enchantId << "' on item " << item.getCellRef().getRefId() << std::endl; + continue; + } + if (enchant->mData.mType != ESM::Enchantment::WhenUsed && enchant->mData.mType != ESM::Enchantment::CastOnce) continue; diff --git a/apps/openmw/mwgui/waitdialog.hpp b/apps/openmw/mwgui/waitdialog.hpp index e8f58c574..13df1f8ae 100644 --- a/apps/openmw/mwgui/waitdialog.hpp +++ b/apps/openmw/mwgui/waitdialog.hpp @@ -8,11 +8,6 @@ namespace MWGui { - namespace Widgets - { - class MWScrollBar; - } - class WaitDialogProgressBar : public WindowBase { public: @@ -51,7 +46,7 @@ namespace MWGui MyGUI::Button* mUntilHealedButton; MyGUI::Button* mWaitButton; MyGUI::Button* mCancelButton; - MWGui::Widgets::MWScrollBar* mHourSlider; + MyGUI::ScrollBar* mHourSlider; TimeAdvancer mTimeAdvancer; bool mSleeping; diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index cd67c6058..d9356da93 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -20,8 +20,9 @@ namespace MWMechanics struct AiFollowStorage : AiTemporaryBase { float mTimer; + bool mMoving; - AiFollowStorage() : mTimer(0.f) {} + AiFollowStorage() : mTimer(0.f), mMoving(false) {} }; int AiFollow::mFollowIndexCounter = 0; @@ -64,10 +65,11 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); + AiFollowStorage& storage = state.get(); + // AiFollow requires the target to be in range and within sight for the initial activation if (!mActive) { - AiFollowStorage& storage = state.get(); storage.mTimer -= duration; if (storage.mTimer < 0) @@ -126,7 +128,15 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte //Set the target destination from the actor ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; - if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < followDistance) //Stop when you get close + float dist = distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]); + const float threshold = 10; + + if (storage.mMoving) //Stop when you get close + storage.mMoving = (dist > followDistance); + else + storage.mMoving = (dist > followDistance + threshold); + + if(!storage.mMoving) { actor.getClass().getMovementSettings(actor).mPosition[1] = 0; @@ -141,9 +151,9 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte } //Check if you're far away - if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) > 450) + if(dist > 450) actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, true); //Make NPC run - else if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < 325) //Have a bit of a dead zone, otherwise npc will constantly flip between running and not when right on the edge of the running threshhold + else if(dist < 325) //Have a bit of a dead zone, otherwise npc will constantly flip between running and not when right on the edge of the running threshhold actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, false); //make NPC walk return false; diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 1cd9649f7..bedff8bcd 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -88,7 +88,8 @@ void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor, float dur MWWorld::Ptr door = getNearbyDoor(actor); if (door != MWWorld::Ptr()) // NOTE: checks interior cells only { - if (!door.getCellRef().getTeleport() && door.getCellRef().getTrap().empty() && door.getClass().getDoorState(door) == 0) { //Open the door if untrapped + if (!door.getCellRef().getTeleport() && door.getCellRef().getTrap().empty() + && door.getCellRef().getLockLevel() <= 0 && door.getClass().getDoorState(door) == 0) { MWBase::Environment::get().getWorld()->activateDoor(door, 1); } } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 12927101d..761c5ece9 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -295,7 +295,7 @@ namespace MWMechanics creatureStats.getSpells().add(*it); // forced update and current value adjustments - //mActors.updateActor (ptr, 0); + mActors.updateActor (ptr, 0); for (int i=0; i<3; ++i) { diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 6d6b9d950..70d097687 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -123,6 +123,9 @@ namespace MWMechanics } } + if (spell->mData.mType == ESM::Spell::ST_Power) + return stats.getSpells().canUsePower(spell->mId) ? 100 : 0; + if (spell->mData.mType != ESM::Spell::ST_Spell) return 100; @@ -817,6 +820,10 @@ namespace MWMechanics sndMgr->playSound3D(mCaster, "Spell Failure " + schools[school], 1.0f, 1.0f); return false; } + + // A power can be used once per 24h + if (spell->mData.mType == ESM::Spell::ST_Power) + stats.getSpells().usePower(spell->mId); } if (mCaster == getPlayer() && spellIncreasesSkill(spell)) diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index 6d7673a59..330d78a20 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -312,7 +312,7 @@ namespace MWMechanics bool Spells::canUsePower(const std::string &power) const { - std::map::const_iterator it = mUsedPowers.find(power); + std::map::const_iterator it = mUsedPowers.find(Misc::StringUtils::lowerCase(power)); if (it == mUsedPowers.end() || it->second + 24 <= MWBase::Environment::get().getWorld()->getTimeStamp()) return true; else @@ -321,7 +321,7 @@ namespace MWMechanics void Spells::usePower(const std::string &power) { - mUsedPowers[power] = MWBase::Environment::get().getWorld()->getTimeStamp(); + mUsedPowers[Misc::StringUtils::lowerCase(power)] = MWBase::Environment::get().getWorld()->getTimeStamp(); } void Spells::readState(const ESM::SpellState &state) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 37ec4c213..b2fa87310 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1275,15 +1275,26 @@ namespace MWRender material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); stateset->setAttributeAndModes(material, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); - stateset->setRenderBinMode(osg::StateSet::OVERRIDE_RENDERBIN_DETAILS); - stateset->setNestRenderBins(false); mObjectRoot->setStateSet(stateset); } else { mObjectRoot->setStateSet(NULL); } + + setRenderBin(); + } + + void Animation::setRenderBin() + { + if (mAlpha != 1.f) + { + osg::StateSet* stateset = mObjectRoot->getOrCreateStateSet(); + stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); + stateset->setRenderBinMode(osg::StateSet::OVERRIDE_RENDERBIN_DETAILS); + } + else if (osg::StateSet* stateset = mObjectRoot->getStateSet()) + stateset->setRenderBinToInherit(); } void Animation::setLightEffect(float effect) @@ -1320,13 +1331,16 @@ namespace MWRender { mHeadController = NULL; - NodeMap::iterator found = mNodeMap.find("bip01 head"); - if (found != mNodeMap.end() && dynamic_cast(found->second.get())) + if (mPtr.getClass().isBipedal(mPtr)) { - osg::Node* node = found->second; - mHeadController = new RotateController(mObjectRoot.get()); - node->addUpdateCallback(mHeadController); - mActiveControllers.insert(std::make_pair(node, mHeadController)); + NodeMap::iterator found = mNodeMap.find("bip01 head"); + if (found != mNodeMap.end() && dynamic_cast(found->second.get())) + { + osg::Node* node = found->second; + mHeadController = new RotateController(mObjectRoot.get()); + node->addUpdateCallback(mHeadController); + mActiveControllers.insert(std::make_pair(node, mHeadController)); + } } } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 94b792c0d..1e46cc71a 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -307,6 +307,9 @@ protected: void addGlow(osg::ref_ptr node, osg::Vec4f glowColor); + /// Set the render bin for this animation's object root. May be customized by subclasses. + virtual void setRenderBin(); + public: Animation(const MWWorld::Ptr &ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 99ee886e1..cba6c5696 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -2,6 +2,9 @@ #include #include +#include + +#include #include @@ -28,6 +31,7 @@ #include "camera.hpp" #include "rotatecontroller.hpp" +#include "renderbin.hpp" namespace { @@ -303,6 +307,50 @@ void NpcAnimation::setViewMode(NpcAnimation::ViewMode viewMode) mViewMode = viewMode; rebuild(); + + setRenderBin(); +} + +/// @brief A RenderBin callback to clear the depth buffer before rendering. +class DepthClearCallback : public osgUtil::RenderBin::DrawCallback +{ +public: + DepthClearCallback() + { + mDepth = new osg::Depth; + mDepth->setWriteMask(true); + } + + virtual void drawImplementation(osgUtil::RenderBin* bin, osg::RenderInfo& renderInfo, osgUtil::RenderLeaf*& previous) + { + renderInfo.getState()->applyAttribute(mDepth); + + glClear(GL_DEPTH_BUFFER_BIT); + + bin->drawImplementation(renderInfo, previous); + } + + osg::ref_ptr mDepth; +}; + +void NpcAnimation::setRenderBin() +{ + if (mViewMode == VM_FirstPerson) + { + static bool prototypeAdded = false; + if (!prototypeAdded) + { + osg::ref_ptr depthClearBin (new osgUtil::RenderBin); + depthClearBin->setDrawCallback(new DepthClearCallback); + osgUtil::RenderBin::addRenderBinPrototype("DepthClear", depthClearBin); + prototypeAdded = true; + } + + osg::StateSet* stateset = mObjectRoot->getOrCreateStateSet(); + stateset->setRenderBinDetails(RenderBin_FirstPerson, "DepthClear", osg::StateSet::OVERRIDE_RENDERBIN_DETAILS); + } + else + Animation::setRenderBin(); } void NpcAnimation::rebuild() diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 06f40f847..b4272226d 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -81,6 +81,8 @@ private: void addPartGroup(int group, int priority, const std::vector &parts, bool enchantedGlow=false, osg::Vec4f* glowColor=NULL); + virtual void setRenderBin(); + osg::ref_ptr mFirstPersonNeckController; protected: diff --git a/apps/openmw/mwrender/renderbin.hpp b/apps/openmw/mwrender/renderbin.hpp index 63ccdd259..c14f61142 100644 --- a/apps/openmw/mwrender/renderbin.hpp +++ b/apps/openmw/mwrender/renderbin.hpp @@ -5,14 +5,15 @@ namespace MWRender { /// Defines the render bin numbers used in the OpenMW scene graph. The bin with the lowest number is rendered first. - /// Beware of RenderBin nesting, in most cases you will want to use setNestRenderBins(false). enum RenderBins { RenderBin_Sky = -1, - RenderBin_Default = 0, + RenderBin_Default = 0, // osg::StateSet::OPAQUE_BIN RenderBin_Water = 9, - RenderBin_OcclusionQuery = 10, - RenderBin_SunGlare = 11 + RenderBin_DepthSorted = 10, // osg::StateSet::TRANSPARENT_BIN + RenderBin_OcclusionQuery = 11, + RenderBin_FirstPerson = 12, + RenderBin_SunGlare = 13 }; } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index f4b8aa451..cae6541af 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -493,6 +493,8 @@ namespace MWRender texture->setInternalFormat(GL_RGB); texture->setTextureSize(w, h); texture->setResizeNonPowerOfTwoHint(false); + texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); rttCamera->attach(osg::Camera::COLOR_BUFFER, texture); image->setDataType(GL_UNSIGNED_BYTE); diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 6f1b26733..8de8a61fc 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -724,22 +724,30 @@ private: , mTimeOfDayFade(1.f) , mGlareView(1.f) { - + const MWWorld::Fallback* fallback = MWBase::Environment::get().getWorld()->getFallback(); + mColor = fallback->getFallbackColour("Weather_Sun_Glare_Fader_Color"); + mSunGlareFaderMax = fallback->getFallbackFloat("Weather_Sun_Glare_Fader_Max"); + mSunGlareFaderAngleMax = fallback->getFallbackFloat("Weather_Sun_Glare_Fader_Angle_Max"); + + // Replicating a design flaw in MW. The color was being set on both ambient and emissive properties, which multiplies the result by two, + // then finally gets clamped by the fixed function pipeline. With the default INI settings, only the red component gets clamped, + // so the resulting color looks more orange than red. + mColor *= 2; + for (int i=0; i<3; ++i) + mColor[i] = std::min(1.f, mColor[i]); } virtual void operator ()(osg::Node* node, osg::NodeVisitor* nv) { osgUtil::CullVisitor* cv = static_cast(nv); - float angleRadians = getAngleToSunInRadians(cv->getCurrentCamera()); + float angleRadians = getAngleToSunInRadians(*cv->getCurrentRenderStage()->getInitialViewMatrix()); float visibleRatio = getVisibleRatio(cv->getCurrentCamera()); - const float angleMaxRadians = osg::DegreesToRadians(30.f); // Sun Glare Fader Angle Max + const float angleMaxRadians = osg::DegreesToRadians(mSunGlareFaderAngleMax); float value = 1.f - std::min(1.f, angleRadians / angleMaxRadians); - - const float sunGlareFaderMax = 0.5f; - float fade = value * sunGlareFaderMax; + float fade = value * mSunGlareFaderMax; fade *= mTimeOfDayFade * mGlareView * visibleRatio; @@ -754,17 +762,8 @@ private: osg::ref_ptr mat (createUnlitMaterial()); - osg::Vec4f sunGlareFaderColor (222/255.f, 95/255.f, 39/255.f, 1); - - // Replicating a design flaw in MW. The color was being set on both ambient and emissive properties, which multiplies the result by two, - // then finally gets clamped by the fixed function pipeline. With the default INI settings, only the red component gets clamped, - // so the resulting color looks more orange than red. - sunGlareFaderColor *= 2; - for (int i=0; i<3; ++i) - sunGlareFaderColor[i] = std::min(1.f, sunGlareFaderColor[i]); - mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0,0,0,fade)); - mat->setEmission(osg::Material::FRONT_AND_BACK, sunGlareFaderColor); + mat->setEmission(osg::Material::FRONT_AND_BACK, mColor); stateset->setAttributeAndModes(mat, osg::StateAttribute::ON); @@ -785,10 +784,10 @@ private: } private: - float getAngleToSunInRadians(osg::Camera* camera) const + float getAngleToSunInRadians(const osg::Matrix& viewMatrix) const { osg::Vec3d eye, center, up; - camera->getViewMatrixAsLookAt(eye, center, up); + viewMatrix.getLookAt(eye, center, up); osg::Vec3d forward = center - eye; osg::Vec3d sun = mSunTransform->getPosition(); @@ -802,6 +801,9 @@ private: osg::ref_ptr mSunTransform; float mTimeOfDayFade; float mGlareView; + osg::Vec4f mColor; + float mSunGlareFaderMax; + float mSunGlareFaderAngleMax; }; osg::ref_ptr mUpdater; diff --git a/apps/openmw/mwscript/compilercontext.cpp b/apps/openmw/mwscript/compilercontext.cpp index 083f463bc..4a7038e1c 100644 --- a/apps/openmw/mwscript/compilercontext.cpp +++ b/apps/openmw/mwscript/compilercontext.cpp @@ -12,6 +12,7 @@ #include "../mwworld/ptr.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/manualref.hpp" namespace MWScript { @@ -42,9 +43,9 @@ namespace MWScript } else { - MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPtr (id, false); + MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), id); - script = ptr.getClass().getScript (ptr); + script = ref.getPtr().getClass().getScript (ref.getPtr()); reference = true; } @@ -82,7 +83,8 @@ namespace MWScript store.get().search (name) || store.get().search (name) || store.get().search (name) || - store.get().search (name); + store.get().search (name) || + store.get().search (name); } bool CompilerContext::isJournalId (const std::string& name) const diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 8c6f7d259..bcaaeff94 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -140,7 +140,11 @@ void MWWorld::ContainerStore::unstack(const Ptr &ptr, const Ptr& container) { if (ptr.getRefData().getCount() <= 1) return; - addNewStack(ptr, ptr.getRefData().getCount()-1); + MWWorld::ContainerStoreIterator it = addNewStack(ptr, ptr.getRefData().getCount()-1); + const std::string script = it->getClass().getScript(*it); + if (!script.empty()) + MWBase::Environment::get().getWorld()->getLocalScripts().add(script, *it); + remove(ptr, ptr.getRefData().getCount()-1, container); } @@ -411,41 +415,48 @@ void MWWorld::ContainerStore::fill (const ESM::InventoryList& items, const std:: void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std::string& owner, int count, bool topLevel, const std::string& levItem) { - ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), id, count); - - if (ref.getPtr().getTypeName()==typeid (ESM::ItemLevList).name()) - { - const ESM::ItemLevList* levItem = ref.getPtr().get()->mBase; + try { + ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), id, count); - if (topLevel && std::abs(count) > 1 && levItem->mFlags & ESM::ItemLevList::Each) + if (ref.getPtr().getTypeName()==typeid (ESM::ItemLevList).name()) { - for (int i=0; i 0 ? 1 : -1, true, levItem->mId); - return; + const ESM::ItemLevList* levItem = ref.getPtr().get()->mBase; + + if (topLevel && std::abs(count) > 1 && levItem->mFlags & ESM::ItemLevList::Each) + { + for (int i=0; i 0 ? 1 : -1, true, levItem->mId); + return; + } + else + { + std::string id = MWMechanics::getLevelledItem(ref.getPtr().get()->mBase, false); + if (id.empty()) + return; + addInitialItem(id, owner, count, false, levItem->mId); + } } else { - std::string id = MWMechanics::getLevelledItem(ref.getPtr().get()->mBase, false); - if (id.empty()) - return; - addInitialItem(id, owner, count, false, levItem->mId); + // A negative count indicates restocking items + // For a restocking levelled item, remember what we spawned so we can delete it later when the merchant restocks + if (!levItem.empty() && count < 0) + { + if (mLevelledItemMap.find(id) == mLevelledItemMap.end()) + mLevelledItemMap[id] = 0; + mLevelledItemMap[id] += std::abs(count); + } + count = std::abs(count); + + ref.getPtr().getCellRef().setOwner(owner); + addImp (ref.getPtr(), count); } } - else + catch (const std::exception& e) { - // A negative count indicates restocking items - // For a restocking levelled item, remember what we spawned so we can delete it later when the merchant restocks - if (!levItem.empty() && count < 0) - { - if (mLevelledItemMap.find(id) == mLevelledItemMap.end()) - mLevelledItemMap[id] = 0; - mLevelledItemMap[id] += std::abs(count); - } - count = std::abs(count); - - ref.getPtr().getCellRef().setOwner(owner); - addImp (ref.getPtr(), count); + std::cerr << "Error in MWWorld::ContainerStore::addInitialItem: " << e.what() << std::endl; } + } void MWWorld::ContainerStore::restock (const ESM::InventoryList& items, const MWWorld::Ptr& ptr, const std::string& owner) diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 0755c1555..ebba4ae30 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -645,8 +645,15 @@ void MWWorld::InventoryStore::updateRechargingItems() { if (it->getClass().getEnchantment(*it) != "") { - const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find( - it->getClass().getEnchantment(*it)); + std::string enchantmentId = it->getClass().getEnchantment(*it); + const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().search( + enchantmentId); + if (!enchantment) + { + std::cerr << "Can't find enchantment '" << enchantmentId << "' on item " << it->getCellRef().getRefId() << std::endl; + continue; + } + if (enchantment->mData.mType == ESM::Enchantment::WhenUsed || enchantment->mData.mType == ESM::Enchantment::WhenStrikes) mRechargingItems.push_back(std::make_pair(it, static_cast(enchantment->mData.mCharge))); diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index b647a6c88..7a471eaa4 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -1029,7 +1029,8 @@ namespace MWWorld // Script //========================================================================= - inline void Store::load(ESM::ESMReader &esm, const std::string &id) { + template <> + void Store::load(ESM::ESMReader &esm, const std::string &id) { ESM::Script scpt; scpt.load(esm); Misc::StringUtils::toLower(scpt.mId); @@ -1045,7 +1046,8 @@ namespace MWWorld // StartScript //========================================================================= - inline void Store::load(ESM::ESMReader &esm, const std::string &id) + template <> + void Store::load(ESM::ESMReader &esm, const std::string &id) { ESM::StartScript s; s.load(esm); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 3ff7a750a..d994a35ee 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -204,6 +204,10 @@ namespace MWWorld mLevitationEnabled = true; mTeleportEnabled = true; + mGodMode = false; + mScriptsEnabled = true; + mSky = true; + // Rebuild player setupPlayer(); @@ -297,9 +301,6 @@ namespace MWWorld mDoorStates.clear(); - mGodMode = false; - mScriptsEnabled = true; - mSky = true; mTeleportEnabled = true; mLevitationEnabled = true; @@ -2659,10 +2660,6 @@ namespace MWWorld { const ESM::Spell* spell = getStore().get().find(selectedSpell); - // A power can be used once per 24h - if (spell->mData.mType == ESM::Spell::ST_Power) - stats.getSpells().usePower(spell->mId); - cast.cast(spell); } else if (actor.getClass().hasInventoryStore(actor)) diff --git a/components/compiler/exprparser.cpp b/components/compiler/exprparser.cpp index b588b6196..c0375b436 100644 --- a/components/compiler/exprparser.cpp +++ b/components/compiler/exprparser.cpp @@ -652,6 +652,13 @@ namespace Compiler return true; } + if (code ==Scanner::S_plus && mNextOperand) + { + // Also unary, but +, just ignore it + mTokenLoc = loc; + return true; + } + if (code==Scanner::S_open) { if (mNextOperand) diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index cd645f0cf..6e65c3183 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -179,7 +179,7 @@ namespace Compiler extensions.registerInstruction ("setjournalindex", "cl", opcodeSetJournalIndex); extensions.registerFunction ("getjournalindex", 'l', "c", opcodeGetJournalIndex); extensions.registerInstruction ("addtopic", "S" , opcodeAddTopic); - extensions.registerInstruction ("choice", "j/SlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSl", opcodeChoice); + extensions.registerInstruction ("choice", "j/SlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSl", opcodeChoice); extensions.registerInstruction("forcegreeting","",opcodeForceGreeting, opcodeForceGreetingExplicit); extensions.registerInstruction("goodbye", "", opcodeGoodbye); diff --git a/components/compiler/lineparser.cpp b/components/compiler/lineparser.cpp index 032af7d65..c1622c3e0 100644 --- a/components/compiler/lineparser.cpp +++ b/components/compiler/lineparser.cpp @@ -555,7 +555,7 @@ namespace Compiler } if (mAllowExpression && mState==BeginState && - (code==Scanner::S_open || code==Scanner::S_minus)) + (code==Scanner::S_open || code==Scanner::S_minus || code==Scanner::S_plus)) { scanner.putbackSpecial (code, loc); parseExpression (scanner, loc); diff --git a/components/compiler/scanner.cpp b/components/compiler/scanner.cpp index f25e69e39..3c5bb7747 100644 --- a/components/compiler/scanner.cpp +++ b/components/compiler/scanner.cpp @@ -175,7 +175,7 @@ namespace Compiler { value += c; } - else if (isStringCharacter (c)) + else if (c!='-' && isStringCharacter (c)) { error = true; value += c; diff --git a/components/interpreter/interpreter.cpp b/components/interpreter/interpreter.cpp index 263cea9a5..1892e4a6f 100644 --- a/components/interpreter/interpreter.cpp +++ b/components/interpreter/interpreter.cpp @@ -132,7 +132,34 @@ namespace Interpreter throw std::runtime_error (error.str()); } - Interpreter::Interpreter() + void Interpreter::begin() + { + if (mRunning) + { + mCallstack.push (mRuntime); + mRuntime.clear(); + } + else + { + mRunning = true; + } + } + + void Interpreter::end() + { + if (mCallstack.empty()) + { + mRuntime.clear(); + mRunning = false; + } + else + { + mRuntime = mCallstack.top(); + mCallstack.pop(); + } + } + + Interpreter::Interpreter() : mRunning (false) {} Interpreter::~Interpreter() @@ -202,19 +229,29 @@ namespace Interpreter { assert (codeSize>=4); - mRuntime.configure (code, codeSize, context); + begin(); - int opcodes = static_cast (code[0]); + try + { + mRuntime.configure (code, codeSize, context); - const Type_Code *codeBlock = code + 4; + int opcodes = static_cast (code[0]); - while (mRuntime.getPC()>=0 && mRuntime.getPC()=0 && mRuntime.getPC() +#include #include "runtime.hpp" #include "types.hpp" @@ -14,6 +15,8 @@ namespace Interpreter class Interpreter { + std::stack mCallstack; + bool mRunning; Runtime mRuntime; std::map mSegment0; std::map mSegment1; @@ -32,6 +35,10 @@ namespace Interpreter void abortUnknownSegment (Type_Code code); + void begin(); + + void end(); + public: Interpreter(); diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 54a02c950..2b73f779f 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -57,8 +57,8 @@ namespace } } - // Collect all properties affecting the given node that should be applied to an osg::Material. - void collectMaterialProperties(const Nif::Node* nifNode, std::vector& out) + // Collect all properties affecting the given drawable that should be handled on drawable basis rather than on the node hierarchy above it. + void collectDrawableProperties(const Nif::Node* nifNode, std::vector& out) { const Nif::PropertyList& props = nifNode->props; for (size_t i = 0; i parent) - collectMaterialProperties(nifNode->parent, out); + collectDrawableProperties(nifNode->parent, out); } class FrameSwitch : public osg::Group @@ -113,6 +114,7 @@ namespace // NodeCallback used to have a transform always oriented towards the camera. Can have translation and scale // set just like a regular MatrixTransform, but the rotation set will be overridden in order to face the camera. + // Must be set as a cull callback. class BillboardCallback : public osg::NodeCallback { public: @@ -160,24 +162,34 @@ namespace struct UpdateMorphGeometry : public osg::Drawable::CullCallback { UpdateMorphGeometry() + : mLastFrameNumber(0) { } UpdateMorphGeometry(const UpdateMorphGeometry& copy, const osg::CopyOp& copyop) : osg::Drawable::CullCallback(copy, copyop) + , mLastFrameNumber(0) { } META_Object(NifOsg, UpdateMorphGeometry) - virtual bool cull(osg::NodeVisitor *, osg::Drawable * drw, osg::State *) const + virtual bool cull(osg::NodeVisitor* nv, osg::Drawable * drw, osg::State *) const { osgAnimation::MorphGeometry* geom = static_cast(drw); if (!geom) return false; + + if (mLastFrameNumber == nv->getFrameStamp()->getFrameNumber()) + return false; + mLastFrameNumber = nv->getFrameStamp()->getFrameNumber(); + geom->transformSoftwareMethod(); return false; } + + private: + mutable unsigned int mLastFrameNumber; }; // Callback to return a static bounding box for a MorphGeometry. The idea is to not recalculate the bounding box @@ -861,9 +873,9 @@ namespace NifOsg osg::ref_ptr geode (new osg::Geode); geode->addDrawable(partsys); - std::vector materialProps; - collectMaterialProperties(nifNode, materialProps); - applyMaterialProperties(parentNode, materialProps, composite, true, animflags); + std::vector drawableProps; + collectDrawableProperties(nifNode, drawableProps); + applyDrawableProperties(parentNode, drawableProps, composite, true, animflags); // Particles don't have normals, so can't be diffuse lit. osg::Material* mat = static_cast(parentNode->getStateSet()->getAttribute(osg::StateAttribute::MATERIAL)); @@ -923,9 +935,9 @@ namespace NifOsg // - if there are no vertex colors, we need to disable colorMode. // - there are 3 "overlapping" nif properties that all affect the osg::Material, handling them // above the actual renderable would be tedious. - std::vector materialProps; - collectMaterialProperties(triShape, materialProps); - applyMaterialProperties(parentNode, materialProps, composite, !data->colors->empty(), animflags); + std::vector drawableProps; + collectDrawableProperties(triShape, drawableProps); + applyDrawableProperties(parentNode, drawableProps, composite, !data->colors->empty(), animflags); } void handleTriShape(const Nif::NiTriShape* triShape, osg::Group* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) @@ -1212,42 +1224,12 @@ namespace NifOsg case Nif::RC_NiVertexColorProperty: case Nif::RC_NiSpecularProperty: { - // Handled in handleTriShape so we know whether vertex colors are available + // Handled on drawable level so we know whether vertex colors are available break; } case Nif::RC_NiAlphaProperty: { - const Nif::NiAlphaProperty* alphaprop = static_cast(property); - osg::BlendFunc* blendfunc = new osg::BlendFunc; - osg::StateSet* stateset = node->getOrCreateStateSet(); - if (alphaprop->flags&1) - { - blendfunc->setFunction(getBlendMode((alphaprop->flags>>1)&0xf), - getBlendMode((alphaprop->flags>>5)&0xf)); - stateset->setAttributeAndModes(blendfunc, osg::StateAttribute::ON); - - bool noSort = (alphaprop->flags>>13)&1; - if (!noSort) - { - stateset->setNestRenderBins(false); - stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); - } - } - else - { - stateset->setAttributeAndModes(blendfunc, osg::StateAttribute::OFF); - stateset->setNestRenderBins(false); - stateset->setRenderingHint(osg::StateSet::OPAQUE_BIN); - } - - osg::AlphaFunc* alphafunc = new osg::AlphaFunc; - if((alphaprop->flags>>9)&1) - { - alphafunc->setFunction(getTestMode((alphaprop->flags>>10)&0x7), alphaprop->data.threshold/255.f); - stateset->setAttributeAndModes(alphafunc, osg::StateAttribute::ON); - } - else - stateset->setAttributeAndModes(alphafunc, osg::StateAttribute::OFF); + // Handled on drawable level to prevent RenderBin nesting issues break; } case Nif::RC_NiTexturingProperty: @@ -1377,7 +1359,7 @@ namespace NifOsg } } - void applyMaterialProperties(osg::Node* node, const std::vector& properties, SceneUtil::CompositeStateSetUpdater* composite, + void applyDrawableProperties(osg::Node* node, const std::vector& properties, SceneUtil::CompositeStateSetUpdater* composite, bool hasVertexColors, int animflags) { osg::StateSet* stateset = node->getOrCreateStateSet(); @@ -1433,6 +1415,43 @@ namespace NifOsg mat->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); break; } + break; + } + case Nif::RC_NiAlphaProperty: + { + const Nif::NiAlphaProperty* alphaprop = static_cast(property); + osg::BlendFunc* blendfunc = new osg::BlendFunc; + if (alphaprop->flags&1) + { + blendfunc->setFunction(getBlendMode((alphaprop->flags>>1)&0xf), + getBlendMode((alphaprop->flags>>5)&0xf)); + stateset->setAttributeAndModes(blendfunc, osg::StateAttribute::ON); + + bool noSort = (alphaprop->flags>>13)&1; + if (!noSort) + stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); + else + stateset->setRenderBinToInherit(); + } + else + { + stateset->removeAttribute(osg::StateAttribute::BLENDFUNC); + stateset->removeMode(GL_BLEND); + stateset->setRenderBinToInherit(); + } + + osg::AlphaFunc* alphafunc = new osg::AlphaFunc; + if((alphaprop->flags>>9)&1) + { + alphafunc->setFunction(getTestMode((alphaprop->flags>>10)&0x7), alphaprop->data.threshold/255.f); + stateset->setAttributeAndModes(alphafunc, osg::StateAttribute::ON); + } + else + { + stateset->removeAttribute(osg::StateAttribute::ALPHAFUNC); + stateset->removeMode(GL_ALPHA_TEST); + } + break; } } } diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index 07ec0aff6..18d7ddd46 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -64,6 +64,16 @@ namespace SceneUtil std::vector > mLights; }; + LightManager* findLightManager(const osg::NodePath& path) + { + for (unsigned int i=0;i(path[i])) + return lightManager; + } + return NULL; + } + // Set on a LightSource. Adds the light source to its light manager for the current frame. // This allows us to keep track of the current lights in the scene graph without tying creation & destruction to the manager. class CollectLightCallback : public osg::NodeCallback @@ -82,14 +92,8 @@ namespace SceneUtil { if (!mLightManager) { - for (unsigned int i=0;igetNodePath().size(); ++i) - { - if (LightManager* lightManager = dynamic_cast(nv->getNodePath()[i])) - { - mLightManager = lightManager; - break; - } - } + mLightManager = findLightManager(nv->getNodePath()); + if (!mLightManager) throw std::runtime_error("can't find parent LightManager"); } @@ -126,15 +130,13 @@ namespace SceneUtil }; LightManager::LightManager() - : mLightsInViewSpace(false) - , mStartLight(0) + : mStartLight(0) { setUpdateCallback(new LightManagerUpdateCallback); } LightManager::LightManager(const LightManager ©, const osg::CopyOp ©op) : osg::Group(copy, copyop) - , mLightsInViewSpace(false) , mStartLight(copy.mStartLight) { @@ -142,8 +144,8 @@ namespace SceneUtil void LightManager::update() { - mLightsInViewSpace = false; mLights.clear(); + mLightsInViewSpace.clear(); // do an occasional cleanup for orphaned lights if (mStateSetCache.size() > 5000) @@ -161,22 +163,6 @@ namespace SceneUtil mLights.push_back(l); } - void LightManager::prepareForCamera(osg::Camera *cam) - { - // later on we need to store this per camera - if (!mLightsInViewSpace) - { - for (std::vector::iterator it = mLights.begin(); it != mLights.end(); ++it) - { - LightSourceTransform& l = *it; - osg::Matrix worldViewMat = l.mWorldMatrix * cam->getViewMatrix(); - l.mViewBound = osg::BoundingSphere(osg::Vec3f(0,0,0), l.mLightSource->getRadius()); - transformBoundingSphere(worldViewMat, l.mViewBound); - } - mLightsInViewSpace = true; - } - } - osg::ref_ptr LightManager::getLightListStateSet(const LightList &lightList) { // possible optimization: return a StateSet containing all requested lights plus some extra lights (if a suitable one exists) @@ -212,6 +198,30 @@ namespace SceneUtil return mLights; } + const std::vector& LightManager::getLightsInViewSpace(osg::Camera *camera, const osg::RefMatrix* viewMatrix) + { + osg::observer_ptr camPtr (camera); + std::map, LightSourceViewBoundCollection>::iterator it = mLightsInViewSpace.find(camPtr); + + if (it == mLightsInViewSpace.end()) + { + it = mLightsInViewSpace.insert(std::make_pair(camPtr, LightSourceViewBoundCollection())).first; + + for (std::vector::iterator lightIt = mLights.begin(); lightIt != mLights.end(); ++lightIt) + { + osg::Matrix worldViewMat = lightIt->mWorldMatrix * (*viewMatrix); + osg::BoundingSphere viewBound = osg::BoundingSphere(osg::Vec3f(0,0,0), lightIt->mLightSource->getRadius()); + transformBoundingSphere(worldViewMat, viewBound); + + LightSourceViewBound l; + l.mLightSource = lightIt->mLightSource; + l.mViewBound = viewBound; + it->second.push_back(l); + } + } + return it->second; + } + void LightManager::setStartLight(int start) { mStartLight = start; @@ -241,7 +251,7 @@ namespace SceneUtil } - bool sortLights (const LightManager::LightSourceTransform* left, const LightManager::LightSourceTransform* right) + bool sortLights (const LightManager::LightSourceViewBound* left, const LightManager::LightSourceViewBound* right) { return left->mViewBound.center().length2() - left->mViewBound.radius2()/4.f < right->mViewBound.center().length2() - right->mViewBound.radius2()/4.f; } @@ -258,14 +268,7 @@ namespace SceneUtil if (!mLightManager) { - for (unsigned int i=0;igetNodePath().size(); ++i) - { - if (LightManager* lightManager = dynamic_cast(nv->getNodePath()[i])) - { - mLightManager = lightManager; - break; - } - } + mLightManager = findLightManager(nv->getNodePath()); if (!mLightManager) { traverse(node, nv); @@ -273,13 +276,14 @@ namespace SceneUtil } } - mLightManager->prepareForCamera(cv->getCurrentCamera()); - // Possible optimizations: // - cull list of lights by the camera frustum // - organize lights in a quad tree - const std::vector& lights = mLightManager->getLights(); + // Don't use Camera::getViewMatrix, that one might be relative to another camera! + const osg::RefMatrix* viewMatrix = cv->getCurrentRenderStage()->getInitialViewMatrix(); + + const std::vector& lights = mLightManager->getLightsInViewSpace(cv->getCurrentCamera(), viewMatrix); if (lights.size()) { @@ -288,10 +292,10 @@ namespace SceneUtil osg::Matrixf mat = *cv->getModelViewMatrix(); transformBoundingSphere(mat, nodeBound); - std::vector lightList; + LightManager::LightList lightList; for (unsigned int i=0; i& getLights() const; - typedef std::vector LightList; + struct LightSourceViewBound + { + LightSource* mLightSource; + osg::BoundingSphere mViewBound; + }; + + const std::vector& getLightsInViewSpace(osg::Camera* camera, const osg::RefMatrix* viewMatrix); + + typedef std::vector LightList; osg::ref_ptr getLightListStateSet(const LightList& lightList); @@ -98,7 +103,8 @@ namespace SceneUtil // Lights collected from the scene graph. Only valid during the cull traversal. std::vector mLights; - bool mLightsInViewSpace; + typedef std::vector LightSourceViewBoundCollection; + std::map, LightSourceViewBoundCollection> mLightsInViewSpace; // < Light list hash , StateSet > typedef std::map > LightStateSetMap; diff --git a/components/sceneutil/riggeometry.cpp b/components/sceneutil/riggeometry.cpp index 2a67c6ce6..8eb08f546 100644 --- a/components/sceneutil/riggeometry.cpp +++ b/components/sceneutil/riggeometry.cpp @@ -60,7 +60,7 @@ public: RigGeometry::RigGeometry() : mSkeleton(NULL) - , mFirstFrame(true) + , mLastFrameNumber(0) , mBoundsFirstFrame(true) { setCullCallback(new UpdateRigGeometry); @@ -72,7 +72,7 @@ RigGeometry::RigGeometry(const RigGeometry ©, const osg::CopyOp ©op) : osg::Geometry(copy, copyop) , mSkeleton(NULL) , mInfluenceMap(copy.mInfluenceMap) - , mFirstFrame(copy.mFirstFrame) + , mLastFrameNumber(0) , mBoundsFirstFrame(copy.mBoundsFirstFrame) { setSourceGeometry(copy.mSourceGeometry); @@ -206,9 +206,12 @@ void RigGeometry::update(osg::NodeVisitor* nv) return; } - if (!mSkeleton->getActive() && !mFirstFrame) + if (!mSkeleton->getActive() && mLastFrameNumber != 0) return; - mFirstFrame = false; + + if (mLastFrameNumber == nv->getFrameStamp()->getFrameNumber()) + return; + mLastFrameNumber = nv->getFrameStamp()->getFrameNumber(); mSkeleton->updateBoneMatrices(nv); diff --git a/components/sceneutil/riggeometry.hpp b/components/sceneutil/riggeometry.hpp index bd7c586c4..e51fc0cf6 100644 --- a/components/sceneutil/riggeometry.hpp +++ b/components/sceneutil/riggeometry.hpp @@ -64,7 +64,7 @@ namespace SceneUtil BoneSphereMap mBoneSphereMap; - bool mFirstFrame; + unsigned int mLastFrameNumber; bool mBoundsFirstFrame; bool initFromParentSkeleton(osg::NodeVisitor* nv); diff --git a/files/mygui/openmw_count_window.layout b/files/mygui/openmw_count_window.layout index e9a1cb2c8..27b62ebf0 100644 --- a/files/mygui/openmw_count_window.layout +++ b/files/mygui/openmw_count_window.layout @@ -13,6 +13,8 @@ + +