diff --git a/.gitignore b/.gitignore index f22f1bd49c..c061ca637e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ CMakeFiles */CMakeFiles CMakeCache.txt cmake_install.cmake -CMakeLists.txt.user Makefile makefile build @@ -22,6 +21,8 @@ Doxygen .project .settings .directory +## qt-creator +CMakeLists.txt.user* ## resources data diff --git a/.travis.yml b/.travis.yml index 112cf94f16..04d019c0d8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,7 @@ before_install: - sudo apt-get install -qq libqt4-dev libxaw7-dev libxrandr-dev libfreeimage-dev libpng-dev - sudo apt-get install -qq libopenal-dev libmpg123-dev libsndfile1-dev - sudo apt-get install -qq libavcodec-dev libavformat-dev libavdevice-dev libavutil-dev libswscale-dev libpostproc-dev - - sudo apt-get install -qq libbullet-dev libogre-1.8-dev libmygui-dev libsdl2-dev libunshield-dev + - sudo apt-get install -qq libbullet-dev libogre-1.9-dev libmygui-dev libsdl2-dev libunshield-dev - sudo mkdir /usr/src/gtest/build - cd /usr/src/gtest/build - sudo cmake .. -DBUILD_SHARED_LIBS=1 diff --git a/CMakeLists.txt b/CMakeLists.txt index f6014dff6e..64f8121c49 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -99,8 +99,6 @@ set(OENGINE_BULLET ${LIBDIR}/openengine/bullet/BtOgreExtras.h ${LIBDIR}/openengine/bullet/BtOgreGP.h ${LIBDIR}/openengine/bullet/BtOgrePG.h - ${LIBDIR}/openengine/bullet/CMotionState.cpp - ${LIBDIR}/openengine/bullet/CMotionState.h ${LIBDIR}/openengine/bullet/physic.cpp ${LIBDIR}/openengine/bullet/physic.hpp ${LIBDIR}/openengine/bullet/BulletShapeLoader.cpp @@ -212,7 +210,7 @@ if (HAVE_UNORDERED_MAP) endif () -set(BOOST_COMPONENTS system filesystem program_options thread date_time wave) +set(BOOST_COMPONENTS system filesystem program_options) IF(BOOST_STATIC) set(Boost_USE_STATIC_LIBS ON) diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index e9fb1c5379..cc09452c91 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -989,8 +989,7 @@ void Record::print() std::cout << " Faction: " << mData.mFaction << std::endl; std::cout << " Flags: " << npcFlags(mData.mFlags) << std::endl; - // Seriously? - if (mData.mNpdt52.mGold == -10) + if (mData.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) { std::cout << " Level: " << mData.mNpdt12.mLevel << std::endl; std::cout << " Reputation: " << (int)mData.mNpdt12.mReputation << std::endl; @@ -1022,7 +1021,7 @@ void Record::print() std::cout << " Luck: " << (int)mData.mNpdt52.mLuck << std::endl; std::cout << " Skills:" << std::endl; - for (int i = 0; i != 27; i++) + for (int i = 0; i != ESM::Skill::Length; i++) std::cout << " " << skillLabel(i) << ": " << (int)((unsigned char)mData.mNpdt52.mSkills[i]) << std::endl; diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index 18c555a249..e4638c31b4 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -137,8 +137,3 @@ if (BUILD_WITH_CODE_COVERAGE) target_link_libraries(omwlauncher gcov) endif() -# Workaround for binutil => 2.23 problem when linking, should be fixed eventually upstream -if (UNIX AND NOT APPLE) -target_link_libraries(omwlauncher dl Xt) -endif() - diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 4012a1fbd5..9b3c4e1b02 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -219,7 +219,7 @@ bool Launcher::MainDialog::showFirstRunDialog() } // Create the file if it doesn't already exist, else the importer will fail - QString path = QString::fromStdString(mCfgMgr.getUserPath().string()) + QString("openmw.cfg"); + QString path = QString::fromStdString(mCfgMgr.getUserConfigPath().string()) + QString("openmw.cfg"); QFile file(path); if (!file.exists()) { @@ -334,7 +334,7 @@ bool Launcher::MainDialog::setupLauncherSettings() { mLauncherSettings.setMultiValueEnabled(true); - QString userPath = QString::fromStdString(mCfgMgr.getUserPath().string()); + QString userPath = QString::fromStdString(mCfgMgr.getUserConfigPath().string()); QStringList paths; paths.append(QString("launcher.cfg")); @@ -440,9 +440,35 @@ bool Launcher::expansions(Launcher::UnshieldThread& cd) bool Launcher::MainDialog::setupGameSettings() { - QString userPath = QString::fromStdString(mCfgMgr.getUserPath().string()); + QString userPath = QString::fromStdString(mCfgMgr.getUserConfigPath().string()); QString globalPath = QString::fromStdString(mCfgMgr.getGlobalPath().string()); + // Load the user config file first, separately + // So we can write it properly, uncontaminated + QString path = userPath + QLatin1String("openmw.cfg"); + QFile file(path); + + qDebug() << "Loading config file:" << qPrintable(path); + + if (file.exists()) { + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error opening OpenMW configuration file")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(QObject::tr("
Could not open %0 for reading

\ + Please make sure you have the right permissions \ + and try again.
").arg(file.fileName())); + msgBox.exec(); + return false; + } + QTextStream stream(&file); + stream.setCodec(QTextCodec::codecForName("UTF-8")); + + mGameSettings.readUserFile(stream); + } + + // Now the rest QStringList paths; paths.append(userPath + QString("openmw.cfg")); paths.append(QString("openmw.cfg")); @@ -565,7 +591,7 @@ bool Launcher::MainDialog::setupGraphicsSettings() { mGraphicsSettings.setMultiValueEnabled(false); - QString userPath = QString::fromStdString(mCfgMgr.getUserPath().string()); + QString userPath = QString::fromStdString(mCfgMgr.getUserConfigPath().string()); QString globalPath = QString::fromStdString(mCfgMgr.getGlobalPath().string()); QFile localDefault(QString("settings-default.cfg")); @@ -652,7 +678,7 @@ bool Launcher::MainDialog::writeSettings() mGraphicsPage->saveSettings(); mDataFilesPage->saveSettings(); - QString userPath = QString::fromStdString(mCfgMgr.getUserPath().string()); + QString userPath = QString::fromStdString(mCfgMgr.getUserConfigPath().string()); QDir dir(userPath); if (!dir.exists()) { diff --git a/apps/launcher/settings/gamesettings.cpp b/apps/launcher/settings/gamesettings.cpp index 41113c35aa..e7e5cf1ea7 100644 --- a/apps/launcher/settings/gamesettings.cpp +++ b/apps/launcher/settings/gamesettings.cpp @@ -90,6 +90,16 @@ QStringList Launcher::GameSettings::values(const QString &key, const QStringList } bool Launcher::GameSettings::readFile(QTextStream &stream) +{ + return readFile(stream, mSettings); +} + +bool Launcher::GameSettings::readUserFile(QTextStream &stream) +{ + return readFile(stream, mUserSettings); +} + +bool Launcher::GameSettings::readFile(QTextStream &stream, QMap &settings) { QMap cache; QRegExp keyRe("^([^=]+)\\s*=\\s*(.+)$"); @@ -107,10 +117,10 @@ bool Launcher::GameSettings::readFile(QTextStream &stream) // Don't remove existing data entries if (key != QLatin1String("data")) - mSettings.remove(key); + settings.remove(key); QStringList values = cache.values(key); - values.append(mSettings.values(key)); + values.append(settings.values(key)); if (!values.contains(value)) { cache.insertMulti(key, value); @@ -118,23 +128,24 @@ bool Launcher::GameSettings::readFile(QTextStream &stream) } } - if (mSettings.isEmpty()) { - mSettings = cache; // This is the first time we read a file + if (settings.isEmpty()) { + settings = cache; // This is the first time we read a file validatePaths(); return true; } // Merge the changed keys with those which didn't - mSettings.unite(cache); + settings.unite(cache); validatePaths(); return true; } + bool Launcher::GameSettings::writeFile(QTextStream &stream) { // Iterate in reverse order to preserve insertion order - QMapIterator i(mSettings); + QMapIterator i(mUserSettings); i.toBack(); while (i.hasPrevious()) { @@ -162,7 +173,7 @@ bool Launcher::GameSettings::writeFile(QTextStream &stream) } - QStringList content = mSettings.values(QString("content")); + QStringList content = mUserSettings.values(QString("content")); for (int i = content.count(); i--;) { stream << "content=" << content.at(i) << "\n"; } @@ -172,14 +183,14 @@ bool Launcher::GameSettings::writeFile(QTextStream &stream) bool Launcher::GameSettings::hasMaster() { - bool result = false; - QStringList content = mSettings.values(QString("content")); - for (int i = 0; i < content.count(); ++i) { - if (content.at(i).contains(".omwgame") || content.at(i).contains(".esm")) { - result = true; - break; + bool result = false; + QStringList content = mSettings.values(QString("content")); + for (int i = 0; i < content.count(); ++i) { + if (content.at(i).contains(".omwgame") || content.at(i).contains(".esm")) { + result = true; + break; + } } - } - return result; + return result; } diff --git a/apps/launcher/settings/gamesettings.hpp b/apps/launcher/settings/gamesettings.hpp index 60236200a9..df82150742 100644 --- a/apps/launcher/settings/gamesettings.hpp +++ b/apps/launcher/settings/gamesettings.hpp @@ -31,6 +31,7 @@ namespace Launcher inline void setValue(const QString &key, const QString &value) { mSettings.insert(key, value); + mUserSettings.insert(key, value); } inline void setMultiValue(const QString &key, const QString &value) @@ -38,11 +39,16 @@ namespace Launcher QStringList values = mSettings.values(key); if (!values.contains(value)) mSettings.insertMulti(key, value); + + values = mUserSettings.values(key); + if (!values.contains(value)) + mUserSettings.insertMulti(key, value); } inline void remove(const QString &key) { mSettings.remove(key); + mUserSettings.remove(key); } inline QStringList getDataDirs() { return mDataDirs; } @@ -52,7 +58,11 @@ namespace Launcher bool hasMaster(); QStringList values(const QString &key, const QStringList &defaultValues = QStringList()); + bool readFile(QTextStream &stream); + bool readFile(QTextStream &stream, QMap &settings); + bool readUserFile(QTextStream &stream); + bool writeFile(QTextStream &stream); private: @@ -60,6 +70,7 @@ namespace Launcher void validatePaths(); QMap mSettings; + QMap mUserSettings; QStringList mDataDirs; QString mDataLocal; diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index b8b7e4c9da..cf15891144 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -16,7 +16,7 @@ MwIniImporter::MwIniImporter() const char *map[][2] = { { "fps", "General:Show FPS" }, - { "nosound", "General:Disable Audio" }, + { "no-sound", "General:Disable Audio" }, { 0, 0 } }; const char *fallback[] = { diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 1197e20140..e2dffdbde4 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -143,7 +143,7 @@ if(WIN32) set(QT_USE_QTMAIN TRUE) endif(WIN32) -find_package(Qt4 COMPONENTS QtCore QtGui QtNetwork QtXml QtXmlPatterns REQUIRED) +find_package(Qt4 COMPONENTS QtCore QtGui QtNetwork REQUIRED) include(${QT_USE_FILE}) qt4_wrap_ui(OPENCS_UI_HDR ${OPENCS_UI}) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 1c1e37c2d4..44926610b3 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -86,10 +86,6 @@ void CS::Editor::setupDataFiles() return; } - // Set the charset for reading the esm/esp files - // QString encoding = QString::fromStdString(variables["encoding"].as()); - //mFileDialog.setEncoding(encoding); - dataDirs.insert (dataDirs.end(), dataLocal.begin(), dataLocal.end()); mDocumentManager.setResourceDir (variables["resources"].as()); diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp index 341bdc7800..57eaf2d253 100644 --- a/apps/opencs/main.cpp +++ b/apps/opencs/main.cpp @@ -42,10 +42,10 @@ int main(int argc, char *argv[]) // TODO: Ogre startup shouldn't be here, but it currently has to: // SceneWidget destructor will delete the created render window, which would be called _after_ Root has shut down :( - OgreInit::OgreInit ogreInit; - ogreInit.init("./opencsOgre.log"); // TODO log path? Application mApplication (argc, argv); + OgreInit::OgreInit ogreInit; + ogreInit.init("./opencsOgre.log"); // TODO log path? #ifdef Q_OS_MAC QDir dir(QCoreApplication::applicationDirPath()); diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 27f4f498a4..3ef14ee7e5 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -2221,7 +2221,7 @@ void CSMDoc::Document::createBase() CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, const std::vector< boost::filesystem::path >& files, const boost::filesystem::path& savePath, const boost::filesystem::path& resDir, bool new_) : mSavePath (savePath), mContentFiles (files), mTools (mData), mResDir(resDir), - mProjectPath ((configuration.getUserPath() / "projects") / + mProjectPath ((configuration.getUserDataPath() / "projects") / (savePath.filename().string() + ".project")), mSaving (*this, mProjectPath) { @@ -2254,7 +2254,7 @@ CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, co } else { - boost::filesystem::path locCustomFiltersPath (configuration.getUserPath()); + boost::filesystem::path locCustomFiltersPath (configuration.getUserDataPath()); locCustomFiltersPath /= "defaultfilters"; if (boost::filesystem::exists(locCustomFiltersPath)) diff --git a/apps/opencs/model/doc/documentmanager.cpp b/apps/opencs/model/doc/documentmanager.cpp index 024c46beae..3ff75c9c15 100644 --- a/apps/opencs/model/doc/documentmanager.cpp +++ b/apps/opencs/model/doc/documentmanager.cpp @@ -15,7 +15,7 @@ CSMDoc::DocumentManager::DocumentManager (const Files::ConfigurationManager& configuration) : mConfiguration (configuration) { - boost::filesystem::path projectPath = configuration.getUserPath() / "projects"; + boost::filesystem::path projectPath = configuration.getUserDataPath() / "projects"; if (!boost::filesystem::is_directory (projectPath)) boost::filesystem::create_directories (projectPath); @@ -53,4 +53,4 @@ bool CSMDoc::DocumentManager::removeDocument (Document *document) void CSMDoc::DocumentManager::setResourceDir (const boost::filesystem::path& parResDir) { mResDir = boost::filesystem::system_complete(parResDir); -} \ No newline at end of file +} diff --git a/apps/opencs/model/settings/usersettings.cpp b/apps/opencs/model/settings/usersettings.cpp index 1ce28ed75c..94cee8a43b 100644 --- a/apps/opencs/model/settings/usersettings.cpp +++ b/apps/opencs/model/settings/usersettings.cpp @@ -251,7 +251,7 @@ void CSMSettings::UserSettings::loadSettings (const QString &fileName) bool localOk = loadFromFile(localFilePath); //user - mUserFilePath = QString::fromStdString(mCfgMgr.getUserPath().string()) + fileName; + mUserFilePath = QString::fromStdString(mCfgMgr.getUserConfigPath().string()) + fileName; loadFromFile(mUserFilePath); if (!(localOk || globalOk)) diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index 7a13137205..9c0d5b0fd5 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -263,7 +263,7 @@ namespace static const char *sCreatureTypes[] = { - "Creature", "Deadra", "Undead", "Humanoid", 0 + "Creature", "Daedra", "Undead", "Humanoid", 0 }; static const char *sWeaponTypes[] = @@ -342,4 +342,4 @@ std::vector CSMWorld::Columns::getEnums (ColumnId column) } return enums; -} \ No newline at end of file +} diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 0b35640243..69849909fd 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -19,8 +19,8 @@ source_group(game FILES ${GAME} ${GAME_HEADER}) add_openmw_dir (mwrender renderingmanager debugging sky camera animation npcanimation creatureanimation activatoranimation actors objects renderinginterface localmap occlusionquery water shadows - compositors characterpreview externalrendering globalmap videoplayer ripplesimulation refraction - terrainstorage + characterpreview externalrendering globalmap videoplayer ripplesimulation refraction + terrainstorage renderconst ) add_openmw_dir (mwinput @@ -74,6 +74,7 @@ add_openmw_dir (mwmechanics mechanicsmanagerimp stat character creaturestats magiceffects movement actors objects drawstate spells activespells npcstats aipackage aisequence alchemy aiwander aitravel aifollow aiescort aiactivate aicombat repair enchanting pathfinding security spellsuccess spellcasting + disease ) add_openmw_dir (mwstate @@ -86,6 +87,8 @@ add_openmw_dir (mwbase ) # Main executable +set(BOOST_COMPONENTS system filesystem program_options thread wave) +find_package(Boost REQUIRED COMPONENTS ${BOOST_COMPONENTS}) IF(OGRE_STATIC) ADD_DEFINITIONS(-DENABLE_PLUGIN_OctreeSceneManager -DENABLE_PLUGIN_ParticleFX -DENABLE_PLUGIN_GL) @@ -115,6 +118,7 @@ add_definitions(${SOUND_DEFINE}) target_link_libraries(openmw ${OGRE_LIBRARIES} ${OGRE_STATIC_PLUGINS} + ${SHINY_LIBRARIES} ${Boost_LIBRARIES} ${OPENAL_LIBRARY} ${SOUND_INPUT_LIBRARY} @@ -122,7 +126,6 @@ target_link_libraries(openmw ${MYGUI_LIBRARIES} ${SDL2_LIBRARY} ${MYGUI_PLATFORM_LIBRARIES} - ${SHINY_LIBRARIES} "oics" "sdl4ogre" components @@ -141,12 +144,6 @@ if (UNIX AND NOT APPLE) target_link_libraries(openmw ${CMAKE_THREAD_LIBS_INIT}) endif() -# Workaround for binutil => 2.23 problem when linking, should be fixed eventually upstream -if (UNIX AND NOT APPLE) -target_link_libraries(openmw dl Xt) -endif() - - if(APPLE) find_library(COCOA_FRAMEWORK Cocoa) find_library(IOKIT_FRAMEWORK IOKit) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 5b8872b0f0..2e9a1324bb 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -66,10 +66,6 @@ void OMW::Engine::executeLocalScripts() localScripts.setIgnore (MWWorld::Ptr()); } -void OMW::Engine::setAnimationVerbose(bool animverbose) -{ -} - bool OMW::Engine::frameStarted (const Ogre::FrameEvent& evt) { bool paused = MWBase::Environment::get().getWindowManager()->isGuiMode(); @@ -316,7 +312,7 @@ std::string OMW::Engine::loadSettings (Settings::Manager & settings) throw std::runtime_error ("No default settings file found! Make sure the file \"settings-default.cfg\" was properly installed."); // load user settings if they exist, otherwise just load the default settings as user settings - const std::string settingspath = mCfgMgr.getUserPath().string() + "/settings.cfg"; + const std::string settingspath = mCfgMgr.getUserConfigPath().string() + "/settings.cfg"; if (boost::filesystem::exists(settingspath)) settings.loadUser(settingspath); else if (boost::filesystem::exists(localdefault)) @@ -328,12 +324,16 @@ std::string OMW::Engine::loadSettings (Settings::Manager & settings) // load nif overrides NifOverrides::Overrides nifOverrides; - if (boost::filesystem::exists(mCfgMgr.getLocalPath().string() + "/transparency-overrides.cfg")) - nifOverrides.loadTransparencyOverrides(mCfgMgr.getLocalPath().string() + "/transparency-overrides.cfg"); - else if (boost::filesystem::exists(mCfgMgr.getGlobalPath().string() + "/transparency-overrides.cfg")) - nifOverrides.loadTransparencyOverrides(mCfgMgr.getGlobalPath().string() + "/transparency-overrides.cfg"); - - settings.setBool("hardware cursors", "GUI", true); + std::string transparencyOverrides = "/transparency-overrides.cfg"; + std::string materialOverrides = "/material-overrides.cfg"; + if (boost::filesystem::exists(mCfgMgr.getLocalPath().string() + transparencyOverrides)) + nifOverrides.loadTransparencyOverrides(mCfgMgr.getLocalPath().string() + transparencyOverrides); + else if (boost::filesystem::exists(mCfgMgr.getGlobalPath().string() + transparencyOverrides)) + nifOverrides.loadTransparencyOverrides(mCfgMgr.getGlobalPath().string() + transparencyOverrides); + if (boost::filesystem::exists(mCfgMgr.getLocalPath().string() + materialOverrides)) + nifOverrides.loadMaterialOverrides(mCfgMgr.getLocalPath().string() + materialOverrides); + else if (boost::filesystem::exists(mCfgMgr.getGlobalPath().string() + materialOverrides)) + nifOverrides.loadMaterialOverrides(mCfgMgr.getGlobalPath().string() + materialOverrides); return settingspath; } @@ -341,7 +341,7 @@ std::string OMW::Engine::loadSettings (Settings::Manager & settings) void OMW::Engine::prepareEngine (Settings::Manager & settings) { mEnvironment.setStateManager ( - new MWState::StateManager (mCfgMgr.getUserPath() / "saves", mContentFiles.at (0))); + new MWState::StateManager (mCfgMgr.getUserDataPath() / "saves", mContentFiles.at (0))); Nif::NIFFile::CacheLock cachelock; @@ -391,7 +391,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.getUserPath() / "input.xml").string(); + std::string keybinderUser = (mCfgMgr.getUserConfigPath() / "input.xml").string(); bool keybinderUserExists = boost::filesystem::exists(keybinderUser); MWInput::InputManager* input = new MWInput::InputManager (*mOgre, *this, keybinderUser, keybinderUserExists, mGrab); mEnvironment.setInputManager (input); @@ -540,6 +540,8 @@ void OMW::Engine::activate() std::string script = MWWorld::Class::get (ptr).getScript (ptr); + MWBase::Environment::get().getWorld()->breakInvisibility(MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); + if (!script.empty()) { MWBase::Environment::get().getWorld()->getLocalScripts().setIgnore (ptr); @@ -557,7 +559,7 @@ void OMW::Engine::screenshot() // Count screenshots. int shotCount = 0; - const std::string screenshotPath = mCfgMgr.getUserPath().string(); + const std::string& screenshotPath = mCfgMgr.getUserDataPath().string(); // Find the first unused filename with a do-while std::ostringstream stream; diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index a59bc3f59a..8b2a65b7ed 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -170,8 +170,6 @@ namespace OMW /// Font encoding void setEncoding(const ToUTF8::FromType& encoding); - void setAnimationVerbose(bool animverbose); - void setFallbackValues(std::map map); /// Enable console-only script functionality diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 4d169c74ad..6261a6fd00 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -3,8 +3,7 @@ #include -#include -#include +#include #include "engine.hpp" #if defined(_WIN32) && !defined(_CONSOLE) @@ -122,10 +121,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat ("content", bpo::value()->default_value(StringsVector(), "") ->multitoken(), "content file(s): esm/esp, or omwgame/omwaddon") - ("anim-verbose", bpo::value()->implicit_value(true) - ->default_value(false), "output animation indices files") - - ("nosound", bpo::value()->implicit_value(true) + ("no-sound", bpo::value()->implicit_value(true) ->default_value(false), "disable all sounds") ("script-verbose", bpo::value()->implicit_value(true) @@ -169,8 +165,6 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat bpo::store(valid_opts, variables); bpo::notify(variables); - cfgMgr.readConfiguration(variables, desc); - bool run = true; if (variables.count ("help")) @@ -188,6 +182,8 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat if (!run) return false; + cfgMgr.readConfiguration(variables, desc); + engine.setGrabMouse(!variables.count("no-grab")); // Font encoding settings @@ -238,10 +234,9 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat engine.setSkipMenu (variables["skip-menu"].as()); // other settings - engine.setSoundUsage(!variables["nosound"].as()); + engine.setSoundUsage(!variables["no-sound"].as()); engine.setScriptsVerbosity(variables["script-verbose"].as()); engine.setCompileAll(variables["script-all"].as()); - engine.setAnimationVerbose(variables["anim-verbose"].as()); engine.setFallbackValues(variables["fallback"].as().mMap); engine.setScriptConsoleMode (variables["script-console"].as()); engine.setStartupScript (variables["script-run"].as()); @@ -283,7 +278,7 @@ int main(int argc, char**argv) catch (std::exception &e) { #if OGRE_PLATFORM == OGRE_PLATFORM_LINUX || OGRE_PLATFORM == OGRE_PLATFORM_APPLE - if (isatty(fileno(stdin))) + if (isatty(fileno(stdin)) || !SDL_WasInit(SDL_INIT_VIDEO)) std::cerr << "\nERROR: " << e.what() << std::endl; else #endif diff --git a/apps/openmw/mwbase/environment.cpp b/apps/openmw/mwbase/environment.cpp index 052bba9abf..3bc15746ec 100644 --- a/apps/openmw/mwbase/environment.cpp +++ b/apps/openmw/mwbase/environment.cpp @@ -153,15 +153,15 @@ void MWBase::Environment::cleanup() delete mScriptManager; mScriptManager = 0; + delete mWindowManager; + mWindowManager = 0; + delete mWorld; mWorld = 0; delete mSoundManager; mSoundManager = 0; - delete mWindowManager; - mWindowManager = 0; - delete mInputManager; mInputManager = 0; diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index c47ad066b5..6c85be5fd2 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -137,8 +137,8 @@ namespace MWBase virtual void wmUpdateFps(float fps, unsigned int triangleCount, unsigned int batchCount) = 0; /// Set value for the given ID. - virtual void setValue (const std::string& id, const MWMechanics::Stat& value) = 0; - virtual void setValue (int parSkill, const MWMechanics::Stat& value) = 0; + virtual void setValue (const std::string& id, const MWMechanics::AttributeValue& value) = 0; + virtual void setValue (int parSkill, const MWMechanics::SkillValue& value) = 0; virtual void setValue (const std::string& id, const MWMechanics::DynamicStat& value) = 0; virtual void setValue (const std::string& id, const std::string& value) = 0; virtual void setValue (const std::string& id, int value) = 0; @@ -204,6 +204,7 @@ namespace MWBase virtual void activateQuickKey (int index) = 0; + virtual std::string getSelectedSpell() = 0; virtual void setSelectedSpell(const std::string& spellId, int successChancePercent) = 0; virtual void setSelectedEnchantItem(const MWWorld::Ptr& item) = 0; virtual void setSelectedWeapon(const MWWorld::Ptr& item) = 0; @@ -235,8 +236,8 @@ namespace MWBase virtual void onFrame (float frameDuration) = 0; /// \todo get rid of this stuff. Move it to the respective UI element classes, if needed. - virtual std::map > getPlayerSkillValues() = 0; - virtual std::map > getPlayerAttributeValues() = 0; + virtual std::map getPlayerSkillValues() = 0; + virtual std::map getPlayerAttributeValues() = 0; virtual SkillList getPlayerMinorSkills() = 0; virtual SkillList getPlayerMajorSkills() = 0; diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index f7041a47ba..06f6d6fac6 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -81,7 +81,6 @@ namespace MWBase Render_CollisionDebug, Render_Wireframe, Render_Pathgrid, - Render_Compositors, Render_BoundingBoxes }; @@ -142,7 +141,7 @@ namespace MWBase virtual Ogre::Vector2 getNorthVector (MWWorld::CellStore* cell) = 0; ///< get north vector (OGRE coordinates) for given interior cell - virtual std::vector getDoorMarkers (MWWorld::CellStore* cell) = 0; + virtual void getDoorMarkers (MWWorld::CellStore* cell, std::vector& out) = 0; ///< get a list of teleport door markers for a given cell, to be displayed on the local map virtual void getInteriorMapPosition (Ogre::Vector2 position, float& nX, float& nY, int &x, int& y) = 0; @@ -440,12 +439,44 @@ namespace MWBase virtual bool toggleGodMode() = 0; + /** + * @brief startSpellCast attempt to start casting a spell. Might fail immediately if conditions are not met. + * @param actor + * @return true if the spell can be casted (i.e. the animation should start) + */ + virtual bool startSpellCast (const MWWorld::Ptr& actor) = 0; + virtual void castSpell (const MWWorld::Ptr& actor) = 0; virtual void launchProjectile (const std::string& id, bool stack, const ESM::EffectList& effects, const MWWorld::Ptr& actor, const std::string& sourceName) = 0; virtual const std::vector& getContentFiles() const = 0; + + virtual void breakInvisibility (const MWWorld::Ptr& actor) = 0; + + // Are we in an exterior or pseudo-exterior cell and it's night? + virtual bool isDark() const = 0; + + virtual bool findInteriorPositionInWorldSpace(MWWorld::CellStore* cell, Ogre::Vector3& result) = 0; + + /// Teleports \a ptr to the reference of \a id (e.g. DivineMarker, PrisonMarker, TempleMarker) + /// closest to \a worldPos. + /// @note id must be lower case + virtual void teleportToClosestMarker (const MWWorld::Ptr& ptr, + const std::string& id, Ogre::Vector3 worldPos) = 0; + + enum DetectionType + { + Detect_Enchantment, + Detect_Key, + Detect_Creature + }; + /// List all references (filtered by \a type) detected by \a ptr. The range + /// is determined by the current magnitude of the "Detect X" magic effect belonging to \a type. + /// @note This also works for references in containers. + virtual void listDetectedReferences (const MWWorld::Ptr& ptr, std::vector& out, + DetectionType type) = 0; }; } diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index 697b755792..53c62273dc 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(ref->mBase->mData.mValue, "#{sValue}"); + text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index f3f36542a1..0fae1b05c3 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -172,7 +172,7 @@ namespace MWClass if (ptr.getCellRef().mCharge == -1) return ref->mBase->mData.mValue; else - return ref->mBase->mData.mValue * (ptr.getCellRef().mCharge / getItemMaxHealth(ptr)); + return ref->mBase->mData.mValue * (static_cast(ptr.getCellRef().mCharge) / getItemMaxHealth(ptr)); } void Armor::registerSelf() @@ -248,7 +248,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(ref->mBase->mData.mValue, "#{sValue}"); + text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); @@ -291,44 +291,36 @@ namespace MWClass { MWWorld::InventoryStore& invStore = MWWorld::Class::get(npc).getInventoryStore(npc); + if (ptr.getCellRef().mCharge == 0) + return std::make_pair(0, "#{sInventoryMessage1}"); + // slots that this item can be equipped in std::pair, bool> slots_ = MWWorld::Class::get(ptr).getEquipmentSlots(ptr); + if (slots_.first.empty()) + return std::make_pair(0, ""); + std::string npcRace = npc.get()->mBase->mRace; - for (std::vector::const_iterator slot=slots_.first.begin(); - slot!=slots_.first.end(); ++slot) + // Beast races cannot equip shoes / boots, or full helms (head part vs hair part) + const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().find(npcRace); + if(race->mData.mFlags & ESM::Race::Beast) { + std::vector parts = ptr.get()->mBase->mParts.mParts; - // Beast races cannot equip shoes / boots, or full helms (head part vs hair part) - const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().find(npcRace); - if(race->mData.mFlags & ESM::Race::Beast) + for(std::vector::iterator itr = parts.begin(); itr != parts.end(); ++itr) { - std::vector parts = ptr.get()->mBase->mParts.mParts; - - if(*slot == MWWorld::InventoryStore::Slot_Helmet) - { - for(std::vector::iterator itr = parts.begin(); itr != parts.end(); ++itr) - { - if((*itr).mPart == ESM::PRT_Head) - { - return std::make_pair(0, "#{sNotifyMessage13}"); - } - } - } - - if (*slot == MWWorld::InventoryStore::Slot_Boots) - { - for(std::vector::iterator itr = parts.begin(); itr != parts.end(); ++itr) - { - if((*itr).mPart == ESM::PRT_LFoot || (*itr).mPart == ESM::PRT_RFoot) - { - return std::make_pair(0, "#{sNotifyMessage14}"); - } - } - } + if((*itr).mPart == ESM::PRT_Head) + return std::make_pair(0, "#{sNotifyMessage13}"); + if((*itr).mPart == ESM::PRT_LFoot || (*itr).mPart == ESM::PRT_RFoot) + return std::make_pair(0, "#{sNotifyMessage14}"); } + } + for (std::vector::const_iterator slot=slots_.first.begin(); + slot!=slots_.first.end(); ++slot) + { + // If equipping a shield, check if there's a twohanded weapon conflicting with it if(*slot == MWWorld::InventoryStore::Slot_CarriedLeft) { MWWorld::ContainerStoreIterator weapon = invStore.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index b22cbc31fc..1da9209706 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(ref->mBase->mData.mValue, "#{sValue}"); + text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index 8941f36275..ffa96260df 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -191,7 +191,7 @@ namespace MWClass std::string text; text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); + text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); @@ -235,37 +235,26 @@ namespace MWClass // slots that this item can be equipped in std::pair, bool> slots_ = MWWorld::Class::get(ptr).getEquipmentSlots(ptr); + if (slots_.first.empty()) + return std::make_pair(0, ""); + std::string npcRace = npc.get()->mBase->mRace; - for (std::vector::const_iterator slot=slots_.first.begin(); - slot!=slots_.first.end(); ++slot) + // Beast races cannot equip shoes / boots, or full helms (head part vs hair part) + const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().find(npcRace); + if(race->mData.mFlags & ESM::Race::Beast) { + std::vector parts = ptr.get()->mBase->mParts.mParts; - // Beast races cannot equip shoes / boots, or full helms (head part vs hair part) - const ESM::Race* race = MWBase::Environment::get().getWorld()->getStore().get().find(npcRace); - if(race->mData.mFlags & ESM::Race::Beast) + for(std::vector::iterator itr = parts.begin(); itr != parts.end(); ++itr) { - std::vector parts = ptr.get()->mBase->mParts.mParts; - - if(*slot == MWWorld::InventoryStore::Slot_Helmet) - { - for(std::vector::iterator itr = parts.begin(); itr != parts.end(); ++itr) - { - if((*itr).mPart == ESM::PRT_Head) - return std::make_pair(0, "#{sNotifyMessage13}"); - } - } - - if (*slot == MWWorld::InventoryStore::Slot_Boots) - { - for(std::vector::iterator itr = parts.begin(); itr != parts.end(); ++itr) - { - if((*itr).mPart == ESM::PRT_LFoot || (*itr).mPart == ESM::PRT_RFoot) - return std::make_pair(0, "#{sNotifyMessage15}"); - } - } + if((*itr).mPart == ESM::PRT_Head) + return std::make_pair(0, "#{sNotifyMessage13}"); + if((*itr).mPart == ESM::PRT_LFoot || (*itr).mPart == ESM::PRT_RFoot) + return std::make_pair(0, "#{sNotifyMessage15}"); } } + return std::make_pair (1, ""); } diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 9834807821..98919d6f41 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -68,14 +68,14 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); // creature stats - data->mCreatureStats.getAttribute(0).set (ref->mBase->mData.mStrength); - data->mCreatureStats.getAttribute(1).set (ref->mBase->mData.mIntelligence); - data->mCreatureStats.getAttribute(2).set (ref->mBase->mData.mWillpower); - data->mCreatureStats.getAttribute(3).set (ref->mBase->mData.mAgility); - data->mCreatureStats.getAttribute(4).set (ref->mBase->mData.mSpeed); - data->mCreatureStats.getAttribute(5).set (ref->mBase->mData.mEndurance); - data->mCreatureStats.getAttribute(6).set (ref->mBase->mData.mPersonality); - data->mCreatureStats.getAttribute(7).set (ref->mBase->mData.mLuck); + data->mCreatureStats.setAttribute(ESM::Attribute::Strength, ref->mBase->mData.mStrength); + data->mCreatureStats.setAttribute(ESM::Attribute::Intelligence, ref->mBase->mData.mIntelligence); + data->mCreatureStats.setAttribute(ESM::Attribute::Willpower, ref->mBase->mData.mWillpower); + data->mCreatureStats.setAttribute(ESM::Attribute::Agility, ref->mBase->mData.mAgility); + data->mCreatureStats.setAttribute(ESM::Attribute::Speed, ref->mBase->mData.mSpeed); + data->mCreatureStats.setAttribute(ESM::Attribute::Endurance, ref->mBase->mData.mEndurance); + data->mCreatureStats.setAttribute(ESM::Attribute::Personality, ref->mBase->mData.mPersonality); + data->mCreatureStats.setAttribute(ESM::Attribute::Luck, ref->mBase->mData.mLuck); data->mCreatureStats.setHealth (ref->mBase->mData.mHealth); data->mCreatureStats.setMagicka (ref->mBase->mData.mMana); data->mCreatureStats.setFatigue (ref->mBase->mData.mFatigue); @@ -84,10 +84,10 @@ namespace MWClass data->mCreatureStats.getAiSequence().fill(ref->mBase->mAiPackage); - data->mCreatureStats.setAiSetting (0, ref->mBase->mAiData.mHello); - data->mCreatureStats.setAiSetting (1, ref->mBase->mAiData.mFight); - data->mCreatureStats.setAiSetting (2, ref->mBase->mAiData.mFlee); - data->mCreatureStats.setAiSetting (3, ref->mBase->mAiData.mAlarm); + data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Hello, ref->mBase->mAiData.mHello); + data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Fight, ref->mBase->mAiData.mFight); + data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Flee, ref->mBase->mAiData.mFlee); + data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Alarm, ref->mBase->mAiData.mAlarm); // spells for (std::vector::const_iterator iter (ref->mBase->mSpells.mList.begin()); @@ -199,7 +199,7 @@ namespace MWClass else { MWMechanics::DynamicStat fatigue(getCreatureStats(ptr).getFatigue()); - fatigue.setCurrent(fatigue.getCurrent() - damage); + fatigue.setCurrent(fatigue.getCurrent() - damage, true); getCreatureStats(ptr).setFatigue(fatigue); } } @@ -413,6 +413,14 @@ namespace MWClass return MWWorld::Ptr(&cell.mCreatures.insert(*ref), &cell); } + bool Creature::isFlying(const MWWorld::Ptr &ptr) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + + return ref->mBase->mFlags & ESM::Creature::Flies; + } + int Creature::getSndGenTypeFromName(const MWWorld::Ptr &ptr, const std::string &name) { if(name == "left") diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index 0d8694ff81..34e19ebdc7 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -99,6 +99,8 @@ namespace MWClass isActor() const { return true; } + + virtual bool isFlying (const MWWorld::Ptr &ptr) const; }; } diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index 06d9d5d235..4296d4e1b6 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -145,7 +145,7 @@ namespace MWClass std::string text; text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); + text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index a031d25563..6a6133cb92 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -183,7 +183,7 @@ namespace MWClass std::string text; text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); + text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index 73b47d6af9..e1dc5b2e14 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -89,7 +89,7 @@ namespace MWClass if (ptr.getCellRef().mCharge == -1) return ref->mBase->mData.mValue; else - return ref->mBase->mData.mValue * (ptr.getCellRef().mCharge / getItemMaxHealth(ptr)); + return ref->mBase->mData.mValue * (static_cast(ptr.getCellRef().mCharge) / getItemMaxHealth(ptr)); } void Lockpick::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(ref->mBase->mData.mValue, "#{sValue}"); + text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index 1a40c45554..d211891035 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -242,7 +242,11 @@ namespace MWClass item.get(); return !ref->mBase->mData.mIsKey && (npcServices & ESM::NPC::Misc) - && !Misc::StringUtils::ciEqual(item.getCellRef().mRefID, "gold_001"); + && !Misc::StringUtils::ciEqual(item.getCellRef().mRefID, "gold_001") + && !Misc::StringUtils::ciEqual(item.getCellRef().mRefID, "gold_005") + && !Misc::StringUtils::ciEqual(item.getCellRef().mRefID, "gold_010") + && !Misc::StringUtils::ciEqual(item.getCellRef().mRefID, "gold_025") + && !Misc::StringUtils::ciEqual(item.getCellRef().mRefID, "gold_100"); } float Miscellaneous::getWeight(const MWWorld::Ptr &ptr) const @@ -252,4 +256,11 @@ namespace MWClass return ref->mBase->mData.mWeight; } + bool Miscellaneous::isKey(const MWWorld::Ptr &ptr) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + return ref->mBase->mData.mIsKey; + } + } diff --git a/apps/openmw/mwclass/misc.hpp b/apps/openmw/mwclass/misc.hpp index 16a8e8c055..16e9ca10b0 100644 --- a/apps/openmw/mwclass/misc.hpp +++ b/apps/openmw/mwclass/misc.hpp @@ -57,6 +57,8 @@ namespace MWClass virtual float getWeight (const MWWorld::Ptr& ptr) const; virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; + + virtual bool isKey (const MWWorld::Ptr &ptr) const; }; } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 8ff8081bc7..9d3002dfdc 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -21,6 +21,7 @@ #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/movement.hpp" #include "../mwmechanics/spellcasting.hpp" +#include "../mwmechanics/disease.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/actiontalk.hpp" @@ -63,11 +64,10 @@ namespace bool male = (npc->mFlags & ESM::NPC::Female) == 0; int level = creatureStats.getLevel(); - for (int i=0; imData.mAttributeValues[i]; - creatureStats.getAttribute(i).setBase (male ? attribute.mMale : attribute.mFemale); + creatureStats.setAttribute(i, male ? attribute.mMale : attribute.mFemale); } // class bonus @@ -79,13 +79,13 @@ namespace int attribute = class_->mData.mAttribute[i]; if (attribute>=0 && attribute<8) { - creatureStats.getAttribute(attribute).setBase ( + creatureStats.setAttribute(attribute, creatureStats.getAttribute(attribute).getBase() + 10); } } // skill bonus - for (int attribute=0; attribute((level-1) * modifierSum+0.5), 100) ); } @@ -131,6 +131,89 @@ namespace creatureStats.setHealth(static_cast (0.5 * (strength + endurance)) + multiplier * (creatureStats.getLevel() - 1)); } + + /** + * @brief autoCalculateSkills + * + * Skills are calculated with following formulae ( http://www.uesp.net/wiki/Morrowind:NPCs#Skills ): + * + * Skills: (Level - 1) × (Majority Multiplier + Specialization Multiplier) + * + * The Majority Multiplier is 1.0 for a Major or Minor Skill, or 0.1 for a Miscellaneous Skill. + * + * The Specialization Multiplier is 0.5 for a Skill in the same Specialization as the class, + * zero for other Skills. + * + * and by adding class, race, specialization bonus. + */ + void autoCalculateSkills(const ESM::NPC* npc, MWMechanics::NpcStats& npcStats) + { + const ESM::Class *class_ = + MWBase::Environment::get().getWorld()->getStore().get().find(npc->mClass); + + unsigned int level = npcStats.getLevel(); + + const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get().find(npc->mRace); + + + for (int i = 0; i < 2; ++i) + { + int bonus = (i==0) ? 10 : 25; + + for (int i2 = 0; i2 < 5; ++i2) + { + int index = class_->mData.mSkills[i2][i]; + if (index >= 0 && index < ESM::Skill::Length) + { + npcStats.getSkill(index).setBase (npcStats.getSkill(index).getBase() + bonus); + } + } + } + + for (int skillIndex = 0; skillIndex < ESM::Skill::Length; ++skillIndex) + { + float majorMultiplier = 0.1f; + float specMultiplier = 0.0f; + + int raceBonus = 0; + int specBonus = 0; + + for (int raceSkillIndex = 0; raceSkillIndex < 7; ++raceSkillIndex) + { + if (race->mData.mBonus[raceSkillIndex].mSkill == skillIndex) + { + raceBonus = race->mData.mBonus[raceSkillIndex].mBonus; + break; + } + } + + for (int k = 0; k < 5; ++k) + { + // is this a minor or major skill? + if ((class_->mData.mSkills[k][0] == skillIndex) || (class_->mData.mSkills[k][1] == skillIndex)) + { + majorMultiplier = 1.0f; + break; + } + } + + // is this skill in the same Specialization as the class? + const ESM::Skill* skill = MWBase::Environment::get().getWorld()->getStore().get().find(skillIndex); + if (skill->mData.mSpecialization == class_->mData.mSpecialization) + { + specMultiplier = 0.5f; + specBonus = 5; + } + + npcStats.getSkill(skillIndex).setBase( + std::min( + npcStats.getSkill(skillIndex).getBase() + + 5 + + raceBonus + + specBonus + + static_cast((level-1) * (majorMultiplier + specMultiplier)), 100)); + } + } } namespace MWClass @@ -173,7 +256,7 @@ namespace MWClass { std::string faction = ref->mBase->mFaction; Misc::StringUtils::toLower(faction); - if(ref->mBase->mNpdt52.mGold != -10) + if(ref->mBase->mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) { data->mNpcStats.getFactionRanks()[faction] = (int)ref->mBase->mNpdt52.mRank; } @@ -185,21 +268,22 @@ namespace MWClass // creature stats int gold=0; - if(ref->mBase->mNpdt52.mGold != -10) + if(ref->mBase->mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) { gold = ref->mBase->mNpdt52.mGold; - for (int i=0; i<27; ++i) + for (unsigned int i=0; i< ESM::Skill::Length; ++i) data->mNpcStats.getSkill (i).setBase (ref->mBase->mNpdt52.mSkills[i]); - data->mNpcStats.getAttribute(0).set (ref->mBase->mNpdt52.mStrength); - data->mNpcStats.getAttribute(1).set (ref->mBase->mNpdt52.mIntelligence); - data->mNpcStats.getAttribute(2).set (ref->mBase->mNpdt52.mWillpower); - data->mNpcStats.getAttribute(3).set (ref->mBase->mNpdt52.mAgility); - data->mNpcStats.getAttribute(4).set (ref->mBase->mNpdt52.mSpeed); - data->mNpcStats.getAttribute(5).set (ref->mBase->mNpdt52.mEndurance); - data->mNpcStats.getAttribute(6).set (ref->mBase->mNpdt52.mPersonality); - data->mNpcStats.getAttribute(7).set (ref->mBase->mNpdt52.mLuck); + data->mNpcStats.setAttribute(ESM::Attribute::Strength, ref->mBase->mNpdt52.mStrength); + data->mNpcStats.setAttribute(ESM::Attribute::Intelligence, ref->mBase->mNpdt52.mIntelligence); + data->mNpcStats.setAttribute(ESM::Attribute::Willpower, ref->mBase->mNpdt52.mWillpower); + data->mNpcStats.setAttribute(ESM::Attribute::Agility, ref->mBase->mNpdt52.mAgility); + data->mNpcStats.setAttribute(ESM::Attribute::Speed, ref->mBase->mNpdt52.mSpeed); + data->mNpcStats.setAttribute(ESM::Attribute::Endurance, ref->mBase->mNpdt52.mEndurance); + data->mNpcStats.setAttribute(ESM::Attribute::Personality, ref->mBase->mNpdt52.mPersonality); + data->mNpcStats.setAttribute(ESM::Attribute::Luck, ref->mBase->mNpdt52.mLuck); + data->mNpcStats.setHealth (ref->mBase->mNpdt52.mHealth); data->mNpcStats.setMagicka (ref->mBase->mNpdt52.mMana); data->mNpcStats.setFatigue (ref->mBase->mNpdt52.mFatigue); @@ -220,14 +304,15 @@ namespace MWClass data->mNpcStats.setReputation(ref->mBase->mNpdt12.mReputation); autoCalculateAttributes(ref->mBase, data->mNpcStats); + autoCalculateSkills(ref->mBase, data->mNpcStats); } data->mNpcStats.getAiSequence().fill(ref->mBase->mAiPackage); - data->mNpcStats.setAiSetting (0, ref->mBase->mAiData.mHello); - data->mNpcStats.setAiSetting (1, ref->mBase->mAiData.mFight); - data->mNpcStats.setAiSetting (2, ref->mBase->mAiData.mFlee); - data->mNpcStats.setAiSetting (3, ref->mBase->mAiData.mAlarm); + data->mNpcStats.setAiSetting (MWMechanics::CreatureStats::AI_Hello, ref->mBase->mAiData.mHello); + data->mNpcStats.setAiSetting (MWMechanics::CreatureStats::AI_Fight, ref->mBase->mAiData.mFight); + data->mNpcStats.setAiSetting (MWMechanics::CreatureStats::AI_Flee, ref->mBase->mAiData.mFlee); + data->mNpcStats.setAiSetting (MWMechanics::CreatureStats::AI_Alarm, ref->mBase->mAiData.mAlarm); // spells for (std::vector::const_iterator iter (ref->mBase->mSpells.mList.begin()); @@ -366,8 +451,8 @@ namespace MWClass (stats.getAttribute(ESM::Attribute::Agility).getModified() / 5.0f) + (stats.getAttribute(ESM::Attribute::Luck).getModified() / 10.0f); hitchance *= stats.getFatigueTerm(); - hitchance += mageffects.get(MWMechanics::EffectKey(ESM::MagicEffect::FortifyAttack)).mMagnitude - - mageffects.get(MWMechanics::EffectKey(ESM::MagicEffect::Blind)).mMagnitude; + hitchance += mageffects.get(ESM::MagicEffect::FortifyAttack).mMagnitude - + mageffects.get(ESM::MagicEffect::Blind).mMagnitude; hitchance -= otherstats.getEvasion(); if((::rand()/(RAND_MAX+1.0)) > hitchance/100.0f) @@ -409,6 +494,11 @@ namespace MWClass if (!MWBase::Environment::get().getWorld()->getGodModeState()) weapon.getCellRef().mCharge -= std::min(std::max(1, (int)(damage * gmst.find("fWeaponDamageMult")->getFloat())), weapon.getCellRef().mCharge); + + // Weapon broken? unequip it + if (weapon.getCellRef().mCharge == 0) + weapon = *inv.unequipItem(weapon, ptr); + } healthdmg = true; } @@ -428,7 +518,8 @@ namespace MWClass MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f); } - healthdmg = (otherstats.getFatigue().getCurrent() < 1.0f); + healthdmg = (otherstats.getFatigue().getCurrent() < 1.0f) + || (otherstats.getMagicEffects().get(ESM::MagicEffect::Paralyze).mMagnitude > 0); if(stats.isWerewolf()) { healthdmg = true; @@ -514,12 +605,15 @@ namespace MWClass ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1); } + if (!attacker.isEmpty()) + MWMechanics::diseaseContact(ptr, attacker); + if(damage > 0.0f) { // 'ptr' is losing health. Play a 'hit' voiced dialog entry if not already saying // something, alert the character controller, scripts, etc. - MWBase::Environment::get().getDialogueManager()->say(ptr, "hit"); + MWBase::Environment::get().getDialogueManager()->say(ptr, "thief"); if(object.isEmpty()) { @@ -560,6 +654,11 @@ namespace MWClass armorref.mCharge = armor.get()->mBase->mData.mHealth; armorref.mCharge -= std::min(std::max(1, (int)damagediff), armorref.mCharge); + + // Armor broken? unequip it + if (armorref.mCharge == 0) + inv.unequipItem(armor, ptr); + switch(get(armor).getEquipmentSkill(armor)) { case ESM::Skill::LightArmor: @@ -586,7 +685,7 @@ namespace MWClass else { MWMechanics::DynamicStat fatigue(getCreatureStats(ptr).getFatigue()); - fatigue.setCurrent(fatigue.getCurrent() - damage); + fatigue.setCurrent(fatigue.getCurrent() - damage, true); getCreatureStats(ptr).setFatigue(fatigue); } } @@ -628,6 +727,8 @@ namespace MWClass return boost::shared_ptr(new MWWorld::ActionOpen(ptr, true)); if(get(actor).getStance(actor, MWWorld::Class::Sneak)) return boost::shared_ptr(new MWWorld::ActionOpen(ptr)); // stealing + if(get(ptr).getCreatureStats(ptr).isHostile()) + return boost::shared_ptr(new MWWorld::FailedAction("#{sActorInCombat}")); return boost::shared_ptr(new MWWorld::ActionTalk(ptr)); } @@ -752,10 +853,11 @@ namespace MWClass float moveSpeed; if(normalizedEncumbrance >= 1.0f) moveSpeed = 0.0f; - else if(mageffects.get(MWMechanics::EffectKey(10/*levitate*/)).mMagnitude > 0) + else if(mageffects.get(ESM::MagicEffect::Levitate).mMagnitude > 0 && + world->isLevitationEnabled()) { float flySpeed = 0.01f*(npcdata->mNpcStats.getAttribute(ESM::Attribute::Speed).getModified() + - mageffects.get(MWMechanics::EffectKey(10/*levitate*/)).mMagnitude); + mageffects.get(ESM::MagicEffect::Levitate).mMagnitude); flySpeed = fMinFlySpeed->getFloat() + flySpeed*(fMaxFlySpeed->getFloat() - fMinFlySpeed->getFloat()); flySpeed *= 1.0f - fEncumberedMoveEffect->getFloat() * normalizedEncumbrance; flySpeed = std::max(0.0f, flySpeed); @@ -766,7 +868,7 @@ namespace MWClass float swimSpeed = walkSpeed; if(Npc::getStance(ptr, Run, false)) swimSpeed = runSpeed; - swimSpeed *= 1.0f + 0.01f * mageffects.get(MWMechanics::EffectKey(1/*swift swim*/)).mMagnitude; + 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(); moveSpeed = swimSpeed; @@ -800,7 +902,7 @@ namespace MWClass float x = fJumpAcrobaticsBase->getFloat() + std::pow(a / 15.0f, fJumpAcroMultiplier->getFloat()); x += 3.0f * b * fJumpAcroMultiplier->getFloat(); - x += mageffects.get(MWMechanics::EffectKey(ESM::MagicEffect::Jump)).mMagnitude * 64; + x += mageffects.get(ESM::MagicEffect::Jump).mMagnitude * 64; x *= encumbranceTerm; if(Npc::getStance(ptr, Run, false)) @@ -823,7 +925,7 @@ namespace MWClass { const float acrobaticsSkill = MWWorld::Class::get(ptr).getNpcStats (ptr).getSkill(ESM::Skill::Acrobatics).getModified(); const CustomData *npcdata = static_cast(ptr.getRefData().getCustomData()); - const float jumpSpellBonus = npcdata->mNpcStats.getMagicEffects().get(MWMechanics::EffectKey(ESM::MagicEffect::Jump)).mMagnitude; + 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(); @@ -928,8 +1030,8 @@ namespace MWClass if(!stats.isWerewolf()) { weight = getContainerStore(ptr).getWeight(); - weight -= stats.getMagicEffects().get(MWMechanics::EffectKey(ESM::MagicEffect::Feather)).mMagnitude; - weight += stats.getMagicEffects().get(MWMechanics::EffectKey(ESM::MagicEffect::Burden)).mMagnitude; + weight -= stats.getMagicEffects().get(ESM::MagicEffect::Feather).mMagnitude; + weight += stats.getMagicEffects().get(ESM::MagicEffect::Burden).mMagnitude; if(weight < 0.0f) weight = 0.0f; } diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 883473eb33..e276c58aa5 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -128,7 +128,7 @@ namespace MWClass std::string text; text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); + text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); info.effects = MWGui::Widgets::MWEffectList::effectListFromESM(&ref->mBase->mEffects); diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index 845c2a0d00..b54464acdc 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -88,7 +88,7 @@ namespace MWClass if (ptr.getCellRef().mCharge == -1) return ref->mBase->mData.mValue; else - return ref->mBase->mData.mValue * (ptr.getCellRef().mCharge / getItemMaxHealth(ptr)); + return ref->mBase->mData.mValue * (static_cast(ptr.getCellRef().mCharge) / getItemMaxHealth(ptr)); } void Probe::registerSelf() @@ -140,7 +140,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(ref->mBase->mData.mValue, "#{sValue}"); + text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index dbfa9f0f62..ce2b4ff10c 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -79,7 +79,7 @@ namespace MWClass if (ptr.getCellRef().mCharge == -1) return ref->mBase->mData.mValue; else - return ref->mBase->mData.mValue * (ptr.getCellRef().mCharge / getItemMaxHealth(ptr)); + return ref->mBase->mData.mValue * (static_cast(ptr.getCellRef().mCharge) / getItemMaxHealth(ptr)); } void Repair::registerSelf() @@ -144,7 +144,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(ref->mBase->mData.mValue, "#{sValue}"); + text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index b1bf2b0b7f..5e93e0d81b 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -157,7 +157,7 @@ namespace MWClass if (ptr.getCellRef().mCharge == -1) return ref->mBase->mData.mValue; else - return ref->mBase->mData.mValue * (ptr.getCellRef().mCharge / getItemMaxHealth(ptr)); + return ref->mBase->mData.mValue * (static_cast(ptr.getCellRef().mCharge) / getItemMaxHealth(ptr)); } void Weapon::registerSelf() @@ -346,7 +346,7 @@ namespace MWClass } text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); + text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); info.enchant = ref->mBase->mEnchant; @@ -388,28 +388,26 @@ namespace MWClass std::pair Weapon::canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const { + if (ptr.getCellRef().mCharge == 0) + return std::make_pair(0, "#{sInventoryMessage1}"); + std::pair, bool> slots_ = MWWorld::Class::get(ptr).getEquipmentSlots(ptr); - // equip the item in the first free slot - for (std::vector::const_iterator slot=slots_.first.begin(); - slot!=slots_.first.end(); ++slot) + if (slots_.first.empty()) + return std::make_pair (0, ""); + + if(ptr.get()->mBase->mData.mType == ESM::Weapon::LongBladeTwoHand || + ptr.get()->mBase->mData.mType == ESM::Weapon::BluntTwoClose || + ptr.get()->mBase->mData.mType == ESM::Weapon::BluntTwoWide || + ptr.get()->mBase->mData.mType == ESM::Weapon::SpearTwoWide || + ptr.get()->mBase->mData.mType == ESM::Weapon::AxeTwoHand || + ptr.get()->mBase->mData.mType == ESM::Weapon::MarksmanBow || + ptr.get()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow) { - if(*slot == MWWorld::InventoryStore::Slot_CarriedRight) - { - if(ptr.get()->mBase->mData.mType == ESM::Weapon::LongBladeTwoHand || - ptr.get()->mBase->mData.mType == ESM::Weapon::BluntTwoClose || - ptr.get()->mBase->mData.mType == ESM::Weapon::BluntTwoWide || - ptr.get()->mBase->mData.mType == ESM::Weapon::SpearTwoWide || - ptr.get()->mBase->mData.mType == ESM::Weapon::AxeTwoHand || - ptr.get()->mBase->mData.mType == ESM::Weapon::MarksmanBow || - ptr.get()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow) - { - return std::make_pair (2, ""); - } - } - return std::make_pair(1, ""); + return std::make_pair (2, ""); } - return std::make_pair (0, ""); + + return std::make_pair(1, ""); } boost::shared_ptr Weapon::use (const MWWorld::Ptr& ptr) const diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 52493bf765..b979865628 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -251,7 +251,7 @@ namespace MWDialogue } } - void DialogueManager::executeTopic (const std::string& topic, bool randomResponse) + void DialogueManager::executeTopic (const std::string& topic) { Filter filter (mActor, mChoice, mTalkedTo); @@ -262,12 +262,9 @@ namespace MWDialogue MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow(); - std::vector infos = filter.list (dialogue, true, true); - - if (!infos.empty()) + const ESM::DialInfo* info = filter.search(dialogue, true); + if (info) { - const ESM::DialInfo* info = infos[randomResponse ? std::rand() % infos.size() : 0]; - parseText (info->mResponse); std::string title; @@ -413,10 +410,6 @@ namespace MWDialogue void DialogueManager::goodbyeSelected() { - // Do not close the dialogue window if the player has to answer a question - if (mIsInChoice) - return; - MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Dialogue); // Apply disposition change to NPC's base disposition @@ -474,6 +467,8 @@ namespace MWDialogue void DialogueManager::goodbye() { + mIsInChoice = true; + MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow(); win->goodbye(); @@ -511,7 +506,7 @@ namespace MWDialogue text = "Bribe"; } - executeTopic (text + (success ? " Success" : " Fail"), true); + executeTopic (text + (success ? " Success" : " Fail")); } int DialogueManager::getTemporaryDispositionChange() const diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp index 1b7abed45a..c9aad10220 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp @@ -44,7 +44,7 @@ namespace MWDialogue bool compile (const std::string& cmd,std::vector& code); void executeScript (const std::string& script); - void executeTopic (const std::string& topic, bool randomResponse=false); + void executeTopic (const std::string& topic); public: diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp index c6089c9e49..e994b769e4 100644 --- a/apps/openmw/mwdialogue/filter.cpp +++ b/apps/openmw/mwdialogue/filter.cpp @@ -5,6 +5,7 @@ #include "../mwbase/world.hpp" #include "../mwbase/journal.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/dialoguemanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" @@ -133,7 +134,8 @@ bool MWDialogue::Filter::testDisposition (const ESM::DialInfo& info, bool invert if (isCreature) return true; - int actorDisposition = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mActor); + int actorDisposition = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mActor) + + MWBase::Environment::get().getDialogueManager()->getTemporaryDispositionChange(); // For service refusal, the disposition check is inverted. However, a value of 0 still means "always succeed". return invert ? (info.mData.mDisposition == 0 || actorDisposition < info.mData.mDisposition) : (actorDisposition >= info.mData.mDisposition); @@ -277,7 +279,8 @@ int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) con case SelectWrapper::Function_AiSetting: - return MWWorld::Class::get (mActor).getCreatureStats (mActor).getAiSetting (select.getArgument()); + return MWWorld::Class::get (mActor).getCreatureStats (mActor).getAiSetting ( + (MWMechanics::CreatureStats::AiSetting)select.getArgument()).getModified(); case SelectWrapper::Function_PcAttribute: @@ -512,7 +515,8 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co case SelectWrapper::Function_PcVampire: - return MWWorld::Class::get (player).getNpcStats (player).isVampire(); + return MWWorld::Class::get (player).getCreatureStats(player).getMagicEffects(). + get(ESM::MagicEffect::Vampirism).mMagnitude > 0; case SelectWrapper::Function_TalkedToPc: diff --git a/apps/openmw/mwgui/bookwindow.cpp b/apps/openmw/mwgui/bookwindow.cpp index efe089689b..c28ef96ef1 100644 --- a/apps/openmw/mwgui/bookwindow.cpp +++ b/apps/openmw/mwgui/bookwindow.cpp @@ -44,6 +44,11 @@ namespace MWGui adjustButton(mNextPageButton); adjustButton(mPrevPageButton); + mLeftPage->setNeedMouseFocus(true); + mLeftPage->eventMouseWheel += MyGUI::newDelegate(this, &BookWindow::onMouseWheel); + mRightPage->setNeedMouseFocus(true); + mRightPage->eventMouseWheel += MyGUI::newDelegate(this, &BookWindow::onMouseWheel); + if (mNextPageButton->getSize().width == 64) { // english button has a 7 pixel wide strip of garbage on its right edge @@ -54,6 +59,14 @@ namespace MWGui center(); } + void BookWindow::onMouseWheel(MyGUI::Widget *_sender, int _rel) + { + if (_rel < 0) + nextPage(); + else + prevPage(); + } + void BookWindow::clearPages() { for (std::vector::iterator it=mPages.begin(); @@ -89,6 +102,7 @@ namespace MWGui parent = mRightPage; MyGUI::Widget* pageWidget = parent->createWidgetReal("", MyGUI::FloatCoord(0.0,0.0,1.0,1.0), MyGUI::Align::Default, "BookPage" + boost::lexical_cast(i)); + pageWidget->setNeedMouseFocus(false); parser.parsePage(*it, pageWidget, mLeftPage->getSize().width); mPages.push_back(pageWidget); ++i; diff --git a/apps/openmw/mwgui/bookwindow.hpp b/apps/openmw/mwgui/bookwindow.hpp index ef87dd9c4c..f8821ab50b 100644 --- a/apps/openmw/mwgui/bookwindow.hpp +++ b/apps/openmw/mwgui/bookwindow.hpp @@ -25,6 +25,7 @@ namespace MWGui void onPrevPageButtonClicked (MyGUI::Widget* sender); void onCloseButtonClicked (MyGUI::Widget* sender); void onTakeButtonClicked (MyGUI::Widget* sender); + void onMouseWheel(MyGUI::Widget* _sender, int _rel); void updatePages(); void clearPages(); diff --git a/apps/openmw/mwgui/charactercreation.cpp b/apps/openmw/mwgui/charactercreation.cpp index b829f219d7..04507cfc61 100644 --- a/apps/openmw/mwgui/charactercreation.cpp +++ b/apps/openmw/mwgui/charactercreation.cpp @@ -75,7 +75,7 @@ namespace MWGui mGenerateClassSpecializations[2] = 0; } - void CharacterCreation::setValue (const std::string& id, const MWMechanics::Stat& value) + void CharacterCreation::setValue (const std::string& id, const MWMechanics::AttributeValue& value) { if (mReviewDialog) { @@ -113,7 +113,7 @@ namespace MWGui } } - void CharacterCreation::setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::Stat& value) + void CharacterCreation::setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value) { if (mReviewDialog) mReviewDialog->setSkillValue(parSkill, value); @@ -229,8 +229,8 @@ namespace MWGui } { - std::map > attributes = MWBase::Environment::get().getWindowManager()->getPlayerAttributeValues(); - for (std::map >::iterator it = attributes.begin(); + std::map attributes = MWBase::Environment::get().getWindowManager()->getPlayerAttributeValues(); + for (std::map::iterator it = attributes.begin(); it != attributes.end(); ++it) { mReviewDialog->setAttribute(static_cast (it->first), it->second); @@ -238,8 +238,8 @@ namespace MWGui } { - std::map > skills = MWBase::Environment::get().getWindowManager()->getPlayerSkillValues(); - for (std::map >::iterator it = skills.begin(); + std::map skills = MWBase::Environment::get().getWindowManager()->getPlayerSkillValues(); + for (std::map::iterator it = skills.begin(); it != skills.end(); ++it) { mReviewDialog->setSkillValue(static_cast (it->first), it->second); diff --git a/apps/openmw/mwgui/charactercreation.hpp b/apps/openmw/mwgui/charactercreation.hpp index b80aaae41c..924f40c282 100644 --- a/apps/openmw/mwgui/charactercreation.hpp +++ b/apps/openmw/mwgui/charactercreation.hpp @@ -31,9 +31,9 @@ namespace MWGui //Show a dialog void spawnDialog(const char id); - void setValue (const std::string& id, const MWMechanics::Stat& value); + void setValue (const std::string& id, const MWMechanics::AttributeValue& value); void setValue (const std::string& id, const MWMechanics::DynamicStat& value); - void setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::Stat& value); + void setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value); void configureSkills (const SkillList& major, const SkillList& minor); void doRenderUpdate(); diff --git a/apps/openmw/mwgui/class.cpp b/apps/openmw/mwgui/class.cpp index c33e54d6b4..6c46f21763 100644 --- a/apps/openmw/mwgui/class.cpp +++ b/apps/openmw/mwgui/class.cpp @@ -466,7 +466,7 @@ namespace MWGui std::string CreateClassDialog::getName() const { - return mEditName->getOnlyText(); + return mEditName->getCaption(); } std::string CreateClassDialog::getDescription() const diff --git a/apps/openmw/mwgui/class.hpp b/apps/openmw/mwgui/class.hpp index 15fc89658f..e74370a4cd 100644 --- a/apps/openmw/mwgui/class.hpp +++ b/apps/openmw/mwgui/class.hpp @@ -228,8 +228,8 @@ namespace MWGui DescriptionDialog(); ~DescriptionDialog(); - std::string getTextInput() const { return mTextEdit ? mTextEdit->getOnlyText() : ""; } - void setTextInput(const std::string &text) { if (mTextEdit) mTextEdit->setOnlyText(text); } + std::string getTextInput() const { return mTextEdit->getCaption(); } + void setTextInput(const std::string &text) { mTextEdit->setCaption(text); } protected: void onOkClicked(MyGUI::Widget* _sender); diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp index 96bc204c15..f3805b255a 100644 --- a/apps/openmw/mwgui/console.cpp +++ b/apps/openmw/mwgui/console.cpp @@ -119,8 +119,6 @@ namespace MWGui // Set up the log window mHistory->setOverflowToTheLeft(true); - mHistory->setEditStatic(true); - mHistory->setVisibleVScroll(true); // compiler Compiler::registerExtensions (mExtensions, mConsoleOnlyScripts); @@ -215,7 +213,7 @@ namespace MWGui { std::vector matches; listNames(); - mCommandLine->setCaption(complete( mCommandLine->getCaption(), matches )); + mCommandLine->setCaption(complete( mCommandLine->getOnlyText(), matches )); #if 0 int i = 0; for(std::vector::iterator it=matches.begin(); it < matches.end(); ++it,++i ) @@ -234,7 +232,7 @@ namespace MWGui { // If the user was editing a string, store it for later if(mCurrent == mCommandHistory.end()) - mEditString = mCommandLine->getCaption(); + mEditString = mCommandLine->getOnlyText(); if(mCurrent != mCommandHistory.begin()) { @@ -259,7 +257,7 @@ namespace MWGui void Console::acceptCommand(MyGUI::EditBox* _sender) { - const std::string &cm = mCommandLine->getCaption(); + const std::string &cm = mCommandLine->getOnlyText(); if(cm.empty()) return; // Add the command to the history, and set the current pointer to @@ -406,13 +404,14 @@ namespace MWGui setTitle("#{sConsoleTitle} (" + object.getCellRef().mRefID + ")"); mPtr = object; } + // User clicked on an object. Restore focus to the console command line. + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCommandLine); } else { setTitle("#{sConsoleTitle}"); mPtr = MWWorld::Ptr(); } - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCommandLine); } void Console::onReferenceUnavailable() diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index 19ed4dbc00..b7c6e3367d 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -61,8 +61,9 @@ namespace MWGui mDraggedWidget = baseWidget; MyGUI::ImageBox* image = baseWidget->createWidget("ImageBox", MyGUI::IntCoord(5, 5, 32, 32), MyGUI::Align::Default); - int pos = path.rfind("."); - path.erase(pos); + size_t pos = path.rfind("."); + if (pos != std::string::npos) + path.erase(pos); path.append(".dds"); image->setImageTexture(path); image->setNeedMouseFocus(false); diff --git a/apps/openmw/mwgui/containeritemmodel.cpp b/apps/openmw/mwgui/containeritemmodel.cpp index 6b0fbd8903..bcb8440bfb 100644 --- a/apps/openmw/mwgui/containeritemmodel.cpp +++ b/apps/openmw/mwgui/containeritemmodel.cpp @@ -76,10 +76,7 @@ void ContainerItemModel::copyItem (const ItemStack& item, size_t count) const MWWorld::Ptr& source = mItemSources[mItemSources.size()-1]; if (item.mBase.getContainerStore() == &source.getClass().getContainerStore(source)) throw std::runtime_error("Item to copy needs to be from a different container!"); - int origCount = item.mBase.getRefData().getCount(); - item.mBase.getRefData().setCount(count); - source.getClass().getContainerStore(source).add(item.mBase, source); - item.mBase.getRefData().setCount(origCount); + source.getClass().getContainerStore(source).add(item.mBase, count, source); } void ContainerItemModel::removeItem (const ItemStack& item, size_t count) diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index 71995f97fd..914302d84e 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -302,6 +302,8 @@ namespace MWGui void DialogueWindow::onByeClicked(MyGUI::Widget* _sender) { + if (!mEnabled || MWBase::Environment::get().getDialogueManager()->isInChoice()) + return; MWBase::Environment::get().getDialogueManager()->goodbyeSelected(); } diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index f0843834d5..8ef5e59d08 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -55,6 +55,8 @@ namespace MWGui , mWorldMouseOver(false) , mEnemyHealthTimer(0) , mIsDrowning(false) + , mWeaponSpellTimer(0.f) + , mDrowningFlashTheta(0.f) { setCoord(0,0, width, height); @@ -174,38 +176,32 @@ namespace MWGui void HUD::setValue(const std::string& id, const MWMechanics::DynamicStat& value) { - static const char *ids[] = - { - "HBar", "MBar", "FBar", 0 - }; + int current = std::max(0, static_cast(value.getCurrent())); + int modified = static_cast(value.getModified()); - for (int i=0; ids[i]; ++i) - if (ids[i]==id) - { - MyGUI::Widget* w; - std::string valStr = boost::lexical_cast(value.getCurrent()) + "/" + boost::lexical_cast(value.getModified()); - switch (i) - { - case 0: - mHealth->setProgressRange (value.getModified()); - mHealth->setProgressPosition (value.getCurrent()); - getWidget(w, "HealthFrame"); - w->setUserString("Caption_HealthDescription", "#{sHealthDesc}\n" + valStr); - break; - case 1: - mMagicka->setProgressRange (value.getModified()); - mMagicka->setProgressPosition (value.getCurrent()); - getWidget(w, "MagickaFrame"); - w->setUserString("Caption_HealthDescription", "#{sIntDesc}\n" + valStr); - break; - case 2: - mStamina->setProgressRange (value.getModified()); - mStamina->setProgressPosition (value.getCurrent()); - getWidget(w, "FatigueFrame"); - w->setUserString("Caption_HealthDescription", "#{sFatDesc}\n" + valStr); - break; - } - } + MyGUI::Widget* w; + std::string valStr = boost::lexical_cast(current) + "/" + boost::lexical_cast(modified); + if (id == "HBar") + { + mHealth->setProgressRange(modified); + mHealth->setProgressPosition(current); + getWidget(w, "HealthFrame"); + w->setUserString("Caption_HealthDescription", "#{sHealthDesc}\n" + valStr); + } + else if (id == "MBar") + { + mMagicka->setProgressRange (modified); + mMagicka->setProgressPosition (current); + getWidget(w, "MagickaFrame"); + w->setUserString("Caption_HealthDescription", "#{sIntDesc}\n" + valStr); + } + else if (id == "FBar") + { + mStamina->setProgressRange (modified); + mStamina->setProgressPosition (current); + getWidget(w, "FatigueFrame"); + w->setUserString("Caption_HealthDescription", "#{sFatDesc}\n" + valStr); + } } void HUD::setDrowningTimeLeft(float time) diff --git a/apps/openmw/mwgui/inventoryitemmodel.cpp b/apps/openmw/mwgui/inventoryitemmodel.cpp index 672ea9c16f..d26feba88a 100644 --- a/apps/openmw/mwgui/inventoryitemmodel.cpp +++ b/apps/openmw/mwgui/inventoryitemmodel.cpp @@ -42,10 +42,7 @@ void InventoryItemModel::copyItem (const ItemStack& item, size_t count) { if (item.mBase.getContainerStore() == &mActor.getClass().getContainerStore(mActor)) throw std::runtime_error("Item to copy needs to be from a different container!"); - int origCount = item.mBase.getRefData().getCount(); - item.mBase.getRefData().setCount(count); - mActor.getClass().getContainerStore(mActor).add(item.mBase, mActor); - item.mBase.getRefData().setCount(origCount); + mActor.getClass().getContainerStore(mActor).add(item.mBase, count, mActor); } @@ -72,7 +69,7 @@ void InventoryItemModel::update() // NOTE: Don't show WerewolfRobe objects in the inventory, or allow them to be taken. // Vanilla likely uses a hack like this since there's no other way to prevent it from // being shown or taken. - if(item.getCellRef().mRefID == "WerewolfRobe") + if(item.getCellRef().mRefID == "werewolfrobe") continue; ItemStack newItem (item, this, item.getRefData().getCount()); diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 56c474c89a..21da53c6df 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -120,10 +120,13 @@ namespace MWGui Settings::Manager::getFloat(setting + " y", "Windows") * viewSize.height); MyGUI::IntSize size (Settings::Manager::getFloat(setting + " w", "Windows") * viewSize.width, Settings::Manager::getFloat(setting + " h", "Windows") * viewSize.height); + + if (size.width != mMainWidget->getWidth() || size.height != mMainWidget->getHeight()) + mPreviewDirty = true; + mMainWidget->setPosition(pos); mMainWidget->setSize(size); adjustPanes(); - mPreviewDirty = true; } TradeItemModel* InventoryWindow::getTradeModel() @@ -160,6 +163,14 @@ namespace MWGui MWWorld::Ptr object = item.mBase; int count = item.mCount; + // Bound items may not be moved + if (item.mBase.getCellRef().mRefID.size() > 6 + && item.mBase.getCellRef().mRefID.substr(0,6) == "bound_") + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sBarterDialog12}"); + return; + } + if (item.mType == ItemStack::Type_Equipped) { MWWorld::InventoryStore& invStore = MWWorld::Class::get(mPtr).getInventoryStore(mPtr); @@ -353,10 +364,7 @@ namespace MWGui MWWorld::ContainerStore& invStore = MWWorld::Class::get(mPtr).getContainerStore(mPtr); MWWorld::ContainerStoreIterator it = invStore.begin(); - int origCount = ptr.getRefData().getCount(); - ptr.getRefData().setCount(mDragAndDrop->mDraggedCount); - it = invStore.add(ptr, mPtr); - ptr.getRefData().setCount(origCount); + it = invStore.add(ptr, mDragAndDrop->mDraggedCount, mPtr); mDragAndDrop->mSourceModel->removeItem(mDragAndDrop->mItem, mDragAndDrop->mDraggedCount); ptr = *it; @@ -414,7 +422,7 @@ namespace MWGui // NOTE: Don't allow users to select WerewolfRobe objects in the inventory. Vanilla // likely uses a hack like this since there's no other way to prevent it from being // taken. - if(item.getCellRef().mRefID == "WerewolfRobe") + if(item.getCellRef().mRefID == "werewolfrobe") return MWWorld::Ptr(); return item; } @@ -510,7 +518,7 @@ namespace MWGui // add to player inventory // can't use ActionTake here because we need an MWWorld::Ptr to the newly inserted object MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); - MWWorld::Ptr newObject = *MWWorld::Class::get (player).getContainerStore (player).add (object, player); + MWWorld::Ptr newObject = *player.getClass().getContainerStore (player).add (object, object.getRefData().getCount(), player); // remove from world MWBase::Environment::get().getWorld()->deleteObject (object); diff --git a/apps/openmw/mwgui/levelupdialog.cpp b/apps/openmw/mwgui/levelupdialog.cpp index 7c0191d495..e3d8f5dd7b 100644 --- a/apps/openmw/mwgui/levelupdialog.cpp +++ b/apps/openmw/mwgui/levelupdialog.cpp @@ -166,11 +166,12 @@ namespace MWGui // increase attributes for (int i=0; i<3; ++i) { - MWMechanics::Stat& attribute = creatureStats.getAttribute(mSpentAttributes[i]); + MWMechanics::AttributeValue attribute = creatureStats.getAttribute(mSpentAttributes[i]); attribute.setBase (attribute.getBase () + pcStats.getLevelupAttributeMultiplier (mSpentAttributes[i])); if (attribute.getBase() >= 100) attribute.setBase(100); + creatureStats.setAttribute(mSpentAttributes[i], attribute); } creatureStats.levelUp(); diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 4bd383c2f3..868b582096 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -1,8 +1,6 @@ #include "loadingscreen.hpp" #include -#include -#include #include @@ -199,28 +197,7 @@ namespace MWGui MWBase::Environment::get().getInputManager()->update(0, true); - Ogre::CompositorChain* chain = Ogre::CompositorManager::getSingleton().getCompositorChain(mWindow->getViewport(0)); - - bool hasCompositor = chain->getCompositor ("gbufferFinalizer"); - - - if (!hasCompositor) - { - mWindow->getViewport(0)->setClearEveryFrame(false); - } - else - { - if (!mFirstLoad) - { - mBackgroundMaterial->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureName(chain->getCompositor ("gbufferFinalizer")->getTextureInstance ("no_mrt_output", 0)->getName()); - mRectangle->setVisible(true); - } - - for (unsigned int i = 0; igetNumCompositors(); ++i) - { - Ogre::CompositorManager::getSingleton().setCompositorEnabled(mWindow->getViewport(0), chain->getCompositor(i)->getCompositor()->getName(), false); - } - } + mWindow->getViewport(0)->setClearEveryFrame(false); // First, swap buffers from last draw, then, queue an update of the // window contents, but don't swap buffers (which would have @@ -231,15 +208,8 @@ namespace MWGui mWindow->update(false); - if (!hasCompositor) - mWindow->getViewport(0)->setClearEveryFrame(true); - else - { - for (unsigned int i = 0; igetNumCompositors(); ++i) - { - Ogre::CompositorManager::getSingleton().setCompositorEnabled(mWindow->getViewport(0), chain->getCompositor(i)->getCompositor()->getName(), true); - } - } + mWindow->getViewport(0)->setClearEveryFrame(true); + mRectangle->setVisible(false); diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index 93e1d11a3d..2f34df2360 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -103,27 +103,80 @@ namespace MWGui void LocalMapBase::onMarkerFocused (MyGUI::Widget* w1, MyGUI::Widget* w2) { + // Workaround to not make the marker visible if it's under fog of war applyFogOfWar (); } void LocalMapBase::onMarkerUnfocused (MyGUI::Widget* w1, MyGUI::Widget* w2) { + // Workaround to not make the marker visible if it's under fog of war applyFogOfWar (); } + MyGUI::IntPoint LocalMapBase::getMarkerPosition(float worldX, float worldY, MarkerPosition& markerPos) + { + MyGUI::IntPoint widgetPos; + // normalized cell coordinates + float nX,nY; + + markerPos.interior = mInterior; + + if (!mInterior) + { + int cellX, cellY; + MWBase::Environment::get().getWorld()->positionToIndex(worldX, worldY, cellX, cellY); + const int cellSize = 8192; + nX = (worldX - cellSize * cellX) / cellSize; + // Image space is -Y up, cells are Y up + nY = 1 - (worldY - cellSize * cellY) / cellSize; + + float cellDx = cellX - mCurX; + float cellDy = cellY - mCurY; + + markerPos.cellX = cellX; + markerPos.cellY = cellY; + + widgetPos = MyGUI::IntPoint(nX * 512 + (1+cellDx) * 512, + nY * 512 - (cellDy-1) * 512); + } + else + { + int cellX, cellY; + Ogre::Vector2 worldPos (worldX, worldY); + MWBase::Environment::get().getWorld ()->getInteriorMapPosition (worldPos, nX, nY, cellX, cellY); + + markerPos.cellX = cellX; + markerPos.cellY = cellY; + + widgetPos = MyGUI::IntPoint(nX * 512 + (1+cellX-mCurX) * 512, + nY * 512 + (1+cellY-mCurY) * 512); + } + + markerPos.nX = nX; + markerPos.nY = nY; + return widgetPos; + } + void LocalMapBase::setActiveCell(const int x, const int y, bool interior) { - if (x==mCurX && y==mCurY && mInterior==interior && !mChanged) return; // don't do anything if we're still in the same cell + if (x==mCurX && y==mCurY && mInterior==interior && !mChanged) + return; // don't do anything if we're still in the same cell + + mCurX = x; + mCurY = y; + mInterior = interior; + mChanged = false; // clear all previous markers for (unsigned int i=0; i< mLocalMap->getChildCount(); ++i) { - if (mLocalMap->getChildAt(i)->getName ().substr (0, 6) == "Marker") + if (mLocalMap->getChildAt(i)->getName ().substr (0, 4) == "Door") { MyGUI::Gui::getInstance ().destroyWidget (mLocalMap->getChildAt(i)); } } + // Update the map textures for (int mx=0; mx<3; ++mx) { for (int my=0; my<3; ++my) @@ -138,78 +191,57 @@ namespace MWGui box->setImageTexture(image); else box->setImageTexture("black.png"); + } + } + MWBase::World* world = MWBase::Environment::get().getWorld(); - // door markers - - // interior map only consists of one cell, so handle the markers only once - if (interior && (mx != 2 || my != 2)) - continue; - - MWWorld::CellStore* cell; - if (interior) - cell = MWBase::Environment::get().getWorld ()->getInterior (mPrefix); - else - cell = MWBase::Environment::get().getWorld ()->getExterior (x+mx-1, y-(my-1)); - - std::vector doors = MWBase::Environment::get().getWorld ()->getDoorMarkers (cell); - - for (std::vector::iterator it = doors.begin(); it != doors.end(); ++it) + // Retrieve the door markers we want to show + std::vector doors; + if (interior) + { + MWWorld::CellStore* cell = world->getInterior (mPrefix); + world->getDoorMarkers(cell, doors); + } + else + { + for (int dX=-1; dX<2; ++dX) + { + for (int dY=-1; dY<2; ++dY) { - MWBase::World::DoorMarker marker = *it; - - // convert world coordinates to normalized cell coordinates - MyGUI::IntCoord widgetCoord; - float nX,nY; - int cellDx, cellDy; - if (!interior) - { - const int cellSize = 8192; - - nX = (marker.x - cellSize * (x+mx-1)) / cellSize; - nY = 1 - (marker.y - cellSize * (y-(my-1))) / cellSize; - - widgetCoord = MyGUI::IntCoord(nX * 512 - 4 + mx * 512, nY * 512 - 4 + my * 512, 8, 8); - } - else - { - Ogre::Vector2 position (marker.x, marker.y); - MWBase::Environment::get().getWorld ()->getInteriorMapPosition (position, nX, nY, cellDx, cellDy); - - widgetCoord = MyGUI::IntCoord(nX * 512 - 4 + (1+cellDx-x) * 512, nY * 512 - 4 + (1+cellDy-y) * 512, 8, 8); - } - - static int counter = 0; - ++counter; - MyGUI::Button* markerWidget = mLocalMap->createWidget("ButtonImage", - widgetCoord, MyGUI::Align::Default, "Marker" + boost::lexical_cast(counter)); - markerWidget->setImageResource("DoorMarker"); - markerWidget->setUserString("ToolTipType", "Layout"); - markerWidget->setUserString("ToolTipLayout", "TextToolTipOneLine"); - markerWidget->setUserString("Caption_TextOneLine", marker.name); - markerWidget->setUserString("IsMarker", "true"); - markerWidget->eventMouseSetFocus += MyGUI::newDelegate(this, &LocalMapBase::onMarkerFocused); - markerWidget->eventMouseLostFocus += MyGUI::newDelegate(this, &LocalMapBase::onMarkerUnfocused); - - MarkerPosition markerPos; - markerPos.interior = interior; - markerPos.cellX = interior ? cellDx : x + mx - 1; - markerPos.cellY = interior ? cellDy : y + ((my - 1)*-1); - markerPos.nX = nX; - markerPos.nY = nY; - - markerWidget->setUserData(markerPos); + MWWorld::CellStore* cell = world->getExterior (mCurX+dX, mCurY+dY); + world->getDoorMarkers(cell, doors); } - - } } - mInterior = interior; - mCurX = x; - mCurY = y; - mChanged = false; - // fog of war + // Create a widget for each marker + int counter = 0; + for (std::vector::iterator it = doors.begin(); it != doors.end(); ++it) + { + MWBase::World::DoorMarker marker = *it; + + MarkerPosition markerPos; + MyGUI::IntPoint widgetPos = getMarkerPosition(marker.x, marker.y, markerPos); + MyGUI::IntCoord widgetCoord(widgetPos.left - 4, + widgetPos.top - 4, + 8, 8); + ++counter; + MyGUI::Button* markerWidget = mLocalMap->createWidget("ButtonImage", + widgetCoord, MyGUI::Align::Default, "Door" + boost::lexical_cast(counter)); + markerWidget->setImageResource("DoorMarker"); + markerWidget->setUserString("ToolTipType", "Layout"); + markerWidget->setUserString("ToolTipLayout", "TextToolTipOneLine"); + markerWidget->setUserString("Caption_TextOneLine", marker.name); + markerWidget->eventMouseSetFocus += MyGUI::newDelegate(this, &LocalMapBase::onMarkerFocused); + markerWidget->eventMouseLostFocus += MyGUI::newDelegate(this, &LocalMapBase::onMarkerUnfocused); + // Used by tooltips to not show the tooltip if marker is hidden by fog of war + markerWidget->setUserString("IsMarker", "true"); + markerWidget->setUserData(markerPos); + } + + updateMarkers(); + applyFogOfWar(); // set the compass texture again, because MyGUI determines sorting of ImageBox widgets @@ -222,6 +254,8 @@ namespace MWGui void LocalMapBase::setPlayerPos(const float x, const float y) { + updateMarkers(); + if (x == mLastPositionX && y == mLastPositionY) return; @@ -255,6 +289,88 @@ namespace MWGui mLastDirectionY = y; } + void LocalMapBase::addDetectionMarkers(int type) + { + std::vector markers; + MWBase::World* world = MWBase::Environment::get().getWorld(); + world->listDetectedReferences( + world->getPlayer().getPlayer(), + markers, MWBase::World::DetectionType(type)); + if (markers.empty()) + return; + + std::string markerTexture; + MyGUI::Colour markerColour; + if (type == MWBase::World::Detect_Creature) + { + markerTexture = "textures\\menu_map_dcreature.dds"; + markerColour = MyGUI::Colour(1,0,0,1); + } + if (type == MWBase::World::Detect_Key) + { + markerTexture = "textures\\menu_map_dkey.dds"; + markerColour = MyGUI::Colour(0,1,0,1); + } + if (type == MWBase::World::Detect_Enchantment) + { + markerTexture = "textures\\menu_map_dmagic.dds"; + markerColour = MyGUI::Colour(0,0,1,1); + } + + int counter = 0; + for (std::vector::iterator it = markers.begin(); it != markers.end(); ++it) + { + const ESM::Position& worldPos = it->getRefData().getPosition(); + MarkerPosition markerPos; + MyGUI::IntPoint widgetPos = getMarkerPosition(worldPos.pos[0], worldPos.pos[1], markerPos); + MyGUI::IntCoord widgetCoord(widgetPos.left - 4, + widgetPos.top - 4, + 8, 8); + ++counter; + MyGUI::ImageBox* markerWidget = mLocalMap->createWidget("ImageBox", + widgetCoord, MyGUI::Align::Default, "Marker" + boost::lexical_cast(counter)); + markerWidget->setImageTexture(markerTexture); + markerWidget->setUserString("IsMarker", "true"); + markerWidget->setUserData(markerPos); + markerWidget->setColour(markerColour); + } + } + + void LocalMapBase::updateMarkers() + { + // clear all previous markers + for (unsigned int i=0; i< mLocalMap->getChildCount(); ++i) + { + if (mLocalMap->getChildAt(i)->getName ().substr (0, 6) == "Marker") + { + MyGUI::Gui::getInstance ().destroyWidget (mLocalMap->getChildAt(i)); + } + } + + addDetectionMarkers(MWBase::World::Detect_Creature); + addDetectionMarkers(MWBase::World::Detect_Key); + addDetectionMarkers(MWBase::World::Detect_Enchantment); + + // Add marker for the spot marked with Mark magic effect + MWWorld::CellStore* markedCell = NULL; + ESM::Position markedPosition; + MWBase::Environment::get().getWorld()->getPlayer().getMarkedPosition(markedCell, markedPosition); + if (markedCell && markedCell->isExterior() == !mInterior + && (!mInterior || Misc::StringUtils::ciEqual(markedCell->mCell->mName, mPrefix))) + { + MarkerPosition markerPos; + MyGUI::IntPoint widgetPos = getMarkerPosition(markedPosition.pos[0], markedPosition.pos[1], markerPos); + MyGUI::IntCoord widgetCoord(widgetPos.left - 4, + widgetPos.top - 4, + 8, 8); + MyGUI::ImageBox* markerWidget = mLocalMap->createWidget("ImageBox", + widgetCoord, MyGUI::Align::Default, "MarkerMarked"); + markerWidget->setImageTexture("textures\\menu_map_smark.dds"); + markerWidget->setUserString("IsMarker", "true"); + markerWidget->setUserData(markerPos); + } + } + // ------------------------------------------------------------------------------------------ MapWindow::MapWindow(const std::string& cacheDir) @@ -319,7 +435,7 @@ namespace MWGui static int _counter=0; MyGUI::Button* markerWidget = mGlobalMapImage->createWidget("ButtonImage", - widgetCoord, MyGUI::Align::Default, "Marker" + boost::lexical_cast(_counter)); + widgetCoord, MyGUI::Align::Default, "Door" + boost::lexical_cast(_counter)); markerWidget->setImageResource("DoorMarker"); markerWidget->setUserString("ToolTipType", "Layout"); markerWidget->setUserString("ToolTipLayout", "TextToolTipOneLine"); @@ -385,7 +501,7 @@ namespace MWGui for (unsigned int i=0; igetChildCount (); ++i) { - if (mGlobalMapImage->getChildAt (i)->getName().substr(0,6) == "Marker") + if (mGlobalMapImage->getChildAt (i)->getName().substr(0,4) == "Door") mGlobalMapImage->getChildAt (i)->castType()->setImageResource("DoorMarker"); } @@ -396,20 +512,18 @@ namespace MWGui void MapWindow::globalMapUpdatePlayer () { - Ogre::Vector3 pos = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer().getRefData ().getBaseNode ()->_getDerivedPosition (); - Ogre::Quaternion orient = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer().getRefData ().getBaseNode ()->_getDerivedOrientation (); - Ogre::Vector2 dir (orient.yAxis ().x, orient.yAxis().y); - - float worldX, worldY; - mGlobalMapRender->worldPosToImageSpace (pos.x, pos.y, worldX, worldY); - worldX *= mGlobalMapRender->getWidth(); - worldY *= mGlobalMapRender->getHeight(); - - - // for interiors, we have no choice other than using the last position & direction. - /// \todo save this last position in the savegame? + // For interiors, position is set by WindowManager via setGlobalMapPlayerPosition if (MWBase::Environment::get().getWorld ()->isCellExterior ()) { + Ogre::Vector3 pos = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer().getRefData ().getBaseNode ()->_getDerivedPosition (); + Ogre::Quaternion orient = MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer().getRefData ().getBaseNode ()->_getDerivedOrientation (); + Ogre::Vector2 dir (orient.yAxis ().x, orient.yAxis().y); + + float worldX, worldY; + mGlobalMapRender->worldPosToImageSpace (pos.x, pos.y, worldX, worldY); + worldX *= mGlobalMapRender->getWidth(); + worldY *= mGlobalMapRender->getHeight(); + mPlayerArrowGlobal->setPosition(MyGUI::IntPoint(worldX - 16, worldY - 16)); MyGUI::ISubWidget* main = mPlayerArrowGlobal->getSubWidgetMain(); @@ -444,4 +558,19 @@ namespace MWGui "#{sWorld}"); } + void MapWindow::setGlobalMapPlayerPosition(float worldX, float worldY) + { + float x, y; + mGlobalMapRender->worldPosToImageSpace (worldX, worldY, x, y); + x *= mGlobalMapRender->getWidth(); + y *= mGlobalMapRender->getHeight(); + + mPlayerArrowGlobal->setPosition(MyGUI::IntPoint(x - 16, y - 16)); + + // set the view offset so that player is in the center + MyGUI::IntSize viewsize = mGlobalMap->getSize(); + MyGUI::IntPoint viewoffs(0.5*viewsize.width - x, 0.5*viewsize.height - y); + mGlobalMap->setViewOffset(viewoffs); + } + } diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index 5518ab4a8f..7df2105dcf 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -55,9 +55,16 @@ namespace MWGui void onMarkerFocused(MyGUI::Widget* w1, MyGUI::Widget* w2); void onMarkerUnfocused(MyGUI::Widget* w1, MyGUI::Widget* w2); + MyGUI::IntPoint getMarkerPosition (float worldX, float worldY, MarkerPosition& markerPos); + virtual void notifyPlayerUpdate() {} virtual void notifyMapChanged() {} + // Update markers (Detect X effects, Mark/Recall effects) + // Note, door markers handled in setActiveCell + void updateMarkers(); + void addDetectionMarkers(int type); + OEngine::GUI::Layout* mLayout; bool mMapDragAndDrop; @@ -81,6 +88,8 @@ namespace MWGui void addVisitedLocation(const std::string& name, int x, int y); // adds the marker to the global map void cellExplored(int x, int y); + void setGlobalMapPlayerPosition (float worldX, float worldY); + virtual void open(); private: diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index 8486218f0b..378e76e467 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -16,6 +16,15 @@ namespace MWGui mLastButtonPressed = -1; } + MessageBoxManager::~MessageBoxManager () + { + std::vector::iterator it(mMessageBoxes.begin()); + for (; it != mMessageBoxes.end(); ++it) + { + delete *it; + } + } + void MessageBoxManager::onFrame (float frameDuration) { std::vector::iterator it; @@ -141,7 +150,6 @@ namespace MWGui , mMaxTime(0) { // defines - mFixedWidth = 300; mBottomPadding = 20; mNextBoxPadding = 20; @@ -149,41 +157,21 @@ namespace MWGui mMessageWidget->setOverflowToTheLeft(true); mMessageWidget->setCaptionWithReplacing(mMessage); - - MyGUI::IntSize size; - size.width = mFixedWidth; - size.height = 100; // dummy - - mMessageWidget->setSize(size); - - MyGUI::IntSize textSize = mMessageWidget->getTextSize(); - - size.height = mHeight = textSize.height + 20; // this is the padding between the text and the box - - mMainWidget->setSize(size); - size.width -= 15; // this is to center the text (see messagebox.layout, Widget type="Edit" position="-2 -3 0 0") - mMessageWidget->setSize(size); } void MessageBox::update (int height) { MyGUI::IntSize gameWindowSize = MyGUI::RenderManager::getInstance().getViewSize(); - MyGUI::IntCoord coord; - coord.left = (gameWindowSize.width - mFixedWidth)/2; - coord.top = (gameWindowSize.height - mHeight - height - mBottomPadding); - - MyGUI::IntSize size; - size.width = mFixedWidth; - size.height = mHeight; + MyGUI::IntPoint pos; + pos.left = (gameWindowSize.width - mMainWidget->getWidth())/2; + pos.top = (gameWindowSize.height - mMainWidget->getHeight() - height - mBottomPadding); - mMainWidget->setCoord(coord); - mMainWidget->setSize(size); - mMainWidget->setVisible(true); + mMainWidget->setPosition(pos); } int MessageBox::getHeight () { - return mHeight+mNextBoxPadding; // 20 is the padding between this and the next MessageBox + return mMainWidget->getHeight()+mNextBoxPadding; // 20 is the padding between this and the next MessageBox } diff --git a/apps/openmw/mwgui/messagebox.hpp b/apps/openmw/mwgui/messagebox.hpp index ce3a85ab3e..0288f366ce 100644 --- a/apps/openmw/mwgui/messagebox.hpp +++ b/apps/openmw/mwgui/messagebox.hpp @@ -23,6 +23,7 @@ namespace MWGui { public: MessageBoxManager (); + ~MessageBoxManager (); void onFrame (float frameDuration); void createMessageBox (const std::string& message, bool stat = false); void removeStaticMessageBox (); @@ -63,10 +64,8 @@ namespace MWGui protected: MessageBoxManager& mMessageBoxManager; - int mHeight; const std::string& mMessage; MyGUI::EditBox* mMessageWidget; - int mFixedWidth; int mBottomPadding; int mNextBoxPadding; }; diff --git a/apps/openmw/mwgui/pickpocketitemmodel.cpp b/apps/openmw/mwgui/pickpocketitemmodel.cpp index 16be5f6cca..13ee4396d0 100644 --- a/apps/openmw/mwgui/pickpocketitemmodel.cpp +++ b/apps/openmw/mwgui/pickpocketitemmodel.cpp @@ -40,6 +40,14 @@ namespace MWGui for (size_t i = 0; igetItemCount(); ++i) { const ItemStack& item = mSourceModel->getItem(i); + + // Bound items may not be stolen + if (item.mBase.getCellRef().mRefID.size() > 6 + && item.mBase.getCellRef().mRefID.substr(0,6) == "bound_") + { + continue; + } + if (std::find(mHiddenItems.begin(), mHiddenItems.end(), item) == mHiddenItems.end() && item.mType != ItemStack::Type_Equipped) mItems.push_back(item); diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 3956766499..b8f52cd1fb 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -269,13 +269,39 @@ namespace MWGui MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player); - MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); - MWMechanics::Spells& spells = stats.getSpells(); + + if (type == Type_Item || type == Type_MagicItem) + { + MWWorld::Ptr item = *button->getChildAt (0)->getUserData(); + // make sure the item is available + if (item.getRefData ().getCount() < 1) + { + // Try searching for a compatible replacement + std::string id = item.getCellRef().mRefID; + + for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) + { + if (Misc::StringUtils::ciEqual(it->getCellRef().mRefID, id)) + { + item = *it; + button->getChildAt(0)->setUserData(item); + break; + } + } + + if (item.getRefData().getCount() < 1) + { + // No replacement was found + MWBase::Environment::get().getWindowManager ()->messageBox ( + "#{sQuickMenu5} " + MWWorld::Class::get(item).getName(item)); + return; + } + } + } if (type == Type_Magic) { std::string spellId = button->getChildAt(0)->getUserString("Spell"); - spells.setSelectedSpell(spellId); store.setSelectedEnchantItem(store.end()); MWBase::Environment::get().getWindowManager()->setSelectedSpell(spellId, int(MWMechanics::getSpellSuccessChance(spellId, player))); } @@ -283,15 +309,6 @@ namespace MWGui { MWWorld::Ptr item = *button->getChildAt (0)->getUserData(); - // make sure the item is available - if (item.getRefData ().getCount() < 1) - { - // TODO: Try to find a replacement with the same ID? - MWBase::Environment::get().getWindowManager ()->messageBox ( - "#{sQuickMenu5} " + MWWorld::Class::get(item).getName(item)); - return; - } - boost::shared_ptr action = MWWorld::Class::get(item).use(item); action->execute (MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); @@ -310,14 +327,6 @@ namespace MWGui { MWWorld::Ptr item = *button->getChildAt (0)->getUserData(); - // make sure the item is available - if (item.getRefData ().getCount() == 0) - { - MWBase::Environment::get().getWindowManager ()->messageBox ( - "#{sQuickMenu5} " + MWWorld::Class::get(item).getName(item)); - return; - } - // retrieve ContainerStoreIterator to the item MWWorld::ContainerStoreIterator it = store.begin(); for (; it != store.end(); ++it) @@ -336,13 +345,9 @@ namespace MWGui MWWorld::ActionEquip action(item); action.execute (MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer ()); - - // since we changed equipping status, update the inventory window - MWBase::Environment::get().getWindowManager()->getInventoryWindow()->updateItemView(); } store.setSelectedEnchantItem(it); - spells.setSelectedSpell(""); MWBase::Environment::get().getWindowManager()->setSelectedEnchantItem(item); } } diff --git a/apps/openmw/mwgui/review.cpp b/apps/openmw/mwgui/review.cpp index dfc86a547b..e27e40ae64 100644 --- a/apps/openmw/mwgui/review.cpp +++ b/apps/openmw/mwgui/review.cpp @@ -65,7 +65,7 @@ namespace MWGui getWidget(attribute, std::string("Attribute") + boost::lexical_cast(idx)); mAttributeWidgets.insert(std::make_pair(static_cast(ESM::Attribute::sAttributeIds[idx]), attribute)); attribute->setAttributeId(ESM::Attribute::sAttributeIds[idx]); - attribute->setAttributeValue(Widgets::MWAttribute::AttributeValue(0, 0)); + attribute->setAttributeValue(Widgets::MWAttribute::AttributeValue()); } // Setup skills @@ -74,7 +74,7 @@ namespace MWGui for (int i = 0; i < ESM::Skill::Length; ++i) { - mSkillValues.insert(std::make_pair(i, MWMechanics::Stat())); + mSkillValues.insert(std::make_pair(i, MWMechanics::SkillValue())); mSkillWidgetMap.insert(std::make_pair(i, static_cast (0))); } @@ -152,7 +152,7 @@ namespace MWGui mFatigue->setUserString("Caption_HealthDescription", "#{sFatDesc}\n" + valStr); } - void ReviewDialog::setAttribute(ESM::Attribute::AttributeID attributeId, const MWMechanics::Stat& value) + void ReviewDialog::setAttribute(ESM::Attribute::AttributeID attributeId, const MWMechanics::AttributeValue& value) { std::map::iterator attr = mAttributeWidgets.find(static_cast(attributeId)); if (attr == mAttributeWidgets.end()) @@ -161,7 +161,7 @@ namespace MWGui attr->second->setAttributeValue(value); } - void ReviewDialog::setSkillValue(ESM::Skill::SkillEnum skillId, const MWMechanics::Stat& value) + void ReviewDialog::setSkillValue(ESM::Skill::SkillEnum skillId, const MWMechanics::SkillValue& value) { mSkillValues[skillId] = value; MyGUI::TextBox* widget = mSkillWidgetMap[skillId]; @@ -279,9 +279,9 @@ namespace MWGui continue; assert(skillId >= 0 && skillId < ESM::Skill::Length); const std::string &skillNameId = ESM::Skill::sSkillNameIds[skillId]; - const MWMechanics::Stat &stat = mSkillValues.find(skillId)->second; - float base = stat.getBase(); - float modified = stat.getModified(); + const MWMechanics::SkillValue &stat = mSkillValues.find(skillId)->second; + int base = stat.getBase(); + int modified = stat.getModified(); std::string state = "normal"; if (modified > base) diff --git a/apps/openmw/mwgui/review.hpp b/apps/openmw/mwgui/review.hpp index 1c24fec745..5d0767d217 100644 --- a/apps/openmw/mwgui/review.hpp +++ b/apps/openmw/mwgui/review.hpp @@ -38,10 +38,10 @@ namespace MWGui void setMagicka(const MWMechanics::DynamicStat& value); void setFatigue(const MWMechanics::DynamicStat& value); - void setAttribute(ESM::Attribute::AttributeID attributeId, const MWMechanics::Stat& value); + void setAttribute(ESM::Attribute::AttributeID attributeId, const MWMechanics::AttributeValue& value); void configureSkills(const SkillList& major, const SkillList& minor); - void setSkillValue(ESM::Skill::SkillEnum skillId, const MWMechanics::Stat& value); + void setSkillValue(ESM::Skill::SkillEnum skillId, const MWMechanics::SkillValue& value); virtual void open(); @@ -85,7 +85,7 @@ namespace MWGui std::map mAttributeWidgets; SkillList mMajorSkills, mMinorSkills, mMiscSkills; - std::map > mSkillValues; + std::map mSkillValues; std::map mSkillWidgetMap; std::string mName, mRaceId, mBirthSignId; ESM::Class mKlass; diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 1969c56518..c99e2d0de3 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -92,6 +92,7 @@ namespace MWGui { getWidget(mOkButton, "OkButton"); getWidget(mBestAttackButton, "BestAttackButton"); + getWidget(mGrabCursorButton, "GrabCursorButton"); getWidget(mSubtitlesButton, "SubtitlesButton"); getWidget(mCrosshairButton, "CrosshairButton"); getWidget(mResolutionList, "ResolutionList"); @@ -133,6 +134,7 @@ namespace MWGui mSubtitlesButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mCrosshairButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mBestAttackButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); + mGrabCursorButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mInvertYButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onOkButtonClicked); mShadersButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onShadersToggled); @@ -201,6 +203,7 @@ namespace MWGui mSubtitlesButton->setCaptionWithReplacing(Settings::Manager::getBool("subtitles", "GUI") ? "#{sOn}" : "#{sOff}"); mCrosshairButton->setCaptionWithReplacing(Settings::Manager::getBool("crosshair", "HUD") ? "#{sOn}" : "#{sOff}"); mBestAttackButton->setCaptionWithReplacing(Settings::Manager::getBool("best attack", "Game") ? "#{sOn}" : "#{sOff}"); + mGrabCursorButton->setCaptionWithReplacing(Settings::Manager::getBool("grab cursor", "Input") ? "#{sOn}" : "#{sOff}"); float fovVal = (Settings::Manager::getFloat("field of view", "General")-sFovMin)/(sFovMax-sFovMin); mFOVSlider->setScrollPosition(fovVal * (mFOVSlider->getScrollRange()-1)); @@ -393,7 +396,8 @@ namespace MWGui Settings::Manager::setBool("subtitles", "GUI", newState); else if (_sender == mBestAttackButton) Settings::Manager::setBool("best attack", "Game", newState); - + else if (_sender == mGrabCursorButton) + Settings::Manager::setBool("grab cursor", "Input", newState); apply(); } } diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index c81a86ab0e..6b9ce414b8 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -33,6 +33,7 @@ namespace MWGui MyGUI::Button* mSubtitlesButton; MyGUI::Button* mCrosshairButton; MyGUI::Button* mBestAttackButton; + MyGUI::Button* mGrabCursorButton; // graphics MyGUI::ListBox* mResolutionList; diff --git a/apps/openmw/mwgui/spellicons.cpp b/apps/openmw/mwgui/spellicons.cpp index e93e96c4b2..891206532e 100644 --- a/apps/openmw/mwgui/spellicons.cpp +++ b/apps/openmw/mwgui/spellicons.cpp @@ -22,7 +22,8 @@ namespace MWGui { void EffectSourceVisitor::visit (MWMechanics::EffectKey key, - const std::string& sourceName, float magnitude, float remainingTime) + const std::string& sourceName, const std::string& casterHandle, + float magnitude, float remainingTime) { MagicEffectInfo newEffectSource; newEffectSource.mKey = key; diff --git a/apps/openmw/mwgui/spellicons.hpp b/apps/openmw/mwgui/spellicons.hpp index a29e2a00ab..1bb80f3d4c 100644 --- a/apps/openmw/mwgui/spellicons.hpp +++ b/apps/openmw/mwgui/spellicons.hpp @@ -43,7 +43,8 @@ namespace MWGui std::map > mEffectSources; virtual void visit (MWMechanics::EffectKey key, - const std::string& sourceName, float magnitude, float remainingTime = -1); + const std::string& sourceName, const std::string& casterHandle, + float magnitude, float remainingTime = -1); }; class SpellIcons diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index 42a0b98657..2ca783bfc7 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -86,61 +86,8 @@ namespace MWGui MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); MWMechanics::Spells& spells = stats.getSpells(); - // the following code switches between selected enchanted item and selected spell (only one of these - // can be active at a time) - std::string selectedSpell = spells.getSelectedSpell(); - MWWorld::Ptr selectedItem; - if (store.getSelectedEnchantItem() != store.end()) - { - selectedSpell = ""; - selectedItem = *store.getSelectedEnchantItem(); - - bool allowSelectedItem = true; - - // make sure that the item is still in the player inventory, otherwise it can't be selected - bool found = false; - for (MWWorld::ContainerStoreIterator it(store.begin()); it != store.end(); ++it) - { - if (*it == selectedItem) - found = true; - } - if (!found) - allowSelectedItem = false; - - // if the selected item can be equipped, make sure that it actually is equipped - std::pair, bool> slots_; - slots_ = MWWorld::Class::get(selectedItem).getEquipmentSlots(selectedItem); - if (!slots_.first.empty()) - { - bool equipped = false; - for (int i=0; i < MWWorld::InventoryStore::Slots; ++i) - { - if (store.getSlot(i) != store.end() && *store.getSlot(i) == selectedItem) - { - equipped = true; - break; - } - } - - if (!equipped) - allowSelectedItem = false; - } - - if (!allowSelectedItem) - { - store.setSelectedEnchantItem(store.end()); - spells.setSelectedSpell(""); - MWBase::Environment::get().getWindowManager()->unsetSelectedSpell(); - selectedItem = MWWorld::Ptr(); - } - } - - - for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it) - { spellList.push_back (it->first); - } const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); @@ -210,7 +157,7 @@ namespace MWGui t->eventMouseWheel += MyGUI::newDelegate(this, &SpellWindow::onMouseWheel); t->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onSpellSelected); - if (*it == selectedSpell) + if (*it == MWBase::Environment::get().getWindowManager()->getSelectedSpell()) t->setStateSelected(true); mHeight += spellHeight; @@ -229,7 +176,7 @@ namespace MWGui t->setUserString("Spell", *it); t->eventMouseWheel += MyGUI::newDelegate(this, &SpellWindow::onMouseWheel); t->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onSpellSelected); - t->setStateSelected(*it == selectedSpell); + t->setStateSelected(*it == MWBase::Environment::get().getWindowManager()->getSelectedSpell()); // cost / success chance MyGUI::Button* costChance = mSpellView->createWidget("SpellText", @@ -239,7 +186,7 @@ namespace MWGui costChance->setCaption(cost + "/" + chance); costChance->setTextAlign(MyGUI::Align::Right); costChance->setNeedMouseFocus(false); - costChance->setStateSelected(*it == selectedSpell); + costChance->setStateSelected(*it == MWBase::Environment::get().getWindowManager()->getSelectedSpell()); mHeight += spellHeight; @@ -276,7 +223,9 @@ namespace MWGui t->setUserString("Equipped", equipped ? "true" : "false"); t->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onEnchantedItemSelected); t->eventMouseWheel += MyGUI::newDelegate(this, &SpellWindow::onMouseWheel); - t->setStateSelected(item == selectedItem); + if (store.getSelectedEnchantItem() != store.end()) + t->setStateSelected(item == *store.getSelectedEnchantItem()); + // cost / charge MyGUI::Button* costCharge = mSpellView->createWidget(equipped ? "SpellText" : "SpellTextUnequipped", @@ -302,7 +251,8 @@ namespace MWGui costCharge->setCaption(cost + "/" + charge); costCharge->setTextAlign(MyGUI::Align::Right); costCharge->setNeedMouseFocus(false); - costCharge->setStateSelected(item == selectedItem); + if (store.getSelectedEnchantItem() != store.end()) + costCharge->setStateSelected(item == *store.getSelectedEnchantItem()); mHeight += spellHeight; } @@ -349,9 +299,7 @@ namespace MWGui void SpellWindow::onEnchantedItemSelected(MyGUI::Widget* _sender) { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); - MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player); - MWMechanics::Spells& spells = stats.getSpells(); MWWorld::Ptr item = *_sender->getUserData(); // retrieve ContainerStoreIterator to the item @@ -379,7 +327,6 @@ namespace MWGui } store.setSelectedEnchantItem(it); - spells.setSelectedSpell(""); MWBase::Environment::get().getWindowManager()->setSelectedEnchantItem(item); updateSpells(); @@ -389,9 +336,7 @@ namespace MWGui { std::string spellId = _sender->getUserString("Spell"); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); - MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player); - MWMechanics::Spells& spells = stats.getSpells(); if (MyGUI::InputManager::getInstance().isShiftPressed()) { @@ -419,7 +364,6 @@ namespace MWGui } else { - spells.setSelectedSpell(spellId); store.setSelectedEnchantItem(store.end()); MWBase::Environment::get().getWindowManager()->setSelectedSpell(spellId, int(MWMechanics::getSpellSuccessChance(spellId, player))); } @@ -449,11 +393,8 @@ namespace MWGui MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); MWMechanics::Spells& spells = stats.getSpells(); - if (spells.getSelectedSpell() == mSpellToDelete) - { - spells.setSelectedSpell(""); + if (MWBase::Environment::get().getWindowManager()->getSelectedSpell() == mSpellToDelete) MWBase::Environment::get().getWindowManager()->unsetSelectedSpell(); - } spells.remove(mSpellToDelete); diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index fabf1bccc6..549cf65abf 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -61,7 +61,7 @@ namespace MWGui for (int i = 0; i < ESM::Skill::Length; ++i) { - mSkillValues.insert(std::pair >(i, MWMechanics::Stat())); + mSkillValues.insert(std::pair(i, MWMechanics::SkillValue())); mSkillWidgetMap.insert(std::pair(i, (MyGUI::TextBox*)NULL)); } @@ -102,7 +102,7 @@ namespace MWGui adjustWindowCaption(); } - void StatsWindow::setValue (const std::string& id, const MWMechanics::Stat& value) + void StatsWindow::setValue (const std::string& id, const MWMechanics::AttributeValue& value) { static const char *ids[] = { @@ -134,38 +134,28 @@ namespace MWGui void StatsWindow::setValue (const std::string& id, const MWMechanics::DynamicStat& value) { - static const char *ids[] = - { - "HBar", "MBar", "FBar", - 0 - }; + int current = std::max(0, static_cast(value.getCurrent())); + int modified = static_cast(value.getModified()); - for (int i=0; ids[i]; ++i) - { - if (ids[i]==id) - { - std::string id (ids[i]); - setBar (id, id + "T", static_cast(value.getCurrent()), static_cast(value.getModified())); + setBar (id, id + "T", current, modified); - // health, magicka, fatigue tooltip - MyGUI::Widget* w; - std::string valStr = boost::lexical_cast(value.getCurrent()) + "/" + boost::lexical_cast(value.getModified()); - if (i==0) - { - getWidget(w, "Health"); - w->setUserString("Caption_HealthDescription", "#{sHealthDesc}\n" + valStr); - } - else if (i==1) - { - getWidget(w, "Magicka"); - w->setUserString("Caption_HealthDescription", "#{sIntDesc}\n" + valStr); - } - else if (i==2) - { - getWidget(w, "Fatigue"); - w->setUserString("Caption_HealthDescription", "#{sFatDesc}\n" + valStr); - } - } + // health, magicka, fatigue tooltip + MyGUI::Widget* w; + std::string valStr = boost::lexical_cast(current) + "/" + boost::lexical_cast(modified); + if (id == "HBar") + { + getWidget(w, "Health"); + w->setUserString("Caption_HealthDescription", "#{sHealthDesc}\n" + valStr); + } + else if (id == "MBar") + { + getWidget(w, "Magicka"); + w->setUserString("Caption_HealthDescription", "#{sIntDesc}\n" + valStr); + } + else if (id == "FBar") + { + getWidget(w, "Fatigue"); + w->setUserString("Caption_HealthDescription", "#{sFatDesc}\n" + valStr); } } @@ -189,7 +179,7 @@ namespace MWGui } } - void StatsWindow::setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::Stat& value) + void StatsWindow::setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value) { mSkillValues[parSkill] = value; MyGUI::TextBox* widget = mSkillWidgetMap[(int)parSkill]; @@ -368,22 +358,20 @@ namespace MWGui continue; assert(skillId >= 0 && skillId < ESM::Skill::Length); const std::string &skillNameId = ESM::Skill::sSkillNameIds[skillId]; - const MWMechanics::Stat &stat = mSkillValues.find(skillId)->second; - float base = stat.getBase(); - float modified = stat.getModified(); - int progressPercent = (modified - float(static_cast(modified))) * 100; + const MWMechanics::SkillValue &stat = mSkillValues.find(skillId)->second; + int base = stat.getBase(); + int modified = stat.getModified(); + int progressPercent = stat.getProgress() * 100; const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); const ESM::Skill* skill = esmStore.get().find(skillId); - assert(skill); std::string icon = "icons\\k\\" + ESM::Skill::sIconNames[skillId]; const ESM::Attribute* attr = esmStore.get().find(skill->mData.mAttribute); - assert(attr); std::string state = "normal"; if (modified > base) @@ -494,7 +482,6 @@ namespace MWGui ESM::RankData rankData = faction->mData.mRankData[it->second+1]; const ESM::Attribute* attr1 = store.get().find(faction->mData.mAttribute[0]); const ESM::Attribute* attr2 = store.get().find(faction->mData.mAttribute[1]); - assert(attr1 && attr2); text += "\n#BF9959#{" + attr1->mName + "}: " + boost::lexical_cast(rankData.mAttribute1) + ", #{" + attr2->mName + "}: " + boost::lexical_cast(rankData.mAttribute2); diff --git a/apps/openmw/mwgui/statswindow.hpp b/apps/openmw/mwgui/statswindow.hpp index 49b44a2e16..28d96ca90f 100644 --- a/apps/openmw/mwgui/statswindow.hpp +++ b/apps/openmw/mwgui/statswindow.hpp @@ -26,11 +26,11 @@ namespace MWGui void setPlayerName(const std::string& playerName); /// Set value for the given ID. - void setValue (const std::string& id, const MWMechanics::Stat& value); + void setValue (const std::string& id, const MWMechanics::AttributeValue& value); void setValue (const std::string& id, const MWMechanics::DynamicStat& value); void setValue (const std::string& id, const std::string& value); void setValue (const std::string& id, int value); - void setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::Stat& value); + void setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value); void configureSkills (const SkillList& major, const SkillList& minor); void setReputation (int reputation) { if (reputation != mReputation) mChanged = true; this->mReputation = reputation; } @@ -61,7 +61,7 @@ namespace MWGui MyGUI::ScrollView* mSkillView; SkillList mMajorSkills, mMinorSkills, mMiscSkills; - std::map > mSkillValues; + std::map mSkillValues; std::map mSkillWidgetMap; std::map mFactionWidgetMap; FactionList mFactions; ///< Stores a list of factions and the current rank diff --git a/apps/openmw/mwgui/textinput.hpp b/apps/openmw/mwgui/textinput.hpp index 1f53263ecd..1ed80fc1e9 100644 --- a/apps/openmw/mwgui/textinput.hpp +++ b/apps/openmw/mwgui/textinput.hpp @@ -15,8 +15,8 @@ namespace MWGui public: TextInputDialog(); - std::string getTextInput() const { return mTextEdit ? mTextEdit->getOnlyText() : ""; } - void setTextInput(const std::string &text) { if (mTextEdit) mTextEdit->setOnlyText(text); } + std::string getTextInput() const { return mTextEdit->getCaption(); } + void setTextInput(const std::string &text) { mTextEdit->setCaption(text); } void setNextButtonShow(bool shown); void setTextLabel(const std::string &label); diff --git a/apps/openmw/mwgui/tradeitemmodel.cpp b/apps/openmw/mwgui/tradeitemmodel.cpp index e836355d3e..5c12843da0 100644 --- a/apps/openmw/mwgui/tradeitemmodel.cpp +++ b/apps/openmw/mwgui/tradeitemmodel.cpp @@ -154,6 +154,13 @@ namespace MWGui if(!MWWorld::Class::get(base).canSell(base, services)) continue; + // Bound items may not be bought + if (item.mBase.getCellRef().mRefID.size() > 6 + && item.mBase.getCellRef().mRefID.substr(0,6) == "bound_") + { + continue; + } + // don't show equipped items if(mMerchant.getTypeName() == typeid(ESM::NPC).name()) { diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index d544b83cf4..6000de858a 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -24,6 +24,7 @@ #include "containeritemmodel.hpp" #include "tradeitemmodel.hpp" #include "countdialog.hpp" +#include "dialogue.hpp" namespace MWGui { @@ -296,10 +297,10 @@ namespace MWGui const MWMechanics::NpcStats &sellerStats = MWWorld::Class::get(mPtr).getNpcStats(mPtr); const MWMechanics::NpcStats &playerStats = MWWorld::Class::get(playerPtr).getNpcStats(playerPtr); - float a1 = std::min(playerStats.getSkill(ESM::Skill::Mercantile).getModified(), 100.f); + float a1 = std::min(playerStats.getSkill(ESM::Skill::Mercantile).getModified(), 100); float b1 = std::min(0.1f * playerStats.getAttribute(ESM::Attribute::Luck).getModified(), 10.f); float c1 = std::min(0.2f * playerStats.getAttribute(ESM::Attribute::Personality).getModified(), 10.f); - float d1 = std::min(sellerStats.getSkill(ESM::Skill::Mercantile).getModified(), 100.f); + float d1 = std::min(sellerStats.getSkill(ESM::Skill::Mercantile).getModified(), 100); float e1 = std::min(0.1f * sellerStats.getAttribute(ESM::Attribute::Luck).getModified(), 10.f); float f1 = std::min(0.2f * sellerStats.getAttribute(ESM::Attribute::Personality).getModified(), 10.f); @@ -340,6 +341,9 @@ namespace MWGui addOrRemoveGold(-mCurrentBalance, mPtr); } + MWBase::Environment::get().getWindowManager()->getDialogueWindow()->addResponse( + MWBase::Environment::get().getWorld()->getStore().get().find("sBarterDialog5")->getString()); + std::string sound = "Item Gold Up"; MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp index 071bb88049..5cc65947f2 100644 --- a/apps/openmw/mwgui/waitdialog.cpp +++ b/apps/openmw/mwgui/waitdialog.cpp @@ -112,7 +112,7 @@ namespace MWGui float hourlyHealthDelta = stats.getAttribute(ESM::Attribute::Endurance).getModified() * 0.1; - bool stunted = (stats.getMagicEffects().get(MWMechanics::EffectKey(ESM::MagicEffect::StuntedMagicka)).mMagnitude > 0); + bool stunted = (stats.getMagicEffects().get(ESM::MagicEffect::StuntedMagicka).mMagnitude > 0); float fRestMagicMult = store.get().find("fRestMagicMult")->getFloat(); float hourlyMagickaDelta = fRestMagicMult * stats.getAttribute(ESM::Attribute::Intelligence).getModified(); diff --git a/apps/openmw/mwgui/widgets.cpp b/apps/openmw/mwgui/widgets.cpp index c37ae15fa1..b30cf2bae8 100644 --- a/apps/openmw/mwgui/widgets.cpp +++ b/apps/openmw/mwgui/widgets.cpp @@ -178,7 +178,7 @@ namespace MWGui } if (mAttributeValueWidget) { - AttributeValue::Type modified = mValue.getModified(), base = mValue.getBase(); + int modified = mValue.getModified(), base = mValue.getBase(); static_cast(mAttributeValueWidget)->setCaption(boost::lexical_cast(modified)); if (modified > base) mAttributeValueWidget->_setWidgetState("increased"); @@ -528,14 +528,9 @@ namespace MWGui if (mBarTextWidget) { - if (mValue >= 0 && mMax > 0) - { - std::stringstream out; - out << mValue << "/" << mMax; - static_cast(mBarTextWidget)->setCaption(out.str().c_str()); - } - else - static_cast(mBarTextWidget)->setCaption(""); + std::stringstream out; + out << mValue << "/" << mMax; + static_cast(mBarTextWidget)->setCaption(out.str().c_str()); } } void MWDynamicStat::setTitle(const std::string& text) diff --git a/apps/openmw/mwgui/widgets.hpp b/apps/openmw/mwgui/widgets.hpp index 1630ab3c9f..adc56f423f 100644 --- a/apps/openmw/mwgui/widgets.hpp +++ b/apps/openmw/mwgui/widgets.hpp @@ -133,7 +133,7 @@ namespace MWGui public: MWAttribute(); - typedef MWMechanics::Stat AttributeValue; + typedef MWMechanics::AttributeValue AttributeValue; void setAttributeId(int attributeId); void setAttributeValue(const AttributeValue& value); diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 6e3a85f0c6..9e57f50419 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -16,6 +16,7 @@ #include "../mwbase/inputmanager.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/player.hpp" #include "console.hpp" #include "journalwindow.hpp" @@ -112,9 +113,6 @@ namespace MWGui , mPlayerMinorSkills() , mPlayerMajorSkills() , mPlayerSkillValues() - , mPlayerHealth() - , mPlayerMagicka() - , mPlayerFatigue() , mGui(NULL) , mGuiModes() , mCursorManager(NULL) @@ -177,11 +175,11 @@ namespace MWGui MyGUI::InputManager::getInstance().eventChangeKeyFocus += MyGUI::newDelegate(this, &WindowManager::onKeyFocusChanged); - mCursorManager->setEnabled(true); - onCursorChange(MyGUI::PointerManager::getInstance().getDefaultPointer()); SDL_ShowCursor(false); + mCursorManager->setEnabled(true); + // hide mygui's pointer MyGUI::PointerManager::getInstance().setVisible(false); } @@ -251,12 +249,12 @@ namespace MWGui // Setup player stats for (int i = 0; i < ESM::Attribute::Length; ++i) { - mPlayerAttributes.insert(std::make_pair(ESM::Attribute::sAttributeIds[i], MWMechanics::Stat())); + mPlayerAttributes.insert(std::make_pair(ESM::Attribute::sAttributeIds[i], MWMechanics::AttributeValue())); } for (int i = 0; i < ESM::Skill::Length; ++i) { - mPlayerSkillValues.insert(std::make_pair(ESM::Skill::sSkillIds[i], MWMechanics::Stat())); + mPlayerSkillValues.insert(std::make_pair(ESM::Skill::sSkillIds[i], MWMechanics::SkillValue())); } // Set up visibility @@ -325,6 +323,7 @@ namespace MWGui delete mSoulgemDialog; delete mCursorManager; delete mRecharge; + delete mCompanionWindow; cleanupGarbage(); @@ -545,7 +544,7 @@ namespace MWGui } } - void WindowManager::setValue (const std::string& id, const MWMechanics::Stat& value) + void WindowManager::setValue (const std::string& id, const MWMechanics::AttributeValue& value) { mStatsWindow->setValue (id, value); mCharGen->setValue(id, value); @@ -576,7 +575,7 @@ namespace MWGui } - void WindowManager::setValue (int parSkill, const MWMechanics::Stat& value) + void WindowManager::setValue (int parSkill, const MWMechanics::SkillValue& value) { /// \todo Don't use the skill enum as a parameter type (we will have to drop it anyway, once we /// allow custom skills. @@ -590,32 +589,8 @@ namespace MWGui mStatsWindow->setValue (id, value); mHud->setValue (id, value); mCharGen->setValue(id, value); - if (id == "HBar") - { - mPlayerHealth = value; - } - else if (id == "MBar") - { - mPlayerMagicka = value; - } - else if (id == "FBar") - { - mPlayerFatigue = value; - } } - #if 0 - MWMechanics::DynamicStat WindowManager::getValue(const std::string& id) - { - if(id == "HBar") - return mPlayerHealth; - else if (id == "MBar") - return mPlayerMagicka; - else if (id == "FBar") - return mPlayerFatigue; - } - #endif - void WindowManager::setValue (const std::string& id, const std::string& value) { mStatsWindow->setValue (id, value); @@ -788,6 +763,13 @@ namespace MWGui { mMap->setCellPrefix( cell->mCell->mName ); mHud->setCellPrefix( cell->mCell->mName ); + + Ogre::Vector3 worldPos; + if (!MWBase::Environment::get().getWorld()->findInteriorPositionInWorldSpace(cell, worldPos)) + worldPos = MWBase::Environment::get().getWorld()->getPlayer().getLastKnownExteriorPosition(); + else + MWBase::Environment::get().getWorld()->getPlayer().setLastKnownExteriorPosition(worldPos); + mMap->setGlobalMapPlayerPosition(worldPos.x, worldPos.y); } } @@ -1023,6 +1005,7 @@ namespace MWGui void WindowManager::setSelectedSpell(const std::string& spellId, int successChancePercent) { + mSelectedSpell = spellId; mHud->setSelectedSpell(spellId, successChancePercent); const ESM::Spell* spell = @@ -1033,6 +1016,7 @@ namespace MWGui void WindowManager::setSelectedEnchantItem(const MWWorld::Ptr& item) { + mSelectedSpell = ""; const ESM::Enchantment* ench = MWBase::Environment::get().getWorld()->getStore().get() .find(MWWorld::Class::get(item).getEnchantment(item)); @@ -1052,6 +1036,7 @@ namespace MWGui void WindowManager::unsetSelectedSpell() { + mSelectedSpell = ""; mHud->unsetSelectedSpell(); mSpellWindow->setTitle("#{sNone}"); } @@ -1179,12 +1164,12 @@ namespace MWGui return mGuiModes.back(); } - std::map > WindowManager::getPlayerSkillValues() + std::map WindowManager::getPlayerSkillValues() { return mPlayerSkillValues; } - std::map > WindowManager::getPlayerAttributeValues() + std::map WindowManager::getPlayerAttributeValues() { return mPlayerAttributes; } diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 4f19602958..a3b135ad4e 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -152,8 +152,8 @@ namespace MWGui virtual void wmUpdateFps(float fps, unsigned int triangleCount, unsigned int batchCount); ///< Set value for the given ID. - virtual void setValue (const std::string& id, const MWMechanics::Stat& value); - virtual void setValue (int parSkill, const MWMechanics::Stat& value); + virtual void setValue (const std::string& id, const MWMechanics::AttributeValue& value); + virtual void setValue (int parSkill, const MWMechanics::SkillValue& value); virtual void setValue (const std::string& id, const MWMechanics::DynamicStat& value); virtual void setValue (const std::string& id, const std::string& value); virtual void setValue (const std::string& id, int value); @@ -200,6 +200,7 @@ namespace MWGui virtual void activateQuickKey (int index); + virtual std::string getSelectedSpell() { return mSelectedSpell; } virtual void setSelectedSpell(const std::string& spellId, int successChancePercent); virtual void setSelectedEnchantItem(const MWWorld::Ptr& item); virtual void setSelectedWeapon(const MWWorld::Ptr& item); @@ -228,8 +229,8 @@ namespace MWGui virtual void onFrame (float frameDuration); /// \todo get rid of this stuff. Move it to the respective UI element classes, if needed. - virtual std::map > getPlayerSkillValues(); - virtual std::map > getPlayerAttributeValues(); + virtual std::map getPlayerSkillValues(); + virtual std::map getPlayerAttributeValues(); virtual SkillList getPlayerMinorSkills(); virtual SkillList getPlayerMajorSkills(); @@ -288,6 +289,8 @@ namespace MWGui void trackWindow(OEngine::GUI::Layout* layout, const std::string& name); void onWindowChangeCoord(MyGUI::Window* _sender); + std::string mSelectedSpell; + OEngine::GUI::MyGUIManager *mGuiManager; OEngine::Render::OgreRenderer *mRendering; HUD *mHud; @@ -343,11 +346,9 @@ namespace MWGui // Various stats about player as needed by window manager std::string mPlayerName; std::string mPlayerRaceId; - std::map > mPlayerAttributes; + std::map mPlayerAttributes; SkillList mPlayerMajorSkills, mPlayerMinorSkills; - std::map > mPlayerSkillValues; - MWMechanics::DynamicStat mPlayerHealth, mPlayerMagicka, mPlayerFatigue; - + std::map mPlayerSkillValues; MyGUI::Gui *mGui; // Gui std::vector mGuiModes; diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index e6e349c4dd..a3c508f042 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include @@ -20,7 +21,6 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/statemanager.hpp" -#include "../mwgui/bookwindow.hpp" #include "../mwmechanics/creaturestats.hpp" using namespace ICS; @@ -104,6 +104,7 @@ namespace MWInput , mCameraSensitivity (Settings::Manager::getFloat("camera sensitivity", "Input")) , mUISensitivity (Settings::Manager::getFloat("ui sensitivity", "Input")) , mCameraYMultiplier (Settings::Manager::getFloat("camera y multiplier", "Input")) + , mGrabCursor (Settings::Manager::getBool("grab cursor", "Input")) , mPreviewPOVDelay(0.f) , mTimeIdle(0.f) , mOverencumberedMessageDelay(0.f) @@ -290,7 +291,7 @@ namespace MWInput mInputManager->setMouseRelative(is_relative); //we let the mouse escape in the main menu - mInputManager->setGrabPointer(grab); + mInputManager->setGrabPointer(grab && (mGrabCursor || is_relative)); //we switched to non-relative mode, move our cursor to where the in-game //cursor is @@ -383,10 +384,9 @@ namespace MWInput MWBase::Environment::get().getWorld()->togglePreviewMode(true); } } else { - if (mPreviewPOVDelay > 0.5) { - //disable preview mode - MWBase::Environment::get().getWorld()->togglePreviewMode(false); - } else if (mPreviewPOVDelay > 0.f) { + //disable preview mode + MWBase::Environment::get().getWorld()->togglePreviewMode(false); + if (mPreviewPOVDelay > 0.f && mPreviewPOVDelay <= 0.5) { MWBase::Environment::get().getWorld()->togglePOV(); } mPreviewPOVDelay = 0.f; @@ -436,6 +436,9 @@ namespace MWInput if (it->first == "Input" && it->second == "ui sensitivity") mUISensitivity = Settings::Manager::getFloat("ui sensitivity", "Input"); + if (it->first == "Input" && it->second == "grab cursor") + mGrabCursor = Settings::Manager::getBool("grab cursor", "Input"); + } } @@ -499,6 +502,9 @@ namespace MWInput edit->deleteTextSelection(); } } + } + if (edit && !edit->getEditStatic()) + { if (arg.keysym.sym == SDLK_c && (arg.keysym.mod & SDL_Keymod(KMOD_CTRL))) { std::string text = edit->getTextSelection(); @@ -593,15 +599,6 @@ namespace MWInput mMouseWheel = int(arg.z); MyGUI::InputManager::getInstance().injectMouseMove( int(mMouseX), int(mMouseY), mMouseWheel); - - //if the player is reading a book and flicking the mouse wheel - if (MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_Book && arg.zrel) - { - if (arg.zrel < 0) - MWBase::Environment::get().getWindowManager()->getBookWindow()->nextPage(); - else - MWBase::Environment::get().getWindowManager()->getBookWindow()->prevPage(); - } } if (mMouseLookEnabled) @@ -678,7 +675,7 @@ namespace MWInput if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return; // Not allowed before the magic window is accessible - if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Magic)) + if (!mControlSwitch["playermagic"]) return; MWMechanics::DrawState_ state = mPlayer->getDrawState(); @@ -693,7 +690,7 @@ namespace MWInput if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return; // Not allowed before the inventory window is accessible - if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) + if (!mControlSwitch["playerfighting"]) return; MWMechanics::DrawState_ state = mPlayer->getDrawState(); @@ -833,9 +830,11 @@ namespace MWInput void InputManager::updateIdleTime(float dt) { + static const float vanityDelay = MWBase::Environment::get().getWorld()->getStore().get() + .find("fVanityDelay")->getFloat(); if (mTimeIdle >= 0.f) mTimeIdle += dt; - if (mTimeIdle > 30.f) { + if (mTimeIdle > vanityDelay) { MWBase::Environment::get().getWorld()->toggleVanityMode(true); mTimeIdle = -1.f; } diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index 8efa6cfc5a..4eaee9b690 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -1,5 +1,5 @@ -#ifndef _MWINPUT_MWINPUTMANAGERIMP_H -#define _MWINPUT_MWINPUTMANAGERIMP_H +#ifndef MWINPUT_MWINPUTMANAGERIMP_H +#define MWINPUT_MWINPUTMANAGERIMP_H #include "../mwgui/mode.hpp" @@ -138,6 +138,8 @@ namespace MWInput bool mDragDrop; + bool mGrabCursor; + bool mInvertY; float mCameraSensitivity; diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index dc79901b0e..2a71659742 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -126,7 +126,8 @@ namespace MWMechanics return mSpells; } - void ActiveSpells::addSpell(const std::string &id, bool stack, std::vector effects, const std::string &displayName) + void ActiveSpells::addSpell(const std::string &id, bool stack, std::vector effects, + const std::string &displayName, const std::string& casterHandle) { bool exists = false; for (TContainer::const_iterator it = begin(); it != end(); ++it) @@ -139,6 +140,7 @@ namespace MWMechanics params.mTimeStamp = MWBase::Environment::get().getWorld()->getTimeStamp(); params.mEffects = effects; params.mDisplayName = displayName; + params.mCasterHandle = casterHandle; if (!exists || stack) mSpells.insert (std::make_pair(id, params)); @@ -148,6 +150,12 @@ namespace MWMechanics mSpellsChanged = true; } + void ActiveSpells::removeEffects(const std::string &id) + { + mSpells.erase(Misc::StringUtils::lowerCase(id)); + mSpellsChanged = true; + } + void ActiveSpells::visitEffectSources(EffectSourceVisitor &visitor) const { for (TContainer::const_iterator it = begin(); it != end(); ++it) @@ -164,14 +172,22 @@ namespace MWMechanics float magnitude = effectIt->mMagnitude; if (magnitude) - visitor.visit(effectIt->mKey, name, magnitude, remainingTime); + visitor.visit(effectIt->mKey, name, it->second.mCasterHandle, magnitude, remainingTime); } } } - void ActiveSpells::purgeAll() + void ActiveSpells::purgeAll(float chance) { - mSpells.clear(); + for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ) + { + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + if (roll < chance) + mSpells.erase(it++); + else + ++it; + } + mSpellsChanged = true; } void ActiveSpells::purgeEffect(short effectId) @@ -187,6 +203,6 @@ namespace MWMechanics effectIt++; } } - + mSpellsChanged = true; } } diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index b3f499c6b2..2ddb4ec556 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -37,8 +37,8 @@ namespace MWMechanics MWWorld::TimeStamp mTimeStamp; std::string mDisplayName; - // TODO: To handle CASTER_LINKED flag (spell is purged when caster dies), - // we should probably store a handle to the caster here. + // Handle to the caster that that inflicted this spell on us + std::string mCasterHandle; }; typedef std::multimap TContainer; @@ -76,14 +76,19 @@ namespace MWMechanics /// \param stack If false, the spell is not added if one with the same ID exists already. /// \param effects /// \param displayName Name for display in magic menu. + /// \param casterHandle /// - void addSpell (const std::string& id, bool stack, std::vector effects, const std::string& displayName); + void addSpell (const std::string& id, bool stack, std::vector effects, + const std::string& displayName, const std::string& casterHandle); - /// Remove all active effects with this id + /// Removes the active effects from this spell/potion/.. with \a id + void removeEffects (const std::string& id); + + /// Remove all active effects with this effect id void purgeEffect (short effectId); - /// Remove all active effects - void purgeAll (); + /// Remove all active effects, if roll succeeds (for each effect) + void purgeAll (float chance); bool isSpellActive (std::string id) const; ///< case insensitive diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 7180eb8833..c488d4d2b8 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -46,15 +46,116 @@ void adjustBoundItem (const std::string& item, bool bound, const MWWorld::Ptr& a } } +bool disintegrateSlot (MWWorld::Ptr ptr, int slot, float disintegrate) +{ + // TODO: remove this check once creatures support inventory store + if (ptr.getTypeName() == typeid(ESM::NPC).name()) + { + MWWorld::InventoryStore& inv = ptr.getClass().getInventoryStore(ptr); + MWWorld::ContainerStoreIterator item = + inv.getSlot(slot); + if (item != inv.end()) + { + if (!item->getClass().hasItemHealth(*item)) + return false; + if (item->getCellRef().mCharge == -1) + item->getCellRef().mCharge = item->getClass().getItemMaxHealth(*item); + + if (item->getCellRef().mCharge == 0) + return false; + + item->getCellRef().mCharge -= + std::min(disintegrate, + static_cast(item->getCellRef().mCharge)); + + if (item->getCellRef().mCharge == 0) + { + // Will unequip the broken item and try to find a replacement + if (ptr.getRefData().getHandle() != "player") + inv.autoEquip(ptr); + else + inv.unequipItem(*item, ptr); + } + + return true; + } + } + return true; +} + + } namespace MWMechanics { + + class SoulTrap : public MWMechanics::EffectSourceVisitor + { + MWWorld::Ptr mCreature; + MWWorld::Ptr mActor; + public: + SoulTrap(MWWorld::Ptr trappedCreature) + : mCreature(trappedCreature) {} + + virtual void visit (MWMechanics::EffectKey key, + const std::string& sourceName, const std::string& casterHandle, + float magnitude, float remainingTime = -1) + { + if (key.mId != ESM::MagicEffect::Soultrap) + return; + if (magnitude <= 0) + return; + + MWBase::World* world = MWBase::Environment::get().getWorld(); + + MWWorld::Ptr caster = world->searchPtrViaHandle(casterHandle); + if (caster.isEmpty() || !caster.getClass().isActor()) + return; + + static const float fSoulgemMult = world->getStore().get().find("fSoulgemMult")->getFloat(); + + float creatureSoulValue = mCreature.get()->mBase->mData.mSoul; + + // Use the smallest soulgem that is large enough to hold the soul + MWWorld::ContainerStore& container = caster.getClass().getContainerStore(caster); + MWWorld::ContainerStoreIterator gem = container.end(); + float gemCapacity = std::numeric_limits().max(); + std::string soulgemFilter = "misc_soulgem"; // no other way to check for soulgems? :/ + for (MWWorld::ContainerStoreIterator it = container.begin(MWWorld::ContainerStore::Type_Miscellaneous); + it != container.end(); ++it) + { + const std::string& id = it->getCellRef().mRefID; + if (id.size() >= soulgemFilter.size() + && id.substr(0,soulgemFilter.size()) == soulgemFilter) + { + float thisGemCapacity = it->get()->mBase->mData.mValue * fSoulgemMult; + if (thisGemCapacity >= creatureSoulValue && thisGemCapacity < gemCapacity + && it->getCellRef().mSoul.empty()) + { + gem = it; + gemCapacity = thisGemCapacity; + } + } + } + + if (gem == container.end()) + return; + + // Set the soul on just one of the gems, not the whole stack + gem->getContainerStore()->unstack(*gem, caster); + gem->getCellRef().mSoul = mCreature.getCellRef().mRefID; + + if (caster.getRefData().getHandle() == "player") + MWBase::Environment::get().getWindowManager()->messageBox("#{sSoultrapSuccess}"); + } + }; + void Actors::updateActor (const MWWorld::Ptr& ptr, float duration) { // magic effects adjustMagicEffects (ptr); - calculateDynamicStats (ptr); + if (ptr.getClass().getCreatureStats(ptr).needToRecalcDynamicStats()) + calculateDynamicStats (ptr); calculateCreatureStatModifiers (ptr, duration); if(!MWBase::Environment::get().getWindowManager()->isGuiMode()) @@ -71,7 +172,7 @@ namespace MWMechanics float d = sqrt((actorpos.pos[0] - playerpos.pos[0])*(actorpos.pos[0] - playerpos.pos[0]) +(actorpos.pos[1] - playerpos.pos[1])*(actorpos.pos[1] - playerpos.pos[1]) +(actorpos.pos[2] - playerpos.pos[2])*(actorpos.pos[2] - playerpos.pos[2])); - float fight = ptr.getClass().getCreatureStats(ptr).getAiSetting(1); + float fight = ptr.getClass().getCreatureStats(ptr).getAiSetting(CreatureStats::AI_Fight).getModified(); float disp = 100; //creatures don't have disposition, so set it to 100 by default if(ptr.getTypeName() == typeid(ESM::NPC).name()) { @@ -127,7 +228,7 @@ namespace MWMechanics now += creatureStats.getActiveSpells().getMagicEffects(); - MagicEffects diff = MagicEffects::diff (creatureStats.getMagicEffects(), now); + //MagicEffects diff = MagicEffects::diff (creatureStats.getMagicEffects(), now); creatureStats.setMagicEffects(now); @@ -177,7 +278,7 @@ namespace MWMechanics { // the actor is sleeping, restore health and magicka - bool stunted = stats.getMagicEffects ().get(MWMechanics::EffectKey(ESM::MagicEffect::StuntedMagicka)).mMagnitude > 0; + bool stunted = stats.getMagicEffects ().get(ESM::MagicEffect::StuntedMagicka).mMagnitude > 0; DynamicStat health = stats.getHealth(); health.setCurrent (health.getCurrent() + 0.1 * endurance); @@ -216,7 +317,7 @@ namespace MWMechanics // attributes for(int i = 0;i < ESM::Attribute::Length;++i) { - Stat stat = creatureStats.getAttribute(i); + AttributeValue stat = creatureStats.getAttribute(i); stat.setModifier(effects.get(EffectKey(ESM::MagicEffect::FortifyAttribute, i)).mMagnitude - effects.get(EffectKey(ESM::MagicEffect::DrainAttribute, i)).mMagnitude - effects.get(EffectKey(ESM::MagicEffect::AbsorbAttribute, i)).mMagnitude); @@ -228,18 +329,69 @@ namespace MWMechanics for(int i = 0;i < 3;++i) { DynamicStat stat = creatureStats.getDynamic(i); - stat.setModifier(effects.get(EffectKey(ESM::MagicEffect::FortifyHealth+i)).mMagnitude - - effects.get(EffectKey(ESM::MagicEffect::DrainHealth+i)).mMagnitude); + stat.setModifier(effects.get(ESM::MagicEffect::FortifyHealth+i).mMagnitude - + effects.get(ESM::MagicEffect::DrainHealth+i).mMagnitude); - float currentDiff = creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::RestoreHealth+i)).mMagnitude - - creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::DamageHealth+i)).mMagnitude - - creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::AbsorbHealth+i)).mMagnitude; + float currentDiff = creatureStats.getMagicEffects().get(ESM::MagicEffect::RestoreHealth+i).mMagnitude + - creatureStats.getMagicEffects().get(ESM::MagicEffect::DamageHealth+i).mMagnitude + - creatureStats.getMagicEffects().get(ESM::MagicEffect::AbsorbHealth+i).mMagnitude; stat.setCurrent(stat.getCurrent() + currentDiff * duration); creatureStats.setDynamic(i, stat); } + // AI setting modifiers + int creature = !ptr.getClass().isNpc(); + if (creature && ptr.get()->mBase->mData.mType == ESM::Creature::Humanoid) + creature = false; + // Note: the Creature variants only work on normal creatures, not on daedra or undead creatures. + if (!creature || ptr.get()->mBase->mData.mType == ESM::Creature::Creatures) + { + Stat stat = creatureStats.getAiSetting(CreatureStats::AI_Fight); + stat.setModifier(creatureStats.getMagicEffects().get(ESM::MagicEffect::FrenzyHumanoid+creature).mMagnitude + - creatureStats.getMagicEffects().get(ESM::MagicEffect::CalmHumanoid+creature).mMagnitude); + creatureStats.setAiSetting(CreatureStats::AI_Fight, stat); + + stat = creatureStats.getAiSetting(CreatureStats::AI_Flee); + stat.setModifier(creatureStats.getMagicEffects().get(ESM::MagicEffect::DemoralizeHumanoid+creature).mMagnitude + - creatureStats.getMagicEffects().get(ESM::MagicEffect::RallyHumanoid+creature).mMagnitude); + creatureStats.setAiSetting(CreatureStats::AI_Flee, stat); + } + if (creature && ptr.get()->mBase->mData.mType == ESM::Creature::Undead) + { + Stat stat = creatureStats.getAiSetting(CreatureStats::AI_Flee); + stat.setModifier(creatureStats.getMagicEffects().get(ESM::MagicEffect::TurnUndead).mMagnitude); + creatureStats.setAiSetting(CreatureStats::AI_Flee, stat); + } + + // Apply disintegration (reduces item health) + float disintegrateWeapon = effects.get(ESM::MagicEffect::DisintegrateWeapon).mMagnitude; + if (disintegrateWeapon > 0) + disintegrateSlot(ptr, MWWorld::InventoryStore::Slot_CarriedRight, disintegrateWeapon*duration); + float disintegrateArmor = effects.get(ESM::MagicEffect::DisintegrateArmor).mMagnitude; + if (disintegrateArmor > 0) + { + // According to UESP + int priorities[] = { + MWWorld::InventoryStore::Slot_CarriedLeft, + MWWorld::InventoryStore::Slot_Cuirass, + MWWorld::InventoryStore::Slot_LeftPauldron, + MWWorld::InventoryStore::Slot_RightPauldron, + MWWorld::InventoryStore::Slot_LeftGauntlet, + MWWorld::InventoryStore::Slot_RightGauntlet, + MWWorld::InventoryStore::Slot_Helmet, + MWWorld::InventoryStore::Slot_Greaves, + MWWorld::InventoryStore::Slot_Boots + }; + + for (unsigned int i=0; i health = creatureStats.getHealth(); for (unsigned int i=0; i boundItemsMap; if (boundItemsMap.empty()) { - boundItemsMap[ESM::MagicEffect::BoundBattleAxe] = "battle_axe"; - boundItemsMap[ESM::MagicEffect::BoundBoots] = "boots"; - boundItemsMap[ESM::MagicEffect::BoundCuirass] = "cuirass"; - boundItemsMap[ESM::MagicEffect::BoundDagger] = "dagger"; - boundItemsMap[ESM::MagicEffect::BoundGloves] = "gauntlet"; // Note: needs both _left and _right variants, see below - boundItemsMap[ESM::MagicEffect::BoundHelm] = "helm"; - boundItemsMap[ESM::MagicEffect::BoundLongbow] = "longbow"; - boundItemsMap[ESM::MagicEffect::BoundLongsword] = "longsword"; - boundItemsMap[ESM::MagicEffect::BoundMace] = "mace"; - boundItemsMap[ESM::MagicEffect::BoundShield] = "shield"; - boundItemsMap[ESM::MagicEffect::BoundSpear] = "spear"; + boundItemsMap[ESM::MagicEffect::BoundBattleAxe] = "sMagicBoundBattleAxeID"; + boundItemsMap[ESM::MagicEffect::BoundBoots] = "sMagicBoundBootsID"; + boundItemsMap[ESM::MagicEffect::BoundCuirass] = "sMagicBoundCuirassID"; + boundItemsMap[ESM::MagicEffect::BoundDagger] = "sMagicBoundDaggerID"; + boundItemsMap[ESM::MagicEffect::BoundGloves] = "sMagicBoundLeftGauntletID"; // Note: needs RightGauntlet variant too (see below) + boundItemsMap[ESM::MagicEffect::BoundHelm] = "sMagicBoundHelmID"; + boundItemsMap[ESM::MagicEffect::BoundLongbow] = "sMagicBoundLongbowID"; + boundItemsMap[ESM::MagicEffect::BoundLongsword] = "sMagicBoundLongswordID"; + boundItemsMap[ESM::MagicEffect::BoundMace] = "sMagicBoundMaceID"; + boundItemsMap[ESM::MagicEffect::BoundShield] = "sMagicBoundShieldID"; + boundItemsMap[ESM::MagicEffect::BoundSpear] = "sMagicBoundSpearID"; } for (std::map::iterator it = boundItemsMap.begin(); it != boundItemsMap.end(); ++it) { bool found = creatureStats.mBoundItems.find(it->first) != creatureStats.mBoundItems.end(); - int magnitude = creatureStats.getMagicEffects().get(EffectKey(it->first)).mMagnitude; + int magnitude = creatureStats.getMagicEffects().get(it->first).mMagnitude; if (found != (magnitude > 0)) { - std::string item = "bound_" + it->second; + std::string itemGmst = it->second; + std::string item = MWBase::Environment::get().getWorld()->getStore().get().find( + itemGmst)->getString(); if (it->first == ESM::MagicEffect::BoundGloves) { - adjustBoundItem(item + "_left", magnitude > 0, ptr); - adjustBoundItem(item + "_right", magnitude > 0, ptr); + adjustBoundItem("sMagicBoundLeftGauntletID", magnitude > 0, ptr); + adjustBoundItem("sMagicBoundRightGauntletID", magnitude > 0, ptr); } else adjustBoundItem(item, magnitude > 0, ptr); @@ -319,32 +473,34 @@ namespace MWMechanics static std::map summonMap; if (summonMap.empty()) { - summonMap[ESM::MagicEffect::SummonAncestralGhost] = "ancestor_ghost_summon"; - summonMap[ESM::MagicEffect::SummonBear] = "BM_bear_black_summon"; - summonMap[ESM::MagicEffect::SummonBonelord] = "bonelord_summon"; - summonMap[ESM::MagicEffect::SummonBonewalker] = "bonewalker_summon"; - summonMap[ESM::MagicEffect::SummonBonewolf] = "BM_wolf_bone_summon"; - summonMap[ESM::MagicEffect::SummonCenturionSphere] = "centurion_sphere_summon"; - summonMap[ESM::MagicEffect::SummonClannfear] = "clannfear_summon"; - summonMap[ESM::MagicEffect::SummonDaedroth] = "daedroth_summon"; - summonMap[ESM::MagicEffect::SummonDremora] = "dremora_summon"; - summonMap[ESM::MagicEffect::SummonFabricant] = "fabricant_summon"; - summonMap[ESM::MagicEffect::SummonFlameAtronach] = "atronach_flame_summon"; - summonMap[ESM::MagicEffect::SummonFrostAtronach] = "atronach_frost_summon"; - summonMap[ESM::MagicEffect::SummonGoldenSaint] = "golden saint_summon"; - summonMap[ESM::MagicEffect::SummonGreaterBonewalker] = "bonewalker_greater_summ"; - summonMap[ESM::MagicEffect::SummonHunger] = "hunger_summon"; - summonMap[ESM::MagicEffect::SummonScamp] = "scamp_summon"; - summonMap[ESM::MagicEffect::SummonSkeletalMinion] = "skeleton_summon"; - summonMap[ESM::MagicEffect::SummonStormAtronach] = "atronach_storm_summon"; - summonMap[ESM::MagicEffect::SummonWingedTwilight] = "winged twilight_summon"; - summonMap[ESM::MagicEffect::SummonWolf] = "BM_wolf_grey_summon"; + summonMap[ESM::MagicEffect::SummonAncestralGhost] = "sMagicAncestralGhostID"; + summonMap[ESM::MagicEffect::SummonBonelord] = "sMagicBonelordID"; + summonMap[ESM::MagicEffect::SummonBonewalker] = "sMagicLeastBonewalkerID"; + summonMap[ESM::MagicEffect::SummonCenturionSphere] = "sMagicCenturionSphereID"; + summonMap[ESM::MagicEffect::SummonClannfear] = "sMagicClannfearID"; + summonMap[ESM::MagicEffect::SummonDaedroth] = "sMagicDaedrothID"; + summonMap[ESM::MagicEffect::SummonDremora] = "sMagicDremoraID"; + summonMap[ESM::MagicEffect::SummonFabricant] = "sMagicFabricantID"; + summonMap[ESM::MagicEffect::SummonFlameAtronach] = "sMagicFlameAtronachID"; + summonMap[ESM::MagicEffect::SummonFrostAtronach] = "sMagicFrostAtronachID"; + summonMap[ESM::MagicEffect::SummonGoldenSaint] = "sMagicGoldenSaintID"; + summonMap[ESM::MagicEffect::SummonGreaterBonewalker] = "sMagicGreaterBonewalkerID"; + summonMap[ESM::MagicEffect::SummonHunger] = "sMagicHungerID"; + summonMap[ESM::MagicEffect::SummonScamp] = "sMagicScampID"; + summonMap[ESM::MagicEffect::SummonSkeletalMinion] = "sMagicSkeletalMinionID"; + summonMap[ESM::MagicEffect::SummonStormAtronach] = "sMagicStormAtronachID"; + summonMap[ESM::MagicEffect::SummonWingedTwilight] = "sMagicWingedTwilightID"; + summonMap[ESM::MagicEffect::SummonWolf] = "sMagicCreature01ID"; + summonMap[ESM::MagicEffect::SummonBear] = "sMagicCreature02ID"; + summonMap[ESM::MagicEffect::SummonBonewolf] = "sMagicCreature03ID"; + summonMap[ESM::MagicEffect::SummonCreature04] = "sMagicCreature04ID"; + summonMap[ESM::MagicEffect::SummonCreature05] = "sMagicCreature05ID"; } for (std::map::iterator it = summonMap.begin(); it != summonMap.end(); ++it) { bool found = creatureStats.mSummonedCreatures.find(it->first) != creatureStats.mSummonedCreatures.end(); - int magnitude = creatureStats.getMagicEffects().get(EffectKey(it->first)).mMagnitude; + int magnitude = creatureStats.getMagicEffects().get(it->first).mMagnitude; if (found != (magnitude > 0)) { if (magnitude > 0) @@ -361,15 +517,20 @@ namespace MWMechanics ipos.rot[1] = 0; ipos.rot[2] = 0; - MWWorld::CellStore* store = ptr.getCell(); - MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), it->second, 1); - ref.getPtr().getCellRef().mPos = ipos; + std::string creatureID = + MWBase::Environment::get().getWorld()->getStore().get().find(it->second)->getString(); - // TODO: Add AI to follow player and fight for him + if (!creatureID.empty()) + { + MWWorld::CellStore* store = ptr.getCell(); + MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), creatureID, 1); + ref.getPtr().getCellRef().mPos = ipos; - creatureStats.mSummonedCreatures.insert(std::make_pair(it->first, - MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),*store,ipos).getRefData().getHandle())); + // TODO: Add AI to follow player and fight for him + creatureStats.mSummonedCreatures.insert(std::make_pair(it->first, + MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),*store,ipos).getRefData().getHandle())); + } } else { @@ -395,7 +556,7 @@ namespace MWMechanics // skills for(int i = 0;i < ESM::Skill::Length;++i) { - Stat& skill = npcStats.getSkill(i); + SkillValue& skill = npcStats.getSkill(i); skill.setModifier(effects.get(EffectKey(ESM::MagicEffect::FortifySkill, i)).mMagnitude - effects.get(EffectKey(ESM::MagicEffect::DrainSkill, i)).mMagnitude - effects.get(EffectKey(ESM::MagicEffect::AbsorbSkill, i)).mMagnitude); @@ -439,15 +600,69 @@ namespace MWMechanics void Actors::updateEquippedLight (const MWWorld::Ptr& ptr, float duration) { - //If holding a light... + bool isPlayer = ptr.getRefData().getHandle()=="player"; + MWWorld::InventoryStore &inventoryStore = MWWorld::Class::get(ptr).getInventoryStore(ptr); MWWorld::ContainerStoreIterator heldIter = inventoryStore.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); + /** + * Automatically equip NPCs torches at night and unequip them at day + */ + if (!isPlayer) + { + MWWorld::ContainerStoreIterator torch = inventoryStore.end(); + for (MWWorld::ContainerStoreIterator it = inventoryStore.begin(); it != inventoryStore.end(); ++it) + { + if (it->getTypeName() == typeid(ESM::Light).name()) + { + torch = it; + break; + } + } + + if (MWBase::Environment::get().getWorld()->isDark()) + { + if (torch != inventoryStore.end()) + { + if (!MWWorld::Class::get (ptr).getCreatureStats (ptr).isHostile()) + { + // For non-hostile NPCs, unequip whatever is in the left slot in favor of a light. + if (heldIter != inventoryStore.end() && heldIter->getTypeName() != typeid(ESM::Light).name()) + inventoryStore.unequipItem(*heldIter, ptr); + // Also unequip twohanded weapons which conflict with anything in CarriedLeft + if (torch->getClass().canBeEquipped(*torch, ptr).first == 3) + inventoryStore.unequipSlot(MWWorld::InventoryStore::Slot_CarriedRight, ptr); + } + + heldIter = inventoryStore.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); + + // If we have a torch and can equip it (left slot free, no + // twohanded weapon in right slot), then equip it now. + if (heldIter == inventoryStore.end() + && torch->getClass().canBeEquipped(*torch, ptr).first == 1) + { + inventoryStore.equip(MWWorld::InventoryStore::Slot_CarriedLeft, torch, ptr); + } + } + } + else + { + if (heldIter != inventoryStore.end() && heldIter->getTypeName() == typeid(ESM::Light).name()) + { + // At day, unequip lights and auto equip shields or other suitable items + // (Note: autoEquip will ignore lights) + inventoryStore.autoEquip(ptr); + } + } + } + + heldIter = inventoryStore.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); + + //If holding a light... if(heldIter.getType() == MWWorld::ContainerStore::Type_Light) { // Use time from the player's light - bool isPlayer = ptr.getRefData().getHandle()=="player"; if(isPlayer) { float timeRemaining = heldIter->getClass().getRemainingUsageTime(*heldIter); @@ -482,6 +697,16 @@ namespace MWMechanics Actors::Actors() {} + Actors::~Actors() + { + PtrControllerMap::iterator it(mActors.begin()); + for (; it != mActors.end(); ++it) + { + delete it->second; + it->second = NULL; + } + } + void Actors::addActor (const MWWorld::Ptr& ptr) { // erase previous death events since we are currently only tracking them while in an active cell @@ -572,6 +797,18 @@ namespace MWMechanics } } + // Apply soultrap + if (iter->first.getTypeName() == typeid(ESM::Creature).name()) + { + SoulTrap soulTrap (iter->first); + stats.getActiveSpells().visitEffectSources(soulTrap); + } + + // Reset magic effects and recalculate derived effects + // One case where we need this is to make sure bound items are removed upon death + stats.setMagicEffects(MWMechanics::MagicEffects()); + calculateCreatureStatModifiers(iter->first, 0); + if (iter->second->kill()) { ++mDeathCount[cls.getId(iter->first)]; @@ -592,7 +829,12 @@ namespace MWMechanics iter->second->updateContinuousVfx(); for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter) + { + if (iter->first.getClass().getCreatureStats(iter->first).getMagicEffects().get( + ESM::MagicEffect::Paralyze).mMagnitude > 0) + iter->second->skipAnim(); iter->second->update(duration); + } } } void Actors::restoreDynamicStats() diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 6afdefdbdc..83aff63e3c 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -25,14 +25,12 @@ namespace MWMechanics { class Actors { - typedef std::map PtrControllerMap; - PtrControllerMap mActors; - - std::map mDeathCount; - - void updateNpc(const MWWorld::Ptr &ptr, float duration, bool paused); + typedef std::map PtrControllerMap; + PtrControllerMap mActors; + std::map mDeathCount; + void updateNpc(const MWWorld::Ptr &ptr, float duration, bool paused); void adjustMagicEffects (const MWWorld::Ptr& creature); @@ -50,6 +48,7 @@ namespace MWMechanics public: Actors(); + ~Actors(); /// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently /// paused we may want to do it manually (after equipping permanent enchantment) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 39a97df6c1..6f643aa68c 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -91,6 +91,8 @@ namespace MWMechanics mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]); float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); + + // TODO: use movement settings instead of rotating directly MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false); MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; @@ -105,6 +107,7 @@ namespace MWMechanics float directionResult = sqrt(directionX * directionX + directionY * directionY); zAngle = Ogre::Radian( acos(directionY / directionResult) * sgn(asin(directionX / directionResult)) ).valueDegrees(); + // TODO: use movement settings instead of rotating directly MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false); mPathFinder.clearPath(); diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index 3615c8546e..5099625c06 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -161,6 +161,7 @@ namespace MWMechanics if(distanceBetweenResult <= mMaxDist * mMaxDist) { float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); + // TODO: use movement settings instead of rotating directly MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false); MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; mMaxDist = 470; diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index 08d7586388..f56c759967 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -97,6 +97,7 @@ namespace MWMechanics } float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); + // TODO: use movement settings instead of rotating directly world->rotateObject(actor, 0, 0, zAngle, false); movement.mPosition[1] = 1; diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index f66f7ca620..93c94a3f49 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -236,6 +236,7 @@ namespace MWMechanics if(mWalking) { float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); + // TODO: use movement settings instead of rotating directly world->rotateObject(actor, 0, 0, zAngle, false); MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 614d697ec5..bc2773b6ef 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -351,7 +351,6 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim , mSkipAnim(false) , mSecondsOfRunning(0) , mSecondsOfSwimming(0) - , mFallHeight(0) { if(!mAnimation) return; @@ -427,10 +426,10 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun { forcestateupdate = true; - // Shields shouldn't be visible during spellcasting + // Shields/torches shouldn't be visible during spellcasting or hand-to-hand // There seems to be no text keys for this purpose, except maybe for "[un]equip start/stop", // but they are also present in weapon drawing animation. - mAnimation->showShield(weaptype != WeapType_Spell); + mAnimation->showCarriedLeft(weaptype != WeapType_Spell && weaptype != WeapType_HandToHand); std::string weapgroup; if(weaptype == WeapType_None) @@ -502,13 +501,25 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun { if(mUpperBodyState == UpperCharState_WeapEquiped) { + MWBase::Environment::get().getWorld()->breakInvisibility(mPtr); mAttackType.clear(); if(mWeaponType == WeapType_Spell) { + // Unset casting flag, otherwise pressing the mouse button down would + // continue casting every frame if there is no animation + mPtr.getClass().getCreatureStats(mPtr).setAttackingOrSpell(false); + const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - const std::string spellid = stats.getSpells().getSelectedSpell(); - if(!spellid.empty()) + // For the player, set the spell we want to cast + // This has to be done at the start of the casting animation, + // *not* when selecting a spell in the GUI (otherwise you could change the spell mid-animation) + if (mPtr.getRefData().getHandle() == "player") + stats.getSpells().setSelectedSpell(MWBase::Environment::get().getWindowManager()->getSelectedSpell()); + + std::string spellid = stats.getSpells().getSelectedSpell(); + + if(!spellid.empty() && MWBase::Environment::get().getWorld()->startSpellCast(mPtr)) { static const std::string schools[] = { "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" @@ -707,17 +718,18 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun } } - MWWorld::ContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); - if(torch != inv.end() && torch->getTypeName() == typeid(ESM::Light).name()) + if(torch != inv.end() && torch->getTypeName() == typeid(ESM::Light).name() + && mWeaponType != WeapType_Spell && mWeaponType != WeapType_HandToHand) + { - if(!mAnimation->isPlaying("torch")) - mAnimation->play("torch", Priority_Torch, - MWRender::Animation::Group_LeftArm, false, - 1.0f, "start", "stop", 0.0f, (~(size_t)0)); + mAnimation->play("torch", Priority_Torch, MWRender::Animation::Group_LeftArm, + false, 1.0f, "start", "stop", 0.0f, (~(size_t)0)); } - else if(mAnimation->isPlaying("torch")) + else if (mAnimation->isPlaying("torch")) + { mAnimation->disable("torch"); + } return forcestateupdate; } @@ -728,6 +740,8 @@ void CharacterController::update(float duration) const MWWorld::Class &cls = MWWorld::Class::get(mPtr); Ogre::Vector3 movement(0.0f); + updateVisibility(); + if(!cls.isActor()) { if(mAnimQueue.size() > 1) @@ -787,10 +801,10 @@ void CharacterController::update(float duration) } if(sneak || inwater || flying) - { vec.z = 0.0f; - mFallHeight = mPtr.getRefData().getPosition().pos[2]; - } + + if (inwater || flying) + cls.getCreatureStats(mPtr).land(); if(!onground && !flying && !inwater) { @@ -799,11 +813,7 @@ void CharacterController::update(float duration) if (world->isSlowFalling(mPtr)) { // SlowFalling spell effect is active, do not keep previous fall height - mFallHeight = mPtr.getRefData().getPosition().pos[2]; - } - else - { - mFallHeight = std::max(mFallHeight, mPtr.getRefData().getPosition().pos[2]); + cls.getCreatureStats(mPtr).land(); } const MWWorld::Store &gmst = world->getStore().get(); @@ -859,7 +869,8 @@ void CharacterController::update(float duration) mJumpState = JumpState_Landing; vec.z = 0.0f; - float healthLost = cls.getFallDamage(mPtr, mFallHeight - mPtr.getRefData().getPosition().pos[2]); + float height = cls.getCreatureStats(mPtr).land(); + float healthLost = cls.getFallDamage(mPtr, height); if (healthLost > 0.0f) { const float fatigueTerm = cls.getCreatureStats(mPtr).getFatigueTerm(); @@ -880,8 +891,6 @@ void CharacterController::update(float duration) //TODO: actor falls over } } - - mFallHeight = mPtr.getRefData().getPosition().pos[2]; } else { @@ -920,6 +929,9 @@ void CharacterController::update(float duration) } } + if (onground) + cls.getCreatureStats(mPtr).land(); + if(movestate != CharState_None) clearAnimQueue(); @@ -944,9 +956,12 @@ void CharacterController::update(float duration) refreshCurrentAnims(idlestate, movestate, forcestateupdate); rot *= duration * Ogre::Math::RadiansToDegrees(1.0f); - world->rotateObject(mPtr, rot.x, rot.y, rot.z, true); - world->queueMovement(mPtr, vec); + if (!mSkipAnim) + { + world->rotateObject(mPtr, rot.x, rot.y, rot.z, true); + world->queueMovement(mPtr, vec); + } movement = vec; } else if(cls.getCreatureStats(mPtr).isDead()) @@ -1141,4 +1156,25 @@ void CharacterController::updateContinuousVfx() } } +void CharacterController::updateVisibility() +{ + if (!mPtr.getClass().isActor()) + return; + float alpha = 1.f; + if (mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Invisibility).mMagnitude) + { + if (mPtr.getRefData().getHandle() == "player") + alpha = 0.4f; + else + alpha = 0.f; + } + float chameleon = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Chameleon).mMagnitude; + if (chameleon) + { + alpha *= std::max(0.2f, (100.f - chameleon)/100.f); + } + + mAnimation->setAlpha(alpha); +} + } diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 5e7a7c6d41..3c32de2945 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -156,9 +156,6 @@ class CharacterController float mSecondsOfSwimming; float mSecondsOfRunning; - // used for acrobatics progress and fall damages - float mFallHeight; - std::string mAttackType; // slash, chop or thrust void refreshCurrentAnims(CharacterState idle, CharacterState movement, bool force=false); @@ -173,6 +170,8 @@ class CharacterController bool updateNpcState(bool onground, bool inwater, bool isrunning, bool sneak); + void updateVisibility(); + public: CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim); virtual ~CharacterController(); diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 345b5a1563..85f6cfdbc0 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -14,7 +14,8 @@ namespace MWMechanics mTalkedTo (false), mAlarmed (false), mAttacked (false), mHostile (false), mAttackingOrSpell(false), mAttackType(AT_Chop), - mIsWerewolf(false) + mIsWerewolf(false), + mFallHeight(0), mRecalcDynamicStats(false) { for (int i=0; i<4; ++i) mAiSettings[i] = 0; @@ -73,7 +74,7 @@ namespace MWMechanics - gmst.find ("fFatigueMult")->getFloat() * (1-normalised); } - const Stat &CreatureStats::getAttribute(int index) const + const AttributeValue &CreatureStats::getAttribute(int index) const { if (index < 0 || index > 7) { throw std::runtime_error("attribute index is out of range"); @@ -121,20 +122,12 @@ namespace MWMechanics return mLevel; } - int CreatureStats::getAiSetting (int index) const + Stat CreatureStats::getAiSetting (AiSetting index) const { assert (index>=0 && index<4); return mAiSettings[index]; } - Stat &CreatureStats::getAttribute(int index) - { - if (index < 0 || index > 7) { - throw std::runtime_error("attribute index is out of range"); - } - return (!mIsWerewolf ? mAttributes[index] : mWerewolfAttributes[index]); - } - const DynamicStat &CreatureStats::getDynamic(int index) const { if (index < 0 || index > 2) { @@ -163,11 +156,29 @@ namespace MWMechanics return mMagicEffects; } - void CreatureStats::setAttribute(int index, const Stat &value) + void CreatureStats::setAttribute(int index, int base) + { + AttributeValue current = getAttribute(index); + current.setBase(base); + setAttribute(index, current); + } + + void CreatureStats::setAttribute(int index, const AttributeValue &value) { if (index < 0 || index > 7) { throw std::runtime_error("attribute index is out of range"); } + + const AttributeValue& currentValue = !mIsWerewolf ? mAttributes[index] : mWerewolfAttributes[index]; + + if (value != currentValue) + { + if (index != ESM::Attribute::Luck + && index != ESM::Attribute::Personality + && index != ESM::Attribute::Speed) + mRecalcDynamicStats = true; + } + if(!mIsWerewolf) mAttributes[index] = value; else @@ -217,6 +228,10 @@ namespace MWMechanics void CreatureStats::setMagicEffects(const MagicEffects &effects) { + if (effects.get(ESM::MagicEffect::FortifyMaximumMagicka).mMagnitude + != mMagicEffects.get(ESM::MagicEffect::FortifyMaximumMagicka).mMagnitude) + mRecalcDynamicStats = true; + mMagicEffects = effects; } @@ -225,12 +240,18 @@ namespace MWMechanics mAttackingOrSpell = attackingOrSpell; } - void CreatureStats::setAiSetting (int index, int value) + void CreatureStats::setAiSetting (AiSetting index, Stat value) { assert (index>=0 && index<4); mAiSettings[index] = value; } + void CreatureStats::setAiSetting (AiSetting index, int base) + { + Stat stat(base); + setAiSetting(index, stat); + } + bool CreatureStats::isDead() const { return mDead; @@ -251,8 +272,10 @@ namespace MWMechanics if (mDead) { if (mDynamic[0].getCurrent()<1) - mDynamic[0].setCurrent (1); - + { + mDynamic[0].setModified(mDynamic[0].getModified(), 1); + mDynamic[0].setCurrent(1); + } if (mDynamic[0].getCurrent()>=1) mDead = false; } @@ -328,7 +351,7 @@ namespace MWMechanics float evasion = (getAttribute(ESM::Attribute::Agility).getModified() / 5.0f) + (getAttribute(ESM::Attribute::Luck).getModified() / 10.0f); evasion *= getFatigueTerm(); - evasion += mMagicEffects.get(EffectKey(ESM::MagicEffect::Sanctuary)).mMagnitude; + evasion += mMagicEffects.get(ESM::MagicEffect::Sanctuary).mMagnitude; return evasion; } @@ -356,4 +379,26 @@ namespace MWMechanics { mUsedPowers[power] = MWBase::Environment::get().getWorld()->getTimeStamp(); } + + void CreatureStats::addToFallHeight(float height) + { + mFallHeight += height; + } + + float CreatureStats::land() + { + float height = mFallHeight; + mFallHeight = 0; + return height; + } + + bool CreatureStats::needToRecalcDynamicStats() + { + if (mRecalcDynamicStats) + { + mRecalcDynamicStats = false; + return true; + } + return false; + } } diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index f28f50fc67..322970e738 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -18,13 +18,13 @@ namespace MWMechanics /// class CreatureStats { - Stat mAttributes[8]; + AttributeValue mAttributes[8]; DynamicStat mDynamic[3]; // health, magicka, fatigue int mLevel; Spells mSpells; ActiveSpells mActiveSpells; MagicEffects mMagicEffects; - int mAiSettings[4]; + Stat mAiSettings[4]; AiSequence mAiSequence; float mLevelHealthBonus; bool mDead; @@ -36,23 +36,36 @@ namespace MWMechanics bool mHostile; bool mAttackingOrSpell;//for the player, this is true if the left mouse button is pressed, false if not. + float mFallHeight; + int mAttackType; std::string mLastHitObject; // The last object to hit this actor + // Do we need to recalculate stats derived from attributes or other factors? + bool mRecalcDynamicStats; + std::map mUsedPowers; protected: bool mIsWerewolf; - Stat mWerewolfAttributes[8]; + AttributeValue mWerewolfAttributes[8]; public: CreatureStats(); + bool needToRecalcDynamicStats(); + + void addToFallHeight(float height); + + /// Reset the fall height + /// @return total fall height + float land(); + bool canUsePower (const std::string& power) const; void usePower (const std::string& power); - const Stat & getAttribute(int index) const; + const AttributeValue & getAttribute(int index) const; const DynamicStat & getHealth() const; @@ -72,18 +85,15 @@ namespace MWMechanics int getLevel() const; - int getAiSetting (int index) const; - ///< 0: hello, 1 fight, 2 flee, 3 alarm - - Stat & getAttribute(int index); - Spells & getSpells(); ActiveSpells & getActiveSpells(); MagicEffects & getMagicEffects(); - void setAttribute(int index, const Stat &value); + void setAttribute(int index, const AttributeValue &value); + // Shortcut to set only the base + void setAttribute(int index, int base); void setHealth(const DynamicStat &value); @@ -112,8 +122,16 @@ namespace MWMechanics void setLevel(int level); - void setAiSetting (int index, int value); - ///< 0: hello, 1 fight, 2 flee, 3 alarm + enum AiSetting + { + AI_Hello, + AI_Fight, + AI_Flee, + AI_Alarm + }; + void setAiSetting (AiSetting index, Stat value); + void setAiSetting (AiSetting index, int base); + Stat getAiSetting (AiSetting index) const; const AiSequence& getAiSequence() const; diff --git a/apps/openmw/mwmechanics/disease.hpp b/apps/openmw/mwmechanics/disease.hpp new file mode 100644 index 0000000000..d3ea825cf5 --- /dev/null +++ b/apps/openmw/mwmechanics/disease.hpp @@ -0,0 +1,53 @@ +#ifndef OPENMW_MECHANICS_DISEASE_H +#define OPENMW_MECHANICS_DISEASE_H + +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" +#include "../mwworld/ptr.hpp" +#include "../mwworld/class.hpp" +#include "../mwmechanics/spells.hpp" +#include "../mwmechanics/creaturestats.hpp" + +namespace MWMechanics +{ + + /// Call when \a actor has got in contact with \a carrier (e.g. hit by him, or loots him) + inline void diseaseContact (MWWorld::Ptr actor, MWWorld::Ptr carrier) + { + if (!carrier.getClass().isActor()) + return; + + float fDiseaseXferChance = + MWBase::Environment::get().getWorld()->getStore().get().find( + "fDiseaseXferChance")->getFloat(); + + Spells& spells = carrier.getClass().getCreatureStats(carrier).getSpells(); + for (Spells::TIterator it = spells.begin(); it != spells.end(); ++it) + { + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(it->first); + if (spell->mData.mType == ESM::Spell::ST_Disease + || spell->mData.mType == ESM::Spell::ST_Blight) + { + float roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + if (roll < fDiseaseXferChance) + { + // Contracted disease! + actor.getClass().getCreatureStats(actor).getSpells().add(it->first); + + if (actor.getRefData().getHandle() == "player") + { + std::string msg = "sMagicContractDisease"; + msg = MWBase::Environment::get().getWorld()->getStore().get().find(msg)->getString(); + if (msg.find("%s") != std::string::npos) + msg.replace(msg.find("%s"), 2, spell->mName); + MWBase::Environment::get().getWindowManager()->messageBox(msg); + } + } + } + } + } +} + + +#endif diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index 7e11acdb0c..3991454dbc 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -90,7 +90,7 @@ namespace MWMechanics // Add the new item to player inventory and remove the old one store.remove(mOldItemPtr, 1, player); - store.add(newItemPtr, player); + store.add(newItemPtr, 1, player); if(!mSelfEnchanting) payForEnchantment(); diff --git a/apps/openmw/mwmechanics/magiceffects.hpp b/apps/openmw/mwmechanics/magiceffects.hpp index 2c1b363b7d..45abda21d9 100644 --- a/apps/openmw/mwmechanics/magiceffects.hpp +++ b/apps/openmw/mwmechanics/magiceffects.hpp @@ -56,7 +56,8 @@ namespace MWMechanics struct EffectSourceVisitor { virtual void visit (MWMechanics::EffectKey key, - const std::string& sourceName, float magnitude, float remainingTime = -1) = 0; + const std::string& sourceName, const std::string& casterHandle, + float magnitude, float remainingTime = -1) = 0; }; /// \brief Effects currently affecting a NPC or creature diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index b6a7a7b755..a4d1b6fec1 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -12,6 +12,8 @@ #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" +#include "spellcasting.hpp" + namespace MWMechanics { void MechanicsManager::buildPlayer() @@ -31,15 +33,14 @@ namespace MWMechanics for (int i=0; i<27; ++i) npcStats.getSkill (i).setBase (player->mNpdt52.mSkills[i]); - creatureStats.getAttribute(0).setBase (player->mNpdt52.mStrength); - creatureStats.getAttribute(1).setBase (player->mNpdt52.mIntelligence); - creatureStats.getAttribute(2).setBase (player->mNpdt52.mWillpower); - creatureStats.getAttribute(3).setBase (player->mNpdt52.mAgility); - creatureStats.getAttribute(4).setBase (player->mNpdt52.mSpeed); - creatureStats.getAttribute(5).setBase (player->mNpdt52.mEndurance); - creatureStats.getAttribute(6).setBase (player->mNpdt52.mPersonality); - creatureStats.getAttribute(7).setBase (player->mNpdt52.mLuck); - + creatureStats.setAttribute(ESM::Attribute::Strength, player->mNpdt52.mStrength); + creatureStats.setAttribute(ESM::Attribute::Intelligence, player->mNpdt52.mIntelligence); + creatureStats.setAttribute(ESM::Attribute::Willpower, player->mNpdt52.mWillpower); + creatureStats.setAttribute(ESM::Attribute::Agility, player->mNpdt52.mAgility); + creatureStats.setAttribute(ESM::Attribute::Speed, player->mNpdt52.mSpeed); + creatureStats.setAttribute(ESM::Attribute::Endurance, player->mNpdt52.mEndurance); + creatureStats.setAttribute(ESM::Attribute::Personality, player->mNpdt52.mPersonality); + creatureStats.setAttribute(ESM::Attribute::Luck, player->mNpdt52.mLuck); const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); @@ -55,7 +56,7 @@ namespace MWMechanics { const ESM::Race::MaleFemale& attribute = race->mData.mAttributeValues[i]; - creatureStats.getAttribute(i).setBase (male ? attribute.mMale : attribute.mFemale); + creatureStats.setAttribute(i, male ? attribute.mMale : attribute.mFemale); } for (int i=0; i<27; ++i) @@ -106,7 +107,7 @@ namespace MWMechanics int attribute = class_->mData.mAttribute[i]; if (attribute>=0 && attribute<8) { - creatureStats.getAttribute(attribute).setBase ( + creatureStats.setAttribute(attribute, creatureStats.getAttribute(attribute).getBase() + 10); } } @@ -124,6 +125,19 @@ namespace MWMechanics npcStats.getSkill (index).setBase ( npcStats.getSkill (index).getBase() + bonus); } + + if (i==1) + { + // Major skill - add starting spells for this skill if existing + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + MWWorld::Store::iterator it = store.get().begin(); + for (; it != store.get().end(); ++it) + { + if (it->mData.mFlags & ESM::Spell::F_PCStart + && spellSchoolToSkill(getSpellSchool(&*it, ptr)) == index) + creatureStats.getSpells().add(it->mId); + } + } } } @@ -467,6 +481,8 @@ namespace MWMechanics if (playerStats.getDrawState() == MWMechanics::DrawState_Weapon) x += MWBase::Environment::get().getWorld()->getStore().get().find("fDispWeaponDrawn")->getFloat(); + x += ptr.getClass().getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::Charm).mMagnitude; + int effective_disposition = std::max(0,std::min(int(x),100));//, normally clamped to [0..100] when used return effective_disposition; } @@ -485,10 +501,10 @@ namespace MWMechanics // otherwise one would get different prices when exiting and re-entering the dialogue window... int clampedDisposition = std::max(0, std::min(getDerivedDisposition(ptr) + MWBase::Environment::get().getDialogueManager()->getTemporaryDispositionChange(),100)); - float a = std::min(playerStats.getSkill(ESM::Skill::Mercantile).getModified(), 100.f); + float a = std::min(playerStats.getSkill(ESM::Skill::Mercantile).getModified(), 100); float b = std::min(0.1f * playerStats.getAttribute(ESM::Attribute::Luck).getModified(), 10.f); float c = std::min(0.2f * playerStats.getAttribute(ESM::Attribute::Personality).getModified(), 10.f); - float d = std::min(sellerStats.getSkill(ESM::Skill::Mercantile).getModified(), 100.f); + float d = std::min(sellerStats.getSkill(ESM::Skill::Mercantile).getModified(), 100); float e = std::min(0.1f * sellerStats.getAttribute(ESM::Attribute::Luck).getModified(), 10.f); float f = std::min(0.2f * sellerStats.getAttribute(ESM::Attribute::Personality).getModified(), 10.f); @@ -591,8 +607,12 @@ namespace MWMechanics { float s = int(r * fPerDieRollMult * fPerTempMult); - npcStats.setAiSetting (2, std::max(0, std::min(100, npcStats.getAiSetting (2) + int(std::max(iPerMinChange, s))))); - npcStats.setAiSetting (1, std::max(0, std::min(100, npcStats.getAiSetting (1) + int(std::min(-iPerMinChange, -s))))); + int flee = npcStats.getAiSetting(MWMechanics::CreatureStats::AI_Flee).getBase(); + int fight = npcStats.getAiSetting(MWMechanics::CreatureStats::AI_Fight).getBase(); + npcStats.setAiSetting (MWMechanics::CreatureStats::AI_Flee, + std::max(0, std::min(100, flee + int(std::max(iPerMinChange, s))))); + npcStats.setAiSetting (MWMechanics::CreatureStats::AI_Fight, + std::max(0, std::min(100, fight + int(std::min(-iPerMinChange, -s))))); } float c = -std::abs(int(r * fPerDieRollMult)); @@ -626,8 +646,12 @@ namespace MWMechanics { float s = c * fPerDieRollMult * fPerTempMult; - npcStats.setAiSetting (2, std::max(0, std::min(100, npcStats.getAiSetting (2) + std::min(-int(iPerMinChange), int(-s))))); - npcStats.setAiSetting (1, std::max(0, std::min(100, npcStats.getAiSetting (1) + std::max(int(iPerMinChange), int(s))))); + int flee = npcStats.getAiSetting (CreatureStats::AI_Flee).getBase(); + int fight = npcStats.getAiSetting (CreatureStats::AI_Fight).getBase(); + npcStats.setAiSetting (CreatureStats::AI_Flee, + std::max(0, std::min(100, flee + std::min(-int(iPerMinChange), int(-s))))); + npcStats.setAiSetting (CreatureStats::AI_Fight, + std::max(0, std::min(100, fight + std::max(int(iPerMinChange), int(s))))); } x = int(-c * fPerDieRollMult); diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index 0b36982890..94dd971867 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -28,7 +28,6 @@ MWMechanics::NpcStats::NpcStats() , mBounty (0) , mLevelProgress(0) , mDisposition(0) -, mVampire (0) , mReputation(0) , mWerewolfKills (0) , mProfit(0) @@ -84,17 +83,17 @@ void MWMechanics::NpcStats::setMovementFlag (Flag flag, bool state) mMovementFlags &= ~flag; } -const MWMechanics::Stat& MWMechanics::NpcStats::getSkill (int index) const +const MWMechanics::SkillValue& MWMechanics::NpcStats::getSkill (int index) const { - if (index<0 || index>=27) + if (index<0 || index>=ESM::Skill::Length) throw std::runtime_error ("skill index out of range"); return (!mIsWerewolf ? mSkill[index] : mWerewolfSkill[index]); } -MWMechanics::Stat& MWMechanics::NpcStats::getSkill (int index) +MWMechanics::SkillValue& MWMechanics::NpcStats::getSkill (int index) { - if (index<0 || index>=27) + if (index<0 || index>=ESM::Skill::Length) throw std::runtime_error ("skill index out of range"); return (!mIsWerewolf ? mSkill[index] : mWerewolfSkill[index]); @@ -197,34 +196,25 @@ void MWMechanics::NpcStats::useSkill (int skillIndex, const ESM::Class& class_, if(mIsWerewolf) return; - float base = getSkill (skillIndex).getBase(); + MWMechanics::SkillValue value = getSkill (skillIndex); - int level = static_cast (base); + value.setProgress(value.getProgress() + getSkillGain (skillIndex, class_, usageType)); - base += getSkillGain (skillIndex, class_, usageType); - - if (static_cast (base)!=level) + if (value.getProgress()>=1) { // skill leveled up increaseSkill(skillIndex, class_, false); } - else - getSkill (skillIndex).setBase (base); } void MWMechanics::NpcStats::increaseSkill(int skillIndex, const ESM::Class &class_, bool preserveProgress) { - float base = getSkill (skillIndex).getBase(); - - int level = static_cast (base); + int base = getSkill (skillIndex).getBase(); - if (level >= 100) + if (base >= 100) return; - if (preserveProgress) - base += 1; - else - base = level+1; + base += 1; // if this is a major or minor skill of the class, increase level progress bool levelProgress = false; @@ -260,6 +250,8 @@ void MWMechanics::NpcStats::increaseSkill(int skillIndex, const ESM::Class &clas } getSkill (skillIndex).setBase (base); + if (!preserveProgress) + getSkill(skillIndex).setProgress(0); } int MWMechanics::NpcStats::getLevelProgress () const @@ -325,16 +317,6 @@ void MWMechanics::NpcStats::setFactionReputation (const std::string& faction, in mFactionReputation[faction] = value; } -bool MWMechanics::NpcStats::isVampire() const -{ - return mVampire; -} - -void MWMechanics::NpcStats::setVampire (bool set) -{ - mVampire = set; -} - int MWMechanics::NpcStats::getReputation() const { return mReputation; @@ -387,7 +369,7 @@ void MWMechanics::NpcStats::setWerewolf (bool set) // Oh, Bethesda. It's "Intelligence". std::string name = "fWerewolf"+((i==ESM::Attribute::Intelligence) ? std::string("Intellegence") : ESM::Attribute::sAttributeNames[i]); - mWerewolfAttributes[i].setModified(int(gmst.find(name)->getFloat()), 0); + mWerewolfAttributes[i].setBase(int(gmst.find(name)->getFloat())); } for(size_t i = 0;i < ESM::Skill::Length;i++) @@ -401,7 +383,7 @@ void MWMechanics::NpcStats::setWerewolf (bool set) // "Mercantile"! >_< std::string name = "fWerewolf"+((i==ESM::Skill::Mercantile) ? std::string("Merchantile") : ESM::Skill::sSkillNames[i]); - mWerewolfSkill[i].setModified(int(gmst.find(name)->getFloat()), 0); + mWerewolfSkill[i].setBase(int(gmst.find(name)->getFloat())); } } mIsWerewolf = set; diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp index 6b7efa5b72..586e068322 100644 --- a/apps/openmw/mwmechanics/npcstats.hpp +++ b/apps/openmw/mwmechanics/npcstats.hpp @@ -45,12 +45,11 @@ namespace MWMechanics DrawState_ mDrawState; int mDisposition; unsigned int mMovementFlags; - Stat mSkill[27]; - Stat mWerewolfSkill[27]; + SkillValue mSkill[27]; + SkillValue mWerewolfSkill[27]; int mBounty; std::set mExpelled; std::map mFactionReputation; - bool mVampire; int mReputation; int mWerewolfKills; int mProfit; @@ -94,8 +93,8 @@ namespace MWMechanics void setMovementFlag (Flag flag, bool state); - const Stat& getSkill (int index) const; - Stat& getSkill (int index); + const SkillValue& getSkill (int index) const; + SkillValue& getSkill (int index); const std::map& getFactionRanks() const; std::map& getFactionRanks(); @@ -135,10 +134,6 @@ namespace MWMechanics void setFactionReputation (const std::string& faction, int value); - bool isVampire() const; - - void setVampire (bool set); - bool hasSkillsForRank (const std::string& factionId, int rank) const; bool isWerewolf() const; diff --git a/apps/openmw/mwmechanics/objects.cpp b/apps/openmw/mwmechanics/objects.cpp index 8a1e6ee6bf..d2d779599a 100644 --- a/apps/openmw/mwmechanics/objects.cpp +++ b/apps/openmw/mwmechanics/objects.cpp @@ -14,6 +14,16 @@ Objects::Objects() { } +Objects::~Objects() +{ + PtrControllerMap::iterator it(mObjects.begin()); + for (; it != mObjects.end();++it) + { + delete it->second; + it->second = NULL; + } +} + void Objects::addObject(const MWWorld::Ptr& ptr) { removeObject(ptr); diff --git a/apps/openmw/mwmechanics/objects.hpp b/apps/openmw/mwmechanics/objects.hpp index 5cdcdaa0af..32432c130a 100644 --- a/apps/openmw/mwmechanics/objects.hpp +++ b/apps/openmw/mwmechanics/objects.hpp @@ -21,6 +21,7 @@ namespace MWMechanics public: Objects(); + ~Objects(); void addObject (const MWWorld::Ptr& ptr); ///< Register an animated object diff --git a/apps/openmw/mwmechanics/repair.cpp b/apps/openmw/mwmechanics/repair.cpp index 38b2a48d7b..5e8a46fd4a 100644 --- a/apps/openmw/mwmechanics/repair.cpp +++ b/apps/openmw/mwmechanics/repair.cpp @@ -24,21 +24,13 @@ void Repair::repair(const MWWorld::Ptr &itemToRepair) MWWorld::LiveCellRef *ref = mTool.get(); + // unstack tool if required + player.getClass().getContainerStore(player).unstack(mTool, player); + // reduce number of uses left int uses = (mTool.getCellRef().mCharge != -1) ? mTool.getCellRef().mCharge : ref->mBase->mData.mUses; mTool.getCellRef().mCharge = uses-1; - // unstack tool if required - if (mTool.getRefData().getCount() > 1 && uses == ref->mBase->mData.mUses) - { - MWWorld::ContainerStore& store = MWWorld::Class::get(player).getContainerStore(player); - MWWorld::ContainerStoreIterator it = store.add(mTool, player); - it->getRefData().setCount(mTool.getRefData().getCount()-1); - it->getCellRef().mCharge = -1; - - mTool.getRefData().setCount(1); - } - MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); MWMechanics::NpcStats& npcStats = MWWorld::Class::get(player).getNpcStats(player); diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 74816d12e2..52fb0805a5 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -7,6 +7,8 @@ #include "../mwworld/containerstore.hpp" +#include "../mwworld/player.hpp" +#include "../mwworld/actionteleport.hpp" #include "../mwrender/animation.hpp" @@ -66,6 +68,12 @@ namespace MWMechanics MWBase::Environment::get().getWorld()->getStore().get().find ( effectIt->mEffectID); + // If player is healing someone, show the target's HP bar + if (caster.getRefData().getHandle() == "player" && target != caster + && effectIt->mEffectID == ESM::MagicEffect::RestoreHealth + && target.getClass().isActor()) + MWBase::Environment::get().getWindowManager()->setEnemy(target); + float magnitudeMult = 1; if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful && target.getClass().isActor()) { @@ -131,9 +139,10 @@ namespace MWMechanics { float random = std::rand() / static_cast(RAND_MAX); float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * random; - magnitude *= magnitudeMult; + magnitude *= magnitudeMult; - if (target.getClass().isActor() && !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) + bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration); + if (target.getClass().isActor() && hasDuration) { ActiveSpells::Effect effect; effect.mKey = MWMechanics::EffectKey(*effectIt); @@ -152,12 +161,23 @@ namespace MWMechanics ActiveSpells::Effect effect_ = effect; effect_.mMagnitude *= -1; effects.push_back(effect_); - caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell("", true, effects, mSourceName); + caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell("", true, + effects, mSourceName, caster.getRefData().getHandle()); } } } else - applyInstantEffect(target, effectIt->mEffectID, magnitude); + applyInstantEffect(target, EffectKey(*effectIt), magnitude); + + // HACK: Damage attribute/skill actually has a duration, even though the actual effect is instant and permanent. + // This was probably just done to have the effect visible in the magic menu for a while + // to notify the player they've been damaged? + if (effectIt->mEffectID == ESM::MagicEffect::DamageAttribute + || effectIt->mEffectID == ESM::MagicEffect::DamageSkill + || effectIt->mEffectID == ESM::MagicEffect::RestoreAttribute + || effectIt->mEffectID == ESM::MagicEffect::RestoreSkill + ) + applyInstantEffect(target, EffectKey(*effectIt), magnitude); if (target.getClass().isActor() || magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) { @@ -196,11 +216,13 @@ namespace MWMechanics inflict(caster, target, reflectedEffects, range, true); if (appliedLastingEffects.size()) - target.getClass().getCreatureStats(target).getActiveSpells().addSpell(mId, mStack, appliedLastingEffects, mSourceName); + target.getClass().getCreatureStats(target).getActiveSpells().addSpell(mId, mStack, appliedLastingEffects, + mSourceName, caster.getRefData().getHandle()); } - void CastSpell::applyInstantEffect(const MWWorld::Ptr &target, short effectId, float magnitude) + void CastSpell::applyInstantEffect(const MWWorld::Ptr &target, MWMechanics::EffectKey effect, float magnitude) { + short effectId = effect.mId; if (!target.getClass().isActor()) { if (effectId == ESM::MagicEffect::Lock) @@ -223,6 +245,28 @@ namespace MWMechanics } else { + if (effectId == ESM::MagicEffect::DamageAttribute || effectId == ESM::MagicEffect::RestoreAttribute) + { + int attribute = effect.mArg; + AttributeValue value = target.getClass().getCreatureStats(target).getAttribute(attribute); + if (effectId == ESM::MagicEffect::DamageAttribute) + value.damage(magnitude); + else + value.restore(magnitude); + target.getClass().getCreatureStats(target).setAttribute(attribute, value); + } + else if (effectId == ESM::MagicEffect::DamageSkill || effectId == ESM::MagicEffect::RestoreSkill) + { + if (target.getTypeName() != typeid(ESM::NPC).name()) + return; + int skill = effect.mArg; + SkillValue& value = target.getClass().getNpcStats(target).getSkill(skill); + if (effectId == ESM::MagicEffect::DamageSkill) + value.damage(magnitude); + else + value.restore(magnitude); + } + if (effectId == ESM::MagicEffect::CurePoison) target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect(ESM::MagicEffect::Poison); else if (effectId == ESM::MagicEffect::CureParalyzation) @@ -234,27 +278,45 @@ namespace MWMechanics else if (effectId == ESM::MagicEffect::CureCorprusDisease) target.getClass().getCreatureStats(target).getSpells().purgeCorprusDisease(); else if (effectId == ESM::MagicEffect::Dispel) - target.getClass().getCreatureStats(target).getActiveSpells().purgeAll(); + target.getClass().getCreatureStats(target).getActiveSpells().purgeAll(magnitude); else if (effectId == ESM::MagicEffect::RemoveCurse) target.getClass().getCreatureStats(target).getSpells().purgeCurses(); - else if (effectId == ESM::MagicEffect::DivineIntervention) + if (target.getRefData().getHandle() != "player") + return; + if (!MWBase::Environment::get().getWorld()->isTeleportingEnabled()) + return; + + Ogre::Vector3 worldPos; + if (!MWBase::Environment::get().getWorld()->findInteriorPositionInWorldSpace(target.getCell(), worldPos)) + worldPos = MWBase::Environment::get().getWorld()->getPlayer().getLastKnownExteriorPosition(); + + if (effectId == ESM::MagicEffect::DivineIntervention) { - // We need to be able to get the world location of an interior cell before implementing this - // or alternatively, the last known exterior location of the player, which is how vanilla does it. + MWBase::Environment::get().getWorld()->teleportToClosestMarker(target, "divinemarker", worldPos); } else if (effectId == ESM::MagicEffect::AlmsiviIntervention) { - // Same as above + MWBase::Environment::get().getWorld()->teleportToClosestMarker(target, "templemarker", worldPos); } else if (effectId == ESM::MagicEffect::Mark) { - // TODO + MWBase::Environment::get().getWorld()->getPlayer().markPosition( + target.getCell(), target.getRefData().getPosition()); } else if (effectId == ESM::MagicEffect::Recall) { - // TODO + MWWorld::CellStore* markedCell = NULL; + ESM::Position markedPosition; + + MWBase::Environment::get().getWorld()->getPlayer().getMarkedPosition(markedCell, markedPosition); + if (markedCell) + { + MWWorld::ActionTeleport action(markedCell->isExterior() ? "" : markedCell->mCell->mName, + markedPosition); + action.execute(target); + } } } } @@ -300,10 +362,11 @@ namespace MWMechanics if (item.getCellRef().mEnchantmentCharge == -1) item.getCellRef().mEnchantmentCharge = enchantment->mData.mCharge; - if (mCaster.getRefData().getHandle() == "player" && item.getCellRef().mEnchantmentCharge < castCost) + if (item.getCellRef().mEnchantmentCharge < castCost) { // TODO: Should there be a sound here? - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientCharge}"); + if (mCaster.getRefData().getHandle() == "player") + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientCharge}"); return false; } @@ -370,40 +433,15 @@ namespace MWMechanics fatigue.setCurrent(std::max(0.f, fatigue.getCurrent() - fatigueLoss)); stats.setFatigue(fatigue); - // Check mana bool fail = false; - DynamicStat magicka = stats.getMagicka(); - if (magicka.getCurrent() < spell->mData.mCost) - { - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientSP}"); - fail = true; - } - - // Reduce mana - if (!fail) - { - magicka.setCurrent(magicka.getCurrent() - spell->mData.mCost); - stats.setMagicka(magicka); - } - - // If this is a power, check if it was already used in last 24h - if (!fail && spell->mData.mType & ESM::Spell::ST_Power) - { - if (stats.canUsePower(spell->mId)) - stats.usePower(spell->mId); - else - { - MWBase::Environment::get().getWindowManager()->messageBox("#{sPowerAlreadyUsed}"); - fail = true; - } - } // Check success int successChance = getSpellSuccessChance(spell, mCaster); int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] if (!fail && roll >= successChance) { - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicSkillFail}"); + if (mCaster.getRefData().getHandle() == "player") + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicSkillFail}"); fail = true; } diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index e2efaa140b..a55c45fd16 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -203,7 +203,7 @@ namespace MWMechanics void inflict (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, const ESM::EffectList& effects, ESM::RangeType range, bool reflected=false); - void applyInstantEffect (const MWWorld::Ptr& target, short effectId, float magnitude); + void applyInstantEffect (const MWWorld::Ptr& target, MWMechanics::EffectKey effect, float magnitude); }; } diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index 6e7ac6f315..21781c530c 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -34,13 +34,14 @@ namespace MWMechanics random.resize(spell->mEffects.mList.size()); for (unsigned int i=0; i (std::rand()) / RAND_MAX; - mSpells.insert (std::make_pair (spellId, random)); + mSpells.insert (std::make_pair (Misc::StringUtils::lowerCase(spellId), random)); } } void Spells::remove (const std::string& spellId) { - TContainer::iterator iter = mSpells.find (spellId); + std::string lower = Misc::StringUtils::lowerCase(spellId); + TContainer::iterator iter = mSpells.find (lower); if (iter!=mSpells.end()) mSpells.erase (iter); @@ -125,7 +126,7 @@ namespace MWMechanics const ESM::Spell *spell = MWBase::Environment::get().getWorld()->getStore().get().find (iter->first); - if (spell->mData.mType & ESM::Spell::ST_Disease) + if (spell->mData.mType == ESM::Spell::ST_Disease) mSpells.erase(iter++); else iter++; @@ -139,7 +140,7 @@ namespace MWMechanics const ESM::Spell *spell = MWBase::Environment::get().getWorld()->getStore().get().find (iter->first); - if (spell->mData.mType & ESM::Spell::ST_Blight) + if (spell->mData.mType == ESM::Spell::ST_Blight) mSpells.erase(iter++); else iter++; @@ -192,7 +193,7 @@ namespace MWMechanics effectIt != list.mList.end(); ++effectIt, ++i) { float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * it->second[i]; - visitor.visit(MWMechanics::EffectKey(*effectIt), spell->mName, magnitude); + visitor.visit(MWMechanics::EffectKey(*effectIt), spell->mName, "", magnitude); } } } diff --git a/apps/openmw/mwmechanics/stat.hpp b/apps/openmw/mwmechanics/stat.hpp index 65d47c9c08..77b3f63645 100644 --- a/apps/openmw/mwmechanics/stat.hpp +++ b/apps/openmw/mwmechanics/stat.hpp @@ -162,14 +162,26 @@ namespace MWMechanics setCurrent (getCurrent()+diff); } - void setCurrent (const T& value) + void setCurrent (const T& value, bool allowDecreaseBelowZero = false) { - mCurrent = value; + if (value > mCurrent) + { + // increase + mCurrent = value; - if (mCurrent<0) + if (mCurrent > getModified()) + mCurrent = getModified(); + } + else if (value > 0 || allowDecreaseBelowZero) + { + // allowed decrease + mCurrent = value; + } + else if (mCurrent > 0) + { + // capped decrease mCurrent = 0; - else if (mCurrent>getModified()) - mCurrent = getModified(); + } } void setModifier (const T& modifier) @@ -193,6 +205,46 @@ namespace MWMechanics { return !(left==right); } + + class AttributeValue + { + int mBase; + int mModifier; + int mDamage; + + public: + AttributeValue() : mBase(0), mModifier(0), mDamage(0) {} + + int getModified() const { return std::max(0, mBase - mDamage + mModifier); } + int getBase() const { return mBase; } + int getModifier() const { return mModifier; } + + void setBase(int base) { mBase = std::max(0, base); } + void setModifier(int mod) { mModifier = mod; } + + void damage(int damage) { mDamage += damage; } + void restore(int amount) { mDamage -= std::min(mDamage, amount); } + int getDamage() const { return mDamage; } + }; + + class SkillValue : public AttributeValue + { + float mProgress; + public: + float getProgress() const { return mProgress; } + void setProgress(float progress) { mProgress = progress; } + }; + + inline bool operator== (const AttributeValue& left, const AttributeValue& right) + { + return left.getBase() == right.getBase() + && left.getModifier() == right.getModifier() + && left.getDamage() == right.getDamage(); + } + inline bool operator!= (const AttributeValue& left, const AttributeValue& right) + { + return !(left == right); + } } #endif diff --git a/apps/openmw/mwrender/activatoranimation.hpp b/apps/openmw/mwrender/activatoranimation.hpp index f3ea38f447..eb3e5815e7 100644 --- a/apps/openmw/mwrender/activatoranimation.hpp +++ b/apps/openmw/mwrender/activatoranimation.hpp @@ -1,5 +1,5 @@ -#ifndef _GAME_RENDER_ACTIVATORANIMATION_H -#define _GAME_RENDER_ACTIVATORANIMATION_H +#ifndef GAME_RENDER_ACTIVATORANIMATION_H +#define GAME_RENDER_ACTIVATORANIMATION_H #include "animation.hpp" diff --git a/apps/openmw/mwrender/actors.cpp b/apps/openmw/mwrender/actors.cpp index 5eecfaf35a..7513fbbe76 100644 --- a/apps/openmw/mwrender/actors.cpp +++ b/apps/openmw/mwrender/actors.cpp @@ -150,9 +150,12 @@ void Actors::removeCell(MWWorld::CellStore* store) } } -void Actors::update (float duration) +void Actors::update (Ogre::Camera* camera) { - // Nothing to do + for(PtrAnimationMap::iterator iter = mAllActors.begin();iter != mAllActors.end(); ++iter) + { + iter->second->preRender(camera); + } } Animation* Actors::getAnimation(const MWWorld::Ptr &ptr) diff --git a/apps/openmw/mwrender/actors.hpp b/apps/openmw/mwrender/actors.hpp index 61a0808f57..af71525fa7 100644 --- a/apps/openmw/mwrender/actors.hpp +++ b/apps/openmw/mwrender/actors.hpp @@ -1,5 +1,5 @@ -#ifndef _GAME_RENDER_ACTORS_H -#define _GAME_RENDER_ACTORS_H +#ifndef GAME_RENDER_ACTORS_H +#define GAME_RENDER_ACTORS_H #include @@ -47,7 +47,7 @@ namespace MWRender void removeCell(MWWorld::CellStore* store); - void update (float duration); + void update (Ogre::Camera* camera); /// Updates containing cell for object rendering data void updateObjectCell(const MWWorld::Ptr &old, const MWWorld::Ptr &cur); diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index dddbc2c733..a9b76093c9 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -51,28 +51,6 @@ void Animation::EffectAnimationValue::setValue(Ogre::Real) { } - -void Animation::destroyObjectList(Ogre::SceneManager *sceneMgr, NifOgre::ObjectList &objects) -{ - for(size_t i = 0;i < objects.mLights.size();i++) - { - Ogre::Light *light = objects.mLights[i]; - // If parent is a scene node, it was created specifically for this light. Destroy it now. - if(light->isAttached() && !light->isParentTagPoint()) - sceneMgr->destroySceneNode(light->getParentSceneNode()); - sceneMgr->destroyLight(light); - } - for(size_t i = 0;i < objects.mParticles.size();i++) - sceneMgr->destroyParticleSystem(objects.mParticles[i]); - for(size_t i = 0;i < objects.mEntities.size();i++) - sceneMgr->destroyEntity(objects.mEntities[i]); - objects.mControllers.clear(); - objects.mLights.clear(); - objects.mParticles.clear(); - objects.mEntities.clear(); - objects.mSkelBase = NULL; -} - Animation::Animation(const MWWorld::Ptr &ptr, Ogre::SceneNode *node) : mPtr(ptr) , mCamera(NULL) @@ -90,13 +68,9 @@ Animation::Animation(const MWWorld::Ptr &ptr, Ogre::SceneNode *node) Animation::~Animation() { - for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) - destroyObjectList(mInsert->getCreator(), it->mObjects); + mEffects.clear(); mAnimSources.clear(); - - Ogre::SceneManager *sceneMgr = mInsert->getCreator(); - destroyObjectList(sceneMgr, mObjectRoot); } @@ -105,7 +79,7 @@ void Animation::setObjectRoot(const std::string &model, bool baseonly) OgreAssert(mAnimSources.empty(), "Setting object root while animation sources are set!"); mSkelBase = NULL; - destroyObjectList(mInsert->getCreator(), mObjectRoot); + mObjectRoot.setNull(); if(model.empty()) return; @@ -126,11 +100,11 @@ void Animation::setObjectRoot(const std::string &model, bool baseonly) mObjectRoot = (!baseonly ? NifOgre::Loader::createObjects(mInsert, mdlname) : NifOgre::Loader::createObjectBase(mInsert, mdlname)); - if(mObjectRoot.mSkelBase) + if(mObjectRoot->mSkelBase) { - mSkelBase = mObjectRoot.mSkelBase; + mSkelBase = mObjectRoot->mSkelBase; - Ogre::AnimationStateSet *aset = mObjectRoot.mSkelBase->getAllAnimationStates(); + Ogre::AnimationStateSet *aset = mObjectRoot->mSkelBase->getAllAnimationStates(); Ogre::AnimationStateIterator asiter = aset->getAnimationStateIterator(); while(asiter.hasMoreElements()) { @@ -141,7 +115,7 @@ void Animation::setObjectRoot(const std::string &model, bool baseonly) // Set the bones as manually controlled since we're applying the // transformations manually - Ogre::SkeletonInstance *skelinst = mObjectRoot.mSkelBase->getSkeleton(); + Ogre::SkeletonInstance *skelinst = mObjectRoot->mSkelBase->getSkeleton(); Ogre::Skeleton::BoneIterator boneiter = skelinst->getBoneIterator(); while(boneiter.hasMoreElements()) boneiter.getNext()->setManuallyControlled(true); @@ -162,39 +136,31 @@ void Animation::setObjectRoot(const std::string &model, bool baseonly) else mAttachedObjects.clear(); - for(size_t i = 0;i < mObjectRoot.mControllers.size();i++) + for(size_t i = 0;i < mObjectRoot->mControllers.size();i++) { - if(mObjectRoot.mControllers[i].getSource().isNull()) - mObjectRoot.mControllers[i].setSource(mAnimationValuePtr[0]); + if(mObjectRoot->mControllers[i].getSource().isNull()) + mObjectRoot->mControllers[i].setSource(mAnimationValuePtr[0]); } } struct AddGlow { Ogre::Vector3* mColor; - AddGlow(Ogre::Vector3* col) : mColor(col) {} + NifOgre::MaterialControllerManager* mMaterialControllerMgr; + AddGlow(Ogre::Vector3* col, NifOgre::MaterialControllerManager* materialControllerMgr) + : mColor(col) + , mMaterialControllerMgr(materialControllerMgr) + {} - // TODO: integrate this with material controllers? void operator()(Ogre::Entity* entity) const { - unsigned int numsubs = entity->getNumSubEntities(); - for(unsigned int i = 0;i < numsubs;++i) - { - unsigned int numsubs = entity->getNumSubEntities(); - for(unsigned int i = 0;i < numsubs;++i) - { - Ogre::SubEntity* subEnt = entity->getSubEntity(i); - std::string newName = subEnt->getMaterialName() + "@fx"; - if (sh::Factory::getInstance().searchInstance(newName) == NULL) - { - sh::MaterialInstance* instance = - sh::Factory::getInstance().createMaterialInstance(newName, subEnt->getMaterialName()); - instance->setProperty("env_map", sh::makeProperty(new sh::BooleanValue(true))); - instance->setProperty("env_map_color", sh::makeProperty(new sh::Vector3(mColor->x, mColor->y, mColor->z))); - } - subEnt->setMaterialName(newName); - } - } + if (!entity->getNumSubEntities()) + return; + Ogre::MaterialPtr writableMaterial = mMaterialControllerMgr->getWritableMaterial(entity); + sh::MaterialInstance* instance = sh::Factory::getInstance().getMaterialInstance(writableMaterial->getName()); + + instance->setProperty("env_map", sh::makeProperty(new sh::BooleanValue(true))); + instance->setProperty("env_map_color", sh::makeProperty(new sh::Vector3(mColor->x, mColor->y, mColor->z))); } }; @@ -219,6 +185,7 @@ public: for(unsigned int i = 0;i < numsubs;++i) { Ogre::SubEntity* subEnt = entity->getSubEntity(i); + sh::Factory::getInstance()._ensureMaterial(subEnt->getMaterial()->getName(), "Default"); subEnt->setRenderQueueGroup(subEnt->getMaterial()->isTransparent() ? mTransQueue : mSolidQueue); } } @@ -233,16 +200,16 @@ public: } }; -void Animation::setRenderProperties(const NifOgre::ObjectList &objlist, Ogre::uint32 visflags, Ogre::uint8 solidqueue, Ogre::uint8 transqueue, Ogre::Real dist, bool enchantedGlow, Ogre::Vector3* glowColor) +void Animation::setRenderProperties(NifOgre::ObjectScenePtr objlist, Ogre::uint32 visflags, Ogre::uint8 solidqueue, Ogre::uint8 transqueue, Ogre::Real dist, bool enchantedGlow, Ogre::Vector3* glowColor) { - std::for_each(objlist.mEntities.begin(), objlist.mEntities.end(), + std::for_each(objlist->mEntities.begin(), objlist->mEntities.end(), VisQueueSet(visflags, solidqueue, transqueue, dist)); - std::for_each(objlist.mParticles.begin(), objlist.mParticles.end(), + std::for_each(objlist->mParticles.begin(), objlist->mParticles.end(), VisQueueSet(visflags, solidqueue, transqueue, dist)); if (enchantedGlow) - std::for_each(objlist.mEntities.begin(), objlist.mEntities.end(), - AddGlow(glowColor)); + std::for_each(objlist->mEntities.begin(), objlist->mEntities.end(), + AddGlow(glowColor, &objlist->mMaterialControllerMgr)); } @@ -340,7 +307,7 @@ void Animation::clearAnimSources() } -void Animation::addExtraLight(Ogre::SceneManager *sceneMgr, NifOgre::ObjectList &objlist, const ESM::Light *light) +void Animation::addExtraLight(Ogre::SceneManager *sceneMgr, NifOgre::ObjectScenePtr objlist, const ESM::Light *light) { const MWWorld::Fallback *fallback = MWBase::Environment::get().getWorld()->getFallback(); @@ -353,8 +320,8 @@ void Animation::addExtraLight(Ogre::SceneManager *sceneMgr, NifOgre::ObjectList if((light->mData.mFlags&ESM::Light::Negative)) color *= -1; - objlist.mLights.push_back(sceneMgr->createLight()); - Ogre::Light *olight = objlist.mLights.back(); + objlist->mLights.push_back(sceneMgr->createLight()); + Ogre::Light *olight = objlist->mLights.back(); olight->setDiffuseColour(color); Ogre::ControllerValueRealPtr src(Ogre::ControllerManager::getSingleton().getFrameTimeSource()); @@ -366,7 +333,7 @@ void Animation::addExtraLight(Ogre::SceneManager *sceneMgr, NifOgre::ObjectList (light->mData.mFlags&ESM::Light::PulseSlow) ? OEngine::Render::LT_PulseSlow : OEngine::Render::LT_Normal )); - objlist.mControllers.push_back(Ogre::Controller(src, dest, func)); + objlist->mControllers.push_back(Ogre::Controller(src, dest, func)); bool interior = !(mPtr.isInCell() && mPtr.getCell()->mCell->isExterior()); bool quadratic = fallback->getFallbackBool("LightAttenuation_OutQuadInLin") ? @@ -392,14 +359,14 @@ void Animation::addExtraLight(Ogre::SceneManager *sceneMgr, NifOgre::ObjectList } // 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); + if(objlist->mSkelBase && objlist->mSkelBase->getSkeleton()->hasBone("AttachLight")) + objlist->mSkelBase->attachObjectToBone("AttachLight", olight); else { Ogre::AxisAlignedBox bounds = Ogre::AxisAlignedBox::BOX_NULL; - for(size_t i = 0;i < objlist.mEntities.size();i++) + for(size_t i = 0;i < objlist->mEntities.size();i++) { - Ogre::Entity *ent = objlist.mEntities[i]; + Ogre::Entity *ent = objlist->mEntities[i]; bounds.merge(ent->getBoundingBox()); } @@ -942,8 +909,8 @@ Ogre::Vector3 Animation::runAnimation(float duration) ++stateiter; } - for(size_t i = 0;i < mObjectRoot.mControllers.size();i++) - mObjectRoot.mControllers[i].update(); + for(size_t i = 0;i < mObjectRoot->mControllers.size();i++) + mObjectRoot->mControllers[i].update(); // Apply group controllers for(size_t grp = 0;grp < sNumGroups;grp++) @@ -986,7 +953,7 @@ public: void Animation::enableLights(bool enable) { - std::for_each(mObjectRoot.mLights.begin(), mObjectRoot.mLights.end(), ToggleLight(enable)); + std::for_each(mObjectRoot->mLights.begin(), mObjectRoot->mLights.end(), ToggleLight(enable)); } @@ -1005,7 +972,7 @@ public: Ogre::AxisAlignedBox Animation::getWorldBounds() { Ogre::AxisAlignedBox bounds = Ogre::AxisAlignedBox::BOX_NULL; - std::for_each(mObjectRoot.mEntities.begin(), mObjectRoot.mEntities.end(), MergeBounds(&bounds)); + std::for_each(mObjectRoot->mEntities.begin(), mObjectRoot->mEntities.end(), MergeBounds(&bounds)); return bounds; } @@ -1030,6 +997,16 @@ void Animation::detachObjectFromBone(Ogre::MovableObject *obj) mSkelBase->detachObjectFromBone(obj); } +bool Animation::isPlaying(Group group) const +{ + for (AnimStateMap::const_iterator stateiter = mStates.begin(); stateiter != mStates.end(); ++stateiter) + { + if(stateiter->second.mGroups == group) + return true; + } + return false; +} + void Animation::addEffect(const std::string &model, int effectId, bool loop, const std::string &bonename, std::string texture) { // Early out if we already have this effect @@ -1051,32 +1028,31 @@ void Animation::addEffect(const std::string &model, int effectId, bool loop, con params.mObjects = NifOgre::Loader::createObjects(mInsert, model); else params.mObjects = NifOgre::Loader::createObjects(mSkelBase, bonename, mInsert, model); + + setRenderProperties(params.mObjects, RV_Misc, + RQG_Main, RQG_Alpha, 0.f, false, NULL); + params.mLoop = loop; params.mEffectId = effectId; params.mBoneName = bonename; - for(size_t i = 0;i < params.mObjects.mControllers.size();i++) + for(size_t i = 0;i < params.mObjects->mControllers.size();i++) { - if(params.mObjects.mControllers[i].getSource().isNull()) - params.mObjects.mControllers[i].setSource(Ogre::SharedPtr (new EffectAnimationValue())); + if(params.mObjects->mControllers[i].getSource().isNull()) + params.mObjects->mControllers[i].setSource(Ogre::SharedPtr (new EffectAnimationValue())); } if (!texture.empty()) { - 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]; - sh::Factory::getInstance()._ensureMaterial(partSys->getMaterialName(), "Default"); - Ogre::MaterialPtr mat = Ogre::MaterialManager::getSingleton().getByName(partSys->getMaterialName()); - static int count = 0; - Ogre::String materialName = "openmw/" + Ogre::StringConverter::toString(count++); - // TODO: destroy when effect is removed - Ogre::MaterialPtr newMat = mat->clone(materialName); - partSys->setMaterialName(materialName); - - 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 = newMat->getTechnique(t); + Ogre::Technique* tech = mat->getTechnique(t); for (int p=0; pgetNumPasses(); ++p) { Ogre::Pass* pass = tech->getPass(p); @@ -1099,7 +1075,6 @@ void Animation::removeEffect(int effectId) { if (it->mEffectId == effectId) { - destroyObjectList(mInsert->getCreator(), it->mObjects); mEffects.erase(it); return; } @@ -1119,32 +1094,31 @@ void Animation::updateEffects(float duration) { for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ) { - NifOgre::ObjectList& objects = it->mObjects; - for(size_t i = 0; i < objects.mControllers.size() ;i++) + NifOgre::ObjectScenePtr objects = it->mObjects; + for(size_t i = 0; i < objects->mControllers.size() ;i++) { - EffectAnimationValue* value = dynamic_cast(objects.mControllers[i].getSource().get()); + EffectAnimationValue* value = dynamic_cast(objects->mControllers[i].getSource().get()); if (value) value->addTime(duration); - objects.mControllers[i].update(); + objects->mControllers[i].update(); } - if (objects.mControllers[0].getSource()->getValue() >= objects.mMaxControllerLength) + if (objects->mControllers[0].getSource()->getValue() >= objects->mMaxControllerLength) { if (it->mLoop) { // Start from the beginning again; carry over the remainder - float remainder = objects.mControllers[0].getSource()->getValue() - objects.mMaxControllerLength; - for(size_t i = 0; i < objects.mControllers.size() ;i++) + float remainder = objects->mControllers[0].getSource()->getValue() - objects->mMaxControllerLength; + for(size_t i = 0; i < objects->mControllers.size() ;i++) { - EffectAnimationValue* value = dynamic_cast(objects.mControllers[i].getSource().get()); + EffectAnimationValue* value = dynamic_cast(objects->mControllers[i].getSource().get()); if (value) value->resetTime(remainder); } } else { - destroyObjectList(mInsert->getCreator(), it->mObjects); it = mEffects.erase(it); continue; } @@ -1153,6 +1127,16 @@ void Animation::updateEffects(float duration) } } +void Animation::preRender(Ogre::Camera *camera) +{ + for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) + { + NifOgre::ObjectScenePtr objects = it->mObjects; + objects->rotateBillboardNodes(camera); + } + mObjectRoot->rotateBillboardNodes(camera); +} + // TODO: Should not be here Ogre::Vector3 Animation::getEnchantmentColor(MWWorld::Ptr item) { @@ -1205,6 +1189,7 @@ public: unsigned int numsubs = ent->getNumSubEntities(); for(unsigned int i = 0;i < numsubs;++i) { + sh::Factory::getInstance()._ensureMaterial(ent->getSubEntity(i)->getMaterial()->getName(), "Default"); if(ent->getSubEntity(i)->getMaterial()->isTransparent()) return true; } @@ -1214,19 +1199,22 @@ public: bool ObjectAnimation::canBatch() const { - if(!mObjectRoot.mParticles.empty() || !mObjectRoot.mLights.empty() || !mObjectRoot.mControllers.empty()) + if(!mObjectRoot->mParticles.empty() || !mObjectRoot->mLights.empty() || !mObjectRoot->mControllers.empty()) + return false; + if (!mObjectRoot->mBillboardNodes.empty()) return false; - return std::find_if(mObjectRoot.mEntities.begin(), mObjectRoot.mEntities.end(), - FindEntityTransparency()) == mObjectRoot.mEntities.end(); + return std::find_if(mObjectRoot->mEntities.begin(), mObjectRoot->mEntities.end(), + FindEntityTransparency()) == mObjectRoot->mEntities.end(); } void ObjectAnimation::fillBatch(Ogre::StaticGeometry *sg) { - std::vector::reverse_iterator iter = mObjectRoot.mEntities.rbegin(); - for(;iter != mObjectRoot.mEntities.rend();++iter) + std::vector::reverse_iterator iter = mObjectRoot->mEntities.rbegin(); + for(;iter != mObjectRoot->mEntities.rend();++iter) { Ogre::Node *node = (*iter)->getParentNode(); - sg->addEntity(*iter, node->_getDerivedPosition(), node->_getDerivedOrientation(), node->_getDerivedScale()); + if ((*iter)->isVisible()) + sg->addEntity(*iter, node->_getDerivedPosition(), node->_getDerivedOrientation(), node->_getDerivedScale()); } } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index e28aecbc13..573f769c33 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -1,5 +1,5 @@ -#ifndef _GAME_RENDER_ANIMATION_H -#define _GAME_RENDER_ANIMATION_H +#ifndef GAME_RENDER_ANIMATION_H +#define GAME_RENDER_ANIMATION_H #include #include @@ -112,7 +112,7 @@ protected: struct EffectParams { std::string mModelName; // Just here so we don't add the same effect twice - NifOgre::ObjectList mObjects; + NifOgre::ObjectScenePtr mObjects; int mEffectId; bool mLoop; std::string mBoneName; @@ -125,7 +125,7 @@ protected: Ogre::SceneNode *mInsert; Ogre::Entity *mSkelBase; - NifOgre::ObjectList mObjectRoot; + NifOgre::ObjectScenePtr mObjectRoot; AnimSourceList mAnimSources; Ogre::Node *mAccumRoot; Ogre::Node *mNonAccumRoot; @@ -187,11 +187,9 @@ protected: void addAnimSource(const std::string &model); /** Adds an additional light to the given object list using the specified ESM record. */ - void addExtraLight(Ogre::SceneManager *sceneMgr, NifOgre::ObjectList &objlist, const ESM::Light *light); + void addExtraLight(Ogre::SceneManager *sceneMgr, NifOgre::ObjectScenePtr objlist, const ESM::Light *light); - static void destroyObjectList(Ogre::SceneManager *sceneMgr, NifOgre::ObjectList &objects); - - static void setRenderProperties(const NifOgre::ObjectList &objlist, Ogre::uint32 visflags, Ogre::uint8 solidqueue, + static void setRenderProperties(NifOgre::ObjectScenePtr objlist, Ogre::uint32 visflags, Ogre::uint8 solidqueue, Ogre::uint8 transqueue, Ogre::Real dist=0.0f, bool enchantedGlow=false, Ogre::Vector3* glowColor=NULL); @@ -217,6 +215,11 @@ public: void addEffect (const std::string& model, int effectId, bool loop = false, const std::string& bonename = "", std::string texture = ""); void removeEffect (int effectId); void getLoopingEffects (std::vector& out); + + /// Prepare this animation for being rendered with \a camera (rotates billboard nodes) + virtual void preRender (Ogre::Camera* camera); + + virtual void setAlpha(float alpha) {} private: void updateEffects(float duration); @@ -255,6 +258,8 @@ public: /** Returns true if the named animation group is playing. */ bool isPlaying(const std::string &groupname) const; + bool isPlaying(Group group) const; + /** Gets info about the given animation group. * \param groupname Animation group to check. * \param complete Stores completion amount (0 = at start key, 0.5 = half way between start and stop keys), etc. @@ -274,7 +279,7 @@ public: virtual Ogre::Vector3 runAnimation(float duration); virtual void showWeapons(bool showWeapon); - virtual void showShield(bool show) {} + virtual void showCarriedLeft(bool show) {} void enableLights(bool enable); diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index 9af3987a8e..9a35725ee3 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -19,23 +19,27 @@ namespace MWRender Camera::Camera (Ogre::Camera *camera) : mCamera(camera), mCameraNode(NULL), + mAnimation(NULL), mFirstPersonView(true), mPreviewMode(false), mFreeLook(true), - mHeight(128.f), - mCameraDistance(300.f), - mDistanceAdjusted(false), - mAnimation(NULL), mNearest(30.f), mFurthest(800.f), mIsNearest(false), - mIsFurthest(false) + mIsFurthest(false), + mHeight(128.f), + mCameraDistance(300.f), + mDistanceAdjusted(false), + mVanityToggleQueued(false), + mViewModeToggleQueued(false) { mVanity.enabled = false; mVanity.allowed = true; + mPreviewCam.pitch = 0.f; mPreviewCam.yaw = 0.f; mPreviewCam.offset = 400.f; + mMainCam.pitch = 0.f; mMainCam.yaw = 0.f; mMainCam.offset = 400.f; } @@ -103,6 +107,23 @@ namespace MWRender void Camera::update(float duration, bool paused) { + if (!mAnimation->isPlaying(MWRender::Animation::Group_UpperBody)) + { + // Now process the view changes we queued earlier + if (mVanityToggleQueued) + { + toggleVanityMode(!mVanity.enabled); + mVanityToggleQueued = false; + } + if (mViewModeToggleQueued) + { + + togglePreviewMode(false); + toggleViewMode(); + mViewModeToggleQueued = false; + } + } + updateListener(); if (paused) return; @@ -121,6 +142,14 @@ namespace MWRender void Camera::toggleViewMode() { + // Changing the view will stop all playing animations, so if we are playing + // anything important, queue the view change for later + if (mAnimation->isPlaying(MWRender::Animation::Group_UpperBody)) + { + mViewModeToggleQueued = true; + return; + } + mFirstPersonView = !mFirstPersonView; processViewChange(); @@ -140,6 +169,14 @@ namespace MWRender bool Camera::toggleVanityMode(bool enable) { + // Changing the view will stop all playing animations, so if we are playing + // anything important, queue the view change for later + if (mAnimation->isPlaying(MWRender::Animation::Group_UpperBody)) + { + mVanityToggleQueued = true; + return false; + } + if(!mVanity.allowed && enable) return false; @@ -168,6 +205,9 @@ namespace MWRender void Camera::togglePreviewMode(bool enable) { + if (mAnimation->isPlaying(MWRender::Animation::Group_UpperBody)) + return; + if(mPreviewMode == enable) return; @@ -184,7 +224,6 @@ namespace MWRender } mCamera->setPosition(0.f, 0.f, offset); - rotateCamera(Ogre::Vector3(getPitch(), 0.f, getYaw()), false); } void Camera::setSneakOffset() @@ -241,6 +280,11 @@ namespace MWRender } } + float Camera::getCameraDistance() const + { + return mCamera->getPosition().z; + } + void Camera::setCameraDistance(float dist, bool adjust, bool override) { if(mFirstPersonView && !mPreviewMode && !mVanity.enabled) @@ -319,6 +363,7 @@ namespace MWRender mAnimation->setViewMode(NpcAnimation::VM_Normal); mCameraNode->attachObject(mCamera); } + rotateCamera(Ogre::Vector3(getPitch(), 0.f, getYaw()), false); } void Camera::getPosition(Ogre::Vector3 &focal, Ogre::Vector3 &camera) diff --git a/apps/openmw/mwrender/camera.hpp b/apps/openmw/mwrender/camera.hpp index baf2f3685a..d31d9e56c0 100644 --- a/apps/openmw/mwrender/camera.hpp +++ b/apps/openmw/mwrender/camera.hpp @@ -47,6 +47,9 @@ namespace MWRender bool mDistanceAdjusted; + bool mVanityToggleQueued; + bool mViewModeToggleQueued; + /// Updates sound manager listener data void updateListener(); @@ -77,6 +80,7 @@ namespace MWRender bool toggleVanityMode(bool enable); void allowVanityMode(bool allow); + /// @note this may be ignored if an important animation is currently playing void togglePreviewMode(bool enable); /// \brief Lowers the camera for sneak. @@ -101,6 +105,8 @@ namespace MWRender /// Restore default camera distance for current mode. void setCameraDistance(); + float getCameraDistance() const; + void setAnimation(NpcAnimation *anim); /// Stores focal and camera world positions in passed arguments diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 5e659ca1d7..643225515b 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -128,7 +128,7 @@ namespace MWRender InventoryPreview::InventoryPreview(MWWorld::Ptr character) - : CharacterPreview(character, 512, 1024, "CharacterPreview", Ogre::Vector3(0, 65, -180), Ogre::Vector3(0,65,0)) + : CharacterPreview(character, 512, 1024, "CharacterPreview", Ogre::Vector3(0, 62, -200), Ogre::Vector3(0, 62, 0)) , mSelectionBuffer(NULL) { } @@ -140,8 +140,7 @@ namespace MWRender void InventoryPreview::update(int sizeX, int sizeY) { - // TODO: can we avoid this. Vampire state needs to be updated. - mAnimation->rebuild(); + mAnimation->updateParts(); MWWorld::InventoryStore &inv = MWWorld::Class::get(mCharacter).getInventoryStore(mCharacter); MWWorld::ContainerStoreIterator iter = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); @@ -177,12 +176,8 @@ namespace MWRender groupname = "inventoryhandtohand"; } - // TODO see above - //if(groupname != mCurrentAnimGroup) - //{ - mCurrentAnimGroup = groupname; - mAnimation->play(mCurrentAnimGroup, 1, Animation::Group_All, false, 1.0f, "start", "stop", 0.0f, 0); - //} + mCurrentAnimGroup = groupname; + mAnimation->play(mCurrentAnimGroup, 1, Animation::Group_All, false, 1.0f, "start", "stop", 0.0f, 0); MWWorld::ContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); if(torch != inv.end() && torch->getTypeName() == typeid(ESM::Light).name()) @@ -194,7 +189,6 @@ namespace MWRender else if(mAnimation->getInfo("torch")) mAnimation->disable("torch"); - mAnimation->updateParts(); mAnimation->runAnimation(0.0f); mViewport->setDimensions (0, 0, std::min(1.f, float(sizeX) / float(512)), std::min(1.f, float(sizeY) / float(1024))); diff --git a/apps/openmw/mwrender/compositors.cpp b/apps/openmw/mwrender/compositors.cpp deleted file mode 100644 index b1c98a3067..0000000000 --- a/apps/openmw/mwrender/compositors.cpp +++ /dev/null @@ -1,108 +0,0 @@ -#include "compositors.hpp" - -#include -#include -#include -#include - -using namespace MWRender; - -Compositors::Compositors(Ogre::Viewport* vp) : - mViewport(vp) - , mEnabled(true) -{ -} - -Compositors::~Compositors() -{ - Ogre::CompositorManager::getSingleton().removeCompositorChain(mViewport); -} - -void Compositors::setEnabled (const bool enabled) -{ - for (CompositorMap::iterator it=mCompositors.begin(); - it != mCompositors.end(); ++it) - { - Ogre::CompositorManager::getSingleton().setCompositorEnabled(mViewport, it->first, enabled && it->second.first); - } - mEnabled = enabled; -} - -void Compositors::recreate() -{ - Ogre::CompositorManager::getSingleton().removeCompositorChain(mViewport); - - CompositorMap temp = mCompositors; - mCompositors.clear(); - - for (CompositorMap::iterator it=temp.begin(); - it != temp.end(); ++it) - { - addCompositor(it->first, it->second.second); - setCompositorEnabled(it->first, mEnabled && it->second.first); - } -} - -void Compositors::addCompositor (const std::string& name, const int priority) -{ - int id = 0; - - for (CompositorMap::iterator it=mCompositors.begin(); - it != mCompositors.end(); ++it) - { - if (it->second.second > priority) - break; - ++id; - } - Ogre::CompositorManager::getSingleton().addCompositor (mViewport, name, id); - - mCompositors[name] = std::make_pair(false, priority); -} - -void Compositors::setCompositorEnabled (const std::string& name, const bool enabled) -{ - mCompositors[name].first = enabled; - Ogre::CompositorManager::getSingleton().setCompositorEnabled (mViewport, name, enabled && mEnabled); -} - -void Compositors::removeAll() -{ - Ogre::CompositorManager::getSingleton().removeCompositorChain(mViewport); - - mCompositors.clear(); -} - -bool Compositors::anyCompositorEnabled() -{ - for (CompositorMap::iterator it=mCompositors.begin(); - it != mCompositors.end(); ++it) - { - if (it->second.first && mEnabled) - return true; - } - return false; -} - -void Compositors::countTrianglesBatches(unsigned int &triangles, unsigned int &batches) -{ - triangles = 0; - batches = 0; - - Ogre::CompositorInstance* c = NULL; - Ogre::CompositorChain* chain = Ogre::CompositorManager::getSingleton().getCompositorChain (mViewport); - // accumulate tris & batches from all compositors with all their render targets - for (unsigned int i=0; i < chain->getNumCompositors(); ++i) - { - if (chain->getCompositor(i)->getEnabled()) - { - c = chain->getCompositor(i); - for (unsigned int j = 0; j < c->getTechnique()->getNumTargetPasses(); ++j) - { - std::string textureName = c->getTechnique()->getTargetPass(j)->getOutputName(); - Ogre::RenderTarget* rt = c->getRenderTarget(textureName); - triangles += rt->getTriangleCount(); - batches += rt->getBatchCount(); - } - } - } -} diff --git a/apps/openmw/mwrender/compositors.hpp b/apps/openmw/mwrender/compositors.hpp deleted file mode 100644 index e5dd7503ce..0000000000 --- a/apps/openmw/mwrender/compositors.hpp +++ /dev/null @@ -1,64 +0,0 @@ -#ifndef GAME_MWRENDER_COMPOSITORS_H -#define GAME_MWRENDER_COMPOSITORS_H - -#include -#include - -namespace Ogre -{ - class Viewport; -} - -namespace MWRender -{ - typedef std::map < std::string, std::pair > CompositorMap; - - /// \brief Manages a set of compositors for one viewport - class Compositors - { - public: - Compositors(Ogre::Viewport* vp); - virtual ~Compositors(); - - /** - * enable or disable all compositors globally - */ - void setEnabled (const bool enabled); - - void setViewport(Ogre::Viewport* vp) { mViewport = vp; } - - /// recreate compositors (call this after viewport size changes) - void recreate(); - - bool toggle() { setEnabled(!mEnabled); return mEnabled; } - - /** - * enable or disable a specific compositor - * @note enable has no effect if all compositors are globally disabled - */ - void setCompositorEnabled (const std::string& name, const bool enabled); - - /** - * @param name of compositor - * @param priority, lower number will be first in the chain - */ - void addCompositor (const std::string& name, const int priority); - - bool anyCompositorEnabled(); - - void countTrianglesBatches(unsigned int &triangles, unsigned int &batches); - - void removeAll (); - - protected: - /// maps compositor name to its "enabled" state - CompositorMap mCompositors; - - bool mEnabled; - - Ogre::Viewport* mViewport; - }; - -} - -#endif diff --git a/apps/openmw/mwrender/creatureanimation.hpp b/apps/openmw/mwrender/creatureanimation.hpp index 0c277d1985..a902df5d83 100644 --- a/apps/openmw/mwrender/creatureanimation.hpp +++ b/apps/openmw/mwrender/creatureanimation.hpp @@ -1,5 +1,5 @@ -#ifndef _GAME_RENDER_CREATUREANIMATION_H -#define _GAME_RENDER_CREATUREANIMATION_H +#ifndef GAME_RENDER_CREATUREANIMATION_H +#define GAME_RENDER_CREATUREANIMATION_H #include "animation.hpp" diff --git a/apps/openmw/mwrender/debugging.hpp b/apps/openmw/mwrender/debugging.hpp index 4a574017ce..39be34cb02 100644 --- a/apps/openmw/mwrender/debugging.hpp +++ b/apps/openmw/mwrender/debugging.hpp @@ -1,5 +1,5 @@ -#ifndef _GAME_RENDER_MWSCENE_H -#define _GAME_RENDER_MWSCENE_H +#ifndef GAME_RENDER_MWSCENE_H +#define GAME_RENDER_MWSCENE_H #include #include diff --git a/apps/openmw/mwrender/globalmap.hpp b/apps/openmw/mwrender/globalmap.hpp index dd3787b62b..aad9adcc42 100644 --- a/apps/openmw/mwrender/globalmap.hpp +++ b/apps/openmw/mwrender/globalmap.hpp @@ -1,5 +1,5 @@ -#ifndef _GAME_RENDER_GLOBALMAP_H -#define _GAME_RENDER_GLOBALMAP_H +#ifndef GAME_RENDER_GLOBALMAP_H +#define GAME_RENDER_GLOBALMAP_H #include diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index f70533d22d..f147ae7b70 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -225,64 +225,54 @@ void LocalMap::render(const float x, const float y, tex = TextureManager::getSingleton().getByName(texture); if (tex.isNull()) { - // try loading from disk - //if (boost::filesystem::exists(texture+".jpg")) - //{ - /// \todo - //} - //else + // render + tex = TextureManager::getSingleton().createManual( + texture, + ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + TEX_TYPE_2D, + xw*sMapResolution/sSize, yw*sMapResolution/sSize, + 0, + PF_R8G8B8, + TU_RENDERTARGET); + + RenderTarget* rtt = tex->getBuffer()->getRenderTarget(); + + rtt->setAutoUpdated(false); + Viewport* vp = rtt->addViewport(mCellCamera); + vp->setOverlaysEnabled(false); + vp->setShadowsEnabled(false); + vp->setBackgroundColour(ColourValue(0, 0, 0)); + vp->setVisibilityMask(RV_Map); + vp->setMaterialScheme("local_map"); + + rtt->update(); + + // create "fog of war" texture + TexturePtr tex2 = TextureManager::getSingleton().createManual( + texture + "_fog", + ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + TEX_TYPE_2D, + xw*sFogOfWarResolution/sSize, yw*sFogOfWarResolution/sSize, + 0, + PF_A8R8G8B8, + TU_DYNAMIC_WRITE_ONLY_DISCARDABLE); + + // create a buffer to use for dynamic operations + std::vector buffer; + buffer.resize(sFogOfWarResolution*sFogOfWarResolution); + + // initialize to (0, 0, 0, 1) + for (int p=0; pgetBuffer()->getRenderTarget(); - - rtt->setAutoUpdated(false); - Viewport* vp = rtt->addViewport(mCellCamera); - vp->setOverlaysEnabled(false); - vp->setShadowsEnabled(false); - vp->setBackgroundColour(ColourValue(0, 0, 0)); - vp->setVisibilityMask(RV_Map); - vp->setMaterialScheme("local_map"); - - rtt->update(); - - // create "fog of war" texture - TexturePtr tex2 = TextureManager::getSingleton().createManual( - texture + "_fog", - ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - TEX_TYPE_2D, - xw*sFogOfWarResolution/sSize, yw*sFogOfWarResolution/sSize, - 0, - PF_A8R8G8B8, - TU_DYNAMIC_WRITE_ONLY_DISCARDABLE); - - // create a buffer to use for dynamic operations - std::vector buffer; - buffer.resize(sFogOfWarResolution*sFogOfWarResolution); - - // initialize to (0, 0, 0, 1) - for (int p=0; pgetBuffer()->lock(HardwareBuffer::HBL_DISCARD), &buffer[0], sFogOfWarResolution*sFogOfWarResolution*4); - tex2->getBuffer()->unlock(); + buffer[p] = (255 << 24); + } - mBuffers[texture] = buffer; + memcpy(tex2->getBuffer()->lock(HardwareBuffer::HBL_DISCARD), &buffer[0], sFogOfWarResolution*sFogOfWarResolution*4); + tex2->getBuffer()->unlock(); - // save to cache for next time - //rtt->writeContentsToFile("./" + texture + ".jpg"); - } + mBuffers[texture] = buffer; } + mRenderingManager->enableLights(true); mLight->setVisible(false); @@ -339,8 +329,6 @@ void LocalMap::updatePlayer (const Ogre::Vector3& position, const Ogre::Quaterni Vector3 playerdirection = mCameraRotNode->convertWorldToLocalOrientation(orientation).yAxis(); - Vector2 min(mBounds.getMinimum().x, mBounds.getMinimum().y); - if (!mInterior) { x = std::ceil(pos.x / sSize)-1; diff --git a/apps/openmw/mwrender/localmap.hpp b/apps/openmw/mwrender/localmap.hpp index 5384896407..638469d2d7 100644 --- a/apps/openmw/mwrender/localmap.hpp +++ b/apps/openmw/mwrender/localmap.hpp @@ -1,5 +1,5 @@ -#ifndef _GAME_RENDER_LOCALMAP_H -#define _GAME_RENDER_LOCALMAP_H +#ifndef GAME_RENDER_LOCALMAP_H +#define GAME_RENDER_LOCALMAP_H #include diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 50e41062a8..75744af38c 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -5,6 +5,8 @@ #include #include +#include + #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/class.hpp" @@ -59,6 +61,15 @@ std::string getVampireHead(const std::string& race, bool female) namespace MWRender { +float SayAnimationValue::getValue() const +{ + if (MWBase::Environment::get().getSoundManager()->sayDone(mReference)) + return 0; + else + // TODO: Use the loudness of the currently playing sound + return 1; +} + static NpcAnimation::PartBoneMap createPartListMap() { NpcAnimation::PartBoneMap result; @@ -97,10 +108,6 @@ NpcAnimation::~NpcAnimation() { if (!mListenerDisabled) mPtr.getClass().getInventoryStore(mPtr).setListener(NULL, mPtr); - - Ogre::SceneManager *sceneMgr = mInsert->getCreator(); - for(size_t i = 0;i < ESM::PRT_Count;i++) - destroyObjectList(sceneMgr, mObjectParts[i]); } @@ -110,11 +117,15 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, int v mListenerDisabled(disableListener), mViewMode(viewMode), mShowWeapons(false), - mShowShield(true), - mFirstPersonOffset(0.f, 0.f, 0.f) + mShowCarriedLeft(true), + mFirstPersonOffset(0.f, 0.f, 0.f), + mAlpha(1.f), + mNpcType(Type_Normal) { mNpc = mPtr.get()->mBase; + mSayAnimationValue = Ogre::SharedPtr(new SayAnimationValue(mPtr)); + for(size_t i = 0;i < ESM::PRT_Count;i++) { mPartslots[i] = -1; //each slot is empty @@ -147,8 +158,8 @@ void NpcAnimation::updateNpcBase() const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Race *race = store.get().find(mNpc->mRace); - bool isWerewolf = mPtr.getClass().getNpcStats(mPtr).isWerewolf(); - bool vampire = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Vampirism).mMagnitude; + bool isWerewolf = (mNpcType == Type_Werewolf); + bool isVampire = (mNpcType == Type_Vampire); if (isWerewolf) { @@ -157,7 +168,7 @@ void NpcAnimation::updateNpcBase() } else { - if (vampire) + if (isVampire) mHeadModel = getVampireHead(mNpc->mRace, mNpc->mFlags & ESM::NPC::Female); else mHeadModel = "meshes\\" + store.get().find(mNpc->mHead)->mModel; @@ -211,10 +222,24 @@ void NpcAnimation::updateNpcBase() } void NpcAnimation::updateParts() -{ +{ + mAlpha = 1.f; const MWWorld::Class &cls = MWWorld::Class::get(mPtr); MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr); + NpcType curType = Type_Normal; + if (cls.getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Vampirism).mMagnitude > 0) + curType = Type_Vampire; + if (cls.getNpcStats(mPtr).isWerewolf()) + curType = Type_Werewolf; + + if (curType != mNpcType) + { + mNpcType = curType; + rebuild(); + return; + } + static const struct { int mSlot; int mBasePriority; @@ -308,7 +333,7 @@ void NpcAnimation::updateParts() } showWeapons(mShowWeapons); - showShield(mShowShield); + showCarriedLeft(mShowCarriedLeft); // Remember body parts so we only have to search through the store once for each race/gender/viewmode combination static std::map< std::pair,std::vector > sRaceMapping; @@ -318,7 +343,7 @@ void NpcAnimation::updateParts() static const int Flag_Female = 1<<0; static const int Flag_FirstPerson = 1<<1; - bool isWerewolf = cls.getNpcStats(mPtr).isWerewolf(); + bool isWerewolf = (mNpcType == Type_Werewolf); int flags = (isWerewolf ? -1 : 0); if(!mNpc->isMale()) flags |= Flag_Female; @@ -436,18 +461,18 @@ public: } }; -NifOgre::ObjectList NpcAnimation::insertBoundedPart(const std::string &model, int group, const std::string &bonename, bool enchantedGlow, Ogre::Vector3* glowColor) +NifOgre::ObjectScenePtr NpcAnimation::insertBoundedPart(const std::string &model, int group, const std::string &bonename, bool enchantedGlow, Ogre::Vector3* glowColor) { - NifOgre::ObjectList objects = NifOgre::Loader::createObjects(mSkelBase, bonename, mInsert, model); + NifOgre::ObjectScenePtr objects = NifOgre::Loader::createObjects(mSkelBase, bonename, mInsert, model); setRenderProperties(objects, (mViewMode == VM_FirstPerson) ? RV_FirstPerson : mVisibilityFlags, RQG_Main, RQG_Alpha, 0, enchantedGlow, glowColor); - std::for_each(objects.mEntities.begin(), objects.mEntities.end(), SetObjectGroup(group)); - std::for_each(objects.mParticles.begin(), objects.mParticles.end(), SetObjectGroup(group)); + std::for_each(objects->mEntities.begin(), objects->mEntities.end(), SetObjectGroup(group)); + std::for_each(objects->mParticles.begin(), objects->mParticles.end(), SetObjectGroup(group)); - if(objects.mSkelBase) + if(objects->mSkelBase) { - Ogre::AnimationStateSet *aset = objects.mSkelBase->getAllAnimationStates(); + Ogre::AnimationStateSet *aset = objects->mSkelBase->getAllAnimationStates(); Ogre::AnimationStateIterator asiter = aset->getAnimationStateIterator(); while(asiter.hasMoreElements()) { @@ -455,7 +480,7 @@ NifOgre::ObjectList NpcAnimation::insertBoundedPart(const std::string &model, in state->setEnabled(false); state->setLoop(false); } - Ogre::SkeletonInstance *skelinst = objects.mSkelBase->getSkeleton(); + Ogre::SkeletonInstance *skelinst = objects->mSkelBase->getSkeleton(); Ogre::Skeleton::BoneIterator boneiter = skelinst->getBoneIterator(); while(boneiter.hasMoreElements()) boneiter.getNext()->setManuallyControlled(true); @@ -483,11 +508,13 @@ Ogre::Vector3 NpcAnimation::runAnimation(float timepassed) for(size_t i = 0;i < ESM::PRT_Count;i++) { - std::vector >::iterator ctrl(mObjectParts[i].mControllers.begin()); - for(;ctrl != mObjectParts[i].mControllers.end();ctrl++) + if (mObjectParts[i].isNull()) + continue; + std::vector >::iterator ctrl(mObjectParts[i]->mControllers.begin()); + for(;ctrl != mObjectParts[i]->mControllers.end();ctrl++) ctrl->update(); - Ogre::Entity *ent = mObjectParts[i].mSkelBase; + Ogre::Entity *ent = mObjectParts[i]->mSkelBase; if(!ent) continue; updateSkeletonInstance(baseinst, ent->getSkeleton()); ent->getAllAnimationStates()->_notifyDirty(); @@ -501,7 +528,7 @@ void NpcAnimation::removeIndividualPart(ESM::PartReferenceType type) mPartPriorities[type] = 0; mPartslots[type] = -1; - destroyObjectList(mInsert->getCreator(), mObjectParts[type]); + mObjectParts[type].setNull(); } void NpcAnimation::reserveIndividualPart(ESM::PartReferenceType type, int group, int priority) @@ -533,12 +560,12 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g mPartPriorities[type] = priority; mObjectParts[type] = insertBoundedPart(mesh, group, sPartList.at(type), enchantedGlow, glowColor); - if(mObjectParts[type].mSkelBase) + if(mObjectParts[type]->mSkelBase) { - Ogre::SkeletonInstance *skel = mObjectParts[type].mSkelBase->getSkeleton(); - if(mObjectParts[type].mSkelBase->isParentTagPoint()) + Ogre::SkeletonInstance *skel = mObjectParts[type]->mSkelBase->getSkeleton(); + if(mObjectParts[type]->mSkelBase->isParentTagPoint()) { - Ogre::Node *root = mObjectParts[type].mSkelBase->getParentNode(); + Ogre::Node *root = mObjectParts[type]->mSkelBase->getParentNode(); if(skel->hasBone("BoneOffset")) { Ogre::Bone *offset = skel->getBone("BoneOffset"); @@ -558,15 +585,18 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g } // TODO: - // type == ESM::PRT_Head should get an animation source based on the current output of - // the actor's 'say' sound (0 = silent, 1 = loud?). // type == ESM::PRT_Weapon should get an animation source based on the current offset // of the weapon attack animation (from its beginning, or start marker?) - std::vector >::iterator ctrl(mObjectParts[type].mControllers.begin()); - for(;ctrl != mObjectParts[type].mControllers.end();ctrl++) + std::vector >::iterator ctrl(mObjectParts[type]->mControllers.begin()); + for(;ctrl != mObjectParts[type]->mControllers.end();ctrl++) { if(ctrl->getSource().isNull()) + { ctrl->setSource(mNullAnimationValuePtr); + + if (type == ESM::PRT_Head) + ctrl->setSource(mSayAnimationValue); + } } return true; @@ -635,34 +665,28 @@ void NpcAnimation::showWeapons(bool showWeapon) { removeIndividualPart(ESM::PRT_Weapon); } + mAlpha = 1.f; } -void NpcAnimation::showShield(bool show) +void NpcAnimation::showCarriedLeft(bool show) { - mShowShield = show; + mShowCarriedLeft = show; MWWorld::InventoryStore &inv = MWWorld::Class::get(mPtr).getInventoryStore(mPtr); - MWWorld::ContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); + MWWorld::ContainerStoreIterator iter = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); - if (shield != inv.end() && shield->getTypeName() == typeid(ESM::Light).name()) + if(show && iter != inv.end()) { - // ... Except for lights, which are still shown during spellcasting since they - // have their own (one-handed) casting animations - show = true; - } - if(show && shield != inv.end()) - { - Ogre::Vector3 glowColor = getEnchantmentColor(*shield); - std::string mesh = MWWorld::Class::get(*shield).getModel(*shield); - addOrReplaceIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, 1, - mesh, !shield->getClass().getEnchantment(*shield).empty(), &glowColor); - - if (shield->getTypeName() == typeid(ESM::Light).name()) - addExtraLight(mInsert->getCreator(), mObjectParts[ESM::PRT_Shield], shield->get()->mBase); + Ogre::Vector3 glowColor = getEnchantmentColor(*iter); + std::string mesh = MWWorld::Class::get(*iter).getModel(*iter); + if (addOrReplaceIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, 1, + mesh, !iter->getClass().getEnchantment(*iter).empty(), &glowColor)) + { + if (iter->getTypeName() == typeid(ESM::Light).name()) + addExtraLight(mInsert->getCreator(), mObjectParts[ESM::PRT_Shield], iter->get()->mBase); + } } else - { removeIndividualPart(ESM::PRT_Shield); - } } void NpcAnimation::permanentEffectAdded(const ESM::MagicEffect *magicEffect, bool isNew, bool playSound) @@ -693,4 +717,68 @@ void NpcAnimation::permanentEffectAdded(const ESM::MagicEffect *magicEffect, boo } } +void NpcAnimation::setAlpha(float alpha) +{ + if (alpha == mAlpha) + return; + mAlpha = alpha; + + for (int i=0; imEntities.size(); ++j) + { + Ogre::Entity* ent = mObjectParts[i]->mEntities[j]; + if (ent != mObjectParts[i]->mSkelBase) + applyAlpha(alpha, ent, mObjectParts[i]); + } + } +} + +void NpcAnimation::preRender(Ogre::Camera *camera) +{ + Animation::preRender(camera); + for (int i=0; irotateBillboardNodes(camera); + } +} + +void NpcAnimation::applyAlpha(float alpha, Ogre::Entity *ent, NifOgre::ObjectScenePtr scene) +{ + sh::Factory::getInstance()._ensureMaterial(ent->getSubEntity(0)->getMaterial()->getName(), "Default"); + ent->getSubEntity(0)->setRenderQueueGroup(alpha != 1.f || ent->getSubEntity(0)->getMaterial()->isTransparent() + ? RQG_Alpha : RQG_Main); + + + Ogre::MaterialPtr mat = scene->mMaterialControllerMgr.getWritableMaterial(ent); + if (mAlpha == 1.f) + { + // Don't bother remembering what the original values were. Just remove the techniques and let the factory restore them. + mat->removeAllTechniques(); + sh::Factory::getInstance()._ensureMaterial(mat->getName(), "Default"); + return; + } + + Ogre::Material::TechniqueIterator techs = mat->getTechniqueIterator(); + while(techs.hasMoreElements()) + { + Ogre::Technique *tech = techs.getNext(); + Ogre::Technique::PassIterator passes = tech->getPassIterator(); + while(passes.hasMoreElements()) + { + Ogre::Pass *pass = passes.getNext(); + pass->setSceneBlending(Ogre::SBT_TRANSPARENT_ALPHA); + Ogre::ColourValue diffuse = pass->getDiffuse(); + diffuse.a = alpha; + pass->setDiffuse(diffuse); + pass->setVertexColourTracking(pass->getVertexColourTracking() &~Ogre::TVC_DIFFUSE); + } + } +} + } diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index c33d511ecc..6f0403b9d4 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -1,5 +1,5 @@ -#ifndef _GAME_RENDER_NPCANIMATION_H -#define _GAME_RENDER_NPCANIMATION_H +#ifndef GAME_RENDER_NPCANIMATION_H +#define GAME_RENDER_NPCANIMATION_H #include "animation.hpp" @@ -13,6 +13,18 @@ namespace ESM namespace MWRender { +class SayAnimationValue : public Ogre::ControllerValue +{ +private: + MWWorld::Ptr mReference; +public: + SayAnimationValue(MWWorld::Ptr reference) : mReference(reference) {} + + virtual Ogre::Real getValue() const; + virtual void setValue(Ogre::Real value) + { } +}; + class NpcAnimation : public Animation, public MWWorld::InventoryStoreListener { public: @@ -34,14 +46,22 @@ private: bool mListenerDisabled; // Bounded Parts - NifOgre::ObjectList mObjectParts[ESM::PRT_Count]; + NifOgre::ObjectScenePtr mObjectParts[ESM::PRT_Count]; const ESM::NPC *mNpc; std::string mHeadModel; std::string mHairModel; ViewMode mViewMode; bool mShowWeapons; - bool mShowShield; + bool mShowCarriedLeft; + + enum NpcType + { + Type_Normal, + Type_Werewolf, + Type_Vampire + }; + NpcType mNpcType; int mVisibilityFlags; @@ -50,9 +70,13 @@ private: Ogre::Vector3 mFirstPersonOffset; + Ogre::SharedPtr mSayAnimationValue; + + float mAlpha; + void updateNpcBase(); - NifOgre::ObjectList insertBoundedPart(const std::string &model, int group, const std::string &bonename, + NifOgre::ObjectScenePtr insertBoundedPart(const std::string &model, int group, const std::string &bonename, bool enchantedGlow, Ogre::Vector3* glowColor=NULL); void removeIndividualPart(ESM::PartReferenceType type); @@ -64,6 +88,8 @@ private: void addPartGroup(int group, int priority, const std::vector &parts, bool enchantedGlow=false, Ogre::Vector3* glowColor=NULL); + void applyAlpha(float alpha, Ogre::Entity* ent, NifOgre::ObjectScenePtr scene); + public: /** * @param ptr @@ -82,7 +108,7 @@ public: virtual Ogre::Vector3 runAnimation(float timepassed); virtual void showWeapons(bool showWeapon); - virtual void showShield(bool showShield); + virtual void showCarriedLeft(bool showa); void setViewMode(ViewMode viewMode); @@ -95,6 +121,12 @@ public: /// Rebuilds the NPC, updating their root model, animation sources, and equipment. void rebuild(); + + /// Make the NPC only partially visible + virtual void setAlpha(float alpha); + + /// Prepare this animation for being rendered with \a camera (rotates billboard nodes) + virtual void preRender (Ogre::Camera* camera); }; } diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index 0023d0ea54..e721477ee0 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -245,11 +245,16 @@ void Objects::disableLights() it->second->enableLights(false); } -void Objects::update(const float dt) +void Objects::update(float dt, Ogre::Camera* camera) { PtrAnimationMap::const_iterator it = mObjects.begin(); for(;it != mObjects.end();it++) it->second->runAnimation(dt); + + it = mObjects.begin(); + for(;it != mObjects.end();it++) + it->second->preRender(camera); + } void Objects::rebuildStaticGeometry() diff --git a/apps/openmw/mwrender/objects.hpp b/apps/openmw/mwrender/objects.hpp index 022752fae9..665a1e6570 100644 --- a/apps/openmw/mwrender/objects.hpp +++ b/apps/openmw/mwrender/objects.hpp @@ -1,5 +1,5 @@ -#ifndef _GAME_RENDER_OBJECTS_H -#define _GAME_RENDER_OBJECTS_H +#ifndef GAME_RENDER_OBJECTS_H +#define GAME_RENDER_OBJECTS_H #include #include @@ -48,7 +48,7 @@ public: void enableLights(); void disableLights(); - void update (const float dt); + void update (float dt, Ogre::Camera* camera); ///< per-frame update Ogre::AxisAlignedBox getDimensions(MWWorld::CellStore*); diff --git a/apps/openmw/mwrender/occlusionquery.hpp b/apps/openmw/mwrender/occlusionquery.hpp index 983361c187..6974f37b96 100644 --- a/apps/openmw/mwrender/occlusionquery.hpp +++ b/apps/openmw/mwrender/occlusionquery.hpp @@ -1,5 +1,5 @@ -#ifndef _GAME_OCCLUSION_QUERY_H -#define _GAME_OCCLUSION_QUERY_H +#ifndef GAME_OCCLUSION_QUERY_H +#define GAME_OCCLUSION_QUERY_H #include #include diff --git a/apps/openmw/mwrender/renderinginterface.hpp b/apps/openmw/mwrender/renderinginterface.hpp index 8ae2c0f8f9..02f3c804a0 100644 --- a/apps/openmw/mwrender/renderinginterface.hpp +++ b/apps/openmw/mwrender/renderinginterface.hpp @@ -1,5 +1,5 @@ -#ifndef _GAME_RENDERING_INTERFACE_H -#define _GAME_RENDERING_INTERFACE_H +#ifndef GAME_RENDERING_INTERFACE_H +#define GAME_RENDERING_INTERFACE_H namespace MWRender { diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 8aca3b4891..f97d7bebf5 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -8,10 +8,6 @@ #include #include #include -#include -#include -#include -#include #include #include #include @@ -43,7 +39,6 @@ #include "shadows.hpp" #include "localmap.hpp" #include "water.hpp" -#include "compositors.hpp" #include "npcanimation.hpp" #include "externalrendering.hpp" #include "globalmap.hpp" @@ -60,14 +55,14 @@ RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const b MWWorld::Fallback* fallback) : mRendering(_rend) , mFallback(fallback) - , mObjects(mRendering) - , mActors(mRendering, this) , mPlayerAnimation(NULL) , mAmbientMode(0) , mSunEnabled(0) , mPhysicsEngine(engine) , mTerrain(NULL) { + mActors = new MWRender::Actors(mRendering, this); + mObjects = new MWRender::Objects(mRendering); // select best shader mode bool openGL = (Ogre::Root::getSingleton ().getRenderSystem ()->getName().find("OpenGL") != std::string::npos); bool glES = (Ogre::Root::getSingleton ().getRenderSystem ()->getName().find("OpenGL ES") != std::string::npos); @@ -87,8 +82,6 @@ RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const b mRendering.getWindow()->addListener(this); mRendering.setWindowListener(this); - mCompositors = new Compositors(mRendering.getViewport()); - mWater = 0; // material system @@ -116,10 +109,13 @@ RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const b mFactory->loadAllFiles(); - // Set default mipmap level (NB some APIs ignore this) - // Mipmap generation is currently disabled because it causes issues on Intel/AMD - //TextureManager::getSingleton().setDefaultNumMipmaps(Settings::Manager::getInt("num mipmaps", "General")); + // Compressed textures with 0 mip maps are bugged in 1.8, so disable mipmap generator in that case + // ( https://ogre3d.atlassian.net/browse/OGRE-259 ) +#if OGRE_VERSION >= (1 << 16 | 9 << 8 | 0) + TextureManager::getSingleton().setDefaultNumMipmaps(Settings::Manager::getInt("num mipmaps", "General")); +#else TextureManager::getSingleton().setDefaultNumMipmaps(0); +#endif // Set default texture filtering options TextureFilterOptions tfo; @@ -157,13 +153,11 @@ RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const b sh::Factory::getInstance ().setGlobalSetting ("viewproj_fix", "false"); sh::Factory::getInstance ().setSharedParameter ("vpRow2Fix", sh::makeProperty (new sh::Vector4(0,0,0,0))); - applyCompositors(); - mRootNode = mRendering.getScene()->getRootSceneNode(); mRootNode->createChildSceneNode("player"); - mObjects.setRootNode(mRootNode); - mActors.setRootNode(mRootNode); + mObjects->setRootNode(mRootNode); + mActors->setRootNode(mRootNode); mCamera = new MWRender::Camera(mRendering.getCamera()); @@ -198,9 +192,10 @@ RenderingManager::~RenderingManager () delete mTerrain; delete mLocalMap; delete mOcclusionQuery; - delete mCompositors; delete mWater; delete mVideoPlayer; + delete mActors; + delete mObjects; delete mFactory; } @@ -210,10 +205,10 @@ MWRender::SkyManager* RenderingManager::getSkyManager() } MWRender::Objects& RenderingManager::getObjects(){ - return mObjects; + return *mObjects; } MWRender::Actors& RenderingManager::getActors(){ - return mActors; + return *mActors; } OEngine::Render::Fader* RenderingManager::getFader() @@ -228,8 +223,8 @@ OEngine::Render::Fader* RenderingManager::getFader() void RenderingManager::removeCell (MWWorld::CellStore *store) { - mObjects.removeCell(store); - mActors.removeCell(store); + mObjects->removeCell(store); + mActors->removeCell(store); mDebugging->cellRemoved(store); } @@ -245,7 +240,7 @@ void RenderingManager::toggleWater() void RenderingManager::cellAdded (MWWorld::CellStore *store) { - mObjects.buildStaticGeometry (*store); + mObjects->buildStaticGeometry (*store); sh::Factory::getInstance().unloadUnreferencedMaterials(); mDebugging->cellAdded(store); waterAdded(store); @@ -259,8 +254,8 @@ void RenderingManager::addObject (const MWWorld::Ptr& ptr){ void RenderingManager::removeObject (const MWWorld::Ptr& ptr) { - if (!mObjects.deleteObject (ptr)) - mActors.deleteObject (ptr); + if (!mObjects->deleteObject (ptr)) + mActors->deleteObject (ptr); } void RenderingManager::moveObject (const MWWorld::Ptr& ptr, const Ogre::Vector3& position) @@ -300,9 +295,9 @@ RenderingManager::updateObjectCell(const MWWorld::Ptr &old, const MWWorld::Ptr & parent->removeChild(child); if (MWWorld::Class::get(old).isActor()) { - mActors.updateObjectCell(old, cur); + mActors->updateObjectCell(old, cur); } else { - mObjects.updateObjectCell(old, cur); + mObjects->updateObjectCell(old, cur); } } @@ -320,7 +315,7 @@ void RenderingManager::rebuildPtr(const MWWorld::Ptr &ptr) if(ptr.getRefData().getHandle() == "player") anim = mPlayerAnimation; else if(MWWorld::Class::get(ptr).isActor()) - anim = dynamic_cast(mActors.getAnimation(ptr)); + anim = dynamic_cast(mActors->getAnimation(ptr)); if(anim) { anim->rebuild(); @@ -338,7 +333,7 @@ void RenderingManager::update (float duration, bool paused) MWWorld::Ptr player = world->getPlayer().getPlayer(); - int blind = MWWorld::Class::get(player).getCreatureStats(player).getMagicEffects().get(MWMechanics::EffectKey(ESM::MagicEffect::Blind)).mMagnitude; + int blind = MWWorld::Class::get(player).getCreatureStats(player).getMagicEffects().get(ESM::MagicEffect::Blind).mMagnitude; mRendering.getFader()->setFactor(std::max(0.f, 1.f-(blind / 100.f))); setAmbientMode(); @@ -385,9 +380,9 @@ void RenderingManager::update (float duration, bool paused) if(paused) return; - mActors.update (duration); - mObjects.update (duration); - + mActors->update (mRendering.getCamera()); + mPlayerAnimation->preRender(mRendering.getCamera()); + mObjects->update (duration, mRendering.getCamera()); mSkyManager->update(duration); @@ -481,29 +476,21 @@ bool RenderingManager::toggleRenderMode(int mode) { if (mRendering.getCamera()->getPolygonMode() == PM_SOLID) { - mCompositors->setEnabled(false); - mRendering.getCamera()->setPolygonMode(PM_WIREFRAME); return true; } else { - mCompositors->setEnabled(true); - mRendering.getCamera()->setPolygonMode(PM_SOLID); return false; } } - else if (mode == MWBase::World::Render_BoundingBoxes) + else //if (mode == MWBase::World::Render_BoundingBoxes) { bool show = !mRendering.getScene()->getShowBoundingBoxes(); mRendering.getScene()->showBoundingBoxes(show); return show; } - else //if (mode == MWBase::World::Render_Compositors) - { - return mCompositors->toggle(); - } } void RenderingManager::configureFog(MWWorld::CellStore &mCell) @@ -611,7 +598,7 @@ void RenderingManager::setAmbientColour(const Ogre::ColourValue& colour) mAmbientColor = colour; MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); - int nightEye = MWWorld::Class::get(player).getCreatureStats(player).getMagicEffects().get(MWMechanics::EffectKey(ESM::MagicEffect::NightEye)).mMagnitude; + int nightEye = MWWorld::Class::get(player).getCreatureStats(player).getMagicEffects().get(ESM::MagicEffect::NightEye).mMagnitude; Ogre::ColourValue final = colour; final += Ogre::ColourValue(0.7,0.7,0.7,0) * std::min(1.f, (nightEye/100.f)); @@ -662,7 +649,7 @@ void RenderingManager::requestMap(MWWorld::CellStore* cell) { assert(mTerrain); - Ogre::AxisAlignedBox dims = mObjects.getDimensions(cell); + Ogre::AxisAlignedBox dims = mObjects->getDimensions(cell); Ogre::Vector2 center(cell->mCell->getGridX() + 0.5, cell->mCell->getGridY() + 0.5); dims.merge(mTerrain->getWorldBoundingBox(center)); @@ -672,7 +659,7 @@ void RenderingManager::requestMap(MWWorld::CellStore* cell) mLocalMap->requestMap(cell, dims.getMinimum().z, dims.getMaximum().z); } else - mLocalMap->requestMap(cell, mObjects.getDimensions(cell)); + mLocalMap->requestMap(cell, mObjects->getDimensions(cell)); } void RenderingManager::preCellChange(MWWorld::CellStore* cell) @@ -682,13 +669,13 @@ void RenderingManager::preCellChange(MWWorld::CellStore* cell) void RenderingManager::disableLights(bool sun) { - mObjects.disableLights(); + mObjects->disableLights(); sunDisable(sun); } void RenderingManager::enableLights(bool sun) { - mObjects.enableLights(); + mObjects->enableLights(); sunEnable(sun); } @@ -748,11 +735,6 @@ Ogre::Vector4 RenderingManager::boundingBoxToScreen(Ogre::AxisAlignedBox bounds) return Vector4(min_x, min_y, max_x, max_y); } -Compositors* RenderingManager::getCompositors() -{ - return mCompositors; -} - void RenderingManager::processChangedSettings(const Settings::CategorySettingVector& settings) { bool changeRes = false; @@ -798,7 +780,6 @@ void RenderingManager::processChangedSettings(const Settings::CategorySettingVec } else if (it->second == "shader" && it->first == "Water") { - applyCompositors(); sh::Factory::getInstance ().setGlobalSetting ("simple_water", Settings::Manager::getBool("shader", "Water") ? "false" : "true"); rebuild = true; mRendering.getViewport ()->setClearEveryFrame (true); @@ -864,7 +845,7 @@ void RenderingManager::processChangedSettings(const Settings::CategorySettingVec if (rebuild) { - mObjects.rebuildStaticGeometry(); + mObjects->rebuildStaticGeometry(); if (mTerrain) mTerrain->applyMaterials(Settings::Manager::getBool("enabled", "Shadows"), Settings::Manager::getBool("split", "Shadows")); @@ -886,28 +867,16 @@ void RenderingManager::windowResized(int x, int y) Settings::Manager::setInt("resolution x", "Video", x); Settings::Manager::setInt("resolution y", "Video", y); mRendering.adjustViewport(); - mCompositors->recreate(); mVideoPlayer->setResolution (x, y); MWBase::Environment::get().getWindowManager()->windowResized(x,y); } -void RenderingManager::applyCompositors() -{ -} - void RenderingManager::getTriangleBatchCount(unsigned int &triangles, unsigned int &batches) { - if (mCompositors->anyCompositorEnabled()) - { - mCompositors->countTrianglesBatches(triangles, batches); - } - else - { - triangles = mRendering.getWindow()->getTriangleCount(); - batches = mRendering.getWindow()->getBatchCount(); - } + batches = mRendering.getWindow()->getBatchCount(); + triangles = mRendering.getWindow()->getTriangleCount(); } void RenderingManager::setupPlayer(const MWWorld::Ptr &ptr) @@ -981,13 +950,13 @@ void RenderingManager::setupExternalRendering (MWRender::ExternalRendering& rend Animation* RenderingManager::getAnimation(const MWWorld::Ptr &ptr) { - Animation *anim = mActors.getAnimation(ptr); + Animation *anim = mActors->getAnimation(ptr); if(!anim && ptr.getRefData().getHandle() == "player") anim = mPlayerAnimation; if (!anim) - anim = mObjects.getAnimation(ptr); + anim = mObjects->getAnimation(ptr); return anim; } @@ -1062,4 +1031,9 @@ void RenderingManager::enableTerrain(bool enable) mTerrain->setVisible(false); } +float RenderingManager::getCameraDistance() const +{ + return mCamera->getCameraDistance(); +} + } // namespace diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 5bbc3055d7..5631c94701 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -1,5 +1,5 @@ -#ifndef _GAME_RENDERING_MANAGER_H -#define _GAME_RENDERING_MANAGER_H +#ifndef GAME_RENDERING_MANAGER_H +#define GAME_RENDERING_MANAGER_H #include "sky.hpp" #include "debugging.hpp" @@ -48,7 +48,6 @@ namespace MWRender class Shadows; class LocalMap; class Water; - class Compositors; class ExternalRendering; class GlobalMap; class VideoPlayer; @@ -91,12 +90,12 @@ public: bool vanityRotateCamera(const float *rot); void setCameraDistance(float dist, bool adjust = false, bool override = true); + float getCameraDistance() const; void setupPlayer(const MWWorld::Ptr &ptr); void renderPlayer(const MWWorld::Ptr &ptr); SkyManager* getSkyManager(); - Compositors* getCompositors(); MWRender::Camera* getCamera() const; @@ -226,8 +225,6 @@ private: void setMenuTransparency(float val); - void applyCompositors(); - bool mSunEnabled; MWWorld::Fallback* mFallback; @@ -244,8 +241,8 @@ private: OEngine::Render::OgreRenderer &mRendering; - MWRender::Objects mObjects; - MWRender::Actors mActors; + MWRender::Objects* mObjects; + MWRender::Actors* mActors; MWRender::NpcAnimation *mPlayerAnimation; @@ -271,8 +268,6 @@ private: MWRender::Shadows* mShadows; - MWRender::Compositors* mCompositors; - VideoPlayer* mVideoPlayer; }; diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 03e14dc07d..39f7ccc854 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -285,10 +285,10 @@ void SkyManager::create() // Stars mAtmosphereNight = mRootNode->createChildSceneNode(); - NifOgre::ObjectList objects = NifOgre::Loader::createObjects(mAtmosphereNight, "meshes\\sky_night_01.nif"); - for(size_t i = 0, matidx = 0;i < objects.mEntities.size();i++) + NifOgre::ObjectScenePtr objects = NifOgre::Loader::createObjects(mAtmosphereNight, "meshes\\sky_night_01.nif"); + for(size_t i = 0, matidx = 0;i < objects->mEntities.size();i++) { - Entity* night1_ent = objects.mEntities[i]; + Entity* night1_ent = objects->mEntities[i]; night1_ent->setRenderQueueGroup(RQG_SkiesEarly+1); night1_ent->setVisibilityFlags(RV_Sky); night1_ent->setCastShadows(false); @@ -307,14 +307,14 @@ void SkyManager::create() night1_ent->getSubEntity(j)->setMaterialName(matName); } } - + mObjects.push_back(objects); // Atmosphere (day) mAtmosphereDay = mRootNode->createChildSceneNode(); objects = NifOgre::Loader::createObjects(mAtmosphereDay, "meshes\\sky_atmosphere.nif"); - for(size_t i = 0;i < objects.mEntities.size();i++) + for(size_t i = 0;i < objects->mEntities.size();i++) { - Entity* atmosphere_ent = objects.mEntities[i]; + Entity* atmosphere_ent = objects->mEntities[i]; atmosphere_ent->setCastShadows(false); atmosphere_ent->setRenderQueueGroup(RQG_SkiesEarly); atmosphere_ent->setVisibilityFlags(RV_Sky); @@ -325,14 +325,14 @@ void SkyManager::create() // Using infinite AAB here to prevent being clipped by the custom near clip plane used for reflections/refractions atmosphere_ent->getMesh()->_setBounds (aabInf); } - + mObjects.push_back(objects); // Clouds SceneNode* clouds_node = mRootNode->createChildSceneNode(); objects = NifOgre::Loader::createObjects(clouds_node, "meshes\\sky_clouds_01.nif"); - for(size_t i = 0;i < objects.mEntities.size();i++) + for(size_t i = 0;i < objects->mEntities.size();i++) { - Entity* clouds_ent = objects.mEntities[i]; + Entity* clouds_ent = objects->mEntities[i]; clouds_ent->setVisibilityFlags(RV_Sky); clouds_ent->setRenderQueueGroup(RQG_SkiesEarly+5); for(unsigned int j = 0;j < clouds_ent->getNumSubEntities();j++) @@ -341,6 +341,7 @@ void SkyManager::create() // Using infinite AAB here to prevent being clipped by the custom near clip plane used for reflections/refractions clouds_ent->getMesh()->_setBounds (aabInf); } + mObjects.push_back(objects); mCreated = true; } diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index 3df8846cd4..965907a979 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -11,6 +11,8 @@ #include +#include + #include "../mwworld/weather.hpp" @@ -196,6 +198,8 @@ namespace MWRender Ogre::SceneNode* mAtmosphereDay; Ogre::SceneNode* mAtmosphereNight; + std::vector mObjects; + // remember some settings so we don't have to apply them again if they didnt change Ogre::String mClouds; Ogre::String mNextClouds; diff --git a/apps/openmw/mwrender/videoplayer.cpp b/apps/openmw/mwrender/videoplayer.cpp index ee2b80f731..adf20dc633 100644 --- a/apps/openmw/mwrender/videoplayer.cpp +++ b/apps/openmw/mwrender/videoplayer.cpp @@ -949,9 +949,25 @@ void VideoState::init(const std::string& resourceName) this->format_ctx->pb = ioCtx; // Open video file - /// \todo leak here, ffmpeg or valgrind bug ? + /// + /// format_ctx->pb->buffer must be freed by hand, + /// if not, valgrind will show memleak, see: + /// + /// https://trac.ffmpeg.org/ticket/1357 + /// if(!this->format_ctx || avformat_open_input(&this->format_ctx, resourceName.c_str(), NULL, NULL)) { + if (this->format_ctx != NULL) + { + if (this->format_ctx->pb != NULL) + { + av_free(this->format_ctx->pb->buffer); + this->format_ctx->pb->buffer = NULL; + + av_free(this->format_ctx->pb); + this->format_ctx->pb = NULL; + } + } // "Note that a user-supplied AVFormatContext will be freed on failure." this->format_ctx = NULL; av_free(ioCtx); @@ -989,9 +1005,12 @@ void VideoState::deinit() this->audioq.cond.notify_one(); this->videoq.cond.notify_one(); - this->parse_thread.join(); - this->video_thread.join(); - this->refresh_thread.join(); + if (this->parse_thread.joinable()) + this->parse_thread.join(); + if (this->video_thread.joinable()) + this->video_thread.join(); + if (this->refresh_thread.joinable()) + this->refresh_thread.join(); if(this->audio_st) avcodec_close((*this->audio_st)->codec); @@ -1006,9 +1025,21 @@ void VideoState::deinit() if(this->format_ctx) { - AVIOContext *ioContext = this->format_ctx->pb; + /// + /// format_ctx->pb->buffer must be freed by hand, + /// if not, valgrind will show memleak, see: + /// + /// https://trac.ffmpeg.org/ticket/1357 + /// + if (this->format_ctx->pb != NULL) + { + av_free(this->format_ctx->pb->buffer); + this->format_ctx->pb->buffer = NULL; + + av_free(this->format_ctx->pb); + this->format_ctx->pb = NULL;; + } avformat_close_input(&this->format_ctx); - av_free(ioContext); } } diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 082551f371..0a4db30e9c 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -292,6 +292,7 @@ Water::~Water() delete mReflection; delete mRefraction; + delete mSimulation; } void Water::changeCell(const ESM::Cell* cell) diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp index bc15b4980a..481a412977 100644 --- a/apps/openmw/mwrender/water.hpp +++ b/apps/openmw/mwrender/water.hpp @@ -133,8 +133,6 @@ namespace MWRender { RenderingManager* mRendering; SkyManager* mSky; - std::string mCompositorName; - Ogre::MaterialPtr mMaterial; bool mUnderwaterEffect; diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index 1cdbaa008d..966a064c74 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -225,7 +225,8 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - runtime.push(MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSetting (mIndex)); + runtime.push(MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSetting ( + (MWMechanics::CreatureStats::AiSetting)mIndex).getModified()); } }; template @@ -241,8 +242,11 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - MWWorld::Class::get (ptr).getCreatureStats (ptr).setAiSetting (mIndex, - MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSetting (mIndex) + value); + MWMechanics::CreatureStats::AiSetting setting + = MWMechanics::CreatureStats::AiSetting(mIndex); + + MWWorld::Class::get (ptr).getCreatureStats (ptr).setAiSetting (setting, + MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSetting (setting).getBase() + value); } }; template @@ -258,8 +262,11 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - MWWorld::Class::get (ptr).getCreatureStats (ptr).setAiSetting (mIndex, - value); + MWMechanics::CreatureStats::AiSetting setting = (MWMechanics::CreatureStats::AiSetting)mIndex; + + MWMechanics::Stat stat = ptr.getClass().getCreatureStats(ptr).getAiSetting(setting); + stat.setModified(value, 0); + ptr.getClass().getCreatureStats(ptr).setAiSetting(setting, stat); } }; diff --git a/apps/openmw/mwscript/cellextensions.cpp b/apps/openmw/mwscript/cellextensions.cpp index ce39cfa653..5a75e78157 100644 --- a/apps/openmw/mwscript/cellextensions.cpp +++ b/apps/openmw/mwscript/cellextensions.cpp @@ -43,10 +43,13 @@ namespace MWScript ESM::Position pos; MWBase::World *world = MWBase::Environment::get().getWorld(); - if (world->findExteriorPosition(cell, pos)) { + world->getPlayer().setTeleported(true); + if (world->findExteriorPosition(cell, pos)) + { world->changeToExteriorCell(pos); } - else { + else + { // Change to interior even if findInteriorPosition() // yields false. In this case position will be zero-point. world->findInteriorPosition(cell, pos); @@ -68,13 +71,14 @@ namespace MWScript runtime.pop(); ESM::Position pos; - - MWBase::Environment::get().getWorld()->indexToPosition (x, y, pos.pos[0], pos.pos[1], true); + MWBase::World *world = MWBase::Environment::get().getWorld(); + world->getPlayer().setTeleported(true); + world->indexToPosition (x, y, pos.pos[0], pos.pos[1], true); pos.pos[2] = 0; pos.rot[0] = pos.rot[1] = pos.rot[2] = 0; - MWBase::Environment::get().getWorld()->changeToExteriorCell (pos); + world->changeToExteriorCell (pos); } }; diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index 9e024f8723..504a8638bf 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -127,7 +127,6 @@ op 0x200007e-0x2000084: Enable Controls op 0x2000085-0x200008b: Disable Controls op 0x200008c: Unlock op 0x200008d: Unlock, explicit reference -op 0x200008e: COE op 0x200008e-0x20000a8: GetSkill op 0x20000a9-0x20000c3: GetSkill, explicit reference op 0x20000c4-0x20000de: SetSkill @@ -358,5 +357,18 @@ op 0x2000222: GetLineOfSight op 0x2000223: GetLineOfSightExplicit op 0x2000224: ToggleAI op 0x2000225: ToggleAIExplicit - -opcodes 0x2000226-0x3ffffff unused +op 0x2000226: COE +op 0x2000227: Cast +op 0x2000228: Cast, explicit +op 0x2000229: ExplodeSpell +op 0x200022a: ExplodeSpell, explicit +op 0x200022b: RemoveSpellEffects +op 0x200022c: RemoveSpellEffects, explicit +op 0x200022d: RemoveEffects +op 0x200022e: RemoveEffects, explicit +op 0x200022f: Resurrect +op 0x2000230: Resurrect, explicit +op 0x2000231: GetSpellReadied +op 0x2000232: GetSpellReadied, explicit +op 0x2000233: GetPcJumping +opcodes 0x2000234-0x3ffffff unused diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 8ca97d2881..240a375c30 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -23,6 +23,7 @@ #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/spellcasting.hpp" #include "interpretercontext.hpp" #include "ref.hpp" @@ -57,6 +58,18 @@ namespace MWScript } }; + class OpGetPcJumping : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWBase::World* world = MWBase::Environment::get().getWorld(); + MWWorld::Ptr player = world->getPlayer().getPlayer(); + runtime.push (!world->isOnGround(player) && !world->isFlying(player)); + } + }; + class OpWakeUpPc : public Interpreter::Opcode0 { public: @@ -464,7 +477,20 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - runtime.push(MWWorld::Class::get(ptr).getNpcStats (ptr).getDrawState () == MWMechanics::DrawState_Weapon); + runtime.push(ptr.getClass().getNpcStats (ptr).getDrawState () == MWMechanics::DrawState_Weapon); + } + }; + + template + class OpGetSpellReadied : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + runtime.push(ptr.getClass().getNpcStats (ptr).getDrawState () == MWMechanics::DrawState_Spell); } }; @@ -720,6 +746,42 @@ namespace MWScript } }; + template + class OpCast : public Interpreter::Opcode0 + { + public: + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + std::string spell = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + std::string targetId = ::Misc::StringUtils::lowerCase(runtime.getStringLiteral (runtime[0].mInteger)); + runtime.pop(); + + MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr (targetId, false); + + MWMechanics::CastSpell cast(ptr, target); + cast.cast(spell); + } + }; + + template + class OpExplodeSpell : public Interpreter::Opcode0 + { + public: + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + std::string spell = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + MWMechanics::CastSpell cast(ptr, ptr); + cast.cast(spell); + } + }; void installOpcodes (Interpreter::Interpreter& interpreter) { @@ -741,6 +803,7 @@ namespace MWScript interpreter.installSegment5 (Compiler::Misc::opcodeDontSaveObject, new OpDontSaveObject); interpreter.installSegment5 (Compiler::Misc::opcodeToggleVanityMode, new OpToggleVanityMode); interpreter.installSegment5 (Compiler::Misc::opcodeGetPcSleep, new OpGetPcSleep); + interpreter.installSegment5 (Compiler::Misc::opcodeGetPcJumping, new OpGetPcJumping); interpreter.installSegment5 (Compiler::Misc::opcodeWakeUpPc, new OpWakeUpPc); interpreter.installSegment5 (Compiler::Misc::opcodePlayBink, new OpPlayBink); interpreter.installSegment5 (Compiler::Misc::opcodeGetLocked, new OpGetLocked); @@ -759,6 +822,8 @@ namespace MWScript interpreter.installSegment5 (Compiler::Misc::opcodeGetAttackedExplicit, new OpGetAttacked); interpreter.installSegment5 (Compiler::Misc::opcodeGetWeaponDrawn, new OpGetWeaponDrawn); interpreter.installSegment5 (Compiler::Misc::opcodeGetWeaponDrawnExplicit, new OpGetWeaponDrawn); + interpreter.installSegment5 (Compiler::Misc::opcodeGetSpellReadied, new OpGetSpellReadied); + interpreter.installSegment5 (Compiler::Misc::opcodeGetSpellReadiedExplicit, new OpGetSpellReadied); interpreter.installSegment5 (Compiler::Misc::opcodeGetSpellEffects, new OpGetSpellEffects); interpreter.installSegment5 (Compiler::Misc::opcodeGetSpellEffectsExplicit, new OpGetSpellEffects); interpreter.installSegment5 (Compiler::Misc::opcodeGetCurrentTime, new OpGetCurrentTime); @@ -781,6 +846,10 @@ namespace MWScript interpreter.installSegment5 (Compiler::Misc::opcodeToggleGodMode, new OpToggleGodMode); interpreter.installSegment5 (Compiler::Misc::opcodeDisableLevitation, new OpEnableLevitation); interpreter.installSegment5 (Compiler::Misc::opcodeEnableLevitation, new OpEnableLevitation); + interpreter.installSegment5 (Compiler::Misc::opcodeCast, new OpCast); + interpreter.installSegment5 (Compiler::Misc::opcodeCastExplicit, new OpCast); + interpreter.installSegment5 (Compiler::Misc::opcodeExplodeSpell, new OpExplodeSpell); + interpreter.installSegment5 (Compiler::Misc::opcodeExplodeSpellExplicit, new OpExplodeSpell); } } } diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 603515ff4a..f053e9a97e 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -124,10 +124,9 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - MWWorld::Class::get(ptr) - .getCreatureStats(ptr) - .getAttribute(mIndex) - .setModified (value, 0); + MWMechanics::AttributeValue attribute = ptr.getClass().getCreatureStats(ptr).getAttribute(mIndex); + attribute.setBase (value - (attribute.getModified() - attribute.getBase())); + ptr.getClass().getCreatureStats(ptr).setAttribute(mIndex, attribute); } }; @@ -147,16 +146,12 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - value += - MWWorld::Class::get(ptr) - .getCreatureStats(ptr) - .getAttribute(mIndex) - .getModified(); - - MWWorld::Class::get(ptr) + MWMechanics::AttributeValue attribute = MWWorld::Class::get(ptr) .getCreatureStats(ptr) - .getAttribute(mIndex) - .setModified (value, 0, 100); + .getAttribute(mIndex); + + attribute.setBase (std::min(100, attribute.getBase() + value)); + ptr.getClass().getCreatureStats(ptr).setAttribute(mIndex, attribute); } }; @@ -209,6 +204,7 @@ namespace MWScript .getDynamic (mIndex)); stat.setModified (value, 0); + stat.setCurrent(value); MWWorld::Class::get (ptr).getCreatureStats (ptr).setDynamic (mIndex, stat); } @@ -346,17 +342,13 @@ namespace MWScript const ESM::Class& class_ = *MWBase::Environment::get().getWorld()->getStore().get().find (ref->mBase->mClass); - float level = 0; - float progress = std::modf (stats.getSkill (mIndex).getBase(), &level); - - float modifier = stats.getSkill (mIndex).getModifier(); + float level = stats.getSkill(mIndex).getBase(); + float progress = stats.getSkill(mIndex).getProgress(); - int newLevel = static_cast (value-modifier); + int newLevel = value - (stats.getSkill(mIndex).getModified() - stats.getSkill(mIndex).getBase()); if (newLevel<0) newLevel = 0; - else if (newLevel>100) - newLevel = 100; progress = (progress / stats.getSkillGain (mIndex, class_, -1, level)) * stats.getSkillGain (mIndex, class_, -1, newLevel); @@ -364,8 +356,8 @@ namespace MWScript if (progress>=1) progress = 0.999999999; - stats.getSkill (mIndex).set (newLevel + progress); - stats.getSkill (mIndex).setModifier (modifier); + stats.getSkill (mIndex).setBase (newLevel); + stats.getSkill (mIndex).setProgress(progress); } }; @@ -385,11 +377,10 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - value += MWWorld::Class::get (ptr).getNpcStats (ptr).getSkill (mIndex). - getModified(); + MWMechanics::NpcStats& stats = ptr.getClass().getNpcStats(ptr); - MWWorld::Class::get (ptr).getNpcStats (ptr).getSkill (mIndex). - setModified (value, 0, 100); + stats.getSkill(mIndex). + setBase (std::min(100, stats.getSkill(mIndex).getBase() + value)); } }; @@ -468,6 +459,38 @@ namespace MWScript } }; + template + class OpRemoveSpellEffects : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + std::string spellid = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + MWWorld::Class::get (ptr).getCreatureStats (ptr).getActiveSpells().removeEffects(spellid); + } + }; + + template + class OpRemoveEffects : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + Interpreter::Type_Integer effectId = runtime[0].mInteger; + runtime.pop(); + + MWWorld::Class::get (ptr).getCreatureStats (ptr).getActiveSpells().purgeEffect(effectId); + } + }; + template class OpGetSpell : public Interpreter::Opcode0 { @@ -1081,6 +1104,18 @@ namespace MWScript } }; + template + class OpResurrect : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + ptr.getClass().getCreatureStats(ptr).resurrect(); + } + }; + void installOpcodes (Interpreter::Interpreter& interpreter) { for (int i=0; i); interpreter.installSegment5 (Compiler::Stats::opcodeRemoveSpellExplicit, new OpRemoveSpell); + interpreter.installSegment5 (Compiler::Stats::opcodeRemoveSpellEffects, new OpRemoveSpellEffects); + interpreter.installSegment5 (Compiler::Stats::opcodeRemoveSpellEffectsExplicit, + new OpRemoveSpellEffects); + interpreter.installSegment5 (Compiler::Stats::opcodeResurrect, new OpResurrect); + interpreter.installSegment5 (Compiler::Stats::opcodeResurrectExplicit, + new OpResurrect); + interpreter.installSegment5 (Compiler::Stats::opcodeRemoveEffects, new OpRemoveEffects); + interpreter.installSegment5 (Compiler::Stats::opcodeRemoveEffectsExplicit, + new OpRemoveEffects); interpreter.installSegment5 (Compiler::Stats::opcodeGetSpell, new OpGetSpell); interpreter.installSegment5 (Compiler::Stats::opcodeGetSpellExplicit, new OpGetSpell); diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index ae9ac041e1..e441809778 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -208,6 +208,14 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); + if (!ptr.isInCell()) + return; + + if (ptr.getRefData().getHandle() == "player") + { + MWBase::Environment::get().getWorld()->getPlayer().setTeleported(true); + } + std::string axis = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); Interpreter::Type_Float pos = runtime[0].mFloat; @@ -272,6 +280,14 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); + if (!ptr.isInCell()) + return; + + if (ptr.getRefData().getHandle() == "player") + { + MWBase::Environment::get().getWorld()->getPlayer().setTeleported(true); + } + Interpreter::Type_Float x = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float y = runtime[0].mFloat; @@ -329,6 +345,14 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); + if (!ptr.isInCell()) + return; + + if (ptr.getRefData().getHandle() == "player") + { + MWBase::Environment::get().getWorld()->getPlayer().setTeleported(true); + } + Interpreter::Type_Float x = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float y = runtime[0].mFloat; @@ -593,6 +617,10 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { MWWorld::Ptr ptr = R()(runtime); + + if (!ptr.isInCell()) + return; + ptr.getRefData().getLocalRotation().rot[0] = 0; ptr.getRefData().getLocalRotation().rot[1] = 0; ptr.getRefData().getLocalRotation().rot[2] = 0; @@ -612,6 +640,9 @@ namespace MWScript { const MWWorld::Ptr& ptr = R()(runtime); + if (!ptr.isInCell()) + return; + std::string axis = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); Interpreter::Type_Float movement = (runtime[0].mFloat*MWBase::Environment::get().getFrameDuration()); @@ -647,6 +678,9 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); + if (!ptr.isInCell()) + return; + std::string axis = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); Interpreter::Type_Float movement = (runtime[0].mFloat*MWBase::Environment::get().getFrameDuration()); diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index d5c382a419..c836974425 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -158,6 +158,16 @@ 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) + { + 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); mFormatCtx = NULL; fail("Failed to allocate input stream"); @@ -195,6 +205,14 @@ void FFmpeg_Decoder::open(const std::string &fname) } catch(std::exception &e) { + if (mFormatCtx->pb->buffer != NULL) + { + av_free(mFormatCtx->pb->buffer); + mFormatCtx->pb->buffer = NULL; + } + av_free(mFormatCtx->pb); + mFormatCtx->pb = NULL; + avformat_close_input(&mFormatCtx); throw; } @@ -211,9 +229,22 @@ void FFmpeg_Decoder::close() if(mFormatCtx) { - AVIOContext* context = mFormatCtx->pb; + if (mFormatCtx->pb != NULL) + { + // mFormatCtx->pb->buffer must be freed by hand, + // if not, valgrind will show memleak, see: + // + // https://trac.ffmpeg.org/ticket/1357 + // + if (mFormatCtx->pb->buffer != NULL) + { + av_free(mFormatCtx->pb->buffer); + mFormatCtx->pb->buffer = NULL; + } + av_free(mFormatCtx->pb); + mFormatCtx->pb = NULL; + } avformat_close_input(&mFormatCtx); - av_free(context); } mDataStream.setNull(); diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index c1c891ab47..304c871916 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -252,7 +252,7 @@ namespace MWSound float basevol = volumeFromType(Play_TypeVoice); std::string filePath = "Sound/"+filename; const ESM::Position &pos = ptr.getRefData().getPosition(); - const Ogre::Vector3 objpos(pos.pos[0], pos.pos[1], pos.pos[2]); + const Ogre::Vector3 objpos(pos.pos); MWBase::SoundPtr sound = mOutput->playSound3D(filePath, objpos, 1.0f, basevol, 1.0f, 20.0f, 12750.0f, Play_Normal|Play_TypeVoice, 0); @@ -354,7 +354,7 @@ namespace MWSound float min, max; std::string file = lookup(soundId, volume, min, max); const ESM::Position &pos = ptr.getRefData().getPosition(); - const Ogre::Vector3 objpos(pos.pos[0], pos.pos[1], pos.pos[2]); + const Ogre::Vector3 objpos(pos.pos); sound = mOutput->playSound3D(file, objpos, volume, basevol, pitch, min, max, mode|type, offset); if((mode&Play_NoTrack)) @@ -584,7 +584,7 @@ namespace MWSound if(!ptr.isEmpty()) { const ESM::Position &pos = ptr.getRefData().getPosition(); - const Ogre::Vector3 objpos(pos.pos[0], pos.pos[1], pos.pos[2]); + const Ogre::Vector3 objpos(pos.pos); snditer->first->setPosition(objpos); } //update fade out diff --git a/apps/openmw/mwworld/actionopen.cpp b/apps/openmw/mwworld/actionopen.cpp index 728b6e32bf..e9d8b47161 100644 --- a/apps/openmw/mwworld/actionopen.cpp +++ b/apps/openmw/mwworld/actionopen.cpp @@ -5,6 +5,8 @@ #include "../mwgui/container.hpp" +#include "../mwmechanics/disease.hpp" + #include "class.hpp" #include "containerstore.hpp" @@ -21,6 +23,8 @@ namespace MWWorld if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) return; + MWMechanics::diseaseContact(actor, getTarget()); + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Container); MWBase::Environment::get().getWindowManager()->getContainerWindow()->open(getTarget(), mLoot); } diff --git a/apps/openmw/mwworld/actiontake.cpp b/apps/openmw/mwworld/actiontake.cpp index d3c4aa2f63..867a046cf2 100644 --- a/apps/openmw/mwworld/actiontake.cpp +++ b/apps/openmw/mwworld/actiontake.cpp @@ -14,10 +14,7 @@ namespace MWWorld void ActionTake::executeImp (const Ptr& actor) { - // insert into player's inventory - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPtr ("player", true); - - MWWorld::Class::get (player).getContainerStore (player).add (getTarget(), player); + actor.getClass().getContainerStore (actor).add (getTarget(), getTarget().getRefData().getCount(), actor); MWBase::Environment::get().getWorld()->deleteObject (getTarget()); } diff --git a/apps/openmw/mwworld/actionteleport.cpp b/apps/openmw/mwworld/actionteleport.cpp index ae5ffc3b90..773fde81e3 100644 --- a/apps/openmw/mwworld/actionteleport.cpp +++ b/apps/openmw/mwworld/actionteleport.cpp @@ -3,6 +3,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "player.hpp" namespace MWWorld { @@ -14,9 +15,12 @@ namespace MWWorld void ActionTeleport::executeImp (const Ptr& actor) { + MWBase::World* world = MWBase::Environment::get().getWorld(); + world->getPlayer().setTeleported(true); + if (mCellName.empty()) - MWBase::Environment::get().getWorld()->changeToExteriorCell (mPosition); + world->changeToExteriorCell (mPosition); else - MWBase::Environment::get().getWorld()->changeToInteriorCell (mCellName, mPosition); + world->changeToInteriorCell (mCellName, mPosition); } } diff --git a/apps/openmw/mwworld/cellfunctors.hpp b/apps/openmw/mwworld/cellfunctors.hpp index 4b1f70096a..5115fa02db 100644 --- a/apps/openmw/mwworld/cellfunctors.hpp +++ b/apps/openmw/mwworld/cellfunctors.hpp @@ -4,7 +4,7 @@ #include #include -#include "refdata.hpp" +#include "ptr.hpp" namespace ESM { @@ -18,13 +18,13 @@ namespace MWWorld { std::vector mHandles; - bool operator() (ESM::CellRef& ref, RefData& data) + bool operator() (MWWorld::Ptr ptr) { - Ogre::SceneNode* handle = data.getBaseNode(); + Ogre::SceneNode* handle = ptr.getRefData().getBaseNode(); if (handle) mHandles.push_back (handle); - data.setBaseNode(0); + ptr.getRefData().setBaseNode(0); return true; } }; diff --git a/apps/openmw/mwworld/cells.cpp b/apps/openmw/mwworld/cells.cpp index 2cb22cf59c..c844b689e3 100644 --- a/apps/openmw/mwworld/cells.cpp +++ b/apps/openmw/mwworld/cells.cpp @@ -129,9 +129,7 @@ MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name, CellStore& cell, if (cell.mState==CellStore::State_Preloaded) { - std::string lowerCase = Misc::StringUtils::lowerCase(name); - - if (std::binary_search (cell.mIds.begin(), cell.mIds.end(), lowerCase)) + if (std::binary_search (cell.mIds.begin(), cell.mIds.end(), name)) { cell.load (mStore, mReader); } @@ -261,3 +259,15 @@ MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name) // giving up return Ptr(); } + +void MWWorld::Cells::getExteriorPtrs(const std::string &name, std::vector &out) +{ + for (std::map, CellStore>::iterator iter = mExteriors.begin(); + iter!=mExteriors.end(); ++iter) + { + Ptr ptr = getPtrAndCache (name, iter->second); + if (!ptr.isEmpty()) + out.push_back(ptr); + } + +} diff --git a/apps/openmw/mwworld/cells.hpp b/apps/openmw/mwworld/cells.hpp index 0c51cf4520..31de2f60e8 100644 --- a/apps/openmw/mwworld/cells.hpp +++ b/apps/openmw/mwworld/cells.hpp @@ -47,8 +47,15 @@ namespace MWWorld Ptr getPtr (const std::string& name, CellStore& cellStore, bool searchInContainers = false); ///< \param searchInContainers Only affect loaded cells. + /// @note name must be lower case + /// @note name must be lower case Ptr getPtr (const std::string& name); + + /// Get all Ptrs referencing \a name in exterior cells + /// @note Due to the current implementation of getPtr this only supports one Ptr per cell. + /// @note name must be lower case + void getExteriorPtrs (const std::string& name, std::vector& out); }; } diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 8a01caf183..8731c42dc6 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -143,7 +143,7 @@ namespace MWWorld { if (!iter->mData.getCount()) continue; - if (!functor (iter->mRef, iter->mData)) + if (!functor (MWWorld::Ptr(&*iter, this))) return false; } return true; diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index d3d1aff49b..0119cdea5f 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -139,7 +139,7 @@ namespace MWWorld float Class::getRemainingUsageTime (const Ptr& ptr) const { - throw std::runtime_error ("class does not support time-based uses"); + return -1; } std::string Class::getScript (const Ptr& ptr) const @@ -372,4 +372,10 @@ namespace MWWorld return newPtr; } + + bool Class::isFlying(const Ptr &ptr) const + { + return false; + } + } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 2db293e688..08d769fbe0 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -162,7 +162,7 @@ namespace MWWorld virtual float getRemainingUsageTime (const Ptr& ptr) const; ///< Returns the remaining duration of the object, such as an equippable light - /// source. (default implementation: throw an exception) + /// source. (default implementation: -1, i.e. infinite) virtual std::string getScript (const Ptr& ptr) const; ///< Return name of the script attached to ptr (default implementation: return an empty @@ -290,6 +290,8 @@ namespace MWWorld virtual bool isPersistent (const MWWorld::Ptr& ptr) const; + virtual bool isKey (const MWWorld::Ptr& ptr) const { return false; } + virtual Ptr copyToCell(const Ptr &ptr, CellStore &cell) const; @@ -304,6 +306,8 @@ namespace MWWorld return false; } + virtual bool isFlying(const MWWorld::Ptr& ptr) const; + static const Class& get (const std::string& key); ///< If there is no class for this \a key, an exception is thrown. diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index d1d16ee01d..ba8936bf70 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -81,7 +81,7 @@ void MWWorld::ContainerStore::unstack(const Ptr &ptr, const Ptr& container) { if (ptr.getRefData().getCount() <= 1) return; - addNewStack(ptr)->getRefData().setCount(ptr.getRefData().getCount()-1); + addNewStack(ptr, ptr.getRefData().getCount()-1); remove(ptr, ptr.getRefData().getCount()-1, container); } @@ -109,6 +109,8 @@ bool MWWorld::ContainerStore::stacks(const Ptr& ptr1, const Ptr& ptr2) && ptr1.getCellRef().mOwner == ptr2.getCellRef().mOwner && ptr1.getCellRef().mSoul == ptr2.getCellRef().mSoul + && ptr1.getClass().getRemainingUsageTime(ptr1) == ptr2.getClass().getRemainingUsageTime(ptr2) + && cls1.getScript(ptr1) == cls2.getScript(ptr2) // item that is already partly used up never stacks @@ -121,14 +123,26 @@ bool MWWorld::ContainerStore::stacks(const Ptr& ptr1, const Ptr& ptr2) MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add(const std::string &id, int count, const Ptr &actorPtr) { MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), id, count); - return add(ref.getPtr(), actorPtr); + return add(ref.getPtr(), count, actorPtr, true); } -MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr, const Ptr& actorPtr) +MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr, int count, const Ptr& actorPtr, bool setOwner) { - MWWorld::ContainerStoreIterator it = addImp(itemPtr); + MWWorld::ContainerStoreIterator it = addImp(itemPtr, count); MWWorld::Ptr item = *it; + // we may have copied an item from the world, so reset a few things first + item.getRefData().setBaseNode(NULL); + item.getCellRef().mPos.rot[0] = 0; + item.getCellRef().mPos.rot[1] = 0; + item.getCellRef().mPos.rot[2] = 0; + item.getCellRef().mPos.pos[0] = 0; + item.getCellRef().mPos.pos[1] = 0; + item.getCellRef().mPos.pos[2] = 0; + + if (setOwner) + item.getCellRef().mOwner = actorPtr.getCellRef().mRefID; + std::string script = MWWorld::Class::get(item).getScript(item); if(script != "") { @@ -154,7 +168,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr return it; } -MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr) +MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr, int count) { int type = getType(ptr); @@ -169,20 +183,20 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr) || Misc::StringUtils::ciEqual(ptr.getCellRef().mRefID, "gold_025") || Misc::StringUtils::ciEqual(ptr.getCellRef().mRefID, "gold_100")) { - int count = MWWorld::Class::get(ptr).getValue(ptr) * ptr.getRefData().getCount(); + int realCount = MWWorld::Class::get(ptr).getValue(ptr) * ptr.getRefData().getCount(); for (MWWorld::ContainerStoreIterator iter (begin(type)); iter!=end(); ++iter) { if (Misc::StringUtils::ciEqual((*iter).get()->mRef.mRefID, "gold_001")) { - iter->getRefData().setCount(iter->getRefData().getCount() + count); + iter->getRefData().setCount(iter->getRefData().getCount() + realCount); flagAsModified(); return iter; } } MWWorld::ManualRef ref(esmStore, "Gold_001", count); - return addNewStack(ref.getPtr()); + return addNewStack(ref.getPtr(), count); } // determine whether to stack or not @@ -191,17 +205,17 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr) if (stacks(*iter, ptr)) { // stack - iter->getRefData().setCount( iter->getRefData().getCount() + ptr.getRefData().getCount() ); + iter->getRefData().setCount( iter->getRefData().getCount() + count ); flagAsModified(); return iter; } } // if we got here, this means no stacking - return addNewStack(ptr); + return addNewStack(ptr, count); } -MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addNewStack (const Ptr& ptr) +MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addNewStack (const Ptr& ptr, int count) { ContainerStoreIterator it = begin(); @@ -221,6 +235,8 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addNewStack (const Ptr& case Type_Weapon: weapons.mList.push_back (*ptr.get()); it = ContainerStoreIterator(this, --weapons.mList.end()); break; } + it->getRefData().setCount(count); + flagAsModified(); return it; } @@ -332,7 +348,7 @@ void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std:: else { ref.getPtr().getCellRef().mOwner = owner; - addImp (ref.getPtr()); + addImp (ref.getPtr(), count); } } catch (std::logic_error& e) diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index df7168dfa1..83e490e374 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -51,7 +51,7 @@ namespace MWWorld MWWorld::CellRefList weapons; mutable float mCachedWeight; mutable bool mWeightUpToDate; - ContainerStoreIterator addImp (const Ptr& ptr); + ContainerStoreIterator addImp (const Ptr& ptr, int count); void addInitialItem (const std::string& id, const std::string& owner, int count, unsigned char failChance=0, bool topLevel=true); public: @@ -64,7 +64,7 @@ namespace MWWorld ContainerStoreIterator end(); - virtual ContainerStoreIterator add (const Ptr& itemPtr, const Ptr& actorPtr); + virtual ContainerStoreIterator add (const Ptr& itemPtr, int count, const Ptr& actorPtr, bool setOwner=false); ///< Add the item pointed to by \a ptr to this container. (Stacks automatically if needed) /// /// \note The item pointed to is not required to exist beyond this function call. @@ -72,10 +72,12 @@ namespace MWWorld /// \attention Do not add items to an existing stack by increasing the count instead of /// calling this function! /// + /// @param setOwner Set the owner of the added item to \a actorPtr? + /// /// @return if stacking happened, return iterator to the item that was stacked against, otherwise iterator to the newly inserted item. ContainerStoreIterator add(const std::string& id, int count, const Ptr& actorPtr); - ///< Utility to construct a ManualRef and call add(ptr, actorPtr) + ///< Utility to construct a ManualRef and call add(ptr, count, actorPtr, true) int remove(const std::string& itemId, int count, const Ptr& actor); ///< Remove \a count item(s) designated by \a itemId from this container. @@ -91,9 +93,11 @@ namespace MWWorld ///< Unstack an item in this container. The item's count will be set to 1, then a new stack will be added with (origCount-1). protected: - ContainerStoreIterator addNewStack (const Ptr& ptr); + ContainerStoreIterator addNewStack (const Ptr& ptr, int count); ///< Add the item to this container (do not try to stack it onto existing items) + virtual void flagAsModified(); + public: virtual bool stacks (const Ptr& ptr1, const Ptr& ptr2); @@ -105,10 +109,6 @@ namespace MWWorld void clear(); ///< Empty container. - virtual void flagAsModified(); - ///< \attention This function is internal to the world model and should not be called from - /// outside. - float getWeight() const; ///< Return total weight of the items contained in *this. diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 69e06378a6..422265c0e5 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -75,9 +75,9 @@ MWWorld::InventoryStore& MWWorld::InventoryStore::operator= (const InventoryStor return *this; } -MWWorld::ContainerStoreIterator MWWorld::InventoryStore::add(const Ptr& itemPtr, const Ptr& actorPtr) +MWWorld::ContainerStoreIterator MWWorld::InventoryStore::add(const Ptr& itemPtr, int count, const Ptr& actorPtr, bool setOwner) { - const MWWorld::ContainerStoreIterator& retVal = MWWorld::ContainerStore::add(itemPtr, actorPtr); + const MWWorld::ContainerStoreIterator& retVal = MWWorld::ContainerStore::add(itemPtr, count, actorPtr, setOwner); // Auto-equip items if an armor/clothing item is added, but not for the player nor werewolves if ((actorPtr.getRefData().getHandle() != "player") @@ -118,11 +118,7 @@ void MWWorld::InventoryStore::equip (int slot, const ContainerStoreIterator& ite // unstack item pointed to by iterator if required if (iterator!=end() && !slots_.second && iterator->getRefData().getCount() > 1) // if slots.second is true, item can stay stacked when equipped { - // add the item again with a count of count-1, then set the count of the original (that will be equipped) to 1 - int count = iterator->getRefData().getCount(); - iterator->getRefData().setCount(count-1); - addNewStack(*iterator); - iterator->getRefData().setCount(1); + unstack(*iterator, actor); } mSlots[slot] = iterator; @@ -139,8 +135,13 @@ void MWWorld::InventoryStore::equip (int slot, const ContainerStoreIterator& ite void MWWorld::InventoryStore::unequipAll(const MWWorld::Ptr& actor) { + // Only *one* change event should be fired + mUpdatesEnabled = false; for (int slot=0; slot < MWWorld::InventoryStore::Slots; ++slot) unequipSlot(slot, actor); + mUpdatesEnabled = true; + fireEquipmentChangedEvent(); + updateMagicEffects(actor); } MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getSlot (int slot) @@ -175,11 +176,46 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor) for (ContainerStoreIterator iter (begin()); iter!=end(); ++iter) { Ptr test = *iter; + + // Don't autoEquip lights + if (test.getTypeName() == typeid(ESM::Light).name()) + { + continue; + } + + // Only autoEquip if we are the original owner of the item. + // This stops merchants from auto equipping anything you sell to them. + if (!Misc::StringUtils::ciEqual(test.getCellRef().mOwner, actor.getCellRef().mRefID)) + continue; + int testSkill = MWWorld::Class::get (test).getEquipmentSkill (test); std::pair, bool> itemsSlots = MWWorld::Class::get (*iter).getEquipmentSlots (*iter); + // Skip items that have *only* harmful permanent effects + if (!test.getClass().getEnchantment(test).empty()) + { + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + const ESM::Enchantment* enchantment = store.get().find(test.getClass().getEnchantment(test)); + bool harmfulEffect = false; + bool usefulEffect = false; + if (enchantment->mData.mType == ESM::Enchantment::ConstantEffect) + { + for (std::vector::const_iterator it = enchantment->mEffects.mList.begin(); + it != enchantment->mEffects.mList.end(); ++it) + { + const ESM::MagicEffect* effect = store.get().find(it->mEffectID); + if (effect->mData.mFlags & ESM::MagicEffect::Harmful) + harmfulEffect = true; + else + usefulEffect = true; + } + } + if (harmfulEffect && !usefulEffect) + continue; + } + for (std::vector::const_iterator iter2 (itemsSlots.first.begin()); iter2!=itemsSlots.first.end(); ++iter2) { @@ -239,11 +275,7 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor) // unstack item pointed to by iterator if required if (iter->getRefData().getCount() > 1) { - // add the item again with a count of count-1, then set the count of the original (that will be equipped) to 1 - int count = iter->getRefData().getCount(); - iter->getRefData().setCount(count-1); - addNewStack(*iter); - iter->getRefData().setCount(1); + unstack(*iter, actor); } } @@ -577,7 +609,7 @@ void MWWorld::InventoryStore::visitEffectSources(MWMechanics::EffectSourceVisito const EffectParams& params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID][i]; float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params.mRandom; magnitude *= params.mMultiplier; - visitor.visit(MWMechanics::EffectKey(*effectIt), (**iter).getClass().getName(**iter), magnitude); + visitor.visit(MWMechanics::EffectKey(*effectIt), (**iter).getClass().getName(**iter), "", magnitude); ++i; } @@ -615,3 +647,8 @@ void MWWorld::InventoryStore::rechargeItems(float duration) it->second); } } + +void MWWorld::InventoryStore::purgeEffect(short effectId) +{ + mMagicEffects.add(MWMechanics::EffectKey(effectId), -mMagicEffects.get(MWMechanics::EffectKey(effectId)).mMagnitude); +} diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index 58ff50ead0..067c8261e1 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -113,7 +113,7 @@ namespace MWWorld InventoryStore& operator= (const InventoryStore& store); - virtual ContainerStoreIterator add (const Ptr& itemPtr, const Ptr& actorPtr); + virtual ContainerStoreIterator add (const Ptr& itemPtr, int count, const Ptr& actorPtr, bool setOwner=false); ///< Add the item pointed to by \a ptr to this container. (Stacks automatically if needed) /// Auto-equip items if specific conditions are fulfilled (see the implementation). /// @@ -122,6 +122,8 @@ namespace MWWorld /// \attention Do not add items to an existing stack by increasing the count instead of /// calling this function! /// + /// @param setOwner Set the owner of the added item to \a actorPtr? + /// /// @return if stacking happened, return iterator to the item that was stacked against, otherwise iterator to the newly inserted item. void equip (int slot, const ContainerStoreIterator& iterator, const Ptr& actor); @@ -179,7 +181,10 @@ namespace MWWorld void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor); void rechargeItems (float duration); - /// Restore charge on enchanted items. Note this should only be done for the player. + ///< Restore charge on enchanted items. Note this should only be done for the player. + + void purgeEffect (short effectId); + ///< Remove a magic effect }; } diff --git a/apps/openmw/mwworld/manualref.hpp b/apps/openmw/mwworld/manualref.hpp index 1cdcd8484e..0b21fd78b4 100644 --- a/apps/openmw/mwworld/manualref.hpp +++ b/apps/openmw/mwworld/manualref.hpp @@ -64,7 +64,7 @@ namespace MWWorld // initialise ESM::CellRef& cellRef = mPtr.getCellRef(); - cellRef.mRefID = name; + cellRef.mRefID = Misc::StringUtils::lowerCase(name); cellRef.mRefnum = -1; cellRef.mScale = 1; cellRef.mFactIndex = 0; diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index 433fe08925..2a7f5948e4 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -18,6 +18,8 @@ #include "../mwbase/world.hpp" // FIXME #include "../mwbase/environment.hpp" +#include "../mwmechanics/creaturestats.hpp" + #include #include "../mwworld/esmstore.hpp" @@ -573,9 +575,35 @@ namespace MWWorld if(cell->hasWater()) waterlevel = cell->mWater; + float oldHeight = iter->first.getRefData().getPosition().pos[2]; + + bool waterCollision = false; + if (iter->first.getClass().getCreatureStats(iter->first).getMagicEffects() + .get(ESM::MagicEffect::WaterWalking).mMagnitude + && cell->hasWater() + && !world->isUnderwater(iter->first.getCell(), + Ogre::Vector3(iter->first.getRefData().getPosition().pos))) + waterCollision = true; + + btStaticPlaneShape planeShape(btVector3(0,0,1), waterlevel); + btCollisionObject object; + object.setCollisionShape(&planeShape); + + if (waterCollision) + mEngine->dynamicsWorld->addCollisionObject(&object); + Ogre::Vector3 newpos = MovementSolver::move(iter->first, iter->second, mTimeAccum, world->isFlying(iter->first), waterlevel, mEngine); + + if (waterCollision) + mEngine->dynamicsWorld->removeCollisionObject(&object); + + float heightDiff = newpos.z - oldHeight; + + if (heightDiff < 0) + iter->first.getClass().getCreatureStats(iter->first).addToFallHeight(-heightDiff); + mMovementResults.push_back(std::make_pair(iter->first, newpos)); } diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index e26c2e2a52..c594454028 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -18,8 +18,11 @@ namespace MWWorld { Player::Player (const ESM::NPC *player, const MWBase::World& world) : mCellStore(0), + mLastKnownExteriorPosition(0,0,0), mAutoMove(false), - mForwardBackward (0) + mForwardBackward (0), + mTeleported(false), + mMarkedCell(NULL) { mPlayer.mBase = player; mPlayer.mRef.mRefID = "player"; @@ -145,4 +148,27 @@ namespace MWWorld MWWorld::Ptr ptr = getPlayer(); return MWWorld::Class::get(ptr).getNpcStats(ptr).getDrawState(); } + + bool Player::wasTeleported() const + { + return mTeleported; + } + + void Player::setTeleported(bool teleported) + { + mTeleported = teleported; + } + + void Player::markPosition(CellStore *markedCell, ESM::Position markedPosition) + { + mMarkedCell = markedCell; + mMarkedPosition = markedPosition; + } + + void Player::getMarkedPosition(CellStore*& markedCell, ESM::Position &markedPosition) const + { + markedCell = mMarkedCell; + if (mMarkedCell) + markedPosition = mMarkedPosition; + } } diff --git a/apps/openmw/mwworld/player.hpp b/apps/openmw/mwworld/player.hpp index d78b1901c4..1df848111b 100644 --- a/apps/openmw/mwworld/player.hpp +++ b/apps/openmw/mwworld/player.hpp @@ -6,6 +6,8 @@ #include "../mwmechanics/drawstate.hpp" +#include + namespace ESM { struct NPC; @@ -28,13 +30,30 @@ namespace MWWorld MWWorld::CellStore *mCellStore; std::string mSign; + Ogre::Vector3 mLastKnownExteriorPosition; + + ESM::Position mMarkedPosition; + // If no position was marked, this is NULL + CellStore* mMarkedCell; + bool mAutoMove; int mForwardBackward; - + bool mTeleported; public: Player(const ESM::NPC *player, const MWBase::World& world); + // For mark/recall magic effects + void markPosition (CellStore* markedCell, ESM::Position markedPosition); + void getMarkedPosition (CellStore*& markedCell, ESM::Position& markedPosition) const; + + /// Interiors can not always be mapped to a world position. However + /// world position is still required for divine / almsivi magic effects + /// and the player arrow on the global map. + /// TODO: This should be stored in the savegame, too. + void setLastKnownExteriorPosition (const Ogre::Vector3& position) { mLastKnownExteriorPosition = position; } + Ogre::Vector3 getLastKnownExteriorPosition() const { return mLastKnownExteriorPosition; } + void set (const ESM::NPC *player); void setCell (MWWorld::CellStore *cellStore); @@ -64,6 +83,9 @@ namespace MWWorld void yaw(float yaw); void pitch(float pitch); void roll(float roll); + + bool wasTeleported() const; + void setTeleported(bool teleported); }; } #endif diff --git a/apps/openmw/mwworld/ptr.cpp b/apps/openmw/mwworld/ptr.cpp index 127ab1364c..384bd71b11 100644 --- a/apps/openmw/mwworld/ptr.cpp +++ b/apps/openmw/mwworld/ptr.cpp @@ -25,9 +25,6 @@ ESM::CellRef& MWWorld::Ptr::getCellRef() const { assert(mRef); - if (mContainerStore) - mContainerStore->flagAsModified(); - return mRef->mRef; } @@ -35,9 +32,6 @@ MWWorld::RefData& MWWorld::Ptr::getRefData() const { assert(mRef); - if (mContainerStore) - mContainerStore->flagAsModified(); - return mRef->mData; } diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index cc91b15473..7ad20f4e07 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -203,6 +203,7 @@ namespace MWWorld void Scene::changeCell (int X, int Y, const ESM::Position& position, bool adjustPlayerPos) { + mRendering.enableTerrain(true); Nif::NIFFile::CacheLock cachelock; Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); @@ -349,6 +350,7 @@ namespace MWWorld void Scene::changeToInteriorCell (const std::string& cellName, const ESM::Position& position) { + Nif::NIFFile::CacheLock lock; MWBase::Environment::get().getWorld ()->getFader ()->fadeOut(0.5); mRendering.enableTerrain(false); @@ -436,8 +438,6 @@ namespace MWWorld MWBase::Environment::get().getWorld()->positionToIndex (position.pos[0], position.pos[1], x, y); - mRendering.enableTerrain(true); - changeCell (x, y, position, true); } diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index b7bb944edb..6652748318 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -71,8 +71,6 @@ namespace MWWorld void loadCell (CellStore *cell, Loading::Listener* loadingListener); void changeCell (int X, int Y, const ESM::Position& position, bool adjustPlayerPos); - ///< Move from exterior to interior or from interior cell to a different - /// interior cell. CellStore* getCurrentCell (); diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 8b05d2256a..513dcf6c72 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -195,7 +195,7 @@ void WeatherManager::setWeather(const String& weather, bool instant) } mNextWeather = weather; - mRemainingTransitionTime = mWeatherSettings[mCurrentWeather].mTransitionDelta * 24.f * 3600; + mRemainingTransitionTime = mWeatherSettings[mCurrentWeather].mTransitionDelta * 24.f * 3600.f; } mFirstUpdate = false; } @@ -324,7 +324,8 @@ void WeatherManager::update(float duration) mWeatherUpdateTime -= timePassed; - const bool exterior = (MWBase::Environment::get().getWorld()->isCellExterior() || MWBase::Environment::get().getWorld()->isCellQuasiExterior()); + MWBase::World* world = MWBase::Environment::get().getWorld(); + const bool exterior = (world->isCellExterior() || world->isCellQuasiExterior()); if (!exterior) { mRendering->sunDisable(false); @@ -334,32 +335,7 @@ void WeatherManager::update(float duration) return; } - // Exterior - std::string regionstr = Misc::StringUtils::lowerCase(MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getCell()->mCell->mRegion); - - if (mWeatherUpdateTime <= 0 || regionstr != mCurrentRegion) - { - mCurrentRegion = regionstr; - mWeatherUpdateTime = mHoursBetweenWeatherChanges * 3600; - - std::string weatherType = "clear"; - - if (mRegionOverrides.find(regionstr) != mRegionOverrides.end()) - weatherType = mRegionOverrides[regionstr]; - else - { - // get weather probabilities for the current region - const ESM::Region *region = - MWBase::Environment::get().getWorld()->getStore().get().search (regionstr); - - if (region != 0) - { - weatherType = nextWeather(region); - } - } - - setWeather(weatherType, false); - } + switchToNextWeather(false); if (mNextWeather != "") { @@ -707,3 +683,44 @@ float WeatherManager::getWindSpeed() const { return mWindSpeed; } + +bool WeatherManager::isDark() const +{ + bool exterior = (MWBase::Environment::get().getWorld()->isCellExterior() + || MWBase::Environment::get().getWorld()->isCellQuasiExterior()); + return exterior && (mHour < mSunriseTime || mHour > mNightStart - 1); +} + +void WeatherManager::switchToNextWeather(bool instantly) +{ + MWBase::World* world = MWBase::Environment::get().getWorld(); + if (world->isCellExterior() || world->isCellQuasiExterior()) + { + std::string regionstr = Misc::StringUtils::lowerCase(world->getPlayer().getPlayer().getCell()->mCell->mRegion); + + if (mWeatherUpdateTime <= 0 || regionstr != mCurrentRegion) + { + mCurrentRegion = regionstr; + mWeatherUpdateTime = mHoursBetweenWeatherChanges * 3600; + + std::string weatherType = "clear"; + + if (mRegionOverrides.find(regionstr) != mRegionOverrides.end()) + { + weatherType = mRegionOverrides[regionstr]; + } + else + { + // get weather probabilities for the current region + const ESM::Region *region = world->getStore().get().search (regionstr); + + if (region != 0) + { + weatherType = nextWeather(region); + } + } + + setWeather(weatherType, instantly); + } + } +} diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index 80cbe0418e..fa2d9bd8e1 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -128,6 +128,7 @@ namespace MWWorld * @param ID of the weather setting to shift to */ void changeWeather(const std::string& region, const unsigned int id); + void switchToNextWeather(bool instantly = true); /** * Per-frame update @@ -152,6 +153,9 @@ namespace MWWorld void modRegion(const std::string ®ionid, const std::vector &chances); + /// @see World::isDark + bool isDark() const; + private: float mHour; int mDay, mMonth; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index ceb26ee9f6..83885e5d51 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -38,6 +38,7 @@ #include "cellfunctors.hpp" #include "containerstore.hpp" #include "inventorystore.hpp" +#include "actionteleport.hpp" #include "contentloader.hpp" #include "esmloader.hpp" @@ -508,12 +509,14 @@ namespace MWWorld if (!ptr.isEmpty()) return ptr; + std::string lowerCaseName = Misc::StringUtils::lowerCase(name); + // active cells for (Scene::CellStoreCollection::const_iterator iter (mWorldScene->getActiveCells().begin()); iter!=mWorldScene->getActiveCells().end(); ++iter) { CellStore* cellstore = *iter; - Ptr ptr = mCells.getPtr (name, *cellstore, true); + Ptr ptr = mCells.getPtr (lowerCaseName, *cellstore, true); if (!ptr.isEmpty()) return ptr; @@ -521,7 +524,7 @@ namespace MWWorld if (!activeOnly) { - Ptr ptr = mCells.getPtr (name); + Ptr ptr = mCells.getPtr (lowerCaseName); if (!ptr.isEmpty()) return ptr; @@ -829,32 +832,9 @@ namespace MWWorld MWWorld::Ptr World::getFacedObject() { - std::pair result; - - if (!mRendering->occlusionQuerySupported()) - result = mPhysics->getFacedHandle (getMaxActivationDistance ()); - else - result = std::make_pair (mFacedDistance, mFacedHandle); - - if (result.second.empty()) - return MWWorld::Ptr (); - - MWWorld::Ptr object = searchPtrViaHandle (result.second); - if (object.isEmpty()) - return object; - float ActivationDistance; - - if (MWBase::Environment::get().getWindowManager()->isConsoleMode()) - ActivationDistance = getObjectActivationDistance ()*50; - else if (object.getTypeName ().find("NPC") != std::string::npos) - ActivationDistance = getNpcActivationDistance (); - else - ActivationDistance = getObjectActivationDistance (); - - if (result.first > ActivationDistance) - return MWWorld::Ptr (); - - return object; + if (mFacedHandle.empty()) + return MWWorld::Ptr(); + return searchPtrViaHandle(mFacedHandle); } std::pair World::getHitContact(const MWWorld::Ptr &ptr, float distance) @@ -967,7 +947,7 @@ namespace MWWorld ptr.getRefData().setCount(0); } } - if (haveToMove) + if (haveToMove && ptr.getRefData().getBaseNode()) { mRendering->moveObject(ptr, vec); mPhysics->moveObject (ptr); @@ -1096,7 +1076,7 @@ namespace MWWorld void World::adjustPosition(const Ptr &ptr) { - Ogre::Vector3 pos (ptr.getRefData().getPosition().pos[0], ptr.getRefData().getPosition().pos[1], ptr.getRefData().getPosition().pos[2]); + Ogre::Vector3 pos (ptr.getRefData().getPosition().pos); if(!ptr.getRefData().getBaseNode()) { @@ -1131,7 +1111,7 @@ namespace MWWorld MWWorld::Ptr World::safePlaceObject(const MWWorld::Ptr& ptr,MWWorld::CellStore &Cell,ESM::Position pos) { - return copyObjectToCell(ptr,Cell,pos); + return copyObjectToCell(ptr,Cell,pos,false); } void World::indexToPosition (int cellX, int cellY, float &x, float &y, bool centre) const @@ -1323,7 +1303,7 @@ namespace MWWorld mRendering->playVideo(mFallback.getFallbackString("Movies_New_Game"), true); } - mWeatherManager->update (duration); + updateWeather(duration); mWorldScene->update (duration, paused); @@ -1333,6 +1313,12 @@ namespace MWWorld performUpdateSceneQueries (); updateWindowManager (); + + if (mPlayer->getPlayer().getCell()->isExterior()) + { + ESM::Position pos = mPlayer->getPlayer().getRefData().getPosition(); + mPlayer->setLastKnownExteriorPosition(Ogre::Vector3(pos.pos)); + } } void World::updateWindowManager () @@ -1373,6 +1359,14 @@ namespace MWWorld void World::updateFacedHandle () { + float telekinesisRangeBonus = + mPlayer->getPlayer().getClass().getCreatureStats(mPlayer->getPlayer()).getMagicEffects() + .get(ESM::MagicEffect::Telekinesis).mMagnitude; + telekinesisRangeBonus = feetToGameUnits(telekinesisRangeBonus); + + float activationDistance = getMaxActivationDistance() + telekinesisRangeBonus; + activationDistance += mRendering->getCameraDistance(); + // send new query // figure out which object we want to test against std::vector < std::pair < float, std::string > > results; @@ -1380,13 +1374,13 @@ namespace MWWorld { float x, y; MWBase::Environment::get().getWindowManager()->getMousePosition(x, y); - results = mPhysics->getFacedHandles(x, y, getMaxActivationDistance ()); + results = mPhysics->getFacedHandles(x, y, activationDistance); if (MWBase::Environment::get().getWindowManager()->isConsoleMode()) results = mPhysics->getFacedHandles(x, y, getMaxActivationDistance ()*50); } else { - results = mPhysics->getFacedHandles(getMaxActivationDistance ()); + results = mPhysics->getFacedHandles(activationDistance); } // ignore the player and other things we're not interested in @@ -1469,10 +1463,8 @@ namespace MWWorld return d; } - std::vector World::getDoorMarkers (CellStore* cell) + void World::getDoorMarkers (CellStore* cell, std::vector& out) { - std::vector result; - MWWorld::CellRefList& doors = cell->mDoors; CellRefList::List& refList = doors.mList; for (CellRefList::List::iterator it = refList.begin(); it != refList.end(); ++it) @@ -1488,11 +1480,9 @@ namespace MWWorld newMarker.x = pos.pos[0]; newMarker.y = pos.pos[1]; - result.push_back(newMarker); + out.push_back(newMarker); } } - - return result; } void World::getInteriorMapPosition (Ogre::Vector2 position, float& nX, float& nY, int &x, int& y) @@ -1552,7 +1542,7 @@ namespace MWWorld // copy the object and set its count int origCount = object.getRefData().getCount(); object.getRefData().setCount(amount); - Ptr dropped = copyObjectToCell(object, *cell, pos); + Ptr dropped = copyObjectToCell(object, *cell, pos, true); object.getRefData().setCount(origCount); // only the player place items in the world, so no need to check actor @@ -1573,13 +1563,13 @@ namespace MWWorld } - Ptr World::copyObjectToCell(const Ptr &object, CellStore &cell, const ESM::Position &pos) + Ptr World::copyObjectToCell(const Ptr &object, CellStore &cell, const ESM::Position &pos, bool adjustPos) { /// \todo add searching correct cell for position specified MWWorld::Ptr dropped = MWWorld::Class::get(object).copyToCell(object, cell, pos); - if (object.getClass().isActor()) + if (object.getClass().isActor() || adjustPos) { Ogre::Vector3 min, max; if (mPhysics->getObjectAABB(object, min, max)) { @@ -1615,7 +1605,7 @@ namespace MWWorld pos.rot[1] = 0; Ogre::Vector3 orig = - Ogre::Vector3(pos.pos[0], pos.pos[1], pos.pos[2]); + Ogre::Vector3(pos.pos); Ogre::Vector3 dir = Ogre::Vector3(0, 0, -1); float len = (pos.pos[2] >= 0) ? pos.pos[2] : -pos.pos[2]; @@ -1651,11 +1641,16 @@ namespace MWWorld if(!ptr.getClass().isActor()) return false; - const MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr); - if(stats.getMagicEffects().get(MWMechanics::EffectKey(ESM::MagicEffect::Levitate)).mMagnitude > 0) + if (ptr.getClass().getCreatureStats(ptr).isDead()) + return false; + + if (ptr.getClass().isFlying(ptr)) return true; - // TODO: Check if flying creature + const MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr); + if(stats.getMagicEffects().get(ESM::MagicEffect::Levitate).mMagnitude > 0 + && isLevitationEnabled()) + return true; const OEngine::Physic::PhysicActor *actor = mPhysEngine->getCharacter(ptr.getRefData().getHandle()); if(!actor || !actor->getCollisionMode()) @@ -1671,7 +1666,7 @@ namespace MWWorld return false; const MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr); - if(stats.getMagicEffects().get(MWMechanics::EffectKey(ESM::MagicEffect::SlowFall)).mMagnitude > 0) + if(stats.getMagicEffects().get(ESM::MagicEffect::SlowFall).mMagnitude > 0) return true; return false; @@ -1857,9 +1852,9 @@ namespace MWWorld { std::vector mHandles; - bool operator() (ESM::CellRef& ref, RefData& data) + bool operator() (Ptr ptr) { - Ogre::SceneNode* handle = data.getBaseNode(); + Ogre::SceneNode* handle = ptr.getRefData().getBaseNode(); if (handle) mHandles.push_back(handle->getName()); return true; @@ -1882,6 +1877,13 @@ namespace MWWorld bool World::getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc) { + // This is a placeholder! Needs to go into an NPC awareness check function (see + // https://wiki.openmw.org/index.php?title=Research:NPC_AI_Behaviour#NPC_Awareness_Check ) + if (targetNpc.getClass().getCreatureStats(targetNpc).getMagicEffects().get(ESM::MagicEffect::Invisibility).mMagnitude) + return false; + if (targetNpc.getClass().getCreatureStats(targetNpc).getMagicEffects().get(ESM::MagicEffect::Chameleon).mMagnitude > 100) + return false; + Ogre::Vector3 halfExt1 = mPhysEngine->getCharacter(npc.getRefData().getHandle())->getHalfExtents(); float* pos1 = npc.getRefData().getPosition().pos; Ogre::Vector3 halfExt2 = mPhysEngine->getCharacter(targetNpc.getRefData().getHandle())->getHalfExtents(); @@ -1961,17 +1963,8 @@ namespace MWWorld int y = ext->getGridY(); indexToPosition(x, y, pos.pos[0], pos.pos[1], true); - ESM::Land* land = getStore().get().search(x, y); - if (land) { - if (!land->isDataLoaded(ESM::Land::DATA_VHGT)) { - land->loadData(ESM::Land::DATA_VHGT); - } - pos.pos[2] = land->mLandData->mHeights[ESM::Land::LAND_NUM_VERTS / 2 + 1]; - } - else { - std::cerr << "Land data for cell at (" << x << ", " << y << ") not found\n"; - pos.pos[2] = 0; - } + // Note: Z pos will be adjusted by adjustPosition later + pos.pos[2] = 0; return true; } @@ -2008,6 +2001,10 @@ namespace MWWorld npcStats.setWerewolf(werewolf); + // This is a bit dangerous. Equipped items other than WerewolfRobe may reference + // bones that do not even exist with the werewolf object root. + // Therefore, make sure to unequip everything at once, and only fire the change event + // (which will rebuild the animation parts) afterwards. unequipAll will do this for us. MWWorld::InventoryStore& invStore = MWWorld::Class::get(actor).getInventoryStore(actor); invStore.unequipAll(actor); @@ -2015,13 +2012,17 @@ namespace MWWorld { InventoryStore &inv = actor.getClass().getInventoryStore(actor); - inv.equip(InventoryStore::Slot_Robe, inv.ContainerStore::add("WerewolfRobe", 1, actor), actor); + inv.equip(InventoryStore::Slot_Robe, inv.ContainerStore::add("werewolfrobe", 1, actor), actor); } else { - actor.getClass().getContainerStore(actor).remove("WerewolfRobe", 1, actor); + actor.getClass().getContainerStore(actor).remove("werewolfrobe", 1, actor); } + // NpcAnimation::updateParts will already rebuild the animation when it detects change of Npc type. + // the following is just for reattaching the camera properly. + mRendering->rebuildPtr(actor); + if(actor.getRefData().getHandle() == "player") { // Update the GUI only when called on the player @@ -2039,8 +2040,6 @@ namespace MWWorld windowManager->unsetForceHide(MWGui::GW_Magic); } } - - mRendering->rebuildPtr(actor); } void World::applyWerewolfAcrobatics(const Ptr &actor) @@ -2048,7 +2047,7 @@ namespace MWWorld const Store &gmst = getStore().get(); MWMechanics::NpcStats &stats = Class::get(actor).getNpcStats(actor); - stats.getSkill(ESM::Skill::Acrobatics).setModified(gmst.find("fWerewolfAcrobatics")->getFloat(), 0); + stats.getSkill(ESM::Skill::Acrobatics).setBase(gmst.find("fWerewolfAcrobatics")->getFloat()); } bool World::getGodModeState() @@ -2079,15 +2078,56 @@ namespace MWWorld } } + bool World::startSpellCast(const Ptr &actor) + { + MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); + + std::string message; + bool fail = false; + bool isPlayer = (actor == getPlayer().getPlayer()); + + std::string selectedSpell = stats.getSpells().getSelectedSpell(); + + if (!selectedSpell.empty()) + { + const ESM::Spell* spell = getStore().get().search(selectedSpell); + + // Check mana + MWMechanics::DynamicStat magicka = stats.getMagicka(); + if (magicka.getCurrent() < spell->mData.mCost) + { + message = "#{sMagicInsufficientSP}"; + fail = true; + } + + // If this is a power, check if it was already used in the last 24h + if (!fail && spell->mData.mType == ESM::Spell::ST_Power) + { + if (stats.canUsePower(spell->mId)) + stats.usePower(spell->mId); + else + { + message = "#{sPowerAlreadyUsed}"; + fail = true; + } + } + + // Reduce mana + magicka.setCurrent(magicka.getCurrent() - spell->mData.mCost); + stats.setMagicka(magicka); + } + + if (isPlayer && fail) + MWBase::Environment::get().getWindowManager()->messageBox(message); + + return !fail; + } + void World::castSpell(const Ptr &actor) { MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); InventoryStore& inv = actor.getClass().getInventoryStore(actor); - // Unset casting flag, otherwise pressing the mouse button down would continue casting every frame if using an enchantment - // (which casts instantly without an animation) - stats.setAttackingOrSpell(false); - MWWorld::Ptr target = getFacedObject(); std::string selectedSpell = stats.getSpells().getSelectedSpell(); @@ -2273,4 +2313,160 @@ namespace MWWorld { return mContentFiles; } + + void World::breakInvisibility(const Ptr &actor) + { + actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(ESM::MagicEffect::Invisibility); + actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Invisibility); + } + + bool World::isDark() const + { + return mWeatherManager->isDark(); + } + + bool World::findInteriorPositionInWorldSpace(MWWorld::CellStore* cell, Ogre::Vector3& result) + { + MWWorld::CellRefList& doors = cell->mDoors; + CellRefList::List& refList = doors.mList; + + // Check if any door in the cell leads to an exterior directly + for (CellRefList::List::iterator it = refList.begin(); it != refList.end(); ++it) + { + MWWorld::LiveCellRef& ref = *it; + if (ref.mRef.mTeleport && ref.mRef.mDestCell.empty()) + { + ESM::Position pos = ref.mRef.mDoorDest; + result = Ogre::Vector3(pos.pos); + return true; + } + } + + // No luck :( + return false; + } + + void World::teleportToClosestMarker (const MWWorld::Ptr& ptr, + const std::string& id, Ogre::Vector3 worldPos) + { + MWWorld::Ptr closestMarker; + float closestDistance = FLT_MAX; + + std::vector markers; + mCells.getExteriorPtrs(id, markers); + + for (std::vector::iterator it = markers.begin(); it != markers.end(); ++it) + { + ESM::Position pos = it->getRefData().getPosition(); + Ogre::Vector3 markerPos = Ogre::Vector3(pos.pos); + float distance = worldPos.squaredDistance(markerPos); + if (distance < closestDistance) + { + closestDistance = distance; + closestMarker = *it; + } + + } + + MWWorld::ActionTeleport action("", closestMarker.getRefData().getPosition()); + action.execute(ptr); + } + + void World::updateWeather(float duration) + { + if (mPlayer->wasTeleported()) + { + mPlayer->setTeleported(false); + mWeatherManager->switchToNextWeather(true); + } + + mWeatherManager->update(duration); + } + + struct AddDetectedReference + { + AddDetectedReference(std::vector& out, Ptr detector, World::DetectionType type, float squaredDist) + : mOut(out), mDetector(detector), mType(type), mSquaredDist(squaredDist) + { + } + + std::vector& mOut; + Ptr mDetector; + float mSquaredDist; + World::DetectionType mType; + bool operator() (MWWorld::Ptr ptr) + { + if (Ogre::Vector3(ptr.getRefData().getPosition().pos).squaredDistance( + Ogre::Vector3(mDetector.getRefData().getPosition().pos)) >= mSquaredDist) + return true; + + if (!ptr.getRefData().isEnabled()) + return true; + + // Consider references inside containers as well + if (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)) + { + mOut.push_back(ptr); + return true; + } + } + } + } + + if (needToAdd(ptr)) + mOut.push_back(ptr); + + return true; + } + + bool needToAdd (MWWorld::Ptr ptr) + { + if (mType == World::Detect_Creature && 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()) + return false; + return true; + } + }; + + void World::listDetectedReferences(const Ptr &ptr, std::vector &out, DetectionType type) + { + const MWMechanics::MagicEffects& effects = ptr.getClass().getCreatureStats(ptr).getMagicEffects(); + float dist=0; + if (type == World::Detect_Creature) + dist = effects.get(ESM::MagicEffect::DetectAnimal).mMagnitude; + else if (type == World::Detect_Key) + dist = effects.get(ESM::MagicEffect::DetectKey).mMagnitude; + else if (type == World::Detect_Enchantment) + dist = effects.get(ESM::MagicEffect::DetectEnchantment).mMagnitude; + + if (!dist) + return; + + dist = feetToGameUnits(dist); + + AddDetectedReference functor (out, ptr, type, dist*dist); + + const Scene::CellStoreCollection& active = mWorldScene->getActiveCells(); + for (Scene::CellStoreCollection::const_iterator it = active.begin(); it != active.end(); ++it) + { + MWWorld::CellStore* cellStore = *it; + cellStore->forEach(functor); + } + } + + float World::feetToGameUnits(float feet) + { + // Looks like there is no GMST for this. This factor was determined in experiments + // with the Telekinesis effect. + return feet * 22; + } } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index fb9c4cb685..d7befcc6e5 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -109,7 +109,7 @@ namespace MWWorld }; std::map mProjectiles; - + void updateWeather(float duration); int getDaysPerMonth (int month) const; void rotateObjectImp (const Ptr& ptr, Ogre::Vector3 rot, bool adjust); @@ -117,8 +117,7 @@ namespace MWWorld bool moveObjectImp (const Ptr& ptr, float x, float y, float z); ///< @return true if the active cell (cell player is in) changed - - Ptr copyObjectToCell(const Ptr &ptr, CellStore &cell, const ESM::Position &pos); + Ptr copyObjectToCell(const Ptr &ptr, CellStore &cell, const ESM::Position &pos, bool adjustPos=true); void updateWindowManager (); void performUpdateSceneQueries (); @@ -159,6 +158,8 @@ namespace MWWorld /// Called when \a object is moved to an inactive cell void objectLeftActiveCell (MWWorld::Ptr object, MWWorld::Ptr movedPtr); + float feetToGameUnits(float feet); + public: World (OEngine::Render::OgreRenderer& renderer, @@ -217,7 +218,7 @@ namespace MWWorld virtual Ogre::Vector2 getNorthVector (CellStore* cell); ///< get north vector (OGRE coordinates) for given interior cell - virtual std::vector getDoorMarkers (MWWorld::CellStore* cell); + virtual void getDoorMarkers (MWWorld::CellStore* cell, std::vector& out); ///< get a list of teleport door markers for a given cell, to be displayed on the local map virtual void getInteriorMapPosition (Ogre::Vector2 position, float& nX, float& nY, int &x, int& y); @@ -528,12 +529,42 @@ namespace MWWorld virtual bool toggleGodMode(); + /** + * @brief startSpellCast attempt to start casting a spell. Might fail immediately if conditions are not met. + * @param actor + * @return true if the spell can be casted (i.e. the animation should start) + */ + virtual bool startSpellCast (const MWWorld::Ptr& actor); + + /** + * @brief Cast the actual spell, should be called mid-animation + * @param actor + */ virtual void castSpell (const MWWorld::Ptr& actor); virtual void launchProjectile (const std::string& id, bool stack, const ESM::EffectList& effects, const MWWorld::Ptr& actor, const std::string& sourceName); + virtual const std::vector& getContentFiles() const; + + virtual void breakInvisibility (const MWWorld::Ptr& actor); + // Are we in an exterior or pseudo-exterior cell and it's night? + virtual bool isDark() const; + + virtual bool findInteriorPositionInWorldSpace(MWWorld::CellStore* cell, Ogre::Vector3& result); + + /// Teleports \a ptr to the reference of \a id (e.g. DivineMarker, PrisonMarker, TempleMarker) + /// closest to \a worldPos. + /// @note id must be lower case + virtual void teleportToClosestMarker (const MWWorld::Ptr& ptr, + const std::string& id, Ogre::Vector3 worldPos); + + /// List all references (filtered by \a type) detected by \a ptr. The range + /// is determined by the current magnitude of the "Detect X" magic effect belonging to \a type. + /// @note This also works for references in containers. + virtual void listDetectedReferences (const MWWorld::Ptr& ptr, std::vector& out, + DetectionType type); }; } diff --git a/cmake/FindBullet.cmake b/cmake/FindBullet.cmake index 97feddffeb..b75f3105a6 100644 --- a/cmake/FindBullet.cmake +++ b/cmake/FindBullet.cmake @@ -60,8 +60,6 @@ _FIND_BULLET_LIBRARY(BULLET_COLLISION_LIBRARY BulletCollision) _FIND_BULLET_LIBRARY(BULLET_COLLISION_LIBRARY_DEBUG BulletCollision_Debug BulletCollision_d) _FIND_BULLET_LIBRARY(BULLET_MATH_LIBRARY BulletMath LinearMath) _FIND_BULLET_LIBRARY(BULLET_MATH_LIBRARY_DEBUG BulletMath_Debug BulletMath_d LinearMath_debug LinearMath_d) -_FIND_BULLET_LIBRARY(BULLET_SOFTBODY_LIBRARY BulletSoftBody) -_FIND_BULLET_LIBRARY(BULLET_SOFTBODY_LIBRARY_DEBUG BulletSoftBody_Debug BulletSoftBody_d) # handle the QUIETLY and REQUIRED arguments and set BULLET_FOUND to TRUE if @@ -69,12 +67,11 @@ _FIND_BULLET_LIBRARY(BULLET_SOFTBODY_LIBRARY_DEBUG BulletSoftBody_Debug BulletS include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(Bullet DEFAULT_MSG BULLET_DYNAMICS_LIBRARY BULLET_COLLISION_LIBRARY BULLET_MATH_LIBRARY - BULLET_SOFTBODY_LIBRARY BULLET_INCLUDE_DIR) + BULLET_INCLUDE_DIR) set(BULLET_INCLUDE_DIRS ${BULLET_INCLUDE_DIR}) if(BULLET_FOUND) _BULLET_APPEND_LIBRARIES(BULLET_LIBRARIES BULLET_DYNAMICS_LIBRARY) _BULLET_APPEND_LIBRARIES(BULLET_LIBRARIES BULLET_COLLISION_LIBRARY) _BULLET_APPEND_LIBRARIES(BULLET_LIBRARIES BULLET_MATH_LIBRARY) - _BULLET_APPEND_LIBRARIES(BULLET_LIBRARIES BULLET_SOFTBODY_LIBRARY) endif() diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 6f01216f1a..dbf8c1132a 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -19,7 +19,7 @@ add_component_dir (nif ) add_component_dir (nifogre - ogrenifloader skeleton material mesh particles + ogrenifloader skeleton material mesh particles controller ) add_component_dir (nifbullet @@ -48,8 +48,8 @@ add_component_dir (misc ) add_component_dir (files - linuxpath windowspath macospath fixedpath multidircollection collections fileops configurationmanager - filelibrary constrainedfiledatastream lowlevelfile + linuxpath windowspath macospath fixedpath multidircollection collections configurationmanager + constrainedfiledatastream lowlevelfile ) add_component_dir (compiler diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index e95f6f6985..6194be5327 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -222,6 +222,8 @@ namespace Compiler extensions.registerInstruction ("activate", "", opcodeActivate); extensions.registerInstruction ("lock", "/l", opcodeLock, opcodeLockExplicit); extensions.registerInstruction ("unlock", "", opcodeUnlock, opcodeUnlockExplicit); + extensions.registerInstruction ("cast", "SS", opcodeCast, opcodeCastExplicit); + extensions.registerInstruction ("explodespell", "S", opcodeExplodeSpell, opcodeExplodeSpellExplicit); extensions.registerInstruction ("togglecollisionboxes", "", opcodeToggleCollisionBoxes); extensions.registerInstruction ("togglecollisiongrid", "", opcodeToggleCollisionDebug); extensions.registerInstruction ("tcb", "", opcodeToggleCollisionBoxes); @@ -239,6 +241,7 @@ namespace Compiler extensions.registerInstruction ("togglevanitymode", "", opcodeToggleVanityMode); extensions.registerInstruction ("tvm", "", opcodeToggleVanityMode); extensions.registerFunction ("getpcsleep", 'l', "", opcodeGetPcSleep); + extensions.registerFunction ("getpcjumping", 'l', "", opcodeGetPcJumping); extensions.registerInstruction ("wakeuppc", "", opcodeWakeUpPc); extensions.registerInstruction ("playbink", "Sl", opcodePlayBink); extensions.registerFunction ("getlocked", 'l', "", opcodeGetLocked, opcodeGetLockedExplicit); @@ -249,6 +252,7 @@ namespace Compiler extensions.registerInstruction ("dropsoulgem", "c", opcodeDropSoulGem, opcodeDropSoulGemExplicit); extensions.registerFunction ("getattacked", 'l', "", opcodeGetAttacked, opcodeGetAttackedExplicit); extensions.registerFunction ("getweapondrawn", 'l', "", opcodeGetWeaponDrawn, opcodeGetWeaponDrawnExplicit); + extensions.registerFunction ("getspellreadied", 'l', "", opcodeGetSpellReadied, opcodeGetSpellReadiedExplicit); extensions.registerFunction ("getspelleffects", 'l', "c", opcodeGetSpellEffects, opcodeGetSpellEffectsExplicit); extensions.registerFunction ("getcurrenttime", 'f', "", opcodeGetCurrentTime); extensions.registerInstruction ("setdelete", "l", opcodeSetDelete, opcodeSetDeleteExplicit); @@ -389,6 +393,12 @@ namespace Compiler extensions.registerInstruction ("addspell", "c", opcodeAddSpell, opcodeAddSpellExplicit); extensions.registerInstruction ("removespell", "c", opcodeRemoveSpell, opcodeRemoveSpellExplicit); + extensions.registerInstruction ("removespelleffects", "c", opcodeRemoveSpellEffects, + opcodeRemoveSpellEffectsExplicit); + extensions.registerInstruction ("removeeffects", "l", opcodeRemoveEffects, + opcodeRemoveEffectsExplicit); + extensions.registerInstruction ("resurrect", "", opcodeResurrect, + opcodeResurrectExplicit); extensions.registerFunction ("getspell", 'l', "c", opcodeGetSpell, opcodeGetSpellExplicit); extensions.registerInstruction("pcraiserank","/S",opcodePCRaiseRank); diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp index a885a373de..46524c7cdd 100644 --- a/components/compiler/opcodes.hpp +++ b/components/compiler/opcodes.hpp @@ -25,7 +25,7 @@ namespace Compiler const int opcodeAiFollowExplicit = 0x20023; const int opcodeAiFollowCell = 0x20024; const int opcodeAiFollowCellExplicit = 0x20025; - const int opcodeSetHello = 0x200015e; + const int opcodeSetHello = 0x200015c; const int opcodeSetHelloExplicit = 0x200015d; const int opcodeSetFight = 0x200015e; const int opcodeSetFightExplicit = 0x200015f; @@ -69,7 +69,7 @@ namespace Compiler { const int opcodeCellChanged = 0x2000000; const int opcodeCOC = 0x2000026; - const int opcodeCOE = 0x200008e; + const int opcodeCOE = 0x2000226; const int opcodeGetInterior = 0x2000131; const int opcodeGetPCCell = 0x2000136; const int opcodeGetWaterLevel = 0x2000141; @@ -188,6 +188,7 @@ namespace Compiler const int opcodeDontSaveObject = 0x2000153; const int opcodeToggleVanityMode = 0x2000174; const int opcodeGetPcSleep = 0x200019f; + const int opcodeGetPcJumping = 0x2000233; const int opcodeWakeUpPc = 0x20001a2; const int opcodeGetLocked = 0x20001c7; const int opcodeGetLockedExplicit = 0x20001c8; @@ -205,6 +206,8 @@ namespace Compiler const int opcodeGetAttackedExplicit = 0x20001d4; const int opcodeGetWeaponDrawn = 0x20001d7; const int opcodeGetWeaponDrawnExplicit = 0x20001d8; + const int opcodeGetSpellReadied = 0x2000231; + const int opcodeGetSpellReadiedExplicit = 0x2000232; const int opcodeGetSpellEffects = 0x20001db; const int opcodeGetSpellEffectsExplicit = 0x20001dc; const int opcodeGetCurrentTime = 0x20001dd; @@ -228,6 +231,10 @@ namespace Compiler const int opcodeToggleGodMode = 0x200021f; const int opcodeDisableLevitation = 0x2000220; const int opcodeEnableLevitation = 0x2000221; + const int opcodeCast = 0x2000227; + const int opcodeCastExplicit = 0x2000228; + const int opcodeExplodeSpell = 0x2000229; + const int opcodeExplodeSpellExplicit = 0x200022a; } namespace Sky @@ -365,6 +372,13 @@ namespace Compiler const int opcodeIsWerewolfExplicit = 0x20001fe; const int opcodeGetWerewolfKills = 0x20001e2; + + const int opcodeRemoveSpellEffects = 0x200022b; + const int opcodeRemoveSpellEffectsExplicit = 0x200022c; + const int opcodeRemoveEffects = 0x200022d; + const int opcodeRemoveEffectsExplicit = 0x200022e; + const int opcodeResurrect = 0x200022f; + const int opcodeResurrectExplicit = 0x2000230; } namespace Transformation diff --git a/components/esm/cellref.hpp b/components/esm/cellref.hpp index 47cb0b99ed..5f1066cf8d 100644 --- a/components/esm/cellref.hpp +++ b/components/esm/cellref.hpp @@ -20,7 +20,7 @@ namespace ESM public: int mRefnum; // Reference number - std::string mRefID; // ID of object being referenced + std::string mRefID; // ID of object being referenced (must be lowercase) float mScale; // Scale applied to mesh @@ -89,4 +89,4 @@ namespace ESM }; } -#endif \ No newline at end of file +#endif diff --git a/components/esm/loadcrea.hpp b/components/esm/loadcrea.hpp index 99c4f52257..9e48f7c1a2 100644 --- a/components/esm/loadcrea.hpp +++ b/components/esm/loadcrea.hpp @@ -40,7 +40,7 @@ struct Creature enum Type { Creatures = 0, - Deadra = 1, + Daedra = 1, Undead = 2, Humanoid = 3 }; diff --git a/components/esm/loadnpc.cpp b/components/esm/loadnpc.cpp index 9fff2d885d..e5b851bf0c 100644 --- a/components/esm/loadnpc.cpp +++ b/components/esm/loadnpc.cpp @@ -10,7 +10,7 @@ namespace ESM void NPC::load(ESMReader &esm) { - mNpdt52.mGold = -10; + //mNpdt52.mGold = -10; mPersistent = esm.getRecordFlags() & 0x0400; @@ -29,12 +29,12 @@ void NPC::load(ESMReader &esm) esm.getSubHeader(); if (esm.getSubSize() == 52) { - mNpdtType = 52; + mNpdtType = NPC_DEFAULT; esm.getExact(&mNpdt52, 52); } else if (esm.getSubSize() == 12) { - mNpdtType = 12; + mNpdtType = NPC_WITH_AUTOCALCULATED_STATS; esm.getExact(&mNpdt12, 12); } else @@ -76,9 +76,9 @@ void NPC::save(ESMWriter &esm) const esm.writeHNCString("KNAM", mHair); esm.writeHNOCString("SCRI", mScript); - if (mNpdtType == 52) + if (mNpdtType == NPC_DEFAULT) esm.writeHNT("NPDT", mNpdt52, 52); - else if (mNpdtType == 12) + else if (mNpdtType == NPC_WITH_AUTOCALCULATED_STATS) esm.writeHNT("NPDT", mNpdt12, 12); esm.writeHNT("FLAG", mFlags); @@ -114,7 +114,7 @@ void NPC::save(ESMWriter &esm) const mNpdt52.mLevel = 0; mNpdt52.mStrength = mNpdt52.mIntelligence = mNpdt52.mWillpower = mNpdt52.mAgility = mNpdt52.mSpeed = mNpdt52.mEndurance = mNpdt52.mPersonality = mNpdt52.mLuck = 0; - for (int i=0; i<27; ++i) mNpdt52.mSkills[i] = 0; + for (int i=0; i< Skill::Length; ++i) mNpdt52.mSkills[i] = 0; mNpdt52.mReputation = 0; mNpdt52.mHealth = mNpdt52.mMana = mNpdt52.mFatigue = 0; mNpdt52.mDisposition = 0; diff --git a/components/esm/loadnpc.hpp b/components/esm/loadnpc.hpp index d9e691669c..1eac8d64fe 100644 --- a/components/esm/loadnpc.hpp +++ b/components/esm/loadnpc.hpp @@ -8,6 +8,7 @@ #include "loadcont.hpp" #include "aipackage.hpp" #include "spelllist.hpp" +#include "loadskil.hpp" namespace ESM { @@ -58,6 +59,12 @@ struct NPC Metal = 0x0800 // Metal blood effect (golden?) }; + enum NpcType + { + NPC_WITH_AUTOCALCULATED_STATS = 12, + NPC_DEFAULT = 52 + }; + #pragma pack(push) #pragma pack(1) @@ -73,7 +80,7 @@ struct NPC mPersonality, mLuck; - char mSkills[27]; + char mSkills[Skill::Length]; char mReputation; short mHealth, mMana, mFatigue; char mDisposition, mFactionID, mRank; diff --git a/components/esm/loadtes3.cpp b/components/esm/loadtes3.cpp index e5d6ec8370..d3d5250493 100644 --- a/components/esm/loadtes3.cpp +++ b/components/esm/loadtes3.cpp @@ -28,7 +28,15 @@ void ESM::Header::load (ESMReader &esm) else mFormat = 0; - esm.getHNT (mData, "HEDR", 300); + if (esm.isNextSub("HEDR")) + { + esm.getSubHeader(); + esm.getT(mData.version); + esm.getT(mData.type); + mData.author.assign(esm.getString(sizeof(mData.author.name))); + mData.desc.assign(esm.getString(sizeof(mData.desc.name))); + esm.getT(mData.records); + } while (esm.isNextSub ("MAST")) { @@ -52,4 +60,4 @@ void ESM::Header::save (ESMWriter &esm) esm.writeHNCString ("MAST", iter->name); esm.writeHNT ("DATA", iter->size); } -} \ No newline at end of file +} diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index 75c877dc5d..761b7ca5ad 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -26,9 +26,10 @@ ConfigurationManager::ConfigurationManager() { setupTokensMapping(); - boost::filesystem::create_directories(mFixedPath.getUserPath()); + boost::filesystem::create_directories(mFixedPath.getUserConfigPath()); + boost::filesystem::create_directories(mFixedPath.getUserDataPath()); - mLogPath = mFixedPath.getUserPath(); + mLogPath = mFixedPath.getUserConfigPath(); } ConfigurationManager::~ConfigurationManager() @@ -39,19 +40,19 @@ 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<>::getUserPath)); + mTokensMapping.insert(std::make_pair(userToken, &FixedPath<>::getUserConfigPath)); mTokensMapping.insert(std::make_pair(globalToken, &FixedPath<>::getGlobalDataPath)); } void ConfigurationManager::readConfiguration(boost::program_options::variables_map& variables, boost::program_options::options_description& description) { - loadConfig(mFixedPath.getUserPath(), variables, description); + loadConfig(mFixedPath.getUserConfigPath(), variables, description); boost::program_options::notify(variables); loadConfig(mFixedPath.getLocalPath(), variables, description); boost::program_options::notify(variables); - loadConfig(mFixedPath.getGlobalPath(), variables, description); + loadConfig(mFixedPath.getGlobalConfigPath(), variables, description); boost::program_options::notify(variables); } @@ -141,12 +142,17 @@ void ConfigurationManager::loadConfig(const boost::filesystem::path& path, const boost::filesystem::path& ConfigurationManager::getGlobalPath() const { - return mFixedPath.getGlobalPath(); + return mFixedPath.getGlobalConfigPath(); } -const boost::filesystem::path& ConfigurationManager::getUserPath() const +const boost::filesystem::path& ConfigurationManager::getUserConfigPath() const { - return mFixedPath.getUserPath(); + return mFixedPath.getUserConfigPath(); +} + +const boost::filesystem::path& ConfigurationManager::getUserDataPath() const +{ + return mFixedPath.getUserDataPath(); } const boost::filesystem::path& ConfigurationManager::getLocalPath() const diff --git a/components/files/configurationmanager.hpp b/components/files/configurationmanager.hpp index 4df8716647..35144fe04f 100644 --- a/components/files/configurationmanager.hpp +++ b/components/files/configurationmanager.hpp @@ -36,7 +36,7 @@ struct ConfigurationManager /**< Fixed paths */ const boost::filesystem::path& getGlobalPath() const; - const boost::filesystem::path& getUserPath() const; + const boost::filesystem::path& getUserConfigPath() const; const boost::filesystem::path& getLocalPath() const; const boost::filesystem::path& getGlobalDataPath() const; diff --git a/components/files/filelibrary.cpp b/components/files/filelibrary.cpp deleted file mode 100644 index ce2d95f57c..0000000000 --- a/components/files/filelibrary.cpp +++ /dev/null @@ -1,120 +0,0 @@ -#include "filelibrary.hpp" - -#include - -#include -#include <../components/misc/stringops.hpp> - -namespace Files -{ - // Looks for a string in a vector of strings - bool containsVectorString(const StringVector& list, const std::string& str) - { - for (StringVector::const_iterator iter = list.begin(); - iter != list.end(); ++iter) - { - if (*iter == str) - return true; - } - return false; - } - - // Searches a path and adds the results to the library - void FileLibrary::add(const boost::filesystem::path &root, bool recursive, bool strict, - const StringVector &acceptableExtensions) - { - if (!boost::filesystem::exists(root)) - { - std::cout << "Warning " << root.string() << " does not exist.\n"; - return; - } - - std::string fileExtension; - std::string type; - - // remember the last location of the priority list when listing new items - int length = mPriorityList.size(); - - // First makes a list of all candidate files - FileLister(root, mPriorityList, recursive); - - // Then sort these files into sections according to the folder they belong to - for (PathContainer::iterator listIter = mPriorityList.begin() + length; - listIter != mPriorityList.end(); ++listIter) - { - if( !acceptableExtensions.empty() ) - { - fileExtension = boost::filesystem::path (listIter->extension()).string(); - Misc::StringUtils::toLower(fileExtension); - if(!containsVectorString(acceptableExtensions, fileExtension)) - continue; - } - - type = boost::filesystem::path (listIter->parent_path().leaf()).string(); - if (!strict) - Misc::StringUtils::toLower(type); - - mMap[type].push_back(*listIter); - // std::cout << "Added path: " << listIter->string() << " in section "<< type <second); - } - } - - // Searches the library for an item and returns a boost path to it - boost::filesystem::path FileLibrary::locate(std::string item, bool strict, bool ignoreExtensions, std::string sectionName) - { - boost::filesystem::path result(""); - if (sectionName == "") - { - return FileListLocator(mPriorityList, boost::filesystem::path(item), strict, ignoreExtensions); - } - else - { - if (!containsSection(sectionName, strict)) - { - std::cout << "Warning: There is no section named " << sectionName << "\n"; - return result; - } - result = FileListLocator(mMap[sectionName], boost::filesystem::path(item), strict, ignoreExtensions); - } - return result; - } - - // Prints all the available sections, used for debugging - void FileLibrary::printSections() - { - for(StringPathContMap::const_iterator mapIter = mMap.begin(); - mapIter != mMap.end(); ++mapIter) - { - std::cout << mapIter->first < - -namespace Files -{ - typedef std::map StringPathContMap; - typedef std::vector StringVector; - - /// Looks for a string in a vector of strings - bool containsVectorString(const StringVector& list, const std::string& str); - - /// \brief Searches directories and makes lists of files according to folder name - class FileLibrary - { - private: - StringPathContMap mMap; - PathContainer mEmptyPath; - PathContainer mPriorityList; - - public: - /// Searches a path and adds the results to the library - /// Recursive search and fs strict options are available - /// Takes a vector of acceptable files extensions, if none is given it lists everything. - void add(const boost::filesystem::path &root, bool recursive, bool strict, - const StringVector &acceptableExtensions); - - /// Returns true if the named section exists - /// You can run this check before running section() - bool containsSection(std::string sectionName, bool strict); - - /// Returns a pointer to const for a section of the library - /// which is essentially a PathContainer. - /// If the section does not exists it returns a pointer to an empty path. - const PathContainer* section(std::string sectionName, bool strict); - - /// Searches the library for an item and returns a boost path to it - /// Optionally you can provide a specific section - /// The result is the first that comes up according to alphabetical - /// section naming - boost::filesystem::path locate(std::string item, bool strict, bool ignoreExtensions, std::string sectionName=""); - - /// Prints all the available sections, used for debugging - void printSections(); - }; -} - -#endif diff --git a/components/files/fileops.cpp b/components/files/fileops.cpp deleted file mode 100644 index fbc2eef056..0000000000 --- a/components/files/fileops.cpp +++ /dev/null @@ -1,120 +0,0 @@ -#include "fileops.hpp" - -#include - -#include -#include -#include <../components/misc/stringops.hpp> - -namespace Files -{ - -bool isFile(const char *name) -{ - return boost::filesystem::exists(boost::filesystem::path(name)); -} - - // Returns true if the last part of the superset matches the subset - bool endingMatches(const std::string& superset, const std::string& subset) - { - if (subset.length() > superset.length()) - return false; - return superset.substr(superset.length() - subset.length()) == subset; - } - - // Makes a list of files from a directory - void FileLister( boost::filesystem::path currentPath, Files::PathContainer& list, bool recursive) - { - if (!boost::filesystem::exists(currentPath)) - { - std::cout << "WARNING: " << currentPath.string() << " does not exist.\n"; - return ; - } - if (recursive) - { - for ( boost::filesystem::recursive_directory_iterator end, itr(currentPath.string()); - itr != end; ++itr ) - { - if ( boost::filesystem::is_regular_file(*itr)) - list.push_back(itr->path()); - } - } - else - { - for ( boost::filesystem::directory_iterator end, itr(currentPath.string()); - itr != end; ++itr ) - { - if ( boost::filesystem::is_regular_file(*itr)) - list.push_back(itr->path()); - } - } - } - - // Locates path in path container - boost::filesystem::path FileListLocator (const Files::PathContainer& list, const boost::filesystem::path& toFind, - bool strict, bool ignoreExtensions) - { - boost::filesystem::path result(""); - if (list.empty()) - return result; - - std::string toFindStr; - if (ignoreExtensions) - toFindStr = boost::filesystem::basename(toFind); - else - toFindStr = toFind.string(); - - std::string fullPath; - - // The filesystems slash sets the default slash - std::string slash; - std::string wrongslash; - if(list[0].string().find("\\") != std::string::npos) - { - slash = "\\"; - wrongslash = "/"; - } - else - { - slash = "/"; - wrongslash = "\\"; - } - - // The file being looked for is converted to the new slash - if(toFindStr.find(wrongslash) != std::string::npos ) - { - boost::replace_all(toFindStr, wrongslash, slash); - } - - if (!strict) - { - Misc::StringUtils::toLower(toFindStr); - } - - for (Files::PathContainer::const_iterator it = list.begin(); it != list.end(); ++it) - { - fullPath = it->string(); - if (ignoreExtensions) - fullPath.erase(fullPath.length() - - boost::filesystem::path (it->extension()).string().length()); - - if (!strict) - { - Misc::StringUtils::toLower(fullPath); - } - if(endingMatches(fullPath, toFindStr)) - { - result = *it; - break; - } - } - return result; - } - - // Overloaded form of the locator that takes a string and returns a string - std::string FileListLocator (const Files::PathContainer& list,const std::string& toFind, bool strict, bool ignoreExtensions) - { - return FileListLocator(list, boost::filesystem::path(toFind), strict, ignoreExtensions).string(); - } - -} diff --git a/components/files/fileops.hpp b/components/files/fileops.hpp deleted file mode 100644 index bf1c51485f..0000000000 --- a/components/files/fileops.hpp +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef COMPONENTS_FILES_FILEOPS_HPP -#define COMPONENTS_FILES_FILEOPS_HPP - -#include -#include -#include - -#include - -namespace Files -{ - -///\brief Check if a given path is an existing file (not a directory) -///\param [in] name - filename -bool isFile(const char *name); - - /// A vector of Boost Paths, very handy - typedef std::vector PathContainer; - - /// Makes a list of files from a directory by taking a boost - /// path and a Path Container and adds to the Path container - /// all files in the path. It has a recursive option. - void FileLister( boost::filesystem::path currentPath, Files::PathContainer& list, bool recursive); - - /// Locates boost path in path container - /// returns the path from the container - /// that contains the searched path. - /// If it's not found it returns and empty path - /// Takes care of slashes, backslashes and it has a strict option. - boost::filesystem::path FileListLocator (const Files::PathContainer& list, const boost::filesystem::path& toFind, - bool strict, bool ignoreExtensions); - - /// Overloaded form of the locator that takes a string and returns a string - std::string FileListLocator (const Files::PathContainer& list,const std::string& toFind, bool strict, bool ignoreExtensions); - -} - -#endif /* COMPONENTS_FILES_FILEOPS_HPP */ diff --git a/components/files/fixedpath.hpp b/components/files/fixedpath.hpp index a309dc9fb6..cfd3458ce1 100644 --- a/components/files/fixedpath.hpp +++ b/components/files/fixedpath.hpp @@ -48,8 +48,9 @@ struct FixedPath */ FixedPath(const std::string& application_name) : mPath(application_name + "/") - , mUserPath(mPath.getUserPath()) - , mGlobalPath(mPath.getGlobalPath()) + , mUserConfigPath(mPath.getUserConfigPath()) + , mUserDataPath(mPath.getUserDataPath()) + , mGlobalConfigPath(mPath.getGlobalConfigPath()) , mLocalPath(mPath.getLocalPath()) , mGlobalDataPath(mPath.getGlobalDataPath()) , mInstallPath(mPath.getInstallPath()) @@ -59,28 +60,27 @@ struct FixedPath /** * \brief Return path pointing to the user local configuration directory. - * - * \return boost::filesystem::path */ - const boost::filesystem::path& getUserPath() const + const boost::filesystem::path& getUserConfigPath() const + { + return mUserConfigPath; + } + + const boost::filesystem::path& getUserDataPath() const { - return mUserPath; + return mUserDataPath; } /** * \brief Return path pointing to the global (system) configuration directory. - * - * \return boost::filesystem::path */ - const boost::filesystem::path& getGlobalPath() const + const boost::filesystem::path& getGlobalConfigPath() const { - return mGlobalPath; + return mGlobalConfigPath; } /** * \brief Return path pointing to the directory where application was started. - * - * \return boost::filesystem::path */ const boost::filesystem::path& getLocalPath() const { @@ -105,8 +105,9 @@ struct FixedPath private: PathType mPath; - boost::filesystem::path mUserPath; /**< User path */ - boost::filesystem::path mGlobalPath; /**< Global path */ + boost::filesystem::path mUserConfigPath; /**< User path */ + boost::filesystem::path mUserDataPath; + boost::filesystem::path mGlobalConfigPath; /**< Global path */ boost::filesystem::path mLocalPath; /**< It is the same directory where application was run */ boost::filesystem::path mGlobalDataPath; /**< Global application data path */ diff --git a/components/files/linuxpath.cpp b/components/files/linuxpath.cpp index c974a91d35..d285f4229c 100644 --- a/components/files/linuxpath.cpp +++ b/components/files/linuxpath.cpp @@ -8,6 +8,39 @@ #include #include + +namespace +{ + boost::filesystem::path getUserHome() + { + const char* dir = getenv("HOME"); + if (dir == NULL) + { + struct passwd* pwd = getpwuid(getuid()); + if (pwd != NULL) + { + dir = pwd->pw_dir; + } + } + if (dir == NULL) + return boost::filesystem::path(); + else + return boost::filesystem::path(dir); + } + + boost::filesystem::path getEnv(const std::string& envVariable, const boost::filesystem::path& fallback) + { + const char* result = getenv(envVariable.c_str()); + if (!result) + return fallback; + boost::filesystem::path dir(result); + if (dir.empty()) + return fallback; + else + return dir; + } +} + /** * \namespace Files */ @@ -19,51 +52,22 @@ LinuxPath::LinuxPath(const std::string& application_name) { } -boost::filesystem::path LinuxPath::getUserPath() const +boost::filesystem::path LinuxPath::getUserConfigPath() const { - boost::filesystem::path userPath("."); - - const char* theDir = getenv("HOME"); - if (theDir == NULL) - { - struct passwd* pwd = getpwuid(getuid()); - if (pwd != NULL) - { - theDir = pwd->pw_dir; - } - } - - if (theDir != NULL) - { - userPath = boost::filesystem::path(theDir); - } + return getEnv("XDG_CONFIG_HOME", getUserHome() / ".config") / mName; +} - return userPath / ".config" / mName; +boost::filesystem::path LinuxPath::getUserDataPath() const +{ + return getEnv("XDG_DATA_HOME", getUserHome() / ".local/share") / mName; } boost::filesystem::path LinuxPath::getCachePath() const { - boost::filesystem::path userPath("."); - - const char* theDir = getenv("HOME"); - if (theDir == NULL) - { - struct passwd* pwd = getpwuid(getuid()); - if (pwd != NULL) - { - theDir = pwd->pw_dir; - } - } - - if (theDir != NULL) - { - userPath = boost::filesystem::path(theDir); - } - - return userPath / ".cache" / mName; + return getEnv("XDG_CACHE_HOME", getUserHome() / ".cache") / mName; } -boost::filesystem::path LinuxPath::getGlobalPath() const +boost::filesystem::path LinuxPath::getGlobalConfigPath() const { boost::filesystem::path globalPath("/etc/"); return globalPath / mName; @@ -84,17 +88,9 @@ boost::filesystem::path LinuxPath::getInstallPath() const { boost::filesystem::path installPath; - char *homePath = getenv("HOME"); - if (homePath == NULL) - { - struct passwd* pwd = getpwuid(getuid()); - if (pwd != NULL) - { - homePath = pwd->pw_dir; - } - } + boost::filesystem::path homePath = getUserHome(); - if (homePath != NULL) + if (!homePath.empty()) { boost::filesystem::path wineDefaultRegistry(homePath); wineDefaultRegistry /= ".wine/system.reg"; diff --git a/components/files/linuxpath.hpp b/components/files/linuxpath.hpp index 6acf2a2d5f..b710165b44 100644 --- a/components/files/linuxpath.hpp +++ b/components/files/linuxpath.hpp @@ -20,44 +20,34 @@ struct LinuxPath /** * \brief Return path to the user directory. - * - * \return boost::filesystem::path */ - boost::filesystem::path getUserPath() const; + boost::filesystem::path getUserConfigPath() const; + + boost::filesystem::path getUserDataPath() const; /** - * \brief Return path to the global (system) directory where game files could be placed. - * - * \return boost::filesystem::path + * \brief Return path to the global (system) directory where config files can be placed. */ - boost::filesystem::path getGlobalPath() const; + boost::filesystem::path getGlobalConfigPath() const; /** * \brief Return path to the runtime configuration directory which is the * place where an application was started. - * - * \return boost::filesystem::path */ boost::filesystem::path getLocalPath() const; /** - * \brief - * - * \return boost::filesystem::path + * \brief Return path to the global (system) directory where game files can be placed. */ boost::filesystem::path getGlobalDataPath() const; /** * \brief - * - * \return boost::filesystem::path */ boost::filesystem::path getCachePath() const; /** * \brief Gets the path of the installed Morrowind version if there is one. - * - * \return boost::filesystem::path */ boost::filesystem::path getInstallPath() const; diff --git a/components/files/macospath.cpp b/components/files/macospath.cpp index 9edcd6ef2a..3e53f53061 100644 --- a/components/files/macospath.cpp +++ b/components/files/macospath.cpp @@ -11,9 +11,26 @@ * FIXME: Someone with MacOS system should check this and correct if necessary */ -/** - * \namespace Files - */ +namespace +{ + boost::filesystem::path getUserHome() + { + const char* dir = getenv("HOME"); + if (dir == NULL) + { + struct passwd* pwd = getpwuid(getuid()); + if (pwd != NULL) + { + dir = pwd->pw_dir; + } + } + if (dir == NULL) + return boost::filesystem::path(); + else + return boost::filesystem::path(dir); + } +} + namespace Files { @@ -22,28 +39,24 @@ MacOsPath::MacOsPath(const std::string& application_name) { } -boost::filesystem::path MacOsPath::getUserPath() const +boost::filesystem::path MacOsPath::getUserConfigPath() const { - boost::filesystem::path userPath("."); + boost::filesystem::path userPath (getUserHome()); + userPath /= "Library/Preferences/"; - const char* theDir = getenv("HOME"); - if (theDir == NULL) - { - struct passwd* pwd = getpwuid(getuid()); - if (pwd != NULL) - { - theDir = pwd->pw_dir; - } - } - if (theDir != NULL) - { - userPath = boost::filesystem::path(theDir) / "Library/Preferences/"; - } + return userPath / mName; +} + +boost::filesystem::path MacOsPath::getUserDataPath() const +{ + // TODO: probably wrong? + boost::filesystem::path userPath (getUserHome()); + userPath /= "Library/Preferences/"; return userPath / mName; } -boost::filesystem::path MacOsPath::getGlobalPath() const +boost::filesystem::path MacOsPath::getGlobalConfigPath() const { boost::filesystem::path globalPath("/Library/Preferences/"); return globalPath / mName; @@ -51,23 +64,9 @@ boost::filesystem::path MacOsPath::getGlobalPath() const boost::filesystem::path MacOsPath::getCachePath() const { - boost::filesystem::path userPath("."); - - const char* theDir = getenv("HOME"); - if (theDir == NULL) - { - struct passwd* pwd = getpwuid(getuid()); - if (pwd != NULL) - { - theDir = pwd->pw_dir; - } - } - if (theDir != NULL) - { - userPath = boost::filesystem::path(theDir) / "Library/Caches" / mName; - } - - return userPath; + boost::filesystem::path userPath (getUserHome()); + userPath /= "Library/Caches"; + return userPath / mName; } boost::filesystem::path MacOsPath::getLocalPath() const @@ -85,17 +84,9 @@ boost::filesystem::path MacOsPath::getInstallPath() const { boost::filesystem::path installPath; - char *homePath = getenv("HOME"); - if (homePath == NULL) - { - struct passwd* pwd = getpwuid(getuid()); - if (pwd != NULL) - { - homePath = pwd->pw_dir; - } - } + boost::filesystem::path homePath = getUserHome(); - if (homePath != NULL) + if (!homePath.empty()) { boost::filesystem::path wineDefaultRegistry(homePath); wineDefaultRegistry /= ".wine/system.reg"; diff --git a/components/files/macospath.hpp b/components/files/macospath.hpp index 576ec16812..7a7dc55778 100644 --- a/components/files/macospath.hpp +++ b/components/files/macospath.hpp @@ -23,14 +23,16 @@ struct MacOsPath * * \return boost::filesystem::path */ - boost::filesystem::path getUserPath() const; + boost::filesystem::path getUserConfigPath() const; + + boost::filesystem::path getUserDataPath() const; /** * \brief Return path to the global (system) directory. * * \return boost::filesystem::path */ - boost::filesystem::path getGlobalPath() const; + boost::filesystem::path getGlobalConfigPath() const; /** * \brief Return path to the runtime directory which is the diff --git a/components/files/windowspath.cpp b/components/files/windowspath.cpp index e8f1a2b08f..ea1ca56d3c 100644 --- a/components/files/windowspath.cpp +++ b/components/files/windowspath.cpp @@ -25,7 +25,7 @@ WindowsPath::WindowsPath(const std::string& application_name) { } -boost::filesystem::path WindowsPath::getUserPath() const +boost::filesystem::path WindowsPath::getUserConfigPath() const { boost::filesystem::path userPath("."); @@ -41,7 +41,13 @@ boost::filesystem::path WindowsPath::getUserPath() const return userPath / mName; } -boost::filesystem::path WindowsPath::getGlobalPath() const +boost::filesystem::path WindowsPath::getUserDataPath() const +{ + // Have some chaos, windows people! + return getUserConfigPath(); +} + +boost::filesystem::path WindowsPath::getGlobalConfigPath() const { boost::filesystem::path globalPath("."); @@ -63,12 +69,12 @@ boost::filesystem::path WindowsPath::getLocalPath() const boost::filesystem::path WindowsPath::getGlobalDataPath() const { - return getGlobalPath(); + return getGlobalConfigPath(); } boost::filesystem::path WindowsPath::getCachePath() const { - return getUserPath() / "cache"; + return getUserConfigPath() / "cache"; } boost::filesystem::path WindowsPath::getInstallPath() const diff --git a/components/files/windowspath.hpp b/components/files/windowspath.hpp index 6044b67c21..31d0e0e7c1 100644 --- a/components/files/windowspath.hpp +++ b/components/files/windowspath.hpp @@ -29,14 +29,16 @@ struct WindowsPath * * \return boost::filesystem::path */ - boost::filesystem::path getUserPath() const; + boost::filesystem::path getUserConfigPath() const; + + boost::filesystem::path getUserDataPath() const; /** * \brief Returns "X:\Program Files\" * * \return boost::filesystem::path */ - boost::filesystem::path getGlobalPath() const; + boost::filesystem::path getGlobalConfigPath() const; /** * \brief Return local path which is a location where diff --git a/components/interpreter/interpreter.cpp b/components/interpreter/interpreter.cpp index 10937e6bca..ea1e9fc912 100644 --- a/components/interpreter/interpreter.cpp +++ b/components/interpreter/interpreter.cpp @@ -166,31 +166,37 @@ namespace Interpreter void Interpreter::installSegment0 (int code, Opcode1 *opcode) { + assert(mSegment0.find(code) == mSegment0.end()); mSegment0.insert (std::make_pair (code, opcode)); } void Interpreter::installSegment1 (int code, Opcode2 *opcode) { + assert(mSegment1.find(code) == mSegment1.end()); mSegment1.insert (std::make_pair (code, opcode)); } void Interpreter::installSegment2 (int code, Opcode1 *opcode) { + assert(mSegment2.find(code) == mSegment2.end()); mSegment2.insert (std::make_pair (code, opcode)); } void Interpreter::installSegment3 (int code, Opcode1 *opcode) { + assert(mSegment3.find(code) == mSegment3.end()); mSegment3.insert (std::make_pair (code, opcode)); } void Interpreter::installSegment4 (int code, Opcode2 *opcode) { + assert(mSegment4.find(code) == mSegment4.end()); mSegment4.insert (std::make_pair (code, opcode)); } void Interpreter::installSegment5 (int code, Opcode0 *opcode) { + assert(mSegment5.find(code) == mSegment5.end()); mSegment5.insert (std::make_pair (code, opcode)); } diff --git a/components/misc/utf8stream.hpp b/components/misc/utf8stream.hpp index 19a0688b26..5e9dde2514 100644 --- a/components/misc/utf8stream.hpp +++ b/components/misc/utf8stream.hpp @@ -14,12 +14,12 @@ public: static UnicodeChar sBadChar () { return UnicodeChar (0xFFFFFFFF); } Utf8Stream (Point begin, Point end) : - cur (begin), nxt (begin), end (end) + cur (begin), nxt (begin), end (end), val(Utf8Stream::sBadChar()) { } Utf8Stream (std::pair range) : - cur (range.first), nxt (range.first), end (range.second) + cur (range.first), nxt (range.first), end (range.second), val(Utf8Stream::sBadChar()) { } diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index 011e0e4452..e44f4a6f30 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -306,7 +306,7 @@ public: class NiFlipController : public Controller { public: - int mTexSlot; + int mTexSlot; // NiTexturingProperty::TextureType float mDelta; // Time between two flips. delta = (start_time - stop_time) / num_sources NiSourceTextureList mSources; diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 402eadefb4..0f7e658fb8 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -210,7 +210,7 @@ static const RecordFactoryEntry recordFactories [] = { { "AvoidNode", &construct , RC_AvoidNode }, { "NiBSParticleNode", &construct , RC_NiBSParticleNode }, { "NiBSAnimationNode", &construct , RC_NiBSAnimationNode }, - { "NiBillboardNode", &construct , RC_NiNode }, + { "NiBillboardNode", &construct , RC_NiBillboardNode }, { "NiTriShape", &construct , RC_NiTriShape }, { "NiRotatingParticles", &construct , RC_NiRotatingParticles }, { "NiAutoNormalParticles", &construct , RC_NiAutoNormalParticles }, diff --git a/components/nif/niffile.hpp b/components/nif/niffile.hpp index 6e629772e6..91ae93b40b 100644 --- a/components/nif/niffile.hpp +++ b/components/nif/niffile.hpp @@ -200,7 +200,7 @@ struct KeyListT { } } else - nif->file->warn("Unhandled interpolation type: "+Ogre::StringConverter::toString(mInterpolationType)); + nif->file->fail("Unhandled interpolation type: "+Ogre::StringConverter::toString(mInterpolationType)); } }; typedef KeyListT FloatKeyList; diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 87e342dca5..079b335f05 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -36,6 +36,7 @@ enum RecordType { RC_MISSING = 0, RC_NiNode, + RC_NiBillboardNode, RC_AvoidNode, RC_NiTriShape, RC_NiRotatingParticles, diff --git a/components/nifogre/controller.hpp b/components/nifogre/controller.hpp new file mode 100644 index 0000000000..6d7f6ab3fb --- /dev/null +++ b/components/nifogre/controller.hpp @@ -0,0 +1,95 @@ +#ifndef COMPONENTS_NIFOGRE_CONTROLLER_H +#define COMPONENTS_NIFOGRE_CONTROLLER_H + +#include +#include + +namespace NifOgre +{ + + class ValueInterpolator + { + protected: + float interpKey(const Nif::FloatKeyList::VecType &keys, float time, float def=0.f) const + { + if (keys.size() == 0) + return def; + + if(time <= keys.front().mTime) + return keys.front().mValue; + + Nif::FloatKeyList::VecType::const_iterator iter(keys.begin()+1); + for(;iter != keys.end();iter++) + { + if(iter->mTime < time) + continue; + + Nif::FloatKeyList::VecType::const_iterator last(iter-1); + float a = (time-last->mTime) / (iter->mTime-last->mTime); + return last->mValue + ((iter->mValue - last->mValue)*a); + } + return keys.back().mValue; + } + + Ogre::Vector3 interpKey(const Nif::Vector3KeyList::VecType &keys, float time) const + { + if(time <= keys.front().mTime) + return keys.front().mValue; + + Nif::Vector3KeyList::VecType::const_iterator iter(keys.begin()+1); + for(;iter != keys.end();iter++) + { + if(iter->mTime < time) + continue; + + Nif::Vector3KeyList::VecType::const_iterator last(iter-1); + float a = (time-last->mTime) / (iter->mTime-last->mTime); + return last->mValue + ((iter->mValue - last->mValue)*a); + } + return keys.back().mValue; + } + }; + + // FIXME: Should not be here. + class DefaultFunction : public Ogre::ControllerFunction + { + private: + float mFrequency; + float mPhase; + float mStartTime; + public: + float mStopTime; + + public: + DefaultFunction(const Nif::Controller *ctrl, bool deltaInput) + : Ogre::ControllerFunction(deltaInput) + , mFrequency(ctrl->frequency) + , mPhase(ctrl->phase) + , mStartTime(ctrl->timeStart) + , mStopTime(ctrl->timeStop) + { + if(mDeltaInput) + mDeltaCount = mPhase; + } + + virtual Ogre::Real calculate(Ogre::Real value) + { + if(mDeltaInput) + { + mDeltaCount += value*mFrequency; + if(mDeltaCount < mStartTime) + mDeltaCount = mStopTime - std::fmod(mStartTime - mDeltaCount, + mStopTime - mStartTime); + mDeltaCount = std::fmod(mDeltaCount - mStartTime, + mStopTime - mStartTime) + mStartTime; + return mDeltaCount; + } + + value = std::min(mStopTime, std::max(mStartTime, value+mPhase)); + return value; + } + }; + +} + +#endif diff --git a/components/nifogre/material.cpp b/components/nifogre/material.cpp index 8398dbc2ed..be6ccbed64 100644 --- a/components/nifogre/material.cpp +++ b/components/nifogre/material.cpp @@ -152,11 +152,11 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata, Nif::ControllerPtr ctrls = texprop->controller; while(!ctrls.empty()) { - warn("Unhandled texture controller "+ctrls->recName+" in "+name); + if (ctrls->recType != Nif::RC_NiFlipController) // Handled in ogrenifloader + warn("Unhandled texture controller "+ctrls->recName+" in "+name); ctrls = ctrls->next; } } - needTangents = !texName[Nif::NiTexturingProperty::BumpTexture].empty(); // Alpha modifiers if(alphaprop) @@ -237,7 +237,8 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata, Nif::ControllerPtr ctrls = matprop->controller; while(!ctrls.empty()) { - warn("Unhandled material controller "+ctrls->recName+" in "+name); + if (ctrls->recType != Nif::RC_NiAlphaController && ctrls->recType != Nif::RC_NiMaterialColorController) + warn("Unhandled material controller "+ctrls->recName+" in "+name); ctrls = ctrls->next; } } @@ -323,6 +324,12 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata, instance->setProperty("normalMap", sh::makeProperty(texName[Nif::NiTexturingProperty::BumpTexture])); instance->setProperty("detailMap", sh::makeProperty(texName[Nif::NiTexturingProperty::DetailTexture])); instance->setProperty("emissiveMap", sh::makeProperty(texName[Nif::NiTexturingProperty::GlowTexture])); + instance->setProperty("darkMap", sh::makeProperty(texName[Nif::NiTexturingProperty::DarkTexture])); + if (!texName[Nif::NiTexturingProperty::BaseTexture].empty()) + { + instance->setProperty("use_diffuse_map", sh::makeProperty(new sh::BooleanValue(true))); + instance->setProperty("diffuseMapUVSet", sh::makeProperty(new sh::IntValue(texprop->textures[Nif::NiTexturingProperty::BaseTexture].uvSet))); + } if (!texName[Nif::NiTexturingProperty::GlowTexture].empty()) { instance->setProperty("use_emissive_map", sh::makeProperty(new sh::BooleanValue(true))); @@ -333,6 +340,11 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata, instance->setProperty("use_detail_map", sh::makeProperty(new sh::BooleanValue(true))); instance->setProperty("detailMapUVSet", sh::makeProperty(new sh::IntValue(texprop->textures[Nif::NiTexturingProperty::DetailTexture].uvSet))); } + if (!texName[Nif::NiTexturingProperty::DarkTexture].empty()) + { + instance->setProperty("use_dark_map", sh::makeProperty(new sh::BooleanValue(true))); + instance->setProperty("darkMapUVSet", sh::makeProperty(new sh::IntValue(texprop->textures[Nif::NiTexturingProperty::DarkTexture].uvSet))); + } bool useParallax = !texName[Nif::NiTexturingProperty::BumpTexture].empty() && texName[Nif::NiTexturingProperty::BumpTexture].find("_nh.") != std::string::npos; @@ -342,25 +354,30 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata, { if(i == Nif::NiTexturingProperty::BaseTexture || i == Nif::NiTexturingProperty::DetailTexture || + i == Nif::NiTexturingProperty::DarkTexture || i == Nif::NiTexturingProperty::BumpTexture || i == Nif::NiTexturingProperty::GlowTexture) continue; if(!texName[i].empty()) - warn("Ignored texture "+texName[i]+" on layer "+Ogre::StringConverter::toString(i)); + warn("Ignored texture "+texName[i]+" on layer "+Ogre::StringConverter::toString(i) + " in " + name); } if (vertexColour) instance->setProperty("has_vertex_colour", sh::makeProperty(new sh::BooleanValue(true))); - // Add transparency if NiAlphaProperty was present - NifOverrides::TransparencyResult result = NifOverrides::Overrides::getTransparencyOverride(texName[0]); - if (result.first) + // Override alpha flags based on our override list (transparency-overrides.cfg) + if (!texName[0].empty()) { - alphaFlags = (1<<9) | (6<<10); /* alpha_rejection enabled, greater_equal */ - alphaTest = result.second; - depthFlags = (1<<0) | (1<<1); // depth_write on, depth_check on + NifOverrides::TransparencyResult result = NifOverrides::Overrides::getTransparencyOverride(texName[0]); + if (result.first) + { + alphaFlags = (1<<9) | (6<<10); /* alpha_rejection enabled, greater_equal */ + alphaTest = result.second; + depthFlags = (1<<0) | (1<<1); // depth_write on, depth_check on + } } + // Add transparency if NiAlphaProperty was present if((alphaFlags&1)) { std::string blend_mode; @@ -389,7 +406,12 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata, instance->setProperty("depth_write", sh::makeProperty(new sh::StringValue(((depthFlags>>1)&1) ? "on" : "off"))); // depth_func??? - sh::Factory::getInstance()._ensureMaterial(name, "Default"); + if (!texName[0].empty()) + NifOverrides::Overrides::getMaterialOverrides(texName[0], instance); + + // Don't use texName, as it may be overridden + needTangents = !sh::retrieveValue(instance->getProperty("normalMap"), instance).get().empty(); + return name; } diff --git a/components/nifogre/material.hpp b/components/nifogre/material.hpp index 8843ac6c6c..b02c7c2366 100644 --- a/components/nifogre/material.hpp +++ b/components/nifogre/material.hpp @@ -37,9 +37,9 @@ class NIFMaterialLoader { static std::map sMaterialMap; +public: static std::string findTextureName(const std::string &filename); -public: static Ogre::String getMaterial(const Nif::ShapeData *shapedata, const Ogre::String &name, const Ogre::String &group, const Nif::NiTexturingProperty *texprop, diff --git a/components/nifogre/mesh.cpp b/components/nifogre/mesh.cpp index ca92f62d49..80e377a49f 100644 --- a/components/nifogre/mesh.cpp +++ b/components/nifogre/mesh.cpp @@ -116,19 +116,6 @@ void NIFMeshLoader::createSubMesh(Ogre::Mesh *mesh, const Nif::NiTriShape *shape Ogre::HardwareBuffer::Usage vertUsage = Ogre::HardwareBuffer::HBU_STATIC; bool vertShadowBuffer = false; - if(!shape->controller.empty()) - { - Nif::ControllerPtr ctrl = shape->controller; - do { - if(ctrl->recType == Nif::RC_NiGeomMorpherController) - { - vertUsage = Ogre::HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY; - vertShadowBuffer = true; - break; - } - } while(!(ctrl=ctrl->next).empty()); - } - if(skin != NULL) { vertUsage = Ogre::HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY; @@ -347,6 +334,40 @@ void NIFMeshLoader::createSubMesh(Ogre::Mesh *mesh, const Nif::NiTriShape *shape if (!mesh->suggestTangentVectorBuildParams(Ogre::VES_TANGENT, src,dest)) mesh->buildTangentVectors(Ogre::VES_TANGENT, src,dest); } + + + if(!shape->controller.empty()) + { + Nif::ControllerPtr ctrl = shape->controller; + do { + // Load GeomMorpherController into an Ogre::Pose and Animation + if(ctrl->recType == Nif::RC_NiGeomMorpherController) + { + const Nif::NiGeomMorpherController *geom = + static_cast(ctrl.getPtr()); + + const std::vector& morphs = geom->data.getPtr()->mMorphs; + // Note we are not interested in morph 0, which just contains the original vertices + for (unsigned int i = 1; i < morphs.size(); ++i) + { + Ogre::Pose* pose = mesh->createPose(i); + const Nif::NiMorphData::MorphData& data = morphs[i]; + for (unsigned int v = 0; v < data.mVertices.size(); ++v) + pose->addVertex(v, data.mVertices[v]); + + Ogre::String animationID = Ogre::StringConverter::toString(ctrl->recIndex) + + "_" + Ogre::StringConverter::toString(i); + Ogre::VertexAnimationTrack* track = + mesh->createAnimation(animationID, 0) + ->createVertexTrack(1, Ogre::VAT_POSE); + Ogre::VertexPoseKeyFrame* keyframe = track->createVertexPoseKeyFrame(0); + keyframe->addPoseReference(i-1, 1); + } + + break; + } + } while(!(ctrl=ctrl->next).empty()); + } } diff --git a/components/nifogre/ogrenifloader.cpp b/components/nifogre/ogrenifloader.cpp index a530d060d2..63e9057664 100644 --- a/components/nifogre/ogrenifloader.cpp +++ b/components/nifogre/ogrenifloader.cpp @@ -37,54 +37,253 @@ #include #include +#include + #include #include #include "skeleton.hpp" #include "material.hpp" #include "mesh.hpp" +#include "controller.hpp" namespace NifOgre { -// FIXME: Should not be here. -class DefaultFunction : public Ogre::ControllerFunction +Ogre::MaterialPtr MaterialControllerManager::getWritableMaterial(Ogre::MovableObject *movable) +{ + if (mClonedMaterials.find(movable) != mClonedMaterials.end()) + return mClonedMaterials[movable]; + + else + { + Ogre::MaterialPtr mat; + if (Ogre::Entity* ent = dynamic_cast(movable)) + mat = ent->getSubEntity(0)->getMaterial(); + else if (Ogre::ParticleSystem* partSys = dynamic_cast(movable)) + mat = Ogre::MaterialManager::getSingleton().getByName(partSys->getMaterialName()); + + static int count=0; + Ogre::String newName = mat->getName() + Ogre::StringConverter::toString(count++); + sh::Factory::getInstance().createMaterialInstance(newName, mat->getName()); + // Make sure techniques are created + sh::Factory::getInstance()._ensureMaterial(newName, "Default"); + mat = Ogre::MaterialManager::getSingleton().getByName(newName); + + mClonedMaterials[movable] = mat; + + if (Ogre::Entity* ent = dynamic_cast(movable)) + ent->getSubEntity(0)->setMaterial(mat); + else if (Ogre::ParticleSystem* partSys = dynamic_cast(movable)) + partSys->setMaterialName(mat->getName()); + + return mat; + } +} + +MaterialControllerManager::~MaterialControllerManager() +{ + for (std::map::iterator it = mClonedMaterials.begin(); it != mClonedMaterials.end(); ++it) + { + sh::Factory::getInstance().destroyMaterialInstance(it->second->getName()); + } +} + +ObjectScene::~ObjectScene() +{ + for(size_t i = 0;i < mLights.size();i++) + { + Ogre::Light *light = mLights[i]; + // If parent is a scene node, it was created specifically for this light. Destroy it now. + if(light->isAttached() && !light->isParentTagPoint()) + mSceneMgr->destroySceneNode(light->getParentSceneNode()); + mSceneMgr->destroyLight(light); + } + for(size_t i = 0;i < mParticles.size();i++) + mSceneMgr->destroyParticleSystem(mParticles[i]); + for(size_t i = 0;i < mEntities.size();i++) + mSceneMgr->destroyEntity(mEntities[i]); + mControllers.clear(); + mLights.clear(); + mParticles.clear(); + mEntities.clear(); + mSkelBase = NULL; +} + +void ObjectScene::rotateBillboardNodes(Ogre::Camera *camera) +{ + for (std::vector::iterator it = mBillboardNodes.begin(); it != mBillboardNodes.end(); ++it) + { + assert(mSkelBase); + Ogre::Node* node = *it; + node->_setDerivedOrientation(mSkelBase->getParentNode()->_getDerivedOrientation().Inverse() * + camera->getRealOrientation()); + } +} + +// Animates a texture +class FlipController { -private: - float mFrequency; - float mPhase; - float mStartTime; public: - float mStopTime; + class Value : public Ogre::ControllerValue + { + private: + Ogre::MovableObject* mMovable; + int mTexSlot; + float mDelta; + std::vector mTextures; + MaterialControllerManager* mMaterialControllerMgr; + + public: + Value(Ogre::MovableObject *movable, const Nif::NiFlipController *ctrl, MaterialControllerManager* materialControllerMgr) + : mMovable(movable) + , mMaterialControllerMgr(materialControllerMgr) + { + mTexSlot = ctrl->mTexSlot; + mDelta = ctrl->mDelta; + for (unsigned int i=0; imSources.length(); ++i) + { + const Nif::NiSourceTexture* tex = ctrl->mSources[i].getPtr(); + if (!tex->external) + std::cerr << "Warning: Found internal texture, ignoring." << std::endl; + mTextures.push_back(NIFMaterialLoader::findTextureName(tex->filename)); + } + } + + virtual Ogre::Real getValue() const + { + // Should not be called + return 0.0f; + } + + virtual void setValue(Ogre::Real time) + { + if (mDelta == 0) + return; + int curTexture = int(time / mDelta) % mTextures.size(); + + Ogre::MaterialPtr mat = mMaterialControllerMgr->getWritableMaterial(mMovable); + Ogre::Material::TechniqueIterator techs = mat->getTechniqueIterator(); + while(techs.hasMoreElements()) + { + Ogre::Technique *tech = techs.getNext(); + Ogre::Technique::PassIterator passes = tech->getPassIterator(); + while(passes.hasMoreElements()) + { + Ogre::Pass *pass = passes.getNext(); + Ogre::Pass::TextureUnitStateIterator textures = pass->getTextureUnitStateIterator(); + while (textures.hasMoreElements()) + { + Ogre::TextureUnitState *texture = textures.getNext(); + if ((texture->getName() == "diffuseMap" && mTexSlot == Nif::NiTexturingProperty::BaseTexture) + || (texture->getName() == "normalMap" && mTexSlot == Nif::NiTexturingProperty::BumpTexture) + || (texture->getName() == "detailMap" && mTexSlot == Nif::NiTexturingProperty::DetailTexture) + || (texture->getName() == "darkMap" && mTexSlot == Nif::NiTexturingProperty::DarkTexture) + || (texture->getName() == "emissiveMap" && mTexSlot == Nif::NiTexturingProperty::GlowTexture)) + texture->setTextureName(mTextures[curTexture]); + } + } + } + } + }; + + typedef DefaultFunction Function; +}; +class AlphaController +{ public: - DefaultFunction(const Nif::Controller *ctrl, bool deltaInput) - : Ogre::ControllerFunction(deltaInput) - , mFrequency(ctrl->frequency) - , mPhase(ctrl->phase) - , mStartTime(ctrl->timeStart) - , mStopTime(ctrl->timeStop) + class Value : public Ogre::ControllerValue, public ValueInterpolator { - if(mDeltaInput) - mDeltaCount = mPhase; - } + private: + Ogre::MovableObject* mMovable; + Nif::FloatKeyList mData; + MaterialControllerManager* mMaterialControllerMgr; + + public: + Value(Ogre::MovableObject *movable, const Nif::NiFloatData *data, MaterialControllerManager* materialControllerMgr) + : mMovable(movable) + , mData(data->mKeyList) + , mMaterialControllerMgr(materialControllerMgr) + { + } + + virtual Ogre::Real getValue() const + { + // Should not be called + return 0.0f; + } + + virtual void setValue(Ogre::Real time) + { + float value = interpKey(mData.mKeys, time); + Ogre::MaterialPtr mat = mMaterialControllerMgr->getWritableMaterial(mMovable); + Ogre::Material::TechniqueIterator techs = mat->getTechniqueIterator(); + while(techs.hasMoreElements()) + { + Ogre::Technique *tech = techs.getNext(); + Ogre::Technique::PassIterator passes = tech->getPassIterator(); + while(passes.hasMoreElements()) + { + Ogre::Pass *pass = passes.getNext(); + Ogre::ColourValue diffuse = pass->getDiffuse(); + diffuse.a = value; + pass->setDiffuse(diffuse); + } + } + } + }; - virtual Ogre::Real calculate(Ogre::Real value) + typedef DefaultFunction Function; +}; + +class MaterialColorController +{ +public: + class Value : public Ogre::ControllerValue, public ValueInterpolator { - if(mDeltaInput) + private: + Ogre::MovableObject* mMovable; + Nif::Vector3KeyList mData; + MaterialControllerManager* mMaterialControllerMgr; + + public: + Value(Ogre::MovableObject *movable, const Nif::NiPosData *data, MaterialControllerManager* materialControllerMgr) + : mMovable(movable) + , mData(data->mKeyList) + , mMaterialControllerMgr(materialControllerMgr) { - mDeltaCount += value*mFrequency; - if(mDeltaCount < mStartTime) - mDeltaCount = mStopTime - std::fmod(mStartTime - mDeltaCount, - mStopTime - mStartTime); - mDeltaCount = std::fmod(mDeltaCount - mStartTime, - mStopTime - mStartTime) + mStartTime; - return mDeltaCount; } - value = std::min(mStopTime, std::max(mStartTime, value+mPhase)); - return value; - } + virtual Ogre::Real getValue() const + { + // Should not be called + return 0.0f; + } + + virtual void setValue(Ogre::Real time) + { + Ogre::Vector3 value = interpKey(mData.mKeys, time); + Ogre::MaterialPtr mat = mMaterialControllerMgr->getWritableMaterial(mMovable); + Ogre::Material::TechniqueIterator techs = mat->getTechniqueIterator(); + while(techs.hasMoreElements()) + { + Ogre::Technique *tech = techs.getNext(); + Ogre::Technique::PassIterator passes = tech->getPassIterator(); + while(passes.hasMoreElements()) + { + Ogre::Pass *pass = passes.getNext(); + Ogre::ColourValue diffuse = pass->getDiffuse(); + diffuse.r = value.x; + diffuse.g = value.y; + diffuse.b = value.z; + pass->setDiffuse(diffuse); + } + } + } + }; + + typedef DefaultFunction Function; }; class VisController @@ -108,9 +307,6 @@ public: return mData.back().isSet; } - // FIXME: We are not getting all objects here. Skinned meshes get - // attached to the object's root node, and won't be connected via a - // TagPoint. static void setVisible(Ogre::Node *node, int vis) { Ogre::Node::ChildNodeIterator iter = node->getChildIterator(); @@ -119,6 +315,12 @@ public: node = iter.getNext(); setVisible(node, vis); + // Skinned meshes and particle systems are attached to the scene node, not the bone. + // We use the Node's user data to connect it with the mesh / particle system. + Ogre::Any customData = node->getUserObjectBindings().getUserAny(); + if (!customData.isEmpty()) + Ogre::any_cast(customData)->setVisible(vis); + Ogre::TagPoint *tag = dynamic_cast(node); if(tag != NULL) { @@ -163,48 +365,14 @@ public: class KeyframeController { public: - class Value : public NodeTargetValue + class Value : public NodeTargetValue, public ValueInterpolator { private: Nif::QuaternionKeyList mRotations; Nif::Vector3KeyList mTranslations; Nif::FloatKeyList mScales; - static float interpKey(const Nif::FloatKeyList::VecType &keys, float time) - { - if(time <= keys.front().mTime) - return keys.front().mValue; - - Nif::FloatKeyList::VecType::const_iterator iter(keys.begin()+1); - for(;iter != keys.end();iter++) - { - if(iter->mTime < time) - continue; - - Nif::FloatKeyList::VecType::const_iterator last(iter-1); - float a = (time-last->mTime) / (iter->mTime-last->mTime); - return last->mValue + ((iter->mValue - last->mValue)*a); - } - return keys.back().mValue; - } - - static Ogre::Vector3 interpKey(const Nif::Vector3KeyList::VecType &keys, float time) - { - if(time <= keys.front().mTime) - return keys.front().mValue; - - Nif::Vector3KeyList::VecType::const_iterator iter(keys.begin()+1); - for(;iter != keys.end();iter++) - { - if(iter->mTime < time) - continue; - - Nif::Vector3KeyList::VecType::const_iterator last(iter-1); - float a = (time-last->mTime) / (iter->mTime-last->mTime); - return last->mValue + ((iter->mValue - last->mValue)*a); - } - return keys.back().mValue; - } + using ValueInterpolator::interpKey; static Ogre::Quaternion interpKey(const Nif::QuaternionKeyList::VecType &keys, float time) { @@ -276,43 +444,24 @@ public: class UVController { public: - class Value : public Ogre::ControllerValue + class Value : public Ogre::ControllerValue, public ValueInterpolator { private: - Ogre::MaterialPtr mMaterial; + Ogre::MovableObject* mMovable; Nif::FloatKeyList mUTrans; Nif::FloatKeyList mVTrans; Nif::FloatKeyList mUScale; Nif::FloatKeyList mVScale; - - static float lookupValue(const Nif::FloatKeyList &keys, float time, float def) - { - if(keys.mKeys.size() == 0) - return def; - - if(time <= keys.mKeys.front().mTime) - return keys.mKeys.front().mValue; - - Nif::FloatKeyList::VecType::const_iterator iter(keys.mKeys.begin()+1); - for(;iter != keys.mKeys.end();iter++) - { - if(iter->mTime < time) - continue; - - Nif::FloatKeyList::VecType::const_iterator last(iter-1); - float a = (time-last->mTime) / (iter->mTime-last->mTime); - return last->mValue + ((iter->mValue - last->mValue)*a); - } - return keys.mKeys.back().mValue; - } + MaterialControllerManager* mMaterialControllerMgr; public: - Value(const Ogre::MaterialPtr &material, const Nif::NiUVData *data) - : mMaterial(material) + Value(Ogre::MovableObject* movable, const Nif::NiUVData *data, MaterialControllerManager* materialControllerMgr) + : mMovable(movable) , mUTrans(data->mKeyList[0]) , mVTrans(data->mKeyList[1]) , mUScale(data->mKeyList[2]) , mVScale(data->mKeyList[3]) + , mMaterialControllerMgr(materialControllerMgr) { } virtual Ogre::Real getValue() const @@ -323,12 +472,14 @@ public: virtual void setValue(Ogre::Real value) { - float uTrans = lookupValue(mUTrans, value, 0.0f); - float vTrans = lookupValue(mVTrans, value, 0.0f); - float uScale = lookupValue(mUScale, value, 1.0f); - float vScale = lookupValue(mVScale, value, 1.0f); + float uTrans = interpKey(mUTrans.mKeys, value, 0.0f); + float vTrans = interpKey(mVTrans.mKeys, value, 0.0f); + float uScale = interpKey(mUScale.mKeys, value, 1.0f); + float vScale = interpKey(mVScale.mKeys, value, 1.0f); + + Ogre::MaterialPtr material = mMaterialControllerMgr->getWritableMaterial(mMovable); - Ogre::Material::TechniqueIterator techs = mMaterial->getTechniqueIterator(); + Ogre::Material::TechniqueIterator techs = material->getTechniqueIterator(); while(techs.hasMoreElements()) { Ogre::Technique *tech = techs.getNext(); @@ -380,17 +531,22 @@ public: class GeomMorpherController { public: - class Value : public Ogre::ControllerValue + class Value : public Ogre::ControllerValue, public ValueInterpolator { private: - Ogre::SubEntity *mSubEntity; + Ogre::Entity *mEntity; std::vector mMorphs; + size_t mControllerIndex; + + std::vector mVertices; public: - Value(Ogre::SubEntity *subent, const Nif::NiMorphData *data) - : mSubEntity(subent) + Value(Ogre::Entity *ent, const Nif::NiMorphData *data, size_t controllerIndex) + : mEntity(ent) , mMorphs(data->mMorphs) - { } + , mControllerIndex(controllerIndex) + { + } virtual Ogre::Real getValue() const { @@ -398,9 +554,25 @@ public: return 0.0f; } - virtual void setValue(Ogre::Real value) + virtual void setValue(Ogre::Real time) { - // TODO: Implement + if (mMorphs.size() <= 1) + return; + int i = 1; + for (std::vector::iterator it = mMorphs.begin()+1; it != mMorphs.end(); ++it,++i) + { + float val = 0; + if (!it->mData.mKeys.empty()) + val = interpKey(it->mData.mKeys, time); + val = std::max(0.f, std::min(1.f, val)); + + Ogre::String animationID = Ogre::StringConverter::toString(mControllerIndex) + + "_" + Ogre::StringConverter::toString(i); + + Ogre::AnimationState* state = mEntity->getAnimationState(animationID); + state->setEnabled(val > 0); + state->setWeight(val); + } } }; @@ -419,15 +591,8 @@ class NIFObjectLoader std::cerr << "NIFObjectLoader: Warn: " << msg << std::endl; } - static void fail(const std::string &msg) - { - std::cerr << "NIFObjectLoader: Fail: "<< msg << std::endl; - abort(); - } - - static void createEntity(const std::string &name, const std::string &group, - Ogre::SceneManager *sceneMgr, ObjectList &objectlist, + Ogre::SceneManager *sceneMgr, ObjectScenePtr scene, const Nif::Node *node, int flags, int animflags) { const Nif::NiTriShape *shape = static_cast(node); @@ -442,18 +607,27 @@ class NIFObjectLoader NIFMeshLoader::createMesh(name, fullname, group, shape->recIndex); Ogre::Entity *entity = sceneMgr->createEntity(fullname); + +#if OGRE_VERSION >= (1 << 16 | 10 << 8 | 0) + // Enable skeleton-based bounding boxes. With the static bounding box, + // the animation may cause parts to go outside the box and cause culling problems. + if (entity->hasSkeleton()) + entity->setUpdateBoundingBoxFromSkeleton(true); +#endif + entity->setVisible(!(flags&Nif::NiNode::Flag_Hidden)); - objectlist.mEntities.push_back(entity); - if(objectlist.mSkelBase) + scene->mEntities.push_back(entity); + if(scene->mSkelBase) { if(entity->hasSkeleton()) - entity->shareSkeletonInstanceWith(objectlist.mSkelBase); + entity->shareSkeletonInstanceWith(scene->mSkelBase); else { int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, shape->recIndex); - Ogre::Bone *trgtbone = objectlist.mSkelBase->getSkeleton()->getBone(trgtid); - objectlist.mSkelBase->attachObjectToBone(trgtbone->getName(), entity); + Ogre::Bone *trgtbone = scene->mSkelBase->getSkeleton()->getBone(trgtid); + trgtbone->getUserObjectBindings().setUserAny(Ogre::Any(static_cast(entity))); + scene->mSkelBase->attachObjectToBone(trgtbone->getName(), entity); } } @@ -464,38 +638,103 @@ class NIFObjectLoader { const Nif::NiUVController *uv = static_cast(ctrl.getPtr()); - const Ogre::MaterialPtr &material = entity->getSubEntity(0)->getMaterial(); Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ? Ogre::ControllerManager::getSingleton().getFrameTimeSource() : Ogre::ControllerValueRealPtr()); - Ogre::ControllerValueRealPtr dstval(OGRE_NEW UVController::Value(material, uv->data.getPtr())); + Ogre::ControllerValueRealPtr dstval(OGRE_NEW UVController::Value(entity, uv->data.getPtr(), &scene->mMaterialControllerMgr)); UVController::Function* function = OGRE_NEW UVController::Function(uv, (animflags&Nif::NiNode::AnimFlag_AutoPlay)); - objectlist.mMaxControllerLength = std::max(function->mStopTime, objectlist.mMaxControllerLength); + scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); Ogre::ControllerFunctionRealPtr func(function); - objectlist.mControllers.push_back(Ogre::Controller(srcval, dstval, func)); + scene->mControllers.push_back(Ogre::Controller(srcval, dstval, func)); } else if(ctrl->recType == Nif::RC_NiGeomMorpherController) { const Nif::NiGeomMorpherController *geom = static_cast(ctrl.getPtr()); - Ogre::SubEntity *subent = entity->getSubEntity(0); Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ? Ogre::ControllerManager::getSingleton().getFrameTimeSource() : Ogre::ControllerValueRealPtr()); - Ogre::ControllerValueRealPtr dstval(OGRE_NEW GeomMorpherController::Value(subent, geom->data.getPtr())); + Ogre::ControllerValueRealPtr dstval(OGRE_NEW GeomMorpherController::Value( + entity, geom->data.getPtr(), geom->recIndex)); GeomMorpherController::Function* function = OGRE_NEW GeomMorpherController::Function(geom, (animflags&Nif::NiNode::AnimFlag_AutoPlay)); - objectlist.mMaxControllerLength = std::max(function->mStopTime, objectlist.mMaxControllerLength); + scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); Ogre::ControllerFunctionRealPtr func(function); - objectlist.mControllers.push_back(Ogre::Controller(srcval, dstval, func)); + scene->mControllers.push_back(Ogre::Controller(srcval, dstval, func)); } ctrl = ctrl->next; } + + createMaterialControllers(shape, entity, animflags, scene); } + static void createMaterialControllers (const Nif::Node* node, Ogre::MovableObject* movable, int animflags, ObjectScenePtr scene) + { + const Nif::NiTexturingProperty *texprop = NULL; + const Nif::NiMaterialProperty *matprop = NULL; + const Nif::NiAlphaProperty *alphaprop = NULL; + const Nif::NiVertexColorProperty *vertprop = NULL; + const Nif::NiZBufferProperty *zprop = NULL; + const Nif::NiSpecularProperty *specprop = NULL; + const Nif::NiWireframeProperty *wireprop = NULL; + node->getProperties(texprop, matprop, alphaprop, vertprop, zprop, specprop, wireprop); + + Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ? + Ogre::ControllerManager::getSingleton().getFrameTimeSource() : + Ogre::ControllerValueRealPtr()); + + if(matprop) + { + Nif::ControllerPtr ctrls = matprop->controller; + while(!ctrls.empty()) + { + if (ctrls->recType == Nif::RC_NiAlphaController) + { + const Nif::NiAlphaController *alphaCtrl = dynamic_cast(ctrls.getPtr()); + Ogre::ControllerValueRealPtr dstval(OGRE_NEW AlphaController::Value(movable, alphaCtrl->data.getPtr(), &scene->mMaterialControllerMgr)); + AlphaController::Function* function = OGRE_NEW AlphaController::Function(alphaCtrl, (animflags&Nif::NiNode::AnimFlag_AutoPlay)); + scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); + Ogre::ControllerFunctionRealPtr func(function); + scene->mControllers.push_back(Ogre::Controller(srcval, dstval, func)); + } + else if (ctrls->recType == Nif::RC_NiMaterialColorController) + { + const Nif::NiMaterialColorController *matCtrl = dynamic_cast(ctrls.getPtr()); + Ogre::ControllerValueRealPtr dstval(OGRE_NEW MaterialColorController::Value(movable, matCtrl->data.getPtr(), &scene->mMaterialControllerMgr)); + MaterialColorController::Function* function = OGRE_NEW MaterialColorController::Function(matCtrl, (animflags&Nif::NiNode::AnimFlag_AutoPlay)); + scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); + Ogre::ControllerFunctionRealPtr func(function); + scene->mControllers.push_back(Ogre::Controller(srcval, dstval, func)); + } + + ctrls = ctrls->next; + } + } + if (texprop) + { + Nif::ControllerPtr ctrls = texprop->controller; + while(!ctrls.empty()) + { + if (ctrls->recType == Nif::RC_NiFlipController) + { + const Nif::NiFlipController *flipCtrl = dynamic_cast(ctrls.getPtr()); + + + Ogre::ControllerValueRealPtr dstval(OGRE_NEW FlipController::Value( + movable, flipCtrl, &scene->mMaterialControllerMgr)); + FlipController::Function* function = OGRE_NEW FlipController::Function(flipCtrl, (animflags&Nif::NiNode::AnimFlag_AutoPlay)); + scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); + Ogre::ControllerFunctionRealPtr func(function); + scene->mControllers.push_back(Ogre::Controller(srcval, dstval, func)); + } + + ctrls = ctrls->next; + } + } + } static void createParticleEmitterAffectors(Ogre::ParticleSystem *partsys, const Nif::NiParticleSystemController *partctrl, Ogre::Bone* bone, @@ -514,8 +753,6 @@ class NIFObjectLoader emitter->setParameter("vertical_angle", Ogre::StringConverter::toString(Ogre::Radian(partctrl->verticalAngle).valueDegrees())); emitter->setParameter("horizontal_direction", Ogre::StringConverter::toString(Ogre::Radian(partctrl->horizontalDir).valueDegrees())); emitter->setParameter("horizontal_angle", Ogre::StringConverter::toString(Ogre::Radian(partctrl->horizontalAngle).valueDegrees())); - emitter->setParameter("skelbase", skelBaseName); - emitter->setParameter("bone", bone->getName()); Nif::ExtraPtr e = partctrl->extra; while(!e.empty()) @@ -571,8 +808,8 @@ class NIFObjectLoader } static void createParticleSystem(const std::string &name, const std::string &group, - Ogre::SceneNode *sceneNode, ObjectList &objectlist, - const Nif::Node *partnode, int flags, int partflags) + Ogre::SceneNode *sceneNode, ObjectScenePtr scene, + const Nif::Node *partnode, int flags, int partflags, int animflags) { const Nif::NiAutoNormalParticlesData *particledata = NULL; if(partnode->recType == Nif::RC_NiAutoNormalParticles) @@ -602,13 +839,13 @@ class NIFObjectLoader vertprop, zprop, specprop, wireprop, needTangents)); - partsys->setDefaultDimensions(particledata->particleRadius*2.0f, - particledata->particleRadius*2.0f); partsys->setCullIndividually(false); partsys->setParticleQuota(particledata->numParticles); partsys->setKeepParticlesInLocalSpace(partflags & (Nif::NiNode::ParticleFlag_LocalSpace)); - sceneNode->attachObject(partsys); + int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, partnode->recIndex); + Ogre::Bone *trgtbone = scene->mSkelBase->getSkeleton()->getBone(trgtid); + scene->mSkelBase->attachObjectToBone(trgtbone->getName(), partsys); Nif::ControllerPtr ctrl = partnode->controller; while(!ctrl.empty()) @@ -617,11 +854,16 @@ class NIFObjectLoader { const Nif::NiParticleSystemController *partctrl = static_cast(ctrl.getPtr()); + partsys->setDefaultDimensions(partctrl->size*2, partctrl->size*2); + if(!partctrl->emitter.empty()) { int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, partctrl->emitter->recIndex); - Ogre::Bone *trgtbone = objectlist.mSkelBase->getSkeleton()->getBone(trgtid); - createParticleEmitterAffectors(partsys, partctrl, trgtbone, objectlist.mSkelBase->getName()); + Ogre::Bone *trgtbone = scene->mSkelBase->getSkeleton()->getBone(trgtid); + // Set the emitter bone as user data on the particle system + // so the emitters/affectors can access it easily. + partsys->getUserObjectBindings().setUserAny(Ogre::Any(trgtbone)); + createParticleEmitterAffectors(partsys, partctrl, trgtbone, scene->mSkelBase->getName()); } Ogre::ControllerValueRealPtr srcval((partflags&Nif::NiNode::ParticleFlag_AutoPlay) ? @@ -631,20 +873,22 @@ class NIFObjectLoader ParticleSystemController::Function* function = OGRE_NEW ParticleSystemController::Function(partctrl, (partflags&Nif::NiNode::ParticleFlag_AutoPlay)); - objectlist.mMaxControllerLength = std::max(function->mStopTime, objectlist.mMaxControllerLength); + scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); Ogre::ControllerFunctionRealPtr func(function); - objectlist.mControllers.push_back(Ogre::Controller(srcval, dstval, func)); + scene->mControllers.push_back(Ogre::Controller(srcval, dstval, func)); } ctrl = ctrl->next; } partsys->setVisible(!(flags&Nif::NiNode::Flag_Hidden)); - objectlist.mParticles.push_back(partsys); + scene->mParticles.push_back(partsys); + + createMaterialControllers(partnode, partsys, animflags, scene); } - static void createNodeControllers(const std::string &name, Nif::ControllerPtr ctrl, ObjectList &objectlist, int animflags) + static void createNodeControllers(const std::string &name, Nif::ControllerPtr ctrl, ObjectScenePtr scene, int animflags) { do { if(ctrl->recType == Nif::RC_NiVisController) @@ -652,17 +896,17 @@ class NIFObjectLoader const Nif::NiVisController *vis = static_cast(ctrl.getPtr()); int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, ctrl->target->recIndex); - Ogre::Bone *trgtbone = objectlist.mSkelBase->getSkeleton()->getBone(trgtid); + Ogre::Bone *trgtbone = scene->mSkelBase->getSkeleton()->getBone(trgtid); Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ? Ogre::ControllerManager::getSingleton().getFrameTimeSource() : Ogre::ControllerValueRealPtr()); Ogre::ControllerValueRealPtr dstval(OGRE_NEW VisController::Value(trgtbone, vis->data.getPtr())); VisController::Function* function = OGRE_NEW VisController::Function(vis, (animflags&Nif::NiNode::AnimFlag_AutoPlay)); - objectlist.mMaxControllerLength = std::max(function->mStopTime, objectlist.mMaxControllerLength); + scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); Ogre::ControllerFunctionRealPtr func(function); - objectlist.mControllers.push_back(Ogre::Controller(srcval, dstval, func)); + scene->mControllers.push_back(Ogre::Controller(srcval, dstval, func)); } else if(ctrl->recType == Nif::RC_NiKeyframeController) { @@ -670,16 +914,16 @@ class NIFObjectLoader if(!key->data.empty()) { int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, ctrl->target->recIndex); - Ogre::Bone *trgtbone = objectlist.mSkelBase->getSkeleton()->getBone(trgtid); + Ogre::Bone *trgtbone = scene->mSkelBase->getSkeleton()->getBone(trgtid); Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ? Ogre::ControllerManager::getSingleton().getFrameTimeSource() : Ogre::ControllerValueRealPtr()); Ogre::ControllerValueRealPtr dstval(OGRE_NEW KeyframeController::Value(trgtbone, key->data.getPtr())); KeyframeController::Function* function = OGRE_NEW KeyframeController::Function(key, (animflags&Nif::NiNode::AnimFlag_AutoPlay)); - objectlist.mMaxControllerLength = std::max(function->mStopTime, objectlist.mMaxControllerLength); + scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); Ogre::ControllerFunctionRealPtr func(function); - objectlist.mControllers.push_back(Ogre::Controller(srcval, dstval, func)); + scene->mControllers.push_back(Ogre::Controller(srcval, dstval, func)); } } ctrl = ctrl->next; @@ -728,7 +972,7 @@ class NIFObjectLoader static void createObjects(const std::string &name, const std::string &group, Ogre::SceneNode *sceneNode, const Nif::Node *node, - ObjectList &objectlist, int flags, int animflags, int partflags) + ObjectScenePtr scene, int flags, int animflags, int partflags) { // Do not create objects for the collision shape (includes all children) if(node->recType == Nif::RC_RootCollisionNode) @@ -746,6 +990,17 @@ class NIFObjectLoader else flags |= node->flags; + if (node->recType == Nif::RC_NiBillboardNode) + { + // TODO: figure out what the flags mean. + // NifSkope has names for them, but doesn't implement them. + // Change mBillboardNodes to map + int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, node->recIndex); + Ogre::Bone* bone = scene->mSkelBase->getSkeleton()->getBone(trgtid); + bone->setManuallyControlled(true); + scene->mBillboardNodes.push_back(bone); + } + Nif::ExtraPtr e = node->extra; while(!e.empty()) { @@ -753,8 +1008,11 @@ class NIFObjectLoader { const Nif::NiTextKeyExtraData *tk = static_cast(e.getPtr()); - int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, node->recIndex); - extractTextKeys(tk, objectlist.mTextKeys[trgtid]); + if (scene->mSkelBase) + { + int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, node->recIndex); + extractTextKeys(tk, scene->mTextKeys[trgtid]); + } } else if(e->recType == Nif::RC_NiStringExtraData) { @@ -773,7 +1031,7 @@ class NIFObjectLoader } if(!node->controller.empty() && (node->parent || node->recType != Nif::RC_NiNode)) - createNodeControllers(name, node->controller, objectlist, animflags); + createNodeControllers(name, node->controller, scene, animflags); if(node->recType == Nif::RC_NiCamera) { @@ -782,13 +1040,13 @@ class NIFObjectLoader if(node->recType == Nif::RC_NiTriShape && !(flags&0x80000000)) { - createEntity(name, group, sceneNode->getCreator(), objectlist, node, flags, animflags); + createEntity(name, group, sceneNode->getCreator(), scene, node, flags, animflags); } if((node->recType == Nif::RC_NiAutoNormalParticles || node->recType == Nif::RC_NiRotatingParticles) && !(flags&0x40000000)) { - createParticleSystem(name, group, sceneNode, objectlist, node, flags, partflags); + createParticleSystem(name, group, sceneNode, scene, node, flags, partflags, animflags); } const Nif::NiNode *ninode = dynamic_cast(node); @@ -798,14 +1056,14 @@ class NIFObjectLoader for(size_t i = 0;i < children.length();i++) { if(!children[i].empty()) - createObjects(name, group, sceneNode, children[i].getPtr(), objectlist, flags, animflags, partflags); + createObjects(name, group, sceneNode, children[i].getPtr(), scene, flags, animflags, partflags); } } } static void createSkelBase(const std::string &name, const std::string &group, Ogre::SceneManager *sceneMgr, const Nif::Node *node, - ObjectList &objectlist) + ObjectScenePtr scene) { /* This creates an empty mesh to which a skeleton gets attached. This * is to ensure we have an entity with a skeleton instance, even if all @@ -814,12 +1072,12 @@ class NIFObjectLoader if(meshMgr.getByName(name).isNull()) NIFMeshLoader::createMesh(name, name, group, ~(size_t)0); - objectlist.mSkelBase = sceneMgr->createEntity(name); - objectlist.mEntities.push_back(objectlist.mSkelBase); + scene->mSkelBase = sceneMgr->createEntity(name); + scene->mEntities.push_back(scene->mSkelBase); } public: - static void load(Ogre::SceneNode *sceneNode, ObjectList &objectlist, const std::string &name, const std::string &group, int flags=0) + static void load(Ogre::SceneNode *sceneNode, ObjectScenePtr scene, const std::string &name, const std::string &group, int flags=0) { Nif::NIFFile::ptr nif = Nif::NIFFile::create(name); if(nif->numRoots() < 1) @@ -843,9 +1101,9 @@ public: !NIFSkeletonLoader::createSkeleton(name, group, node).isNull()) { // Create a base skeleton entity if this NIF needs one - createSkelBase(name, group, sceneNode->getCreator(), node, objectlist); + createSkelBase(name, group, sceneNode->getCreator(), node, scene); } - createObjects(name, group, sceneNode, node, objectlist, flags, 0, 0); + createObjects(name, group, sceneNode, node, scene, flags, 0, 0); } static void loadKf(Ogre::Skeleton *skel, const std::string &name, @@ -908,37 +1166,37 @@ public: }; -ObjectList Loader::createObjects(Ogre::SceneNode *parentNode, std::string name, const std::string &group) +ObjectScenePtr Loader::createObjects(Ogre::SceneNode *parentNode, std::string name, const std::string &group) { - ObjectList objectlist; + ObjectScenePtr scene = ObjectScenePtr (new ObjectScene(parentNode->getCreator()));; Misc::StringUtils::toLower(name); - NIFObjectLoader::load(parentNode, objectlist, name, group); + NIFObjectLoader::load(parentNode, scene, name, group); - for(size_t i = 0;i < objectlist.mEntities.size();i++) + for(size_t i = 0;i < scene->mEntities.size();i++) { - Ogre::Entity *entity = objectlist.mEntities[i]; + Ogre::Entity *entity = scene->mEntities[i]; if(!entity->isAttached()) parentNode->attachObject(entity); } - return objectlist; + return scene; } -ObjectList Loader::createObjects(Ogre::Entity *parent, const std::string &bonename, +ObjectScenePtr Loader::createObjects(Ogre::Entity *parent, const std::string &bonename, Ogre::SceneNode *parentNode, std::string name, const std::string &group) { - ObjectList objectlist; + ObjectScenePtr scene = ObjectScenePtr (new ObjectScene(parentNode->getCreator())); Misc::StringUtils::toLower(name); - NIFObjectLoader::load(parentNode, objectlist, name, group); + NIFObjectLoader::load(parentNode, scene, name, group); bool isskinned = false; - for(size_t i = 0;i < objectlist.mEntities.size();i++) + for(size_t i = 0;i < scene->mEntities.size();i++) { - Ogre::Entity *ent = objectlist.mEntities[i]; - if(objectlist.mSkelBase != ent && ent->hasSkeleton()) + Ogre::Entity *ent = scene->mEntities[i]; + if(scene->mSkelBase != ent && ent->hasSkeleton()) { isskinned = true; break; @@ -953,12 +1211,12 @@ ObjectList Loader::createObjects(Ogre::Entity *parent, const std::string &bonena { std::string filter = "@shape=tri "+bonename; Misc::StringUtils::toLower(filter); - for(size_t i = 0;i < objectlist.mEntities.size();i++) + for(size_t i = 0;i < scene->mEntities.size();i++) { - Ogre::Entity *entity = objectlist.mEntities[i]; + Ogre::Entity *entity = scene->mEntities[i]; if(entity->hasSkeleton()) { - if(entity == objectlist.mSkelBase || + if(entity == scene->mSkelBase || entity->getMesh()->getName().find(filter) != std::string::npos) parentNode->attachObject(entity); } @@ -971,9 +1229,9 @@ ObjectList Loader::createObjects(Ogre::Entity *parent, const std::string &bonena } else { - for(size_t i = 0;i < objectlist.mEntities.size();i++) + for(size_t i = 0;i < scene->mEntities.size();i++) { - Ogre::Entity *entity = objectlist.mEntities[i]; + Ogre::Entity *entity = scene->mEntities[i]; if(!entity->isAttached()) { Ogre::TagPoint *tag = parent->attachObjectToBone(bonename, entity); @@ -982,32 +1240,21 @@ ObjectList Loader::createObjects(Ogre::Entity *parent, const std::string &bonena } } - for(size_t i = 0;i < objectlist.mParticles.size();i++) - { - Ogre::ParticleSystem *partsys = objectlist.mParticles[i]; - if(partsys->isAttached()) - partsys->detachFromParent(); - - Ogre::TagPoint *tag = objectlist.mSkelBase->attachObjectToBone( - objectlist.mSkelBase->getSkeleton()->getRootBone()->getName(), partsys); - tag->setScale(scale); - } - - return objectlist; + return scene; } -ObjectList Loader::createObjectBase(Ogre::SceneNode *parentNode, std::string name, const std::string &group) +ObjectScenePtr Loader::createObjectBase(Ogre::SceneNode *parentNode, std::string name, const std::string &group) { - ObjectList objectlist; + ObjectScenePtr scene = ObjectScenePtr (new ObjectScene(parentNode->getCreator())); Misc::StringUtils::toLower(name); - NIFObjectLoader::load(parentNode, objectlist, name, group, 0xC0000000); + NIFObjectLoader::load(parentNode, scene, name, group, 0xC0000000); - if(objectlist.mSkelBase) - parentNode->attachObject(objectlist.mSkelBase); + if(scene->mSkelBase) + parentNode->attachObject(scene->mSkelBase); - return objectlist; + return scene; } diff --git a/components/nifogre/ogrenifloader.hpp b/components/nifogre/ogrenifloader.hpp index de06dd3d50..badb6ccd3b 100644 --- a/components/nifogre/ogrenifloader.hpp +++ b/components/nifogre/ogrenifloader.hpp @@ -25,6 +25,7 @@ #define OPENMW_COMPONENTS_NIFOGRE_OGRENIFLOADER_HPP #include +#include #include #include @@ -37,39 +38,68 @@ namespace NifOgre { +/** + * @brief Clones materials as necessary to not make controllers affect other objects (that share the original material). + */ +class MaterialControllerManager +{ +public: + ~MaterialControllerManager(); + + /// @attention if \a movable is an Entity, it needs to have *one* SubEntity + Ogre::MaterialPtr getWritableMaterial (Ogre::MovableObject* movable); + +private: + std::map mClonedMaterials; +}; + typedef std::multimap TextKeyMap; static const char sTextKeyExtraDataID[] = "TextKeyExtraData"; -struct ObjectList { +struct ObjectScene { Ogre::Entity *mSkelBase; std::vector mEntities; std::vector mParticles; std::vector mLights; + // Nodes that should always face the camera when rendering + std::vector mBillboardNodes; + + Ogre::SceneManager* mSceneMgr; + // The maximum length on any of the controllers. For animations with controllers, but no text keys, consider this the animation length. float mMaxControllerLength; std::map mTextKeys; + MaterialControllerManager mMaterialControllerMgr; + std::vector > mControllers; - ObjectList() : mSkelBase(0), mMaxControllerLength(0) + ObjectScene(Ogre::SceneManager* sceneMgr) : mSkelBase(0), mMaxControllerLength(0), mSceneMgr(sceneMgr) { } + + ~ObjectScene(); + + // Rotate nodes in mBillboardNodes so they face the given camera + void rotateBillboardNodes(Ogre::Camera* camera); }; +typedef Ogre::SharedPtr ObjectScenePtr; + class Loader { public: - static ObjectList createObjects(Ogre::Entity *parent, const std::string &bonename, + static ObjectScenePtr createObjects(Ogre::Entity *parent, const std::string &bonename, Ogre::SceneNode *parentNode, std::string name, const std::string &group="General"); - static ObjectList createObjects(Ogre::SceneNode *parentNode, + static ObjectScenePtr createObjects(Ogre::SceneNode *parentNode, std::string name, const std::string &group="General"); - static ObjectList createObjectBase(Ogre::SceneNode *parentNode, + static ObjectScenePtr createObjectBase(Ogre::SceneNode *parentNode, std::string name, const std::string &group="General"); diff --git a/components/nifogre/particles.cpp b/components/nifogre/particles.cpp index d902387b9a..a1433a6690 100644 --- a/components/nifogre/particles.cpp +++ b/components/nifogre/particles.cpp @@ -16,46 +16,11 @@ class NifEmitter : public Ogre::ParticleEmitter { public: - std::string mSkelBaseName; - Ogre::Bone* mBone; + Ogre::Bone* mEmitterBone; + Ogre::Bone* mParticleBone; Ogre::ParticleSystem* getPartSys() { return mParent; } - class CmdSkelBase : public Ogre::ParamCommand - { - public: - Ogre::String doGet(const void *target) const - { - assert(false && "Unimplemented"); - return ""; - } - void doSet(void *target, const Ogre::String &val) - { - NifEmitter* emitter = static_cast(target); - emitter->mSkelBaseName = val; - } - }; - - class CmdBone : public Ogre::ParamCommand - { - public: - Ogre::String doGet(const void *target) const - { - assert(false && "Unimplemented"); - return ""; - } - void doSet(void *target, const Ogre::String &val) - { - NifEmitter* emitter = static_cast(target); - assert(!emitter->mSkelBaseName.empty() && "Base entity needs to be set first"); - Ogre::ParticleSystem* partsys = emitter->getPartSys(); - Ogre::Entity* ent = partsys->getParentSceneNode()->getCreator()->getEntity(emitter->mSkelBaseName); - Ogre::Bone* bone = ent->getSkeleton()->getBone(val); - assert(bone); - emitter->mBone = bone; - } - }; - /** Command object for the emitter width (see Ogre::ParamCommand).*/ class CmdWidth : public Ogre::ParamCommand { @@ -165,8 +130,10 @@ public: NifEmitter(Ogre::ParticleSystem *psys) : Ogre::ParticleEmitter(psys) - , mBone(NULL) { + mEmitterBone = Ogre::any_cast(psys->getUserObjectBindings().getUserAny()); + Ogre::TagPoint* tag = static_cast(mParent->getParentNode()); + mParticleBone = static_cast(tag->getParent()); initDefaults("Nif"); } @@ -180,7 +147,6 @@ public: /** See Ogre::ParticleEmitter. */ void _initParticle(Ogre::Particle *particle) { - assert (mBone && "No node set"); Ogre::Vector3 xOff, yOff, zOff; // Call superclass @@ -189,23 +155,41 @@ public: xOff = Ogre::Math::SymmetricRandom() * mXRange; yOff = Ogre::Math::SymmetricRandom() * mYRange; zOff = Ogre::Math::SymmetricRandom() * mZRange; - - particle->position = mBone->_getDerivedPosition() + xOff + yOff + zOff; + +#if OGRE_VERSION >= (1 << 16 | 10 << 8 | 0) + Ogre::Vector3& position = particle->mPosition; + Ogre::Vector3& direction = particle->mDirection; + Ogre::ColourValue& colour = particle->mColour; + Ogre::Real& totalTimeToLive = particle->mTotalTimeToLive; + Ogre::Real& timeToLive = particle->mTimeToLive; +#else + Ogre::Vector3& position = particle->position; + Ogre::Vector3& direction = particle->direction; + Ogre::ColourValue& colour = particle->colour; + Ogre::Real& totalTimeToLive = particle->totalTimeToLive; + Ogre::Real& timeToLive = particle->timeToLive; +#endif + + position = xOff + yOff + zOff + + mParticleBone->_getDerivedOrientation().Inverse() * (mEmitterBone->_getDerivedPosition() + - mParticleBone->_getDerivedPosition()); // Generate complex data by reference - genEmissionColour(particle->colour); + genEmissionColour(colour); // NOTE: We do not use mDirection/mAngle for the initial direction. Ogre::Radian hdir = mHorizontalDir + mHorizontalAngle*Ogre::Math::SymmetricRandom(); Ogre::Radian vdir = mVerticalDir + mVerticalAngle*Ogre::Math::SymmetricRandom(); - particle->direction = (mBone->_getDerivedOrientation() * Ogre::Quaternion(hdir, Ogre::Vector3::UNIT_Z) * + direction = (mParticleBone->_getDerivedOrientation().Inverse() + * mEmitterBone->_getDerivedOrientation() * + Ogre::Quaternion(hdir, Ogre::Vector3::UNIT_Z) * Ogre::Quaternion(vdir, Ogre::Vector3::UNIT_X)) * Ogre::Vector3::UNIT_Z; - genEmissionVelocity(particle->direction); + genEmissionVelocity(direction); // Generate simpler data - particle->timeToLive = particle->totalTimeToLive = genEmissionTTL(); + timeToLive = totalTimeToLive = genEmissionTTL(); } /** Overloaded to update the trans. matrix */ @@ -361,16 +345,6 @@ protected: Ogre::PT_REAL), &msHorizontalAngleCmd); - dict->addParameter(Ogre::ParameterDef("bone", - "The bone where the particles should be spawned", - Ogre::PT_STRING), - &msBoneCmd); - - dict->addParameter(Ogre::ParameterDef("skelbase", - "The name of the entity containing the bone (see 'bone' parameter)", - Ogre::PT_STRING), - &msSkelBaseCmd); - return true; } return false; @@ -384,8 +358,6 @@ protected: static CmdVerticalAngle msVerticalAngleCmd; static CmdHorizontalDir msHorizontalDirCmd; static CmdHorizontalAngle msHorizontalAngleCmd; - static CmdBone msBoneCmd; - static CmdSkelBase msSkelBaseCmd; }; NifEmitter::CmdWidth NifEmitter::msWidthCmd; NifEmitter::CmdHeight NifEmitter::msHeightCmd; @@ -394,8 +366,6 @@ NifEmitter::CmdVerticalDir NifEmitter::msVerticalDirCmd; NifEmitter::CmdVerticalAngle NifEmitter::msVerticalAngleCmd; NifEmitter::CmdHorizontalDir NifEmitter::msHorizontalDirCmd; NifEmitter::CmdHorizontalAngle NifEmitter::msHorizontalAngleCmd; -NifEmitter::CmdBone NifEmitter::msBoneCmd; -NifEmitter::CmdSkelBase NifEmitter::msSkelBaseCmd; Ogre::ParticleEmitter* NifEmitterFactory::createEmitter(Ogre::ParticleSystem *psys) { @@ -466,9 +436,13 @@ public: /** See Ogre::ParticleAffector. */ void _initParticle(Ogre::Particle *particle) { +#if OGRE_VERSION >= (1 << 16 | 10 << 8 | 0) + const Ogre::Real life_time = particle->mTotalTimeToLive; + Ogre::Real particle_time = particle->mTimeToLive; +#else const Ogre::Real life_time = particle->totalTimeToLive; Ogre::Real particle_time = particle->timeToLive; - +#endif Ogre::Real width = mParent->getDefaultWidth(); Ogre::Real height = mParent->getDefaultHeight(); if(life_time-particle_time < mGrowTime) @@ -493,9 +467,13 @@ public: while (!pi.end()) { Ogre::Particle *p = pi.getNext(); - const Ogre::Real life_time = p->totalTimeToLive; - Ogre::Real particle_time = p->timeToLive; - +#if OGRE_VERSION >= (1 << 16 | 10 << 8 | 0) + const Ogre::Real life_time = p->mTotalTimeToLive; + Ogre::Real particle_time = p->mTimeToLive; +#else + const Ogre::Real life_time = p->totalTimeToLive; + Ogre::Real particle_time = p->timeToLive; +#endif Ogre::Real width = mParent->getDefaultWidth(); Ogre::Real height = mParent->getDefaultHeight(); if(life_time-particle_time < mGrowTime) @@ -554,47 +532,11 @@ class GravityAffector : public Ogre::ParticleAffector }; public: - std::string mSkelBaseName; - Ogre::Bone* mBone; + Ogre::Bone* mEmitterBone; + Ogre::Bone* mParticleBone; Ogre::ParticleSystem* getPartSys() { return mParent; } - class CmdSkelBase : public Ogre::ParamCommand - { - public: - Ogre::String doGet(const void *target) const - { - assert(false && "Unimplemented"); - return ""; - } - void doSet(void *target, const Ogre::String &val) - { - GravityAffector* affector = static_cast(target); - affector->mSkelBaseName = val; - } - }; - - class CmdBone : public Ogre::ParamCommand - { - public: - Ogre::String doGet(const void *target) const - { - assert(false && "Unimplemented"); - return ""; - } - void doSet(void *target, const Ogre::String &val) - { - GravityAffector* affector = static_cast(target); - assert(!affector->mSkelBaseName.empty() && "Base entity needs to be set first"); - Ogre::ParticleSystem* partsys = affector->getPartSys(); - Ogre::Entity* ent = partsys->getParentSceneNode()->getCreator()->getEntity(affector->mSkelBaseName); - Ogre::Bone* bone = ent->getSkeleton()->getBone(val); - assert(bone); - affector->mBone = bone; - } - }; - - /** Command object for force (see Ogre::ParamCommand).*/ class CmdForce : public Ogre::ParamCommand { @@ -688,8 +630,11 @@ public: , mForceType(Type_Wind) , mPosition(0.0f) , mDirection(0.0f) - , mBone(NULL) { + mEmitterBone = Ogre::any_cast(psys->getUserObjectBindings().getUserAny()); + Ogre::TagPoint* tag = static_cast(mParent->getParentNode()); + mParticleBone = static_cast(tag->getParent()); + mType = "Gravity"; // Init parameters @@ -710,16 +655,6 @@ public: dict->addParameter(Ogre::ParameterDef(force_type_title, force_type_descr, Ogre::PT_STRING), &msForceTypeCmd); dict->addParameter(Ogre::ParameterDef(direction_title, direction_descr, Ogre::PT_VECTOR3), &msDirectionCmd); dict->addParameter(Ogre::ParameterDef(position_title, position_descr, Ogre::PT_VECTOR3), &msPositionCmd); - - dict->addParameter(Ogre::ParameterDef("bone", - "The bone where the particles should be spawned", - Ogre::PT_STRING), - &msBoneCmd); - - dict->addParameter(Ogre::ParameterDef("skelbase", - "The name of the entity containing the bone (see 'bone' parameter)", - Ogre::PT_STRING), - &msSkelBaseCmd); } } @@ -761,8 +696,6 @@ public: static CmdForceType msForceTypeCmd; static CmdDirection msDirectionCmd; static CmdPosition msPositionCmd; - static CmdBone msBoneCmd; - static CmdSkelBase msSkelBaseCmd; protected: void applyWindForce(Ogre::ParticleSystem *psys, Ogre::Real timeElapsed) @@ -772,7 +705,11 @@ protected: while (!pi.end()) { Ogre::Particle *p = pi.getNext(); +#if OGRE_VERSION >= (1 << 16 | 10 << 8 | 0) + p->mDirection += vec; +#else p->direction += vec; +#endif } } @@ -783,9 +720,18 @@ protected: while (!pi.end()) { Ogre::Particle *p = pi.getNext(); - const Ogre::Vector3 vec = ( - (mBone->_getDerivedOrientation() * mPosition + mBone->_getDerivedPosition()) - p->position).normalisedCopy() * force; +#if OGRE_VERSION >= (1 << 16 | 10 << 8 | 0) + Ogre::Vector3 position = p->mPosition; +#else + Ogre::Vector3 position = p->position; +#endif + + Ogre::Vector3 vec = (mPosition - position).normalisedCopy() * force; +#if OGRE_VERSION >= (1 << 16 | 10 << 8 | 0) + p->mDirection += vec; +#else p->direction += vec; +#endif } } @@ -801,8 +747,6 @@ GravityAffector::CmdForce GravityAffector::msForceCmd; GravityAffector::CmdForceType GravityAffector::msForceTypeCmd; GravityAffector::CmdDirection GravityAffector::msDirectionCmd; GravityAffector::CmdPosition GravityAffector::msPositionCmd; -GravityAffector::CmdBone GravityAffector::msBoneCmd; -GravityAffector::CmdSkelBase GravityAffector::msSkelBaseCmd; Ogre::ParticleAffector *GravityAffectorFactory::createAffector(Ogre::ParticleSystem *psys) { diff --git a/components/nifogre/skeleton.cpp b/components/nifogre/skeleton.cpp index 04bffdeab4..e01ae22efd 100644 --- a/components/nifogre/skeleton.cpp +++ b/components/nifogre/skeleton.cpp @@ -30,6 +30,7 @@ void NIFSkeletonLoader::buildBones(Ogre::Skeleton *skel, const Nif::Node *node, node->recType == Nif::RC_RootCollisionNode || /* handled in nifbullet (hopefully) */ node->recType == Nif::RC_NiTriShape || /* Handled in the mesh loader */ node->recType == Nif::RC_NiBSAnimationNode || /* Handled in the object loader */ + node->recType == Nif::RC_NiBillboardNode || /* Handled in the object loader */ node->recType == Nif::RC_NiBSParticleNode || node->recType == Nif::RC_NiCamera || node->recType == Nif::RC_NiAutoNormalParticles || @@ -85,14 +86,23 @@ bool NIFSkeletonLoader::needSkeleton(const Nif::Node *node) { /* We need to be a little aggressive here, since some NIFs have a crap-ton * of nodes and Ogre only supports 256 bones. We will skip a skeleton if: - * There are no bones used for skinning, there are no controllers, there + * There are no bones used for skinning, there are no keyframe controllers, there * are no nodes named "AttachLight", and the tree consists of NiNode, * NiTriShape, and RootCollisionNode types only. */ if(node->boneTrafo) return true; - if(!node->controller.empty() || node->name == "AttachLight") + if(!node->controller.empty()) + { + Nif::ControllerPtr ctrl = node->controller; + do { + if(ctrl->recType == Nif::RC_NiKeyframeController) + return true; + } while(!(ctrl=ctrl->next).empty()); + } + + if (node->name == "AttachLight") return true; if(node->recType == Nif::RC_NiNode || node->recType == Nif::RC_RootCollisionNode) diff --git a/components/nifoverrides/nifoverrides.cpp b/components/nifoverrides/nifoverrides.cpp index 191b4ac2fe..972cf1b843 100644 --- a/components/nifoverrides/nifoverrides.cpp +++ b/components/nifoverrides/nifoverrides.cpp @@ -4,14 +4,51 @@ #include <../components/misc/stringops.hpp> +#include "../extern/shiny/Main/MaterialInstance.hpp" + +#include + using namespace NifOverrides; -Ogre::ConfigFile Overrides::mTransparencyOverrides = Ogre::ConfigFile(); +Overrides::TransparencyOverrideMap Overrides::mTransparencyOverrides = Overrides::TransparencyOverrideMap(); +Overrides::MaterialOverrideMap Overrides::mMaterialOverrides = Overrides::MaterialOverrideMap(); void Overrides::loadTransparencyOverrides (const std::string& file) { - mTransparencyOverrides.load(file); + Ogre::ConfigFile cf; + cf.load(file); + + Ogre::ConfigFile::SectionIterator seci = cf.getSectionIterator(); + while (seci.hasMoreElements()) + { + Ogre::String sectionName = seci.peekNextKey(); + mTransparencyOverrides[sectionName] = + Ogre::StringConverter::parseInt(cf.getSetting("alphaRejectValue", sectionName)); + seci.getNext(); + } +} + +void Overrides::loadMaterialOverrides(const std::string &file) +{ + Ogre::ConfigFile cf; + cf.load(file); + + Ogre::ConfigFile::SectionIterator seci = cf.getSectionIterator(); + while (seci.hasMoreElements()) + { + Ogre::String sectionName = seci.peekNextKey(); + + Ogre::ConfigFile::SettingsMultiMap *settings = seci.getNext(); + Ogre::ConfigFile::SettingsMultiMap::iterator i; + std::map overrides; + for (i = settings->begin(); i != settings->end(); ++i) + { + overrides[i->first] = i->second; + } + mMaterialOverrides[sectionName] = overrides; + } + } TransparencyResult Overrides::getTransparencyOverride(const std::string& texture) @@ -19,20 +56,25 @@ TransparencyResult Overrides::getTransparencyOverride(const std::string& texture TransparencyResult result; result.first = false; - std::string tex = texture; - Misc::StringUtils::toLower(tex); + TransparencyOverrideMap::iterator it = mTransparencyOverrides.find(Misc::StringUtils::lowerCase(texture)); + if (it != mTransparencyOverrides.end()) + { + result.first = true; + result.second = it->second; + } - Ogre::ConfigFile::SectionIterator seci = mTransparencyOverrides.getSectionIterator(); - while (seci.hasMoreElements()) + return result; +} + +void Overrides::getMaterialOverrides(const std::string &texture, sh::MaterialInstance* material) +{ + MaterialOverrideMap::iterator it = mMaterialOverrides.find(Misc::StringUtils::lowerCase(texture)); + if (it != mMaterialOverrides.end()) { - Ogre::String sectionName = seci.peekNextKey(); - if (sectionName == tex) + const std::map& overrides = it->second; + for (std::map::const_iterator it = overrides.begin(); it != overrides.end(); ++it) { - result.first = true; - result.second = Ogre::StringConverter::parseInt(mTransparencyOverrides.getSetting("alphaRejectValue", sectionName)); - break; + material->setProperty(it->first, sh::makeProperty(it->second)); } - seci.getNext(); } - return result; } diff --git a/components/nifoverrides/nifoverrides.hpp b/components/nifoverrides/nifoverrides.hpp index ba2e4cc3c3..edff876d44 100644 --- a/components/nifoverrides/nifoverrides.hpp +++ b/components/nifoverrides/nifoverrides.hpp @@ -3,19 +3,34 @@ #include +namespace sh +{ + class MaterialInstance; +} + namespace NifOverrides { typedef std::pair TransparencyResult; - /// \brief provide overrides for some model / texture properties that bethesda has chosen poorly + /// Allows to provide overrides for some material properties in NIF files. + /// NIFs are a bit limited in that they don't allow specifying a material externally, which is + /// painful for texture modding. + /// We also use this to patch up transparency settings in certain NIFs that bethesda has chosen poorly. class Overrides { public: - static Ogre::ConfigFile mTransparencyOverrides; + typedef std::map TransparencyOverrideMap; + static TransparencyOverrideMap mTransparencyOverrides; + + typedef std::map > MaterialOverrideMap; + static MaterialOverrideMap mMaterialOverrides; + void loadTransparencyOverrides (const std::string& file); + void loadMaterialOverrides (const std::string& file); static TransparencyResult getTransparencyOverride(const std::string& texture); + static void getMaterialOverrides (const std::string& texture, sh::MaterialInstance* instance); }; } diff --git a/components/settings/settings.hpp b/components/settings/settings.hpp index e9858eb945..b7c7d59a92 100644 --- a/components/settings/settings.hpp +++ b/components/settings/settings.hpp @@ -1,5 +1,5 @@ -#ifndef _COMPONENTS_SETTINGS_H -#define _COMPONENTS_SETTINGS_H +#ifndef COMPONENTS_SETTINGS_H +#define COMPONENTS_SETTINGS_H #include diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index b421de5a28..5dcdb7fed8 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -220,9 +220,8 @@ namespace Terrain ++neededTextureUnits; // layer texture // Check if this layer has a normal map - if (mNormalMapping && !mLayerList[layerOffset].mNormalMap.empty()) + if (mNormalMapping && !mLayerList[layerIndex].mNormalMap.empty() && !renderCompositeMap) ++neededTextureUnits; // normal map - if (neededTextureUnits <= remainingTextureUnits) { // We can fit another! @@ -335,6 +334,8 @@ namespace Terrain // Make sure the pass index is fed to the permutation handler, because blendmap components may be different p->mShaderProperties.setProperty ("pass_index", sh::makeProperty(new sh::IntValue(layerOffset))); + assert ((int)p->mTexUnits.size() == OGRE_MAX_TEXTURE_LAYERS - remainingTextureUnits); + layerOffset += numLayersInThisPass; } } diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp index e16ae55ddb..02225cb02d 100644 --- a/components/terrain/quadtreenode.cpp +++ b/components/terrain/quadtreenode.cpp @@ -140,17 +140,19 @@ namespace } QuadTreeNode::QuadTreeNode(World* terrain, ChildDirection dir, float size, const Ogre::Vector2 ¢er, QuadTreeNode* parent) - : mSize(size) - , mCenter(center) - , mParent(parent) - , mDirection(dir) + : mMaterialGenerator(NULL) + , mIsActive(false) , mIsDummy(false) + , mSize(size) + , mLodLevel(Log2(mSize)) + , mBounds(Ogre::AxisAlignedBox::BOX_NULL) + , mWorldBounds(Ogre::AxisAlignedBox::BOX_NULL) + , mDirection(dir) + , mCenter(center) , mSceneNode(NULL) + , mParent(parent) , mTerrain(terrain) , mChunk(NULL) - , mMaterialGenerator(NULL) - , mBounds(Ogre::AxisAlignedBox::BOX_NULL) - , mWorldBounds(Ogre::AxisAlignedBox::BOX_NULL) { mBounds.setNull(); for (int i=0; i<4; ++i) @@ -168,8 +170,6 @@ QuadTreeNode::QuadTreeNode(World* terrain, ChildDirection dir, float size, const pos = mCenter - pos; mSceneNode->setPosition(Ogre::Vector3(pos.x*8192, pos.y*8192, 0)); - mLodLevel = Log2(mSize); - mMaterialGenerator = new MaterialGenerator(mTerrain->getShadersEnabled()); } diff --git a/extern/oics/ICSChannel.h b/extern/oics/ICSChannel.h index f98f0d94d3..5ec6cd575b 100644 --- a/extern/oics/ICSChannel.h +++ b/extern/oics/ICSChannel.h @@ -24,8 +24,8 @@ OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------- */ -#ifndef _Channel_H_ -#define _Channel_H_ +#ifndef OICS_Channel_H_ +#define OICS_Channel_H_ #include "ICSPrerequisites.h" @@ -119,4 +119,4 @@ namespace ICS } -#endif \ No newline at end of file +#endif diff --git a/extern/oics/ICSControl.h b/extern/oics/ICSControl.h index 7939c86b95..ebf75a3fef 100644 --- a/extern/oics/ICSControl.h +++ b/extern/oics/ICSControl.h @@ -24,8 +24,8 @@ OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------- */ -#ifndef _Control_H_ -#define _Control_H_ +#ifndef OICS_Control_H_ +#define OICS_Control_H_ #include "ICSPrerequisites.h" diff --git a/extern/oics/ICSInputControlSystem.h b/extern/oics/ICSInputControlSystem.h index f42f9c0b5f..907cba5fc0 100644 --- a/extern/oics/ICSInputControlSystem.h +++ b/extern/oics/ICSInputControlSystem.h @@ -24,8 +24,8 @@ OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------- */ -#ifndef _InputControlSystem_H_ -#define _InputControlSystem_H_ +#ifndef OICS_InputControlSystem_H_ +#define OICS_InputControlSystem_H_ #include "ICSPrerequisites.h" diff --git a/extern/sdl4ogre/OISCompat.h b/extern/sdl4ogre/OISCompat.h index 3cffa143db..a0acc5837a 100644 --- a/extern/sdl4ogre/OISCompat.h +++ b/extern/sdl4ogre/OISCompat.h @@ -1,5 +1,5 @@ -#ifndef _OIS_SDL_COMPAT_H -#define _OIS_SDL_COMPAT_H +#ifndef OIS_SDL_COMPAT_H +#define OIS_SDL_COMPAT_H #include #include diff --git a/extern/sdl4ogre/cursormanager.hpp b/extern/sdl4ogre/cursormanager.hpp index 35ec92a706..3036b236be 100644 --- a/extern/sdl4ogre/cursormanager.hpp +++ b/extern/sdl4ogre/cursormanager.hpp @@ -1,5 +1,5 @@ -#ifndef _SDL4OGRE_CURSOR_MANAGER_H -#define _SDL4OGRE_CURSOR_MANAGER_H +#ifndef SDL4OGRE_CURSOR_MANAGER_H +#define SDL4OGRE_CURSOR_MANAGER_H #include #include diff --git a/extern/sdl4ogre/sdlcursormanager.hpp b/extern/sdl4ogre/sdlcursormanager.hpp index 7ba69f013e..7e3e59b4a5 100644 --- a/extern/sdl4ogre/sdlcursormanager.hpp +++ b/extern/sdl4ogre/sdlcursormanager.hpp @@ -1,5 +1,5 @@ -#ifndef _SDL4OGRE_CURSORMANAGER_H -#define _SDL4OGRE_CURSORMANAGER_H +#ifndef SDL4OGRE_CURSORMANAGER_H +#define SDL4OGRE_CURSORMANAGER_H #include diff --git a/extern/sdl4ogre/sdlinputwrapper.hpp b/extern/sdl4ogre/sdlinputwrapper.hpp index a2b698f860..f08e3eff6b 100644 --- a/extern/sdl4ogre/sdlinputwrapper.hpp +++ b/extern/sdl4ogre/sdlinputwrapper.hpp @@ -1,5 +1,5 @@ -#ifndef _SDL4OGRE_SDLINPUTWRAPPER_H -#define _SDL4OGRE_SDLINPUTWRAPPER_H +#ifndef SDL4OGRE_SDLINPUTWRAPPER_H +#define SDL4OGRE_SDLINPUTWRAPPER_H #include diff --git a/extern/shiny/Main/Factory.hpp b/extern/shiny/Main/Factory.hpp index 7d52266b55..15c8599583 100644 --- a/extern/shiny/Main/Factory.hpp +++ b/extern/shiny/Main/Factory.hpp @@ -259,9 +259,8 @@ namespace sh Platform* mPlatform; MaterialInstance* findInstance (const std::string& name); - public: - MaterialInstance* searchInstance (const std::string& name); private: + MaterialInstance* searchInstance (const std::string& name); /// @return was anything removed? bool removeCache (const std::string& pattern); diff --git a/extern/shiny/Platforms/Ogre/OgrePlatform.cpp b/extern/shiny/Platforms/Ogre/OgrePlatform.cpp index 3725d5f354..9f309fbcda 100644 --- a/extern/shiny/Platforms/Ogre/OgrePlatform.cpp +++ b/extern/shiny/Platforms/Ogre/OgrePlatform.cpp @@ -64,8 +64,11 @@ namespace sh bool OgrePlatform::supportsShaderSerialization () { - // Not very reliable in OpenGL mode (requires extension), and somehow doesn't work on linux even if the extension is present + #if OGRE_VERSION >= (1 << 16 | 9 << 8 | 0) + return true; + #else return Ogre::Root::getSingleton ().getRenderSystem ()->getName ().find("OpenGL") == std::string::npos; + #endif } bool OgrePlatform::supportsMaterialQueuedListener () @@ -110,10 +113,15 @@ namespace sh void OgrePlatform::serializeShaders (const std::string& file) { - std::fstream output; - output.open(file.c_str(), std::ios::out | std::ios::binary); - Ogre::DataStreamPtr shaderCache (OGRE_NEW Ogre::FileStreamDataStream(file, &output, false)); - Ogre::GpuProgramManager::getSingleton().saveMicrocodeCache(shaderCache); + #if OGRE_VERSION >= (1 << 16 | 9 << 8 | 0) + if (Ogre::GpuProgramManager::getSingleton().isCacheDirty()) + #endif + { + std::fstream output; + output.open(file.c_str(), std::ios::out | std::ios::binary); + Ogre::DataStreamPtr shaderCache (OGRE_NEW Ogre::FileStreamDataStream(file, &output, false)); + Ogre::GpuProgramManager::getSingleton().saveMicrocodeCache(shaderCache); + } } void OgrePlatform::deserializeShaders (const std::string& file) @@ -143,7 +151,7 @@ namespace sh else if (typeid(*value) == typeid(IntValue)) type = Ogre::GCT_INT1; else - assert(0); + throw std::runtime_error("unexpected type"); params->addConstantDefinition(name, type); mSharedParameters[name] = params; } diff --git a/files/materials/objects.mat b/files/materials/objects.mat index 32787e159b..751b512431 100644 --- a/files/materials/objects.mat +++ b/files/materials/objects.mat @@ -8,10 +8,15 @@ material openmw_objects_base diffuseMap black.png normalMap emissiveMap + darkMap use_emissive_map false use_detail_map false + use_diffuse_map false + use_dark_map false emissiveMapUVSet 0 detailMapUVSet 0 + diffuseMapUVSet 0 + darkMapUVSet 0 use_parallax false scene_blend default @@ -34,8 +39,12 @@ material openmw_objects_base normalMap $normalMap emissiveMapUVSet $emissiveMapUVSet detailMapUVSet $detailMapUVSet + diffuseMapUVSet $diffuseMapUVSet + darkMapUVSet $darkMapUVSet emissiveMap $emissiveMap detailMap $detailMap + diffuseMap $diffuseMap + darkMap $darkMap env_map $env_map env_map_color $env_map_color use_parallax $use_parallax @@ -55,8 +64,8 @@ material openmw_objects_base texture_unit diffuseMap { direct_texture $diffuseMap - create_in_ffp true - tex_coord_set $emissiveMapUVSet + create_in_ffp $use_diffuse_map + tex_coord_set $diffuseMapUVSet } texture_unit normalMap @@ -66,12 +75,13 @@ material openmw_objects_base num_mipmaps 4 } - texture_unit emissiveMap + texture_unit darkMap { - create_in_ffp $use_emissive_map - colour_op add - direct_texture $emissiveMap - tex_coord_set $emissiveMapUVSet + create_in_ffp $use_dark_map + colour_op_ex modulate src_current src_texture + alpha_op_ex modulate src_current src_texture + direct_texture $darkMap + tex_coord_set $darkMapUVSet } texture_unit detailMap @@ -82,6 +92,14 @@ material openmw_objects_base tex_coord_set $detailMapUVSet } + texture_unit emissiveMap + { + create_in_ffp $use_emissive_map + colour_op add + direct_texture $emissiveMap + tex_coord_set $emissiveMapUVSet + } + texture_unit envMap { create_in_ffp $env_map diff --git a/files/materials/objects.shader b/files/materials/objects.shader index 5a3d872a5b..93368f1f68 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -17,13 +17,15 @@ #define NORMAL_MAP @shPropertyHasValue(normalMap) #define EMISSIVE_MAP @shPropertyHasValue(emissiveMap) #define DETAIL_MAP @shPropertyHasValue(detailMap) +#define DIFFUSE_MAP @shPropertyHasValue(diffuseMap) +#define DARK_MAP @shPropertyHasValue(darkMap) #define PARALLAX @shPropertyBool(use_parallax) #define PARALLAX_SCALE 0.04 #define PARALLAX_BIAS -0.02 // right now we support 2 UV sets max. implementing them is tedious, and we're probably not going to need more -#define SECOND_UV_SET (@shPropertyString(emissiveMapUVSet) || @shPropertyString(detailMapUVSet)) +#define SECOND_UV_SET (@shPropertyString(emissiveMapUVSet) || @shPropertyString(detailMapUVSet) || @shPropertyString(diffuseMapUVSet) || @shPropertyString(darkMapUVSet)) // if normal mapping is enabled, we force pixel lighting #define VERTEX_LIGHTING (!@shPropertyHasValue(normalMap)) @@ -98,9 +100,7 @@ #if VERTEXCOLOR_MODE != 2 shUniform(float4, materialAmbient) @shAutoConstant(materialAmbient, surface_ambient_colour) #endif -#if VERTEXCOLOR_MODE != 2 shUniform(float4, materialDiffuse) @shAutoConstant(materialDiffuse, surface_diffuse_colour) -#endif #if VERTEXCOLOR_MODE != 1 shUniform(float4, materialEmissive) @shAutoConstant(materialEmissive, surface_emissive_colour) #endif @@ -234,9 +234,7 @@ lightResult.xyz += lightAmbient.xyz * materialAmbient.xyz + materialEmissive.xyz; #endif -#if VERTEXCOLOR_MODE != 2 lightResult.a *= materialDiffuse.a; -#endif #endif } @@ -250,20 +248,26 @@ #endif SH_BEGIN_PROGRAM +#if DIFFUSE_MAP shSampler2D(diffuseMap) +#endif #if NORMAL_MAP shSampler2D(normalMap) #endif -#if EMISSIVE_MAP - shSampler2D(emissiveMap) +#if DARK_MAP + shSampler2D(darkMap) #endif #if DETAIL_MAP shSampler2D(detailMap) #endif +#if EMISSIVE_MAP + shSampler2D(emissiveMap) +#endif + #if ENV_MAP shSampler2D(envMap) shUniform(float3, env_map_color) @shUniformProperty3f(env_map_color, env_map_color) @@ -339,9 +343,7 @@ #if VERTEXCOLOR_MODE != 2 shUniform(float4, materialAmbient) @shAutoConstant(materialAmbient, surface_ambient_colour) #endif - #if VERTEXCOLOR_MODE != 2 shUniform(float4, materialDiffuse) @shAutoConstant(materialDiffuse, surface_diffuse_colour) - #endif #if VERTEXCOLOR_MODE != 1 shUniform(float4, materialEmissive) @shAutoConstant(materialEmissive, surface_emissive_colour) #endif @@ -382,17 +384,33 @@ newUV += (TSeyeDir.xyxy * ( normalTex.a * PARALLAX_SCALE + PARALLAX_BIAS )).xyxy; #endif +#if DIFFUSE_MAP + #if @shPropertyString(diffuseMapUVSet) + float4 diffuse = shSample(diffuseMap, newUV.zw); + #else float4 diffuse = shSample(diffuseMap, newUV.xy); - shOutputColour(0) = diffuse; + #endif +#else + float4 diffuse = float4(1,1,1,1); +#endif #if DETAIL_MAP #if @shPropertyString(detailMapUVSet) - shOutputColour(0) *= shSample(detailMap, newUV.zw)*2; + diffuse *= shSample(detailMap, newUV.zw)*2; +#else + diffuse *= shSample(detailMap, newUV.xy)*2; +#endif +#endif + +#if DARK_MAP +#if @shPropertyString(darkMapUVSet) + diffuse *= shSample(darkMap, newUV.zw); #else - shOutputColour(0) *= shSample(detailMap, newUV.xy)*2; + diffuse *= shSample(darkMap, newUV.xy); #endif #endif + shOutputColour(0) = diffuse; #if !VERTEX_LIGHTING float3 viewPos = shMatrixMult(worldView, float4(objSpacePositionPassthrough.xyz,1)).xyz; @@ -434,9 +452,7 @@ lightResult.xyz += lightAmbient.xyz * materialAmbient.xyz + materialEmissive.xyz; #endif -#if VERTEXCOLOR_MODE != 2 lightResult.a *= materialDiffuse.a; -#endif #endif // shadows only for the first (directional) light diff --git a/files/materials/terrain.shader b/files/materials/terrain.shader index eda80c9e30..86eef36ffa 100644 --- a/files/materials/terrain.shader +++ b/files/materials/terrain.shader @@ -78,7 +78,6 @@ #endif shVertexInput(float2, uv0) - shVertexInput(float2, uv1) // lodDelta, lodThreshold #if LIGHTING shNormalInput(float4) @@ -335,7 +334,7 @@ float2 blendUV = (UV - 0.5) * (16.0 / (16.0+1.0)) + 0.5; float4 albedo = float4(0,0,0,1); - float2 layerUV = UV * 16; + float2 layerUV = float2(UV.x, 1.f-UV.y) * 16; // Reverse Y, required to get proper tangents float2 thisLayerUV; float4 normalTex; @@ -345,6 +344,7 @@ float2 blendUV = (UV - 0.5) * (16.0 / (16.0+1.0)) + 0.5; #endif @shForeach(@shPropertyString(num_layers)) + thisLayerUV = layerUV; #if @shPropertyBool(use_normal_map_@shIterator) normalTex = shSample(normalMap@shIterator, thisLayerUV); #if @shIterator == 0 && IS_FIRST_PASS @@ -354,9 +354,6 @@ float2 blendUV = (UV - 0.5) * (16.0 / (16.0+1.0)) + 0.5; #endif #endif - thisLayerUV = layerUV; - // required to play nicely with the tangents - thisLayerUV.y *= -1; #if @shPropertyBool(use_parallax_@shIterator) thisLayerUV += TSeyeDir.xy * ( normalTex.a * PARALLAX_SCALE + PARALLAX_BIAS ); #endif diff --git a/files/materials/water.mat b/files/materials/water.mat index 1e5f8c8e04..ade55f326f 100644 --- a/files/materials/water.mat +++ b/files/materials/water.mat @@ -37,7 +37,7 @@ material Water texture_unit normalMap { - texture water_nm.png 5 + texture water_nm.png } texture_unit rippleNormalMap diff --git a/files/mygui/CMakeLists.txt b/files/mygui/CMakeLists.txt index 70fdf42489..ef223a6177 100644 --- a/files/mygui/CMakeLists.txt +++ b/files/mygui/CMakeLists.txt @@ -8,7 +8,6 @@ set(MYGUI_FILES black.png core.skin core.xml - EBGaramond-Regular.ttf openmw_alchemy_window.layout openmw_book.layout openmw_box.skin.xml diff --git a/files/mygui/EBGaramond-Regular.ttf b/files/mygui/EBGaramond-Regular.ttf deleted file mode 100644 index 3f6f6c191d..0000000000 Binary files a/files/mygui/EBGaramond-Regular.ttf and /dev/null differ diff --git a/files/mygui/openmw_console.layout b/files/mygui/openmw_console.layout index 7d24a283e1..0c9a97d043 100644 --- a/files/mygui/openmw_console.layout +++ b/files/mygui/openmw_console.layout @@ -7,9 +7,12 @@ - + + + + diff --git a/files/mygui/openmw_console.skin.xml b/files/mygui/openmw_console.skin.xml index 219cce39ae..470451a0ed 100644 --- a/files/mygui/openmw_console.skin.xml +++ b/files/mygui/openmw_console.skin.xml @@ -2,19 +2,6 @@ - - - - - - - - - - - - - diff --git a/files/mygui/openmw_font.xml b/files/mygui/openmw_font.xml index 726bfb281c..e4037561d7 100644 --- a/files/mygui/openmw_font.xml +++ b/files/mygui/openmw_font.xml @@ -1,31 +1,8 @@ - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/files/mygui/openmw_messagebox.layout b/files/mygui/openmw_messagebox.layout index dfdb57648d..b2d29271bc 100644 --- a/files/mygui/openmw_messagebox.layout +++ b/files/mygui/openmw_messagebox.layout @@ -1,11 +1,11 @@ - - - + + + + + diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index e6002b51de..61103963db 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -65,6 +65,12 @@ + + + + + + diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 6e5477330c..4fb7097f84 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -53,8 +53,7 @@ texture filtering = anisotropic anisotropy = 4 # Number of texture mipmaps to generate -# This setting is currently ignored due to mipmap generation problems on Intel/AMD -#num mipmaps = 5 +num mipmaps = 8 shader mode = @@ -156,6 +155,8 @@ voice volume = 1.0 [Input] +grab cursor = true + invert y axis = false camera sensitivity = 1.0 diff --git a/libs/openengine/bullet/BtOgreExtras.h b/libs/openengine/bullet/BtOgreExtras.h index b20a3ff984..9572b8a7b7 100644 --- a/libs/openengine/bullet/BtOgreExtras.h +++ b/libs/openengine/bullet/BtOgreExtras.h @@ -13,8 +13,8 @@ * ===================================================================================== */ -#ifndef _BtOgreShapes_H_ -#define _BtOgreShapes_H_ +#ifndef BtOgreShapes_H_ +#define BtOgreShapes_H_ #include "btBulletDynamicsCommon.h" #include "OgreSimpleRenderable.h" diff --git a/libs/openengine/bullet/BtOgreGP.h b/libs/openengine/bullet/BtOgreGP.h index 4ce2f181ee..dde606a4f2 100644 --- a/libs/openengine/bullet/BtOgreGP.h +++ b/libs/openengine/bullet/BtOgreGP.h @@ -14,8 +14,8 @@ * ===================================================================================== */ -#ifndef _BtOgrePG_H_ -#define _BtOgrePG_H_ +#ifndef BtOgrePG_H_ +#define BtOgrePG_H_ #include "btBulletDynamicsCommon.h" #include "BtOgreExtras.h" diff --git a/libs/openengine/bullet/BtOgrePG.h b/libs/openengine/bullet/BtOgrePG.h index 9ff069a8f9..2e42fe1f91 100644 --- a/libs/openengine/bullet/BtOgrePG.h +++ b/libs/openengine/bullet/BtOgrePG.h @@ -14,8 +14,8 @@ * ===================================================================================== */ -#ifndef _BtOgreGP_H_ -#define _BtOgreGP_H_ +#ifndef BtOgreGP_H_ +#define BtOgreGP_H_ #include "btBulletDynamicsCommon.h" #include "OgreSceneNode.h" diff --git a/libs/openengine/bullet/BulletShapeLoader.h b/libs/openengine/bullet/BulletShapeLoader.h index 98cda859db..0e5c652260 100644 --- a/libs/openengine/bullet/BulletShapeLoader.h +++ b/libs/openengine/bullet/BulletShapeLoader.h @@ -1,5 +1,5 @@ -#ifndef _BULLET_SHAPE_LOADER_H_ -#define _BULLET_SHAPE_LOADER_H_ +#ifndef OPENMW_BULLET_SHAPE_LOADER_H_ +#define OPENMW_BULLET_SHAPE_LOADER_H_ #include #include diff --git a/libs/openengine/bullet/CMotionState.cpp b/libs/openengine/bullet/CMotionState.cpp deleted file mode 100644 index c20415884a..0000000000 --- a/libs/openengine/bullet/CMotionState.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include "CMotionState.h" -#include "physic.hpp" - -#include -#include -#include - -namespace OEngine { -namespace Physic -{ - - CMotionState::CMotionState(PhysicEngine* eng,std::string name) - : isPC(false) - , isNPC(true) - { - pEng = eng; - tr.setIdentity(); - pName = name; - } - - void CMotionState::getWorldTransform(btTransform &worldTrans) const - { - worldTrans = tr; - } - - void CMotionState::setWorldTransform(const btTransform &worldTrans) - { - tr = worldTrans; - - PhysicEvent evt; - evt.isNPC = isNPC; - evt.isPC = isPC; - evt.newTransform = tr; - evt.RigidBodyName = pName; - - if(isPC) - { - pEng->PEventList.push_back(evt); - } - else - { - pEng->NPEventList.push_back(evt); - } - } - -}} diff --git a/libs/openengine/bullet/CMotionState.h b/libs/openengine/bullet/CMotionState.h deleted file mode 100644 index 3508ab4ef1..0000000000 --- a/libs/openengine/bullet/CMotionState.h +++ /dev/null @@ -1,52 +0,0 @@ -#ifndef OENGINE_CMOTIONSTATE_H -#define OENGINE_CMOTIONSTATE_H - -#include -#include - -namespace OEngine { -namespace Physic -{ - class PhysicEngine; - - /** - * A CMotionState is associated with a single RigidBody. - * When the RigidBody is moved by bullet, bullet will call the function setWorldTransform. - * for more info, see the bullet Wiki at btMotionState. - */ - class CMotionState:public btMotionState - { - public: - - CMotionState(PhysicEngine* eng,std::string name); - - /** - * Return the position of the RigidBody. - */ - virtual void getWorldTransform(btTransform &worldTrans) const; - - /** - * Function called by bullet when the RigidBody is moved. - * It add an event to the EventList of the PhysicEngine class. - */ - virtual void setWorldTransform(const btTransform &worldTrans); - - protected: - PhysicEngine* pEng; - btTransform tr; - bool isNPC; - bool isPC; - - std::string pName; - }; - - struct PhysicEvent - { - bool isNPC; - bool isPC; - btTransform newTransform; - std::string RigidBodyName; - }; - -}} -#endif diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index e33edda183..4e80088bf7 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -3,7 +3,6 @@ #include #include #include -#include "CMotionState.h" #include "OgreRoot.h" #include "btKinematicCharacterController.h" #include "BtOgrePG.h" @@ -318,9 +317,7 @@ namespace Physic btVector3 scl(triSize, triSize, 1); hfShape->setLocalScaling(scl); - CMotionState* newMotionState = new CMotionState(this,name); - - btRigidBody::btRigidBodyConstructionInfo CI = btRigidBody::btRigidBodyConstructionInfo(0,newMotionState,hfShape); + btRigidBody::btRigidBodyConstructionInfo CI = btRigidBody::btRigidBodyConstructionInfo(0,0,hfShape); RigidBody* body = new RigidBody(CI,name); body->getWorldTransform().setOrigin(btVector3( (x+0.5)*triSize*(sqrtVerts-1), (y+0.5)*triSize*(sqrtVerts-1), (maxh+minh)/2.f)); @@ -401,12 +398,9 @@ namespace Physic else shape->mRaycastingShape->setLocalScaling( btVector3(scale,scale,scale)); - //create the motionState - CMotionState* newMotionState = new CMotionState(this,name); - //create the real body btRigidBody::btRigidBodyConstructionInfo CI = btRigidBody::btRigidBodyConstructionInfo - (0,newMotionState, raycasting ? shape->mRaycastingShape : shape->mCollisionShape); + (0,0, raycasting ? shape->mRaycastingShape : shape->mCollisionShape); RigidBody* body = new RigidBody(CI,name); body->mPlaceable = placeable; diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp index f28f95ccb8..6cd7244b85 100644 --- a/libs/openengine/bullet/physic.hpp +++ b/libs/openengine/bullet/physic.hpp @@ -38,7 +38,6 @@ namespace MWWorld namespace OEngine { namespace Physic { - class CMotionState; struct PhysicEvent; class PhysicEngine; class RigidBody; @@ -157,17 +156,7 @@ namespace Physic private: void disableCollisionBody(); void enableCollisionBody(); -public: -//HACK: in Visual Studio 2010 and presumably above, this structures alignment -// must be 16, but the built in operator new & delete don't properly -// perform this alignment. -#if _MSC_VER >= 1600 - void * operator new (size_t Size) { return _aligned_malloc (Size, 16); } - void operator delete (void * Data) { _aligned_free (Data); } -#endif - - private: OEngine::Physic::RigidBody* mBody; OEngine::Physic::RigidBody* mRaycastingBody; @@ -329,12 +318,6 @@ public: const btVector3 &origin, btCollisionObject *object); - //event list of non player object - std::list NPEventList; - - //event list affecting the player - std::list PEventList; - //Bullet Stuff btOverlappingPairCache* pairCache; btBroadphaseInterface* broadphase; diff --git a/libs/openengine/ogre/renderer.cpp b/libs/openengine/ogre/renderer.cpp index a0fe6ca849..9e5ec5414d 100644 --- a/libs/openengine/ogre/renderer.cpp +++ b/libs/openengine/ogre/renderer.cpp @@ -11,6 +11,8 @@ #include +#include + #include #include @@ -23,6 +25,13 @@ void OgreRenderer::cleanup() delete mFader; mFader = NULL; + if (mWindow) + Ogre::Root::getSingleton().destroyRenderTarget(mWindow); + mWindow = NULL; + + delete mOgreInit; + mOgreInit = NULL; + // If we don't do this, the desktop resolution is not restored on exit SDL_SetWindowFullscreen(mSDLWindow, 0); @@ -50,7 +59,8 @@ void OgreRenderer::configure(const std::string &logPath, const std::string& rttMode ) { - mRoot = mOgreInit.init(logPath + "/ogre.log"); + mOgreInit = new OgreInit::OgreInit(); + mRoot = mOgreInit->init(logPath + "/ogre.log"); RenderSystem* rs = mRoot->getRenderSystemByName(renderSystem); if (rs == 0) diff --git a/libs/openengine/ogre/renderer.hpp b/libs/openengine/ogre/renderer.hpp index e4af0bf77c..767e7cf99e 100644 --- a/libs/openengine/ogre/renderer.hpp +++ b/libs/openengine/ogre/renderer.hpp @@ -9,8 +9,6 @@ #include -#include - struct SDL_Window; struct SDL_Surface; @@ -26,6 +24,11 @@ namespace Ogre class ParticleAffectorFactory; } +namespace OgreInit +{ + class OgreInit; +} + namespace OEngine { namespace Render @@ -57,7 +60,7 @@ namespace OEngine Ogre::Camera *mCamera; Ogre::Viewport *mView; - OgreInit::OgreInit mOgreInit; + OgreInit::OgreInit* mOgreInit; Fader* mFader; @@ -71,8 +74,9 @@ namespace OEngine , mScene(NULL) , mCamera(NULL) , mView(NULL) - , mWindowListener(NULL) + , mOgreInit(NULL) , mFader(NULL) + , mWindowListener(NULL) { } diff --git a/manual/opencs/.gitignore b/manual/opencs/.gitignore new file mode 100644 index 0000000000..ce5852a376 --- /dev/null +++ b/manual/opencs/.gitignore @@ -0,0 +1,6 @@ +*.backup +*.aux +*.log +*.toc +*.pdf +*.out diff --git a/manual/opencs/creating_file.tex b/manual/opencs/creating_file.tex new file mode 100644 index 0000000000..2c1377ec1e --- /dev/null +++ b/manual/opencs/creating_file.tex @@ -0,0 +1,25 @@ +\section{OpenCS starting dialog} +\subsection{Introduction} +The great day has come. Today, you shall open \OCS{} application. And when you do this, you shall see our starting dialog window that holds three buttons +that can bring both pain and happiness. So just do this, please. + +\subsection{Basics} +Back to the manual? Great! As you can see, the starting window holds just three buttons. Since you are already familiar with our files system, they come +to you with no surprise.\\ + +First, there is a \textbf{Create A New Game} button. Clearly, you should press it when you want to create a game file. Than, what \textbf{Create A New Addon} button do? +Yes! You are right! This button will create any addon content file (and new project file associated with it)! Wonderful! And what the last remaining button do? \textbf{Edit A Content File}? Well, it comes with no surprise that this should be used when you need to alter existing content file, either a game or addon.\\ + +\paragraph{Selecting Files For New Addon} +As We wrote earlier, both \OMW{} and \OCS{} are operating with dependency idea in mind. As You remember you should only depend on files you are actually using. But how?\\ +It is simple. When you click either \textbf{Create new Addon} you will be asked to choose those with a new dialog window. The window is using vertical layout, first you should consider the the top element, the one that allows you to select a game file with drop down menu. Since we are operating on the assumption that there is only one game file loaded at the time, you can depend only on one game file. Next, choose addons that you want to use in your addon with checkboxes.\\ + +The last thing to do is to name your your addon and click create. + +\paragraph{Selecting File for Editing} +Clicking \textbf{Edit A Content File} will show somewhat similar window. Here you should select your Game file with drop down menu. If you want to edit this game file, simply click \textbf{OK} button. If you want to alter addon depending on that file, mark it with checkbox and than click \textbf{Ok} button. + +\subsection{Advanced} +If you are paying attention, you noticed any extra icon with wrench. This one will open small settings window. Those are general OpenCS settings. We will cover this is separate section.\\ + +And that would be it. There is no point spending more time here. We should go forward now. \ No newline at end of file diff --git a/manual/opencs/files_and_directories.tex b/manual/opencs/files_and_directories.tex new file mode 100644 index 0000000000..b483dac5a2 --- /dev/null +++ b/manual/opencs/files_and_directories.tex @@ -0,0 +1,120 @@ +\section{Files and Directories} +\subsection{Introduction} +This section of the manual covers usage of files and directories by the OpenCS. Files and directories are file system concepts, +and you are probably already familiar with it. We won't try to explain this concepts, we will just focus on \OCS. + +\subsection{Used terms} %TODO + +\subsection{Basics} + +\paragraph{Directories} +OpenMW and \OCS{} uses multiple directories on file systems. First of, there is a \textbf{user directory} that holds configuration +files and few different folders. The location of the user directory is hard coded for each supported operating system. + +%TODO list paths. +In addition to this single hard coded directory, both \OMW{} and \OCS{} need a~place to seek for actual data files of the game: +textures, models, sounds and files that store records of objects in game; dialogues and so one -- so called content files. We support +multiple such paths (we call it \textbf{data paths}) as specified in the configuration. Usually one data path points to the directory +where original \MW{} is either installed or unpacked. You are free to specify as many data paths as you would like, +however, there is one special data path that, as described later, is used to store newly created content files. + +\paragraph{Content files} +\BS{} \MW{} engine is using two types of files: ESM (master) and ESP (plugin). The distinction between those +is not clear, and often confusing. You would expect the ESM (master) file is used to specify one master, that is modified by the ESPs plugins, +and indeed: this is the basic idea. However, original expansions also were made as ESM files, even though they essentially could be +described as a really large plugins, and therefore rather use ESP files. There were technical reasons behind this decision -- somewhat valid +in the case of original engine, but clearly it's better to create a system that can be used is more sensible way. \OMW{} achieves +this with our own content file types. + +We support both ESM and ESP files, but in order to make use of new features of OpenMW one should consider using new file types designed +with our engine in mind: game files and addon files together called ``content files``. + +\subparagraph{OpenMW content files} +Game and Addon files are concept somewhat similar to the old ESM/ESP, only in the way it should be from the very beginning. Nothing easier +to describe. If you want to make new game using \OMW{} as engine (so called ``total conversion'') you should create a game file. +If you want to create a addon for existing game file -- simply create addon file. Nothing else matters: The only distinction you should +consider is if your project is about changing other game, or creating a new one. Simple as that. + +Other simple thing about content files are extensions. We are using .omwaddon for addon files and .omwgame for game files. + +%TODO describe what content files contains. and what not. +\subparagraph{\MW{} content files} +Using our content files is recommended solution for projects that are intended to used with \OMW{} engine. However some players +wish to use original \MW{} engine, even with it large flaws and lacking features\footnote{If this is actually wrong, we are very +successful project. Yay!}. Also, since 2002 thousands of ESP/ESM files were created, some with really outstanding content. +Because of this \OCS{} simply has no other choice but support ESP/ESM files. However, if you decided to choose ESP/ESM file instead +using our own content file types you are most likely aim at the original engine compatibility. This subject is covered in the very +last section of this manual. %not finished TODO add the said section. Most likely when more features are present. + +The actual creation of new files is described in the next chapter. Here we are gonna focus only on details that you need to know +in order to create your first \OCS{} file while full understanding your needs. For now let's jut remember that content files +are created inside the user directory, in the the \textbf{data} subfolder (that is the one special data directory mentioned earlier). + +\subparagraph{Dependencies} +Since addon is supposed to change the game it is logical that it also depends on the said game. It simply can not work otherwise. +Just think about it: your modification is changing prize of the iron sword. But what if there is no iron sword in game? That is right: +we get nonsense. What you want to do is to tie your addon to the files you are changing. Those can be either game files (expansion island +for a game) or other addon files (house on the said island). It is a good idea to be dependent only on files that are really changed +in your addon obviously, but sadly there is no other way to achieve this than knowing what you want to do. Again, please remember that +this section of the manual does not cover creating the content files -- it is only theoretical introduction to the subject. For now just +keep in mind that dependencies exist, and is up to you what to decide if your content file should depend on other content file. + +Game files are not intend to have any dependencies for a very simple reasons: player is using only one game file (excluding original +and dirty {ESP/ESM} system) at the time and therefore no game file can depend on other game file, and since game file makes the base +for addon files -- it can not depend on addon files. + +%\subparagraph{Loading order} %TODO +\paragraph{Project files} +Project files act as containers for data not used by the \OMW{} game engine itself, but still useful for OpenCS. The shining example +of this data category are without doubt record filters (described in the later section of the manual you are reading currently). +As a mod author you probably do not need and/or want to distribute project files at all, they are meant to be used only by you. + +As you would imagine, project file makes sense only in combination with actual content files. In fact, each time you start to work +on new content file and project file was not found, it will be created. +Project files extension is, to not surprise ``.project''. The whole name of the project file is the whole name of the content file +with appended extensions. For instance swords.omwaddon file is associated with swords.omwaddon.project file. + +%TODO where are they stored. +Project files are stored inside the user directory, in the \textbf{projects} subfolder. This is the path location for both freshly +created project files, and a place where \OCS{} looks for already existing files. + +\paragraph{Resources files} +%textures, sounds, whatever +Unless we are talking about the fully text based game, like Zork or Rogue, you are expecting that a video game is using some media files: +models with textures, pictures acting as icons, sounds and everything else. Since content files, no matter if it is ESP, ESM or new \OMW{} +file type do not contain any of those, it is clear that they have to be deliver with a different file. It is also clear that this, +let's call it ``resources file``, have to be supported by the engine. Without code handling those files, it is nothing more than +a mathematical abstraction -- something, that lacks meaning for human beings\footnote{Unless we call programmers a human beings.}. +Therefore this section must cover ways to add resources files to your content file, and point out what is supported. We are going +to do just that. Later, you will learn how to make use of those files in your content. + +\subparagraph{Audio} +OpenMW is using {FFmpeg} for audio playback, and so we support every audio type that is supported by this library. This makes a huge list. +Below is only small portion of supported file types. + +\begin{description} + \item mp3 ({MPEG}-1 {Part 3 Layer 3}) popular audio file format and \textit{de facto} standard for storing audio. Used by the \MW{} game. + \item ogg open source, multimedia container file using high quality vorbis audio codec. Recommended. +\end{description} + +\subparagraph{Video} +As in the case of audio files, we are using {FFmepg} to decode video files. The list of supported files is long, we will cover +only the most significant. + +\begin{description} + \item bik videos used by original \MW{} game. + \item mp4 multimedia container which use more advanced codecs ({MPEG-4 Parts 2,3,10}) with a better audio and video compression rate, + but also requiring more {CPU} intensive decoding -- this makes it probably less suited for storing sounds in computer games, but good for videos. + \item webm is a new, shiny and open source video format with excellent compression. It needs quite a lot of processing power to be decoded, + but since game logic is not running during cut scenes we can recommend it for use with \OMW. + \item ogv alternative, open source container using theora codec for video and vorbis for audio. +\end{description} + +\subparagraph{Textures and images} +Original \MW{} game uses {DDS} and {TGA} files for all kind of two dimensional images and textures alike. In addition, engine supported BMP +files for some reason ({BMP} is a terrible format for a video game). We also support extended set of image files -- including {JPEG} and {PNG}. +JPEG and PNG files can be useful in some cases, for instance JPEG file is a valid option for skybox texture and PNG can useful for masks. +However please, keep in mind that JPEG can grow into large sizes quickly and are not the best option with {DirectX} rendering backend. You probabbly still want +to use {DDS} files for textures. + +%\subparagraph{Meshes} %TODO once we will support something more than just nifs \ No newline at end of file diff --git a/manual/opencs/filters.tex b/manual/opencs/filters.tex new file mode 100644 index 0000000000..36d97e0f5e --- /dev/null +++ b/manual/opencs/filters.tex @@ -0,0 +1,205 @@ +\section{Record filters} +\subsection{Introduction} +Filters are the key element of \OCS{} use cases by allowing rapid and easy access to the searched records presented in all tables. +Therefore: in order to use this application fully effective you should make sure that all concepts and instructions written in +the this section of the manual are perfectly clear to you. + +Do not be afraid though, filters are fairly intuitive and easy to use. + +\subsubsection{Used Terms} + +\begin{description} + \item[Filter] is generally speaking a tool able to ``Filter'' (that is: select some elements, while discarding others) according + to the some criteria. In case of \OCS: records are being filtered according to the criteria of user choice. Criteria are written + down in language with simple syntax. + \item[Criteria] describes condition under with any any record is being select by the filter. + \item[Syntax] as you may noticed computers (in general) are rather strict, and expect only strictly formulated orders -- that is: + written with correct syntax. + \item[Expression] is way we are actually performing filtering. Filter can be treated as ``functions'': accepts arguments, and evaluates + either to the true or false for every column record at the time. + \item[N-ary] is any expression that expects one or more expressions as arguments. It is useful for grouping two (or more) other expressions + together in order to create filter that will check for criteria placed in two (again: or more) columns (logical \textit{or}, \textit{and}). + \item[unary] is any expression that expects one other expression. The example is \textit{not} expression. In fact \textit{not} is the only useful + unary expression in \OCS{} record filters. + \item[nullary] is expression that does not accepts other expressions. It accepts arguments specified later. +\end{description} + +\subsubsection{Basics} +In fact you do not need to learn everything about filters in order to use them. In fact all you need to know to achieve decent productivity +with \OCS{} is inside basics section. + +\subsubsection{Interface} +Above each table there is a field that is used to enter filter: either predefined by the \OMW{} developers or made by you, the user. +You probably noticed it before. However there is also completely new element, although using familiar table layout. Go to the application +menu view, and click filters. You should see set of default filters, made by the \OMW{} team in the table with the following columns: filter, +description and modified. + +\begin{description} + \item[ID] contains the name of the filter. + \item[Modified] just like in all other tables you have seen so far modified indicates if a filter was added, modified or removed. + \item[Filter] column containing expression of the filter. + \item[Description] contains the short description of the filter function. Do not expect any surprises there. +\end{description} + +So let's learn how to actually use those to speed up your work. +\subsubsection{Using predefined filters} +Using those filters is quite easy and involves typing inside the filter field above the table. For instance, try to open referencables +table and type in the filters field the following: \mono{project::weapons}. As soon as you complete the text, table will magicly alter +and will show only the weapons. As you could noticed \mono{project::weapons} is nothing else than a~ID of one of the predefined filters. That is it: +in order to use the filter inside the table you simply type it is name inside the filter field. + +To make life easier filter IDs follow simple convention. + +\begin{itemize} + \item Filter ID filtering a specific record type contains usually the name of a specific group. For instance \mono{project::weapons} filter + contains the word weapons (did you noticed?). Plural form is always used. + \item When filtering specific subgroup the ID starts just like in the case of general filter. For instance \mono{project::weaponssilver} will + filter only silver weapons (new mechanic introduced by the \BM{}, silver weapons deal double damage against werewolfs) and + \mono{project::weaponsmagical} will filter only magical weapons (able to hurt ghosts and other supernatural creatures). + \item There are few exceptions from the above rule. For instance there is a \mono{project::added}, \mono{project::removed}, + \mono{project::modyfied}, \mono{project::base}. + You would probably except something more like \mono{project::statusadded} but in this case typing this few extra characters would only + help to break your keyboard faster. +\end{itemize} + +We strongly recommend to take a look at the filters table right now to see what you can filter with that. And try using it! It is very simple. + +\subsection{Advanced} +Back to the manual? Great. + +If you want to create your own filter you have to know exactly what do you want to get in order to translate this into the expressions. +Finally, you will have to write this with correct syntax. As a result table will show only desired rows. + +Advance subsection covers everything that you need to know in order to create any filter you may want to %TODO the filter part is actually wrong +\subsubsection{Namespaces} +Did you noticed that every default filter has \mono{project::} prefix? It is a \textit{namespace}, a~term borrowed from the \CPP{} language. +In case of \OCS{} namespace always means scope of the said object\footnote{You are not supposed to understand this at the moment.}. +But what does it mean in case of filters? Well, short explanation is actually simple. +\begin{description} + \item[project::] namespace indicates that filter is used with the project, in multiple sessions. You can restart \OCS{} and filter + is still there. + \item[session::] namespace indicates that filter is not stored trough multiple sessions and once you will quit \OCS{} (close session) + the filter will be gone. Forever! Until then it can be found inside the filters table. +\end{description} +In addition to this two scopes, there is a third one; called one-shot. One-shot filters are not stored (even during single session) +anywhere and as the name implies they are supposed to be created when needed only once. Good thing about the one-shot filters is that +you do not need to open filters table in order to create it. Instead you just type it directly inside the filter field, starting with +exclamation mark: ``!''. + +Still, you may wonder how you are supposed to write expressions, what expressions you should use, and what syntax looks like. Let's start +with nullary expressions that will allow you to create a basic filter. + +\subsubsection{Nullary expressions} +All nullary expressions are used in similar manner. First off: you have to write it is name (for instance: \mono{string}) and secondly: +condition that will be checked inside brackets (for instance \mono{string(something, something)}). If conditions of your expression will be meet +by a record (technical speaking: expression will evaluate to true) the record will show up in the table. + +It is clear that you need to know what are you checking, that is: what column of the table contains information that you are interested +in and what should be inside specific cell inside this column to meet your requirements. In most cases first word inside brackets sets column +you want to see, while the second one sets desired value inside of the cell. To separate column argument from the value argument use comma. + +\paragraph{String -- string(``column'', ``value'')} +String in programmers language is often\footnote{Often, not always. There are different programming languages using slightly different terms.} +just a word for anything composed of characters. In case of \OCS{} this is in fact true for every value inside the column that is not composed +of the pure numbers. Even columns containing only ``true`` and ``false`` values can be targeted by the string expression\footnote{There is no +Boolean (``true'' or ``false'') value in the \OCS. You should use string for those.}. String evaluates to true, +when record contains in the specified column exactly the same value as specified. + +Since majority of the columns contain string values, string is among the most often used expressions. Examples: +\begin{itemize} + \item \mono{string(``Record Type'', ``Weapon'')} -- will evaluate to true for all records containing \mono{Weapon} in the \mono{Record Type} column cell. + This group contains every weapon (including arrows and bolts) found in the game. + \item \mono{string(``Portable'', ``true'')} -- will evaluate to true for all records containing word true inside \mono{Portable} column cell. + This group contains every portable light sources (lanterns, torches etc.). +\end{itemize} +This is probably enough to create around 90 string filters you will eventually need. However, this expression is even more powerful +-- it accepts regular expressions (also called regexps). Regular expressions is a way to create string criteria that will be matched +by one than just one specific value in the column. For instance, you can display both left and right gauntlets with the following expression: +\mono{string("armor type", ".* gauntlet"))} because \mono{.*} in regexps means just: ``anything''. This filter says: please, show me ``any'' gauntlet. +There are left and right gauntlets in the morrowind so this will evaluate to true for both. Simple, isn't it? + +Creating regexps can be a difficult and annoying -- especially when you need complex criteria. On the other hand, we are under impression +that in reality complex expressions are needed only in sporadic cases. In fact, the truth is: that most of the time only already mentioned +\mono{.*} is needed and therefore the following description of regexps can be skipped by vast majority of readers. + +Before working with Regular Expressions, you should understand what actually are regular expressions. Essentially, the idea is simple: +when you are writing any word, you are using strictly defined letters -- that is: letters create a word. What you want to do with regular +expression is to use set of rules that will match to many words. It is not that difficult to see what it's needed to do so: first, +you will clearly need way to determinate what letters you want to match (word is composed by letters). + +Before introducing other ways to choose between characters, I want explain anchors. Anchors allows you to decide where to ``look'' in the string. +You surely should know about \mono{\textasciicircum} anchor and \mono{\textdollar}. Putting \mono{\textasciicircum} will tell to \OCS{} +to look on the beginning of string, while \mono{\textdollar} is used to mark the end of it. For instance, pattern +\mono{\textasciicircum{}Pink.* elephant.\textdollar} will match any sentence beginning with the word \mono{Pink} and ending with +\mono{ elephant.}. Pink fat elephant. Pink cute elephant. It does not matter what is in between, because \mono{.*} is used. + +You have already seen the power of the simple \mono{.*}. But what if you want to chose between only two (or more) letters? Well, this is when +\mono{[|]} comes in handy. If you write something like: \mono{\textasciicircum[a|k].*} you are simply telling \OCS{} to filter anything that +starts with either \mono{a} or \mono{k}. Using \mono{\textasciicircum[a|k|l].*} will work in the same manner, but it will also cover +strings starting with \mono{l}. + +What if you want to match more than just one latter? Just use \mono{(|)}. it is pretty similar to the above one letter as you see, but it is +used to fit more than just one character. For instance: \mono{\textasciicircum(Pink|Green).* (elephant|crocodile).\textdollar} will be +true for all sentences starting with \mono{Pink} or \mono{Green} and ending with either \mono{elephant.} or \mono{crocodile.}. + +Regular expressions are not the main topic of this manual. If you wish to learn more on this subject please, read the documentation on +Qt regular expressions syntax, or TRE regexp syntax (it is almost like in Qt). Above is just enough to use \OCS{} effectively to be sure. + +\paragraph{Value -- value(``value'', (``open'', ``close''))} +While string expression covers vast group of columns containing string values, there are in fact columns with just numerical values like +``weight``. To filter those we need a value expression. This one works in similar manner to the string filter: first token name and criteria +inside brackets. Clearly, conditions should hold column to test in. However in this case wanted value is specified as a range. +As you would imagine the range can be specified as including a border value, or excluding. We are using two types of brackets for this: +\begin{itemize} + \item To include value use [] brackets. For value equal 5, expression \mono{value(something, [5, 10])} will evaluate to true. + \item To exclude value use () brackets. For value equal 5, expression \mono{value(something, (5, 10))} will evaluate to false. + \item Mixing brackets is completely legal. For value equal 10, expression \mono{value(something, [5, 10)} will evaluate to true. + The same expression will evaluate to false for value equal 10. +\end{itemize} + +\paragraph{``true'' and ``false''} +Nullary \textit{true} and \textit{false} do not accept any arguments, and always evaluates to true (in case of \textit{true}) +and false (in case of \textit{false}) no matter what. The main usage of this expressions is the give users ability to quickly +disable some part of the filter that makes heavy use of the logical expressions. + +\subsubsection{Logical expressions} +This subsection takes care of two remaining groups of expressions: binary and unary. The only unary expression present in the \OCS{} is logical +\textit{not}, while the remaining binary expressions are: \textit{or}, \textit{and}. This clearly makes them (from the user point of view) +belonging to the same group of logical expressions. + +\paragraph{not -- not expression()} +Sometimes you may be in need of reversing the output of the expression. This is where \textit{not} comes in handy. Adding \textit{not} before +expression will revert it: if expression was returning true, it will return false; if it was returning false, it will return true. Brackets +are not needed: \textit{not} will revert only the first expression following it. + +To show this on know example, let's consider the \mono{string("armor type", ".* gauntlet"))} filter. As we mentioned earlier this will return true +for every gauntlet found in game. In order to show everything, but gauntlets we simply do \mono{not string("armor type", ".* gauntlet"))}. +This is probably not the most useful filter on earth, but this is not a surprise: real value of \textit{not} expression shines when combined with +\textit{or}, \textit{and} filters. + +\paragraph{or -- or(expression1(), expression2())} +\textit{Or} is a expression that will return true if one of the arguments evaluates to true. You can use two or more arguments, separated by the comma. + +\textit{Or} expression is useful when showing two different group of records is needed. For instance the standard actor filter is using the following +\mono{or(string(``record type'', npc), string(``record type'', creature))} and will show both npcs and creatures. + +\paragraph{and -- and(expression1(), expression2())} +\textit{And} is a expression that will return true if all arguments evaluates to true. As in the case of ``or'' you can use two or more arguments, +separated by a comma. +As we mentioned earlier in the \textit{not} filter, combining \textit{not} with \textit{and} can be very useful. For instance to show all armor types, +excluding gauntlets you can write the following: \mono{and (not string(``armor type'', ``.* gauntlet''), string(``Record Type'', ``Armor''))}. + +\subsubsection{Creating and saving filter} +In order to create and save new filter, you should go to the filters table, right click and select option ``add record'' from the context menu. +A horizontal widget group at the bottom of the table should show up. From there you should select a namespace responsible for scope of +the filter (described earlier) and desired ID of the filter. After pressing OK button new entry will show up in the filters table. This filter +does nothing at the moment, since it still lacks expressions. In order to add your formula simply double click the filter cell of the new entry +and write it down there. +Done! You are free to use your filter. + +\subsubsection{Replacing the default filters set} +OpenCS allows you to substitute default filters set provided by us, with your own filters. In order to do so you should create a new project, +add desired filters, remove undesired and save. Rename the file to the ``defaultfilters'' (do not forget to remove .omwaddon.project extension) +and place it inside your configuration directory. + +The file acts as template for all new project files from now. If you wish to go back to the old default set, simply rename or remove the custom file. diff --git a/manual/opencs/img/water.png b/manual/opencs/img/water.png new file mode 100644 index 0000000000..885c2d9a73 Binary files /dev/null and b/manual/opencs/img/water.png differ diff --git a/manual/opencs/main.tex b/manual/opencs/main.tex new file mode 100644 index 0000000000..1d8aebe408 --- /dev/null +++ b/manual/opencs/main.tex @@ -0,0 +1,34 @@ +\documentclass[american]{article} +\usepackage[T1]{fontenc} +\usepackage{babel} +\usepackage{txfonts} % Public Times New Roman text & math font +\usepackage[pdftex]{graphicx} +\usepackage[final,colorlinks,pdftex,pdfpagelabels=true]{hyperref} +\author{OpenMW Team} + +\def\pdfBorderAttrs{/Border [0 0 0] } % No border arround Links + +\def\CPP{{C\kern-.05em\raise.23ex\hbox{+\kern-.05em+}}} +\hypersetup{% + pdfauthor={Copyright \textcopyright{} OpenMW Team }, + pdftitle={OpenCS user manual} +} +\def\mono{\texttt} +\def\MW{\textit{Morrowind\texttrademark{}}} +\def\TB{\textit{Tribunal}} +\def\BM{\textit{Bloodmon}} +\def\BS{Bethesda Softworks} +\def\OMW{\hbox{OpenMW}} +\def\OCS{\hbox{OpenCS}} + +\begin{document} + +\title{OpenCS User Manual} +\maketitle +\tableofcontents{} +\input{files_and_directories} +\input{creating_file} +\input{windows} +\input{tables} +\input{filters} +\end{document} diff --git a/manual/opencs/tables.tex b/manual/opencs/tables.tex new file mode 100644 index 0000000000..e7cc06735a --- /dev/null +++ b/manual/opencs/tables.tex @@ -0,0 +1,103 @@ +\section{Tables} + +\subsection{Introduction} +If you have launched \OCS{} already and played around with it for a bit, you have probably gotten the impression that it contains lots of tables. +You'd be spot on: \OCS{} is built around using tables. This does not mean it works just like Microsoft Excel or Libre Office Calc, though. +Due to the vast amounts of information involved with \MW, tables just made the most sense. You have to be able to spot information quickly +and be able to change them on the fly. +Let's browse through the various screens and see what all these tables show. + +\subsection{Used Terms} + +\subsubsection{Glossary} + +\begin{description} + \item[Record:] An entry in \OCS{} representing an item, location, sound, NPC or anything else. + + \item[Reference, Referenceable:] When an item is placed in the world, it does not create a new record each time. For example, the game world might + contain a lot of exquisite belts on different NPCs and in many crates, but they all refer to one specific record: the Exquisite Belt record. + In this case, all those belts in crates and on NPCs are references. The central Exquisite Belt record is called a referenceable. This allows modders + to make changes to all items of the same type. For example, if you want all exquisite belts to have 4000 enchantment points rather than 400, you will + only need to change the referenceable Exquisite Belt rather than all exquisite belts references individually. +\end{description} + +\subsubsection{Recurring Terms} +Some columns are recurring throughout \OCS. They show up in (nearly) every table in \OCS. + +\begin{description} +\item[ID] Each item, location, sound, etc. gets the same unique identifier in both \OCS{} and \MW. This is usually a very self-explanatory name. +For example, the ID for the (unique) black pants of Caius Cosades is ``Caius\_pants''. This allows you to manipulate the game in many ways. For example, +you could add these pants to your inventory by simply opening the console and write: ``player->addItem Caius\_pants''. Either way, in both Morrowind +and \OCS, the ID is the primary way to identify all these different parts of the game. %Wrong! Cells do not have ID, only name. +\item[Modified] This column shows what has happened (if something has happened) to this record. There are four possible states in which it can exist. +\item[Base] means that this record is part of the base game and is in its original state. Usually, if you create a mod, the base game is Morrowind with +optionally the Bloodmoon and Tribunal expansions. +\item[Added] means that this record was not in the base game and has been added by a~modder. +\item[Modified] means that the record is part of the base game, but has been changed in some way. +\item[Deleted] means that this record used to be part of the base game, but has been removed as an entry. This does not mean, however, that the occurrences +in the game itself have been removed! For example, if you remove the CharGen\_Bed entry from morrowind.esm, it does not mean the bedroll in the basement +of the Census and Excise Office in Seyda Neen is gone. You're going to have to delete that reference yourself or make sure that that object is replaced +by something that still exists otherwise you will get crashes in the worst case scenario. +\end{description} + +\subsection{World Screens} +The contents of the game world can be changed by choosing one of the options in the appropriate menu at the top of the screen. + +\subsubsection{Regions} +This describes the general areas of Vvardenfell. Each of these areas has different rules about things such as encounters and weather. + +\begin{description} + \item[Name:] This is how the game will show your location in-game. + \item[Map Colour:] This is a six-digit hexidecimal representation of the colour used to identify the region on the map available in + World > Region Map. If you do not have an application with a colour picker, you can use your favourite search engine to find a colour picker online. + \item[Sleep Encounter:] These are the rules for what kind of enemies you might encounter when you sleep outside in the wild. +\end{description} + +\subsubsection{Cells} +Expansive worlds such as Vvardenfell, with all its items, NPCs, etc. have a lot going on simultaneously. But if you are in Balmora, +why would the computer need to keep track the exact locations of NPCs walking through the corridors in a Vivec canton? All that work would +be quite useless and bring your system to its knees! So the world has been divided up into squares we call "cells". Once your character enters a cell, +the game will load everything that is going on in that cell so you can interact with it. + +In the original \MW{} this could be seen when you were travelling and you would see a small loading bar at the bottom of the screen; +you had just entered a new cell and the game would have to load all the items and NPCs. The Cells screen in \OCS{} provides you with a list of cells +in the game, both the interior cells (houses, dungeons, mines, etc.) and the exterior cells (the outside world). + +\begin{description} + \item[Sleep Forbidden:] Can the player sleep on the floor? In most cities it is forbidden to sleep outside. Sleeping in the wild carries its + own risks of attack, though, and this entry lets you decide if a player should be allowed to sleep on the floor in this cell or not. + + \item[Interior Water:] Should water be rendered in this interior cell? The game world consists of an endless ocean at height 0. Then the landscape + is added. If part of the landscape goes below height 0, the player will see water. (See illustration.) + + Setting the cell's Interior Water to true tells the game that this cell is both an interior cell (inside a building, for example, rather than + in the open air) but that there still needs to be water at height 0. This is useful for dungeons or mines that have water in them. + + Setting the cell's Interior Water to false tells the game that the water at height 0 should not be used. Remember that cells that are in + the outside world are exterior cells and should thus \textit{always} be set to false! + + \item[Interior Sky:] Should this interior cell have a sky? This is a rather unique case. The \TB{} expansion took place in a city on + the mainland. Normally this would require the city to be composed of exterior cells so it has a sky, weather and the like. But if the player is + in an exterior cell and looks at his in-game map, he sees Vvardenfell with an overview of all exterior cells. The player would have to see + the city's very own map, as if he was walking around in an interior cell. + + So the developers decided to create a workaround and take a bit of both: The whole city would technically work exactly like an interior cell, + but it would need a sky as if it was an exterior cell. That is what this is. This is why the vast majority of the cells you will find in this screen + will have this option set to false: It is only meant for these "fake exteriors". + + \item[Region:] To which Region does this cell belong? This has an impact on the way the game handles weather and encounters in this area. + It is also possible for a cell not to belong to any region. + +\end{description} + +\subsubsection{Referenceables} +This is a library of all the items, triggers, containers, NPCs, etc. in the game. There are several kinds of Record Types. Depending on which type +a record is, it will need specific information to function. For example, an NPC needs a value attached to its aggression level. A chest, of course, +does not. All Record Types contain at least a~model. How else would the player see them? Usually they also have a Name, which is what you see +when you hover your reticle over the object. + +Let's go through all Record Types and discuss what you can tell \OCS{} about them. + +\begin{description} + \item[Activator:] This is an item that, when activated, starts a script or even just shows a~tooltip. +\end{description} \ No newline at end of file diff --git a/manual/opencs/windows.tex b/manual/opencs/windows.tex new file mode 100644 index 0000000000..97bb75e792 --- /dev/null +++ b/manual/opencs/windows.tex @@ -0,0 +1,54 @@ +\section{Windows} +\subsection{Introduction} +This section describes the multiple windows interface of the \OCS{} editor. This design principle was chosen in order +to extend the flexibility of the editor, especially on the multiple screens setups and on environments providing advanced +windows management features, like for instance: multiple desktops found commonly on many open source desktop environments. +However, it is enough to have a single large screen to see the advantages of this concept. + +OpenCS windows interface is easy to describe and understand. In fact we decided to minimize use of many windows concepts +applied commonly in various applications. For instance dialog windows are really hard to find in the \OCS. You are free to try, +though. + +Because of this, and the fact that we expect that user is familiar with other applications using windows this section is mostly +focused on practical ways of organizing work with the \OCS. + +\subsection{Basics} +After starting \OCS{} and choosing content files to use a editor window should show up. It probably does not look surprising: +there is a menubar at the top, and there is a~large empty area. That is it: a brand new \OCS{} window contains only menubar +and statusbar. In order to make it a little bit more useful you probably want to enable some panels\footnote{Also known as widgets.}. +You are free to do so, just try to explore the menubar. + +You probably founded out the way to enable and disable some interesting tables, but those will be described later. For now, let's +just focus on the windows itself. + +\paragraph{Creating new windows} +is easy! Just visit view menu, and use the ``New View'' item. Suddenly, out of the blue a new window will show up. As you would expect, +it is also blank, and you are free to add any of the \OCS{} panels. + +\paragraph{Closing opened window} +is also easy! Simply close that window decoration button. We suspect that you knew that already, but better to be sure. +Closing last \OCS{} window will also terminate application session. + +\paragraph{Multi-everything} +is the main foundation of \OCS{} interface. You are free to create as many windows as you want to, free to populate it with +any panels you may want to, and move everything as you wish to -- even if it makes no sense at all. If you just got crazy idea and +you are wonder if you are able to have one hundred \OCS{} windows showing panels of the same type, well most likely you are +able to do so. + +The principle behind this design decision is easy to see for \BS{} made editor, but maybe not so clear for users who are +just about to begin their wonderful journey of modding. + +\subsection{Advanced} +So why? Why this is created in such manner. The answer is frankly simple: because it is effective. When creating a mod, you often +have to work only with just one table. For instance you are just balancing weapons damage and other statistics. It makes sense +to have all the space for just that one table. More often, you are required to work with two and switch them from time to time. +All major graphical environments commonly present in operating systems comes with switcher feature, that is a key shortcut to change +active window. It is very effective and fast when you have only two windows, each holding only one table. Sometimes you have to work +with two at the time, and with one from time to time. Here, you can have one window holding two tables, and second holding just one. + +OpenCS is designed to simply make sense and do not slowdown users. It is as simple as possible (but not simpler), and uses one +flexible approach in all cases. + +There is no point in digging deeper in the windows of \OCS. Let's explore panels, starting with tables. + +%We should write some tips and tricks here. \ No newline at end of file diff --git a/readme.txt b/readme.txt index afcfadea3d..5b9aafcb34 100644 --- a/readme.txt +++ b/readme.txt @@ -36,49 +36,58 @@ https://wiki.openmw.org/index.php?title=Development_Environment_Setup THE DATA PATH -The data path tells OpenMW where to find your Morrowind files. From 0.12.0 on OpenMW should be able to +The data path tells OpenMW where to find your Morrowind files. If you run the launcher, OpenMW should be able to pick up the location of these files on its own, if both Morrowind and OpenMW are installed properly (installing Morrowind under WINE is considered a proper install). -If that does not work for you, please check if you have any leftover openmw.cfg files from versions earlier than 0.12.0. These can interfere with the configuration process, so try to remove then. - -If you are running OpenMW without installing it, you still need to manually adjust the data path. Create a text file named openmw.cfg in the location of the binary and enter the following line: - -data=path to your data directory - -(where you replace "path to your data directory" with the actual location of your data directory) - - COMMAND LINE OPTIONS Syntax: openmw Allowed options: - --help print help message - --version print version information and quit - --data arg (=data) set data directories (later directories have higher priority) - --data-local arg set local data directory (highest priority) - --resources arg (=resources) set resources directory - --start arg (=Beshara) set initial cell - --master arg master file(s) - --plugin arg plugin file(s) - --anim-verbose [=arg(=1)] (=0) output animation indices files - --debug [=arg(=1)] (=0) debug mode - --nosound [=arg(=1)] (=0) disable all sounds - --script-verbose [=arg(=1)] (=0) verbose script output - --script-all [=arg(=1)] (=0) compile all scripts (excluding dialogue scripts) at startup - --script-console [=arg(=1)] (=0) enable console-only script functionality - --script-run arg select a file containing a list of console commands that is executed on startup - --new-game [=arg(=1)] (=0) activate char gen/new game mechanics - --fs-strict [=arg(=1)] (=0) strict file system handling (no case folding) - --encoding arg (=win1252) Character encoding used in OpenMW game messages: - - win1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages - - win1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages - - win1252 - Western European (Latin) alphabet, used by default - - --fallback arg fallback values + --help print help message + --version print version information and quit + --data arg (=data) set data directories (later directories + have higher priority) + --data-local arg set local data directory (highest + priority) + --fallback-archive arg (=fallback-archive) + set fallback BSA archives (later + archives have higher priority) + --resources arg (=resources) set resources directory + --start arg (=Beshara) set initial cell + --content arg content file(s): esm/esp, or + omwgame/omwaddon + --anim-verbose [=arg(=1)] (=0) output animation indices files + --no-sound [=arg(=1)] (=0) disable all sounds + --script-verbose [=arg(=1)] (=0) verbose script output + --script-all [=arg(=1)] (=0) compile all scripts (excluding dialogue + scripts) at startup + --script-console [=arg(=1)] (=0) enable console-only script + functionality + --script-run arg select a file containing a list of + console commands that is executed on + startup + --new-game [=arg(=1)] (=0) activate char gen/new game mechanics + --fs-strict [=arg(=1)] (=0) strict file system handling (no case + folding) + --encoding arg (=win1252) Character encoding used in OpenMW game + messages: + + win1250 - Central and Eastern European + such as Polish, Czech, Slovak, + Hungarian, Slovene, Bosnian, Croatian, + Serbian (Latin script), Romanian and + Albanian languages + + win1251 - Cyrillic alphabet such as + Russian, Bulgarian, Serbian Cyrillic + and other languages + + win1252 - Western European (Latin) + alphabet, used by default + --fallback arg fallback values + --no-grab Don't grab mouse cursor + --activate-dist arg (=-1) activation distance override CHANGELOG