diff --git a/.gitignore b/.gitignore index 3975c4521b..08bf0bad62 100644 --- a/.gitignore +++ b/.gitignore @@ -41,7 +41,7 @@ resources ## generated objects apps/openmw/config.hpp components/version/version.hpp -Docs/mainpage.hpp +docs/mainpage.hpp moc_*.cxx *.cxx_parameters *qrc_launcher.cxx diff --git a/CMakeLists.txt b/CMakeLists.txt index 3632f1e080..9de8efeb54 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,7 +56,7 @@ include(OpenMWMacros) # doxygen main page -configure_file ("${OpenMW_SOURCE_DIR}/Docs/mainpage.hpp.cmake" "${OpenMW_SOURCE_DIR}/Docs/mainpage.hpp") +configure_file ("${OpenMW_SOURCE_DIR}/docs/mainpage.hpp.cmake" "${OpenMW_SOURCE_DIR}/docs/mainpage.hpp") option(MYGUI_STATIC "Link static build of Mygui into the binaries" FALSE) option(OGRE_STATIC "Link static build of Ogre and Ogre Plugins into the binaries" FALSE) @@ -80,13 +80,6 @@ option(USE_FFMPEG "use ffmpeg for sound" ON) # OS X deployment option(OPENMW_OSX_DEPLOYMENT OFF) -if(UNIX AND NOT APPLE) - option(BUILD_WITH_DPKG "enable dpkg-based install for debian and debian derivatives" OFF) - if(BUILD_WITH_DPKG) - find_program(DPKG_PROGRAM dpkg DOC "dpkg program of Debian-based systems") - endif(BUILD_WITH_DPKG) -endif(UNIX AND NOT APPLE) - # Location of morrowind data files if (APPLE) set(MORROWIND_DATA_FILES "./data" CACHE PATH "location of Morrowind data files") @@ -395,46 +388,36 @@ if (CMAKE_COMPILER_IS_GNUCC) endif (CMAKE_COMPILER_IS_GNUCC) IF(NOT WIN32 AND NOT APPLE) - ## Debian and non debian Linux building + # Linux building # Paths - IF (DPKG_PROGRAM) - ## Debian specific - SET(CMAKE_INSTALL_PREFIX "/usr") - SET(DATAROOTDIR "share" CACHE PATH "Sets the root of data directories to a non-default location") - SET(DATADIR "share/games/openmw" CACHE PATH "Sets the openmw data directories to a non-default location") - SET(ICONDIR "share/pixmaps" CACHE PATH "Set icon dir") - SET(SYSCONFDIR "../etc/openmw" CACHE PATH "Set config dir") - ELSE () - ## Non debian specific - SET(BINDIR "${CMAKE_INSTALL_PREFIX}/bin" CACHE PATH "Where to install binaries") - SET(DATAROOTDIR "${CMAKE_INSTALL_PREFIX}/share" CACHE PATH "Sets the root of data directories to a non-default location") - SET(DATADIR "${DATAROOTDIR}/games/openmw" CACHE PATH "Sets the openmw data directories to a non-default location") - SET(ICONDIR "${DATAROOTDIR}/pixmaps" CACHE PATH "Set icon dir") - SET(LICDIR "${DATAROOTDIR}/licenses/openmw" CACHE PATH "Sets the openmw license directory to a non-default location.") - SET(SYSCONFDIR "/etc/openmw" CACHE PATH "Set config dir") + SET(BINDIR "${CMAKE_INSTALL_PREFIX}/bin" CACHE PATH "Where to install binaries") + SET(DATAROOTDIR "${CMAKE_INSTALL_PREFIX}/share" CACHE PATH "Sets the root of data directories to a non-default location") + SET(DATADIR "${DATAROOTDIR}/games/openmw" CACHE PATH "Sets the openmw data directories to a non-default location") + SET(ICONDIR "${DATAROOTDIR}/pixmaps" CACHE PATH "Set icon dir") + SET(LICDIR "${DATAROOTDIR}/licenses/openmw" CACHE PATH "Sets the openmw license directory to a non-default location.") + SET(SYSCONFDIR "/etc/openmw" CACHE PATH "Set config dir") - # Install binaries - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw" DESTINATION "${BINDIR}" ) - IF(BUILD_LAUNCHER) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/omwlauncher" DESTINATION "${BINDIR}" ) - ENDIF(BUILD_LAUNCHER) - IF(BUILD_BSATOOL) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/bsatool" DESTINATION "${BINDIR}" ) - ENDIF(BUILD_BSATOOL) - IF(BUILD_ESMTOOL) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/esmtool" DESTINATION "${BINDIR}" ) - ENDIF(BUILD_ESMTOOL) - IF(BUILD_MWINIIMPORTER) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/mwiniimport" DESTINATION "${BINDIR}" ) - ENDIF(BUILD_MWINIIMPORTER) - IF(BUILD_OPENCS) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/opencs" DESTINATION "${BINDIR}" ) - ENDIF(BUILD_OPENCS) + # Install binaries + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw" DESTINATION "${BINDIR}" ) + IF(BUILD_LAUNCHER) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/omwlauncher" DESTINATION "${BINDIR}" ) + ENDIF(BUILD_LAUNCHER) + IF(BUILD_BSATOOL) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/bsatool" DESTINATION "${BINDIR}" ) + ENDIF(BUILD_BSATOOL) + IF(BUILD_ESMTOOL) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/esmtool" DESTINATION "${BINDIR}" ) + ENDIF(BUILD_ESMTOOL) + IF(BUILD_MWINIIMPORTER) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/mwiniimport" DESTINATION "${BINDIR}" ) + ENDIF(BUILD_MWINIIMPORTER) + IF(BUILD_OPENCS) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/opencs" DESTINATION "${BINDIR}" ) + ENDIF(BUILD_OPENCS) - # Install licenses - INSTALL(FILES "DejaVu Font License.txt" DESTINATION "${LICDIR}" ) - INSTALL(FILES "extern/shiny/License.txt" DESTINATION "${LICDIR}" RENAME "Shiny License.txt" ) - ENDIF (DPKG_PROGRAM) + # Install licenses + INSTALL(FILES "docs/license/DejaVu Font License.txt" DESTINATION "${LICDIR}" ) + INSTALL(FILES "extern/shiny/License.txt" DESTINATION "${LICDIR}" RENAME "Shiny License.txt" ) # Install icon and desktop file INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.desktop" DESTINATION "${DATAROOTDIR}/applications" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "openmw") @@ -463,8 +446,8 @@ if(WIN32) INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.cfg.install" DESTINATION "." RENAME "openmw.cfg") INSTALL(FILES "${OpenMW_SOURCE_DIR}/readme.txt" - "${OpenMW_SOURCE_DIR}/GPL3.txt" - "${OpenMW_SOURCE_DIR}/DejaVu Font License.txt" + "${OpenMW_SOURCE_DIR}/Docs/license/GPL3.txt" + "${OpenMW_SOURCE_DIR}/Docs/license/DejaVu Font License.txt" "${OpenMW_BINARY_DIR}/settings-default.cfg" "${OpenMW_BINARY_DIR}/transparency-overrides.cfg" "${OpenMW_BINARY_DIR}/Release/openmw.exe" @@ -630,8 +613,10 @@ if (WIN32) 4127 # Conditional expression is constant 4242 # Storing value in a variable of a smaller type, possible loss of data 4244 # Storing value of one type in variable of another (size_t in int, for example) + 4267 # Conversion from 'size_t' to 'int', possible loss of data 4305 # Truncating value (double to float, for example) 4309 # Variable overflow, trying to store 128 in a signed char for example + 4351 # New behavior: elements of array 'array' will be default initialized (desired behavior) 4355 # Using 'this' in member initialization list 4505 # Unreferenced local function has been removed 4701 # Potentially uninitialized local variable used @@ -650,7 +635,9 @@ if (WIN32) set(SHINY_OGRE_WARNINGS "${WARNINGS} /wd4101") set_target_properties(shiny.OgrePlatform PROPERTIES COMPILE_FLAGS ${SHINY_OGRE_WARNINGS}) set_target_properties(sdl4ogre PROPERTIES COMPILE_FLAGS ${WARNINGS}) - set_target_properties(oics PROPERTIES COMPILE_FLAGS ${WARNINGS}) + # oics uses tinyxml, which has an initialized but unused variable + set(OICS_WARNINGS "${WARNINGS} /wd4189") + set_target_properties(oics PROPERTIES COMPILE_FLAGS ${OICS_WARNINGS}) set_target_properties(components PROPERTIES COMPILE_FLAGS ${WARNINGS}) if (BUILD_LAUNCHER) set_target_properties(omwlauncher PROPERTIES COMPILE_FLAGS ${WARNINGS}) diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index bcf16091f4..e4ada7d189 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -682,13 +682,11 @@ void Record::print() { std::cout << " Name: " << mData.mName << std::endl; std::cout << " Hidden: " << mData.mData.mIsHidden << std::endl; - if (mData.mData.mUnknown != -1) - std::cout << " Unknown: " << mData.mData.mUnknown << std::endl; std::cout << " Attribute1: " << attributeLabel(mData.mData.mAttribute[0]) << " (" << mData.mData.mAttribute[0] << ")" << std::endl; std::cout << " Attribute2: " << attributeLabel(mData.mData.mAttribute[1]) << " (" << mData.mData.mAttribute[1] << ")" << std::endl; - for (int i = 0; i != 6; i++) + for (int i = 0; i < 7; i++) if (mData.mData.mSkills[i] != -1) std::cout << " Skill: " << skillLabel(mData.mData.mSkills[i]) << " (" << mData.mData.mSkills[i] << ")" << std::endl; diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index ec721a5e59..d7733ba0e1 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -119,10 +119,6 @@ endif(NOT WIN32) -if(DPKG_PROGRAM) - INSTALL(TARGETS omwlauncher RUNTIME DESTINATION games COMPONENT omwlauncher) -endif() - if (BUILD_WITH_CODE_COVERAGE) add_definitions (--coverage) target_link_libraries(omwlauncher gcov) diff --git a/apps/mwiniimporter/CMakeLists.txt b/apps/mwiniimporter/CMakeLists.txt index 702f665138..deab88ce28 100644 --- a/apps/mwiniimporter/CMakeLists.txt +++ b/apps/mwiniimporter/CMakeLists.txt @@ -22,8 +22,3 @@ if (BUILD_WITH_CODE_COVERAGE) add_definitions (--coverage) target_link_libraries(mwiniimport gcov) endif() - -if(DPKG_PROGRAM) - INSTALL(TARGETS mwiniimport RUNTIME DESTINATION games COMPONENT mwiniimport) -endif() - diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index eec1b653d6..385611bd93 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -71,7 +71,7 @@ opencs_units (view/render opencs_units_noqt (view/render navigation navigation1st navigationfree navigationorbit lighting lightingday lightingnight - lightingbright + lightingbright object cell ) opencs_units_noqt (view/world @@ -200,10 +200,6 @@ target_link_libraries(opencs components ) -if(DPKG_PROGRAM) - INSTALL(TARGETS opencs RUNTIME DESTINATION games COMPONENT opencs) -endif() - if(APPLE) INSTALL(TARGETS opencs BUNDLE DESTINATION OpenMW COMPONENT BUNDLE) endif() diff --git a/apps/opencs/model/doc/documentmanager.cpp b/apps/opencs/model/doc/documentmanager.cpp index 096864b772..4020a8f72c 100644 --- a/apps/opencs/model/doc/documentmanager.cpp +++ b/apps/opencs/model/doc/documentmanager.cpp @@ -31,8 +31,8 @@ CSMDoc::DocumentManager::DocumentManager (const Files::ConfigurationManager& con &mLoader, SLOT (loadDocument (CSMDoc::Document *))); connect (&mLoader, SIGNAL (nextStage (CSMDoc::Document *, const std::string&, int)), this, SIGNAL (nextStage (CSMDoc::Document *, const std::string&, int))); - connect (&mLoader, SIGNAL (nextRecord (CSMDoc::Document *)), - this, SIGNAL (nextRecord (CSMDoc::Document *))); + connect (&mLoader, SIGNAL (nextRecord (CSMDoc::Document *, int)), + this, SIGNAL (nextRecord (CSMDoc::Document *, int))); connect (this, SIGNAL (cancelLoading (CSMDoc::Document *)), &mLoader, SLOT (abortLoading (CSMDoc::Document *))); connect (&mLoader, SIGNAL (loadMessage (CSMDoc::Document *, const std::string&)), diff --git a/apps/opencs/model/doc/documentmanager.hpp b/apps/opencs/model/doc/documentmanager.hpp index 9b675826a2..d6824112bb 100644 --- a/apps/opencs/model/doc/documentmanager.hpp +++ b/apps/opencs/model/doc/documentmanager.hpp @@ -79,9 +79,10 @@ namespace CSMDoc void loadingStopped (CSMDoc::Document *document, bool completed, const std::string& error); - void nextStage (CSMDoc::Document *document, const std::string& name, int steps); + void nextStage (CSMDoc::Document *document, const std::string& name, + int totalRecords); - void nextRecord (CSMDoc::Document *document); + void nextRecord (CSMDoc::Document *document, int records); void cancelLoading (CSMDoc::Document *document); diff --git a/apps/opencs/model/doc/loader.cpp b/apps/opencs/model/doc/loader.cpp index c106c06e82..712deb9dfc 100644 --- a/apps/opencs/model/doc/loader.cpp +++ b/apps/opencs/model/doc/loader.cpp @@ -8,7 +8,7 @@ #include "document.hpp" #include "state.hpp" -CSMDoc::Loader::Stage::Stage() : mFile (0), mRecordsLeft (false) {} +CSMDoc::Loader::Stage::Stage() : mFile (0), mRecordsLoaded (0), mRecordsLeft (false) {} CSMDoc::Loader::Loader() @@ -39,13 +39,14 @@ void CSMDoc::Loader::load() Document *document = iter->first; int size = static_cast (document->getContentFiles().size()); + int editedIndex = size-1; // index of the file to be edited/created if (document->isNew()) --size; bool done = false; - const int batchingSize = 100; + const int batchingSize = 50; try { @@ -58,17 +59,21 @@ void CSMDoc::Loader::load() iter->second.mRecordsLeft = false; break; } + else + ++(iter->second.mRecordsLoaded); CSMWorld::UniversalId log (CSMWorld::UniversalId::Type_LoadErrorLog, 0); + { // silence a g++ warning for (CSMDoc::Stage::Messages::const_iterator iter (messages.begin()); iter!=messages.end(); ++iter) { document->getReport (log)->add (iter->first, iter->second); emit loadMessage (document, iter->second); } + } - emit nextRecord (document); + emit nextRecord (document, iter->second.mRecordsLoaded); return; } @@ -77,17 +82,19 @@ void CSMDoc::Loader::load() { boost::filesystem::path path = document->getContentFiles()[iter->second.mFile]; - int steps = document->getData().startLoading (path, iter->second.mFilegetData().startLoading (path, iter->second.mFile!=editedIndex, false); iter->second.mRecordsLeft = true; + iter->second.mRecordsLoaded = 0; - emit nextStage (document, path.filename().string(), steps/batchingSize); + emit nextStage (document, path.filename().string(), steps); } else if (iter->second.mFile==size) { int steps = document->getData().startLoading (document->getProjectPath(), false, true); iter->second.mRecordsLeft = true; + iter->second.mRecordsLoaded = 0; - emit nextStage (document, "Project File", steps/batchingSize); + emit nextStage (document, "Project File", steps); } else { diff --git a/apps/opencs/model/doc/loader.hpp b/apps/opencs/model/doc/loader.hpp index c276e14ff4..d1ee38f9f1 100644 --- a/apps/opencs/model/doc/loader.hpp +++ b/apps/opencs/model/doc/loader.hpp @@ -18,6 +18,7 @@ namespace CSMDoc struct Stage { int mFile; + int mRecordsLoaded; bool mRecordsLeft; Stage(); @@ -56,9 +57,10 @@ namespace CSMDoc ///< Document load has been interrupted either because of a call to abortLoading /// or a problem during loading). In the former case error will be an empty string. - void nextStage (CSMDoc::Document *document, const std::string& name, int steps); + void nextStage (CSMDoc::Document *document, const std::string& name, + int totalRecords); - void nextRecord (CSMDoc::Document *document); + void nextRecord (CSMDoc::Document *document, int records); ///< \note This signal is only given once per group of records. The group size is /// approximately the total number of records divided by the steps value of the /// previous nextStage signal. diff --git a/apps/opencs/model/tools/factioncheck.cpp b/apps/opencs/model/tools/factioncheck.cpp index 42d577163f..ba8cfe1f9b 100644 --- a/apps/opencs/model/tools/factioncheck.cpp +++ b/apps/opencs/model/tools/factioncheck.cpp @@ -42,7 +42,7 @@ void CSMTools::FactionCheckStage::perform (int stage, Messages& messages) // test for non-unique skill std::map skills; // ID, number of occurrences - for (int i=0; i<6; ++i) + for (int i=0; i<7; ++i) if (faction.mData.mSkills[i]!=-1) ++skills[faction.mData.mSkills[i]]; @@ -54,4 +54,4 @@ void CSMTools::FactionCheckStage::perform (int stage, Messages& messages) } /// \todo check data members that can't be edited in the table view -} \ No newline at end of file +} diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index 5d8ddb87d7..76eaecc412 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -217,7 +217,7 @@ namespace CSMWorld bool hasEnums (ColumnId column); std::vector getEnums (ColumnId column); - ///< Returns an empty vector, if \æ column isn't an enum type column. + ///< Returns an empty vector, if \a column isn't an enum type column. } } diff --git a/apps/opencs/model/world/commanddispatcher.cpp b/apps/opencs/model/world/commanddispatcher.cpp index ce6d1e3982..4e146d87c0 100644 --- a/apps/opencs/model/world/commanddispatcher.cpp +++ b/apps/opencs/model/world/commanddispatcher.cpp @@ -1,6 +1,10 @@ #include "commanddispatcher.hpp" +#include + +#include + #include "../doc/document.hpp" #include "idtable.hpp" @@ -88,6 +92,13 @@ void CSMWorld::CommandDispatcher::setEditLock (bool locked) void CSMWorld::CommandDispatcher::setSelection (const std::vector& selection) { mSelection = selection; + std::for_each (mSelection.begin(), mSelection.end(), Misc::StringUtils::toLower); + std::sort (mSelection.begin(), mSelection.end()); +} + +void CSMWorld::CommandDispatcher::setExtendedTypes (const std::vector& types) +{ + mExtendedTypes = types; } bool CSMWorld::CommandDispatcher::canDelete() const @@ -106,6 +117,20 @@ bool CSMWorld::CommandDispatcher::canRevert() const return getRevertableRecords().size()!=0; } +std::vector CSMWorld::CommandDispatcher::getExtendedTypes() const +{ + std::vector tables; + + if (mId==UniversalId::Type_Cells) + { + tables.push_back (mId); + tables.push_back (UniversalId::Type_References); + /// \todo add other cell-specific types + } + + return tables; +} + void CSMWorld::CommandDispatcher::executeDelete() { if (mLocked) @@ -163,3 +188,80 @@ void CSMWorld::CommandDispatcher::executeRevert() if (rows.size()>1) mDocument.getUndoStack().endMacro(); } + +void CSMWorld::CommandDispatcher::executeExtendedDelete() +{ + if (mExtendedTypes.size()>1) + mDocument.getUndoStack().beginMacro (tr ("Extended delete of multiple records")); + + for (std::vector::const_iterator iter (mExtendedTypes.begin()); + iter!=mExtendedTypes.end(); ++iter) + { + if (*iter==mId) + executeDelete(); + else if (*iter==UniversalId::Type_References) + { + IdTable& model = dynamic_cast ( + *mDocument.getData().getTableModel (*iter)); + + const RefCollection& collection = mDocument.getData().getReferences(); + + int size = collection.getSize(); + + for (int i=size-1; i>=0; --i) + { + const Record& record = collection.getRecord (i); + + if (record.mState==RecordBase::State_Deleted) + continue; + + if (!std::binary_search (mSelection.begin(), mSelection.end(), + Misc::StringUtils::lowerCase (record.get().mCell))) + continue; + + mDocument.getUndoStack().push ( + new CSMWorld::DeleteCommand (model, record.get().mId)); + } + } + } + + if (mExtendedTypes.size()>1) + mDocument.getUndoStack().endMacro(); +} + +void CSMWorld::CommandDispatcher::executeExtendedRevert() +{ + if (mExtendedTypes.size()>1) + mDocument.getUndoStack().beginMacro (tr ("Extended revert of multiple records")); + + for (std::vector::const_iterator iter (mExtendedTypes.begin()); + iter!=mExtendedTypes.end(); ++iter) + { + if (*iter==mId) + executeRevert(); + else if (*iter==UniversalId::Type_References) + { + IdTable& model = dynamic_cast ( + *mDocument.getData().getTableModel (*iter)); + + const RefCollection& collection = mDocument.getData().getReferences(); + + int size = collection.getSize(); + + for (int i=size-1; i>=0; --i) + { + const Record& record = collection.getRecord (i); + + if (!std::binary_search (mSelection.begin(), mSelection.end(), + Misc::StringUtils::lowerCase (record.get().mCell))) + continue; + + mDocument.getUndoStack().push ( + new CSMWorld::RevertCommand (model, record.get().mId)); + } + } + } + + if (mExtendedTypes.size()>1) + mDocument.getUndoStack().endMacro(); +} \ No newline at end of file diff --git a/apps/opencs/model/world/commanddispatcher.hpp b/apps/opencs/model/world/commanddispatcher.hpp index 3bf81f0683..50085b1a1e 100644 --- a/apps/opencs/model/world/commanddispatcher.hpp +++ b/apps/opencs/model/world/commanddispatcher.hpp @@ -22,6 +22,7 @@ namespace CSMWorld CSMDoc::Document& mDocument; UniversalId mId; std::vector mSelection; + std::vector mExtendedTypes; std::vector getDeletableRecords() const; @@ -37,16 +38,31 @@ namespace CSMWorld void setSelection (const std::vector& selection); + void setExtendedTypes (const std::vector& types); + ///< Set record lists selected by the user for extended operations. + bool canDelete() const; bool canRevert() const; + /// Return IDs of the record collection that can also be affected when + /// operating on the record collection this dispatcher is used for. + /// + /// \note The returned collection contains the ID of the record collection this + /// dispatcher is used for. However if that record collection does not support + /// the extended mode, the returned vector will be empty instead. + std::vector getExtendedTypes() const; + public slots: void executeDelete(); void executeRevert(); + void executeExtendedDelete(); + + void executeExtendedRevert(); + }; } diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 7f2eac20dc..0319d71acc 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -102,7 +102,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding) mFactions.addColumn (new AttributesColumn (0)); mFactions.addColumn (new AttributesColumn (1)); mFactions.addColumn (new HiddenColumn); - for (int i=0; i<6; ++i) + for (int i=0; i<7; ++i) mFactions.addColumn (new SkillsColumn (i)); mRaces.addColumn (new StringIdColumn); @@ -742,4 +742,4 @@ void CSMWorld::Data::dataChanged (const QModelIndex& topLeft, const QModelIndex& void CSMWorld::Data::rowsChanged (const QModelIndex& parent, int start, int end) { emit idListChanged(); -} \ No newline at end of file +} diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index 57a42d9fbc..a04635b6ff 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -45,7 +45,7 @@ CSMWorld::RefIdCollection::RefIdCollection() ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue, false, false)); baseColumns.mId = &mColumns.back(); mColumns.push_back (RefIdColumn (Columns::ColumnId_Modification, ColumnBase::Display_RecordState, - ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue, false, false)); + ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue, true, false)); baseColumns.mModified = &mColumns.back(); mColumns.push_back (RefIdColumn (Columns::ColumnId_RecordType, ColumnBase::Display_RefRecordType, ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue, false, false)); diff --git a/apps/opencs/view/doc/loader.cpp b/apps/opencs/view/doc/loader.cpp index 7e4754ddf8..ca7c93f9df 100644 --- a/apps/opencs/view/doc/loader.cpp +++ b/apps/opencs/view/doc/loader.cpp @@ -45,7 +45,7 @@ CSVDoc::LoadingDocument::LoadingDocument (CSMDoc::Document *document) mFileProgress->setValue (0); // record progress - mLayout->addWidget (new QLabel ("Records", this)); + mLayout->addWidget (mRecords = new QLabel ("Records", this)); mRecordProgress = new QProgressBar (this); @@ -75,22 +75,30 @@ CSVDoc::LoadingDocument::LoadingDocument (CSMDoc::Document *document) connect (mButtons, SIGNAL (rejected()), this, SLOT (cancel())); } -void CSVDoc::LoadingDocument::nextStage (const std::string& name, int steps) +void CSVDoc::LoadingDocument::nextStage (const std::string& name, int totalRecords) { mFile->setText (QString::fromUtf8 (("Loading: " + name).c_str())); mFileProgress->setValue (mFileProgress->value()+1); mRecordProgress->setValue (0); - mRecordProgress->setMaximum (steps>0 ? steps : 1); + mRecordProgress->setMaximum (totalRecords>0 ? totalRecords : 1); + + mTotalRecords = totalRecords; } -void CSVDoc::LoadingDocument::nextRecord() +void CSVDoc::LoadingDocument::nextRecord (int records) { - int value = mRecordProgress->value()+1; + if (records<=mTotalRecords) + { + mRecordProgress->setValue (records); - if (value<=mRecordProgress->maximum()) - mRecordProgress->setValue (value); + std::ostringstream stream; + + stream << "Records: " << records << " of " << mTotalRecords; + + mRecords->setText (QString::fromUtf8 (stream.str().c_str())); + } } void CSVDoc::LoadingDocument::abort (const std::string& error) @@ -168,20 +176,21 @@ void CSVDoc::Loader::loadingStopped (CSMDoc::Document *document, bool completed, } } -void CSVDoc::Loader::nextStage (CSMDoc::Document *document, const std::string& name, int steps) +void CSVDoc::Loader::nextStage (CSMDoc::Document *document, const std::string& name, + int totalRecords) { std::map::iterator iter = mDocuments.find (document); if (iter!=mDocuments.end()) - iter->second->nextStage (name, steps); + iter->second->nextStage (name, totalRecords); } -void CSVDoc::Loader::nextRecord (CSMDoc::Document *document) +void CSVDoc::Loader::nextRecord (CSMDoc::Document *document, int records) { std::map::iterator iter = mDocuments.find (document); if (iter!=mDocuments.end()) - iter->second->nextRecord(); + iter->second->nextRecord (records); } void CSVDoc::Loader::loadMessage (CSMDoc::Document *document, const std::string& message) diff --git a/apps/opencs/view/doc/loader.hpp b/apps/opencs/view/doc/loader.hpp index 69a8b48ba3..e004007c99 100644 --- a/apps/opencs/view/doc/loader.hpp +++ b/apps/opencs/view/doc/loader.hpp @@ -26,6 +26,7 @@ namespace CSVDoc CSMDoc::Document *mDocument; QLabel *mFile; + QLabel *mRecords; QProgressBar *mFileProgress; QProgressBar *mRecordProgress; bool mAborted; @@ -33,6 +34,7 @@ namespace CSVDoc QLabel *mError; QListWidget *mMessages; QVBoxLayout *mLayout; + int mTotalRecords; private: @@ -42,9 +44,9 @@ namespace CSVDoc LoadingDocument (CSMDoc::Document *document); - void nextStage (const std::string& name, int steps); + void nextStage (const std::string& name, int totalRecords); - void nextRecord(); + void nextRecord (int records); void abort (const std::string& error); @@ -88,9 +90,9 @@ namespace CSVDoc void loadingStopped (CSMDoc::Document *document, bool completed, const std::string& error); - void nextStage (CSMDoc::Document *document, const std::string& name, int steps); + void nextStage (CSMDoc::Document *document, const std::string& name, int totalRecords); - void nextRecord (CSMDoc::Document *document); + void nextRecord (CSMDoc::Document *document, int records); void loadMessage (CSMDoc::Document *document, const std::string& message); }; diff --git a/apps/opencs/view/doc/viewmanager.cpp b/apps/opencs/view/doc/viewmanager.cpp index 02f6a54679..f4d6fc6cb5 100644 --- a/apps/opencs/view/doc/viewmanager.cpp +++ b/apps/opencs/view/doc/viewmanager.cpp @@ -97,8 +97,8 @@ CSVDoc::ViewManager::ViewManager (CSMDoc::DocumentManager& documentManager) &mLoader, SLOT (nextStage (CSMDoc::Document *, const std::string&, int))); connect ( - &mDocumentManager, SIGNAL (nextRecord (CSMDoc::Document *)), - &mLoader, SLOT (nextRecord (CSMDoc::Document *))); + &mDocumentManager, SIGNAL (nextRecord (CSMDoc::Document *, int)), + &mLoader, SLOT (nextRecord (CSMDoc::Document *, int))); connect ( &mDocumentManager, SIGNAL (loadMessage (CSMDoc::Document *, const std::string&)), diff --git a/apps/opencs/view/filter/filterbox.cpp b/apps/opencs/view/filter/filterbox.cpp index e588770b11..7a42ef0a57 100644 --- a/apps/opencs/view/filter/filterbox.cpp +++ b/apps/opencs/view/filter/filterbox.cpp @@ -35,7 +35,11 @@ void CSVFilter::FilterBox::setRecordFilter (const std::string& filter) void CSVFilter::FilterBox::dropEvent (QDropEvent* event) { - std::vector data = dynamic_cast (event->mimeData())->getData(); + const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); + if (!mime) // May happen when non-records (e.g. plain text) are dragged and dropped + return; + + std::vector data = mime->getData(); emit recordDropped(data, event->proposedAction()); } @@ -54,4 +58,4 @@ void CSVFilter::FilterBox::createFilterRequest (std::vector< std::pair< std::str Qt::DropAction action) { mRecordFilterBox->createFilterRequest(filterSource, action); -} \ No newline at end of file +} diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp new file mode 100644 index 0000000000..1e25f42e25 --- /dev/null +++ b/apps/opencs/view/render/cell.cpp @@ -0,0 +1,201 @@ + +#include "cell.hpp" + +#include +#include + +#include + +#include "../../model/world/idtable.hpp" +#include "../../model/world/columns.hpp" +#include "../../model/world/data.hpp" + +bool CSVRender::Cell::removeObject (const std::string& id) +{ + std::map::iterator iter = + mObjects.find (Misc::StringUtils::lowerCase (id)); + + if (iter==mObjects.end()) + return false; + + delete iter->second; + mObjects.erase (iter); + return true; +} + +bool CSVRender::Cell::addObjects (int start, int end) +{ + CSMWorld::IdTable& references = dynamic_cast ( + *mData.getTableModel (CSMWorld::UniversalId::Type_References)); + + int idColumn = references.findColumnIndex (CSMWorld::Columns::ColumnId_Id); + int cellColumn = references.findColumnIndex (CSMWorld::Columns::ColumnId_Cell); + int stateColumn = references.findColumnIndex (CSMWorld::Columns::ColumnId_Modification); + + bool modified = false; + + for (int i=start; i<=end; ++i) + { + std::string cell = Misc::StringUtils::lowerCase (references.data ( + references.index (i, cellColumn)).toString().toUtf8().constData()); + + int state = references.data (references.index (i, stateColumn)).toInt(); + + if (cell==mId && state!=CSMWorld::RecordBase::State_Deleted) + { + std::string id = Misc::StringUtils::lowerCase (references.data ( + references.index (i, idColumn)).toString().toUtf8().constData()); + + mObjects.insert (std::make_pair (id, new Object (mData, mCellNode, id, false))); + modified = true; + } + } + + return modified; +} + +CSVRender::Cell::Cell (CSMWorld::Data& data, Ogre::SceneManager *sceneManager, + const std::string& id, const Ogre::Vector3& origin) +: mData (data), mId (Misc::StringUtils::lowerCase (id)) +{ + mCellNode = sceneManager->getRootSceneNode()->createChildSceneNode(); + mCellNode->setPosition (origin); + + CSMWorld::IdTable& references = dynamic_cast ( + *mData.getTableModel (CSMWorld::UniversalId::Type_References)); + + int rows = references.rowCount(); + + addObjects (0, rows-1); +} + +CSVRender::Cell::~Cell() +{ + for (std::map::iterator iter (mObjects.begin()); + iter!=mObjects.end(); ++iter) + delete iter->second; + + mCellNode->getCreator()->destroySceneNode (mCellNode); +} + +bool CSVRender::Cell::referenceableDataChanged (const QModelIndex& topLeft, + const QModelIndex& bottomRight) +{ + bool modified = false; + + for (std::map::iterator iter (mObjects.begin()); + iter!=mObjects.end(); ++iter) + if (iter->second->referenceableDataChanged (topLeft, bottomRight)) + modified = true; + + return modified; +} + +bool CSVRender::Cell::referenceableAboutToBeRemoved (const QModelIndex& parent, int start, + int end) +{ + if (parent.isValid()) + return false; + + bool modified = false; + + for (std::map::iterator iter (mObjects.begin()); + iter!=mObjects.end(); ++iter) + if (iter->second->referenceableAboutToBeRemoved (parent, start, end)) + modified = true; + + return modified; +} + +bool CSVRender::Cell::referenceDataChanged (const QModelIndex& topLeft, + const QModelIndex& bottomRight) +{ + CSMWorld::IdTable& references = dynamic_cast ( + *mData.getTableModel (CSMWorld::UniversalId::Type_References)); + + int idColumn = references.findColumnIndex (CSMWorld::Columns::ColumnId_Id); + int cellColumn = references.findColumnIndex (CSMWorld::Columns::ColumnId_Cell); + int stateColumn = references.findColumnIndex (CSMWorld::Columns::ColumnId_Modification); + + // list IDs in cell + std::map ids; // id, deleted state + + for (int i=topLeft.row(); i<=bottomRight.row(); ++i) + { + std::string cell = Misc::StringUtils::lowerCase (references.data ( + references.index (i, cellColumn)).toString().toUtf8().constData()); + + if (cell==mId) + { + std::string id = Misc::StringUtils::lowerCase (references.data ( + references.index (i, idColumn)).toString().toUtf8().constData()); + + int state = references.data (references.index (i, stateColumn)).toInt(); + + ids.insert (std::make_pair (id, state==CSMWorld::RecordBase::State_Deleted)); + } + } + + // perform update and remove where needed + bool modified = false; + + for (std::map::iterator iter (mObjects.begin()); + iter!=mObjects.end(); ++iter) + { + if (iter->second->referenceDataChanged (topLeft, bottomRight)) + modified = true; + + std::map::iterator iter2 = ids.find (iter->first); + + if (iter2!=ids.end()) + { + if (iter2->second) + { + removeObject (iter->first); + modified = true; + } + + ids.erase (iter2); + } + } + + // add new objects + for (std::map::iterator iter (ids.begin()); iter!=ids.end(); ++iter) + { + mObjects.insert (std::make_pair ( + iter->first, new Object (mData, mCellNode, iter->first, false))); + + modified = true; + } + + return modified; +} + +bool CSVRender::Cell::referenceAboutToBeRemoved (const QModelIndex& parent, int start, + int end) +{ + if (parent.isValid()) + return false; + + CSMWorld::IdTable& references = dynamic_cast ( + *mData.getTableModel (CSMWorld::UniversalId::Type_References)); + + int idColumn = references.findColumnIndex (CSMWorld::Columns::ColumnId_Id); + + bool modified = false; + + for (int row = start; row<=end; ++row) + if (removeObject (references.data ( + references.index (row, idColumn)).toString().toUtf8().constData())) + modified = true; + + return modified; +} + +bool CSVRender::Cell::referenceAdded (const QModelIndex& parent, int start, int end) +{ + if (parent.isValid()) + return false; + + return addObjects (start, end); +} \ No newline at end of file diff --git a/apps/opencs/view/render/cell.hpp b/apps/opencs/view/render/cell.hpp new file mode 100644 index 0000000000..70adebe45f --- /dev/null +++ b/apps/opencs/view/render/cell.hpp @@ -0,0 +1,73 @@ +#ifndef OPENCS_VIEW_CELL_H +#define OPENCS_VIEW_CELL_H + +#include +#include + +#include + +#include "object.hpp" + +class QModelIndex; + +namespace Ogre +{ + class SceneManager; + class SceneNode; +} + +namespace CSMWorld +{ + class Data; +} + +namespace CSVRender +{ + class Cell + { + CSMWorld::Data& mData; + std::string mId; + Ogre::SceneNode *mCellNode; + std::map mObjects; + + /// Ignored if cell does not have an object with the given ID. + /// + /// \return Was the object deleted? + bool removeObject (const std::string& id); + + /// Add objects from reference table that are within this cell. + /// + /// \return Have any objects been added? + bool addObjects (int start, int end); + + public: + + Cell (CSMWorld::Data& data, Ogre::SceneManager *sceneManager, + const std::string& id, const Ogre::Vector3& origin = Ogre::Vector3 (0, 0, 0)); + + ~Cell(); + + /// \return Did this call result in a modification of the visual representation of + /// this cell? + bool referenceableDataChanged (const QModelIndex& topLeft, + const QModelIndex& bottomRight); + + /// \return Did this call result in a modification of the visual representation of + /// this cell? + bool referenceableAboutToBeRemoved (const QModelIndex& parent, int start, int end); + + /// \return Did this call result in a modification of the visual representation of + /// this cell? + bool referenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + + /// \return Did this call result in a modification of the visual representation of + /// this cell? + bool referenceAboutToBeRemoved (const QModelIndex& parent, int start, int end); + + /// \return Did this call result in a modification of the visual representation of + /// this cell? + bool referenceAdded (const QModelIndex& parent, int start, int end); + }; +} + +#endif diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp new file mode 100644 index 0000000000..bb7c2f386d --- /dev/null +++ b/apps/opencs/view/render/object.cpp @@ -0,0 +1,213 @@ + +#include "object.hpp" + +#include +#include +#include + +#include "../../model/world/data.hpp" +#include "../../model/world/ref.hpp" +#include "../../model/world/refidcollection.hpp" + +void CSVRender::Object::clearSceneNode (Ogre::SceneNode *node) +{ + for (Ogre::SceneNode::ObjectIterator iter = node->getAttachedObjectIterator(); + iter.hasMoreElements(); ) + { + Ogre::MovableObject* object = dynamic_cast (iter.getNext()); + node->getCreator()->destroyMovableObject (object); + } + + for (Ogre::SceneNode::ChildNodeIterator iter = node->getChildIterator(); + iter.hasMoreElements(); ) + { + Ogre::SceneNode* childNode = dynamic_cast (iter.getNext()); + clearSceneNode (childNode); + node->getCreator()->destroySceneNode (childNode); + } +} + +void CSVRender::Object::clear() +{ + mObject.setNull(); + + clearSceneNode (mBase); +} + +void CSVRender::Object::update() +{ + clear(); + + std::string model; + int error = 0; // 1 referemceanöe does not exist, 2 referenceable does not specify a mesh + + const CSMWorld::RefIdCollection& referenceables = mData.getReferenceables(); + + int index = referenceables.searchId (mReferenceableId); + + if (index==-1) + error = 1; + else + { + /// \todo check for Deleted state (error 1) + + model = referenceables.getData (index, + referenceables.findColumnIndex (CSMWorld::Columns::ColumnId_Model)). + toString().toUtf8().constData(); + + if (model.empty()) + error = 2; + } + + if (error) + { + Ogre::Entity* entity = mBase->getCreator()->createEntity (Ogre::SceneManager::PT_CUBE); + entity->setMaterialName("BaseWhite"); /// \todo adjust material according to error + + mBase->attachObject (entity); + } + else + { + mObject = NifOgre::Loader::createObjects (mBase, "Meshes\\" + model); + } +} + +void CSVRender::Object::adjust() +{ + if (mReferenceId.empty()) + return; + + const CSMWorld::CellRef& reference = getReference(); + + // position + if (!mForceBaseToZero) + mBase->setPosition (Ogre::Vector3 ( + reference.mPos.pos[0], reference.mPos.pos[1], reference.mPos.pos[2])); + + // orientation + Ogre::Quaternion xr (Ogre::Radian (-reference.mPos.rot[0]), Ogre::Vector3::UNIT_X); + + Ogre::Quaternion yr (Ogre::Radian (-reference.mPos.rot[1]), Ogre::Vector3::UNIT_Y); + + Ogre::Quaternion zr (Ogre::Radian (-reference.mPos.rot[2]), Ogre::Vector3::UNIT_Z); + + mBase->setOrientation (xr*yr*zr); + + // scale + mBase->setScale (reference.mScale, reference.mScale, reference.mScale); +} + +const CSMWorld::CellRef& CSVRender::Object::getReference() const +{ + if (mReferenceId.empty()) + throw std::logic_error ("object does not represent a reference"); + + return mData.getReferences().getRecord (mReferenceId).get(); +} + +CSVRender::Object::Object (const CSMWorld::Data& data, Ogre::SceneNode *cellNode, + const std::string& id, bool referenceable, bool forceBaseToZero) +: mData (data), mBase (0), mForceBaseToZero (forceBaseToZero) +{ + mBase = cellNode->createChildSceneNode(); + + if (referenceable) + { + mReferenceableId = id; + } + else + { + mReferenceId = id; + mReferenceableId = getReference().mRefID; + } + + update(); + adjust(); +} + +CSVRender::Object::~Object() +{ + clear(); + + if (mBase) + mBase->getCreator()->destroySceneNode (mBase); +} + +bool CSVRender::Object::referenceableDataChanged (const QModelIndex& topLeft, + const QModelIndex& bottomRight) +{ + const CSMWorld::RefIdCollection& referenceables = mData.getReferenceables(); + + int index = referenceables.searchId (mReferenceableId); + + if (index!=-1 && index>=topLeft.row() && index<=bottomRight.row()) + { + update(); + adjust(); + return true; + } + + return false; +} + +bool CSVRender::Object::referenceableAboutToBeRemoved (const QModelIndex& parent, int start, + int end) +{ + const CSMWorld::RefIdCollection& referenceables = mData.getReferenceables(); + + int index = referenceables.searchId (mReferenceableId); + + if (index!=-1 && index>=start && index<=end) + { + // Deletion of referenceable-type objects is handled outside of Object. + if (!mReferenceId.empty()) + { + update(); + adjust(); + return true; + } + } + + return false; +} + +bool CSVRender::Object::referenceDataChanged (const QModelIndex& topLeft, + const QModelIndex& bottomRight) +{ + if (mReferenceId.empty()) + return false; + + const CSMWorld::RefCollection& references = mData.getReferences(); + + int index = references.searchId (mReferenceId); + + if (index!=-1 && index>=topLeft.row() && index<=bottomRight.row()) + { + int columnIndex = + references.findColumnIndex (CSMWorld::Columns::ColumnId_ReferenceableId); + + if (columnIndex>=topLeft.column() && columnIndex<=bottomRight.row()) + { + mReferenceableId = + references.getData (index, columnIndex).toString().toUtf8().constData(); + + update(); + } + + adjust(); + + return true; + } + + return false; +} + +std::string CSVRender::Object::getReferenceId() const +{ + return mReferenceId; +} + +std::string CSVRender::Object::getReferenceableId() const +{ + return mReferenceableId; +} \ No newline at end of file diff --git a/apps/opencs/view/render/object.hpp b/apps/opencs/view/render/object.hpp new file mode 100644 index 0000000000..df39d7393d --- /dev/null +++ b/apps/opencs/view/render/object.hpp @@ -0,0 +1,80 @@ +#ifndef OPENCS_VIEW_OBJECT_H +#define OPENCS_VIEW_OBJECT_H + +#include + +class QModelIndex; + +namespace Ogre +{ + class SceneNode; +} + +namespace CSMWorld +{ + class Data; + class CellRef; +} + +namespace CSVRender +{ + class Object + { + const CSMWorld::Data& mData; + std::string mReferenceId; + std::string mReferenceableId; + Ogre::SceneNode *mBase; + NifOgre::ObjectScenePtr mObject; + bool mForceBaseToZero; + + /// Not implemented + Object (const Object&); + + /// Not implemented + Object& operator= (const Object&); + + /// Destroy all scene nodes and movable objects attached to node. + static void clearSceneNode (Ogre::SceneNode *node); + + /// Remove object from node (includes deleting) + void clear(); + + /// Update model + void update(); + + /// Adjust position, orientation and scale + void adjust(); + + /// Throws an exception if *this was constructed with referenceable + const CSMWorld::CellRef& getReference() const; + + public: + + Object (const CSMWorld::Data& data, Ogre::SceneNode *cellNode, + const std::string& id, bool referenceable, bool forceBaseToZero = false); + /// \param forceBaseToZero If this is a reference ignore the coordinates and place + /// it at 0, 0, 0 instead. + + ~Object(); + + /// \return Did this call result in a modification of the visual representation of + /// this object? + bool referenceableDataChanged (const QModelIndex& topLeft, + const QModelIndex& bottomRight); + + /// \return Did this call result in a modification of the visual representation of + /// this object? + bool referenceableAboutToBeRemoved (const QModelIndex& parent, int start, int end); + + /// \return Did this call result in a modification of the visual representation of + /// this object? + bool referenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + + /// Returns an empty string if this is a refereceable-type object. + std::string getReferenceId() const; + + std::string getReferenceableId() const; + }; +} + +#endif \ No newline at end of file diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index a3f34d218f..20cd6abb5a 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -3,14 +3,137 @@ #include +#include + #include -#include +#include "../../model/world/tablemimedata.hpp" +#include "../../model/world/idtable.hpp" + +bool CSVRender::PagedWorldspaceWidget::adjustCells() +{ + bool modified = false; + bool setCamera = false; + + { + // remove + std::map::iterator iter (mCells.begin()); + + while (iter!=mCells.end()) + { + if (!mSelection.has (iter->first)) + { + delete iter->second; + mCells.erase (iter++); + modified = true; + } + else + ++iter; + } + } + + if (mCells.begin()==mCells.end()) + setCamera = true; + + // add + for (CSMWorld::CellSelection::Iterator iter (mSelection.begin()); iter!=mSelection.end(); + ++iter) + { + if (mCells.find (*iter)==mCells.end()) + { + if (setCamera) + { + setCamera = false; + getCamera()->setPosition (8192*iter->getX()+4096, 8192*iter->getY()+4096, 0); + } + + mCells.insert (std::make_pair (*iter, + new Cell (mDocument.getData(), getSceneManager(), + iter->getId ("std::default")))); + + modified = true; + } + } + + return modified; +} + +void CSVRender::PagedWorldspaceWidget::referenceableDataChanged (const QModelIndex& topLeft, + const QModelIndex& bottomRight) +{ + for (std::map::iterator iter (mCells.begin()); + iter!=mCells.end(); ++iter) + if (iter->second->referenceableDataChanged (topLeft, bottomRight)) + flagAsModified(); +} + +void CSVRender::PagedWorldspaceWidget::referenceableAboutToBeRemoved ( + const QModelIndex& parent, int start, int end) +{ + for (std::map::iterator iter (mCells.begin()); + iter!=mCells.end(); ++iter) + if (iter->second->referenceableAboutToBeRemoved (parent, start, end)) + flagAsModified(); +} + +void CSVRender::PagedWorldspaceWidget::referenceableAdded (const QModelIndex& parent, + int start, int end) +{ + CSMWorld::IdTable& referenceables = dynamic_cast ( + *mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_Referenceables)); + + for (std::map::iterator iter (mCells.begin()); + iter!=mCells.end(); ++iter) + { + QModelIndex topLeft = referenceables.index (start, 0); + QModelIndex bottomRight = + referenceables.index (end, referenceables.columnCount()); + + if (iter->second->referenceableDataChanged (topLeft, bottomRight)) + flagAsModified(); + } +} + +void CSVRender::PagedWorldspaceWidget::referenceDataChanged (const QModelIndex& topLeft, + const QModelIndex& bottomRight) +{ + for (std::map::iterator iter (mCells.begin()); + iter!=mCells.end(); ++iter) + if (iter->second->referenceDataChanged (topLeft, bottomRight)) + flagAsModified(); +} + +void CSVRender::PagedWorldspaceWidget::referenceAboutToBeRemoved (const QModelIndex& parent, + int start, int end) +{ + for (std::map::iterator iter (mCells.begin()); + iter!=mCells.end(); ++iter) + if (iter->second->referenceAboutToBeRemoved (parent, start, end)) + flagAsModified(); +} + +void CSVRender::PagedWorldspaceWidget::referenceAdded (const QModelIndex& parent, int start, + int end) +{ + for (std::map::iterator iter (mCells.begin()); + iter!=mCells.end(); ++iter) + if (iter->second->referenceAdded (parent, start, end)) + flagAsModified(); +} + + CSVRender::PagedWorldspaceWidget::PagedWorldspaceWidget (QWidget* parent, CSMDoc::Document& document) -: WorldspaceWidget (document, parent) +: WorldspaceWidget (document, parent), mDocument (document) {} +CSVRender::PagedWorldspaceWidget::~PagedWorldspaceWidget() +{ + for (std::map::iterator iter (mCells.begin()); + iter!=mCells.end(); ++iter) + delete iter->second; +} + void CSVRender::PagedWorldspaceWidget::useViewHint (const std::string& hint) { if (!hint.empty()) @@ -47,6 +170,10 @@ void CSVRender::PagedWorldspaceWidget::useViewHint (const std::string& hint) void CSVRender::PagedWorldspaceWidget::setCellSelection (const CSMWorld::CellSelection& selection) { mSelection = selection; + + if (adjustCells()) + flagAsModified(); + emit cellSelectionChanged (mSelection); } @@ -72,6 +199,9 @@ void CSVRender::PagedWorldspaceWidget::handleDrop (const std::vector< CSMWorld:: } if (selectionChanged) { + if (adjustCells()) + flagAsModified(); + emit cellSelectionChanged(mSelection); } } diff --git a/apps/opencs/view/render/pagedworldspacewidget.hpp b/apps/opencs/view/render/pagedworldspacewidget.hpp index 0a73c791cb..7f9a66f82b 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.hpp +++ b/apps/opencs/view/render/pagedworldspacewidget.hpp @@ -1,9 +1,12 @@ #ifndef OPENCS_VIEW_PAGEDWORLDSPACEWIDGET_H #define OPENCS_VIEW_PAGEDWORLDSPACEWIDGET_H +#include + #include "../../model/world/cellselection.hpp" #include "worldspacewidget.hpp" +#include "cell.hpp" namespace CSVRender { @@ -11,12 +14,32 @@ namespace CSVRender { Q_OBJECT + CSMDoc::Document& mDocument; CSMWorld::CellSelection mSelection; + std::map mCells; private: std::pair getCoordinatesFromId(const std::string& record) const; + /// Bring mCells into sync with mSelection again. + /// + /// \return Any cells added or removed? + bool adjustCells(); + + virtual void referenceableDataChanged (const QModelIndex& topLeft, + const QModelIndex& bottomRight); + + virtual void referenceableAboutToBeRemoved (const QModelIndex& parent, int start, int end); + + virtual void referenceableAdded (const QModelIndex& index, int start, int end); + + virtual void referenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + + virtual void referenceAboutToBeRemoved (const QModelIndex& parent, int start, int end); + + virtual void referenceAdded (const QModelIndex& index, int start, int end); + public: PagedWorldspaceWidget (QWidget *parent, CSMDoc::Document& document); @@ -24,6 +47,8 @@ namespace CSVRender /// no cells are displayed. The cells to be displayed will be specified later through /// hint system. + virtual ~PagedWorldspaceWidget(); + void useViewHint (const std::string& hint); void setCellSelection (const CSMWorld::CellSelection& selection); diff --git a/apps/opencs/view/render/previewwidget.cpp b/apps/opencs/view/render/previewwidget.cpp index 99e57ea11c..75b4e93967 100644 --- a/apps/opencs/view/render/previewwidget.cpp +++ b/apps/opencs/view/render/previewwidget.cpp @@ -7,194 +7,119 @@ #include "../../model/world/data.hpp" #include "../../model/world/idtable.hpp" -void CSVRender::PreviewWidget::setup() +CSVRender::PreviewWidget::PreviewWidget (CSMWorld::Data& data, + const std::string& id, bool referenceable, QWidget *parent) +: SceneWidget (parent), mData (data), + mObject (data, getSceneManager()->getRootSceneNode(), id, referenceable, true) { setNavigation (&mOrbit); - mNode = getSceneManager()->getRootSceneNode()->createChildSceneNode(); - mNode->setPosition (Ogre::Vector3 (0, 0, 0)); - - setModel(); - QAbstractItemModel *referenceables = mData.getTableModel (CSMWorld::UniversalId::Type_Referenceables); connect (referenceables, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), - this, SLOT (ReferenceableDataChanged (const QModelIndex&, const QModelIndex&))); + this, SLOT (referenceableDataChanged (const QModelIndex&, const QModelIndex&))); connect (referenceables, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), - this, SLOT (ReferenceableAboutToBeRemoved (const QModelIndex&, int, int))); -} + this, SLOT (referenceableAboutToBeRemoved (const QModelIndex&, int, int))); -void CSVRender::PreviewWidget::setModel() -{ - if (mNode) + if (!referenceable) { - mObject.setNull(); + QAbstractItemModel *references = + mData.getTableModel (CSMWorld::UniversalId::Type_References); - if (mReferenceableId.empty()) - return; - - int column = - mData.getReferenceables().findColumnIndex (CSMWorld::Columns::ColumnId_Model); - - int row = mData.getReferenceables().searchId (mReferenceableId); - - if (row==-1) - return; - - QVariant value = mData.getReferenceables().getData (row, column); - - if (!value.isValid()) - return; - - std::string model = value.toString().toUtf8().constData(); - - if (model.empty()) - return; - - mObject = NifOgre::Loader::createObjects (mNode, "Meshes\\" + model); + connect (references, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (referenceDataChanged (const QModelIndex&, const QModelIndex&))); + connect (references, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), + this, SLOT (referenceAboutToBeRemoved (const QModelIndex&, int, int))); } } -void CSVRender::PreviewWidget::adjust() -{ - if (mNode) - { - int row = mData.getReferences().getIndex (mReferenceId); - - float scale = mData.getReferences().getData (row, mData.getReferences(). - findColumnIndex (CSMWorld::Columns::ColumnId_Scale)).toFloat(); - float rotX = mData.getReferences().getData (row, mData.getReferences(). - findColumnIndex (CSMWorld::Columns::ColumnId_PositionXRot)).toFloat(); - float rotY = mData.getReferences().getData (row, mData.getReferences(). - findColumnIndex (CSMWorld::Columns::ColumnId_PositionYRot)).toFloat(); - float rotZ = mData.getReferences().getData (row, mData.getReferences(). - findColumnIndex (CSMWorld::Columns::ColumnId_PositionZRot)).toFloat(); - - mNode->setScale (scale, scale, scale); - - Ogre::Quaternion xr (Ogre::Radian(-rotX), Ogre::Vector3::UNIT_X); - - Ogre::Quaternion yr (Ogre::Radian(-rotY), Ogre::Vector3::UNIT_Y); - - Ogre::Quaternion zr (Ogre::Radian(-rotZ), Ogre::Vector3::UNIT_Z); - - mNode->setOrientation (xr*yr*zr); - } -} - -CSVRender::PreviewWidget::PreviewWidget (CSMWorld::Data& data, - const std::string& referenceableId, QWidget *parent) -: SceneWidget (parent), mData (data), mNode (0), mReferenceableId (referenceableId) -{ - setup(); -} - -CSVRender::PreviewWidget::PreviewWidget (CSMWorld::Data& data, - const std::string& referenceableId, const std::string& referenceId, QWidget *parent) -: SceneWidget (parent), mData (data), mReferenceableId (referenceableId), - mReferenceId (referenceId) -{ - setup(); - - adjust(); - - QAbstractItemModel *references = - mData.getTableModel (CSMWorld::UniversalId::Type_References); - - connect (references, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), - this, SLOT (ReferenceDataChanged (const QModelIndex&, const QModelIndex&))); - connect (references, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), - this, SLOT (ReferenceAboutToBeRemoved (const QModelIndex&, int, int))); -} - -void CSVRender::PreviewWidget::ReferenceableDataChanged (const QModelIndex& topLeft, +void CSVRender::PreviewWidget::referenceableDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { - if (mReferenceableId.empty()) - return; - - CSMWorld::IdTable& referenceables = dynamic_cast ( - *mData.getTableModel (CSMWorld::UniversalId::Type_Referenceables)); - - QModelIndex index = referenceables.getModelIndex (mReferenceableId, 0); - - if (index.row()>=topLeft.row() && index.row()<=bottomRight.row()) - { - /// \todo possible optimisation; check columns and only update if relevant columns have - /// changed - setModel(); + if (mObject.referenceableDataChanged (topLeft, bottomRight)) flagAsModified(); + + if (mObject.getReferenceId().empty()) + { + CSMWorld::IdTable& referenceables = dynamic_cast ( + *mData.getTableModel (CSMWorld::UniversalId::Type_Referenceables)); + + QModelIndex index = referenceables.getModelIndex (mObject.getReferenceableId(), + referenceables.findColumnIndex (CSMWorld::Columns::ColumnId_Modification)); + + if (referenceables.data (index).toInt()==CSMWorld::RecordBase::State_Deleted) + emit closeRequest(); } } -void CSVRender::PreviewWidget::ReferenceableAboutToBeRemoved (const QModelIndex& parent, int start, +void CSVRender::PreviewWidget::referenceableAboutToBeRemoved (const QModelIndex& parent, int start, int end) { - if (mReferenceableId.empty()) + if (mObject.referenceableAboutToBeRemoved (parent, start, end)) + flagAsModified(); + + if (mObject.getReferenceableId().empty()) return; CSMWorld::IdTable& referenceables = dynamic_cast ( *mData.getTableModel (CSMWorld::UniversalId::Type_Referenceables)); - QModelIndex index = referenceables.getModelIndex (mReferenceableId, 0); + QModelIndex index = referenceables.getModelIndex (mObject.getReferenceableId(), 0); if (index.row()>=start && index.row()<=end) { - if (mReferenceId.empty()) + if (mObject.getReferenceId().empty()) { // this is a preview for a referenceble emit closeRequest(); } - else - { - // this is a preview for a reference - mObject.setNull(); - flagAsModified(); - } } } -void CSVRender::PreviewWidget::ReferenceDataChanged (const QModelIndex& topLeft, +void CSVRender::PreviewWidget::referenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { - if (mReferenceId.empty()) + if (mObject.referenceDataChanged (topLeft, bottomRight)) + flagAsModified(); + + if (mObject.getReferenceId().empty()) return; CSMWorld::IdTable& references = dynamic_cast ( *mData.getTableModel (CSMWorld::UniversalId::Type_References)); + // check for deleted state + { + QModelIndex index = references.getModelIndex (mObject.getReferenceId(), + references.findColumnIndex (CSMWorld::Columns::ColumnId_Modification)); + + if (references.data (index).toInt()==CSMWorld::RecordBase::State_Deleted) + { + emit closeRequest(); + return; + } + } + int columnIndex = references.findColumnIndex (CSMWorld::Columns::ColumnId_ReferenceableId); - QModelIndex index = references.getModelIndex (mReferenceId, columnIndex); + QModelIndex index = references.getModelIndex (mObject.getReferenceId(), columnIndex); if (index.row()>=topLeft.row() && index.row()<=bottomRight.row()) - { - /// \todo possible optimisation; check columns and only update if relevant columns have - /// changed - adjust(); - if (index.column()>=topLeft.column() && index.column()<=bottomRight.row()) - { - mReferenceableId = references.data (index).toString().toUtf8().constData(); - emit referenceableIdChanged (mReferenceableId); - setModel(); - } - - flagAsModified(); - } + emit referenceableIdChanged (mObject.getReferenceableId()); } -void CSVRender::PreviewWidget::ReferenceAboutToBeRemoved (const QModelIndex& parent, int start, +void CSVRender::PreviewWidget::referenceAboutToBeRemoved (const QModelIndex& parent, int start, int end) { - if (mReferenceId.empty()) + if (mObject.getReferenceId().empty()) return; CSMWorld::IdTable& references = dynamic_cast ( *mData.getTableModel (CSMWorld::UniversalId::Type_References)); - QModelIndex index = references.getModelIndex (mReferenceId, 0); + QModelIndex index = references.getModelIndex (mObject.getReferenceId(), 0); if (index.row()>=start && index.row()<=end) emit closeRequest(); diff --git a/apps/opencs/view/render/previewwidget.hpp b/apps/opencs/view/render/previewwidget.hpp index 7a63d8fb12..dd6a99c0f9 100644 --- a/apps/opencs/view/render/previewwidget.hpp +++ b/apps/opencs/view/render/previewwidget.hpp @@ -1,11 +1,10 @@ #ifndef OPENCS_VIEW_PREVIEWWIDGET_H #define OPENCS_VIEW_PREVIEWWIDGET_H -#include - #include "scenewidget.hpp" #include "navigationorbit.hpp" +#include "object.hpp" class QModelIndex; @@ -22,26 +21,13 @@ namespace CSVRender CSMWorld::Data& mData; CSVRender::NavigationOrbit mOrbit; - NifOgre::ObjectScenePtr mObject; - Ogre::SceneNode *mNode; - std::string mReferenceId; - std::string mReferenceableId; - - void setup(); - - void setModel(); - - void adjust(); - ///< Adjust referenceable preview according to the reference + Object mObject; public: - PreviewWidget (CSMWorld::Data& data, const std::string& referenceableId, + PreviewWidget (CSMWorld::Data& data, const std::string& id, bool referenceable, QWidget *parent = 0); - PreviewWidget (CSMWorld::Data& data, const std::string& referenceableId, - const std::string& referenceId, QWidget *parent = 0); - signals: void closeRequest(); @@ -50,14 +36,14 @@ namespace CSVRender private slots: - void ReferenceableDataChanged (const QModelIndex& topLeft, + void referenceableDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); - void ReferenceableAboutToBeRemoved (const QModelIndex& parent, int start, int end); + void referenceableAboutToBeRemoved (const QModelIndex& parent, int start, int end); - void ReferenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + void referenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); - void ReferenceAboutToBeRemoved (const QModelIndex& parent, int start, int end); + void referenceAboutToBeRemoved (const QModelIndex& parent, int start, int end); }; } diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index 52e7afefd3..76f6db385d 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -43,7 +43,7 @@ namespace CSVRender mCamera->setPosition (300, 0, 0); mCamera->lookAt (0, 0, 0); mCamera->setNearClipDistance (0.1); - mCamera->setFarClipDistance (30000); + mCamera->setFarClipDistance (300000); ///< \todo make this configurable mCamera->roll (Ogre::Degree (90)); setLighting (&mLightingDay); @@ -87,7 +87,7 @@ namespace CSVRender std::stringstream windowHandle; #ifdef WIN32 - windowHandle << Ogre::StringConverter::toString((unsigned long)(this->winId())); + windowHandle << Ogre::StringConverter::toString((uintptr_t)(this->winId())); #else windowHandle << this->winId(); #endif @@ -137,6 +137,11 @@ namespace CSVRender return mSceneMgr; } + Ogre::Camera *SceneWidget::getCamera() + { + return mCamera; + } + void SceneWidget::flagAsModified() { mUpdate = true; diff --git a/apps/opencs/view/render/scenewidget.hpp b/apps/opencs/view/render/scenewidget.hpp index 7f8f104f15..f6b41942f7 100644 --- a/apps/opencs/view/render/scenewidget.hpp +++ b/apps/opencs/view/render/scenewidget.hpp @@ -49,6 +49,8 @@ namespace CSVRender Ogre::SceneManager *getSceneManager(); + Ogre::Camera *getCamera(); + void flagAsModified(); void setDefaultAmbient (const Ogre::ColourValue& colour); diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index 166c85f443..0b656ddc6e 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -21,6 +21,8 @@ void CSVRender::UnpagedWorldspaceWidget::update() setDefaultAmbient (colour); /// \todo deal with mSunlight and mFog/mForDensity + + flagAsModified(); } CSVRender::UnpagedWorldspaceWidget::UnpagedWorldspaceWidget (const std::string& cellId, CSMDoc::Document& document, QWidget* parent) @@ -29,12 +31,17 @@ CSVRender::UnpagedWorldspaceWidget::UnpagedWorldspaceWidget (const std::string& mCellsModel = &dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); + mReferenceablesModel = &dynamic_cast ( + *document.getData().getTableModel (CSMWorld::UniversalId::Type_Referenceables)); + connect (mCellsModel, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), this, SLOT (cellDataChanged (const QModelIndex&, const QModelIndex&))); connect (mCellsModel, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), this, SLOT (cellRowsAboutToBeRemoved (const QModelIndex&, int, int))); update(); + + mCell.reset (new Cell (document.getData(), getSceneManager(), mCellId)); } void CSVRender::UnpagedWorldspaceWidget::cellDataChanged (const QModelIndex& topLeft, @@ -72,6 +79,62 @@ void CSVRender::UnpagedWorldspaceWidget::handleDrop (const std::vector< CSMWorld mCellId = data.begin()->getId(); update(); emit cellChanged(*data.begin()); + + /// \todo replace mCell +} + +void CSVRender::UnpagedWorldspaceWidget::referenceableDataChanged (const QModelIndex& topLeft, + const QModelIndex& bottomRight) +{ + if (mCell.get()) + if (mCell.get()->referenceableDataChanged (topLeft, bottomRight)) + flagAsModified(); +} + +void CSVRender::UnpagedWorldspaceWidget::referenceableAboutToBeRemoved ( + const QModelIndex& parent, int start, int end) +{ + if (mCell.get()) + if (mCell.get()->referenceableAboutToBeRemoved (parent, start, end)) + flagAsModified(); +} + +void CSVRender::UnpagedWorldspaceWidget::referenceableAdded (const QModelIndex& parent, + int start, int end) +{ + if (mCell.get()) + { + QModelIndex topLeft = mReferenceablesModel->index (start, 0); + QModelIndex bottomRight = + mReferenceablesModel->index (end, mReferenceablesModel->columnCount()); + + if (mCell.get()->referenceableDataChanged (topLeft, bottomRight)) + flagAsModified(); + } +} + +void CSVRender::UnpagedWorldspaceWidget::referenceDataChanged (const QModelIndex& topLeft, + const QModelIndex& bottomRight) +{ + if (mCell.get()) + if (mCell.get()->referenceDataChanged (topLeft, bottomRight)) + flagAsModified(); +} + +void CSVRender::UnpagedWorldspaceWidget::referenceAboutToBeRemoved (const QModelIndex& parent, + int start, int end) +{ + if (mCell.get()) + if (mCell.get()->referenceAboutToBeRemoved (parent, start, end)) + flagAsModified(); +} + +void CSVRender::UnpagedWorldspaceWidget::referenceAdded (const QModelIndex& parent, int start, + int end) +{ + if (mCell.get()) + if (mCell.get()->referenceAdded (parent, start, end)) + flagAsModified(); } CSVRender::WorldspaceWidget::dropRequirments CSVRender::UnpagedWorldspaceWidget::getDropRequirements (CSVRender::WorldspaceWidget::dropType type) const diff --git a/apps/opencs/view/render/unpagedworldspacewidget.hpp b/apps/opencs/view/render/unpagedworldspacewidget.hpp index bb53408452..ee8377fae4 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.hpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.hpp @@ -2,8 +2,10 @@ #define OPENCS_VIEW_UNPAGEDWORLDSPACEWIDGET_H #include +#include #include "worldspacewidget.hpp" +#include "cell.hpp" class QModelIndex; @@ -25,6 +27,8 @@ namespace CSVRender std::string mCellId; CSMWorld::IdTable *mCellsModel; + CSMWorld::IdTable *mReferenceablesModel; + std::auto_ptr mCell; void update(); @@ -37,6 +41,21 @@ namespace CSVRender virtual void handleDrop(const std::vector& data); + private: + + virtual void referenceableDataChanged (const QModelIndex& topLeft, + const QModelIndex& bottomRight); + + virtual void referenceableAboutToBeRemoved (const QModelIndex& parent, int start, int end); + + virtual void referenceableAdded (const QModelIndex& index, int start, int end); + + virtual void referenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + + virtual void referenceAboutToBeRemoved (const QModelIndex& parent, int start, int end); + + virtual void referenceAdded (const QModelIndex& index, int start, int end); + private slots: void cellDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index 59b82bb678..fee2f0a160 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -10,15 +10,30 @@ #include "../world/scenetoolmode.hpp" #include -CSVRender::WorldspaceWidget::WorldspaceWidget (const CSMDoc::Document& document, QWidget* parent) +CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidget* parent) : SceneWidget (parent), mDocument(document) { - Ogre::Entity* ent = getSceneManager()->createEntity("cube", Ogre::SceneManager::PT_CUBE); - ent->setMaterialName("BaseWhite"); - - getSceneManager()->getRootSceneNode()->attachObject(ent); - setAcceptDrops(true); + + QAbstractItemModel *referenceables = + document.getData().getTableModel (CSMWorld::UniversalId::Type_Referenceables); + + connect (referenceables, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (referenceableDataChanged (const QModelIndex&, const QModelIndex&))); + connect (referenceables, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), + this, SLOT (referenceableAboutToBeRemoved (const QModelIndex&, int, int))); + connect (referenceables, SIGNAL (rowsInserted (const QModelIndex&, int, int)), + this, SLOT (referenceableAdded (const QModelIndex&, int, int))); + + QAbstractItemModel *references = + document.getData().getTableModel (CSMWorld::UniversalId::Type_References); + + connect (references, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (referenceDataChanged (const QModelIndex&, const QModelIndex&))); + connect (references, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), + this, SLOT (referenceAboutToBeRemoved (const QModelIndex&, int, int))); + connect (references, SIGNAL (rowsInserted (const QModelIndex&, int, int)), + this, SLOT (referenceAdded (const QModelIndex&, int, int))); } void CSVRender::WorldspaceWidget::selectNavigationMode (const std::string& mode) @@ -120,9 +135,11 @@ void CSVRender::WorldspaceWidget::dragMoveEvent(QDragMoveEvent *event) void CSVRender::WorldspaceWidget::dropEvent (QDropEvent* event) { const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); + if (!mime) // May happen when non-records (e.g. plain text) are dragged and dropped + return; if (mime->fromDocument (mDocument)) { emit dataDropped(mime->getData()); } //not handling drops from different documents at the moment -} \ No newline at end of file +} diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index a14e039154..2af90b0feb 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -47,7 +47,7 @@ namespace CSVRender ignored //either mixed cells, or not cells }; - WorldspaceWidget (const CSMDoc::Document& document, QWidget *parent = 0); + WorldspaceWidget (CSMDoc::Document& document, QWidget *parent = 0); CSVWorld::SceneToolMode *makeNavigationSelector (CSVWorld::SceneToolbar *parent); ///< \attention The created tool is not added to the toolbar (via addTool). Doing that @@ -79,6 +79,19 @@ namespace CSVRender void selectNavigationMode (const std::string& mode); + virtual void referenceableDataChanged (const QModelIndex& topLeft, + const QModelIndex& bottomRight) = 0; + + virtual void referenceableAboutToBeRemoved (const QModelIndex& parent, int start, int end) = 0; + + virtual void referenceableAdded (const QModelIndex& index, int start, int end) = 0; + + virtual void referenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) = 0; + + virtual void referenceAboutToBeRemoved (const QModelIndex& parent, int start, int end) = 0; + + virtual void referenceAdded (const QModelIndex& index, int start, int end) = 0; + signals: void closeRequest(); diff --git a/apps/opencs/view/world/previewsubview.cpp b/apps/opencs/view/world/previewsubview.cpp index e9a30e65d2..49e6d93618 100644 --- a/apps/opencs/view/world/previewsubview.cpp +++ b/apps/opencs/view/world/previewsubview.cpp @@ -23,10 +23,10 @@ CSVWorld::PreviewSubView::PreviewSubView (const CSMWorld::UniversalId& id, CSMDo referenceableIdChanged (referenceableId); mScene = - new CSVRender::PreviewWidget (document.getData(), referenceableId, id.getId(), this); + new CSVRender::PreviewWidget (document.getData(), id.getId(), false, this); } else - mScene = new CSVRender::PreviewWidget (document.getData(), id.getId(), this); + mScene = new CSVRender::PreviewWidget (document.getData(), id.getId(), true, this); SceneToolbar *toolbar = new SceneToolbar (48+6, this); diff --git a/apps/opencs/view/world/regionmap.cpp b/apps/opencs/view/world/regionmap.cpp index 849a1988af..9497e40544 100644 --- a/apps/opencs/view/world/regionmap.cpp +++ b/apps/opencs/view/world/regionmap.cpp @@ -382,6 +382,9 @@ void CSVWorld::RegionMap::dropEvent (QDropEvent* event) } const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); + if (!mime) // May happen when non-records (e.g. plain text) are dragged and dropped + return; + if (mime->fromDocument(mDocument) && mime->holdsType(CSMWorld::UniversalId::Type_Region)) { CSMWorld::UniversalId record (mime->returnMatching (CSMWorld::UniversalId::Type_Region)); @@ -402,4 +405,4 @@ void CSVWorld::RegionMap::dropEvent (QDropEvent* event) mRegionId = record.getId(); } -} \ No newline at end of file +} diff --git a/apps/opencs/view/world/scriptedit.cpp b/apps/opencs/view/world/scriptedit.cpp index b1528d5254..23bc76000d 100644 --- a/apps/opencs/view/world/scriptedit.cpp +++ b/apps/opencs/view/world/scriptedit.cpp @@ -45,19 +45,36 @@ CSVWorld::ScriptEdit::ScriptEdit (QWidget* parent, const CSMDoc::Document& docum void CSVWorld::ScriptEdit::dragEnterEvent (QDragEnterEvent* event) { - setTextCursor (cursorForPosition (event->pos())); - event->acceptProposedAction(); + const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); + if (!mime) + QTextEdit::dragEnterEvent(event); + else + { + setTextCursor (cursorForPosition (event->pos())); + event->acceptProposedAction(); + } } void CSVWorld::ScriptEdit::dragMoveEvent (QDragMoveEvent* event) { - setTextCursor (cursorForPosition (event->pos())); - event->accept(); + const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); + if (!mime) + QTextEdit::dragMoveEvent(event); + else + { + setTextCursor (cursorForPosition (event->pos())); + event->accept(); + } } void CSVWorld::ScriptEdit::dropEvent (QDropEvent* event) { const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); + if (!mime) // May happen when non-records (e.g. plain text) are dragged and dropped + { + QTextEdit::dropEvent(event); + return; + } setTextCursor (cursorForPosition (event->pos())); diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 0fc7dd4b2f..6377b03539 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -44,6 +44,10 @@ void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) mDispatcher->setSelection (records); + std::vector extendedTypes = mDispatcher->getExtendedTypes(); + + mDispatcher->setExtendedTypes (extendedTypes); + // create context menu QMenu menu (this); @@ -63,11 +67,21 @@ void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) menu.addAction (mCreateAction); if (mDispatcher->canRevert()) + { menu.addAction (mRevertAction); + if (!extendedTypes.empty()) + menu.addAction (mExtendedRevertAction); + } + if (mDispatcher->canDelete()) + { menu.addAction (mDeleteAction); + if (!extendedTypes.empty()) + menu.addAction (mExtendedDeleteAction); + } + if (mModel->getFeatures() & CSMWorld::IdTable::Feature_ReorderWithinTopic) { /// \todo allow reordering of multiple rows @@ -101,12 +115,12 @@ void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) if (selectedRows.size()==1) { + int row = selectedRows.begin()->row(); + + row = mProxyModel->mapToSource (mProxyModel->index (row, 0)).row(); + if (mModel->getFeatures() & CSMWorld::IdTable::Feature_View) { - int row = selectedRows.begin()->row(); - - row = mProxyModel->mapToSource (mProxyModel->index (row, 0)).row(); - CSMWorld::UniversalId id = mModel->view (row).first; int index = mDocument.getData().getCells().searchId (id.getId()); @@ -118,7 +132,16 @@ void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) } if (mModel->getFeatures() & CSMWorld::IdTable::Feature_Preview) - menu.addAction (mPreviewAction); + { + QModelIndex index = mModel->index (row, + mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Modification)); + + CSMWorld::RecordBase::State state = static_cast ( + mModel->data (index).toInt()); + + if (state!=CSMWorld::RecordBase::State_Deleted) + menu.addAction (mPreviewAction); + } } menu.exec (event->globalPos()); @@ -203,6 +226,18 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, connect (mPreviewAction, SIGNAL (triggered()), this, SLOT (previewRecord())); addAction (mPreviewAction); + /// \todo add a user option, that redirects the extended action to an input panel (in + /// the bottom bar) that lets the user select which record collections should be + /// modified. + + mExtendedDeleteAction = new QAction (tr ("Extended Delete Record"), this); + connect (mExtendedDeleteAction, SIGNAL (triggered()), mDispatcher, SLOT (executeExtendedDelete())); + addAction (mExtendedDeleteAction); + + mExtendedRevertAction = new QAction (tr ("Extended Revert Record"), this); + connect (mExtendedRevertAction, SIGNAL (triggered()), mDispatcher, SLOT (executeExtendedRevert())); + addAction (mExtendedRevertAction); + connect (mProxyModel, SIGNAL (rowsInserted (const QModelIndex&, int, int)), this, SLOT (tableSizeUpdate())); @@ -351,7 +386,12 @@ void CSVWorld::Table::previewRecord() { std::string id = getUniversalId (selectedRows.begin()->row()).getId(); - emit editRequest (CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Preview, id) , ""); + QModelIndex index = mModel->getModelIndex (id, + mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Modification)); + + if (mModel->data (index)!=CSMWorld::RecordBase::State_Deleted) + emit editRequest (CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Preview, id), + ""); } } @@ -438,6 +478,9 @@ void CSVWorld::Table::dropEvent(QDropEvent *event) } const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); + if (!mime) // May happen when non-records (e.g. plain text) are dragged and dropped + return; + if (mime->fromDocument (mDocument)) { CSMWorld::ColumnBase::Display display = static_cast diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp index 54971fb668..255c430ea5 100644 --- a/apps/opencs/view/world/table.hpp +++ b/apps/opencs/view/world/table.hpp @@ -46,6 +46,8 @@ namespace CSVWorld QAction *mMoveDownAction; QAction *mViewAction; QAction *mPreviewAction; + QAction *mExtendedDeleteAction; + QAction *mExtendedRevertAction; CSMWorld::IdTableProxyModel *mProxyModel; CSMWorld::IdTable *mModel; int mRecordStatusDisplay; diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp index a0bef366b8..327fb1c0e4 100644 --- a/apps/opencs/view/world/tablesubview.cpp +++ b/apps/opencs/view/world/tablesubview.cpp @@ -131,6 +131,9 @@ bool CSVWorld::TableSubView::eventFilter (QObject* object, QEvent* event) { QDropEvent* drop = dynamic_cast(event); const CSMWorld::TableMimeData* data = dynamic_cast(drop->mimeData()); + if (!data) // May happen when non-records (e.g. plain text) are dragged and dropped + return false; + bool handled = data->holdsType(CSMWorld::UniversalId::Type_Filter); if (handled) { diff --git a/apps/opencs/view/world/util.cpp b/apps/opencs/view/world/util.cpp index 5819467c4a..e60a9a4e18 100644 --- a/apps/opencs/view/world/util.cpp +++ b/apps/opencs/view/world/util.cpp @@ -256,6 +256,9 @@ void CSVWorld::DropLineEdit::dragMoveEvent(QDragMoveEvent *event) void CSVWorld::DropLineEdit::dropEvent(QDropEvent *event) { const CSMWorld::TableMimeData* data(dynamic_cast(event->mimeData())); + if (!data) // May happen when non-records (e.g. plain text) are dragged and dropped + return; + emit tableMimeDataDropped(data->getData(), data->getDocumentPtr()); //WIP } diff --git a/apps/opencs/view/world/util.hpp b/apps/opencs/view/world/util.hpp index 1c7e37818b..c95a249820 100644 --- a/apps/opencs/view/world/util.hpp +++ b/apps/opencs/view/world/util.hpp @@ -73,9 +73,9 @@ namespace CSVWorld ~CommandDelegateFactoryCollection(); void add (CSMWorld::ColumnBase::Display display, CommandDelegateFactory *factory); - ///< The ownership of \æ factory is transferred to *this. + ///< The ownership of \a factory is transferred to *this. /// - /// This function must not be called more than once per value of \æ display. + /// This function must not be called more than once per value of \a display. CommandDelegate *makeDelegate (CSMWorld::ColumnBase::Display display, QUndoStack& undoStack, QObject *parent) const; diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 8310e8abfa..5c5b0d16ca 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -15,7 +15,7 @@ add_openmw_dir (mwrender renderingmanager debugging sky camera animation npcanimation creatureanimation activatoranimation actors objects renderinginterface localmap occlusionquery water shadows characterpreview globalmap videoplayer ripplesimulation refraction - terrainstorage renderconst effectmanager weaponanimation + terrainstorage renderconst effectmanager weaponanimation terraingrid ) add_openmw_dir (mwinput @@ -142,10 +142,6 @@ if(APPLE) endif() endif(APPLE) -if(DPKG_PROGRAM) - INSTALL(TARGETS openmw RUNTIME DESTINATION games COMPONENT openmw) -endif(DPKG_PROGRAM) - if (BUILD_WITH_CODE_COVERAGE) add_definitions (--coverage) target_link_libraries(openmw gcov) diff --git a/apps/openmw/crashcatcher.cpp b/apps/openmw/crashcatcher.cpp index 65a0369193..75d2d7953a 100644 --- a/apps/openmw/crashcatcher.cpp +++ b/apps/openmw/crashcatcher.cpp @@ -128,7 +128,7 @@ static void gdb_info(pid_t pid) int fd; /* Create a temp file to put gdb commands into */ - strcpy(respfile, "gdb-respfile-XXXXXX"); + strcpy(respfile, "/tmp/gdb-respfile-XXXXXX"); if((fd=mkstemp(respfile)) >= 0 && (f=fdopen(fd, "w")) != NULL) { fprintf(f, "attach %d\n" @@ -381,10 +381,7 @@ static void crash_handler(const char *logfile) if(logfile) { - char cwd[MAXPATHLEN]; - getcwd(cwd, MAXPATHLEN); - - std::string message = "OpenMW has encountered a fatal error.\nCrash log saved to '" + std::string(cwd) + "/" + std::string(logfile) + "'.\n Please report this to https://bugs.openmw.org !"; + std::string message = "OpenMW has encountered a fatal error.\nCrash log saved to '" + std::string(logfile) + "'.\n Please report this to https://bugs.openmw.org !"; SDL_ShowSimpleMessageBox(0, "Fatal Error", message.c_str(), NULL); } exit(0); diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 3647f8ccb0..f8b4c98568 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -91,7 +91,11 @@ bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt) if (mUseSound) MWBase::Environment::get().getSoundManager()->update(frametime); - bool paused = MWBase::Environment::get().getWindowManager()->isGuiMode(); + // GUI active? Most game processing will be paused, but scripts still run. + bool guiActive = MWBase::Environment::get().getWindowManager()->isGuiMode(); + + // Main menu opened? Then scripts are also paused. + bool paused = MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu); // update game state MWBase::Environment::get().getStateManager()->update (frametime); @@ -99,15 +103,18 @@ bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt) if (MWBase::Environment::get().getStateManager()->getState()== MWBase::StateManager::State_Running) { - // global scripts - MWBase::Environment::get().getScriptManager()->getGlobalScripts().run(); - - // local scripts - executeLocalScripts(); - - MWBase::Environment::get().getWorld()->markCellAsUnchanged(); - if (!paused) + { + // global scripts + MWBase::Environment::get().getScriptManager()->getGlobalScripts().run(); + + // local scripts + executeLocalScripts(); + + MWBase::Environment::get().getWorld()->markCellAsUnchanged(); + } + + if (!guiActive) MWBase::Environment::get().getWorld()->advanceTime( frametime*MWBase::Environment::get().getWorld()->getTimeScaleFactor()/3600); } @@ -118,14 +125,14 @@ bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt) MWBase::StateManager::State_NoGame) { MWBase::Environment::get().getMechanicsManager()->update(frametime, - paused); + guiActive); } if (MWBase::Environment::get().getStateManager()->getState()== MWBase::StateManager::State_Running) { MWWorld::Ptr player = mEnvironment.getWorld()->getPlayerPtr(); - if(!paused && player.getClass().getCreatureStats(player).isDead()) + if(!guiActive && player.getClass().getCreatureStats(player).isDead()) MWBase::Environment::get().getStateManager()->endGame(); } @@ -133,7 +140,7 @@ bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt) if (MWBase::Environment::get().getStateManager()->getState()!= MWBase::StateManager::State_NoGame) { - MWBase::Environment::get().getWorld()->update(frametime, paused); + MWBase::Environment::get().getWorld()->update(frametime, guiActive); } // update GUI @@ -357,7 +364,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) // Create input and UI first to set up a bootstrapping environment for // showing a loading screen and keeping the window responsive while doing so - std::string keybinderUser = (mCfgMgr.getUserConfigPath() / "input.xml").string(); + std::string keybinderUser = (mCfgMgr.getUserConfigPath() / "input_v1.xml").string(); bool keybinderUserExists = boost::filesystem::exists(keybinderUser); MWInput::InputManager* input = new MWInput::InputManager (*mOgre, *this, keybinderUser, keybinderUserExists, mGrab); mEnvironment.setInputManager (input); @@ -489,24 +496,7 @@ void OMW::Engine::activate() if (ptr.getClass().getName(ptr) == "") // objects without name presented to user can never be activated return; - MWScript::InterpreterContext interpreterContext (&ptr.getRefData().getLocals(), ptr); - - interpreterContext.activate (ptr); - - std::string script = ptr.getClass().getScript (ptr); - - MWBase::Environment::get().getWorld()->breakInvisibility(MWBase::Environment::get().getWorld()->getPlayerPtr()); - - if (!script.empty()) - { - MWBase::Environment::get().getWorld()->getLocalScripts().setIgnore (ptr); - MWBase::Environment::get().getScriptManager()->run (script, interpreterContext); - } - - if (!interpreterContext.hasActivationBeenHandled()) - { - interpreterContext.executeActivation(ptr); - } + MWBase::Environment::get().getWorld()->activate(ptr, MWBase::Environment::get().getWorld()->getPlayerPtr()); } void OMW::Engine::screenshot() diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 3098d953ec..adde408b9e 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -4,19 +4,20 @@ #include #include -#include +#include +#include #include "engine.hpp" -#if defined(_WIN32) && !defined(_CONSOLE) #include #include +#include +#if defined(_WIN32) // For OutputDebugString #define WIN32_LEAN_AND_MEAN #include // makes __argc and __argv available on windows #include - #endif @@ -253,58 +254,8 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat return true; } -int main(int argc, char**argv) -{ -#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX || OGRE_PLATFORM == OGRE_PLATFORM_APPLE - // Unix crash catcher - if ((argc == 2 && strcmp(argv[1], "--cc-handle-crash") == 0) || !is_debugger_attached()) - { - int s[5] = { SIGSEGV, SIGILL, SIGFPE, SIGBUS, SIGABRT }; - cc_install_handlers(argc, argv, 5, s, "crash.log", NULL); - std::cout << "Installing crash catcher" << std::endl; - } - else - std::cout << "Running in a debugger, not installing crash catcher" << std::endl; -#endif +#if defined(_WIN32) && defined(_DEBUG) -#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE - // set current dir to bundle path - boost::filesystem::path bundlePath = boost::filesystem::path(Ogre::macBundlePath()).parent_path(); - boost::filesystem::current_path(bundlePath); -#endif - - try - { - Files::ConfigurationManager cfgMgr; - OMW::Engine engine(cfgMgr); - - if (parseOptions(argc, argv, engine, cfgMgr)) - { - engine.go(); - } - } - catch (std::exception &e) - { -#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX || OGRE_PLATFORM == OGRE_PLATFORM_APPLE - if (isatty(fileno(stdin)) || !SDL_WasInit(SDL_INIT_VIDEO)) - std::cerr << "\nERROR: " << e.what() << std::endl; - else -#endif - SDL_ShowSimpleMessageBox(0, "OpenMW: Fatal error", e.what(), NULL); - - return 1; - } - - return 0; -} - -// Platform specific for Windows when there is no console built into the executable. -// Windows will call the WinMain function instead of main in this case, the normal -// main function is then called with the __argc and __argv parameters. -// In addition if it is a debug build it will redirect cout to the debug console in Visual Studio -#if defined(_WIN32) && !defined(_CONSOLE) - -#if defined(_DEBUG) class DebugOutput : public boost::iostreams::sink { public: @@ -318,11 +269,11 @@ public: } }; #else -class Logger : public boost::iostreams::sink +class Tee : public boost::iostreams::sink { public: - Logger(std::ofstream &stream) - : out(stream) + Tee(std::ostream &stream, std::ostream &stream2) + : out(stream), out2(stream2) { } @@ -330,38 +281,107 @@ public: { out.write (str, size); out.flush(); + out2.write (str, size); + out2.flush(); return size; } private: - std::ofstream &out; + std::ostream &out; + std::ostream &out2; }; #endif -int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) +int main(int argc, char**argv) { - std::streambuf* old_rdbuf = std::cout.rdbuf (); + // Some objects used to redirect cout and cerr + // Scope must be here, so this still works inside the catch block for logging exceptions + std::streambuf* cout_rdbuf = std::cout.rdbuf (); + std::streambuf* cerr_rdbuf = std::cerr.rdbuf (); + +#if !(defined(_WIN32) && defined(_DEBUG)) + boost::iostreams::stream_buffer coutsb; + boost::iostreams::stream_buffer cerrsb; +#endif + + std::ostream oldcout(cout_rdbuf); + std::ostream oldcerr(cerr_rdbuf); + + boost::filesystem::ofstream logfile; int ret = 0; -#if defined(_DEBUG) - // Redirect cout to VS debug output when running in debug mode + try { + Files::ConfigurationManager cfgMgr; + +#if defined(_WIN32) && defined(_DEBUG) + // Redirect cout and cerr to VS debug output when running in debug mode boost::iostreams::stream_buffer sb; sb.open(DebugOutput()); -#else - // Redirect cout to openmw.log - std::ofstream logfile ("openmw.log"); - { - boost::iostreams::stream_buffer sb; - sb.open (Logger (logfile)); -#endif std::cout.rdbuf (&sb); + std::cerr.rdbuf (&sb); +#else + // Redirect cout and cerr to openmw.log + logfile.open (boost::filesystem::path(cfgMgr.getLogPath() / "/openmw.log")); - ret = main (__argc, __argv); + coutsb.open (Tee(logfile, oldcout)); + cerrsb.open (Tee(logfile, oldcerr)); - std::cout.rdbuf(old_rdbuf); + std::cout.rdbuf (&coutsb); + std::cerr.rdbuf (&cerrsb); +#endif + + +#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX || OGRE_PLATFORM == OGRE_PLATFORM_APPLE + // Unix crash catcher + if ((argc == 2 && strcmp(argv[1], "--cc-handle-crash") == 0) || !is_debugger_attached()) + { + int s[5] = { SIGSEGV, SIGILL, SIGFPE, SIGBUS, SIGABRT }; + cc_install_handlers(argc, argv, 5, s, (cfgMgr.getLogPath() / "crash.log").string().c_str(), NULL); + std::cout << "Installing crash catcher" << std::endl; + } + else + std::cout << "Running in a debugger, not installing crash catcher" << std::endl; +#endif + +#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE + // set current dir to bundle path + boost::filesystem::path bundlePath = boost::filesystem::path(Ogre::macBundlePath()).parent_path(); + boost::filesystem::current_path(bundlePath); +#endif + + OMW::Engine engine(cfgMgr); + + if (parseOptions(argc, argv, engine, cfgMgr)) + { + engine.go(); + } } + catch (std::exception &e) + { +#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX || OGRE_PLATFORM == OGRE_PLATFORM_APPLE + if (isatty(fileno(stdin))) + std::cerr << "\nERROR: " << e.what() << std::endl; + else +#endif + SDL_ShowSimpleMessageBox(0, "OpenMW: Fatal error", e.what(), NULL); + + ret = 1; + } + + // Restore cout and cerr + std::cout.rdbuf(cout_rdbuf); + std::cerr.rdbuf(cerr_rdbuf); + return ret; } +// Platform specific for Windows when there is no console built into the executable. +// Windows will call the WinMain function instead of main in this case, the normal +// main function is then called with the __argc and __argv parameters. +#if defined(_WIN32) && !defined(_CONSOLE) +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) +{ + return main(__argc, __argv); +} #endif diff --git a/apps/openmw/mwbase/dialoguemanager.hpp b/apps/openmw/mwbase/dialoguemanager.hpp index dfb002cfc0..d0e64b23c8 100644 --- a/apps/openmw/mwbase/dialoguemanager.hpp +++ b/apps/openmw/mwbase/dialoguemanager.hpp @@ -76,6 +76,9 @@ namespace MWBase /// @return faction1's opinion of faction2 virtual int getFactionReaction (const std::string& faction1, const std::string& faction2) const = 0; + + /// Removes the last added topic response for the given actor from the journal + virtual void clearInfoActor (const MWWorld::Ptr& actor) const = 0; }; } diff --git a/apps/openmw/mwbase/journal.hpp b/apps/openmw/mwbase/journal.hpp index a49ebb9bc4..938cec74b2 100644 --- a/apps/openmw/mwbase/journal.hpp +++ b/apps/openmw/mwbase/journal.hpp @@ -59,7 +59,12 @@ namespace MWBase virtual int getJournalIndex (const std::string& id) const = 0; ///< Get the journal index. - virtual void addTopic (const std::string& topicId, const std::string& infoId, const std::string& actorName) = 0; + virtual void addTopic (const std::string& topicId, const std::string& infoId, const MWWorld::Ptr& actor) = 0; + /// \note topicId must be lowercase + + virtual void removeLastAddedTopicResponse (const std::string& topicId, const std::string& actorName) = 0; + ///< Removes the last topic response added for the given topicId and actor name. + /// \note topicId must be lowercase virtual TEntryIter begin() const = 0; ///< Iterator pointing to the begin of the main journal. diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index f31241bdb4..f2b71bd4cc 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace Ogre { @@ -13,6 +14,9 @@ namespace Ogre namespace ESM { struct Class; + + class ESMReader; + class ESMWriter; } namespace MWWorld @@ -21,6 +25,11 @@ namespace MWWorld class CellStore; } +namespace Loading +{ + class Listener; +} + namespace MWBase { /// \brief Interface for game mechanics manager (implemented in MWMechanics) @@ -111,6 +120,7 @@ namespace MWBase /** * @brief Commit a crime. If any actors witness the crime and report it, * reportCrime will be called automatically. + * @note victim may be empty * @param arg Depends on \a type, e.g. for Theft, the value of the item that was stolen. * @return was the crime reported? */ @@ -174,6 +184,18 @@ namespace MWBase virtual std::list getActorsFighting(const MWWorld::Ptr& actor) = 0; virtual void playerLoaded() = 0; + + virtual int countSavedGameRecords() const = 0; + + virtual void write (ESM::ESMWriter& writer, Loading::Listener& listener) const = 0; + + virtual void readRecord (ESM::ESMReader& reader, int32_t type) = 0; + + virtual void clear() = 0; + + /// @param bias Can be used to add an additional aggression bias towards the target, + /// making it more likely for the function to return true. + virtual bool isAggressive (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, int bias=0, bool ignoreDistance=false) = 0; }; } diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 2dfa50eb51..8b407c9bab 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -228,6 +228,7 @@ namespace MWBase virtual void showCrosshair(bool show) = 0; virtual bool getSubtitlesEnabled() = 0; virtual void toggleHud() = 0; + virtual bool toggleGui() = 0; virtual void disallowMouse() = 0; virtual void allowMouse() = 0; @@ -328,6 +329,8 @@ namespace MWBase /** Used when one Modal adds another Modal \param input Pointer to the current modal, to ensure proper modal is removed **/ virtual void removeCurrentModal(MWGui::WindowModal* input) = 0; + + virtual void pinWindow (MWGui::GuiWindow window) = 0; }; } diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 81bec6fe86..49ac5bc159 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -274,6 +274,9 @@ namespace MWBase virtual void adjustPosition (const MWWorld::Ptr& ptr) = 0; ///< Adjust position after load to be on ground. Must be called after model load. + virtual void fixPosition (const MWWorld::Ptr& actor) = 0; + ///< Attempt to fix position so that the Ptr is no longer inside collision geometry. + virtual void deleteObject (const MWWorld::Ptr& ptr) = 0; virtual void moveObject (const MWWorld::Ptr& ptr, float x, float y, float z) = 0; @@ -407,7 +410,7 @@ namespace MWBase virtual void getItemsOwnedBy (const MWWorld::Ptr& npc, std::vector& out) = 0; ///< get all items in active cells owned by this Npc - virtual bool getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc) = 0; + virtual bool getLOS(const MWWorld::Ptr& actor,const MWWorld::Ptr& targetActor) = 0; ///< get Line of Sight (morrowind stupid implementation) virtual float getDistToNearestRayHit(const Ogre::Vector3& from, const Ogre::Vector3& dir, float maxDist) = 0; @@ -470,7 +473,7 @@ namespace MWBase virtual void launchMagicBolt (const std::string& model, const std::string& sound, const std::string& spellId, float speed, bool stack, const ESM::EffectList& effects, - const MWWorld::Ptr& actor, const std::string& sourceName) = 0; + const MWWorld::Ptr& caster, const std::string& sourceName, const Ogre::Vector3& fallbackDirection) = 0; virtual void launchProjectile (MWWorld::Ptr actor, MWWorld::Ptr projectile, const Ogre::Vector3& worldPos, const Ogre::Quaternion& orient, MWWorld::Ptr bow, float speed) = 0; @@ -515,8 +518,18 @@ namespace MWBase /// Spawn a blood effect for \a ptr at \a worldPosition virtual void spawnBloodEffect (const MWWorld::Ptr& ptr, const Ogre::Vector3& worldPosition) = 0; + virtual void spawnEffect (const std::string& model, const std::string& textureOverride, const Ogre::Vector3& worldPos) = 0; + virtual void explodeSpell (const Ogre::Vector3& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const std::string& id, const std::string& sourceName) = 0; + + virtual void activate (const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0; + + /// @see MWWorld::WeatherManager::isInStorm + virtual bool isInStorm() const = 0; + + /// @see MWWorld::WeatherManager::getStormDirection + virtual Ogre::Vector3 getStormDirection() const = 0; }; } diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index 947a9cb948..d61ba038a3 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -124,7 +124,7 @@ namespace MWClass std::string text; text += "\n#{sQuality}: " + MWGui::ToolTips::toString(ref->mBase->mData.mQuality); text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); + text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index 825b14978f..b29bf36b2e 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -168,7 +168,7 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - return ref->mBase->mData.mValue * (static_cast(getItemHealth(ptr)) / getItemMaxHealth(ptr)); + return ref->mBase->mData.mValue; } void Armor::registerSelf() @@ -244,7 +244,7 @@ namespace MWClass + MWGui::ToolTips::toString(ref->mBase->mData.mHealth); text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight) + " (" + typeText + ")"; - text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); + text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index 0cc2e60207..0adee57e31 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -136,7 +136,7 @@ namespace MWClass std::string text; text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); + text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index c0362188b8..dc98e323e1 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -190,7 +190,7 @@ namespace MWClass std::string text; text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); + text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 9498ea52df..53add42746 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -286,7 +286,12 @@ namespace MWClass { const ESM::ContainerState& state2 = dynamic_cast (state); - ensureCustomData (ptr); + if (!ptr.getRefData().getCustomData()) + { + // Create a CustomData, but don't fill it from ESM records (not needed) + std::auto_ptr data (new ContainerCustomData); + ptr.getRefData().setCustomData (data.release()); + } dynamic_cast (*ptr.getRefData().getCustomData()).mContainerStore. readState (state2.mInventory); diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 1a6e4e321d..feb6a27147 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -59,35 +59,38 @@ namespace namespace MWClass { + const Creature::GMST& Creature::getGmst() + { + static GMST gmst; + static bool inited = false; + if (!inited) + { + const MWBase::World *world = MWBase::Environment::get().getWorld(); + const MWWorld::Store &store = world->getStore().get(); + gmst.fMinWalkSpeedCreature = store.find("fMinWalkSpeedCreature"); + gmst.fMaxWalkSpeedCreature = store.find("fMaxWalkSpeedCreature"); + gmst.fEncumberedMoveEffect = store.find("fEncumberedMoveEffect"); + gmst.fSneakSpeedMultiplier = store.find("fSneakSpeedMultiplier"); + gmst.fAthleticsRunBonus = store.find("fAthleticsRunBonus"); + gmst.fBaseRunMultiplier = store.find("fBaseRunMultiplier"); + gmst.fMinFlySpeed = store.find("fMinFlySpeed"); + gmst.fMaxFlySpeed = store.find("fMaxFlySpeed"); + gmst.fSwimRunBase = store.find("fSwimRunBase"); + gmst.fSwimRunAthleticsMult = store.find("fSwimRunAthleticsMult"); + gmst.fKnockDownMult = store.find("fKnockDownMult"); + gmst.iKnockDownOddsMult = store.find("iKnockDownOddsMult"); + gmst.iKnockDownOddsBase = store.find("iKnockDownOddsBase"); + inited = true; + } + return gmst; + } + void Creature::ensureCustomData (const MWWorld::Ptr& ptr) const { if (!ptr.getRefData().getCustomData()) { std::auto_ptr data (new CreatureCustomData); - static bool inited = false; - if(!inited) - { - const MWBase::World *world = MWBase::Environment::get().getWorld(); - const MWWorld::Store &gmst = world->getStore().get(); - - fMinWalkSpeedCreature = gmst.find("fMinWalkSpeedCreature"); - fMaxWalkSpeedCreature = gmst.find("fMaxWalkSpeedCreature"); - fEncumberedMoveEffect = gmst.find("fEncumberedMoveEffect"); - fSneakSpeedMultiplier = gmst.find("fSneakSpeedMultiplier"); - fAthleticsRunBonus = gmst.find("fAthleticsRunBonus"); - fBaseRunMultiplier = gmst.find("fBaseRunMultiplier"); - fMinFlySpeed = gmst.find("fMinFlySpeed"); - fMaxFlySpeed = gmst.find("fMaxFlySpeed"); - fSwimRunBase = gmst.find("fSwimRunBase"); - fSwimRunAthleticsMult = gmst.find("fSwimRunAthleticsMult"); - fKnockDownMult = gmst.find("fKnockDownMult"); - iKnockDownOddsMult = gmst.find("iKnockDownOddsMult"); - iKnockDownOddsBase = gmst.find("iKnockDownOddsBase"); - - inited = true; - } - MWWorld::LiveCellRef *ref = ptr.get(); // creature stats @@ -345,7 +348,9 @@ namespace MWClass getCreatureStats(ptr).setAttacked(true); // Self defense - if (!attacker.isEmpty() && ptr.getClass().getCreatureStats(ptr).getAiSetting(MWMechanics::CreatureStats::AI_Fight).getModified() < 80) + if (!attacker.isEmpty() && !MWBase::Environment::get().getMechanicsManager()->isAggressive(ptr, attacker) + && (canWalk(ptr) || canFly(ptr) || canSwim(ptr))) // No retaliation for totally static creatures + // (they have no movement or attacks anyway) MWBase::Environment::get().getMechanicsManager()->startCombat(ptr, attacker); if(!successful) @@ -374,9 +379,9 @@ namespace MWClass if (damage > 0.f) { // Check for knockdown - float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * fKnockDownMult->getFloat(); + float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * getGmst().fKnockDownMult->getFloat(); float knockdownTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() - * iKnockDownOddsMult->getInt() * 0.01 + iKnockDownOddsBase->getInt(); + * getGmst().iKnockDownOddsMult->getInt() * 0.01 + getGmst().iKnockDownOddsBase->getInt(); int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] if (ishealth && agilityTerm <= damage && knockdownTerm <= roll) { @@ -520,9 +525,10 @@ namespace MWClass float Creature::getSpeed(const MWWorld::Ptr &ptr) const { MWMechanics::CreatureStats& stats = getCreatureStats(ptr); + const GMST& gmst = getGmst(); - float walkSpeed = fMinWalkSpeedCreature->getFloat() + 0.01 * stats.getAttribute(ESM::Attribute::Speed).getModified() - * (fMaxWalkSpeedCreature->getFloat() - fMinWalkSpeedCreature->getFloat()); + float walkSpeed = gmst.fMinWalkSpeedCreature->getFloat() + 0.01 * stats.getAttribute(ESM::Attribute::Speed).getModified() + * (gmst.fMaxWalkSpeedCreature->getFloat() - gmst.fMinWalkSpeedCreature->getFloat()); const MWBase::World *world = MWBase::Environment::get().getWorld(); const MWMechanics::MagicEffects &mageffects = stats.getMagicEffects(); @@ -531,8 +537,8 @@ namespace MWClass bool running = ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run); - float runSpeed = walkSpeed*(0.01f * getSkill(ptr, ESM::Skill::Athletics) * - fAthleticsRunBonus->getFloat() + fBaseRunMultiplier->getFloat()); + // The Run speed difference for creatures comes from the animation speed difference (see runStateToWalkState in character.cpp) + float runSpeed = walkSpeed; float moveSpeed; if(normalizedEncumbrance >= 1.0f) @@ -542,8 +548,8 @@ namespace MWClass { float flySpeed = 0.01f*(stats.getAttribute(ESM::Attribute::Speed).getModified() + mageffects.get(ESM::MagicEffect::Levitate).mMagnitude); - flySpeed = fMinFlySpeed->getFloat() + flySpeed*(fMaxFlySpeed->getFloat() - fMinFlySpeed->getFloat()); - flySpeed *= 1.0f - fEncumberedMoveEffect->getFloat() * normalizedEncumbrance; + flySpeed = gmst.fMinFlySpeed->getFloat() + flySpeed*(gmst.fMaxFlySpeed->getFloat() - gmst.fMinFlySpeed->getFloat()); + flySpeed *= 1.0f - gmst.fEncumberedMoveEffect->getFloat() * normalizedEncumbrance; flySpeed = std::max(0.0f, flySpeed); moveSpeed = flySpeed; } @@ -553,8 +559,8 @@ namespace MWClass if(running) swimSpeed = runSpeed; swimSpeed *= 1.0f + 0.01f * mageffects.get(ESM::MagicEffect::SwiftSwim).mMagnitude; - swimSpeed *= fSwimRunBase->getFloat() + 0.01f*getSkill(ptr, ESM::Skill::Athletics) * - fSwimRunAthleticsMult->getFloat(); + swimSpeed *= gmst.fSwimRunBase->getFloat() + 0.01f*getSkill(ptr, ESM::Skill::Athletics) * + gmst.fSwimRunAthleticsMult->getFloat(); moveSpeed = swimSpeed; } else if(running) @@ -711,7 +717,7 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - return ref->mBase->mFlags & ESM::Creature::Swims; + return ref->mBase->mFlags & ESM::Creature::Swims || ref->mBase->mFlags & ESM::Creature::Bipedal; } bool Creature::canWalk(const MWWorld::Ptr &ptr) const @@ -719,7 +725,7 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - return ref->mBase->mFlags & ESM::Creature::Walks; + return ref->mBase->mFlags & ESM::Creature::Walks || ref->mBase->mFlags & ESM::Creature::Bipedal; } int Creature::getSndGenTypeFromName(const MWWorld::Ptr &ptr, const std::string &name) @@ -796,7 +802,27 @@ namespace MWClass { const ESM::CreatureState& state2 = dynamic_cast (state); - ensureCustomData (ptr); + ensureCustomData(ptr); + + // If we do the following instead we get a sizable speedup, but this causes compatibility issues + // with 0.30 savegames, where some state in CreatureStats was not saved yet, + // and therefore needs to be loaded from ESM records. TODO: re-enable this in a future release. + /* + if (!ptr.getRefData().getCustomData()) + { + // Create a CustomData, but don't fill it from ESM records (not needed) + std::auto_ptr data (new CreatureCustomData); + + MWWorld::LiveCellRef *ref = ptr.get(); + + if (ref->mBase->mFlags & ESM::Creature::Weapon) + data->mContainerStore = new MWWorld::InventoryStore(); + else + data->mContainerStore = new MWWorld::ContainerStore(); + + ptr.getRefData().setCustomData (data.release()); + } + */ CreatureCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); @@ -835,8 +861,7 @@ namespace MWClass ptr.getRefData().setCount(1); // Reset to original position - ESM::Position& pos = ptr.getRefData().getPosition(); - pos = ptr.getCellRef().getPosition(); + ptr.getRefData().setPosition(ptr.getCellRef().getPosition()); ptr.getRefData().setCustomData(NULL); } @@ -850,19 +875,4 @@ namespace MWClass MWWorld::ContainerStore& store = getContainerStore(ptr); store.restock(list, ptr, ptr.getCellRef().getRefId(), ptr.getCellRef().getFaction()); } - - const ESM::GameSetting* Creature::fMinWalkSpeedCreature; - const ESM::GameSetting* Creature::fMaxWalkSpeedCreature; - const ESM::GameSetting *Creature::fEncumberedMoveEffect; - const ESM::GameSetting *Creature::fSneakSpeedMultiplier; - const ESM::GameSetting *Creature::fAthleticsRunBonus; - const ESM::GameSetting *Creature::fBaseRunMultiplier; - const ESM::GameSetting *Creature::fMinFlySpeed; - const ESM::GameSetting *Creature::fMaxFlySpeed; - const ESM::GameSetting *Creature::fSwimRunBase; - const ESM::GameSetting *Creature::fSwimRunAthleticsMult; - const ESM::GameSetting *Creature::fKnockDownMult; - const ESM::GameSetting *Creature::iKnockDownOddsMult; - const ESM::GameSetting *Creature::iKnockDownOddsBase; - } diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index 30573cd154..6920a4b1d3 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -19,20 +19,25 @@ namespace MWClass static int getSndGenTypeFromName(const MWWorld::Ptr &ptr, const std::string &name); - static const ESM::GameSetting *fMinWalkSpeedCreature; - static const ESM::GameSetting *fMaxWalkSpeedCreature; - static const ESM::GameSetting *fEncumberedMoveEffect; - static const ESM::GameSetting *fSneakSpeedMultiplier; - static const ESM::GameSetting *fAthleticsRunBonus; - static const ESM::GameSetting *fBaseRunMultiplier; - static const ESM::GameSetting *fMinFlySpeed; - static const ESM::GameSetting *fMaxFlySpeed; - static const ESM::GameSetting *fSwimRunBase; - static const ESM::GameSetting *fSwimRunAthleticsMult; - static const ESM::GameSetting *fKnockDownMult; - static const ESM::GameSetting *iKnockDownOddsMult; - static const ESM::GameSetting *iKnockDownOddsBase; + // cached GMSTs + struct GMST + { + const ESM::GameSetting *fMinWalkSpeedCreature; + const ESM::GameSetting *fMaxWalkSpeedCreature; + const ESM::GameSetting *fEncumberedMoveEffect; + const ESM::GameSetting *fSneakSpeedMultiplier; + const ESM::GameSetting *fAthleticsRunBonus; + const ESM::GameSetting *fBaseRunMultiplier; + const ESM::GameSetting *fMinFlySpeed; + const ESM::GameSetting *fMaxFlySpeed; + const ESM::GameSetting *fSwimRunBase; + const ESM::GameSetting *fSwimRunAthleticsMult; + const ESM::GameSetting *fKnockDownMult; + const ESM::GameSetting *iKnockDownOddsMult; + const ESM::GameSetting *iKnockDownOddsBase; + }; + static const GMST& getGmst(); public: diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 12645c9f39..e435511b99 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -57,11 +57,13 @@ namespace MWClass physics.addObject(ptr); // Resume the door's opening/closing animation if it wasn't finished - ensureCustomData(ptr); - const DoorCustomData& customData = dynamic_cast(*ptr.getRefData().getCustomData()); - if (customData.mDoorState > 0) + if (ptr.getRefData().getCustomData()) { - MWBase::Environment::get().getWorld()->activateDoor(ptr, customData.mDoorState == 1 ? true : false); + const DoorCustomData& customData = dynamic_cast(*ptr.getRefData().getCustomData()); + if (customData.mDoorState > 0) + { + MWBase::Environment::get().getWorld()->activateDoor(ptr, customData.mDoorState == 1 ? true : false); + } } } @@ -125,7 +127,7 @@ namespace MWClass MWBase::Environment::get().getWindowManager()->messageBox(keyName + " #{sKeyUsed}"); unlock(ptr); //Call the function here. because that makes sense. // using a key disarms the trap - ptr.getCellRef().getTrap() = ""; + ptr.getCellRef().setTrap(""); } if (!needKey || hasKey) diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index 60c0efeb86..6830929239 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -144,7 +144,7 @@ namespace MWClass std::string text; text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); + text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index ef4549268f..8a2c20f699 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -184,7 +184,7 @@ namespace MWClass std::string text; text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); + text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index 19381a3fd5..bc68551291 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -86,7 +86,7 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - return ref->mBase->mData.mValue * (static_cast(getItemHealth(ptr)) / getItemMaxHealth(ptr)); + return ref->mBase->mData.mValue; } void Lockpick::registerSelf() @@ -138,7 +138,7 @@ namespace MWClass text += "\n#{sUses}: " + MWGui::ToolTips::toString(remainingUses); text += "\n#{sQuality}: " + MWGui::ToolTips::toString(ref->mBase->mData.mQuality); text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); + text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 5ec192ab20..c003e0b227 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -228,38 +228,44 @@ namespace namespace MWClass { - void Npc::ensureCustomData (const MWWorld::Ptr& ptr) const + const Npc::GMST& Npc::getGmst() { + static GMST gmst; static bool inited = false; if(!inited) { const MWBase::World *world = MWBase::Environment::get().getWorld(); - const MWWorld::Store &gmst = world->getStore().get(); + const MWWorld::Store &store = world->getStore().get(); - fMinWalkSpeed = gmst.find("fMinWalkSpeed"); - fMaxWalkSpeed = gmst.find("fMaxWalkSpeed"); - fEncumberedMoveEffect = gmst.find("fEncumberedMoveEffect"); - fSneakSpeedMultiplier = gmst.find("fSneakSpeedMultiplier"); - fAthleticsRunBonus = gmst.find("fAthleticsRunBonus"); - fBaseRunMultiplier = gmst.find("fBaseRunMultiplier"); - fMinFlySpeed = gmst.find("fMinFlySpeed"); - fMaxFlySpeed = gmst.find("fMaxFlySpeed"); - fSwimRunBase = gmst.find("fSwimRunBase"); - fSwimRunAthleticsMult = gmst.find("fSwimRunAthleticsMult"); - fJumpEncumbranceBase = gmst.find("fJumpEncumbranceBase"); - fJumpEncumbranceMultiplier = gmst.find("fJumpEncumbranceMultiplier"); - fJumpAcrobaticsBase = gmst.find("fJumpAcrobaticsBase"); - fJumpAcroMultiplier = gmst.find("fJumpAcroMultiplier"); - fJumpRunMultiplier = gmst.find("fJumpRunMultiplier"); - fWereWolfRunMult = gmst.find("fWereWolfRunMult"); - fKnockDownMult = gmst.find("fKnockDownMult"); - iKnockDownOddsMult = gmst.find("iKnockDownOddsMult"); - iKnockDownOddsBase = gmst.find("iKnockDownOddsBase"); - fDamageStrengthBase = gmst.find("fDamageStrengthBase"); - fDamageStrengthMult = gmst.find("fDamageStrengthMult"); + gmst.fMinWalkSpeed = store.find("fMinWalkSpeed"); + gmst.fMaxWalkSpeed = store.find("fMaxWalkSpeed"); + gmst.fEncumberedMoveEffect = store.find("fEncumberedMoveEffect"); + gmst.fSneakSpeedMultiplier = store.find("fSneakSpeedMultiplier"); + gmst.fAthleticsRunBonus = store.find("fAthleticsRunBonus"); + gmst.fBaseRunMultiplier = store.find("fBaseRunMultiplier"); + gmst.fMinFlySpeed = store.find("fMinFlySpeed"); + gmst.fMaxFlySpeed = store.find("fMaxFlySpeed"); + gmst.fSwimRunBase = store.find("fSwimRunBase"); + gmst.fSwimRunAthleticsMult = store.find("fSwimRunAthleticsMult"); + gmst.fJumpEncumbranceBase = store.find("fJumpEncumbranceBase"); + gmst.fJumpEncumbranceMultiplier = store.find("fJumpEncumbranceMultiplier"); + gmst.fJumpAcrobaticsBase = store.find("fJumpAcrobaticsBase"); + gmst.fJumpAcroMultiplier = store.find("fJumpAcroMultiplier"); + gmst.fJumpRunMultiplier = store.find("fJumpRunMultiplier"); + gmst.fWereWolfRunMult = store.find("fWereWolfRunMult"); + gmst.fKnockDownMult = store.find("fKnockDownMult"); + gmst.iKnockDownOddsMult = store.find("iKnockDownOddsMult"); + gmst.iKnockDownOddsBase = store.find("iKnockDownOddsBase"); + gmst.fDamageStrengthBase = store.find("fDamageStrengthBase"); + gmst.fDamageStrengthMult = store.find("fDamageStrengthMult"); inited = true; } + return gmst; + } + + void Npc::ensureCustomData (const MWWorld::Ptr& ptr) const + { if (!ptr.getRefData().getCustomData()) { std::auto_ptr data(new NpcCustomData); @@ -404,11 +410,6 @@ namespace MWClass ptr.get(); assert(ref->mBase != NULL); - //std::string headID = ref->mBase->mHead; - - //int end = headID.find_last_of("head_") - 4; - //std::string bodyRaceID = headID.substr(0, end); - std::string model = "meshes\\base_anim.nif"; const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().find(ref->mBase->mRace); if(race->mData.mFlags & ESM::Race::Beast) @@ -423,9 +424,9 @@ namespace MWClass if(getNpcStats(ptr).isWerewolf()) { const MWBase::World *world = MWBase::Environment::get().getWorld(); - const MWWorld::Store &gmst = world->getStore().get(); + const MWWorld::Store &store = world->getStore().get(); - return gmst.find("sWerewolfPopup")->getString(); + return store.find("sWerewolfPopup")->getString(); } MWWorld::LiveCellRef *ref = ptr.get(); @@ -450,7 +451,9 @@ namespace MWClass void Npc::hit(const MWWorld::Ptr& ptr, int type) const { MWBase::World *world = MWBase::Environment::get().getWorld(); - const MWWorld::Store &gmst = world->getStore().get(); + const GMST& gmst = getGmst(); + + const MWWorld::Store &store = world->getStore().get(); // Get the weapon used (if hand-to-hand, weapon = inv.end()) MWWorld::InventoryStore &inv = getInventoryStore(ptr); @@ -461,9 +464,9 @@ namespace MWClass // Reduce fatigue // somewhat of a guess, but using the weapon weight makes sense - const float fFatigueAttackBase = gmst.find("fFatigueAttackBase")->getFloat(); - const float fFatigueAttackMult = gmst.find("fFatigueAttackMult")->getFloat(); - const float fWeaponFatigueMult = gmst.find("fWeaponFatigueMult")->getFloat(); + const float fFatigueAttackBase = store.find("fFatigueAttackBase")->getFloat(); + const float fFatigueAttackMult = store.find("fFatigueAttackMult")->getFloat(); + const float fWeaponFatigueMult = store.find("fWeaponFatigueMult")->getFloat(); MWMechanics::DynamicStat fatigue = getCreatureStats(ptr).getFatigue(); const float normalizedEncumbrance = getEncumbrance(ptr) / getCapacity(ptr); float fatigueLoss = fFatigueAttackBase + normalizedEncumbrance * fFatigueAttackMult; @@ -472,10 +475,10 @@ namespace MWClass fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss); getCreatureStats(ptr).setFatigue(fatigue); - const float fCombatDistance = gmst.find("fCombatDistance")->getFloat(); + const float fCombatDistance = store.find("fCombatDistance")->getFloat(); float dist = fCombatDistance * (!weapon.isEmpty() ? weapon.get()->mBase->mData.mReach : - gmst.find("fHandToHandReach")->getFloat()); + store.find("fHandToHandReach")->getFloat()); // TODO: Use second to work out the hit angle std::pair result = world->getHitContact(ptr, dist); @@ -522,8 +525,8 @@ namespace MWClass if(attack) { damage = attack[0] + ((attack[1]-attack[0])*stats.getAttackStrength()); - damage *= fDamageStrengthBase->getFloat() + - (stats.getAttribute(ESM::Attribute::Strength).getModified() * fDamageStrengthMult->getFloat() * 0.1); + damage *= gmst.fDamageStrengthBase->getFloat() + + (stats.getAttribute(ESM::Attribute::Strength).getModified() * gmst.fDamageStrengthMult->getFloat() * 0.1); if(weaphashealth) { int weapmaxhealth = weapon.getClass().getItemMaxHealth(weapon); @@ -535,7 +538,7 @@ namespace MWClass { // Reduce weapon charge by at least one, but cap at 0 weaphealth -= std::min(std::max(1, - (int)(damage * gmst.find("fWeaponDamageMult")->getFloat())), weaphealth); + (int)(damage * store.find("fWeaponDamageMult")->getFloat())), weaphealth); weapon.getCellRef().setCharge(weaphealth); } @@ -552,8 +555,8 @@ namespace MWClass // Note: MCP contains an option to include Strength in hand-to-hand damage // calculations. Some mods recommend using it, so we may want to include am // option for it. - float minstrike = gmst.find("fMinHandToHandMult")->getFloat(); - float maxstrike = gmst.find("fMaxHandToHandMult")->getFloat(); + float minstrike = store.find("fMinHandToHandMult")->getFloat(); + float maxstrike = store.find("fMaxHandToHandMult")->getFloat(); damage = stats.getSkill(weapskill).getModified(); damage *= minstrike + ((maxstrike-minstrike)*stats.getAttackStrength()); @@ -567,7 +570,7 @@ namespace MWClass damage *= glob.find("WerewolfClawMult")->mValue.getFloat(); } if(healthdmg) - damage *= gmst.find("fHandtoHandHealthPer")->getFloat(); + damage *= store.find("fHandtoHandHealthPer")->getFloat(); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); if(stats.isWerewolf()) @@ -585,12 +588,12 @@ namespace MWClass bool detected = MWBase::Environment::get().getMechanicsManager()->awarenessCheck(ptr, victim); if(!detected) { - damage *= gmst.find("fCombatCriticalStrikeMult")->getFloat(); + damage *= store.find("fCombatCriticalStrikeMult")->getFloat(); MWBase::Environment::get().getWindowManager()->messageBox("#{sTargetCriticalStrike}"); MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f); } if (othercls.getCreatureStats(victim).getKnockedDown()) - damage *= gmst.find("fCombatKODamageMult")->getFloat(); + damage *= store.find("fCombatKODamageMult")->getFloat(); // Apply "On hit" enchanted weapons std::string enchantmentName = !weapon.isEmpty() ? weapon.getClass().getEnchantment(weapon) : ""; @@ -624,11 +627,12 @@ namespace MWClass // NOTE: 'object' and/or 'attacker' may be empty. // Attacking peaceful NPCs is a crime - // anything below 80 is considered peaceful (see Actors::updateActor) if (!attacker.isEmpty() && !ptr.getClass().getCreatureStats(ptr).isHostile() && - ptr.getClass().getCreatureStats(ptr).getAiSetting(MWMechanics::CreatureStats::AI_Fight).getModified() < 80) + !MWBase::Environment::get().getMechanicsManager()->isAggressive(ptr, attacker)) MWBase::Environment::get().getMechanicsManager()->commitCrime(attacker, ptr, MWBase::MechanicsManager::OT_Assault); + bool wasDead = getCreatureStats(ptr).isDead(); + getCreatureStats(ptr).setAttacked(true); if(!successful) @@ -660,6 +664,8 @@ namespace MWClass // something, alert the character controller, scripts, etc. const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); + const GMST& gmst = getGmst(); + int chance = store.get().find("iVoiceHitOdds")->getInt(); int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] if (roll < chance) @@ -668,9 +674,9 @@ namespace MWClass } // Check for knockdown - float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * fKnockDownMult->getFloat(); + float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * gmst.fKnockDownMult->getFloat(); float knockdownTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() - * iKnockDownOddsMult->getInt() * 0.01 + iKnockDownOddsBase->getInt(); + * gmst.iKnockDownOddsMult->getInt() * 0.01 + gmst.iKnockDownOddsBase->getInt(); roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] if (ishealth && agilityTerm <= damage && knockdownTerm <= roll) { @@ -752,6 +758,22 @@ namespace MWClass fatigue.setCurrent(fatigue.getCurrent() - damage, true); getCreatureStats(ptr).setFatigue(fatigue); } + + if (!wasDead && getCreatureStats(ptr).isDead()) + { + // NPC was killed + if (!attacker.isEmpty() && attacker.getClass().isNpc() && attacker.getClass().getNpcStats(attacker).isWerewolf()) + { + attacker.getClass().getNpcStats(attacker).addWerewolfKill(); + } + + // Simple check for who attacked first: if the player attacked first, a crimeId should be set + // Doesn't handle possible edge case where no one reported the assault, but in such a case, + // for bystanders it is not possible to tell who attacked first, anyway. + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + if (attacker == player && ptr.getClass().getNpcStats(ptr).getCrimeId() != -1 && ptr != player) + MWBase::Environment::get().getMechanicsManager()->commitCrime(player, ptr, MWBase::MechanicsManager::OT_Murder); + } } void Npc::block(const MWWorld::Ptr &ptr) const @@ -853,6 +875,8 @@ namespace MWClass float Npc::getSpeed(const MWWorld::Ptr& ptr) const { const MWBase::World *world = MWBase::Environment::get().getWorld(); + const GMST& gmst = getGmst(); + const NpcCustomData *npcdata = static_cast(ptr.getRefData().getCustomData()); const MWMechanics::MagicEffects &mageffects = npcdata->mNpcStats.getMagicEffects(); @@ -861,17 +885,17 @@ namespace MWClass bool sneaking = ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Sneak); bool running = ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run); - float walkSpeed = fMinWalkSpeed->getFloat() + 0.01f*npcdata->mNpcStats.getAttribute(ESM::Attribute::Speed).getModified()* - (fMaxWalkSpeed->getFloat() - fMinWalkSpeed->getFloat()); - walkSpeed *= 1.0f - fEncumberedMoveEffect->getFloat()*normalizedEncumbrance; + float walkSpeed = gmst.fMinWalkSpeed->getFloat() + 0.01f*npcdata->mNpcStats.getAttribute(ESM::Attribute::Speed).getModified()* + (gmst.fMaxWalkSpeed->getFloat() - gmst.fMinWalkSpeed->getFloat()); + walkSpeed *= 1.0f - gmst.fEncumberedMoveEffect->getFloat()*normalizedEncumbrance; walkSpeed = std::max(0.0f, walkSpeed); if(sneaking) - walkSpeed *= fSneakSpeedMultiplier->getFloat(); + walkSpeed *= gmst.fSneakSpeedMultiplier->getFloat(); float runSpeed = walkSpeed*(0.01f * npcdata->mNpcStats.getSkill(ESM::Skill::Athletics).getModified() * - fAthleticsRunBonus->getFloat() + fBaseRunMultiplier->getFloat()); + gmst.fAthleticsRunBonus->getFloat() + gmst.fBaseRunMultiplier->getFloat()); if(npcdata->mNpcStats.isWerewolf()) - runSpeed *= fWereWolfRunMult->getFloat(); + runSpeed *= gmst.fWereWolfRunMult->getFloat(); float moveSpeed; if(normalizedEncumbrance >= 1.0f) @@ -881,8 +905,8 @@ namespace MWClass { float flySpeed = 0.01f*(npcdata->mNpcStats.getAttribute(ESM::Attribute::Speed).getModified() + mageffects.get(ESM::MagicEffect::Levitate).mMagnitude); - flySpeed = fMinFlySpeed->getFloat() + flySpeed*(fMaxFlySpeed->getFloat() - fMinFlySpeed->getFloat()); - flySpeed *= 1.0f - fEncumberedMoveEffect->getFloat() * normalizedEncumbrance; + flySpeed = gmst.fMinFlySpeed->getFloat() + flySpeed*(gmst.fMaxFlySpeed->getFloat() - gmst.fMinFlySpeed->getFloat()); + flySpeed *= 1.0f - gmst.fEncumberedMoveEffect->getFloat() * normalizedEncumbrance; flySpeed = std::max(0.0f, flySpeed); moveSpeed = flySpeed; } @@ -892,8 +916,8 @@ namespace MWClass if(running) swimSpeed = runSpeed; swimSpeed *= 1.0f + 0.01f * mageffects.get(ESM::MagicEffect::SwiftSwim).mMagnitude; - swimSpeed *= fSwimRunBase->getFloat() + 0.01f*npcdata->mNpcStats.getSkill(ESM::Skill::Athletics).getModified()* - fSwimRunAthleticsMult->getFloat(); + swimSpeed *= gmst.fSwimRunBase->getFloat() + 0.01f*npcdata->mNpcStats.getSkill(ESM::Skill::Athletics).getModified()* + gmst.fSwimRunAthleticsMult->getFloat(); moveSpeed = swimSpeed; } else if(running && !sneaking) @@ -909,9 +933,10 @@ namespace MWClass float Npc::getJump(const MWWorld::Ptr &ptr) const { const NpcCustomData *npcdata = static_cast(ptr.getRefData().getCustomData()); + const GMST& gmst = getGmst(); const MWMechanics::MagicEffects &mageffects = npcdata->mNpcStats.getMagicEffects(); - const float encumbranceTerm = fJumpEncumbranceBase->getFloat() + - fJumpEncumbranceMultiplier->getFloat() * + const float encumbranceTerm = gmst.fJumpEncumbranceBase->getFloat() + + gmst.fJumpEncumbranceMultiplier->getFloat() * (1.0f - Npc::getEncumbrance(ptr)/Npc::getCapacity(ptr)); float a = npcdata->mNpcStats.getSkill(ESM::Skill::Acrobatics).getModified(); @@ -922,14 +947,14 @@ namespace MWClass a = 50.0f; } - float x = fJumpAcrobaticsBase->getFloat() + - std::pow(a / 15.0f, fJumpAcroMultiplier->getFloat()); - x += 3.0f * b * fJumpAcroMultiplier->getFloat(); + float x = gmst.fJumpAcrobaticsBase->getFloat() + + std::pow(a / 15.0f, gmst.fJumpAcroMultiplier->getFloat()); + x += 3.0f * b * gmst.fJumpAcroMultiplier->getFloat(); x += mageffects.get(ESM::MagicEffect::Jump).mMagnitude * 64; x *= encumbranceTerm; if(ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run)) - x *= fJumpRunMultiplier->getFloat(); + x *= gmst.fJumpRunMultiplier->getFloat(); x *= npcdata->mNpcStats.getFatigueTerm(); x -= -627.2f;/*gravity constant*/ x /= 3.0f; @@ -940,19 +965,19 @@ namespace MWClass float Npc::getFallDamage(const MWWorld::Ptr &ptr, float fallHeight) const { MWBase::World *world = MWBase::Environment::get().getWorld(); - const MWWorld::Store &gmst = world->getStore().get(); + const MWWorld::Store &store = world->getStore().get(); - const float fallDistanceMin = gmst.find("fFallDamageDistanceMin")->getFloat(); + const float fallDistanceMin = store.find("fFallDamageDistanceMin")->getFloat(); if (fallHeight >= fallDistanceMin) { const float acrobaticsSkill = ptr.getClass().getNpcStats (ptr).getSkill(ESM::Skill::Acrobatics).getModified(); const NpcCustomData *npcdata = static_cast(ptr.getRefData().getCustomData()); const float jumpSpellBonus = npcdata->mNpcStats.getMagicEffects().get(ESM::MagicEffect::Jump).mMagnitude; - const float fallAcroBase = gmst.find("fFallAcroBase")->getFloat(); - const float fallAcroMult = gmst.find("fFallAcroMult")->getFloat(); - const float fallDistanceBase = gmst.find("fFallDistanceBase")->getFloat(); - const float fallDistanceMult = gmst.find("fFallDistanceMult")->getFloat(); + const float fallAcroBase = store.find("fFallAcroBase")->getFloat(); + const float fallAcroMult = store.find("fFallAcroMult")->getFloat(); + const float fallDistanceBase = store.find("fFallDistanceBase")->getFloat(); + const float fallDistanceMult = store.find("fFallDistanceMult")->getFloat(); float x = fallHeight - fallDistanceMin; x -= (1.5 * acrobaticsSkill) + jumpSpellBonus; @@ -1087,14 +1112,14 @@ namespace MWClass float Npc::getArmorRating (const MWWorld::Ptr& ptr) const { const MWBase::World *world = MWBase::Environment::get().getWorld(); - const MWWorld::Store &gmst = world->getStore().get(); + const MWWorld::Store &store = world->getStore().get(); MWMechanics::NpcStats &stats = getNpcStats(ptr); MWWorld::InventoryStore &invStore = getInventoryStore(ptr); - int iBaseArmorSkill = gmst.find("iBaseArmorSkill")->getInt(); - float fUnarmoredBase1 = gmst.find("fUnarmoredBase1")->getFloat(); - float fUnarmoredBase2 = gmst.find("fUnarmoredBase2")->getFloat(); + int iBaseArmorSkill = store.find("iBaseArmorSkill")->getInt(); + float fUnarmoredBase1 = store.find("fUnarmoredBase1")->getFloat(); + float fUnarmoredBase2 = store.find("fUnarmoredBase2")->getFloat(); int unarmoredSkill = stats.getSkill(ESM::Skill::Unarmored).getModified(); int ratings[MWWorld::InventoryStore::Slots]; @@ -1276,7 +1301,18 @@ namespace MWClass { const ESM::NpcState& state2 = dynamic_cast (state); - ensureCustomData (ptr); + ensureCustomData(ptr); + // If we do the following instead we get a sizable speedup, but this causes compatibility issues + // with 0.30 savegames, where some state in CreatureStats was not saved yet, + // and therefore needs to be loaded from ESM records. TODO: re-enable this in a future release. + /* + if (!ptr.getRefData().getCustomData()) + { + // Create a CustomData, but don't fill it from ESM records (not needed) + std::auto_ptr data (new NpcCustomData); + ptr.getRefData().setCustomData (data.release()); + } + */ NpcCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); @@ -1325,8 +1361,7 @@ namespace MWClass ptr.getRefData().setCount(1); // Reset to original position - ESM::Position& pos = ptr.getRefData().getPosition(); - pos = ptr.getCellRef().getPosition(); + ptr.getRefData().setPosition(ptr.getCellRef().getPosition()); ptr.getRefData().setCustomData(NULL); } @@ -1340,27 +1375,4 @@ namespace MWClass MWWorld::ContainerStore& store = getContainerStore(ptr); store.restock(list, ptr, ptr.getCellRef().getRefId(), ptr.getCellRef().getFaction()); } - - const ESM::GameSetting *Npc::fMinWalkSpeed; - const ESM::GameSetting *Npc::fMaxWalkSpeed; - const ESM::GameSetting *Npc::fEncumberedMoveEffect; - const ESM::GameSetting *Npc::fSneakSpeedMultiplier; - const ESM::GameSetting *Npc::fAthleticsRunBonus; - const ESM::GameSetting *Npc::fBaseRunMultiplier; - const ESM::GameSetting *Npc::fMinFlySpeed; - const ESM::GameSetting *Npc::fMaxFlySpeed; - const ESM::GameSetting *Npc::fSwimRunBase; - const ESM::GameSetting *Npc::fSwimRunAthleticsMult; - const ESM::GameSetting *Npc::fJumpEncumbranceBase; - const ESM::GameSetting *Npc::fJumpEncumbranceMultiplier; - const ESM::GameSetting *Npc::fJumpAcrobaticsBase; - const ESM::GameSetting *Npc::fJumpAcroMultiplier; - const ESM::GameSetting *Npc::fJumpRunMultiplier; - const ESM::GameSetting *Npc::fWereWolfRunMult; - const ESM::GameSetting *Npc::fKnockDownMult; - const ESM::GameSetting *Npc::iKnockDownOddsMult; - const ESM::GameSetting *Npc::iKnockDownOddsBase; - const ESM::GameSetting *Npc::fDamageStrengthBase; - const ESM::GameSetting *Npc::fDamageStrengthMult; - } diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index 356e358b9d..d6b1f8c268 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -17,27 +17,32 @@ namespace MWClass virtual MWWorld::Ptr copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; - static const ESM::GameSetting *fMinWalkSpeed; - static const ESM::GameSetting *fMaxWalkSpeed; - static const ESM::GameSetting *fEncumberedMoveEffect; - static const ESM::GameSetting *fSneakSpeedMultiplier; - static const ESM::GameSetting *fAthleticsRunBonus; - static const ESM::GameSetting *fBaseRunMultiplier; - static const ESM::GameSetting *fMinFlySpeed; - static const ESM::GameSetting *fMaxFlySpeed; - static const ESM::GameSetting *fSwimRunBase; - static const ESM::GameSetting *fSwimRunAthleticsMult; - static const ESM::GameSetting *fJumpEncumbranceBase; - static const ESM::GameSetting *fJumpEncumbranceMultiplier; - static const ESM::GameSetting *fJumpAcrobaticsBase; - static const ESM::GameSetting *fJumpAcroMultiplier; - static const ESM::GameSetting *fJumpRunMultiplier; - static const ESM::GameSetting *fWereWolfRunMult; - static const ESM::GameSetting *fKnockDownMult; - static const ESM::GameSetting *iKnockDownOddsMult; - static const ESM::GameSetting *iKnockDownOddsBase; - static const ESM::GameSetting *fDamageStrengthBase; - static const ESM::GameSetting *fDamageStrengthMult; + struct GMST + { + const ESM::GameSetting *fMinWalkSpeed; + const ESM::GameSetting *fMaxWalkSpeed; + const ESM::GameSetting *fEncumberedMoveEffect; + const ESM::GameSetting *fSneakSpeedMultiplier; + const ESM::GameSetting *fAthleticsRunBonus; + const ESM::GameSetting *fBaseRunMultiplier; + const ESM::GameSetting *fMinFlySpeed; + const ESM::GameSetting *fMaxFlySpeed; + const ESM::GameSetting *fSwimRunBase; + const ESM::GameSetting *fSwimRunAthleticsMult; + const ESM::GameSetting *fJumpEncumbranceBase; + const ESM::GameSetting *fJumpEncumbranceMultiplier; + const ESM::GameSetting *fJumpAcrobaticsBase; + const ESM::GameSetting *fJumpAcroMultiplier; + const ESM::GameSetting *fJumpRunMultiplier; + const ESM::GameSetting *fWereWolfRunMult; + const ESM::GameSetting *fKnockDownMult; + const ESM::GameSetting *iKnockDownOddsMult; + const ESM::GameSetting *iKnockDownOddsBase; + const ESM::GameSetting *fDamageStrengthBase; + const ESM::GameSetting *fDamageStrengthMult; + }; + + static const GMST& getGmst(); public: diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 7440617c25..440121d35b 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -127,7 +127,7 @@ namespace MWClass std::string text; text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); + text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); info.effects = MWGui::Widgets::MWEffectList::effectListFromESM(&ref->mBase->mEffects); @@ -166,13 +166,8 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - MWWorld::Ptr actor = MWBase::Environment::get().getWorld()->getPlayerPtr(); - - // remove used potion (assume it is present in inventory) - ptr.getContainerStore()->remove(ptr, 1, actor); - boost::shared_ptr action ( - new MWWorld::ActionApply (actor, ref->mBase->mId)); + new MWWorld::ActionApply (ptr, ref->mBase->mId)); action->setSound ("Drink"); diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index 5d076a3c5e..ed8625eec0 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -85,7 +85,7 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - return ref->mBase->mData.mValue * (static_cast(getItemHealth(ptr)) / getItemMaxHealth(ptr)); + return ref->mBase->mData.mValue; } void Probe::registerSelf() @@ -137,7 +137,7 @@ namespace MWClass text += "\n#{sUses}: " + MWGui::ToolTips::toString(remainingUses); text += "\n#{sQuality}: " + MWGui::ToolTips::toString(ref->mBase->mData.mQuality); text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); + text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index 9b528a4fce..d7a0805349 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -76,7 +76,7 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - return ref->mBase->mData.mValue * (static_cast(getItemHealth(ptr)) / getItemMaxHealth(ptr)); + return ref->mBase->mData.mValue; } void Repair::registerSelf() @@ -141,7 +141,7 @@ namespace MWClass text += "\n#{sUses}: " + MWGui::ToolTips::toString(remainingUses); text += "\n#{sQuality}: " + MWGui::ToolTips::toString(ref->mBase->mData.mQuality); text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); + text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); diff --git a/apps/openmw/mwclass/static.cpp b/apps/openmw/mwclass/static.cpp index 4ac41350fd..8768bde063 100644 --- a/apps/openmw/mwclass/static.cpp +++ b/apps/openmw/mwclass/static.cpp @@ -14,9 +14,12 @@ namespace MWClass { void Static::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { + MWWorld::LiveCellRef *ref = + ptr.get(); + const std::string model = getModel(ptr); if (!model.empty()) { - renderingInterface.getObjects().insertModel(ptr, model); + renderingInterface.getObjects().insertModel(ptr, model, !ref->mBase->mPersistent); } } diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 26618c021c..66affa599e 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -154,7 +154,7 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - return ref->mBase->mData.mValue * (static_cast(getItemHealth(ptr)) / getItemMaxHealth(ptr)); + return ref->mBase->mData.mValue; } void Weapon::registerSelf() @@ -343,7 +343,7 @@ namespace MWClass } text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); + text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); info.enchant = ref->mBase->mEnchant; diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 57681b7fb0..8d9dc670f2 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -125,6 +125,10 @@ namespace MWDialogue void DialogueManager::startDialogue (const MWWorld::Ptr& actor) { + // Dialogue with dead actor (e.g. through script) should not be allowed. + if (actor.getClass().getCreatureStats(actor).isDead()) + return; + mLastTopic = ""; mPermanentDispositionChange = 0; mTemporaryDispositionChange = 0; @@ -140,7 +144,11 @@ namespace MWDialogue mActorKnownTopics.clear(); MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow(); - win->startDialogue(actor, actor.getClass().getName (actor)); + + // If the dialogue window was already open, keep the existing history + bool resetHistory = (!MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_Dialogue)); + + win->startDialogue(actor, actor.getClass().getName (actor), resetHistory); //setup the list of topics known by the actor. Topics who are also on the knownTopics list will be added to the GUI updateTopics(); @@ -174,10 +182,20 @@ namespace MWDialogue win->addResponse (Interpreter::fixDefinesDialog(info->mResponse, interpreterContext)); executeScript (info->mResultScript); mLastTopic = Misc::StringUtils::lowerCase(it->mId); - break; + return; } } } + + // No greetings found. The dialogue window should not be shown. + // If this is a companion, we must show the companion window directly (used by BM_bear_be_unique). + bool isCompanion = !mActor.getClass().getScript(mActor).empty() + && mActor.getRefData().getLocals().getIntVar(mActor.getClass().getScript(mActor), "companion"); + if (isCompanion) + { + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Companion); + MWBase::Environment::get().getWindowManager()->showCompanionWindow(mActor); + } } bool DialogueManager::compile (const std::string& cmd,std::vector& code) @@ -294,7 +312,7 @@ namespace MWDialogue { if (iter->mId == info->mId) { - MWBase::Environment::get().getJournal()->addTopic (topic, info->mId, mActor.getClass().getName(mActor)); + MWBase::Environment::get().getJournal()->addTopic (topic, info->mId, mActor); break; } } @@ -398,7 +416,7 @@ namespace MWDialogue win->setServices (windowServices); // sort again, because the previous sort was case-sensitive - keywordList.sort(Misc::StringUtils::ciEqual); + keywordList.sort(Misc::StringUtils::ciLess); win->setKeywords(keywordList); mChoice = choice; @@ -472,7 +490,7 @@ namespace MWDialogue { if (iter->mId == info->mId) { - MWBase::Environment::get().getJournal()->addTopic (mLastTopic, info->mId, mActor.getClass().getName(mActor)); + MWBase::Environment::get().getJournal()->addTopic (mLastTopic, info->mId, mActor); break; } } @@ -694,6 +712,15 @@ namespace MWDialogue return diff; } + void DialogueManager::clearInfoActor(const MWWorld::Ptr &actor) const + { + if (actor == mActor && !mLastTopic.empty()) + { + MWBase::Environment::get().getJournal()->removeLastAddedTopicResponse( + mLastTopic, actor.getClass().getName(actor)); + } + } + std::vector ParseHyperText(const std::string& text) { std::vector result; diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp index 94f8f3ec1a..6553ddc014 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp @@ -40,7 +40,7 @@ namespace MWDialogue bool mTalkedTo; int mChoice; - std::string mLastTopic; + std::string mLastTopic; // last topic ID, lowercase bool mIsInChoice; float mTemporaryDispositionChange; @@ -99,6 +99,9 @@ namespace MWDialogue /// @return faction1's opinion of faction2 virtual int getFactionReaction (const std::string& faction1, const std::string& faction2) const; + + /// Removes the last added topic response for the given actor from the journal + virtual void clearInfoActor (const MWWorld::Ptr& actor) const; }; diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp index d301e88aac..08cdb1d003 100644 --- a/apps/openmw/mwdialogue/filter.cpp +++ b/apps/openmw/mwdialogue/filter.cpp @@ -530,7 +530,8 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co case SelectWrapper::Function_ShouldAttack: - return mActor.getClass().getCreatureStats(mActor).getAiSetting(MWMechanics::CreatureStats::AI_Fight).getModified() >= 80; + return MWBase::Environment::get().getMechanicsManager()->isAggressive(mActor, + MWBase::Environment::get().getWorld()->getPlayerPtr()); case SelectWrapper::Function_CreatureTargetted: diff --git a/apps/openmw/mwdialogue/journalentry.cpp b/apps/openmw/mwdialogue/journalentry.cpp index 55574bf3eb..b92d7cacea 100644 --- a/apps/openmw/mwdialogue/journalentry.cpp +++ b/apps/openmw/mwdialogue/journalentry.cpp @@ -5,16 +5,21 @@ #include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwscript/interpretercontext.hpp" + + namespace MWDialogue { Entry::Entry() {} - Entry::Entry (const std::string& topic, const std::string& infoId) + Entry::Entry (const std::string& topic, const std::string& infoId, const MWWorld::Ptr& actor) : mInfoId (infoId) { const ESM::Dialogue *dialogue = @@ -24,8 +29,17 @@ namespace MWDialogue iter!=dialogue->mInfo.end(); ++iter) if (iter->mId == mInfoId) { - /// \todo text replacement - mText = iter->mResponse; + if (actor.isEmpty()) + { + MWScript::InterpreterContext interpreterContext(NULL,MWWorld::Ptr()); + mText = Interpreter::fixDefinesDialog(iter->mResponse, interpreterContext); + } + else + { + MWScript::InterpreterContext interpreterContext(&actor.getRefData().getLocals(),actor); + mText = Interpreter::fixDefinesDialog(iter->mResponse, interpreterContext); + } + return; } @@ -49,8 +63,8 @@ namespace MWDialogue JournalEntry::JournalEntry() {} - JournalEntry::JournalEntry (const std::string& topic, const std::string& infoId) - : Entry (topic, infoId), mTopic (topic) + JournalEntry::JournalEntry (const std::string& topic, const std::string& infoId, const MWWorld::Ptr& actor) + : Entry (topic, infoId, actor), mTopic (topic) {} JournalEntry::JournalEntry (const ESM::JournalEntry& record) @@ -65,7 +79,7 @@ namespace MWDialogue JournalEntry JournalEntry::makeFromQuest (const std::string& topic, int index) { - return JournalEntry (topic, idFromIndex (topic, index)); + return JournalEntry (topic, idFromIndex (topic, index), MWWorld::Ptr()); } std::string JournalEntry::idFromIndex (const std::string& topic, int index) @@ -90,7 +104,7 @@ namespace MWDialogue StampedJournalEntry::StampedJournalEntry (const std::string& topic, const std::string& infoId, int day, int month, int dayOfMonth) - : JournalEntry (topic, infoId), mDay (day), mMonth (month), mDayOfMonth (dayOfMonth) + : JournalEntry (topic, infoId, MWWorld::Ptr()), mDay (day), mMonth (month), mDayOfMonth (dayOfMonth) {} StampedJournalEntry::StampedJournalEntry (const ESM::JournalEntry& record) diff --git a/apps/openmw/mwdialogue/journalentry.hpp b/apps/openmw/mwdialogue/journalentry.hpp index a77ba4f7cf..3ae3efcc8d 100644 --- a/apps/openmw/mwdialogue/journalentry.hpp +++ b/apps/openmw/mwdialogue/journalentry.hpp @@ -8,6 +8,11 @@ namespace ESM struct JournalEntry; } +namespace MWWorld +{ + class Ptr; +} + namespace MWDialogue { /// \brief Basic quest/dialogue/topic entry @@ -19,7 +24,8 @@ namespace MWDialogue Entry(); - Entry (const std::string& topic, const std::string& infoId); + /// actor is optional + Entry (const std::string& topic, const std::string& infoId, const MWWorld::Ptr& actor); Entry (const ESM::JournalEntry& record); @@ -37,7 +43,7 @@ namespace MWDialogue JournalEntry(); - JournalEntry (const std::string& topic, const std::string& infoId); + JournalEntry (const std::string& topic, const std::string& infoId, const MWWorld::Ptr& actor); JournalEntry (const ESM::JournalEntry& record); diff --git a/apps/openmw/mwdialogue/journalimp.cpp b/apps/openmw/mwdialogue/journalimp.cpp index 04aa0534dd..9497347e3a 100644 --- a/apps/openmw/mwdialogue/journalimp.cpp +++ b/apps/openmw/mwdialogue/journalimp.cpp @@ -9,6 +9,7 @@ #include #include "../mwworld/esmstore.hpp" +#include "../mwworld/class.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -103,15 +104,25 @@ namespace MWDialogue quest.setIndex (index); } - void Journal::addTopic (const std::string& topicId, const std::string& infoId, const std::string& actorName) + void Journal::addTopic (const std::string& topicId, const std::string& infoId, const MWWorld::Ptr& actor) { Topic& topic = getTopic (topicId); - JournalEntry entry(topicId, infoId); - entry.mActorName = actorName; + JournalEntry entry(topicId, infoId, actor); + entry.mActorName = actor.getClass().getName(actor); topic.addEntry (entry); } + void Journal::removeLastAddedTopicResponse(const std::string &topicId, const std::string &actorName) + { + Topic& topic = getTopic (topicId); + + topic.removeLastAddedResponse(actorName); + + if (topic.begin() == topic.end()) + mTopics.erase(mTopics.find(topicId)); // All responses removed -> remove topic + } + int Journal::getJournalIndex (const std::string& id) const { TQuestContainer::const_iterator iter = mQuests.find (id); diff --git a/apps/openmw/mwdialogue/journalimp.hpp b/apps/openmw/mwdialogue/journalimp.hpp index 00511f47c1..d15b909fa0 100644 --- a/apps/openmw/mwdialogue/journalimp.hpp +++ b/apps/openmw/mwdialogue/journalimp.hpp @@ -38,7 +38,12 @@ namespace MWDialogue virtual int getJournalIndex (const std::string& id) const; ///< Get the journal index. - virtual void addTopic (const std::string& topicId, const std::string& infoId, const std::string& actorName); + virtual void addTopic (const std::string& topicId, const std::string& infoId, const MWWorld::Ptr& actor); + /// \note topicId must be lowercase + + virtual void removeLastAddedTopicResponse (const std::string& topicId, const std::string& actorName); + ///< Removes the last topic response added for the given topicId and actor name. + /// \note topicId must be lowercase virtual TEntryIter begin() const; ///< Iterator pointing to the begin of the main journal. diff --git a/apps/openmw/mwdialogue/topic.cpp b/apps/openmw/mwdialogue/topic.cpp index f7df305c70..c1a45f841c 100644 --- a/apps/openmw/mwdialogue/topic.cpp +++ b/apps/openmw/mwdialogue/topic.cpp @@ -59,8 +59,16 @@ namespace MWDialogue return mEntries.end(); } - JournalEntry Topic::getEntry (const std::string& infoId) const + void Topic::removeLastAddedResponse (const std::string& actorName) { - return JournalEntry (mTopic, infoId); + for (std::vector::reverse_iterator it = mEntries.rbegin(); + it != mEntries.rend(); ++it) + { + if (it->mActorName == actorName) + { + mEntries.erase( (++it).base() ); // erase doesn't take a reverse_iterator + return; + } + } } } diff --git a/apps/openmw/mwdialogue/topic.hpp b/apps/openmw/mwdialogue/topic.hpp index 02fa6d5246..72486ef8af 100644 --- a/apps/openmw/mwdialogue/topic.hpp +++ b/apps/openmw/mwdialogue/topic.hpp @@ -48,13 +48,13 @@ namespace MWDialogue virtual std::string getName() const; + void removeLastAddedResponse (const std::string& actorName); + TEntryIter begin() const; ///< Iterator pointing to the begin of the journal for this topic. TEntryIter end() const; ///< Iterator pointing past the end of the journal for this topic. - - JournalEntry getEntry (const std::string& infoId) const; }; } diff --git a/apps/openmw/mwgui/bookpage.cpp b/apps/openmw/mwgui/bookpage.cpp index 2cebc8e194..659ba714c6 100644 --- a/apps/openmw/mwgui/bookpage.cpp +++ b/apps/openmw/mwgui/bookpage.cpp @@ -360,10 +360,16 @@ struct TypesetBookImpl::Typesetter : BookTypesetter int spaceLeft = mPageHeight - (curPageStop - curPageStart); int sectionHeight = i->mRect.height (); - if (sectionHeight <= mPageHeight) + // This is NOT equal to i->mRect.height(), which doesn't account for section breaks. + int spaceRequired = (i->mRect.bottom - curPageStop); + if (curPageStart == curPageStop) // If this is a new page, the section break is not needed + spaceRequired = i->mRect.height(); + + if (spaceRequired <= mPageHeight) { - if (sectionHeight > spaceLeft) + if (spaceRequired > spaceLeft) { + // The section won't completely fit on the current page. Finish the current page and start a new one. assert (curPageStart != curPageStop); mBook->mPages.push_back (Page (curPageStart, curPageStop)); diff --git a/apps/openmw/mwgui/confirmationdialog.cpp b/apps/openmw/mwgui/confirmationdialog.cpp index 89f477598f..57c88bfa2e 100644 --- a/apps/openmw/mwgui/confirmationdialog.cpp +++ b/apps/openmw/mwgui/confirmationdialog.cpp @@ -30,9 +30,9 @@ namespace MWGui void ConfirmationDialog::exit() { - eventCancelClicked(); - setVisible(false); + + eventCancelClicked(); } void ConfirmationDialog::onCancelButtonClicked(MyGUI::Widget* _sender) @@ -42,8 +42,8 @@ namespace MWGui void ConfirmationDialog::onOkButtonClicked(MyGUI::Widget* _sender) { - eventOkClicked(); - setVisible(false); + + eventOkClicked(); } } diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp index 2ae4d6ed1c..9f67524ae2 100644 --- a/apps/openmw/mwgui/console.cpp +++ b/apps/openmw/mwgui/console.cpp @@ -435,4 +435,10 @@ namespace MWGui { setSelectedObject(MWWorld::Ptr()); } + + void Console::resetReference() + { + ReferenceInterface::resetReference(); + setSelectedObject(MWWorld::Ptr()); + } } diff --git a/apps/openmw/mwgui/console.hpp b/apps/openmw/mwgui/console.hpp index ec699b317c..90da740c20 100644 --- a/apps/openmw/mwgui/console.hpp +++ b/apps/openmw/mwgui/console.hpp @@ -66,6 +66,8 @@ namespace MWGui void executeFile (const std::string& path); + virtual void resetReference (); + protected: virtual void onReferenceUnavailable(); diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index 4ba454a1ca..011feb4d35 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -38,6 +38,32 @@ namespace MWGui mIsOnDragAndDrop = true; mDragAndDropWidget->setVisible(true); + // If picking up an item that isn't from the player's inventory, the item gets added to player inventory backend + // immediately, even though it's still floating beneath the mouse cursor. A bit counterintuitive, + // but this is how it works in vanilla, and not doing so would break quests (BM_beasts for instance). + ItemModel* playerModel = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getModel(); + if (mSourceModel != playerModel) + { + MWWorld::Ptr item = mSourceModel->moveItem(mItem, mDraggedCount, playerModel); + + playerModel->update(); + + ItemModel::ModelIndex newIndex = -1; + for (unsigned int i=0; igetItemCount(); ++i) + { + if (playerModel->getItem(i).mBase == item) + { + newIndex = i; + break; + } + } + mItem = playerModel->getItem(newIndex); + mSourceModel = playerModel; + + SortFilterItemModel* playerFilterModel = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getSortFilterModel(); + mSourceSortModel = playerFilterModel; + } + std::string sound = mItem.mBase.getClass().getUpSoundId(mItem.mBase); MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index 441e31e327..a6ab1f122b 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -366,10 +366,11 @@ namespace MWGui } } - void DialogueWindow::startDialogue(MWWorld::Ptr actor, std::string npcName) + void DialogueWindow::startDialogue(MWWorld::Ptr actor, std::string npcName, bool resetHistory) { mGoodbye = false; mEnabled = true; + bool sameActor = (mPtr == actor); mPtr = actor; mTopicsList->setEnabled(true); setTitle(npcName); @@ -378,9 +379,12 @@ namespace MWGui mTopicsList->clear(); - for (std::vector::iterator it = mHistoryContents.begin(); it != mHistoryContents.end(); ++it) - delete (*it); - mHistoryContents.clear(); + if (resetHistory || !sameActor) + { + for (std::vector::iterator it = mHistoryContents.begin(); it != mHistoryContents.end(); ++it) + delete (*it); + mHistoryContents.clear(); + } for (std::vector::iterator it = mLinks.begin(); it != mLinks.end(); ++it) delete (*it); @@ -582,13 +586,32 @@ namespace MWGui //Clear the list of topics mTopicsList->clear(); - if (mPtr.getTypeName() == typeid(ESM::NPC).name()) + bool dispositionVisible = false; + if (mPtr.getClass().isNpc()) { + dispositionVisible = true; mDispositionBar->setProgressRange(100); mDispositionBar->setProgressPosition(MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mPtr)); mDispositionText->eraseText(0, mDispositionText->getTextLength()); mDispositionText->addText("#B29154"+boost::lexical_cast(MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mPtr))+std::string("/100")+"#B29154"); } + + bool dispositionWasVisible = mDispositionBar->getVisible(); + + if (dispositionVisible && !dispositionWasVisible) + { + mDispositionBar->setVisible(true); + float offset = mDispositionBar->getHeight()+5; + mTopicsList->setCoord(mTopicsList->getCoord() + MyGUI::IntCoord(0,offset,0,-offset)); + mTopicsList->adjustSize(); + } + else if (!dispositionVisible && dispositionWasVisible) + { + mDispositionBar->setVisible(false); + float offset = mDispositionBar->getHeight()+5; + mTopicsList->setCoord(mTopicsList->getCoord() - MyGUI::IntCoord(0,offset,0,-offset)); + mTopicsList->adjustSize(); + } } void DialogueWindow::goodbye() diff --git a/apps/openmw/mwgui/dialogue.hpp b/apps/openmw/mwgui/dialogue.hpp index fcb1338b5f..516c04942c 100644 --- a/apps/openmw/mwgui/dialogue.hpp +++ b/apps/openmw/mwgui/dialogue.hpp @@ -111,7 +111,7 @@ namespace MWGui void notifyLinkClicked (TypesetBook::InteractiveId link); - void startDialogue(MWWorld::Ptr actor, std::string npcName); + void startDialogue(MWWorld::Ptr actor, std::string npcName, bool resetHistory); void setKeywords(std::list keyWord); void addResponse (const std::string& text, const std::string& title=""); @@ -170,7 +170,7 @@ namespace MWGui BookPage* mHistory; Widgets::MWList* mTopicsList; MyGUI::ScrollBar* mScrollBar; - MyGUI::ProgressPtr mDispositionBar; + MyGUI::Progress* mDispositionBar; MyGUI::EditBox* mDispositionText; PersuasionDialog mPersuasionDialog; diff --git a/apps/openmw/mwgui/fontloader.cpp b/apps/openmw/mwgui/fontloader.cpp index 9d47bc38d1..b7c2007b46 100644 --- a/apps/openmw/mwgui/fontloader.cpp +++ b/apps/openmw/mwgui/fontloader.cpp @@ -258,12 +258,16 @@ namespace MWGui code->addAttribute("bearing", MyGUI::utility::toString(data[i].kerning) + " " + MyGUI::utility::toString((fontSize-data[i].ascent))); - // More hacks! The french game uses U+2019, which is nowhere to be found in - // the CP437 encoding of the font. Fall back to 39 (regular apostrophe) - if (i == 39 && mEncoding == ToUTF8::CP437) + // More hacks! The french game uses several win1252 characters that are not included + // in the cp437 encoding of the font. Fall back to similar available characters. + // Same for U+2013 + std::map additional; + additional[39] = 0x2019; // apostrophe + additional[45] = 0x2013; // dash + if (additional.find(i) != additional.end() && mEncoding == ToUTF8::CP437) { MyGUI::xml::ElementPtr code = codes->createChild("Code"); - code->addAttribute("index", 0x2019); + code->addAttribute("index", additional[i]); code->addAttribute("coord", MyGUI::utility::toString(x1) + " " + MyGUI::utility::toString(y1) + " " + MyGUI::utility::toString(w) + " " diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index d87ac7ec59..b39fd0db73 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -384,6 +384,8 @@ namespace MWGui void HUD::onFrame(float dt) { + LocalMapBase::onFrame(dt); + mCellNameTimer -= dt; mWeaponSpellTimer -= dt; if (mCellNameTimer < 0) diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index b1e8052d8c..abea68185d 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -132,6 +132,11 @@ namespace MWGui adjustPanes(); } + SortFilterItemModel* InventoryWindow::getSortFilterModel() + { + return mSortModel; + } + TradeItemModel* InventoryWindow::getTradeModel() { return mTradeModel; diff --git a/apps/openmw/mwgui/inventorywindow.hpp b/apps/openmw/mwgui/inventorywindow.hpp index df563b3d46..ae7af5719b 100644 --- a/apps/openmw/mwgui/inventorywindow.hpp +++ b/apps/openmw/mwgui/inventorywindow.hpp @@ -37,6 +37,7 @@ namespace MWGui mPreview->rebuild(); } + SortFilterItemModel* getSortFilterModel(); TradeItemModel* getTradeModel(); ItemModel* getModel(); diff --git a/apps/openmw/mwgui/itemwidget.cpp b/apps/openmw/mwgui/itemwidget.cpp index a1ca5cb6c9..7c79f8027c 100644 --- a/apps/openmw/mwgui/itemwidget.cpp +++ b/apps/openmw/mwgui/itemwidget.cpp @@ -33,12 +33,26 @@ namespace MWGui void ItemWidget::setIcon(const std::string &icon) { + // HACK HACK HACK: Don't setImageTexture if it hasn't changed. + // There is a leak in MyGUI for each setImageTexture on the same widget. + // http://www.ogre3d.org/addonforums/viewtopic.php?f=17&t=30251 + if (mCurrentItemTexture == icon) + return; + + mCurrentItemTexture = icon; if (mItem) mItem->setImageTexture(icon); } void ItemWidget::setFrame(const std::string &frame, const MyGUI::IntCoord &coord) { + // HACK HACK HACK: Don't setImageTexture if it hasn't changed. + // There is a leak in MyGUI for each setImageTexture on the same widget. + // http://www.ogre3d.org/addonforums/viewtopic.php?f=17&t=30251 + if (mCurrentFrameTexture == frame) + return; + + mCurrentFrameTexture = frame; if (mFrame) { mFrame->setImageTexture(frame); @@ -69,8 +83,21 @@ namespace MWGui if (ptr.isEmpty()) { if (mFrame) - mFrame->setImageTexture(""); - mItem->setImageTexture(""); + { + // HACK HACK HACK: Don't setImageTexture if it hasn't changed. + // There is a leak in MyGUI for each setImageTexture on the same widget. + // http://www.ogre3d.org/addonforums/viewtopic.php?f=17&t=30251 + if (!mCurrentFrameTexture.empty()) + { + mFrame->setImageTexture(""); + mCurrentFrameTexture = ""; + } + } + if (!mCurrentItemTexture.empty()) + { + mCurrentItemTexture = ""; + mItem->setImageTexture(""); + } return; } diff --git a/apps/openmw/mwgui/itemwidget.hpp b/apps/openmw/mwgui/itemwidget.hpp index 3de98489d0..5cdf712126 100644 --- a/apps/openmw/mwgui/itemwidget.hpp +++ b/apps/openmw/mwgui/itemwidget.hpp @@ -42,6 +42,9 @@ namespace MWGui MyGUI::ImageBox* mItem; MyGUI::ImageBox* mFrame; + + std::string mCurrentItemTexture; + std::string mCurrentFrameTexture; }; } diff --git a/apps/openmw/mwgui/levelupdialog.cpp b/apps/openmw/mwgui/levelupdialog.cpp index 38995ac326..eaa8227225 100644 --- a/apps/openmw/mwgui/levelupdialog.cpp +++ b/apps/openmw/mwgui/levelupdialog.cpp @@ -104,9 +104,10 @@ namespace MWGui int attribute = mSpentAttributes[i]; - int xdiff = mAttributeMultipliers[attribute]->getCaption() == "" ? 0 : 30; + int xdiff = mAttributeMultipliers[attribute]->getCaption() == "" ? 0 : 20; - MyGUI::IntPoint pos = mAttributes[attribute]->getAbsolutePosition() - mMainWidget->getAbsolutePosition() - MyGUI::IntPoint(24+xdiff,-4); + MyGUI::IntPoint pos = mAttributes[attribute]->getAbsolutePosition() - mMainWidget->getAbsolutePosition() - MyGUI::IntPoint(22+xdiff,0); + pos.top += (mAttributes[attribute]->getHeight() - image->getHeight())/2; image->setPosition(pos); } diff --git a/apps/openmw/mwgui/list.cpp b/apps/openmw/mwgui/list.cpp index ca2989646c..b0c514b9d3 100644 --- a/apps/openmw/mwgui/list.cpp +++ b/apps/openmw/mwgui/list.cpp @@ -101,7 +101,7 @@ namespace MWGui size_t viewRange = mScrollView->getCanvasSize().height; if(viewPosition > viewRange) viewPosition = viewRange; - mScrollView->setViewOffset(MyGUI::IntPoint(0, -viewPosition)); + mScrollView->setViewOffset(MyGUI::IntPoint(0, viewPosition * -1)); } void MWList::setPropertyOverride(const std::string &_key, const std::string &_value) diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index f1bbc68cdf..f6d6b81355 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -136,7 +136,7 @@ namespace MWGui Ogre::StringVector groups = Ogre::ResourceGroupManager::getSingleton().getResourceGroups (); for (Ogre::StringVector::iterator it = groups.begin(); it != groups.end(); ++it) { - Ogre::StringVectorPtr resourcesInThisGroup = Ogre::ResourceGroupManager::getSingleton ().findResourceNames (*it, "Splash_*.tga"); + Ogre::StringVectorPtr resourcesInThisGroup = Ogre::ResourceGroupManager::getSingleton ().findResourceNames (*it, "Splash/*.tga"); mResources.insert(mResources.end(), resourcesInThisGroup->begin(), resourcesInThisGroup->end()); } } diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index b5cd61f59c..ca7e877cce 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -68,10 +68,10 @@ namespace MWGui { if (visible) updateMenu(); - else - showBackground( - MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu) && - MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame); + + showBackground( + MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu) && + MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame); OEngine::GUI::Layout::setVisible (visible); } @@ -167,7 +167,7 @@ namespace MWGui mVideo = mVideoBackground->createWidget("ImageBox", 0,0,1,1, MyGUI::Align::Stretch, "Menu"); - mVideo->playVideo("video\\menu_background.bik", false); + mVideo->playVideo("video\\menu_background.bik"); } MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); @@ -204,7 +204,7 @@ namespace MWGui if (!mVideo->update()) { // If finished playing, start again - mVideo->playVideo("video\\menu_background.bik", 0); + mVideo->playVideo("video\\menu_background.bik"); } } } @@ -220,7 +220,6 @@ namespace MWGui MWBase::StateManager::State state = MWBase::Environment::get().getStateManager()->getState(); - showBackground(state == MWBase::StateManager::State_NoGame); mVersionText->setVisible(state == MWBase::StateManager::State_NoGame); std::vector buttons; diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index 29c065f3d5..af26456f27 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -36,6 +36,7 @@ namespace MWGui , mLastDirectionX(0.0f) , mLastDirectionY(0.0f) , mCompass(NULL) + , mMarkerUpdateTimer(0.0f) { } @@ -345,6 +346,18 @@ namespace MWGui markerWidget->setUserString("IsMarker", "true"); markerWidget->setUserData(markerPos); markerWidget->setColour(markerColour); + mMarkerWidgets.push_back(markerWidget); + } + } + + void LocalMapBase::onFrame(float dt) + { + mMarkerUpdateTimer += dt; + + if (mMarkerUpdateTimer >= 0.25) + { + mMarkerUpdateTimer = 0; + updateMarkers(); } } @@ -418,6 +431,9 @@ namespace MWGui { mGlobalMapRender = new MWRender::GlobalMap(""); mGlobalMapRender->render(loadingListener); + mGlobalMap->setCanvasSize (mGlobalMapRender->getWidth(), mGlobalMapRender->getHeight()); + mGlobalMapImage->setSize(mGlobalMapRender->getWidth(), mGlobalMapRender->getHeight()); + mGlobalMapImage->setImageTexture("GlobalMap.png"); mGlobalMapOverlay->setImageTexture("GlobalMapOverlay"); } @@ -471,6 +487,8 @@ namespace MWGui void MapWindow::onFrame(float dt) { + LocalMapBase::onFrame(dt); + for (std::vector::iterator it = mQueuedToExplore.begin(); it != mQueuedToExplore.end(); ++it) { mGlobalMapRender->exploreCell(it->first, it->second); @@ -497,7 +515,6 @@ namespace MWGui else mGlobalMap->setViewOffset( mGlobalMap->getViewOffset() + diff ); - mLastDragPos = MyGUI::IntPoint(_left, _top); } @@ -521,9 +538,6 @@ namespace MWGui void MapWindow::open() { - mGlobalMap->setCanvasSize (mGlobalMapRender->getWidth(), mGlobalMapRender->getHeight()); - mGlobalMapImage->setSize(mGlobalMapRender->getWidth(), mGlobalMapRender->getHeight()); - // force markers to foreground for (unsigned int i=0; igetChildCount (); ++i) { diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index d23b0c2285..c73e5d7a14 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -35,6 +35,8 @@ namespace MWGui void setPlayerDir(const float x, const float y); void setPlayerPos(const float x, const float y); + void onFrame(float dt); + bool toggleFogOfWar(); struct MarkerPosition @@ -73,12 +75,14 @@ namespace MWGui virtual void notifyMapChanged() {} // Update markers (Detect X effects, Mark/Recall effects) - // Note, door markers handled in setActiveCell + // Note, door markers are handled in setActiveCell void updateMarkers(); void addDetectionMarkers(int type); OEngine::GUI::Layout* mLayout; + float mMarkerUpdateTimer; + bool mMapDragAndDrop; float mLastPositionX; diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index d73a0e1223..c4b204de72 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -25,6 +25,23 @@ namespace MWGui } } + void MessageBoxManager::clear() + { + delete mInterMessageBoxe; + mInterMessageBoxe = NULL; + + std::vector::iterator it(mMessageBoxes.begin()); + for (; it != mMessageBoxes.end(); ++it) + { + if (*it == mStaticMessageBox) + mStaticMessageBox = NULL; + delete *it; + } + mMessageBoxes.clear(); + + mLastButtonPressed = -1; + } + void MessageBoxManager::onFrame (float frameDuration) { std::vector::iterator it; diff --git a/apps/openmw/mwgui/messagebox.hpp b/apps/openmw/mwgui/messagebox.hpp index caa37008ca..406d98c484 100644 --- a/apps/openmw/mwgui/messagebox.hpp +++ b/apps/openmw/mwgui/messagebox.hpp @@ -30,6 +30,9 @@ namespace MWGui bool createInteractiveMessageBox (const std::string& message, const std::vector& buttons); bool isInteractiveMessageBox (); + /// Remove all message boxes + void clear(); + bool removeMessageBox (MessageBox *msgbox); void setMessageBoxSpeed (int speed); diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index e142171770..328ef21fb7 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -24,6 +24,7 @@ #include "spellwindow.hpp" #include "itemwidget.hpp" +#include "sortfilteritemmodel.hpp" namespace MWGui @@ -134,6 +135,7 @@ namespace MWGui } mItemSelectionDialog->setVisible(true); mItemSelectionDialog->openContainer(MWBase::Environment::get().getWorld()->getPlayerPtr()); + mItemSelectionDialog->setFilter(SortFilterItemModel::Filter_OnlyUsableItems); mAssignDialog->setVisible (false); } @@ -162,7 +164,7 @@ namespace MWGui void QuickKeysMenu::onAssignItem(MWWorld::Ptr item) { - assert (mSelectedIndex > 0); + assert (mSelectedIndex >= 0); ItemWidget* button = mQuickKeyButtons[mSelectedIndex]; while (button->getChildCount()) // Destroy number label MyGUI::Gui::getInstance().destroyWidget(button->getChildAt(0)); @@ -184,7 +186,7 @@ namespace MWGui void QuickKeysMenu::onAssignMagicItem (MWWorld::Ptr item) { - assert (mSelectedIndex > 0); + assert (mSelectedIndex >= 0); ItemWidget* button = mQuickKeyButtons[mSelectedIndex]; while (button->getChildCount()) // Destroy number label MyGUI::Gui::getInstance().destroyWidget(button->getChildAt(0)); @@ -203,7 +205,7 @@ namespace MWGui void QuickKeysMenu::onAssignMagic (const std::string& spellId) { - assert (mSelectedIndex > 0); + assert (mSelectedIndex >= 0); ItemWidget* button = mQuickKeyButtons[mSelectedIndex]; while (button->getChildCount()) // Destroy number label MyGUI::Gui::getInstance().destroyWidget(button->getChildAt(0)); @@ -245,7 +247,7 @@ namespace MWGui void QuickKeysMenu::activateQuickKey(int index) { - assert (index-1 > 0); + assert (index-1 >= 0); ItemWidget* button = mQuickKeyButtons[index-1]; QuickKeyType type = mAssigned[index-1]; diff --git a/apps/openmw/mwgui/referenceinterface.hpp b/apps/openmw/mwgui/referenceinterface.hpp index df53a42b70..0ba180de86 100644 --- a/apps/openmw/mwgui/referenceinterface.hpp +++ b/apps/openmw/mwgui/referenceinterface.hpp @@ -17,7 +17,7 @@ namespace MWGui void checkReferenceAvailable(); ///< closes the window, if the MW-reference has become unavailable - void resetReference() { mPtr = MWWorld::Ptr(); mCurrentPlayerCell = NULL; } + virtual void resetReference() { mPtr = MWWorld::Ptr(); mCurrentPlayerCell = NULL; } protected: virtual void onReferenceUnavailable() = 0; ///< called when reference has become unavailable diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp index ef5d5858b6..a35415e754 100644 --- a/apps/openmw/mwgui/savegamedialog.cpp +++ b/apps/openmw/mwgui/savegamedialog.cpp @@ -30,11 +30,13 @@ namespace MWGui getWidget(mInfoText, "InfoText"); getWidget(mOkButton, "OkButton"); getWidget(mCancelButton, "CancelButton"); + getWidget(mDeleteButton, "DeleteButton"); getWidget(mSaveList, "SaveList"); getWidget(mSaveNameEdit, "SaveNameEdit"); getWidget(mSpacer, "Spacer"); mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onOkButtonClicked); mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onCancelButtonClicked); + mDeleteButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onDeleteButtonClicked); mCharacterSelection->eventComboChangePosition += MyGUI::newDelegate(this, &SaveGameDialog::onCharacterSelected); mSaveList->eventListChangePosition += MyGUI::newDelegate(this, &SaveGameDialog::onSlotSelected); mSaveList->eventListMouseItemActivate += MyGUI::newDelegate(this, &SaveGameDialog::onSlotMouseClick); @@ -54,13 +56,16 @@ namespace MWGui onSlotSelected(sender, pos); if (pos != MyGUI::ITEM_NONE && MyGUI::InputManager::getInstance().isShiftPressed()) - { - ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog(); - dialog->open("#{sMessage3}"); - dialog->eventOkClicked.clear(); - dialog->eventOkClicked += MyGUI::newDelegate(this, &SaveGameDialog::onDeleteSlotConfirmed); - dialog->eventCancelClicked.clear(); - } + confirmDeleteSave(); + } + + void SaveGameDialog::confirmDeleteSave() + { + ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog(); + dialog->open("#{sMessage3}"); + dialog->eventOkClicked.clear(); + dialog->eventOkClicked += MyGUI::newDelegate(this, &SaveGameDialog::onDeleteSlotConfirmed); + dialog->eventCancelClicked.clear(); } void SaveGameDialog::onDeleteSlotConfirmed() @@ -107,6 +112,7 @@ namespace MWGui mCurrentCharacter = NULL; mCurrentSlot = NULL; mSaveList->removeAllItems(); + onSlotSelected(mSaveList, MyGUI::ITEM_NONE); MWBase::StateManager* mgr = MWBase::Environment::get().getStateManager(); if (mgr->characterBegin() == mgr->characterEnd()) @@ -157,6 +163,8 @@ namespace MWGui } mCharacterSelection->setIndexSelected(selectedIndex); + if (selectedIndex == MyGUI::ITEM_NONE) + mCharacterSelection->setCaption("Select Character ..."); fillSaveList(); @@ -175,6 +183,9 @@ namespace MWGui mCharacterSelection->setVisible(load); mSpacer->setUserString("Hidden", load ? "false" : "true"); + mDeleteButton->setUserString("Hidden", load ? "false" : "true"); + mDeleteButton->setVisible(load); + if (!load) { mCurrentCharacter = MWBase::Environment::get().getStateManager()->getCurrentCharacter (false); @@ -188,6 +199,12 @@ namespace MWGui exit(); } + void SaveGameDialog::onDeleteButtonClicked(MyGUI::Widget *sender) + { + if (mCurrentSlot) + confirmDeleteSave(); + } + void SaveGameDialog::onConfirmationGiven() { accept(true); @@ -225,10 +242,8 @@ namespace MWGui } else { - if (mCurrentCharacter && mCurrentSlot) - { - MWBase::Environment::get().getStateManager()->loadGame (mCurrentCharacter, mCurrentSlot); - } + assert (mCurrentCharacter && mCurrentSlot); + MWBase::Environment::get().getStateManager()->loadGame (mCurrentCharacter, mCurrentSlot); } } @@ -278,6 +293,9 @@ namespace MWGui void SaveGameDialog::onSlotSelected(MyGUI::ListBox *sender, size_t pos) { + mOkButton->setEnabled(pos != MyGUI::ITEM_NONE || mSaving); + mDeleteButton->setEnabled(pos != MyGUI::ITEM_NONE); + if (pos == MyGUI::ITEM_NONE) { mCurrentSlot = NULL; diff --git a/apps/openmw/mwgui/savegamedialog.hpp b/apps/openmw/mwgui/savegamedialog.hpp index 9f44d53708..80cfad2794 100644 --- a/apps/openmw/mwgui/savegamedialog.hpp +++ b/apps/openmw/mwgui/savegamedialog.hpp @@ -24,8 +24,11 @@ namespace MWGui void setLoadOrSave(bool load); private: + void confirmDeleteSave(); + void onCancelButtonClicked (MyGUI::Widget* sender); void onOkButtonClicked (MyGUI::Widget* sender); + void onDeleteButtonClicked (MyGUI::Widget* sender); void onCharacterSelected (MyGUI::ComboBox* sender, size_t pos); // Slot selected (mouse click or arrow keys) void onSlotSelected (MyGUI::ListBox* sender, size_t pos); @@ -51,6 +54,7 @@ namespace MWGui MyGUI::EditBox* mInfoText; MyGUI::Button* mOkButton; MyGUI::Button* mCancelButton; + MyGUI::Button* mDeleteButton; MyGUI::ListBox* mSaveList; MyGUI::EditBox* mSaveNameEdit; MyGUI::Widget* mSpacer; diff --git a/apps/openmw/mwgui/sortfilteritemmodel.cpp b/apps/openmw/mwgui/sortfilteritemmodel.cpp index b8dcbcbbb1..93e5432ca9 100644 --- a/apps/openmw/mwgui/sortfilteritemmodel.cpp +++ b/apps/openmw/mwgui/sortfilteritemmodel.cpp @@ -14,6 +14,7 @@ #include #include "../mwworld/class.hpp" +#include "../mwworld/nullaction.hpp" namespace { @@ -126,6 +127,10 @@ namespace MWGui && !base.get()->mBase->mData.mIsScroll) return false; + if ((mFilter & Filter_OnlyUsableItems) && typeid(*base.getClass().use(base)) == typeid(MWWorld::NullAction) + && base.getClass().getScript(base).empty()) + return false; + return true; } diff --git a/apps/openmw/mwgui/sortfilteritemmodel.hpp b/apps/openmw/mwgui/sortfilteritemmodel.hpp index c7feaa3b92..4af35e7a8b 100644 --- a/apps/openmw/mwgui/sortfilteritemmodel.hpp +++ b/apps/openmw/mwgui/sortfilteritemmodel.hpp @@ -36,6 +36,7 @@ namespace MWGui static const int Filter_OnlyEnchanted = (1<<1); static const int Filter_OnlyEnchantable = (1<<2); static const int Filter_OnlyChargedSoulstones = (1<<3); + static const int Filter_OnlyUsableItems = (1<<4); // Only items with a Use action private: diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index 246ade7bf2..6c4e00ae5c 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -488,14 +488,16 @@ namespace MWGui text += "\n#BF9959#{sExpelled}"; else { - text += std::string("\n#BF9959") + faction->mRanks[it->second]; + int rank = it->second; + rank = std::max(0, std::min(9, rank)); + text += std::string("\n#BF9959") + faction->mRanks[rank]; - if (it->second < 9) + if (rank < 9) { // player doesn't have max rank yet - text += std::string("\n\n#DDC79E#{sNextRank} ") + faction->mRanks[it->second+1]; + text += std::string("\n\n#DDC79E#{sNextRank} ") + faction->mRanks[rank+1]; - ESM::RankData rankData = faction->mData.mRankData[it->second+1]; + ESM::RankData rankData = faction->mData.mRankData[rank+1]; const ESM::Attribute* attr1 = store.get().find(faction->mData.mAttribute[0]); const ESM::Attribute* attr2 = store.get().find(faction->mData.mAttribute[1]); @@ -504,11 +506,18 @@ namespace MWGui text += "\n\n#DDC79E#{sFavoriteSkills}"; text += "\n#BF9959"; - for (int i=0; i<6; ++i) + bool firstSkill = true; + for (int i=0; i<7; ++i) { - text += "#{"+ESM::Skill::sSkillNameIds[faction->mData.mSkills[i]]+"}"; - if (i<5) - text += ", "; + if (faction->mData.mSkills[i] != -1) + { + if (!firstSkill) + text += ", "; + + firstSkill = false; + + text += "#{"+ESM::Skill::sSkillNameIds[faction->mData.mSkills[i]]+"}"; + } } text += "\n"; diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 16b010908a..676a9ee63d 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -233,13 +233,9 @@ namespace MWGui for (std::map::iterator it = userStrings.begin(); it != userStrings.end(); ++it) { - if (it->first == "ToolTipType" - || it->first == "ToolTipLayout" - || it->first == "IsMarker") - continue; - - size_t underscorePos = it->first.find("_"); + if (underscorePos == std::string::npos) + continue; std::string propertyKey = it->first.substr(0, underscorePos); std::string widgetName = it->first.substr(underscorePos+1, it->first.size()-(underscorePos+1)); diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index 7ef472282f..c0a51311f5 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -23,6 +23,19 @@ #include "countdialog.hpp" #include "dialogue.hpp" +namespace +{ + + int getEffectiveValue (MWWorld::Ptr item, int count) + { + int price = item.getClass().getValue(item) * count; + if (item.getClass().hasItemHealth(item)) + price *= (static_cast(item.getClass().getItemHealth(item)) / item.getClass().getItemMaxHealth(item)); + return price; + } + +} + namespace MWGui { const float TradeWindow::sBalanceChangeInitialPause = 0.5; @@ -465,7 +478,7 @@ namespace MWGui void TradeWindow::sellToNpc(const MWWorld::Ptr& item, int count, bool boughtItem) { - int diff = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, item.getClass().getValue(item) * count, boughtItem); + int diff = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, getEffectiveValue(item, count), boughtItem); mCurrentBalance += diff; mCurrentMerchantOffer += diff; @@ -475,7 +488,7 @@ namespace MWGui void TradeWindow::buyFromNpc(const MWWorld::Ptr& item, int count, bool soldItem) { - int diff = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, item.getClass().getValue(item) * count, !soldItem); + int diff = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, getEffectiveValue(item, count), !soldItem); mCurrentBalance -= diff; mCurrentMerchantOffer -= diff; diff --git a/apps/openmw/mwgui/trainingwindow.cpp b/apps/openmw/mwgui/trainingwindow.cpp index 9a2c3b8055..6463db3d79 100644 --- a/apps/openmw/mwgui/trainingwindow.cpp +++ b/apps/openmw/mwgui/trainingwindow.cpp @@ -16,6 +16,24 @@ #include "tooltips.hpp" +namespace +{ +// Sorts a container descending by skill value. If skill value is equal, sorts ascending by skill ID. +// pair +bool sortSkills (const std::pair& left, const std::pair& right) +{ + if (left == right) + return false; + + if (left.second > right.second) + return true; + else if (left.second < right.second) + return false; + + return left.first < right.first; +} +} + namespace MWGui { @@ -52,29 +70,17 @@ namespace MWGui MWMechanics::NpcStats& npcStats = actor.getClass().getNpcStats (actor); // NPC can train you in his best 3 skills - std::vector< std::pair > bestSkills; - bestSkills.push_back (std::make_pair(-1, -1)); - bestSkills.push_back (std::make_pair(-1, -1)); - bestSkills.push_back (std::make_pair(-1, -1)); + std::vector< std::pair > skills; for (int i=0; i bestSkills[j].second) - { - if (j<2) - { - bestSkills[j+1] = bestSkills[j]; - } - bestSkills[j] = std::make_pair(i, value); - break; - } - } + skills.push_back(std::make_pair(i, value)); } + std::sort(skills.begin(), skills.end(), sortSkills); + MyGUI::EnumeratorWidgetPtr widgets = mTrainingOptions->getEnumerator (); MyGUI::Gui::getInstance ().destroyWidgets (widgets); @@ -86,20 +92,20 @@ namespace MWGui for (int i=0; i<3; ++i) { int price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer - (mPtr,pcStats.getSkill (bestSkills[i].first).getBase() * gmst.find("iTrainingMod")->getInt (),true); + (mPtr,pcStats.getSkill (skills[i].first).getBase() * gmst.find("iTrainingMod")->getInt (),true); MyGUI::Button* button = mTrainingOptions->createWidget("SandTextButton", MyGUI::IntCoord(5, 5+i*18, mTrainingOptions->getWidth()-10, 18), MyGUI::Align::Default); button->setEnabled(price <= playerGold); - button->setUserData(bestSkills[i].first); + button->setUserData(skills[i].first); button->eventMouseButtonClick += MyGUI::newDelegate(this, &TrainingWindow::onTrainingSelected); - button->setCaptionWithReplacing("#{" + ESM::Skill::sSkillNameIds[bestSkills[i].first] + "} - " + boost::lexical_cast(price)); + button->setCaptionWithReplacing("#{" + ESM::Skill::sSkillNameIds[skills[i].first] + "} - " + boost::lexical_cast(price)); button->setSize(button->getTextSize ().width+12, button->getSize().height); - ToolTips::createSkillToolTip (button, bestSkills[i].first); + ToolTips::createSkillToolTip (button, skills[i].first); } center(); diff --git a/apps/openmw/mwgui/travelwindow.cpp b/apps/openmw/mwgui/travelwindow.cpp index 79d50cdc59..9aa75173a6 100644 --- a/apps/openmw/mwgui/travelwindow.cpp +++ b/apps/openmw/mwgui/travelwindow.cpp @@ -10,9 +10,11 @@ #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/soundmanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" +#include "../mwworld/actionteleport.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/cellstore.hpp" @@ -50,7 +52,7 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Travel); } - void TravelWindow::addDestination(const std::string& travelId,ESM::Position pos,bool interior) + void TravelWindow::addDestination(const std::string& name,ESM::Position pos,bool interior) { int price = 0; @@ -85,13 +87,12 @@ namespace MWGui oss << price; toAdd->setUserString("price",oss.str()); - toAdd->setCaptionWithReplacing("#{sCell=" + travelId + "} - " + boost::lexical_cast(price)+"#{sgp}"); + toAdd->setCaptionWithReplacing("#{sCell=" + name + "} - " + boost::lexical_cast(price)+"#{sgp}"); toAdd->setSize(toAdd->getTextSize().width,sLineHeight); toAdd->eventMouseWheel += MyGUI::newDelegate(this, &TravelWindow::onMouseWheel); - toAdd->setUserString("Destination", travelId); + toAdd->setUserString("Destination", name); toAdd->setUserData(pos); toAdd->eventMouseButtonClick += MyGUI::newDelegate(this, &TravelWindow::onTravelButtonClick); - mDestinationsWidgetMap.insert(std::make_pair (toAdd, travelId)); } void TravelWindow::clearDestinations() @@ -100,7 +101,6 @@ namespace MWGui mCurrentY = 0; while (mDestinationsView->getChildCount()) MyGUI::Gui::getInstance().destroyWidget(mDestinationsView->getChildAt(0)); - mDestinationsWidgetMap.clear(); } void TravelWindow::startTravel(const MWWorld::Ptr& actor) @@ -118,7 +118,8 @@ namespace MWGui mPtr.get()->mBase->mTransport[i].mPos.pos[1],x,y); if (cellname == "") { - cellname = MWBase::Environment::get().getWorld()->getExterior(x,y)->getCell()->mName; + MWWorld::CellStore* cell = MWBase::Environment::get().getWorld()->getExterior(x,y); + cellname = MWBase::Environment::get().getWorld()->getCellName(cell); interior = false; } addDestination(cellname,mPtr.get()->mBase->mTransport[i].mPos,interior); @@ -140,18 +141,17 @@ namespace MWGui if (playerGoldisExterior()) + // Interior cell -> mages guild transport + MWBase::Environment::get().getSoundManager()->playSound("mysticism cast", 1, 1); player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); MWBase::Environment::get().getWorld ()->getFader ()->fadeOut(1); ESM::Position pos = *_sender->getUserData(); std::string cellname = _sender->getUserString("Destination"); - int x,y; bool interior = _sender->getUserString("interior") == "y"; - MWBase::Environment::get().getWorld()->positionToIndex(pos.pos[0],pos.pos[1],x,y); - if(interior) - MWBase::Environment::get().getWorld()->changeToInteriorCell(cellname, pos); - else + if (!interior) { ESM::Position playerPos = player.getRefData().getPosition(); float d = Ogre::Vector3(pos.pos[0], pos.pos[1], 0).distance( @@ -162,11 +162,12 @@ namespace MWGui MWBase::Environment::get().getMechanicsManager ()->rest (true); } MWBase::Environment::get().getWorld()->advanceTime(hours); - - MWBase::Environment::get().getWorld()->changeToExteriorCell(pos); } - player.getClass().adjustPosition(player); + // Teleports any followers, too. + MWWorld::ActionTeleport action(interior ? cellname : "", pos); + action.execute(player); + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Travel); MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Dialogue); MWBase::Environment::get().getWorld ()->getFader ()->fadeOut(0); diff --git a/apps/openmw/mwgui/travelwindow.hpp b/apps/openmw/mwgui/travelwindow.hpp index 5387bd690d..4328f7ac28 100644 --- a/apps/openmw/mwgui/travelwindow.hpp +++ b/apps/openmw/mwgui/travelwindow.hpp @@ -34,12 +34,10 @@ namespace MWGui MyGUI::ScrollView* mDestinationsView; - std::map mDestinationsWidgetMap; - void onCancelButtonClicked(MyGUI::Widget* _sender); void onTravelButtonClick(MyGUI::Widget* _sender); void onMouseWheel(MyGUI::Widget* _sender, int _rel); - void addDestination(const std::string& destinationID,ESM::Position pos,bool interior); + void addDestination(const std::string& name, ESM::Position pos, bool interior); void clearDestinations(); int mLastPos,mCurrentY; diff --git a/apps/openmw/mwgui/videowidget.cpp b/apps/openmw/mwgui/videowidget.cpp index 8430c1c7bc..cfd837a953 100644 --- a/apps/openmw/mwgui/videowidget.cpp +++ b/apps/openmw/mwgui/videowidget.cpp @@ -4,17 +4,12 @@ namespace MWGui { VideoWidget::VideoWidget() - : mAllowSkipping(true) { - eventKeyButtonPressed += MyGUI::newDelegate(this, &VideoWidget::onKeyPressed); - setNeedKeyFocus(true); } -void VideoWidget::playVideo(const std::string &video, bool allowSkipping) +void VideoWidget::playVideo(const std::string &video) { - mAllowSkipping = allowSkipping; - mPlayer.playVideo(video); setImageTexture(mPlayer.getTextureName()); @@ -30,19 +25,13 @@ int VideoWidget::getVideoHeight() return mPlayer.getVideoHeight(); } -void VideoWidget::onKeyPressed(MyGUI::Widget *_sender, MyGUI::KeyCode _key, MyGUI::Char _char) -{ - if (_key == MyGUI::KeyCode::Escape && mAllowSkipping) - mPlayer.stopVideo(); -} - bool VideoWidget::update() { mPlayer.update(); return mPlayer.isPlaying(); } -void VideoWidget::cleanup() +void VideoWidget::stop() { mPlayer.close(); } diff --git a/apps/openmw/mwgui/videowidget.hpp b/apps/openmw/mwgui/videowidget.hpp index 9360c8359f..ad3d4d5e3c 100644 --- a/apps/openmw/mwgui/videowidget.hpp +++ b/apps/openmw/mwgui/videowidget.hpp @@ -9,7 +9,7 @@ namespace MWGui { /** - * Widget that plays a video. Can be skipped by pressing Esc. + * Widget that plays a video. */ class VideoWidget : public MyGUI::ImageBox { @@ -18,7 +18,7 @@ namespace MWGui VideoWidget(); - void playVideo (const std::string& video, bool allowSkipping); + void playVideo (const std::string& video); int getVideoWidth(); int getVideoHeight(); @@ -26,15 +26,11 @@ namespace MWGui /// @return Is the video still playing? bool update(); - /// Free video player resources (done automatically on destruction) - void cleanup(); + /// Stop video and free resources (done automatically on destruction) + void stop(); private: - bool mAllowSkipping; - MWRender::VideoPlayer mPlayer; - - void onKeyPressed(MyGUI::Widget *_sender, MyGUI::KeyCode _key, MyGUI::Char _char); }; } diff --git a/apps/openmw/mwgui/widgets.cpp b/apps/openmw/mwgui/widgets.cpp index b30cf2bae8..b83ca0b09c 100644 --- a/apps/openmw/mwgui/widgets.cpp +++ b/apps/openmw/mwgui/widgets.cpp @@ -630,8 +630,11 @@ namespace MWGui MyGUI::IntSize AutoSizedButton::getRequestedSize() { - MyGUI::IntSize size = getTextSize() + MyGUI::IntSize(24,0); - size.height = std::max(24, size.height); + MyGUI::IntSize padding(24, 8); + if (isUserString("TextPadding")) + padding = MyGUI::IntSize::parse(getUserString("TextPadding")); + + MyGUI::IntSize size = getTextSize() + MyGUI::IntSize(padding.width,padding.height); return size; } diff --git a/apps/openmw/mwgui/windowbase.cpp b/apps/openmw/mwgui/windowbase.cpp index cc18e6694e..4af0afc1f6 100644 --- a/apps/openmw/mwgui/windowbase.cpp +++ b/apps/openmw/mwgui/windowbase.cpp @@ -21,6 +21,17 @@ void WindowBase::setVisible(bool visible) open(); else if (wasVisible && !visible) close(); + + // This is needed as invisible widgets can retain key focus. + if (!visible) + { + MyGUI::Widget* keyFocus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); + while (keyFocus != mMainWidget && keyFocus != NULL) + keyFocus = keyFocus->getParent(); + + if (keyFocus == mMainWidget) + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(NULL); + } } bool WindowBase::isVisible() diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 35ee2adc90..865ad1dca9 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -118,6 +118,7 @@ namespace MWGui , mCrosshairEnabled(Settings::Manager::getBool ("crosshair", "HUD")) , mSubtitlesEnabled(Settings::Manager::getBool ("subtitles", "GUI")) , mHudEnabled(true) + , mGuiEnabled(true) , mCursorVisible(true) , mPlayerName() , mPlayerRaceId() @@ -202,8 +203,12 @@ namespace MWGui MyGUI::Align::Default, "Overlay"); mVideoBackground->setImageTexture("black.png"); mVideoBackground->setVisible(false); + mVideoBackground->setNeedMouseFocus(true); + mVideoBackground->setNeedKeyFocus(true); mVideoWidget = mVideoBackground->createWidgetReal("ImageBox", 0,0,1,1, MyGUI::Align::Default); + mVideoWidget->setNeedMouseFocus(true); + mVideoWidget->setNeedKeyFocus(true); } void WindowManager::initUI() @@ -263,7 +268,7 @@ namespace MWGui mCompanionWindow = new CompanionWindow(mDragAndDrop, mMessageBoxManager); trackWindow(mCompanionWindow, "companion"); - mInputBlocker = mGui->createWidget("",0,0,w,h,MyGUI::Align::Default,"Windows",""); + mInputBlocker = mGui->createWidget("",0,0,w,h,MyGUI::Align::Default,"Windows"); mHud->setVisible(mHudEnabled); @@ -416,7 +421,7 @@ namespace MWGui mRecharge->setVisible(false); mVideoBackground->setVisible(false); - mHud->setVisible(mHudEnabled); + mHud->setVisible(mHudEnabled && mGuiEnabled); bool gameMode = !isGuiMode(); @@ -426,6 +431,13 @@ namespace MWGui if (gameMode) setKeyFocusWidget (NULL); + if (!mGuiEnabled) + { + if (containsMode(GM_Console)) + mConsole->setVisible(true); + return; + } + // Icons of forced hidden windows are displayed setMinimapVisibility((mAllowed & GW_Map) && (!mMap->pinned() || (mForceHidden & GW_Map))); setWeaponVisibility((mAllowed & GW_Inventory) && (!mInventoryWindow->pinned() || (mForceHidden & GW_Inventory))); @@ -1341,6 +1353,13 @@ namespace MWGui mHud->setVisible (mHudEnabled); } + bool WindowManager::toggleGui() + { + mGuiEnabled = !mGuiEnabled; + updateVisible(); + return mGuiEnabled; + } + bool WindowManager::getRestEnabled() { //Enable rest dialogue if character creation finished @@ -1498,6 +1517,7 @@ namespace MWGui { mMap->clear(); mQuickKeysMenu->clear(); + mMessageBoxManager->clear(); mTrainingWindow->resetReference(); mDialogueWindow->resetReference(); @@ -1559,7 +1579,15 @@ namespace MWGui void WindowManager::playVideo(const std::string &name, bool allowSkipping) { - mVideoWidget->playVideo("video\\" + name, allowSkipping); + mVideoWidget->playVideo("video\\" + name); + + mVideoWidget->eventKeyButtonPressed.clear(); + mVideoBackground->eventKeyButtonPressed.clear(); + if (allowSkipping) + { + mVideoWidget->eventKeyButtonPressed += MyGUI::newDelegate(this, &WindowManager::onVideoKeyPressed); + mVideoBackground->eventKeyButtonPressed += MyGUI::newDelegate(this, &WindowManager::onVideoKeyPressed); + } // Turn off all rendering except for the GUI mRendering->getScene()->clearSpecialCaseRenderQueues(); @@ -1587,7 +1615,7 @@ namespace MWGui mRendering->getWindow()->update(); } - mVideoWidget->cleanup(); + mVideoWidget->stop(); setCursorVisible(cursorWasVisible); @@ -1628,4 +1656,33 @@ namespace MWGui if(input == mCurrentModals.top()) mCurrentModals.pop(); } + + void WindowManager::onVideoKeyPressed(MyGUI::Widget *_sender, MyGUI::KeyCode _key, MyGUI::Char _char) + { + if (_key == MyGUI::KeyCode::Escape) + mVideoWidget->stop(); + } + + void WindowManager::pinWindow(GuiWindow window) + { + switch (window) + { + case GW_Inventory: + mInventoryWindow->setPinned(true); + break; + case GW_Map: + mMap->setPinned(true); + break; + case GW_Magic: + mSpellWindow->setPinned(true); + break; + case GW_Stats: + mStatsWindow->setPinned(true); + break; + default: + break; + } + + updateVisible(); + } } diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index b1dbf3a24a..8093d637e2 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -12,6 +12,9 @@ #include "../mwbase/windowmanager.hpp" +#include +#include + namespace MyGUI { class Gui; @@ -220,6 +223,9 @@ namespace MWGui virtual bool getSubtitlesEnabled(); virtual void toggleHud(); + /// Turn visibility of *all* GUI elements on or off (HUD and all windows, except the console) + virtual bool toggleGui(); + virtual void disallowMouse(); virtual void allowMouse(); virtual void notifyInputActionBound(); @@ -316,6 +322,8 @@ namespace MWGui \param input Pointer to the current modal, to ensure proper modal is removed **/ virtual void removeCurrentModal(WindowModal* input); + virtual void pinWindow (MWGui::GuiWindow window); + private: bool mConsoleOnlyScripts; @@ -376,6 +384,7 @@ namespace MWGui bool mCrosshairEnabled; bool mSubtitlesEnabled; bool mHudEnabled; + bool mGuiEnabled; bool mCursorVisible; void setCursorVisible(bool visible); @@ -424,6 +433,9 @@ namespace MWGui void onCursorChange(const std::string& name); void onKeyFocusChanged(MyGUI::Widget* widget); + // Key pressed while playing a video + void onVideoKeyPressed(MyGUI::Widget *_sender, MyGUI::KeyCode _key, MyGUI::Char _char); + void sizeVideo(int screenWidth, int screenHeight); }; } diff --git a/apps/openmw/mwgui/windowpinnablebase.cpp b/apps/openmw/mwgui/windowpinnablebase.cpp index 47364337c7..919d315f27 100644 --- a/apps/openmw/mwgui/windowpinnablebase.cpp +++ b/apps/openmw/mwgui/windowpinnablebase.cpp @@ -25,6 +25,12 @@ namespace MWGui onPinToggled(); } + void WindowPinnableBase::setPinned(bool pinned) + { + if (pinned != mPinned) + onPinButtonClicked(mPinButton); + } + void WindowPinnableBase::setPinButtonVisible(bool visible) { mPinButton->setVisible(visible); diff --git a/apps/openmw/mwgui/windowpinnablebase.hpp b/apps/openmw/mwgui/windowpinnablebase.hpp index cd393f9187..3aad60988c 100644 --- a/apps/openmw/mwgui/windowpinnablebase.hpp +++ b/apps/openmw/mwgui/windowpinnablebase.hpp @@ -12,6 +12,7 @@ namespace MWGui public: WindowPinnableBase(const std::string& parLayout); bool pinned() { return mPinned; } + void setPinned (bool pinned); void setPinButtonVisible(bool visible); private: diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 85d5cdb8e1..afdde6fb01 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -26,7 +26,7 @@ #include "../mwworld/inventorystore.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/npcstats.hpp" #include "../mwdialogue/dialoguemanagerimp.hpp" @@ -117,7 +117,7 @@ namespace MWInput , mPreviewPOVDelay(0.f) , mTimeIdle(0.f) , mOverencumberedMessageDelay(0.f) - , mAlwaysRunActive(false) + , mAlwaysRunActive(Settings::Manager::getBool("always run", "Input")) , mAttemptJump(false) , mControlsDisabled(false) { @@ -277,7 +277,7 @@ namespace MWInput showQuickKeysMenu(); break; case A_ToggleHUD: - MWBase::Environment::get().getWindowManager()->toggleHud(); + MWBase::Environment::get().getWindowManager()->toggleGui(); break; case A_QuickSave: quickSave(); @@ -547,6 +547,10 @@ namespace MWInput } if (!mControlsDisabled) mInputBinder->keyPressed (arg); + + // Clear MyGUI's clipboard, so it doesn't interfere with our own clipboard implementation. + // We do not use MyGUI's clipboard manager because it doesn't support system clipboard integration with SDL. + MyGUI::ClipboardManager::getInstance().clearClipboardData("Text"); } void InputManager::textInput(const SDL_TextInputEvent &arg) @@ -807,8 +811,9 @@ namespace MWInput if (MyGUI::InputManager::getInstance ().isModalAny()) return; - // Toggle between game mode and journal mode - if(!MWBase::Environment::get().getWindowManager()->isGuiMode() && MWBase::Environment::get().getWindowManager ()->getJournalAllowed()) + if((!MWBase::Environment::get().getWindowManager()->isGuiMode() + || MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_Dialogue) + && MWBase::Environment::get().getWindowManager ()->getJournalAllowed()) { MWBase::Environment::get().getSoundManager()->playSound ("book open", 1.0, 1.0); MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Journal); @@ -817,11 +822,18 @@ namespace MWInput { MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); } - // .. but don't touch any other mode. } void InputManager::quickKey (int index) { + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + if (player.getClass().getNpcStats(player).isWerewolf()) + { + // Cannot use items or spells while in werewolf form + MWBase::Environment::get().getWindowManager()->messageBox("#{sWerewolfRefusal}"); + return; + } + if (!MWBase::Environment::get().getWindowManager()->isGuiMode()) MWBase::Environment::get().getWindowManager()->activateQuickKey (index); } @@ -830,7 +842,18 @@ namespace MWInput { if (!MWBase::Environment::get().getWindowManager()->isGuiMode () && MWBase::Environment::get().getWorld()->getGlobalFloat ("chargenstate")==-1) + { + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + if (player.getClass().getNpcStats(player).isWerewolf()) + { + // Cannot use items or spells while in werewolf form + MWBase::Environment::get().getWindowManager()->messageBox("#{sWerewolfRefusal}"); + return; + } + MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_QuickKeysMenu); + + } else if (MWBase::Environment::get().getWindowManager()->getMode () == MWGui::GM_QuickKeysMenu) { while(MyGUI::InputManager::getInstance().isModalAny()) { //Handle any open Modal windows MWBase::Environment::get().getWindowManager()->getCurrentModal()->exit(); @@ -857,6 +880,8 @@ namespace MWInput { if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return; mAlwaysRunActive = !mAlwaysRunActive; + + Settings::Manager::setBool("always run", "Input", mAlwaysRunActive); } void InputManager::resetIdleTime() @@ -955,11 +980,6 @@ namespace MWInput mInputBinder->addMouseButtonBinding (control, defaultMouseButtonBindings[i], ICS::Control::INCREASE); } } - - // Printscreen key should not be allowed because it's captured by system screenshot function - // We check this explicitely here to fix up pre-0.26 config files. Can be removed after a few versions - if (mInputBinder->getKeyBinding(mInputBinder->getControl(A_Screenshot), ICS::Control::INCREASE) == SDLK_PRINTSCREEN) - mInputBinder->addKeyBinding(mInputBinder->getControl(A_Screenshot), SDLK_F12, ICS::Control::INCREASE); } std::string InputManager::getActionDescription (int action) diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index 6c4c93128f..9c1a5e613b 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -40,6 +40,10 @@ namespace MWMechanics void readState (const ESM::ActiveSpells& state); void writeState (ESM::ActiveSpells& state) const; + TIterator begin() const; + + TIterator end() const; + private: mutable TContainer mSpells; @@ -57,10 +61,6 @@ namespace MWMechanics const TContainer& getActiveSpells() const; - TIterator begin() const; - - TIterator end() const; - public: ActiveSpells(); diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 5727996d95..6ea65275ba 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -20,6 +20,8 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwrender/animation.hpp" + #include "npcstats.hpp" #include "creaturestats.hpp" #include "movement.hpp" @@ -199,32 +201,28 @@ namespace MWMechanics || (!actor1.getClass().canSwim(actor1) && MWBase::Environment::get().getWorld()->isSwimming(actor2)))) // creature can't swim to target return; - float fight; + bool aggressive; if (againstPlayer) - fight = actor1.getClass().getCreatureStats(actor1).getAiSetting(CreatureStats::AI_Fight).getModified(); + aggressive = MWBase::Environment::get().getMechanicsManager()->isAggressive(actor1, actor2); else { - fight = 0; + aggressive = false; // if one of actors is creature then we should make a decision to start combat or not // NOTE: function doesn't take into account combat between 2 creatures if (!actor1.getClass().isNpc()) { // if creature is hostile then it is necessarily to start combat - if (creatureStats.isHostile()) fight = 100; - else fight = creatureStats.getAiSetting(CreatureStats::AI_Fight).getModified(); + if (creatureStats.isHostile()) aggressive = true; + else aggressive = MWBase::Environment::get().getMechanicsManager()->isAggressive(actor1, actor2); } } - ESM::Position actor1Pos = actor1.getRefData().getPosition(); - ESM::Position actor2Pos = actor2.getRefData().getPosition(); - float d = Ogre::Vector3(actor1Pos.pos).distance(Ogre::Vector3(actor2Pos.pos)); - - if( (fight == 100 && d <= 5000) - || (fight >= 95 && d <= 3000) - || (fight >= 90 && d <= 2000) - || (fight >= 80 && d <= 1000)) + if(aggressive) { + const ESM::Position& actor1Pos = actor2.getRefData().getPosition(); + const ESM::Position& actor2Pos = actor2.getRefData().getPosition(); + float d = Ogre::Vector3(actor1Pos.pos).distance(Ogre::Vector3(actor2Pos.pos)); if (againstPlayer || actor2.getClass().getCreatureStats(actor2).getAiSequence().canAddTarget(actor2Pos, d)) { bool LOS = MWBase::Environment::get().getWorld()->getLOS(actor1, actor2); @@ -344,6 +342,8 @@ namespace MWMechanics CreatureStats &creatureStats = ptr.getClass().getCreatureStats(ptr); const MagicEffects &effects = creatureStats.getMagicEffects(); + bool wasDead = creatureStats.isDead(); + // attributes for(int i = 0;i < ESM::Attribute::Length;++i) { @@ -456,6 +456,50 @@ namespace MWMechanics } creatureStats.setHealth(health); + if (!wasDead && creatureStats.isDead()) + { + // The actor was killed by a magic effect. Figure out if the player was responsible for it. + const ActiveSpells& spells = creatureStats.getActiveSpells(); + bool killedByPlayer = false; + bool murderedByPlayer = false; + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + for (ActiveSpells::TIterator it = spells.begin(); it != spells.end(); ++it) + { + const ActiveSpells::ActiveSpellParams& spell = it->second; + for (std::vector::const_iterator effectIt = spell.mEffects.begin(); + effectIt != spell.mEffects.end(); ++effectIt) + { + int effectId = effectIt->mEffectId; + bool isDamageEffect = false; + for (unsigned int i=0; isearchPtrViaActorId(spell.mCasterActorId); + if (isDamageEffect && caster == player) + { + killedByPlayer = true; + // Simple check for who attacked first: if the player attacked first, a crimeId should be set + // Doesn't handle possible edge case where no one reported the assault, but in such a case, + // for bystanders it is not possible to tell who attacked first, anyway. + if (ptr.getClass().isNpc() && ptr.getClass().getNpcStats(ptr).getCrimeId() != -1 + && ptr != player) + murderedByPlayer = true; + } + } + } + if (murderedByPlayer) + MWBase::Environment::get().getMechanicsManager()->commitCrime(player, ptr, MWBase::MechanicsManager::OT_Murder); + if (killedByPlayer && player.getClass().getNpcStats(player).isWerewolf()) + player.getClass().getNpcStats(player).addWerewolfKill(); + } + // TODO: dirty flag for magic effects to avoid some unnecessary work below? // Update bound effects @@ -486,8 +530,12 @@ namespace MWMechanics itemGmst)->getString(); if (it->first == ESM::MagicEffect::BoundGloves) { - adjustBoundItem("sMagicBoundLeftGauntletID", magnitude > 0, ptr); - adjustBoundItem("sMagicBoundRightGauntletID", magnitude > 0, ptr); + item = MWBase::Environment::get().getWorld()->getStore().get().find( + "sMagicBoundLeftGauntletID")->getString(); + adjustBoundItem(item, magnitude > 0, ptr); + item = MWBase::Environment::get().getWorld()->getStore().get().find( + "sMagicBoundRightGauntletID")->getString(); + adjustBoundItem(item, magnitude > 0, ptr); } else adjustBoundItem(item, magnitude > 0, ptr); @@ -529,7 +577,8 @@ namespace MWMechanics for (std::map::iterator it = summonMap.begin(); it != summonMap.end(); ++it) { - bool found = creatureStats.mSummonedCreatures.find(it->first) != creatureStats.mSummonedCreatures.end(); + std::map& creatureMap = creatureStats.getSummonedCreatureMap(); + bool found = creatureMap.find(it->first) != creatureMap.end(); int magnitude = creatureStats.getMagicEffects().get(it->first).mMagnitude; if (found != (magnitude > 0)) { @@ -563,17 +612,25 @@ namespace MWMechanics summonedCreatureStats.getAiSequence().stack(package, ref.getPtr()); int creatureActorId = summonedCreatureStats.getActorId(); - MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,ipos); + MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,ipos); - // TODO: VFX_SummonStart, VFX_SummonEnd - creatureStats.mSummonedCreatures.insert(std::make_pair(it->first, creatureActorId)); + MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(placed); + if (anim) + { + const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() + .search("VFX_Summon_Start"); + if (fx) + anim->addEffect("meshes\\" + fx->mModel, -1, false); + } + + creatureMap.insert(std::make_pair(it->first, creatureActorId)); } } else { // Summon lifetime has expired. Try to delete the creature. - int actorId = creatureStats.mSummonedCreatures[it->first]; - creatureStats.mSummonedCreatures.erase(it->first); + int actorId = creatureMap[it->first]; + creatureMap.erase(it->first); MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(actorId); if (!ptr.isEmpty()) @@ -581,24 +638,38 @@ namespace MWMechanics // TODO: Show death animation before deleting? We shouldn't allow looting the corpse while the animation // plays though, which is a rather lame exploit in vanilla. MWBase::Environment::get().getWorld()->deleteObject(ptr); - creatureStats.mSummonedCreatures.erase(it->first); + + const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() + .search("VFX_Summon_End"); + if (fx) + MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + fx->mModel, + "", Ogre::Vector3(ptr.getRefData().getPosition().pos)); } else { // We didn't find the creature. It's probably in an inactive cell. // Add to graveyard so we can delete it when the cell becomes active. - creatureStats.mSummonGraveyard.push_back(actorId); + std::vector& graveyard = creatureStats.getSummonedCreatureGraveyard(); + graveyard.push_back(actorId); } } } } - for (std::vector::iterator it = creatureStats.mSummonGraveyard.begin(); it != creatureStats.mSummonGraveyard.end(); ) + std::vector& graveyard = creatureStats.getSummonedCreatureGraveyard(); + for (std::vector::iterator it = graveyard.begin(); it != graveyard.end(); ) { MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(*it); if (!ptr.isEmpty()) { - it = creatureStats.mSummonGraveyard.erase(it); + it = graveyard.erase(it); + + const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() + .search("VFX_Summon_End"); + if (fx) + MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + fx->mModel, + "", Ogre::Vector3(ptr.getRefData().getPosition().pos)); + MWBase::Environment::get().getWorld()->deleteObject(ptr); } else @@ -768,7 +839,6 @@ namespace MWMechanics if (ptr.getClass().isClass(ptr, "Guard") && creatureStats.getAiSequence().getTypeId() != AiPackage::TypeIdPursue && !creatureStats.isHostile()) { - /// \todo Move me! I shouldn't be here... const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); float cutoff = float(esmStore.get().find("iCrimeThreshold")->getInt()); // Force dialogue on sight if bounty is greater than the cutoff @@ -796,7 +866,6 @@ namespace MWMechanics creatureStats.getAiSequence().stopCombat(); // Reset factors to attack - // TODO: Not a complete list, disposition changes? creatureStats.setHostile(false); creatureStats.setAttacked(false); creatureStats.setAlarmed(false); @@ -804,15 +873,6 @@ namespace MWMechanics // Update witness crime id npcStats.setCrimeId(-1); } - else if (!creatureStats.isHostile() && creatureStats.getAiSequence().getTypeId() != AiPackage::TypeIdPursue) - { - if (ptr.getClass().isClass(ptr, "Guard")) - creatureStats.getAiSequence().stack(AiPursue(player), ptr); - else - { - MWBase::Environment::get().getMechanicsManager()->startCombat(ptr, player); - } - } } } } @@ -928,7 +988,7 @@ namespace MWMechanics if (timerUpdateAITargets == 0 && iter->first.getTypeName() == typeid(ESM::Creature).name() && !listGuards.empty()) { sBasePoint = Ogre::Vector3(iter->first.getRefData().getPosition().pos); - listGuards.sort(comparePtrDist); // try to engage combat starting from the nearest creature + listGuards.sort(comparePtrDist); // try to engage combat starting from the nearest guard for (std::list::iterator it = listGuards.begin(); it != listGuards.end(); ++it) { @@ -970,6 +1030,9 @@ namespace MWMechanics } // Kill dead actors, update some variables + + int hostilesCount = 0; // need to know this to play Battle music + for(PtrControllerMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { const MWWorld::Class &cls = iter->first.getClass(); @@ -988,6 +1051,8 @@ namespace MWMechanics if(!stats.isDead()) { + if (stats.isHostile()) hostilesCount++; + if(iter->second->isDead()) { // Actor has been resurrected. Notify the CharacterController and re-enable collision. @@ -1045,6 +1110,23 @@ namespace MWMechanics } } + // check if we still have any player enemies to switch music + static bool isBattleMusic = false; + + if (isBattleMusic && hostilesCount == 0) + { + MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore")); + isBattleMusic = false; + } + else if (!isBattleMusic && hostilesCount > 0) + { + MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Battle")); + isBattleMusic = true; + } + + static float sneakTimer = 0.f; // times update of sneak icon + static float sneakSkillTimer = 0.f; // times sneak skill progress from "avoid notice" + // if player is in sneak state see if anyone detects him if (player.getClass().getCreatureStats(player).getMovementFlag(MWMechanics::CreatureStats::Flag_Sneak)) { @@ -1052,27 +1134,54 @@ namespace MWMechanics const int radius = esmStore.get().find("fSneakUseDist")->getInt(); bool detected = false; - for (PtrControllerMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) + static float fSneakUseDelay = esmStore.get().find("fSneakUseDelay")->getFloat(); + + if (sneakTimer >= fSneakUseDelay) + sneakTimer = 0.f; + + if (sneakTimer == 0.f) { - if (iter->first == player) // not the player - continue; + // Set when an NPC is within line of sight and distance, but is still unaware. Used for skill progress. + bool avoidedNotice = false; - // is the player in range and can they be detected - if ( (Ogre::Vector3(iter->first.getRefData().getPosition().pos).squaredDistance(Ogre::Vector3(player.getRefData().getPosition().pos)) <= radius*radius) - && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, iter->first) - && MWBase::Environment::get().getWorld()->getLOS(player, iter->first)) + for (PtrControllerMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { - detected = true; - MWBase::Environment::get().getWindowManager()->setSneakVisibility(false); - break; - } - } + if (iter->first == player) // not the player + continue; - if (!detected) - MWBase::Environment::get().getWindowManager()->setSneakVisibility(true); + // is the player in range and can they be detected + if (Ogre::Vector3(iter->first.getRefData().getPosition().pos).squaredDistance(Ogre::Vector3(player.getRefData().getPosition().pos)) <= radius*radius + && MWBase::Environment::get().getWorld()->getLOS(player, iter->first)) + { + if (MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, iter->first)) + { + detected = true; + avoidedNotice = false; + MWBase::Environment::get().getWindowManager()->setSneakVisibility(false); + break; + } + else if (!detected) + avoidedNotice = true; + } + } + + if (sneakSkillTimer >= fSneakUseDelay) + sneakSkillTimer = 0.f; + + if (avoidedNotice && sneakSkillTimer == 0.f) + player.getClass().skillUsageSucceeded(player, ESM::Skill::Sneak, 0); + + if (!detected) + MWBase::Environment::get().getWindowManager()->setSneakVisibility(true); + } + sneakTimer += duration; + sneakSkillTimer += duration; } else + { + sneakTimer = 0.f; MWBase::Environment::get().getWindowManager()->setSneakVisibility(false); + } } } void Actors::restoreDynamicStats(bool sleep) @@ -1181,4 +1290,36 @@ namespace MWMechanics } return list; } + + void Actors::write (ESM::ESMWriter& writer, Loading::Listener& listener) const + { + writer.startRecord(ESM::REC_DCOU); + for (std::map::const_iterator it = mDeathCount.begin(); it != mDeathCount.end(); ++it) + { + writer.writeHNString("ID__", it->first); + writer.writeHNT ("COUN", it->second); + } + writer.endRecord(ESM::REC_DCOU); + + listener.increaseProgress(1); + } + + void Actors::readRecord (ESM::ESMReader& reader, int32_t type) + { + if (type == ESM::REC_DCOU) + { + while (reader.isNextSub("ID__")) + { + std::string id = reader.getHString(); + int count; + reader.getHNT (count, "COUN"); + mDeathCount[id] = count; + } + } + } + + void Actors::clear() + { + mDeathCount.clear(); + } } diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index cc95660dc2..4784162f48 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -112,6 +112,12 @@ namespace MWMechanics /**ie AiCombat is active and the target is the actor **/ std::list getActorsFighting(const MWWorld::Ptr& actor); + void write (ESM::ESMWriter& writer, Loading::Listener& listener) const; + + void readRecord (ESM::ESMReader& reader, int32_t type); + + void clear(); // Clear death counter + private: PtrControllerMap mActors; diff --git a/apps/openmw/mwmechanics/aiactivate.cpp b/apps/openmw/mwmechanics/aiactivate.cpp index 3dfacb853d..9e01c3fe72 100644 --- a/apps/openmw/mwmechanics/aiactivate.cpp +++ b/apps/openmw/mwmechanics/aiactivate.cpp @@ -1,10 +1,11 @@ #include "aiactivate.hpp" +#include + #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/action.hpp" #include "../mwworld/cellstore.hpp" #include "steering.hpp" @@ -19,26 +20,28 @@ MWMechanics::AiActivate *MWMechanics::AiActivate::clone() const return new AiActivate(*this); } bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor,float duration) -{ - ESM::Position pos = actor.getRefData().getPosition(); //position of the actor - const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mObjectId, false); //The target to follow - - if(target == MWWorld::Ptr()) - return true; //Target doesn't exist - - //Set the target desition from the actor - ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; - - if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < 200) { //Stop when you get close - actor.getClass().getMovementSettings(actor).mPosition[1] = 0; - MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(mObjectId,false); - target.getClass().activate(target,actor).get()->execute(actor); //Arrest player - return true; - } - else { - pathTo(actor, dest, duration); //Go to the destination - } - +{ + ESM::Position pos = actor.getRefData().getPosition(); //position of the actor + const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mObjectId, false); //The target to follow + + if(target == MWWorld::Ptr()) + return true; //Target doesn't exist + + //Set the target desition from the actor + ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; + + if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < 200) { //Stop when you get close + actor.getClass().getMovementSettings(actor).mPosition[1] = 0; + MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(mObjectId,false); + + MWBase::Environment::get().getWorld()->activate(target, actor); + + return true; + } + else { + pathTo(actor, dest, duration); //Go to the destination + } + return false; } @@ -46,3 +49,20 @@ int MWMechanics::AiActivate::getTypeId() const { return TypeIdActivate; } + +void MWMechanics::AiActivate::writeState(ESM::AiSequence::AiSequence &sequence) const +{ + std::auto_ptr activate(new ESM::AiSequence::AiActivate()); + activate->mTargetId = mObjectId; + + ESM::AiSequence::AiPackageContainer package; + package.mType = ESM::AiSequence::Ai_Activate; + package.mPackage = activate.release(); + sequence.mPackages.push_back(package); +} + +MWMechanics::AiActivate::AiActivate(const ESM::AiSequence::AiActivate *activate) + : mObjectId(activate->mTargetId) +{ + +} diff --git a/apps/openmw/mwmechanics/aiactivate.hpp b/apps/openmw/mwmechanics/aiactivate.hpp index 0e660e967d..8003c2a361 100644 --- a/apps/openmw/mwmechanics/aiactivate.hpp +++ b/apps/openmw/mwmechanics/aiactivate.hpp @@ -6,20 +6,33 @@ #include "pathfinding.hpp" +namespace ESM +{ +namespace AiSequence +{ + struct AiActivate; +} +} + namespace MWMechanics -{ - /// \brief Causes actor to walk to activatable object and activate it +{ + /// \brief Causes actor to walk to activatable object and activate it /** Will activate when close to object **/ class AiActivate : public AiPackage { - public: - /// Constructor + public: + /// Constructor /** \param objectId Reference to object to activate **/ AiActivate(const std::string &objectId); + + AiActivate(const ESM::AiSequence::AiActivate* activate); + virtual AiActivate *clone() const; virtual bool execute (const MWWorld::Ptr& actor,float duration); virtual int getTypeId() const; + virtual void writeState(ESM::AiSequence::AiSequence& sequence) const; + private: std::string mObjectId; int mCellX; diff --git a/apps/openmw/mwmechanics/aiavoiddoor.cpp b/apps/openmw/mwmechanics/aiavoiddoor.cpp index ea6f296cc8..bab8bca280 100644 --- a/apps/openmw/mwmechanics/aiavoiddoor.cpp +++ b/apps/openmw/mwmechanics/aiavoiddoor.cpp @@ -78,8 +78,14 @@ MWMechanics::AiAvoidDoor *MWMechanics::AiAvoidDoor::clone() const return new AiAvoidDoor(*this); } - int MWMechanics::AiAvoidDoor::getTypeId() const +int MWMechanics::AiAvoidDoor::getTypeId() const { return TypeIdAvoidDoor; } +unsigned int MWMechanics::AiAvoidDoor::getPriority() const +{ + return 2; +} + + diff --git a/apps/openmw/mwmechanics/aiavoiddoor.hpp b/apps/openmw/mwmechanics/aiavoiddoor.hpp index d2a2e33a1f..2374fbc1be 100644 --- a/apps/openmw/mwmechanics/aiavoiddoor.hpp +++ b/apps/openmw/mwmechanics/aiavoiddoor.hpp @@ -24,6 +24,8 @@ namespace MWMechanics virtual int getTypeId() const; + virtual unsigned int getPriority() const; + private: float mDuration; MWWorld::Ptr mDoorPtr; diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 9ec770c9d1..2606daa88e 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -1,8 +1,8 @@ #include "aicombat.hpp" #include -#include +#include #include "../mwworld/class.hpp" #include "../mwworld/timestamp.hpp" @@ -14,6 +14,8 @@ #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/dialoguemanager.hpp" +#include "../mwrender/animation.hpp" + #include "creaturestats.hpp" #include "steering.hpp" @@ -30,7 +32,12 @@ namespace } //chooses an attack depending on probability to avoid uniformity - void chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement); + ESM::Weapon::AttackType chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement); + + void getMinMaxAttackDuration(const MWWorld::Ptr& actor, float (*fMinMaxDurations)[2]); + + Ogre::Vector3 AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, const Ogre::Vector3& vLastTargetPos, + float duration, int weapType, float strength); float getZAngleToDir(const Ogre::Vector3& dir, float dirLen = 0.0f) { @@ -76,28 +83,42 @@ namespace namespace MWMechanics { - static const float MAX_ATTACK_DURATION = 0.35f; static const float DOOR_CHECK_INTERVAL = 1.5f; // same as AiWander // NOTE: MIN_DIST_TO_DOOR_SQUARED is defined in obstacle.hpp AiCombat::AiCombat(const MWWorld::Ptr& actor) : - mTargetActorId(actor.getClass().getCreatureStats(actor).getActorId()), - mTimerAttack(0), - mTimerReact(0), - mTimerCombatMove(0), - mFollowTarget(false), - mReadyToAttack(false), - mAttack(false), - mCombatMove(false), - mMovement(), - mForceNoShortcut(false), - mShortcutFailPos(), - mBackOffDoor(false), - mCell(NULL), - mDoorIter(actor.getCell()->get().mList.end()), - mDoors(actor.getCell()->get()), - mDoorCheckDuration(0) + mTargetActorId(actor.getClass().getCreatureStats(actor).getActorId()) + , mMinMaxAttackDuration() + , mMovement() { + init(); + + mLastTargetPos = Ogre::Vector3(actor.getRefData().getPosition().pos); + } + + AiCombat::AiCombat(const ESM::AiSequence::AiCombat *combat) + : mMinMaxAttackDuration() + , mMovement() + { + mTargetActorId = combat->mTargetActorId; + + init(); + } + + void AiCombat::init() + { + mTimerAttack = 0; + mTimerReact = 0; + mTimerCombatMove = 0; + mFollowTarget = false; + mReadyToAttack = false; + mAttack = false; + mCombatMove = false; + mForceNoShortcut = false; + mStrength = 0; + mCell = NULL; + mLastTargetPos = Ogre::Vector3(0,0,0); + mMinMaxAttackDurationInitialised = false; } /* @@ -158,17 +179,20 @@ namespace MWMechanics return true; const MWWorld::Class& actorClass = actor.getClass(); - MWBase::World& world = *MWBase::Environment::get().getWorld(); + MWBase::World* world = MWBase::Environment::get().getWorld(); - if ((!actorClass.isNpc() && target == world.getPlayerPtr() && - actorClass.canSwim(actor) && !actorClass.canWalk(actor) // pure water creature - && !world.isSwimming(target)) // Player moved out of water - || (!actorClass.canSwim(actor) && world.isSwimming(target))) // creature can't swim to Player + if (!actorClass.isNpc() && + // 1. pure water creature and Player moved out of water + ((target == world->getPlayerPtr() && + actorClass.canSwim(actor) && !actor.getClass().canWalk(actor) && !world->isSwimming(target)) + // 2. creature can't swim to target + || (!actorClass.canSwim(actor) && world->isSwimming(target)))) { - actorClass.getCreatureStats(actor).setHostile(false); + if (target == world->getPlayerPtr()) + actorClass.getCreatureStats(actor).setHostile(false); actorClass.getCreatureStats(actor).setAttackingOrSpell(false); return true; - } + } //Update every frame if(mCombatMove) @@ -182,9 +206,9 @@ namespace MWMechanics } } - actor.getClass().getMovementSettings(actor) = mMovement; - actor.getClass().getMovementSettings(actor).mRotation[0] = 0; - actor.getClass().getMovementSettings(actor).mRotation[2] = 0; + actorClass.getMovementSettings(actor) = mMovement; + actorClass.getMovementSettings(actor).mRotation[0] = 0; + actorClass.getMovementSettings(actor).mRotation[2] = 0; if(mMovement.mRotation[2] != 0) { @@ -196,8 +220,31 @@ namespace MWMechanics if(smoothTurn(actor, Ogre::Degree(mMovement.mRotation[0]), 0)) mMovement.mRotation[0] = 0; } - mTimerAttack -= duration; - actor.getClass().getCreatureStats(actor).setAttackingOrSpell(mAttack); + //TODO: Some skills affect period of strikes.For berserk-like style period ~ 0.25f + float attacksPeriod = 1.0f; + + ESM::Weapon::AttackType attackType; + + if(mReadyToAttack) + { + if (!mMinMaxAttackDurationInitialised) + { + // TODO: this must be updated when a different weapon is equipped + getMinMaxAttackDuration(actor, mMinMaxAttackDuration); + mMinMaxAttackDurationInitialised = true; + } + + if (mTimerAttack < 0) mAttack = false; + + mTimerAttack -= duration; + } + else + { + mTimerAttack = -attacksPeriod; + mAttack = false; + } + + actorClass.getCreatureStats(actor).setAttackingOrSpell(mAttack); float tReaction = 0.25f; if(mTimerReact < tReaction) @@ -216,73 +263,37 @@ namespace MWMechanics mCell = actor.getCell(); } - //actual attacking logic - //TODO: Some skills affect period of strikes.For berserk-like style period ~ 0.25f - float attacksPeriod = 1.0f; - if(mReadyToAttack) - { - if(mTimerAttack <= -attacksPeriod) - { - //TODO: should depend on time between 'start' to 'min attack' - //for better controlling of NPCs' attack strength. - //Also it seems that this time is different for slash/thrust/chop - mTimerAttack = MAX_ATTACK_DURATION * static_cast(rand())/RAND_MAX; - mAttack = true; - - //say a provoking combat phrase - if (actor.getClass().isNpc()) - { - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - int chance = store.get().find("iVoiceAttackOdds")->getInt(); - int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] - if (roll < chance) - { - MWBase::Environment::get().getDialogueManager()->say(actor, "attack"); - } - } - } - else if (mTimerAttack <= 0) - mAttack = false; - } - else - { - mTimerAttack = -attacksPeriod; - mAttack = false; - } - - const MWWorld::Class &actorCls = actor.getClass(); const ESM::Weapon *weapon = NULL; MWMechanics::WeaponType weaptype; - float weapRange, weapSpeed = 1.0f; + float weapRange; - actorCls.getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); + actorClass.getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); // Get weapon characteristics - if (actorCls.hasInventoryStore(actor)) + if (actorClass.hasInventoryStore(actor)) { - MWMechanics::DrawState_ state = actorCls.getCreatureStats(actor).getDrawState(); + MWMechanics::DrawState_ state = actorClass.getCreatureStats(actor).getDrawState(); if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing) - actorCls.getCreatureStats(actor).setDrawState(MWMechanics::DrawState_Weapon); + actorClass.getCreatureStats(actor).setDrawState(MWMechanics::DrawState_Weapon); // TODO: Check equipped weapon and equip a different one if we can't attack with it // (e.g. no ammunition, or wrong type of ammunition equipped, etc. autoEquip is not very smart in this regard)) //Get weapon speed and range MWWorld::ContainerStoreIterator weaponSlot = - MWMechanics::getActiveWeapon(actorCls.getCreatureStats(actor), actorCls.getInventoryStore(actor), &weaptype); + MWMechanics::getActiveWeapon(actorClass.getCreatureStats(actor), actorClass.getInventoryStore(actor), &weaptype); if (weaptype == WeapType_HandToHand) { - const MWWorld::Store &gmst = - MWBase::Environment::get().getWorld()->getStore().get(); - weapRange = gmst.find("fHandToHandReach")->getFloat(); + static float fHandToHandReach = + world->getStore().get().find("fHandToHandReach")->getFloat(); + weapRange = fHandToHandReach; } else if (weaptype != WeapType_PickProbe && weaptype != WeapType_Spell) { // All other WeapTypes are actually weapons, so get is safe. weapon = weaponSlot->get()->mBase; weapRange = weapon->mData.mReach; - weapSpeed = weapon->mData.mSpeed; } weapRange *= 100.0f; } @@ -292,6 +303,51 @@ namespace MWMechanics weapRange = 150; //TODO: use true attack range (the same problem in Creature::hit) } + float rangeAttack; + float rangeFollow; + bool distantCombat = false; + if (weaptype == WeapType_BowAndArrow || weaptype == WeapType_Crossbow || weaptype == WeapType_Thrown) + { + rangeAttack = 1000; // TODO: should depend on archer skill + rangeFollow = 0; // not needed in ranged combat + distantCombat = true; + } + else + { + rangeAttack = weapRange; + rangeFollow = 300; + } + + // start new attack + if(mReadyToAttack) + { + if(mTimerAttack <= -attacksPeriod) + { + mAttack = true; // attack starts just now + + if (!distantCombat) attackType = chooseBestAttack(weapon, mMovement); + else attackType = ESM::Weapon::AT_Chop; // cause it's =0 + + mStrength = static_cast(rand()) / RAND_MAX; + + // Note: may be 0 for some animations + mTimerAttack = mMinMaxAttackDuration[attackType][0] + + (mMinMaxAttackDuration[attackType][1] - mMinMaxAttackDuration[attackType][0]) * mStrength; + + //say a provoking combat phrase + if (actor.getClass().isNpc()) + { + const MWWorld::ESMStore &store = world->getStore(); + int chance = store.get().find("iVoiceAttackOdds")->getInt(); + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + if (roll < chance) + { + MWBase::Environment::get().getDialogueManager()->say(actor, "attack"); + } + } + } + } + /* * Some notes on meanings of variables: @@ -322,21 +378,6 @@ namespace MWMechanics * target even if LOS is not achieved) */ - float rangeAttack; - float rangeFollow; - bool distantCombat = false; - if (weaptype == WeapType_BowAndArrow || weaptype == WeapType_Crossbow || weaptype == WeapType_Thrown) - { - rangeAttack = 1000; // TODO: should depend on archer skill - rangeFollow = 0; // not needed in ranged combat - distantCombat = true; - } - else - { - rangeAttack = weapRange; - rangeFollow = 300; - } - ESM::Position pos = actor.getRefData().getPosition(); Ogre::Vector3 vActorPos(pos.pos); Ogre::Vector3 vTargetPos(target.getRefData().getPosition().pos); @@ -345,48 +386,55 @@ namespace MWMechanics bool isStuck = false; float speed = 0.0f; - if(mMovement.mPosition[1] && (Ogre::Vector3(mLastPos.pos) - vActorPos).length() < (speed = actorCls.getSpeed(actor)) * tReaction / 2) + if(mMovement.mPosition[1] && (mLastActorPos - vActorPos).length() < (speed = actorClass.getSpeed(actor)) * tReaction / 2) isStuck = true; - mLastPos = pos; + mLastActorPos = vActorPos; // check if actor can move along z-axis - bool canMoveByZ = (actorCls.canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor)) - || MWBase::Environment::get().getWorld()->isFlying(actor); + bool canMoveByZ = (actorClass.canSwim(actor) && world->isSwimming(actor)) + || world->isFlying(actor); - // determine vertical angle to target - // if actor can move along z-axis it will control movement dir - // if can't - it will control correct aiming - mMovement.mRotation[0] = getXAngleToDir(vDirToTarget, distToTarget); + // for distant combat we should know if target is in LOS even if distToTarget < rangeAttack + bool inLOS = distantCombat ? world->getLOS(actor, target) : true; - // (within strike dist) || (not quite strike dist while following) - if(distToTarget < rangeAttack || (distToTarget <= rangeFollow && mFollowTarget && !isStuck) ) + // (within attack dist) || (not quite attack dist while following) + if(inLOS && (distToTarget < rangeAttack || (distToTarget <= rangeFollow && mFollowTarget && !isStuck))) { //Melee and Close-up combat + + // getXAngleToDir determines vertical angle to target: + // if actor can move along z-axis it will control movement dir + // if can't - it will control correct aiming. + // note: in getZAngleToDir if we preserve dir.z then horizontal angle can be inaccurate + if (distantCombat) + { + Ogre::Vector3 vAimDir = AimDirToMovingTarget(actor, target, mLastTargetPos, tReaction, weaptype, mStrength); + mLastTargetPos = vTargetPos; + mMovement.mRotation[0] = getXAngleToDir(vAimDir); + mMovement.mRotation[2] = getZAngleToDir(Ogre::Vector3(vAimDir.x, vAimDir.y, 0)); + } + else + { + mMovement.mRotation[0] = getXAngleToDir(vDirToTarget, distToTarget); + mMovement.mRotation[2] = getZAngleToDir(Ogre::Vector3(vDirToTarget.x, vDirToTarget.y, 0)); + } - // if we preserve dir.z then horizontal angle can be inaccurate - mMovement.mRotation[2] = getZAngleToDir(Ogre::Vector3(vDirToTarget.x, vDirToTarget.y, 0)); - - // (not quite strike dist while following) + // (not quite attack dist while following) if (mFollowTarget && distToTarget > rangeAttack) { //Close-up combat: just run up on target mMovement.mPosition[1] = 1; } - else // (within strike dist) + else // (within attack dist) { - mMovement.mPosition[1] = 0; - - // set slash/thrust/chop attack - if (mAttack && !distantCombat) chooseBestAttack(weapon, mMovement); - if(mMovement.mPosition[0] || mMovement.mPosition[1]) { mTimerCombatMove = 0.1f + 0.1f * static_cast(rand())/RAND_MAX; mCombatMove = true; } // only NPCs are smart enough to use dodge movements - else if(actorCls.isNpc() && (!distantCombat || (distantCombat && distToTarget < rangeAttack/2))) + else if(actorClass.isNpc() && (!distantCombat || (distantCombat && distToTarget < rangeAttack/2))) { //apply sideway movement (kind of dodging) with some probability if(static_cast(rand())/RAND_MAX < 0.25) @@ -410,13 +458,13 @@ namespace MWMechanics else // remote pathfinding { bool preferShortcut = false; - bool inLOS = MWBase::Environment::get().getWorld()->getLOS(actor, target); + if (!distantCombat) inLOS = world->getLOS(actor, target); // check if shortcut is available if(inLOS && (!isStuck || mReadyToAttack) && (!mForceNoShortcut || (Ogre::Vector3(mShortcutFailPos.pos) - vActorPos).length() >= PATHFIND_SHORTCUT_RETRY_DIST)) { - if(speed == 0.0f) speed = actorCls.getSpeed(actor); + if(speed == 0.0f) speed = actorClass.getSpeed(actor); // maximum dist before pit/obstacle for actor to avoid them depending on his speed float maxAvoidDist = tReaction * speed + speed / MAX_VEL_ANGULAR.valueRadians() * 2; // *2 - for reliability preferShortcut = checkWayIsClear(vActorPos, vTargetPos, Ogre::Vector3(vDirToTarget.x, vDirToTarget.y, 0).length() > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2); @@ -427,6 +475,8 @@ namespace MWMechanics if(preferShortcut) { + if (canMoveByZ) + mMovement.mRotation[0] = getXAngleToDir(vDirToTarget, distToTarget); mMovement.mRotation[2] = getZAngleToDir(vDirToTarget, distToTarget); mForceNoShortcut = false; mShortcutFailPos.pos[0] = mShortcutFailPos.pos[1] = mShortcutFailPos.pos[2] = 0; @@ -474,18 +524,24 @@ namespace MWMechanics } mMovement.mPosition[1] = 1; + if (mReadyToAttack) + { + // to stop possible sideway moving after target moved out of attack range + mCombatMove = true; + mTimerCombatMove = 0; + } mReadyToAttack = false; } if(!isStuck && distToTarget > rangeAttack && !distantCombat) { //special run attack; it shouldn't affect melee combat tactics - if(actorCls.getMovementSettings(actor).mPosition[1] == 1) + if(actorClass.getMovementSettings(actor).mPosition[1] == 1) { - //check if actor can overcome the distance = distToTarget - attackerWeapRange - //less than in time of playing weapon anim from 'start' to 'hit' tags (t_swing) - //then start attacking - float speed1 = actorCls.getSpeed(actor); + /* check if actor can overcome the distance = distToTarget - attackerWeapRange + less than in time of swinging with weapon (t_swing), then start attacking + */ + float speed1 = actorClass.getSpeed(actor); float speed2 = target.getClass().getSpeed(target); if(target.getClass().getMovementSettings(target).mPosition[0] == 0 && target.getClass().getMovementSettings(target).mPosition[1] == 0) @@ -494,13 +550,16 @@ namespace MWMechanics float s1 = distToTarget - weapRange; float t = s1/speed1; float s2 = speed2 * t; - float t_swing = (MAX_ATTACK_DURATION/2) / weapSpeed;//instead of 0.17 should be the time of playing weapon anim from 'start' to 'hit' tags + float t_swing = + mMinMaxAttackDuration[ESM::Weapon::AT_Thrust][0] + + (mMinMaxAttackDuration[ESM::Weapon::AT_Thrust][1] - mMinMaxAttackDuration[ESM::Weapon::AT_Thrust][0]) * static_cast(rand()) / RAND_MAX; + if (t + s2/speed1 <= t_swing) { mReadyToAttack = true; if(mTimerAttack <= -attacksPeriod) { - mTimerAttack = MAX_ATTACK_DURATION * static_cast(rand())/RAND_MAX; + mTimerAttack = t_swing; mAttack = true; } } @@ -511,70 +570,22 @@ namespace MWMechanics // coded at 250ms or 1/4 second // // TODO: Add a parameter to vary DURATION_SAME_SPOT? - MWWorld::CellStore *cell = actor.getCell(); if((distToTarget > rangeAttack || mFollowTarget) && mObstacleCheck.check(actor, tReaction)) // check if evasive action needed { - // first check if we're walking into a door - mDoorCheckDuration += 1.0f; // add time taken for obstacle check - if(mDoorCheckDuration >= DOOR_CHECK_INTERVAL && !cell->getCell()->isExterior()) - { - mDoorCheckDuration = 0; - // Check all the doors in this cell - mDoors = cell->get(); // update - mDoorIter = mDoors.mList.begin(); - for (; mDoorIter != mDoors.mList.end(); ++mDoorIter) - { - MWWorld::LiveCellRef& ref = *mDoorIter; - float minSqr = 1.3*1.3*MIN_DIST_TO_DOOR_SQUARED; // for legibility - if(vActorPos.squaredDistance(Ogre::Vector3(ref.mRef.getPosition().pos)) < minSqr && - ref.mData.getLocalRotation().rot[2] < 0.4f) // even small opening - { - //std::cout<<"closed door id \""<getCell()->isExterior() && !mDoors.mList.empty()) - { - MWWorld::LiveCellRef& ref = *mDoorIter; - float minSqr = 1.6 * 1.6 * MIN_DIST_TO_DOOR_SQUARED; // for legibility - // TODO: add reaction to checking open doors - if(mBackOffDoor && - vActorPos.squaredDistance(Ogre::Vector3(ref.mRef.getPosition().pos)) < minSqr) - { - mMovement.mPosition[1] = -0.2; // back off, but slowly - } - else if(mBackOffDoor && - mDoorIter != mDoors.mList.end() && - ref.mData.getLocalRotation().rot[2] >= 1) - { - mDoorIter = mDoors.mList.end(); - mBackOffDoor = false; - //std::cout<<"open door id \""< combat(new ESM::AiSequence::AiCombat()); + combat->mTargetActorId = mTargetActorId; + + ESM::AiSequence::AiPackageContainer package; + package.mType = ESM::AiSequence::Ai_Combat; + package.mPackage = combat.release(); + sequence.mPackages.push_back(package); + } } namespace { -void chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement) +ESM::Weapon::AttackType chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement) { + ESM::Weapon::AttackType attackType; + if (weapon == NULL) { //hand-to-hand deal equal damage for each type @@ -663,34 +687,167 @@ void chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement { movement.mPosition[0] = (static_cast(rand())/RAND_MAX < 0.5f)? 1: -1; movement.mPosition[1] = 0; + attackType = ESM::Weapon::AT_Slash; } else if(roll <= 0.666f) //forward punch + { movement.mPosition[1] = 1; + attackType = ESM::Weapon::AT_Thrust; + } else { movement.mPosition[1] = movement.mPosition[0] = 0; + attackType = ESM::Weapon::AT_Chop; } + } + else + { + //the more damage attackType deals the more probability it has + int slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1])/2; + int chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1])/2; + int thrust = (weapon->mData.mThrust[0] + weapon->mData.mThrust[1])/2; + + float total = slash + chop + thrust; + + float roll = static_cast(rand())/RAND_MAX; + if(roll <= static_cast(slash)/total) + { + movement.mPosition[0] = (static_cast(rand())/RAND_MAX < 0.5f)? 1: -1; + movement.mPosition[1] = 0; + attackType = ESM::Weapon::AT_Slash; + } + else if(roll <= (static_cast(slash) + static_cast(thrust))/total) + { + movement.mPosition[1] = 1; + attackType = ESM::Weapon::AT_Thrust; + } + else + { + movement.mPosition[1] = movement.mPosition[0] = 0; + attackType = ESM::Weapon::AT_Chop; + } + } + + return attackType; +} + +void getMinMaxAttackDuration(const MWWorld::Ptr& actor, float (*fMinMaxDurations)[2]) +{ + if (!actor.getClass().hasInventoryStore(actor)) // creatures + { + fMinMaxDurations[0][0] = fMinMaxDurations[0][1] = 0.1f; + fMinMaxDurations[1][0] = fMinMaxDurations[1][1] = 0.1f; + fMinMaxDurations[2][0] = fMinMaxDurations[2][1] = 0.1f; return; } - //the more damage attackType deals the more probability it has - int slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1])/2; - int chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1])/2; - int thrust = (weapon->mData.mThrust[0] + weapon->mData.mThrust[1])/2; + // get weapon information: type and speed + const ESM::Weapon *weapon = NULL; + MWMechanics::WeaponType weaptype; - float total = slash + chop + thrust; + MWWorld::ContainerStoreIterator weaponSlot = + MWMechanics::getActiveWeapon(actor.getClass().getCreatureStats(actor), actor.getClass().getInventoryStore(actor), &weaptype); - float roll = static_cast(rand())/RAND_MAX; - if(roll <= static_cast(slash)/total) + float weapSpeed; + if (weaptype != MWMechanics::WeapType_HandToHand) { - movement.mPosition[0] = (static_cast(rand())/RAND_MAX < 0.5f)? 1: -1; - movement.mPosition[1] = 0; + weapon = weaponSlot->get()->mBase; + weapSpeed = weapon->mData.mSpeed; + } + else weapSpeed = 1.0f; + + MWRender::Animation *anim = MWBase::Environment::get().getWorld()->getAnimation(actor); + + std::string weapGroup; + MWMechanics::getWeaponGroup(weaptype, weapGroup); + weapGroup = weapGroup + ": "; + + bool bRangedWeap = (weaptype >= MWMechanics::WeapType_BowAndArrow && weaptype <= MWMechanics::WeapType_Thrown); + + const char *attackType[] = {"chop ", "slash ", "thrust ", "shoot "}; + + std::string textKey = "start"; + std::string textKey2; + + // get durations for each attack type + for (int i = 0; i < (bRangedWeap ? 1 : 3); i++) + { + float start1 = anim->getTextKeyTime(weapGroup + (bRangedWeap ? attackType[3] : attackType[i]) + textKey); + + if (start1 < 0) + { + fMinMaxDurations[i][0] = fMinMaxDurations[i][1] = 0.1f; + continue; + } + + textKey2 = "min attack"; + float start2 = anim->getTextKeyTime(weapGroup + (bRangedWeap ? attackType[3] : attackType[i]) + textKey2); + + fMinMaxDurations[i][0] = (start2 - start1) / weapSpeed; + + textKey2 = "max attack"; + start1 = anim->getTextKeyTime(weapGroup + (bRangedWeap ? attackType[3] : attackType[i]) + textKey2); + + fMinMaxDurations[i][1] = fMinMaxDurations[i][0] + (start1 - start2) / weapSpeed; + } + +} + +Ogre::Vector3 AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, const Ogre::Vector3& vLastTargetPos, + float duration, int weapType, float strength) +{ + float projSpeed; + + // get projectile speed (depending on weapon type) + if (weapType == ESM::Weapon::MarksmanThrown) + { + static float fThrownWeaponMinSpeed = + MWBase::Environment::get().getWorld()->getStore().get().find("fThrownWeaponMinSpeed")->getFloat(); + static float fThrownWeaponMaxSpeed = + MWBase::Environment::get().getWorld()->getStore().get().find("fThrownWeaponMaxSpeed")->getFloat(); + + projSpeed = + fThrownWeaponMinSpeed + (fThrownWeaponMaxSpeed - fThrownWeaponMinSpeed) * strength; } - else if(roll <= (static_cast(slash) + static_cast(thrust))/total) - movement.mPosition[1] = 1; else - movement.mPosition[1] = movement.mPosition[0] = 0; + { + static float fProjectileMinSpeed = + MWBase::Environment::get().getWorld()->getStore().get().find("fProjectileMinSpeed")->getFloat(); + static float fProjectileMaxSpeed = + MWBase::Environment::get().getWorld()->getStore().get().find("fProjectileMaxSpeed")->getFloat(); + + projSpeed = + fProjectileMinSpeed + (fProjectileMaxSpeed - fProjectileMinSpeed) * strength; + } + + // idea: perpendicular to dir to target speed components of target move vector and projectile vector should be the same + + Ogre::Vector3 vActorPos = Ogre::Vector3(actor.getRefData().getPosition().pos); + Ogre::Vector3 vTargetPos = Ogre::Vector3(target.getRefData().getPosition().pos); + Ogre::Vector3 vDirToTarget = vTargetPos - vActorPos; + float distToTarget = vDirToTarget.length(); + + Ogre::Vector3 vTargetMoveDir = vTargetPos - vLastTargetPos; + vTargetMoveDir /= duration; // |vTargetMoveDir| is target real speed in units/sec now + + Ogre::Vector3 vPerpToDir = vDirToTarget.crossProduct(Ogre::Vector3::UNIT_Z); + + float velPerp = vTargetMoveDir.dotProduct(vPerpToDir.normalisedCopy()); + float velDir = vTargetMoveDir.dotProduct(vDirToTarget.normalisedCopy()); + + // time to collision between target and projectile + float t_collision; + + float projVelDirSquared = projSpeed * projSpeed - velPerp * velPerp; + float projDistDiff = vDirToTarget.dotProduct(vTargetMoveDir.normalisedCopy()); + projDistDiff = sqrt(distToTarget * distToTarget - projDistDiff * projDistDiff); + + if (projVelDirSquared > 0) + t_collision = projDistDiff / (sqrt(projVelDirSquared) - velDir); + else t_collision = 0; // speed of projectile is not enough to reach moving target + + return vTargetPos + vTargetMoveDir * t_collision - vActorPos; } } diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 4b728ff221..311dee6179 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -8,10 +8,20 @@ #include "movement.hpp" #include "obstacle.hpp" +#include + #include "../mwworld/cellstore.hpp" // for Doors #include "../mwbase/world.hpp" +namespace ESM +{ + namespace AiSequence + { + struct AiCombat; + } +} + namespace MWMechanics { /// \brief Causes the actor to fight another actor @@ -22,6 +32,10 @@ namespace MWMechanics /** \param actor Actor to fight **/ AiCombat(const MWWorld::Ptr& actor); + AiCombat (const ESM::AiSequence::AiCombat* combat); + + void init(); + virtual AiCombat *clone() const; virtual bool execute (const MWWorld::Ptr& actor,float duration); @@ -33,6 +47,8 @@ namespace MWMechanics ///Returns target ID MWWorld::Ptr getTarget() const; + virtual void writeState(ESM::AiSequence::AiSequence &sequence) const; + private: PathFinder mPathFinder; // controls duration of the actual strike @@ -46,22 +62,22 @@ namespace MWMechanics bool mReadyToAttack, mAttack; bool mFollowTarget; bool mCombatMove; - bool mBackOffDoor; + + float mStrength; // this is actually make sense only in ranged combat + float mMinMaxAttackDuration[3][2]; // slash, thrust, chop has different durations + bool mMinMaxAttackDurationInitialised; bool mForceNoShortcut; ESM::Position mShortcutFailPos; - ESM::Position mLastPos; + Ogre::Vector3 mLastActorPos; MWMechanics::Movement mMovement; + int mTargetActorId; + Ogre::Vector3 mLastTargetPos; const MWWorld::CellStore* mCell; ObstacleCheck mObstacleCheck; - float mDoorCheckDuration; - // TODO: for some reason mDoors.searchViaHandle() returns - // null pointers, workaround by keeping an iterator - MWWorld::CellRefList::List::iterator mDoorIter; - MWWorld::CellRefList& mDoors; void buildNewPath(const MWWorld::Ptr& actor, const MWWorld::Ptr& target); }; diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index 07bb9726ef..3f5724077f 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -1,12 +1,13 @@ #include "aiescort.hpp" +#include + #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/timestamp.hpp" #include "steering.hpp" #include "movement.hpp" @@ -20,7 +21,7 @@ namespace MWMechanics { AiEscort::AiEscort(const std::string &actorId, int duration, float x, float y, float z) - : mActorId(actorId), mX(x), mY(y), mZ(z), mDuration(duration) + : mActorId(actorId), mX(x), mY(y), mZ(z), mRemainingDuration(duration) , mCellX(std::numeric_limits::max()) , mCellY(std::numeric_limits::max()) { @@ -29,32 +30,29 @@ namespace MWMechanics // The CS Help File states that if a duration is given, the AI package will run for that long // BUT if a location is givin, it "trumps" the duration so it will simply escort to that location. if(mX != 0 || mY != 0 || mZ != 0) - mDuration = 0; - - else - { - MWWorld::TimeStamp startTime = MWBase::Environment::get().getWorld()->getTimeStamp(); - mStartingSecond = ((startTime.getHour() - int(startTime.getHour())) * 100); - } + mRemainingDuration = 0; } AiEscort::AiEscort(const std::string &actorId, const std::string &cellId,int duration, float x, float y, float z) - : mActorId(actorId), mCellId(cellId), mX(x), mY(y), mZ(z), mDuration(duration) + : mActorId(actorId), mCellId(cellId), mX(x), mY(y), mZ(z), mRemainingDuration(duration) , mCellX(std::numeric_limits::max()) , mCellY(std::numeric_limits::max()) { mMaxDist = 470; // The CS Help File states that if a duration is given, the AI package will run for that long - // BUT if a location is givin, it "trumps" the duration so it will simply escort to that location. + // BUT if a location is given, it "trumps" the duration so it will simply escort to that location. if(mX != 0 || mY != 0 || mZ != 0) - mDuration = 0; + mRemainingDuration = 0; + } - else - { - MWWorld::TimeStamp startTime = MWBase::Environment::get().getWorld()->getTimeStamp(); - mStartingSecond = ((startTime.getHour() - int(startTime.getHour())) * 100); - } + AiEscort::AiEscort(const ESM::AiSequence::AiEscort *escort) + : mActorId(escort->mTargetId), mX(escort->mData.mX), mY(escort->mData.mY), mZ(escort->mData.mZ) + , mCellX(std::numeric_limits::max()) + , mCellY(std::numeric_limits::max()) + , mCellId(escort->mCellId) + , mRemainingDuration(escort->mRemainingDuration) + { } @@ -67,11 +65,10 @@ namespace MWMechanics { // If AiEscort has ran for as long or longer then the duration specified // and the duration is not infinite, the package is complete. - if(mDuration != 0) + if(mRemainingDuration != 0) { - MWWorld::TimeStamp current = MWBase::Environment::get().getWorld()->getTimeStamp(); - unsigned int currentSecond = ((current.getHour() - int(current.getHour())) * 100); - if(currentSecond - mStartingSecond >= mDuration) + mRemainingDuration -= duration; + if (duration <= 0) return true; } @@ -89,7 +86,7 @@ namespace MWMechanics if(distanceBetweenResult <= mMaxDist * mMaxDist) { - if(pathTo(actor,ESM::Pathgrid::Point(mX,mY,mZ),duration)) //Returns true on path complete + if(pathTo(actor,ESM::Pathgrid::Point(mX,mY,mZ),duration)) //Returns true on path complete return true; mMaxDist = 470; } @@ -108,5 +105,21 @@ namespace MWMechanics { return TypeIdEscort; } + + void AiEscort::writeState(ESM::AiSequence::AiSequence &sequence) const + { + std::auto_ptr escort(new ESM::AiSequence::AiEscort()); + escort->mData.mX = mX; + escort->mData.mY = mY; + escort->mData.mZ = mZ; + escort->mTargetId = mActorId; + escort->mRemainingDuration = mRemainingDuration; + escort->mCellId = mCellId; + + ESM::AiSequence::AiPackageContainer package; + package.mType = ESM::AiSequence::Ai_Escort; + package.mPackage = escort.release(); + sequence.mPackages.push_back(package); + } } diff --git a/apps/openmw/mwmechanics/aiescort.hpp b/apps/openmw/mwmechanics/aiescort.hpp index 3771417fa2..820df969f1 100644 --- a/apps/openmw/mwmechanics/aiescort.hpp +++ b/apps/openmw/mwmechanics/aiescort.hpp @@ -6,6 +6,14 @@ #include "pathfinding.hpp" +namespace ESM +{ +namespace AiSequence +{ + struct AiEscort; +} +} + namespace MWMechanics { /// \brief AI Package to have an NPC lead the player to a specific point @@ -21,12 +29,16 @@ namespace MWMechanics \implement AiEscortCell **/ AiEscort(const std::string &actorId,const std::string &cellId,int duration, float x, float y, float z); + AiEscort(const ESM::AiSequence::AiEscort* escort); + virtual AiEscort *clone() const; virtual bool execute (const MWWorld::Ptr& actor,float duration); virtual int getTypeId() const; + void writeState(ESM::AiSequence::AiSequence &sequence) const; + private: std::string mActorId; std::string mCellId; @@ -34,8 +46,7 @@ namespace MWMechanics float mY; float mZ; float mMaxDist; - unsigned int mStartingSecond; - unsigned int mDuration; + float mRemainingDuration; // In seconds PathFinder mPathFinder; int mCellX; diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index f1296a9493..5ab7e17305 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -1,5 +1,9 @@ #include "aifollow.hpp" + #include + +#include + #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwworld/class.hpp" @@ -12,16 +16,16 @@ #include "steering.hpp" MWMechanics::AiFollow::AiFollow(const std::string &actorId,float duration, float x, float y, float z) -: mAlwaysFollow(false), mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(""), AiPackage() +: mAlwaysFollow(false), mRemainingDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId("") { } MWMechanics::AiFollow::AiFollow(const std::string &actorId,const std::string &cellId,float duration, float x, float y, float z) -: mAlwaysFollow(false), mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(cellId), AiPackage() +: mAlwaysFollow(false), mRemainingDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(cellId) { } MWMechanics::AiFollow::AiFollow(const std::string &actorId) -: mAlwaysFollow(true), mDuration(0), mX(0), mY(0), mZ(0), mActorId(actorId), mCellId(""), AiPackage() +: mAlwaysFollow(true), mRemainingDuration(0), mX(0), mY(0), mZ(0), mActorId(actorId), mCellId("") { } @@ -35,8 +39,13 @@ bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration) if(!mAlwaysFollow) //Update if you only follow for a bit { - if(mTotalTime > mDuration && mDuration != 0) //Check if we've run out of time - return true; + //Check if we've run out of time + if (mRemainingDuration != 0) + { + mRemainingDuration -= duration; + if (duration <= 0) + return true; + } if((pos.pos[0]-mX)*(pos.pos[0]-mX) + (pos.pos[1]-mY)*(pos.pos[1]-mY) + @@ -55,7 +64,7 @@ bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration) } } - //Set the target desition from the actor + //Set the target destination from the actor ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < 100) //Stop when you get close @@ -83,7 +92,32 @@ MWMechanics::AiFollow *MWMechanics::AiFollow::clone() const return new AiFollow(*this); } - int MWMechanics::AiFollow::getTypeId() const +int MWMechanics::AiFollow::getTypeId() const { return TypeIdFollow; } + +void MWMechanics::AiFollow::writeState(ESM::AiSequence::AiSequence &sequence) const +{ + std::auto_ptr follow(new ESM::AiSequence::AiFollow()); + follow->mData.mX = mX; + follow->mData.mY = mY; + follow->mData.mZ = mZ; + follow->mTargetId = mActorId; + follow->mRemainingDuration = mRemainingDuration; + follow->mCellId = mCellId; + follow->mAlwaysFollow = mAlwaysFollow; + + ESM::AiSequence::AiPackageContainer package; + package.mType = ESM::AiSequence::Ai_Follow; + package.mPackage = follow.release(); + sequence.mPackages.push_back(package); +} + +MWMechanics::AiFollow::AiFollow(const ESM::AiSequence::AiFollow *follow) + : mAlwaysFollow(follow->mAlwaysFollow), mRemainingDuration(follow->mRemainingDuration) + , mX(follow->mData.mX), mY(follow->mData.mY), mZ(follow->mData.mZ) + , mActorId(follow->mTargetId), mCellId(follow->mCellId) +{ + +} diff --git a/apps/openmw/mwmechanics/aifollow.hpp b/apps/openmw/mwmechanics/aifollow.hpp index 10a381410a..e9587b36eb 100644 --- a/apps/openmw/mwmechanics/aifollow.hpp +++ b/apps/openmw/mwmechanics/aifollow.hpp @@ -6,13 +6,21 @@ #include "pathfinding.hpp" #include +namespace ESM +{ +namespace AiSequence +{ + struct AiFollow; +} +} + namespace MWMechanics { /// \brief AiPackage for an actor to follow another actor/the PC /** The AI will follow the target until a condition (time, or position) are set. Both can be disabled to cause the actor to follow the other indefinitely - **/ - class AiFollow : public AiPackage - { + **/ + class AiFollow : public AiPackage + { public: /// Follow Actor for duration or until you arrive at a world position AiFollow(const std::string &ActorId,float duration, float X, float Y, float Z); @@ -21,6 +29,8 @@ namespace MWMechanics /// Follow Actor indefinitively AiFollow(const std::string &ActorId); + AiFollow(const ESM::AiSequence::AiFollow* follow); + virtual AiFollow *clone() const; virtual bool execute (const MWWorld::Ptr& actor,float duration); @@ -30,16 +40,18 @@ namespace MWMechanics /// Returns the actor being followed std::string getFollowedActor(); + virtual void writeState (ESM::AiSequence::AiSequence& sequence) const; + private: /// This will make the actor always follow. /** Thus ignoring mDuration and mX,mY,mZ (used for summoned creatures). **/ bool mAlwaysFollow; - float mDuration; + float mRemainingDuration; // Seconds float mX; float mY; float mZ; std::string mActorId; - std::string mCellId; - }; -} -#endif + std::string mCellId; + }; +} +#endif diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 2144aa11d9..7790942b2f 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -26,8 +26,6 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Po //Update various Timers mTimer += duration; //Update timer mStuckTimer += duration; //Update stuck timer - mTotalTime += duration; //Update total time following - ESM::Position pos = actor.getRefData().getPosition(); //position of the actor diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 055958384a..983777c0ad 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -12,6 +12,14 @@ namespace MWWorld class Ptr; } +namespace ESM +{ + namespace AiSequence + { + class AiSequence; + } +} + namespace MWMechanics { /// \brief Base class for AI packages @@ -51,6 +59,8 @@ namespace MWMechanics /// Higher number is higher priority (0 being the lowest) virtual unsigned int getPriority() const {return 0;} + virtual void writeState (ESM::AiSequence::AiSequence& sequence) const {} + protected: /// Causes the actor to attempt to walk to the specified location /** \return If the actor has arrived at his destination **/ @@ -60,12 +70,11 @@ namespace MWMechanics ObstacleCheck mObstacleCheck; float mDoorCheckDuration; - float mTimer; - float mStuckTimer; - float mTotalTime; + float mTimer; + float mStuckTimer; + + MWWorld::Ptr mLastDoorChecked; //Used to ensure we don't try to CONSTANTLY open a door - MWWorld::Ptr mLastDoorChecked; //Used to ensure we don't try to CONSTANTLY open a door - ESM::Position mStuckPos; ESM::Pathgrid::Point mPrevDest; }; diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp index cd9d34e085..60f671c12a 100644 --- a/apps/openmw/mwmechanics/aipursue.cpp +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -1,5 +1,7 @@ #include "aipursue.hpp" +#include + #include "../mwbase/environment.hpp" #include "../mwworld/class.hpp" @@ -18,6 +20,12 @@ AiPursue::AiPursue(const MWWorld::Ptr& actor) : mTargetActorId(actor.getClass().getCreatureStats(actor).getActorId()) { } + +AiPursue::AiPursue(const ESM::AiSequence::AiPursue *pursue) + : mTargetActorId(pursue->mTargetActorId) +{ +} + AiPursue *MWMechanics::AiPursue::clone() const { return new AiPursue(*this); @@ -57,4 +65,15 @@ MWWorld::Ptr AiPursue::getTarget() const return MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); } +void AiPursue::writeState(ESM::AiSequence::AiSequence &sequence) const +{ + std::auto_ptr pursue(new ESM::AiSequence::AiPursue()); + pursue->mTargetActorId = mTargetActorId; + + ESM::AiSequence::AiPackageContainer package; + package.mType = ESM::AiSequence::Ai_Pursue; + package.mPackage = pursue.release(); + sequence.mPackages.push_back(package); +} + } // namespace MWMechanics diff --git a/apps/openmw/mwmechanics/aipursue.hpp b/apps/openmw/mwmechanics/aipursue.hpp index 27affc9acd..18a22b6763 100644 --- a/apps/openmw/mwmechanics/aipursue.hpp +++ b/apps/openmw/mwmechanics/aipursue.hpp @@ -7,6 +7,14 @@ #include "pathfinding.hpp" +namespace ESM +{ +namespace AiSequence +{ + struct AiPursue; +} +} + namespace MWMechanics { /// \brief Makes the actor very closely follow the actor @@ -20,12 +28,16 @@ namespace MWMechanics /** \param actor Actor to pursue **/ AiPursue(const MWWorld::Ptr& actor); + AiPursue(const ESM::AiSequence::AiPursue* pursue); + virtual AiPursue *clone() const; virtual bool execute (const MWWorld::Ptr& actor,float duration); virtual int getTypeId() const; MWWorld::Ptr getTarget() const; + virtual void writeState (ESM::AiSequence::AiSequence& sequence) const; + private: int mTargetActorId; // The actor to pursue diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 8a6a69b278..3aeeee65a4 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -11,6 +11,8 @@ #include "aicombat.hpp" #include "aipursue.hpp" +#include + #include "../mwworld/class.hpp" #include "creaturestats.hpp" #include "npcstats.hpp" @@ -86,7 +88,7 @@ bool AiSequence::canAddTarget(const ESM::Position& actorPos, float distToTarget) else { // add new target only if current target (player) is farther - ESM::Position &targetPos = combat->getTarget().getRefData().getPosition(); + const ESM::Position &targetPos = combat->getTarget().getRefData().getPosition(); float distToCurrTarget = (Ogre::Vector3(targetPos.pos) - Ogre::Vector3(actorPos.pos)).length(); return (distToCurrTarget > distToTarget); @@ -151,7 +153,7 @@ void AiSequence::execute (const MWWorld::Ptr& actor,float duration) } else { - ESM::Position &targetPos = target.getRefData().getPosition(); + const ESM::Position &targetPos = target.getRefData().getPosition(); float distTo = (Ogre::Vector3(targetPos.pos) - vActorPos).length(); if (distTo < nearestDist) @@ -219,6 +221,11 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor) { return; // target is already pursued } + if((*iter)->getTypeId() == AiPackage::TypeIdCombat && package.getTypeId() == AiPackage::TypeIdCombat + && static_cast(*iter)->getTarget() == static_cast(&package)->getTarget()) + { + return; // already in combat with this actor + } else if ((*iter)->getTypeId() == AiPackage::TypeIdWander) static_cast(*iter)->setReturnPosition(Ogre::Vector3(actor.getRefData().getPosition().pos)); } @@ -258,7 +265,8 @@ void AiSequence::fill(const ESM::AIPackageList &list) if (it->mType == ESM::AI_Wander) { ESM::AIWander data = it->mWander; - std::vector idles; + std::vector idles; + idles.reserve(8); for (int i=0; i<8; ++i) idles.push_back(data.mIdle[i]); package = new MWMechanics::AiWander(data.mDistance, data.mDuration, data.mTimeOfDay, idles, data.mShouldRepeat); @@ -287,4 +295,77 @@ void AiSequence::fill(const ESM::AIPackageList &list) } } +void AiSequence::writeState(ESM::AiSequence::AiSequence &sequence) const +{ + for (std::list::const_iterator iter (mPackages.begin()); iter!=mPackages.end(); ++iter) + { + (*iter)->writeState(sequence); + } +} + +void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence) +{ + if (!sequence.mPackages.empty()) + clear(); + + for (std::vector::const_iterator it = sequence.mPackages.begin(); + it != sequence.mPackages.end(); ++it) + { + switch (it->mType) + { + case ESM::AiSequence::Ai_Wander: + { + MWMechanics::AiWander* wander = new AiWander( + dynamic_cast(it->mPackage)); + mPackages.push_back(wander); + break; + } + case ESM::AiSequence::Ai_Travel: + { + MWMechanics::AiTravel* travel = new AiTravel( + dynamic_cast(it->mPackage)); + mPackages.push_back(travel); + break; + } + case ESM::AiSequence::Ai_Escort: + { + MWMechanics::AiEscort* escort = new AiEscort( + dynamic_cast(it->mPackage)); + mPackages.push_back(escort); + break; + } + case ESM::AiSequence::Ai_Follow: + { + MWMechanics::AiFollow* follow = new AiFollow( + dynamic_cast(it->mPackage)); + mPackages.push_back(follow); + break; + } + case ESM::AiSequence::Ai_Activate: + { + MWMechanics::AiActivate* activate = new AiActivate( + dynamic_cast(it->mPackage)); + mPackages.push_back(activate); + break; + } + case ESM::AiSequence::Ai_Combat: + { + MWMechanics::AiCombat* combat = new AiCombat( + dynamic_cast(it->mPackage)); + mPackages.push_back(combat); + break; + } + case ESM::AiSequence::Ai_Pursue: + { + MWMechanics::AiPursue* pursue = new AiPursue( + dynamic_cast(it->mPackage)); + mPackages.push_back(pursue); + break; + } + default: + break; + } + } +} + } // namespace MWMechanics diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index 41a280da80..b789d33cd0 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -10,6 +10,14 @@ namespace MWWorld class Ptr; } +namespace ESM +{ + namespace AiSequence + { + class AiSequence; + } +} + namespace MWMechanics { class AiPackage; @@ -90,6 +98,9 @@ namespace MWMechanics /** Typically used for loading from the ESM \see ESM::AIPackageList **/ void fill (const ESM::AIPackageList& list); + + void writeState (ESM::AiSequence::AiSequence& sequence) const; + void readState (const ESM::AiSequence::AiSequence& sequence); }; } diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index 024656b38a..db137037d5 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -1,5 +1,7 @@ #include "aitravel.hpp" +#include + #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" @@ -19,6 +21,15 @@ namespace MWMechanics { } + AiTravel::AiTravel(const ESM::AiSequence::AiTravel *travel) + : mX(travel->mData.mX), mY(travel->mData.mY), mZ(travel->mData.mZ) + , mPathFinder() + , mCellX(std::numeric_limits::max()) + , mCellY(std::numeric_limits::max()) + { + + } + AiTravel *MWMechanics::AiTravel::clone() const { return new AiTravel(*this); @@ -92,5 +103,18 @@ namespace MWMechanics { return TypeIdTravel; } + + void AiTravel::writeState(ESM::AiSequence::AiSequence &sequence) const + { + std::auto_ptr travel(new ESM::AiSequence::AiTravel()); + travel->mData.mX = mX; + travel->mData.mY = mY; + travel->mData.mZ = mZ; + + ESM::AiSequence::AiPackageContainer package; + package.mType = ESM::AiSequence::Ai_Travel; + package.mPackage = travel.release(); + sequence.mPackages.push_back(package); + } } diff --git a/apps/openmw/mwmechanics/aitravel.hpp b/apps/openmw/mwmechanics/aitravel.hpp index ea7f1dc329..91ee302534 100644 --- a/apps/openmw/mwmechanics/aitravel.hpp +++ b/apps/openmw/mwmechanics/aitravel.hpp @@ -5,14 +5,26 @@ #include "pathfinding.hpp" +namespace ESM +{ +namespace AiSequence +{ + struct AiTravel; +} +} + namespace MWMechanics -{ +{ /// \brief Causes the AI to travel to the specified point class AiTravel : public AiPackage { - public: + public: /// Default constructor AiTravel(float x, float y, float z); + AiTravel(const ESM::AiSequence::AiTravel* travel); + + void writeState(ESM::AiSequence::AiSequence &sequence) const; + virtual AiTravel *clone() const; virtual bool execute (const MWWorld::Ptr& actor,float duration); diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 1c870bda47..b97554e2a6 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -3,6 +3,8 @@ #include #include +#include + #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" @@ -21,31 +23,29 @@ namespace MWMechanics static const int COUNT_BEFORE_RESET = 200; // TODO: maybe no longer needed static const float DOOR_CHECK_INTERVAL = 1.5f; - AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat): + AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat): mDistance(distance), mDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle), mRepeat(repeat) - , mCellX(std::numeric_limits::max()) - , mCellY(std::numeric_limits::max()) - , mXCell(0) - , mYCell(0) - , mCell(NULL) - , mStuckCount(0) // TODO: maybe no longer needed - , mDoorCheckDuration(0) - , mTrimCurrentNode(false) - , mReaction(0) - , mGreetDistanceMultiplier(0) - , mGreetDistanceReset(0) - , mChance(0) - , mRotate(false) - , mTargetAngle(0) - , mSaidGreeting(false) - , mHasReturnPosition(false) - , mReturnPosition(0,0,0) { - for(unsigned short counter = 0; counter < mIdle.size(); counter++) - { - if(mIdle[counter] >= 127 || mIdle[counter] < 0) - mIdle[counter] = 0; - } + mIdle.resize(8, 0); + init(); + } + + void AiWander::init() + { + mCellX = std::numeric_limits::max(); + mCellY = std::numeric_limits::max(); + mXCell = 0; + mYCell = 0; + mCell = NULL; + mStuckCount = 0;// TODO: maybe no longer needed + mDoorCheckDuration = 0; + mTrimCurrentNode = false; + mReaction = 0; + mRotate = false; + mTargetAngle = 0; + mSaidGreeting = false; + mHasReturnPosition = false; + mReturnPosition = Ogre::Vector3(0,0,0); if(mDistance < 0) mDistance = 0; @@ -56,15 +56,6 @@ namespace MWMechanics mStartTime = MWBase::Environment::get().getWorld()->getTimeStamp(); mPlayedIdle = 0; - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - mIdleChanceMultiplier = - store.get().find("fIdleChanceMultiplier")->getFloat(); - - mGreetDistanceMultiplier = - store.get().find("iGreetDistanceMultiplier")->getInt(); - mGreetDistanceReset = - store.get().find("fGreetDistanceReset")->getFloat(); - mChance = store.get().find("fVoiceIdleOdds")->getFloat(); mStoredAvailableNodes = false; mChooseAction = true; @@ -163,6 +154,17 @@ namespace MWMechanics } } + // Are we there yet? + if(mWalking && + mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2])) + { + stopWalking(actor); + mMoveNow = false; + mWalking = false; + mChooseAction = true; + mHasReturnPosition = false; + } + if(mWalking) // have not yet reached the destination { // turn towards the next point in mPath @@ -219,11 +221,12 @@ namespace MWMechanics } mReaction += duration; - if(mReaction > 0.25f) // FIXME: hard coded constant + if(mReaction < 0.25f) // FIXME: hard coded constant { - mReaction = 0; return false; } + else + mReaction = 0; // NOTE: everything below get updated every 0.25 seconds @@ -394,7 +397,10 @@ namespace MWMechanics MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); // Don't bother if the player is out of hearing range - if (roll < mChance && Ogre::Vector3(player.getRefData().getPosition().pos).squaredDistance(Ogre::Vector3(pos.pos)) < 1500*1500) + static float fVoiceIdleOdds = MWBase::Environment::get().getWorld()->getStore() + .get().find("fVoiceIdleOdds")->getFloat(); + + if (roll < fVoiceIdleOdds && Ogre::Vector3(player.getRefData().getPosition().pos).squaredDistance(Ogre::Vector3(pos.pos)) < 1500*1500) MWBase::Environment::get().getDialogueManager()->say(actor, "idle"); } } @@ -406,7 +412,10 @@ namespace MWMechanics // Play a random voice greeting if the player gets too close int hello = cStats.getAiSetting(CreatureStats::AI_Hello).getModified(); float helloDistance = hello; - helloDistance *= mGreetDistanceMultiplier; + static int iGreetDistanceMultiplier =MWBase::Environment::get().getWorld()->getStore() + .get().find("iGreetDistanceMultiplier")->getInt(); + + helloDistance *= iGreetDistanceMultiplier; MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); Ogre::Vector3 playerPos(player.getRefData().getPosition().pos); @@ -455,7 +464,10 @@ namespace MWMechanics } else { - if (playerDistSqr >= mGreetDistanceReset*mGreetDistanceReset * mGreetDistanceMultiplier*mGreetDistanceMultiplier) + static float fGreetDistanceReset = MWBase::Environment::get().getWorld()->getStore() + .get().find("fGreetDistanceReset")->getFloat(); + + if (playerDistSqr >= fGreetDistanceReset*fGreetDistanceReset * iGreetDistanceMultiplier*iGreetDistanceMultiplier) mSaidGreeting = false; } @@ -524,17 +536,6 @@ namespace MWMechanics } } - // Are we there yet? - if(mWalking && - mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2])) - { - stopWalking(actor); - mMoveNow = false; - mWalking = false; - mChooseAction = true; - mHasReturnPosition = false; - } - return false; // AiWander package not yet completed } @@ -632,8 +633,11 @@ namespace MWMechanics for(unsigned int counter = 0; counter < mIdle.size(); counter++) { - unsigned short idleChance = mIdleChanceMultiplier * mIdle[counter]; - unsigned short randSelect = (int)(rand() / ((double)RAND_MAX + 1) * int(100 / mIdleChanceMultiplier)); + static float fIdleChanceMultiplier = MWBase::Environment::get().getWorld()->getStore() + .get().find("fIdleChanceMultiplier")->getFloat(); + + unsigned short idleChance = fIdleChanceMultiplier * mIdle[counter]; + unsigned short randSelect = (int)(rand() / ((double)RAND_MAX + 1) * int(100 / fIdleChanceMultiplier)); if(randSelect < idleChance && randSelect > idleRoll) { mPlayedIdle = counter+2; @@ -641,5 +645,37 @@ namespace MWMechanics } } } + + void AiWander::writeState(ESM::AiSequence::AiSequence &sequence) const + { + std::auto_ptr wander(new ESM::AiSequence::AiWander()); + wander->mData.mDistance = mDistance; + wander->mData.mDuration = mDuration; + wander->mData.mTimeOfDay = mTimeOfDay; + wander->mStartTime = mStartTime.toEsm(); + assert (mIdle.size() == 8); + for (int i=0; i<8; ++i) + wander->mData.mIdle[i] = mIdle[i]; + wander->mData.mShouldRepeat = mRepeat; + + ESM::AiSequence::AiPackageContainer package; + package.mType = ESM::AiSequence::Ai_Wander; + package.mPackage = wander.release(); + sequence.mPackages.push_back(package); + } + + AiWander::AiWander (const ESM::AiSequence::AiWander* wander) + { + init(); + + mDistance = wander->mData.mDistance; + mDuration = wander->mData.mDuration; + mStartTime = MWWorld::TimeStamp(wander->mStartTime); + mTimeOfDay = wander->mData.mTimeOfDay; + for (int i=0; i<8; ++i) + mIdle.push_back(wander->mData.mIdle[i]); + + mRepeat = wander->mData.mShouldRepeat; + } } diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 6481b2a01c..7abd19e27a 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -12,6 +12,14 @@ #include "../mwworld/timestamp.hpp" +namespace ESM +{ + namespace AiSequence + { + struct AiWander; + } +} + namespace MWMechanics { /// \brief Causes the Actor to wander within a specified range @@ -24,7 +32,11 @@ namespace MWMechanics \param timeOfDay Start time of the package, if it has a duration. Currently unimplemented \param idle Chances of each idle to play (9 in total) \param repeat Repeat wander or not **/ - AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat); + AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat); + + AiWander (const ESM::AiSequence::AiWander* wander); + + void init(); virtual AiPackage *clone() const; @@ -36,6 +48,8 @@ namespace MWMechanics /** In case another AI package moved the actor elsewhere **/ void setReturnPosition (const Ogre::Vector3& position); + virtual void writeState(ESM::AiSequence::AiSequence &sequence) const; + private: void stopWalking(const MWWorld::Ptr& actor); void playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect); @@ -45,13 +59,10 @@ namespace MWMechanics int mDistance; // how far the actor can wander from the spawn point int mDuration; int mTimeOfDay; - std::vector mIdle; + std::vector mIdle; bool mRepeat; bool mSaidGreeting; - int mGreetDistanceMultiplier; - float mGreetDistanceReset; - float mChance; bool mHasReturnPosition; // NOTE: Could be removed if mReturnPosition was initialized to actor position, // if we had the actor in the AiWander constructor... @@ -74,7 +85,6 @@ namespace MWMechanics bool mMoveNow; bool mWalking; - float mIdleChanceMultiplier; unsigned short mPlayedIdle; MWWorld::TimeStamp mStartTime; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index db4e599291..2c5d68ceb7 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -20,6 +20,7 @@ #include "character.hpp" #include +#include #include "movement.hpp" #include "npcstats.hpp" @@ -54,6 +55,43 @@ std::string getBestAttack (const ESM::Weapon* weapon) return "thrust"; } +// Converts a movement Run state to its equivalent Walk state. +MWMechanics::CharacterState runStateToWalkState (MWMechanics::CharacterState state) +{ + using namespace MWMechanics; + CharacterState ret = state; + switch (state) + { + case CharState_RunForward: + ret = CharState_WalkForward; + break; + case CharState_RunBack: + ret = CharState_WalkBack; + break; + case CharState_RunLeft: + ret = CharState_WalkLeft; + break; + case CharState_RunRight: + ret = CharState_WalkRight; + break; + case CharState_SwimRunForward: + ret = CharState_SwimWalkForward; + break; + case CharState_SwimRunBack: + ret = CharState_SwimWalkBack; + break; + case CharState_SwimRunLeft: + ret = CharState_SwimWalkLeft; + break; + case CharState_SwimRunRight: + ret = CharState_SwimWalkRight; + break; + default: + break; + } + return ret; +} + } namespace MWMechanics @@ -234,6 +272,8 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat 1.0f, "start", "stop", 0.0f, ~0ul); } + updateIdleStormState(); + if(force && mJumpState != JumpState_None) { std::string jump; @@ -296,7 +336,17 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat { std::string::size_type swimpos = movement.find("swim"); if(swimpos == std::string::npos) - movement.clear(); + { + std::string::size_type runpos = movement.find("run"); + if (runpos != std::string::npos) + { + movement.replace(runpos, runpos+3, "walk"); + if (!mAnimation->hasAnimation(movement)) + movement.clear(); + } + else + movement.clear(); + } else { movegroup = MWRender::Animation::Group_LowerBody; @@ -320,7 +370,18 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat bool isrunning = mPtr.getClass().getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run); - if(mMovementSpeed > 0.0f && (vel=mAnimation->getVelocity(mCurrentMovement)) > 1.0f) + // For non-flying creatures, MW uses the Walk animation to calculate the animation velocity + // even if we are running. This must be replicated, otherwise the observed speed would differ drastically. + std::string anim = mCurrentMovement; + if (mPtr.getClass().getTypeName() == typeid(ESM::Creature).name() + && !(mPtr.get()->mBase->mFlags & ESM::Creature::Flies)) + { + CharacterState walkState = runStateToWalkState(mMovementState); + const StateInfo *stateinfo = std::find_if(sMovementList, sMovementListEnd, FindCharState(walkState)); + anim = stateinfo->groupname; + } + + if(mMovementSpeed > 0.0f && (vel=mAnimation->getVelocity(anim)) > 1.0f) { mMovementAnimVelocity = vel; speedmult = mMovementSpeed / vel; @@ -409,13 +470,6 @@ MWWorld::ContainerStoreIterator getActiveWeapon(CreatureStats &stats, MWWorld::I void CharacterController::playDeath(float startpoint, CharacterState death) { - if (mPtr == MWBase::Environment::get().getWorld()->getPlayerPtr()) - { - // The first-person animations do not include death, so we need to - // force-switch to third person before playing the death animation. - MWBase::Environment::get().getWorld()->useDeathCamera(); - } - switch (death) { case CharState_SwimDeath: @@ -435,9 +489,23 @@ void CharacterController::playDeath(float startpoint, CharacterState death) mPtr.getClass().getCreatureStats(mPtr).setDeathAnimation(mDeathState - CharState_Death1); // For dead actors, refreshCurrentAnims is no longer called, so we need to disable the movement state manually. + // Note that these animations wouldn't actually be visible (due to the Death animation's priority being higher). + // However, they could still trigger text keys, such as Hit events, or sounds. mMovementState = CharState_None; mAnimation->disable(mCurrentMovement); mCurrentMovement = ""; + mUpperBodyState = UpperCharState_Nothing; + mAnimation->disable(mCurrentWeapon); + mCurrentWeapon = ""; + mHitState = CharState_None; + mAnimation->disable(mCurrentHit); + mCurrentHit = ""; + mIdleState = CharState_None; + mAnimation->disable(mCurrentIdle); + mCurrentIdle = ""; + mJumpState = JumpState_None; + mAnimation->disable(mCurrentJump); + mCurrentJump = ""; mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::Group_All, false, 1.0f, "start", "stop", startpoint, 0); @@ -445,6 +513,13 @@ void CharacterController::playDeath(float startpoint, CharacterState death) void CharacterController::playRandomDeath(float startpoint) { + if (mPtr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + { + // The first-person animations do not include death, so we need to + // force-switch to third person before playing the death animation. + MWBase::Environment::get().getWorld()->useDeathCamera(); + } + if(MWBase::Environment::get().getWorld()->isSwimming(mPtr) && mAnimation->hasAnimation("swimdeath")) { mDeathState = CharState_SwimDeath; @@ -495,11 +570,12 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim if (cls.hasInventoryStore(mPtr)) { getActiveWeapon(cls.getCreatureStats(mPtr), cls.getInventoryStore(mPtr), &mWeaponType); - if(mWeaponType != WeapType_None) + if(mWeaponType != WeapType_None && mWeaponType != WeapType_Spell && mWeaponType != WeapType_HandToHand) { getWeaponGroup(mWeaponType, mCurrentWeapon); mUpperBodyState = UpperCharState_WeapEquiped; mAnimation->showWeapons(true); + mAnimation->setWeaponGroup(mCurrentWeapon); } mAnimation->showCarriedLeft(mWeaponType != WeapType_Spell && mWeaponType != WeapType_HandToHand); } @@ -508,8 +584,8 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim mIdleState = CharState_Idle; else { - /* FIXME: Get the actual death state used. */ - mDeathState = CharState_Death1; + int deathindex = mPtr.getClass().getCreatureStats(mPtr).getDeathAnimation(); + playDeath(1.0f, CharacterState(CharState_Death1 + deathindex)); } } else @@ -521,12 +597,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim } - if(mDeathState != CharState_None) - { - int deathindex = mPtr.getClass().getCreatureStats(mPtr).getDeathAnimation(); - playDeath(1.0f, CharacterState(CharState_Death1 + deathindex)); - } - else + if(mDeathState == CharState_None) refreshCurrentAnims(mIdleState, mMovementState, true); } @@ -540,6 +611,45 @@ void CharacterController::updatePtr(const MWWorld::Ptr &ptr) mPtr = ptr; } +void CharacterController::updateIdleStormState() +{ + bool inStormDirection = false; + if (MWBase::Environment::get().getWorld()->isInStorm()) + { + Ogre::Vector3 stormDirection = MWBase::Environment::get().getWorld()->getStormDirection(); + Ogre::Vector3 characterDirection = mPtr.getRefData().getBaseNode()->getOrientation().yAxis(); + inStormDirection = stormDirection.angleBetween(characterDirection) > Ogre::Degree(120); + } + if (inStormDirection && mUpperBodyState == UpperCharState_Nothing && mAnimation->hasAnimation("idlestorm")) + { + float complete = 0; + mAnimation->getInfo("idlestorm", &complete); + + if (complete == 0) + mAnimation->play("idlestorm", Priority_Torch, MWRender::Animation::Group_RightArm, false, + 1.0f, "start", "loop start", 0.0f, 0); + else if (complete == 1) + mAnimation->play("idlestorm", Priority_Torch, MWRender::Animation::Group_RightArm, false, + 1.0f, "loop start", "loop stop", 0.0f, ~0ul); + } + else + { + if (mUpperBodyState == UpperCharState_Nothing) + { + if (mAnimation->isPlaying("idlestorm")) + { + if (mAnimation->getCurrentTime("idlestorm") < mAnimation->getTextKeyTime("idlestorm: loop stop")) + { + mAnimation->play("idlestorm", Priority_Torch, MWRender::Animation::Group_RightArm, true, + 1.0f, "loop stop", "stop", 0.0f, 0); + } + } + } + else + mAnimation->disable("idlestorm"); + } +} + bool CharacterController::updateCreatureState() { const MWWorld::Class &cls = mPtr.getClass(); @@ -832,6 +942,8 @@ bool CharacterController::updateWeaponState() MWRender::Animation::Group_UpperBody, false, weapSpeed, mAttackType+" max attack", mAttackType+" min hit", 1.0f-complete, 0); + + complete = 0.f; mUpperBodyState = UpperCharState_MaxAttackToMinHit; } else if (mHitState == CharState_KnockDown) @@ -975,7 +1087,7 @@ bool CharacterController::updateWeaponState() } //if playing combat animation and lowerbody is not busy switch to whole body animation - if((weaptype != WeapType_None || UpperCharState_UnEquipingWeap) && animPlaying) + if((weaptype != WeapType_None || mUpperBodyState == UpperCharState_UnEquipingWeap) && animPlaying) { if( mMovementState != CharState_None || mJumpState != JumpState_None || @@ -1293,6 +1405,9 @@ void CharacterController::update(float duration) if (mMovementAnimVelocity == 0) world->queueMovement(mPtr, vec); } + else + // We must always queue movement, even if there is none, to apply gravity. + world->queueMovement(mPtr, Ogre::Vector3(0.0f)); movement = vec; cls.getMovementSettings(mPtr).mPosition[0] = cls.getMovementSettings(mPtr).mPosition[1] = cls.getMovementSettings(mPtr).mPosition[2] = 0; @@ -1334,6 +1449,8 @@ void CharacterController::update(float duration) if(mMovementAnimVelocity > 0) world->queueMovement(mPtr, moved); } + else if (mAnimation) + mAnimation->updateEffects(duration); mSkipAnim = false; } diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 5cefe13bc8..59c20db8fe 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -178,6 +178,7 @@ class CharacterController bool updateWeaponState(); bool updateCreatureState(); + void updateIdleStormState(); void updateVisibility(); diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index be62e8315f..6feb5879b0 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -231,9 +231,13 @@ namespace MWMechanics if (damage > 0) MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition); - float fProjectileThrownStoreChance = gmst.find("fProjectileThrownStoreChance")->getFloat(); - if ((::rand()/(RAND_MAX+1.0)) < fProjectileThrownStoreChance/100.f) - victim.getClass().getContainerStore(victim).add(projectile, 1, victim); + // Arrows shot at enemies have a chance to turn up in their inventory + if (victim != MWBase::Environment::get().getWorld()->getPlayerPtr()) + { + float fProjectileThrownStoreChance = gmst.find("fProjectileThrownStoreChance")->getFloat(); + if ((::rand()/(RAND_MAX+1.0)) < fProjectileThrownStoreChance/100.f) + victim.getClass().getContainerStore(victim).add(projectile, 1, victim); + } victim.getClass().onHit(victim, damage, true, projectile, attacker, true); } diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 7fd26c25c9..af35a109a6 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -14,7 +14,7 @@ namespace MWMechanics int CreatureStats::sActorId = 0; CreatureStats::CreatureStats() - : mLevel (0), mDead (false), mDied (false), mFriendlyHits (0), + : mLevel (0), mDead (false), mDied (false), mMurdered(false), mFriendlyHits (0), mTalkedTo (false), mAlarmed (false), mAttacked (false), mHostile (false), mAttackingOrSpell(false), @@ -245,6 +245,21 @@ namespace MWMechanics mDied = false; } + bool CreatureStats::hasBeenMurdered() const + { + return mMurdered; + } + + void CreatureStats::notifyMurder() + { + mMurdered = true; + } + + void CreatureStats::clearHasBeenMurdered() + { + mMurdered = false; + } + void CreatureStats::resurrect() { if (mDead) @@ -479,6 +494,7 @@ namespace MWMechanics state.mDead = mDead; state.mDied = mDied; + state.mMurdered = mMurdered; state.mFriendlyHits = mFriendlyHits; state.mTalkedTo = mTalkedTo; state.mAlarmed = mAlarmed; @@ -503,6 +519,14 @@ namespace MWMechanics mSpells.writeState(state.mSpells); mActiveSpells.writeState(state.mActiveSpells); + mAiSequence.writeState(state.mAiSequence); + + state.mSummonedCreatureMap = mSummonedCreatures; + state.mSummonGraveyard = mSummonGraveyard; + + state.mHasAiSettings = true; + for (int i=0; i<4; ++i) + mAiSettings[i].writeState (state.mAiSettings[i]); } void CreatureStats::readState (const ESM::CreatureStats& state) @@ -519,6 +543,7 @@ namespace MWMechanics mDead = state.mDead; mDied = state.mDied; + mMurdered = state.mMurdered; mFriendlyHits = state.mFriendlyHits; mTalkedTo = state.mTalkedTo; mAlarmed = state.mAlarmed; @@ -543,6 +568,14 @@ namespace MWMechanics mSpells.readState(state.mSpells); mActiveSpells.readState(state.mActiveSpells); + mAiSequence.readState(state.mAiSequence); + + mSummonedCreatures = state.mSummonedCreatureMap; + mSummonGraveyard = state.mSummonGraveyard; + + if (state.mHasAiSettings) + for (int i=0; i<4; ++i) + mAiSettings[i].readState(state.mAiSettings[i]); } void CreatureStats::setLastRestockTime(MWWorld::TimeStamp tradeTime) @@ -603,4 +636,14 @@ namespace MWMechanics { mDeathAnimation = index; } + + std::map& CreatureStats::getSummonedCreatureMap() + { + return mSummonedCreatures; + } + + std::vector& CreatureStats::getSummonedCreatureGraveyard() + { + return mSummonGraveyard; + } } diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index 8b2398dfbd..b365a0b89c 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -35,6 +35,7 @@ namespace MWMechanics AiSequence mAiSequence; bool mDead; bool mDied; + bool mMurdered; int mFriendlyHits; bool mTalkedTo; bool mAlarmed; @@ -67,6 +68,12 @@ namespace MWMechanics // The index of the death animation that was played unsigned char mDeathAnimation; + // + std::map mSummonedCreatures; + // Contains ActorIds of summoned creatures with an expired lifetime that have not been deleted yet. + // This may be necessary when the creature is in an inactive cell. + std::vector mSummonGraveyard; + protected: // These two are only set by NpcStats, but they are declared in CreatureStats to prevent using virtual methods. bool mIsWerewolf; @@ -164,6 +171,12 @@ namespace MWMechanics void clearHasDied(); + bool hasBeenMurdered() const; + + void clearHasBeenMurdered(); + + void notifyMurder(); + void resurrect(); bool hasCommonDisease() const; @@ -208,6 +221,9 @@ namespace MWMechanics void setBlock(bool value); bool getBlock() const; + std::map& getSummonedCreatureMap(); + std::vector& getSummonedCreatureGraveyard(); + enum Flag { Flag_ForceRun = 1, @@ -233,13 +249,6 @@ namespace MWMechanics // TODO: Put it somewhere else? std::set mBoundItems; - // TODO: store in savegame - // TODO: encapsulate? - // - std::map mSummonedCreatures; - // Contains summoned creatures with an expired lifetime that have not been deleted yet. - std::vector mSummonGraveyard; - void writeState (ESM::CreatureStats& state) const; void readState (const ESM::CreatureStats& state); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 900ea72cad..6e515142de 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -14,6 +14,7 @@ #include "../mwworld/player.hpp" #include "../mwmechanics/aicombat.hpp" +#include "../mwmechanics/aipursue.hpp" #include @@ -500,7 +501,8 @@ namespace MWMechanics { if (!playerStats.getExpelled(npcFaction)) { - reaction = playerStats.getFactionReputation(npcFaction); + // faction reaction towards itself. yes, that exists + reaction = MWBase::Environment::get().getDialogueManager()->getFactionReaction(npcFaction, npcFaction); rank = playerStats.getFactionRanks().find(npcFaction)->second; } @@ -827,6 +829,8 @@ namespace MWMechanics // NOTE: int arg can be from itemTaken() so DON'T modify it, since it is // passed to reportCrime later on in this function. + // NOTE: victim may be empty + // Only player can commit crime if (player.getRefData().getHandle() != "player") return false; @@ -834,7 +838,7 @@ namespace MWMechanics const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); // What amount of alarm did this crime generate? - int alarm; + int alarm = 0; if (type == OT_Trespassing || type == OT_SleepingInOwnedBed) alarm = esmStore.get().find("iAlarmTresspass")->getInt(); else if (type == OT_Pickpocket) @@ -845,43 +849,44 @@ namespace MWMechanics alarm = esmStore.get().find("iAlarmKilling")->getInt(); else if (type == OT_Theft) alarm = esmStore.get().find("iAlarmStealing")->getInt(); - else - return false; - // Innocent until proven guilty bool reported = false; // Find all the actors within the alarm radius std::vector neighbors; - mActors.getObjectsInRange(Ogre::Vector3(player.getRefData().getPosition().pos), - esmStore.get().find("fAlarmRadius")->getInt(), neighbors); - int id = MWBase::Environment::get().getWorld()->getPlayer().getNewCrimeId(); + Ogre::Vector3 from = Ogre::Vector3(player.getRefData().getPosition().pos); + float radius = esmStore.get().find("fAlarmRadius")->getFloat(); - // Find actors who witnessed the crime + mActors.getObjectsInRange(from, radius, neighbors); + + // victim should be considered even beyond alarm radius + if (!victim.isEmpty() && from.squaredDistance(Ogre::Vector3(victim.getRefData().getPosition().pos)) > radius*radius) + neighbors.push_back(victim); + + bool victimAware = false; + + // Find actors who directly witnessed the crime for (std::vector::iterator it = neighbors.begin(); it != neighbors.end(); ++it) { - if (*it == player) continue; // not the player + if (*it == player) + continue; // skip player + if (it->getClass().getCreatureStats(*it).isDead()) + continue; // Was the crime seen? - if (MWBase::Environment::get().getWorld()->getLOS(player, *it) && awarenessCheck(player, *it) ) + if ((MWBase::Environment::get().getWorld()->getLOS(player, *it) && awarenessCheck(player, *it) ) + // Murder crime can be reported even if no one saw it (hearing is enough, I guess). + // TODO: Add mod support for stealth executions! + || (type == OT_Murder && *it != victim)) { - // TODO: Add more messages + if (*it == victim) + victimAware = true; + + // TODO: are there other messages? if (type == OT_Theft) MWBase::Environment::get().getDialogueManager()->say(*it, "thief"); - if (*it == victim) - { - // Self-defense - // The victim is aware of the criminal/assailant. If being assaulted, fight back now - // (regardless of whether the assault is reported or not) - // This applies to both NPCs and creatures - - // ... except if this is a guard: then the player is given a chance to pay a fine / go to jail instead - if (type == OT_Assault && !it->getClass().isClass(*it, "guard")) - MWBase::Environment::get().getMechanicsManager()->startCombat(victim, player); - } - // Crime reporting only applies to NPCs if (!it->getClass().isNpc()) continue; @@ -890,35 +895,25 @@ namespace MWMechanics if (it->getClass().getCreatureStats(*it).getAiSetting(CreatureStats::AI_Alarm).getBase() >= alarm) { reported = true; - - // Tell everyone, including yourself - for (std::vector::iterator it1 = neighbors.begin(); it1 != neighbors.end(); ++it1) - { - if ( *it1 == player - || !it1->getClass().isNpc()) continue; // not the player and is an NPC - - // Will other witnesses paticipate in crime - if ( it1->getClass().getCreatureStats(*it1).getAiSetting(CreatureStats::AI_Alarm).getBase() >= alarm - || type == OT_Assault ) - { - it1->getClass().getNpcStats(*it1).setCrimeId(id); - } - - // Mark as Alarmed for dialogue - it1->getClass().getCreatureStats(*it1).setAlarmed(true); - } } } } + if (reported) reportCrime(player, victim, type, arg); + else if (victimAware && !victim.isEmpty() && type == OT_Assault) + startCombat(victim, player); + return reported; } - void MechanicsManager::reportCrime(const MWWorld::Ptr &ptr, const MWWorld::Ptr &victim, OffenseType type, int arg) + void MechanicsManager::reportCrime(const MWWorld::Ptr &player, const MWWorld::Ptr &victim, OffenseType type, int arg) { const MWWorld::Store& store = MWBase::Environment::get().getWorld()->getStore().get(); + if (type == OT_Murder && !victim.isEmpty()) + victim.getClass().getCreatureStats(victim).notifyMurder(); + // Bounty for each type of crime if (type == OT_Trespassing || type == OT_SleepingInOwnedBed) arg = store.find("iCrimeTresspass")->getInt(); @@ -929,10 +924,13 @@ namespace MWMechanics else if (type == OT_Murder) arg = store.find("iCrimeKilling")->getInt(); else if (type == OT_Theft) + { arg *= store.find("fCrimeStealing")->getFloat(); + arg = std::max(1, arg); // Minimum bounty of 1, in case items with zero value are stolen + } MWBase::Environment::get().getWindowManager()->messageBox("#{sCrimeMessage}"); - ptr.getClass().getNpcStats(ptr).setBounty(ptr.getClass().getNpcStats(ptr).getBounty() + player.getClass().getNpcStats(player).setBounty(player.getClass().getNpcStats(player).getBounty() + arg); // If committing a crime against a faction member, expell from the faction @@ -941,9 +939,81 @@ namespace MWMechanics std::string factionID; if(!victim.getClass().getNpcStats(victim).getFactionRanks().empty()) factionID = victim.getClass().getNpcStats(victim).getFactionRanks().begin()->first; - if (ptr.getClass().getNpcStats(ptr).isSameFaction(victim.getClass().getNpcStats(victim))) + if (player.getClass().getNpcStats(player).isSameFaction(victim.getClass().getNpcStats(victim))) { - ptr.getClass().getNpcStats(ptr).expell(factionID); + player.getClass().getNpcStats(player).expell(factionID); + } + } + + // Make surrounding actors within alarm distance respond to the crime + std::vector neighbors; + + const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); + + Ogre::Vector3 from = Ogre::Vector3(player.getRefData().getPosition().pos); + float radius = esmStore.get().find("fAlarmRadius")->getFloat(); + + mActors.getObjectsInRange(from, radius, neighbors); + + // victim should be considered even beyond alarm radius + if (!victim.isEmpty() && from.squaredDistance(Ogre::Vector3(victim.getRefData().getPosition().pos)) > radius*radius) + neighbors.push_back(victim); + + int id = MWBase::Environment::get().getWorld()->getPlayer().getNewCrimeId(); + + // What amount of provocation did this crime generate? + // Controls whether witnesses will engage combat with the criminal. + int fight = 0; + if (type == OT_Trespassing || type == OT_SleepingInOwnedBed) + fight = esmStore.get().find("iFightTrespass")->getInt(); + else if (type == OT_Pickpocket) + fight = esmStore.get().find("iFightPickpocket")->getInt(); + else if (type == OT_Assault) // Note: iFightAttack is for the victim, iFightAttacking for witnesses? + fight = esmStore.get().find("iFightAttack")->getInt(); + else if (type == OT_Murder) + fight = esmStore.get().find("iFightKilling")->getInt(); + else if (type == OT_Theft) + fight = esmStore.get().find("fFightStealing")->getFloat(); + + const int iFightAttacking = esmStore.get().find("iFightAttacking")->getInt(); + + // Tell everyone (including the original reporter) in alarm range + for (std::vector::iterator it = neighbors.begin(); it != neighbors.end(); ++it) + { + if ( *it == player + || !it->getClass().isNpc() || it->getClass().getCreatureStats(*it).isDead()) continue; + + int aggression = fight; + + // Note: iFightAttack is used for the victim, iFightAttacking for witnesses? + if (*it != victim && type == OT_Assault) + aggression = iFightAttacking; + + if (it->getClass().isClass(*it, "guard")) + { + // Mark as Alarmed for dialogue + it->getClass().getCreatureStats(*it).setAlarmed(true); + + // Set the crime ID, which we will use to calm down participants + // once the bounty has been paid. + it->getClass().getNpcStats(*it).setCrimeId(id); + + it->getClass().getCreatureStats(*it).getAiSequence().stack(AiPursue(player), *it); + } + else + { + bool aggressive = MWBase::Environment::get().getMechanicsManager()->isAggressive(*it, player, aggression, true); + if (aggressive) + { + startCombat(*it, player); + + // Set the crime ID, which we will use to calm down participants + // once the bounty has been paid. + it->getClass().getNpcStats(*it).setCrimeId(id); + + // Mark as Alarmed for dialogue + it->getClass().getCreatureStats(*it).setAlarmed(true); + } } } } @@ -1021,10 +1091,29 @@ namespace MWMechanics { ptr.getClass().getCreatureStats(ptr).getAiSequence().stack(MWMechanics::AiCombat(target), ptr); if (target == MWBase::Environment::get().getWorld()->getPlayerPtr()) + { ptr.getClass().getCreatureStats(ptr).setHostile(true); + // if guard starts combat with player, guards pursuing player should do the same + if (ptr.getClass().isClass(ptr, "Guard")) + { + for (Actors::PtrControllerMap::const_iterator iter = mActors.begin(); iter != mActors.end(); ++iter) + { + if (iter->first.getClass().isClass(iter->first, "Guard")) + { + MWMechanics::AiSequence& aiSeq = iter->first.getClass().getCreatureStats(iter->first).getAiSequence(); + if (aiSeq.getTypeId() == MWMechanics::AiPackage::TypeIdPursue) + { + aiSeq.stopPursuit(); + aiSeq.stack(MWMechanics::AiCombat(target), ptr); + } + } + } + } + } + // Must be done after the target is set up, so that CreatureTargetted dialogue filter works properly - if (ptr.getClass().isNpc()) + if (ptr.getClass().isNpc() && !ptr.getClass().getCreatureStats(ptr).isDead()) MWBase::Environment::get().getDialogueManager()->say(ptr, "attack"); } @@ -1047,4 +1136,52 @@ namespace MWMechanics std::list MechanicsManager::getActorsFighting(const MWWorld::Ptr& actor) { return mActors.getActorsFighting(actor); } + + int MechanicsManager::countSavedGameRecords() const + { + return 1; // Death counter + } + + void MechanicsManager::write(ESM::ESMWriter &writer, Loading::Listener &listener) const + { + mActors.write(writer, listener); + } + + void MechanicsManager::readRecord(ESM::ESMReader &reader, int32_t type) + { + mActors.readRecord(reader, type); + } + + void MechanicsManager::clear() + { + mActors.clear(); + } + + bool MechanicsManager::isAggressive(const MWWorld::Ptr &ptr, const MWWorld::Ptr &target, int bias, bool ignoreDistance) + { + Ogre::Vector3 pos1 (ptr.getRefData().getPosition().pos); + Ogre::Vector3 pos2 (target.getRefData().getPosition().pos); + + float d = 0; + if (!ignoreDistance) + d = pos1.distance(pos2); + + static int iFightDistanceBase = MWBase::Environment::get().getWorld()->getStore().get().find( + "iFightDistanceBase")->getInt(); + static float fFightDistanceMultiplier = MWBase::Environment::get().getWorld()->getStore().get().find( + "fFightDistanceMultiplier")->getFloat(); + static float fFightDispMult = MWBase::Environment::get().getWorld()->getStore().get().find( + "fFightDispMult")->getFloat(); + + int disposition = 50; + if (ptr.getClass().isNpc()) + disposition = getDerivedDisposition(ptr); + + int fight = std::max(0.f, ptr.getClass().getCreatureStats(ptr).getAiSetting(CreatureStats::AI_Fight).getModified() + + (iFightDistanceBase - fFightDistanceMultiplier * d) + + ((50 - disposition) * fFightDispMult)) + + bias; + + return (fight >= 100); + } } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index dcd12ee140..596e6887ec 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -111,6 +111,7 @@ namespace MWMechanics /** * @brief Commit a crime. If any actors witness the crime and report it, * reportCrime will be called automatically. + * @note victim may be empty * @param arg Depends on \a type, e.g. for Theft, the value of the item that was stolen. * @return was the crime reported? */ @@ -147,6 +148,18 @@ namespace MWMechanics virtual bool isAIActive(); virtual void playerLoaded(); + + virtual int countSavedGameRecords() const; + + virtual void write (ESM::ESMWriter& writer, Loading::Listener& listener) const; + + virtual void readRecord (ESM::ESMReader& reader, int32_t type); + + virtual void clear(); + + /// @param bias Can be used to add an additional aggression bias towards the target, + /// making it more likely for the function to return true. + virtual bool isAggressive (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, int bias=0, bool ignoreDistance=false); }; } diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index d50f2c5ae4..579969f9d2 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -361,8 +361,14 @@ bool MWMechanics::NpcStats::hasSkillsForRank (const std::string& factionId, int std::vector skills; - for (int i=0; i<6; ++i) - skills.push_back (static_cast (getSkill (faction.mData.mSkills[i]).getModified())); + for (int i=0; i<7; ++i) + { + if (faction.mData.mSkills[i] != -1) + skills.push_back (static_cast (getSkill (faction.mData.mSkills[i]).getModified())); + } + + if (skills.empty()) + return true; std::sort (skills.begin(), skills.end()); @@ -373,6 +379,9 @@ bool MWMechanics::NpcStats::hasSkillsForRank (const std::string& factionId, int if (*iter=rankData.mSkill2; } @@ -387,6 +396,8 @@ void MWMechanics::NpcStats::setWerewolf (bool set) { const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); + mWerewolfKills = 0; + for(size_t i = 0;i < ESM::Attribute::Length;i++) { mWerewolfAttributes[i] = getAttribute(i); @@ -418,6 +429,11 @@ int MWMechanics::NpcStats::getWerewolfKills() const return mWerewolfKills; } +void MWMechanics::NpcStats::addWerewolfKill() +{ + ++mWerewolfKills; +} + int MWMechanics::NpcStats::getProfit() const { return mProfit; diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp index 185a58b947..a066760d00 100644 --- a/apps/openmw/mwmechanics/npcstats.hpp +++ b/apps/openmw/mwmechanics/npcstats.hpp @@ -126,6 +126,9 @@ namespace MWMechanics int getWerewolfKills() const; + /// Increments mWerewolfKills by 1. + void addWerewolfKill(); + float getTimeToStartDrowning() const; /// Sets time left for the creature to drown if it stays underwater. /// @param time value from [0,20] diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index d77a35ea48..62c1756c27 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -90,12 +90,12 @@ namespace namespace MWMechanics { - float distanceZCorrected(ESM::Pathgrid::Point point, float x, float y, float z) + float sqrDistanceZCorrected(ESM::Pathgrid::Point point, float x, float y, float z) { x -= point.mX; y -= point.mY; z -= point.mZ; - return sqrt(x * x + y * y + 0.1 * z * z); + return (x * x + y * y + 0.1 * z * z); } float distance(ESM::Pathgrid::Point point, float x, float y, float z) @@ -296,7 +296,7 @@ namespace MWMechanics return true; ESM::Pathgrid::Point nextPoint = *mPath.begin(); - if(distanceZCorrected(nextPoint, x, y, z) < 64) + if(sqrDistanceZCorrected(nextPoint, x, y, z) < 64*64) { mPath.pop_front(); if(mPath.empty()) mIsPathConstructed = false; @@ -311,7 +311,7 @@ namespace MWMechanics return true; ESM::Pathgrid::Point nextPoint = *mPath.begin(); - if(distanceZCorrected(nextPoint, x, y, z) < 64) + if(sqrDistanceZCorrected(nextPoint, x, y, z) < 64*64) { mPath.pop_front(); if(mPath.empty()) diff --git a/apps/openmw/mwmechanics/pathgrid.cpp b/apps/openmw/mwmechanics/pathgrid.cpp index c3fa0a662e..4983a4a4f2 100644 --- a/apps/openmw/mwmechanics/pathgrid.cpp +++ b/apps/openmw/mwmechanics/pathgrid.cpp @@ -100,6 +100,9 @@ namespace MWMechanics if(!cell) return false; + if(mIsGraphConstructed) + return true; + mCell = cell; mIsExterior = cell->isExterior(); mPathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*cell); @@ -107,8 +110,6 @@ namespace MWMechanics if(!mPathgrid) return false; - if(mIsGraphConstructed) - return true; mGraph.resize(mPathgrid->mPoints.size()); for(int i = 0; i < static_cast (mPathgrid->mEdges.size()); i++) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index c996e90d6f..6467730dda 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -148,13 +148,17 @@ namespace MWMechanics return school; } - float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell) + float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, + const ESM::Spell* spell, const MagicEffects* effects) { const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find ( effectId); const MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); + const MWMechanics::MagicEffects* magicEffects = &stats.getMagicEffects(); + if (effects) + magicEffects = effects; float resisted = 0; if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful) @@ -165,9 +169,9 @@ namespace MWMechanics float resistance = 0; if (resistanceEffect != -1) - resistance += stats.getMagicEffects().get(resistanceEffect).mMagnitude; + resistance += magicEffects->get(resistanceEffect).mMagnitude; if (weaknessEffect != -1) - resistance -= stats.getMagicEffects().get(weaknessEffect).mMagnitude; + resistance -= magicEffects->get(weaknessEffect).mMagnitude; float willpower = stats.getAttribute(ESM::Attribute::Willpower).getModified(); @@ -204,9 +208,10 @@ namespace MWMechanics return resisted; } - float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell) + float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, + const ESM::Spell* spell, const MagicEffects* effects) { - float resistance = getEffectResistance(effectId, actor, caster, spell); + float resistance = getEffectResistance(effectId, actor, caster, spell, effects); if (resistance >= 0) return 1 - resistance / 100.f; else @@ -261,6 +266,13 @@ namespace MWMechanics bool firstAppliedEffect = true; bool anyHarmfulEffect = false; + // HACK: cache target's magic effects here, and add any applied effects to it. Use the cached effects for determining resistance. + // This is required for Weakness effects in a spell to apply to any subsequent effects in the spell. + // Otherwise, they'd only apply after the whole spell was added. + MagicEffects targetEffects; + if (target.getClass().isActor()) + targetEffects += target.getClass().getCreatureStats(target).getMagicEffects(); + bool castByPlayer = (!caster.isEmpty() && caster.getRefData().getHandle() == "player"); for (std::vector::const_iterator effectIt (effects.mList.begin()); @@ -328,7 +340,7 @@ namespace MWMechanics } // Try reflecting - if (!reflected && magnitudeMult > 0 && caster != target && !(magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable)) + if (!reflected && magnitudeMult > 0 && !caster.isEmpty() && caster != target && !(magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable)) { int reflect = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Reflect).mMagnitude; int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] @@ -346,8 +358,7 @@ namespace MWMechanics // Try resisting if (magnitudeMult > 0 && target.getClass().isActor()) { - - magnitudeMult = MWMechanics::getEffectMultiplier(effectIt->mEffectID, target, caster, spell); + magnitudeMult = MWMechanics::getEffectMultiplier(effectIt->mEffectID, target, caster, spell, &targetEffects); if (magnitudeMult == 0) { // Fully resisted, show message @@ -375,6 +386,8 @@ namespace MWMechanics effect.mDuration = effectIt->mDuration; effect.mMagnitude = magnitude; + targetEffects.add(MWMechanics::EffectKey(*effectIt), MWMechanics::EffectParam(effect.mMagnitude)); + appliedLastingEffects.push_back(effect); // For absorb effects, also apply the effect to the caster - but with a negative @@ -452,14 +465,14 @@ namespace MWMechanics if (!appliedLastingEffects.empty()) { int casterActorId = -1; - if (caster.getClass().isActor()) + if (!caster.isEmpty() && caster.getClass().isActor()) casterActorId = caster.getClass().getCreatureStats(caster).getActorId(); target.getClass().getCreatureStats(target).getActiveSpells().addSpell(mId, mStack, appliedLastingEffects, mSourceName, casterActorId); } // Notify the target actor they've been hit - if (anyHarmfulEffect && target.getClass().isActor() && target != caster && caster.getClass().isActor()) + if (anyHarmfulEffect && target.getClass().isActor() && target != caster && !caster.isEmpty() && caster.getClass().isActor()) target.getClass().onHit(target, 0.f, true, MWWorld::Ptr(), caster, true); } @@ -644,7 +657,9 @@ namespace MWMechanics getProjectileInfo(enchantment->mEffects, projectileModel, sound, speed); if (!projectileModel.empty()) MWBase::Environment::get().getWorld()->launchMagicBolt(projectileModel, sound, mId, speed, - false, enchantment->mEffects, mCaster, mSourceName); + false, enchantment->mEffects, mCaster, mSourceName, + // Not needed, enchantments can only be cast by actors + Ogre::Vector3(1,0,0)); return true; } @@ -729,8 +744,18 @@ namespace MWMechanics float speed = 0; getProjectileInfo(spell->mEffects, projectileModel, sound, speed); if (!projectileModel.empty()) + { + Ogre::Vector3 fallbackDirection (0,1,0); + // Fall back to a "caster to target" direction if we have no other means of determining it + // (e.g. when cast by a non-actor) + if (!mTarget.isEmpty()) + fallbackDirection = + Ogre::Vector3(mTarget.getRefData().getPosition().pos)- + Ogre::Vector3(mCaster.getRefData().getPosition().pos); + MWBase::Environment::get().getWorld()->launchMagicBolt(projectileModel, sound, mId, speed, - false, spell->mEffects, mCaster, mSourceName); + false, spell->mEffects, mCaster, mSourceName, fallbackDirection); + } return true; } diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index dce4b792e2..b526a43530 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -18,6 +18,7 @@ namespace ESM namespace MWMechanics { class EffectKey; + class MagicEffects; ESM::Skill::SkillEnum spellSchoolToSkill(int school); @@ -35,15 +36,19 @@ namespace MWMechanics int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor); /// @return >=100 for fully resisted. can also return negative value for damage amplification. - float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell = NULL); + /// @param effects Override the actor's current magicEffects. Useful if there are effects currently + /// being applied (but not applied yet) that should also be considered. + float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, + const ESM::Spell* spell = NULL, const MagicEffects* effects = NULL); - float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell = NULL); + float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, + const ESM::Spell* spell = NULL, const MagicEffects* effects = NULL); class CastSpell { private: - MWWorld::Ptr mCaster; - MWWorld::Ptr mTarget; + MWWorld::Ptr mCaster; // May be empty + MWWorld::Ptr mTarget; // May be empty public: bool mStack; std::string mId; // ID of spell, potion, item etc @@ -54,8 +59,13 @@ namespace MWMechanics CastSpell(const MWWorld::Ptr& caster, const MWWorld::Ptr& target); bool cast (const ESM::Spell* spell); + + /// @note mCaster must be an actor bool cast (const MWWorld::Ptr& item); + + /// @note mCaster must be an NPC bool cast (const ESM::Ingredient* ingredient); + bool cast (const ESM::Potion* potion); /// @note Auto detects if spell, ingredient or potion diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 9ed4cb6d3c..872740d74b 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -144,12 +144,6 @@ void Animation::setObjectRoot(const std::string &model, bool baseonly) } else mAttachedObjects.clear(); - - for(size_t i = 0;i < mObjectRoot->mControllers.size();i++) - { - if(mObjectRoot->mControllers[i].getSource().isNull()) - mObjectRoot->mControllers[i].setSource(mAnimationTimePtr[0]); - } } struct AddGlow @@ -309,6 +303,12 @@ void Animation::addAnimSource(const std::string &model) ctrls[i].setSource(mAnimationTimePtr[grp]); grpctrls[grp].push_back(ctrls[i]); } + + for (unsigned int i = 0; i < mObjectRoot->mControllers.size(); ++i) + { + if (mObjectRoot->mControllers[i].getSource().isNull()) + mObjectRoot->mControllers[i].setSource(mAnimationTimePtr[0]); + } } void Animation::clearAnimSources() @@ -356,28 +356,40 @@ void Animation::addExtraLight(Ogre::SceneManager *sceneMgr, NifOgre::ObjectScene objlist->mControllers.push_back(Ogre::Controller(src, dest, func)); bool interior = !(mPtr.isInCell() && mPtr.getCell()->getCell()->isExterior()); - bool quadratic = fallback->getFallbackBool("LightAttenuation_OutQuadInLin") ? - !interior : fallback->getFallbackBool("LightAttenuation_UseQuadratic"); + + static bool outQuadInLin = fallback->getFallbackBool("LightAttenuation_OutQuadInLin"); + static bool useQuadratic = fallback->getFallbackBool("LightAttenuation_UseQuadratic"); + static float quadraticValue = fallback->getFallbackFloat("LightAttenuation_QuadraticValue"); + static float quadraticRadiusMult = fallback->getFallbackFloat("LightAttenuation_QuadraticRadiusMult"); + static bool useLinear = fallback->getFallbackBool("LightAttenuation_UseLinear"); + static float linearRadiusMult = fallback->getFallbackFloat("LightAttenuation_LinearRadiusMult"); + static float linearValue = fallback->getFallbackFloat("LightAttenuation_LinearValue"); + + bool quadratic = useQuadratic && (!outQuadInLin || !interior); + // with the standard 1 / (c + d*l + d*d*q) equation the attenuation factor never becomes zero, // so we ignore lights if their attenuation falls below this factor. const float threshold = 0.03; - if (!quadratic) + float quadraticAttenuation = 0; + float linearAttenuation = 0; + float activationRange = 0; + if (quadratic) { - float r = radius * fallback->getFallbackFloat("LightAttenuation_LinearRadiusMult"); - float attenuation = fallback->getFallbackFloat("LightAttenuation_LinearValue") / r; - float activationRange = 1.0f / (threshold * attenuation); - olight->setAttenuation(activationRange, 0, attenuation, 0); + float r = radius * quadraticRadiusMult; + quadraticAttenuation = quadraticValue / std::pow(r, 2); + activationRange = std::sqrt(1.0f / (threshold * quadraticAttenuation)); } - else + if (useLinear) { - float r = radius * fallback->getFallbackFloat("LightAttenuation_QuadraticRadiusMult"); - float attenuation = fallback->getFallbackFloat("LightAttenuation_QuadraticValue") / std::pow(r, 2); - float activationRange = std::sqrt(1.0f / (threshold * attenuation)); - olight->setAttenuation(activationRange, 0, 0, attenuation); + float r = radius * linearRadiusMult; + linearAttenuation = linearValue / r; + activationRange = std::max(activationRange, 1.0f / (threshold * linearAttenuation)); } + olight->setAttenuation(activationRange, 0, linearAttenuation, quadraticAttenuation); + // If there's an AttachLight bone, attach the light to that, otherwise put it in the center, if(objlist->mSkelBase && objlist->mSkelBase->getSkeleton()->hasBone("AttachLight")) objlist->mSkelBase->attachObjectToBone("AttachLight", olight); @@ -455,16 +467,17 @@ float Animation::calcAnimVelocity(const NifOgre::TextKeyMap &keys, NifOgre::Node const std::string stop = groupname+": stop"; float starttime = std::numeric_limits::max(); float stoptime = 0.0f; - NifOgre::TextKeyMap::const_iterator keyiter(keys.begin()); - while(keyiter != keys.end()) + // Have to find keys in reverse (see reset method) + NifOgre::TextKeyMap::const_reverse_iterator keyiter(keys.rbegin()); + while(keyiter != keys.rend()) { if(keyiter->second == start || keyiter->second == loopstart) - starttime = keyiter->first; - else if(keyiter->second == loopstop || keyiter->second == stop) { - stoptime = keyiter->first; + starttime = keyiter->first; break; } + else if(keyiter->second == loopstop || keyiter->second == stop) + stoptime = keyiter->first; ++keyiter; } @@ -580,31 +593,39 @@ void Animation::updatePosition(float oldtime, float newtime, Ogre::Vector3 &posi bool Animation::reset(AnimState &state, const NifOgre::TextKeyMap &keys, const std::string &groupname, const std::string &start, const std::string &stop, float startpoint) { - const NifOgre::TextKeyMap::const_iterator groupstart = findGroupStart(keys, groupname); + // Look for text keys in reverse. This normally wouldn't matter, but for some reason undeadwolf_2.nif has two + // separate walkforward keys, and the last one is supposed to be used. + NifOgre::TextKeyMap::const_reverse_iterator groupend(keys.rbegin()); + for(;groupend != keys.rend();++groupend) + { + if(groupend->second.compare(0, groupname.size(), groupname) == 0 && + groupend->second.compare(groupname.size(), 2, ": ") == 0) + break; + } std::string starttag = groupname+": "+start; - NifOgre::TextKeyMap::const_iterator startkey(groupstart); - while(startkey != keys.end() && startkey->second != starttag) + NifOgre::TextKeyMap::const_reverse_iterator startkey(groupend); + while(startkey != keys.rend() && startkey->second != starttag) ++startkey; - if(startkey == keys.end() && start == "loop start") + if(startkey == keys.rend() && start == "loop start") { starttag = groupname+": start"; - startkey = groupstart; - while(startkey != keys.end() && startkey->second != starttag) + startkey = groupend; + while(startkey != keys.rend() && startkey->second != starttag) ++startkey; } - if(startkey == keys.end()) + if(startkey == keys.rend()) return false; const std::string stoptag = groupname+": "+stop; - NifOgre::TextKeyMap::const_iterator stopkey(groupstart); - while(stopkey != keys.end() + NifOgre::TextKeyMap::const_reverse_iterator stopkey(groupend); + while(stopkey != keys.rend() // We have to ignore extra garbage at the end. // The Scrib's idle3 animation has "Idle3: Stop." instead of "Idle3: Stop". // Why, just why? :( && (stopkey->second.size() < stoptag.size() || stopkey->second.substr(0,stoptag.size()) != stoptag)) ++stopkey; - if(stopkey == keys.end()) + if(stopkey == keys.rend()) return false; if(startkey->first > stopkey->first) @@ -616,18 +637,24 @@ bool Animation::reset(AnimState &state, const NifOgre::TextKeyMap &keys, const s state.mStopTime = stopkey->first; state.mTime = state.mStartTime + ((state.mStopTime - state.mStartTime) * startpoint); + + // mLoopStartTime and mLoopStopTime normally get assigned when encountering these keys while playing the animation + // (see handleTextKey). But if startpoint is already past these keys, we need to assign them now. if(state.mTime > state.mStartTime) { const std::string loopstarttag = groupname+": loop start"; const std::string loopstoptag = groupname+": loop stop"; - NifOgre::TextKeyMap::const_iterator key(groupstart); - while(key->first <= state.mTime && key != stopkey) + + NifOgre::TextKeyMap::const_reverse_iterator key(groupend); + for (; key != startkey && key != keys.rend(); ++key) { - if(key->second == loopstarttag) + if (key->first > state.mTime) + continue; + + if (key->second == loopstarttag) state.mLoopStartTime = key->first; - else if(key->second == loopstoptag) + else if (key->second == loopstoptag) state.mLoopStopTime = key->first; - ++key; } } @@ -642,7 +669,8 @@ void split(const std::string &s, char delim, std::vector &elems) { } } -void Animation::handleTextKey(AnimState &state, const std::string &groupname, const NifOgre::TextKeyMap::const_iterator &key) +void Animation::handleTextKey(AnimState &state, const std::string &groupname, const NifOgre::TextKeyMap::const_iterator &key, + const NifOgre::TextKeyMap& textkeys) { //float time = key->first; const std::string &evt = key->second; @@ -716,6 +744,34 @@ void Animation::handleTextKey(AnimState &state, const std::string &groupname, co else mPtr.getClass().hit(mPtr); } + else if (!groupname.empty() && groupname.compare(0, groupname.size()-1, "attack") == 0 + && evt.compare(off, len, "start") == 0) + { + NifOgre::TextKeyMap::const_iterator hitKey = key; + + // Not all animations have a hit key defined. If there is none, the hit happens with the start key. + bool hasHitKey = false; + while (hitKey != textkeys.end()) + { + if (hitKey->second == groupname + ": hit") + { + hasHitKey = true; + break; + } + if (hitKey->second == groupname + ": stop") + break; + ++hitKey; + } + if (!hasHitKey) + { + if (groupname == "attack1") + mPtr.getClass().hit(mPtr, ESM::Weapon::AT_Chop); + else if (groupname == "attack2") + mPtr.getClass().hit(mPtr, ESM::Weapon::AT_Slash); + else if (groupname == "attack3") + mPtr.getClass().hit(mPtr, ESM::Weapon::AT_Thrust); + } + } else if (evt.compare(off, len, "shoot attach") == 0) attachArrow(); else if (evt.compare(off, len, "shoot release") == 0) @@ -794,7 +850,7 @@ void Animation::play(const std::string &groupname, int priority, int groups, boo NifOgre::TextKeyMap::const_iterator textkey(textkeys.lower_bound(state.mTime)); while(textkey != textkeys.end() && textkey->first <= state.mTime) { - handleTextKey(state, groupname, textkey); + handleTextKey(state, groupname, textkey, textkeys); ++textkey; } @@ -809,7 +865,7 @@ void Animation::play(const std::string &groupname, int priority, int groups, boo textkey = textkeys.lower_bound(state.mTime); while(textkey != textkeys.end() && textkey->first <= state.mTime) { - handleTextKey(state, groupname, textkey); + handleTextKey(state, groupname, textkey, textkeys); ++textkey; } } @@ -905,10 +961,10 @@ bool Animation::getInfo(const std::string &groupname, float *complete, float *sp float Animation::getStartTime(const std::string &groupname) const { - AnimSourceList::const_iterator iter(mAnimSources.begin()); - for(;iter != mAnimSources.end();iter++) + for(AnimSourceList::const_iterator iter(mAnimSources.begin()); iter != mAnimSources.end(); ++iter) { const NifOgre::TextKeyMap &keys = (*iter)->mTextKeys; + NifOgre::TextKeyMap::const_iterator found = findGroupStart(keys, groupname); if(found != keys.end()) return found->first; @@ -916,6 +972,22 @@ float Animation::getStartTime(const std::string &groupname) const return -1.f; } +float Animation::getTextKeyTime(const std::string &textKey) const +{ + for(AnimSourceList::const_iterator iter(mAnimSources.begin()); iter != mAnimSources.end(); ++iter) + { + const NifOgre::TextKeyMap &keys = (*iter)->mTextKeys; + + for(NifOgre::TextKeyMap::const_iterator iterKey(keys.begin()); iterKey != keys.end(); ++iterKey) + { + if(iterKey->second.compare(0, textKey.size(), textKey) == 0) + return iterKey->first; + } + } + + return -1.f; +} + float Animation::getCurrentTime(const std::string &groupname) const { AnimStateMap::const_iterator iter = mStates.find(groupname); @@ -971,7 +1043,7 @@ Ogre::Vector3 Animation::runAnimation(float duration) while(textkey != textkeys.end() && textkey->first <= state.mTime) { - handleTextKey(state, stateiter->first, textkey); + handleTextKey(state, stateiter->first, textkey, textkeys); ++textkey; } @@ -985,7 +1057,7 @@ Ogre::Vector3 Animation::runAnimation(float duration) textkey = textkeys.lower_bound(state.mTime); while(textkey != textkeys.end() && textkey->first <= state.mTime) { - handleTextKey(state, stateiter->first, textkey); + handleTextKey(state, stateiter->first, textkey, textkeys); ++textkey; } @@ -999,7 +1071,11 @@ Ogre::Vector3 Animation::runAnimation(float duration) if(!state.mPlaying && state.mAutoDisable) { + if(mNonAccumCtrl && stateiter->first == mAnimationTimePtr[0]->getAnimName()) + mAccumRoot->setPosition(0.f,0.f,0.f); + mStates.erase(stateiter++); + resetActiveGroups(); } else @@ -1007,7 +1083,10 @@ Ogre::Vector3 Animation::runAnimation(float duration) } for(size_t i = 0;i < mObjectRoot->mControllers.size();i++) - mObjectRoot->mControllers[i].update(); + { + if(!mObjectRoot->mControllers[i].getSource().isNull()) + mObjectRoot->mControllers[i].update(); + } // Apply group controllers for(size_t grp = 0;grp < sNumGroups;grp++) @@ -1141,20 +1220,68 @@ void Animation::addEffect(const std::string &model, int effectId, bool loop, con params.mObjects->mControllers[i].setSource(Ogre::SharedPtr (new EffectAnimationTime())); } - if (!texture.empty()) + + // Do some manual adjustments on the created entities/particle systems + + // It looks like vanilla MW totally ignores lighting settings for effects attached to characters. + // If we don't do this, some effects will look way too dark depending on the environment + // (e.g. magic_cast_dst.nif). They were clearly meant to use emissive lighting. + // We used to have this hack in the NIF material loader, but for effects not attached to characters + // (e.g. ash storms) the lighting settings do seem to be in use. Is there maybe a flag we have missed? + Ogre::ColourValue ambient = Ogre::ColourValue(0.f, 0.f, 0.f); + Ogre::ColourValue diffuse = Ogre::ColourValue(0.f, 0.f, 0.f); + Ogre::ColourValue specular = Ogre::ColourValue(0.f, 0.f, 0.f); + Ogre::ColourValue emissive = Ogre::ColourValue(1.f, 1.f, 1.f); + for(size_t i = 0;i < params.mObjects->mParticles.size(); ++i) { - for(size_t i = 0;i < params.mObjects->mParticles.size(); ++i) + Ogre::ParticleSystem* partSys = params.mObjects->mParticles[i]; + + Ogre::MaterialPtr mat = params.mObjects->mMaterialControllerMgr.getWritableMaterial(partSys); + + for (int t=0; tgetNumTechniques(); ++t) { - Ogre::ParticleSystem* partSys = params.mObjects->mParticles[i]; - - Ogre::MaterialPtr mat = params.mObjects->mMaterialControllerMgr.getWritableMaterial(partSys); - - for (int t=0; tgetNumTechniques(); ++t) + Ogre::Technique* tech = mat->getTechnique(t); + for (int p=0; pgetNumPasses(); ++p) { - Ogre::Technique* tech = mat->getTechnique(t); - for (int p=0; pgetNumPasses(); ++p) + Ogre::Pass* pass = tech->getPass(p); + + pass->setAmbient(ambient); + pass->setDiffuse(diffuse); + pass->setSpecular(specular); + pass->setEmissive(emissive); + + if (!texture.empty()) + { + for (int tex=0; texgetNumTextureUnitStates(); ++tex) + { + Ogre::TextureUnitState* tus = pass->getTextureUnitState(tex); + tus->setTextureName("textures\\" + texture); + } + } + } + } + } + for(size_t i = 0;i < params.mObjects->mEntities.size(); ++i) + { + Ogre::Entity* ent = params.mObjects->mEntities[i]; + if (ent == params.mObjects->mSkelBase) + continue; + Ogre::MaterialPtr mat = params.mObjects->mMaterialControllerMgr.getWritableMaterial(ent); + + for (int t=0; tgetNumTechniques(); ++t) + { + Ogre::Technique* tech = mat->getTechnique(t); + for (int p=0; pgetNumPasses(); ++p) + { + Ogre::Pass* pass = tech->getPass(p); + + pass->setAmbient(ambient); + pass->setDiffuse(diffuse); + pass->setSpecular(specular); + pass->setEmissive(emissive); + + if (!texture.empty()) { - Ogre::Pass* pass = tech->getPass(p); for (int tex=0; texgetNumTextureUnitStates(); ++tex) { Ogre::TextureUnitState* tus = pass->getTextureUnitState(tex); diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 564bb73ef1..e15bd6ecbe 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -173,7 +173,8 @@ protected: const std::string &groupname, const std::string &start, const std::string &stop, float startpoint); - void handleTextKey(AnimState &state, const std::string &groupname, const NifOgre::TextKeyMap::const_iterator &key); + void handleTextKey(AnimState &state, const std::string &groupname, const NifOgre::TextKeyMap::const_iterator &key, + const NifOgre::TextKeyMap& map); /* Sets the root model of the object. If 'baseonly' is true, then any meshes or particle * systems in the model are ignored (useful for NPCs, where only the skeleton is needed for @@ -210,7 +211,7 @@ public: /** * @brief Add an effect mesh attached to a bone or the insert scene node * @param model - * @param effectId An ID for this effect. Note that adding the same ID again won't add another effect. + * @param effectId An ID for this effect by which you can identify it later. If this is not wanted, set to -1. * @param loop Loop the effect. If false, it is removed automatically after it finishes playing. If true, * you need to remove it manually using removeEffect when the effect should end. * @param bonename Bone to attach to, or empty string to use the scene node instead @@ -225,9 +226,6 @@ public: virtual void preRender (Ogre::Camera* camera); virtual void setAlpha(float alpha) {} -private: - void updateEffects(float duration); - public: void updatePtr(const MWWorld::Ptr &ptr); @@ -277,6 +275,9 @@ public: /// Get the absolute position in the animation track of the first text key with the given group. float getStartTime(const std::string &groupname) const; + /// Get the absolute position in the animation track of the text key + float getTextKeyTime(const std::string &textKey) const; + /// Get the current absolute position in the animation track for the animation that is currently playing from the given group. float getCurrentTime(const std::string& groupname) const; @@ -297,6 +298,9 @@ public: virtual Ogre::Vector3 runAnimation(float duration); + /// This is typically called as part of runAnimation, but may be called manually if needed. + void updateEffects(float duration); + virtual void showWeapons(bool showWeapon); virtual void showCarriedLeft(bool show) {} virtual void attachArrow() {} diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 0eb883953e..f2447cb702 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -184,6 +184,18 @@ Ogre::Vector3 CreatureWeaponAnimation::runAnimation(float duration) { Ogre::Vector3 ret = Animation::runAnimation(duration); pitchSkeleton(mPtr.getRefData().getPosition().rot[0], mSkelBase->getSkeleton()); + + if (!mWeapon.isNull()) + { + for (unsigned int i=0; imControllers.size(); ++i) + mWeapon->mControllers[i].update(); + } + if (!mShield.isNull()) + { + for (unsigned int i=0; imControllers.size(); ++i) + mShield->mControllers[i].update(); + } + return ret; } diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index 0537ed5160..1ccfd9527c 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -62,6 +62,12 @@ namespace MWRender loadingListener->setProgressRange((mMaxX-mMinX+1) * (mMaxY-mMinY+1)); loadingListener->setProgress(0); + const Ogre::ColourValue waterShallowColour(0.15, 0.2, 0.19); + const Ogre::ColourValue waterDeepColour(0.1, 0.14, 0.13); + const Ogre::ColourValue groundColour(0.254, 0.19, 0.13); + const Ogre::ColourValue mountainColour(0.05, 0.05, 0.05); + const Ogre::ColourValue hillColour(0.16, 0.12, 0.08); + //if (!boost::filesystem::exists(mCacheDir + "/GlobalMap.png")) if (1) { @@ -91,12 +97,6 @@ namespace MWRender int texelX = (x-mMinX) * cellSize + cellX; int texelY = (mHeight-1) - ((y-mMinY) * cellSize + cellY); - Ogre::ColourValue waterShallowColour(0.15, 0.2, 0.19); - Ogre::ColourValue waterDeepColour(0.1, 0.14, 0.13); - Ogre::ColourValue groundColour(0.254, 0.19, 0.13); - Ogre::ColourValue mountainColour(0.05, 0.05, 0.05); - Ogre::ColourValue hillColour(0.16, 0.12, 0.08); - unsigned char r,g,b; if (land) diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 2b0323675e..62907fcc35 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -319,7 +319,7 @@ void LocalMap::createFogOfWar(const std::string& texturePrefix) std::vector buffer; // initialize to (0, 0, 0, 1) - buffer.resize(sFogOfWarResolution*sFogOfWarResolution, (255 << 24)); + buffer.resize(sFogOfWarResolution*sFogOfWarResolution, 0xFF000000); // upload to the texture memcpy(tex->getBuffer()->lock(HardwareBuffer::HBL_DISCARD), &buffer[0], sFogOfWarResolution*sFogOfWarResolution*4); diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index 7953a31178..d9e20e1f8e 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -73,7 +73,7 @@ void Objects::insertBegin(const MWWorld::Ptr& ptr) ptr.getRefData().setBaseNode(insert); } -void Objects::insertModel(const MWWorld::Ptr &ptr, const std::string &mesh) +void Objects::insertModel(const MWWorld::Ptr &ptr, const std::string &mesh, bool batch) { insertBegin(ptr); @@ -99,7 +99,7 @@ void Objects::insertModel(const MWWorld::Ptr &ptr, const std::string &mesh) mBounds[ptr.getCell()] = Ogre::AxisAlignedBox::BOX_NULL; mBounds[ptr.getCell()].merge(bounds); - if(ptr.getTypeName() == typeid(ESM::Static).name() && + if(batch && Settings::Manager::getBool("use static geometry", "Objects") && anim->canBatch()) { diff --git a/apps/openmw/mwrender/objects.hpp b/apps/openmw/mwrender/objects.hpp index 665a1e6570..02e974e2d8 100644 --- a/apps/openmw/mwrender/objects.hpp +++ b/apps/openmw/mwrender/objects.hpp @@ -41,7 +41,7 @@ public: , mRootNode(NULL) {} ~Objects(){} - void insertModel(const MWWorld::Ptr& ptr, const std::string &model); + void insertModel(const MWWorld::Ptr& ptr, const std::string &model, bool batch=false); ObjectAnimation* getAnimation(const MWWorld::Ptr &ptr); diff --git a/apps/openmw/mwrender/refraction.cpp b/apps/openmw/mwrender/refraction.cpp index f22968e9d0..7d728b721d 100644 --- a/apps/openmw/mwrender/refraction.cpp +++ b/apps/openmw/mwrender/refraction.cpp @@ -33,9 +33,9 @@ namespace MWRender Ogre::Viewport* vp = mRenderTarget->addViewport(mCamera); vp->setOverlaysEnabled(false); vp->setShadowsEnabled(false); - vp->setVisibilityMask(RV_Actors + RV_Misc + RV_Statics + RV_StaticsSmall + RV_Terrain + RV_Sky); + vp->setVisibilityMask(RV_Actors + RV_Misc + RV_Statics + RV_StaticsSmall + RV_Terrain + RV_Sky + RV_FirstPerson); vp->setMaterialScheme("water_refraction"); - vp->setBackgroundColour (Ogre::ColourValue(0.18039, 0.23137, 0.25490)); + vp->setBackgroundColour (Ogre::ColourValue(0.090195, 0.115685, 0.12745)); mRenderTarget->setAutoUpdated(true); mRenderTarget->addListener(this); } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 8a22c63c64..23edb3a7fb 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -22,7 +22,7 @@ #include #include -#include +#include #include "../mwworld/esmstore.hpp" #include "../mwworld/class.hpp" @@ -45,6 +45,7 @@ #include "globalmap.hpp" #include "terrainstorage.hpp" #include "effectmanager.hpp" +#include "terraingrid.hpp" using namespace MWRender; using namespace Ogre; @@ -223,6 +224,9 @@ MWRender::Camera* RenderingManager::getCamera() const void RenderingManager::removeCell (MWWorld::CellStore *store) { + if (store->isExterior()) + mTerrain->unloadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); + mLocalMap->saveFogOfWar(store); mObjects->removeCell(store); mActors->removeCell(store); @@ -241,6 +245,9 @@ bool RenderingManager::toggleWater() void RenderingManager::cellAdded (MWWorld::CellStore *store) { + if (store->isExterior()) + mTerrain->loadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); + mObjects->buildStaticGeometry (*store); sh::Factory::getInstance().unloadUnreferencedMaterials(); mDebugging->cellAdded(store); @@ -506,10 +513,19 @@ void RenderingManager::configureFog(const float density, const Ogre::ColourValue mFogColour = colour; float max = Settings::Manager::getFloat("max viewing distance", "Viewing distance"); - mFogStart = max / (density) * Settings::Manager::getFloat("fog start factor", "Viewing distance"); - mFogEnd = max / (density) * Settings::Manager::getFloat("fog end factor", "Viewing distance"); + if (density == 0) + { + mFogStart = 0; + mFogEnd = std::numeric_limits().max(); + mRendering.getCamera()->setFarClipDistance (max); + } + else + { + mFogStart = max / (density) * Settings::Manager::getFloat("fog start factor", "Viewing distance"); + mFogEnd = max / (density) * Settings::Manager::getFloat("fog end factor", "Viewing distance"); + mRendering.getCamera()->setFarClipDistance (max / density); + } - mRendering.getCamera()->setFarClipDistance ( Settings::Manager::getFloat("max viewing distance", "Viewing distance") / density ); } void RenderingManager::applyFog (bool underwater) @@ -522,9 +538,10 @@ void RenderingManager::applyFog (bool underwater) } else { - mRendering.getScene()->setFog (FOG_LINEAR, Ogre::ColourValue(0.18039, 0.23137, 0.25490), 0, 0, 1000); - mRendering.getViewport()->setBackgroundColour (Ogre::ColourValue(0.18039, 0.23137, 0.25490)); - mWater->setViewportBackground (Ogre::ColourValue(0.18039, 0.23137, 0.25490)); + Ogre::ColourValue clv(0.090195, 0.115685, 0.12745); + mRendering.getScene()->setFog (FOG_LINEAR, Ogre::ColourValue(clv), 0, 0, 1000); + mRendering.getViewport()->setBackgroundColour (Ogre::ColourValue(clv)); + mWater->setViewportBackground (Ogre::ColourValue(clv)); } } @@ -631,12 +648,12 @@ void RenderingManager::sunDisable(bool real) } } -void RenderingManager::setSunDirection(const Ogre::Vector3& direction) +void RenderingManager::setSunDirection(const Ogre::Vector3& direction, bool is_moon) { // direction * -1 (because 'direction' is camera to sun vector and not sun to camera), if (mSun) mSun->setDirection(Vector3(-direction.x, -direction.y, -direction.z)); - mSkyManager->setSunDirection(direction); + mSkyManager->setSunDirection(direction, is_moon); } void RenderingManager::setGlare(bool glare) @@ -1029,9 +1046,12 @@ void RenderingManager::enableTerrain(bool enable) { if (!mTerrain) { - mTerrain = new Terrain::World(mRendering.getScene(), new MWRender::TerrainStorage(), RV_Terrain, - Settings::Manager::getBool("distant land", "Terrain"), - Settings::Manager::getBool("shader", "Terrain"), Terrain::Align_XY, 1, 64); + if (Settings::Manager::getBool("distant land", "Terrain")) + mTerrain = new Terrain::DefaultWorld(mRendering.getScene(), new MWRender::TerrainStorage(), RV_Terrain, + Settings::Manager::getBool("shader", "Terrain"), Terrain::Align_XY, 1, 64); + else + mTerrain = new MWRender::TerrainGrid(mRendering.getScene(), new MWRender::TerrainStorage(), RV_Terrain, + Settings::Manager::getBool("shader", "Terrain"), Terrain::Align_XY); mTerrain->applyMaterials(Settings::Manager::getBool("enabled", "Shadows"), Settings::Manager::getBool("split", "Shadows")); mTerrain->update(mRendering.getCamera()->getRealPosition()); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index f539f9270f..ea7905cf5a 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -145,7 +145,7 @@ public: void setAmbientColour(const Ogre::ColourValue& colour); void setSunColour(const Ogre::ColourValue& colour); - void setSunDirection(const Ogre::Vector3& direction); + void setSunDirection(const Ogre::Vector3& direction, bool is_moon); void sunEnable(bool real); ///< @param real whether or not to really disable the sunlight (otherwise just set diffuse to 0) void sunDisable(bool real); diff --git a/apps/openmw/mwrender/ripplesimulation.cpp b/apps/openmw/mwrender/ripplesimulation.cpp index 74216c1de4..64b5e48c38 100644 --- a/apps/openmw/mwrender/ripplesimulation.cpp +++ b/apps/openmw/mwrender/ripplesimulation.cpp @@ -165,7 +165,7 @@ void RippleSimulation::addImpulses() // for non-player actors this is done in updateObjectCell it->mPtr = MWBase::Environment::get().getWorld ()->getPlayerPtr(); } - float* _currentPos = it->mPtr.getRefData().getPosition().pos; + const float* _currentPos = it->mPtr.getRefData().getPosition().pos; Ogre::Vector3 currentPos (_currentPos[0], _currentPos[1], _currentPos[2]); if ( (currentPos - it->mLastEmitPosition).length() > 2 diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 90c08c2995..8354cca5d7 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -8,10 +8,11 @@ #include #include #include -#include +#include #include #include #include +#include #include @@ -218,6 +219,7 @@ SkyManager::SkyManager(Ogre::SceneNode *root, Ogre::Camera *pCamera) , mSceneMgr(NULL) , mAtmosphereDay(NULL) , mAtmosphereNight(NULL) + , mCloudNode(NULL) , mClouds() , mNextClouds() , mCloudBlendFactor(0.0f) @@ -234,10 +236,16 @@ SkyManager::SkyManager(Ogre::SceneNode *root, Ogre::Camera *pCamera) , mCreated(false) , mCloudAnimationTimer(0.f) , mMoonRed(false) + , mParticleNode(NULL) + , mRainEnabled(false) + , mRainTimer(0) + , mRainSpeed(0) + , mRainFrequency(1) + , mStormDirection(0,-1,0) + , mIsStorm(false) { mSceneMgr = root->getCreator(); mRootNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(); - mRootNode->setInheritOrientation(false); } void SkyManager::create() @@ -329,8 +337,8 @@ void SkyManager::create() mObjects.push_back(objects); // Clouds - SceneNode* clouds_node = mRootNode->createChildSceneNode(); - objects = NifOgre::Loader::createObjects(clouds_node, "meshes\\sky_clouds_01.nif"); + mCloudNode = mRootNode->createChildSceneNode(); + objects = NifOgre::Loader::createObjects(mCloudNode, "meshes\\sky_clouds_01.nif"); for(size_t i = 0;i < objects->mEntities.size();i++) { Entity* clouds_ent = objects->mEntities[i]; @@ -349,6 +357,7 @@ void SkyManager::create() SkyManager::~SkyManager() { + clearRain(); delete mSun; delete mSunGlare; delete mMasser; @@ -367,11 +376,97 @@ int SkyManager::getSecundaPhase() const return mSecunda->getPhaseInt(); } +void SkyManager::clearRain() +{ + for (std::map::iterator it = mRainModels.begin(); it != mRainModels.end();) + { + it->second.setNull(); + Ogre::SceneNode* parent = it->first->getParentSceneNode(); + mSceneMgr->destroySceneNode(it->first); + mSceneMgr->destroySceneNode(parent); + mRainModels.erase(it++); + } +} + +void SkyManager::updateRain(float dt) +{ + // Move existing rain + // Note: if rain gets disabled, we let the existing rain drops finish falling down. + float minHeight = 200; + for (std::map::iterator it = mRainModels.begin(); it != mRainModels.end();) + { + Ogre::Vector3 pos = it->first->getPosition(); + pos.z -= mRainSpeed * dt; + it->first->setPosition(pos); + if (pos.z < -minHeight) + { + it->second.setNull(); + Ogre::SceneNode* parent = it->first->getParentSceneNode(); + mSceneMgr->destroySceneNode(it->first); + mSceneMgr->destroySceneNode(parent); + mRainModels.erase(it++); + } + else + ++it; + } + + // Spawn new rain + float rainFrequency = mRainFrequency; + float startHeight = 700; + if (mRainEnabled) + { + mRainTimer += dt; + if (mRainTimer >= 1.f/rainFrequency) + { + mRainTimer = 0; + + const float rangeRandom = 100; + float xOffs = (std::rand()/(RAND_MAX+1.0)) * rangeRandom - (rangeRandom/2); + float yOffs = (std::rand()/(RAND_MAX+1.0)) * rangeRandom - (rangeRandom/2); + Ogre::SceneNode* sceneNode = mCamera->getParentSceneNode()->createChildSceneNode(); + sceneNode->setInheritOrientation(false); + + // Create a separate node to control the offset, since a node with setInheritOrientation(false) will still + // consider the orientation of the parent node for its position, just not for its orientation + Ogre::SceneNode* offsetNode = sceneNode->createChildSceneNode(Ogre::Vector3(xOffs,yOffs,startHeight)); + + NifOgre::ObjectScenePtr objects = NifOgre::Loader::createObjects(offsetNode, mRainEffect); + for (unsigned int i=0; imEntities.size(); ++i) + { + objects->mEntities[i]->setRenderQueueGroup(RQG_Alpha); + objects->mEntities[i]->setVisibilityFlags(RV_Sky); + } + for (unsigned int i=0; imParticles.size(); ++i) + { + objects->mParticles[i]->setRenderQueueGroup(RQG_Alpha); + objects->mParticles[i]->setVisibilityFlags(RV_Sky); + } + mRainModels[offsetNode] = objects; + } + } +} + void SkyManager::update(float duration) { if (!mEnabled) return; const MWWorld::Fallback* fallback=MWBase::Environment::get().getWorld()->getFallback(); + if (!mParticle.isNull()) + { + for (unsigned int i=0; imControllers.size(); ++i) + mParticle->mControllers[i].update(); + + if (mIsStorm) + mParticleNode->setOrientation(Ogre::Vector3::UNIT_Y.getRotationTo(mStormDirection)); + } + + if (mIsStorm) + mCloudNode->setOrientation(Ogre::Vector3::UNIT_Y.getRotationTo(mStormDirection)); + else + mCloudNode->setOrientation(Ogre::Quaternion::IDENTITY); + + updateRain(duration); + // UV Scroll the clouds mCloudAnimationTimer += duration * mCloudSpeed; sh::Factory::getInstance().setSharedParameter ("cloudAnimationTimer", @@ -422,13 +517,22 @@ void SkyManager::enable() if (!mCreated) create(); + if (mParticleNode) + mParticleNode->setVisible(true); + mRootNode->setVisible(true); mEnabled = true; } void SkyManager::disable() { + if (mParticleNode) + mParticleNode->setVisible(false); + + clearRain(); + mRootNode->setVisible(false); + mEnabled = false; } @@ -441,6 +545,43 @@ void SkyManager::setWeather(const MWWorld::WeatherResult& weather) { if (!mCreated) return; + mRainEffect = weather.mRainEffect; + mRainEnabled = !mRainEffect.empty(); + mRainFrequency = weather.mRainFrequency; + mRainSpeed = weather.mRainSpeed; + mIsStorm = weather.mIsStorm; + + if (mCurrentParticleEffect != weather.mParticleEffect) + { + mCurrentParticleEffect = weather.mParticleEffect; + + if (mCurrentParticleEffect.empty()) + { + mParticle.setNull(); + } + else + { + if (!mParticleNode) + { + mParticleNode = mCamera->getParentSceneNode()->createChildSceneNode(); + mParticleNode->setInheritOrientation(false); + } + + mParticle = NifOgre::Loader::createObjects(mParticleNode, mCurrentParticleEffect); + for(size_t i = 0; i < mParticle->mParticles.size(); ++i) + { + ParticleSystem* particle = mParticle->mParticles[i]; + particle->setRenderQueueGroup(RQG_Alpha); + particle->setVisibilityFlags(RV_Sky); + } + for (size_t i = 0; i < mParticle->mControllers.size(); ++i) + { + if (mParticle->mControllers[i].getSource().isNull()) + mParticle->mControllers[i].setSource(Ogre::ControllerManager::getSingleton().getFrameTimeSource()); + } + } + } + if (mClouds != weather.mCloudTexture) { sh::Factory::getInstance().setTextureAlias ("cloud_texture_1", "textures\\"+weather.mCloudTexture); @@ -546,14 +687,19 @@ void SkyManager::sunDisable() mSunEnabled = false; } -void SkyManager::setSunDirection(const Vector3& direction) +void SkyManager::setStormDirection(const Vector3 &direction) +{ + mStormDirection = direction; +} + +void SkyManager::setSunDirection(const Vector3& direction, bool is_moon) { if (!mCreated) return; mSun->setPosition(direction); mSunGlare->setPosition(direction); float height = direction.z; - float fade = ( height > 0.5) ? 1.0 : height * 2; + float fade = is_moon ? 0.0 : (( height > 0.5) ? 1.0 : height * 2); sh::Factory::getInstance ().setSharedParameter ("waterSunFade_sunHeight", sh::makeProperty(new sh::Vector2(fade, height))); } diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index 965907a979..7c31150f3d 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -148,7 +148,11 @@ namespace MWRender void sunDisable(); - void setSunDirection(const Ogre::Vector3& direction); + void setRainSpeed(float speed); + + void setStormDirection(const Ogre::Vector3& direction); + + void setSunDirection(const Ogre::Vector3& direction, bool is_moon); void setMasserDirection(const Ogre::Vector3& direction); @@ -176,10 +180,15 @@ namespace MWRender void create(); ///< no need to call this, automatically done on first enable() + void updateRain(float dt); + void clearRain(); + bool mCreated; bool mMoonRed; + bool mIsStorm; + float mHour; int mDay; int mMonth; @@ -198,8 +207,18 @@ namespace MWRender Ogre::SceneNode* mAtmosphereDay; Ogre::SceneNode* mAtmosphereNight; + Ogre::SceneNode* mCloudNode; + std::vector mObjects; + Ogre::SceneNode* mParticleNode; + NifOgre::ObjectScenePtr mParticle; + + std::map mRainModels; + float mRainTimer; + + Ogre::Vector3 mStormDirection; + // remember some settings so we don't have to apply them again if they didnt change Ogre::String mClouds; Ogre::String mNextClouds; @@ -211,6 +230,8 @@ namespace MWRender Ogre::ColourValue mSkyColour; Ogre::ColourValue mFogColour; + std::string mCurrentParticleEffect; + Ogre::Light* mLightning; float mRemainingTransitionTime; @@ -218,6 +239,11 @@ namespace MWRender float mGlare; // target float mGlareFade; // actual + bool mRainEnabled; + std::string mRainEffect; + float mRainSpeed; + float mRainFrequency; + bool mEnabled; bool mSunEnabled; bool mMasserEnabled; diff --git a/apps/openmw/mwrender/terraingrid.cpp b/apps/openmw/mwrender/terraingrid.cpp new file mode 100644 index 0000000000..4688fbfd9f --- /dev/null +++ b/apps/openmw/mwrender/terraingrid.cpp @@ -0,0 +1,165 @@ +#include "terraingrid.hpp" + +#include +#include +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + +#include + +namespace MWRender +{ + +TerrainGrid::TerrainGrid(Ogre::SceneManager *sceneMgr, Terrain::Storage *storage, int visibilityFlags, bool shaders, Terrain::Alignment align) + : Terrain::World(sceneMgr, storage, visibilityFlags, shaders, align) + , mVisible(true) +{ + mRootNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(); +} + +TerrainGrid::~TerrainGrid() +{ + while (!mGrid.empty()) + { + unloadCell(mGrid.begin()->first.first, mGrid.begin()->first.second); + } + + mSceneMgr->destroySceneNode(mRootNode); +} + +void TerrainGrid::update(const Ogre::Vector3 &cameraPos) +{ +} + +void TerrainGrid::loadCell(int x, int y) +{ + if (mGrid.find(std::make_pair(x, y)) != mGrid.end()) + return; // already loaded + + Ogre::Vector2 center(x+0.5, y+0.5); + float minH, maxH; + if (!mStorage->getMinMaxHeights(1, center, minH, maxH)) + return; // no terrain defined + + Ogre::Vector3 min (-0.5*mStorage->getCellWorldSize(), + -0.5*mStorage->getCellWorldSize(), + minH); + Ogre::Vector3 max (0.5*mStorage->getCellWorldSize(), + 0.5*mStorage->getCellWorldSize(), + maxH); + + Ogre::AxisAlignedBox bounds(min, max); + + GridElement element; + + Ogre::Vector2 worldCenter = center*mStorage->getCellWorldSize(); + element.mSceneNode = mRootNode->createChildSceneNode(Ogre::Vector3(worldCenter.x, worldCenter.y, 0)); + + std::vector positions; + std::vector normals; + std::vector colours; + mStorage->fillVertexBuffers(0, 1, center, mAlign, positions, normals, colours); + + element.mChunk = new Terrain::Chunk(mCache.getUVBuffer(), bounds, positions, normals, colours); + element.mChunk->setIndexBuffer(mCache.getIndexBuffer(0)); + + std::vector blendmaps; + std::vector layerList; + mStorage->getBlendmaps(1, center, mShaders, blendmaps, layerList); + + element.mMaterialGenerator.setLayerList(layerList); + + // upload blendmaps to GPU + std::vector blendTextures; + for (std::vector::const_iterator it = blendmaps.begin(); it != blendmaps.end(); ++it) + { + static int count=0; + Ogre::TexturePtr map = Ogre::TextureManager::getSingleton().createManual("terrain/blend/" + + Ogre::StringConverter::toString(count++), Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + Ogre::TEX_TYPE_2D, it->getWidth(), it->getHeight(), 0, it->format); + + Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(it->data, it->getWidth()*it->getHeight()*Ogre::PixelUtil::getNumElemBytes(it->format), true)); + map->loadRawData(stream, it->getWidth(), it->getHeight(), it->format); + blendTextures.push_back(map); + } + + element.mMaterialGenerator.setBlendmapList(blendTextures); + + element.mSceneNode->attachObject(element.mChunk); + updateMaterial(element); + + mGrid[std::make_pair(x,y)] = element; +} + +void TerrainGrid::updateMaterial(GridElement &element) +{ + element.mMaterialGenerator.enableShadows(getShadowsEnabled()); + element.mMaterialGenerator.enableSplitShadows(getSplitShadowsEnabled()); + element.mChunk->setMaterial(element.mMaterialGenerator.generate()); +} + +void TerrainGrid::unloadCell(int x, int y) +{ + Grid::iterator it = mGrid.find(std::make_pair(x,y)); + if (it == mGrid.end()) + return; + + GridElement& element = it->second; + delete element.mChunk; + element.mChunk = NULL; + + const std::vector& blendmaps = element.mMaterialGenerator.getBlendmapList(); + for (std::vector::const_iterator it = blendmaps.begin(); it != blendmaps.end(); ++it) + Ogre::TextureManager::getSingleton().remove((*it)->getName()); + + mSceneMgr->destroySceneNode(element.mSceneNode); + element.mSceneNode = NULL; + + mGrid.erase(it); +} + +void TerrainGrid::applyMaterials(bool shadows, bool splitShadows) +{ + mShadows = shadows; + mSplitShadows = splitShadows; + for (Grid::iterator it = mGrid.begin(); it != mGrid.end(); ++it) + { + updateMaterial(it->second); + } +} + +bool TerrainGrid::getVisible() +{ + return mVisible; +} + +void TerrainGrid::setVisible(bool visible) +{ + mVisible = visible; + mRootNode->setVisible(visible); +} + +Ogre::AxisAlignedBox TerrainGrid::getWorldBoundingBox (const Ogre::Vector2& center) +{ + int cellX, cellY; + MWBase::Environment::get().getWorld()->positionToIndex(center.x, center.y, cellX, cellY); + + Grid::iterator it = mGrid.find(std::make_pair(cellX, cellY)); + if (it == mGrid.end()) + return Ogre::AxisAlignedBox::BOX_NULL; + + Terrain::Chunk* chunk = it->second.mChunk; + Ogre::SceneNode* node = it->second.mSceneNode; + Ogre::AxisAlignedBox box = chunk->getBoundingBox(); + box = Ogre::AxisAlignedBox(box.getMinimum() + node->getPosition(), box.getMaximum() + node->getPosition()); + return box; +} + +void TerrainGrid::syncLoad() +{ + +} + +} diff --git a/apps/openmw/mwrender/terraingrid.hpp b/apps/openmw/mwrender/terraingrid.hpp new file mode 100644 index 0000000000..1b5250dcfe --- /dev/null +++ b/apps/openmw/mwrender/terraingrid.hpp @@ -0,0 +1,75 @@ +#ifndef OPENMW_MWRENDER_TERRAINGRID_H +#define OPENMW_MWRENDER_TERRAINGRID_H + +#include +#include + +namespace Terrain +{ + class Chunk; +} + +namespace MWRender +{ + + struct GridElement + { + Ogre::SceneNode* mSceneNode; + + Terrain::MaterialGenerator mMaterialGenerator; + + Terrain::Chunk* mChunk; + }; + + /// @brief Simple terrain implementation that loads cells in a grid, with no LOD + class TerrainGrid : public Terrain::World + { + public: + /// @note takes ownership of \a storage + /// @param sceneMgr scene manager to use + /// @param storage Storage instance to get terrain data from (heights, normals, colors, textures..) + /// @param visbilityFlags visibility flags for the created meshes + /// @param shaders Whether to use splatting shader, or multi-pass fixed function splatting. Shader is usually + /// faster so this is just here for compatibility. + /// @param align The align of the terrain, see Alignment enum + TerrainGrid(Ogre::SceneManager* sceneMgr, + Terrain::Storage* storage, int visibilityFlags, bool shaders, Terrain::Alignment align); + ~TerrainGrid(); + + /// Update chunk LODs according to this camera position + virtual void update (const Ogre::Vector3& cameraPos); + + virtual void loadCell(int x, int y); + virtual void unloadCell(int x, int y); + + /// Get the world bounding box of a chunk of terrain centered at \a center + virtual Ogre::AxisAlignedBox getWorldBoundingBox (const Ogre::Vector2& center); + + /// Show or hide the whole terrain + /// @note this setting may be invalidated once you call Terrain::update, so do not call it while the terrain should be hidden + virtual void setVisible(bool visible); + virtual bool getVisible(); + + /// Recreate materials used by terrain chunks. This should be called whenever settings of + /// the material factory are changed. (Relying on the factory to update those materials is not + /// enough, since turning a feature on/off can change the number of texture units available for layer/blend + /// textures, and to properly respond to this we may need to change the structure of the material, such as + /// adding or removing passes. This can only be achieved by a full rebuild.) + virtual void applyMaterials(bool shadows, bool splitShadows); + + /// Wait until all background loading is complete. + virtual void syncLoad(); + + private: + void updateMaterial (GridElement& element); + + typedef std::map, GridElement> Grid; + Grid mGrid; + + Ogre::SceneNode* mRootNode; + bool mVisible; + }; + +} + +#endif diff --git a/apps/openmw/mwrender/terrainstorage.hpp b/apps/openmw/mwrender/terrainstorage.hpp index b5e6012f3a..c1fb744451 100644 --- a/apps/openmw/mwrender/terrainstorage.hpp +++ b/apps/openmw/mwrender/terrainstorage.hpp @@ -46,7 +46,7 @@ namespace MWRender /// Create textures holding layer blend values for a terrain chunk. /// @note The terrain chunk shouldn't be larger than one cell since otherwise we might /// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used. - /// @note May be called from *one* background thread. + /// @note May be called from background threads. /// @param chunkSize size of the terrain chunk in cell units /// @param chunkCenter center of the chunk in cell units /// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) - @@ -62,7 +62,7 @@ namespace MWRender /// This variant is provided to eliminate the overhead of virtual function calls when retrieving a large number of blendmaps at once. /// @note The terrain chunks shouldn't be larger than one cell since otherwise we might /// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used. - /// @note May be called from *one* background thread. + /// @note May be called from background threads. /// @param nodes A collection of nodes for which to retrieve the aforementioned data /// @param out Output vector /// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) - diff --git a/apps/openmw/mwrender/videoplayer.cpp b/apps/openmw/mwrender/videoplayer.cpp index 79f2fa9483..03e74697c1 100644 --- a/apps/openmw/mwrender/videoplayer.cpp +++ b/apps/openmw/mwrender/videoplayer.cpp @@ -747,7 +747,7 @@ double VideoState::synchronize_video(AVFrame *src_frame, double pts) * buffer. We use this to store the global_pts in * a frame at the time it is allocated. */ -static uint64_t global_video_pkt_pts = AV_NOPTS_VALUE; +static uint64_t global_video_pkt_pts = static_cast(AV_NOPTS_VALUE); static int our_get_buffer(struct AVCodecContext *c, AVFrame *pic) { int ret = avcodec_default_get_buffer(c, pic); diff --git a/apps/openmw/mwrender/weaponanimation.cpp b/apps/openmw/mwrender/weaponanimation.cpp index 5f953bd838..cd9ae16b8b 100644 --- a/apps/openmw/mwrender/weaponanimation.cpp +++ b/apps/openmw/mwrender/weaponanimation.cpp @@ -150,9 +150,19 @@ void WeaponAnimation::pitchSkeleton(float xrot, Ogre::SkeletonInstance* skel) return; float pitch = xrot * mPitchFactor; - Ogre::Node *node = skel->getBone("Bip01 Spine2"); + Ogre::Node *node; + + // In spherearcher.nif, we have spine, not Spine. Not sure if all bone names should be case insensitive? + if (skel->hasBone("Bip01 spine2")) + node = skel->getBone("Bip01 spine2"); + else + node = skel->getBone("Bip01 Spine2"); node->pitch(Ogre::Radian(-pitch/2), Ogre::Node::TS_WORLD); - node = skel->getBone("Bip01 Spine1"); + + if (skel->hasBone("Bip01 spine1")) // in spherearcher.nif + node = skel->getBone("Bip01 spine1"); + else + node = skel->getBone("Bip01 Spine1"); node->pitch(Ogre::Radian(-pitch/2), Ogre::Node::TS_WORLD); } diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index cc17905df6..d844138d69 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -186,7 +186,7 @@ namespace MWScript Interpreter::Type_Integer time = runtime[0].mFloat; runtime.pop(); - std::vector idleList; + std::vector idleList; bool repeat = false; for(int i=1; i < 10 && arg0; ++i) @@ -194,6 +194,7 @@ namespace MWScript if(!repeat) repeat = true; Interpreter::Type_Integer idleValue = runtime[0].mInteger; + idleValue = std::min(255, std::max(0, idleValue)); idleList.push_back(idleValue); runtime.pop(); --arg0; @@ -370,6 +371,12 @@ namespace MWScript MWWorld::Ptr actor = MWBase::Environment::get().getWorld()->getPtr(actorID, true); + if(!actor.getClass().isActor() || !observer.getClass().isActor()) + { + runtime.push(0); + return; + } + Interpreter::Type_Integer value = MWBase::Environment::get().getWorld()->getLOS(observer, actor) && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(actor, observer); @@ -391,9 +398,10 @@ namespace MWScript std::string actorID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); + MWWorld::Ptr dest = MWBase::Environment::get().getWorld()->getPtr(actorID,true); bool value = false; - if(dest != MWWorld::Ptr() ) + if(dest != MWWorld::Ptr() && source.getClass().isActor() && dest.getClass().isActor()) { value = MWBase::Environment::get().getWorld()->getLOS(source,dest); } @@ -469,6 +477,16 @@ namespace MWScript } }; + template + class OpFace : public Interpreter::Opcode0 + { + public: + virtual void execute(Interpreter::Runtime& runtime) + { + /// \todo implement + } + }; + void installOpcodes (Interpreter::Interpreter& interpreter) { interpreter.installSegment3 (Compiler::Ai::opcodeAIActivate, new OpAiActivate); @@ -530,6 +548,9 @@ namespace MWScript interpreter.installSegment5 (Compiler::Ai::opcodeGetFleeExplicit, new OpGetAiSetting(2)); interpreter.installSegment5 (Compiler::Ai::opcodeGetAlarm, new OpGetAiSetting(3)); interpreter.installSegment5 (Compiler::Ai::opcodeGetAlarmExplicit, new OpGetAiSetting(3)); + + interpreter.installSegment5 (Compiler::Ai::opcodeFace, new OpFace); + interpreter.installSegment5 (Compiler::Ai::opcodeFaceExplicit, new OpFace); } } } diff --git a/apps/openmw/mwscript/cellextensions.cpp b/apps/openmw/mwscript/cellextensions.cpp index 2e2e9b6985..94d7340295 100644 --- a/apps/openmw/mwscript/cellextensions.cpp +++ b/apps/openmw/mwscript/cellextensions.cpp @@ -47,6 +47,7 @@ namespace MWScript if (world->findExteriorPosition(cell, pos)) { world->changeToExteriorCell(pos); + world->fixPosition(world->getPlayerPtr()); } else { @@ -79,6 +80,7 @@ namespace MWScript pos.rot[0] = pos.rot[1] = pos.rot[2] = 0; world->changeToExteriorCell (pos); + world->fixPosition(world->getPlayerPtr()); } }; diff --git a/apps/openmw/mwscript/dialogueextensions.cpp b/apps/openmw/mwscript/dialogueextensions.cpp index 9dde65ab2c..ecaf4253e8 100644 --- a/apps/openmw/mwscript/dialogueextensions.cpp +++ b/apps/openmw/mwscript/dialogueextensions.cpp @@ -227,14 +227,23 @@ namespace MWScript std::string faction2 = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - // ignore extra garbage argument - runtime.pop(); - runtime.push(MWBase::Environment::get().getDialogueManager() ->getFactionReaction(faction1, faction2)); } }; + template + class OpClearInfoActor : public Interpreter::Opcode0 + { + public: + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + MWBase::Environment::get().getDialogueManager()->clearInfoActor(ptr); + } + }; + void installOpcodes (Interpreter::Interpreter& interpreter) { @@ -256,6 +265,8 @@ namespace MWScript interpreter.installSegment5 (Compiler::Dialogue::opcodeSameFactionExplicit, new OpSameFaction); interpreter.installSegment5 (Compiler::Dialogue::opcodeModFactionReaction, new OpModFactionReaction); interpreter.installSegment5 (Compiler::Dialogue::opcodeGetFactionReaction, new OpGetFactionReaction); + interpreter.installSegment5 (Compiler::Dialogue::opcodeClearInfoActor, new OpClearInfoActor); + interpreter.installSegment5 (Compiler::Dialogue::opcodeClearInfoActorExplicit, new OpClearInfoActor); } } diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index 24b0b6f7aa..319feee0e0 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -57,7 +57,8 @@ op 0x20028: RemoveSoulGem, explicit reference op 0x20029: PCRaiseRank, explicit reference op 0x2002a: PCLowerRank, explicit reference op 0x2002b: PCJoinFaction, explicit reference -opcodes 0x2002c-0x3ffff unused +op 0x2002c: MenuTest +opcodes 0x2002d-0x3ffff unused Segment 4: (not implemented yet) @@ -393,5 +394,14 @@ op 0x2000241: onKnockoutExplicit op 0x2000242: ModFactionReaction op 0x2000243: GetFactionReaction op 0x2000244: Activate, explicit +op 0x2000245: ClearInfoActor +op 0x2000246: ClearInfoActor, explicit +op 0x2000247: BetaComment +op 0x2000248: BetaComment, explicit +op 0x2000249: OnMurder +op 0x200024a: OnMurder, explicit +op 0x200024b: ToggleMenus +op 0x200024c: Face +op 0x200024d: Face, explicit -opcodes 0x2000245-0x3ffffff unused +opcodes 0x200024e-0x3ffffff unused diff --git a/apps/openmw/mwscript/guiextensions.cpp b/apps/openmw/mwscript/guiextensions.cpp index be241a5649..afc745beb9 100644 --- a/apps/openmw/mwscript/guiextensions.cpp +++ b/apps/openmw/mwscript/guiextensions.cpp @@ -162,6 +162,56 @@ namespace MWScript } }; + class OpMenuTest : public Interpreter::Opcode1 + { + public: + + virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) + { + int arg=0; + if(arg0>0) + { + arg = runtime[0].mInteger; + runtime.pop(); + } + + + if (arg == 0) + { + MWGui::GuiMode modes[] = { MWGui::GM_Inventory, MWGui::GM_Container }; + + for (int i=0; i<2; ++i) + { + if (MWBase::Environment::get().getWindowManager()->containsMode(modes[i])) + MWBase::Environment::get().getWindowManager()->removeGuiMode(modes[i]); + } + } + else + { + MWGui::GuiWindow gw = MWGui::GW_None; + if (arg == 3) + gw = MWGui::GW_Stats; + if (arg == 4) + gw = MWGui::GW_Inventory; + if (arg == 5) + gw = MWGui::GW_Magic; + if (arg == 6) + gw = MWGui::GW_Map; + + MWBase::Environment::get().getWindowManager()->pinWindow(gw); + } + } + }; + + class OpToggleMenus : public Interpreter::Opcode0 + { + public: + virtual void execute(Interpreter::Runtime &runtime) + { + bool state = MWBase::Environment::get().getWindowManager()->toggleGui(); + runtime.getContext().report(state ? "GUI -> On" : "GUI -> Off"); + } + }; void installOpcodes (Interpreter::Interpreter& interpreter) { @@ -200,6 +250,8 @@ namespace MWScript interpreter.installSegment5 (Compiler::Gui::opcodeShowMap, new OpShowMap); interpreter.installSegment5 (Compiler::Gui::opcodeFillMap, new OpFillMap); + interpreter.installSegment3 (Compiler::Gui::opcodeMenuTest, new OpMenuTest); + interpreter.installSegment5 (Compiler::Gui::opcodeToggleMenus, new OpToggleMenus); } } } diff --git a/apps/openmw/mwscript/interpretercontext.cpp b/apps/openmw/mwscript/interpretercontext.cpp index 6bf50371b8..028f83a61a 100644 --- a/apps/openmw/mwscript/interpretercontext.cpp +++ b/apps/openmw/mwscript/interpretercontext.cpp @@ -378,12 +378,20 @@ namespace MWScript float InterpreterContext::getDistance (const std::string& name, const std::string& id) const { - const MWWorld::Ptr ref2 = getReference (id, false, false); + // NOTE: id may be empty, indicating an implicit reference + + MWWorld::Ptr ref2; + + if (id.empty()) + ref2 = getReference("", true, true); + else + ref2 = MWBase::Environment::get().getWorld()->searchPtr(id, true); + // If either actor is in a non-active cell, return a large value (just like vanilla) if (ref2.isEmpty()) return std::numeric_limits().max(); - const MWWorld::Ptr ref = getReference (name, false, false); + const MWWorld::Ptr ref = MWBase::Environment::get().getWorld()->searchPtr(name, true); if (ref.isEmpty()) return std::numeric_limits().max(); @@ -419,11 +427,10 @@ namespace MWScript mActivationHandled = false; } - void InterpreterContext::executeActivation(MWWorld::Ptr ptr) + void InterpreterContext::executeActivation(MWWorld::Ptr ptr, MWWorld::Ptr actor) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - boost::shared_ptr action = (ptr.getClass().activate(ptr, player)); - action->execute (player); + boost::shared_ptr action = (ptr.getClass().activate(ptr, actor)); + action->execute (actor); if (mActivated == ptr) mActivationHandled = true; } diff --git a/apps/openmw/mwscript/interpretercontext.hpp b/apps/openmw/mwscript/interpretercontext.hpp index 1137efed31..99026392ed 100644 --- a/apps/openmw/mwscript/interpretercontext.hpp +++ b/apps/openmw/mwscript/interpretercontext.hpp @@ -118,6 +118,7 @@ namespace MWScript virtual void stopScript (const std::string& name); 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 @@ -129,7 +130,7 @@ namespace MWScript ///< Store reference acted upon. The actual execution of the action does not /// take place here. - void executeActivation(MWWorld::Ptr ptr); + void executeActivation(MWWorld::Ptr ptr, MWWorld::Ptr actor); ///< Execute the activation action for this ptr. If ptr is mActivated, mark activation as handled. void clearActivation(); diff --git a/apps/openmw/mwscript/locals.cpp b/apps/openmw/mwscript/locals.cpp index 094fe085a2..177536fe9e 100644 --- a/apps/openmw/mwscript/locals.cpp +++ b/apps/openmw/mwscript/locals.cpp @@ -21,6 +21,11 @@ namespace MWScript mFloats.resize (script.mData.mNumFloats, 0); } + bool Locals::isEmpty() const + { + return (mShorts.empty() && mLongs.empty() && mFloats.empty()); + } + int Locals::getIntVar(const std::string &script, const std::string &var) { Compiler::Locals locals = MWBase::Environment::get().getScriptManager()->getLocals(script); diff --git a/apps/openmw/mwscript/locals.hpp b/apps/openmw/mwscript/locals.hpp index d17d1be2dc..1e8c6e12a4 100644 --- a/apps/openmw/mwscript/locals.hpp +++ b/apps/openmw/mwscript/locals.hpp @@ -20,6 +20,9 @@ namespace MWScript std::vector mLongs; std::vector mFloats; + /// Are there any locals? + bool isEmpty() const; + void configure (const ESM::Script& script); bool setVarByInt(const std::string& script, const std::string& var, int val); int getIntVar (const std::string& script, const std::string& var); ///< if var does not exist, returns 0 diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 9d6d5e50dc..76b99d04ab 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -121,7 +121,7 @@ namespace MWScript MWWorld::Ptr ptr = R()(runtime); - context.executeActivation(ptr); + context.executeActivation(ptr, MWBase::Environment::get().getWorld()->getPlayerPtr()); } }; @@ -859,6 +859,47 @@ namespace MWScript } }; + template + class OpBetaComment : public Interpreter::Opcode0 + { + public: + virtual void execute(Interpreter::Runtime &runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + std::stringstream msg; + + msg << "Content file: "; + + if (ptr.getCellRef().getRefNum().mContentFile == -1) + msg << "[None]" << std::endl; + else + { + std::vector contentFiles = MWBase::Environment::get().getWorld()->getContentFiles(); + + msg << contentFiles.at (ptr.getCellRef().getRefNum().mContentFile) << std::endl; + } + + msg << "RefID: " << ptr.getCellRef().getRefId() << std::endl; + + if (ptr.isInCell()) + { + MWWorld::CellStore* cell = ptr.getCell(); + msg << "Cell: " << MWBase::Environment::get().getWorld()->getCellName(cell) << std::endl; + + Ogre::Vector3 pos (ptr.getRefData().getPosition().pos); + msg << "Coordinates: " << pos << std::endl; + } + + std::string notes = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + if (!notes.empty()) + msg << "Notes: " << notes << std::endl; + + runtime.getContext().report(msg.str()); + } + }; + void installOpcodes (Interpreter::Interpreter& interpreter) { interpreter.installSegment5 (Compiler::Misc::opcodeXBox, new OpXBox); @@ -932,6 +973,8 @@ namespace MWScript interpreter.installSegment5 (Compiler::Misc::opcodeExplodeSpellExplicit, new OpExplodeSpell); interpreter.installSegment5 (Compiler::Misc::opcodeGetPcInJail, new OpGetPcInJail); interpreter.installSegment5 (Compiler::Misc::opcodeGetPcTraveling, new OpGetPcTraveling); + interpreter.installSegment5 (Compiler::Misc::opcodeBetaComment, new OpBetaComment); + interpreter.installSegment5 (Compiler::Misc::opcodeBetaCommentExplicit, new OpBetaComment); } } } diff --git a/apps/openmw/mwscript/scriptmanagerimp.cpp b/apps/openmw/mwscript/scriptmanagerimp.cpp index 74c85dbbf7..7b858dacf5 100644 --- a/apps/openmw/mwscript/scriptmanagerimp.cpp +++ b/apps/openmw/mwscript/scriptmanagerimp.cpp @@ -64,12 +64,12 @@ namespace MWScript Success = false; } - if (!Success && mVerbose) + if (!Success) { std::cerr - << "compiling failed: " << name << std::endl - << script->mScriptText - << std::endl << std::endl; + << "compiling failed: " << name << std::endl; + if (mVerbose) + std::cerr << script->mScriptText << std::endl << std::endl; } if (Success) diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 8b61237a1d..5c47dea0f2 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -587,7 +587,9 @@ namespace MWScript } else { - player.getClass().getNpcStats(player).getFactionRanks()[factionID] = player.getClass().getNpcStats(player).getFactionRanks()[factionID] +1; + player.getClass().getNpcStats(player).getFactionRanks()[factionID] = + std::min(player.getClass().getNpcStats(player).getFactionRanks()[factionID] +1, + 9); } } } @@ -621,7 +623,8 @@ namespace MWScript MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); if(player.getClass().getNpcStats(player).getFactionRanks().find(factionID) != player.getClass().getNpcStats(player).getFactionRanks().end()) { - player.getClass().getNpcStats(player).getFactionRanks()[factionID] = player.getClass().getNpcStats(player).getFactionRanks()[factionID] -1; + player.getClass().getNpcStats(player).getFactionRanks()[factionID] = + std::max(0, player.getClass().getNpcStats(player).getFactionRanks()[factionID]-1); } } } @@ -688,8 +691,11 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - ptr.getClass().getNpcStats (ptr).setBaseDisposition - (ptr.getClass().getNpcStats (ptr).getBaseDisposition() + value); + if (ptr.getClass().isNpc()) + ptr.getClass().getNpcStats (ptr).setBaseDisposition + (ptr.getClass().getNpcStats (ptr).getBaseDisposition() + value); + + // else: must not throw exception (used by an Almalexia dialogue script) } }; @@ -705,7 +711,8 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - ptr.getClass().getNpcStats (ptr).setBaseDisposition (value); + if (ptr.getClass().isNpc()) + ptr.getClass().getNpcStats (ptr).setBaseDisposition (value); } }; @@ -718,7 +725,10 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - runtime.push (MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(ptr)); + if (!ptr.getClass().isNpc()) + runtime.push(0); + else + runtime.push (MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(ptr)); } }; @@ -1022,7 +1032,7 @@ namespace MWScript return; std::map& ranks = ptr.getClass().getNpcStats(ptr).getFactionRanks (); - ranks[factionID] = ranks[factionID]+1; + ranks[factionID] = std::min(9, ranks[factionID]+1); } }; @@ -1049,7 +1059,7 @@ namespace MWScript return; std::map& ranks = ptr.getClass().getNpcStats(ptr).getFactionRanks (); - ranks[factionID] = ranks[factionID]-1; + ranks[factionID] = std::max(0, ranks[factionID]-1); } }; @@ -1072,6 +1082,25 @@ namespace MWScript } }; + template + class OpOnMurder : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + Interpreter::Type_Integer value = + ptr.getClass().getCreatureStats (ptr).hasBeenMurdered(); + + if (value) + ptr.getClass().getCreatureStats (ptr).clearHasBeenMurdered(); + + runtime.push (value); + } + }; + template class OpOnKnockout : public Interpreter::Opcode0 { @@ -1264,6 +1293,8 @@ namespace MWScript interpreter.installSegment5 (Compiler::Stats::opcodeOnDeath, new OpOnDeath); interpreter.installSegment5 (Compiler::Stats::opcodeOnDeathExplicit, new OpOnDeath); + interpreter.installSegment5 (Compiler::Stats::opcodeOnMurder, new OpOnMurder); + interpreter.installSegment5 (Compiler::Stats::opcodeOnMurderExplicit, new OpOnMurder); interpreter.installSegment5 (Compiler::Stats::opcodeOnKnockout, new OpOnKnockout); interpreter.installSegment5 (Compiler::Stats::opcodeOnKnockoutExplicit, new OpOnKnockout); diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index a944a31b8d..3860257ad6 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -323,7 +323,7 @@ namespace MWScript } else { - throw std::runtime_error ("unknown cell"); + throw std::runtime_error (std::string("unknown cell (") + cellID + ")"); } } }; @@ -420,7 +420,7 @@ namespace MWScript } else { - throw std::runtime_error ("unknown cell"); + throw std::runtime_error ( std::string("unknown cell (") + cellID + ")"); } } }; @@ -578,7 +578,7 @@ namespace MWScript Interpreter::Type_Float rotation = (runtime[0].mFloat*MWBase::Environment::get().getFrameDuration()); runtime.pop(); - float *objRot = ptr.getRefData().getPosition().rot; + const float *objRot = ptr.getRefData().getPosition().rot; float ax = Ogre::Radian(objRot[0]).valueDegrees(); float ay = Ogre::Radian(objRot[1]).valueDegrees(); @@ -613,9 +613,12 @@ namespace MWScript if (!ptr.isInCell()) return; - ptr.getRefData().getLocalRotation().rot[0] = 0; - ptr.getRefData().getLocalRotation().rot[1] = 0; - ptr.getRefData().getLocalRotation().rot[2] = 0; + MWWorld::LocalRotation rot; + rot.rot[0] = 0; + rot.rot[1] = 0; + rot.rot[2] = 0; + ptr.getRefData().setLocalRotation(rot); + MWBase::Environment::get().getWorld()->rotateObject(ptr, 0,0,0,true); MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().pos[0], ptr.getCellRef().getPosition().pos[1], ptr.getCellRef().getPosition().pos[2]); @@ -678,7 +681,7 @@ namespace MWScript Interpreter::Type_Float movement = (runtime[0].mFloat*MWBase::Environment::get().getFrameDuration()); runtime.pop(); - float *objPos = ptr.getRefData().getPosition().pos; + const float *objPos = ptr.getRefData().getPosition().pos; if (axis == "x") { diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index 75f7ccae4c..10a782b964 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -159,17 +159,21 @@ void FFmpeg_Decoder::open(const std::string &fname) mFormatCtx->pb = avio_alloc_context(NULL, 0, 0, this, readPacket, writePacket, seek); if(!mFormatCtx->pb || avformat_open_input(&mFormatCtx, fname.c_str(), NULL, NULL) != 0) { - if (mFormatCtx->pb != NULL) + // "Note that a user-supplied AVFormatContext will be freed on failure". + if (mFormatCtx) { - if (mFormatCtx->pb->buffer != NULL) - { - av_free(mFormatCtx->pb->buffer); - mFormatCtx->pb->buffer = NULL; - } - av_free(mFormatCtx->pb); - mFormatCtx->pb = NULL; + if (mFormatCtx->pb != NULL) + { + if (mFormatCtx->pb->buffer != NULL) + { + av_free(mFormatCtx->pb->buffer); + mFormatCtx->pb->buffer = NULL; + } + av_free(mFormatCtx->pb); + mFormatCtx->pb = NULL; + } + avformat_free_context(mFormatCtx); } - avformat_free_context(mFormatCtx); mFormatCtx = NULL; fail("Failed to allocate input stream"); } diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 4a3093b10d..ba7b4f3bab 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -42,6 +42,7 @@ namespace MWSound , mListenerPos(0,0,0) , mListenerDir(1,0,0) , mListenerUp(0,0,1) + , mListenerUnderwater(false) { if(!useSound) return; @@ -179,6 +180,7 @@ namespace MWSound if(!mOutput->isInitialized()) return; std::cout <<"Playing "<begin(), resourcesInThisGroup->end()); + Ogre::StringVector groups = Ogre::ResourceGroupManager::getSingleton().getResourceGroups (); + for (Ogre::StringVector::iterator it = groups.begin(); it != groups.end(); ++it) + { + Ogre::StringVectorPtr resourcesInThisGroup = mResourceMgr.findResourceNames(*it, + "Music/"+mCurrentPlaylist+"/*"); + filelist.insert(filelist.end(), resourcesInThisGroup->begin(), resourcesInThisGroup->end()); + } + mMusicFiles[mCurrentPlaylist] = filelist; } + else + filelist = mMusicFiles[mCurrentPlaylist]; if(!filelist.size()) return; int i = rand()%filelist.size(); + + // Don't play the same music track twice in a row + if (filelist[i] == mLastPlayedMusic) + { + i = (i+1) % filelist.size(); + } + streamMusicFull(filelist[i]); } @@ -372,7 +386,7 @@ namespace MWSound sound = mOutput->playSound3D(file, initialPos, volume, basevol, pitch, min, max, mode|type, offset); mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), soundId); } - catch(std::exception &e) + catch(std::exception &) { //std::cout <<"Sound Error: "<getPlayerPtr(); - const ESM::Cell *cell = player.getCell()->getCell(); - Environment env = Env_Normal; - if((cell->mData.mFlags&cell->HasWater) && mListenerPos.z < cell->mWater) + if (mListenerUnderwater) { env = Env_Underwater; //play underwater sound @@ -669,6 +679,12 @@ namespace MWSound mListenerPos = pos; mListenerDir = dir; mListenerUp = up; + + MWWorld::Ptr player = + MWBase::Environment::get().getWorld()->getPlayerPtr(); + const ESM::Cell *cell = player.getCell()->getCell(); + + mListenerUnderwater = ((cell->mData.mFlags&cell->HasWater) && mListenerPos.z < cell->mWater); } void SoundManager::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &updated) diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index ab9dcf7345..380cfe2552 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -31,6 +31,10 @@ namespace MWSound std::auto_ptr mOutput; + // Caches available music tracks by + std::map mMusicFiles; + std::string mLastPlayedMusic; // The music file that was last played + float mMasterVolume; float mSFXVolume; float mMusicVolume; @@ -46,6 +50,7 @@ namespace MWSound MWBase::SoundPtr mUnderwaterSound; + bool mListenerUnderwater; Ogre::Vector3 mListenerPos; Ogre::Vector3 mListenerDir; Ogre::Vector3 mListenerUp; diff --git a/apps/openmw/mwstate/character.cpp b/apps/openmw/mwstate/character.cpp index 5fe80ce0ca..6f569f078c 100644 --- a/apps/openmw/mwstate/character.cpp +++ b/apps/openmw/mwstate/character.cpp @@ -54,9 +54,28 @@ void MWState::Character::addSlot (const ESM::SavedGame& profile) Slot slot; std::ostringstream stream; - stream << mNext++; + + // The profile description is user-supplied, so we need to escape the path + for (std::string::const_iterator it = profile.mDescription.begin(); it != profile.mDescription.end(); ++it) + { + if (std::isalnum(*it)) // Ignores multibyte characters and non alphanumeric characters + stream << *it; + else + stream << "_"; + } slot.mPath = mPath / stream.str(); + + // Append an index if necessary to ensure a unique file + int i=0; + while (boost::filesystem::exists(slot.mPath)) + { + std::ostringstream test; + test << stream.str(); + test << " - " << ++i; + slot.mPath = mPath / test.str(); + } + slot.mProfile = profile; slot.mTimeStamp = std::time (0); @@ -64,7 +83,7 @@ void MWState::Character::addSlot (const ESM::SavedGame& profile) } MWState::Character::Character (const boost::filesystem::path& saves, const std::string& game) -: mPath (saves), mNext (0) +: mPath (saves) { if (!boost::filesystem::is_directory (mPath)) { @@ -82,13 +101,6 @@ MWState::Character::Character (const boost::filesystem::path& saves, const std:: addSlot (slotPath, game); } catch (...) {} // ignoring bad saved game files for now - - std::istringstream stream (slotPath.filename().string()); - - int index = 0; - - if ((stream >> index) && index>=mNext) - mNext = index+1; } std::sort (mSlots.begin(), mSlots.end()); diff --git a/apps/openmw/mwstate/character.hpp b/apps/openmw/mwstate/character.hpp index 8745332892..4703f0cca3 100644 --- a/apps/openmw/mwstate/character.hpp +++ b/apps/openmw/mwstate/character.hpp @@ -26,7 +26,6 @@ namespace MWState boost::filesystem::path mPath; std::vector mSlots; - int mNext; void addSlot (const boost::filesystem::path& path, const std::string& game); diff --git a/apps/openmw/mwstate/charactermanager.cpp b/apps/openmw/mwstate/charactermanager.cpp index d773904db2..70e9f09258 100644 --- a/apps/openmw/mwstate/charactermanager.cpp +++ b/apps/openmw/mwstate/charactermanager.cpp @@ -3,12 +3,13 @@ #include #include +#include // std::isalnum #include MWState::CharacterManager::CharacterManager (const boost::filesystem::path& saves, const std::string& game) -: mPath (saves), mNext (0), mCurrent (0), mGame (game) +: mPath (saves), mCurrent (0), mGame (game) { if (!boost::filesystem::is_directory (mPath)) { @@ -28,21 +29,14 @@ MWState::CharacterManager::CharacterManager (const boost::filesystem::path& save if (character.begin()!=character.end()) mCharacters.push_back (character); } - - std::istringstream stream (characterDir.filename().string()); - - int index = 0; - - if ((stream >> index) && index>=mNext) - mNext = index+1; } } } -MWState::Character *MWState::CharacterManager::getCurrentCharacter (bool create) +MWState::Character *MWState::CharacterManager::getCurrentCharacter (bool create, const std::string& name) { if (!mCurrent && create) - createCharacter(); + createCharacter(name); return mCurrent; } @@ -63,13 +57,31 @@ void MWState::CharacterManager::deleteSlot(const MWState::Character *character, } } -void MWState::CharacterManager::createCharacter() +void MWState::CharacterManager::createCharacter(const std::string& name) { std::ostringstream stream; - stream << mNext++; + + // The character name is user-supplied, so we need to escape the path + for (std::string::const_iterator it = name.begin(); it != name.end(); ++it) + { + if (std::isalnum(*it)) // Ignores multibyte characters and non alphanumeric characters + stream << *it; + else + stream << "_"; + } boost::filesystem::path path = mPath / stream.str(); + // Append an index if necessary to ensure a unique directory + int i=0; + while (boost::filesystem::exists(path)) + { + std::ostringstream test; + test << stream.str(); + test << " - " << ++i; + path = mPath / test.str(); + } + mCharacters.push_back (Character (path, mGame)); mCurrent = &mCharacters.back(); diff --git a/apps/openmw/mwstate/charactermanager.hpp b/apps/openmw/mwstate/charactermanager.hpp index adf9d2ef44..c44c10b5a2 100644 --- a/apps/openmw/mwstate/charactermanager.hpp +++ b/apps/openmw/mwstate/charactermanager.hpp @@ -10,7 +10,6 @@ namespace MWState class CharacterManager { boost::filesystem::path mPath; - int mNext; // Uses std::list, so that mCurrent stays valid when characters are deleted std::list mCharacters; @@ -32,13 +31,15 @@ namespace MWState CharacterManager (const boost::filesystem::path& saves, const std::string& game); - Character *getCurrentCharacter (bool create = true); + Character *getCurrentCharacter (bool create, const std::string& name); ///< \param create Create a new character, if there is no current character. + /// \param name The character name to use in case a new character is created. void deleteSlot(const MWState::Character *character, const MWState::Slot *slot); - void createCharacter(); + void createCharacter(const std::string& name); ///< Create new character within saved game management + /// \param name Name for the character (does not need to be unique) void setCurrentCharacter (const Character *character); diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 0e56365d60..a3604cc66e 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -45,6 +45,7 @@ void MWState::StateManager::cleanup (bool force) MWBase::Environment::get().getWorld()->clear(); MWBase::Environment::get().getWindowManager()->clear(); MWBase::Environment::get().getInputManager()->clear(); + MWBase::Environment::get().getMechanicsManager()->clear(); mState = State_NoGame; mCharacterManager.clearCurrentCharacter(); @@ -184,9 +185,9 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot encoded->read(&profile.mScreenshot[0], encoded->size()); if (!slot) - slot = mCharacterManager.getCurrentCharacter()->createSlot (profile); + slot = getCurrentCharacter()->createSlot (profile); else - slot = mCharacterManager.getCurrentCharacter()->updateSlot (slot, profile); + slot = getCurrentCharacter()->updateSlot (slot, profile); boost::filesystem::ofstream stream (slot->mPath, std::ios::binary); @@ -200,12 +201,20 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot writer.addMaster (*iter, 0); // not using the size information anyway -> use value of 0 writer.setFormat (ESM::Header::CurrentFormat); + + // all unused + writer.setVersion(0); + writer.setType(0); + writer.setAuthor(""); + writer.setDescription(""); + int recordCount = 1 // saved game header +MWBase::Environment::get().getJournal()->countSavedGameRecords() +MWBase::Environment::get().getWorld()->countSavedGameRecords() +MWBase::Environment::get().getScriptManager()->getGlobalScripts().countSavedGameRecords() +MWBase::Environment::get().getDialogueManager()->countSavedGameRecords() - +MWBase::Environment::get().getWindowManager()->countSavedGameRecords(); + +MWBase::Environment::get().getWindowManager()->countSavedGameRecords() + +MWBase::Environment::get().getMechanicsManager()->countSavedGameRecords(); writer.setRecordCount (recordCount); writer.save (stream); @@ -226,6 +235,7 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot MWBase::Environment::get().getWorld()->write (writer, listener); MWBase::Environment::get().getScriptManager()->getGlobalScripts().write (writer, listener); MWBase::Environment::get().getWindowManager()->write(writer, listener); + MWBase::Environment::get().getMechanicsManager()->write(writer, listener); // Ensure we have written the number of records that was estimated if (writer.getRecordCount() != recordCount+1) // 1 extra for TES3 record @@ -233,6 +243,9 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot writer.close(); + if (stream.fail()) + throw std::runtime_error("Write operation failed"); + Settings::Manager::setString ("character", "Saves", slot->mPath.parent_path().filename().string()); } @@ -246,6 +259,10 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot std::vector buttons; buttons.push_back("#{sOk}"); MWBase::Environment::get().getWindowManager()->messageBox(error.str(), buttons); + + // If no file was written, clean up the slot + if (slot && !boost::filesystem::exists(slot->mPath)) + getCurrentCharacter()->deleteSlot(slot); } } @@ -350,6 +367,11 @@ void MWState::StateManager::loadGame (const Character *character, const Slot *sl MWBase::Environment::get().getWindowManager()->readRecord(reader, n.val); break; + case ESM::REC_DCOU: + + MWBase::Environment::get().getMechanicsManager()->readRecord(reader, n.val); + break; + default: // ignore invalid records @@ -412,7 +434,10 @@ void MWState::StateManager::deleteGame(const MWState::Character *character, cons MWState::Character *MWState::StateManager::getCurrentCharacter (bool create) { - return mCharacterManager.getCurrentCharacter (create); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + std::string name = player.getClass().getName(player); + + return mCharacterManager.getCurrentCharacter (create, name); } MWState::StateManager::CharacterIterator MWState::StateManager::characterBegin() @@ -433,11 +458,12 @@ void MWState::StateManager::update (float duration) if (mAskLoadRecent) { int iButton = MWBase::Environment::get().getWindowManager()->readPressedButton(); - if(iButton==0) + MWState::Character *curCharacter = getCurrentCharacter(false); + if(iButton==0 && curCharacter) { mAskLoadRecent = false; //Load last saved game for current character - MWState::Character *curCharacter = getCurrentCharacter(); + MWState::Slot lastSave = *curCharacter->begin(); loadGame(curCharacter, &lastSave); } diff --git a/apps/openmw/mwworld/actionapply.cpp b/apps/openmw/mwworld/actionapply.cpp index 6b12cc3e65..bfd64c85d8 100644 --- a/apps/openmw/mwworld/actionapply.cpp +++ b/apps/openmw/mwworld/actionapply.cpp @@ -6,30 +6,36 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "../mwworld/containerstore.hpp" + namespace MWWorld { - ActionApply::ActionApply (const Ptr& target, const std::string& id) - : Action (false, target), mId (id) + ActionApply::ActionApply (const Ptr& object, const std::string& id) + : Action (false, object), mId (id) {} void ActionApply::executeImp (const Ptr& actor) { MWBase::Environment::get().getWorld()->breakInvisibility(actor); - getTarget().getClass().apply (getTarget(), mId, actor); + actor.getClass().apply (actor, mId, actor); + + actor.getClass().getContainerStore(actor).remove(getTarget(), 1, actor); } - ActionApplyWithSkill::ActionApplyWithSkill (const Ptr& target, const std::string& id, + ActionApplyWithSkill::ActionApplyWithSkill (const Ptr& object, const std::string& id, int skillIndex, int usageType) - : Action (false, target), mId (id), mSkillIndex (skillIndex), mUsageType (usageType) + : Action (false, object), mId (id), mSkillIndex (skillIndex), mUsageType (usageType) {} void ActionApplyWithSkill::executeImp (const Ptr& actor) { MWBase::Environment::get().getWorld()->breakInvisibility(actor); - if (getTarget().getClass().apply (getTarget(), mId, actor) && mUsageType!=-1) - getTarget().getClass().skillUsageSucceeded (actor, mSkillIndex, mUsageType); + if (actor.getClass().apply (actor, mId, actor) && mUsageType!=-1) + actor.getClass().skillUsageSucceeded (actor, mSkillIndex, mUsageType); + + actor.getClass().getContainerStore(actor).remove(getTarget(), 1, actor); } } diff --git a/apps/openmw/mwworld/actionapply.hpp b/apps/openmw/mwworld/actionapply.hpp index 669a190675..5294745a61 100644 --- a/apps/openmw/mwworld/actionapply.hpp +++ b/apps/openmw/mwworld/actionapply.hpp @@ -16,7 +16,7 @@ namespace MWWorld public: - ActionApply (const Ptr& target, const std::string& id); + ActionApply (const Ptr& object, const std::string& id); }; class ActionApplyWithSkill : public Action @@ -29,7 +29,7 @@ namespace MWWorld public: - ActionApplyWithSkill (const Ptr& target, const std::string& id, + ActionApplyWithSkill (const Ptr& object, const std::string& id, int skillIndex, int usageType); }; } diff --git a/apps/openmw/mwworld/cellref.hpp b/apps/openmw/mwworld/cellref.hpp index 4db362b1e0..689671c025 100644 --- a/apps/openmw/mwworld/cellref.hpp +++ b/apps/openmw/mwworld/cellref.hpp @@ -42,7 +42,8 @@ namespace MWWorld float getScale() const; void setScale(float scale); - // Position and rotation of this object within the cell + // The *original* position and rotation as it was given in the Construction Set. + // Current position and rotation of the object is stored in RefData. ESM::Position getPosition() const; void setPosition (const ESM::Position& position); diff --git a/apps/openmw/mwworld/cells.cpp b/apps/openmw/mwworld/cells.cpp index 16c1b497cd..ef3d299a92 100644 --- a/apps/openmw/mwworld/cells.cpp +++ b/apps/openmw/mwworld/cells.cpp @@ -193,8 +193,10 @@ MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name) } // Then check cells that are already listed - for (std::map, CellStore>::iterator iter = mExteriors.begin(); - iter!=mExteriors.end(); ++iter) + // Search in reverse, this is a workaround for an ambiguous chargen_plank reference in the vanilla game. + // there is one at -22,16 and one at -2,-9, the latter should be used. + for (std::map, CellStore>::reverse_iterator iter = mExteriors.rbegin(); + iter!=mExteriors.rend(); ++iter) { Ptr ptr = getPtrAndCache (name, iter->second); if (!ptr.isEmpty()) diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 63cdbfb1a3..ce8d77758c 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -410,6 +410,9 @@ namespace MWWorld loadRefs (store, esm); mState = State_Loaded; + + // TODO: the pathgrid graph only needs to be loaded for active cells, so move this somewhere else. + // In a simple test, loading the graph for all cells in MW + expansions took 200 ms mPathgridGraph.load(mCell); } } @@ -447,10 +450,25 @@ namespace MWWorld if (deleted) continue; + // Don't list reference if it was moved to a different cell. + ESM::MovedCellRefTracker::const_iterator iter = + std::find(mCell->mMovedRefs.begin(), mCell->mMovedRefs.end(), ref.mRefNum); + if (iter != mCell->mMovedRefs.end()) { + continue; + } + mIds.push_back (Misc::StringUtils::lowerCase (ref.mRefID)); } } + // List moved references, from separately tracked list. + for (ESM::CellRefTracker::const_iterator it = mCell->mLeasedRefs.begin(); it != mCell->mLeasedRefs.end(); ++it) + { + ESM::CellRef &ref = const_cast(*it); + + mIds.push_back(Misc::StringUtils::lowerCase(ref.mRefID)); + } + std::sort (mIds.begin(), mIds.end()); } diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index c13ecfab5a..446519b877 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -366,7 +366,7 @@ namespace MWWorld Class::copyToCell(const Ptr &ptr, CellStore &cell, const ESM::Position &pos) const { Ptr newPtr = copyToCell(ptr, cell); - newPtr.getRefData().getPosition() = pos; + newPtr.getRefData().setPosition(pos); return newPtr; } diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 8a076c3fc0..e330ddaeed 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -92,6 +92,8 @@ void MWWorld::ContainerStore::storeStates (const CellRefList& collection, for (typename CellRefList::List::const_iterator iter (collection.mList.begin()); iter!=collection.mList.end(); ++iter) { + if (iter->mData.getCount() == 0) + continue; ESM::ObjectState state; storeState (*iter, state); int slot = equipable ? getSlot (*iter) : -1; diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index cd6cc4a165..12831e7dc1 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -99,6 +99,13 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) } it->second->load(esm, id); + // DELE can also occur after the usual subrecords + if (esm.isNextSub("DELE")) { + esm.skipRecord(); + it->second->eraseStatic(id); + continue; + } + if (n.val==ESM::REC_DIAL) { dialogue = const_cast(mDialogs.find(id)); } else { diff --git a/apps/openmw/mwworld/manualref.hpp b/apps/openmw/mwworld/manualref.hpp index 3842e7ff1b..b77257e47e 100644 --- a/apps/openmw/mwworld/manualref.hpp +++ b/apps/openmw/mwworld/manualref.hpp @@ -35,6 +35,8 @@ namespace MWWorld cellRef.mTeleport = false; cellRef.mLockLevel = 0; cellRef.mReferenceBlocked = 0; + cellRef.mFltv = 0; + cellRef.mNam0 = 0; LiveCellRef ref(cellRef, base); diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index fde774662e..2013937612 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include @@ -26,10 +27,63 @@ #include "../mwworld/esmstore.hpp" #include "../mwworld/cellstore.hpp" +#include "../apps/openmw/mwrender/animation.hpp" +#include "../apps/openmw/mwbase/world.hpp" +#include "../apps/openmw/mwbase/environment.hpp" + #include "ptr.hpp" #include "class.hpp" using namespace Ogre; + +namespace +{ + +void animateCollisionShapes (std::map& map) +{ + for (std::map::iterator it = map.begin(); + it != map.end(); ++it) + { + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaHandle(it->first->mName); + if (ptr.isEmpty()) // Shouldn't happen + throw std::runtime_error("can't find Ptr"); + + MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(ptr); + if (!animation) // Shouldn't happen either, since keyframe-controlled objects are not batched in StaticGeometry + throw std::runtime_error("can't find Animation for " + ptr.getCellRef().getRefId()); + + OEngine::Physic::AnimatedShapeInstance& instance = it->second; + + std::map& shapes = instance.mAnimatedShapes; + for (std::map::iterator shapeIt = shapes.begin(); + shapeIt != shapes.end(); ++shapeIt) + { + + Ogre::Node* bone; + if (shapeIt->first.empty()) + // HACK: see NifSkeletonLoader::buildBones + bone = animation->getNode(" "); + else + bone = animation->getNode(shapeIt->first); + + if (bone == NULL) + throw std::runtime_error("can't find bone"); + + btCompoundShape* compound = dynamic_cast(instance.mCompound); + + btTransform trans; + trans.setOrigin(BtOgre::Convert::toBullet(bone->_getDerivedPosition())); + trans.setRotation(BtOgre::Convert::toBullet(bone->_getDerivedOrientation())); + + compound->getChildShape(shapeIt->second)->setLocalScaling(BtOgre::Convert::toBullet(bone->_getDerivedScale())); + compound->updateChildTransform(shapeIt->second, trans); + } + } +} + +} + + namespace MWWorld { @@ -164,7 +218,7 @@ namespace MWWorld public: - static Ogre::Vector3 traceDown(const MWWorld::Ptr &ptr, OEngine::Physic::PhysicEngine *engine) + static Ogre::Vector3 traceDown(const MWWorld::Ptr &ptr, OEngine::Physic::PhysicEngine *engine, float maxHeight) { const ESM::Position &refpos = ptr.getRefData().getPosition(); Ogre::Vector3 position(refpos.pos); @@ -173,9 +227,8 @@ namespace MWWorld if (!physicActor) return position; - const int maxHeight = 200.f; OEngine::Physic::ActorTracer tracer; - tracer.findGround(physicActor->getCollisionBody(), position, position-Ogre::Vector3(0,0,maxHeight), engine); + tracer.findGround(physicActor, position, position-Ogre::Vector3(0,0,maxHeight), engine); if(tracer.mFraction >= 1.0f) { physicActor->setOnGround(false); @@ -193,6 +246,11 @@ namespace MWWorld const ESM::Position &refpos = ptr.getRefData().getPosition(); Ogre::Vector3 position(refpos.pos); + // Early-out for totally static creatures + // (Not sure if gravity should still apply?) + if (!ptr.getClass().canWalk(ptr) && !ptr.getClass().canFly(ptr) && !ptr.getClass().canSwim(ptr)) + return position; + /* Anything to collide with? */ OEngine::Physic::PhysicActor *physicActor = engine->getCharacter(ptr.getRefData().getHandle()); if(!physicActor || !physicActor->getCollisionMode()) @@ -259,15 +317,22 @@ namespace MWWorld wasOnGround = physicActor->getOnGround(); // store current state tracer.doTrace(colobj, position, position - Ogre::Vector3(0,0,2), engine); // check if down 2 possible if(tracer.mFraction < 1.0f && getSlope(tracer.mPlaneNormal) <= sMaxSlope) + { isOnGround = true; + // if we're on the ground, don't try to fall any more + velocity.z = std::max(0.0f, velocity.z); + } } } - // NOTE: isOnGround was initialised false, so should stay false if falling or sliding horizontally - if(isOnGround) + // Now that we have the effective movement vector, apply wind forces to it + if (MWBase::Environment::get().getWorld()->isInStorm()) { - // if we're on the ground, don't try to fall any more - velocity.z = std::max(0.0f, velocity.z); // NOTE: two different velocity assignments above + Ogre::Vector3 stormDirection = MWBase::Environment::get().getWorld()->getStormDirection(); + Ogre::Degree angle = stormDirection.angleBetween(velocity); + static const float fStromWalkMult = MWBase::Environment::get().getWorld()->getStore().get() + .find("fStromWalkMult")->getFloat(); + velocity *= 1.f-(fStromWalkMult * (angle.valueDegrees()/180.f)); } Ogre::Vector3 newPosition = position; @@ -299,7 +364,7 @@ namespace MWWorld continue; // velocity updated, calculate nextpos again } - if(!newPosition.positionCloses(nextpos, 0.00000001)) + if(newPosition.squaredDistance(nextpos) > 0.00000001*0.00000001) { // trace to where character would go if there were no obstructions tracer.doTrace(colobj, newPosition, nextpos, engine); @@ -537,9 +602,9 @@ namespace MWWorld return mEngine->getCollisions(ptr.getRefData().getBaseNode()->getName()); } - Ogre::Vector3 PhysicsSystem::traceDown(const MWWorld::Ptr &ptr) + Ogre::Vector3 PhysicsSystem::traceDown(const MWWorld::Ptr &ptr, float maxHeight) { - return MovementSolver::traceDown(ptr, mEngine); + return MovementSolver::traceDown(ptr, mEngine, maxHeight); } void PhysicsSystem::addHeightField (float* heights, @@ -559,11 +624,10 @@ namespace MWWorld std::string mesh = ptr.getClass().getModel(ptr); Ogre::SceneNode* node = ptr.getRefData().getBaseNode(); handleToMesh[node->getName()] = mesh; - OEngine::Physic::RigidBody* body = mEngine->createAndAdjustRigidBody( + mEngine->createAndAdjustRigidBody( mesh, node->getName(), node->getScale().x, node->getPosition(), node->getOrientation(), 0, 0, false, placeable); - OEngine::Physic::RigidBody* raycastingBody = mEngine->createAndAdjustRigidBody( + mEngine->createAndAdjustRigidBody( mesh, node->getName(), node->getScale().x, node->getPosition(), node->getOrientation(), 0, 0, true, placeable); - mEngine->addRigidBody(body, true, raycastingBody); } void PhysicsSystem::addActor (const Ptr& ptr) @@ -602,9 +666,10 @@ namespace MWWorld Ogre::SceneNode* node = ptr.getRefData().getBaseNode(); const std::string &handle = node->getName(); const Ogre::Quaternion &rotation = node->getOrientation(); + + // TODO: map to MWWorld::Ptr for faster access if (OEngine::Physic::PhysicActor* act = mEngine->getCharacter(handle)) { - //Needs to be changed act->setRotation(rotation); } if (OEngine::Physic::RigidBody* body = mEngine->getRigidBody(handle)) @@ -735,8 +800,10 @@ namespace MWWorld btCollisionObject object; object.setCollisionShape(&planeShape); + // TODO: this seems to have a slight performance impact if (waterCollision) - mEngine->dynamicsWorld->addCollisionObject(&object); + mEngine->mDynamicsWorld->addCollisionObject(&object, + 0xff, OEngine::Physic::CollisionType_Actor); // 100 points of slowfall reduce gravity by 90% (this is just a guess) float slowFall = 1-std::min(std::max(0.f, (effects.get(ESM::MagicEffect::SlowFall).mMagnitude / 100.f) * 0.9f), 0.9f); @@ -746,7 +813,7 @@ namespace MWWorld waterlevel, slowFall, mEngine); if (waterCollision) - mEngine->dynamicsWorld->removeCollisionObject(&object); + mEngine->mDynamicsWorld->removeCollisionObject(&object); float heightDiff = newpos.z - oldHeight; @@ -762,4 +829,12 @@ namespace MWWorld return mMovementResults; } + + void PhysicsSystem::stepSimulation(float dt) + { + animateCollisionShapes(mEngine->mAnimatedShapes); + animateCollisionShapes(mEngine->mAnimatedRaycastingShapes); + + mEngine->stepSimulation(dt); + } } diff --git a/apps/openmw/mwworld/physicssystem.hpp b/apps/openmw/mwworld/physicssystem.hpp index c590b40c87..8e0be95d57 100644 --- a/apps/openmw/mwworld/physicssystem.hpp +++ b/apps/openmw/mwworld/physicssystem.hpp @@ -53,8 +53,10 @@ namespace MWWorld bool toggleCollisionMode(); + void stepSimulation(float dt); + std::vector getCollisions(const MWWorld::Ptr &ptr); ///< get handles this object collides with - Ogre::Vector3 traceDown(const MWWorld::Ptr &ptr); + Ogre::Vector3 traceDown(const MWWorld::Ptr &ptr, float maxHeight); std::pair getFacedHandle(float queryDistance); std::pair getHitContact(const std::string &name, diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 9913b888b2..12908ca9da 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -44,8 +44,9 @@ namespace MWWorld cellRef.mRefID = "player"; mPlayer = LiveCellRef(cellRef, player); - float* playerPos = mPlayer.mData.getPosition().pos; - playerPos[0] = playerPos[1] = playerPos[2] = 0; + ESM::Position playerPos = mPlayer.mData.getPosition(); + playerPos.pos[0] = playerPos.pos[1] = playerPos.pos[2] = 0; + mPlayer.mData.setPosition(playerPos); } void Player::set(const ESM::NPC *player) @@ -245,7 +246,15 @@ namespace MWWorld MWBase::World& world = *MWBase::Environment::get().getWorld(); - mCellStore = world.getCell (player.mCellId); + try + { + mCellStore = world.getCell (player.mCellId); + } + catch (...) + { + // Cell no longer exists. Place the player in a default cell. + mCellStore = world.getExterior(0,0); + } if (!player.mBirthsign.empty() && !world.getStore().get().search (player.mBirthsign)) diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 4e4f0b271a..fb376bb930 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -57,22 +57,35 @@ namespace MWWorld void ProjectileManager::launchMagicBolt(const std::string &model, const std::string &sound, const std::string &spellId, float speed, bool stack, - const ESM::EffectList &effects, const Ptr &actor, const std::string &sourceName) + const ESM::EffectList &effects, const Ptr &caster, const std::string &sourceName, + const Ogre::Vector3& fallbackDirection) { - // Spawn at 0.75 * ActorHeight - float height = mPhysEngine.getCharacter(actor.getRefData().getHandle())->getHalfExtents().z * 2 * 0.75; + float height = 0; + if (OEngine::Physic::PhysicActor* actor = mPhysEngine.getCharacter(caster.getRefData().getHandle())) + height = actor->getHalfExtents().z * 2 * 0.75; // Spawn at 0.75 * ActorHeight - Ogre::Vector3 pos(actor.getRefData().getPosition().pos); + Ogre::Vector3 pos(caster.getRefData().getPosition().pos); pos.z += height; - Ogre::Quaternion orient = Ogre::Quaternion(Ogre::Radian(actor.getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * - Ogre::Quaternion(Ogre::Radian(actor.getRefData().getPosition().rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X); + if (MWBase::Environment::get().getWorld()->isUnderwater(caster.getCell(), pos)) // Underwater casting not possible + return; + + Ogre::Quaternion orient; + if (caster.getClass().isActor()) + orient = Ogre::Quaternion(Ogre::Radian(caster.getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * + Ogre::Quaternion(Ogre::Radian(caster.getRefData().getPosition().rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X); + else + orient = Ogre::Vector3::UNIT_Y.getRotationTo(fallbackDirection); MagicBoltState state; state.mSourceName = sourceName; state.mId = model; state.mSpellId = spellId; - state.mActorId = actor.getClass().getCreatureStats(actor).getActorId(); + state.mCasterHandle = caster.getRefData().getHandle(); + if (caster.getClass().isActor()) + state.mActorId = caster.getClass().getCreatureStats(caster).getActorId(); + else + state.mActorId = -1; state.mSpeed = speed; state.mStack = stack; state.mSoundId = sound; @@ -152,7 +165,9 @@ namespace MWWorld { MWWorld::Ptr obstacle = MWBase::Environment::get().getWorld()->searchPtrViaHandle(cIt->second); - MWWorld::Ptr caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->mActorId); + MWWorld::Ptr caster = MWBase::Environment::get().getWorld()->searchPtrViaHandle(it->mCasterHandle); + if (caster.isEmpty()) + caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->mActorId); if (!obstacle.isEmpty() && obstacle == caster) continue; @@ -176,6 +191,11 @@ namespace MWWorld hit = true; } + + // Explodes when hitting water + if (MWBase::Environment::get().getWorld()->isUnderwater(MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(), newPos)) + hit = true; + if (hit) { MWWorld::Ptr caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->mActorId); diff --git a/apps/openmw/mwworld/projectilemanager.hpp b/apps/openmw/mwworld/projectilemanager.hpp index da965a4cff..6e84b9efb2 100644 --- a/apps/openmw/mwworld/projectilemanager.hpp +++ b/apps/openmw/mwworld/projectilemanager.hpp @@ -39,9 +39,10 @@ namespace MWWorld ProjectileManager (Ogre::SceneManager* sceneMgr, OEngine::Physic::PhysicEngine& engine); + /// If caster is an actor, the actor's facing orientation is used. Otherwise fallbackDirection is used. void launchMagicBolt (const std::string& model, const std::string &sound, const std::string &spellId, float speed, bool stack, const ESM::EffectList& effects, - const MWWorld::Ptr& actor, const std::string& sourceName); + const MWWorld::Ptr& caster, const std::string& sourceName, const Ogre::Vector3& fallbackDirection); void launchProjectile (MWWorld::Ptr actor, MWWorld::Ptr projectile, const Ogre::Vector3& pos, const Ogre::Quaternion& orient, MWWorld::Ptr bow, float speed); @@ -64,9 +65,15 @@ namespace MWWorld NifOgre::ObjectScenePtr mObject; Ogre::SceneNode* mNode; - // Actor who shot this projectile int mActorId; + // actorId doesn't work for non-actors, so we also keep track of the Ogre-handle. + // For non-actors, the caster ptr is mainly needed to prevent the projectile + // from colliding with its caster. + // TODO: this will break when the game is saved and reloaded, since there is currently + // no way to write identifiers for non-actors to a savegame. + std::string mCasterHandle; + // MW-id of this projectile std::string mId; }; diff --git a/apps/openmw/mwworld/refdata.cpp b/apps/openmw/mwworld/refdata.cpp index 2e267b37ce..f4bc64b708 100644 --- a/apps/openmw/mwworld/refdata.cpp +++ b/apps/openmw/mwworld/refdata.cpp @@ -152,7 +152,8 @@ namespace MWWorld { mLocals.configure (script); mHasLocals = true; - mChanged = true; + if (!mLocals.isEmpty()) + mChanged = true; } } @@ -188,15 +189,25 @@ namespace MWWorld mEnabled = false; } - ESM::Position& RefData::getPosition() + void RefData::setPosition(const ESM::Position& pos) { mChanged = true; + mPosition = pos; + } + + const ESM::Position& RefData::getPosition() + { return mPosition; } - LocalRotation& RefData::getLocalRotation() + void RefData::setLocalRotation(const LocalRotation& rot) { mChanged = true; + mLocalRotation = rot; + } + + const LocalRotation& RefData::getLocalRotation() + { return mLocalRotation; } diff --git a/apps/openmw/mwworld/refdata.hpp b/apps/openmw/mwworld/refdata.hpp index a8ffad6843..db66c091bb 100644 --- a/apps/openmw/mwworld/refdata.hpp +++ b/apps/openmw/mwworld/refdata.hpp @@ -54,7 +54,7 @@ namespace MWWorld RefData(); /// @param cellRef Used to copy constant data such as position into this class where it can - /// be altered without effecting the original data. This makes it possible + /// be altered without affecting the original data. This makes it possible /// to reset the position as the orignal data is still held in the CellRef RefData (const ESM::CellRef& cellRef); @@ -100,12 +100,14 @@ namespace MWWorld void disable(); - ESM::Position& getPosition(); + void setPosition (const ESM::Position& pos); + const ESM::Position& getPosition(); - LocalRotation& getLocalRotation(); + void setLocalRotation (const LocalRotation& rotation); + const LocalRotation& getLocalRotation(); void setCustomData (CustomData *data); - ///< Set custom data (potentially replacing old custom data). The ownership of \æ data is + ///< Set custom data (potentially replacing old custom data). The ownership of \a data is /// transferred to this. CustomData *getCustomData(); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index f753ae1f45..b2faa1a010 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -22,6 +22,30 @@ namespace { + void updateObjectLocalRotation (const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics, + MWRender::RenderingManager& rendering) + { + if (ptr.getRefData().getBaseNode() != NULL) + { + Ogre::Quaternion worldRotQuat(Ogre::Radian(ptr.getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z); + if (!ptr.getClass().isActor()) + worldRotQuat = Ogre::Quaternion(Ogre::Radian(ptr.getRefData().getPosition().rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X)* + Ogre::Quaternion(Ogre::Radian(ptr.getRefData().getPosition().rot[1]), Ogre::Vector3::NEGATIVE_UNIT_Y)* worldRotQuat; + + float x = ptr.getRefData().getLocalRotation().rot[0]; + float y = ptr.getRefData().getLocalRotation().rot[1]; + float z = ptr.getRefData().getLocalRotation().rot[2]; + + Ogre::Quaternion rot(Ogre::Radian(z), Ogre::Vector3::NEGATIVE_UNIT_Z); + if (!ptr.getClass().isActor()) + rot = Ogre::Quaternion(Ogre::Radian(x), Ogre::Vector3::NEGATIVE_UNIT_X)* + Ogre::Quaternion(Ogre::Radian(y), Ogre::Vector3::NEGATIVE_UNIT_Y)*rot; + + ptr.getRefData().getBaseNode()->setOrientation(worldRotQuat*rot); + physics.rotateObject(ptr); + } + } + struct InsertFunctor { MWWorld::CellStore& mCell; @@ -60,11 +84,7 @@ namespace mRendering.addObject (ptr); ptr.getClass().insertObject (ptr, mPhysics); - float ax = Ogre::Radian(ptr.getRefData().getLocalRotation().rot[0]).valueDegrees(); - float ay = Ogre::Radian(ptr.getRefData().getLocalRotation().rot[1]).valueDegrees(); - float az = Ogre::Radian(ptr.getRefData().getLocalRotation().rot[2]).valueDegrees(); - MWBase::Environment::get().getWorld()->localRotateObject (ptr, ax, ay, az); - + updateObjectLocalRotation(ptr, mPhysics, mRendering); MWBase::Environment::get().getWorld()->scaleObject (ptr, ptr.getCellRef().getScale()); ptr.getClass().adjustPosition (ptr); } @@ -85,6 +105,20 @@ namespace namespace MWWorld { + void Scene::updateObjectLocalRotation (const Ptr& ptr) + { + ::updateObjectLocalRotation(ptr, *mPhysics, mRendering); + } + + void Scene::updateObjectRotation (const Ptr& ptr) + { + if(ptr.getRefData().getBaseNode() != 0) + { + mRendering.rotateObject(ptr); + mPhysics->rotateObject(ptr); + } + } + void Scene::update (float duration, bool paused) { if (mNeedMapUpdate) diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index 4496447544..e0eeee1871 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -103,6 +103,10 @@ namespace MWWorld void removeObjectFromScene (const Ptr& ptr); ///< Remove an object from the scene, but not from the world model. + void updateObjectLocalRotation (const Ptr& ptr); + + void updateObjectRotation (const Ptr& ptr); + bool isCellActive(const CellStore &cell); Ptr searchPtrViaHandle (const std::string& handle); diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 1156cbc152..fdeb290e5f 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -3,22 +3,8 @@ namespace MWWorld { - -void Store::load(ESM::ESMReader &esm, const std::string &id) +void Store::handleMovedCellRefs(ESM::ESMReader& esm, ESM::Cell* cell) { - // Don't automatically assume that a new cell must be spawned. Multiple plugins write to the same cell, - // and we merge all this data into one Cell object. However, we can't simply search for the cell id, - // as many exterior cells do not have a name. Instead, we need to search by (x,y) coordinates - and they - // are not available until both cells have been loaded! So first, proceed as usual. - - // All cells have a name record, even nameless exterior cells. - std::string idLower = Misc::StringUtils::lowerCase(id); - ESM::Cell *cell = new ESM::Cell; - cell->mName = id; - - //First part of cell loading - cell->preLoad(esm); - //Handling MovedCellRefs, there is no way to do it inside loadcell while (esm.isNextSub("MVRF")) { ESM::CellRef ref; @@ -43,35 +29,58 @@ void Store::load(ESM::ESMReader &esm, const std::string &id) else *iter = ref; } +} - //Second part of cell loading - cell->postLoad(esm); +void Store::load(ESM::ESMReader &esm, const std::string &id) +{ + // Don't automatically assume that a new cell must be spawned. Multiple plugins write to the same cell, + // and we merge all this data into one Cell object. However, we can't simply search for the cell id, + // as many exterior cells do not have a name. Instead, we need to search by (x,y) coordinates - and they + // are not available until both cells have been loaded at least partially! - if(cell->mData.mFlags & ESM::Cell::Interior) + // All cells have a name record, even nameless exterior cells. + std::string idLower = Misc::StringUtils::lowerCase(id); + ESM::Cell cell; + cell.mName = id; + + // Load the (x,y) coordinates of the cell, if it is an exterior cell, + // so we can find the cell we need to merge with + cell.loadData(esm); + + if(cell.mData.mFlags & ESM::Cell::Interior) { // Store interior cell by name, try to merge with existing parent data. ESM::Cell *oldcell = const_cast(search(idLower)); if (oldcell) { - // push the new references on the list of references to manage - oldcell->mContextList.push_back(cell->mContextList.at(0)); - // copy list into new cell - cell->mContextList = oldcell->mContextList; - // have new cell replace old cell - ESM::Cell::merge(oldcell, cell); + // merge new cell into old cell + // push the new references on the list of references to manage (saveContext = true) + oldcell->mData = cell.mData; + oldcell->loadCell(esm, true); } else - mInt[idLower] = *cell; + { + // spawn a new cell + cell.loadCell(esm, true); + + mInt[idLower] = cell; + } } else { // Store exterior cells by grid position, try to merge with existing parent data. - ESM::Cell *oldcell = const_cast(search(cell->getGridX(), cell->getGridY())); + ESM::Cell *oldcell = const_cast(search(cell.getGridX(), cell.getGridY())); if (oldcell) { + // merge new cell into old cell + oldcell->mData = cell.mData; + oldcell->loadCell(esm, false); + + // handle moved ref (MVRF) subrecords + handleMovedCellRefs (esm, &cell); + // push the new references on the list of references to manage - oldcell->mContextList.push_back(cell->mContextList.at(0)); - // copy list into new cell - cell->mContextList = oldcell->mContextList; + oldcell->postLoad(esm); + // merge lists of leased references, use newer data in case of conflict - for (ESM::MovedCellRefTracker::const_iterator it = cell->mMovedRefs.begin(); it != cell->mMovedRefs.end(); ++it) { + for (ESM::MovedCellRefTracker::const_iterator it = cell.mMovedRefs.begin(); it != cell.mMovedRefs.end(); ++it) { // remove reference from current leased ref tracker and add it to new cell ESM::MovedCellRefTracker::iterator itold = std::find(oldcell->mMovedRefs.begin(), oldcell->mMovedRefs.end(), it->mRefNum); if (itold != oldcell->mMovedRefs.end()) { @@ -82,13 +91,24 @@ void Store::load(ESM::ESMReader &esm, const std::string &id) *itold = *it; } } - cell->mMovedRefs = oldcell->mMovedRefs; - // have new cell replace old cell - ESM::Cell::merge(oldcell, cell); + + // We don't need to merge mLeasedRefs of cell / oldcell. This list is filled when another cell moves a + // reference to this cell, so the list for the new cell should be empty. The list for oldcell, + // however, could have leased refs in it and so should be kept. } else - mExt[std::make_pair(cell->mData.mX, cell->mData.mY)] = *cell; + { + // spawn a new cell + cell.loadCell(esm, false); + + // handle moved ref (MVRF) subrecords + handleMovedCellRefs (esm, &cell); + + // push the new references on the list of references to manage + cell.postLoad(esm); + + mExt[std::make_pair(cell.mData.mX, cell.mData.mY)] = cell; + } } - delete cell; } } diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 1dfb2f9766..3e7e7a5f98 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -552,23 +552,16 @@ namespace MWWorld template <> class Store : public StoreBase { - struct ExtCmp - { - bool operator()(const ESM::Cell &x, const ESM::Cell &y) { - if (x.mData.mX == y.mData.mX) { - return x.mData.mY < y.mData.mY; - } - return x.mData.mX < y.mData.mX; - } - }; - struct DynamicExtCmp { bool operator()(const std::pair &left, const std::pair &right) const { - if (left.first == right.first) { - return left.second < right.second; - } - return left.first < right.first; + if (left.first == right.first && left.second == right.second) + return false; + + if (left.first == right.first) + return left.second > right.second; + + return left.first > right.first; } }; @@ -591,6 +584,8 @@ namespace MWWorld return search(cell.mName); } + void handleMovedCellRefs(ESM::ESMReader& esm, ESM::Cell* cell); + public: ESMStore *mEsmStore; diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 25f523bee9..fb45cb0343 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -58,6 +58,28 @@ void WeatherManager::setFallbackWeather(Weather& weather,const std::string& name weather.mWindSpeed = mFallback->getFallbackFloat("Weather_"+upper+"_Wind_Speed"); weather.mCloudSpeed = mFallback->getFallbackFloat("Weather_"+upper+"_Cloud_Speed"); weather.mGlareView = mFallback->getFallbackFloat("Weather_"+upper+"_Glare_View"); + weather.mCloudTexture = mFallback->getFallbackString("Weather_"+upper+"_Cloud_Texture"); + + bool usesPrecip = mFallback->getFallbackBool("Weather_"+upper+"_Using_Precip"); + if (usesPrecip) + weather.mRainEffect = "meshes\\raindrop.nif"; + weather.mRainSpeed = mRainSpeed; + weather.mRainFrequency = mFallback->getFallbackFloat("Weather_"+upper+"_Rain_Entrance_Speed"); + /* +Unhandled: +Rain Diameter=600 ? +Rain Height Min=200 ? +Rain Height Max=700 ? +Rain Threshold=0.6 ? +Max Raindrops=650 ? +*/ + + size_t offset = weather.mCloudTexture.find(".tga"); + if (offset != std::string::npos) + weather.mCloudTexture.replace(offset, weather.mCloudTexture.length() - offset, ".dds"); + + weather.mIsStorm = (name == "ashstorm" || name == "blight"); + mWeatherSettings[name] = weather; } @@ -93,7 +115,8 @@ WeatherManager::WeatherManager(MWRender::RenderingManager* rendering,MWWorld::Fa mHour(14), mCurrentWeather("clear"), mNextWeather(""), mFirstUpdate(true), mWeatherUpdateTime(0), mThunderFlash(0), mThunderChance(0), mThunderChanceNeeded(50), mThunderSoundDelay(0), mRemainingTransitionTime(0), - mTimePassed(0), mFallback(fallback), mWindSpeed(0.f), mRendering(rendering) + mTimePassed(0), mFallback(fallback), mWindSpeed(0.f), mRendering(rendering), mIsStorm(false), + mStormDirection(0,1,0) { //Globals mThunderSoundID0 = mFallback->getFallbackString("Weather_Thunderstorm_Thunder_Sound_ID_0"); @@ -110,6 +133,8 @@ WeatherManager::WeatherManager(MWRender::RenderingManager* rendering,MWWorld::Fa mThunderThreshold = mFallback->getFallbackFloat("Weather_Thunderstorm_Thunder_Threshold"); mThunderSoundDelay = 0.25; + mRainSpeed = mFallback->getFallbackFloat("Weather_Precip_Gravity"); + //Some useful values /* TODO: Use pre-sunrise_time, pre-sunset_time, * post-sunrise_time, and post-sunset_time to better @@ -123,48 +148,44 @@ WeatherManager::WeatherManager(MWRender::RenderingManager* rendering,MWWorld::Fa //Weather Weather clear; - clear.mCloudTexture = "tx_sky_clear.dds"; setFallbackWeather(clear,"clear"); Weather cloudy; - cloudy.mCloudTexture = "tx_sky_cloudy.dds"; setFallbackWeather(cloudy,"cloudy"); Weather foggy; - foggy.mCloudTexture = "tx_sky_foggy.dds"; setFallbackWeather(foggy,"foggy"); Weather thunderstorm; - thunderstorm.mCloudTexture = "tx_sky_thunder.dds"; thunderstorm.mRainLoopSoundID = "rain heavy"; + thunderstorm.mRainEffect = "meshes\\raindrop.nif"; setFallbackWeather(thunderstorm,"thunderstorm"); Weather rain; - rain.mCloudTexture = "tx_sky_rainy.dds"; rain.mRainLoopSoundID = "rain"; + rain.mRainEffect = "meshes\\raindrop.nif"; setFallbackWeather(rain,"rain"); Weather overcast; - overcast.mCloudTexture = "tx_sky_overcast.dds"; setFallbackWeather(overcast,"overcast"); Weather ashstorm; - ashstorm.mCloudTexture = "tx_sky_ashstorm.dds"; ashstorm.mAmbientLoopSoundID = "ashstorm"; + ashstorm.mParticleEffect = "meshes\\ashcloud.nif"; setFallbackWeather(ashstorm,"ashstorm"); Weather blight; - blight.mCloudTexture = "tx_sky_blight.dds"; blight.mAmbientLoopSoundID = "blight"; + blight.mParticleEffect = "meshes\\blightcloud.nif"; setFallbackWeather(blight,"blight"); Weather snow; - snow.mCloudTexture = "tx_bm_sky_snow.dds"; + snow.mParticleEffect = "meshes\\snow.nif"; setFallbackWeather(snow, "snow"); Weather blizzard; - blizzard.mCloudTexture = "tx_bm_sky_blizzard.dds"; blizzard.mAmbientLoopSoundID = "BM Blizzard"; + blizzard.mParticleEffect = "meshes\\blizzard.nif"; setFallbackWeather(blizzard,"blizzard"); } @@ -214,6 +235,14 @@ void WeatherManager::setResult(const String& weatherType) mResult.mAmbientLoopSoundID = current.mAmbientLoopSoundID; mResult.mSunColor = current.mSunDiscSunsetColor; + mResult.mIsStorm = current.mIsStorm; + + mResult.mRainSpeed = current.mRainSpeed; + mResult.mRainFrequency = current.mRainFrequency; + + mResult.mParticleEffect = current.mParticleEffect; + mResult.mRainEffect = current.mRainEffect; + mResult.mNight = (mHour < mSunriseTime || mHour > mNightStart - 1); mResult.mFogDepth = mResult.mNight ? current.mLandFogNightDepth : current.mLandFogDayDepth; @@ -316,6 +345,12 @@ void WeatherManager::transition(float factor) mResult.mNightFade = lerp(current.mNightFade, other.mNightFade, factor); mResult.mNight = current.mNight; + + mResult.mIsStorm = current.mIsStorm; + mResult.mParticleEffect = current.mParticleEffect; + mResult.mRainEffect = current.mRainEffect; + mResult.mRainSpeed = current.mRainSpeed; + mResult.mRainFrequency = current.mRainFrequency; } void WeatherManager::update(float duration) @@ -353,6 +388,18 @@ void WeatherManager::update(float duration) setResult(mCurrentWeather); mWindSpeed = mResult.mWindSpeed; + mIsStorm = mResult.mIsStorm; + + if (mIsStorm) + { + MWWorld::Ptr player = world->getPlayerPtr(); + Ogre::Vector3 playerPos (player.getRefData().getPosition().pos); + Ogre::Vector3 redMountainPos (19950, 72032, 27831); + + mStormDirection = (playerPos - redMountainPos); + mStormDirection.z = 0; + mRendering->getSkyManager()->setStormDirection(mStormDirection); + } mRendering->configureFog(mResult.mFogDepth, mResult.mFogColor); @@ -378,11 +425,13 @@ void WeatherManager::update(float duration) int facing = (mHour > 13.f) ? 1 : -1; + bool sun_is_moon = mHour >= mNightStart || mHour <= mSunriseTime; + Vector3 final( (height - 1) * facing, (height - 1) * facing, height); - mRendering->setSunDirection(final); + mRendering->setSunDirection(final, sun_is_moon); /* * TODO: import separated fadeInStart/Finish, fadeOutStart/Finish @@ -491,7 +540,6 @@ void WeatherManager::update(float duration) mRendering->getSkyManager()->setWeather(mResult); - // Play sounds if (mNextWeather == "") { @@ -768,3 +816,13 @@ void WeatherManager::switchToNextWeather(bool instantly) } } } + +bool WeatherManager::isInStorm() const +{ + return mIsStorm; +} + +Ogre::Vector3 WeatherManager::getStormDirection() const +{ + return mStormDirection; +} diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index 3e9df504ba..292d06747f 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -5,6 +5,7 @@ #include #include +#include namespace ESM { @@ -57,7 +58,15 @@ namespace MWWorld bool mNight; // use night skybox float mNightFade; // fading factor for night skybox + bool mIsStorm; + std::string mAmbientLoopSoundID; + + std::string mParticleEffect; + + std::string mRainEffect; + float mRainSpeed; + float mRainFrequency; }; @@ -100,7 +109,7 @@ namespace MWWorld // Duration of weather transition (in days) float mTransitionDelta; - // No idea what this one is used for? + // Used by scripts to animate signs, etc based on the wind (GetWindSpeed) float mWindSpeed; // Cloud animation speed multiplier @@ -119,7 +128,25 @@ namespace MWWorld // Rain sound effect std::string mRainLoopSoundID; - /// \todo disease chance + // Is this an ash storm / blight storm? If so, the following will happen: + // - The particles and clouds will be oriented so they appear to come from the Red Mountain. + // - Characters will animate their hand to protect eyes from the storm when looking in its direction (idlestorm animation) + // - Slower movement when walking against the storm (fStromWalkMult) + bool mIsStorm; + + // How fast does rain travel down? + // In Morrowind.ini this is set globally, but we may want to change it per weather later. + float mRainSpeed; + + // How often does a new rain mesh spawn? + float mRainFrequency; + + std::string mParticleEffect; + + std::string mRainEffect; + + // Note: For Weather Blight, there is a "Disease Chance" (=0.1) setting. But according to MWSFD this feature + // is broken in the vanilla game and was disabled. }; /// @@ -151,6 +178,11 @@ namespace MWWorld float getWindSpeed() const; + /// Are we in an ash or blight storm? + bool isInStorm() const; + + Ogre::Vector3 getStormDirection() const; + void advanceTime(double hours) { mTimePassed += hours*3600; @@ -170,6 +202,9 @@ namespace MWWorld private: float mHour; float mWindSpeed; + bool mIsStorm; + Ogre::Vector3 mStormDirection; + MWWorld::Fallback* mFallback; void setFallbackWeather(Weather& weather,const std::string& name); MWRender::RenderingManager* mRendering; @@ -208,6 +243,7 @@ namespace MWWorld typedef std::map > RegionModMap; RegionModMap mRegionMods; + float mRainSpeed; float mSunriseTime; float mSunsetTime; float mSunriseDuration; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index ebdac7ba14..33405b4d86 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1,4 +1,5 @@ #include "worldimp.hpp" + #ifdef _WIN32 #include #elif defined HAVE_UNORDERED_MAP @@ -36,6 +37,8 @@ #include "../mwrender/sky.hpp" #include "../mwrender/animation.hpp" +#include "../mwscript/interpretercontext.hpp" + #include "../mwclass/door.hpp" #include "player.hpp" @@ -52,6 +55,20 @@ using namespace Ogre; +namespace +{ + +// Wraps a value to (-PI, PI] +void wrap(float& rad) +{ + if (rad>0) + rad = std::fmod(rad+Ogre::Math::PI, 2.0f*Ogre::Math::PI)-Ogre::Math::PI; + else + rad = std::fmod(rad-Ogre::Math::PI, 2.0f*Ogre::Math::PI)+Ogre::Math::PI; +} + +} + namespace MWWorld { struct GameContentLoader : public ContentLoader @@ -940,15 +957,17 @@ namespace MWWorld void World::moveObject(const Ptr &ptr, CellStore* newCell, float x, float y, float z) { - ESM::Position &pos = ptr.getRefData().getPosition(); + ESM::Position pos = ptr.getRefData().getPosition(); pos.pos[0] = x; pos.pos[1] = y; pos.pos[2] = z; + ptr.getRefData().setPosition(pos); + Ogre::Vector3 vec(x, y, z); - CellStore *currCell = ptr.isInCell() ? ptr.getCell() : NULL; + CellStore *currCell = ptr.isInCell() ? ptr.getCell() : NULL; // currCell == NULL should only happen for player, during initial startup bool isPlayer = ptr == mPlayer->getPlayer(); bool haveToMove = isPlayer || mWorldScene->isCellActive(*currCell); @@ -970,9 +989,20 @@ namespace MWWorld } else { - if (!mWorldScene->isCellActive(*currCell)) - ptr.getClass().copyToCell(ptr, *newCell, pos); - else if (!mWorldScene->isCellActive(*newCell)) + bool currCellActive = mWorldScene->isCellActive(*currCell); + bool newCellActive = mWorldScene->isCellActive(*newCell); + if (!currCellActive && newCellActive) + { + MWWorld::Ptr newPtr = ptr.getClass().copyToCell(ptr, *newCell, pos); + mWorldScene->addObjectToScene(newPtr); + + std::string script = newPtr.getClass().getScript(newPtr); + if (!script.empty()) { + mLocalScripts.add(script, newPtr); + } + addContainerScripts(newPtr, newCell); + } + else if (!newCellActive && currCellActive) { mWorldScene->removeObjectFromScene(ptr); mLocalScripts.remove(ptr); @@ -983,7 +1013,9 @@ namespace MWWorld .copyToCell(ptr, *newCell); newPtr.getRefData().setBaseNode(0); } - else + else if (!currCellActive && !newCellActive) + ptr.getClass().copyToCell(ptr, *newCell); + else // both cells active { MWWorld::Ptr copy = ptr.getClass().copyToCell(ptr, *newCell, pos); @@ -1051,7 +1083,8 @@ namespace MWWorld const float two_pi = Ogre::Math::TWO_PI; const float pi = Ogre::Math::PI; - float *objRot = ptr.getRefData().getPosition().rot; + ESM::Position pos = ptr.getRefData().getPosition(); + float *objRot = pos.rot; if(adjust) { objRot[0] += rot.x; @@ -1088,55 +1121,33 @@ namespace MWWorld while(objRot[2] < -pi) objRot[2] += two_pi; while(objRot[2] > pi) objRot[2] -= two_pi; - if(ptr.getRefData().getBaseNode() != 0) - { - mRendering->rotateObject(ptr); - mPhysics->rotateObject(ptr); - } + ptr.getRefData().setPosition(pos); + + mWorldScene->updateObjectRotation(ptr); } void World::localRotateObject (const Ptr& ptr, float x, float y, float z) { - if (ptr.getRefData().getBaseNode() != 0) { + if (ptr.getRefData().getBaseNode() != 0) + { + LocalRotation rot = ptr.getRefData().getLocalRotation(); + rot.rot[0]=Ogre::Degree(x).valueRadians(); + rot.rot[1]=Ogre::Degree(y).valueRadians(); + rot.rot[2]=Ogre::Degree(z).valueRadians(); - ptr.getRefData().getLocalRotation().rot[0]=Ogre::Degree(x).valueRadians(); - ptr.getRefData().getLocalRotation().rot[1]=Ogre::Degree(y).valueRadians(); - ptr.getRefData().getLocalRotation().rot[2]=Ogre::Degree(z).valueRadians(); + wrap(rot.rot[0]); + wrap(rot.rot[1]); + wrap(rot.rot[2]); - float fullRotateRad=Ogre::Degree(360).valueRadians(); + ptr.getRefData().setLocalRotation(rot); - while(ptr.getRefData().getLocalRotation().rot[0]>=fullRotateRad) - ptr.getRefData().getLocalRotation().rot[0]-=fullRotateRad; - while(ptr.getRefData().getLocalRotation().rot[1]>=fullRotateRad) - ptr.getRefData().getLocalRotation().rot[1]-=fullRotateRad; - while(ptr.getRefData().getLocalRotation().rot[2]>=fullRotateRad) - ptr.getRefData().getLocalRotation().rot[2]-=fullRotateRad; - - while(ptr.getRefData().getLocalRotation().rot[0]<=-fullRotateRad) - ptr.getRefData().getLocalRotation().rot[0]+=fullRotateRad; - while(ptr.getRefData().getLocalRotation().rot[1]<=-fullRotateRad) - ptr.getRefData().getLocalRotation().rot[1]+=fullRotateRad; - while(ptr.getRefData().getLocalRotation().rot[2]<=-fullRotateRad) - ptr.getRefData().getLocalRotation().rot[2]+=fullRotateRad; - - Ogre::Quaternion worldRotQuat(Ogre::Radian(ptr.getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z); - if (!ptr.getClass().isActor()) - worldRotQuat = Ogre::Quaternion(Ogre::Radian(ptr.getRefData().getPosition().rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X)* - Ogre::Quaternion(Ogre::Radian(ptr.getRefData().getPosition().rot[1]), Ogre::Vector3::NEGATIVE_UNIT_Y)* worldRotQuat; - - Ogre::Quaternion rot(Ogre::Degree(z), Ogre::Vector3::NEGATIVE_UNIT_Z); - if (!ptr.getClass().isActor()) - rot = Ogre::Quaternion(Ogre::Degree(x), Ogre::Vector3::NEGATIVE_UNIT_X)* - Ogre::Quaternion(Ogre::Degree(y), Ogre::Vector3::NEGATIVE_UNIT_Y)*rot; - - ptr.getRefData().getBaseNode()->setOrientation(worldRotQuat*rot); - mPhysics->rotateObject(ptr); + mWorldScene->updateObjectLocalRotation(ptr); } } void World::adjustPosition(const Ptr &ptr) { - Ogre::Vector3 pos (ptr.getRefData().getPosition().pos); + ESM::Position pos (ptr.getRefData().getPosition()); if(!ptr.getRefData().getBaseNode()) { @@ -1144,21 +1155,34 @@ namespace MWWorld return; } - float terrainHeight = mRendering->getTerrainHeightAt(pos); + float terrainHeight = mRendering->getTerrainHeightAt(Ogre::Vector3(pos.pos)); - if (pos.z < terrainHeight) - pos.z = terrainHeight; + if (pos.pos[2] < terrainHeight) + pos.pos[2] = terrainHeight; - ptr.getRefData().getPosition().pos[2] = pos.z + 20; // place slightly above. will snap down to ground with code below + pos.pos[2] += 20; // place slightly above. will snap down to ground with code below + + ptr.getRefData().setPosition(pos); if (!isFlying(ptr)) { - Ogre::Vector3 traced = mPhysics->traceDown(ptr); - if (traced.z < pos.z) - pos.z = traced.z; + Ogre::Vector3 traced = mPhysics->traceDown(ptr, 200); + if (traced.z < pos.pos[2]) + pos.pos[2] = traced.z; } - moveObject(ptr, ptr.getCell(), pos.x, pos.y, pos.z); + moveObject(ptr, ptr.getCell(), pos.pos[0], pos.pos[1], pos.pos[2]); + } + + void World::fixPosition(const Ptr &actor) + { + const float dist = 8000; + ESM::Position pos (actor.getRefData().getPosition()); + pos.pos[2] += dist; + actor.getRefData().setPosition(pos); + + Ogre::Vector3 traced = mPhysics->traceDown(actor, dist*1.1); + moveObject(actor, actor.getCell(), traced.x, traced.y, traced.z); } void World::rotateObject (const Ptr& ptr,float x,float y,float z, bool adjust) @@ -1222,7 +1246,7 @@ namespace MWWorld if(player != results.end()) moveObjectImp(player->first, player->second.x, player->second.y, player->second.z); - mPhysEngine->stepSimulation(duration); + mPhysics->stepSimulation(duration); } bool World::castRay (float x1, float y1, float z1, float x2, float y2, float z2) @@ -1424,7 +1448,7 @@ namespace MWWorld // cast a ray from player to sun to detect if the sun is visible // this is temporary until we find a better place to put this code // currently its here because we need to access the physics system - float* p = mPlayer->getPlayer().getRefData().getPosition().pos; + const float* p = mPlayer->getPlayer().getRefData().getPosition().pos; Vector3 sun = mRendering->getSkyManager()->getRealSunPos(); mRendering->getSkyManager()->setGlare(!mPhysics->castRay(Ogre::Vector3(p[0], p[1], p[2]), sun)); } @@ -1462,8 +1486,13 @@ namespace MWWorld std::vector < std::pair < float, std::string > >::iterator it = results.begin(); while (it != results.end()) { - if ( (*it).second.find("HeightField") != std::string::npos // not interested in terrain - || getPtrViaHandle((*it).second) == mPlayer->getPlayer() ) // not interested in player (unless you want to talk to yourself) + if ((*it).second.find("HeightField") != std::string::npos) // Don't attempt to getPtrViaHandle on terrain + { + ++it; + continue; + } + + if (getPtrViaHandle((*it).second) == mPlayer->getPlayer() ) // not interested in player (unless you want to talk to yourself) { it = results.erase(it); } @@ -1471,7 +1500,8 @@ namespace MWWorld ++it; } - if (results.empty()) + if (results.empty() + || results.front().second.find("HeightField") != std::string::npos) // Blocked by terrain { mFacedHandle = ""; mFacedDistance = FLT_MAX; @@ -1677,6 +1707,14 @@ namespace MWWorld MWWorld::Ptr dropped = object.getClass().copyToCell(object, *cell, pos); + // Reset some position values that could be uninitialized if this item came from a container + LocalRotation localRotation; + localRotation.rot[0] = 0; + localRotation.rot[1] = 0; + localRotation.rot[2] = 0; + dropped.getRefData().setLocalRotation(localRotation); + dropped.getCellRef().setPosition(pos); + if (mWorldScene->isCellActive(*cell)) { if (dropped.getRefData().isEnabled()) { mWorldScene->addObjectToScene(dropped); @@ -1703,14 +1741,15 @@ namespace MWWorld Ogre::Vector3 orig = Ogre::Vector3(pos.pos); + orig.z += 20; Ogre::Vector3 dir = Ogre::Vector3(0, 0, -1); - float len = (pos.pos[2] >= 0) ? pos.pos[2] : -pos.pos[2]; - len += 100.0; + float len = 100.0; std::pair hit = mPhysics->castRay(orig, dir, len); - pos.pos[2] = hit.second.z; + if (hit.first) + pos.pos[2] = hit.second.z; // copy the object and set its count int origCount = object.getRefData().getCount(); @@ -1736,6 +1775,9 @@ namespace MWWorld bool World::isFlying(const MWWorld::Ptr &ptr) const { + const MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr); + bool isParalyzed = (stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).mMagnitude > 0); + if(!ptr.getClass().isActor()) return false; @@ -1743,9 +1785,8 @@ namespace MWWorld return false; if (ptr.getClass().canFly(ptr)) - return true; + return !isParalyzed; - const MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr); if(stats.getMagicEffects().get(ESM::MagicEffect::Levitate).mMagnitude > 0 && isLevitationEnabled()) return true; @@ -1772,7 +1813,7 @@ namespace MWWorld bool World::isSubmerged(const MWWorld::Ptr &object) const { - float *fpos = object.getRefData().getPosition().pos; + const float *fpos = object.getRefData().getPosition().pos; Ogre::Vector3 pos(fpos[0], fpos[1], fpos[2]); const OEngine::Physic::PhysicActor *actor = mPhysEngine->getCharacter(object.getRefData().getHandle()); @@ -1785,7 +1826,7 @@ namespace MWWorld World::isSwimming(const MWWorld::Ptr &object) const { /// \todo add check ifActor() - only actors can swim - float *fpos = object.getRefData().getPosition().pos; + const float *fpos = object.getRefData().getPosition().pos; Ogre::Vector3 pos(fpos[0], fpos[1], fpos[2]); /// \fixme 3/4ths submerged? @@ -1829,7 +1870,7 @@ namespace MWWorld Ogre::Vector3 pos(ptr.getRefData().getPosition().pos); OEngine::Physic::ActorTracer tracer; // a small distance above collision object is considered "on ground" - tracer.findGround(physactor->getCollisionBody(), + tracer.findGround(physactor, pos, pos - Ogre::Vector3(0, 0, 1.5f), // trace a small amount down mPhysEngine); @@ -1970,6 +2011,22 @@ namespace MWWorld return 0.f; } + bool World::isInStorm() const + { + if (isCellExterior() || isCellQuasiExterior()) + return mWeatherManager->isInStorm(); + else + return false; + } + + Ogre::Vector3 World::getStormDirection() const + { + if (isCellExterior() || isCellQuasiExterior()) + return mWeatherManager->getStormDirection(); + else + return Ogre::Vector3(0,1,0); + } + void World::getContainersOwnedBy (const MWWorld::Ptr& npc, std::vector& out) { const Scene::CellStoreCollection& collection = mWorldScene->getActiveCells(); @@ -2013,20 +2070,24 @@ namespace MWWorld } } - bool World::getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc) + bool World::getLOS(const MWWorld::Ptr& actor,const MWWorld::Ptr& targetActor) { - if (!targetNpc.getRefData().isEnabled() || !npc.getRefData().isEnabled()) + if (!targetActor.getRefData().isEnabled() || !actor.getRefData().isEnabled()) return false; // cannot get LOS unless both NPC's are enabled - Ogre::Vector3 halfExt1 = mPhysEngine->getCharacter(npc.getRefData().getHandle())->getHalfExtents(); - float* pos1 = npc.getRefData().getPosition().pos; - Ogre::Vector3 halfExt2 = mPhysEngine->getCharacter(targetNpc.getRefData().getHandle())->getHalfExtents(); - float* pos2 = targetNpc.getRefData().getPosition().pos; + if (!targetActor.getRefData().getBaseNode() || !targetActor.getRefData().getBaseNode()) + return false; // not in active cell - btVector3 from(pos1[0],pos1[1],pos1[2]+halfExt1.z); - btVector3 to(pos2[0],pos2[1],pos2[2]+halfExt2.z); + Ogre::Vector3 halfExt1 = mPhysEngine->getCharacter(actor.getRefData().getHandle())->getHalfExtents(); + const float* pos1 = actor.getRefData().getPosition().pos; + Ogre::Vector3 halfExt2 = mPhysEngine->getCharacter(targetActor.getRefData().getHandle())->getHalfExtents(); + const float* pos2 = targetActor.getRefData().getPosition().pos; + + btVector3 from(pos1[0],pos1[1],pos1[2]+halfExt1.z*2*0.9); // eye level + btVector3 to(pos2[0],pos2[1],pos2[2]+halfExt2.z*2*0.9); std::pair result = mPhysEngine->rayTest(from, to,false); if(result.first == "") return true; + return false; } @@ -2281,6 +2342,7 @@ namespace MWWorld { MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); + // TODO: this only works for the player MWWorld::Ptr target = getFacedObject(); std::string selectedSpell = stats.getSpells().getSelectedSpell(); @@ -2311,9 +2373,9 @@ namespace MWWorld void World::launchMagicBolt (const std::string& model, const std::string &sound, const std::string &spellId, float speed, bool stack, const ESM::EffectList& effects, - const MWWorld::Ptr& actor, const std::string& sourceName) + const MWWorld::Ptr& caster, const std::string& sourceName, const Ogre::Vector3& fallbackDirection) { - mProjectileManager->launchMagicBolt(model, sound, spellId, speed, stack, effects, actor, sourceName); + mProjectileManager->launchMagicBolt(model, sound, spellId, speed, stack, effects, caster, sourceName, fallbackDirection); } const std::vector& World::getContentFiles() const @@ -2326,6 +2388,9 @@ namespace MWWorld actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(ESM::MagicEffect::Invisibility); if (actor.getClass().hasInventoryStore(actor)) actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Invisibility); + + // Normally updated once per frame, but here it is kinda important to do it right away. + MWBase::Environment::get().getMechanicsManager()->updateMagicEffects(actor); } bool World::isDark() const @@ -2427,14 +2492,15 @@ namespace MWWorld if (!ptr.getRefData().isEnabled()) return true; - // Consider references inside containers as well - if (ptr.getClass().isActor() || ptr.getClass().getTypeName() == typeid(ESM::Container).name()) + // Consider references inside containers as well (except if we are looking for a Creature, they cannot be in containers) + if (mType != World::Detect_Creature && + (ptr.getClass().isActor() || ptr.getClass().getTypeName() == typeid(ESM::Container).name())) { MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); { for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { - if (needToAdd(*it)) + if (needToAdd(*it, mDetector)) { mOut.push_back(ptr); return true; @@ -2443,16 +2509,25 @@ namespace MWWorld } } - if (needToAdd(ptr)) + if (needToAdd(ptr, mDetector)) mOut.push_back(ptr); return true; } - bool needToAdd (MWWorld::Ptr ptr) + bool needToAdd (MWWorld::Ptr ptr, MWWorld::Ptr detector) { - if (mType == World::Detect_Creature && ptr.getClass().getTypeName() != typeid(ESM::Creature).name()) - return false; + if (mType == World::Detect_Creature) + { + // If in werewolf form, this detects only NPCs, otherwise only creatures + if (detector.getClass().isNpc() && detector.getClass().getNpcStats(detector).isWerewolf()) + { + if (ptr.getClass().getTypeName() != typeid(ESM::NPC).name()) + return false; + } + else if (ptr.getClass().getTypeName() != typeid(ESM::Creature).name()) + return false; + } if (mType == World::Detect_Key && !ptr.getClass().isKey(ptr)) return false; if (mType == World::Detect_Enchantment && ptr.getClass().getEnchantment(ptr).empty()) @@ -2584,6 +2659,8 @@ namespace MWWorld int days = std::max(1, bounty / iDaysinPrisonMod); advanceTime(days * 24); + for (int i=0; irest (true); std::set skills; for (int day=0; day buttons; buttons.push_back("#{sOk}"); MWBase::Environment::get().getWindowManager()->messageBox(message, buttons); @@ -2695,6 +2770,11 @@ namespace MWWorld mRendering->spawnEffect(model, texture, worldPosition); } + void World::spawnEffect(const std::string &model, const std::string &textureOverride, const Vector3 &worldPos) + { + mRendering->spawnEffect(model, textureOverride, worldPos); + } + void World::explodeSpell(const Vector3 &origin, const ESM::EffectList &effects, const Ptr &caster, const std::string& id, const std::string& sourceName) { @@ -2758,4 +2838,22 @@ namespace MWWorld cast.inflict(apply->first, caster, effects, ESM::RT_Target, false, true); } } + + 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 (!script.empty()) + { + getLocalScripts().setIgnore (object); + MWBase::Environment::get().getScriptManager()->run (script, interpreterContext); + } + if (!interpreterContext.hasActivationBeenHandled()) + interpreterContext.executeActivation(object, actor); + } } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 0a396ef5c6..08d7eb42dd 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -263,6 +263,9 @@ namespace MWWorld virtual void adjustPosition (const Ptr& ptr); ///< Adjust position after load to be on ground. Must be called after model load. + virtual void fixPosition (const Ptr& actor); + ///< Attempt to fix position so that the Ptr is no longer inside collision geometry. + virtual void enable (const Ptr& ptr); virtual void disable (const Ptr& ptr); @@ -336,10 +339,11 @@ namespace MWWorld virtual void scaleObject (const Ptr& ptr, float scale); - /// Rotates object, uses degrees + /// World rotates object, uses degrees /// \param adjust indicates rotation should be set or adjusted virtual void rotateObject (const Ptr& ptr,float x,float y,float z, bool adjust = false); + /// Local rotates object, uses degrees virtual void localRotateObject (const Ptr& ptr, float x, float y, float z); virtual MWWorld::Ptr safePlaceObject(const MWWorld::Ptr& ptr, MWWorld::CellStore* cell, ESM::Position pos); @@ -481,7 +485,7 @@ namespace MWWorld virtual void getItemsOwnedBy (const MWWorld::Ptr& npc, std::vector& out); ///< get all items in active cells owned by this Npc - virtual bool getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc); + virtual bool getLOS(const MWWorld::Ptr& actor,const MWWorld::Ptr& targetActor); ///< get Line of Sight (morrowind stupid implementation) virtual float getDistToNearestRayHit(const Ogre::Vector3& from, const Ogre::Vector3& dir, float maxDist); @@ -545,7 +549,7 @@ namespace MWWorld virtual void launchMagicBolt (const std::string& model, const std::string& sound, const std::string& spellId, float speed, bool stack, const ESM::EffectList& effects, - const MWWorld::Ptr& actor, const std::string& sourceName); + const MWWorld::Ptr& caster, const std::string& sourceName, const Ogre::Vector3& fallbackDirection); virtual void launchProjectile (MWWorld::Ptr actor, MWWorld::Ptr projectile, const Ogre::Vector3& worldPos, const Ogre::Quaternion& orient, MWWorld::Ptr bow, float speed); @@ -584,8 +588,18 @@ namespace MWWorld /// Spawn a blood effect for \a ptr at \a worldPosition virtual void spawnBloodEffect (const MWWorld::Ptr& ptr, const Ogre::Vector3& worldPosition); + virtual void spawnEffect (const std::string& model, const std::string& textureOverride, const Ogre::Vector3& worldPos); + virtual void explodeSpell (const Ogre::Vector3& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const std::string& id, const std::string& sourceName); + + virtual void activate (const MWWorld::Ptr& object, const MWWorld::Ptr& actor); + + /// @see MWWorld::WeatherManager::isInStorm + virtual bool isInStorm() const; + + /// @see MWWorld::WeatherManager::getStormDirection + virtual Ogre::Vector3 getStormDirection() const; }; } diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index e0166138e4..b8ebb84b19 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -42,6 +42,7 @@ add_component_dir (esm loadweap records aipackage effectlist spelllist variant variantimp loadtes3 cellref filter savedgame journalentry queststate locals globalscript player objectstate cellid cellstate globalmap lightstate inventorystate containerstate npcstate creaturestate dialoguestate statstate npcstats creaturestats weatherstate quickkeys fogstate spellstate activespells creaturelevliststate doorstate projectilestate + aisequence ) add_component_dir (misc @@ -71,7 +72,7 @@ add_component_dir (translation add_definitions(-DTERRAIN_USE_SHADER=1) add_component_dir (terrain - quadtreenode chunk world storage material buffercache defs + quadtreenode chunk world defaultworld storage material buffercache defs backgroundloader ) add_component_dir (loadinglistener diff --git a/components/compiler/controlparser.hpp b/components/compiler/controlparser.hpp index 1175a0ed58..59958fd902 100644 --- a/components/compiler/controlparser.hpp +++ b/components/compiler/controlparser.hpp @@ -52,7 +52,7 @@ namespace Compiler Literals& literals); void appendCode (std::vector& code) const; - ///< store generated code in \æ code. + ///< store generated code in \a code. virtual bool parseName (const std::string& name, const TokenLoc& loc, Scanner& scanner); diff --git a/components/compiler/exprparser.cpp b/components/compiler/exprparser.cpp index ed628278b5..e54b2e2a8f 100644 --- a/components/compiler/exprparser.cpp +++ b/components/compiler/exprparser.cpp @@ -775,7 +775,7 @@ namespace Compiler { parser.reset(); - if (optional) + if (optional || *iter == 'X') parser.setOptional (true); scanner.scan (parser); @@ -783,17 +783,20 @@ namespace Compiler if (optional && parser.isEmpty()) break; - std::vector tmp; + if (*iter != 'X') + { + std::vector tmp; - char type = parser.append (tmp); + char type = parser.append (tmp); - if (type!=*iter) - Generator::convert (tmp, type, *iter); + if (type!=*iter) + Generator::convert (tmp, type, *iter); - stack.push (tmp); + stack.push (tmp); - if (optional) - ++optionalCount; + if (optional) + ++optionalCount; + } } } diff --git a/components/compiler/extensions.hpp b/components/compiler/extensions.hpp index 3f91ca357d..d229751dee 100644 --- a/components/compiler/extensions.hpp +++ b/components/compiler/extensions.hpp @@ -20,7 +20,8 @@ namespace Compiler l - Integer
s - Short
S - String, case preserved
- x - Optional, ignored argument + x - Optional, ignored string argument + X - Optional, ignored integer argument **/ typedef std::string ScriptArgs; @@ -105,7 +106,7 @@ namespace Compiler ///< Append code for function to \a code. void listKeywords (std::vector& keywords) const; - ///< Append all known keywords to \æ kaywords. + ///< Append all known keywords to \a kaywords. }; } diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index 0f726a52d2..e095958d18 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -70,6 +70,7 @@ namespace Compiler extensions.registerFunction ("getlineofsight", 'l', "c", opcodeGetLineOfSight, opcodeGetLineOfSightExplicit); extensions.registerFunction ("getlos", 'l', "c", opcodeGetLineOfSight, opcodeGetLineOfSightExplicit); extensions.registerFunction("gettarget", 'l', "c", opcodeGetTarget, opcodeGetTargetExplicit); + extensions.registerInstruction("face", "llX", opcodeFace, opcodeFaceExplicit); } } @@ -167,7 +168,6 @@ namespace Compiler extensions.registerFunction ("getjournalindex", 'l', "c", opcodeGetJournalIndex); extensions.registerInstruction ("addtopic", "S" , opcodeAddTopic); extensions.registerInstruction ("choice", "/SlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSl", opcodeChoice); - extensions.registerInstruction("forcegreeting","",opcodeForceGreeting); extensions.registerInstruction("forcegreeting","",opcodeForceGreeting, opcodeForceGreetingExplicit); extensions.registerInstruction("goodbye", "", opcodeGoodbye); @@ -180,7 +180,8 @@ namespace Compiler extensions.registerFunction("samefaction", 'l', "", opcodeSameFaction, opcodeSameFactionExplicit); extensions.registerInstruction("modfactionreaction", "ccl", opcodeModFactionReaction); - extensions.registerFunction("getfactionreaction", 'l', "ccl", opcodeGetFactionReaction); + extensions.registerFunction("getfactionreaction", 'l', "ccX", opcodeGetFactionReaction); + extensions.registerInstruction("clearinfoactor", "", opcodeClearInfoActor, opcodeClearInfoActorExplicit); } } @@ -215,6 +216,9 @@ namespace Compiler extensions.registerInstruction ("showmap", "S", opcodeShowMap); extensions.registerInstruction ("fillmap", "", opcodeFillMap); + extensions.registerInstruction ("menutest", "/l", opcodeMenuTest); + extensions.registerInstruction ("togglemenus", "", opcodeToggleMenus); + extensions.registerInstruction ("tm", "", opcodeToggleMenus); } } @@ -280,6 +284,8 @@ namespace Compiler extensions.registerInstruction ("enablelevitation", "", opcodeEnableLevitation); extensions.registerFunction ("getpcinjail", 'l', "", opcodeGetPcInJail); extensions.registerFunction ("getpctraveling", 'l', "", opcodeGetPcTraveling); + extensions.registerInstruction ("betacomment", "S", opcodeBetaComment, opcodeBetaCommentExplicit); + extensions.registerInstruction ("bc", "S", opcodeBetaComment, opcodeBetaCommentExplicit); } } @@ -400,7 +406,7 @@ namespace Compiler extensions.registerInstruction ("setpccrimelevel", "f", opcodeSetPCCrimeLevel); extensions.registerInstruction ("modpccrimelevel", "f", opcodeModPCCrimeLevel); - extensions.registerInstruction ("addspell", "cx", opcodeAddSpell, opcodeAddSpellExplicit); + extensions.registerInstruction ("addspell", "cxX", opcodeAddSpell, opcodeAddSpellExplicit); extensions.registerInstruction ("removespell", "c", opcodeRemoveSpell, opcodeRemoveSpellExplicit); extensions.registerInstruction ("removespelleffects", "c", opcodeRemoveSpellEffects, @@ -438,7 +444,7 @@ namespace Compiler extensions.registerFunction ("getrace", 'l', "c", opcodeGetRace, opcodeGetRaceExplicit); - extensions.registerFunction ("getwerewolfkills", 'f', "", opcodeGetWerewolfKills); + extensions.registerFunction ("getwerewolfkills", 'l', "", opcodeGetWerewolfKills); extensions.registerFunction ("pcexpelled", 'l', "/S", opcodePcExpelled, opcodePcExpelledExplicit); extensions.registerInstruction ("pcexpell", "/S", opcodePcExpell, opcodePcExpellExplicit); extensions.registerInstruction ("pcclearexpelled", "/S", opcodePcClearExpelled, opcodePcClearExpelledExplicit); @@ -446,6 +452,7 @@ namespace Compiler extensions.registerInstruction ("lowerrank", "", opcodeLowerRank, opcodeLowerRankExplicit); extensions.registerFunction ("ondeath", 'l', "", opcodeOnDeath, opcodeOnDeathExplicit); + extensions.registerFunction ("onmurder", 'l', "", opcodeOnMurder, opcodeOnMurderExplicit); extensions.registerFunction ("onknockout", 'l', "", opcodeOnKnockout, opcodeOnKnockoutExplicit); extensions.registerFunction ("iswerewolf", 'l', "", opcodeIsWerewolf, opcodeIsWerewolfExplicit); diff --git a/components/compiler/fileparser.hpp b/components/compiler/fileparser.hpp index 13c7fb5376..00f96cff0a 100644 --- a/components/compiler/fileparser.hpp +++ b/components/compiler/fileparser.hpp @@ -31,7 +31,7 @@ namespace Compiler ///< Return script name. void getCode (std::vector& code) const; - ///< store generated code in \æ code. + ///< store generated code in \a code. const Locals& getLocals() const; ///< get local variable declarations. diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp index 8796c53c54..85ac578f34 100644 --- a/components/compiler/opcodes.hpp +++ b/components/compiler/opcodes.hpp @@ -59,6 +59,8 @@ namespace Compiler const int opcodeStartCombatExplicit = 0x200023b; const int opcodeStopCombat = 0x200023c; const int opcodeStopCombatExplicit = 0x200023d; + const int opcodeFace = 0x200024c; + const int opcodeFaceExplicit = 0x200024d; } namespace Animation @@ -154,6 +156,8 @@ namespace Compiler const int opcodeSameFactionExplicit = 0x20001b6; const int opcodeModFactionReaction = 0x2000242; const int opcodeGetFactionReaction = 0x2000243; + const int opcodeClearInfoActor = 0x2000245; + const int opcodeClearInfoActorExplicit = 0x2000246; } namespace Gui @@ -175,6 +179,8 @@ namespace Compiler const int opcodeToggleFullHelp = 0x2000151; const int opcodeShowMap = 0x20001a0; const int opcodeFillMap = 0x20001a1; + const int opcodeMenuTest = 0x2002c; + const int opcodeToggleMenus = 0x200024b; } namespace Misc @@ -204,6 +210,8 @@ namespace Compiler const int opcodeGetLockedExplicit = 0x20001c8; const int opcodeGetEffect = 0x20001cf; const int opcodeGetEffectExplicit = 0x20001d0; + const int opcodeBetaComment = 0x2000247; + const int opcodeBetaCommentExplicit = 0x2000248; const int opcodeAddSoulGem = 0x20001f3; const int opcodeAddSoulGemExplicit = 0x20001f4; const int opcodeRemoveSoulGem = 0x20027; @@ -380,6 +388,8 @@ namespace Compiler const int opcodeLowerRankExplicit = 0x20001eb; const int opcodeOnDeath = 0x20001fc; const int opcodeOnDeathExplicit = 0x2000205; + const int opcodeOnMurder = 0x2000249; + const int opcodeOnMurderExplicit = 0x200024a; const int opcodeOnKnockout = 0x2000240; const int opcodeOnKnockoutExplicit = 0x2000241; diff --git a/components/compiler/output.hpp b/components/compiler/output.hpp index 37b88cee92..c544fb715e 100644 --- a/components/compiler/output.hpp +++ b/components/compiler/output.hpp @@ -22,7 +22,7 @@ namespace Compiler Output (Locals& locals); void getCode (std::vector& code) const; - ///< store generated code in \æ code. + ///< store generated code in \a code. const Literals& getLiterals() const; diff --git a/components/compiler/scanner.hpp b/components/compiler/scanner.hpp index 19f4ca96ad..344ae05825 100644 --- a/components/compiler/scanner.hpp +++ b/components/compiler/scanner.hpp @@ -119,7 +119,7 @@ namespace Compiler ///< put back a keyword token void listKeywords (std::vector& keywords); - ///< Append all known keywords to \æ kaywords. + ///< Append all known keywords to \a kaywords. }; } diff --git a/components/compiler/scriptparser.hpp b/components/compiler/scriptparser.hpp index 000244c793..edabb9c5c8 100644 --- a/components/compiler/scriptparser.hpp +++ b/components/compiler/scriptparser.hpp @@ -27,7 +27,7 @@ namespace Compiler bool end = false); void getCode (std::vector& code) const; - ///< store generated code in \æ code. + ///< store generated code in \a code. virtual bool parseName (const std::string& name, const TokenLoc& loc, Scanner& scanner); diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 0d274474c6..f5bc2369a4 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -239,7 +239,7 @@ bool ContentSelectorModel::ContentModel::setData(const QModelIndex &index, const return false; EsmFile *file = item(index.row()); - QString fileName = file->filePath(); + QString fileName = file->fileName(); bool success = false; switch(role) @@ -266,7 +266,7 @@ bool ContentSelectorModel::ContentModel::setData(const QModelIndex &index, const if (success) { - success = setCheckState(fileName, value.toBool()); + success = setCheckState(file->filePath(), value.toBool()); emit dataChanged(index, index); } } @@ -277,19 +277,19 @@ bool ContentSelectorModel::ContentModel::setData(const QModelIndex &index, const int checkValue = value.toInt(); bool success = false; bool setState = false; - if ((checkValue==Qt::Checked) && !isChecked(fileName)) + if ((checkValue==Qt::Checked) && !isChecked(file->filePath())) { setState = true; success = true; } - else if ((checkValue == Qt::Checked) && isChecked (fileName)) + else if ((checkValue == Qt::Checked) && isChecked (file->filePath())) setState = true; else if (checkValue == Qt::Unchecked) setState = true; if (setState) { - setCheckState(fileName, success); + setCheckState(file->filePath(), success); emit dataChanged(index, index); } @@ -517,10 +517,10 @@ void ContentSelectorModel::ContentModel::sortFiles() } } -bool ContentSelectorModel::ContentModel::isChecked(const QString& name) const +bool ContentSelectorModel::ContentModel::isChecked(const QString& filepath) const { - if (mCheckStates.contains(name)) - return (mCheckStates[name] == Qt::Checked); + if (mCheckStates.contains(filepath)) + return (mCheckStates[filepath] == Qt::Checked); return false; } @@ -543,12 +543,12 @@ void ContentSelectorModel::ContentModel::refreshModel() emit dataChanged (index(0,0), index(rowCount()-1,0)); } -bool ContentSelectorModel::ContentModel::setCheckState(const QString &name, bool checkState) +bool ContentSelectorModel::ContentModel::setCheckState(const QString &filepath, bool checkState) { - if (name.isEmpty()) + if (filepath.isEmpty()) return false; - const EsmFile *file = item(name); + const EsmFile *file = item(filepath); if (!file) return false; @@ -558,8 +558,8 @@ bool ContentSelectorModel::ContentModel::setCheckState(const QString &name, bool if (checkState) state = Qt::Checked; - mCheckStates[name] = state; - emit dataChanged(indexFromItem(item(name)), indexFromItem(item(name))); + mCheckStates[filepath] = state; + emit dataChanged(indexFromItem(item(filepath)), indexFromItem(item(filepath))); if (file->isGameFile()) refreshModel(); @@ -586,7 +586,10 @@ bool ContentSelectorModel::ContentModel::setCheckState(const QString &name, bool { foreach (const EsmFile *downstreamFile, mFiles) { - if (downstreamFile->gameFiles().contains(name)) + QFileInfo fileInfo(filepath); + QString filename = fileInfo.fileName(); + + if (downstreamFile->gameFiles().contains(filename)) { if (mCheckStates.contains(downstreamFile->filePath())) mCheckStates[downstreamFile->filePath()] = Qt::Unchecked; diff --git a/components/contentselector/model/contentmodel.hpp b/components/contentselector/model/contentmodel.hpp index 8c8c2124bc..6d147bce98 100644 --- a/components/contentselector/model/contentmodel.hpp +++ b/components/contentselector/model/contentmodel.hpp @@ -45,8 +45,8 @@ namespace ContentSelectorModel const EsmFile *item(const QString &name) const; bool isEnabled (QModelIndex index) const; - bool isChecked(const QString &name) const; - bool setCheckState(const QString &name, bool isChecked); + bool isChecked(const QString &filepath) const; + bool setCheckState(const QString &filepath, bool isChecked); void setCheckStates (const QStringList &fileList, bool isChecked); ContentFileList checkedItems() const; void uncheckAll(); diff --git a/components/contentselector/model/esmfile.hpp b/components/contentselector/model/esmfile.hpp index ca24b52d11..7e1edcaba6 100644 --- a/components/contentselector/model/esmfile.hpp +++ b/components/contentselector/model/esmfile.hpp @@ -53,6 +53,8 @@ namespace ContentSelectorModel inline QDateTime modified() const { return mModified; } inline float format() const { return mFormat; } inline QString filePath() const { return mPath; } + + /// @note Contains file names, not paths. inline const QStringList &gameFiles() const { return mGameFiles; } inline QString description() const { return mDescription; } inline QString toolTip() const { return sToolTip.arg(mAuthor) diff --git a/components/esm/aisequence.cpp b/components/esm/aisequence.cpp new file mode 100644 index 0000000000..80440bdd3e --- /dev/null +++ b/components/esm/aisequence.cpp @@ -0,0 +1,211 @@ +#include "aisequence.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" + +#include "defs.hpp" + +#include + +namespace ESM +{ +namespace AiSequence +{ + + void AiWander::load(ESMReader &esm) + { + esm.getHNT (mData, "DATA"); + esm.getHNT(mStartTime, "STAR"); + } + + void AiWander::save(ESMWriter &esm) const + { + esm.writeHNT ("DATA", mData); + esm.writeHNT ("STAR", mStartTime); + } + + void AiTravel::load(ESMReader &esm) + { + esm.getHNT (mData, "DATA"); + } + + void AiTravel::save(ESMWriter &esm) const + { + esm.writeHNT ("DATA", mData); + } + + void AiEscort::load(ESMReader &esm) + { + esm.getHNT (mData, "DATA"); + mTargetId = esm.getHNString("TARG"); + esm.getHNT (mRemainingDuration, "DURA"); + mCellId = esm.getHNOString ("CELL"); + } + + void AiEscort::save(ESMWriter &esm) const + { + esm.writeHNT ("DATA", mData); + esm.writeHNString ("TARG", mTargetId); + esm.writeHNT ("DURA", mRemainingDuration); + if (!mCellId.empty()) + esm.writeHNString ("CELL", mCellId); + } + + void AiFollow::load(ESMReader &esm) + { + esm.getHNT (mData, "DATA"); + mTargetId = esm.getHNString("TARG"); + esm.getHNT (mRemainingDuration, "DURA"); + mCellId = esm.getHNOString ("CELL"); + esm.getHNT (mAlwaysFollow, "ALWY"); + } + + void AiFollow::save(ESMWriter &esm) const + { + esm.writeHNT ("DATA", mData); + esm.writeHNString("TARG", mTargetId); + esm.writeHNT ("DURA", mRemainingDuration); + if (!mCellId.empty()) + esm.writeHNString ("CELL", mCellId); + esm.writeHNT ("ALWY", mAlwaysFollow); + } + + void AiActivate::load(ESMReader &esm) + { + mTargetId = esm.getHNString("TARG"); + } + + void AiActivate::save(ESMWriter &esm) const + { + esm.writeHNString("TARG", mTargetId); + } + + void AiCombat::load(ESMReader &esm) + { + esm.getHNT (mTargetActorId, "TARG"); + } + + void AiCombat::save(ESMWriter &esm) const + { + esm.writeHNT ("TARG", mTargetActorId); + } + + void AiPursue::load(ESMReader &esm) + { + esm.getHNT (mTargetActorId, "TARG"); + } + + void AiPursue::save(ESMWriter &esm) const + { + esm.writeHNT ("TARG", mTargetActorId); + } + + AiSequence::~AiSequence() + { + for (std::vector::iterator it = mPackages.begin(); it != mPackages.end(); ++it) + delete it->mPackage; + } + + void AiSequence::save(ESMWriter &esm) const + { + for (std::vector::const_iterator it = mPackages.begin(); it != mPackages.end(); ++it) + { + esm.writeHNT ("AIPK", it->mType); + switch (it->mType) + { + case Ai_Wander: + static_cast(it->mPackage)->save(esm); + break; + case Ai_Travel: + static_cast(it->mPackage)->save(esm); + break; + case Ai_Escort: + static_cast(it->mPackage)->save(esm); + break; + case Ai_Follow: + static_cast(it->mPackage)->save(esm); + break; + case Ai_Activate: + static_cast(it->mPackage)->save(esm); + break; + case Ai_Combat: + static_cast(it->mPackage)->save(esm); + break; + case Ai_Pursue: + static_cast(it->mPackage)->save(esm); + break; + + default: + break; + } + } + } + + void AiSequence::load(ESMReader &esm) + { + while (esm.isNextSub("AIPK")) + { + int type; + esm.getHT(type); + + mPackages.push_back(AiPackageContainer()); + mPackages.back().mType = type; + + switch (type) + { + case Ai_Wander: + { + std::auto_ptr ptr (new AiWander()); + ptr->load(esm); + mPackages.back().mPackage = ptr.release(); + break; + } + case Ai_Travel: + { + std::auto_ptr ptr (new AiTravel()); + ptr->load(esm); + mPackages.back().mPackage = ptr.release(); + break; + } + case Ai_Escort: + { + std::auto_ptr ptr (new AiEscort()); + ptr->load(esm); + mPackages.back().mPackage = ptr.release(); + break; + } + case Ai_Follow: + { + std::auto_ptr ptr (new AiFollow()); + ptr->load(esm); + mPackages.back().mPackage = ptr.release(); + break; + } + case Ai_Activate: + { + std::auto_ptr ptr (new AiActivate()); + ptr->load(esm); + mPackages.back().mPackage = ptr.release(); + break; + } + case Ai_Combat: + { + std::auto_ptr ptr (new AiCombat()); + ptr->load(esm); + mPackages.back().mPackage = ptr.release(); + break; + } + case Ai_Pursue: + { + std::auto_ptr ptr (new AiPursue()); + ptr->load(esm); + mPackages.back().mPackage = ptr.release(); + break; + } + default: + return; + } + } + } +} +} diff --git a/components/esm/aisequence.hpp b/components/esm/aisequence.hpp new file mode 100644 index 0000000000..bf0e17fa06 --- /dev/null +++ b/components/esm/aisequence.hpp @@ -0,0 +1,154 @@ +#ifndef OPENMW_COMPONENTS_ESM_AISEQUENCE_H +#define OPENMW_COMPONENTS_ESM_AISEQUENCE_H + +#include +#include + +#include "defs.hpp" + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + namespace AiSequence + { + + // format 0, saved games only + // As opposed to AiPackageList, this stores the "live" version of AI packages. + + enum AiPackages + { + Ai_Wander = ESM::FourCC<'W','A','N','D'>::value, + Ai_Travel = ESM::FourCC<'T','R','A','V'>::value, + Ai_Escort = ESM::FourCC<'E','S','C','O'>::value, + Ai_Follow = ESM::FourCC<'F','O','L','L'>::value, + Ai_Activate = ESM::FourCC<'A','C','T','I'>::value, + Ai_Combat = ESM::FourCC<'C','O','M','B'>::value, + Ai_Pursue = ESM::FourCC<'P','U','R','S'>::value + }; + + + struct AiPackage + { + virtual ~AiPackage() {} + }; + + +#pragma pack(push,1) + struct AiWanderData + { + short mDistance; + short mDuration; + unsigned char mTimeOfDay; + unsigned char mIdle[8]; + unsigned char mShouldRepeat; + }; + struct AiTravelData + { + float mX, mY, mZ; + }; + struct AiEscortData + { + float mX, mY, mZ; + short mDuration; + }; + +#pragma pack(pop) + + struct AiWander : AiPackage + { + AiWanderData mData; + ESM::TimeStamp mStartTime; + + /// \todo add more AiWander state + + void load(ESMReader &esm); + void save(ESMWriter &esm) const; + }; + + struct AiTravel : AiPackage + { + AiTravelData mData; + + void load(ESMReader &esm); + void save(ESMWriter &esm) const; + }; + + struct AiEscort : AiPackage + { + AiEscortData mData; + + std::string mTargetId; + std::string mCellId; + float mRemainingDuration; + + void load(ESMReader &esm); + void save(ESMWriter &esm) const; + }; + + struct AiFollow : AiPackage + { + AiEscortData mData; + + std::string mTargetId; + std::string mCellId; + float mRemainingDuration; + + bool mAlwaysFollow; + + void load(ESMReader &esm); + void save(ESMWriter &esm) const; + }; + + struct AiActivate : AiPackage + { + std::string mTargetId; + + void load(ESMReader &esm); + void save(ESMWriter &esm) const; + }; + + struct AiCombat : AiPackage + { + int mTargetActorId; + + void load(ESMReader &esm); + void save(ESMWriter &esm) const; + }; + + struct AiPursue : AiPackage + { + int mTargetActorId; + + void load(ESMReader &esm); + void save(ESMWriter &esm) const; + }; + + struct AiPackageContainer + { + int mType; + + AiPackage* mPackage; + }; + + struct AiSequence + { + AiSequence() {} + ~AiSequence(); + + std::vector mPackages; + + void load (ESMReader &esm); + void save (ESMWriter &esm) const; + + private: + AiSequence(const AiSequence&); + AiSequence& operator=(const AiSequence&); + }; + + } + +} + +#endif diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp index d785fb2b60..84c638d9c7 100644 --- a/components/esm/cellref.cpp +++ b/components/esm/cellref.cpp @@ -108,13 +108,13 @@ void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory) cons esm.writeHNT("NAM9", mGoldValue); } - if (mTeleport && !inInventory) + if (!inInventory && mTeleport) { esm.writeHNT("DODT", mDoorDest); esm.writeHNOCString("DNAM", mDestCell); } - if (mLockLevel != 0 && !inInventory) { + if (!inInventory && mLockLevel != 0) { esm.writeHNT("FLTV", mLockLevel); } @@ -127,13 +127,13 @@ void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory) cons if (mReferenceBlocked != -1) esm.writeHNT("UNAM", mReferenceBlocked); - if (mFltv != 0 && !inInventory) + if (!inInventory && mFltv != 0) esm.writeHNT("FLTV", mFltv); if (!inInventory) esm.writeHNT("DATA", mPos, 24); - if (mNam0 != 0 && !inInventory) + if (!inInventory && mNam0 != 0) esm.writeHNT("NAM0", mNam0); } @@ -158,6 +158,7 @@ void ESM::CellRef::blank() mReferenceBlocked = 0; mFltv = 0; mNam0 = 0; + mTeleport = false; for (int i=0; i<3; ++i) { diff --git a/components/esm/creaturestats.cpp b/components/esm/creaturestats.cpp index 3860e9351f..0a9a361093 100644 --- a/components/esm/creaturestats.cpp +++ b/components/esm/creaturestats.cpp @@ -21,6 +21,9 @@ void ESM::CreatureStats::load (ESMReader &esm) mDied = false; esm.getHNOT (mDied, "DIED"); + mMurdered = false; + esm.getHNOT (mMurdered, "MURD"); + mFriendlyHits = 0; esm.getHNOT (mFriendlyHits, "FRHT"); @@ -79,6 +82,32 @@ void ESM::CreatureStats::load (ESMReader &esm) mSpells.load(esm); mActiveSpells.load(esm); + mAiSequence.load(esm); + + while (esm.isNextSub("SUMM")) + { + int magicEffect; + esm.getHT(magicEffect); + int actorId; + esm.getHNT (actorId, "ACID"); + mSummonedCreatureMap[magicEffect] = actorId; + } + + while (esm.isNextSub("GRAV")) + { + int actorId; + esm.getHT(actorId); + mSummonGraveyard.push_back(actorId); + } + + mHasAiSettings = false; + esm.getHNOT(mHasAiSettings, "AISE"); + + if (mHasAiSettings) + { + for (int i=0; i<4; ++i) + mAiSettings[i].load(esm); + } } void ESM::CreatureStats::save (ESMWriter &esm) const @@ -101,6 +130,9 @@ void ESM::CreatureStats::save (ESMWriter &esm) const if (mDied) esm.writeHNT ("DIED", mDied); + if (mMurdered) + esm.writeHNT ("MURD", mMurdered); + if (mFriendlyHits) esm.writeHNT ("FRHT", mFriendlyHits); @@ -160,4 +192,20 @@ void ESM::CreatureStats::save (ESMWriter &esm) const mSpells.save(esm); mActiveSpells.save(esm); + mAiSequence.save(esm); + + for (std::map::const_iterator it = mSummonedCreatureMap.begin(); it != mSummonedCreatureMap.end(); ++it) + { + esm.writeHNT ("SUMM", it->first); + esm.writeHNT ("ACID", it->second); + } + + for (std::vector::const_iterator it = mSummonGraveyard.begin(); it != mSummonGraveyard.end(); ++it) + { + esm.writeHNT ("GRAV", *it); + } + + esm.writeHNT("AISE", mHasAiSettings); + for (int i=0; i<4; ++i) + mAiSettings[i].save(esm); } diff --git a/components/esm/creaturestats.hpp b/components/esm/creaturestats.hpp index 5ca3d071fe..7ae57da16c 100644 --- a/components/esm/creaturestats.hpp +++ b/components/esm/creaturestats.hpp @@ -11,6 +11,7 @@ #include "spellstate.hpp" #include "activespells.hpp" +#include "aisequence.hpp" namespace ESM { @@ -23,12 +24,21 @@ namespace ESM StatState mAttributes[8]; StatState mDynamic[3]; + AiSequence::AiSequence mAiSequence; + + bool mHasAiSettings; + StatState mAiSettings[4]; + + std::map mSummonedCreatureMap; + std::vector mSummonGraveyard; + ESM::TimeStamp mTradeTime; int mGoldPool; int mActorId; bool mDead; bool mDied; + bool mMurdered; int mFriendlyHits; bool mTalkedTo; bool mAlarmed; diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index bdeb95291f..f967af274a 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -111,6 +111,7 @@ enum RecNameInts REC_ACTC = FourCC<'A','C','T','C'>::value, REC_MPRJ = FourCC<'M','P','R','J'>::value, REC_PROJ = FourCC<'P','R','O','J'>::value, + REC_DCOU = FourCC<'D','C','O','U'>::value, // format 1 REC_FILT = 0x544C4946 diff --git a/components/esm/esmwriter.cpp b/components/esm/esmwriter.cpp index 9d8d943d97..06572ce8f4 100644 --- a/components/esm/esmwriter.cpp +++ b/components/esm/esmwriter.cpp @@ -18,6 +18,11 @@ namespace ESM mHeader.mData.version = ver; } + void ESMWriter::setType(int type) + { + mHeader.mData.type = type; + } + void ESMWriter::setAuthor(const std::string& auth) { mHeader.mData.author.assign (auth); diff --git a/components/esm/esmwriter.hpp b/components/esm/esmwriter.hpp index ca4f422172..e57c6e45d8 100644 --- a/components/esm/esmwriter.hpp +++ b/components/esm/esmwriter.hpp @@ -25,10 +25,15 @@ class ESMWriter ESMWriter(); unsigned int getVersion() const; + + // Set various header data (ESM::Header::Data). All of the below functions must be called before writing, + // otherwise this data will be left uninitialized. void setVersion(unsigned int ver = 0x3fa66666); + void setType(int type); void setEncoder(ToUTF8::Utf8Encoder *encoding); void setAuthor(const std::string& author); void setDescription(const std::string& desc); + // Set the record count for writing it in the file header void setRecordCount (int count); // Counts how many records we have actually written. diff --git a/components/esm/inventorystate.cpp b/components/esm/inventorystate.cpp index 1b0bc772e9..2154faa830 100644 --- a/components/esm/inventorystate.cpp +++ b/components/esm/inventorystate.cpp @@ -37,6 +37,8 @@ void ESM::InventoryState::load (ESMReader &esm) LightState state; int slot; read (esm, state, slot); + if (state.mCount == 0) + continue; mLights.push_back (std::make_pair (state, slot)); } else @@ -44,6 +46,8 @@ void ESM::InventoryState::load (ESMReader &esm) ObjectState state; int slot; read (esm, state, slot); + if (state.mCount == 0) + continue; mItems.push_back (std::make_pair (state, std::make_pair (id, slot))); } } diff --git a/components/esm/loadalch.cpp b/components/esm/loadalch.cpp index f6bfc6a11d..aac88482ff 100644 --- a/components/esm/loadalch.cpp +++ b/components/esm/loadalch.cpp @@ -10,7 +10,7 @@ namespace ESM void Potion::load(ESMReader &esm) { - mModel = esm.getHNString("MODL"); + mModel = esm.getHNOString("MODL"); mIcon = esm.getHNOString("TEXT"); // not ITEX here for some reason mScript = esm.getHNOString("SCRI"); mName = esm.getHNOString("FNAM"); diff --git a/components/esm/loadcell.cpp b/components/esm/loadcell.cpp index 0830c5de69..83864569fb 100644 --- a/components/esm/loadcell.cpp +++ b/components/esm/loadcell.cpp @@ -50,18 +50,14 @@ namespace ESM return ref.mRefNum == refNum; } - void Cell::load(ESMReader &esm, bool saveContext) { - // Ignore this for now, it might mean we should delete the entire - // cell? - // TODO: treat the special case "another plugin moved this ref, but we want to delete it"! - if (esm.isNextSub("DELE")) { - esm.skipHSub(); - } - - esm.getHNT(mData, "DATA", 12); + loadData(esm); + loadCell(esm, saveContext); +} +void Cell::loadCell(ESMReader &esm, bool saveContext) +{ mNAM0 = 0; if (mData.mFlags & Interior) @@ -73,12 +69,10 @@ void Cell::load(ESMReader &esm, bool saveContext) esm.getHT(waterl); mWater = (float) waterl; mWaterInt = true; - mHasWaterLevelRecord = true; } else if (esm.isNextSub("WHGT")) { esm.getHT(mWater); - mHasWaterLevelRecord = true; } // Quasi-exterior cells have a region (which determines the @@ -107,6 +101,18 @@ void Cell::load(ESMReader &esm, bool saveContext) } } +void Cell::loadData(ESMReader &esm) +{ + // Ignore this for now, it might mean we should delete the entire + // cell? + // TODO: treat the special case "another plugin moved this ref, but we want to delete it"! + if (esm.isNextSub("DELE")) { + esm.skipHSub(); + } + + esm.getHNT(mData, "DATA", 12); +} + void Cell::preLoad(ESMReader &esm) //Can't be "load" because it conflicts with function in esmtool { this->load(esm, false); @@ -124,14 +130,12 @@ void Cell::save(ESMWriter &esm) const esm.writeHNT("DATA", mData, 12); if (mData.mFlags & Interior) { - if (mHasWaterLevelRecord) { - if (mWaterInt) { - int water = - (mWater >= 0) ? (int) (mWater + 0.5) : (int) (mWater - 0.5); - esm.writeHNT("INTV", water); - } else { - esm.writeHNT("WHGT", mWater); - } + if (mWaterInt) { + int water = + (mWater >= 0) ? (int) (mWater + 0.5) : (int) (mWater - 0.5); + esm.writeHNT("INTV", water); + } else { + esm.writeHNT("WHGT", mWater); } if (mData.mFlags & QuasiEx) @@ -228,19 +232,6 @@ bool Cell::getNextMVRF(ESMReader &esm, MovedCellRef &mref) mAmbi.mFogDensity = 0; } - void Cell::merge(Cell *original, Cell *modified) - { - float waterLevel = original->mWater; - if (modified->mHasWaterLevelRecord) - { - waterLevel = modified->mWater; - } - // else: keep original water level, instead of resetting to 0 - - *original = *modified; - original->mWater = waterLevel; - } - CellId Cell::getCellId() const { CellId id; diff --git a/components/esm/loadcell.hpp b/components/esm/loadcell.hpp index 28204c9ee1..fb4b6b28ac 100644 --- a/components/esm/loadcell.hpp +++ b/components/esm/loadcell.hpp @@ -78,10 +78,7 @@ struct Cell float mFogDensity; }; - Cell() : mWater(0), mHasWaterLevelRecord(false) {} - - /// Merge \a modified into \a original - static void merge (Cell* original, Cell* modified); + Cell() : mWater(0) {} // Interior cells are indexed by this (it's the 'id'), for exterior // cells it is optional. @@ -93,8 +90,8 @@ struct Cell std::vector mContextList; // File position; multiple positions for multiple plugin support DATAstruct mData; AMBIstruct mAmbi; + float mWater; // Water level - bool mHasWaterLevelRecord; bool mWaterInt; int mMapColor; int mNAM0; @@ -109,7 +106,10 @@ struct Cell // This method is left in for compatibility with esmtool. Parsing moved references currently requires // passing ESMStore, bit it does not know about this parameter, so we do it this way. - void load(ESMReader &esm, bool saveContext = true); + void load(ESMReader &esm, bool saveContext = true); // Load everything (except references) + void loadData(ESMReader &esm); // Load DATAstruct only + void loadCell(ESMReader &esm, bool saveContext = true); // Load everything, except DATAstruct and references + void save(ESMWriter &esm) const; bool isExterior() const diff --git a/components/esm/loadfact.cpp b/components/esm/loadfact.cpp index 0924efb174..db7e5b7b44 100644 --- a/components/esm/loadfact.cpp +++ b/components/esm/loadfact.cpp @@ -12,7 +12,7 @@ namespace ESM int& Faction::FADTstruct::getSkill (int index, bool ignored) { - if (index<0 || index>=6) + if (index<0 || index>=7) throw std::logic_error ("skill index out of range"); return mSkills[index]; @@ -20,7 +20,7 @@ namespace ESM int Faction::FADTstruct::getSkill (int index, bool ignored) const { - if (index<0 || index>=6) + if (index<0 || index>=7) throw std::logic_error ("skill index out of range"); return mSkills[index]; @@ -75,7 +75,6 @@ void Faction::save(ESMWriter &esm) const { mName.clear(); mData.mAttribute[0] = mData.mAttribute[1] = 0; - mData.mUnknown = -1; mData.mIsHidden = 0; for (int i=0; i<10; ++i) @@ -87,7 +86,7 @@ void Faction::save(ESMWriter &esm) const mRanks[i].clear(); } - for (int i=0; i<6; ++i) + for (int i=0; i<7; ++i) mData.mSkills[i] = 0; mReactions.clear(); diff --git a/components/esm/loadfact.hpp b/components/esm/loadfact.hpp index 75e30a5bf1..d31670fe28 100644 --- a/components/esm/loadfact.hpp +++ b/components/esm/loadfact.hpp @@ -40,8 +40,9 @@ struct Faction RankData mRankData[10]; - int mSkills[6]; // IDs of skills this faction require - int mUnknown; // Always -1? + int mSkills[7]; // IDs of skills this faction require + // Each element will either contain an ESM::Skill index, or -1. + int mIsHidden; // 1 - hidden from player int& getSkill (int index, bool ignored = false); diff --git a/components/esm/loadinfo.cpp b/components/esm/loadinfo.cpp index f86ad3b51b..c9860dcef1 100644 --- a/components/esm/loadinfo.cpp +++ b/components/esm/loadinfo.cpp @@ -18,6 +18,9 @@ void DialInfo::load(ESMReader &esm) esm.getHT(mData, 12); } + if (!esm.hasMoreSubs()) + return; + // What follows is somewhat spaghetti-ish, but it's worth if for // an extra speedup. INFO is by far the most common record type. diff --git a/components/esm/loadstat.cpp b/components/esm/loadstat.cpp index a71f22dc23..53d1b4bb59 100644 --- a/components/esm/loadstat.cpp +++ b/components/esm/loadstat.cpp @@ -10,6 +10,7 @@ namespace ESM void Static::load(ESMReader &esm) { + mPersistent = esm.getRecordFlags() & 0x0400; mModel = esm.getHNString("MODL"); } void Static::save(ESMWriter &esm) const diff --git a/components/esm/loadstat.hpp b/components/esm/loadstat.hpp index d912d10583..45b05136ad 100644 --- a/components/esm/loadstat.hpp +++ b/components/esm/loadstat.hpp @@ -26,6 +26,8 @@ struct Static std::string mId, mModel; + bool mPersistent; + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/esm/statstate.hpp b/components/esm/statstate.hpp index 4b4023bc22..f1a3b4d794 100644 --- a/components/esm/statstate.hpp +++ b/components/esm/statstate.hpp @@ -12,7 +12,8 @@ namespace ESM struct StatState { T mBase; - T mMod; + T mMod; // Note: can either be the modifier, or the modified value. + // A bit inconsistent, but we can't fix this without breaking compatibility. T mCurrent; T mDamage; float mProgress; @@ -30,7 +31,9 @@ namespace ESM void StatState::load (ESMReader &esm) { esm.getHNT (mBase, "STBA"); - esm.getHNT (mMod, "STMO"); + + mMod = 0; + esm.getHNOT (mMod, "STMO"); mCurrent = 0; esm.getHNOT (mCurrent, "STCU"); mDamage = 0; @@ -43,7 +46,9 @@ namespace ESM void StatState::save (ESMWriter &esm) const { esm.writeHNT ("STBA", mBase); - esm.writeHNT ("STMO", mMod); + + if (mMod != 0) + esm.writeHNT ("STMO", mMod); if (mCurrent) esm.writeHNT ("STCU", mCurrent); @@ -56,4 +61,4 @@ namespace ESM } } -#endif \ No newline at end of file +#endif diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index ffa911b44f..58d75d1fd7 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -18,7 +18,7 @@ static const char* const openmwCfgFile = "openmw.cfg"; const char* const mwToken = "?mw?"; const char* const localToken = "?local?"; -const char* const userToken = "?user?"; +const char* const userDataToken = "?userdata?"; const char* const globalToken = "?global?"; ConfigurationManager::ConfigurationManager() @@ -40,7 +40,7 @@ void ConfigurationManager::setupTokensMapping() { mTokensMapping.insert(std::make_pair(mwToken, &FixedPath<>::getInstallPath)); mTokensMapping.insert(std::make_pair(localToken, &FixedPath<>::getLocalPath)); - mTokensMapping.insert(std::make_pair(userToken, &FixedPath<>::getUserConfigPath)); + mTokensMapping.insert(std::make_pair(userDataToken, &FixedPath<>::getUserDataPath)); mTokensMapping.insert(std::make_pair(globalToken, &FixedPath<>::getGlobalDataPath)); } diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index e44f4a6f30..920e634e65 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -89,7 +89,14 @@ public: float lifetime; float lifetimeRandom; - int emitFlags; // Bit 0: Emit Rate toggle bit (0 = auto adjust, 1 = use Emit Rate value) + enum EmitFlags + { + NoAutoAdjust = 0x1 // If this flag is set, we use the emitRate value. Otherwise, + // we calculate an emit rate so that the maximum number of particles + // in the system (numParticles) is never exceeded. + }; + int emitFlags; + Ogre::Vector3 offsetRandom; NodePtr emitter; diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 10847e8f4d..84f4aaceeb 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -172,7 +172,8 @@ NIFFile::ptr NIFFile::create (const std::string &name) { return LoadedCache::cre /// Open a NIF stream. The name is used for error messages. NIFFile::NIFFile(const std::string &name, psudo_private_modifier) - : filename(name) + : ver(0) + , filename(name) { parse(); } diff --git a/components/nif/niffile.hpp b/components/nif/niffile.hpp index daec80ea1c..d701242635 100644 --- a/components/nif/niffile.hpp +++ b/components/nif/niffile.hpp @@ -161,10 +161,15 @@ struct KeyListT { unsigned int mInterpolationType; VecType mKeys; + KeyListT() : mInterpolationType(sLinearInterpolation) {} + //Read in a KeyGroup (see http://niftools.sourceforge.net/doc/nif/NiKeyframeData.html) void read(NIFStream *nif, bool force=false) { assert(nif); + + mInterpolationType = 0; + size_t count = nif->getUInt(); if(count == 0 && !force) return; @@ -177,42 +182,49 @@ struct KeyListT { KeyT key; NIFStream &nifReference = *nif; - for(size_t i = 0;i < count;i++) + + if(mInterpolationType == sLinearInterpolation) { - if(mInterpolationType == sLinearInterpolation) + for(size_t i = 0;i < count;i++) { readTimeAndValue(nifReference, key); mKeys.push_back(key); } - else if(mInterpolationType == sQuadraticInterpolation) + } + else if(mInterpolationType == sQuadraticInterpolation) + { + for(size_t i = 0;i < count;i++) { readQuadratic(nifReference, key); mKeys.push_back(key); } - else if(mInterpolationType == sTBCInterpolation) + } + else if(mInterpolationType == sTBCInterpolation) + { + for(size_t i = 0;i < count;i++) { readTBC(nifReference, key); mKeys.push_back(key); } - //XYZ keys aren't actually read here. - //data.hpp sees that the last type read was sXYZInterpolation and: - // Eats a floating point number, then - // Re-runs the read function 3 more times, with force enabled so that the previous values aren't cleared. - // When it does that it's reading in a bunch of sLinearInterpolation keys, not sXYZInterpolation. - else if(mInterpolationType == sXYZInterpolation) - { - //Don't try to read XYZ keys into the wrong part - if ( count != 1 ) - nif->file->fail("XYZ_ROTATION_KEY count should always be '1' . Retrieved Value: "+Ogre::StringConverter::toString(count)); - } - else if (0 == mInterpolationType) - { - if (count != 0) - nif->file->fail("Interpolation type 0 doesn't work with keys"); - } - else - nif->file->fail("Unhandled interpolation type: "+Ogre::StringConverter::toString(mInterpolationType)); } + //XYZ keys aren't actually read here. + //data.hpp sees that the last type read was sXYZInterpolation and: + // Eats a floating point number, then + // Re-runs the read function 3 more times, with force enabled so that the previous values aren't cleared. + // When it does that it's reading in a bunch of sLinearInterpolation keys, not sXYZInterpolation. + else if(mInterpolationType == sXYZInterpolation) + { + //Don't try to read XYZ keys into the wrong part + if ( count != 1 ) + nif->file->fail("XYZ_ROTATION_KEY count should always be '1' . Retrieved Value: "+Ogre::StringConverter::toString(count)); + } + else if (0 == mInterpolationType) + { + if (count != 0) + nif->file->fail("Interpolation type 0 doesn't work with keys"); + } + else + nif->file->fail("Unhandled interpolation type: "+Ogre::StringConverter::toString(mInterpolationType)); } private: diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index cdddb94d0a..31d4e10d64 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -49,20 +49,6 @@ typedef unsigned char ubyte; namespace NifBullet { -struct TriangleMeshShape : public btBvhTriangleMeshShape -{ - TriangleMeshShape(btStridingMeshInterface* meshInterface, bool useQuantizedAabbCompression) - : btBvhTriangleMeshShape(meshInterface, useQuantizedAabbCompression) - { - } - - virtual ~TriangleMeshShape() - { - delete getTriangleInfoMap(); - delete m_meshInterface; - } -}; - ManualBulletShapeLoader::~ManualBulletShapeLoader() { } @@ -81,9 +67,8 @@ void ManualBulletShapeLoader::loadResource(Ogre::Resource *resource) mBoundingBox = NULL; mShape->mBoxTranslation = Ogre::Vector3(0,0,0); mShape->mBoxRotation = Ogre::Quaternion::IDENTITY; - mHasShape = false; - - btTriangleMesh* mesh1 = new btTriangleMesh(); + mCompoundShape = NULL; + mStaticMesh = NULL; // Load the NIF. TODO: Wrap this in a try-catch block once we're out // of the early stages of development. Right now we WANT to catch @@ -111,19 +96,36 @@ void ManualBulletShapeLoader::loadResource(Ogre::Resource *resource) mShape->mHasCollisionNode = hasRootCollisionNode(node); //do a first pass - handleNode(mesh1, node,0,false,false,false); + handleNode(node,0,false,false,false); if(mBoundingBox != NULL) { mShape->mCollisionShape = mBoundingBox; - delete mesh1; - } - else if (mHasShape && mShape->mCollide) - { - mShape->mCollisionShape = new TriangleMeshShape(mesh1,true); + delete mStaticMesh; + if (mCompoundShape) + { + int n = mCompoundShape->getNumChildShapes(); + for(int i=0; i getChildShape(i)); + delete mCompoundShape; + mShape->mAnimatedShapes.clear(); + } } else - delete mesh1; + { + if (mCompoundShape) + { + mShape->mCollisionShape = mCompoundShape; + if (mStaticMesh) + { + btTransform trans; + trans.setIdentity(); + mCompoundShape->addChildShape(trans, new TriangleMeshShape(mStaticMesh,true)); + } + } + else if (mStaticMesh) + mShape->mCollisionShape = new TriangleMeshShape(mStaticMesh,true); + } //second pass which create a shape for raycasting. mResourceName = mShape->getName(); @@ -131,23 +133,23 @@ void ManualBulletShapeLoader::loadResource(Ogre::Resource *resource) mBoundingBox = NULL; mShape->mBoxTranslation = Ogre::Vector3(0,0,0); mShape->mBoxRotation = Ogre::Quaternion::IDENTITY; - mHasShape = false; + mStaticMesh = NULL; + mCompoundShape = NULL; - btTriangleMesh* mesh2 = new btTriangleMesh(); + handleNode(node,0,true,true,false); - handleNode(mesh2, node,0,true,true,false); - - if(mBoundingBox != NULL) + if (mCompoundShape) { - mShape->mRaycastingShape = mBoundingBox; - delete mesh2; + mShape->mRaycastingShape = mCompoundShape; + if (mStaticMesh) + { + btTransform trans; + trans.setIdentity(); + mCompoundShape->addChildShape(trans, new TriangleMeshShape(mStaticMesh,true)); + } } - else if (mHasShape) - { - mShape->mRaycastingShape = new TriangleMeshShape(mesh2,true); - } - else - delete mesh2; + else if (mStaticMesh) + mShape->mRaycastingShape = new TriangleMeshShape(mStaticMesh,true); } bool ManualBulletShapeLoader::hasRootCollisionNode(Nif::Node const * node) @@ -172,14 +174,18 @@ bool ManualBulletShapeLoader::hasRootCollisionNode(Nif::Node const * node) return false; } -void ManualBulletShapeLoader::handleNode(btTriangleMesh* mesh, const Nif::Node *node, int flags, +void ManualBulletShapeLoader::handleNode(const Nif::Node *node, int flags, bool isCollisionNode, - bool raycasting, bool isMarker) + bool raycasting, bool isMarker, bool isAnimated) { // Accumulate the flags from all the child nodes. This works for all // the flags we currently use, at least. flags |= node->flags; + if (!node->controller.empty() && node->controller->recType == Nif::RC_NiKeyframeController + && (node->controller->flags & Nif::NiNode::ControllerFlag_Active)) + isAnimated = true; + if (!raycasting) isCollisionNode = isCollisionNode || (node->recType == Nif::RC_RootCollisionNode); else @@ -227,10 +233,12 @@ void ManualBulletShapeLoader::handleNode(btTriangleMesh* mesh, const Nif::Node * if ( (isCollisionNode || (!mShape->mHasCollisionNode && !raycasting)) && (!isMarker || (mShape->mHasCollisionNode && !raycasting))) { + // NOTE: a trishape with hasBounds=true, but no BBoxCollision flag should NOT go through handleNiTriShape! + // It must be ignored completely. + // (occurs in tr_ex_imp_wall_arch_04.nif) if(node->hasBounds) { - // Checking for BBoxCollision flag causes issues with some actors :/ - if (!(node->flags & Nif::NiNode::Flag_Hidden)) + if (flags & Nif::NiNode::Flag_BBoxCollision && !raycasting) { mShape->mBoxTranslation = node->boundPos; mShape->mBoxRotation = node->boundRot; @@ -240,7 +248,7 @@ void ManualBulletShapeLoader::handleNode(btTriangleMesh* mesh, const Nif::Node * else if(node->recType == Nif::RC_NiTriShape) { mShape->mCollide = !(flags&0x800); - handleNiTriShape(mesh, static_cast(node), flags, node->getWorldTransform(), raycasting); + handleNiTriShape(static_cast(node), flags, node->getWorldTransform(), raycasting, isAnimated); } } @@ -252,13 +260,13 @@ void ManualBulletShapeLoader::handleNode(btTriangleMesh* mesh, const Nif::Node * for(size_t i = 0;i < list.length();i++) { if(!list[i].empty()) - handleNode(mesh, list[i].getPtr(), flags, isCollisionNode, raycasting, isMarker); + handleNode(list[i].getPtr(), flags, isCollisionNode, raycasting, isMarker, isAnimated); } } } -void ManualBulletShapeLoader::handleNiTriShape(btTriangleMesh* mesh, const Nif::NiTriShape *shape, int flags, const Ogre::Matrix4 &transform, - bool raycasting) +void ManualBulletShapeLoader::handleNiTriShape(const Nif::NiTriShape *shape, int flags, const Ogre::Matrix4 &transform, + bool raycasting, bool isAnimated) { assert(shape != NULL); @@ -281,17 +289,64 @@ void ManualBulletShapeLoader::handleNiTriShape(btTriangleMesh* mesh, const Nif:: // bother setting it up. return; - mHasShape = true; + if (!shape->skin.empty()) + isAnimated = false; - const Nif::NiTriShapeData *data = shape->data.getPtr(); - const std::vector &vertices = data->vertices; - const short *triangles = &data->triangles[0]; - for(size_t i = 0;i < data->triangles.size();i+=3) + if (isAnimated) { - Ogre::Vector3 b1 = transform*vertices[triangles[i+0]]; - Ogre::Vector3 b2 = transform*vertices[triangles[i+1]]; - Ogre::Vector3 b3 = transform*vertices[triangles[i+2]]; - mesh->addTriangle(btVector3(b1.x,b1.y,b1.z),btVector3(b2.x,b2.y,b2.z),btVector3(b3.x,b3.y,b3.z)); + if (!mCompoundShape) + mCompoundShape = new btCompoundShape(); + + btTriangleMesh* childMesh = new btTriangleMesh(); + + const Nif::NiTriShapeData *data = shape->data.getPtr(); + + childMesh->preallocateVertices(data->vertices.size()); + childMesh->preallocateIndices(data->triangles.size()); + + const std::vector &vertices = data->vertices; + const std::vector &triangles = data->triangles; + + for(size_t i = 0;i < data->triangles.size();i+=3) + { + Ogre::Vector3 b1 = vertices[triangles[i+0]]; + Ogre::Vector3 b2 = vertices[triangles[i+1]]; + Ogre::Vector3 b3 = vertices[triangles[i+2]]; + childMesh->addTriangle(btVector3(b1.x,b1.y,b1.z),btVector3(b2.x,b2.y,b2.z),btVector3(b3.x,b3.y,b3.z)); + } + + TriangleMeshShape* childShape = new TriangleMeshShape(childMesh,true); + + childShape->setLocalScaling(btVector3(transform[0][0], transform[1][1], transform[2][2])); + + Ogre::Quaternion q = transform.extractQuaternion(); + Ogre::Vector3 v = transform.getTrans(); + btTransform trans(btQuaternion(q.x, q.y, q.z, q.w), btVector3(v.x, v.y, v.z)); + + if (raycasting) + mShape->mAnimatedRaycastingShapes.insert(std::make_pair(shape->name, mCompoundShape->getNumChildShapes())); + else + mShape->mAnimatedShapes.insert(std::make_pair(shape->name, mCompoundShape->getNumChildShapes())); + + mCompoundShape->addChildShape(trans, childShape); + } + else + { + if (!mStaticMesh) + mStaticMesh = new btTriangleMesh(); + + // Static shape, just transform all vertices into position + const Nif::NiTriShapeData *data = shape->data.getPtr(); + const std::vector &vertices = data->vertices; + const std::vector &triangles = data->triangles; + + for(size_t i = 0;i < data->triangles.size();i+=3) + { + Ogre::Vector3 b1 = transform*vertices[triangles[i+0]]; + Ogre::Vector3 b2 = transform*vertices[triangles[i+1]]; + Ogre::Vector3 b3 = transform*vertices[triangles[i+2]]; + mStaticMesh->addTriangle(btVector3(b1.x,b1.y,b1.z),btVector3(b2.x,b2.y,b2.z),btVector3(b3.x,b3.y,b3.z)); + } } } @@ -304,4 +359,53 @@ void ManualBulletShapeLoader::load(const std::string &name,const std::string &gr OEngine::Physic::BulletShapeManager::getSingleton().create(name,group,true,this); } +bool findBoundingBox (const Nif::Node* node, Ogre::Vector3& halfExtents, Ogre::Vector3& translation, Ogre::Quaternion& orientation) +{ + if(node->hasBounds) + { + if (!(node->flags & Nif::NiNode::Flag_Hidden)) + { + translation = node->boundPos; + orientation = node->boundRot; + halfExtents = node->boundXYZ; + return true; + } + } + + const Nif::NiNode *ninode = dynamic_cast(node); + if(ninode) + { + const Nif::NodeList &list = ninode->children; + for(size_t i = 0;i < list.length();i++) + { + if(!list[i].empty()) + if (findBoundingBox(list[i].getPtr(), halfExtents, translation, orientation)) + return true; + } + } + return false; +} + +bool getBoundingBox(const std::string& nifFile, Ogre::Vector3& halfExtents, Ogre::Vector3& translation, Ogre::Quaternion& orientation) +{ + Nif::NIFFile::ptr pnif (Nif::NIFFile::create (nifFile)); + Nif::NIFFile & nif = *pnif.get (); + + if (nif.numRoots() < 1) + { + return false; + } + + Nif::Record *r = nif.getRoot(0); + assert(r != NULL); + + Nif::Node *node = dynamic_cast(r); + if (node == NULL) + { + return false; + } + + return findBoundingBox(node, halfExtents, translation, orientation); +} + } // namespace NifBullet diff --git a/components/nifbullet/bulletnifloader.hpp b/components/nifbullet/bulletnifloader.hpp index d1e8763053..a9ee968b96 100644 --- a/components/nifbullet/bulletnifloader.hpp +++ b/components/nifbullet/bulletnifloader.hpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -44,6 +45,22 @@ namespace Nif namespace NifBullet { +// Subclass btBhvTriangleMeshShape to auto-delete the meshInterface +struct TriangleMeshShape : public btBvhTriangleMeshShape +{ + TriangleMeshShape(btStridingMeshInterface* meshInterface, bool useQuantizedAabbCompression) + : btBvhTriangleMeshShape(meshInterface, useQuantizedAabbCompression) + { + } + + virtual ~TriangleMeshShape() + { + delete getTriangleInfoMap(); + delete m_meshInterface; + } +}; + + /** *Load bulletShape from NIF files. */ @@ -52,8 +69,9 @@ class ManualBulletShapeLoader : public OEngine::Physic::BulletShapeLoader public: ManualBulletShapeLoader() : mShape(NULL) + , mStaticMesh(NULL) + , mCompoundShape(NULL) , mBoundingBox(NULL) - , mHasShape(false) { } @@ -88,7 +106,8 @@ private: /** *Parse a node. */ - void handleNode(btTriangleMesh* mesh, Nif::Node const *node, int flags, bool isCollisionNode, bool raycasting, bool isMarker); + void handleNode(Nif::Node const *node, int flags, bool isCollisionNode, + bool raycasting, bool isMarker, bool isAnimated=false); /** *Helper function @@ -98,16 +117,24 @@ private: /** *convert a NiTriShape to a bullet trishape. */ - void handleNiTriShape(btTriangleMesh* mesh, const Nif::NiTriShape *shape, int flags, const Ogre::Matrix4 &transform, bool raycasting); + void handleNiTriShape(const Nif::NiTriShape *shape, int flags, const Ogre::Matrix4 &transform, bool raycasting, bool isAnimated); std::string mResourceName; OEngine::Physic::BulletShape* mShape;//current shape - btBoxShape *mBoundingBox; - bool mHasShape; + btCompoundShape* mCompoundShape; + + btTriangleMesh* mStaticMesh; + + btBoxShape *mBoundingBox; }; + +bool getBoundingBox(const std::string& nifFile, Ogre::Vector3& halfExtents, Ogre::Vector3& translation, Ogre::Quaternion& orientation); + +bool findBoundingBox(const Nif::Node* node, Ogre::Vector3& halfExtents, Ogre::Vector3& translation, Ogre::Quaternion& orientation); + } #endif diff --git a/components/nifogre/material.cpp b/components/nifogre/material.cpp index 3a87e1d529..44831c13bb 100644 --- a/components/nifogre/material.cpp +++ b/components/nifogre/material.cpp @@ -106,7 +106,7 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata, const Nif::NiZBufferProperty *zprop, const Nif::NiSpecularProperty *specprop, const Nif::NiWireframeProperty *wireprop, - bool &needTangents) + bool &needTangents, bool particleMaterial) { Ogre::MaterialManager &matMgr = Ogre::MaterialManager::getSingleton(); Ogre::MaterialPtr material = matMgr.getByName(name); @@ -245,6 +245,11 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata, } } + if (particleMaterial) + { + alpha = 1.f; // Apparently ignored, might be overridden by particle vertex colors? + } + { // Generate a hash out of all properties that can affect the material. size_t h = 0; diff --git a/components/nifogre/material.hpp b/components/nifogre/material.hpp index b02c7c2366..abe1982eb9 100644 --- a/components/nifogre/material.hpp +++ b/components/nifogre/material.hpp @@ -49,7 +49,7 @@ public: const Nif::NiZBufferProperty *zprop, const Nif::NiSpecularProperty *specprop, const Nif::NiWireframeProperty *wireprop, - bool &needTangents); + bool &needTangents, bool particleMaterial=false); }; } diff --git a/components/nifogre/ogrenifloader.cpp b/components/nifogre/ogrenifloader.cpp index 36d7508214..eed320756c 100644 --- a/components/nifogre/ogrenifloader.cpp +++ b/components/nifogre/ogrenifloader.cpp @@ -756,7 +756,12 @@ class NIFObjectLoader Ogre::ParticleEmitter *emitter = partsys->addEmitter("Nif"); emitter->setParticleVelocity(partctrl->velocity - partctrl->velocityRandom*0.5f, partctrl->velocity + partctrl->velocityRandom*0.5f); - emitter->setEmissionRate(partctrl->emitRate); + + if (partctrl->emitFlags & Nif::NiParticleSystemController::NoAutoAdjust) + emitter->setEmissionRate(partctrl->emitRate); + else + emitter->setEmissionRate(partctrl->numParticles / (partctrl->lifetime + partctrl->lifetimeRandom/2)); + emitter->setTimeToLive(partctrl->lifetime, partctrl->lifetime + partctrl->lifetimeRandom); emitter->setParameter("width", Ogre::StringConverter::toString(partctrl->offsetRandom.x)); @@ -850,7 +855,10 @@ class NIFObjectLoader partsys->setMaterialName(NIFMaterialLoader::getMaterial(particledata, fullname, group, texprop, matprop, alphaprop, vertprop, zprop, specprop, - wireprop, needTangents)); + wireprop, needTangents, + // MW doesn't light particles, but the MaterialProperty + // used still has lighting, so that must be ignored. + true)); partsys->setCullIndividually(false); partsys->setParticleQuota(particledata->numParticles); diff --git a/components/nifogre/skeleton.cpp b/components/nifogre/skeleton.cpp index 26647e595d..c96f03950f 100644 --- a/components/nifogre/skeleton.cpp +++ b/components/nifogre/skeleton.cpp @@ -14,10 +14,24 @@ namespace NifOgre void NIFSkeletonLoader::buildBones(Ogre::Skeleton *skel, const Nif::Node *node, Ogre::Bone *parent) { Ogre::Bone *bone; - if(!skel->hasBone(node->name)) - bone = skel->createBone(node->name); + if (node->name.empty()) + { + // HACK: use " " instead of empty name, otherwise Ogre will replace it with an auto-generated + // name in SkeletonInstance::cloneBoneAndChildren. + static const char* emptyname = " "; + if (!skel->hasBone(emptyname)) + bone = skel->createBone(emptyname); + else + bone = skel->createBone(); + } else - bone = skel->createBone(); + { + if(!skel->hasBone(node->name)) + bone = skel->createBone(node->name); + else + bone = skel->createBone(); + } + if(parent) parent->addChild(bone); mNifToOgreHandleMap[node->recIndex] = bone->getHandle(); diff --git a/components/terrain/backgroundloader.cpp b/components/terrain/backgroundloader.cpp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/components/terrain/buffercache.cpp b/components/terrain/buffercache.cpp index f693c0e402..a3e67af5bd 100644 --- a/components/terrain/buffercache.cpp +++ b/components/terrain/buffercache.cpp @@ -118,7 +118,7 @@ namespace Terrain // North row = verts-1; - outerStep = 1 << (lodDeltas[North] + lodLevel); + outerStep = size_t(1) << (lodDeltas[North] + lodLevel); for (size_t col = 0; col < verts-1; col += outerStep) { indices.push_back(verts*(col+outerStep)+row); @@ -142,7 +142,7 @@ namespace Terrain // West size_t col = 0; - outerStep = 1 << (lodDeltas[West] + lodLevel); + outerStep = size_t(1) << (lodDeltas[West] + lodLevel); for (size_t row = 0; row < verts-1; row += outerStep) { indices.push_back(verts*col+row+outerStep); @@ -166,7 +166,7 @@ namespace Terrain // East col = verts-1; - outerStep = 1 << (lodDeltas[East] + lodLevel); + outerStep = size_t(1) << (lodDeltas[East] + lodLevel); for (size_t row = 0; row < verts-1; row += outerStep) { indices.push_back(verts*col+row); diff --git a/components/terrain/chunk.cpp b/components/terrain/chunk.cpp index bb8710b872..9c60ee0173 100644 --- a/components/terrain/chunk.cpp +++ b/components/terrain/chunk.cpp @@ -8,19 +8,17 @@ #include - -#include "world.hpp" // FIXME: for LoadResponseData, move to backgroundloader.hpp - namespace Terrain { - Chunk::Chunk(Ogre::HardwareVertexBufferSharedPtr uvBuffer, const Ogre::AxisAlignedBox& bounds, const LoadResponseData& data) + Chunk::Chunk(Ogre::HardwareVertexBufferSharedPtr uvBuffer, const Ogre::AxisAlignedBox& bounds, + const std::vector& positions, const std::vector& normals, const std::vector& colours) : mBounds(bounds) , mOwnMaterial(false) { mVertexData = OGRE_NEW Ogre::VertexData; mVertexData->vertexStart = 0; - mVertexData->vertexCount = data.mPositions.size()/3; + mVertexData->vertexCount = positions.size()/3; // Set up the vertex declaration, which specifies the info for each vertex (normals, colors, UVs, etc) Ogre::VertexDeclaration* vertexDecl = mVertexData->vertexDeclaration; @@ -48,9 +46,9 @@ namespace Terrain Ogre::HardwareVertexBufferSharedPtr colourBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_COLOUR), mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC); - vertexBuffer->writeData(0, vertexBuffer->getSizeInBytes(), &data.mPositions[0], true); - normalBuffer->writeData(0, normalBuffer->getSizeInBytes(), &data.mNormals[0], true); - colourBuffer->writeData(0, colourBuffer->getSizeInBytes(), &data.mColours[0], true); + vertexBuffer->writeData(0, vertexBuffer->getSizeInBytes(), &positions[0], true); + normalBuffer->writeData(0, normalBuffer->getSizeInBytes(), &normals[0], true); + colourBuffer->writeData(0, colourBuffer->getSizeInBytes(), &colours[0], true); mVertexData->vertexBufferBinding->setBinding(0, vertexBuffer); mVertexData->vertexBufferBinding->setBinding(1, normalBuffer); diff --git a/components/terrain/chunk.hpp b/components/terrain/chunk.hpp index 9550b30460..9b2ed76ac6 100644 --- a/components/terrain/chunk.hpp +++ b/components/terrain/chunk.hpp @@ -7,16 +7,16 @@ namespace Terrain { - class BufferCache; - struct LoadResponseData; - /** - * @brief Renders a chunk of terrain, either using alpha splatting or a composite map. + * @brief A movable object representing a chunk of terrain. */ class Chunk : public Ogre::Renderable, public Ogre::MovableObject { public: - Chunk (Ogre::HardwareVertexBufferSharedPtr uvBuffer, const Ogre::AxisAlignedBox& bounds, const LoadResponseData& data); + Chunk (Ogre::HardwareVertexBufferSharedPtr uvBuffer, const Ogre::AxisAlignedBox& bounds, + const std::vector& positions, + const std::vector& normals, + const std::vector& colours); virtual ~Chunk(); diff --git a/components/terrain/defaultworld.cpp b/components/terrain/defaultworld.cpp new file mode 100644 index 0000000000..9436582353 --- /dev/null +++ b/components/terrain/defaultworld.cpp @@ -0,0 +1,315 @@ +#include "defaultworld.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "storage.hpp" +#include "quadtreenode.hpp" + +namespace +{ + + bool isPowerOfTwo(int x) + { + return ( (x > 0) && ((x & (x - 1)) == 0) ); + } + + int nextPowerOfTwo (int v) + { + if (isPowerOfTwo(v)) return v; + int depth=0; + while(v) + { + v >>= 1; + depth++; + } + return 1 << depth; + } + + Terrain::QuadTreeNode* findNode (const Ogre::Vector2& center, Terrain::QuadTreeNode* node) + { + if (center == node->getCenter()) + return node; + + if (center.x > node->getCenter().x && center.y > node->getCenter().y) + return findNode(center, node->getChild(Terrain::NE)); + else if (center.x > node->getCenter().x && center.y < node->getCenter().y) + return findNode(center, node->getChild(Terrain::SE)); + else if (center.x < node->getCenter().x && center.y > node->getCenter().y) + return findNode(center, node->getChild(Terrain::NW)); + else //if (center.x < node->getCenter().x && center.y < node->getCenter().y) + return findNode(center, node->getChild(Terrain::SW)); + } + +} + +namespace Terrain +{ + + const Ogre::uint REQ_ID_CHUNK = 1; + const Ogre::uint REQ_ID_LAYERS = 2; + + DefaultWorld::DefaultWorld(Ogre::SceneManager* sceneMgr, + Storage* storage, int visibilityFlags, bool shaders, Alignment align, float minBatchSize, float maxBatchSize) + : World(sceneMgr, storage, visibilityFlags, shaders, align) + , mMinBatchSize(minBatchSize) + , mMaxBatchSize(maxBatchSize) + , mVisible(true) + , mMaxX(0) + , mMinX(0) + , mMaxY(0) + , mMinY(0) + , mChunksLoading(0) + , mWorkQueueChannel(0) + , mLayerLoadPending(true) + { +#if TERRAIN_USE_SHADER == 0 + if (mShaders) + std::cerr << "Compiled Terrain without shader support, disabling..." << std::endl; + mShaders = false; +#endif + + mCompositeMapSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC); + + /// \todo make composite map size configurable + Ogre::Camera* compositeMapCam = mCompositeMapSceneMgr->createCamera("a"); + mCompositeMapRenderTexture = Ogre::TextureManager::getSingleton().createManual( + "terrain/comp/rt", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + Ogre::TEX_TYPE_2D, 128, 128, 0, Ogre::PF_A8B8G8R8, Ogre::TU_RENDERTARGET); + mCompositeMapRenderTarget = mCompositeMapRenderTexture->getBuffer()->getRenderTarget(); + mCompositeMapRenderTarget->setAutoUpdated(false); + mCompositeMapRenderTarget->addViewport(compositeMapCam); + + storage->getBounds(mMinX, mMaxX, mMinY, mMaxY); + + int origSizeX = mMaxX-mMinX; + int origSizeY = mMaxY-mMinY; + + // Dividing a quad tree only works well for powers of two, so round up to the nearest one + int size = nextPowerOfTwo(std::max(origSizeX, origSizeY)); + + // Adjust the center according to the new size + float centerX = (mMinX+mMaxX)/2.f + (size-origSizeX)/2.f; + float centerY = (mMinY+mMaxY)/2.f + (size-origSizeY)/2.f; + + mRootSceneNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(); + + // While building the quadtree, remember leaf nodes since we need to load their layers + LayersRequestData data; + data.mPack = getShadersEnabled(); + + mRootNode = new QuadTreeNode(this, Root, size, Ogre::Vector2(centerX, centerY), NULL); + buildQuadTree(mRootNode, data.mNodes); + //loadingListener->indicateProgress(); + mRootNode->initAabb(); + //loadingListener->indicateProgress(); + mRootNode->initNeighbours(); + //loadingListener->indicateProgress(); + + Ogre::WorkQueue* wq = Ogre::Root::getSingleton().getWorkQueue(); + mWorkQueueChannel = wq->getChannel("LargeTerrain"); + wq->addRequestHandler(mWorkQueueChannel, this); + wq->addResponseHandler(mWorkQueueChannel, this); + + // Start loading layers in the background (for leaf nodes) + wq->addRequest(mWorkQueueChannel, REQ_ID_LAYERS, Ogre::Any(data)); + } + + DefaultWorld::~DefaultWorld() + { + Ogre::WorkQueue* wq = Ogre::Root::getSingleton().getWorkQueue(); + wq->removeRequestHandler(mWorkQueueChannel, this); + wq->removeResponseHandler(mWorkQueueChannel, this); + + delete mRootNode; + } + + void DefaultWorld::buildQuadTree(QuadTreeNode *node, std::vector& leafs) + { + float halfSize = node->getSize()/2.f; + + if (node->getSize() <= mMinBatchSize) + { + // We arrived at a leaf + float minZ,maxZ; + Ogre::Vector2 center = node->getCenter(); + float cellWorldSize = getStorage()->getCellWorldSize(); + if (mStorage->getMinMaxHeights(node->getSize(), center, minZ, maxZ)) + { + Ogre::AxisAlignedBox bounds(Ogre::Vector3(-halfSize*cellWorldSize, -halfSize*cellWorldSize, minZ), + Ogre::Vector3(halfSize*cellWorldSize, halfSize*cellWorldSize, maxZ)); + convertBounds(bounds); + node->setBoundingBox(bounds); + leafs.push_back(node); + } + else + node->markAsDummy(); // no data available for this node, skip it + return; + } + + if (node->getCenter().x - halfSize > mMaxX + || node->getCenter().x + halfSize < mMinX + || node->getCenter().y - halfSize > mMaxY + || node->getCenter().y + halfSize < mMinY ) + // Out of bounds of the actual terrain - this will happen because + // we rounded the size up to the next power of two + { + node->markAsDummy(); + return; + } + + // Not a leaf, create its children + node->createChild(SW, halfSize, node->getCenter() - halfSize/2.f); + node->createChild(SE, halfSize, node->getCenter() + Ogre::Vector2(halfSize/2.f, -halfSize/2.f)); + node->createChild(NW, halfSize, node->getCenter() + Ogre::Vector2(-halfSize/2.f, halfSize/2.f)); + node->createChild(NE, halfSize, node->getCenter() + halfSize/2.f); + buildQuadTree(node->getChild(SW), leafs); + buildQuadTree(node->getChild(SE), leafs); + buildQuadTree(node->getChild(NW), leafs); + buildQuadTree(node->getChild(NE), leafs); + + // if all children are dummy, we are also dummy + for (int i=0; i<4; ++i) + { + if (!node->getChild((ChildDirection)i)->isDummy()) + return; + } + node->markAsDummy(); + } + + void DefaultWorld::update(const Ogre::Vector3& cameraPos) + { + if (!mVisible) + return; + mRootNode->update(cameraPos); + mRootNode->updateIndexBuffers(); + } + + Ogre::AxisAlignedBox DefaultWorld::getWorldBoundingBox (const Ogre::Vector2& center) + { + if (center.x > mMaxX + || center.x < mMinX + || center.y > mMaxY + || center.y < mMinY) + return Ogre::AxisAlignedBox::BOX_NULL; + QuadTreeNode* node = findNode(center, mRootNode); + return node->getWorldBoundingBox(); + } + + void DefaultWorld::renderCompositeMap(Ogre::TexturePtr target) + { + mCompositeMapRenderTarget->update(); + target->getBuffer()->blit(mCompositeMapRenderTexture->getBuffer()); + } + + void DefaultWorld::clearCompositeMapSceneManager() + { + mCompositeMapSceneMgr->destroyAllManualObjects(); + mCompositeMapSceneMgr->clearScene(); + } + + void DefaultWorld::applyMaterials(bool shadows, bool splitShadows) + { + mShadows = shadows; + mSplitShadows = splitShadows; + mRootNode->applyMaterials(); + } + + void DefaultWorld::setVisible(bool visible) + { + if (visible && !mVisible) + mSceneMgr->getRootSceneNode()->addChild(mRootSceneNode); + else if (!visible && mVisible) + mSceneMgr->getRootSceneNode()->removeChild(mRootSceneNode); + + mVisible = visible; + } + + bool DefaultWorld::getVisible() + { + return mVisible; + } + + void DefaultWorld::syncLoad() + { + while (mChunksLoading || mLayerLoadPending) + { + OGRE_THREAD_SLEEP(0); + Ogre::Root::getSingleton().getWorkQueue()->processResponses(); + } + } + + Ogre::WorkQueue::Response* DefaultWorld::handleRequest(const Ogre::WorkQueue::Request *req, const Ogre::WorkQueue *srcQ) + { + if (req->getType() == REQ_ID_CHUNK) + { + const LoadRequestData data = Ogre::any_cast(req->getData()); + + QuadTreeNode* node = data.mNode; + + LoadResponseData* responseData = new LoadResponseData(); + + getStorage()->fillVertexBuffers(node->getNativeLodLevel(), node->getSize(), node->getCenter(), getAlign(), + responseData->mPositions, responseData->mNormals, responseData->mColours); + + return OGRE_NEW Ogre::WorkQueue::Response(req, true, Ogre::Any(responseData)); + } + else // REQ_ID_LAYERS + { + const LayersRequestData data = Ogre::any_cast(req->getData()); + + LayersResponseData* responseData = new LayersResponseData(); + + getStorage()->getBlendmaps(data.mNodes, responseData->mLayerCollections, data.mPack); + + return OGRE_NEW Ogre::WorkQueue::Response(req, true, Ogre::Any(responseData)); + } + } + + void DefaultWorld::handleResponse(const Ogre::WorkQueue::Response *res, const Ogre::WorkQueue *srcQ) + { + assert(res->succeeded() && "Response failure not handled"); + + if (res->getRequest()->getType() == REQ_ID_CHUNK) + { + LoadResponseData* data = Ogre::any_cast(res->getData()); + + const LoadRequestData requestData = Ogre::any_cast(res->getRequest()->getData()); + + requestData.mNode->load(*data); + + delete data; + + --mChunksLoading; + } + else // REQ_ID_LAYERS + { + LayersResponseData* data = Ogre::any_cast(res->getData()); + + for (std::vector::iterator it = data->mLayerCollections.begin(); it != data->mLayerCollections.end(); ++it) + { + it->mTarget->loadLayers(*it); + } + + delete data; + + mRootNode->loadMaterials(); + + mLayerLoadPending = false; + } + } + + void DefaultWorld::queueLoad(QuadTreeNode *node) + { + LoadRequestData data; + data.mNode = node; + + Ogre::Root::getSingleton().getWorkQueue()->addRequest(mWorkQueueChannel, REQ_ID_CHUNK, Ogre::Any(data)); + ++mChunksLoading; + } +} diff --git a/components/terrain/defaultworld.hpp b/components/terrain/defaultworld.hpp new file mode 100644 index 0000000000..8769a0d88d --- /dev/null +++ b/components/terrain/defaultworld.hpp @@ -0,0 +1,156 @@ +#ifndef COMPONENTS_TERRAIN_H +#define COMPONENTS_TERRAIN_H + +#include +#include +#include + +#include "world.hpp" + +namespace Ogre +{ + class Camera; +} + +namespace Terrain +{ + + class QuadTreeNode; + class Storage; + + /** + * @brief A quadtree-based terrain implementation suitable for large data sets. \n + * Near cells are rendered with alpha splatting, distant cells are merged + * together in batches and have their layers pre-rendered onto a composite map. \n + * Cracks at LOD transitions are avoided using stitching. + * @note Multiple cameras are not supported yet + */ + class DefaultWorld : public World, public Ogre::WorkQueue::RequestHandler, public Ogre::WorkQueue::ResponseHandler + { + public: + /// @note takes ownership of \a storage + /// @param sceneMgr scene manager to use + /// @param storage Storage instance to get terrain data from (heights, normals, colors, textures..) + /// @param visbilityFlags visibility flags for the created meshes + /// @param shaders Whether to use splatting shader, or multi-pass fixed function splatting. Shader is usually + /// faster so this is just here for compatibility. + /// @param align The align of the terrain, see Alignment enum + /// @param minBatchSize Minimum size of a terrain batch along one side (in cell units). Used for building the quad tree. + /// @param maxBatchSize Maximum size of a terrain batch along one side (in cell units). Used when traversing the quad tree. + DefaultWorld(Ogre::SceneManager* sceneMgr, + Storage* storage, int visibilityFlags, bool shaders, Alignment align, float minBatchSize, float maxBatchSize); + ~DefaultWorld(); + + /// Update chunk LODs according to this camera position + /// @note Calling this method might lead to composite textures being rendered, so it is best + /// not to call it when render commands are still queued, since that would cause a flush. + virtual void update (const Ogre::Vector3& cameraPos); + + /// Get the world bounding box of a chunk of terrain centered at \a center + virtual Ogre::AxisAlignedBox getWorldBoundingBox (const Ogre::Vector2& center); + + Ogre::SceneNode* getRootSceneNode() { return mRootSceneNode; } + + /// Show or hide the whole terrain + /// @note this setting will be invalidated once you call Terrain::update, so do not call it while the terrain should be hidden + virtual void setVisible(bool visible); + virtual bool getVisible(); + + /// Recreate materials used by terrain chunks. This should be called whenever settings of + /// the material factory are changed. (Relying on the factory to update those materials is not + /// enough, since turning a feature on/off can change the number of texture units available for layer/blend + /// textures, and to properly respond to this we may need to change the structure of the material, such as + /// adding or removing passes. This can only be achieved by a full rebuild.) + virtual void applyMaterials(bool shadows, bool splitShadows); + + int getMaxBatchSize() { return mMaxBatchSize; } + + /// Wait until all background loading is complete. + void syncLoad(); + + private: + // Called from a background worker thread + Ogre::WorkQueue::Response* handleRequest(const Ogre::WorkQueue::Request* req, const Ogre::WorkQueue* srcQ); + // Called from the main thread + void handleResponse(const Ogre::WorkQueue::Response* res, const Ogre::WorkQueue* srcQ); + Ogre::uint16 mWorkQueueChannel; + + bool mVisible; + + QuadTreeNode* mRootNode; + Ogre::SceneNode* mRootSceneNode; + + /// The number of chunks currently loading in a background thread. If 0, we have finished loading! + int mChunksLoading; + + Ogre::SceneManager* mCompositeMapSceneMgr; + + /// Bounds in cell units + float mMinX, mMaxX, mMinY, mMaxY; + + /// Minimum size of a terrain batch along one side (in cell units) + float mMinBatchSize; + /// Maximum size of a terrain batch along one side (in cell units) + float mMaxBatchSize; + + void buildQuadTree(QuadTreeNode* node, std::vector& leafs); + + // Are layers for leaf nodes loaded? This is done once at startup (but in a background thread) + bool mLayerLoadPending; + + public: + // ----INTERNAL---- + Ogre::SceneManager* getCompositeMapSceneManager() { return mCompositeMapSceneMgr; } + + bool areLayersLoaded() { return !mLayerLoadPending; } + + // Delete all quads + void clearCompositeMapSceneManager(); + void renderCompositeMap (Ogre::TexturePtr target); + + // Adds a WorkQueue request to load a chunk for this node in the background. + void queueLoad (QuadTreeNode* node); + + private: + Ogre::RenderTarget* mCompositeMapRenderTarget; + Ogre::TexturePtr mCompositeMapRenderTexture; + }; + + struct LoadRequestData + { + QuadTreeNode* mNode; + + friend std::ostream& operator<<(std::ostream& o, const LoadRequestData& r) + { return o; } + }; + + struct LoadResponseData + { + std::vector mPositions; + std::vector mNormals; + std::vector mColours; + + friend std::ostream& operator<<(std::ostream& o, const LoadResponseData& r) + { return o; } + }; + + struct LayersRequestData + { + std::vector mNodes; + bool mPack; + + friend std::ostream& operator<<(std::ostream& o, const LayersRequestData& r) + { return o; } + }; + + struct LayersResponseData + { + std::vector mLayerCollections; + + friend std::ostream& operator<<(std::ostream& o, const LayersResponseData& r) + { return o; } + }; + +} + +#endif diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index 771dcdf91c..a40e576ed9 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -36,8 +36,8 @@ std::string getBlendmapComponentForLayer (int layerIndex) namespace Terrain { - MaterialGenerator::MaterialGenerator(bool shaders) - : mShaders(shaders) + MaterialGenerator::MaterialGenerator() + : mShaders(true) , mShadows(false) , mSplitShadows(false) , mNormalMapping(true) diff --git a/components/terrain/material.hpp b/components/terrain/material.hpp index 7f607a7af8..b9000cb1b9 100644 --- a/components/terrain/material.hpp +++ b/components/terrain/material.hpp @@ -11,11 +11,7 @@ namespace Terrain class MaterialGenerator { public: - /// @param layerList layer textures - /// @param blendmapList blend textures - /// @param shaders Whether to use shaders. With a shader, blendmap packing can be used (4 channels instead of one), - /// so if this parameter is true, then the supplied blend maps are expected to be packed. - MaterialGenerator (bool shaders); + MaterialGenerator (); void setLayerList (const std::vector& layerList) { mLayerList = layerList; } bool hasLayers() { return mLayerList.size(); } @@ -23,6 +19,7 @@ namespace Terrain const std::vector& getBlendmapList() { return mBlendmapList; } void setCompositeMap (const std::string& name) { mCompositeMap = name; } + void enableShaders(bool shaders) { mShaders = shaders; } void enableShadows(bool shadows) { mShadows = shadows; } void enableNormalMapping(bool normalMapping) { mNormalMapping = normalMapping; } void enableParallaxMapping(bool parallaxMapping) { mParallaxMapping = parallaxMapping; } diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp index 37c638da0c..44974eeb1d 100644 --- a/components/terrain/quadtreenode.cpp +++ b/components/terrain/quadtreenode.cpp @@ -6,7 +6,7 @@ #include #include -#include "world.hpp" +#include "defaultworld.hpp" #include "chunk.hpp" #include "storage.hpp" #include "buffercache.hpp" @@ -142,7 +142,7 @@ namespace } } -QuadTreeNode::QuadTreeNode(World* terrain, ChildDirection dir, float size, const Ogre::Vector2 ¢er, QuadTreeNode* parent) +QuadTreeNode::QuadTreeNode(DefaultWorld* terrain, ChildDirection dir, float size, const Ogre::Vector2 ¢er, QuadTreeNode* parent) : mMaterialGenerator(NULL) , mIsDummy(false) , mSize(size) @@ -178,7 +178,8 @@ QuadTreeNode::QuadTreeNode(World* terrain, ChildDirection dir, float size, const mSceneNode->setPosition(sceneNodePos); - mMaterialGenerator = new MaterialGenerator(mTerrain->getShadersEnabled()); + mMaterialGenerator = new MaterialGenerator(); + mMaterialGenerator->enableShaders(mTerrain->getShadersEnabled()); } void QuadTreeNode::createChild(ChildDirection id, float size, const Ogre::Vector2 ¢er) @@ -386,11 +387,9 @@ void QuadTreeNode::load(const LoadResponseData &data) { assert (!mChunk); - mChunk = new Chunk(mTerrain->getBufferCache().getUVBuffer(), mBounds, data); - mChunk->setVisibilityFlags(mTerrain->getVisiblityFlags()); + mChunk = new Chunk(mTerrain->getBufferCache().getUVBuffer(), mBounds, data.mPositions, data.mNormals, data.mColours); + mChunk->setVisibilityFlags(mTerrain->getVisibilityFlags()); mChunk->setCastShadows(true); - if (!mTerrain->getDistantLandEnabled()) - mChunk->setRenderingDistance(8192); mSceneNode->attachObject(mChunk); mMaterialGenerator->enableShadows(mTerrain->getShadowsEnabled()); @@ -550,7 +549,8 @@ void QuadTreeNode::prepareForCompositeMap(Ogre::TRect area) if (mIsDummy) { // TODO - store this default material somewhere instead of creating one for each empty cell - MaterialGenerator matGen(mTerrain->getShadersEnabled()); + MaterialGenerator matGen; + matGen.enableShaders(mTerrain->getShadersEnabled()); std::vector layer; layer.push_back(mTerrain->getStorage()->getDefaultLayer()); matGen.setLayerList(layer); diff --git a/components/terrain/quadtreenode.hpp b/components/terrain/quadtreenode.hpp index c575894879..6265727011 100644 --- a/components/terrain/quadtreenode.hpp +++ b/components/terrain/quadtreenode.hpp @@ -14,7 +14,7 @@ namespace Ogre namespace Terrain { - class World; + class DefaultWorld; class Chunk; class MaterialGenerator; struct LoadResponseData; @@ -48,7 +48,7 @@ namespace Terrain /// @param size size (in *cell* units!) /// @param center center (in *cell* units!) /// @param parent parent node - QuadTreeNode (World* terrain, ChildDirection dir, float size, const Ogre::Vector2& center, QuadTreeNode* parent); + QuadTreeNode (DefaultWorld* terrain, ChildDirection dir, float size, const Ogre::Vector2& center, QuadTreeNode* parent); ~QuadTreeNode(); /// Rebuild all materials @@ -95,7 +95,7 @@ namespace Terrain const Ogre::AxisAlignedBox& getWorldBoundingBox(); - World* getTerrain() { return mTerrain; } + DefaultWorld* getTerrain() { return mTerrain; } /// Adjust LODs for the given camera position, possibly splitting up chunks or merging them. /// @param force Always choose to render this node, even if not the perfect LOD. @@ -158,7 +158,7 @@ namespace Terrain Chunk* mChunk; - World* mTerrain; + DefaultWorld* mTerrain; Ogre::TexturePtr mCompositeMap; diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp index 3d968470f3..49fb9b5c9b 100644 --- a/components/terrain/world.cpp +++ b/components/terrain/world.cpp @@ -1,356 +1,60 @@ #include "world.hpp" #include -#include -#include -#include -#include -#include -#include #include "storage.hpp" -#include "quadtreenode.hpp" - -namespace -{ - - bool isPowerOfTwo(int x) - { - return ( (x > 0) && ((x & (x - 1)) == 0) ); - } - - int nextPowerOfTwo (int v) - { - if (isPowerOfTwo(v)) return v; - int depth=0; - while(v) - { - v >>= 1; - depth++; - } - return 1 << depth; - } - - Terrain::QuadTreeNode* findNode (const Ogre::Vector2& center, Terrain::QuadTreeNode* node) - { - if (center == node->getCenter()) - return node; - - if (center.x > node->getCenter().x && center.y > node->getCenter().y) - return findNode(center, node->getChild(Terrain::NE)); - else if (center.x > node->getCenter().x && center.y < node->getCenter().y) - return findNode(center, node->getChild(Terrain::SE)); - else if (center.x < node->getCenter().x && center.y > node->getCenter().y) - return findNode(center, node->getChild(Terrain::NW)); - else //if (center.x < node->getCenter().x && center.y < node->getCenter().y) - return findNode(center, node->getChild(Terrain::SW)); - } - -} namespace Terrain { - const Ogre::uint REQ_ID_CHUNK = 1; - const Ogre::uint REQ_ID_LAYERS = 2; +World::World(Ogre::SceneManager* sceneMgr, + Storage* storage, int visibilityFlags, bool shaders, Alignment align) + : mStorage(storage) + , mSceneMgr(sceneMgr) + , mVisibilityFlags(visibilityFlags) + , mShaders(shaders) + , mAlign(align) + , mCache(storage->getCellVertices()) +{ +} - World::World(Ogre::SceneManager* sceneMgr, - Storage* storage, int visibilityFlags, bool distantLand, bool shaders, Alignment align, float minBatchSize, float maxBatchSize) - : mStorage(storage) - , mMinBatchSize(minBatchSize) - , mMaxBatchSize(maxBatchSize) - , mSceneMgr(sceneMgr) - , mVisibilityFlags(visibilityFlags) - , mDistantLand(distantLand) - , mShaders(shaders) - , mVisible(true) - , mAlign(align) - , mMaxX(0) - , mMinX(0) - , mMaxY(0) - , mMinY(0) - , mChunksLoading(0) - , mWorkQueueChannel(0) - , mCache(storage->getCellVertices()) - , mLayerLoadPending(true) +World::~World() +{ + delete mStorage; +} + +float World::getHeightAt(const Ogre::Vector3 &worldPos) +{ + return mStorage->getHeightAt(worldPos); +} + +void World::convertPosition(float &x, float &y, float &z) +{ + Terrain::convertPosition(mAlign, x, y, z); +} + +void World::convertPosition(Ogre::Vector3 &pos) +{ + convertPosition(pos.x, pos.y, pos.z); +} + +void World::convertBounds(Ogre::AxisAlignedBox& bounds) +{ + switch (mAlign) { -#if TERRAIN_USE_SHADER == 0 - if (mShaders) - std::cerr << "Compiled Terrain without shader support, disabling..." << std::endl; - mShaders = false; -#endif - - mCompositeMapSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC); - - /// \todo make composite map size configurable - Ogre::Camera* compositeMapCam = mCompositeMapSceneMgr->createCamera("a"); - mCompositeMapRenderTexture = Ogre::TextureManager::getSingleton().createManual( - "terrain/comp/rt", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - Ogre::TEX_TYPE_2D, 128, 128, 0, Ogre::PF_A8B8G8R8, Ogre::TU_RENDERTARGET); - mCompositeMapRenderTarget = mCompositeMapRenderTexture->getBuffer()->getRenderTarget(); - mCompositeMapRenderTarget->setAutoUpdated(false); - mCompositeMapRenderTarget->addViewport(compositeMapCam); - - storage->getBounds(mMinX, mMaxX, mMinY, mMaxY); - - int origSizeX = mMaxX-mMinX; - int origSizeY = mMaxY-mMinY; - - // Dividing a quad tree only works well for powers of two, so round up to the nearest one - int size = nextPowerOfTwo(std::max(origSizeX, origSizeY)); - - // Adjust the center according to the new size - float centerX = (mMinX+mMaxX)/2.f + (size-origSizeX)/2.f; - float centerY = (mMinY+mMaxY)/2.f + (size-origSizeY)/2.f; - - mRootSceneNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(); - - // While building the quadtree, remember leaf nodes since we need to load their layers - LayersRequestData data; - data.mPack = getShadersEnabled(); - - mRootNode = new QuadTreeNode(this, Root, size, Ogre::Vector2(centerX, centerY), NULL); - buildQuadTree(mRootNode, data.mNodes); - //loadingListener->indicateProgress(); - mRootNode->initAabb(); - //loadingListener->indicateProgress(); - mRootNode->initNeighbours(); - //loadingListener->indicateProgress(); - - Ogre::WorkQueue* wq = Ogre::Root::getSingleton().getWorkQueue(); - mWorkQueueChannel = wq->getChannel("LargeTerrain"); - wq->addRequestHandler(mWorkQueueChannel, this); - wq->addResponseHandler(mWorkQueueChannel, this); - - // Start loading layers in the background (for leaf nodes) - wq->addRequest(mWorkQueueChannel, REQ_ID_LAYERS, Ogre::Any(data)); - } - - World::~World() - { - Ogre::WorkQueue* wq = Ogre::Root::getSingleton().getWorkQueue(); - wq->removeRequestHandler(mWorkQueueChannel, this); - wq->removeResponseHandler(mWorkQueueChannel, this); - - delete mRootNode; - delete mStorage; - } - - void World::buildQuadTree(QuadTreeNode *node, std::vector& leafs) - { - float halfSize = node->getSize()/2.f; - - if (node->getSize() <= mMinBatchSize) - { - // We arrived at a leaf - float minZ,maxZ; - Ogre::Vector2 center = node->getCenter(); - float cellWorldSize = getStorage()->getCellWorldSize(); - if (mStorage->getMinMaxHeights(node->getSize(), center, minZ, maxZ)) - { - Ogre::AxisAlignedBox bounds(Ogre::Vector3(-halfSize*cellWorldSize, -halfSize*cellWorldSize, minZ), - Ogre::Vector3(halfSize*cellWorldSize, halfSize*cellWorldSize, maxZ)); - convertBounds(bounds); - node->setBoundingBox(bounds); - leafs.push_back(node); - } - else - node->markAsDummy(); // no data available for this node, skip it - return; - } - - if (node->getCenter().x - halfSize > mMaxX - || node->getCenter().x + halfSize < mMinX - || node->getCenter().y - halfSize > mMaxY - || node->getCenter().y + halfSize < mMinY ) - // Out of bounds of the actual terrain - this will happen because - // we rounded the size up to the next power of two - { - node->markAsDummy(); - return; - } - - // Not a leaf, create its children - node->createChild(SW, halfSize, node->getCenter() - halfSize/2.f); - node->createChild(SE, halfSize, node->getCenter() + Ogre::Vector2(halfSize/2.f, -halfSize/2.f)); - node->createChild(NW, halfSize, node->getCenter() + Ogre::Vector2(-halfSize/2.f, halfSize/2.f)); - node->createChild(NE, halfSize, node->getCenter() + halfSize/2.f); - buildQuadTree(node->getChild(SW), leafs); - buildQuadTree(node->getChild(SE), leafs); - buildQuadTree(node->getChild(NW), leafs); - buildQuadTree(node->getChild(NE), leafs); - - // if all children are dummy, we are also dummy - for (int i=0; i<4; ++i) - { - if (!node->getChild((ChildDirection)i)->isDummy()) - return; - } - node->markAsDummy(); - } - - void World::update(const Ogre::Vector3& cameraPos) - { - if (!mVisible) - return; - mRootNode->update(cameraPos); - mRootNode->updateIndexBuffers(); - } - - Ogre::AxisAlignedBox World::getWorldBoundingBox (const Ogre::Vector2& center) - { - if (center.x > mMaxX - || center.x < mMinX - || center.y > mMaxY - || center.y < mMinY) - return Ogre::AxisAlignedBox::BOX_NULL; - QuadTreeNode* node = findNode(center, mRootNode); - return node->getWorldBoundingBox(); - } - - void World::renderCompositeMap(Ogre::TexturePtr target) - { - mCompositeMapRenderTarget->update(); - target->getBuffer()->blit(mCompositeMapRenderTexture->getBuffer()); - } - - void World::clearCompositeMapSceneManager() - { - mCompositeMapSceneMgr->destroyAllManualObjects(); - mCompositeMapSceneMgr->clearScene(); - } - - float World::getHeightAt(const Ogre::Vector3 &worldPos) - { - return mStorage->getHeightAt(worldPos); - } - - void World::applyMaterials(bool shadows, bool splitShadows) - { - mShadows = shadows; - mSplitShadows = splitShadows; - mRootNode->applyMaterials(); - } - - void World::setVisible(bool visible) - { - if (visible && !mVisible) - mSceneMgr->getRootSceneNode()->addChild(mRootSceneNode); - else if (!visible && mVisible) - mSceneMgr->getRootSceneNode()->removeChild(mRootSceneNode); - - mVisible = visible; - } - - bool World::getVisible() - { - return mVisible; - } - - void World::convertPosition(float &x, float &y, float &z) - { - Terrain::convertPosition(mAlign, x, y, z); - } - - void World::convertPosition(Ogre::Vector3 &pos) - { - convertPosition(pos.x, pos.y, pos.z); - } - - void World::convertBounds(Ogre::AxisAlignedBox& bounds) - { - switch (mAlign) - { - case Align_XY: - return; - case Align_XZ: - convertPosition(bounds.getMinimum()); - convertPosition(bounds.getMaximum()); - // Because we changed sign of Z - std::swap(bounds.getMinimum().z, bounds.getMaximum().z); - return; - case Align_YZ: - convertPosition(bounds.getMinimum()); - convertPosition(bounds.getMaximum()); - return; - } - } - - void World::syncLoad() - { - while (mChunksLoading || mLayerLoadPending) - { - OGRE_THREAD_SLEEP(0); - Ogre::Root::getSingleton().getWorkQueue()->processResponses(); - } - } - - Ogre::WorkQueue::Response* World::handleRequest(const Ogre::WorkQueue::Request *req, const Ogre::WorkQueue *srcQ) - { - if (req->getType() == REQ_ID_CHUNK) - { - const LoadRequestData data = Ogre::any_cast(req->getData()); - - QuadTreeNode* node = data.mNode; - - LoadResponseData* responseData = new LoadResponseData(); - - getStorage()->fillVertexBuffers(node->getNativeLodLevel(), node->getSize(), node->getCenter(), getAlign(), - responseData->mPositions, responseData->mNormals, responseData->mColours); - - return OGRE_NEW Ogre::WorkQueue::Response(req, true, Ogre::Any(responseData)); - } - else // REQ_ID_LAYERS - { - const LayersRequestData data = Ogre::any_cast(req->getData()); - - LayersResponseData* responseData = new LayersResponseData(); - - getStorage()->getBlendmaps(data.mNodes, responseData->mLayerCollections, data.mPack); - - return OGRE_NEW Ogre::WorkQueue::Response(req, true, Ogre::Any(responseData)); - } - } - - void World::handleResponse(const Ogre::WorkQueue::Response *res, const Ogre::WorkQueue *srcQ) - { - assert(res->succeeded() && "Response failure not handled"); - - if (res->getRequest()->getType() == REQ_ID_CHUNK) - { - LoadResponseData* data = Ogre::any_cast(res->getData()); - - const LoadRequestData requestData = Ogre::any_cast(res->getRequest()->getData()); - - requestData.mNode->load(*data); - - delete data; - - --mChunksLoading; - } - else // REQ_ID_LAYERS - { - LayersResponseData* data = Ogre::any_cast(res->getData()); - - for (std::vector::iterator it = data->mLayerCollections.begin(); it != data->mLayerCollections.end(); ++it) - { - it->mTarget->loadLayers(*it); - } - - delete data; - - mRootNode->loadMaterials(); - - mLayerLoadPending = false; - } - } - - void World::queueLoad(QuadTreeNode *node) - { - LoadRequestData data; - data.mNode = node; - - Ogre::Root::getSingleton().getWorkQueue()->addRequest(mWorkQueueChannel, REQ_ID_CHUNK, Ogre::Any(data)); - ++mChunksLoading; + case Align_XY: + return; + case Align_XZ: + convertPosition(bounds.getMinimum()); + convertPosition(bounds.getMaximum()); + // Because we changed sign of Z + std::swap(bounds.getMinimum().z, bounds.getMaximum().z); + return; + case Align_YZ: + convertPosition(bounds.getMinimum()); + convertPosition(bounds.getMaximum()); + return; } } + +} diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index 26a6d034dc..beca7903ab 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -1,50 +1,38 @@ -#ifndef COMPONENTS_TERRAIN_H -#define COMPONENTS_TERRAIN_H +#ifndef COMPONENTS_TERRAIN_WORLD_H +#define COMPONENTS_TERRAIN_WORLD_H -#include -#include -#include +#include #include "defs.hpp" #include "buffercache.hpp" namespace Ogre { - class Camera; + class SceneManager; } namespace Terrain { - - class QuadTreeNode; class Storage; /** - * @brief A quadtree-based terrain implementation suitable for large data sets. \n - * Near cells are rendered with alpha splatting, distant cells are merged - * together in batches and have their layers pre-rendered onto a composite map. \n - * Cracks at LOD transitions are avoided using stitching. - * @note Multiple cameras are not supported yet + * @brief The basic interface for a terrain world. How the terrain chunks are paged and displayed + * is up to the implementation. */ - class World : public Ogre::WorkQueue::RequestHandler, public Ogre::WorkQueue::ResponseHandler + class World { public: /// @note takes ownership of \a storage /// @param sceneMgr scene manager to use /// @param storage Storage instance to get terrain data from (heights, normals, colors, textures..) /// @param visbilityFlags visibility flags for the created meshes - /// @param distantLand Whether to draw all of the terrain, or only a 3x3 grid around the camera. - /// This is a temporary option until it can be streamlined. /// @param shaders Whether to use splatting shader, or multi-pass fixed function splatting. Shader is usually /// faster so this is just here for compatibility. /// @param align The align of the terrain, see Alignment enum - /// @param minBatchSize Minimum size of a terrain batch along one side (in cell units). Used for building the quad tree. - /// @param maxBatchSize Maximum size of a terrain batch along one side (in cell units). Used when traversing the quad tree. World(Ogre::SceneManager* sceneMgr, - Storage* storage, int visiblityFlags, bool distantLand, bool shaders, Alignment align, float minBatchSize, float maxBatchSize); - ~World(); + Storage* storage, int visiblityFlags, bool shaders, Alignment align); + virtual ~World(); - bool getDistantLandEnabled() { return mDistantLand; } bool getShadersEnabled() { return mShaders; } bool getShadowsEnabled() { return mShadows; } bool getSplitShadowsEnabled() { return mSplitShadows; } @@ -54,138 +42,60 @@ namespace Terrain /// Update chunk LODs according to this camera position /// @note Calling this method might lead to composite textures being rendered, so it is best /// not to call it when render commands are still queued, since that would cause a flush. - void update (const Ogre::Vector3& cameraPos); + virtual void update (const Ogre::Vector3& cameraPos) = 0; + + // This is only a hint and may be ignored by the implementation. + virtual void loadCell(int x, int y) {} + virtual void unloadCell(int x, int y) {} /// Get the world bounding box of a chunk of terrain centered at \a center - Ogre::AxisAlignedBox getWorldBoundingBox (const Ogre::Vector2& center); + virtual Ogre::AxisAlignedBox getWorldBoundingBox (const Ogre::Vector2& center) = 0; Ogre::SceneManager* getSceneManager() { return mSceneMgr; } - Ogre::SceneNode* getRootSceneNode() { return mRootSceneNode; } - Storage* getStorage() { return mStorage; } /// Show or hide the whole terrain - /// @note this setting will be invalidated once you call Terrain::update, so do not call it while the terrain should be hidden - void setVisible(bool visible); - bool getVisible(); + /// @note this setting may be invalidated once you call Terrain::update, so do not call it while the terrain should be hidden + virtual void setVisible(bool visible) = 0; + virtual bool getVisible() = 0; /// Recreate materials used by terrain chunks. This should be called whenever settings of /// the material factory are changed. (Relying on the factory to update those materials is not /// enough, since turning a feature on/off can change the number of texture units available for layer/blend /// textures, and to properly respond to this we may need to change the structure of the material, such as /// adding or removing passes. This can only be achieved by a full rebuild.) - void applyMaterials(bool shadows, bool splitShadows); + virtual void applyMaterials(bool shadows, bool splitShadows) = 0; - int getVisiblityFlags() { return mVisibilityFlags; } - - int getMaxBatchSize() { return mMaxBatchSize; } - - void enableSplattingShader(bool enabled); + int getVisibilityFlags() { return mVisibilityFlags; } Alignment getAlign() { return mAlign; } /// Wait until all background loading is complete. - void syncLoad(); + virtual void syncLoad() {} - private: - // Called from a background worker thread - Ogre::WorkQueue::Response* handleRequest(const Ogre::WorkQueue::Request* req, const Ogre::WorkQueue* srcQ); - // Called from the main thread - void handleResponse(const Ogre::WorkQueue::Response* res, const Ogre::WorkQueue* srcQ); - Ogre::uint16 mWorkQueueChannel; - - bool mDistantLand; + protected: bool mShaders; bool mShadows; bool mSplitShadows; - bool mVisible; Alignment mAlign; - QuadTreeNode* mRootNode; - Ogre::SceneNode* mRootSceneNode; Storage* mStorage; int mVisibilityFlags; - /// The number of chunks currently loading in a background thread. If 0, we have finished loading! - int mChunksLoading; - Ogre::SceneManager* mSceneMgr; - Ogre::SceneManager* mCompositeMapSceneMgr; - - /// Bounds in cell units - float mMinX, mMaxX, mMinY, mMaxY; - - /// Minimum size of a terrain batch along one side (in cell units) - float mMinBatchSize; - /// Maximum size of a terrain batch along one side (in cell units) - float mMaxBatchSize; - - void buildQuadTree(QuadTreeNode* node, std::vector& leafs); BufferCache mCache; - // Are layers for leaf nodes loaded? This is done once at startup (but in a background thread) - bool mLayerLoadPending; - public: // ----INTERNAL---- - Ogre::SceneManager* getCompositeMapSceneManager() { return mCompositeMapSceneMgr; } BufferCache& getBufferCache() { return mCache; } - bool areLayersLoaded() { return !mLayerLoadPending; } - - // Delete all quads - void clearCompositeMapSceneManager(); - void renderCompositeMap (Ogre::TexturePtr target); - // Convert the given position from Z-up align, i.e. Align_XY to the wanted align set in mAlign void convertPosition (float& x, float& y, float& z); void convertPosition (Ogre::Vector3& pos); void convertBounds (Ogre::AxisAlignedBox& bounds); - - // Adds a WorkQueue request to load a chunk for this node in the background. - void queueLoad (QuadTreeNode* node); - - private: - Ogre::RenderTarget* mCompositeMapRenderTarget; - Ogre::TexturePtr mCompositeMapRenderTexture; - }; - - struct LoadRequestData - { - QuadTreeNode* mNode; - - friend std::ostream& operator<<(std::ostream& o, const LoadRequestData& r) - { return o; } - }; - - struct LoadResponseData - { - std::vector mPositions; - std::vector mNormals; - std::vector mColours; - - friend std::ostream& operator<<(std::ostream& o, const LoadResponseData& r) - { return o; } - }; - - struct LayersRequestData - { - std::vector mNodes; - bool mPack; - - friend std::ostream& operator<<(std::ostream& o, const LayersRequestData& r) - { return o; } - }; - - struct LayersResponseData - { - std::vector mLayerCollections; - - friend std::ostream& operator<<(std::ostream& o, const LayersResponseData& r) - { return o; } }; } diff --git a/components/translation/translation.cpp b/components/translation/translation.cpp index 423c3971a0..5341240afa 100644 --- a/components/translation/translation.cpp +++ b/components/translation/translation.cpp @@ -42,7 +42,7 @@ namespace Translation void Storage::loadDataFromStream(ContainerType& container, std::istream& stream) { std::string line; - while (!stream.eof()) + while (!stream.eof() && !stream.fail()) { std::getline( stream, line ); if (!line.empty() && *line.rbegin() == '\r') diff --git a/credits.txt b/credits.txt index 092f5c15e8..5c757f957a 100644 --- a/credits.txt +++ b/credits.txt @@ -19,8 +19,8 @@ Alexander Olofsson (Ace) Artem Kotsynyak (greye) Arthur Moore (EmperorArthur) athile +Bret Curtis (psi29a) Britt Mathis (galdor557) -BrotherBrick cc9cii Chris Boyce (slothlife) Chris Robinson (KittyCat) @@ -79,7 +79,7 @@ Torben Leif Carrington (TorbenC) Packagers: Alexander Olofsson (Ace) - Windows -BrotherBrick - Ubuntu Linux +Bret Curtis (psi29a) - Ubuntu Linux Edmondo Tommasina (edmondo) - Gentoo Linux Julian Ospald (hasufell) - Gentoo Linux Karl-Felix Glatzer (k1ll) - Linux Binaries @@ -106,6 +106,7 @@ sir_herrbatka - Forum Administrator Formula Research: +Hrnchamd Epsilon fragonard Greendogo diff --git a/Docs/Doxyfile b/docs/Doxyfile similarity index 99% rename from Docs/Doxyfile rename to docs/Doxyfile index 43c3312ad3..156e23abd2 100644 --- a/Docs/Doxyfile +++ b/docs/Doxyfile @@ -576,7 +576,7 @@ WARN_LOGFILE = INPUT = apps \ components \ libs \ - Docs + docs # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is diff --git a/Docs/DoxyfilePages b/docs/DoxyfilePages similarity index 99% rename from Docs/DoxyfilePages rename to docs/DoxyfilePages index 5ce82a7c29..dca9d7a12a 100644 --- a/Docs/DoxyfilePages +++ b/docs/DoxyfilePages @@ -576,7 +576,7 @@ WARN_LOGFILE = INPUT = apps \ components \ libs \ - Docs + docs # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is diff --git a/DejaVu Font License.txt b/docs/license/DejaVu Font License.txt similarity index 100% rename from DejaVu Font License.txt rename to docs/license/DejaVu Font License.txt diff --git a/GPL3.txt b/docs/license/GPL3.txt similarity index 100% rename from GPL3.txt rename to docs/license/GPL3.txt diff --git a/Docs/mainpage.hpp.cmake b/docs/mainpage.hpp.cmake similarity index 100% rename from Docs/mainpage.hpp.cmake rename to docs/mainpage.hpp.cmake diff --git a/extern/oics/ICSInputControlSystem.cpp b/extern/oics/ICSInputControlSystem.cpp index a053bbab4e..34d16ed2bd 100644 --- a/extern/oics/ICSInputControlSystem.cpp +++ b/extern/oics/ICSInputControlSystem.cpp @@ -41,8 +41,6 @@ namespace ICS this->mActive = active; - this->fillSDLKeysMap(); - ICS_LOG("Channel count = " + ToString(channelCount) ); for(size_t i=0;i( getKeyBinding(*o, Control/*::ControlChangingDirection*/::INCREASE)).c_str() ); keyBinder.SetAttribute( "direction", "INCREASE" ); control.InsertEndChild(keyBinder); @@ -443,7 +438,7 @@ namespace ICS { TiXmlElement keyBinder( "KeyBinder" ); - keyBinder.SetAttribute( "key", keyCodeToString( + keyBinder.SetAttribute( "key", ToString( getKeyBinding(*o, Control/*::ControlChangingDirection*/::DECREASE)).c_str() ); keyBinder.SetAttribute( "direction", "DECREASE" ); control.InsertEndChild(keyBinder); @@ -806,128 +801,14 @@ namespace ICS mDetectingBindingControl = NULL; } - void InputControlSystem::fillSDLKeysMap() - { - mKeys["UNASSIGNED"]= SDLK_UNKNOWN; - mKeys["ESCAPE"]= SDLK_ESCAPE; - mKeys["1"]= SDLK_1; - mKeys["2"]= SDLK_2; - mKeys["3"]= SDLK_3; - mKeys["4"]= SDLK_4; - mKeys["5"]= SDLK_5; - mKeys["6"]= SDLK_6; - mKeys["7"]= SDLK_7; - mKeys["8"]= SDLK_8; - mKeys["9"]= SDLK_9; - mKeys["0"]= SDLK_0; - mKeys["MINUS"]= SDLK_MINUS; - mKeys["EQUALS"]= SDLK_EQUALS; - mKeys["BACK"]= SDLK_BACKSPACE; - mKeys["TAB"]= SDLK_TAB; - mKeys["Q"]= SDLK_q; - mKeys["W"]= SDLK_w; - mKeys["E"]= SDLK_e; - mKeys["R"]= SDLK_r; - mKeys["T"]= SDLK_t; - mKeys["Y"]= SDLK_y; - mKeys["U"]= SDLK_u; - mKeys["I"]= SDLK_i; - mKeys["O"]= SDLK_o; - mKeys["P"]= SDLK_p; - mKeys["LBRACKET"]= SDLK_LEFTBRACKET; - mKeys["RBRACKET"]= SDLK_RIGHTBRACKET; - mKeys["RETURN"]= SDLK_RETURN; - mKeys["LCONTROL"]= SDLK_LCTRL; - mKeys["A"]= SDLK_a; - mKeys["S"]= SDLK_s; - mKeys["D"]= SDLK_d; - mKeys["F"]= SDLK_f; - mKeys["G"]= SDLK_g; - mKeys["H"]= SDLK_h; - mKeys["J"]= SDLK_j; - mKeys["K"]= SDLK_k; - mKeys["L"]= SDLK_l; - mKeys["SEMICOLON"]= SDLK_SEMICOLON; - mKeys["APOSTROPHE"]= SDLK_QUOTE; - mKeys["GRAVE"]= SDLK_BACKQUOTE; - mKeys["LSHIFT"]= SDLK_LSHIFT; - mKeys["BACKSLASH"]= SDLK_BACKSLASH; - mKeys["Z"]= SDLK_z; - mKeys["X"]= SDLK_x; - mKeys["C"]= SDLK_c; - mKeys["V"]= SDLK_v; - mKeys["B"]= SDLK_b; - mKeys["N"]= SDLK_n; - mKeys["M"]= SDLK_m; - mKeys["COMMA"]= SDLK_COMMA; - mKeys["PERIOD"]= SDLK_PERIOD; - mKeys["SLASH"]= SDLK_SLASH; - mKeys["RSHIFT"]= SDLK_RSHIFT; - mKeys["MULTIPLY"]= SDLK_ASTERISK; - mKeys["LMENU"]= SDLK_LALT; - mKeys["SPACE"]= SDLK_SPACE; - mKeys["CAPITAL"]= SDLK_CAPSLOCK; - mKeys["F1"]= SDLK_F1; - mKeys["F2"]= SDLK_F2; - mKeys["F3"]= SDLK_F3; - mKeys["F4"]= SDLK_F4; - mKeys["F5"]= SDLK_F5; - mKeys["F6"]= SDLK_F6; - mKeys["F7"]= SDLK_F7; - mKeys["F8"]= SDLK_F8; - mKeys["F9"]= SDLK_F9; - mKeys["F10"]= SDLK_F10; - mKeys["F11"]= SDLK_F11; - mKeys["F12"]= SDLK_F12; - mKeys["NUMLOCK"]= SDLK_NUMLOCKCLEAR; - mKeys["SCROLL"]= SDLK_SCROLLLOCK; - mKeys["NUMPAD7"]= SDLK_KP_7; - mKeys["NUMPAD8"]= SDLK_KP_8; - mKeys["NUMPAD9"]= SDLK_KP_9; - mKeys["SUBTRACT"]= SDLK_KP_MINUS; - mKeys["NUMPAD4"]= SDLK_KP_4; - mKeys["NUMPAD5"]= SDLK_KP_5; - mKeys["NUMPAD6"]= SDLK_KP_6; - mKeys["ADD"]= SDLK_KP_PLUS; - mKeys["NUMPAD1"]= SDLK_KP_1; - mKeys["NUMPAD2"]= SDLK_KP_2; - mKeys["NUMPAD3"]= SDLK_KP_3; - mKeys["NUMPAD0"]= SDLK_KP_0; - mKeys["DECIMAL"]= SDLK_KP_DECIMAL; - mKeys["RCONTROL"]= SDLK_RCTRL; - mKeys["DIVIDE"]= SDLK_SLASH; - mKeys["SYSRQ"]= SDLK_SYSREQ; - mKeys["PRNTSCRN"] = SDLK_PRINTSCREEN; - mKeys["RMENU"]= SDLK_RALT; - mKeys["PAUSE"]= SDLK_PAUSE; - mKeys["HOME"]= SDLK_HOME; - mKeys["UP"]= SDLK_UP; - mKeys["PGUP"]= SDLK_PAGEUP; - mKeys["LEFT"]= SDLK_LEFT; - mKeys["RIGHT"]= SDLK_RIGHT; - mKeys["END"]= SDLK_END; - mKeys["DOWN"]= SDLK_DOWN; - mKeys["PGDOWN"]= SDLK_PAGEDOWN; - mKeys["INSERT"]= SDLK_INSERT; - mKeys["DELETE"]= SDLK_DELETE; - - mKeys["NUMPADENTER"]= SDLK_KP_ENTER; - - for(std::map::iterator it = mKeys.begin() - ; it != mKeys.end() ; it++) - { - mKeyCodes[ it->second ] = it->first; - } - } - std::string InputControlSystem::keyCodeToString(SDL_Keycode key) { - return mKeyCodes[key]; + return std::string(SDL_GetKeyName(key)); } SDL_Keycode InputControlSystem::stringToKeyCode(std::string key) { - return mKeys[key]; + return SDL_GetKeyFromName(key.c_str()); } void InputControlSystem::adjustMouseRegion(Uint16 width, Uint16 height) diff --git a/extern/oics/ICSInputControlSystem.h b/extern/oics/ICSInputControlSystem.h index 4aaecdea9f..1e9c78e602 100644 --- a/extern/oics/ICSInputControlSystem.h +++ b/extern/oics/ICSInputControlSystem.h @@ -208,8 +208,6 @@ namespace ICS std::vector mChannels; ControlsKeyBinderMapType mControlsKeyBinderMap; - std::map mKeys; - std::map mKeyCodes; bool mActive; InputControlSystemLog* mLog; @@ -227,8 +225,6 @@ namespace ICS private: - void fillSDLKeysMap(); - Uint16 mClientWidth; Uint16 mClientHeight; }; diff --git a/extern/oics/ICSInputControlSystem_keyboard.cpp b/extern/oics/ICSInputControlSystem_keyboard.cpp index 0a9a34d63c..8e7415b5b1 100644 --- a/extern/oics/ICSInputControlSystem_keyboard.cpp +++ b/extern/oics/ICSInputControlSystem_keyboard.cpp @@ -43,7 +43,7 @@ namespace ICS dir = Control::DECREASE; } - addKeyBinding(mControls.back(), mKeys[xmlKeyBinder->Attribute("key")], dir); + addKeyBinding(mControls.back(), FromString(xmlKeyBinder->Attribute("key")), dir); xmlKeyBinder = xmlKeyBinder->NextSiblingElement("KeyBinder"); } diff --git a/extern/oics/ICSPrerequisites.h b/extern/oics/ICSPrerequisites.h index 52daea3f47..6e6cd814ba 100644 --- a/extern/oics/ICSPrerequisites.h +++ b/extern/oics/ICSPrerequisites.h @@ -36,6 +36,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include #include +#include /* std::min and std::max for MSVC 2013 */ #include "tinyxml.h" @@ -90,7 +91,7 @@ namespace ICS // from http://www.cplusplus.com/forum/articles/9645/ template - T FromString ( const std::string &Text )//Text not by const reference so that the function can be used with a + T FromString ( const std::string &Text )//Text not by const reference so that the function can be used with a { //character array as argument std::stringstream ss(Text); T result; diff --git a/extern/sdl4ogre/sdlinputwrapper.cpp b/extern/sdl4ogre/sdlinputwrapper.cpp index 82db5ea99f..f65dfb376f 100644 --- a/extern/sdl4ogre/sdlinputwrapper.cpp +++ b/extern/sdl4ogre/sdlinputwrapper.cpp @@ -239,9 +239,11 @@ namespace SFO mWrapPointer = false; - //eep, wrap the pointer manually if the input driver doesn't support - //relative positioning natively - bool success = SDL_SetRelativeMouseMode(relative ? SDL_TRUE : SDL_FALSE) == 0; + // eep, wrap the pointer manually if the input driver doesn't support + // relative positioning natively + // also use wrapping if no-grab was specified in options (SDL_SetRelativeMouseMode + // appears to eat the mouse cursor when pausing in a debugger) + bool success = mAllowGrab && SDL_SetRelativeMouseMode(relative ? SDL_TRUE : SDL_FALSE) == 0; if(relative && !success) mWrapPointer = true; @@ -338,9 +340,6 @@ namespace SFO { //lifted from OIS's SDLKeyboard.cpp - //TODO: Consider switching to scancodes so we - //can properly support international keyboards - //look at SDL_GetKeyFromScancode and SDL_GetKeyName mKeyMap.insert( KeyMap::value_type(SDLK_UNKNOWN, OIS::KC_UNASSIGNED)); mKeyMap.insert( KeyMap::value_type(SDLK_ESCAPE, OIS::KC_ESCAPE) ); mKeyMap.insert( KeyMap::value_type(SDLK_1, OIS::KC_1) ); diff --git a/extern/sdl4ogre/sdlinputwrapper.hpp b/extern/sdl4ogre/sdlinputwrapper.hpp index f08e3eff6b..2757018146 100644 --- a/extern/sdl4ogre/sdlinputwrapper.hpp +++ b/extern/sdl4ogre/sdlinputwrapper.hpp @@ -1,6 +1,8 @@ #ifndef SDL4OGRE_SDLINPUTWRAPPER_H #define SDL4OGRE_SDLINPUTWRAPPER_H +#define NOMINMAX + #include #include diff --git a/extern/sdl4ogre/sdlwindowhelper.cpp b/extern/sdl4ogre/sdlwindowhelper.cpp index 2a14cc6b4d..3ea39cff7d 100644 --- a/extern/sdl4ogre/sdlwindowhelper.cpp +++ b/extern/sdl4ogre/sdlwindowhelper.cpp @@ -32,7 +32,7 @@ SDLWindowHelper::SDLWindowHelper (SDL_Window* window, int w, int h, #ifdef WIN32 case SDL_SYSWM_WINDOWS: // Windows code - winHandle = Ogre::StringConverter::toString((unsigned long)wmInfo.info.win.window); + winHandle = Ogre::StringConverter::toString((uintptr_t)wmInfo.info.win.window); break; #elif __MACOSX__ case SDL_SYSWM_COCOA: diff --git a/files/materials/clouds.shader b/files/materials/clouds.shader index b76f2ded72..d3e5f083c7 100644 --- a/files/materials/clouds.shader +++ b/files/materials/clouds.shader @@ -3,25 +3,27 @@ #ifdef SH_VERTEX_SHADER SH_BEGIN_PROGRAM - shUniform(float4x4, view) @shAutoConstant(view, view_matrix) -shUniform(float4x4, projection) @shAutoConstant(projection, projection_matrix) + shUniform(float4x4, worldview) @shAutoConstant(worldview, worldview_matrix) + shUniform(float4x4, proj) @shAutoConstant(proj, projection_matrix) shVertexInput(float2, uv0) shOutput(float2, UV) shOutput(float, alphaFade) SH_START_PROGRAM { - float4x4 viewFixed = view; + float4x4 worldviewFixed = worldview; + #if !SH_GLSL - viewFixed[0][3] = 0; - viewFixed[1][3] = 0; - viewFixed[2][3] = 0; + worldviewFixed[0][3] = 0; + worldviewFixed[1][3] = 0; + worldviewFixed[2][3] = 0; #else - viewFixed[3][0] = 0; - viewFixed[3][1] = 0; - viewFixed[3][2] = 0; + worldviewFixed[3][0] = 0; + worldviewFixed[3][1] = 0; + worldviewFixed[3][2] = 0; #endif - shOutputPosition = shMatrixMult(projection, shMatrixMult(viewFixed, shInputPosition)); + + shOutputPosition = shMatrixMult(proj, shMatrixMult(worldviewFixed, shInputPosition)); UV = uv0; alphaFade = (shInputPosition.z <= 200.f) ? ((shInputPosition.z <= 100.f) ? 0.0 : 0.25) : 1.0; } diff --git a/files/materials/stars.shader b/files/materials/stars.shader index 33f22bd14b..b84d371358 100644 --- a/files/materials/stars.shader +++ b/files/materials/stars.shader @@ -3,8 +3,8 @@ #ifdef SH_VERTEX_SHADER SH_BEGIN_PROGRAM - shUniform(float4x4, view) @shAutoConstant(view, view_matrix) -shUniform(float4x4, projection) @shAutoConstant(projection, projection_matrix) + shUniform(float4x4, worldview) @shAutoConstant(worldview, worldview_matrix) + shUniform(float4x4, proj) @shAutoConstant(proj, projection_matrix) shVertexInput(float2, uv0) shOutput(float2, UV) @@ -12,17 +12,18 @@ shUniform(float4x4, projection) @shAutoConstant(projection, projection_matrix) SH_START_PROGRAM { - float4x4 viewFixed = view; + float4x4 worldviewFixed = worldview; #if !SH_GLSL - viewFixed[0][3] = 0; - viewFixed[1][3] = 0; - viewFixed[2][3] = 0; + worldviewFixed[0][3] = 0; + worldviewFixed[1][3] = 0; + worldviewFixed[2][3] = 0; #else - viewFixed[3][0] = 0; - viewFixed[3][1] = 0; - viewFixed[3][2] = 0; + worldviewFixed[3][0] = 0; + worldviewFixed[3][1] = 0; + worldviewFixed[3][2] = 0; #endif - shOutputPosition = shMatrixMult(projection, shMatrixMult(viewFixed, shInputPosition)); + + shOutputPosition = shMatrixMult(proj, shMatrixMult(worldviewFixed, shInputPosition)); UV = uv0; fade = (shInputPosition.z > 50) ? 1 : 0; diff --git a/files/materials/underwater.h b/files/materials/underwater.h index 8474f299d0..332a0fd7d7 100644 --- a/files/materials/underwater.h +++ b/files/materials/underwater.h @@ -1,4 +1,4 @@ -#define UNDERWATER_COLOUR float3(0.18039, 0.23137, 0.25490) +#define UNDERWATER_COLOUR float3(0.090195, 0.115685, 0.12745) #define VISIBILITY 1000.0 // how far you can look through water diff --git a/files/materials/water.shader b/files/materials/water.shader index 87e90a2916..701154ffa3 100644 --- a/files/materials/water.shader +++ b/files/materials/water.shader @@ -340,7 +340,7 @@ #if REFRACTION float3 refraction = shSample(refractionMap, (screenCoords-(normal.xy*REFR_BUMP))*1.0).rgb; - + // brighten up the refraction underwater refraction = (cameraPos.z < 0) ? shSaturate(refraction * 1.5) : refraction; #endif @@ -351,7 +351,7 @@ #if REFRACTION shOutputColour(0).xyz = shLerp( shLerp(refraction, scatterColour, lightScatter), reflection, fresnel) + specular * sunSpecular.xyz; #else - shOutputColour(0).xyz = shLerp(reflection, float3(0.18039, 0.23137, 0.25490), (1.0-fresnel)*0.5) + specular * sunSpecular.xyz; + shOutputColour(0).xyz = shLerp(reflection, float3(0.090195, 0.115685, 0.12745), (1.0-fresnel)*0.5) + specular * sunSpecular.xyz; #endif // fog float fogValue = shSaturate((depthPassthrough - fogParams.y) * fogParams.w); diff --git a/files/mygui/openmw_console.layout b/files/mygui/openmw_console.layout index 0c9a97d043..c88c1955d8 100644 --- a/files/mygui/openmw_console.layout +++ b/files/mygui/openmw_console.layout @@ -13,10 +13,13 @@ + - + + + diff --git a/files/mygui/openmw_dialogue_window.layout b/files/mygui/openmw_dialogue_window.layout index 78daa0705f..5a7cd772da 100644 --- a/files/mygui/openmw_dialogue_window.layout +++ b/files/mygui/openmw_dialogue_window.layout @@ -4,28 +4,28 @@ - + - - + + - + - - + - + diff --git a/files/mygui/openmw_dialogue_window_skin.xml b/files/mygui/openmw_dialogue_window_skin.xml index 4f68a90faf..5e16fd1f69 100644 --- a/files/mygui/openmw_dialogue_window_skin.xml +++ b/files/mygui/openmw_dialogue_window_skin.xml @@ -13,6 +13,7 @@ + diff --git a/files/mygui/openmw_levelup_dialog.layout b/files/mygui/openmw_levelup_dialog.layout index 765bf88a83..0b12d7a052 100644 --- a/files/mygui/openmw_levelup_dialog.layout +++ b/files/mygui/openmw_levelup_dialog.layout @@ -31,17 +31,18 @@ - - - - - - - - + + + + + + + + + @@ -55,6 +56,7 @@ + @@ -68,6 +70,7 @@ + @@ -81,6 +84,7 @@ + @@ -95,6 +99,7 @@ + @@ -108,6 +113,7 @@ + @@ -121,6 +127,7 @@ + @@ -134,6 +141,7 @@ + diff --git a/files/mygui/openmw_map_window.layout b/files/mygui/openmw_map_window.layout index 6e0efce7e1..b842888a19 100644 --- a/files/mygui/openmw_map_window.layout +++ b/files/mygui/openmw_map_window.layout @@ -17,8 +17,6 @@ - - diff --git a/files/mygui/openmw_savegame_dialog.layout b/files/mygui/openmw_savegame_dialog.layout index ceb1a84288..7015336368 100644 --- a/files/mygui/openmw_savegame_dialog.layout +++ b/files/mygui/openmw_savegame_dialog.layout @@ -2,8 +2,8 @@ - - + + @@ -49,10 +49,14 @@ - + + + + + diff --git a/files/mygui/openmw_text.skin.xml b/files/mygui/openmw_text.skin.xml index d4c72c75b4..4c1117cf5f 100644 --- a/files/mygui/openmw_text.skin.xml +++ b/files/mygui/openmw_text.skin.xml @@ -32,6 +32,13 @@ + + + + + + + diff --git a/files/mygui/openmw_wait_dialog.layout b/files/mygui/openmw_wait_dialog.layout index eeb7012eb0..7c065038de 100644 --- a/files/mygui/openmw_wait_dialog.layout +++ b/files/mygui/openmw_wait_dialog.layout @@ -19,6 +19,8 @@ + + diff --git a/files/openmw.cfg b/files/openmw.cfg index 4633141a69..b67b79a964 100644 --- a/files/openmw.cfg +++ b/files/openmw.cfg @@ -1,4 +1,4 @@ data="?global?data" data="?mw?Data Files" -data-local="?user?data" +data-local="?userdata?data" resources=${OPENMW_RESOURCE_FILES} diff --git a/files/openmw.cfg.local b/files/openmw.cfg.local index d6ca2d5549..6a578542df 100644 --- a/files/openmw.cfg.local +++ b/files/openmw.cfg.local @@ -1,5 +1,5 @@ data="?global?data" data="?mw?Data Files" data=./data -data-local="?user?data" +data-local="?userdata?data" resources=./resources diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 9eed2c7d92..fac2a0f977 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -65,8 +65,6 @@ shader mode = enabled = false # Split the shadow maps, allows for a larger shadow distance -# Warning: enabling this will cause some terrain textures to disappear due to -# hitting the texture unit limit of the terrain material split = false # Increasing shadow distance will lower the shadow quality. @@ -166,7 +164,7 @@ ui sensitivity = 1.0 camera y multiplier = 1.0 -ui y multiplier = 1.0 +always run = false [Game] # Always use the most powerful attack when striking with a weapon (chop, slash or thrust) diff --git a/libs/openengine/bullet/BulletShapeLoader.cpp b/libs/openengine/bullet/BulletShapeLoader.cpp index 5528924a9a..bcb2ba6b27 100644 --- a/libs/openengine/bullet/BulletShapeLoader.cpp +++ b/libs/openengine/bullet/BulletShapeLoader.cpp @@ -42,7 +42,7 @@ void BulletShape::deleteShape(btCollisionShape* shape) { if(shape->isCompound()) { - btCompoundShape* ms = static_cast(mCollisionShape); + btCompoundShape* ms = static_cast(shape); int a = ms->getNumChildShapes(); for(int i=0; i mAnimatedShapes; + + std::map mAnimatedRaycastingShapes; + btCollisionShape* mCollisionShape; btCollisionShape* mRaycastingShape; diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index 8d9ac3c228..6bc544af4a 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -11,120 +11,159 @@ #include #include +namespace +{ + +// Create a copy of the given collision shape (responsibility of user to delete the returned shape). +btCollisionShape *duplicateCollisionShape(btCollisionShape *shape) +{ + if(shape->isCompound()) + { + btCompoundShape *comp = static_cast(shape); + btCompoundShape *newShape = new btCompoundShape; + + int numShapes = comp->getNumChildShapes(); + for(int i = 0;i < numShapes;i++) + { + btCollisionShape *child = duplicateCollisionShape(comp->getChildShape(i)); + btTransform trans = comp->getChildTransform(i); + newShape->addChildShape(trans, child); + } + + return newShape; + } + + if(btBvhTriangleMeshShape *trishape = dynamic_cast(shape)) + { + btTriangleMesh* oldMesh = dynamic_cast(trishape->getMeshInterface()); + btTriangleMesh* newMesh = new btTriangleMesh(*oldMesh); + NifBullet::TriangleMeshShape *newShape = new NifBullet::TriangleMeshShape(newMesh, true); + + return newShape; + } + + throw std::logic_error(std::string("Unhandled Bullet shape duplication: ")+shape->getName()); +} + +void deleteShape(btCollisionShape* shape) +{ + if(shape!=NULL) + { + if(shape->isCompound()) + { + btCompoundShape* ms = static_cast(shape); + int a = ms->getNumChildShapes(); + for(int i=0; i getChildShape(i)); + } + } + delete shape; + } +} + +} + namespace OEngine { namespace Physic { PhysicActor::PhysicActor(const std::string &name, const std::string &mesh, PhysicEngine *engine, const Ogre::Vector3 &position, const Ogre::Quaternion &rotation, float scale) - : mName(name), mEngine(engine), mMesh(mesh), mBoxScaledTranslation(0,0,0), mBoxRotationInverse(0,0,0,0) - , mBody(0), mRaycastingBody(0), mOnGround(false), mCollisionMode(true), mBoxRotation(0,0,0,0) - , mCollisionBody(true) + : mName(name), mEngine(engine), mMesh(mesh) + , mBody(0), mOnGround(false), mInternalCollisionMode(true) + , mExternalCollisionMode(true) , mForce(0.0f) + , mScale(scale) { - mBody = mEngine->createAndAdjustRigidBody(mMesh, mName, scale, position, rotation, &mBoxScaledTranslation, &mBoxRotation); - mRaycastingBody = mEngine->createAndAdjustRigidBody(mMesh, mName, scale, position, rotation, &mBoxScaledTranslation, &mBoxRotation, true); - Ogre::Quaternion inverse = mBoxRotation.Inverse(); - mBoxRotationInverse = Ogre::Quaternion(inverse.w, inverse.x, inverse.y,inverse.z); - mEngine->addRigidBody(mBody, false, mRaycastingBody,true); //Add rigid body to dynamics world, but do not add to object map + if (!NifBullet::getBoundingBox(mMesh, mHalfExtents, mMeshTranslation, mMeshOrientation)) + { + mHalfExtents = Ogre::Vector3(0.f); + mMeshTranslation = Ogre::Vector3(0.f); + mMeshOrientation = Ogre::Quaternion::IDENTITY; + } + + // Use capsule shape only if base is square (nonuniform scaling apparently doesn't work on it) + if (std::abs(mHalfExtents.x-mHalfExtents.y)= mHalfExtents.x) + { + // Could also be btCapsuleShapeZ, but the movement solver seems to have issues with it (jumping on slopes doesn't work) + mShape.reset(new btCylinderShapeZ(BtOgre::Convert::toBullet(mHalfExtents))); + } + else + mShape.reset(new btBoxShape(BtOgre::Convert::toBullet(mHalfExtents))); + + mShape->setLocalScaling(btVector3(scale,scale,scale)); + + btRigidBody::btRigidBodyConstructionInfo CI = btRigidBody::btRigidBodyConstructionInfo + (0,0, mShape.get()); + mBody = new RigidBody(CI, name); + mBody->mPlaceable = false; + + setPosition(position); + setRotation(rotation); + + mEngine->mDynamicsWorld->addRigidBody(mBody, CollisionType_Actor, + CollisionType_Actor|CollisionType_World|CollisionType_HeightMap); } PhysicActor::~PhysicActor() { if(mBody) { - mEngine->dynamicsWorld->removeRigidBody(mBody); + mEngine->mDynamicsWorld->removeRigidBody(mBody); delete mBody; - } - if(mRaycastingBody) - { - mEngine->dynamicsWorld->removeRigidBody(mRaycastingBody); - delete mRaycastingBody; - } + } } void PhysicActor::enableCollisionMode(bool collision) { - mCollisionMode = collision; + mInternalCollisionMode = collision; } void PhysicActor::enableCollisionBody(bool collision) { assert(mBody); - if(collision && !mCollisionBody) enableCollisionBody(); - if(!collision && mCollisionBody) disableCollisionBody(); - mCollisionBody = collision; + if(collision && !mExternalCollisionMode) enableCollisionBody(); + if(!collision && mExternalCollisionMode) disableCollisionBody(); + mExternalCollisionMode = collision; } - void PhysicActor::setPosition(const Ogre::Vector3 &pos) + const Ogre::Vector3& PhysicActor::getPosition() const + { + return mPosition; + } + + void PhysicActor::setPosition(const Ogre::Vector3 &position) { assert(mBody); - if(pos != getPosition()) - { - mEngine->adjustRigidBody(mBody, pos, getRotation(), mBoxScaledTranslation, mBoxRotation); - mEngine->adjustRigidBody(mRaycastingBody, pos, getRotation(), mBoxScaledTranslation, mBoxRotation); - } + + mPosition = position; + + btTransform tr = mBody->getWorldTransform(); + Ogre::Quaternion meshrot = mMeshOrientation; + Ogre::Vector3 transrot = meshrot * (mMeshTranslation * mScale); + Ogre::Vector3 newPosition = transrot + position; + + tr.setOrigin(BtOgre::Convert::toBullet(newPosition)); + mBody->setWorldTransform(tr); } - void PhysicActor::setRotation(const Ogre::Quaternion &quat) + void PhysicActor::setRotation (const Ogre::Quaternion& rotation) { - assert(mBody); - if(!quat.equals(getRotation(), Ogre::Radian(0))){ - mEngine->adjustRigidBody(mBody, getPosition(), quat, mBoxScaledTranslation, mBoxRotation); - mEngine->adjustRigidBody(mRaycastingBody, getPosition(), quat, mBoxScaledTranslation, mBoxRotation); - - } + btTransform tr = mBody->getWorldTransform(); + tr.setRotation(BtOgre::Convert::toBullet(mMeshOrientation * rotation)); + mBody->setWorldTransform(tr); } - - Ogre::Vector3 PhysicActor::getPosition() + void PhysicActor::setScale(float scale) { - assert(mBody); - btVector3 vec = mBody->getWorldTransform().getOrigin(); - Ogre::Quaternion rotation = Ogre::Quaternion(mBody->getWorldTransform().getRotation().getW(), mBody->getWorldTransform().getRotation().getX(), - mBody->getWorldTransform().getRotation().getY(), mBody->getWorldTransform().getRotation().getZ()); - Ogre::Vector3 transrot = rotation * mBoxScaledTranslation; - Ogre::Vector3 visualPosition = Ogre::Vector3(vec.getX(), vec.getY(), vec.getZ()) - transrot; - return visualPosition; - } - - Ogre::Quaternion PhysicActor::getRotation() - { - assert(mBody); - btQuaternion quat = mBody->getWorldTransform().getRotation(); - return Ogre::Quaternion(quat.getW(), quat.getX(), quat.getY(), quat.getZ()) * mBoxRotationInverse; - } - - void PhysicActor::setScale(float scale){ - //We only need to change the scaled box translation, box rotations remain the same. - assert(mBody); - mBoxScaledTranslation = mBoxScaledTranslation / mBody->getCollisionShape()->getLocalScaling().getX(); - mBoxScaledTranslation *= scale; - Ogre::Vector3 pos = getPosition(); - Ogre::Quaternion rot = getRotation(); - if(mBody){ - mEngine->dynamicsWorld->removeRigidBody(mBody); - mEngine->dynamicsWorld->removeRigidBody(mRaycastingBody); - delete mBody; - delete mRaycastingBody; - } - //Create the newly scaled rigid body - mBody = mEngine->createAndAdjustRigidBody(mMesh, mName, scale, pos, rot); - mRaycastingBody = mEngine->createAndAdjustRigidBody(mMesh, mName, scale, pos, rot, 0, 0, true); - mEngine->addRigidBody(mCollisionBody ? mBody : 0, false, mRaycastingBody,true); //Add rigid body to dynamics world, but do not add to object map + mScale = scale; + mShape->setLocalScaling(btVector3(scale,scale,scale)); + setPosition(mPosition); } Ogre::Vector3 PhysicActor::getHalfExtents() const { - if(mBody) - { - btBoxShape *box = static_cast(mBody->getCollisionShape()); - if(box != NULL) - { - btVector3 size = box->getHalfExtentsWithMargin(); - return Ogre::Vector3(size.getX(), size.getY(), size.getZ()); - } - } - return Ogre::Vector3(0.0f); + return mHalfExtents * mScale; } void PhysicActor::setInertialForce(const Ogre::Vector3 &force) @@ -139,12 +178,16 @@ namespace Physic void PhysicActor::disableCollisionBody() { - mEngine->dynamicsWorld->removeRigidBody(mBody); + mEngine->mDynamicsWorld->removeRigidBody(mBody); + mEngine->mDynamicsWorld->addRigidBody(mBody, CollisionType_Actor, + CollisionType_World|CollisionType_HeightMap); } void PhysicActor::enableCollisionBody() { - mEngine->dynamicsWorld->addRigidBody(mBody,CollisionType_Actor,CollisionType_World|CollisionType_HeightMap); + mEngine->mDynamicsWorld->removeRigidBody(mBody); + mEngine->mDynamicsWorld->addRigidBody(mBody, CollisionType_Actor, + CollisionType_Actor|CollisionType_World|CollisionType_HeightMap); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -189,8 +232,8 @@ namespace Physic broadphase = new btDbvtBroadphase(); // The world. - dynamicsWorld = new btDiscreteDynamicsWorld(dispatcher,broadphase,solver,collisionConfiguration); - dynamicsWorld->setGravity(btVector3(0,0,-10)); + mDynamicsWorld = new btDiscreteDynamicsWorld(dispatcher,broadphase,solver,collisionConfiguration); + mDynamicsWorld->setGravity(btVector3(0,0,-10)); if(BulletShapeManager::getSingletonPtr() == NULL) { @@ -208,10 +251,10 @@ namespace Physic if(!isDebugCreated) { Ogre::SceneNode* node = mSceneMgr->getRootSceneNode()->createChildSceneNode(); - mDebugDrawer = new BtOgre::DebugDrawer(node, dynamicsWorld); - dynamicsWorld->setDebugDrawer(mDebugDrawer); + mDebugDrawer = new BtOgre::DebugDrawer(node, mDynamicsWorld); + mDynamicsWorld->setDebugDrawer(mDebugDrawer); isDebugCreated = true; - dynamicsWorld->debugDrawWorld(); + mDynamicsWorld->debugDrawWorld(); } } @@ -238,10 +281,15 @@ namespace Physic PhysicEngine::~PhysicEngine() { + for (std::map::iterator it = mAnimatedShapes.begin(); it != mAnimatedShapes.end(); ++it) + deleteShape(it->second.mCompound); + for (std::map::iterator it = mAnimatedRaycastingShapes.begin(); it != mAnimatedRaycastingShapes.end(); ++it) + deleteShape(it->second.mCompound); + HeightFieldContainer::iterator hf_it = mHeightFieldMap.begin(); for (; hf_it != mHeightFieldMap.end(); ++hf_it) { - dynamicsWorld->removeRigidBody(hf_it->second.mBody); + mDynamicsWorld->removeRigidBody(hf_it->second.mBody); delete hf_it->second.mShape; delete hf_it->second.mBody; } @@ -251,7 +299,7 @@ namespace Physic { if (rb_it->second != NULL) { - dynamicsWorld->removeRigidBody(rb_it->second); + mDynamicsWorld->removeRigidBody(rb_it->second); delete rb_it->second; rb_it->second = NULL; @@ -262,7 +310,7 @@ namespace Physic { if (rb_it->second != NULL) { - dynamicsWorld->removeRigidBody(rb_it->second); + mDynamicsWorld->removeRigidBody(rb_it->second); delete rb_it->second; rb_it->second = NULL; @@ -281,7 +329,7 @@ namespace Physic delete mDebugDrawer; - delete dynamicsWorld; + delete mDynamicsWorld; delete solver; delete collisionConfiguration; delete dispatcher; @@ -331,7 +379,7 @@ namespace Physic mHeightFieldMap [name] = hf; - dynamicsWorld->addRigidBody(body,CollisionType_HeightMap|CollisionType_Raycasting, + mDynamicsWorld->addRigidBody(body,CollisionType_HeightMap, CollisionType_World|CollisionType_Actor|CollisionType_Raycasting); } @@ -343,7 +391,7 @@ namespace Physic HeightField hf = mHeightFieldMap [name]; - dynamicsWorld->removeRigidBody(hf.mBody); + mDynamicsWorld->removeRigidBody(hf.mBody); delete hf.mShape; delete hf.mBody; @@ -367,7 +415,6 @@ namespace Physic { std::string sid = (boost::format("%07.3f") % scale).str(); std::string outputstring = mesh + sid; - //std::cout << "The string" << outputstring << "\n"; //get the shape from the .nif mShapeLoader->load(outputstring,"General"); @@ -397,17 +444,38 @@ namespace Physic if (!shape->mRaycastingShape && raycasting) return NULL; - if (!raycasting) - shape->mCollisionShape->setLocalScaling( btVector3(scale,scale,scale)); - else - shape->mRaycastingShape->setLocalScaling( btVector3(scale,scale,scale)); + btCollisionShape* collisionShape = raycasting ? shape->mRaycastingShape : shape->mCollisionShape; + + // If this is an animated compound shape, we must duplicate it so we can animate + // multiple instances independently. + if (!raycasting && !shape->mAnimatedShapes.empty()) + collisionShape = duplicateCollisionShape(collisionShape); + if (raycasting && !shape->mAnimatedRaycastingShapes.empty()) + collisionShape = duplicateCollisionShape(collisionShape); + + collisionShape->setLocalScaling( btVector3(scale,scale,scale)); //create the real body btRigidBody::btRigidBodyConstructionInfo CI = btRigidBody::btRigidBodyConstructionInfo - (0,0, raycasting ? shape->mRaycastingShape : shape->mCollisionShape); + (0,0, collisionShape); RigidBody* body = new RigidBody(CI,name); body->mPlaceable = placeable; + if (!raycasting && !shape->mAnimatedShapes.empty()) + { + AnimatedShapeInstance instance; + instance.mAnimatedShapes = shape->mAnimatedShapes; + instance.mCompound = collisionShape; + mAnimatedShapes[body] = instance; + } + if (raycasting && !shape->mAnimatedRaycastingShapes.empty()) + { + AnimatedShapeInstance instance; + instance.mAnimatedShapes = shape->mAnimatedRaycastingShapes; + instance.mCompound = collisionShape; + mAnimatedRaycastingShapes[body] = instance; + } + if(scaledBoxTranslation != 0) *scaledBoxTranslation = shape->mBoxTranslation * scale; if(boxRotation != 0) @@ -415,34 +483,20 @@ namespace Physic adjustRigidBody(body, position, rotation, shape->mBoxTranslation * scale, shape->mBoxRotation); + if (!raycasting) + { + assert (mCollisionObjectMap.find(name) == mCollisionObjectMap.end()); + mCollisionObjectMap[name] = body; + mDynamicsWorld->addRigidBody(body,CollisionType_World,CollisionType_World|CollisionType_Actor|CollisionType_HeightMap); + } + else + { + assert (mRaycastingObjectMap.find(name) == mRaycastingObjectMap.end()); + mRaycastingObjectMap[name] = body; + mDynamicsWorld->addRigidBody(body,CollisionType_Raycasting,CollisionType_Raycasting); + } + return body; - - } - - void PhysicEngine::addRigidBody(RigidBody* body, bool addToMap, RigidBody* raycastingBody,bool actor) - { - if(!body && !raycastingBody) - return; // nothing to do - - const std::string& name = (body ? body->mName : raycastingBody->mName); - - if (body){ - if(actor) dynamicsWorld->addRigidBody(body,CollisionType_Actor,CollisionType_World|CollisionType_HeightMap); - else dynamicsWorld->addRigidBody(body,CollisionType_World,CollisionType_World|CollisionType_Actor|CollisionType_HeightMap); - } - - if (raycastingBody) - dynamicsWorld->addRigidBody(raycastingBody,CollisionType_Raycasting,CollisionType_Raycasting|CollisionType_World); - - if(addToMap){ - removeRigidBody(name); - deleteRigidBody(name); - - if (body) - mCollisionObjectMap[name] = body; - if (raycastingBody) - mRaycastingObjectMap[name] = raycastingBody; - } } void PhysicEngine::removeRigidBody(const std::string &name) @@ -453,7 +507,7 @@ namespace Physic RigidBody* body = it->second; if(body != NULL) { - dynamicsWorld->removeRigidBody(body); + mDynamicsWorld->removeRigidBody(body); } } it = mRaycastingObjectMap.find(name); @@ -462,7 +516,7 @@ namespace Physic RigidBody* body = it->second; if(body != NULL) { - dynamicsWorld->removeRigidBody(body); + mDynamicsWorld->removeRigidBody(body); } } } @@ -476,6 +530,10 @@ namespace Physic if(body != NULL) { + if (mAnimatedShapes.find(body) != mAnimatedShapes.end()) + deleteShape(mAnimatedShapes[body].mCompound); + mAnimatedShapes.erase(body); + delete body; } mCollisionObjectMap.erase(it); @@ -487,6 +545,10 @@ namespace Physic if(body != NULL) { + if (mAnimatedRaycastingShapes.find(body) != mAnimatedRaycastingShapes.end()) + deleteShape(mAnimatedRaycastingShapes[body].mCompound); + mAnimatedRaycastingShapes.erase(body); + delete body; } mRaycastingObjectMap.erase(it); @@ -605,7 +667,7 @@ namespace Physic if (!body) // fall back to raycasting body if there is no collision body body = getRigidBody(name, true); ContactTestResultCallback callback; - dynamicsWorld->contactTest(body, callback); + mDynamicsWorld->contactTest(body, callback); return callback.mResult; } @@ -615,16 +677,16 @@ namespace Physic btCollisionObject *object) { DeepestNotMeContactTestResultCallback callback(filter, origin); + callback.m_collisionFilterGroup = CollisionType_Actor; callback.m_collisionFilterMask = CollisionType_World | CollisionType_HeightMap | CollisionType_Actor; - dynamicsWorld->contactTest(object, callback); + mDynamicsWorld->contactTest(object, callback); return std::make_pair(callback.mObject, callback.mContactPoint); } - void PhysicEngine::stepSimulation(double deltaT) { // This seems to be needed for character controller objects - dynamicsWorld->stepSimulation(deltaT,10, 1/60.0); + mDynamicsWorld->stepSimulation(deltaT,10, 1/60.0); if(isDebugCreated) { mDebugDrawer->step(); @@ -640,8 +702,6 @@ namespace Physic PhysicActor* newActor = new PhysicActor(name, mesh, this, position, rotation, scale); - - //dynamicsWorld->addAction( newActor->mCharacter ); mActorMap[name] = newActor; } @@ -653,7 +713,6 @@ namespace Physic PhysicActor* act = it->second; if(act != NULL) { - delete act; } mActorMap.erase(it); @@ -684,14 +743,15 @@ namespace Physic float d = -1; btCollisionWorld::ClosestRayResultCallback resultCallback1(from, to); + resultCallback1.m_collisionFilterGroup = 0xff; if(raycastingObjectOnly) - resultCallback1.m_collisionFilterMask = CollisionType_Raycasting; + resultCallback1.m_collisionFilterMask = CollisionType_Raycasting|CollisionType_Actor; else resultCallback1.m_collisionFilterMask = CollisionType_World; if(!ignoreHeightMap) resultCallback1.m_collisionFilterMask = resultCallback1.m_collisionFilterMask | CollisionType_HeightMap; - dynamicsWorld->rayTest(from, to, resultCallback1); + mDynamicsWorld->rayTest(from, to, resultCallback1); if (resultCallback1.hasHit()) { name = static_cast(*resultCallback1.m_collisionObject).mName; @@ -724,6 +784,7 @@ namespace Physic std::pair PhysicEngine::sphereCast (float radius, btVector3& from, btVector3& to) { OurClosestConvexResultCallback callback(from, to); + callback.m_collisionFilterGroup = 0xff; callback.m_collisionFilterMask = OEngine::Physic::CollisionType_World|OEngine::Physic::CollisionType_HeightMap; btSphereShape shape(radius); @@ -732,7 +793,7 @@ namespace Physic btTransform from_ (btrot, from); btTransform to_ (btrot, to); - dynamicsWorld->convexSweepTest(&shape, from_, to_, callback); + mDynamicsWorld->convexSweepTest(&shape, from_, to_, callback); if (callback.hasHit()) return std::make_pair(true, callback.m_closestHitFraction); @@ -743,8 +804,9 @@ namespace Physic std::vector< std::pair > PhysicEngine::rayTest2(btVector3& from, btVector3& to) { MyRayResultCallback resultCallback1; - resultCallback1.m_collisionFilterMask = CollisionType_Raycasting; - dynamicsWorld->rayTest(from, to, resultCallback1); + resultCallback1.m_collisionFilterGroup = 0xff; + resultCallback1.m_collisionFilterMask = CollisionType_Raycasting|CollisionType_Actor|CollisionType_HeightMap; + mDynamicsWorld->rayTest(from, to, resultCallback1); std::vector< std::pair > results = resultCallback1.results; std::vector< std::pair > results2; diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp index 803986d5b7..e37caee386 100644 --- a/libs/openengine/bullet/physic.hpp +++ b/libs/openengine/bullet/physic.hpp @@ -76,13 +76,13 @@ namespace Physic RigidBody(btRigidBody::btRigidBodyConstructionInfo& CI,std::string name); virtual ~RigidBody(); std::string mName; + + // Hack: placeable objects (that can be picked up by the player) have different collision behaviour. + // This variable needs to be passed to BulletNifLoader. bool mPlaceable; }; - /** - * A physic actor uses a rigid body based on box shapes. - * Pmove is used to move the physic actor around the dynamic world. - */ + class PhysicActor { public: @@ -92,13 +92,6 @@ namespace Physic void setPosition(const Ogre::Vector3 &pos); - /** - * This adjusts the rotation of a PhysicActor - * If we have any problems with this (getting stuck in pmove) we should change it - * from setting the visual orientation to setting the orientation of the rigid body directly. - */ - void setRotation(const Ogre::Quaternion &quat); - /** * Sets the collisionMode for this actor. If disabled, the actor can fly and clip geometry. */ @@ -111,28 +104,20 @@ namespace Physic bool getCollisionMode() const { - return mCollisionMode; + return mInternalCollisionMode; } - - /** - * This returns the visual position of the PhysicActor (used to position a scenenode). - * Note - this is different from the position of the contained mBody. - */ - Ogre::Vector3 getPosition(); - - /** - * Returns the visual orientation of the PhysicActor - */ - Ogre::Quaternion getRotation(); - /** * Sets the scale of the PhysicActor */ void setScale(float scale); + void setRotation (const Ogre::Quaternion& rotation); + + const Ogre::Vector3& getPosition() const; + /** - * Returns the half extents for this PhysiActor + * Returns the (scaled) half extents */ Ogre::Vector3 getHalfExtents() const; @@ -153,7 +138,7 @@ namespace Physic bool getOnGround() const { - return mCollisionMode && mOnGround; + return mInternalCollisionMode && mOnGround; } btCollisionObject *getCollisionBody() const @@ -165,17 +150,21 @@ namespace Physic void disableCollisionBody(); void enableCollisionBody(); - OEngine::Physic::RigidBody* mBody; - OEngine::Physic::RigidBody* mRaycastingBody; + boost::shared_ptr mShape; - Ogre::Vector3 mBoxScaledTranslation; - Ogre::Quaternion mBoxRotation; - Ogre::Quaternion mBoxRotationInverse; + OEngine::Physic::RigidBody* mBody; + + Ogre::Quaternion mMeshOrientation; + Ogre::Vector3 mMeshTranslation; + Ogre::Vector3 mHalfExtents; + + float mScale; + Ogre::Vector3 mPosition; Ogre::Vector3 mForce; bool mOnGround; - bool mCollisionMode; - bool mCollisionBody; + bool mInternalCollisionMode; + bool mExternalCollisionMode; std::string mMesh; std::string mName; @@ -189,6 +178,14 @@ namespace Physic RigidBody* mBody; }; + struct AnimatedShapeInstance + { + btCollisionShape* mCompound; + + // Maps bone name to child index in the compound shape + std::map mAnimatedShapes; + }; + /** * The PhysicEngine class contain everything which is needed for Physic. * It's needed that Ogre Resources are set up before the PhysicEngine is created. @@ -239,11 +236,6 @@ namespace Physic */ void removeHeightField(int x, int y); - /** - * Add a RigidBody to the simulation - */ - void addRigidBody(RigidBody* body, bool addToMap = true, RigidBody* raycastingBody = NULL,bool actor = false); - /** * Remove a RigidBody from the simulation. It does not delete it, and does not remove it from the RigidBodyMap. */ @@ -335,7 +327,7 @@ namespace Physic btDefaultCollisionConfiguration* collisionConfiguration; btSequentialImpulseConstraintSolver* solver; btCollisionDispatcher* dispatcher; - btDiscreteDynamicsWorld* dynamicsWorld; + btDiscreteDynamicsWorld* mDynamicsWorld; //the NIF file loader. BulletShapeLoader* mShapeLoader; @@ -346,8 +338,14 @@ namespace Physic typedef std::map RigidBodyContainer; RigidBodyContainer mCollisionObjectMap; + // Compound shapes that must be animated each frame based on bone positions + // the index refers to an element in mCollisionObjectMap + std::map mAnimatedShapes; + RigidBodyContainer mRaycastingObjectMap; + std::map mAnimatedRaycastingShapes; + typedef std::map PhysicActorContainer; PhysicActorContainer mActorMap; diff --git a/libs/openengine/bullet/trace.cpp b/libs/openengine/bullet/trace.cpp index 6eab43a606..ad140f1f7e 100644 --- a/libs/openengine/bullet/trace.cpp +++ b/libs/openengine/bullet/trace.cpp @@ -65,12 +65,13 @@ void ActorTracer::doTrace(btCollisionObject *actor, const Ogre::Vector3 &start, to.setOrigin(btend); ClosestNotMeConvexResultCallback newTraceCallback(actor, btstart-btend, btScalar(0.0)); - newTraceCallback.m_collisionFilterMask = CollisionType_World | CollisionType_HeightMap | - CollisionType_Actor; + // Inherit the actor's collision group and mask + newTraceCallback.m_collisionFilterGroup = actor->getBroadphaseHandle()->m_collisionFilterGroup; + newTraceCallback.m_collisionFilterMask = actor->getBroadphaseHandle()->m_collisionFilterMask; btCollisionShape *shape = actor->getCollisionShape(); assert(shape->isConvex()); - enginePass->dynamicsWorld->convexSweepTest(static_cast(shape), + enginePass->mDynamicsWorld->convexSweepTest(static_cast(shape), from, to, newTraceCallback); // Copy the hit data over to our trace results struct: @@ -89,27 +90,26 @@ void ActorTracer::doTrace(btCollisionObject *actor, const Ogre::Vector3 &start, } } -void ActorTracer::findGround(btCollisionObject *actor, const Ogre::Vector3 &start, const Ogre::Vector3 &end, const PhysicEngine *enginePass) +void ActorTracer::findGround(const OEngine::Physic::PhysicActor* actor, const Ogre::Vector3 &start, const Ogre::Vector3 &end, const PhysicEngine *enginePass) { const btVector3 btstart(start.x, start.y, start.z+1.0f); const btVector3 btend(end.x, end.y, end.z+1.0f); - const btTransform &trans = actor->getWorldTransform(); + const btTransform &trans = actor->getCollisionBody()->getWorldTransform(); btTransform from(trans.getBasis(), btstart); btTransform to(trans.getBasis(), btend); - ClosestNotMeConvexResultCallback newTraceCallback(actor, btstart-btend, btScalar(0.0)); - newTraceCallback.m_collisionFilterMask = CollisionType_World | CollisionType_HeightMap | - CollisionType_Actor; + ClosestNotMeConvexResultCallback newTraceCallback(actor->getCollisionBody(), btstart-btend, btScalar(0.0)); + // Inherit the actor's collision group and mask + newTraceCallback.m_collisionFilterGroup = actor->getCollisionBody()->getBroadphaseHandle()->m_collisionFilterGroup; + newTraceCallback.m_collisionFilterMask = actor->getCollisionBody()->getBroadphaseHandle()->m_collisionFilterMask; - const btBoxShape *shape = dynamic_cast(actor->getCollisionShape()); - assert(shape); + btVector3 halfExtents(actor->getHalfExtents().x, actor->getHalfExtents().y, actor->getHalfExtents().z); - btVector3 halfExtents = shape->getHalfExtentsWithMargin(); halfExtents[2] = 1.0f; - btBoxShape box(halfExtents); + btCylinderShapeZ base(halfExtents); - enginePass->dynamicsWorld->convexSweepTest(&box, from, to, newTraceCallback); + enginePass->mDynamicsWorld->convexSweepTest(&base, from, to, newTraceCallback); if(newTraceCallback.hasHit()) { const btVector3& tracehitnormal = newTraceCallback.m_hitNormalWorld; diff --git a/libs/openengine/bullet/trace.h b/libs/openengine/bullet/trace.h index 92795c87fa..b9fbce64d9 100644 --- a/libs/openengine/bullet/trace.h +++ b/libs/openengine/bullet/trace.h @@ -12,6 +12,7 @@ namespace OEngine namespace Physic { class PhysicEngine; + class PhysicActor; struct ActorTracer { @@ -22,7 +23,7 @@ namespace Physic void doTrace(btCollisionObject *actor, const Ogre::Vector3 &start, const Ogre::Vector3 &end, const PhysicEngine *enginePass); - void findGround(btCollisionObject *actor, const Ogre::Vector3 &start, const Ogre::Vector3 &end, + void findGround(const OEngine::Physic::PhysicActor* actor, const Ogre::Vector3 &start, const Ogre::Vector3 &end, const PhysicEngine *enginePass); }; }