diff --git a/CI/before_script.linux.sh b/CI/before_script.linux.sh index 17667ad28..93be1cb48 100755 --- a/CI/before_script.linux.sh +++ b/CI/before_script.linux.sh @@ -5,4 +5,4 @@ mkdir build cd build export CODE_COVERAGE=1 if [ "${CC}" = "clang" ]; then export CODE_COVERAGE=0; fi -${ANALYZE}cmake .. -DBUILD_WITH_CODE_COVERAGE=${CODE_COVERAGE} -DBUILD_UNITTESTS=1 -DCMAKE_INSTALL_PREFIX=/usr -DBINDIR=/usr/games -DCMAKE_BUILD_TYPE="RelWithDebInfo" -DUSE_SYSTEM_TINYXML=TRUE +${ANALYZE}cmake .. -DBUILD_WITH_CODE_COVERAGE=${CODE_COVERAGE} -DBUILD_UNITTESTS=1 -DCMAKE_INSTALL_PREFIX=/usr -DBINDIR=/usr/games -DCMAKE_BUILD_TYPE="None" -DUSE_SYSTEM_TINYXML=TRUE diff --git a/CMakeLists.txt b/CMakeLists.txt index 719df3e34..baaa241f6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -190,10 +190,6 @@ if (WIN32) add_definitions(-DNOMINMAX -DWIN32_LEAN_AND_MEAN) endif() -if (ANDROID) - set(OPENGL_ES TRUE CACHE BOOL "enable opengl es support for android" FORCE) -endif (ANDROID) - option(OPENGL_ES "enable opengl es support" FALSE ) if (OPENGL_ES) @@ -377,7 +373,7 @@ endif() # CXX Compiler settings if (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-unused-parameter -std=c++98 -pedantic -Wno-long-long") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wundef -Wno-unused-parameter -std=c++98 -pedantic -Wno-long-long") if (CMAKE_CXX_COMPILER_ID STREQUAL Clang AND NOT APPLE) execute_process(COMMAND ${CMAKE_C_COMPILER} --version OUTPUT_VARIABLE CLANG_VERSION) @@ -566,7 +562,9 @@ endif(WIN32) # Extern add_subdirectory (extern/osg-ffmpeg-videoplayer) add_subdirectory (extern/oics) -add_subdirectory (extern/osgQt) +if (USE_QT) + add_subdirectory (extern/osgQt) +endif() # Components add_subdirectory (components) diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index ce0216066..4caf76090 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -26,7 +26,7 @@ opencs_units_noqt (model/world universalid record commands columnbase columnimp scriptcontext cell refidcollection refidadapter refiddata refidadapterimp ref collectionbase refcollection columns infocollection tablemimedata cellcoordinates cellselection resources resourcesmanager scope pathgrid landtexture land nestedtablewrapper nestedcollection nestedcoladapterimp nestedinfocollection - idcompletionmanager metadata defaultgmsts + idcompletionmanager metadata defaultgmsts infoselectwrapper ) opencs_hdrs_noqt (model/world @@ -42,7 +42,7 @@ opencs_units_noqt (model/tools mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck birthsigncheck spellcheck referencecheck referenceablecheck scriptcheck bodypartcheck startscriptcheck search searchoperation searchstage pathgridcheck soundgencheck magiceffectcheck - mergestages gmstcheck + mergestages gmstcheck topicinfocheck journalcheck ) opencs_hdrs_noqt (model/tools diff --git a/apps/opencs/model/prefs/state.hpp b/apps/opencs/model/prefs/state.hpp index bcd76c671..fffadee5e 100644 --- a/apps/opencs/model/prefs/state.hpp +++ b/apps/opencs/model/prefs/state.hpp @@ -1,4 +1,4 @@ -#ifndef CSV_PREFS_STATE_H +#ifndef CSM_PREFS_STATE_H #define CSM_PREFS_STATE_H #include diff --git a/apps/opencs/model/tools/journalcheck.cpp b/apps/opencs/model/tools/journalcheck.cpp new file mode 100644 index 000000000..bdd14ddf0 --- /dev/null +++ b/apps/opencs/model/tools/journalcheck.cpp @@ -0,0 +1,79 @@ +#include "journalcheck.hpp" + +#include +#include + +CSMTools::JournalCheckStage::JournalCheckStage(const CSMWorld::IdCollection &journals, + const CSMWorld::InfoCollection& journalInfos) + : mJournals(journals), mJournalInfos(journalInfos) +{} + +int CSMTools::JournalCheckStage::setup() +{ + return mJournals.getSize(); +} + +void CSMTools::JournalCheckStage::perform(int stage, CSMDoc::Messages& messages) +{ + const CSMWorld::Record &journalRecord = mJournals.getRecord(stage); + + if (journalRecord.isDeleted()) + return; + + const ESM::Dialogue &journal = journalRecord.get(); + int statusNamedCount = 0; + int totalInfoCount = 0; + std::set questIndices; + + CSMWorld::InfoCollection::Range range = mJournalInfos.getTopicRange(journal.mId); + + for (CSMWorld::InfoCollection::RecordConstIterator it = range.first; it != range.second; ++it) + { + const CSMWorld::Record infoRecord = (*it); + + if (infoRecord.isDeleted()) + continue; + + const CSMWorld::Info& journalInfo = infoRecord.get(); + + totalInfoCount += 1; + + if (journalInfo.mQuestStatus == ESM::DialInfo::QS_Name) + { + statusNamedCount += 1; + } + + if (journalInfo.mResponse.empty()) + { + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_JournalInfo, journalInfo.mId); + + messages.add(id, "Journal Info: missing description", "", CSMDoc::Message::Severity_Warning); + } + + std::pair::iterator, bool> result = questIndices.insert(journalInfo.mData.mJournalIndex); + + // Duplicate index + if (result.second == false) + { + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_JournalInfo, journalInfo.mId); + + std::ostringstream stream; + stream << "Journal: duplicated quest index " << journalInfo.mData.mJournalIndex; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); + } + } + + if (totalInfoCount == 0) + { + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Journal, journal.mId); + + messages.add(id, "Journal: no defined Journal Infos", "", CSMDoc::Message::Severity_Warning); + } + else if (statusNamedCount > 1) + { + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Journal, journal.mId); + + messages.add(id, "Journal: multiple infos with quest status \"Named\"", "", CSMDoc::Message::Severity_Error); + } +} diff --git a/apps/opencs/model/tools/journalcheck.hpp b/apps/opencs/model/tools/journalcheck.hpp new file mode 100644 index 000000000..c9f619698 --- /dev/null +++ b/apps/opencs/model/tools/journalcheck.hpp @@ -0,0 +1,35 @@ +#ifndef CSM_TOOLS_JOURNALCHECK_H +#define CSM_TOOLS_JOURNALCHECK_H + +#include + +#include "../world/idcollection.hpp" +#include "../world/infocollection.hpp" + +#include "../doc/stage.hpp" + +namespace CSMTools +{ + /// \brief VerifyStage: make sure that journal infos are good + class JournalCheckStage : public CSMDoc::Stage + { + public: + + JournalCheckStage(const CSMWorld::IdCollection& journals, + const CSMWorld::InfoCollection& journalInfos); + + virtual int setup(); + ///< \return number of steps + + virtual void perform(int stage, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages + + private: + + const CSMWorld::IdCollection& mJournals; + const CSMWorld::InfoCollection& mJournalInfos; + + }; +} + +#endif diff --git a/apps/opencs/model/tools/tools.cpp b/apps/opencs/model/tools/tools.cpp index e750092b9..f538a716e 100644 --- a/apps/opencs/model/tools/tools.cpp +++ b/apps/opencs/model/tools/tools.cpp @@ -30,6 +30,8 @@ #include "magiceffectcheck.hpp" #include "mergeoperation.hpp" #include "gmstcheck.hpp" +#include "topicinfocheck.hpp" +#include "journalcheck.hpp" CSMDoc::OperationHolder *CSMTools::Tools::get (int type) { @@ -111,9 +113,24 @@ CSMDoc::OperationHolder *CSMTools::Tools::getVerifier() mData.getReferenceables(), mData.getResources (CSMWorld::UniversalId::Type_Icons), mData.getResources (CSMWorld::UniversalId::Type_Textures))); - + mVerifierOperation->appendStage (new GmstCheckStage (mData.getGmsts())); + mVerifierOperation->appendStage (new TopicInfoCheckStage (mData.getTopicInfos(), + mData.getCells(), + mData.getClasses(), + mData.getFactions(), + mData.getGmsts(), + mData.getGlobals(), + mData.getJournals(), + mData.getRaces(), + mData.getRegions(), + mData.getTopics(), + mData.getReferenceables().getDataSet(), + mData.getResources (CSMWorld::UniversalId::Type_SoundsRes))); + + mVerifierOperation->appendStage (new JournalCheckStage(mData.getJournals(), mData.getJournalInfos())); + mVerifier.setOperation (mVerifierOperation); } diff --git a/apps/opencs/model/tools/topicinfocheck.cpp b/apps/opencs/model/tools/topicinfocheck.cpp new file mode 100644 index 000000000..f6af2a945 --- /dev/null +++ b/apps/opencs/model/tools/topicinfocheck.cpp @@ -0,0 +1,441 @@ +#include "topicinfocheck.hpp" + +#include + +#include "../world/infoselectwrapper.hpp" + +CSMTools::TopicInfoCheckStage::TopicInfoCheckStage( + const CSMWorld::InfoCollection& topicInfos, + const CSMWorld::IdCollection& cells, + const CSMWorld::IdCollection& classes, + const CSMWorld::IdCollection& factions, + const CSMWorld::IdCollection& gmsts, + const CSMWorld::IdCollection& globals, + const CSMWorld::IdCollection& journals, + const CSMWorld::IdCollection& races, + const CSMWorld::IdCollection& regions, + const CSMWorld::IdCollection &topics, + const CSMWorld::RefIdData& referencables, + const CSMWorld::Resources& soundFiles) + : mTopicInfos(topicInfos), + mCells(cells), + mClasses(classes), + mFactions(factions), + mGameSettings(gmsts), + mGlobals(globals), + mJournals(journals), + mRaces(races), + mRegions(regions), + mTopics(topics), + mReferencables(referencables), + mSoundFiles(soundFiles) +{} + +int CSMTools::TopicInfoCheckStage::setup() +{ + // Generate list of cell names for reference checking + + mCellNames.clear(); + for (int i = 0; i < mCells.getSize(); ++i) + { + const CSMWorld::Record& cellRecord = mCells.getRecord(i); + + if (cellRecord.isDeleted()) + continue; + + mCellNames.insert(cellRecord.get().mName); + } + // Cell names can also include region names + for (int i = 0; i < mRegions.getSize(); ++i) + { + const CSMWorld::Record& regionRecord = mRegions.getRecord(i); + + if (regionRecord.isDeleted()) + continue; + + mCellNames.insert(regionRecord.get().mName); + } + // Default cell name + int index = mGameSettings.searchId("sDefaultCellname"); + if (index != -1) + { + const CSMWorld::Record& gmstRecord = mGameSettings.getRecord(index); + + if (!gmstRecord.isDeleted() && gmstRecord.get().mValue.getType() == ESM::VT_String) + { + mCellNames.insert(gmstRecord.get().mValue.getString()); + } + } + + return mTopicInfos.getSize(); +} + +void CSMTools::TopicInfoCheckStage::perform(int stage, CSMDoc::Messages& messages) +{ + const CSMWorld::Record& infoRecord = mTopicInfos.getRecord(stage); + + if (infoRecord.isDeleted()) + return; + + const CSMWorld::Info& topicInfo = infoRecord.get(); + + // There should always be a topic that matches + int topicIndex = mTopics.searchId(topicInfo.mTopicId); + + const CSMWorld::Record& topicRecord = mTopics.getRecord(topicIndex); + + if (topicRecord.isDeleted()) + return; + + const ESM::Dialogue& topic = topicRecord.get(); + + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_TopicInfo, topicInfo.mId); + + // Check fields + + if (!topicInfo.mActor.empty()) + { + verifyActor(topicInfo.mActor, id, messages); + } + + if (!topicInfo.mClass.empty()) + { + verifyId(topicInfo.mClass, mClasses, id, messages); + } + + if (!topicInfo.mCell.empty()) + { + verifyCell(topicInfo.mCell, id, messages); + } + + if (!topicInfo.mFaction.empty()) + { + if (verifyId(topicInfo.mFaction, mFactions, id, messages)) + { + verifyFactionRank(topicInfo.mFaction, topicInfo.mData.mRank, id, messages); + } + } + + if (!topicInfo.mPcFaction.empty()) + { + if (verifyId(topicInfo.mPcFaction, mFactions, id, messages)) + { + verifyFactionRank(topicInfo.mPcFaction, topicInfo.mData.mPCrank, id, messages); + } + } + + if (topicInfo.mData.mGender < -1 || topicInfo.mData.mGender > 1) + { + std::ostringstream stream; + messages.add(id, "Gender: Value is invalid", "", CSMDoc::Message::Severity_Error); + } + + if (!topicInfo.mRace.empty()) + { + verifyId(topicInfo.mRace, mRaces, id, messages); + } + + if (!topicInfo.mSound.empty()) + { + verifySound(topicInfo.mSound, id, messages); + } + + if (topicInfo.mResponse.empty() && topic.mType != ESM::Dialogue::Voice) + { + messages.add(id, "Response is empty", "", CSMDoc::Message::Severity_Warning); + } + + // Check info conditions + + for (std::vector::const_iterator it = topicInfo.mSelects.begin(); + it != topicInfo.mSelects.end(); ++it) + { + verifySelectStruct((*it), id, messages); + } +} + +// Verification functions + +bool CSMTools::TopicInfoCheckStage::verifyActor(const std::string& actor, const CSMWorld::UniversalId& id, + CSMDoc::Messages& messages) +{ + const std::string specifier = "Actor"; + + CSMWorld::RefIdData::LocalIndex index = mReferencables.searchId(actor); + + if (index.first == -1) + { + writeMissingIdError(specifier, actor, id, messages); + return false; + } + else if (mReferencables.getRecord(index).isDeleted()) + { + writeDeletedRecordError(specifier, actor, id, messages); + return false; + } + else if (index.second != CSMWorld::UniversalId::Type_Npc && index.second != CSMWorld::UniversalId::Type_Creature) + { + writeInvalidTypeError(specifier, actor, index.second, "NPC or Creature", id, messages); + return false; + } + + return true; +} + +bool CSMTools::TopicInfoCheckStage::verifyCell(const std::string& cell, const CSMWorld::UniversalId& id, + CSMDoc::Messages& messages) +{ + const std::string specifier = "Cell"; + + if (mCellNames.find(cell) == mCellNames.end()) + { + writeMissingIdError(specifier, cell, id, messages); + return false; + } + + return true; +} + +bool CSMTools::TopicInfoCheckStage::verifyFactionRank(const std::string& factionName, int rank, const CSMWorld::UniversalId& id, + CSMDoc::Messages& messages) +{ + if (rank < -1) + { + std::ostringstream stream; + stream << "Rank or PC Rank is set to " << rank << ", but should be set to -1 if no rank is required"; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); + return false; + } + + int index = mFactions.searchId(factionName); + + const ESM::Faction &faction = mFactions.getRecord(index).get(); + + int limit = 0; + for (; limit < 10; ++limit) + { + if (faction.mRanks[limit].empty()) + break; + } + + if (rank >= limit) + { + std::ostringstream stream; + stream << "Rank or PC Rank is set to " << rank << " which is more than the maximum of " << limit - 1 + << " for the " << factionName << " faction"; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); + return false; + } + + return true; +} + +bool CSMTools::TopicInfoCheckStage::verifyItem(const std::string& item, const CSMWorld::UniversalId& id, + CSMDoc::Messages& messages) +{ + const std::string specifier = "Item"; + + CSMWorld::RefIdData::LocalIndex index = mReferencables.searchId(item); + + if (index.first == -1) + { + writeMissingIdError(specifier, item, id, messages); + return false; + } + else if (mReferencables.getRecord(index).isDeleted()) + { + writeDeletedRecordError(specifier, item, id, messages); + return false; + } + else + { + switch (index.second) + { + case CSMWorld::UniversalId::Type_Potion: + case CSMWorld::UniversalId::Type_Apparatus: + case CSMWorld::UniversalId::Type_Armor: + case CSMWorld::UniversalId::Type_Book: + case CSMWorld::UniversalId::Type_Clothing: + case CSMWorld::UniversalId::Type_Ingredient: + case CSMWorld::UniversalId::Type_Light: + case CSMWorld::UniversalId::Type_Lockpick: + case CSMWorld::UniversalId::Type_Miscellaneous: + case CSMWorld::UniversalId::Type_Probe: + case CSMWorld::UniversalId::Type_Repair: + case CSMWorld::UniversalId::Type_Weapon: + case CSMWorld::UniversalId::Type_ItemLevelledList: + break; + + default: + writeInvalidTypeError(specifier, item, index.second, "Potion, Armor, Book, etc.", id, messages); + return false; + } + } + + return true; +} + +bool CSMTools::TopicInfoCheckStage::verifySelectStruct(const ESM::DialInfo::SelectStruct& select, + const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) +{ + CSMWorld::ConstInfoSelectWrapper infoCondition(select); + + if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_None) + { + messages.add(id, "Invalid Info Condition: " + infoCondition.toString(), "", CSMDoc::Message::Severity_Error); + return false; + } + else if (!infoCondition.variantTypeIsValid()) + { + std::ostringstream stream; + stream << "Info Condition: Value for \"" << infoCondition.toString() << "\" has a type of "; + + switch (select.mValue.getType()) + { + case ESM::VT_None: stream << "None"; break; + case ESM::VT_Short: stream << "Short"; break; + case ESM::VT_Int: stream << "Int"; break; + case ESM::VT_Long: stream << "Long"; break; + case ESM::VT_Float: stream << "Float"; break; + case ESM::VT_String: stream << "String"; break; + default: stream << "Unknown"; break; + } + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); + return false; + } + else if (infoCondition.conditionIsAlwaysTrue()) + { + std::ostringstream stream; + stream << "Info Condition: " << infoCondition.toString() << " is always true"; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Warning); + return false; + } + else if (infoCondition.conditionIsNeverTrue()) + { + std::ostringstream stream; + stream << "Info Condition: " << infoCondition.toString() << " is never true"; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Warning); + return false; + } + + // Id checks + if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Global && + !verifyId(infoCondition.getVariableName(), mGlobals, id, messages)) + { + return false; + } + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Journal && + !verifyId(infoCondition.getVariableName(), mJournals, id, messages)) + { + return false; + } + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Item && + !verifyItem(infoCondition.getVariableName(), id, messages)) + { + return false; + } + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Dead && + !verifyActor(infoCondition.getVariableName(), id, messages)) + { + return false; + } + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotId && + !verifyActor(infoCondition.getVariableName(), id, messages)) + { + return false; + } + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotFaction && + !verifyId(infoCondition.getVariableName(), mFactions, id, messages)) + { + return false; + } + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotClass && + !verifyId(infoCondition.getVariableName(), mClasses, id, messages)) + { + return false; + } + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotRace && + !verifyId(infoCondition.getVariableName(), mRaces, id, messages)) + { + return false; + } + else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotCell && + !verifyCell(infoCondition.getVariableName(), id, messages)) + { + return false; + } + + return true; +} + +bool CSMTools::TopicInfoCheckStage::verifySound(const std::string& sound, const CSMWorld::UniversalId& id, + CSMDoc::Messages& messages) +{ + const std::string specifier = "Sound File"; + + if (mSoundFiles.searchId(sound) == -1) + { + writeMissingIdError(specifier, sound, id, messages); + return false; + } + + return true; +} + +template +bool CSMTools::TopicInfoCheckStage::verifyId(const std::string& name, const CSMWorld::IdCollection& collection, + const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) +{ + int index = collection.searchId(name); + + if (index == -1) + { + writeMissingIdError(T::getRecordType(), name, id, messages); + return false; + } + else if (collection.getRecord(index).isDeleted()) + { + writeDeletedRecordError(T::getRecordType(), name, id, messages); + return false; + } + + return true; +} + +// Error functions + +void CSMTools::TopicInfoCheckStage::writeMissingIdError(const std::string& specifier, const std::string& missingId, + const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) +{ + std::ostringstream stream; + stream << specifier << ": ID or name \"" << missingId << "\" could not be found"; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); +} + +void CSMTools::TopicInfoCheckStage::writeDeletedRecordError(const std::string& specifier, const std::string& recordId, + const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) +{ + std::ostringstream stream; + stream << specifier << ": Deleted record with ID \"" << recordId << "\" is being referenced"; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); +} + +void CSMTools::TopicInfoCheckStage::writeInvalidTypeError(const std::string& specifier, const std::string& invalidId, + CSMWorld::UniversalId::Type invalidType, const std::string& expectedType, const CSMWorld::UniversalId& id, + CSMDoc::Messages& messages) +{ + CSMWorld::UniversalId tempId(invalidType, invalidId); + + std::ostringstream stream; + stream << specifier << ": invalid type of " << tempId.getTypeName() << " was found for referencable \"" + << invalidId << "\" (can be of type " << expectedType << ")"; + + messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); +} diff --git a/apps/opencs/model/tools/topicinfocheck.hpp b/apps/opencs/model/tools/topicinfocheck.hpp new file mode 100644 index 000000000..510901dac --- /dev/null +++ b/apps/opencs/model/tools/topicinfocheck.hpp @@ -0,0 +1,95 @@ +#ifndef CSM_TOOLS_TOPICINFOCHECK_HPP +#define CSM_TOOLS_TOPICINFOCHECK_HPP + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "../world/cell.hpp" +#include "../world/idcollection.hpp" +#include "../world/infocollection.hpp" +#include "../world/refiddata.hpp" +#include "../world/resources.hpp" + +#include "../doc/stage.hpp" + +namespace CSMTools +{ + /// \brief VerifyStage: check topics + class TopicInfoCheckStage : public CSMDoc::Stage + { + public: + + TopicInfoCheckStage( + const CSMWorld::InfoCollection& topicInfos, + const CSMWorld::IdCollection& cells, + const CSMWorld::IdCollection& classes, + const CSMWorld::IdCollection& factions, + const CSMWorld::IdCollection& gmsts, + const CSMWorld::IdCollection& globals, + const CSMWorld::IdCollection& journals, + const CSMWorld::IdCollection& races, + const CSMWorld::IdCollection& regions, + const CSMWorld::IdCollection& topics, + const CSMWorld::RefIdData& referencables, + const CSMWorld::Resources& soundFiles); + + virtual int setup(); + ///< \return number of steps + + virtual void perform(int step, CSMDoc::Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages + + private: + + const CSMWorld::InfoCollection& mTopicInfos; + + const CSMWorld::IdCollection& mCells; + const CSMWorld::IdCollection& mClasses; + const CSMWorld::IdCollection& mFactions; + const CSMWorld::IdCollection& mGameSettings; + const CSMWorld::IdCollection& mGlobals; + const CSMWorld::IdCollection& mJournals; + const CSMWorld::IdCollection& mRaces; + const CSMWorld::IdCollection& mRegions; + const CSMWorld::IdCollection& mTopics; + + const CSMWorld::RefIdData& mReferencables; + const CSMWorld::Resources& mSoundFiles; + + std::set mCellNames; + + // These return false when not successful and write an error + bool verifyActor(const std::string& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + bool verifyCell(const std::string& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + bool verifyFactionRank(const std::string& name, int rank, const CSMWorld::UniversalId& id, + CSMDoc::Messages& messages); + bool verifyItem(const std::string& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + bool verifySelectStruct(const ESM::DialInfo::SelectStruct& select, const CSMWorld::UniversalId& id, + CSMDoc::Messages& messages); + bool verifySound(const std::string& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + + template + bool verifyId(const std::string& name, const CSMWorld::IdCollection& collection, + const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + + // Common error messages + void writeMissingIdError(const std::string& specifier, const std::string& missingId, + const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + + void writeDeletedRecordError(const std::string& specifier, const std::string& recordId, + const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + + void writeInvalidTypeError(const std::string& specifier, const std::string& invalidId, + CSMWorld::UniversalId::Type invalidType, const std::string& expectedType, + const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + }; +} + +#endif diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index d0d3a1671..32aa7edca 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -3,6 +3,7 @@ #include #include "universalid.hpp" +#include "infoselectwrapper.hpp" namespace CSMWorld { @@ -273,8 +274,8 @@ namespace CSMWorld { ColumnId_InfoList, "Info List" }, { ColumnId_InfoCondition, "Info Conditions" }, { ColumnId_InfoCondFunc, "Function" }, - { ColumnId_InfoCondVar, "Func/Variable" }, - { ColumnId_InfoCondComp, "Comp" }, + { ColumnId_InfoCondVar, "Variable/Object" }, + { ColumnId_InfoCondComp, "Relation" }, { ColumnId_InfoCondValue, "Values" }, { ColumnId_OriginalCell, "Original Cell" }, @@ -546,18 +547,6 @@ namespace "AI Wander", "AI Travel", "AI Follow", "AI Escort", "AI Activate", 0 }; - static const char *sInfoCondFunc[] = - { - " ", "Function", "Global", "Local", "Journal", - "Item", "Dead", "Not ID", "Not Faction", "Not Class", - "Not Race", "Not Cell", "Not Local", 0 - }; - - static const char *sInfoCondComp[] = - { - "!=", "<", "<=", "=", ">", ">=", 0 - }; - const char **getEnumNames (CSMWorld::Columns::ColumnId column) { switch (column) @@ -585,10 +574,8 @@ namespace case CSMWorld::Columns::ColumnId_EffectId: return sEffectId; case CSMWorld::Columns::ColumnId_PartRefType: return sPartRefType; case CSMWorld::Columns::ColumnId_AiPackageType: return sAiPackageType; - case CSMWorld::Columns::ColumnId_InfoCondFunc: return sInfoCondFunc; - // FIXME: don't have dynamic value enum delegate, use Display_String for now - //case CSMWorld::Columns::ColumnId_InfoCond: return sInfoCond; - case CSMWorld::Columns::ColumnId_InfoCondComp: return sInfoCondComp; + case CSMWorld::Columns::ColumnId_InfoCondFunc: return CSMWorld::ConstInfoSelectWrapper::FunctionEnumStrings; + case CSMWorld::Columns::ColumnId_InfoCondComp: return CSMWorld::ConstInfoSelectWrapper::RelationEnumStrings; default: return 0; } diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp index 097e83f7c..f8c72a390 100644 --- a/apps/opencs/model/world/commands.cpp +++ b/apps/opencs/model/world/commands.cpp @@ -68,9 +68,6 @@ void CSMWorld::ModifyCommand::undo() void CSMWorld::CreateCommand::applyModifications() { - for (std::map::const_iterator iter (mValues.begin()); iter!=mValues.end(); ++iter) - mModel.setData (mModel.getModelIndex (mId, iter->first), iter->second); - if (!mNestedValues.empty()) { CSMWorld::IdTree *tree = dynamic_cast(&mModel); @@ -114,7 +111,7 @@ void CSMWorld::CreateCommand::setType (UniversalId::Type type) void CSMWorld::CreateCommand::redo() { - mModel.addRecord (mId, mType); + mModel.addRecordWithData (mId, mValues, mType); applyModifications(); } diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 6eccb7483..b54df596c 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -271,7 +271,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc new NestedChildColumn (Columns::ColumnId_InfoCondFunc, ColumnBase::Display_InfoCondFunc)); // FIXME: don't have dynamic value enum delegate, use Display_String for now mTopicInfos.getNestableColumn(index)->addColumn( - new NestedChildColumn (Columns::ColumnId_InfoCondVar, ColumnBase::Display_String)); + new NestedChildColumn (Columns::ColumnId_InfoCondVar, ColumnBase::Display_InfoCondVar)); mTopicInfos.getNestableColumn(index)->addColumn( new NestedChildColumn (Columns::ColumnId_InfoCondComp, ColumnBase::Display_InfoCondComp)); mTopicInfos.getNestableColumn(index)->addColumn( diff --git a/apps/opencs/model/world/idcompletionmanager.cpp b/apps/opencs/model/world/idcompletionmanager.cpp index 20cd8652c..7f3221342 100644 --- a/apps/opencs/model/world/idcompletionmanager.cpp +++ b/apps/opencs/model/world/idcompletionmanager.cpp @@ -60,6 +60,10 @@ std::vector CSMWorld::IdCompletionManager::getDis { types.push_back(current->first); } + + // Hack for Display_InfoCondVar + types.push_back(CSMWorld::ColumnBase::Display_InfoCondVar); + return types; } @@ -104,7 +108,7 @@ void CSMWorld::IdCompletionManager::generateCompleters(CSMWorld::Data &data) QAbstractItemView *popup = new CSVWidget::CompleterPopup(); completer->setPopup(popup); // The completer takes ownership of the popup completer->setMaxVisibleItems(10); - + mCompleters[current->first] = completer; } } diff --git a/apps/opencs/model/world/idtable.cpp b/apps/opencs/model/world/idtable.cpp index bd1179cea..4d8a0842f 100644 --- a/apps/opencs/model/world/idtable.cpp +++ b/apps/opencs/model/world/idtable.cpp @@ -149,6 +149,21 @@ void CSMWorld::IdTable::addRecord (const std::string& id, UniversalId::Type type endInsertRows(); } +void CSMWorld::IdTable::addRecordWithData (const std::string& id, + const std::map& data, UniversalId::Type type) +{ + int index = mIdCollection->getAppendIndex (id, type); + + beginInsertRows (QModelIndex(), index, index); + + mIdCollection->appendBlankRecord (id, type); + + for (std::map::const_iterator iter (data.begin()); iter!=data.end(); ++iter) + mIdCollection->setData (index, iter->first, iter->second); + + endInsertRows(); +} + void CSMWorld::IdTable::cloneRecord(const std::string& origin, const std::string& destination, CSMWorld::UniversalId::Type type) diff --git a/apps/opencs/model/world/idtable.hpp b/apps/opencs/model/world/idtable.hpp index 9ecba0214..8c2f8a46d 100644 --- a/apps/opencs/model/world/idtable.hpp +++ b/apps/opencs/model/world/idtable.hpp @@ -53,6 +53,10 @@ namespace CSMWorld void addRecord (const std::string& id, UniversalId::Type type = UniversalId::Type_None); ///< \param type Will be ignored, unless the collection supports multiple record types + void addRecordWithData (const std::string& id, const std::map& data, + UniversalId::Type type = UniversalId::Type_None); + ///< \param type Will be ignored, unless the collection supports multiple record types + void cloneRecord(const std::string& origin, const std::string& destination, UniversalId::Type type = UniversalId::Type_None); diff --git a/apps/opencs/model/world/infoselectwrapper.cpp b/apps/opencs/model/world/infoselectwrapper.cpp new file mode 100644 index 000000000..d036b1965 --- /dev/null +++ b/apps/opencs/model/world/infoselectwrapper.cpp @@ -0,0 +1,893 @@ +#include "infoselectwrapper.hpp" + +#include +#include +#include + +const size_t CSMWorld::ConstInfoSelectWrapper::RuleMinSize = 5; + +const size_t CSMWorld::ConstInfoSelectWrapper::FunctionPrefixOffset = 1; +const size_t CSMWorld::ConstInfoSelectWrapper::FunctionIndexOffset = 2; +const size_t CSMWorld::ConstInfoSelectWrapper::RelationIndexOffset = 4; +const size_t CSMWorld::ConstInfoSelectWrapper::VarNameOffset = 5; + +const char* CSMWorld::ConstInfoSelectWrapper::FunctionEnumStrings[] = +{ + "Rank Low", + "Rank High", + "Rank Requirement", + "Reputation", + "Health Percent", + "PC Reputation", + "PC Level", + "PC Health Percent", + "PC Magicka", + "PC Fatigue", + "PC Strength", + "PC Block", + "PC Armorer", + "PC Medium Armor", + "PC Heavy Armor", + "PC Blunt Weapon", + "PC Long Blade", + "PC Axe", + "PC Spear", + "PC Athletics", + "PC Enchant", + "PC Detruction", + "PC Alteration", + "PC Illusion", + "PC Conjuration", + "PC Mysticism", + "PC Restoration", + "PC Alchemy", + "PC Unarmored", + "PC Security", + "PC Sneak", + "PC Acrobatics", + "PC Light Armor", + "PC Short Blade", + "PC Marksman", + "PC Merchantile", + "PC Speechcraft", + "PC Hand to Hand", + "PC Sex", + "PC Expelled", + "PC Common Disease", + "PC Blight Disease", + "PC Clothing Modifier", + "PC Crime Level", + "Same Sex", + "Same Race", + "Same Faction", + "Faction Rank Difference", + "Detected", + "Alarmed", + "Choice", + "PC Intelligence", + "PC Willpower", + "PC Agility", + "PC Speed", + "PC Endurance", + "PC Personality", + "PC Luck", + "PC Corpus", + "Weather", + "PC Vampire", + "Level", + "Attacked", + "Talked to PC", + "PC Health", + "Creature Target", + "Friend Hit", + "Fight", + "Hello", + "Alarm", + "Flee", + "Should Attack", + "Werewolf", + "PC Werewolf Kills", + "Global", + "Local", + "Journal", + "Item", + "Dead", + "Not Id", + "Not Faction", + "Not Class", + "Not Race", + "Not Cell", + "Not Local", + 0 +}; + +const char* CSMWorld::ConstInfoSelectWrapper::RelationEnumStrings[] = +{ + "=", + "!=", + ">", + ">=", + "<", + "<=", + 0 +}; + +const char* CSMWorld::ConstInfoSelectWrapper::ComparisonEnumStrings[] = +{ + "Boolean", + "Integer", + "Numeric", + 0 +}; + +// static functions + +std::string CSMWorld::ConstInfoSelectWrapper::convertToString(FunctionName name) +{ + if (name < Function_None) + return FunctionEnumStrings[name]; + else + return "(Invalid Data: Function)"; +} + +std::string CSMWorld::ConstInfoSelectWrapper::convertToString(RelationType type) +{ + if (type < Relation_None) + return RelationEnumStrings[type]; + else + return "(Invalid Data: Relation)"; +} + +std::string CSMWorld::ConstInfoSelectWrapper::convertToString(ComparisonType type) +{ + if (type < Comparison_None) + return ComparisonEnumStrings[type]; + else + return "(Invalid Data: Comparison)"; +} + +// ConstInfoSelectWrapper + +CSMWorld::ConstInfoSelectWrapper::ConstInfoSelectWrapper(const ESM::DialInfo::SelectStruct& select) + : mConstSelect(select) +{ + readRule(); +} + +CSMWorld::ConstInfoSelectWrapper::FunctionName CSMWorld::ConstInfoSelectWrapper::getFunctionName() const +{ + return mFunctionName; +} + +CSMWorld::ConstInfoSelectWrapper::RelationType CSMWorld::ConstInfoSelectWrapper::getRelationType() const +{ + return mRelationType; +} + +CSMWorld::ConstInfoSelectWrapper::ComparisonType CSMWorld::ConstInfoSelectWrapper::getComparisonType() const +{ + return mComparisonType; +} + +bool CSMWorld::ConstInfoSelectWrapper::hasVariable() const +{ + return mHasVariable; +} + +const std::string& CSMWorld::ConstInfoSelectWrapper::getVariableName() const +{ + return mVariableName; +} + +bool CSMWorld::ConstInfoSelectWrapper::conditionIsAlwaysTrue() const +{ + if (!variantTypeIsValid()) + return false; + + if (mComparisonType == Comparison_Boolean || mComparisonType == Comparison_Integer) + { + if (mConstSelect.mValue.getType() == ESM::VT_Float) + return conditionIsAlwaysTrue(getConditionFloatRange(), getValidIntRange()); + else + return conditionIsAlwaysTrue(getConditionIntRange(), getValidIntRange()); + } + else if (mComparisonType == Comparison_Numeric) + { + if (mConstSelect.mValue.getType() == ESM::VT_Float) + return conditionIsAlwaysTrue(getConditionFloatRange(), getValidFloatRange()); + else + return conditionIsAlwaysTrue(getConditionIntRange(), getValidFloatRange()); + } + + return false; +} + +bool CSMWorld::ConstInfoSelectWrapper::conditionIsNeverTrue() const +{ + if (!variantTypeIsValid()) + return false; + + if (mComparisonType == Comparison_Boolean || mComparisonType == Comparison_Integer) + { + if (mConstSelect.mValue.getType() == ESM::VT_Float) + return conditionIsNeverTrue(getConditionFloatRange(), getValidIntRange()); + else + return conditionIsNeverTrue(getConditionIntRange(), getValidIntRange()); + } + else if (mComparisonType == Comparison_Numeric) + { + if (mConstSelect.mValue.getType() == ESM::VT_Float) + return conditionIsNeverTrue(getConditionFloatRange(), getValidFloatRange()); + else + return conditionIsNeverTrue(getConditionIntRange(), getValidFloatRange()); + } + + return false; +} + +bool CSMWorld::ConstInfoSelectWrapper::variantTypeIsValid() const +{ + return (mConstSelect.mValue.getType() == ESM::VT_Int || mConstSelect.mValue.getType() == ESM::VT_Float); +} + +const ESM::Variant& CSMWorld::ConstInfoSelectWrapper::getVariant() const +{ + return mConstSelect.mValue; +} + +std::string CSMWorld::ConstInfoSelectWrapper::toString() const +{ + std::ostringstream stream; + stream << convertToString(mFunctionName) << " "; + + if (mHasVariable) + stream << mVariableName << " "; + + stream << convertToString(mRelationType) << " "; + + switch (mConstSelect.mValue.getType()) + { + case ESM::VT_Int: + stream << mConstSelect.mValue.getInteger(); + break; + + case ESM::VT_Float: + stream << mConstSelect.mValue.getFloat(); + break; + + default: + stream << "(Invalid value type)"; + break; + } + + return stream.str(); +} + +void CSMWorld::ConstInfoSelectWrapper::readRule() +{ + if (mConstSelect.mSelectRule.size() < RuleMinSize) + throw std::runtime_error("InfoSelectWrapper: rule is to small"); + + readFunctionName(); + readRelationType(); + readVariableName(); + updateHasVariable(); + updateComparisonType(); +} + +void CSMWorld::ConstInfoSelectWrapper::readFunctionName() +{ + char functionPrefix = mConstSelect.mSelectRule[FunctionPrefixOffset]; + std::string functionIndex = mConstSelect.mSelectRule.substr(FunctionIndexOffset, 2); + int convertedIndex = -1; + + // Read in function index, form ## from 00 .. 73, skip leading zero + if (functionIndex[0] == '0') + functionIndex = functionIndex[1]; + + std::stringstream stream; + stream << functionIndex; + stream >> convertedIndex; + + switch (functionPrefix) + { + case '1': + if (convertedIndex >= 0 && convertedIndex <= 73) + mFunctionName = static_cast(convertedIndex); + else + mFunctionName = Function_None; + break; + + case '2': mFunctionName = Function_Global; break; + case '3': mFunctionName = Function_Local; break; + case '4': mFunctionName = Function_Journal; break; + case '5': mFunctionName = Function_Item; break; + case '6': mFunctionName = Function_Dead; break; + case '7': mFunctionName = Function_NotId; break; + case '8': mFunctionName = Function_NotFaction; break; + case '9': mFunctionName = Function_NotClass; break; + case 'A': mFunctionName = Function_NotRace; break; + case 'B': mFunctionName = Function_NotCell; break; + case 'C': mFunctionName = Function_NotLocal; break; + default: mFunctionName = Function_None; break; + } +} + +void CSMWorld::ConstInfoSelectWrapper::readRelationType() +{ + char relationIndex = mConstSelect.mSelectRule[RelationIndexOffset]; + + switch (relationIndex) + { + case '0': mRelationType = Relation_Equal; break; + case '1': mRelationType = Relation_NotEqual; break; + case '2': mRelationType = Relation_Greater; break; + case '3': mRelationType = Relation_GreaterOrEqual; break; + case '4': mRelationType = Relation_Less; break; + case '5': mRelationType = Relation_LessOrEqual; break; + default: mRelationType = Relation_None; + } +} + +void CSMWorld::ConstInfoSelectWrapper::readVariableName() +{ + if (mConstSelect.mSelectRule.size() >= VarNameOffset) + mVariableName = mConstSelect.mSelectRule.substr(VarNameOffset); + else + mVariableName.clear(); +} + +void CSMWorld::ConstInfoSelectWrapper::updateHasVariable() +{ + switch (mFunctionName) + { + case Function_Global: + case Function_Local: + case Function_Journal: + case Function_Item: + case Function_Dead: + case Function_NotId: + case Function_NotFaction: + case Function_NotClass: + case Function_NotRace: + case Function_NotCell: + case Function_NotLocal: + mHasVariable = true; + break; + + default: + mHasVariable = false; + break; + } +} + +void CSMWorld::ConstInfoSelectWrapper::updateComparisonType() +{ + switch (mFunctionName) + { + // Boolean + case Function_NotId: + case Function_NotFaction: + case Function_NotClass: + case Function_NotRace: + case Function_NotCell: + case Function_NotLocal: + case Function_PcExpelled: + case Function_PcCommonDisease: + case Function_PcBlightDisease: + case Function_SameSex: + case Function_SameRace: + case Function_SameFaction: + case Function_Detected: + case Function_Alarmed: + case Function_PcCorpus: + case Function_PcVampire: + case Function_Attacked: + case Function_TalkedToPc: + case Function_ShouldAttack: + case Function_Werewolf: + mComparisonType = Comparison_Boolean; + break; + + // Integer + case Function_Journal: + case Function_Item: + case Function_Dead: + case Function_RankLow: + case Function_RankHigh: + case Function_RankRequirement: + case Function_Reputation: + case Function_PcReputation: + case Function_PcLevel: + case Function_PcStrength: + case Function_PcBlock: + case Function_PcArmorer: + case Function_PcMediumArmor: + case Function_PcHeavyArmor: + case Function_PcBluntWeapon: + case Function_PcLongBlade: + case Function_PcAxe: + case Function_PcSpear: + case Function_PcAthletics: + case Function_PcEnchant: + case Function_PcDestruction: + case Function_PcAlteration: + case Function_PcIllusion: + case Function_PcConjuration: + case Function_PcMysticism: + case Function_PcRestoration: + case Function_PcAlchemy: + case Function_PcUnarmored: + case Function_PcSecurity: + case Function_PcSneak: + case Function_PcAcrobatics: + case Function_PcLightArmor: + case Function_PcShortBlade: + case Function_PcMarksman: + case Function_PcMerchantile: + case Function_PcSpeechcraft: + case Function_PcHandToHand: + case Function_PcGender: + case Function_PcClothingModifier: + case Function_PcCrimeLevel: + case Function_FactionRankDifference: + case Function_Choice: + case Function_PcIntelligence: + case Function_PcWillpower: + case Function_PcAgility: + case Function_PcSpeed: + case Function_PcEndurance: + case Function_PcPersonality: + case Function_PcLuck: + case Function_Weather: + case Function_Level: + case Function_CreatureTarget: + case Function_FriendHit: + case Function_Fight: + case Function_Hello: + case Function_Alarm: + case Function_Flee: + case Function_PcWerewolfKills: + mComparisonType = Comparison_Integer; + break; + + // Numeric + case Function_Global: + case Function_Local: + + case Function_Health_Percent: + case Function_PcHealthPercent: + case Function_PcMagicka: + case Function_PcFatigue: + case Function_PcHealth: + mComparisonType = Comparison_Numeric; + break; + + default: + mComparisonType = Comparison_None; + break; + } +} + +std::pair CSMWorld::ConstInfoSelectWrapper::getConditionIntRange() const +{ + const int IntMax = std::numeric_limits::max(); + const int IntMin = std::numeric_limits::min(); + const std::pair InvalidRange(IntMax, IntMin); + + int value = mConstSelect.mValue.getInteger(); + + switch (mRelationType) + { + case Relation_Equal: + case Relation_NotEqual: + return std::pair(value, value); + + case Relation_Greater: + if (value == IntMax) + { + return InvalidRange; + } + else + { + return std::pair(value + 1, IntMax); + } + break; + + case Relation_GreaterOrEqual: + return std::pair(value, IntMax); + + case Relation_Less: + if (value == IntMin) + { + return InvalidRange; + } + else + { + return std::pair(IntMin, value - 1); + } + + case Relation_LessOrEqual: + return std::pair(IntMin, value); + + default: + throw std::logic_error("InfoSelectWrapper: relation does not have a range"); + } +} + +std::pair CSMWorld::ConstInfoSelectWrapper::getConditionFloatRange() const +{ + const float FloatMax = std::numeric_limits::infinity(); + const float FloatMin = -std::numeric_limits::infinity(); + const float Epsilon = std::numeric_limits::epsilon(); + const std::pair InvalidRange(FloatMax, FloatMin); + + float value = mConstSelect.mValue.getFloat(); + + switch (mRelationType) + { + case Relation_Equal: + case Relation_NotEqual: + return std::pair(value, value); + + case Relation_Greater: + return std::pair(value + Epsilon, FloatMax); + + case Relation_GreaterOrEqual: + return std::pair(value, FloatMax); + + case Relation_Less: + return std::pair(FloatMin, value - Epsilon); + + case Relation_LessOrEqual: + return std::pair(FloatMin, value); + + default: + throw std::logic_error("InfoSelectWrapper: given relation does not have a range"); + } +} + +std::pair CSMWorld::ConstInfoSelectWrapper::getValidIntRange() const +{ + const int IntMax = std::numeric_limits::max(); + const int IntMin = std::numeric_limits::min(); + + switch (mFunctionName) + { + // Boolean + case Function_NotId: + case Function_NotFaction: + case Function_NotClass: + case Function_NotRace: + case Function_NotCell: + case Function_NotLocal: + case Function_PcExpelled: + case Function_PcCommonDisease: + case Function_PcBlightDisease: + case Function_SameSex: + case Function_SameRace: + case Function_SameFaction: + case Function_Detected: + case Function_Alarmed: + case Function_PcCorpus: + case Function_PcVampire: + case Function_Attacked: + case Function_TalkedToPc: + case Function_ShouldAttack: + case Function_Werewolf: + return std::pair(0, 1); + + // Integer + case Function_RankLow: + case Function_RankHigh: + case Function_Reputation: + case Function_PcReputation: + case Function_Journal: + return std::pair(IntMin, IntMax); + + case Function_Item: + case Function_Dead: + case Function_PcLevel: + case Function_PcStrength: + case Function_PcBlock: + case Function_PcArmorer: + case Function_PcMediumArmor: + case Function_PcHeavyArmor: + case Function_PcBluntWeapon: + case Function_PcLongBlade: + case Function_PcAxe: + case Function_PcSpear: + case Function_PcAthletics: + case Function_PcEnchant: + case Function_PcDestruction: + case Function_PcAlteration: + case Function_PcIllusion: + case Function_PcConjuration: + case Function_PcMysticism: + case Function_PcRestoration: + case Function_PcAlchemy: + case Function_PcUnarmored: + case Function_PcSecurity: + case Function_PcSneak: + case Function_PcAcrobatics: + case Function_PcLightArmor: + case Function_PcShortBlade: + case Function_PcMarksman: + case Function_PcMerchantile: + case Function_PcSpeechcraft: + case Function_PcHandToHand: + case Function_PcClothingModifier: + case Function_PcCrimeLevel: + case Function_Choice: + case Function_PcIntelligence: + case Function_PcWillpower: + case Function_PcAgility: + case Function_PcSpeed: + case Function_PcEndurance: + case Function_PcPersonality: + case Function_PcLuck: + case Function_Level: + case Function_PcWerewolfKills: + return std::pair(0, IntMax); + + case Function_Fight: + case Function_Hello: + case Function_Alarm: + case Function_Flee: + return std::pair(0, 100); + + case Function_Weather: + return std::pair(0, 9); + + case Function_FriendHit: + return std::pair(0, 4); + + case Function_RankRequirement: + return std::pair(0, 3); + + case Function_CreatureTarget: + return std::pair(0, 2); + + case Function_PcGender: + return std::pair(0, 1); + + case Function_FactionRankDifference: + return std::pair(-9, 9); + + // Numeric + case Function_Global: + case Function_Local: + return std::pair(IntMin, IntMax); + + case Function_PcMagicka: + case Function_PcFatigue: + case Function_PcHealth: + return std::pair(0, IntMax); + + case Function_Health_Percent: + case Function_PcHealthPercent: + return std::pair(0, 100); + + default: + throw std::runtime_error("InfoSelectWrapper: function does not exist"); + } +} + +std::pair CSMWorld::ConstInfoSelectWrapper::getValidFloatRange() const +{ + const float FloatMax = std::numeric_limits::infinity(); + const float FloatMin = -std::numeric_limits::infinity(); + + switch (mFunctionName) + { + // Numeric + case Function_Global: + case Function_Local: + case Function_NotLocal: + return std::pair(FloatMin, FloatMax); + + case Function_PcMagicka: + case Function_PcFatigue: + case Function_PcHealth: + return std::pair(0, FloatMax); + + case Function_Health_Percent: + case Function_PcHealthPercent: + return std::pair(0, 100); + + default: + throw std::runtime_error("InfoSelectWrapper: function does not exist or is not numeric"); + } +} + +template +bool CSMWorld::ConstInfoSelectWrapper::rangeContains(T1 value, std::pair range) const +{ + return (value >= range.first && value <= range.second); +} + +template +bool CSMWorld::ConstInfoSelectWrapper::rangeFullyContains(std::pair containingRange, + std::pair testRange) const +{ + return (containingRange.first <= testRange.first) && (testRange.second <= containingRange.second); +} + +template +bool CSMWorld::ConstInfoSelectWrapper::rangesOverlap(std::pair range1, std::pair range2) const +{ + // One of the bounds of either range should fall within the other range + return + (range1.first <= range2.first && range2.first <= range1.second) || + (range1.first <= range2.second && range2.second <= range1.second) || + (range2.first <= range1.first && range1.first <= range2.second) || + (range2.first <= range1.second && range1.second <= range2.second); +} + +template +bool CSMWorld::ConstInfoSelectWrapper::rangesMatch(std::pair range1, std::pair range2) const +{ + return (range1.first == range2.first && range1.second == range2.second); +} + +template +bool CSMWorld::ConstInfoSelectWrapper::conditionIsAlwaysTrue(std::pair conditionRange, + std::pair validRange) const +{ + switch (mRelationType) + { + case Relation_Equal: + return false; + + case Relation_NotEqual: + // If value is not within range, it will always be true + return !rangeContains(conditionRange.first, validRange); + + case Relation_Greater: + case Relation_GreaterOrEqual: + case Relation_Less: + case Relation_LessOrEqual: + // If the valid range is completely within the condition range, it will always be true + return rangeFullyContains(conditionRange, validRange); + + default: + throw std::logic_error("InfoCondition: operator can not be used to compare"); + } + + return false; +} + +template +bool CSMWorld::ConstInfoSelectWrapper::conditionIsNeverTrue(std::pair conditionRange, + std::pair validRange) const +{ + switch (mRelationType) + { + case Relation_Equal: + return !rangeContains(conditionRange.first, validRange); + + case Relation_NotEqual: + return false; + + case Relation_Greater: + case Relation_GreaterOrEqual: + case Relation_Less: + case Relation_LessOrEqual: + // If ranges do not overlap, it will never be true + return !rangesOverlap(conditionRange, validRange); + + default: + throw std::logic_error("InfoCondition: operator can not be used to compare"); + } + + return false; +} + +// InfoSelectWrapper + +CSMWorld::InfoSelectWrapper::InfoSelectWrapper(ESM::DialInfo::SelectStruct& select) + : CSMWorld::ConstInfoSelectWrapper(select), mSelect(select) +{ +} + +void CSMWorld::InfoSelectWrapper::setFunctionName(FunctionName name) +{ + mFunctionName = name; + updateHasVariable(); + updateComparisonType(); +} + +void CSMWorld::InfoSelectWrapper::setRelationType(RelationType type) +{ + mRelationType = type; +} + +void CSMWorld::InfoSelectWrapper::setVariableName(const std::string& name) +{ + mVariableName = name; +} + +void CSMWorld::InfoSelectWrapper::setDefaults() +{ + if (!variantTypeIsValid()) + mSelect.mValue.setType(ESM::VT_Int); + + switch (mComparisonType) + { + case Comparison_Boolean: + setRelationType(Relation_Equal); + mSelect.mValue.setInteger(1); + break; + + case Comparison_Integer: + case Comparison_Numeric: + setRelationType(Relation_Greater); + mSelect.mValue.setInteger(0); + break; + + default: + // Do nothing + break; + } + + update(); +} + +void CSMWorld::InfoSelectWrapper::update() +{ + std::ostringstream stream; + + // Leading 0 + stream << '0'; + + // Write Function + + bool writeIndex = false; + size_t functionIndex = static_cast(mFunctionName); + + switch (mFunctionName) + { + case Function_None: stream << '0'; break; + case Function_Global: stream << '2'; break; + case Function_Local: stream << '3'; break; + case Function_Journal: stream << '4'; break; + case Function_Item: stream << '5'; break; + case Function_Dead: stream << '6'; break; + case Function_NotId: stream << '7'; break; + case Function_NotFaction: stream << '8'; break; + case Function_NotClass: stream << '9'; break; + case Function_NotRace: stream << 'A'; break; + case Function_NotCell: stream << 'B'; break; + case Function_NotLocal: stream << 'C'; break; + default: stream << '1'; writeIndex = true; break; + } + + if (writeIndex && functionIndex < 10) // leading 0 + stream << '0' << functionIndex; + else if (writeIndex) + stream << functionIndex; + else + stream << "00"; + + // Write Relation + switch (mRelationType) + { + case Relation_Equal: stream << '0'; break; + case Relation_NotEqual: stream << '1'; break; + case Relation_Greater: stream << '2'; break; + case Relation_GreaterOrEqual: stream << '3'; break; + case Relation_Less: stream << '4'; break; + case Relation_LessOrEqual: stream << '5'; break; + default: stream << '0'; break; + } + + if (mHasVariable) + stream << mVariableName; + + mSelect.mSelectRule = stream.str(); +} + +ESM::Variant& CSMWorld::InfoSelectWrapper::getVariant() +{ + return mSelect.mValue; +} diff --git a/apps/opencs/model/world/infoselectwrapper.hpp b/apps/opencs/model/world/infoselectwrapper.hpp new file mode 100644 index 000000000..ce26a46dc --- /dev/null +++ b/apps/opencs/model/world/infoselectwrapper.hpp @@ -0,0 +1,243 @@ +#ifndef CSM_WORLD_INFOSELECTWRAPPER_H +#define CSM_WORLD_INFOSELECTWRAPPER_H + +#include + +namespace CSMWorld +{ + // ESM::DialInfo::SelectStruct.mSelectRule + // 012345... + // ^^^ ^^ + // ||| || + // ||| |+------------- condition variable string + // ||| +-------------- comparison type, ['0'..'5']; e.g. !=, <, >=, etc + // ||+---------------- function index (encoded, where function == '1') + // |+----------------- function, ['1'..'C']; e.g. Global, Local, Not ID, etc + // +------------------ unknown + // + + // Wrapper for DialInfo::SelectStruct + class ConstInfoSelectWrapper + { + public: + + // Order matters + enum FunctionName + { + Function_RankLow=0, + Function_RankHigh, + Function_RankRequirement, + Function_Reputation, + Function_Health_Percent, + Function_PcReputation, + Function_PcLevel, + Function_PcHealthPercent, + Function_PcMagicka, + Function_PcFatigue, + Function_PcStrength, + Function_PcBlock, + Function_PcArmorer, + Function_PcMediumArmor, + Function_PcHeavyArmor, + Function_PcBluntWeapon, + Function_PcLongBlade, + Function_PcAxe, + Function_PcSpear, + Function_PcAthletics, + Function_PcEnchant, + Function_PcDestruction, + Function_PcAlteration, + Function_PcIllusion, + Function_PcConjuration, + Function_PcMysticism, + Function_PcRestoration, + Function_PcAlchemy, + Function_PcUnarmored, + Function_PcSecurity, + Function_PcSneak, + Function_PcAcrobatics, + Function_PcLightArmor, + Function_PcShortBlade, + Function_PcMarksman, + Function_PcMerchantile, + Function_PcSpeechcraft, + Function_PcHandToHand, + Function_PcGender, + Function_PcExpelled, + Function_PcCommonDisease, + Function_PcBlightDisease, + Function_PcClothingModifier, + Function_PcCrimeLevel, + Function_SameSex, + Function_SameRace, + Function_SameFaction, + Function_FactionRankDifference, + Function_Detected, + Function_Alarmed, + Function_Choice, + Function_PcIntelligence, + Function_PcWillpower, + Function_PcAgility, + Function_PcSpeed, + Function_PcEndurance, + Function_PcPersonality, + Function_PcLuck, + Function_PcCorpus, + Function_Weather, + Function_PcVampire, + Function_Level, + Function_Attacked, + Function_TalkedToPc, + Function_PcHealth, + Function_CreatureTarget, + Function_FriendHit, + Function_Fight, + Function_Hello, + Function_Alarm, + Function_Flee, + Function_ShouldAttack, + Function_Werewolf, + Function_PcWerewolfKills=73, + + Function_Global, + Function_Local, + Function_Journal, + Function_Item, + Function_Dead, + Function_NotId, + Function_NotFaction, + Function_NotClass, + Function_NotRace, + Function_NotCell, + Function_NotLocal, + + Function_None + }; + + enum RelationType + { + Relation_Equal, + Relation_NotEqual, + Relation_Greater, + Relation_GreaterOrEqual, + Relation_Less, + Relation_LessOrEqual, + + Relation_None + }; + + enum ComparisonType + { + Comparison_Boolean, + Comparison_Integer, + Comparison_Numeric, + + Comparison_None + }; + + static const size_t RuleMinSize; + + static const size_t FunctionPrefixOffset; + static const size_t FunctionIndexOffset; + static const size_t RelationIndexOffset; + static const size_t VarNameOffset; + + static const char* FunctionEnumStrings[]; + static const char* RelationEnumStrings[]; + static const char* ComparisonEnumStrings[]; + + static std::string convertToString(FunctionName name); + static std::string convertToString(RelationType type); + static std::string convertToString(ComparisonType type); + + ConstInfoSelectWrapper(const ESM::DialInfo::SelectStruct& select); + + FunctionName getFunctionName() const; + RelationType getRelationType() const; + ComparisonType getComparisonType() const; + + bool hasVariable() const; + const std::string& getVariableName() const; + + bool conditionIsAlwaysTrue() const; + bool conditionIsNeverTrue() const; + bool variantTypeIsValid() const; + + const ESM::Variant& getVariant() const; + + std::string toString() const; + + protected: + + void readRule(); + void readFunctionName(); + void readRelationType(); + void readVariableName(); + void updateHasVariable(); + void updateComparisonType(); + + std::pair getConditionIntRange() const; + std::pair getConditionFloatRange() const; + + std::pair getValidIntRange() const; + std::pair getValidFloatRange() const; + + template + bool rangeContains(Type1 value, std::pair range) const; + + template + bool rangesOverlap(std::pair range1, std::pair range2) const; + + template + bool rangeFullyContains(std::pair containing, std::pair test) const; + + template + bool rangesMatch(std::pair range1, std::pair range2) const; + + template + bool conditionIsAlwaysTrue(std::pair conditionRange, std::pair validRange) const; + + template + bool conditionIsNeverTrue(std::pair conditionRange, std::pair validRange) const; + + FunctionName mFunctionName; + RelationType mRelationType; + ComparisonType mComparisonType; + + bool mHasVariable; + std::string mVariableName; + + private: + + const ESM::DialInfo::SelectStruct& mConstSelect; + }; + + // Wrapper for DialInfo::SelectStruct that can modify the wrapped select struct + class InfoSelectWrapper : public ConstInfoSelectWrapper + { + public: + + InfoSelectWrapper(ESM::DialInfo::SelectStruct& select); + + // Wrapped SelectStruct will not be modified until update() is called + void setFunctionName(FunctionName name); + void setRelationType(RelationType type); + void setVariableName(const std::string& name); + + // Modified wrapped SelectStruct + void update(); + + // This sets properties based on the function name to its defaults and updates the wrapped object + void setDefaults(); + + ESM::Variant& getVariant(); + + private: + + ESM::DialInfo::SelectStruct& mSelect; + + void writeRule(); + }; +} + +#endif diff --git a/apps/opencs/model/world/nestedcoladapterimp.cpp b/apps/opencs/model/world/nestedcoladapterimp.cpp index 92b4b9e62..82df7bd11 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.cpp +++ b/apps/opencs/model/world/nestedcoladapterimp.cpp @@ -6,6 +6,7 @@ #include "idcollection.hpp" #include "pathgrid.hpp" #include "info.hpp" +#include "infoselectwrapper.hpp" namespace CSMWorld { @@ -529,16 +530,6 @@ namespace CSMWorld return 1; // fixed at size 1 } - // ESM::DialInfo::SelectStruct.mSelectRule - // 012345... - // ^^^ ^^ - // ||| || - // ||| |+------------- condition variable string - // ||| +-------------- comparison type, ['0'..'5']; e.g. !=, <, >=, etc - // ||+---------------- function index (encoded, where function == '1') - // |+----------------- function, ['1'..'C']; e.g. Global, Local, Not ID, etc - // +------------------ unknown - // InfoConditionAdapter::InfoConditionAdapter () {} void InfoConditionAdapter::addRow(Record& record, int position) const @@ -547,11 +538,11 @@ namespace CSMWorld std::vector& conditions = info.mSelects; - // blank row + // default row ESM::DialInfo::SelectStruct condStruct; - condStruct.mSelectRule = "00000"; + condStruct.mSelectRule = "01000"; condStruct.mValue = ESM::Variant(); - condStruct.mValue.setType(ESM::VT_Int); // default to ints + condStruct.mValue.setType(ESM::VT_Int); conditions.insert(conditions.begin()+position, condStruct); @@ -589,89 +580,6 @@ namespace CSMWorld return new NestedTableWrapper >(record.get().mSelects); } - // See the mappings in MWDialogue::SelectWrapper::getArgument - // from ESM::Attribute, ESM::Skill and MWMechanics::CreatureStats (for AI) - static std::map populateEncToInfoFunc() - { - std::map funcMap; - funcMap["00"] = "Rank Low"; - funcMap["01"] = "Rank High"; - funcMap["02"] = "Rank Requirement"; - funcMap["03"] = "Reputation"; - funcMap["04"] = "Health Percent"; - funcMap["05"] = "PC Reputation"; - funcMap["06"] = "PC Level"; - funcMap["07"] = "PC Health Percent"; - funcMap["08"] = "PC Magicka"; - funcMap["09"] = "PC Fatigue"; - funcMap["10"] = "PC Strength"; - funcMap["11"] = "PC Block"; - funcMap["12"] = "PC Armorer"; - funcMap["13"] = "PC Medium Armor"; - funcMap["14"] = "PC Heavy Armor"; - funcMap["15"] = "PC Blunt Weapon"; - funcMap["16"] = "PC Long Blade"; - funcMap["17"] = "PC Axe"; - funcMap["18"] = "PC Spear"; - funcMap["19"] = "PC Athletics"; - funcMap["20"] = "PC Enchant"; - funcMap["21"] = "PC Destruction"; - funcMap["22"] = "PC Alteration"; - funcMap["23"] = "PC Illusion"; - funcMap["24"] = "PC Conjuration"; - funcMap["25"] = "PC Mysticism"; - funcMap["26"] = "PC Restoration"; - funcMap["27"] = "PC Alchemy"; - funcMap["28"] = "PC Unarmored"; - funcMap["29"] = "PC Security"; - funcMap["30"] = "PC Sneak"; - funcMap["31"] = "PC Acrobatics"; - funcMap["32"] = "PC Light Armor"; - funcMap["33"] = "PC Short Blade"; - funcMap["34"] = "PC Marksman"; - funcMap["35"] = "PC Merchantile"; - funcMap["36"] = "PC Speechcraft"; - funcMap["37"] = "PC Hand To Hand"; - funcMap["38"] = "PC Sex"; - funcMap["39"] = "PC Expelled"; - funcMap["40"] = "PC Common Disease"; - funcMap["41"] = "PC Blight Disease"; - funcMap["42"] = "PC Clothing Modifier"; - funcMap["43"] = "PC Crime Level"; - funcMap["44"] = "Same Sex"; - funcMap["45"] = "Same Race"; - funcMap["46"] = "Same Faction"; - funcMap["47"] = "Faction Rank Difference"; - funcMap["48"] = "Detected"; - funcMap["49"] = "Alarmed"; - funcMap["50"] = "Choice"; - funcMap["51"] = "PC Intelligence"; - funcMap["52"] = "PC Willpower"; - funcMap["53"] = "PC Agility"; - funcMap["54"] = "PC Speed"; - funcMap["55"] = "PC Endurance"; - funcMap["56"] = "PC Personality"; - funcMap["57"] = "PC Luck"; - funcMap["58"] = "PC Corpus"; - funcMap["59"] = "Weather"; - funcMap["60"] = "PC Vampire"; - funcMap["61"] = "Level"; - funcMap["62"] = "Attacked"; - funcMap["63"] = "Talked To PC"; - funcMap["64"] = "PC Health"; - funcMap["65"] = "Creature Target"; - funcMap["66"] = "Friend Hit"; - funcMap["67"] = "Fight"; - funcMap["68"] = "Hello"; - funcMap["69"] = "Alarm"; - funcMap["70"] = "Flee"; - funcMap["71"] = "Should Attack"; - funcMap["72"] = "Werewolf"; - funcMap["73"] = "PC Werewolf Kills"; - return funcMap; - } - static const std::map sEncToInfoFunc = populateEncToInfoFunc(); - QVariant InfoConditionAdapter::getData(const Record& record, int subRowIndex, int subColIndex) const { @@ -682,70 +590,36 @@ namespace CSMWorld if (subRowIndex < 0 || subRowIndex >= static_cast (conditions.size())) throw std::runtime_error ("index out of range"); + ConstInfoSelectWrapper infoSelectWrapper(conditions[subRowIndex]); + switch (subColIndex) { case 0: { - char condType = conditions[subRowIndex].mSelectRule[1]; - switch (condType) - { - case '0': return 0; // blank space - case '1': return 1; // Function - case '2': return 2; // Global - case '3': return 3; // Local - case '4': return 4; // Journal - case '5': return 5; // Item - case '6': return 6; // Dead - case '7': return 7; // Not ID - case '8': return 8; // Not Factio - case '9': return 9; // Not Class - case 'A': return 10; // Not Race - case 'B': return 11; // Not Cell - case 'C': return 12; // Not Local - default: return QVariant(); // TODO: log an error? - } + return infoSelectWrapper.getFunctionName(); } case 1: { - if (conditions[subRowIndex].mSelectRule[1] == '1') - { - // throws an exception if the encoding is not found - return sEncToInfoFunc.at(conditions[subRowIndex].mSelectRule.substr(2, 2)).c_str(); - } + if (infoSelectWrapper.hasVariable()) + return QString(infoSelectWrapper.getVariableName().c_str()); else - return QString(conditions[subRowIndex].mSelectRule.substr(5).c_str()); + return ""; } case 2: { - char compType = conditions[subRowIndex].mSelectRule[4]; - switch (compType) - { - case '0': return 3; // = - case '1': return 0; // != - case '2': return 4; // > - case '3': return 5; // >= - case '4': return 1; // < - case '5': return 2; // <= - default: return QVariant(); // TODO: log an error? - } + return infoSelectWrapper.getRelationType(); } case 3: { - switch (conditions[subRowIndex].mValue.getType()) + switch (infoSelectWrapper.getVariant().getType()) { - case ESM::VT_String: - { - return QString::fromUtf8 (conditions[subRowIndex].mValue.getString().c_str()); - } case ESM::VT_Int: - case ESM::VT_Short: - case ESM::VT_Long: { - return conditions[subRowIndex].mValue.getInteger(); + return infoSelectWrapper.getVariant().getInteger(); } case ESM::VT_Float: { - return conditions[subRowIndex].mValue.getFloat(); + return infoSelectWrapper.getVariant().getFloat(); } default: return QVariant(); } @@ -764,101 +638,63 @@ namespace CSMWorld if (subRowIndex < 0 || subRowIndex >= static_cast (conditions.size())) throw std::runtime_error ("index out of range"); + InfoSelectWrapper infoSelectWrapper(conditions[subRowIndex]); + bool conversionResult = false; + switch (subColIndex) { - case 0: + case 0: // Function { - // See sInfoCondFunc in columns.cpp for the enum values - switch (value.toInt()) - { - // FIXME: when these change the values of the other columns need to change - // correspondingly (and automatically) - case 1: - { - conditions[subRowIndex].mSelectRule[1] = '1'; // Function - // default to "Rank Low" - conditions[subRowIndex].mSelectRule[2] = '0'; - conditions[subRowIndex].mSelectRule[3] = '0'; - break; - } - case 2: conditions[subRowIndex].mSelectRule[1] = '2'; break; // Global - case 3: conditions[subRowIndex].mSelectRule[1] = '3'; break; // Local - case 4: conditions[subRowIndex].mSelectRule[1] = '4'; break; // Journal - case 5: conditions[subRowIndex].mSelectRule[1] = '5'; break; // Item - case 6: conditions[subRowIndex].mSelectRule[1] = '6'; break; // Dead - case 7: conditions[subRowIndex].mSelectRule[1] = '7'; break; // Not ID - case 8: conditions[subRowIndex].mSelectRule[1] = '8'; break; // Not Faction - case 9: conditions[subRowIndex].mSelectRule[1] = '9'; break; // Not Class - case 10: conditions[subRowIndex].mSelectRule[1] = 'A'; break; // Not Race - case 11: conditions[subRowIndex].mSelectRule[1] = 'B'; break; // Not Cell - case 12: conditions[subRowIndex].mSelectRule[1] = 'C'; break; // Not Local - default: return; // return without saving - } - break; - } - case 1: - { - if (conditions[subRowIndex].mSelectRule[1] == '1') - { - std::map::const_iterator it = sEncToInfoFunc.begin(); - for (;it != sEncToInfoFunc.end(); ++it) - { - if (it->second == value.toString().toUtf8().constData()) - { - std::string rule = conditions[subRowIndex].mSelectRule.substr(0, 2); - rule.append(it->first); - // leave old values for undo (NOTE: may not be vanilla's behaviour) - rule.append(conditions[subRowIndex].mSelectRule.substr(4)); - conditions[subRowIndex].mSelectRule = rule; - break; - } - } + infoSelectWrapper.setFunctionName(static_cast(value.toInt())); - if (it == sEncToInfoFunc.end()) - return; // return without saving; TODO: maybe log an error here - } - else + if (infoSelectWrapper.getComparisonType() != ConstInfoSelectWrapper::Comparison_Numeric && + infoSelectWrapper.getVariant().getType() != ESM::VT_Int) { - // FIXME: validate the string values before saving, based on the current function - std::string rule = conditions[subRowIndex].mSelectRule.substr(0, 5); - conditions[subRowIndex].mSelectRule = rule.append(value.toString().toUtf8().constData()); + infoSelectWrapper.getVariant().setType(ESM::VT_Int); } + + infoSelectWrapper.update(); break; } - case 2: + case 1: // Variable { - // See sInfoCondComp in columns.cpp for the enum values - switch (value.toInt()) - { - case 0: conditions[subRowIndex].mSelectRule[4] = '1'; break; // != - case 1: conditions[subRowIndex].mSelectRule[4] = '4'; break; // < - case 2: conditions[subRowIndex].mSelectRule[4] = '5'; break; // <= - case 3: conditions[subRowIndex].mSelectRule[4] = '0'; break; // = - case 4: conditions[subRowIndex].mSelectRule[4] = '2'; break; // > - case 5: conditions[subRowIndex].mSelectRule[4] = '3'; break; // >= - default: return; // return without saving - } + infoSelectWrapper.setVariableName(value.toString().toUtf8().constData()); + infoSelectWrapper.update(); break; } - case 3: + case 2: // Relation { - switch (conditions[subRowIndex].mValue.getType()) + infoSelectWrapper.setRelationType(static_cast(value.toInt())); + infoSelectWrapper.update(); + break; + } + case 3: // Value + { + switch (infoSelectWrapper.getComparisonType()) { - case ESM::VT_String: + case ConstInfoSelectWrapper::Comparison_Numeric: { - conditions[subRowIndex].mValue.setString (value.toString().toUtf8().constData()); + // QVariant seems to have issues converting 0 + if ((value.toInt(&conversionResult) && conversionResult) || value.toString().compare("0") == 0) + { + infoSelectWrapper.getVariant().setType(ESM::VT_Int); + infoSelectWrapper.getVariant().setInteger(value.toInt()); + } + else if (value.toFloat(&conversionResult) && conversionResult) + { + infoSelectWrapper.getVariant().setType(ESM::VT_Float); + infoSelectWrapper.getVariant().setFloat(value.toFloat()); + } break; } - case ESM::VT_Int: - case ESM::VT_Short: - case ESM::VT_Long: + case ConstInfoSelectWrapper::Comparison_Boolean: + case ConstInfoSelectWrapper::Comparison_Integer: { - conditions[subRowIndex].mValue.setInteger (value.toInt()); - break; - } - case ESM::VT_Float: - { - conditions[subRowIndex].mValue.setFloat (value.toFloat()); + if ((value.toInt(&conversionResult) && conversionResult) || value.toString().compare("0") == 0) + { + infoSelectWrapper.getVariant().setType(ESM::VT_Int); + infoSelectWrapper.getVariant().setInteger(value.toInt()); + } break; } default: break; diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index 039624c84..7885bf595 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -108,7 +108,7 @@ void CSMWorld::IngredEffectRefIdAdapter::setNestedTable (const RefIdColumn* colu ESM::Ingredient ingredient = record.get(); ingredient.mData = - static_cast >&>(nestedTable).mNestedTable.at(0); + static_cast >&>(nestedTable).mNestedTable.at(0); record.setModified (ingredient); } @@ -120,11 +120,11 @@ CSMWorld::NestedTableWrapperBase* CSMWorld::IngredEffectRefIdAdapter::nestedTabl static_cast&> (data.getRecord (RefIdData::LocalIndex (index, mType))); // return the whole struct - std::vector wrap; + std::vector wrap; wrap.push_back(record.get().mData); // deleted by dtor of NestedTableStoring - return new NestedTableWrapper >(wrap); + return new NestedTableWrapper >(wrap); } QVariant CSMWorld::IngredEffectRefIdAdapter::getNestedData (const RefIdColumn *column, @@ -1129,7 +1129,7 @@ void CSMWorld::CreatureAttributesRefIdAdapter::setNestedTable (const RefIdColumn // store the whole struct creature.mData = - static_cast > &>(nestedTable).mNestedTable.at(0); + static_cast > &>(nestedTable).mNestedTable.at(0); record.setModified (creature); } @@ -1141,10 +1141,10 @@ CSMWorld::NestedTableWrapperBase* CSMWorld::CreatureAttributesRefIdAdapter::nest static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); // return the whole struct - std::vector wrap; + std::vector wrap; wrap.push_back(record.get().mData); // deleted by dtor of NestedTableStoring - return new NestedTableWrapper >(wrap); + return new NestedTableWrapper >(wrap); } QVariant CSMWorld::CreatureAttributesRefIdAdapter::getNestedData (const RefIdColumn *column, @@ -1235,7 +1235,7 @@ void CSMWorld::CreatureAttackRefIdAdapter::setNestedTable (const RefIdColumn* co // store the whole struct creature.mData = - static_cast > &>(nestedTable).mNestedTable.at(0); + static_cast > &>(nestedTable).mNestedTable.at(0); record.setModified (creature); } @@ -1247,10 +1247,10 @@ CSMWorld::NestedTableWrapperBase* CSMWorld::CreatureAttackRefIdAdapter::nestedTa static_cast&> (data.getRecord (RefIdData::LocalIndex (index, UniversalId::Type_Creature))); // return the whole struct - std::vector wrap; + std::vector wrap; wrap.push_back(record.get().mData); // deleted by dtor of NestedTableStoring - return new NestedTableWrapper >(wrap); + return new NestedTableWrapper >(wrap); } QVariant CSMWorld::CreatureAttackRefIdAdapter::getNestedData (const RefIdColumn *column, diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index d666304fa..f82ef3150 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include "../../model/world/idtable.hpp" @@ -308,12 +309,19 @@ void CSVRender::Cell::setCellArrows (int mask) void CSVRender::Cell::setCellMarker() { bool cellExists = false; + bool isInteriorCell = false; + int cellIndex = mData.getCells().searchId(mId); if (cellIndex > -1) { - cellExists = !mData.getCells().getRecord(cellIndex).isDeleted(); + const CSMWorld::Record& cellRecord = mData.getCells().getRecord(cellIndex); + cellExists = !cellRecord.isDeleted(); + isInteriorCell = cellRecord.get().mData.mFlags & ESM::Cell::Interior; + } + + if (!isInteriorCell) { + mCellMarker.reset(new CellMarker(mCellNode, mCoordinates, cellExists)); } - mCellMarker.reset(new CellMarker(mCellNode, mCoordinates, cellExists)); } CSMWorld::CellCoordinates CSVRender::Cell::getCoordinates() const diff --git a/apps/opencs/view/world/idcompletiondelegate.cpp b/apps/opencs/view/world/idcompletiondelegate.cpp index 970490828..7f0f4ae46 100644 --- a/apps/opencs/view/world/idcompletiondelegate.cpp +++ b/apps/opencs/view/world/idcompletiondelegate.cpp @@ -1,6 +1,7 @@ #include "idcompletiondelegate.hpp" #include "../../model/world/idcompletionmanager.hpp" +#include "../../model/world/infoselectwrapper.hpp" #include "../widget/droplineedit.hpp" @@ -27,6 +28,56 @@ QWidget *CSVWorld::IdCompletionDelegate::createEditor(QWidget *parent, return NULL; } + // The completer for InfoCondVar needs to return a completer based on the first column + if (display == CSMWorld::ColumnBase::Display_InfoCondVar) + { + QModelIndex sibling = index.sibling(index.row(), 0); + int conditionFunction = sibling.model()->data(sibling, Qt::EditRole).toInt(); + + switch (conditionFunction) + { + case CSMWorld::ConstInfoSelectWrapper::Function_Global: + { + return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_GlobalVariable); + } + case CSMWorld::ConstInfoSelectWrapper::Function_Journal: + { + return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Journal); + } + case CSMWorld::ConstInfoSelectWrapper::Function_Item: + { + return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Referenceable); + } + case CSMWorld::ConstInfoSelectWrapper::Function_Dead: + case CSMWorld::ConstInfoSelectWrapper::Function_NotId: + { + return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Referenceable); + } + case CSMWorld::ConstInfoSelectWrapper::Function_NotFaction: + { + return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Faction); + } + case CSMWorld::ConstInfoSelectWrapper::Function_NotClass: + { + return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Class); + } + case CSMWorld::ConstInfoSelectWrapper::Function_NotRace: + { + return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Race); + } + case CSMWorld::ConstInfoSelectWrapper::Function_NotCell: + { + return createEditor (parent, option, index, CSMWorld::ColumnBase::Display_Cell); + } + case CSMWorld::ConstInfoSelectWrapper::Function_Local: + case CSMWorld::ConstInfoSelectWrapper::Function_NotLocal: + { + return new CSVWidget::DropLineEdit(display, parent); + } + default: return 0; // The rest of them can't be edited anyway + } + } + CSMWorld::IdCompletionManager &completionManager = getDocument().getIdCompletionManager(); CSVWidget::DropLineEdit *editor = new CSVWidget::DropLineEdit(display, parent); editor->setCompleter(completionManager.getCompleter(display).get()); diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index b3a58d18e..62457cae6 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -76,8 +76,6 @@ void OMW::Engine::executeLocalScripts() &script.second.getRefData().getLocals(), script.second); mEnvironment.getScriptManager()->run (script.first, interpreterContext); } - - localScripts.setIgnore (MWWorld::Ptr()); } void OMW::Engine::frame(float frametime) @@ -453,8 +451,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) Settings::Manager::getString("texture mag filter", "General"), Settings::Manager::getString("texture min filter", "General"), Settings::Manager::getString("texture mipmap", "General"), - Settings::Manager::getInt("anisotropy", "General"), - NULL + Settings::Manager::getInt("anisotropy", "General") ); // Create input and UI first to set up a bootstrapping environment for diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 52697d670..de5f15d64 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -231,15 +231,16 @@ namespace MWBase virtual float getTimeScaleFactor() const = 0; - virtual void changeToInteriorCell (const std::string& cellName, - const ESM::Position& position) = 0; + virtual void changeToInteriorCell (const std::string& cellName, const ESM::Position& position, bool changeEvent=true) = 0; ///< Move to interior cell. + ///< @param changeEvent If false, do not trigger cell change flag or detect worldspace changes - virtual void changeToExteriorCell (const ESM::Position& position) = 0; + virtual void changeToExteriorCell (const ESM::Position& position, bool changeEvent=true) = 0; ///< Move to exterior cell. + ///< @param changeEvent If false, do not trigger cell change flag or detect worldspace changes - virtual void changeToCell (const ESM::CellId& cellId, const ESM::Position& position, bool detectWorldSpaceChange=true) = 0; - ///< @param detectWorldSpaceChange if true, clean up worldspace-specific data when the world space changes + virtual void changeToCell (const ESM::CellId& cellId, const ESM::Position& position, bool changeEvent=true) = 0; + ///< @param changeEvent If false, do not trigger cell change flag or detect worldspace changes virtual const ESM::Cell *getExterior (const std::string& cellName) const = 0; ///< Return a cell matching the given name or a 0-pointer, if there is no such cell. @@ -277,8 +278,12 @@ namespace MWBase virtual void rotateObject(const MWWorld::Ptr& ptr,float x,float y,float z, bool adjust = false) = 0; - virtual MWWorld::Ptr safePlaceObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, ESM::Position pos) = 0; - ///< place an object in a "safe" location (ie not in the void, etc). + virtual MWWorld::Ptr placeObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, ESM::Position pos) = 0; + ///< Place an object. Makes a copy of the Ptr. + + virtual MWWorld::Ptr safePlaceObject (const MWWorld::ConstPtr& ptr, const MWWorld::ConstPtr& referenceObject, MWWorld::CellStore* referenceCell, int direction, float distance) = 0; + ///< Place an object in a safe place next to \a referenceObject. \a direction and \a distance specify the wanted placement + /// relative to \a referenceObject (but the object may be placed somewhere else if the wanted location is obstructed). virtual void indexToPosition (int cellX, int cellY, float &x, float &y, bool centre = false) const = 0; diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 0f021b5a2..182177d52 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -306,18 +306,7 @@ namespace MWClass } // Apply "On hit" enchanted weapons - std::string enchantmentName = !weapon.isEmpty() ? weapon.getClass().getEnchantment(weapon) : ""; - if (!enchantmentName.empty()) - { - const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find( - enchantmentName); - if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes) - { - MWMechanics::CastSpell cast(ptr, victim); - cast.mHitPosition = hitPosition; - cast.cast(weapon); - } - } + MWMechanics::applyOnStrikeEnchantment(ptr, victim, weapon, hitPosition); } else if (isBipedal(ptr)) { @@ -770,7 +759,18 @@ namespace MWClass void Creature::respawn(const MWWorld::Ptr &ptr) const { - if (isFlagBitSet(ptr, ESM::Creature::Respawn)) + const MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); + if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead()) + return; + + const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); + static const float fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->getFloat(); + static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->getFloat(); + + float delay = ptr.getRefData().getCount() == 0 ? fCorpseClearDelay : std::min(fCorpseRespawnDelay, fCorpseClearDelay); + + if (isFlagBitSet(ptr, ESM::Creature::Respawn) + && creatureStats.getTimeOfDeath() + delay <= MWBase::Environment::get().getWorld()->getTimeStamp()) { if (ptr.getCellRef().hasContentFile()) { diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp index e2f29ea72..e0e890b60 100644 --- a/apps/openmw/mwclass/creaturelevlist.cpp +++ b/apps/openmw/mwclass/creaturelevlist.cpp @@ -44,7 +44,28 @@ namespace MWClass ensureCustomData(ptr); CreatureLevListCustomData& customData = ptr.getRefData().getCustomData()->asCreatureLevListCustomData(); - customData.mSpawn = true; + if (customData.mSpawn) + return; + + MWWorld::Ptr creature = (customData.mSpawnActorId == -1) ? MWWorld::Ptr() : MWBase::Environment::get().getWorld()->searchPtrViaActorId(customData.mSpawnActorId); + if (!creature.isEmpty()) + { + const MWMechanics::CreatureStats& creatureStats = creature.getClass().getCreatureStats(creature); + if (creature.getRefData().getCount() == 0) + customData.mSpawn = true; + else if (creatureStats.isDead()) + { + const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); + static const float fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->getFloat(); + static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->getFloat(); + + float delay = std::min(fCorpseRespawnDelay, fCorpseClearDelay); + if (creatureStats.getTimeOfDeath() + delay <= MWBase::Environment::get().getWorld()->getTimeStamp()) + customData.mSpawn = true; + } + } + else + customData.mSpawn = true; } void CreatureLevList::registerSelf() @@ -56,8 +77,9 @@ namespace MWClass void CreatureLevList::getModelsToPreload(const MWWorld::Ptr &ptr, std::vector &models) const { + // disable for now, too many false positives + /* const MWWorld::LiveCellRef *ref = ptr.get(); - for (std::vector::const_iterator it = ref->mBase->mList.begin(); it != ref->mBase->mList.end(); ++it) { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); @@ -68,6 +90,7 @@ namespace MWClass MWWorld::ManualRef ref(store, it->mId); ref.getPtr().getClass().getModelsToPreload(ref.getPtr(), models); } + */ } void CreatureLevList::insertObjectRendering(const MWWorld::Ptr &ptr, const std::string& model, MWRender::RenderingInterface &renderingInterface) const @@ -97,7 +120,7 @@ namespace MWClass const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); MWWorld::ManualRef ref(store, id); ref.getPtr().getCellRef().setPosition(ptr.getCellRef().getPosition()); - MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(), ptr.getCell() , ptr.getCellRef().getPosition()); + MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->placeObject(ref.getPtr(), ptr.getCell() , ptr.getCellRef().getPosition()); customData.mSpawnActorId = placed.getClass().getCreatureStats(placed).getActorId(); customData.mSpawn = false; } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 474985f7b..ee0112ac9 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -637,18 +637,7 @@ namespace MWClass damage *= store.find("fCombatKODamageMult")->getFloat(); // Apply "On hit" enchanted weapons - std::string enchantmentName = !weapon.isEmpty() ? weapon.getClass().getEnchantment(weapon) : ""; - if (!enchantmentName.empty()) - { - const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find( - enchantmentName); - if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes) - { - MWMechanics::CastSpell cast(ptr, victim); - cast.mHitPosition = hitPosition; - cast.cast(weapon); - } - } + MWMechanics::applyOnStrikeEnchantment(ptr, victim, weapon, hitPosition); MWMechanics::applyElementalShields(ptr, victim); @@ -1303,7 +1292,18 @@ namespace MWClass void Npc::respawn(const MWWorld::Ptr &ptr) const { - if (ptr.get()->mBase->mFlags & ESM::NPC::Respawn) + const MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); + if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead()) + return; + + const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); + static const float fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->getFloat(); + static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->getFloat(); + + float delay = ptr.getRefData().getCount() == 0 ? fCorpseClearDelay : std::min(fCorpseRespawnDelay, fCorpseClearDelay); + + if (ptr.get()->mBase->mFlags & ESM::NPC::Respawn + && creatureStats.getTimeOfDeath() + delay <= MWBase::Environment::get().getWorld()->getTimeStamp()) { if (ptr.getCellRef().hasContentFile()) { diff --git a/apps/openmw/mwdialogue/keywordsearch.hpp b/apps/openmw/mwdialogue/keywordsearch.hpp index 3532dc22b..f296f223f 100644 --- a/apps/openmw/mwdialogue/keywordsearch.hpp +++ b/apps/openmw/mwdialogue/keywordsearch.hpp @@ -158,7 +158,7 @@ public: } // resolve overlapping keywords - while (matches.size()) + while (!matches.empty()) { int longestKeywordSize = 0; typename std::vector::iterator longestKeyword = matches.begin(); diff --git a/apps/openmw/mwgui/alchemywindow.cpp b/apps/openmw/mwgui/alchemywindow.cpp index 3299846b7..61a0efc46 100644 --- a/apps/openmw/mwgui/alchemywindow.cpp +++ b/apps/openmw/mwgui/alchemywindow.cpp @@ -67,46 +67,29 @@ namespace MWGui { MWMechanics::Alchemy::Result result = mAlchemy->create (mNameEdit->getCaption ()); - if (result == MWMechanics::Alchemy::Result_NoName) + switch (result) { + case MWMechanics::Alchemy::Result_NoName: MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage37}"); - return; - } - - // check if mortar & pestle is available (always needed) - if (result == MWMechanics::Alchemy::Result_NoMortarAndPestle) - { + break; + case MWMechanics::Alchemy::Result_NoMortarAndPestle: MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage45}"); - return; - } - - // make sure 2 or more ingredients were selected - if (result == MWMechanics::Alchemy::Result_LessThanTwoIngredients) - { + break; + case MWMechanics::Alchemy::Result_LessThanTwoIngredients: MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage6a}"); - return; - } - - if (result == MWMechanics::Alchemy::Result_NoEffects) - { - MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage8}"); - MWBase::Environment::get().getSoundManager()->playSound("potion fail", 1.f, 1.f); - return; - } - - if (result == MWMechanics::Alchemy::Result_Success) - { + break; + case MWMechanics::Alchemy::Result_Success: MWBase::Environment::get().getWindowManager()->messageBox("#{sPotionSuccess}"); MWBase::Environment::get().getSoundManager()->playSound("potion success", 1.f, 1.f); - } - else if (result == MWMechanics::Alchemy::Result_RandomFailure) - { - // potion failed + break; + case MWMechanics::Alchemy::Result_NoEffects: + case MWMechanics::Alchemy::Result_RandomFailure: MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage8}"); MWBase::Environment::get().getSoundManager()->playSound("potion fail", 1.f, 1.f); + break; } - // reduce count of the ingredients + // remove ingredient slots that have been fully used up for (int i=0; i<4; ++i) if (mIngredients[i]->isUserString("ToolTipType")) { diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp index 1777d86ad..aeff7dc39 100644 --- a/apps/openmw/mwgui/console.cpp +++ b/apps/openmw/mwgui/console.cpp @@ -240,7 +240,7 @@ namespace MWGui mCommandLine->setCaption(newCaption); // List candidates if repeatedly pressing tab - if (oldCaption == newCaption && matches.size()) + if (oldCaption == newCaption && !matches.empty()) { int i = 0; printOK(""); diff --git a/apps/openmw/mwgui/containeritemmodel.cpp b/apps/openmw/mwgui/containeritemmodel.cpp index b2befc3ba..1e4749695 100644 --- a/apps/openmw/mwgui/containeritemmodel.cpp +++ b/apps/openmw/mwgui/containeritemmodel.cpp @@ -37,7 +37,7 @@ ContainerItemModel::ContainerItemModel(const std::vector& itemSour : mItemSources(itemSources) , mWorldItems(worldItems) { - assert (mItemSources.size()); + assert (!mItemSources.empty()); } ContainerItemModel::ContainerItemModel (const MWWorld::Ptr& source) diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 86ecd9dfb..a0833194b 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -27,13 +27,6 @@ namespace { - std::string fpsLevelToStr(int level) - { - if (level == 0) - return "#{sOff}"; - else //if (level == 1) - return "#{sOn}"; - } std::string textureMipmappingToStr(const std::string& val) { @@ -182,13 +175,9 @@ namespace MWGui getWidget(mOkButton, "OkButton"); getWidget(mResolutionList, "ResolutionList"); getWidget(mFullscreenButton, "FullscreenButton"); - getWidget(mVSyncButton, "VSyncButton"); getWidget(mWindowBorderButton, "WindowBorderButton"); getWidget(mTextureFilteringButton, "TextureFilteringButton"); getWidget(mAnisotropyBox, "AnisotropyBox"); - getWidget(mShadersButton, "ShadersButton"); - getWidget(mShadowsEnabledButton, "ShadowsEnabledButton"); - getWidget(mShadowsTextureSize, "ShadowsTextureSize"); getWidget(mControlsBox, "ControlsBox"); getWidget(mResetControlsButton, "ResetControlsButton"); getWidget(mKeyboardSwitch, "KeyboardButton"); @@ -218,8 +207,6 @@ namespace MWGui mWaterTextureSize->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onWaterTextureSizeChanged); - mShadowsTextureSize->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onShadowTextureSizeChanged); - mKeyboardSwitch->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onKeyboardSwitchClicked); mControllerSwitch->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onControllerSwitchClicked); @@ -260,13 +247,6 @@ namespace MWGui if (waterTextureSize >= 2048) mWaterTextureSize->setIndexSelected(2); - mShadowsTextureSize->setCaption (Settings::Manager::getString ("texture size", "Shadows")); - - if (!Settings::Manager::getBool("shaders", "Objects")) - { - mShadowsEnabledButton->setEnabled(false); - } - mWindowBorderButton->setEnabled(!Settings::Manager::getBool("fullscreen", "Video")); mKeyboardSwitch->setStateSelected(true); @@ -346,12 +326,6 @@ namespace MWGui apply(); } - void SettingsWindow::onShadowTextureSizeChanged(MyGUI::ComboBox *_sender, size_t pos) - { - Settings::Manager::setString("texture size", "Shadows", _sender->getItemNameAt(pos)); - apply(); - } - void SettingsWindow::onButtonToggled(MyGUI::Widget* _sender) { std::string on = MWBase::Environment::get().getWindowManager()->getGameSettingString("sOn", "On"); @@ -368,21 +342,6 @@ namespace MWGui newState = true; } - if (_sender == mShadersButton) - { - if (newState == false) - { - // shadows not supported - mShadowsEnabledButton->setEnabled(false); - mShadowsEnabledButton->setCaptionWithReplacing("#{sOff}"); - Settings::Manager::setBool("enabled", "Shadows", false); - } - else - { - mShadowsEnabledButton->setEnabled(true); - } - } - if (_sender == mFullscreenButton) { // check if this resolution is supported in fullscreen diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index 421465309..5b12cc557 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -28,17 +28,12 @@ namespace MWGui // graphics MyGUI::ListBox* mResolutionList; MyGUI::Button* mFullscreenButton; - MyGUI::Button* mVSyncButton; MyGUI::Button* mWindowBorderButton; MyGUI::ComboBox* mTextureFilteringButton; MyGUI::Widget* mAnisotropyBox; - MyGUI::Button* mShadersButton; MyGUI::ComboBox* mWaterTextureSize; - MyGUI::Button* mShadowsEnabledButton; - MyGUI::ComboBox* mShadowsTextureSize; - // controls MyGUI::ScrollView* mControlsBox; MyGUI::Button* mResetControlsButton; @@ -58,8 +53,6 @@ namespace MWGui void onWaterTextureSizeChanged(MyGUI::ComboBox* _sender, size_t pos); - void onShadowTextureSizeChanged(MyGUI::ComboBox* _sender, size_t pos); - void onRebindAction(MyGUI::Widget* _sender); void onInputTabMouseWheel(MyGUI::Widget* _sender, int _rel); void onResetDefaultBindings(MyGUI::Widget* _sender); diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 70aab95df..4a689d964 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1377,7 +1377,7 @@ namespace MWMechanics { if ((*it)->getTypeId() == MWMechanics::AiPackage::TypeIdFollow) { - MWWorld::Ptr followTarget = static_cast(*it)->getTarget(); + MWWorld::Ptr followTarget = (*it)->getTarget(); if (followTarget.isEmpty()) continue; if (followTarget == actor) diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index fffab8d77..1fc6c52a4 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -119,7 +119,7 @@ namespace MWMechanics return TypeIdEscort; } - MWWorld::Ptr AiEscort::getTarget() + MWWorld::Ptr AiEscort::getTarget() const { return MWBase::Environment::get().getWorld()->getPtr(mActorId, false); } diff --git a/apps/openmw/mwmechanics/aiescort.hpp b/apps/openmw/mwmechanics/aiescort.hpp index cdb0f7936..677cf6f43 100644 --- a/apps/openmw/mwmechanics/aiescort.hpp +++ b/apps/openmw/mwmechanics/aiescort.hpp @@ -37,7 +37,7 @@ namespace MWMechanics virtual int getTypeId() const; - MWWorld::Ptr getTarget(); + MWWorld::Ptr getTarget() const; virtual bool sideWithTarget() const { return true; } void writeState(ESM::AiSequence::AiSequence &sequence) const; diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index 1430d42f2..02daec19e 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -200,7 +200,7 @@ void AiFollow::writeState(ESM::AiSequence::AiSequence &sequence) const sequence.mPackages.push_back(package); } -MWWorld::Ptr AiFollow::getTarget() +MWWorld::Ptr AiFollow::getTarget() const { if (mActorId == -2) return MWWorld::Ptr(); diff --git a/apps/openmw/mwmechanics/aifollow.hpp b/apps/openmw/mwmechanics/aifollow.hpp index 017ea7122..0f955879a 100644 --- a/apps/openmw/mwmechanics/aifollow.hpp +++ b/apps/openmw/mwmechanics/aifollow.hpp @@ -31,7 +31,7 @@ namespace MWMechanics AiFollow(const ESM::AiSequence::AiFollow* follow); - MWWorld::Ptr getTarget(); + MWWorld::Ptr getTarget() const; virtual bool sideWithTarget() const { return true; } virtual bool followTargetThroughDoors() const { return true; } @@ -60,7 +60,7 @@ namespace MWMechanics float mY; float mZ; std::string mActorRefId; - int mActorId; + mutable int mActorId; std::string mCellId; bool mActive; // have we spotted the target? int mFollowIndex; diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 58ba7dfe8..863fe05ef 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -20,7 +20,7 @@ MWMechanics::AiPackage::~AiPackage() {} -MWWorld::Ptr MWMechanics::AiPackage::getTarget() +MWWorld::Ptr MWMechanics::AiPackage::getTarget() const { return MWWorld::Ptr(); } diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 72bb4487c..f938a34a1 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -72,7 +72,7 @@ namespace MWMechanics virtual void fastForward(const MWWorld::Ptr& actor, AiState& state) {} /// Get the target actor the AI is targeted at (not applicable to all AI packages, default return empty Ptr) - virtual MWWorld::Ptr getTarget(); + virtual MWWorld::Ptr getTarget() const; /// Return true if having this AiPackage makes the actor side with the target in fights (default false) virtual bool sideWithTarget() const; diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 71733d613..5cfc1264a 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -68,9 +68,8 @@ bool AiSequence::getCombatTarget(MWWorld::Ptr &targetActor) const { if (getTypeId() != AiPackage::TypeIdCombat) return false; - const AiCombat *combat = static_cast(mPackages.front()); - targetActor = combat->getTarget(); + targetActor = mPackages.front()->getTarget(); return !targetActor.isEmpty(); } @@ -114,8 +113,7 @@ bool AiSequence::isInCombat(const MWWorld::Ptr &actor) const { if ((*it)->getTypeId() == AiPackage::TypeIdCombat) { - const AiCombat *combat = static_cast(*it); - if (combat->getTarget() == actor) + if ((*it)->getTarget() == actor) return true; } } @@ -255,7 +253,7 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor) for (std::list::const_iterator iter (mPackages.begin()); iter!=mPackages.end(); ++iter) { if((*iter)->getTypeId() == AiPackage::TypeIdCombat && package.getTypeId() == AiPackage::TypeIdCombat - && static_cast(*iter)->getTarget() == static_cast(&package)->getTarget()) + && (*iter)->getTarget() == (&package)->getTarget()) { return; // already in combat with this actor } diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 8231e572e..27c7f6a93 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -255,7 +255,7 @@ namespace MWMechanics // Construct a new path if there isn't one if(!storage.mPathFinder.isPathConstructed()) { - if (mAllowedNodes.size()) + if (!mAllowedNodes.empty()) { setPathToAnAllowedNode(actor, storage, pos); } diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 67707d028..0035ebb1a 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2140,10 +2140,10 @@ void CharacterController::updateHeadTracking(float duration) if (!mHeadTrackTarget.isEmpty()) { - osg::MatrixList mats = head->getWorldMatrices(); - if (mats.empty()) + osg::NodePathList nodepaths = head->getParentalNodePaths(); + if (nodepaths.empty()) return; - osg::Matrixf mat = mats[0]; + osg::Matrixf mat = osg::computeLocalToWorld(nodepaths[0]); osg::Vec3f headPos = mat.getTrans(); osg::Vec3f direction; @@ -2154,9 +2154,9 @@ void CharacterController::updateHeadTracking(float duration) node = anim->getNode("Bip01 Head"); if (node != NULL) { - osg::MatrixList mats = node->getWorldMatrices(); - if (mats.size()) - direction = mats[0].getTrans() - headPos; + osg::NodePathList nodepaths = node->getParentalNodePaths(); + if (!nodepaths.empty()) + direction = osg::computeLocalToWorld(nodepaths[0]).getTrans() - headPos; } else // no head node to look at, fall back to look at center of collision box diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index a01dc7079..8d9d4cb3a 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -29,29 +29,29 @@ float signedAngleRadians (const osg::Vec3f& v1, const osg::Vec3f& v2, const osg: return std::atan2((normal * (v1 ^ v2)), (v1 * v2)); } -bool applyEnchantment (const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, const MWWorld::Ptr& object, const osg::Vec3f& hitPosition) -{ - std::string enchantmentName = !object.isEmpty() ? object.getClass().getEnchantment(object) : ""; - if (!enchantmentName.empty()) - { - const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find( - enchantmentName); - if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes) - { - MWMechanics::CastSpell cast(attacker, victim); - cast.mHitPosition = hitPosition; - cast.cast(object); - return true; - } - } - return false; -} - } namespace MWMechanics { + bool applyOnStrikeEnchantment (const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, const MWWorld::Ptr& object, const osg::Vec3f& hitPosition) + { + std::string enchantmentName = !object.isEmpty() ? object.getClass().getEnchantment(object) : ""; + if (!enchantmentName.empty()) + { + const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find( + enchantmentName); + if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes) + { + MWMechanics::CastSpell cast(attacker, victim); + cast.mHitPosition = hitPosition; + cast.cast(object, false); + return true; + } + } + return false; + } + bool blockMeleeAttack(const MWWorld::Ptr &attacker, const MWWorld::Ptr &blocker, const MWWorld::Ptr &weapon, float damage, float attackStrength) { if (!blocker.getClass().hasInventoryStore(blocker)) @@ -215,9 +215,9 @@ namespace MWMechanics damage *= gmst.find("fCombatKODamageMult")->getFloat(); // Apply "On hit" effect of the weapon - bool appliedEnchantment = applyEnchantment(attacker, victim, weapon, hitPosition); + bool appliedEnchantment = applyOnStrikeEnchantment(attacker, victim, weapon, hitPosition); if (weapon != projectile) - appliedEnchantment = applyEnchantment(attacker, victim, projectile, hitPosition); + appliedEnchantment = applyOnStrikeEnchantment(attacker, victim, projectile, hitPosition); if (damage > 0) MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition); diff --git a/apps/openmw/mwmechanics/combat.hpp b/apps/openmw/mwmechanics/combat.hpp index ca78d7956..7d0b3b78f 100644 --- a/apps/openmw/mwmechanics/combat.hpp +++ b/apps/openmw/mwmechanics/combat.hpp @@ -6,6 +6,8 @@ namespace MWMechanics { +bool applyOnStrikeEnchantment(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, const MWWorld::Ptr& object, const osg::Vec3f& hitPosition); + /// @return can we block the attack? bool blockMeleeAttack (const MWWorld::Ptr& attacker, const MWWorld::Ptr& blocker, const MWWorld::Ptr& weapon, float damage, float attackStrength); diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 6933c40a3..13396e3fa 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -22,7 +22,7 @@ namespace MWMechanics mKnockdown(false), mKnockdownOneFrame(false), mKnockdownOverOneFrame(false), mHitRecovery(false), mBlock(false), mMovementFlags(0), mFallHeight(0), mRecalcMagicka(false), mLastRestock(0,0), mGoldPool(0), mActorId(-1), - mDeathAnimation(0), mLevel (0) + mDeathAnimation(0), mTimeOfDeath(), mLevel (0) { for (int i=0; i<4; ++i) mAiSettings[i] = 0; @@ -187,6 +187,9 @@ namespace MWMechanics if (index==0 && mDynamic[index].getCurrent()<1) { + if (!mDead) + mTimeOfDeath = MWBase::Environment::get().getWorld()->getTimeStamp(); + mDead = true; mDynamic[index].setModifier(0); @@ -503,6 +506,7 @@ namespace MWMechanics state.mLevel = mLevel; state.mActorId = mActorId; state.mDeathAnimation = mDeathAnimation; + state.mTimeOfDeath = mTimeOfDeath.toEsm(); mSpells.writeState(state.mSpells); mActiveSpells.writeState(state.mActiveSpells); @@ -549,6 +553,7 @@ namespace MWMechanics mLevel = state.mLevel; mActorId = state.mActorId; mDeathAnimation = state.mDeathAnimation; + mTimeOfDeath = MWWorld::TimeStamp(state.mTimeOfDeath); mSpells.readState(state.mSpells); mActiveSpells.readState(state.mActiveSpells); @@ -622,6 +627,11 @@ namespace MWMechanics mDeathAnimation = index; } + MWWorld::TimeStamp CreatureStats::getTimeOfDeath() const + { + return mTimeOfDeath; + } + std::map& CreatureStats::getSummonedCreatureMap() { return mSummonedCreatures; diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index 46c5bab31..734e87319 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -65,6 +65,8 @@ namespace MWMechanics // The index of the death animation that was played unsigned char mDeathAnimation; + MWWorld::TimeStamp mTimeOfDeath; + public: typedef std::pair SummonKey; // private: @@ -259,6 +261,8 @@ namespace MWMechanics unsigned char getDeathAnimation() const; void setDeathAnimation(unsigned char index); + MWWorld::TimeStamp getTimeOfDeath() const; + int getActorId(); ///< Will generate an actor ID, if the actor does not have one yet. diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 84da270eb..7fbc3a853 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1330,7 +1330,7 @@ namespace MWMechanics { if ((*it)->getTypeId() == AiPackage::TypeIdCombat) { - MWWorld::Ptr target = static_cast(*it)->getTarget(); + MWWorld::Ptr target = (*it)->getTarget(); if (!target.isEmpty() && target.getClass().isNpc()) isFightingNpc = true; } diff --git a/apps/openmw/mwmechanics/obstacle.cpp b/apps/openmw/mwmechanics/obstacle.cpp index 5815d8cbe..11bbd1e31 100644 --- a/apps/openmw/mwmechanics/obstacle.cpp +++ b/apps/openmw/mwmechanics/obstacle.cpp @@ -120,14 +120,10 @@ namespace MWMechanics const MWWorld::Class& cls = actor.getClass(); ESM::Position pos = actor.getRefData().getPosition(); - // actors can move at most 60 fps (the physics framerate). - // the max() can be removed if we implement physics interpolation. - float movementDuration = std::max(1/60.f, duration); - if(mDistSameSpot == -1) mDistSameSpot = DIST_SAME_SPOT * cls.getSpeed(actor); - float distSameSpot = mDistSameSpot * movementDuration; + float distSameSpot = mDistSameSpot * duration; bool samePosition = (osg::Vec2f(pos.pos[0], pos.pos[1]) - osg::Vec2f(mPrevX, mPrevY)).length2() < distSameSpot * distSameSpot; diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 4c5138835..8882d14a8 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -62,13 +62,6 @@ namespace } } - void applyDynamicStatsEffect(int attribute, const MWWorld::Ptr& target, float magnitude) - { - MWMechanics::DynamicStat value = target.getClass().getCreatureStats(target).getDynamic(attribute); - value.setCurrent(value.getCurrent()+magnitude, attribute == 2); - target.getClass().getCreatureStats(target).setDynamic(attribute, value); - } - } namespace MWMechanics @@ -694,7 +687,7 @@ namespace MWMechanics throw std::runtime_error("ID type cannot be casted"); } - bool CastSpell::cast(const MWWorld::Ptr &item) + bool CastSpell::cast(const MWWorld::Ptr &item, bool launchProjectile) { std::string enchantmentName = item.getClass().getEnchantment(item); if (enchantmentName.empty()) @@ -761,15 +754,20 @@ namespace MWMechanics inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Touch); } - std::string projectileModel; - std::string sound; - float speed = 0; - getProjectileInfo(enchantment->mEffects, projectileModel, sound, speed); - if (!projectileModel.empty()) - MWBase::Environment::get().getWorld()->launchMagicBolt(projectileModel, sound, mId, speed, - false, enchantment->mEffects, mCaster, mSourceName, - // Not needed, enchantments can only be cast by actors - osg::Vec3f(1,0,0)); + if (launchProjectile) + { + std::string projectileModel; + std::string sound; + float speed = 0; + getProjectileInfo(enchantment->mEffects, projectileModel, sound, speed); + if (!projectileModel.empty()) + MWBase::Environment::get().getWorld()->launchMagicBolt(projectileModel, sound, mId, speed, + false, enchantment->mEffects, mCaster, mSourceName, + // Not needed, enchantments can only be cast by actors + osg::Vec3f(1,0,0)); + } + else if (!mTarget.isEmpty()) + inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Target); return true; } diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index 5b48bd4a8..5bc618058 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -81,7 +81,8 @@ namespace MWMechanics bool cast (const ESM::Spell* spell); /// @note mCaster must be an actor - bool cast (const MWWorld::Ptr& item); + /// @param launchProjectile If set to false, "on target" effects are directly applied instead of being launched as projectile originating from the caster. + bool cast (const MWWorld::Ptr& item, bool launchProjectile=true); /// @note mCaster must be an NPC bool cast (const ESM::Ingredient* ingredient); diff --git a/apps/openmw/mwmechanics/summoning.cpp b/apps/openmw/mwmechanics/summoning.cpp index 636e19907..cf7771485 100644 --- a/apps/openmw/mwmechanics/summoning.cpp +++ b/apps/openmw/mwmechanics/summoning.cpp @@ -114,31 +114,16 @@ namespace MWMechanics bool found = creatureMap.find(std::make_pair(it->first, it->second)) != creatureMap.end(); if (!found) { - ESM::Position ipos = mActor.getRefData().getPosition(); - osg::Vec3f pos(ipos.asVec3()); - - osg::Quat rot (-ipos.rot[2], osg::Vec3f(0,0,1)); - const float distance = 50; - pos = pos + (rot * osg::Vec3f(0,1,0)) * distance; - ipos.pos[0] = pos.x(); - ipos.pos[1] = pos.y(); - ipos.pos[2] = pos.z(); - ipos.rot[0] = 0; - ipos.rot[1] = 0; - ipos.rot[2] = 0; - const std::string& creatureGmst = summonMap[it->first]; std::string creatureID = MWBase::Environment::get().getWorld()->getStore().get().find(creatureGmst)->getString(); if (!creatureID.empty()) { - MWWorld::CellStore* store = mActor.getCell(); int creatureActorId = -1; try { MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), creatureID, 1); - ref.getPtr().getCellRef().setPosition(ipos); MWMechanics::CreatureStats& summonedCreatureStats = ref.getPtr().getClass().getCreatureStats(ref.getPtr()); @@ -147,7 +132,7 @@ namespace MWMechanics summonedCreatureStats.getAiSequence().stack(package, ref.getPtr()); creatureActorId = summonedCreatureStats.getActorId(); - MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,ipos); + MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(), mActor, mActor.getCell(), 0, 120.f); MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(placed); if (anim) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index abca3b266..7f7183b9e 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -52,8 +52,8 @@ namespace class GlowUpdater : public SceneUtil::StateSetUpdater { public: - GlowUpdater(osg::Vec4f color, const std::vector >& textures) - : mTexUnit(1) // FIXME: might not always be 1 + GlowUpdater(int texUnit, osg::Vec4f color, const std::vector >& textures) + : mTexUnit(texUnit) , mColor(color) , mTextures(textures) { @@ -1041,6 +1041,25 @@ namespace MWRender return mObjectRoot.get(); } + class FindLowestUnusedTexUnitVisitor : public osg::NodeVisitor + { + public: + FindLowestUnusedTexUnitVisitor() + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mLowestUnusedTexUnit(0) + { + } + + virtual void apply(osg::Node& node) + { + if (osg::StateSet* stateset = node.getStateSet()) + mLowestUnusedTexUnit = std::max(mLowestUnusedTexUnit, int(stateset->getTextureAttributeList().size())); + + traverse(node); + } + int mLowestUnusedTexUnit; + }; + void Animation::addGlow(osg::ref_ptr node, osg::Vec4f glowColor) { std::vector > textures; @@ -1055,14 +1074,29 @@ namespace MWRender osg::ref_ptr image = mResourceSystem->getImageManager()->getImage(stream.str()); osg::ref_ptr tex (new osg::Texture2D(image)); + tex->setName("envMap"); tex->setWrap(osg::Texture::WRAP_S, osg::Texture2D::REPEAT); tex->setWrap(osg::Texture::WRAP_T, osg::Texture2D::REPEAT); mResourceSystem->getSceneManager()->applyFilterSettings(tex); textures.push_back(tex); } - osg::ref_ptr glowupdater (new GlowUpdater(glowColor, textures)); + FindLowestUnusedTexUnitVisitor findLowestUnusedTexUnitVisitor; + node->accept(findLowestUnusedTexUnitVisitor); + int texUnit = findLowestUnusedTexUnitVisitor.mLowestUnusedTexUnit; + osg::ref_ptr glowupdater (new GlowUpdater(texUnit, glowColor, textures)); node->addUpdateCallback(glowupdater); + + // set a texture now so that the ShaderVisitor can find it + osg::ref_ptr writableStateSet = NULL; + if (!node->getStateSet()) + writableStateSet = node->getOrCreateStateSet(); + else + writableStateSet = osg::clone(node->getStateSet(), osg::CopyOp::SHALLOW_COPY); + writableStateSet->setTextureAttributeAndModes(texUnit, textures.front(), osg::StateAttribute::ON); + writableStateSet->addUniform(new osg::Uniform("envMapColor", glowColor)); + + mResourceSystem->getSceneManager()->recreateShaders(node); } // TODO: Should not be here @@ -1244,10 +1278,14 @@ namespace MWRender stateset->setAttributeAndModes(material, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); mObjectRoot->setStateSet(stateset); + + mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot); } else { mObjectRoot->setStateSet(NULL); + + mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot); } setRenderBin(); @@ -1315,9 +1353,24 @@ namespace MWRender if (found != getNodeMap().end()) { osg::MatrixTransform* node = found->second; - mHeadController = new RotateController(mObjectRoot.get()); - node->addUpdateCallback(mHeadController); - mActiveControllers.insert(std::make_pair(node, mHeadController)); + + bool foundKeyframeCtrl = false; + osg::Callback* cb = node->getUpdateCallback(); + while (cb) + { + if (dynamic_cast(cb)) + { + foundKeyframeCtrl = true; + break; + } + } + + if (foundKeyframeCtrl) + { + mHeadController = new RotateController(mObjectRoot.get()); + node->addUpdateCallback(mHeadController); + mActiveControllers.insert(std::make_pair(node, mHeadController)); + } } } } diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index 88934414f..5a3f2bea3 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -88,10 +88,10 @@ namespace MWRender const osg::Node* trackNode = mTrackingNode; if (!trackNode) return osg::Vec3d(); - osg::MatrixList mats = trackNode->getWorldMatrices(); - if (!mats.size()) + osg::NodePathList nodepaths = trackNode->getParentalNodePaths(); + if (nodepaths.empty()) return osg::Vec3d(); - const osg::Matrix& worldMat = mats[0]; + osg::Matrix worldMat = osg::computeLocalToWorld(nodepaths[0]); osg::Vec3d position = worldMat.getTrans(); if (!isFirstPerson()) diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 258854054..c858b57b7 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -2,6 +2,7 @@ #include +#include #include #include #include @@ -98,10 +99,16 @@ namespace MWRender osg::ref_ptr lightManager = new SceneUtil::LightManager; lightManager->setStartLight(1); - osg::ref_ptr stateset = new osg::StateSet; + osg::ref_ptr stateset = lightManager->getOrCreateStateSet(); stateset->setMode(GL_LIGHTING, osg::StateAttribute::ON); stateset->setMode(GL_NORMALIZE, osg::StateAttribute::ON); stateset->setMode(GL_CULL_FACE, osg::StateAttribute::ON); + // assign large value to effectively turn off fog + // shaders don't respect glDisable(GL_FOG) + osg::ref_ptr fog (new osg::Fog); + fog->setStart(10000000); + fog->setEnd(10000000); + stateset->setAttributeAndModes(fog, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); osg::ref_ptr lightmodel = new osg::LightModel; lightmodel->setAmbientIntensity(osg::Vec4(0.25, 0.25, 0.25, 1.0)); @@ -123,7 +130,6 @@ namespace MWRender lightSource->setStateSetModes(*stateset, osg::StateAttribute::ON); - lightManager->setStateSet(stateset); lightManager->addChild(lightSource); mCamera->addChild(lightManager); @@ -359,10 +365,10 @@ namespace MWRender traverse(node, nv); // Now update camera utilizing the updated head position - osg::MatrixList mats = mNodeToFollow->getWorldMatrices(); - if (!mats.size()) + osg::NodePathList nodepaths = mNodeToFollow->getParentalNodePaths(); + if (nodepaths.empty()) return; - osg::Matrix worldMat = mats[0]; + osg::Matrix worldMat = osg::computeLocalToWorld(nodepaths[0]); osg::Vec3 headOffset = worldMat.getTrans(); cam->setViewMatrixAsLookAt(headOffset + mPosOffset, headOffset + mLookAtOffset, osg::Vec3(0,0,1)); diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index 9e8a545f8..b3b6cc3b0 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -402,7 +402,7 @@ namespace MWRender || bounds.mMinY > bounds.mMaxY) throw std::runtime_error("invalid map bounds"); - if (!map.mImageData.size()) + if (map.mImageData.empty()) return; Files::IMemStream istream(&map.mImageData[0], map.mImageData.size()); diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index fd224ba41..8340ab78a 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -180,7 +180,12 @@ osg::ref_ptr LocalMap::createOrthographicCamera(float x, float y, f stateset->setMode(GL_LIGHTING, osg::StateAttribute::ON); stateset->setMode(GL_NORMALIZE, osg::StateAttribute::ON); stateset->setMode(GL_CULL_FACE, osg::StateAttribute::ON); - stateset->setMode(GL_FOG, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); + // assign large value to effectively turn off fog + // shaders don't respect glDisable(GL_FOG) + osg::ref_ptr fog (new osg::Fog); + fog->setStart(10000000); + fog->setEnd(10000000); + stateset->setAttributeAndModes(fog, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); osg::ref_ptr lightmodel = new osg::LightModel; lightmodel->setAmbientIntensity(osg::Vec4(0.3f, 0.3f, 0.3f, 1.f)); @@ -627,7 +632,7 @@ void LocalMap::MapSegment::initFogOfWar() void LocalMap::MapSegment::loadFogOfWar(const ESM::FogTexture &esm) { const std::vector& data = esm.mImageData; - if (!data.size()) + if (data.empty()) { initFogOfWar(); return; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 29b641f6b..57f784b2c 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -168,6 +168,14 @@ namespace MWRender , mFieldOfViewOverridden(false) { resourceSystem->getSceneManager()->setParticleSystemMask(MWRender::Mask_ParticleSystem); + resourceSystem->getSceneManager()->setShaderPath(resourcePath + "/shaders"); + resourceSystem->getSceneManager()->setForceShaders(Settings::Manager::getBool("force shaders", "Shaders")); + resourceSystem->getSceneManager()->setClampLighting(Settings::Manager::getBool("clamp lighting", "Shaders")); + resourceSystem->getSceneManager()->setForcePerPixelLighting(Settings::Manager::getBool("force per pixel lighting", "Shaders")); + resourceSystem->getSceneManager()->setAutoUseNormalMaps(Settings::Manager::getBool("auto use object normal maps", "Shaders")); + resourceSystem->getSceneManager()->setNormalMapPattern(Settings::Manager::getString("normal map pattern", "Shaders")); + resourceSystem->getSceneManager()->setAutoUseSpecularMaps(Settings::Manager::getBool("auto use object specular maps", "Shaders")); + resourceSystem->getSceneManager()->setSpecularMapPattern(Settings::Manager::getString("specular map pattern", "Shaders")); osg::ref_ptr sceneRoot = new SceneUtil::LightManager; sceneRoot->setLightingMask(Mask_Lighting); @@ -189,7 +197,9 @@ namespace MWRender mWater.reset(new Water(mRootNode, sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), fallback, resourcePath)); mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), - new TerrainStorage(mResourceSystem->getVFS(), false), Mask_Terrain, mUnrefQueue.get())); + new TerrainStorage(mResourceSystem->getVFS(), Settings::Manager::getString("normal map pattern", "Shaders"), Settings::Manager::getBool("auto use terrain normal maps", "Shaders"), + Settings::Manager::getString("terrain specular map pattern", "Shaders"), Settings::Manager::getBool("auto use terrain specular maps", "Shaders")), + Mask_Terrain, &mResourceSystem->getSceneManager()->getShaderManager(), mUnrefQueue.get())); mCamera.reset(new Camera(mViewer->getCamera())); @@ -333,7 +343,9 @@ namespace MWRender { setAmbientColour(SceneUtil::colourFromRGB(cell->mAmbi.mAmbient)); - mSunLight->setDiffuse(SceneUtil::colourFromRGB(cell->mAmbi.mSunlight)); + osg::Vec4f diffuse = SceneUtil::colourFromRGB(cell->mAmbi.mSunlight); + mSunLight->setDiffuse(diffuse); + mSunLight->setSpecular(diffuse); mSunLight->setDirection(osg::Vec3f(1.f,-1.f,-1.f)); } @@ -836,16 +848,18 @@ namespace MWRender void RenderingManager::updateTextureFiltering() { - if (mTerrain.get()) - mTerrain->updateCache(); + mViewer->stopThreading(); mResourceSystem->getSceneManager()->setFilterSettings( Settings::Manager::getString("texture mag filter", "General"), Settings::Manager::getString("texture min filter", "General"), Settings::Manager::getString("texture mipmap", "General"), - Settings::Manager::getInt("anisotropy", "General"), - mViewer + Settings::Manager::getInt("anisotropy", "General") ); + + mTerrain->updateTextureFiltering(); + + mViewer->startThreading(); } void RenderingManager::updateAmbient() diff --git a/apps/openmw/mwrender/rotatecontroller.cpp b/apps/openmw/mwrender/rotatecontroller.cpp index 11f5b943d..534cc7490 100644 --- a/apps/openmw/mwrender/rotatecontroller.cpp +++ b/apps/openmw/mwrender/rotatecontroller.cpp @@ -44,11 +44,11 @@ void RotateController::operator()(osg::Node *node, osg::NodeVisitor *nv) osg::Quat RotateController::getWorldOrientation(osg::Node *node) { // this could be optimized later, we just need the world orientation, not the full matrix - osg::MatrixList worldMats = node->getWorldMatrices(mRelativeTo); + osg::NodePathList nodepaths = node->getParentalNodePaths(mRelativeTo); osg::Quat worldOrient; - if (!worldMats.empty()) + if (!nodepaths.empty()) { - osg::Matrixf worldMat = worldMats[0]; + osg::Matrixf worldMat = osg::computeLocalToWorld(nodepaths[0]); worldOrient = worldMat.getRotate(); } return worldOrient; diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index f10beca6c..95204d5eb 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -1125,6 +1125,9 @@ SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneMana , mSunEnabled(true) { osg::ref_ptr skyroot (new CameraRelativeTransform); + // Assign empty program to specify we don't want shaders + // The shaders generated by the SceneManager can't handle everything we need + skyroot->getOrCreateStateSet()->setAttributeAndModes(new osg::Program(), osg::StateAttribute::OVERRIDE); skyroot->setNodeMask(Mask_Sky); parentNode->addChild(skyroot); diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp index 02947b5dd..cc1c3a0bc 100644 --- a/apps/openmw/mwrender/terrainstorage.cpp +++ b/apps/openmw/mwrender/terrainstorage.cpp @@ -9,21 +9,9 @@ namespace MWRender { - TerrainStorage::TerrainStorage(const VFS::Manager* vfs, bool preload) - : ESMTerrain::Storage(vfs) + TerrainStorage::TerrainStorage(const VFS::Manager* vfs, const std::string& normalMapPattern, bool autoUseNormalMaps, const std::string& specularMapPattern, bool autoUseSpecularMaps) + : ESMTerrain::Storage(vfs, normalMapPattern, autoUseNormalMaps, specularMapPattern, autoUseSpecularMaps) { - if (preload) - { - const MWWorld::ESMStore &esmStore = - MWBase::Environment::get().getWorld()->getStore(); - - MWWorld::Store::iterator it = esmStore.get().begin(); - for (; it != esmStore.get().end(); ++it) - { - const ESM::Land* land = &*it; - land->loadData(ESM::Land::DATA_VCLR|ESM::Land::DATA_VHGT|ESM::Land::DATA_VNML|ESM::Land::DATA_VTEX); - } - } } void TerrainStorage::getBounds(float& minX, float& maxX, float& minY, float& maxY) diff --git a/apps/openmw/mwrender/terrainstorage.hpp b/apps/openmw/mwrender/terrainstorage.hpp index a12ffd540..759c7dfcf 100644 --- a/apps/openmw/mwrender/terrainstorage.hpp +++ b/apps/openmw/mwrender/terrainstorage.hpp @@ -14,9 +14,7 @@ namespace MWRender virtual const ESM::LandTexture* getLandTexture(int index, short plugin); public: - ///@param preload Preload all Land records at startup? If using the multithreaded terrain component, this - /// should be set to "true" in order to avoid race conditions. - TerrainStorage(const VFS::Manager* vfs, bool preload); + TerrainStorage(const VFS::Manager* vfs, const std::string& normalMapPattern = "", bool autoUseNormalMaps = false, const std::string& specularMapPattern = "", bool autoUseSpecularMaps = false); /// Get bounds of the whole terrain in cell units virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY); diff --git a/apps/openmw/mwrender/util.cpp b/apps/openmw/mwrender/util.cpp index 0e1eaf0f4..876ec285f 100644 --- a/apps/openmw/mwrender/util.cpp +++ b/apps/openmw/mwrender/util.cpp @@ -18,6 +18,7 @@ void overrideTexture(const std::string &texture, Resource::ResourceSystem *resou osg::ref_ptr tex = new osg::Texture2D(resourceSystem->getImageManager()->getImage(correctedTexture)); tex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); tex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + tex->setName("diffuseMap"); osg::ref_ptr stateset; if (node->getStateSet()) diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index dba85aeb7..340c07785 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -9,13 +9,11 @@ #include #include #include -#include #include #include #include #include #include -#include #include @@ -306,7 +304,12 @@ public: setUpdateCallback(new NoTraverseCallback); // No need for fog here, we are already applying fog on the water surface itself as well as underwater fog - getOrCreateStateSet()->setMode(GL_FOG, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); + // assign large value to effectively turn off fog + // shaders don't respect glDisable(GL_FOG) + osg::ref_ptr fog (new osg::Fog); + fog->setStart(10000000); + fog->setEnd(10000000); + getOrCreateStateSet()->setAttributeAndModes(fog, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); mClipCullNode = new ClipCullNode; addChild(mClipCullNode); @@ -318,7 +321,6 @@ public: mRefractionTexture->setInternalFormat(GL_RGB); mRefractionTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); mRefractionTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); - mRefractionTexture->getOrCreateUserDataContainer()->addDescription("dont_override_filter"); attach(osg::Camera::COLOR_BUFFER, mRefractionTexture); @@ -330,7 +332,6 @@ public: mRefractionDepthTexture->setSourceType(GL_UNSIGNED_INT); mRefractionDepthTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); mRefractionDepthTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); - mRefractionDepthTexture->getOrCreateUserDataContainer()->addDescription("dont_override_filter"); attach(osg::Camera::DEPTH_BUFFER, mRefractionDepthTexture); } @@ -375,7 +376,9 @@ public: setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); setReferenceFrame(osg::Camera::RELATIVE_RF); - setCullMask(Mask_Effect|Mask_Scene|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Player|Mask_Lighting); + bool reflectActors = Settings::Manager::getBool("reflect actors", "Water"); + + setCullMask(Mask_Effect|Mask_Scene|Mask_Terrain|Mask_ParticleSystem|Mask_Sky|Mask_Player|Mask_Lighting|(reflectActors ? Mask_Actor : 0)); setNodeMask(Mask_RenderToTexture); unsigned int rttSize = Settings::Manager::getInt("rtt size", "Water"); @@ -391,7 +394,6 @@ public: mReflectionTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); mReflectionTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); mReflectionTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - mReflectionTexture->getOrCreateUserDataContainer()->addDescription("dont_override_filter"); attach(osg::Camera::COLOR_BUFFER, mReflectionTexture); @@ -563,7 +565,7 @@ void Water::createSimpleWaterStateSet(osg::Node* node, float alpha) textures.push_back(tex); } - if (!textures.size()) + if (textures.empty()) return; float fps = mFallback->getFallbackFloat("Water_SurfaceFPS"); diff --git a/apps/openmw/mwrender/weaponanimation.cpp b/apps/openmw/mwrender/weaponanimation.cpp index 8fd294ccd..2627d3fc6 100644 --- a/apps/openmw/mwrender/weaponanimation.cpp +++ b/apps/openmw/mwrender/weaponanimation.cpp @@ -114,10 +114,10 @@ void WeaponAnimation::releaseArrow(MWWorld::Ptr actor, float attackStrength) osg::Node* weaponNode = getWeaponNode(); if (!weaponNode) return; - osg::MatrixList mats = weaponNode->getWorldMatrices(); - if (mats.empty()) + osg::NodePathList nodepaths = weaponNode->getParentalNodePaths(); + if (nodepaths.empty()) return; - osg::Vec3f launchPos = mats[0].getTrans(); + osg::Vec3f launchPos = osg::computeLocalToWorld(nodepaths[0]).getTrans(); float fThrownWeaponMinSpeed = gmst.find("fThrownWeaponMinSpeed")->getFloat(); float fThrownWeaponMaxSpeed = gmst.find("fThrownWeaponMaxSpeed")->getFloat(); @@ -140,10 +140,10 @@ void WeaponAnimation::releaseArrow(MWWorld::Ptr actor, float attackStrength) return; osg::ref_ptr ammoNode = mAmmunition->getNode(); - osg::MatrixList mats = ammoNode->getWorldMatrices(); - if (mats.empty()) + osg::NodePathList nodepaths = ammoNode->getParentalNodePaths(); + if (nodepaths.empty()) return; - osg::Vec3f launchPos = mats[0].getTrans(); + osg::Vec3f launchPos = osg::computeLocalToWorld(nodepaths[0]).getTrans(); float fProjectileMinSpeed = gmst.find("fProjectileMinSpeed")->getFloat(); float fProjectileMaxSpeed = gmst.find("fProjectileMaxSpeed")->getFloat(); diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index 93219c649..d3070c79b 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -449,5 +449,7 @@ op 0x2000300: EnableLevelupMenu op 0x2000301: ToggleScripts op 0x2000302: Fixme op 0x2000303: Fixme, explicit +op 0x2000304: Show +op 0x2000305: Show, explicit opcodes 0x2000304-0x3ffffff unused diff --git a/apps/openmw/mwscript/interpretercontext.cpp b/apps/openmw/mwscript/interpretercontext.cpp index 7a6afe2e0..79f856398 100644 --- a/apps/openmw/mwscript/interpretercontext.cpp +++ b/apps/openmw/mwscript/interpretercontext.cpp @@ -138,8 +138,7 @@ namespace MWScript InterpreterContext::InterpreterContext ( MWScript::Locals *locals, MWWorld::Ptr reference, const std::string& targetId) - : mLocals (locals), mReference (reference), - mActivationHandled (false), mTargetId (targetId) + : mLocals (locals), mReference (reference), mTargetId (targetId) { // If we run on a reference (local script, dialogue script or console with object // selected), store the ID of that reference store it so it can be inherited by @@ -477,37 +476,10 @@ namespace MWScript return static_cast(std::sqrt(diff[0] * diff[0] + diff[1] * diff[1] + diff[2] * diff[2])); } - bool InterpreterContext::hasBeenActivated (const MWWorld::Ptr& ptr) - { - if (!mActivated.isEmpty() && mActivated==ptr) - { - mActivationHandled = true; - return true; - } - - return false; - } - - bool InterpreterContext::hasActivationBeenHandled() const - { - return mActivationHandled; - } - - void InterpreterContext::activate (const MWWorld::Ptr& ptr) - { - mActivated = ptr; - mActivationHandled = false; - } - void InterpreterContext::executeActivation(MWWorld::Ptr ptr, MWWorld::Ptr actor) { boost::shared_ptr action = (ptr.getClass().activate(ptr, actor)); action->execute (actor); - if (mActivated == ptr) - { - mActivationHandled = true; - mActivated = MWWorld::Ptr(); - } } float InterpreterContext::getSecondsPassed() const diff --git a/apps/openmw/mwscript/interpretercontext.hpp b/apps/openmw/mwscript/interpretercontext.hpp index 3c43444cc..fdd5aa55f 100644 --- a/apps/openmw/mwscript/interpretercontext.hpp +++ b/apps/openmw/mwscript/interpretercontext.hpp @@ -27,9 +27,6 @@ namespace MWScript Locals *mLocals; mutable MWWorld::Ptr mReference; - MWWorld::Ptr mActivated; - bool mActivationHandled; - std::string mTargetId; /// If \a id is empty, a reference the script is run from is returned or in case @@ -131,16 +128,6 @@ namespace MWScript virtual float getDistance (const std::string& name, const std::string& id = "") const; ///< @note if \a id is empty, assumes an implicit reference - bool hasBeenActivated (const MWWorld::Ptr& ptr); - ///< \attention Calling this function for the right reference will mark the action as - /// been handled. - - bool hasActivationBeenHandled() const; - - void activate (const MWWorld::Ptr& ptr); - ///< Store reference acted upon. The actual execution of the action does not - /// take place here. - void executeActivation(MWWorld::Ptr ptr, MWWorld::Ptr actor); ///< Execute the activation action for this ptr. If ptr is mActivated, mark activation as handled. diff --git a/apps/openmw/mwscript/locals.cpp b/apps/openmw/mwscript/locals.cpp index ee93ea2e7..5c9ffa07a 100644 --- a/apps/openmw/mwscript/locals.cpp +++ b/apps/openmw/mwscript/locals.cpp @@ -97,6 +97,32 @@ namespace MWScript return 0; } + float Locals::getFloatVar(const std::string &script, const std::string &var) + { + ensure (script); + + const Compiler::Locals& locals = MWBase::Environment::get().getScriptManager()->getLocals(script); + int index = locals.getIndex(var); + char type = locals.getType(var); + if(index != -1) + { + switch(type) + { + case 's': + return mShorts.at (index); + + case 'l': + return mLongs.at (index); + + case 'f': + return mFloats.at(index); + default: + return 0; + } + } + return 0; + } + bool Locals::setVarByInt(const std::string& script, const std::string& var, int val) { ensure (script); diff --git a/apps/openmw/mwscript/locals.hpp b/apps/openmw/mwscript/locals.hpp index bb4df42bf..d63411a94 100644 --- a/apps/openmw/mwscript/locals.hpp +++ b/apps/openmw/mwscript/locals.hpp @@ -51,6 +51,12 @@ namespace MWScript /// \note Locals will be automatically configured first, if necessary int getIntVar (const std::string& script, const std::string& var); + /// if var does not exist, returns 0 + /// @note var needs to be in lowercase + /// + /// \note Locals will be automatically configured first, if necessary + float getFloatVar (const std::string& script, const std::string& var); + /// \note If locals have not been configured yet, no data is written. /// /// \return Locals written? diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 51f0c6c55..1830be693 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -142,7 +142,7 @@ namespace MWScript MWWorld::Ptr ptr = context.getReference(); - runtime.push (context.hasBeenActivated (ptr)); + runtime.push (ptr.getRefData().onActivate()); } }; @@ -158,7 +158,8 @@ namespace MWScript MWWorld::Ptr ptr = R()(runtime); - context.executeActivation(ptr, MWMechanics::getPlayer()); + if (ptr.getRefData().activateByScript()) + context.executeActivation(ptr, MWMechanics::getPlayer()); } }; @@ -848,6 +849,70 @@ namespace MWScript } }; + template + class OpShow : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime, false); + std::string var = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + + std::stringstream output; + + if (!ptr.isEmpty()) + { + const std::string& script = ptr.getClass().getScript(ptr); + if (script.empty()) + { + output << ptr.getCellRef().getRefId() << " has no script " << std::endl; + } + else + { + const Compiler::Locals& locals = + MWBase::Environment::get().getScriptManager()->getLocals(script); + char type = locals.getType(var); + switch (type) + { + case 'l': + case 's': + output << ptr.getCellRef().getRefId() << "." << var << ": " << ptr.getRefData().getLocals().getIntVar(script, var); + break; + case 'f': + output << ptr.getCellRef().getRefId() << "." << var << ": " << ptr.getRefData().getLocals().getFloatVar(script, var); + break; + default: + output << "unknown local '" << var << "' for '" << ptr.getCellRef().getRefId() << "'"; + break; + } + } + } + else + { + MWBase::World *world = MWBase::Environment::get().getWorld(); + char type = world->getGlobalVariableType (var); + + switch (type) + { + case 's': + output << runtime.getContext().getGlobalShort (var); + break; + case 'l': + output << runtime.getContext().getGlobalLong (var); + break; + case 'f': + output << runtime.getContext().getGlobalFloat (var); + break; + default: + output << "unknown global variable"; + } + } + runtime.getContext().report(output.str()); + } + }; + template class OpShowVars : public Interpreter::Opcode0 { @@ -1265,6 +1330,8 @@ namespace MWScript interpreter.installSegment5 (Compiler::Misc::opcodeEnableTeleporting, new OpEnableTeleporting); interpreter.installSegment5 (Compiler::Misc::opcodeShowVars, new OpShowVars); interpreter.installSegment5 (Compiler::Misc::opcodeShowVarsExplicit, new OpShowVars); + interpreter.installSegment5 (Compiler::Misc::opcodeShow, new OpShow); + interpreter.installSegment5 (Compiler::Misc::opcodeShowExplicit, new OpShow); interpreter.installSegment5 (Compiler::Misc::opcodeToggleGodMode, new OpToggleGodMode); interpreter.installSegment5 (Compiler::Misc::opcodeToggleScripts, new OpToggleScripts); interpreter.installSegment5 (Compiler::Misc::opcodeDisableLevitation, new OpEnableLevitation); diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 64c126de1..e9e13e74f 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -318,8 +318,8 @@ namespace MWScript ptr = MWBase::Environment::get().getWorld()->moveObject(ptr,store,x,y,z); dynamic_cast(runtime.getContext()).updatePtr(base,ptr); - float ax = osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[0]); - float ay = osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[1]); + float ax = ptr.getRefData().getPosition().rot[0]; + float ay = ptr.getRefData().getPosition().rot[1]; // Note that you must specify ZRot in minutes (1 degree = 60 minutes; north = 0, east = 5400, south = 10800, west = 16200) // except for when you position the player, then degrees must be used. // See "Morrowind Scripting for Dummies (9th Edition)" pages 50 and 54 for reference. @@ -374,14 +374,14 @@ namespace MWScript } dynamic_cast(runtime.getContext()).updatePtr(base,ptr); - float ax = osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[0]); - float ay = osg::RadiansToDegrees(ptr.getRefData().getPosition().rot[1]); + float ax = ptr.getRefData().getPosition().rot[0]; + float ay = ptr.getRefData().getPosition().rot[1]; // Note that you must specify ZRot in minutes (1 degree = 60 minutes; north = 0, east = 5400, south = 10800, west = 16200) // except for when you position the player, then degrees must be used. // See "Morrowind Scripting for Dummies (9th Edition)" pages 50 and 54 for reference. if(ptr != MWMechanics::getPlayer()) zRot = zRot/60.0f; - MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,zRot); + MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,osg::DegreesToRadians(zRot)); ptr.getClass().adjustPosition(ptr, false); } }; @@ -434,7 +434,7 @@ namespace MWScript pos.rot[2] = osg::DegreesToRadians(zRotDegrees); MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(),itemID); ref.getPtr().getCellRef().setPosition(pos); - MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,pos); + MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->placeObject(ref.getPtr(),store,pos); placed.getClass().adjustPosition(placed, true); } } @@ -482,7 +482,7 @@ namespace MWScript pos.rot[2] = osg::DegreesToRadians(zRotDegrees); MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(),itemID); ref.getPtr().getCellRef().setPosition(pos); - MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,pos); + MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->placeObject(ref.getPtr(),store,pos); placed.getClass().adjustPosition(placed, true); } }; @@ -516,38 +516,10 @@ namespace MWScript for (int i=0; igetStore(), itemID, 1); - ref.getPtr().getCellRef().setPosition(ipos); - MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,ipos); + MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(), actor, actor.getCell(), direction, distance); } } }; diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index e8ceaa40f..ae8601594 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -366,7 +366,7 @@ namespace MWSound else filelist = mMusicFiles[mCurrentPlaylist]; - if(!filelist.size()) + if(filelist.empty()) return; int i = Misc::Rng::rollDice(filelist.size()); diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 89d31c485..1bf97cb7e 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -493,7 +493,8 @@ void MWState::StateManager::loadGame (const Character *character, const std::str // but some mods may be using it as a reload detector. MWBase::Environment::get().getScriptManager()->getGlobalScripts().addStartup(); - // Do not trigger erroneous cellChanged events + // Since we passed "changeEvent=false" to changeCell, we shouldn't have triggered the cell change flag. + // But make sure the flag is cleared anyway in case it was set from an earlier game. MWBase::Environment::get().getWorld()->markCellAsUnchanged(); } catch (const std::exception& e) diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 6acb41dc3..f2881d68f 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -166,6 +166,8 @@ namespace MWWorld , mBulletShapeManager(bulletShapeManager) , mTerrain(terrain) , mExpiryDelay(0.0) + , mMinCacheSize(0) + , mMaxCacheSize(0) { } @@ -197,6 +199,23 @@ namespace MWWorld return; } + while (mPreloadCells.size() >= mMaxCacheSize) + { + // throw out oldest cell to make room + PreloadMap::iterator oldestCell = mPreloadCells.begin(); + double oldestTimestamp = DBL_MAX; + for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end(); ++it) + { + if (it->second.mTimeStamp < oldestTimestamp) + { + oldestTimestamp = it->second.mTimeStamp; + oldestCell = it; + } + } + + mPreloadCells.erase(oldestCell); + } + osg::ref_ptr item (new PreloadItem(cell, mResourceSystem->getSceneManager(), mBulletShapeManager, mResourceSystem->getKeyframeManager(), mTerrain)); mWorkQueue->addWorkItem(item); @@ -210,11 +229,9 @@ namespace MWWorld void CellPreloader::updateCache(double timestamp) { - // TODO: add settings for a minimum/maximum size of the cache - for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end();) { - if (it->second.mTimeStamp < timestamp - mExpiryDelay) + if (mPreloadCells.size() >= mMinCacheSize && it->second.mTimeStamp < timestamp - mExpiryDelay) mPreloadCells.erase(it++); else ++it; @@ -229,6 +246,16 @@ namespace MWWorld mExpiryDelay = expiryDelay; } + void CellPreloader::setMinCacheSize(unsigned int num) + { + mMinCacheSize = num; + } + + void CellPreloader::setMaxCacheSize(unsigned int num) + { + mMaxCacheSize = num; + } + void CellPreloader::setWorkQueue(osg::ref_ptr workQueue) { mWorkQueue = workQueue; diff --git a/apps/openmw/mwworld/cellpreloader.hpp b/apps/openmw/mwworld/cellpreloader.hpp index 437395397..f78e66e75 100644 --- a/apps/openmw/mwworld/cellpreloader.hpp +++ b/apps/openmw/mwworld/cellpreloader.hpp @@ -38,6 +38,12 @@ namespace MWWorld /// How long to keep a preloaded cell in cache after it's no longer requested. void setExpiryDelay(double expiryDelay); + /// The minimum number of preloaded cells before unused cells get thrown out. + void setMinCacheSize(unsigned int num); + + /// The maximum number of preloaded cells. + void setMaxCacheSize(unsigned int num); + void setWorkQueue(osg::ref_ptr workQueue); private: @@ -46,6 +52,8 @@ namespace MWWorld Terrain::World* mTerrain; osg::ref_ptr mWorkQueue; double mExpiryDelay; + unsigned int mMinCacheSize; + unsigned int mMaxCacheSize; struct PreloadEntry { diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index dcaa73e93..13f74e22b 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -889,11 +889,19 @@ namespace MWWorld return mFogState.get(); } + void clearCorpse(const MWWorld::Ptr& ptr) + { + const MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); + static const float fCorpseClearDelay = MWBase::Environment::get().getWorld()->getStore().get().find("fCorpseClearDelay")->getFloat(); + if (creatureStats.isDead() && !ptr.getClass().isPersistent(ptr) && creatureStats.getTimeOfDeath() + fCorpseClearDelay <= MWBase::Environment::get().getWorld()->getTimeStamp()) + MWBase::Environment::get().getWorld()->deleteObject(ptr); + } + void CellStore::respawn() { if (mState == State_Loaded) { - static int iMonthsToRespawn = MWBase::Environment::get().getWorld()->getStore().get().find("iMonthsToRespawn")->getInt(); + static const int iMonthsToRespawn = MWBase::Environment::get().getWorld()->getStore().get().find("iMonthsToRespawn")->getInt(); if (MWBase::Environment::get().getWorld()->getTimeStamp() - mLastRespawn > 24*30*iMonthsToRespawn) { mLastRespawn = MWBase::Environment::get().getWorld()->getTimeStamp(); @@ -902,21 +910,25 @@ namespace MWWorld Ptr ptr (&*it, this); ptr.getClass().respawn(ptr); } - for (CellRefList::List::iterator it (mCreatures.mList.begin()); it!=mCreatures.mList.end(); ++it) - { - Ptr ptr (&*it, this); - ptr.getClass().respawn(ptr); - } - for (CellRefList::List::iterator it (mNpcs.mList.begin()); it!=mNpcs.mList.end(); ++it) - { - Ptr ptr (&*it, this); - ptr.getClass().respawn(ptr); - } - for (CellRefList::List::iterator it (mCreatureLists.mList.begin()); it!=mCreatureLists.mList.end(); ++it) - { - Ptr ptr (&*it, this); - ptr.getClass().respawn(ptr); - } + } + + for (CellRefList::List::iterator it (mCreatures.mList.begin()); it!=mCreatures.mList.end(); ++it) + { + Ptr ptr (&*it, this); + clearCorpse(ptr); + ptr.getClass().respawn(ptr); + } + for (CellRefList::List::iterator it (mNpcs.mList.begin()); it!=mNpcs.mList.end(); ++it) + { + Ptr ptr (&*it, this); + clearCorpse(ptr); + ptr.getClass().respawn(ptr); + } + for (CellRefList::List::iterator it (mCreatureLists.mList.begin()); it!=mCreatureLists.mList.end(); ++it) + { + Ptr ptr (&*it, this); + // no need to clearCorpse, handled as part of mCreatures + ptr.getClass().respawn(ptr); } } } diff --git a/apps/openmw/mwworld/localscripts.cpp b/apps/openmw/mwworld/localscripts.cpp index f0174ac52..5f6d10b31 100644 --- a/apps/openmw/mwworld/localscripts.cpp +++ b/apps/openmw/mwworld/localscripts.cpp @@ -66,11 +66,6 @@ MWWorld::LocalScripts::LocalScripts (const MWWorld::ESMStore& store) : mStore (s mIter = mScripts.end(); } -void MWWorld::LocalScripts::setIgnore (const ConstPtr& ptr) -{ - mIgnore = ptr; -} - void MWWorld::LocalScripts::startIteration() { mIter = mScripts.begin(); @@ -81,11 +76,8 @@ bool MWWorld::LocalScripts::getNext(std::pair& script) while (mIter!=mScripts.end()) { std::list >::iterator iter = mIter++; - if (mIgnore.isEmpty() || iter->second!=mIgnore) - { - script = *iter; - return true; - } + script = *iter; + return true; } return false; } diff --git a/apps/openmw/mwworld/localscripts.hpp b/apps/openmw/mwworld/localscripts.hpp index 6c2118ea8..c698daf04 100644 --- a/apps/openmw/mwworld/localscripts.hpp +++ b/apps/openmw/mwworld/localscripts.hpp @@ -17,17 +17,12 @@ namespace MWWorld { std::list > mScripts; std::list >::iterator mIter; - MWWorld::ConstPtr mIgnore; const MWWorld::ESMStore& mStore; public: LocalScripts (const MWWorld::ESMStore& store); - void setIgnore (const ConstPtr& ptr); - ///< Mark a single reference for ignoring during iteration over local scripts (will revoke - /// previous ignores). - void startIteration(); ///< Set the iterator to the begin of the script list. diff --git a/apps/openmw/mwworld/refdata.cpp b/apps/openmw/mwworld/refdata.cpp index 1da6b53fa..f85abcc32 100644 --- a/apps/openmw/mwworld/refdata.cpp +++ b/apps/openmw/mwworld/refdata.cpp @@ -8,8 +8,18 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +namespace +{ +enum RefDataFlags +{ + Flag_SuppressActivate = 1, // If set, activation will be suppressed and redirected to the OnActivate flag, which can then be handled by a script. + Flag_OnActivate = 2 +}; +} + namespace MWWorld { + void RefData::copy (const RefData& refData) { mBaseNode = refData.mBaseNode; @@ -19,6 +29,7 @@ namespace MWWorld mPosition = refData.mPosition; mChanged = refData.mChanged; mDeletedByContentFile = refData.mDeletedByContentFile; + mFlags = refData.mFlags; mCustomData = refData.mCustomData ? refData.mCustomData->clone() : 0; } @@ -32,7 +43,7 @@ namespace MWWorld } RefData::RefData() - : mBaseNode(0), mDeletedByContentFile(false), mEnabled (true), mCount (1), mCustomData (0), mChanged(false) + : mBaseNode(0), mDeletedByContentFile(false), mEnabled (true), mCount (1), mCustomData (0), mChanged(false), mFlags(0) { for (int i=0; i<3; ++i) { @@ -45,7 +56,7 @@ namespace MWWorld : mBaseNode(0), mDeletedByContentFile(false), mEnabled (true), mCount (1), mPosition (cellRef.mPos), mCustomData (0), - mChanged(false) // Loading from ESM/ESP files -> assume unchanged + mChanged(false), mFlags(0) // Loading from ESM/ESP files -> assume unchanged { } @@ -55,8 +66,12 @@ namespace MWWorld mCount (objectState.mCount), mPosition (objectState.mPosition), mCustomData (0), - mChanged(true) // Loading from a savegame -> assume changed + mChanged(true), mFlags(objectState.mFlags) // Loading from a savegame -> assume changed { + // "Note that the ActivationFlag_UseEnabled is saved to the reference, + // which will result in permanently suppressed activation if the reference script is removed. + // This occurred when removing the animated containers mod, and the fix in MCP is to reset UseEnabled to true on loading a game." + mFlags &= (~Flag_SuppressActivate); } RefData::RefData (const RefData& refData) @@ -80,6 +95,7 @@ namespace MWWorld objectState.mEnabled = mEnabled; objectState.mCount = mCount; objectState.mPosition = mPosition; + objectState.mFlags = mFlags; } RefData& RefData::operator= (const RefData& refData) @@ -219,4 +235,38 @@ namespace MWWorld { return mChanged; } + + bool RefData::activate() + { + if (!(mFlags & Flag_SuppressActivate)) + return true; + else + { + mFlags |= Flag_OnActivate; + return false; + } + } + + bool RefData::onActivate() + { + mFlags |= Flag_SuppressActivate; + + if (mFlags & Flag_OnActivate) + { + mFlags &= (~Flag_OnActivate); + return true; + } + return false; + } + + bool RefData::activateByScript() + { + if (mFlags & Flag_SuppressActivate) + { + mFlags &= (~Flag_SuppressActivate); + return true; + } + else + return false; + } } diff --git a/apps/openmw/mwworld/refdata.hpp b/apps/openmw/mwworld/refdata.hpp index d87ffdb70..9e662e430 100644 --- a/apps/openmw/mwworld/refdata.hpp +++ b/apps/openmw/mwworld/refdata.hpp @@ -50,6 +50,8 @@ namespace MWWorld bool mChanged; + unsigned int mFlags; + public: RefData(); @@ -122,6 +124,12 @@ namespace MWWorld const CustomData *getCustomData() const; + bool activate(); + + bool onActivate(); + + bool activateByScript(); + bool hasChanged() const; ///< Has this RefData changed since it was originally loaded? }; diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 308809bd2..ba2829469 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -240,7 +240,7 @@ namespace MWWorld mActiveCells.erase(*iter); } - void Scene::loadCell (CellStore *cell, Loading::Listener* loadingListener) + void Scene::loadCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn) { std::pair result = mActiveCells.insert(cell); @@ -268,14 +268,22 @@ namespace MWWorld mPhysics->addHeightField (data->mHeights, cell->getCell()->getGridX(), cell->getCell()->getGridY(), worldsize / (verts-1), verts); } + else + { + static std::vector defaultHeight; + defaultHeight.resize(verts*verts, ESM::Land::DEFAULT_HEIGHT); + mPhysics->addHeightField (&defaultHeight[0], cell->getCell()->getGridX(), cell->getCell()->getGridY(), + worldsize / (verts-1), verts); + } } - cell->respawn(); - // register local scripts // do this before insertCell, to make sure we don't add scripts from levelled creature spawning twice MWBase::Environment::get().getWorld()->getLocalScripts().addCell (cell); + if (respawn) + cell->respawn(); + // ... then references. This is important for adjustPosition to work correctly. /// \todo rescale depending on the state of a new GMST insertCell (*cell, true, loadingListener); @@ -329,7 +337,7 @@ namespace MWWorld } } - void Scene::changeCellGrid (int X, int Y) + void Scene::changeCellGrid (int X, int Y, bool changeEvent) { Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::ScopedLoad load(loadingListener); @@ -403,7 +411,7 @@ namespace MWWorld { CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(x, y); - loadCell (cell, loadingListener); + loadCell (cell, loadingListener, changeEvent); } } } @@ -411,7 +419,8 @@ namespace MWWorld CellStore* current = MWBase::Environment::get().getWorld()->getExterior(X,Y); MWBase::Environment::get().getWindowManager()->changeCell(current); - mCellChanged = true; + if (changeEvent) + mCellChanged = true; mPreloader->updateCache(mRendering.getReferenceTime()); } @@ -463,9 +472,11 @@ namespace MWWorld mPhysics->setUnrefQueue(rendering.getUnrefQueue()); - float cacheExpiryDelay = Settings::Manager::getFloat("cache expiry delay", "Cells"); - rendering.getResourceSystem()->setExpiryDelay(cacheExpiryDelay); - mPreloader->setExpiryDelay(cacheExpiryDelay); + rendering.getResourceSystem()->setExpiryDelay(Settings::Manager::getFloat("cache expiry delay", "Cells")); + + mPreloader->setExpiryDelay(Settings::Manager::getFloat("preload cell expiry delay", "Cells")); + mPreloader->setMinCacheSize(Settings::Manager::getInt("preload cell cache min", "Cells")); + mPreloader->setMaxCacheSize(Settings::Manager::getInt("preload cell cache max", "Cells")); } Scene::~Scene() @@ -482,7 +493,7 @@ namespace MWWorld return mActiveCells; } - void Scene::changeToInteriorCell (const std::string& cellName, const ESM::Position& position) + void Scene::changeToInteriorCell (const std::string& cellName, const ESM::Position& position, bool changeEvent) { CellStore *cell = MWBase::Environment::get().getWorld()->getInterior(cellName); bool loadcell = (mCurrentCell == NULL); @@ -528,7 +539,7 @@ namespace MWWorld loadingListener->setProgressRange(refsToLoad); // Load cell. - loadCell (cell, loadingListener); + loadCell (cell, loadingListener, changeEvent); changePlayerCell(cell, position, true); @@ -538,21 +549,24 @@ namespace MWWorld // Sky system MWBase::Environment::get().getWorld()->adjustSky(); - mCellChanged = true; MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.5); + if (changeEvent) + mCellChanged = true; + + MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.5); MWBase::Environment::get().getWindowManager()->changeCell(mCurrentCell); mPreloader->updateCache(mRendering.getReferenceTime()); } - void Scene::changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos) + void Scene::changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos, bool changeEvent) { int x = 0; int y = 0; MWBase::Environment::get().getWorld()->positionToIndex (position.pos[0], position.pos[1], x, y); - changeCellGrid(x, y); + changeCellGrid(x, y, changeEvent); CellStore* current = MWBase::Environment::get().getWorld()->getExterior(x, y); changePlayerCell(current, position, adjustPlayerPos); diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index 5c429f7c7..6cba9c3be 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -71,7 +71,7 @@ namespace MWWorld void insertCell (CellStore &cell, bool rescale, Loading::Listener* loadingListener); // Load and unload cells as necessary to create a cell grid with "X" and "Y" in the center - void changeCellGrid (int X, int Y); + void changeCellGrid (int X, int Y, bool changeEvent = true); void getGridCenter(int& cellX, int& cellY); @@ -90,7 +90,7 @@ namespace MWWorld void unloadCell (CellStoreCollection::iterator iter); - void loadCell (CellStore *cell, Loading::Listener* loadingListener); + void loadCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn); void playerMoved (const osg::Vec3f& pos); @@ -103,11 +103,13 @@ namespace MWWorld bool hasCellChanged() const; ///< Has the set of active cells changed, since the last frame? - void changeToInteriorCell (const std::string& cellName, const ESM::Position& position); + void changeToInteriorCell (const std::string& cellName, const ESM::Position& position, bool changeEvent=true); ///< Move to interior cell. + /// @param changeEvent Set cellChanged flag? - void changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos); + void changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos, bool changeEvent=true); ///< Move to exterior cell. + /// @param changeEvent Set cellChanged flag? void changeToVoid(); ///< Change into a void diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 98517f543..2ab027cd6 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -962,11 +962,11 @@ namespace MWWorld return mTimeScale->getFloat(); } - void World::changeToInteriorCell (const std::string& cellName, const ESM::Position& position) + void World::changeToInteriorCell (const std::string& cellName, const ESM::Position& position, bool changeEvent) { mPhysics->clearQueuedMovement(); - if (mCurrentWorldSpace != cellName) + if (changeEvent && mCurrentWorldSpace != cellName) { // changed worldspace mProjectileManager->clear(); @@ -976,34 +976,34 @@ namespace MWWorld } removeContainerScripts(getPlayerPtr()); - mWorldScene->changeToInteriorCell(cellName, position); + mWorldScene->changeToInteriorCell(cellName, position, changeEvent); addContainerScripts(getPlayerPtr(), getPlayerPtr().getCell()); } - void World::changeToExteriorCell (const ESM::Position& position) + void World::changeToExteriorCell (const ESM::Position& position, bool changeEvent) { mPhysics->clearQueuedMovement(); - if (mCurrentWorldSpace != "sys::default") // FIXME + if (changeEvent && mCurrentWorldSpace != "sys::default") // FIXME { // changed worldspace mProjectileManager->clear(); mRendering->notifyWorldSpaceChanged(); } removeContainerScripts(getPlayerPtr()); - mWorldScene->changeToExteriorCell(position, true); + mWorldScene->changeToExteriorCell(position, true, changeEvent); addContainerScripts(getPlayerPtr(), getPlayerPtr().getCell()); } - void World::changeToCell (const ESM::CellId& cellId, const ESM::Position& position, bool detectWorldSpaceChange) + void World::changeToCell (const ESM::CellId& cellId, const ESM::Position& position, bool changeEvent) { - if (!detectWorldSpaceChange) + if (!changeEvent) mCurrentWorldSpace = cellId.mWorldspace; if (cellId.mPaged) - changeToExteriorCell (position); + changeToExteriorCell (position, changeEvent); else - changeToInteriorCell (cellId.mWorldspace, position); + changeToInteriorCell (cellId.mWorldspace, position, changeEvent); } void World::markCellAsUnchanged() @@ -1051,9 +1051,9 @@ namespace MWWorld if(!node) node = anim->getNode("Bip01 Head"); if(node) { - osg::MatrixList mats = node->getWorldMatrices(); - if(!mats.empty()) - return mats[0]; + osg::NodePathList nodepaths = node->getParentalNodePaths(); + if(!nodepaths.empty()) + return osg::computeLocalToWorld(nodepaths[0]); } } return osg::Matrixf::translate(actor.getRefData().getPosition().asVec3()); @@ -1325,11 +1325,50 @@ namespace MWWorld rotateObjectImp(ptr, osg::Vec3f(x, y, z), adjust); } - MWWorld::Ptr World::safePlaceObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, ESM::Position pos) + MWWorld::Ptr World::placeObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, ESM::Position pos) { return copyObjectToCell(ptr,cell,pos,ptr.getRefData().getCount(),false); } + MWWorld::Ptr World::safePlaceObject(const ConstPtr &ptr, const ConstPtr &referenceObject, MWWorld::CellStore* referenceCell, int direction, float distance) + { + ESM::Position ipos = referenceObject.getRefData().getPosition(); + osg::Vec3f pos(ipos.asVec3()); + osg::Quat orientation(ipos.rot[2], osg::Vec3f(0,0,-1)); + + int fallbackDirections[4] = {direction, (direction+3)%4, (direction+2)%4, (direction+1)%4}; + + for (int i=0; i<4; ++i) + { + direction = fallbackDirections[i]; + // check if spawn point is safe, fall back to another direction if not + osg::Vec3f spawnPoint = pos; + if (direction == 0) spawnPoint = pos + (orientation * osg::Vec3f(0,1,0)) * distance; + else if(direction == 1) spawnPoint = pos - (orientation * osg::Vec3f(0,1,0)) * distance; + else if(direction == 2) spawnPoint = pos - (orientation * osg::Vec3f(1,0,0)) * distance; + else if(direction == 3) spawnPoint = pos + (orientation * osg::Vec3f(1,0,0)) * distance; + spawnPoint.z() += 30; // move up a little to account for slopes, will snap down later + + if (!castRay(spawnPoint.x(), spawnPoint.y(), spawnPoint.z(), + pos.x(), pos.y(), pos.z() + 20)) + { + // safe + ipos.pos[0] = spawnPoint.x(); + ipos.pos[1] = spawnPoint.y(); + ipos.pos[2] = spawnPoint.z(); + break; + } + } + + ipos.rot[0] = referenceObject.getRefData().getPosition().rot[0]; + ipos.rot[1] = referenceObject.getRefData().getPosition().rot[1]; + ipos.rot[2] = referenceObject.getRefData().getPosition().rot[2]; + + MWWorld::Ptr placed = copyObjectToCell(ptr, referenceCell, ipos, ptr.getRefData().getCount(), false); + placed.getClass().adjustPosition(placed, true); // snap to ground + return placed; + } + void World::indexToPosition (int cellX, int cellY, float &x, float &y, bool centre) const { const int cellSize = 8192; @@ -3025,23 +3064,9 @@ namespace MWWorld if (selectedCreature.empty()) return; - ESM::Position ipos = mPlayer->getPlayer().getRefData().getPosition(); - osg::Vec3f pos(ipos.asVec3()); - osg::Quat rot(-ipos.rot[2], osg::Vec3f(0,0,1)); - const float distance = 50; - pos = pos + (rot * osg::Vec3f(0,1,0)) * distance; - ipos.pos[0] = pos.x(); - ipos.pos[1] = pos.y(); - ipos.pos[2] = pos.z(); - ipos.rot[0] = 0; - ipos.rot[1] = 0; - ipos.rot[2] = 0; - - MWWorld::CellStore* cell = mPlayer->getPlayer().getCell(); MWWorld::ManualRef ref(getStore(), selectedCreature, 1); - ref.getPtr().getCellRef().setPosition(ipos); - safePlaceObject(ref.getPtr(), cell, ipos); + safePlaceObject(ref.getPtr(), getPlayerPtr(), getPlayerPtr().getCell(), 0, 220.f); } } @@ -3148,25 +3173,16 @@ namespace MWWorld void World::activate(const Ptr &object, const Ptr &actor) { - MWScript::InterpreterContext interpreterContext (&object.getRefData().getLocals(), object); - interpreterContext.activate (object); - - std::string script = object.getClass().getScript (object); - breakInvisibility(actor); if (mScriptsEnabled) { - if (!script.empty()) + if (object.getRefData().activate()) { - getLocalScripts().setIgnore (object); - MWBase::Environment::get().getScriptManager()->run (script, interpreterContext); + boost::shared_ptr action = (object.getClass().activate(object, actor)); + action->execute (actor); } - if (!interpreterContext.hasActivationBeenHandled()) - interpreterContext.executeActivation(object, actor); } - else - interpreterContext.executeActivation(object, actor); } struct ResetActorsVisitor diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 6cc5cdc11..d1298a39b 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -324,15 +324,16 @@ namespace MWWorld virtual float getTimeScaleFactor() const; - virtual void changeToInteriorCell (const std::string& cellName, - const ESM::Position& position); + virtual void changeToInteriorCell (const std::string& cellName, const ESM::Position& position, bool changeEvent = true); ///< Move to interior cell. + ///< @param changeEvent If false, do not trigger cell change flag or detect worldspace changes - virtual void changeToExteriorCell (const ESM::Position& position); + virtual void changeToExteriorCell (const ESM::Position& position, bool changeEvent = true); ///< Move to exterior cell. + ///< @param changeEvent If false, do not trigger cell change flag or detect worldspace changes - virtual void changeToCell (const ESM::CellId& cellId, const ESM::Position& position, bool detectWorldSpaceChange=true); - ///< @param detectWorldSpaceChange if true, clean up worldspace-specific data when the world space changes + virtual void changeToCell (const ESM::CellId& cellId, const ESM::Position& position, bool changeEvent=true); + ///< @param changeEvent If false, do not trigger cell change flag or detect worldspace changes virtual const ESM::Cell *getExterior (const std::string& cellName) const; ///< Return a cell matching the given name or a 0-pointer, if there is no such cell. @@ -366,8 +367,12 @@ namespace MWWorld /// \param adjust indicates rotation should be set or adjusted virtual void rotateObject (const Ptr& ptr,float x,float y,float z, bool adjust = false); - virtual MWWorld::Ptr safePlaceObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, ESM::Position pos); - ///< place an object in a "safe" location (ie not in the void, etc). Makes a copy of the Ptr. + virtual MWWorld::Ptr placeObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, ESM::Position pos); + ///< Place an object. Makes a copy of the Ptr. + + virtual MWWorld::Ptr safePlaceObject (const MWWorld::ConstPtr& ptr, const MWWorld::ConstPtr& referenceObject, MWWorld::CellStore* referenceCell, int direction, float distance); + ///< Place an object in a safe place next to \a referenceObject. \a direction and \a distance specify the wanted placement + /// relative to \a referenceObject (but the object may be placed somewhere else if the wanted location is obstructed). virtual float getMaxActivationDistance(); diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 0ac32f65e..2508cdc82 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -44,6 +44,10 @@ add_component_dir (resource scenemanager keyframemanager imagemanager bulletshapemanager bulletshape niffilemanager objectcache multiobjectcache resourcesystem resourcemanager ) +add_component_dir (shader + shadermanager shadervisitor + ) + add_component_dir (sceneutil clone attach visitor util statesetupdater controller skeleton riggeometry lightcontroller lightmanager lightutil positionattitudetransform workqueue unrefqueue diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index 6916945f9..161d8adb4 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -300,6 +300,7 @@ namespace Compiler extensions.registerInstruction ("disableteleporting", "", opcodeDisableTeleporting); extensions.registerInstruction ("enableteleporting", "", opcodeEnableTeleporting); extensions.registerInstruction ("showvars", "", opcodeShowVars, opcodeShowVarsExplicit); + extensions.registerInstruction ("show", "c", opcodeShow, opcodeShowExplicit); extensions.registerInstruction ("sv", "", opcodeShowVars, opcodeShowVarsExplicit); extensions.registerInstruction("tgm", "", opcodeToggleGodMode); extensions.registerInstruction("togglegodmode", "", opcodeToggleGodMode); diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp index feed5513e..430b88484 100644 --- a/components/compiler/opcodes.hpp +++ b/components/compiler/opcodes.hpp @@ -277,6 +277,8 @@ namespace Compiler const int opcodeEnableTeleporting = 0x2000216; const int opcodeShowVars = 0x200021d; const int opcodeShowVarsExplicit = 0x200021e; + const int opcodeShow = 0x2000304; + const int opcodeShowExplicit = 0x2000305; const int opcodeToggleGodMode = 0x200021f; const int opcodeToggleScripts = 0x2000301; const int opcodeDisableLevitation = 0x2000220; diff --git a/components/esm/creaturestats.cpp b/components/esm/creaturestats.cpp index 9bdbf9668..9b60382e9 100644 --- a/components/esm/creaturestats.cpp +++ b/components/esm/creaturestats.cpp @@ -87,6 +87,10 @@ void ESM::CreatureStats::load (ESMReader &esm) mDeathAnimation = 0; esm.getHNOT (mDeathAnimation, "DANM"); + mTimeOfDeath.mDay = 0; + mTimeOfDeath.mHour = 0; + esm.getHNOT (mTimeOfDeath, "DTIM"); + mSpells.load(esm); mActiveSpells.load(esm); mAiSequence.load(esm); @@ -193,6 +197,9 @@ void ESM::CreatureStats::save (ESMWriter &esm) const if (mDeathAnimation) esm.writeHNT ("DANM", mDeathAnimation); + if (mTimeOfDeath.mHour != 0 && mTimeOfDeath.mDay != 0) + esm.writeHNT ("DTIM", mTimeOfDeath); + mSpells.save(esm); mActiveSpells.save(esm); mAiSequence.save(esm); diff --git a/components/esm/creaturestats.hpp b/components/esm/creaturestats.hpp index 426e89055..66b708e27 100644 --- a/components/esm/creaturestats.hpp +++ b/components/esm/creaturestats.hpp @@ -57,6 +57,7 @@ namespace ESM bool mRecalcDynamicStats; int mDrawState; unsigned char mDeathAnimation; + ESM::TimeStamp mTimeOfDeath; int mLevel; diff --git a/components/esm/loadland.hpp b/components/esm/loadland.hpp index 8a8d6fdd2..a2bf1573e 100644 --- a/components/esm/loadland.hpp +++ b/components/esm/loadland.hpp @@ -46,6 +46,9 @@ struct Land DATA_VTEX = 16 }; + // default height to use in case there is no Land record + static const int DEFAULT_HEIGHT = -2048; + // number of vertices per side static const int LAND_SIZE = 65; diff --git a/components/esm/loadtes3.cpp b/components/esm/loadtes3.cpp index df35a2579..60d411fc6 100644 --- a/components/esm/loadtes3.cpp +++ b/components/esm/loadtes3.cpp @@ -53,14 +53,14 @@ void ESM::Header::load (ESMReader &esm) { esm.getSubHeader(); mSCRD.resize(esm.getSubSize()); - if (mSCRD.size()) + if (!mSCRD.empty()) esm.getExact(&mSCRD[0], mSCRD.size()); } if (esm.isNextSub("SCRS")) { esm.getSubHeader(); mSCRS.resize(esm.getSubSize()); - if (mSCRS.size()) + if (!mSCRS.empty()) esm.getExact(&mSCRS[0], mSCRS.size()); } } diff --git a/components/esm/objectstate.cpp b/components/esm/objectstate.cpp index d736bab66..b80c72ffe 100644 --- a/components/esm/objectstate.cpp +++ b/components/esm/objectstate.cpp @@ -27,6 +27,9 @@ void ESM::ObjectState::load (ESMReader &esm) if (esm.isNextSub("LROT")) esm.skipHSub(); // local rotation, no longer used + mFlags = 0; + esm.getHNOT (mFlags, "FLAG"); + // obsolete int unused; esm.getHNOT(unused, "LTIM"); @@ -55,6 +58,9 @@ void ESM::ObjectState::save (ESMWriter &esm, bool inInventory) const if (!inInventory) esm.writeHNT ("POS_", mPosition, 24); + if (mFlags != 0) + esm.writeHNT ("FLAG", mFlags); + if (!mHasCustomState) esm.writeHNT ("HCUS", false); } @@ -70,6 +76,7 @@ void ESM::ObjectState::blank() mPosition.pos[i] = 0; mPosition.rot[i] = 0; } + mFlags = 0; mHasCustomState = true; } diff --git a/components/esm/objectstate.hpp b/components/esm/objectstate.hpp index 215cb74ba..5b78074af 100644 --- a/components/esm/objectstate.hpp +++ b/components/esm/objectstate.hpp @@ -24,6 +24,7 @@ namespace ESM unsigned char mEnabled; int mCount; ESM::Position mPosition; + unsigned int mFlags; // Is there any class-specific state following the ObjectState bool mHasCustomState; diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index cead73070..86d1e08e6 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -16,10 +16,14 @@ namespace ESMTerrain { - const float defaultHeight = -2048; + const float defaultHeight = ESM::Land::DEFAULT_HEIGHT; - Storage::Storage(const VFS::Manager *vfs) + Storage::Storage(const VFS::Manager *vfs, const std::string& normalMapPattern, bool autoUseNormalMaps, const std::string& specularMapPattern, bool autoUseSpecularMaps) : mVFS(vfs) + , mNormalMapPattern(normalMapPattern) + , mAutoUseNormalMaps(autoUseNormalMaps) + , mSpecularMapPattern(specularMapPattern) + , mAutoUseSpecularMaps(autoUseSpecularMaps) { } @@ -508,9 +512,11 @@ namespace ESMTerrain return found->second; Terrain::LayerInfo info; - info.mParallax = false; + //info.mParallax = false; info.mSpecular = false; info.mDiffuseMap = texture; + + /* std::string texture_ = texture; boost::replace_last(texture_, ".", "_nh."); @@ -519,20 +525,24 @@ namespace ESMTerrain info.mNormalMap = texture_; info.mParallax = true; } - else + */ + if (mAutoUseNormalMaps) { - texture_ = texture; - boost::replace_last(texture_, ".", "_n."); + std::string texture_ = texture; + boost::replace_last(texture_, ".", mNormalMapPattern + "."); if (mVFS->exists(texture_)) info.mNormalMap = texture_; } - texture_ = texture; - boost::replace_last(texture_, ".", "_diffusespec."); - if (mVFS->exists(texture_)) + if (mAutoUseSpecularMaps) { - info.mDiffuseMap = texture_; - info.mSpecular = true; + std::string texture_ = texture; + boost::replace_last(texture_, ".", mSpecularMapPattern + "."); + if (mVFS->exists(texture_)) + { + info.mDiffuseMap = texture_; + info.mSpecular = true; + } } mLayerInfoMap[texture] = info; @@ -544,7 +554,7 @@ namespace ESMTerrain { Terrain::LayerInfo info; info.mDiffuseMap = "textures\\_land_default.dds"; - info.mParallax = false; + //info.mParallax = false; info.mSpecular = false; return info; } diff --git a/components/esmterrain/storage.hpp b/components/esmterrain/storage.hpp index 9ca39e6f3..7b8b844ff 100644 --- a/components/esmterrain/storage.hpp +++ b/components/esmterrain/storage.hpp @@ -27,7 +27,7 @@ namespace ESMTerrain virtual const ESM::LandTexture* getLandTexture(int index, short plugin) = 0; public: - Storage(const VFS::Manager* vfs); + Storage(const VFS::Manager* vfs, const std::string& normalMapPattern = "", bool autoUseNormalMaps = false, const std::string& specularMapPattern = "", bool autoUseSpecularMaps = false); /// Data is loaded first, if necessary. Will return a 0-pointer if there is no data for /// any of the data types specified via \a flags. Will also return a 0-pointer if there @@ -109,6 +109,12 @@ namespace ESMTerrain std::map mLayerInfoMap; OpenThreads::Mutex mLayerInfoMutex; + std::string mNormalMapPattern; + bool mAutoUseNormalMaps; + + std::string mSpecularMapPattern; + bool mAutoUseSpecularMaps; + Terrain::LayerInfo getLayerInfo(const std::string& texture); }; diff --git a/components/interpreter/defines.cpp b/components/interpreter/defines.cpp index 2ceb857c4..fafaf1fef 100644 --- a/components/interpreter/defines.cpp +++ b/components/interpreter/defines.cpp @@ -26,13 +26,14 @@ namespace Interpreter{ return a.length() > b.length(); } - std::string fixDefinesReal(std::string text, char eschar, bool isBook, Context& context) + std::string fixDefinesReal(std::string text, bool dialogue, Context& context) { unsigned int start = 0; std::ostringstream retval; for(unsigned int i = 0; i < text.length(); i++) { - if(text[i] == eschar) + char eschar = text[i]; + if(eschar == '%' || eschar == '^') { retval << text.substr(start, i - start); std::string temp = Misc::StringUtils::lowerCase(text.substr(i+1, 100)); @@ -113,7 +114,7 @@ namespace Interpreter{ retval << context.getCurrentCellName(); } - else if(eschar == '%' && !isBook) { // In Dialogue, not messagebox + else if(dialogue) { // In Dialogue, not messagebox if( (found = check(temp, "faction", &i, &start))){ retval << context.getNPCFaction(); } @@ -207,15 +208,15 @@ namespace Interpreter{ return retval.str (); } - std::string fixDefinesDialog(std::string text, Context& context){ - return fixDefinesReal(text, '%', false, context); + std::string fixDefinesDialog(const std::string& text, Context& context){ + return fixDefinesReal(text, true, context); } - std::string fixDefinesMsgBox(std::string text, Context& context){ - return fixDefinesReal(text, '^', false, context); + std::string fixDefinesMsgBox(const std::string& text, Context& context){ + return fixDefinesReal(text, false, context); } - std::string fixDefinesBook(std::string text, Context& context){ - return fixDefinesReal(text, '%', true, context); + std::string fixDefinesBook(const std::string& text, Context& context){ + return fixDefinesReal(text, false, context); } } diff --git a/components/interpreter/defines.hpp b/components/interpreter/defines.hpp index 00c4386b8..3471b2030 100644 --- a/components/interpreter/defines.hpp +++ b/components/interpreter/defines.hpp @@ -5,9 +5,9 @@ #include "context.hpp" namespace Interpreter{ - std::string fixDefinesDialog(std::string text, Context& context); - std::string fixDefinesMsgBox(std::string text, Context& context); - std::string fixDefinesBook(std::string text, Context& context); + std::string fixDefinesDialog(const std::string& text, Context& context); + std::string fixDefinesMsgBox(const std::string& text, Context& context); + std::string fixDefinesBook(const std::string& text, Context& context); } #endif diff --git a/components/nif/data.cpp b/components/nif/data.cpp index 08c268dde..1de7d0966 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -154,7 +154,7 @@ void NiFloatData::read(NIFStream *nif) void NiPixelData::read(NIFStream *nif) { - nif->getInt(); // always 0 or 1 + fmt = (Format)nif->getUInt(); rmask = nif->getInt(); // usually 0xff gmask = nif->getInt(); // usually 0xff00 @@ -169,19 +169,23 @@ void NiPixelData::read(NIFStream *nif) mips = nif->getInt(); // Bytes per pixel, should be bpp * 8 - /*int bytes =*/ nif->getInt(); + /* int bytes = */ nif->getInt(); for(int i=0; igetInt(); - /*int y =*/ nif->getInt(); - /*int offset =*/ nif->getInt(); + Mipmap m; + m.width = nif->getUInt(); + m.height = nif->getUInt(); + m.dataOffset = nif->getUInt(); + mipmaps.push_back(m); } - // Skip the data + // Read the data unsigned int dataSize = nif->getInt(); - nif->skip(dataSize); + data.reserve(dataSize); + for (unsigned i=0; igetChar()); } void NiColorData::read(NIFStream *nif) diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 95f244129..89886b66f 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -105,9 +105,30 @@ public: class NiPixelData : public Record { public: + enum Format + { + NIPXFMT_RGB8, + NIPXFMT_RGBA8, + NIPXFMT_PAL8, + NIPXFMT_DXT1, + NIPXFMT_DXT3, + NIPXFMT_DXT5, + NIPXFMT_DXT5_ALT + }; + Format fmt; + unsigned int rmask, gmask, bmask, amask; int bpp, mips; + struct Mipmap + { + int width, height; + int dataOffset; + }; + std::vector mipmaps; + + std::vector data; + void read(NIFStream *nif); }; diff --git a/components/nif/effect.cpp b/components/nif/effect.cpp index 79cd10431..7947e301d 100644 --- a/components/nif/effect.cpp +++ b/components/nif/effect.cpp @@ -19,14 +19,20 @@ void NiTextureEffect::read(NIFStream *nif) { NiDynamicEffect::read(nif); - /* - 3 x Vector4 = [1,0,0,0] - int = 2 - int = 0 or 3 - int = 2 - int = 2 - */ - nif->skip(16*4); + // Model Projection Matrix + nif->skip(3 * 3 * sizeof(float)); + + // Model Projection Transform + nif->skip(3 * sizeof(float)); + + // Texture Filtering + nif->skip(4); + + clamp = nif->getUInt(); + + textureType = (TextureType)nif->getUInt(); + + coordGenType = (CoordGenType)nif->getUInt(); texture.read(nif); diff --git a/components/nif/effect.hpp b/components/nif/effect.hpp index 02647e444..015809a68 100644 --- a/components/nif/effect.hpp +++ b/components/nif/effect.hpp @@ -70,6 +70,26 @@ struct NiSpotLight : public NiPointLight struct NiTextureEffect : NiDynamicEffect { NiSourceTexturePtr texture; + unsigned int clamp; + + enum TextureType + { + Projected_Light = 0, + Projected_Shadow = 1, + Environment_Map = 2, + Fog_Map = 3 + }; + TextureType textureType; + + enum CoordGenType + { + World_Parallel = 0, + World_Perspective, + Sphere_Map, + Specular_Cube_Map, + Diffuse_Cube_Map + }; + CoordGenType coordGenType; void read(NIFStream *nif); void post(NIFFile *nif); diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 19afe49d6..9ac544c42 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -266,6 +266,11 @@ void BulletNifLoader::handleNiTriShape(const Nif::NiTriShape *shape, int flags, if (!shape->skin.empty()) isAnimated = false; + if (shape->data.empty()) + return; + if (shape->data->triangles->empty()) + return; + if (isAnimated) { if (!mCompoundShape) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 8f4076884..ab6bfcff3 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -6,6 +6,7 @@ #include #include #include +#include // resource #include @@ -36,6 +37,7 @@ #include #include +#include #include #include #include @@ -439,6 +441,81 @@ namespace NifOsg return lod; } + osg::ref_ptr handleSourceTexture(const Nif::NiSourceTexture* st, Resource::ImageManager* imageManager) + { + if (!st) + return NULL; + + osg::ref_ptr image; + if (!st->external && !st->data.empty()) + { + image = handleInternalTexture(st->data.getPtr()); + } + else + { + std::string filename = Misc::ResourceHelpers::correctTexturePath(st->filename, imageManager->getVFS()); + image = imageManager->getImage(filename); + } + return image; + } + + void handleEffect(const Nif::Node* nifNode, osg::Node* node, Resource::ImageManager* imageManager) + { + if (nifNode->recType != Nif::RC_NiTextureEffect) + { + std::cerr << "Unhandled effect " << nifNode->recName << " in " << mFilename << std::endl; + return; + } + + const Nif::NiTextureEffect* textureEffect = static_cast(nifNode); + if (textureEffect->textureType != Nif::NiTextureEffect::Environment_Map) + { + std::cerr << "Unhandled NiTextureEffect type " << textureEffect->textureType << std::endl; + return; + } + + osg::ref_ptr texGen (new osg::TexGen); + switch (textureEffect->coordGenType) + { + case Nif::NiTextureEffect::World_Parallel: + texGen->setMode(osg::TexGen::OBJECT_LINEAR); + break; + case Nif::NiTextureEffect::World_Perspective: + texGen->setMode(osg::TexGen::EYE_LINEAR); + break; + case Nif::NiTextureEffect::Sphere_Map: + texGen->setMode(osg::TexGen::SPHERE_MAP); + break; + default: + std::cerr << "Unhandled NiTextureEffect coordGenType " << textureEffect->coordGenType << std::endl; + return; + } + + osg::ref_ptr texture2d (new osg::Texture2D(handleSourceTexture(textureEffect->texture.getPtr(), imageManager))); + texture2d->setName("envMap"); + unsigned int clamp = static_cast(textureEffect->clamp); + int wrapT = (clamp) & 0x1; + int wrapS = (clamp >> 1) & 0x1; + texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP); + texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP); + + osg::ref_ptr texEnv = new osg::TexEnvCombine; + texEnv->setCombine_Alpha(osg::TexEnvCombine::REPLACE); + texEnv->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS); + texEnv->setCombine_RGB(osg::TexEnvCombine::ADD); + texEnv->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); + texEnv->setSource1_RGB(osg::TexEnvCombine::TEXTURE); + + int texUnit = 3; // FIXME + + osg::StateSet* stateset = node->getOrCreateStateSet(); + stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(texUnit, texGen, osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON); + + stateset->addUniform(new osg::Uniform("envMapColor", osg::Vec4f(1,1,1,1))); + } + osg::ref_ptr handleNode(const Nif::Node* nifNode, osg::Group* parentNode, Resource::ImageManager* imageManager, std::vector boundTextures, int animflags, int particleflags, bool skipMeshes, TextKeyMap* textKeys, osg::Node* rootNode=NULL) { @@ -582,13 +659,18 @@ namespace NifOsg const Nif::NiNode *ninode = dynamic_cast(nifNode); if(ninode) { + const Nif::NodeList &effects = ninode->effects; + for (size_t i = 0; i < effects.length(); ++i) + { + if (!effects[i].empty()) + handleEffect(effects[i].getPtr(), node, imageManager); + } + const Nif::NodeList &children = ninode->children; for(size_t i = 0;i < children.length();++i) { if(!children[i].empty()) - { handleNode(children[i].getPtr(), node, imageManager, boundTextures, animflags, particleflags, skipMeshes, textKeys, rootNode); - } } } @@ -707,8 +789,7 @@ namespace NifOsg wrapT = inherit->getWrap(osg::Texture2D::WRAP_T); } - std::string filename = Misc::ResourceHelpers::correctTexturePath(st->filename, imageManager->getVFS()); - osg::ref_ptr texture (new osg::Texture2D(imageManager->getImage(filename))); + osg::ref_ptr texture (new osg::Texture2D(handleSourceTexture(st.getPtr(), imageManager))); texture->setWrap(osg::Texture::WRAP_S, wrapS); texture->setWrap(osg::Texture::WRAP_T, wrapT); textures.push_back(texture); @@ -962,7 +1043,7 @@ namespace NifOsg if (uvSet >= (int)data->uvlist.size()) { std::cerr << "Warning: out of bounds UV set " << uvSet << " on TriShape \"" << triShape->name << "\" in " << mFilename << std::endl; - if (data->uvlist.size()) + if (!data->uvlist.empty()) geometry->setTexCoordArray(textureStage, data->uvlist[0]); continue; } @@ -1040,7 +1121,7 @@ namespace NifOsg triShapeToGeometry(triShape, morphGeom, parentNode, composite, boundTextures, animflags); const std::vector& morphs = morpher->data.getPtr()->mMorphs; - if (!morphs.size()) + if (morphs.empty()) return morphGeom; // Note we are not interested in morph 0, which just contains the original vertices for (unsigned int i = 1; i < morphs.size(); ++i) @@ -1210,6 +1291,195 @@ namespace NifOsg } } + osg::ref_ptr handleInternalTexture(const Nif::NiPixelData* pixelData) + { + osg::ref_ptr image (new osg::Image); + + GLenum pixelformat = 0; + switch (pixelData->fmt) + { + case Nif::NiPixelData::NIPXFMT_RGB8: + pixelformat = GL_RGB; + break; + case Nif::NiPixelData::NIPXFMT_RGBA8: + pixelformat = GL_RGBA; + break; + default: + std::cerr << "Unhandled internal pixel format " << pixelData->fmt << " in " << mFilename << std::endl; + return NULL; + } + + if (pixelData->mipmaps.empty()) + return NULL; + + unsigned int width = 0; + unsigned int height = 0; + + std::vector mipmapVector; + for (unsigned int i=0; imipmaps.size()-3; ++i) + { + const Nif::NiPixelData::Mipmap& mip = pixelData->mipmaps[i]; + + size_t mipSize = mip.height * mip.width * pixelData->bpp / 8; + if (mipSize + mip.dataOffset > pixelData->data.size()) + { + std::cerr << "Internal texture's mipmap data out of bounds" << std::endl; + return NULL; + } + + if (i != 0) + mipmapVector.push_back(mip.dataOffset); + else + { + width = mip.width; + height = mip.height; + } + } + + if (width <= 0 || height <= 0) + { + std::cerr << "Width and height must be non zero " << std::endl; + return NULL; + } + + unsigned char* data = new unsigned char[pixelData->data.size()]; + memcpy(data, &pixelData->data[0], pixelData->data.size()); + + image->setImage(width, height, 1, pixelformat, pixelformat, GL_UNSIGNED_BYTE, data, osg::Image::USE_NEW_DELETE); + image->setMipmapLevels(mipmapVector); + image->flipVertical(); + + return image; + } + + void handleTextureProperty(const Nif::NiTexturingProperty* texprop, osg::StateSet* stateset, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, std::vector& boundTextures, int animflags) + { + if (!boundTextures.empty()) + { + // overriding a parent NiTexturingProperty, so remove what was previously bound + for (unsigned int i=0; isetTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF); + boundTextures.clear(); + } + + for (int i=0; itextures[i].inUse) + { + switch(i) + { + //These are handled later on + case Nif::NiTexturingProperty::BaseTexture: + case Nif::NiTexturingProperty::GlowTexture: + case Nif::NiTexturingProperty::DarkTexture: + case Nif::NiTexturingProperty::BumpTexture: + case Nif::NiTexturingProperty::DetailTexture: + break; + case Nif::NiTexturingProperty::GlossTexture: + { + std::cerr << "NiTexturingProperty::GlossTexture in " << mFilename << " not currently used." << std::endl; + continue; + } + case Nif::NiTexturingProperty::DecalTexture: + { + std::cerr << "NiTexturingProperty::DecalTexture in " << mFilename << " not currently used." << std::endl; + continue; + } + default: + { + std::cerr << "Warning: unhandled texture stage " << i << " in " << mFilename << std::endl; + continue; + } + } + + const Nif::NiTexturingProperty::Texture& tex = texprop->textures[i]; + if(tex.texture.empty()) + { + std::cerr << "Warning: texture layer " << i << " is in use but empty in " << mFilename << std::endl; + continue; + } + const Nif::NiSourceTexture *st = tex.texture.getPtr(); + osg::ref_ptr image = handleSourceTexture(st, imageManager); + + unsigned int clamp = static_cast(tex.clamp); + int wrapT = (clamp) & 0x1; + int wrapS = (clamp >> 1) & 0x1; + + // create a new texture, will later attempt to share using the SharedStateManager + osg::ref_ptr texture2d (new osg::Texture2D(image)); + texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP); + texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP); + + int texUnit = boundTextures.size(); + + stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); + + if (i == Nif::NiTexturingProperty::GlowTexture) + { + osg::TexEnvCombine* texEnv = new osg::TexEnvCombine; + texEnv->setCombine_Alpha(osg::TexEnvCombine::REPLACE); + texEnv->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS); + texEnv->setCombine_RGB(osg::TexEnvCombine::ADD); + texEnv->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); + texEnv->setSource1_RGB(osg::TexEnvCombine::TEXTURE); + + stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON); + } + else if (i == Nif::NiTexturingProperty::DarkTexture) + { + osg::TexEnv* texEnv = new osg::TexEnv; + texEnv->setMode(osg::TexEnv::MODULATE); + stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON); + } + else if (i == Nif::NiTexturingProperty::DetailTexture) + { + osg::TexEnvCombine* texEnv = new osg::TexEnvCombine; + texEnv->setScale_RGB(2.f); + texEnv->setCombine_Alpha(GL_MODULATE); + texEnv->setOperand0_Alpha(GL_SRC_ALPHA); + texEnv->setOperand1_Alpha(GL_SRC_ALPHA); + texEnv->setSource0_Alpha(GL_PREVIOUS_ARB); + texEnv->setSource1_Alpha(GL_TEXTURE); + texEnv->setCombine_RGB(GL_MODULATE); + texEnv->setOperand0_RGB(GL_SRC_COLOR); + texEnv->setOperand1_RGB(GL_SRC_COLOR); + texEnv->setSource0_RGB(GL_PREVIOUS_ARB); + texEnv->setSource1_RGB(GL_TEXTURE); + stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON); + } + else if (i == Nif::NiTexturingProperty::BumpTexture) + { + // Set this texture to Off by default since we can't render it with the fixed-function pipeline + stateset->setTextureMode(texUnit, GL_TEXTURE_2D, osg::StateAttribute::OFF); + } + + switch (i) + { + case Nif::NiTexturingProperty::BaseTexture: + texture2d->setName("diffuseMap"); + break; + case Nif::NiTexturingProperty::BumpTexture: + texture2d->setName("normalMap"); + break; + case Nif::NiTexturingProperty::GlowTexture: + texture2d->setName("emissiveMap"); + break; + case Nif::NiTexturingProperty::DarkTexture: + texture2d->setName("darkMap"); + break; + case Nif::NiTexturingProperty::DetailTexture: + texture2d->setName("detailMap"); + break; + default: + break; + } + + boundTextures.push_back(tex.uvSet); + } + } + handleTextureControllers(texprop, composite, imageManager, stateset, animflags); + } + void handleProperty(const Nif::Property *property, osg::Node *node, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, std::vector& boundTextures, int animflags) { @@ -1283,115 +1553,7 @@ namespace NifOsg { const Nif::NiTexturingProperty* texprop = static_cast(property); osg::StateSet* stateset = node->getOrCreateStateSet(); - - if (boundTextures.size()) - { - // overriding a parent NiTexturingProperty, so remove what was previously bound - for (unsigned int i=0; isetTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF); - boundTextures.clear(); - } - - for (int i=0; itextures[i].inUse) - { - switch(i) - { - //These are handled later on - case Nif::NiTexturingProperty::BaseTexture: - case Nif::NiTexturingProperty::GlowTexture: - case Nif::NiTexturingProperty::DarkTexture: - case Nif::NiTexturingProperty::DetailTexture: - break; - case Nif::NiTexturingProperty::GlossTexture: - { - std::cerr << "NiTexturingProperty::GlossTexture in " << mFilename << " not currently used." << std::endl; - continue; - } - case Nif::NiTexturingProperty::BumpTexture: - { - std::cerr << "NiTexturingProperty::BumpTexture in " << mFilename << " not currently used." << std::endl; - continue; - } - case Nif::NiTexturingProperty::DecalTexture: - { - std::cerr << "NiTexturingProperty::DecalTexture in " << mFilename << " not currently used." << std::endl; - continue; - } - default: - { - std::cerr << "Warning: unhandled texture stage " << i << " in " << mFilename << std::endl; - continue; - } - } - - const Nif::NiTexturingProperty::Texture& tex = texprop->textures[i]; - if(tex.texture.empty()) - { - std::cerr << "Warning: texture layer " << i << " is in use but empty in " << mFilename << std::endl; - continue; - } - const Nif::NiSourceTexture *st = tex.texture.getPtr(); - if (!st->external) - { - std::cerr << "Warning: unhandled internal texture in " << mFilename << std::endl; - continue; - } - - std::string filename = Misc::ResourceHelpers::correctTexturePath(st->filename, imageManager->getVFS()); - - unsigned int clamp = static_cast(tex.clamp); - int wrapT = (clamp) & 0x1; - int wrapS = (clamp >> 1) & 0x1; - - // create a new texture, will later attempt to share using the SharedStateManager - osg::ref_ptr texture2d (new osg::Texture2D(imageManager->getImage(filename))); - texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP); - texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP); - - int texUnit = boundTextures.size(); - - stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); - - if (i == Nif::NiTexturingProperty::GlowTexture) - { - osg::TexEnvCombine* texEnv = new osg::TexEnvCombine; - texEnv->setCombine_Alpha(osg::TexEnvCombine::REPLACE); - texEnv->setSource0_Alpha(osg::TexEnvCombine::PREVIOUS); - texEnv->setCombine_RGB(osg::TexEnvCombine::ADD); - texEnv->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); - texEnv->setSource1_RGB(osg::TexEnvCombine::TEXTURE); - - stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON); - } - else if (i == Nif::NiTexturingProperty::DarkTexture) - { - osg::TexEnv* texEnv = new osg::TexEnv; - texEnv->setMode(osg::TexEnv::MODULATE); - stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON); - } - else if (i == Nif::NiTexturingProperty::DetailTexture) - { - osg::TexEnvCombine* texEnv = new osg::TexEnvCombine; - texEnv->setScale_RGB(2.f); - texEnv->setCombine_Alpha(GL_MODULATE); - texEnv->setOperand0_Alpha(GL_SRC_ALPHA); - texEnv->setOperand1_Alpha(GL_SRC_ALPHA); - texEnv->setSource0_Alpha(GL_PREVIOUS_ARB); - texEnv->setSource1_Alpha(GL_TEXTURE); - texEnv->setCombine_RGB(GL_MODULATE); - texEnv->setOperand0_RGB(GL_SRC_COLOR); - texEnv->setOperand1_RGB(GL_SRC_COLOR); - texEnv->setSource0_RGB(GL_PREVIOUS_ARB); - texEnv->setSource1_RGB(GL_TEXTURE); - stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON); - } - - boundTextures.push_back(tex.uvSet); - } - handleTextureControllers(texprop, composite, imageManager, stateset, animflags); - } + handleTextureProperty(texprop, stateset, composite, imageManager, boundTextures, animflags); break; } // unused by mw diff --git a/components/nifosg/particle.cpp b/components/nifosg/particle.cpp index 500339722..935442c84 100644 --- a/components/nifosg/particle.cpp +++ b/components/nifosg/particle.cpp @@ -261,10 +261,10 @@ void Emitter::emitParticles(double dt) osg::Matrix worldToPs; // maybe this could be optimized by halting at the lowest common ancestor of the particle and emitter nodes - osg::MatrixList worldMats = getParticleSystem()->getWorldMatrices(); - if (!worldMats.empty()) + osg::NodePathList partsysNodePaths = getParticleSystem()->getParentalNodePaths(); + if (!partsysNodePaths.empty()) { - const osg::Matrix psToWorld = worldMats[0]; + osg::Matrix psToWorld = osg::computeLocalToWorld(partsysNodePaths[0]); worldToPs = osg::Matrix::inverse(psToWorld); } diff --git a/components/nifosg/particle.hpp b/components/nifosg/particle.hpp index a1ed3f3d0..1e1714369 100644 --- a/components/nifosg/particle.hpp +++ b/components/nifosg/particle.hpp @@ -7,7 +7,6 @@ #include #include -#include #include #include diff --git a/components/resource/imagemanager.cpp b/components/resource/imagemanager.cpp index 870fa370b..ea3adc704 100644 --- a/components/resource/imagemanager.cpp +++ b/components/resource/imagemanager.cpp @@ -121,6 +121,7 @@ namespace Resource } osg::Image* image = result.getImage(); + image->setFileName(normalized); if (!checkSupported(image, filename)) { mCache->addEntryToObjectCache(normalized, mWarningImage); diff --git a/components/resource/objectcache.cpp b/components/resource/objectcache.cpp index a0a3cf3a5..7caf5366c 100644 --- a/components/resource/objectcache.cpp +++ b/components/resource/objectcache.cpp @@ -14,6 +14,7 @@ #include "objectcache.hpp" #include +#include namespace Resource { @@ -119,4 +120,22 @@ void ObjectCache::releaseGLObjects(osg::State* state) } } +void ObjectCache::accept(osg::NodeVisitor &nv) +{ + OpenThreads::ScopedLock lock(_objectCacheMutex); + + for(ObjectCacheMap::iterator itr = _objectCache.begin(); + itr != _objectCache.end(); + ++itr) + { + osg::Object* object = itr->second.first.get(); + if (object) + { + osg::Node* node = dynamic_cast(object); + if (node) + node->accept(nv); + } + } +} + } diff --git a/components/resource/objectcache.hpp b/components/resource/objectcache.hpp index 79ebabd5b..82aca76f0 100644 --- a/components/resource/objectcache.hpp +++ b/components/resource/objectcache.hpp @@ -27,6 +27,7 @@ namespace osg { class Object; class State; + class NodeVisitor; } namespace Resource { @@ -66,6 +67,9 @@ class ObjectCache : public osg::Referenced /** call releaseGLObjects on all objects attached to the object cache.*/ void releaseGLObjects(osg::State* state); + /** call node->accept(nv); for all nodes in the objectCache. */ + void accept(osg::NodeVisitor& nv); + protected: virtual ~ObjectCache(); diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 85a4afa30..113406ef6 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -10,8 +10,6 @@ #include -#include - #include #include @@ -24,6 +22,9 @@ #include #include +#include +#include + #include "imagemanager.hpp" #include "niffilemanager.hpp" #include "objectcache.hpp" @@ -68,10 +69,10 @@ namespace void transformInitialParticles(osgParticle::ParticleSystem* partsys, osg::Node* node) { - osg::MatrixList mats = node->getWorldMatrices(); - if (mats.empty()) + osg::NodePathList nodepaths = node->getParentalNodePaths(); + if (nodepaths.empty()) return; - osg::Matrixf worldMat = mats[0]; + osg::Matrixf worldMat = osg::computeLocalToWorld(nodepaths[0]); worldMat.orthoNormalize(worldMat); // scale is already applied on the particle node for (int i=0; inumParticles(); ++i) { @@ -139,16 +140,16 @@ namespace Resource virtual void apply(osg::Node& node) { if (osgFX::Effect* effect = dynamic_cast(&node)) - apply(*effect); + applyEffect(*effect); osg::StateSet* stateset = node.getStateSet(); if (stateset) - apply(stateset); + applyStateSet(stateset); traverse(node); } - void apply(osgFX::Effect& effect) + void applyEffect(osgFX::Effect& effect) { for (int i =0; igetNumPasses(); ++pass) { if (tech->getPassStateSet(pass)) - apply(tech->getPassStateSet(pass)); + applyStateSet(tech->getPassStateSet(pass)); } } } @@ -165,40 +166,33 @@ namespace Resource { osg::StateSet* stateset = geode.getStateSet(); if (stateset) - apply(stateset); + applyStateSet(stateset); for (unsigned int i=0; igetStateSet(); if (stateset) - apply(stateset); + applyStateSet(stateset); } } - void apply(osg::StateSet* stateset) + void applyStateSet(osg::StateSet* stateset) { const osg::StateSet::TextureAttributeList& texAttributes = stateset->getTextureAttributeList(); for(unsigned int unit=0;unitgetTextureAttribute(unit, osg::StateAttribute::TEXTURE); if (texture) - apply(texture); + applyStateAttribute(texture); } } - void apply(osg::StateAttribute* attr) + void applyStateAttribute(osg::StateAttribute* attr) { osg::Texture* tex = attr->asTexture(); if (tex) { - if (tex->getUserDataContainer()) - { - const std::vector& descriptions = tex->getUserDataContainer()->getDescriptions(); - if (std::find(descriptions.begin(), descriptions.end(), "dont_override_filter") != descriptions.end()) - return; - } - tex->setFilter(osg::Texture::MIN_FILTER, mMinFilter); tex->setFilter(osg::Texture::MAG_FILTER, mMagFilter); tex->setMaxAnisotropy(mMaxAnisotropy); @@ -214,6 +208,12 @@ namespace Resource SceneManager::SceneManager(const VFS::Manager *vfs, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager) : ResourceManager(vfs) + , mShaderManager(new Shader::ShaderManager) + , mForceShaders(false) + , mClampLighting(true) + , mForcePerPixelLighting(false) + , mAutoUseNormalMaps(false) + , mAutoUseSpecularMaps(false) , mInstanceCache(new MultiObjectCache) , mImageManager(imageManager) , mNifFileManager(nifFileManager) @@ -225,11 +225,81 @@ namespace Resource { } + void SceneManager::setForceShaders(bool force) + { + mForceShaders = force; + } + + bool SceneManager::getForceShaders() const + { + return mForceShaders; + } + + void SceneManager::recreateShaders(osg::ref_ptr node) + { + Shader::ShaderVisitor shaderVisitor(*mShaderManager.get(), *mImageManager, "objects_vertex.glsl", "objects_fragment.glsl"); + shaderVisitor.setForceShaders(mForceShaders); + shaderVisitor.setClampLighting(mClampLighting); + shaderVisitor.setForcePerPixelLighting(mForcePerPixelLighting); + shaderVisitor.setAllowedToModifyStateSets(false); + node->accept(shaderVisitor); + } + + void SceneManager::setClampLighting(bool clamp) + { + mClampLighting = clamp; + } + + bool SceneManager::getClampLighting() const + { + return mClampLighting; + } + + void SceneManager::setForcePerPixelLighting(bool force) + { + mForcePerPixelLighting = force; + } + + bool SceneManager::getForcePerPixelLighting() const + { + return mForcePerPixelLighting; + } + + void SceneManager::setAutoUseNormalMaps(bool use) + { + mAutoUseNormalMaps = use; + } + + void SceneManager::setNormalMapPattern(const std::string &pattern) + { + mNormalMapPattern = pattern; + } + + void SceneManager::setAutoUseSpecularMaps(bool use) + { + mAutoUseSpecularMaps = use; + } + + void SceneManager::setSpecularMapPattern(const std::string &pattern) + { + mSpecularMapPattern = pattern; + } + SceneManager::~SceneManager() { // this has to be defined in the .cpp file as we can't delete incomplete types } + Shader::ShaderManager &SceneManager::getShaderManager() + { + return *mShaderManager.get(); + } + + void SceneManager::setShaderPath(const std::string &path) + { + mShaderManager->setShaderPath(path); + } + /// @brief Callback to read image files from the VFS. class ImageReadCallback : public osgDB::ReadFileCallback { @@ -338,6 +408,16 @@ namespace Resource SetFilterSettingsControllerVisitor setFilterSettingsControllerVisitor(mMinFilter, mMagFilter, mMaxAnisotropy); loaded->accept(setFilterSettingsControllerVisitor); + Shader::ShaderVisitor shaderVisitor(*mShaderManager.get(), *mImageManager, "objects_vertex.glsl", "objects_fragment.glsl"); + shaderVisitor.setForceShaders(mForceShaders); + shaderVisitor.setClampLighting(mClampLighting); + shaderVisitor.setForcePerPixelLighting(mForcePerPixelLighting); + shaderVisitor.setAutoUseNormalMaps(mAutoUseNormalMaps); + shaderVisitor.setNormalMapPattern(mNormalMapPattern); + shaderVisitor.setAutoUseSpecularMaps(mAutoUseSpecularMaps); + shaderVisitor.setSpecularMapPattern(mSpecularMapPattern); + loaded->accept(shaderVisitor); + // share state mSharedStateMutex.lock(); osgDB::Registry::instance()->getOrCreateSharedStateManager()->share(loaded.get()); @@ -401,6 +481,7 @@ namespace Resource void SceneManager::releaseGLObjects(osg::State *state) { mCache->releaseGLObjects(state); + mInstanceCache->releaseGLObjects(state); } void SceneManager::setIncrementalCompileOperation(osgUtil::IncrementalCompileOperation *ico) @@ -425,8 +506,7 @@ namespace Resource } void SceneManager::setFilterSettings(const std::string &magfilter, const std::string &minfilter, - const std::string &mipmap, int maxAnisotropy, - osgViewer::Viewer *viewer) + const std::string &mipmap, int maxAnisotropy) { osg::Texture::FilterMode min = osg::Texture::LINEAR; osg::Texture::FilterMode mag = osg::Texture::LINEAR; @@ -458,23 +538,15 @@ namespace Resource min = osg::Texture::LINEAR_MIPMAP_LINEAR; } - if(viewer) viewer->stopThreading(); - mMinFilter = min; mMagFilter = mag; mMaxAnisotropy = std::max(1, maxAnisotropy); - mCache->clear(); - SetFilterSettingsControllerVisitor setFilterSettingsControllerVisitor (mMinFilter, mMagFilter, mMaxAnisotropy); SetFilterSettingsVisitor setFilterSettingsVisitor (mMinFilter, mMagFilter, mMaxAnisotropy); - if (viewer && viewer->getSceneData()) - { - viewer->getSceneData()->accept(setFilterSettingsControllerVisitor); - viewer->getSceneData()->accept(setFilterSettingsVisitor); - } - if(viewer) viewer->startThreading(); + mCache->accept(setFilterSettingsVisitor); + mCache->accept(setFilterSettingsControllerVisitor); } void SceneManager::applyFilterSettings(osg::Texture *tex) diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 8357e63cd..00eb68e76 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -21,9 +22,9 @@ namespace osgUtil class IncrementalCompileOperation; } -namespace osgViewer +namespace Shader { - class Viewer; + class ShaderManager; } namespace Resource @@ -39,6 +40,35 @@ namespace Resource SceneManager(const VFS::Manager* vfs, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager); ~SceneManager(); + Shader::ShaderManager& getShaderManager(); + + /// Re-create shaders for this node, need to call this if texture stages or vertex color mode have changed. + void recreateShaders(osg::ref_ptr node); + + /// @see ShaderVisitor::setForceShaders + void setForceShaders(bool force); + bool getForceShaders() const; + + /// @see ShaderVisitor::setClampLighting + void setClampLighting(bool clamp); + bool getClampLighting() const; + + /// @see ShaderVisitor::setForcePerPixelLighting + void setForcePerPixelLighting(bool force); + bool getForcePerPixelLighting() const; + + /// @see ShaderVisitor::setAutoUseNormalMaps + void setAutoUseNormalMaps(bool use); + + /// @see ShaderVisitor::setNormalMapPattern + void setNormalMapPattern(const std::string& pattern); + + void setAutoUseSpecularMaps(bool use); + + void setSpecularMapPattern(const std::string& pattern); + + void setShaderPath(const std::string& path); + /// Get a read-only copy of this scene "template" /// @note If the given filename does not exist or fails to load, an error marker mesh will be used instead. /// If even the error marker mesh can not be found, an exception is thrown. @@ -83,10 +113,9 @@ namespace Resource /// @param mask The node mask to apply to loaded particle system nodes. void setParticleSystemMask(unsigned int mask); - /// @param viewer used to apply the new filter settings to the existing scene graph. If there is no scene yet, you can pass a NULL viewer. + /// @warning It is unsafe to call this method while the draw thread is using textures! call Viewer::stopThreading first. void setFilterSettings(const std::string &magfilter, const std::string &minfilter, - const std::string &mipmap, int maxAnisotropy, - osgViewer::Viewer *viewer); + const std::string &mipmap, int maxAnisotropy); /// Apply filter settings to the given texture. Note, when loading an object through this scene manager (i.e. calling getTemplate or createInstance) /// the filter settings are applied automatically. This method is provided for textures that were created outside of the SceneManager. @@ -103,6 +132,15 @@ namespace Resource osg::ref_ptr createInstance(const std::string& name); + std::auto_ptr mShaderManager; + bool mForceShaders; + bool mClampLighting; + bool mForcePerPixelLighting; + bool mAutoUseNormalMaps; + std::string mNormalMapPattern; + bool mAutoUseSpecularMaps; + std::string mSpecularMapPattern; + osg::ref_ptr mInstanceCache; OpenThreads::Mutex mSharedStateMutex; diff --git a/components/sceneutil/attach.cpp b/components/sceneutil/attach.cpp index d8cfa428a..38330901d 100644 --- a/components/sceneutil/attach.cpp +++ b/components/sceneutil/attach.cpp @@ -56,9 +56,18 @@ namespace SceneUtil CopyRigVisitor copyVisitor(handle, filter); toAttach->accept(copyVisitor); - master->asGroup()->addChild(handle); - - return handle; + if (handle->getNumChildren() == 1) + { + osg::ref_ptr newHandle = handle->getChild(0); + handle->removeChild(newHandle); + master->asGroup()->addChild(newHandle); + return newHandle; + } + else + { + master->asGroup()->addChild(handle); + return handle; + } } else { diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index 64448e73e..da29647c2 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -45,6 +45,8 @@ namespace SceneUtil virtual void apply(osg::State& state) const { + if (mLights.empty()) + return; osg::Matrix modelViewMatrix = state.getModelViewMatrix(); state.applyModelViewMatrix(state.getInitialViewMatrix()); @@ -192,19 +194,28 @@ namespace SceneUtil return found->second; else { - + osg::ref_ptr stateset = new osg::StateSet; std::vector > lights; for (unsigned int i=0; imLightSource->getLight(frameNum)); + } + // the first light state attribute handles the actual state setting for all lights + // it's best to batch these up so that we don't need to touch the modelView matrix more than necessary osg::ref_ptr attr = new LightStateAttribute(mStartLight, lights); - - osg::ref_ptr stateset = new osg::StateSet; - // don't use setAttributeAndModes, that does not support light indices! stateset->setAttribute(attr, osg::StateAttribute::ON); stateset->setAssociatedModes(attr, osg::StateAttribute::ON); + // need to push some dummy attributes to ensure proper state tracking + // lights need to reset to their default when the StateSet is popped + for (unsigned int i=1; i dummy = new LightStateAttribute(mStartLight+i, std::vector >()); + stateset->setAttribute(dummy, osg::StateAttribute::ON); + } + stateSetCache.insert(std::make_pair(hash, stateset)); return stateset; } @@ -242,6 +253,17 @@ namespace SceneUtil void LightManager::setStartLight(int start) { mStartLight = start; + + // Set default light state to zero + for (int i=start; i<8; ++i) + { + osg::ref_ptr defaultLight (new osg::Light(i)); + defaultLight->setAmbient(osg::Vec4()); + defaultLight->setDiffuse(osg::Vec4()); + defaultLight->setSpecular(osg::Vec4()); + defaultLight->setConstantAttenuation(0.f); + getOrCreateStateSet()->setAttributeAndModes(defaultLight, osg::StateAttribute::OFF); + } } int LightManager::getStartLight() const @@ -329,7 +351,7 @@ namespace SceneUtil mLightList.push_back(&l); } } - if (mLightList.size()) + if (!mLightList.empty()) { unsigned int maxLights = static_cast (8 - mLightManager->getStartLight()); diff --git a/components/sceneutil/lightutil.hpp b/components/sceneutil/lightutil.hpp index 810ffa318..d6c970340 100644 --- a/components/sceneutil/lightutil.hpp +++ b/components/sceneutil/lightutil.hpp @@ -8,7 +8,7 @@ namespace osg namespace ESM { - class Light; + struct Light; } namespace SceneUtil diff --git a/components/sceneutil/skeleton.cpp b/components/sceneutil/skeleton.cpp index d1299c058..f311de246 100644 --- a/components/sceneutil/skeleton.cpp +++ b/components/sceneutil/skeleton.cpp @@ -38,6 +38,8 @@ Skeleton::Skeleton() , mNeedToUpdateBoneMatrices(true) , mActive(true) , mLastFrameNumber(0) + , mTraversedEvenFrame(false) + , mTraversedOddFrame(false) { } @@ -48,6 +50,8 @@ Skeleton::Skeleton(const Skeleton ©, const osg::CopyOp ©op) , mNeedToUpdateBoneMatrices(true) , mActive(copy.mActive) , mLastFrameNumber(0) + , mTraversedEvenFrame(false) + , mTraversedOddFrame(false) { } @@ -111,6 +115,11 @@ void Skeleton::updateBoneMatrices(osg::NodeVisitor* nv) mLastFrameNumber = nv->getTraversalNumber(); + if (mLastFrameNumber % 2 == 0) + mTraversedEvenFrame = true; + else + mTraversedOddFrame = true; + if (mNeedToUpdateBoneMatrices) { if (mRootBone.get()) @@ -140,7 +149,7 @@ void Skeleton::traverse(osg::NodeVisitor& nv) if (!getActive() && nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR // need to process at least 2 frames before shutting off update, since we need to have both frame-alternating RigGeometries initialized // this would be more naturally handled if the double-buffering was implemented in RigGeometry itself rather than in a FrameSwitch decorator node - && mLastFrameNumber != 0 && mLastFrameNumber+2 <= nv.getTraversalNumber()) + && mLastFrameNumber != 0 && mTraversedEvenFrame && mTraversedOddFrame) return; osg::Group::traverse(nv); } diff --git a/components/sceneutil/skeleton.hpp b/components/sceneutil/skeleton.hpp index d98d36751..9ca1dd49d 100644 --- a/components/sceneutil/skeleton.hpp +++ b/components/sceneutil/skeleton.hpp @@ -69,6 +69,8 @@ namespace SceneUtil bool mActive; unsigned int mLastFrameNumber; + bool mTraversedEvenFrame; + bool mTraversedOddFrame; }; } diff --git a/components/sceneutil/workqueue.cpp b/components/sceneutil/workqueue.cpp index bb1d1f1f7..bbd9f4840 100644 --- a/components/sceneutil/workqueue.cpp +++ b/components/sceneutil/workqueue.cpp @@ -87,7 +87,7 @@ osg::ref_ptr WorkQueue::removeWorkItem() { mCondition.wait(&mMutex); } - if (mQueue.size()) + if (!mQueue.empty()) { osg::ref_ptr item = mQueue.front(); mQueue.pop(); diff --git a/components/sdlutil/sdlgraphicswindow.cpp b/components/sdlutil/sdlgraphicswindow.cpp index 66d53b096..c2cf2536e 100644 --- a/components/sdlutil/sdlgraphicswindow.cpp +++ b/components/sdlutil/sdlgraphicswindow.cpp @@ -91,7 +91,7 @@ void GraphicsWindowSDL2::init() SDL_Window *oldWin = SDL_GL_GetCurrentWindow(); SDL_GLContext oldCtx = SDL_GL_GetCurrentContext(); -#ifdef OPENGL_ES +#if defined(OPENGL_ES) || defined(ANDROID) SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 1); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp new file mode 100644 index 000000000..ce77f46dc --- /dev/null +++ b/components/shader/shadermanager.cpp @@ -0,0 +1,163 @@ +#include "shadermanager.hpp" + +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include + +namespace Shader +{ + + void ShaderManager::setShaderPath(const std::string &path) + { + mPath = path; + } + + bool parseIncludes(boost::filesystem::path shaderPath, std::string& source) + { + boost::replace_all(source, "\r\n", "\n"); + + std::set includedFiles; + size_t foundPos = 0; + int fileNumber = 1; + while ((foundPos = source.find("#include")) != std::string::npos) + { + size_t start = source.find('"', foundPos); + if (start == std::string::npos || start == source.size()-1) + { + std::cerr << "Invalid #include " << std::endl; + return false; + } + size_t end = source.find('"', start+1); + if (end == std::string::npos) + { + std::cerr << "Invalid #include " << std::endl; + return false; + } + std::string includeFilename = source.substr(start+1, end-(start+1)); + boost::filesystem::path includePath = shaderPath / includeFilename; + boost::filesystem::ifstream includeFstream; + includeFstream.open(includePath); + if (includeFstream.fail()) + { + std::cerr << "Failed to open " << includePath.string() << std::endl; + return false; + } + + std::stringstream buffer; + buffer << includeFstream.rdbuf(); + + // insert #line directives so we get correct line numbers in compiler errors + int includedFileNumber = fileNumber++; + + int lineNumber = std::count(source.begin(), source.begin() + foundPos, '\n'); + + std::stringstream toInsert; + toInsert << "#line 0 " << includedFileNumber << "\n" << buffer.str() << "\n#line " << lineNumber << " 0\n"; + + source.replace(foundPos, (end-foundPos+1), toInsert.str()); + + if (includedFiles.insert(includePath).second == false) + { + std::cerr << "Detected cyclic #includes" << std::endl; + return false; + } + } + return true; + } + + bool parseDefines(std::string& source, const ShaderManager::DefineMap& defines) + { + const char escapeCharacter = '@'; + size_t foundPos = 0; + while ((foundPos = source.find(escapeCharacter)) != std::string::npos) + { + size_t endPos = source.find_first_of(" \n\r()[].;", foundPos); + if (endPos == std::string::npos) + { + std::cerr << "Unexpected EOF" << std::endl; + return false; + } + std::string define = source.substr(foundPos+1, endPos - (foundPos+1)); + ShaderManager::DefineMap::const_iterator defineFound = defines.find(define); + if (defineFound == defines.end()) + { + std::cerr << "Undefined " << define << std::endl; + return false; + } + else + { + source.replace(foundPos, endPos-foundPos, defineFound->second); + } + } + return true; + } + + osg::ref_ptr ShaderManager::getShader(const std::string &shaderTemplate, const ShaderManager::DefineMap &defines, osg::Shader::Type shaderType) + { + OpenThreads::ScopedLock lock(mMutex); + + // read the template if we haven't already + TemplateMap::iterator templateIt = mShaderTemplates.find(shaderTemplate); + if (templateIt == mShaderTemplates.end()) + { + boost::filesystem::path p = (boost::filesystem::path(mPath) / shaderTemplate); + boost::filesystem::ifstream stream; + stream.open(p); + if (stream.fail()) + { + std::cerr << "Failed to open " << p.string() << std::endl; + return NULL; + } + std::stringstream buffer; + buffer << stream.rdbuf(); + + // parse includes + std::string source = buffer.str(); + if (!parseIncludes(boost::filesystem::path(mPath), source)) + return NULL; + + templateIt = mShaderTemplates.insert(std::make_pair(shaderTemplate, source)).first; + } + + ShaderMap::iterator shaderIt = mShaders.find(std::make_pair(shaderTemplate, defines)); + if (shaderIt == mShaders.end()) + { + std::string shaderSource = templateIt->second; + if (!parseDefines(shaderSource, defines)) + return NULL; + + osg::ref_ptr shader (new osg::Shader(shaderType)); + shader->setShaderSource(shaderSource); + // Assign a unique name to allow the SharedStateManager to compare shaders efficiently + static unsigned int counter = 0; + shader->setName(boost::lexical_cast(counter++)); + + shaderIt = mShaders.insert(std::make_pair(std::make_pair(shaderTemplate, defines), shader)).first; + } + return shaderIt->second; + } + + osg::ref_ptr ShaderManager::getProgram(osg::ref_ptr vertexShader, osg::ref_ptr fragmentShader) + { + OpenThreads::ScopedLock lock(mMutex); + ProgramMap::iterator found = mPrograms.find(std::make_pair(vertexShader, fragmentShader)); + if (found == mPrograms.end()) + { + osg::ref_ptr program (new osg::Program); + program->addShader(vertexShader); + program->addShader(fragmentShader); + found = mPrograms.insert(std::make_pair(std::make_pair(vertexShader, fragmentShader), program)).first; + } + return found->second; + } + +} diff --git a/components/shader/shadermanager.hpp b/components/shader/shadermanager.hpp new file mode 100644 index 000000000..5196dbe80 --- /dev/null +++ b/components/shader/shadermanager.hpp @@ -0,0 +1,55 @@ +#ifndef OPENMW_COMPONENTS_SHADERMANAGER_H +#define OPENMW_COMPONENTS_SHADERMANAGER_H + +#include +#include + +#include + +#include + +#include + +namespace Shader +{ + + /// @brief Reads shader template files and turns them into a concrete shader, based on a list of define's. + /// @par Shader templates can get the value of a define with the syntax @define. + class ShaderManager + { + public: + void setShaderPath(const std::string& path); + + typedef std::map DefineMap; + + /// Create or retrieve a shader instance. + /// @param shaderTemplate The filename of the shader template. + /// @param defines Define values that can be retrieved by the shader template. + /// @param shaderType The type of shader (usually vertex or fragment shader). + /// @note May return NULL on failure. + /// @note Thread safe. + osg::ref_ptr getShader(const std::string& shaderTemplate, const DefineMap& defines, osg::Shader::Type shaderType); + + osg::ref_ptr getProgram(osg::ref_ptr vertexShader, osg::ref_ptr fragmentShader); + + + private: + std::string mPath; + + // + typedef std::map TemplateMap; + TemplateMap mShaderTemplates; + + typedef std::pair MapKey; + typedef std::map > ShaderMap; + ShaderMap mShaders; + + typedef std::map, osg::ref_ptr >, osg::ref_ptr > ProgramMap; + ProgramMap mPrograms; + + OpenThreads::Mutex mMutex; + }; + +} + +#endif diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp new file mode 100644 index 000000000..b06ceafde --- /dev/null +++ b/components/shader/shadervisitor.cpp @@ -0,0 +1,363 @@ +#include "shadervisitor.hpp" + +#include + +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include + +#include "shadermanager.hpp" + +namespace Shader +{ + + ShaderVisitor::ShaderRequirements::ShaderRequirements() + : mShaderRequired(false) + , mColorMaterial(false) + , mVertexColorMode(GL_AMBIENT_AND_DIFFUSE) + , mMaterialOverridden(false) + , mTexStageRequiringTangents(-1) + { + } + + ShaderVisitor::ShaderRequirements::~ShaderRequirements() + { + + } + + ShaderVisitor::ShaderVisitor(ShaderManager& shaderManager, Resource::ImageManager& imageManager, const std::string &defaultVsTemplate, const std::string &defaultFsTemplate) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mForceShaders(false) + , mClampLighting(true) + , mForcePerPixelLighting(false) + , mAllowedToModifyStateSets(true) + , mAutoUseNormalMaps(false) + , mAutoUseSpecularMaps(false) + , mShaderManager(shaderManager) + , mImageManager(imageManager) + , mDefaultVsTemplate(defaultVsTemplate) + , mDefaultFsTemplate(defaultFsTemplate) + { + mRequirements.push_back(ShaderRequirements()); + } + + void ShaderVisitor::setForceShaders(bool force) + { + mForceShaders = force; + } + + void ShaderVisitor::setClampLighting(bool clamp) + { + mClampLighting = clamp; + } + + void ShaderVisitor::setForcePerPixelLighting(bool force) + { + mForcePerPixelLighting = force; + } + + void ShaderVisitor::apply(osg::Node& node) + { + if (node.getStateSet()) + { + pushRequirements(); + applyStateSet(node.getStateSet(), node); + traverse(node); + popRequirements(); + } + else + traverse(node); + } + + osg::StateSet* getWritableStateSet(osg::Node& node) + { + if (!node.getStateSet()) + return node.getOrCreateStateSet(); + + osg::ref_ptr newStateSet = osg::clone(node.getStateSet(), osg::CopyOp::SHALLOW_COPY); + node.setStateSet(newStateSet); + return newStateSet.get(); + } + + const char* defaultTextures[] = { "diffuseMap", "normalMap", "emissiveMap", "darkMap", "detailMap", "envMap", "specularMap" }; + bool isTextureNameRecognized(const std::string& name) + { + for (unsigned int i=0; i stateset, osg::Node& node) + { + osg::StateSet* writableStateSet = NULL; + if (mAllowedToModifyStateSets) + writableStateSet = node.getStateSet(); + const osg::StateSet::TextureAttributeList& texAttributes = stateset->getTextureAttributeList(); + if (!texAttributes.empty()) + { + const osg::Texture* diffuseMap = NULL; + const osg::Texture* normalMap = NULL; + const osg::Texture* specularMap = NULL; + for(unsigned int unit=0;unitgetTextureAttribute(unit, osg::StateAttribute::TEXTURE); + if (attr) + { + const osg::Texture* texture = attr->asTexture(); + if (texture) + { + std::string texName = texture->getName(); + if ((texName.empty() || !isTextureNameRecognized(texName)) && unit == 0) + texName = "diffuseMap"; + + if (!texName.empty()) + { + mRequirements.back().mTextures[unit] = texName; + if (texName == "normalMap") + { + mRequirements.back().mTexStageRequiringTangents = unit; + mRequirements.back().mShaderRequired = true; + if (!writableStateSet) + writableStateSet = getWritableStateSet(node); + // normal maps are by default off since the FFP can't render them, now that we'll use shaders switch to On + writableStateSet->setTextureMode(unit, GL_TEXTURE_2D, osg::StateAttribute::ON); + normalMap = texture; + } + if (texName == "diffuseMap") + diffuseMap = texture; + if (texName == "specularMap") + specularMap = texture; + } + else + std::cerr << "ShaderVisitor encountered unknown texture " << texture << std::endl; + } + } + } + + if (mAutoUseNormalMaps && diffuseMap != NULL && normalMap == NULL) + { + std::string normalMap = diffuseMap->getImage(0)->getFileName(); + boost::replace_last(normalMap, ".", mNormalMapPattern + "."); + if (mImageManager.getVFS()->exists(normalMap)) + { + osg::ref_ptr normalMapTex (new osg::Texture2D(mImageManager.getImage(normalMap))); + normalMapTex->setWrap(osg::Texture::WRAP_S, diffuseMap->getWrap(osg::Texture::WRAP_S)); + normalMapTex->setWrap(osg::Texture::WRAP_T, diffuseMap->getWrap(osg::Texture::WRAP_T)); + normalMapTex->setFilter(osg::Texture::MIN_FILTER, diffuseMap->getFilter(osg::Texture::MIN_FILTER)); + normalMapTex->setFilter(osg::Texture::MAG_FILTER, diffuseMap->getFilter(osg::Texture::MAG_FILTER)); + normalMapTex->setMaxAnisotropy(diffuseMap->getMaxAnisotropy()); + normalMapTex->setName("normalMap"); + + int unit = texAttributes.size(); + if (!writableStateSet) + writableStateSet = getWritableStateSet(node); + writableStateSet->setTextureAttributeAndModes(unit, normalMapTex, osg::StateAttribute::ON); + mRequirements.back().mTextures[unit] = "normalMap"; + mRequirements.back().mTexStageRequiringTangents = unit; + mRequirements.back().mShaderRequired = true; + } + } + if (mAutoUseSpecularMaps && diffuseMap != NULL && specularMap == NULL) + { + std::string specularMap = diffuseMap->getImage(0)->getFileName(); + boost::replace_last(specularMap, ".", mSpecularMapPattern + "."); + if (mImageManager.getVFS()->exists(specularMap)) + { + osg::ref_ptr specularMapTex (new osg::Texture2D(mImageManager.getImage(specularMap))); + specularMapTex->setWrap(osg::Texture::WRAP_S, diffuseMap->getWrap(osg::Texture::WRAP_S)); + specularMapTex->setWrap(osg::Texture::WRAP_T, diffuseMap->getWrap(osg::Texture::WRAP_T)); + specularMapTex->setFilter(osg::Texture::MIN_FILTER, diffuseMap->getFilter(osg::Texture::MIN_FILTER)); + specularMapTex->setFilter(osg::Texture::MAG_FILTER, diffuseMap->getFilter(osg::Texture::MAG_FILTER)); + specularMapTex->setMaxAnisotropy(diffuseMap->getMaxAnisotropy()); + specularMapTex->setName("specularMap"); + + int unit = texAttributes.size(); + if (!writableStateSet) + writableStateSet = getWritableStateSet(node); + writableStateSet->setTextureAttributeAndModes(unit, specularMapTex, osg::StateAttribute::ON); + mRequirements.back().mTextures[unit] = "specularMap"; + mRequirements.back().mShaderRequired = true; + } + } + } + + const osg::StateSet::AttributeList& attributes = stateset->getAttributeList(); + for (osg::StateSet::AttributeList::const_iterator it = attributes.begin(); it != attributes.end(); ++it) + { + if (it->first.first == osg::StateAttribute::MATERIAL) + { + if (!mRequirements.back().mMaterialOverridden || it->second.second & osg::StateAttribute::PROTECTED) + { + if (it->second.second & osg::StateAttribute::OVERRIDE) + mRequirements.back().mMaterialOverridden = true; + + const osg::Material* mat = static_cast(it->second.first.get()); + mRequirements.back().mColorMaterial = (mat->getColorMode() != osg::Material::OFF); + mRequirements.back().mVertexColorMode = mat->getColorMode(); + } + } + } + } + + void ShaderVisitor::pushRequirements() + { + mRequirements.push_back(mRequirements.back()); + } + + void ShaderVisitor::popRequirements() + { + mRequirements.pop_back(); + } + + void ShaderVisitor::createProgram(const ShaderRequirements &reqs, osg::Node& node) + { + osg::StateSet* writableStateSet = NULL; + if (mAllowedToModifyStateSets) + writableStateSet = node.getOrCreateStateSet(); + else + writableStateSet = getWritableStateSet(node); + + ShaderManager::DefineMap defineMap; + for (unsigned int i=0; i::const_iterator texIt = reqs.mTextures.begin(); texIt != reqs.mTextures.end(); ++texIt) + { + defineMap[texIt->second] = "1"; + defineMap[texIt->second + std::string("UV")] = boost::lexical_cast(texIt->first); + } + + if (!reqs.mColorMaterial) + defineMap["colorMode"] = "0"; + else + { + switch (reqs.mVertexColorMode) + { + default: + case GL_AMBIENT_AND_DIFFUSE: + defineMap["colorMode"] = "2"; + break; + case GL_EMISSION: + defineMap["colorMode"] = "1"; + break; + } + } + + defineMap["forcePPL"] = mForcePerPixelLighting ? "1" : "0"; + defineMap["clamp"] = mClampLighting ? "1" : "0"; + + osg::ref_ptr vertexShader (mShaderManager.getShader(mDefaultVsTemplate, defineMap, osg::Shader::VERTEX)); + osg::ref_ptr fragmentShader (mShaderManager.getShader(mDefaultFsTemplate, defineMap, osg::Shader::FRAGMENT)); + + if (vertexShader && fragmentShader) + { + writableStateSet->setAttributeAndModes(mShaderManager.getProgram(vertexShader, fragmentShader), osg::StateAttribute::ON); + + for (std::map::const_iterator texIt = reqs.mTextures.begin(); texIt != reqs.mTextures.end(); ++texIt) + { + writableStateSet->addUniform(new osg::Uniform(texIt->second.c_str(), texIt->first), osg::StateAttribute::ON); + } + } + } + + void ShaderVisitor::apply(osg::Geometry& geometry) + { + bool needPop = (geometry.getStateSet() != NULL); + if (geometry.getStateSet()) + { + pushRequirements(); + applyStateSet(geometry.getStateSet(), geometry); + } + + if (!mRequirements.empty()) + { + const ShaderRequirements& reqs = mRequirements.back(); + + if (mAllowedToModifyStateSets) + { + // make sure that all UV sets are there + for (std::map::const_iterator it = reqs.mTextures.begin(); it != reqs.mTextures.end(); ++it) + { + if (geometry.getTexCoordArray(it->first) == NULL) + geometry.setTexCoordArray(it->first, geometry.getTexCoordArray(0)); + } + } + + if (reqs.mTexStageRequiringTangents != -1 && mAllowedToModifyStateSets) + { + osg::ref_ptr generator (new osgUtil::TangentSpaceGenerator); + generator->generate(&geometry, reqs.mTexStageRequiringTangents); + + geometry.setTexCoordArray(7, generator->getTangentArray(), osg::Array::BIND_PER_VERTEX); + } + + // TODO: find a better place for the stateset + if (reqs.mShaderRequired || mForceShaders) + createProgram(reqs, geometry); + } + + if (needPop) + popRequirements(); + } + + void ShaderVisitor::apply(osg::Drawable& drawable) + { + // non-Geometry drawable (e.g. particle system) + bool needPop = (drawable.getStateSet() != NULL); + + if (drawable.getStateSet()) + { + pushRequirements(); + applyStateSet(drawable.getStateSet(), drawable); + } + + if (!mRequirements.empty()) + { + const ShaderRequirements& reqs = mRequirements.back(); + // TODO: find a better place for the stateset + if (reqs.mShaderRequired || mForceShaders) + createProgram(reqs, drawable); + } + + if (needPop) + popRequirements(); + } + + void ShaderVisitor::setAllowedToModifyStateSets(bool allowed) + { + mAllowedToModifyStateSets = allowed; + } + + void ShaderVisitor::setAutoUseNormalMaps(bool use) + { + mAutoUseNormalMaps = use; + } + + void ShaderVisitor::setNormalMapPattern(const std::string &pattern) + { + mNormalMapPattern = pattern; + } + + void ShaderVisitor::setAutoUseSpecularMaps(bool use) + { + mAutoUseSpecularMaps = use; + } + + void ShaderVisitor::setSpecularMapPattern(const std::string &pattern) + { + mSpecularMapPattern = pattern; + } + +} diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp new file mode 100644 index 000000000..682a9c60f --- /dev/null +++ b/components/shader/shadervisitor.hpp @@ -0,0 +1,100 @@ +#ifndef OPENMW_COMPONENTS_SHADERVISITOR_H +#define OPENMW_COMPONENTS_SHADERVISITOR_H + +#include + +namespace Resource +{ + class ImageManager; +} + +namespace Shader +{ + + class ShaderManager; + + /// @brief Adjusts the given subgraph to render using shaders. + class ShaderVisitor : public osg::NodeVisitor + { + public: + ShaderVisitor(ShaderManager& shaderManager, Resource::ImageManager& imageManager, const std::string& defaultVsTemplate, const std::string& defaultFsTemplate); + + /// By default, only bump mapped objects will have a shader added to them. + /// Setting force = true will cause all objects to render using shaders, regardless of having a bump map. + void setForceShaders(bool force); + + /// Set whether lighting is clamped for visual compatibility with the fixed function pipeline. + void setClampLighting(bool clamp); + + /// By default, only bump mapped objects use per-pixel lighting. + /// Setting force = true will cause all shaders to use per-pixel lighting, regardless of having a bump map. + void setForcePerPixelLighting(bool force); + + /// Set if we are allowed to modify StateSets encountered in the graph (default true). + /// @par If set to false, then instead of modifying, the StateSet will be cloned and this new StateSet will be assigned to the node. + /// @par This option is useful when the ShaderVisitor is run on a "live" subgraph that may have already been submitted for rendering. + void setAllowedToModifyStateSets(bool allowed); + + /// Automatically use normal maps if a file with suitable name exists (see normal map pattern). + void setAutoUseNormalMaps(bool use); + + void setNormalMapPattern(const std::string& pattern); + + void setAutoUseSpecularMaps(bool use); + + void setSpecularMapPattern(const std::string& pattern); + + virtual void apply(osg::Node& node); + + virtual void apply(osg::Drawable& drawable); + virtual void apply(osg::Geometry& geometry); + + void applyStateSet(osg::ref_ptr stateset, osg::Node& node); + + void pushRequirements(); + void popRequirements(); + + private: + bool mForceShaders; + bool mClampLighting; + bool mForcePerPixelLighting; + bool mAllowedToModifyStateSets; + + bool mAutoUseNormalMaps; + std::string mNormalMapPattern; + + bool mAutoUseSpecularMaps; + std::string mSpecularMapPattern; + + ShaderManager& mShaderManager; + Resource::ImageManager& mImageManager; + + struct ShaderRequirements + { + ShaderRequirements(); + ~ShaderRequirements(); + + // + std::map mTextures; + + bool mShaderRequired; + + bool mColorMaterial; + // osg::Material::ColorMode + int mVertexColorMode; + bool mMaterialOverridden; + + // -1 == no tangents required + int mTexStageRequiringTangents; + }; + std::vector mRequirements; + + std::string mDefaultVsTemplate; + std::string mDefaultFsTemplate; + + void createProgram(const ShaderRequirements& reqs, osg::Node& node); + }; + +} + +#endif diff --git a/components/terrain/buffercache.cpp b/components/terrain/buffercache.cpp index 2a72ea59b..a72dac026 100644 --- a/components/terrain/buffercache.cpp +++ b/components/terrain/buffercache.cpp @@ -219,7 +219,7 @@ namespace Terrain osg::ref_ptr buffer; - if (verts*verts > (0xffffu)) + if (verts*verts <= (0xffffu)) buffer = createIndexBuffer(flags, verts); else buffer = createIndexBuffer(flags, verts); diff --git a/components/terrain/buffercache.hpp b/components/terrain/buffercache.hpp index d1a57f811..172b9d672 100644 --- a/components/terrain/buffercache.hpp +++ b/components/terrain/buffercache.hpp @@ -3,6 +3,7 @@ #include #include +#include #include diff --git a/components/terrain/defs.hpp b/components/terrain/defs.hpp index 234e05a98..10095bec0 100644 --- a/components/terrain/defs.hpp +++ b/components/terrain/defs.hpp @@ -18,8 +18,10 @@ namespace Terrain { std::string mDiffuseMap; std::string mNormalMap; - bool mParallax; // Height info in normal map alpha channel? + //bool mParallax; // Height info in normal map alpha channel? bool mSpecular; // Specular info in diffuse map alpha channel? + + bool requiresShaders() const { return !mNormalMap.empty() || mSpecular; } }; } diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index 59d06f254..b9405ef86 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -1,21 +1,26 @@ #include "material.hpp" +#include +#include + #include #include #include #include #include -#include + +#include + namespace Terrain { - FixedFunctionTechnique::FixedFunctionTechnique(const std::vector >& layers, + FixedFunctionTechnique::FixedFunctionTechnique(const std::vector& layers, const std::vector >& blendmaps, int blendmapScale, float layerTileSize) { bool firstLayer = true; int i=0; - for (std::vector >::const_iterator it = layers.begin(); it != layers.end(); ++it) + for (std::vector::const_iterator it = layers.begin(); it != layers.end(); ++it) { osg::ref_ptr stateset (new osg::StateSet); @@ -53,7 +58,7 @@ namespace Terrain } // Add the actual layer texture multiplied by the alpha map. - osg::ref_ptr tex = *it; + osg::ref_ptr tex = it->mDiffuseMap; stateset->setTextureAttributeAndModes(texunit, tex.get()); osg::ref_ptr texMat (new osg::TexMat); @@ -66,23 +71,107 @@ namespace Terrain } } - Effect::Effect(const std::vector > &layers, const std::vector > &blendmaps, + ShaderTechnique::ShaderTechnique(Shader::ShaderManager& shaderManager, bool forcePerPixelLighting, bool clampLighting, const std::vector& layers, + const std::vector >& blendmaps, int blendmapScale, float layerTileSize) + { + bool firstLayer = true; + int i=0; + for (std::vector::const_iterator it = layers.begin(); it != layers.end(); ++it) + { + osg::ref_ptr stateset (new osg::StateSet); + + if (!firstLayer) + { + stateset->setMode(GL_BLEND, osg::StateAttribute::ON); + osg::ref_ptr depth (new osg::Depth); + depth->setFunction(osg::Depth::EQUAL); + stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); + } + + int texunit = 0; + + stateset->setTextureAttributeAndModes(texunit, it->mDiffuseMap); + + osg::ref_ptr texMat (new osg::TexMat); + texMat->setMatrix(osg::Matrix::scale(osg::Vec3f(layerTileSize,layerTileSize,1.f))); + stateset->setTextureAttributeAndModes(texunit, texMat, osg::StateAttribute::ON); + + stateset->addUniform(new osg::Uniform("diffuseMap", texunit)); + + if(!firstLayer) + { + ++texunit; + osg::ref_ptr blendmap = blendmaps.at(i++); + + stateset->setTextureAttributeAndModes(texunit, blendmap.get()); + + // This is to map corner vertices directly to the center of a blendmap texel. + osg::Matrixf texMat; + float scale = (blendmapScale/(static_cast(blendmapScale)+1.f)); + texMat.preMultTranslate(osg::Vec3f(0.5f, 0.5f, 0.f)); + texMat.preMultScale(osg::Vec3f(scale, scale, 1.f)); + texMat.preMultTranslate(osg::Vec3f(-0.5f, -0.5f, 0.f)); + + stateset->setTextureAttributeAndModes(texunit, new osg::TexMat(texMat)); + stateset->addUniform(new osg::Uniform("blendMap", texunit)); + } + + if (it->mNormalMap) + { + ++texunit; + stateset->setTextureAttributeAndModes(texunit, it->mNormalMap); + stateset->addUniform(new osg::Uniform("normalMap", texunit)); + } + + Shader::ShaderManager::DefineMap defineMap; + defineMap["forcePPL"] = forcePerPixelLighting ? "1" : "0"; + defineMap["clamp"] = clampLighting ? "1" : "0"; + defineMap["normalMap"] = (it->mNormalMap) ? "1" : "0"; + defineMap["blendMap"] = !firstLayer ? "1" : "0"; + defineMap["colorMode"] = "2"; + defineMap["specularMap"] = it->mSpecular ? "1" : "0"; + + osg::ref_ptr vertexShader = shaderManager.getShader("terrain_vertex.glsl", defineMap, osg::Shader::VERTEX); + osg::ref_ptr fragmentShader = shaderManager.getShader("terrain_fragment.glsl", defineMap, osg::Shader::FRAGMENT); + if (!vertexShader || !fragmentShader) + throw std::runtime_error("Unable to create shader"); + + stateset->setAttributeAndModes(shaderManager.getProgram(vertexShader, fragmentShader)); + + firstLayer = false; + + addPass(stateset); + } + } + + Effect::Effect(bool useShaders, bool forcePerPixelLighting, bool clampLighting, Shader::ShaderManager* shaderManager, const std::vector &layers, const std::vector > &blendmaps, int blendmapScale, float layerTileSize) - : mLayers(layers) + : mShaderManager(shaderManager) + , mUseShaders(useShaders) + , mForcePerPixelLighting(forcePerPixelLighting) + , mClampLighting(clampLighting) + , mLayers(layers) , mBlendmaps(blendmaps) , mBlendmapScale(blendmapScale) , mLayerTileSize(layerTileSize) { - osg::ref_ptr material (new osg::Material); - material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); - getOrCreateStateSet()->setAttributeAndModes(material, osg::StateAttribute::ON); - selectTechnique(0); } bool Effect::define_techniques() { - addTechnique(new FixedFunctionTechnique(mLayers, mBlendmaps, mBlendmapScale, mLayerTileSize)); + try + { + if (mUseShaders && mShaderManager) + addTechnique(new ShaderTechnique(*mShaderManager, mForcePerPixelLighting, mClampLighting, mLayers, mBlendmaps, mBlendmapScale, mLayerTileSize)); + else + addTechnique(new FixedFunctionTechnique(mLayers, mBlendmaps, mBlendmapScale, mLayerTileSize)); + } + catch (std::exception& e) + { + std::cerr << "Error: " << e.what() << std::endl; + addTechnique(new FixedFunctionTechnique(mLayers, mBlendmaps, mBlendmapScale, mLayerTileSize)); + } return true; } diff --git a/components/terrain/material.hpp b/components/terrain/material.hpp index dd00e41ed..61d5724d7 100644 --- a/components/terrain/material.hpp +++ b/components/terrain/material.hpp @@ -11,14 +11,37 @@ namespace osg class Texture2D; } +namespace Shader +{ + class ShaderManager; +} + namespace Terrain { + struct TextureLayer + { + osg::ref_ptr mDiffuseMap; + osg::ref_ptr mNormalMap; // optional + bool mSpecular; + }; + class FixedFunctionTechnique : public osgFX::Technique { public: FixedFunctionTechnique( - const std::vector >& layers, + const std::vector& layers, + const std::vector >& blendmaps, int blendmapScale, float layerTileSize); + + protected: + virtual void define_passes() {} + }; + + class ShaderTechnique : public osgFX::Technique + { + public: + ShaderTechnique(Shader::ShaderManager& shaderManager, bool forcePerPixelLighting, bool clampLighting, + const std::vector& layers, const std::vector >& blendmaps, int blendmapScale, float layerTileSize); protected: @@ -28,8 +51,8 @@ namespace Terrain class Effect : public osgFX::Effect { public: - Effect( - const std::vector >& layers, + Effect(bool useShaders, bool forcePerPixelLighting, bool clampLighting, Shader::ShaderManager* shaderManager, + const std::vector& layers, const std::vector >& blendmaps, int blendmapScale, float layerTileSize); virtual bool define_techniques(); @@ -48,7 +71,11 @@ namespace Terrain } private: - std::vector > mLayers; + Shader::ShaderManager* mShaderManager; + bool mUseShaders; + bool mForcePerPixelLighting; + bool mClampLighting; + std::vector mLayers; std::vector > mBlendmaps; int mBlendmapScale; float mLayerTileSize; diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index 981a984e4..abfac4a71 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -2,7 +2,7 @@ #include -#include +#include #include @@ -50,12 +50,16 @@ namespace namespace Terrain { -TerrainGrid::TerrainGrid(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, Storage* storage, int nodeMask, SceneUtil::UnrefQueue* unrefQueue) +TerrainGrid::TerrainGrid(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, Storage* storage, int nodeMask, Shader::ShaderManager* shaderManager, SceneUtil::UnrefQueue* unrefQueue) : Terrain::World(parent, resourceSystem, ico, storage, nodeMask) , mNumSplits(4) , mCache((storage->getCellVertices()-1)/static_cast(mNumSplits) + 1) , mUnrefQueue(unrefQueue) + , mShaderManager(shaderManager) { + osg::ref_ptr material (new osg::Material); + material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); + mTerrainRoot->getOrCreateStateSet()->setAttributeAndModes(material, osg::StateAttribute::ON); } TerrainGrid::~TerrainGrid() @@ -148,11 +152,16 @@ osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chu osg::ref_ptr textureCompileDummy (new osg::Node); unsigned int dummyTextureCounter = 0; - std::vector > layerTextures; + bool useShaders = mResourceSystem->getSceneManager()->getForceShaders(); + if (!mResourceSystem->getSceneManager()->getClampLighting()) + useShaders = true; // always use shaders when lighting is unclamped, this is to avoid lighting seams between a terrain chunk with normal maps and one without normal maps + std::vector layers; { OpenThreads::ScopedLock lock(mTextureCacheMutex); for (std::vector::const_iterator it = layerList.begin(); it != layerList.end(); ++it) { + TextureLayer textureLayer; + textureLayer.mSpecular = it->mSpecular; osg::ref_ptr texture = mTextureCache[it->mDiffuseMap]; if (!texture) { @@ -162,8 +171,28 @@ osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chu mResourceSystem->getSceneManager()->applyFilterSettings(texture); mTextureCache[it->mDiffuseMap] = texture; } - layerTextures.push_back(texture); - textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(dummyTextureCounter++, layerTextures.back()); + textureLayer.mDiffuseMap = texture; + textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(dummyTextureCounter++, texture); + + if (!it->mNormalMap.empty()) + { + texture = mTextureCache[it->mNormalMap]; + if (!texture) + { + texture = new osg::Texture2D(mResourceSystem->getImageManager()->getImage(it->mNormalMap)); + texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); + texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); + mResourceSystem->getSceneManager()->applyFilterSettings(texture); + mTextureCache[it->mNormalMap] = texture; + } + textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(dummyTextureCounter++, texture); + textureLayer.mNormalMap = texture; + } + + if (it->requiresShaders()) + useShaders = true; + + layers.push_back(textureLayer); } } @@ -175,7 +204,6 @@ osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chu texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); texture->setResizeNonPowerOfTwoHint(false); - texture->getOrCreateUserDataContainer()->addDescription("dont_override_filter"); blendmapTextures.push_back(texture); textureCompileDummy->getOrCreateStateSet()->setTextureAttributeAndModes(dummyTextureCounter++, blendmapTextures.back()); @@ -186,7 +214,8 @@ osg::ref_ptr TerrainGrid::buildTerrain (osg::Group* parent, float chu geometry->setTexCoordArray(i, mCache.getUVBuffer()); float blendmapScale = ESM::Land::LAND_TEXTURE_SIZE*chunkSize; - osg::ref_ptr effect (new Terrain::Effect(layerTextures, blendmapTextures, blendmapScale, blendmapScale)); + osg::ref_ptr effect (new Terrain::Effect(mShaderManager ? useShaders : false, mResourceSystem->getSceneManager()->getForcePerPixelLighting(), mResourceSystem->getSceneManager()->getClampLighting(), + mShaderManager, layers, blendmapTextures, blendmapScale, blendmapScale)); effect->addCullCallback(new SceneUtil::LightListCallback); @@ -278,4 +307,11 @@ void TerrainGrid::updateCache() } } +void TerrainGrid::updateTextureFiltering() +{ + OpenThreads::ScopedLock lock(mTextureCacheMutex); + for (TextureCache::iterator it = mTextureCache.begin(); it != mTextureCache.end(); ++it) + mResourceSystem->getSceneManager()->applyFilterSettings(it->second); +} + } diff --git a/components/terrain/terraingrid.hpp b/components/terrain/terraingrid.hpp index 46a95e817..defcce8b3 100644 --- a/components/terrain/terraingrid.hpp +++ b/components/terrain/terraingrid.hpp @@ -4,13 +4,22 @@ #include #include "world.hpp" -#include "material.hpp" namespace SceneUtil { class UnrefQueue; } +namespace Shader +{ + class ShaderManager; +} + +namespace osg +{ + class Texture2D; +} + namespace Terrain { @@ -18,7 +27,7 @@ namespace Terrain class TerrainGrid : public Terrain::World { public: - TerrainGrid(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, Storage* storage, int nodeMask, SceneUtil::UnrefQueue* unrefQueue = NULL); + TerrainGrid(osg::Group* parent, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico, Storage* storage, int nodeMask, Shader::ShaderManager* shaderManager = NULL, SceneUtil::UnrefQueue* unrefQueue = NULL); ~TerrainGrid(); /// Load a terrain cell and store it in cache for later use. @@ -36,6 +45,10 @@ namespace Terrain /// @note Thread safe. void updateCache(); + /// Apply the scene manager's texture filtering settings to all cached textures. + /// @note Thread safe. + void updateTextureFiltering(); + private: osg::ref_ptr buildTerrain (osg::Group* parent, float chunkSize, const osg::Vec2f& chunkCenter); @@ -55,6 +68,8 @@ namespace Terrain BufferCache mCache; osg::ref_ptr mUnrefQueue; + + Shader::ShaderManager* mShaderManager; }; } diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index 992438a6a..f5638750c 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -39,6 +39,8 @@ namespace Terrain Storage* storage, int nodeMask); virtual ~World(); + virtual void updateTextureFiltering() {} + virtual void updateCache() {} float getHeightAt (const osg::Vec3f& worldPos); diff --git a/components/vfs/bsaarchive.hpp b/components/vfs/bsaarchive.hpp index 25ad60e0a..a6e274037 100644 --- a/components/vfs/bsaarchive.hpp +++ b/components/vfs/bsaarchive.hpp @@ -34,4 +34,4 @@ namespace VFS } -#endif \ No newline at end of file +#endif diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index cf7fe1be7..8ff850cae 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -10,7 +10,7 @@ - + @@ -31,7 +31,7 @@ - + @@ -52,7 +52,7 @@ - + @@ -74,7 +74,7 @@ - + @@ -84,7 +84,7 @@ - + @@ -94,7 +94,7 @@ - + @@ -104,7 +104,7 @@ - + @@ -119,7 +119,7 @@ - + @@ -130,7 +130,7 @@ - + @@ -141,7 +141,7 @@ - + @@ -152,7 +152,7 @@ - + @@ -163,7 +163,7 @@ - + @@ -189,7 +189,7 @@ - + @@ -201,7 +201,7 @@ - + @@ -257,18 +257,6 @@ - - - - - - - - - - - - @@ -277,7 +265,7 @@ - + @@ -332,7 +320,7 @@ - + @@ -344,7 +332,7 @@ - + @@ -366,7 +354,7 @@ - + @@ -377,7 +365,7 @@ - + @@ -396,12 +384,21 @@ + + + + + + + + + + @@ -479,6 +476,7 @@ + --> diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 4a9a5dd65..c886cee70 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -44,7 +44,7 @@ preload enabled = true preload exterior grid = true # Preload possible fast travel destinations. -preload fast travel = true +preload fast travel = false # Preload the locations that doors lead to. preload doors = true @@ -52,9 +52,19 @@ preload doors = true # Preloading distance threshold preload distance = 1000 -# How long to keep preloaded cells and cached models/textures/collision shapes in cache -# after they're no longer referenced/required (in seconds) -cache expiry delay = 300 +# The minimum amount of cells in the preload cache before unused cells start to get thrown out (see "preload cell expiry delay"). +# This value should be lower or equal to 'preload cell cache max'. +preload cell cache min = 12 + +# The maximum amount of cells in the preload cache. A too high value could cause you to run out of memory. +# You may need to reduce this setting when running lots of mods or high-res texture replacers. +preload cell cache max = 20 + +# How long to keep preloaded cells in cache after they're no longer referenced/required (in seconds) +preload cell expiry delay = 5 + +# How long to keep models/textures/collision shapes in cache after they're no longer referenced/required (in seconds) +cache expiry delay = 5 [Map] @@ -147,6 +157,55 @@ texture min filter = linear # Texture mipmap type. (none, nearest, or linear). texture mipmap = nearest +[Shaders] + +# Force rendering with shaders. By default, only bump-mapped objects will use shaders. +# Enabling this option may cause slightly different visuals if the "clamp lighting" option +# is set to false. Otherwise, there should not be a visual difference. +force shaders = false + +# Force the use of per pixel lighting. By default, only bump mapped objects use per-pixel lighting. +# Has no effect if the 'force shaders' option is false. +# Enabling per-pixel lighting can result in visual differences to the original MW engine. It is not +# recommended to enable this option when using vanilla Morrowind files, because certain lights in Morrowind +# rely on vertex lighting to look as intended. +force per pixel lighting = false + +# Restrict the amount of lighting that an object can receive to a maximum of (1,1,1). +# Only affects objects that render with shaders (see 'force shaders' option). Always affects terrain. +# Setting this option to 'true' results in fixed-function compatible lighting, but the lighting +# may appear 'dull' and there might be color shifts. +# Setting this option to 'false' results in more realistic lighting. +clamp lighting = true + +# If this option is enabled, normal maps are automatically recognized and used if they are named appropriately +# (see 'normal map pattern', e.g. for a base texture foo.dds, the normal map texture would have to be named foo_n.dds). +# If this option is disabled, normal maps are only used if they are explicitely listed within the mesh file (.nif or .osg file). +# Affects objects. +auto use object normal maps = false + +# If this option is enabled, specular maps are automatically recognized and used if they are named appropriately +# (see 'specular map pattern', e.g. for a base texture foo.dds, the specular map texture would have to be named foo_spec.dds). +# If this option is disabled, normal maps are only used if they are explicitely listed within the mesh file (.osg file, not supported in .nif files). +# Affects objects. +auto use object specular maps = false + +# See 'auto use object normal maps'. Affects terrain. +auto use terrain normal maps = false + +# If a file with pattern 'terrain specular map pattern' exists, use that file as a 'diffuse specular' map. The texture +# must contain the layer color in the RGB channel (as usual), and a specular multiplier in the alpha channel. +auto use terrain specular maps = false + +# The filename pattern to probe for when detecting normal maps (see 'auto use object normal maps', 'auto use terrain normal maps') +normal map pattern = _n + +# The filename pattern to probe for when detecting object specular maps (see 'auto use object specular maps') +specular map pattern = _spec + +# The filename pattern to probe for when detecting terrain specular maps (see 'auto use terrain specular maps') +terrain specular map pattern = _diffusespec + [Input] # Capture control of the cursor prevent movement outside the window. @@ -264,53 +323,8 @@ rtt size = 512 # Enable refraction which affects visibility through water plane. refraction = false -[Objects] - -# Enable shaders for objects other than water. Unused. -shaders = true - -[Terrain] - -# Use shaders for terrain? Unused. -shader = true - -# Distant land is rendered? Unused. -distant land = false - -[Shadows] - -# Enable shadows. Other shadow settings disabled if false. Unused. -enabled = false - -# Size of the shadow textures in pixels. Unused. (e.g. 256 to 2048). -texture size = 1024 - -# Actors cast shadows. Unused. -actor shadows = true - -# Static objects cast shadows. Unused. -statics shadows = true - -# Terrain cast shadows. Unused. -terrain shadows = true - -# Miscellaneous objects cast shadows. Unused. -misc shadows = true - -# Debugging of shadows. Unused. -debug = false - -# Fraction of distance after which shadow starts to fade out. Unused. -fade start = 0.8 - -# Split shadow maps, allowing for a larger shadow distance. Unused. -split = false - -# Distance for shadows if not split. Smaller is poorer. Unused. -shadow distance = 1300 - -# Distance for shadows if split. Unused. -split shadow distance = 14000 +# Draw NPCs and creatures on water reflections. +reflect actors = false [Windows] diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index fc4706c1f..31db4a3eb 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -6,6 +6,11 @@ set(SHADER_FILES water_vertex.glsl water_fragment.glsl water_nm.png + objects_vertex.glsl + objects_fragment.glsl + terrain_vertex.glsl + terrain_fragment.glsl + lighting.glsl ) copy_all_files(${CMAKE_CURRENT_SOURCE_DIR} ${DDIR} "${SHADER_FILES}") diff --git a/files/shaders/lighting.glsl b/files/shaders/lighting.glsl new file mode 100644 index 000000000..08b369b5b --- /dev/null +++ b/files/shaders/lighting.glsl @@ -0,0 +1,53 @@ +#define MAX_LIGHTS 8 + +vec4 doLighting(vec3 viewPos, vec3 viewNormal, vec4 vertexColor) +{ + vec3 lightDir; + float d; + +#if @colorMode == 2 + vec4 diffuse = vertexColor; + vec3 ambient = vertexColor.xyz; +#else + vec4 diffuse = gl_FrontMaterial.diffuse; + vec3 ambient = gl_FrontMaterial.ambient.xyz; +#endif + vec4 lightResult = vec4(0.0, 0.0, 0.0, diffuse.a); + + for (int i=0; i