diff --git a/CMakeLists.txt b/CMakeLists.txt index 392fdfc66..7f349e223 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,7 @@ set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/) message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) -set(OPENMW_VERSION_MINOR 29) +set(OPENMW_VERSION_MINOR 30) set(OPENMW_VERSION_RELEASE 0) set(OPENMW_VERSION_COMMITHASH "") @@ -36,18 +36,8 @@ if(EXISTS ${PROJECT_SOURCE_DIR}/.git) string(REGEX REPLACE "^openmw-[0-9]+\\.([0-9]+).*" "\\1" GIT_VERSION_MINOR "${VERSION}") string(REGEX REPLACE "^openmw-[0-9]+\\.[0-9]+\\.([0-9]+).*" "\\1" GIT_VERSION_RELEASE "${VERSION}") - set(GIT_VERSION "${GIT_VERSION_MAJOR}.${GIT_VERSION_MINOR}.${GIT_VERSION_RELEASE}") - - if(NOT ${OPENMW_VERSION} STREQUAL ${GIT_VERSION}) - message(FATAL_ERROR "Silly Zini forgot to update the version again...") - else(NOT ${OPENMW_VERSION} STREQUAL ${GIT_VERSION}) - set(OPENMW_VERSION_MAJOR ${GIT_VERSION_MAJOR}) - set(OPENMW_VERSION_MINOR ${GIT_VERSION_MINOR}) - set(OPENMW_VERSION_RELEASE ${GIT_VERSION_RELEASE}) - - set(OPENMW_VERSION_COMMITHASH "${COMMITHASH}") - set(OPENMW_VERSION_TAGHASH "${TAGHASH}") - endif(NOT ${OPENMW_VERSION} STREQUAL ${GIT_VERSION}) + set(OPENMW_VERSION_COMMITHASH "${COMMITHASH}") + set(OPENMW_VERSION_TAGHASH "${TAGHASH}") message(STATUS "OpenMW version ${OPENMW_VERSION}") else(MATCH) @@ -86,8 +76,6 @@ option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest ang GMock fram # Sound source selection option(USE_FFMPEG "use ffmpeg for sound" ON) -option(USE_AUDIERE "use audiere for sound" ON) -option(USE_MPG123 "use mpg123 + libsndfile for sound" ON) # OS X deployment option(OPENMW_OSX_DEPLOYMENT OFF) @@ -131,6 +119,7 @@ set(OENGINE_OGRE ) set(OENGINE_GUI + ${LIBDIR}/openengine/gui/loglistener.cpp ${LIBDIR}/openengine/gui/manager.cpp ${LIBDIR}/openengine/gui/layout.hpp ) @@ -171,27 +160,6 @@ if (USE_FFMPEG) endif (FFMPEG_FOUND) endif (USE_FFMPEG) -if (USE_AUDIERE AND NOT GOT_SOUND_INPUT) - find_package(Audiere) - if (AUDIERE_FOUND) - set(SOUND_INPUT_INCLUDES ${SOUND_INPUT_INCLUDES} ${AUDIERE_INCLUDE_DIR}) - set(SOUND_INPUT_LIBRARY ${SOUND_INPUT_LIBRARY} ${AUDIERE_LIBRARY}) - set(SOUND_DEFINE ${SOUND_DEFINE} -DOPENMW_USE_AUDIERE) - set(GOT_SOUND_INPUT 1) - endif (AUDIERE_FOUND) -endif (USE_AUDIERE AND NOT GOT_SOUND_INPUT) - -if (USE_MPG123 AND NOT GOT_SOUND_INPUT) - find_package(MPG123 REQUIRED) - find_package(SNDFILE REQUIRED) - if (MPG123_FOUND AND SNDFILE_FOUND) - set(SOUND_INPUT_INCLUDES ${SOUND_INPUT_INCLUDES} ${MPG123_INCLUDE_DIR} ${SNDFILE_INCLUDE_DIR}) - set(SOUND_INPUT_LIBRARY ${SOUND_INPUT_LIBRARY} ${MPG123_LIBRARY} ${SNDFILE_LIBRARY}) - set(SOUND_DEFINE ${SOUND_DEFINE} -DOPENMW_USE_MPG123) - set(GOT_SOUND_INPUT 1) - endif (MPG123_FOUND AND SNDFILE_FOUND) -endif (USE_MPG123 AND NOT GOT_SOUND_INPUT) - if (NOT GOT_SOUND_INPUT) message(WARNING "--------------------") message(WARNING "Failed to find any sound input packages") @@ -257,6 +225,9 @@ endif () set(BOOST_COMPONENTS system filesystem program_options) +if(WIN32) + set(BOOST_COMPONENTS ${BOOST_COMPONENTS} locale) +endif(WIN32) IF(BOOST_STATIC) set(Boost_USE_STATIC_LIBS ON) @@ -368,8 +339,8 @@ configure_file(${OpenMW_SOURCE_DIR}/files/openmw.cfg.local configure_file(${OpenMW_SOURCE_DIR}/files/openmw.cfg "${OpenMW_BINARY_DIR}/openmw.cfg.install") -configure_file(${OpenMW_SOURCE_DIR}/files/opencs.cfg - "${OpenMW_BINARY_DIR}/opencs.cfg") +configure_file(${OpenMW_SOURCE_DIR}/files/opencs.ini + "${OpenMW_BINARY_DIR}/opencs.ini") configure_file(${OpenMW_SOURCE_DIR}/files/opencs/defaultfilters "${OpenMW_BINARY_DIR}/resources/defaultfilters" COPYONLY) @@ -434,7 +405,6 @@ IF(NOT WIN32 AND NOT APPLE) # Install licenses INSTALL(FILES "DejaVu Font License.txt" DESTINATION "${LICDIR}" ) - INSTALL(FILES "OFL.txt" DESTINATION "${LICDIR}" ) INSTALL(FILES "extern/shiny/License.txt" DESTINATION "${LICDIR}" RENAME "Shiny License.txt" ) ENDIF (DPKG_PROGRAM) @@ -451,7 +421,7 @@ IF(NOT WIN32 AND NOT APPLE) INSTALL(FILES "${OpenMW_BINARY_DIR}/transparency-overrides.cfg" DESTINATION "${SYSCONFDIR}" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "openmw") INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.cfg.install" DESTINATION "${SYSCONFDIR}" RENAME "openmw.cfg" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "openmw") IF(BUILD_OPENCS) - INSTALL(FILES "${OpenMW_BINARY_DIR}/opencs.cfg" DESTINATION "${SYSCONFDIR}" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "opencs") + INSTALL(FILES "${OpenMW_BINARY_DIR}/opencs.ini" DESTINATION "${SYSCONFDIR}" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "opencs") ENDIF(BUILD_OPENCS) # Install resources @@ -480,7 +450,7 @@ if(WIN32) ENDIF(BUILD_MWINIIMPORTER) IF(BUILD_OPENCS) INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/opencs.exe" DESTINATION ".") - INSTALL(FILES "${OpenMW_BINARY_DIR}/opencs.cfg" DESTINATION ".") + INSTALL(FILES "${OpenMW_BINARY_DIR}/opencs.ini" DESTINATION ".") ENDIF(BUILD_OPENCS) INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION ".") @@ -618,6 +588,7 @@ if (WIN32) 4706 # Assignment in conditional expression 4738 # Storing 32-bit float result in memory, possible loss of performance 4986 # Undocumented warning that occurs in the crtdbg.h file + 4987 # nonstandard extension used (triggered by setjmp.h) 4996 # Function was declared deprecated # cause by ogre extensivly @@ -634,7 +605,9 @@ if (WIN32) 4305 # Truncating value (double to float, for example) 4309 # Variable overflow, trying to store 128 in a signed char for example 4355 # Using 'this' in member initialization list + 4505 # Unreferenced local function has been removed 4701 # Potentially uninitialized local variable used + 4702 # Unreachable code 4800 # Boolean optimization warning, e.g. myBool = (myInt != 0) instead of myBool = myInt ) @@ -642,19 +615,31 @@ if (WIN32) set(WARNINGS "${WARNINGS} /wd${d}") endforeach(d) - set_target_properties(shiny PROPERTIES COMPILE_FLAGS ${WARNINGS}) - set_target_properties(shiny.OgrePlatform PROPERTIES COMPILE_FLAGS ${WARNINGS}) + # boost::wave has a few issues with signed / unsigned conversions, so we suppress those here + set(SHINY_WARNINGS "${WARNINGS} /wd4245") + set_target_properties(shiny PROPERTIES COMPILE_FLAGS ${SHINY_WARNINGS}) + # there's an unreferenced local variable in the ogre platform, suppress it + set(SHINY_OGRE_WARNINGS "${WARNINGS} /wd4101") + set_target_properties(shiny.OgrePlatform PROPERTIES COMPILE_FLAGS ${SHINY_OGRE_WARNINGS}) + set_target_properties(sdl4ogre PROPERTIES COMPILE_FLAGS ${WARNINGS}) + set_target_properties(oics PROPERTIES COMPILE_FLAGS ${WARNINGS}) set_target_properties(components PROPERTIES COMPILE_FLAGS ${WARNINGS}) if (BUILD_LAUNCHER) set_target_properties(omwlauncher PROPERTIES COMPILE_FLAGS ${WARNINGS}) endif (BUILD_LAUNCHER) set_target_properties(openmw PROPERTIES COMPILE_FLAGS ${WARNINGS}) - if (BUILD_BSATOOL) + if (BUILD_BSATOOL) set_target_properties(bsatool PROPERTIES COMPILE_FLAGS ${WARNINGS}) - endif (BUILD_BSATOOL) + endif (BUILD_BSATOOL) if (BUILD_ESMTOOL) set_target_properties(esmtool PROPERTIES COMPILE_FLAGS ${WARNINGS}) endif (BUILD_ESMTOOL) + if (BUILD_OPENCS) + set_target_properties(opencs PROPERTIES COMPILE_FLAGS ${WARNINGS}) + endif (BUILD_OPENCS) + if (BUILD_MWINIIMPORTER) + set_target_properties(mwiniimport PROPERTIES COMPILE_FLAGS ${WARNINGS}) + endif (BUILD_MWINIIMPORTER) endif(MSVC) # Same for MinGW @@ -687,7 +672,7 @@ if (APPLE) install(FILES "${OpenMW_BINARY_DIR}/openmw.cfg.install" RENAME "openmw.cfg" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) install(FILES "${OpenMW_BINARY_DIR}/settings-default.cfg" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) install(FILES "${OpenMW_BINARY_DIR}/transparency-overrides.cfg" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) - install(FILES "${OpenMW_BINARY_DIR}/opencs.cfg" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) + install(FILES "${OpenMW_BINARY_DIR}/opencs.ini" DESTINATION "${INSTALL_SUBDIR}" COMPONENT Runtime) set(CPACK_GENERATOR "DragNDrop") set(CPACK_PACKAGE_VERSION ${OPENMW_VERSION}) diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index e9731d626..eef96c8c9 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -333,7 +333,7 @@ int load(Arguments& info) // Is the user interested in this record type? bool interested = true; - if (info.types.size() > 0) + if (!info.types.empty()) { std::vector::iterator match; match = std::find(info.types.begin(), info.types.end(), diff --git a/apps/esmtool/labels.cpp b/apps/esmtool/labels.cpp index 7a42e6900..ef45989ef 100644 --- a/apps/esmtool/labels.cpp +++ b/apps/esmtool/labels.cpp @@ -387,7 +387,7 @@ std::string magicEffectLabel(int idx) "sEffectSummonCreature04", "sEffectSummonCreature05" }; - if (idx >= 0 && idx <= 143) + if (idx >= 0 && idx <= 142) return magicEffectLabels[idx]; else return "Invalid"; @@ -471,7 +471,7 @@ std::string skillLabel(int idx) "Speechcraft", "Hand-to-hand" }; - if (idx >= 0 && idx <= 27) + if (idx >= 0 && idx <= 26) return skillLabels[idx]; else return "Invalid"; @@ -498,7 +498,7 @@ std::string rangeTypeLabel(int idx) "Touch", "Target" }; - if (idx >= 0 && idx <= 3) + if (idx >= 0 && idx <= 2) return rangeTypeLabels[idx]; else return "Invalid"; diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index 184d11bb4..bcf16091f 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -124,7 +124,7 @@ void printEffectList(ESM::EffectList effects) { int i = 0; std::vector::iterator eit; - for (eit = effects.mList.begin(); eit != effects.mList.end(); eit++) + for (eit = effects.mList.begin(); eit != effects.mList.end(); ++eit) { std::cout << " Effect[" << i << "]: " << magicEffectLabel(eit->mEffectID) << " (" << eit->mEffectID << ")" << std::endl; @@ -651,7 +651,7 @@ void Record::print() // Sadly, there are no DialInfos, because the loader dumps as it // loads, rather than loading and then dumping. :-( Anyone mind if // I change this? - std::vector::iterator iit; + ESM::Dialogue::InfoContainer::iterator iit; for (iit = mData.mInfo.begin(); iit != mData.mInfo.end(); iit++) std::cout << "INFO!" << iit->mId << std::endl; } @@ -707,9 +707,9 @@ void Record::print() std::cout << " Faction Reaction: " << mData.mData.mRankData[i].mFactReaction << std::endl; } - std::vector::iterator rit; + std::map::iterator rit; for (rit = mData.mReactions.begin(); rit != mData.mReactions.end(); rit++) - std::cout << " Reaction: " << rit->mReaction << " = " << rit->mFaction << std::endl; + std::cout << " Reaction: " << rit->second << " = " << rit->first << std::endl; } template<> diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index 8abdf0019..638237f34 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -214,13 +214,13 @@ QStringList Launcher::GraphicsPage::getAvailableOptions(const QString &key, Ogre uint row = 0; Ogre::ConfigOptionMap options = renderer->getConfigOptions(); - for (Ogre::ConfigOptionMap::iterator i = options.begin (); i != options.end (); i++, row++) + for (Ogre::ConfigOptionMap::iterator i = options.begin (); i != options.end (); ++i, ++row) { Ogre::StringVector::iterator opt_it; uint idx = 0; for (opt_it = i->second.possibleValues.begin(); - opt_it != i->second.possibleValues.end(); opt_it++, idx++) + opt_it != i->second.possibleValues.end(); ++opt_it, ++idx) { if (strcmp (key.toStdString().c_str(), i->first.c_str()) == 0) { result << ((key == "FSAA") ? QString("MSAA ") : QString("")) + QString::fromStdString((*opt_it).c_str()).simplified(); diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 5cf8f8a89..41d662d30 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -41,11 +41,11 @@ Launcher::MainDialog::MainDialog(QWidget *parent) // Check if the font is installed if (!fonts.contains("EB Garamond")) { - QString font = QString::fromStdString(mCfgMgr.getGlobalDataPath().string()) + QString("resources/mygui/EBGaramond-Regular.ttf"); + QString font = QString::fromUtf8(mCfgMgr.getGlobalDataPath().string().c_str()) + QString("resources/mygui/EBGaramond-Regular.ttf"); file.setFileName(font); if (!file.exists()) { - font = QString::fromStdString(mCfgMgr.getLocalPath().string()) + QString("resources/mygui/EBGaramond-Regular.ttf"); + font = QString::fromUtf8(mCfgMgr.getLocalPath().string().c_str()) + QString("resources/mygui/EBGaramond-Regular.ttf"); } fontDatabase.addApplicationFont(font); @@ -243,7 +243,7 @@ bool Launcher::MainDialog::showFirstRunDialog() } // Create the file if it doesn't already exist, else the importer will fail - QString path = QString::fromStdString(mCfgMgr.getUserConfigPath().string()) + QString("openmw.cfg"); + QString path = QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str()) + QString("openmw.cfg"); QFile file(path); if (!file.exists()) { @@ -358,7 +358,7 @@ bool Launcher::MainDialog::setupLauncherSettings() { mLauncherSettings.setMultiValueEnabled(true); - QString userPath = QString::fromStdString(mCfgMgr.getUserConfigPath().string()); + QString userPath = QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str()); QStringList paths; paths.append(QString("launcher.cfg")); @@ -464,8 +464,8 @@ bool Launcher::expansions(Launcher::UnshieldThread& cd) bool Launcher::MainDialog::setupGameSettings() { - QString userPath = QString::fromStdString(mCfgMgr.getUserConfigPath().string()); - QString globalPath = QString::fromStdString(mCfgMgr.getGlobalPath().string()); + QString userPath = QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str()); + QString globalPath = QString::fromUtf8(mCfgMgr.getGlobalPath().string().c_str()); // Load the user config file first, separately // So we can write it properly, uncontaminated @@ -594,7 +594,7 @@ bool Launcher::MainDialog::setupGameSettings() while(expansions(cd)); - selectedFile = QString::fromStdString(cd.GetMWEsmPath()); + selectedFile = QString::fromUtf8(cd.GetMWEsmPath().c_str()); } #endif // WIN32 @@ -615,8 +615,8 @@ bool Launcher::MainDialog::setupGraphicsSettings() { mGraphicsSettings.setMultiValueEnabled(false); - QString userPath = QString::fromStdString(mCfgMgr.getUserConfigPath().string()); - QString globalPath = QString::fromStdString(mCfgMgr.getGlobalPath().string()); + QString userPath = QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str()); + QString globalPath = QString::fromUtf8(mCfgMgr.getGlobalPath().string().c_str()); QFile localDefault(QString("settings-default.cfg")); QFile globalDefault(globalPath + QString("settings-default.cfg")); @@ -702,7 +702,7 @@ bool Launcher::MainDialog::writeSettings() mGraphicsPage->saveSettings(); mDataFilesPage->saveSettings(); - QString userPath = QString::fromStdString(mCfgMgr.getUserConfigPath().string()); + QString userPath = QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str()); QDir dir(userPath); if (!dir.exists()) { @@ -806,7 +806,7 @@ void Launcher::MainDialog::play() msgBox.setWindowTitle(tr("No game file selected")); msgBox.setIcon(QMessageBox::Warning); msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
You do not have no game file selected.

\ + msgBox.setText(tr("
You do not have a game file selected.

\ OpenMW will not start without a game file selected.
")); msgBox.exec(); return; diff --git a/apps/launcher/settings/gamesettings.cpp b/apps/launcher/settings/gamesettings.cpp index e7e5cf1ea..2f3dd9a45 100644 --- a/apps/launcher/settings/gamesettings.cpp +++ b/apps/launcher/settings/gamesettings.cpp @@ -45,7 +45,8 @@ void Launcher::GameSettings::validatePaths() Files::PathContainer dataDirs; foreach (const QString &path, paths) { - dataDirs.push_back(Files::PathContainer::value_type(path.toStdString())); + QByteArray bytes = path.toUtf8(); + dataDirs.push_back(Files::PathContainer::value_type(std::string(bytes.constData(), bytes.length()))); } // Parse the data dirs to convert the tokenized paths @@ -53,7 +54,7 @@ void Launcher::GameSettings::validatePaths() mDataDirs.clear(); for (Files::PathContainer::iterator it = dataDirs.begin(); it != dataDirs.end(); ++it) { - QString path = QString::fromStdString(it->string()); + QString path = QString::fromUtf8(it->string().c_str()); path.remove(QChar('\"')); QDir dir(path); @@ -68,12 +69,13 @@ void Launcher::GameSettings::validatePaths() return; dataDirs.clear(); - dataDirs.push_back(Files::PathContainer::value_type(local.toStdString())); + QByteArray bytes = local.toUtf8(); + dataDirs.push_back(Files::PathContainer::value_type(std::string(bytes.constData(), bytes.length()))); mCfgMgr.processPaths(dataDirs); if (!dataDirs.empty()) { - QString path = QString::fromStdString(dataDirs.front().string()); + QString path = QString::fromUtf8(dataDirs.front().string().c_str()); path.remove(QChar('\"')); QDir dir(path); diff --git a/apps/launcher/unshieldthread.cpp b/apps/launcher/unshieldthread.cpp index 52f935710..3d0f2e5da 100644 --- a/apps/launcher/unshieldthread.cpp +++ b/apps/launcher/unshieldthread.cpp @@ -1,6 +1,6 @@ #include "unshieldthread.hpp" -#include +#include #include namespace bfs = boost::filesystem; @@ -49,7 +49,7 @@ namespace std::string read_to_string(const bfs::path& path) { - std::ifstream strstream(path.c_str(), std::ios::in | std::ios::binary); + bfs::ifstream strstream(path, std::ios::in | std::ios::binary); std::string str; strstream.seekg(0, std::ios::end); @@ -201,7 +201,7 @@ namespace add_setting("Archives", "Archive 1", "Bloodmoon.bsa", ini); } - std::ofstream inistream(ini_path.c_str()); + bfs::ofstream inistream((ini_path)); inistream << ini; inistream.close(); } diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index 648ab3ebe..3a52592ae 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -1,6 +1,5 @@ #include "importer.hpp" -#include -#include + #include #include #include @@ -9,6 +8,10 @@ #include #include +#include +#include + +namespace bfs = boost::filesystem; MwIniImporter::MwIniImporter() : mVerbose(false) @@ -661,7 +664,7 @@ MwIniImporter::multistrmap MwIniImporter::loadIniFile(const std::string& filenam std::string section(""); MwIniImporter::multistrmap map; - boost::iostreams::streamfile(filename.c_str()); + bfs::ifstream file((bfs::path(filename))); ToUTF8::Utf8Encoder encoder(mEncoding); std::string line; @@ -674,6 +677,10 @@ MwIniImporter::multistrmap MwIniImporter::loadIniFile(const std::string& filenam line = line.substr(0, line.length()-1); } + if(line.empty()) { + continue; + } + if(line[0] == '[') { int pos = line.find(']'); if(pos < 2) { @@ -690,10 +697,6 @@ MwIniImporter::multistrmap MwIniImporter::loadIniFile(const std::string& filenam line = line.substr(0,comment_pos); } - if(line.empty()) { - continue; - } - int pos = line.find("="); if(pos < 1) { continue; @@ -720,7 +723,7 @@ MwIniImporter::multistrmap MwIniImporter::loadCfgFile(const std::string& filenam std::cout << "load cfg file: " << filename << std::endl; MwIniImporter::multistrmap map; - boost::iostreams::streamfile(filename.c_str()); + bfs::ifstream file((bfs::path(filename))); std::string line; while (std::getline(file, line)) { @@ -858,7 +861,7 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini) co } } -void MwIniImporter::writeToFile(boost::iostreams::stream &out, const multistrmap &cfg) { +void MwIniImporter::writeToFile(std::ostream &out, const multistrmap &cfg) { for(multistrmap::const_iterator it=cfg.begin(); it != cfg.end(); ++it) { for(std::vector::const_iterator entry=it->second.begin(); entry != it->second.end(); ++entry) { diff --git a/apps/mwiniimporter/importer.hpp b/apps/mwiniimporter/importer.hpp index 784980e09..72b14ba75 100644 --- a/apps/mwiniimporter/importer.hpp +++ b/apps/mwiniimporter/importer.hpp @@ -1,12 +1,11 @@ #ifndef MWINIIMPORTER_IMPORTER #define MWINIIMPORTER_IMPORTER 1 -#include -#include #include #include #include #include +#include #include @@ -24,7 +23,7 @@ class MwIniImporter { void mergeFallback(multistrmap &cfg, const multistrmap &ini) const; void importGameFiles(multistrmap &cfg, const multistrmap &ini) const; void importArchives(multistrmap &cfg, const multistrmap &ini) const; - static void writeToFile(boost::iostreams::stream &out, const multistrmap &cfg); + static void writeToFile(std::ostream &out, const multistrmap &cfg); private: static void insertMultistrmap(multistrmap &cfg, const std::string& key, const std::string& value); diff --git a/apps/mwiniimporter/main.cpp b/apps/mwiniimporter/main.cpp index 364a6b1a4..c10103cd6 100644 --- a/apps/mwiniimporter/main.cpp +++ b/apps/mwiniimporter/main.cpp @@ -1,14 +1,59 @@ #include "importer.hpp" -#include #include +#include + #include #include +#include +#include namespace bpo = boost::program_options; +namespace bfs = boost::filesystem; +#ifndef _WIN32 int main(int argc, char *argv[]) { +#else + +// Include on Windows only +#include + +class utf8argv +{ +public: + + utf8argv(int argc, wchar_t *wargv[]) + { + args.reserve(argc); + argv = new const char *[argc]; + + for (int i = 0; i < argc; ++i) { + args.push_back(boost::locale::conv::utf_to_utf(wargv[i])); + argv[i] = args.back().c_str(); + } + } + + ~utf8argv() { delete[] argv; } + char **get() const { return const_cast(argv); } + +private: + + const char **argv; + std::vector args; +}; + +/* The only way to pass Unicode on Winodws with CLI is to use wide + characters interface which presents UTF-16 encoding. The rest of + OpenMW application stack assumes UTF-8 encoding, therefore this + conversion. + For boost::filesystem::path::imbue see components/files/windowspath.cpp +*/ +int wmain(int argc, wchar_t *wargv[]) { + utf8argv converter(argc, wargv); + char **argv = converter.get(); + boost::filesystem::path::imbue(boost::locale::generator().generate("")); +#endif bpo::options_description desc("Syntax: mwiniimporter inifile configfile\nAllowed options"); bpo::positional_options_description p_desc; desc.add_options() @@ -94,7 +139,7 @@ int main(int argc, char *argv[]) { } std::cout << "write to: " << outputFile << std::endl; - boost::iostreams::stream file(outputFile); + bfs::ofstream file((bfs::path(outputFile))); importer.writeToFile(file, cfg); return 0; diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index cbe90b1d3..4576432e1 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -5,11 +5,11 @@ opencs_units (. editor) set (CMAKE_BUILD_TYPE DEBUG) opencs_units (model/doc - document operation saving + document operation saving documentmanager loader ) opencs_units_noqt (model/doc - documentmanager stage savingstate savingstages + stage savingstate savingstages ) opencs_hdrs_noqt (model/doc @@ -44,7 +44,7 @@ opencs_units_noqt (model/tools opencs_units (view/doc viewmanager view operations operation subview startup filedialog newgame - filewidget adjusterwidget + filewidget adjusterwidget loader ) @@ -60,7 +60,7 @@ opencs_hdrs_noqt (view/doc opencs_units (view/world table tablesubview scriptsubview util regionmapsubview tablebottombox creator genericcreator cellcreator referenceablecreator referencecreator scenesubview scenetoolbar scenetool - scenetoolmode infocreator scriptedit dialoguesubview previewsubview regionmap + scenetoolmode infocreator scriptedit dialoguesubview previewsubview regionmap dragrecordtable ) opencs_units (view/render @@ -88,34 +88,30 @@ opencs_units_noqt (view/tools ) opencs_units (view/settings - abstractblock - proxyblock - abstractwidget - usersettingsdialog - datadisplayformatpage - windowpage + settingwindow + dialog + page + view + booleanview + textview + listview + rangeview + resizeablestackedwidget + spinbox ) opencs_units_noqt (view/settings - abstractpage - blankpage - groupblock - customblock - groupbox - itemblock - settingwidget - toggleblock - support + frame ) opencs_units (model/settings usersettings - settingcontainer + setting + connector ) -opencs_units_noqt (model/settings +opencs_hdrs_noqt (model/settings support - settingsitem ) opencs_units_noqt (model/filter @@ -149,6 +145,10 @@ if(WIN32) endif(WIN32) set(BOOST_COMPONENTS system filesystem program_options thread wave) +if(WIN32) + set(BOOST_COMPONENTS ${BOOST_COMPONENTS} locale) +endif(WIN32) + find_package(Boost REQUIRED COMPONENTS ${BOOST_COMPONENTS}) find_package(Qt4 COMPONENTS QtCore QtGui QtNetwork REQUIRED) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 87660a60b..b00373587 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -20,14 +20,15 @@ #include "model/world/data.hpp" CS::Editor::Editor (OgreInit::OgreInit& ogreInit) -: mDocumentManager (mCfgMgr), mViewManager (mDocumentManager), +: mUserSettings (mCfgMgr), mDocumentManager (mCfgMgr), mViewManager (mDocumentManager), mIpcServerName ("org.openmw.OpenCS") { std::pair > config = readConfig(); setupDataFiles (config.first); - CSMSettings::UserSettings::instance().loadSettings ("opencs.cfg"); + CSMSettings::UserSettings::instance().loadSettings ("opencs.ini"); + mSettings.setModel (CSMSettings::UserSettings::instance()); ogreInit.init ((mCfgMgr.getUserConfigPath() / "opencsOgre.log").string()); @@ -37,6 +38,11 @@ CS::Editor::Editor (OgreInit::OgreInit& ogreInit) mNewGame.setLocalData (mLocal); mFileDialog.setLocalData (mLocal); + connect (&mDocumentManager, SIGNAL (documentAdded (CSMDoc::Document *)), + this, SLOT (documentAdded (CSMDoc::Document *))); + connect (&mDocumentManager, SIGNAL (lastDocumentDeleted()), + this, SLOT (lastDocumentDeleted())); + connect (&mViewManager, SIGNAL (newGameRequest ()), this, SLOT (createGame ())); connect (&mViewManager, SIGNAL (newAddonRequest ()), this, SLOT (createAddon ())); connect (&mViewManager, SIGNAL (loadDocumentRequest ()), this, SLOT (loadDocument ())); @@ -84,6 +90,9 @@ std::pair > CS::Editor::readConfi mCfgMgr.readConfiguration(variables, desc); + mDocumentManager.setEncoding ( + ToUTF8::calculateEncoding (variables["encoding"].as())); + mDocumentManager.setResourceDir (mResources = variables["resources"].as()); mFsStrict = variables["fs-strict"].as(); @@ -117,6 +126,13 @@ std::pair > CS::Editor::readConfi dataDirs.insert (dataDirs.end(), dataLocal.begin(), dataLocal.end()); + //iterate the data directories and add them to the file dialog for loading + for (Files::PathContainer::const_iterator iter = dataDirs.begin(); iter != dataDirs.end(); ++iter) + { + QString path = QString::fromUtf8 (iter->string().c_str()); + mFileDialog.addFiles(path); + } + return std::make_pair (dataDirs, variables["fallback-archive"].as >()); } @@ -150,9 +166,8 @@ void CS::Editor::openFiles (const boost::filesystem::path &savePath) foreach (const QString &path, mFileDialog.selectedFilePaths()) files.push_back(path.toUtf8().constData()); - CSMDoc::Document *document = mDocumentManager.addDocument (files, savePath, false); + mDocumentManager.addDocument (files, savePath, false); - mViewManager.addView (document); mFileDialog.hide(); } @@ -166,9 +181,8 @@ void CS::Editor::createNewFile (const boost::filesystem::path &savePath) files.push_back(mFileDialog.filename().toUtf8().constData()); - CSMDoc::Document *document = mDocumentManager.addDocument (files, savePath, true); + mDocumentManager.addDocument (files, savePath, true); - mViewManager.addView (document); mFileDialog.hide(); } @@ -178,9 +192,7 @@ void CS::Editor::createNewGame (const boost::filesystem::path& file) files.push_back (file); - CSMDoc::Document *document = mDocumentManager.addDocument (files, file, true); - - mViewManager.addView (document); + mDocumentManager.addDocument (files, file, true); mNewGame.hide(); } @@ -287,3 +299,13 @@ std::auto_ptr CS::Editor::setupGraphics() return factory; } + +void CS::Editor::documentAdded (CSMDoc::Document *document) +{ + mViewManager.addView (document); +} + +void CS::Editor::lastDocumentDeleted() +{ + exit (0); +} diff --git a/apps/opencs/editor.hpp b/apps/opencs/editor.hpp index 164398fb7..d88da9865 100644 --- a/apps/opencs/editor.hpp +++ b/apps/opencs/editor.hpp @@ -24,7 +24,7 @@ #include "view/doc/filedialog.hpp" #include "view/doc/newgame.hpp" -#include "view/settings/usersettingsdialog.hpp" +#include "view/settings/dialog.hpp" namespace OgreInit { @@ -43,7 +43,7 @@ namespace CS CSVDoc::ViewManager mViewManager; CSVDoc::StartupDialogue mStartup; CSVDoc::NewGameDialogue mNewGame; - CSVSettings::UserSettingsDialog mSettings; + CSVSettings::Dialog mSettings; CSVDoc::FileDialog mFileDialog; boost::filesystem::path mLocal; boost::filesystem::path mResources; @@ -85,6 +85,10 @@ namespace CS void showSettings(); + void documentAdded (CSMDoc::Document *document); + + void lastDocumentDeleted(); + private: QString mIpcServerName; diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp index eded36394..b184a1ef1 100644 --- a/apps/opencs/main.cpp +++ b/apps/opencs/main.cpp @@ -3,18 +3,24 @@ #include #include +#include #include #include +#include #include #include +#include "model/world/universalid.hpp" + #ifdef Q_OS_MAC #include #endif +Q_DECLARE_METATYPE (std::string) + class Application : public QApplication { private: @@ -42,6 +48,9 @@ int main(int argc, char *argv[]) { Q_INIT_RESOURCE (resources); + qRegisterMetaType ("std::string"); + qRegisterMetaType ("CSMWorld::UniversalId"); + OgreInit::OgreInit ogreInit; std::auto_ptr shinyFactory; diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 3ef14ee7e..f452008ac 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -8,23 +8,6 @@ #include #endif -void CSMDoc::Document::load (const std::vector::const_iterator& begin, - const std::vector::const_iterator& end, bool lastAsModified) -{ - assert (begin!=end); - - std::vector::const_iterator end2 (end); - - if (lastAsModified) - --end2; - - for (std::vector::const_iterator iter (begin); iter!=end2; ++iter) - getData().loadFile (*iter, true, false); - - if (lastAsModified) - getData().loadFile (*end2, false, false); -} - void CSMDoc::Document::addGmsts() { static const char *gmstFloats[] = @@ -2219,64 +2202,42 @@ 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.getUserDataPath() / "projects") / - (savePath.filename().string() + ".project")), - mSaving (*this, mProjectPath) +CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, + const std::vector< boost::filesystem::path >& files, bool new_, + const boost::filesystem::path& savePath, const boost::filesystem::path& resDir, + ToUTF8::FromType encoding) +: mSavePath (savePath), mContentFiles (files), mNew (new_), mData (encoding), mTools (mData), + mResDir(resDir), + mProjectPath ((configuration.getUserDataPath() / "projects") / + (savePath.filename().string() + ".project")), + mSaving (*this, mProjectPath, encoding) { - if (files.empty()) + if (mContentFiles.empty()) throw std::runtime_error ("Empty content file sequence"); - if (new_ && files.size()==1) - createBase(); - else - { - std::vector::const_iterator end = files.end(); - - if (new_) - --end; - - load (files.begin(), end, !new_); - } - - if (new_) - { - mData.setDescription (""); - mData.setAuthor (""); - } - - bool filtersFound = false; - - if (boost::filesystem::exists (mProjectPath)) - { - filtersFound = true; - } - else + if (!boost::filesystem::exists (mProjectPath)) { boost::filesystem::path locCustomFiltersPath (configuration.getUserDataPath()); locCustomFiltersPath /= "defaultfilters"; - if (boost::filesystem::exists(locCustomFiltersPath)) + if (boost::filesystem::exists (locCustomFiltersPath)) { boost::filesystem::copy_file (locCustomFiltersPath, mProjectPath); - filtersFound = true; } else { - boost::filesystem::path filters(mResDir); - filters /= "defaultfilters"; - - if (boost::filesystem::exists(filters)) - { - boost::filesystem::copy_file(filters, mProjectPath); - filtersFound = true; - } + boost::filesystem::copy_file (mResDir / "defaultfilters", mProjectPath); } } - if (filtersFound) - getData().loadFile (mProjectPath, false, true); + if (mNew) + { + mData.setDescription (""); + mData.setAuthor (""); + + if (mContentFiles.size()==1) + createBase(); + } addOptionalGmsts(); addOptionalGlobals(); @@ -2288,8 +2249,10 @@ CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, co connect (&mSaving, SIGNAL (progress (int, int, int)), this, SLOT (progress (int, int, int))); connect (&mSaving, SIGNAL (done (int)), this, SLOT (operationDone (int))); - connect (&mSaving, SIGNAL (reportMessage (const QString&, int)), - this, SLOT (reportMessage (const QString&, int))); + + connect ( + &mSaving, SIGNAL (reportMessage (const CSMWorld::UniversalId&, const std::string&, int)), + this, SLOT (reportMessage (const CSMWorld::UniversalId&, const std::string&, int))); } CSMDoc::Document::~Document() @@ -2322,11 +2285,21 @@ const boost::filesystem::path& CSMDoc::Document::getSavePath() const return mSavePath; } +const boost::filesystem::path& CSMDoc::Document::getProjectPath() const +{ + return mProjectPath; +} + const std::vector& CSMDoc::Document::getContentFiles() const { return mContentFiles; } +bool CSMDoc::Document::isNew() const +{ + return mNew; +} + void CSMDoc::Document::save() { if (mSaving.isRunning()) @@ -2358,10 +2331,11 @@ void CSMDoc::Document::modificationStateChanged (bool clean) emit stateChanged (getState(), this); } -void CSMDoc::Document::reportMessage (const QString& message, int type) +void CSMDoc::Document::reportMessage (const CSMWorld::UniversalId& id, const std::string& message, + int type) { /// \todo find a better way to get these messages to the user. - std::cout << message.toUtf8().constData() << std::endl; + std::cout << message << std::endl; } void CSMDoc::Document::operationDone (int type) diff --git a/apps/opencs/model/doc/document.hpp b/apps/opencs/model/doc/document.hpp index 201fb4342..a6f8aaae2 100644 --- a/apps/opencs/model/doc/document.hpp +++ b/apps/opencs/model/doc/document.hpp @@ -9,6 +9,8 @@ #include #include +#include + #include "../world/data.hpp" #include "../tools/tools.hpp" @@ -39,6 +41,7 @@ namespace CSMDoc boost::filesystem::path mSavePath; std::vector mContentFiles; + bool mNew; CSMWorld::Data mData; CSMTools::Tools mTools; boost::filesystem::path mProjectPath; @@ -53,10 +56,6 @@ namespace CSMDoc Document (const Document&); Document& operator= (const Document&); - void load (const std::vector::const_iterator& begin, - const std::vector::const_iterator& end, bool lastAsModified); - ///< \param lastAsModified Store the last file in Modified instead of merging it into Base. - void createBase(); void addGmsts(); @@ -72,9 +71,9 @@ namespace CSMDoc public: Document (const Files::ConfigurationManager& configuration, - const std::vector< boost::filesystem::path >& files, - const boost::filesystem::path& savePath, - const boost::filesystem::path& resDir, bool new_); + const std::vector< boost::filesystem::path >& files, bool new_, + const boost::filesystem::path& savePath, const boost::filesystem::path& resDir, + ToUTF8::FromType encoding); ~Document(); @@ -84,10 +83,15 @@ namespace CSMDoc const boost::filesystem::path& getSavePath() const; + const boost::filesystem::path& getProjectPath() const; + const std::vector& getContentFiles() const; ///< \attention The last element in this collection is the file that is being edited, /// but with its original path instead of the save path. + bool isNew() const; + ///< Is this a newly created content file? + void save(); CSMWorld::UniversalId verify(); @@ -111,7 +115,8 @@ namespace CSMDoc void modificationStateChanged (bool clean); - void reportMessage (const QString& message, int type); + void reportMessage (const CSMWorld::UniversalId& id, const std::string& message, + int type); void operationDone (int type); diff --git a/apps/opencs/model/doc/documentmanager.cpp b/apps/opencs/model/doc/documentmanager.cpp index 3ff75c9c1..096864b77 100644 --- a/apps/opencs/model/doc/documentmanager.cpp +++ b/apps/opencs/model/doc/documentmanager.cpp @@ -13,44 +13,88 @@ #include "document.hpp" CSMDoc::DocumentManager::DocumentManager (const Files::ConfigurationManager& configuration) -: mConfiguration (configuration) +: mConfiguration (configuration), mEncoding (ToUTF8::WINDOWS_1252) { boost::filesystem::path projectPath = configuration.getUserDataPath() / "projects"; if (!boost::filesystem::is_directory (projectPath)) boost::filesystem::create_directories (projectPath); + + mLoader.moveToThread (&mLoaderThread); + mLoaderThread.start(); + + connect (&mLoader, SIGNAL (documentLoaded (Document *)), + this, SLOT (documentLoaded (Document *))); + connect (&mLoader, SIGNAL (documentNotLoaded (Document *, const std::string&)), + this, SLOT (documentNotLoaded (Document *, const std::string&))); + connect (this, SIGNAL (loadRequest (CSMDoc::Document *)), + &mLoader, SLOT (loadDocument (CSMDoc::Document *))); + connect (&mLoader, SIGNAL (nextStage (CSMDoc::Document *, const std::string&, int)), + this, SIGNAL (nextStage (CSMDoc::Document *, const std::string&, int))); + connect (&mLoader, SIGNAL (nextRecord (CSMDoc::Document *)), + this, SIGNAL (nextRecord (CSMDoc::Document *))); + connect (this, SIGNAL (cancelLoading (CSMDoc::Document *)), + &mLoader, SLOT (abortLoading (CSMDoc::Document *))); + connect (&mLoader, SIGNAL (loadMessage (CSMDoc::Document *, const std::string&)), + this, SIGNAL (loadMessage (CSMDoc::Document *, const std::string&))); } CSMDoc::DocumentManager::~DocumentManager() { + mLoaderThread.quit(); + mLoader.hasThingsToDo().wakeAll(); + mLoaderThread.wait(); + for (std::vector::iterator iter (mDocuments.begin()); iter!=mDocuments.end(); ++iter) delete *iter; } -CSMDoc::Document *CSMDoc::DocumentManager::addDocument (const std::vector& files, const boost::filesystem::path& savePath, +void CSMDoc::DocumentManager::addDocument (const std::vector& files, const boost::filesystem::path& savePath, bool new_) { - Document *document = new Document (mConfiguration, files, savePath, mResDir, new_); + Document *document = new Document (mConfiguration, files, new_, savePath, mResDir, mEncoding); mDocuments.push_back (document); - return document; + emit loadRequest (document); + + mLoader.hasThingsToDo().wakeAll(); } -bool CSMDoc::DocumentManager::removeDocument (Document *document) +void CSMDoc::DocumentManager::removeDocument (CSMDoc::Document *document) { std::vector::iterator iter = std::find (mDocuments.begin(), mDocuments.end(), document); if (iter==mDocuments.end()) - throw std::runtime_error ("removing invalid document"); + throw std::runtime_error ("removing invalid document"); mDocuments.erase (iter); delete document; - return mDocuments.empty(); + if (mDocuments.empty()) + emit lastDocumentDeleted(); } void CSMDoc::DocumentManager::setResourceDir (const boost::filesystem::path& parResDir) { mResDir = boost::filesystem::system_complete(parResDir); } + +void CSMDoc::DocumentManager::setEncoding (ToUTF8::FromType encoding) +{ + mEncoding = encoding; +} + +void CSMDoc::DocumentManager::documentLoaded (Document *document) +{ + emit documentAdded (document); + emit loadingStopped (document, true, ""); +} + +void CSMDoc::DocumentManager::documentNotLoaded (Document *document, const std::string& error) +{ + emit loadingStopped (document, false, error); + + if (error.empty()) // do not remove the document yet, if we have an error + removeDocument (document); +} \ No newline at end of file diff --git a/apps/opencs/model/doc/documentmanager.hpp b/apps/opencs/model/doc/documentmanager.hpp index b969862e9..9b675826a 100644 --- a/apps/opencs/model/doc/documentmanager.hpp +++ b/apps/opencs/model/doc/documentmanager.hpp @@ -6,6 +6,13 @@ #include +#include +#include + +#include + +#include "loader.hpp" + namespace Files { class ConfigurationManager; @@ -15,10 +22,15 @@ namespace CSMDoc { class Document; - class DocumentManager + class DocumentManager : public QObject { + Q_OBJECT + std::vector mDocuments; const Files::ConfigurationManager& mConfiguration; + QThread mLoaderThread; + Loader mLoader; + ToUTF8::FromType mEncoding; DocumentManager (const DocumentManager&); DocumentManager& operator= (const DocumentManager&); @@ -29,20 +41,51 @@ namespace CSMDoc ~DocumentManager(); - Document *addDocument (const std::vector< boost::filesystem::path >& files, - const boost::filesystem::path& savePath, - bool new_); - ///< The ownership of the returned document is not transferred to the caller. - /// - /// \param new_ Do not load the last content file in \a files and instead create in an + void addDocument (const std::vector< boost::filesystem::path >& files, + const boost::filesystem::path& savePath, bool new_); + ///< \param new_ Do not load the last content file in \a files and instead create in an /// appropriate way. - bool removeDocument (Document *document); - ///< \return last document removed? void setResourceDir (const boost::filesystem::path& parResDir); - - private: + + void setEncoding (ToUTF8::FromType encoding); + + private: + boost::filesystem::path mResDir; + + private slots: + + void documentLoaded (Document *document); + ///< The ownership of \a document is not transferred. + + void documentNotLoaded (Document *document, const std::string& error); + ///< Document load has been interrupted either because of a call to abortLoading + /// or a problem during loading). In the former case error will be an empty string. + + public slots: + + void removeDocument (CSMDoc::Document *document); + ///< Emits the lastDocumentDeleted signal, if applicable. + + signals: + + void documentAdded (CSMDoc::Document *document); + + void loadRequest (CSMDoc::Document *document); + + void lastDocumentDeleted(); + + void loadingStopped (CSMDoc::Document *document, bool completed, + const std::string& error); + + void nextStage (CSMDoc::Document *document, const std::string& name, int steps); + + void nextRecord (CSMDoc::Document *document); + + void cancelLoading (CSMDoc::Document *document); + + void loadMessage (CSMDoc::Document *document, const std::string& message); }; } diff --git a/apps/opencs/model/doc/loader.cpp b/apps/opencs/model/doc/loader.cpp new file mode 100644 index 000000000..c106c06e8 --- /dev/null +++ b/apps/opencs/model/doc/loader.cpp @@ -0,0 +1,130 @@ + +#include "loader.hpp" + +#include + +#include "../tools/reportmodel.hpp" + +#include "document.hpp" +#include "state.hpp" + +CSMDoc::Loader::Stage::Stage() : mFile (0), mRecordsLeft (false) {} + + +CSMDoc::Loader::Loader() +{ + QTimer *timer = new QTimer (this); + + connect (timer, SIGNAL (timeout()), this, SLOT (load())); + timer->start(); +} + +QWaitCondition& CSMDoc::Loader::hasThingsToDo() +{ + return mThingsToDo; +} + +void CSMDoc::Loader::load() +{ + if (mDocuments.empty()) + { + mMutex.lock(); + mThingsToDo.wait (&mMutex); + mMutex.unlock(); + return; + } + + std::vector >::iterator iter = mDocuments.begin(); + + Document *document = iter->first; + + int size = static_cast (document->getContentFiles().size()); + + if (document->isNew()) + --size; + + bool done = false; + + const int batchingSize = 100; + + try + { + if (iter->second.mRecordsLeft) + { + CSMDoc::Stage::Messages messages; + for (int i=0; igetData().continueLoading (messages)) + { + iter->second.mRecordsLeft = false; + break; + } + + CSMWorld::UniversalId log (CSMWorld::UniversalId::Type_LoadErrorLog, 0); + + for (CSMDoc::Stage::Messages::const_iterator iter (messages.begin()); + iter!=messages.end(); ++iter) + { + document->getReport (log)->add (iter->first, iter->second); + emit loadMessage (document, iter->second); + } + + emit nextRecord (document); + + return; + } + + if (iter->second.mFilegetContentFiles()[iter->second.mFile]; + + int steps = document->getData().startLoading (path, iter->second.mFilesecond.mRecordsLeft = true; + + emit nextStage (document, path.filename().string(), steps/batchingSize); + } + else if (iter->second.mFile==size) + { + int steps = document->getData().startLoading (document->getProjectPath(), false, true); + iter->second.mRecordsLeft = true; + + emit nextStage (document, "Project File", steps/batchingSize); + } + else + { + done = true; + } + + ++(iter->second.mFile); + } + catch (const std::exception& e) + { + mDocuments.erase (iter); + emit documentNotLoaded (document, e.what()); + return; + } + + if (done) + { + mDocuments.erase (iter); + emit documentLoaded (document); + } +} + +void CSMDoc::Loader::loadDocument (CSMDoc::Document *document) +{ + mDocuments.push_back (std::make_pair (document, Stage())); +} + +void CSMDoc::Loader::abortLoading (CSMDoc::Document *document) +{ + for (std::vector >::iterator iter = mDocuments.begin(); + iter!=mDocuments.end(); ++iter) + { + if (iter->first==document) + { + mDocuments.erase (iter); + emit documentNotLoaded (document, ""); + break; + } + } +} diff --git a/apps/opencs/model/doc/loader.hpp b/apps/opencs/model/doc/loader.hpp new file mode 100644 index 000000000..c276e14ff --- /dev/null +++ b/apps/opencs/model/doc/loader.hpp @@ -0,0 +1,71 @@ +#ifndef CSM_DOC_LOADER_H +#define CSM_DOC_LOADER_H + +#include + +#include +#include +#include + +namespace CSMDoc +{ + class Document; + + class Loader : public QObject + { + Q_OBJECT + + struct Stage + { + int mFile; + bool mRecordsLeft; + + Stage(); + }; + + QMutex mMutex; + QWaitCondition mThingsToDo; + std::vector > mDocuments; + + public: + + Loader(); + + QWaitCondition& hasThingsToDo(); + + private slots: + + void load(); + + public slots: + + void loadDocument (CSMDoc::Document *document); + ///< The ownership of \a document is not transferred. + + void abortLoading (CSMDoc::Document *document); + ///< Abort loading \a docuemnt (ignored if \a document has already finished being + /// loaded). Will result in a documentNotLoaded signal, once the Loader has finished + /// cleaning up. + + signals: + + void documentLoaded (Document *document); + ///< The ownership of \a document is not transferred. + + void documentNotLoaded (Document *document, const std::string& error); + ///< Document load has been interrupted either because of a call to abortLoading + /// or a problem during loading). In the former case error will be an empty string. + + void nextStage (CSMDoc::Document *document, const std::string& name, int steps); + + void nextRecord (CSMDoc::Document *document); + ///< \note This signal is only given once per group of records. The group size is + /// approximately the total number of records divided by the steps value of the + /// previous nextStage signal. + + void loadMessage (CSMDoc::Document *document, const std::string& message); + ///< Non-critical load error or warning + }; +} + +#endif diff --git a/apps/opencs/model/doc/operation.cpp b/apps/opencs/model/doc/operation.cpp index d29cc2631..42a432043 100644 --- a/apps/opencs/model/doc/operation.cpp +++ b/apps/opencs/model/doc/operation.cpp @@ -6,6 +6,8 @@ #include +#include "../world/universalid.hpp" + #include "state.hpp" #include "stage.hpp" @@ -80,7 +82,7 @@ void CSMDoc::Operation::abort() void CSMDoc::Operation::executeStage() { - std::vector messages; + Stage::Messages messages; while (mCurrentStage!=mStages.end()) { @@ -97,7 +99,7 @@ void CSMDoc::Operation::executeStage() } catch (const std::exception& e) { - emit reportMessage (e.what(), mType); + emit reportMessage (CSMWorld::UniversalId(), e.what(), mType); abort(); } @@ -108,8 +110,8 @@ void CSMDoc::Operation::executeStage() emit progress (mCurrentStepTotal, mTotalSteps ? mTotalSteps : 1, mType); - for (std::vector::const_iterator iter (messages.begin()); iter!=messages.end(); ++iter) - emit reportMessage (iter->c_str(), mType); + for (Stage::Messages::const_iterator iter (messages.begin()); iter!=messages.end(); ++iter) + emit reportMessage (iter->first, iter->second, mType); if (mCurrentStage==mStages.end()) exit(); diff --git a/apps/opencs/model/doc/operation.hpp b/apps/opencs/model/doc/operation.hpp index 316eda78f..651283880 100644 --- a/apps/opencs/model/doc/operation.hpp +++ b/apps/opencs/model/doc/operation.hpp @@ -5,6 +5,11 @@ #include +namespace CSMWorld +{ + class UniversalId; +} + namespace CSMDoc { class Stage; @@ -46,7 +51,8 @@ namespace CSMDoc void progress (int current, int max, int type); - void reportMessage (const QString& message, int type); + void reportMessage (const CSMWorld::UniversalId& id, const std::string& message, + int type); void done (int type); diff --git a/apps/opencs/model/doc/saving.cpp b/apps/opencs/model/doc/saving.cpp index 73278ba97..45b53f4fe 100644 --- a/apps/opencs/model/doc/saving.cpp +++ b/apps/opencs/model/doc/saving.cpp @@ -8,8 +8,9 @@ #include "savingstages.hpp" #include "document.hpp" -CSMDoc::Saving::Saving (Document& document, const boost::filesystem::path& projectPath) -: Operation (State_Saving, true, true), mDocument (document), mState (*this, projectPath) +CSMDoc::Saving::Saving (Document& document, const boost::filesystem::path& projectPath, + ToUTF8::FromType encoding) +: Operation (State_Saving, true, true), mDocument (document), mState (*this, projectPath, encoding) { // save project file appendStage (new OpenSaveStage (mDocument, mState, true)); @@ -64,7 +65,11 @@ CSMDoc::Saving::Saving (Document& document, const boost::filesystem::path& proje appendStage (new WriteRefIdCollectionStage (mDocument, mState)); + appendStage (new CollectionReferencesStage (mDocument, mState)); + appendStage (new WriteCellCollectionStage (mDocument, mState)); + + // close file and clean up appendStage (new CloseSaveStage (mState)); appendStage (new FinalSavingStage (mDocument, mState)); diff --git a/apps/opencs/model/doc/saving.hpp b/apps/opencs/model/doc/saving.hpp index cd1bbef98..44239b21b 100644 --- a/apps/opencs/model/doc/saving.hpp +++ b/apps/opencs/model/doc/saving.hpp @@ -3,6 +3,8 @@ #include +#include + #include "operation.hpp" #include "savingstate.hpp" @@ -19,7 +21,8 @@ namespace CSMDoc public: - Saving (Document& document, const boost::filesystem::path& projectPath); + Saving (Document& document, const boost::filesystem::path& projectPath, + ToUTF8::FromType encoding); }; } diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index d7df2117d..d36c2fb86 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -9,6 +9,8 @@ #include +#include + #include "../world/infocollection.hpp" #include "document.hpp" @@ -23,11 +25,13 @@ int CSMDoc::OpenSaveStage::setup() return 1; } -void CSMDoc::OpenSaveStage::perform (int stage, std::vector& messages) +void CSMDoc::OpenSaveStage::perform (int stage, Messages& messages) { mState.start (mDocument, mProjectFile); - mState.getStream().open ((mProjectFile ? mState.getPath() : mState.getTmpPath()).string().c_str()); + mState.getStream().open ( + mProjectFile ? mState.getPath() : mState.getTmpPath(), + std::ios::binary); if (!mState.getStream().is_open()) throw std::runtime_error ("failed to open stream for saving"); @@ -43,7 +47,7 @@ int CSMDoc::WriteHeaderStage::setup() return 1; } -void CSMDoc::WriteHeaderStage::perform (int stage, std::vector& messages) +void CSMDoc::WriteHeaderStage::perform (int stage, Messages& messages) { mState.getWriter().setVersion(); @@ -96,7 +100,7 @@ int CSMDoc::WriteDialogueCollectionStage::setup() return mTopics.getSize(); } -void CSMDoc::WriteDialogueCollectionStage::perform (int stage, std::vector& messages) +void CSMDoc::WriteDialogueCollectionStage::perform (int stage, Messages& messages) { const CSMWorld::Record& topic = mTopics.getRecord (stage); @@ -191,7 +195,7 @@ int CSMDoc::WriteRefIdCollectionStage::setup() return mDocument.getData().getReferenceables().getSize(); } -void CSMDoc::WriteRefIdCollectionStage::perform (int stage, std::vector& messages) +void CSMDoc::WriteRefIdCollectionStage::perform (int stage, Messages& messages) { mDocument.getData().getReferenceables().save (stage, mState.getWriter()); } @@ -204,7 +208,7 @@ CSMDoc::WriteFilterStage::WriteFilterStage (Document& document, SavingState& sta mDocument (document), mScope (scope) {} -void CSMDoc::WriteFilterStage::perform (int stage, std::vector& messages) +void CSMDoc::WriteFilterStage::perform (int stage, Messages& messages) { const CSMWorld::Record& record = mDocument.getData().getFilters().getRecord (stage); @@ -214,6 +218,143 @@ void CSMDoc::WriteFilterStage::perform (int stage, std::vector& mes } +CSMDoc::CollectionReferencesStage::CollectionReferencesStage (Document& document, + SavingState& state) +: mDocument (document), mState (state) +{} + +int CSMDoc::CollectionReferencesStage::setup() +{ + mState.getSubRecords().clear(); + + int size = mDocument.getData().getReferences().getSize(); + + int steps = size/100; + if (size%100) ++steps; + + return steps; +} + +void CSMDoc::CollectionReferencesStage::perform (int stage, Messages& messages) +{ + int size = mDocument.getData().getReferences().getSize(); + + for (int i=stage*100; i& record = + mDocument.getData().getReferences().getRecord (i); + + if (record.mState==CSMWorld::RecordBase::State_Deleted || + record.mState==CSMWorld::RecordBase::State_Modified || + record.mState==CSMWorld::RecordBase::State_ModifiedOnly) + { + mState.getSubRecords()[Misc::StringUtils::lowerCase (record.get().mCell)] + .push_back (i); + } + } +} + + +CSMDoc::WriteCellCollectionStage::WriteCellCollectionStage (Document& document, + SavingState& state) +: mDocument (document), mState (state) +{} + +int CSMDoc::WriteCellCollectionStage::setup() +{ + return mDocument.getData().getCells().getSize(); +} + +void CSMDoc::WriteCellCollectionStage::perform (int stage, Messages& messages) +{ + const CSMWorld::Record& cell = + mDocument.getData().getCells().getRecord (stage); + + std::map >::const_iterator references = + mState.getSubRecords().find (Misc::StringUtils::lowerCase (cell.get().mId)); + + if (cell.mState==CSMWorld::RecordBase::State_Modified || + cell.mState==CSMWorld::RecordBase::State_ModifiedOnly || + references!=mState.getSubRecords().end()) + { + bool interior = cell.get().mId.substr (0, 1)!="#"; + + // write cell data + mState.getWriter().startRecord (cell.mModified.sRecordId); + + mState.getWriter().writeHNOCString ("NAME", cell.get().mName); + + ESM::Cell cell2 = cell.get(); + + if (interior) + cell2.mData.mFlags |= ESM::Cell::Interior; + else + { + cell2.mData.mFlags &= ~ESM::Cell::Interior; + + std::istringstream stream (cell.get().mId.c_str()); + char ignore; + stream >> ignore >> cell2.mData.mX >> cell2.mData.mY; + } + cell2.save (mState.getWriter()); + + // write references + if (references!=mState.getSubRecords().end()) + { + // first pass: find highest RefNum + int lastRefNum = -1; + + for (std::vector::const_iterator iter (references->second.begin()); + iter!=references->second.end(); ++iter) + { + const CSMWorld::Record& ref = + mDocument.getData().getReferences().getRecord (*iter); + + if (ref.get().mRefNum.mContentFile==0 && ref.get().mRefNum.mIndex>lastRefNum) + lastRefNum = ref.get().mRefNum.mIndex; + } + + // second pass: write + for (std::vector::const_iterator iter (references->second.begin()); + iter!=references->second.end(); ++iter) + { + const CSMWorld::Record& ref = + mDocument.getData().getReferences().getRecord (*iter); + + if (ref.mState==CSMWorld::RecordBase::State_Modified || + ref.mState==CSMWorld::RecordBase::State_ModifiedOnly) + { + if (ref.get().mRefNum.mContentFile==-2) + { + if (lastRefNum>=0xffffff) + throw std::runtime_error ( + "RefNums exhausted in cell: " + cell.get().mId); + + ESM::CellRef ref2 = ref.get(); + ref2.mRefNum.mContentFile = 0; + ref2.mRefNum.mIndex = ++lastRefNum; + + ref2.save (mState.getWriter()); + } + else + ref.get().save (mState.getWriter()); + } + else if (ref.mState==CSMWorld::RecordBase::State_Deleted) + { + /// \todo write record with delete flag + } + } + } + + mState.getWriter().endRecord (cell.mModified.sRecordId); + } + else if (cell.mState==CSMWorld::RecordBase::State_Deleted) + { + /// \todo write record with delete flag + } +} + + CSMDoc::CloseSaveStage::CloseSaveStage (SavingState& state) : mState (state) {} @@ -223,7 +364,7 @@ int CSMDoc::CloseSaveStage::setup() return 1; } -void CSMDoc::CloseSaveStage::perform (int stage, std::vector& messages) +void CSMDoc::CloseSaveStage::perform (int stage, Messages& messages) { mState.getStream().close(); @@ -241,7 +382,7 @@ int CSMDoc::FinalSavingStage::setup() return 1; } -void CSMDoc::FinalSavingStage::perform (int stage, std::vector& messages) +void CSMDoc::FinalSavingStage::perform (int stage, Messages& messages) { if (mState.hasError()) { @@ -260,4 +401,4 @@ void CSMDoc::FinalSavingStage::perform (int stage, std::vector& mes mDocument.getUndoStack().setClean(); } -} \ No newline at end of file +} diff --git a/apps/opencs/model/doc/savingstages.hpp b/apps/opencs/model/doc/savingstages.hpp index b8eb0a3b3..dcb1a8650 100644 --- a/apps/opencs/model/doc/savingstages.hpp +++ b/apps/opencs/model/doc/savingstages.hpp @@ -39,7 +39,7 @@ namespace CSMDoc virtual int setup(); ///< \return number of steps - virtual void perform (int stage, std::vector& messages); + virtual void perform (int stage, Messages& messages); ///< Messages resulting from this stage will be appended to \a messages. }; @@ -57,7 +57,7 @@ namespace CSMDoc virtual int setup(); ///< \return number of steps - virtual void perform (int stage, std::vector& messages); + virtual void perform (int stage, Messages& messages); ///< Messages resulting from this stage will be appended to \a messages. }; @@ -75,7 +75,7 @@ namespace CSMDoc virtual int setup(); ///< \return number of steps - virtual void perform (int stage, std::vector& messages); + virtual void perform (int stage, Messages& messages); ///< Messages resulting from this stage will be appended to \a messages. }; @@ -92,7 +92,7 @@ namespace CSMDoc } template - void WriteCollectionStage::perform (int stage, std::vector& messages) + void WriteCollectionStage::perform (int stage, Messages& messages) { CSMWorld::RecordBase::State state = mCollection.getRecord (stage).mState; @@ -130,7 +130,7 @@ namespace CSMDoc virtual int setup(); ///< \return number of steps - virtual void perform (int stage, std::vector& messages); + virtual void perform (int stage, Messages& messages); ///< Messages resulting from this stage will be appended to \a messages. }; @@ -147,7 +147,7 @@ namespace CSMDoc virtual int setup(); ///< \return number of steps - virtual void perform (int stage, std::vector& messages); + virtual void perform (int stage, Messages& messages); ///< Messages resulting from this stage will be appended to \a messages. }; @@ -161,10 +161,42 @@ namespace CSMDoc WriteFilterStage (Document& document, SavingState& state, CSMFilter::Filter::Scope scope); - virtual void perform (int stage, std::vector& messages); + virtual void perform (int stage, Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages. + + }; + + class CollectionReferencesStage : public Stage + { + Document& mDocument; + SavingState& mState; + + public: + + CollectionReferencesStage (Document& document, SavingState& state); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, Messages& messages); ///< Messages resulting from this stage will be appended to \a messages. }; + class WriteCellCollectionStage : public Stage + { + Document& mDocument; + SavingState& mState; + + public: + + WriteCellCollectionStage (Document& document, SavingState& state); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, Messages& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; class CloseSaveStage : public Stage { @@ -177,7 +209,7 @@ namespace CSMDoc virtual int setup(); ///< \return number of steps - virtual void perform (int stage, std::vector& messages); + virtual void perform (int stage, Messages& messages); ///< Messages resulting from this stage will be appended to \a messages. }; @@ -193,7 +225,7 @@ namespace CSMDoc virtual int setup(); ///< \return number of steps - virtual void perform (int stage, std::vector& messages); + virtual void perform (int stage, Messages& messages); ///< Messages resulting from this stage will be appended to \a messages. }; } diff --git a/apps/opencs/model/doc/savingstate.cpp b/apps/opencs/model/doc/savingstate.cpp index 4a1abb888..84bca1e95 100644 --- a/apps/opencs/model/doc/savingstate.cpp +++ b/apps/opencs/model/doc/savingstate.cpp @@ -4,11 +4,9 @@ #include "operation.hpp" #include "document.hpp" -CSMDoc::SavingState::SavingState (Operation& operation, const boost::filesystem::path& projectPath) -: mOperation (operation), - /// \todo set encoding properly, once config implementation has been fixed. - mEncoder (ToUTF8::calculateEncoding ("win1252")), - mProjectPath (projectPath), mProjectFile (false) +CSMDoc::SavingState::SavingState (Operation& operation, const boost::filesystem::path& projectPath, + ToUTF8::FromType encoding) +: mOperation (operation), mEncoder (encoding), mProjectPath (projectPath), mProjectFile (false) { mWriter.setEncoder (&mEncoder); } @@ -27,6 +25,8 @@ void CSMDoc::SavingState::start (Document& document, bool project) mStream.clear(); + mSubRecords.clear(); + if (project) mPath = mProjectPath; else @@ -49,7 +49,7 @@ const boost::filesystem::path& CSMDoc::SavingState::getTmpPath() const return mTmpPath; } -std::ofstream& CSMDoc::SavingState::getStream() +boost::filesystem::ofstream& CSMDoc::SavingState::getStream() { return mStream; } @@ -62,4 +62,9 @@ ESM::ESMWriter& CSMDoc::SavingState::getWriter() bool CSMDoc::SavingState::isProjectFile() const { return mProjectFile; -} \ No newline at end of file +} + +std::map >& CSMDoc::SavingState::getSubRecords() +{ + return mSubRecords; +} diff --git a/apps/opencs/model/doc/savingstate.hpp b/apps/opencs/model/doc/savingstate.hpp index 8cf7883e5..577fc734d 100644 --- a/apps/opencs/model/doc/savingstate.hpp +++ b/apps/opencs/model/doc/savingstate.hpp @@ -2,11 +2,15 @@ #define CSM_DOC_SAVINGSTATE_H #include +#include #include +#include #include +#include + namespace CSMDoc { class Operation; @@ -18,14 +22,16 @@ namespace CSMDoc boost::filesystem::path mPath; boost::filesystem::path mTmpPath; ToUTF8::Utf8Encoder mEncoder; - std::ofstream mStream; + boost::filesystem::ofstream mStream; ESM::ESMWriter mWriter; boost::filesystem::path mProjectPath; bool mProjectFile; + std::map > mSubRecords; // record ID, list of subrecords public: - SavingState (Operation& operation, const boost::filesystem::path& projectPath); + SavingState (Operation& operation, const boost::filesystem::path& projectPath, + ToUTF8::FromType encoding); bool hasError() const; @@ -36,15 +42,17 @@ namespace CSMDoc const boost::filesystem::path& getTmpPath() const; - std::ofstream& getStream(); + boost::filesystem::ofstream& getStream(); ESM::ESMWriter& getWriter(); bool isProjectFile() const; ///< Currently saving project file? (instead of content file) + + std::map >& getSubRecords(); }; } -#endif \ No newline at end of file +#endif diff --git a/apps/opencs/model/doc/stage.hpp b/apps/opencs/model/doc/stage.hpp index 1f96c60b4..ca34c2229 100644 --- a/apps/opencs/model/doc/stage.hpp +++ b/apps/opencs/model/doc/stage.hpp @@ -4,18 +4,22 @@ #include #include +#include "../world/universalid.hpp" + namespace CSMDoc { class Stage { public: + typedef std::vector > Messages; + virtual ~Stage(); virtual int setup() = 0; ///< \return number of steps - virtual void perform (int stage, std::vector& messages) = 0; + virtual void perform (int stage, Messages& messages) = 0; ///< Messages resulting from this stage will be appended to \a messages. }; } diff --git a/apps/opencs/model/doc/state.hpp b/apps/opencs/model/doc/state.hpp index 04e6fae89..6e1a1c4f4 100644 --- a/apps/opencs/model/doc/state.hpp +++ b/apps/opencs/model/doc/state.hpp @@ -3,17 +3,18 @@ namespace CSMDoc { - enum State - { - State_Modified = 1, - State_Locked = 2, - State_Operation = 4, + enum State + { + State_Modified = 1, + State_Locked = 2, + State_Operation = 4, - State_Saving = 8, - State_Verifying = 16, - State_Compiling = 32, // not implemented yet - State_Searching = 64 // not implemented yet - }; + State_Saving = 8, + State_Verifying = 16, + State_Compiling = 32, // not implemented yet + State_Searching = 64, // not implemented yet + State_Loading = 128 // pseudo-state; can not be encountered in a loaded document + }; } #endif diff --git a/apps/opencs/model/settings/connector.cpp b/apps/opencs/model/settings/connector.cpp new file mode 100644 index 000000000..5e1d64544 --- /dev/null +++ b/apps/opencs/model/settings/connector.cpp @@ -0,0 +1,128 @@ +#include "connector.hpp" +#include "../../view/settings/view.hpp" +#include "../../view/settings/page.hpp" + +CSMSettings::Connector::Connector(CSVSettings::View *master, + QObject *parent) + : mMasterView (master), QObject(parent) +{} + +void CSMSettings::Connector::addSlaveView (CSVSettings::View *view, + QList &masterProxyValues) +{ + mSlaveViews.append (view); + + mProxyListMap[view->viewKey()].append (masterProxyValues); +} + +QList CSMSettings::Connector::getSlaveViewValues() const +{ + QList list; + + foreach (const CSVSettings::View *view, mSlaveViews) + list.append (view->selectedValues()); + + return list; +} + +bool CSMSettings::Connector::proxyListsMatch ( + const QList &list1, + const QList &list2) const +{ + bool success = true; + + for (int i = 0; i < list1.size(); i++) + { + success = stringListsMatch (list1.at(i), list2.at(i)); + + if (!success) + break; + } + return success; +} + +void CSMSettings::Connector::slotUpdateMaster() const +{ + //list of the current values for each slave. + QList slaveValueList = getSlaveViewValues(); + + int masterColumn = -1; + + /* + * A row in the master view is one of the values in the + * master view's data model. This corresponds directly to the number of + * values in a proxy list contained in the ProxyListMap member. + * Thus, we iterate each "column" in the master proxy list + * (one for each vlaue in the master. Each column represents + * one master value's corresponding list of slave values. We examine + * each master value's list, comparing it to the current slave value list, + * stopping when we find a match using proxyListsMatch(). + * + * If no match is found, clear the master view's value + */ + + for (int i = 0; i < mMasterView->rowCount(); i++) + { + QList proxyValueList; + + foreach (const QString &settingKey, mProxyListMap.keys()) + { + // append the proxy value list stored in the i'th column + // for each setting key. A setting key is the id of the setting + // in page.name format. + proxyValueList.append (mProxyListMap.value(settingKey).at(i)); + } + + if (proxyListsMatch (slaveValueList, proxyValueList)) + { + masterColumn = i; + break; + } + } + + QString masterValue = mMasterView->value (masterColumn); + + mMasterView->setSelectedValue (masterValue); +} + +void CSMSettings::Connector::slotUpdateSlaves() const +{ + int row = mMasterView->currentIndex(); + + if (row == -1) + return; + + //iterate the proxy lists for the chosen master index + //and pass the list to each slave for updating + for (int i = 0; i < mSlaveViews.size(); i++) + { + QList proxyList = + mProxyListMap.value(mSlaveViews.at(i)->viewKey()); + + mSlaveViews.at(i)->setSelectedValues (proxyList.at(row)); + } +} + +bool CSMSettings::Connector::stringListsMatch ( + const QStringList &list1, + const QStringList &list2) const +{ + //returns a "sloppy" match, verifying that each list contains all the same + //items, though not necessarily in the same order. + + if (list1.size() != list2.size()) + return false; + + QStringList tempList(list2); + + //iterate each value in the list, removing one occurrence of the value in + //the other list. If no corresponding value is found, test fails + foreach (const QString &value, list1) + { + if (!tempList.contains(value)) + return false; + + tempList.removeOne(value); + } + return true; +} diff --git a/apps/opencs/model/settings/connector.hpp b/apps/opencs/model/settings/connector.hpp new file mode 100644 index 000000000..aaf9936d5 --- /dev/null +++ b/apps/opencs/model/settings/connector.hpp @@ -0,0 +1,67 @@ +#ifndef CSMSETTINGS_CONNECTOR_HPP +#define CSMSETTINGS_CONNECTOR_HPP + +#include +#include +#include +#include + +#include "support.hpp" + +namespace CSVSettings { + class View; +} + +namespace CSMSettings { + + class Connector : public QObject + { + Q_OBJECT + + CSVSettings::View *mMasterView; + + ///map using the view pointer as a key to it's index value + QList mSlaveViews; + + ///list of proxy values for each master value. + ///value list order is indexed to the master value index. + QMap < QString, QList > mProxyListMap; + + public: + explicit Connector(CSVSettings::View *master, + QObject *parent = 0); + + ///Set the view which acts as a proxy for other setting views + void setMasterView (CSVSettings::View *view); + + ///Add a view to be updated / update to the master + void addSlaveView (CSVSettings::View *view, + QList &masterProxyValues); + + private: + + ///loosely matches lists of proxy values across registered slaves + ///against a proxy value list for a given master value + bool proxyListsMatch (const QList &list1, + const QList &list2) const; + + ///loosely matches two string lists + bool stringListsMatch (const QStringList &list1, + const QStringList &list2) const; + + ///retrieves current values of registered slave views + QList getSlaveViewValues() const; + + public slots: + + ///updates slave views with proxy values associated with current + ///master value + void slotUpdateSlaves() const; + + ///updates master value associated with the currently selected + ///slave values, if applicable. + void slotUpdateMaster() const; + }; +} + +#endif // CSMSETTINGS_CONNECTOR_HPP diff --git a/apps/opencs/model/settings/setting.cpp b/apps/opencs/model/settings/setting.cpp new file mode 100644 index 000000000..2f86d4ff8 --- /dev/null +++ b/apps/opencs/model/settings/setting.cpp @@ -0,0 +1,391 @@ +#include "setting.hpp" +#include "support.hpp" + +CSMSettings::Setting::Setting(SettingType typ, const QString &settingName, + const QString &pageName) + : mIsEditorSetting (false) +{ + buildDefaultSetting(); + + int settingType = static_cast (typ); + + //even-numbered setting types are multi-valued + if ((settingType % 2) == 0) + setProperty (Property_IsMultiValue, QVariant(true).toString()); + + //view type is related to setting type by an order of magnitude + setProperty (Property_SettingType, QVariant (settingType).toString()); + setProperty (Property_Page, pageName); + setProperty (Property_Name, settingName); +} + +void CSMSettings::Setting::buildDefaultSetting() +{ + int arrLen = sizeof(sPropertyDefaults) / sizeof (*sPropertyDefaults); + + for (int i = 0; i < arrLen; i++) + { + QStringList propertyList; + + if (i list; + + foreach (const QString &val, vals) + list << (QStringList() << val); + + mProxies [setting->page() + '/' + setting->name()] = list; +} + +void CSMSettings::Setting::addProxy (const Setting *setting, + const QList &list) +{ + if (serializable()) + setProperty (Property_Serializable, false); + + mProxies [setting->page() + '/' + setting->name()] = list; +} + +void CSMSettings::Setting::setColumnSpan (int value) +{ + setProperty (Property_ColumnSpan, value); +} + +int CSMSettings::Setting::columnSpan() const +{ + return property (Property_ColumnSpan).at(0).toInt(); +} + +void CSMSettings::Setting::setDeclaredValues (QStringList list) +{ + setProperty (Property_DeclaredValues, list); +} + +QStringList CSMSettings::Setting::declaredValues() const +{ + return property (Property_DeclaredValues); +} + +QStringList CSMSettings::Setting::property (SettingProperty prop) const +{ + if (prop >= mProperties.size()) + return QStringList(); + + return mProperties.at(prop); +} + +void CSMSettings::Setting::setDefaultValue (int value) +{ + setDefaultValues (QStringList() << QVariant (value).toString()); +} + +void CSMSettings::Setting::setDefaultValue (double value) +{ + setDefaultValues (QStringList() << QVariant (value).toString()); +} + +void CSMSettings::Setting::setDefaultValue (const QString &value) +{ + setDefaultValues (QStringList() << value); +} + +void CSMSettings::Setting::setDefaultValues (const QStringList &values) +{ + setProperty (Property_DefaultValues, values); +} + +QStringList CSMSettings::Setting::defaultValues() const +{ + return property (Property_DefaultValues); +} + +void CSMSettings::Setting::setDelimiter (const QString &value) +{ + setProperty (Property_Delimiter, value); +} + +QString CSMSettings::Setting::delimiter() const +{ + return property (Property_Delimiter).at(0); +} + +void CSMSettings::Setting::setEditorSetting(bool state) +{ + mIsEditorSetting = true; +} + +bool CSMSettings::Setting::isEditorSetting() const +{ + return mIsEditorSetting; +} +void CSMSettings::Setting::setIsMultiLine (bool state) +{ + setProperty (Property_IsMultiLine, state); +} + +bool CSMSettings::Setting::isMultiLine() const +{ + return (property (Property_IsMultiLine).at(0) == "true"); +} + +void CSMSettings::Setting::setIsMultiValue (bool state) +{ + setProperty (Property_IsMultiValue, state); +} + +bool CSMSettings::Setting::isMultiValue() const +{ + return (property (Property_IsMultiValue).at(0) == "true"); +} + +const CSMSettings::ProxyValueMap &CSMSettings::Setting::proxyLists() const +{ + return mProxies; +} + +void CSMSettings::Setting::setSerializable (bool state) +{ + setProperty (Property_Serializable, state); +} + +bool CSMSettings::Setting::serializable() const +{ + return (property (Property_Serializable).at(0) == "true"); +} + +void CSMSettings::Setting::setSpecialValueText(const QString &text) +{ + setProperty (Property_SpecialValueText, text); +} + +QString CSMSettings::Setting::specialValueText() const +{ + return property (Property_SpecialValueText).at(0); +} + +void CSMSettings::Setting::setName (const QString &value) +{ + setProperty (Property_Name, value); +} + +QString CSMSettings::Setting::name() const +{ + return property (Property_Name).at(0); +} + +void CSMSettings::Setting::setPage (const QString &value) +{ + setProperty (Property_Page, value); +} + +QString CSMSettings::Setting::page() const +{ + return property (Property_Page).at(0); +} + +void CSMSettings::Setting::setPrefix (const QString &value) +{ + setProperty (Property_Prefix, value); +} + +QString CSMSettings::Setting::prefix() const +{ + return property (Property_Prefix).at(0); +} + +void CSMSettings::Setting::setRowSpan (const int value) +{ + setProperty (Property_RowSpan, value); +} + +int CSMSettings::Setting::rowSpan () const +{ + return property (Property_RowSpan).at(0).toInt(); +} + +void CSMSettings::Setting::setSingleStep (int value) +{ + setProperty (Property_SingleStep, value); +} + +void CSMSettings::Setting::setSingleStep (double value) +{ + setProperty (Property_SingleStep, value); +} + +QString CSMSettings::Setting::singleStep() const +{ + return property (Property_SingleStep).at(0); +} + +void CSMSettings::Setting::setSuffix (const QString &value) +{ + setProperty (Property_Suffix, value); +} + +QString CSMSettings::Setting::suffix() const +{ + return property (Property_Suffix).at(0); +} + +void CSMSettings::Setting::setTickInterval (int value) +{ + setProperty (Property_TickInterval, value); +} + +int CSMSettings::Setting::tickInterval () const +{ + return property (Property_TickInterval).at(0).toInt(); +} + +void CSMSettings::Setting::setTicksAbove (bool state) +{ + setProperty (Property_TicksAbove, state); +} + +bool CSMSettings::Setting::ticksAbove() const +{ + return (property (Property_TicksAbove).at(0) == "true"); +} + +void CSMSettings::Setting::setTicksBelow (bool state) +{ + setProperty (Property_TicksBelow, state); +} + +bool CSMSettings::Setting::ticksBelow() const +{ + return (property (Property_TicksBelow).at(0) == "true"); +} + +void CSMSettings::Setting::setType (int settingType) +{ + setProperty (Property_SettingType, settingType); +} + +CSMSettings::SettingType CSMSettings::Setting::type() const +{ + return static_cast ( property ( + Property_SettingType).at(0).toInt()); +} + +void CSMSettings::Setting::setMaximum (int value) +{ + setProperty (Property_Maximum, value); +} + +void CSMSettings::Setting::setMaximum (double value) +{ + setProperty (Property_Maximum, value); +} + +QString CSMSettings::Setting::maximum() const +{ + return property (Property_Maximum).at(0); +} + +void CSMSettings::Setting::setMinimum (int value) +{ + setProperty (Property_Minimum, value); +} + +void CSMSettings::Setting::setMinimum (double value) +{ + setProperty (Property_Minimum, value); +} + +QString CSMSettings::Setting::minimum() const +{ + return property (Property_Minimum).at(0); +} + +CSVSettings::ViewType CSMSettings::Setting::viewType() const +{ + return static_cast ( property ( + Property_SettingType).at(0).toInt() / 10); +} + +void CSMSettings::Setting::setViewColumn (int value) +{ + setProperty (Property_ViewColumn, value); +} + +int CSMSettings::Setting::viewColumn() const +{ + return property (Property_ViewColumn).at(0).toInt(); +} + +void CSMSettings::Setting::setViewLocation (int row, int column) +{ + setViewRow (row); + setViewColumn (column); +} + +void CSMSettings::Setting::setViewRow (int value) +{ + setProperty (Property_ViewRow, value); +} + +int CSMSettings::Setting::viewRow() const +{ + return property (Property_ViewRow).at(0).toInt(); +} + +void CSMSettings::Setting::setWidgetWidth (int value) +{ + setProperty (Property_WidgetWidth, value); +} + +int CSMSettings::Setting::widgetWidth() const +{ + return property (Property_WidgetWidth).at(0).toInt(); +} + +void CSMSettings::Setting::setWrapping (bool state) +{ + setProperty (Property_Wrapping, state); +} + +bool CSMSettings::Setting::wrapping() const +{ + return (property (Property_Wrapping).at(0) == "true"); +} + +void CSMSettings::Setting::setProperty (SettingProperty prop, bool value) +{ + setProperty (prop, QStringList() << QVariant (value).toString()); +} + +void CSMSettings::Setting::setProperty (SettingProperty prop, int value) +{ + setProperty (prop, QStringList() << QVariant (value).toString()); +} + +void CSMSettings::Setting::setProperty (SettingProperty prop, double value) +{ + setProperty (prop, QStringList() << QVariant (value).toString()); +} + +void CSMSettings::Setting::setProperty (SettingProperty prop, + const QString &value) +{ + setProperty (prop, QStringList() << value); +} + +void CSMSettings::Setting::setProperty (SettingProperty prop, + const QStringList &value) +{ + if (prop < mProperties.size()) + mProperties.replace (prop, value); +} diff --git a/apps/opencs/model/settings/setting.hpp b/apps/opencs/model/settings/setting.hpp new file mode 100644 index 000000000..e40302f00 --- /dev/null +++ b/apps/opencs/model/settings/setting.hpp @@ -0,0 +1,150 @@ +#ifndef CSMSETTINGS_SETTING_HPP +#define CSMSETTINGS_SETTING_HPP + +#include +#include +#include "support.hpp" + +namespace CSMSettings +{ + //QString is the setting id in the form of "page/name" + //QList is a list of stringlists of proxy values. + //Order is important! Proxy stringlists are matched against + //master values by their position in the QList. + typedef QMap > ProxyValueMap; + + ///Setting class is the interface for the User Settings. It contains + ///a great deal of boiler plate to provide the core API functions, as + ///well as the property() functions which use enumeration to be iterable. + ///This makes the Setting class capable of being manipulated by script. + ///See CSMSettings::support.hpp for enumerations / string values. + class Setting + { + QList mProperties; + QStringList mDefaults; + + bool mIsEditorSetting; + + ProxyValueMap mProxies; + + public: + + explicit Setting(SettingType typ, const QString &settingName, + const QString &pageName); + + void addProxy (const Setting *setting, const QStringList &vals); + void addProxy (const Setting *setting, const QList &list); + + const QList &properties() const { return mProperties; } + const ProxyValueMap &proxies() const { return mProxies; } + + void setColumnSpan (int value); + int columnSpan() const; + + void setDeclaredValues (QStringList list); + QStringList declaredValues() const; + + void setDefaultValue (int value); + void setDefaultValue (double value); + void setDefaultValue (const QString &value); + + void setDefaultValues (const QStringList &values); + QStringList defaultValues() const; + + void setDelimiter (const QString &value); + QString delimiter() const; + + void setEditorSetting (bool state); + bool isEditorSetting() const; + + void setIsMultiLine (bool state); + bool isMultiLine() const; + + void setIsMultiValue (bool state); + bool isMultiValue() const; + + void setMask (const QString &value); + QString mask() const; + + void setMaximum (int value); + void setMaximum (double value); + QString maximum() const; + + void setMinimum (int value); + void setMinimum (double value); + QString minimum() const; + + void setName (const QString &value); + QString name() const; + + void setPage (const QString &value); + QString page() const; + + void setPrefix (const QString &value); + QString prefix() const; + + void setRowSpan (const int value); + int rowSpan() const; + + const ProxyValueMap &proxyLists() const; + + void setSerializable (bool state); + bool serializable() const; + + void setSpecialValueText (const QString &text); + QString specialValueText() const; + + void setSingleStep (int value); + void setSingleStep (double value); + QString singleStep() const; + + void setSuffix (const QString &value); + QString suffix() const; + + void setTickInterval (int value); + int tickInterval() const; + + void setTicksAbove (bool state); + bool ticksAbove() const; + + void setTicksBelow (bool state); + bool ticksBelow() const; + + void setViewColumn (int value); + int viewColumn() const; + + void setViewLocation (int row = -1, int column = -1); + + void setViewRow (int value); + int viewRow() const; + + void setType (int settingType); + CSMSettings::SettingType type() const; + + CSVSettings::ViewType viewType() const; + + void setWrapping (bool state); + bool wrapping() const; + + void setWidgetWidth (int value); + int widgetWidth() const; + + ///returns the specified property value + QStringList property (SettingProperty prop) const; + + ///boilerplate code to convert setting values of common types + void setProperty (SettingProperty prop, bool value); + void setProperty (SettingProperty prop, int value); + void setProperty (SettingProperty prop, double value); + void setProperty (SettingProperty prop, const QString &value); + void setProperty (SettingProperty prop, const QStringList &value); + + void addProxy (Setting* setting, + QMap &proxyMap); + + protected: + void buildDefaultSetting(); + }; +} + +#endif // CSMSETTINGS_SETTING_HPP diff --git a/apps/opencs/model/settings/settingcontainer.cpp b/apps/opencs/model/settings/settingcontainer.cpp deleted file mode 100644 index a75a84ec5..000000000 --- a/apps/opencs/model/settings/settingcontainer.cpp +++ /dev/null @@ -1,82 +0,0 @@ -#include "settingcontainer.hpp" - -#include - -CSMSettings::SettingContainer::SettingContainer(QObject *parent) : - QObject(parent), mValue (0), mValues (0) -{ -} - -CSMSettings::SettingContainer::SettingContainer(const QString &value, QObject *parent) : - QObject(parent), mValue (new QString (value)), mValues (0) -{ -} - -void CSMSettings::SettingContainer::insert (const QString &value) -{ - if (mValue) - { - mValues = new QStringList; - mValues->push_back (*mValue); - mValues->push_back (value); - - delete mValue; - mValue = 0; - } - else - { - delete mValue; - mValue = new QString (value); - } - -} - -void CSMSettings::SettingContainer::update (const QString &value, int index) -{ - if (isEmpty()) - mValue = new QString(value); - - else if (mValue) - *mValue = value; - - else if (mValues) - mValues->replace(index, value); -} - -QString CSMSettings::SettingContainer::getValue (int index) const -{ - QString retVal(""); - - //if mValue is valid, it's a single-value property. - //ignore the index and return the value - if (mValue) - retVal = *mValue; - - //otherwise, if it's a multivalued property - //return the appropriate value at the index - else if (mValues) - { - if (index == -1) - retVal = mValues->at(0); - - else if (index < mValues->size()) - retVal = mValues->at(index); - } - - return retVal; -} - -int CSMSettings::SettingContainer::count () const -{ - int retVal = 0; - - if (!isEmpty()) - { - if (mValues) - retVal = mValues->size(); - else - retVal = 1; - } - - return retVal; -} diff --git a/apps/opencs/model/settings/settingcontainer.hpp b/apps/opencs/model/settings/settingcontainer.hpp deleted file mode 100644 index 5af298a57..000000000 --- a/apps/opencs/model/settings/settingcontainer.hpp +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef SETTINGCONTAINER_HPP -#define SETTINGCONTAINER_HPP - -#include - -class QStringList; - -namespace CSMSettings -{ - class SettingContainer : public QObject - { - Q_OBJECT - - QString *mValue; - QStringList *mValues; - - public: - - explicit SettingContainer (QObject *parent = 0); - explicit SettingContainer (const QString &value, QObject *parent = 0); - - /// add a value to the container - /// multiple values supported - void insert (const QString &value); - - /// update an existing value - /// index specifies multiple values - void update (const QString &value, int index = 0); - - /// return value at specified index - QString getValue (int index = -1) const; - - /// retrieve list of all values - inline QStringList *getValues() const { return mValues; } - - /// return size of list - int count() const; - - /// test for empty container - /// useful for default-constructed containers returned by QMap when invalid key is passed - inline bool isEmpty() const { return (!mValue && !mValues); } - - inline bool isMultiValue() const { return (mValues); } - }; -} - -#endif // SETTINGCONTAINER_HPP diff --git a/apps/opencs/model/settings/settingsitem.cpp b/apps/opencs/model/settings/settingsitem.cpp deleted file mode 100644 index 5d897448a..000000000 --- a/apps/opencs/model/settings/settingsitem.cpp +++ /dev/null @@ -1,104 +0,0 @@ -#include "settingsitem.hpp" - -#include - -bool CSMSettings::SettingsItem::updateItem (const QStringList *values) -{ - QStringList::ConstIterator it = values->begin(); - - //if the item is not multivalued, - //save the last value passed in the container - if (!mIsMultiValue) - { - it = values->end(); - it--; - } - - bool isValid = true; - QString value (""); - - for (; it != values->end(); ++it) - { - value = *it; - isValid = validate(value); - - //skip only the invalid values - if (!isValid) - continue; - - insert(value); - } - - return isValid; -} - -bool CSMSettings::SettingsItem::updateItem (const QString &value) -{ - //takes a value or a SettingsContainer and updates itself accordingly - //after validating the data against it's own definition - - QString newValue = value; - - if (!validate (newValue)) - newValue = mDefaultValue; - - bool success = (getValue() != newValue); - - if (success) - { - if (mIsMultiValue) - insert (newValue); - else - update (newValue); - } - return success; -} - -bool CSMSettings::SettingsItem::updateItem(int valueListIndex) -{ - bool success = false; - - if (mValueList) - { - if (mValueList->size() > valueListIndex) - success = updateItem (mValueList->at(valueListIndex)); - } - return success; -} - -bool CSMSettings::SettingsItem::validate (const QString &value) -{ - //if there is no value list or value pair, there is no validation to do - bool isValid = !(!mValueList->isEmpty() || mValuePair); - - if (!isValid && !mValueList->isEmpty()) - { - for (QStringList::Iterator it = mValueList->begin(); it != mValueList->end(); ++it) - // foreach (QString listItem, *mValueList) - { - isValid = (value == *it); - - if (isValid) - break; - } - } - else if (!isValid && mValuePair) - { - int numVal = value.toInt(); - - isValid = (numVal > mValuePair->left.toInt() && numVal < mValuePair->right.toInt()); - } - - return isValid; -} - -void CSMSettings::SettingsItem::setDefaultValue (const QString &value) -{ - mDefaultValue = value; - update (value); -} - -QString CSMSettings::SettingsItem::getDefaultValue() const -{ - return mDefaultValue; -} diff --git a/apps/opencs/model/settings/settingsitem.hpp b/apps/opencs/model/settings/settingsitem.hpp deleted file mode 100644 index 87a85e8e4..000000000 --- a/apps/opencs/model/settings/settingsitem.hpp +++ /dev/null @@ -1,67 +0,0 @@ -#ifndef SETTINGSITEM_HPP -#define SETTINGSITEM_HPP - -#include -#include "support.hpp" -#include "settingcontainer.hpp" - -namespace CSMSettings -{ - /// Represents a setting including metadata - /// (valid values, ranges, defaults, and multivalue status - class SettingsItem : public SettingContainer - { - QStringPair *mValuePair; - QStringList *mValueList; - bool mIsMultiValue; - QString mDefaultValue; - - public: - explicit SettingsItem(QString name, bool isMultiValue, - const QString& defaultValue, QObject *parent = 0) - : SettingContainer(defaultValue, parent), - mIsMultiValue (isMultiValue), mValueList (0), - mValuePair (0), mDefaultValue (defaultValue) - { - QObject::setObjectName(name); - } - - /// updateItem overloads for updating setting value - /// provided a list of values (multi-valued), - /// a specific value - /// or an index value corresponding to the mValueList - bool updateItem (const QStringList *values); - bool updateItem (const QString &value); - bool updateItem (int valueListIndex); - - /// retrieve list of valid values for setting - inline QStringList *getValueList() { return mValueList; } - - /// write list of valid values for setting - inline void setValueList (QStringList *valueList) { mValueList = valueList; } - - /// valuePair used for spin boxes (max / min) - inline QStringPair *getValuePair() { return mValuePair; } - - /// set value range (spinbox / integer use) - inline void setValuePair (QStringPair valuePair) - { - delete mValuePair; - mValuePair = new QStringPair(valuePair); - } - - inline bool isMultivalue () { return mIsMultiValue; } - - void setDefaultValue (const QString &value); - QString getDefaultValue () const; - - private: - - /// Verifies that the supplied value is one of the following: - /// 1. Within the limits of the value pair (min / max) - /// 2. One of the values indicated in the value list - bool validate (const QString &value); - }; -} -#endif // SETTINGSITEM_HPP - diff --git a/apps/opencs/model/settings/support.cpp b/apps/opencs/model/settings/support.cpp deleted file mode 100644 index d79edfdb3..000000000 --- a/apps/opencs/model/settings/support.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "support.hpp" diff --git a/apps/opencs/model/settings/support.hpp b/apps/opencs/model/settings/support.hpp index 4ffd01b73..229e293b8 100644 --- a/apps/opencs/model/settings/support.hpp +++ b/apps/opencs/model/settings/support.hpp @@ -1,39 +1,145 @@ -#ifndef MODEL_SUPPORT_HPP -#define MODEL_SUPPORT_HPP +#ifndef SETTING_SUPPORT_HPP +#define SETTING_SUPPORT_HPP -#include +#include +#include +#include +#include #include -class QLayout; -class QWidget; -class QListWidgetItem; - +//Enums namespace CSMSettings { - class SettingContainer; + ///Enumerated properties for scripting + enum SettingProperty + { + Property_Name = 0, + Property_Page = 1, + Property_SettingType = 2, + Property_IsMultiValue = 3, + Property_IsMultiLine = 4, + Property_WidgetWidth = 5, + Property_ViewRow = 6, + Property_ViewColumn = 7, + Property_Delimiter = 8, + Property_Serializable = 9, + Property_ColumnSpan = 10, + Property_RowSpan = 11, + Property_Minimum = 12, + Property_Maximum = 13, + Property_SpecialValueText = 14, + Property_Prefix = 15, + Property_Suffix = 16, + Property_SingleStep = 17, + Property_Wrapping = 18, + Property_TickInterval = 19, + Property_TicksAbove = 20, + Property_TicksBelow = 21, + + //Stringlists should always be the last items + Property_DefaultValues = 22, + Property_DeclaredValues = 23, + Property_DefinedValues = 24, + Property_Proxies = 25 + }; + + ///Basic setting widget types. + enum SettingType + { + /* + * 0 - 9 - Boolean widgets + * 10-19 - List widgets + * 21-29 - Range widgets + * 31-39 - Text widgets + * + * Each range corresponds to a View_Type enum by a factor of 10. + * + * Even-numbered values are single-value widgets + * Odd-numbered values are multi-valued widgets + */ - typedef QList SettingList; - typedef QMap SettingMap; - typedef QMap SectionMap; + Type_CheckBox = 0, + Type_RadioButton = 1, + Type_ListView = 10, + Type_ComboBox = 11, + Type_SpinBox = 21, + Type_DoubleSpinBox = 23, + Type_Slider = 25, + Type_Dial = 27, + Type_TextArea = 30, + Type_LineEdit = 31, + Type_Undefined = 40 + }; - struct QStringPair +} + +namespace CSVSettings +{ + ///Categorical view types which encompass the setting widget types + enum ViewType { - QStringPair(): left (""), right ("") - {} + ViewType_Boolean = 0, + ViewType_List = 1, + ViewType_Range = 2, + ViewType_Text = 3, + ViewType_Undefined = 4 + }; +} - QStringPair (const QString &leftValue, const QString &rightValue) - : left (leftValue), right(rightValue) - {} - QStringPair (const QStringPair &pair) - : left (pair.left), right (pair.right) - {} +namespace CSMSettings +{ + ///used to construct default settings in the Setting class + struct PropertyDefaultValues + { + int id; + QString name; + QVariant value; + }; - QString left; - QString right; + ///strings which correspond to setting values. These strings represent + ///the script language keywords which would be used to declare setting + ///views for 3rd party addons + const QString sPropertyNames[] = + { + "name", "page", "setting_type", "is_multi_value", + "is_multi_line", "widget_width", "view_row", "view_column", "delimiter", + "is_serializable","column_span", "row_span", "minimum", "maximum", + "special_value_text", "prefix", "suffix", "single_step", "wrapping", + "tick_interval", "ticks_above", "ticks_below", + "defaults", "declarations", "definitions", "proxies" + }; - bool isEmpty() const - { return (left.isEmpty() && right.isEmpty()); } + ///Default values for a setting. Used in setting creation. + const QString sPropertyDefaults[] = + { + "", //name + "", //page + "40", //setting type + "false", //multivalue + "false", //multiline + "7", //widget width + "-1", //view row + "-1", //view column + ",", //delimiter + "true", //serialized + "1", //column span + "1", //row span + "0", //value range + "0", //value minimum + "0", //value maximum + "", //special text + "", //prefix + "", //suffix + "false", //wrapping + "1", //tick interval + "false", //ticks above + "true", //ticks below + "", //default values + "", //declared values + "", //defined values + "" //proxy values }; } -#endif // MODEL_SUPPORT_HPP + +#endif // VIEW_SUPPORT_HPP diff --git a/apps/opencs/model/settings/usersettings.cpp b/apps/opencs/model/settings/usersettings.cpp index 94cee8a43..04f98f0d6 100644 --- a/apps/opencs/model/settings/usersettings.cpp +++ b/apps/opencs/model/settings/usersettings.cpp @@ -1,19 +1,15 @@ #include "usersettings.hpp" -#include -#include -#include -#include -#include -#include -#include - +#include #include #include -#include "settingcontainer.hpp" #include +#include "setting.hpp" +#include "support.hpp" +#include + /** * Workaround for problems with whitespaces in paths in older versions of Boost library */ @@ -32,324 +28,400 @@ namespace boost CSMSettings::UserSettings *CSMSettings::UserSettings::mUserSettingsInstance = 0; -CSMSettings::UserSettings::UserSettings() +CSMSettings::UserSettings::UserSettings (const Files::ConfigurationManager& configurationManager) +: mCfgMgr (configurationManager) { assert(!mUserSettingsInstance); mUserSettingsInstance = this; - mReadWriteMessage = QObject::tr("
Could not open or create file for writing

\ - Please make sure you have the right permissions and try again.
"); + mSettingDefinitions = 0; - mReadOnlyMessage = QObject::tr("
Could not open file for reading

\ - Please make sure you have the right permissions and try again.
"); - - buildEditorSettingDefaults(); + buildSettingModelDefaults(); } -void CSMSettings::UserSettings::buildEditorSettingDefaults() +void CSMSettings::UserSettings::buildSettingModelDefaults() { - SettingContainer *windowHeight = new SettingContainer("768", this); - SettingContainer *windowWidth = new SettingContainer("1024", this); - SettingContainer *rsDelegate = new SettingContainer("Icon and Text", this); - SettingContainer *refIdTypeDelegate = new SettingContainer("Icon and Text", this); + QString section = "Window Size"; + { + Setting *width = createSetting (Type_LineEdit, section, "Width"); + Setting *height = createSetting (Type_LineEdit, section, "Height"); - windowHeight->setObjectName ("Height"); - windowWidth->setObjectName ("Width"); - rsDelegate->setObjectName ("Record Status Display"); - refIdTypeDelegate->setObjectName ("Referenceable ID Type Display"); + width->setWidgetWidth (5); + height->setWidgetWidth (8); - SettingMap *displayFormatMap = new SettingMap; - SettingMap *windowSizeMap = new SettingMap; + width->setDefaultValues (QStringList() << "1024"); + height->setDefaultValues (QStringList() << "768"); - displayFormatMap->insert (rsDelegate->objectName(), rsDelegate ); - displayFormatMap->insert (refIdTypeDelegate->objectName(), refIdTypeDelegate); + width->setEditorSetting (true); + height->setEditorSetting (true); - windowSizeMap->insert (windowWidth->objectName(), windowWidth ); - windowSizeMap->insert (windowHeight->objectName(), windowHeight ); + height->setViewLocation (2,2); + width->setViewLocation (2,1); - mEditorSettingDefaults.insert ("Display Format", displayFormatMap); - mEditorSettingDefaults.insert ("Window Size", windowSizeMap); -} + /* + *Create the proxy setting for predefined values + */ + Setting *preDefined = createSetting (Type_ComboBox, section, + "Pre-Defined"); -CSMSettings::UserSettings::~UserSettings() -{ - mUserSettingsInstance = 0; -} - -QTextStream *CSMSettings::UserSettings::openFileStream (const QString &filePath, bool isReadOnly) const -{ - QIODevice::OpenMode openFlags = QIODevice::Text; + preDefined->setDeclaredValues (QStringList() << "640 x 480" + << "800 x 600" << "1024 x 768" << "1440 x 900"); - if (isReadOnly) - openFlags = QIODevice::ReadOnly | openFlags; - else - openFlags = QIODevice::ReadWrite | QIODevice::Truncate | openFlags; + preDefined->setViewLocation (1, 1); + preDefined->setWidgetWidth (10); + preDefined->setColumnSpan (2); - QFile *file = new QFile(filePath); - QTextStream *stream = 0; + preDefined->addProxy (width, + QStringList() << "640" << "800" << "1024" << "1440" + ); - if (file->open(openFlags)) - { - stream = new QTextStream(file); - stream->setCodec(QTextCodec::codecForName("UTF-8")); + preDefined->addProxy (height, + QStringList() << "480" << "600" << "768" << "900" + ); } - return stream; - -} - -bool CSMSettings::UserSettings::writeSettings(QMap &settings) -{ - QTextStream *stream = openFileStream(mUserFilePath); - - bool success = (stream); - - if (success) + section = "Display Format"; { - QList keyList = settings.keys(); - - foreach (QString key, keyList) - { - SettingList *sectionSettings = settings[key]; + QString defaultValue = "Icon and Text"; - *stream << "[" << key << "]" << '\n'; + QStringList values = QStringList() + << defaultValue << "Icon Only" << "Text Only"; - foreach (SettingContainer *item, *sectionSettings) - *stream << item->objectName() << " = " << item->getValue() << '\n'; - } + Setting *rsd = createSetting (Type_RadioButton, + section, "Record Status Display"); - stream->device()->close(); - delete stream; - stream = 0; - } - else - { - displayFileErrorMessage(mReadWriteMessage, false); - } + Setting *ritd = createSetting (Type_RadioButton, + section, "Referenceable ID Type Display"); - return (success); -} + rsd->setDeclaredValues (values); + ritd->setDeclaredValues (values); + rsd->setEditorSetting (true); + ritd->setEditorSetting (true); + } -const CSMSettings::SectionMap &CSMSettings::UserSettings::getSectionMap() const -{ - return mSectionSettings; + section = "Proxy Selection Test"; + { + /****************************************************************** + * There are three types of values: + * + * Declared values + * + * Pre-determined values, typically for + * combobox drop downs and boolean (radiobutton / checkbox) labels. + * These values represent the total possible list of values that + * may define a setting. No other values are allowed. + * + * Defined values + * + * Values which represent the actual, current value of + * a setting. For settings with declared values, this must be one + * or several declared values, as appropriate. + * + * Proxy values + * Values the proxy master updates the proxy slave when + * it's own definition is set / changed. These are definitions for + * proxy slave settings, but must match any declared values the + * proxy slave has, if any. + *******************************************************************/ +/* + //create setting objects, specifying the basic widget type, + //the page name, and the view name + + Setting *masterBoolean = createSetting (Type_RadioButton, section, + "Master Proxy"); + + Setting *slaveBoolean = createSetting (Type_CheckBox, section, + "Proxy Checkboxes"); + + Setting *slaveSingleText = createSetting (Type_LineEdit, section, + "Proxy TextBox 1"); + + Setting *slaveMultiText = createSetting (Type_LineEdit, section, + "ProxyTextBox 2"); + + Setting *slaveAlphaSpinbox = createSetting (Type_SpinBox, section, + "Alpha Spinbox"); + + Setting *slaveIntegerSpinbox = createSetting (Type_SpinBox, section, + "Int Spinbox"); + + Setting *slaveDoubleSpinbox = createSetting (Type_DoubleSpinBox, + section, "Double Spinbox"); + + Setting *slaveSlider = createSetting (Type_Slider, section, "Slider"); + + Setting *slaveDial = createSetting (Type_Dial, section, "Dial"); + + //set declared values for selected views + masterBoolean->setDeclaredValues (QStringList() + << "Profile One" << "Profile Two" + << "Profile Three" << "Profile Four"); + + slaveBoolean->setDeclaredValues (QStringList() + << "One" << "Two" << "Three" << "Four" << "Five"); + + slaveAlphaSpinbox->setDeclaredValues (QStringList() + << "One" << "Two" << "Three" << "Four"); + + + masterBoolean->addProxy (slaveBoolean, QList () + << (QStringList() << "One" << "Three") + << (QStringList() << "One" << "Three") + << (QStringList() << "One" << "Three" << "Five") + << (QStringList() << "Two" << "Four") + ); + + masterBoolean->addProxy (slaveSingleText, QList () + << (QStringList() << "Text A") + << (QStringList() << "Text B") + << (QStringList() << "Text A") + << (QStringList() << "Text C") + ); + + masterBoolean->addProxy (slaveMultiText, QList () + << (QStringList() << "One" << "Three") + << (QStringList() << "One" << "Three") + << (QStringList() << "One" << "Three" << "Five") + << (QStringList() << "Two" << "Four") + ); + + masterBoolean->addProxy (slaveAlphaSpinbox, QList () + << (QStringList() << "Four") + << (QStringList() << "Three") + << (QStringList() << "Two") + << (QStringList() << "One")); + + masterBoolean->addProxy (slaveIntegerSpinbox, QList () + << (QStringList() << "0") + << (QStringList() << "7") + << (QStringList() << "14") + << (QStringList() << "21")); + + masterBoolean->addProxy (slaveDoubleSpinbox, QList () + << (QStringList() << "0.17") + << (QStringList() << "0.34") + << (QStringList() << "0.51") + << (QStringList() << "0.68")); + + masterBoolean->addProxy (slaveSlider, QList () + << (QStringList() << "25") + << (QStringList() << "50") + << (QStringList() << "75") + << (QStringList() << "100") + ); + + masterBoolean->addProxy (slaveDial, QList () + << (QStringList() << "25") + << (QStringList() << "50") + << (QStringList() << "75") + << (QStringList() << "100") + ); + + //settings with proxies are not serialized by default + //other settings non-serialized for demo purposes + slaveBoolean->setSerializable (false); + slaveSingleText->setSerializable (false); + slaveMultiText->setSerializable (false); + slaveAlphaSpinbox->setSerializable (false); + slaveIntegerSpinbox->setSerializable (false); + slaveDoubleSpinbox->setSerializable (false); + slaveSlider->setSerializable (false); + slaveDial->setSerializable (false); + + slaveBoolean->setDefaultValues (QStringList() + << "One" << "Three" << "Five"); + + slaveSingleText->setDefaultValue ("Text A"); + + slaveMultiText->setDefaultValues (QStringList() + << "One" << "Three" << "Five"); + + slaveSingleText->setWidgetWidth (24); + slaveMultiText->setWidgetWidth (24); + + slaveAlphaSpinbox->setDefaultValue ("Two"); + slaveAlphaSpinbox->setWidgetWidth (20); + //slaveAlphaSpinbox->setPrefix ("No. "); + //slaveAlphaSpinbox->setSuffix ("!"); + slaveAlphaSpinbox->setWrapping (true); + + slaveIntegerSpinbox->setDefaultValue (14); + slaveIntegerSpinbox->setMinimum (0); + slaveIntegerSpinbox->setMaximum (58); + slaveIntegerSpinbox->setPrefix ("$"); + slaveIntegerSpinbox->setSuffix (".00"); + slaveIntegerSpinbox->setWidgetWidth (10); + slaveIntegerSpinbox->setSpecialValueText ("Nothing!"); + + slaveDoubleSpinbox->setDefaultValue (0.51); + slaveDoubleSpinbox->setSingleStep(0.17); + slaveDoubleSpinbox->setMaximum(4.0); + + slaveSlider->setMinimum (0); + slaveSlider->setMaximum (100); + slaveSlider->setDefaultValue (75); + slaveSlider->setWidgetWidth (100); + slaveSlider->setTicksAbove (true); + slaveSlider->setTickInterval (25); + + slaveDial->setMinimum (0); + slaveDial->setMaximum (100); + slaveDial->setSingleStep (5); + slaveDial->setDefaultValue (75); + slaveDial->setTickInterval (25); +*/ + } } -const CSMSettings::SettingMap *CSMSettings::UserSettings::getSettings(const QString §ionName) const +CSMSettings::UserSettings::~UserSettings() { - return getValidSettings(sectionName); + mUserSettingsInstance = 0; } -bool CSMSettings::UserSettings::loadFromFile(const QString &filePath) +void CSMSettings::UserSettings::loadSettings (const QString &fileName) { - if (filePath.isEmpty()) - return false; - - SectionMap loadedSettings; + QString userFilePath = QString::fromUtf8 + (mCfgMgr.getUserConfigPath().string().c_str()); - QTextStream *stream = openFileStream (filePath, true); + QString globalFilePath = QString::fromUtf8 + (mCfgMgr.getGlobalPath().string().c_str()); - bool success = (stream); + QString otherFilePath = globalFilePath; - if (success) + //test for local only if global fails (uninstalled copy) + if (!QFile (globalFilePath + fileName).exists()) { - //looks for a square bracket, "'\\[" - //that has one or more "not nothing" in it, "([^]]+)" - //and is closed with a square bracket, "\\]" - - QRegExp sectionRe("^\\[([^]]+)\\]"); - - //Find any character(s) that is/are not equal sign(s), "[^=]+" - //followed by an optional whitespace, an equal sign, and another optional whitespace, "\\s*=\\s*" - //and one or more periods, "(.+)" - - QRegExp keyRe("^([^=]+)\\s*=\\s*(.+)$"); + //if global is invalid, use the local path + otherFilePath = QString::fromUtf8 + (mCfgMgr.getLocalPath().string().c_str()); + } - CSMSettings::SettingMap *settings = 0; - QString section = "none"; + QSettings::setPath + (QSettings::IniFormat, QSettings::UserScope, userFilePath); - while (!stream->atEnd()) - { - QString line = stream->readLine().simplified(); + QSettings::setPath + (QSettings::IniFormat, QSettings::SystemScope, otherFilePath); - if (line.isEmpty() || line.startsWith("#")) - continue; + mSettingDefinitions = new QSettings + (QSettings::IniFormat, QSettings::UserScope, "opencs", QString(), this); +} - //if a section is found, push it onto a new QStringList - //and push the QStringList onto - if (sectionRe.exactMatch(line)) - { - //add the previous section's settings to the member map - if (settings) - loadedSettings.insert(section, settings); - - //save new section and create a new list - section = sectionRe.cap(1); - settings = new SettingMap; - continue; - } +bool CSMSettings::UserSettings::hasSettingDefinitions + (const QString &viewKey) const +{ + return (mSettingDefinitions->contains (viewKey)); +} - if (keyRe.indexIn(line) != -1) - { - SettingContainer *sc = new SettingContainer (keyRe.cap(2).simplified()); - sc->setObjectName(keyRe.cap(1).simplified()); - (*settings)[keyRe.cap(1).simplified()] = sc; - } +void CSMSettings::UserSettings::setDefinitions + (const QString &key, const QStringList &list) +{ + mSettingDefinitions->setValue (key, list); +} - } +void CSMSettings::UserSettings::saveDefinitions() const +{ + mSettingDefinitions->sync(); +} - loadedSettings.insert(section, settings); +QString CSMSettings::UserSettings::settingValue (const QString &settingKey) +{ + if (!mSettingDefinitions->contains (settingKey)) + return QString(); - stream->device()->close(); - delete stream; - stream = 0; - } + QStringList defs = mSettingDefinitions->value (settingKey).toStringList(); - mergeMap (loadedSettings); + if (defs.isEmpty()) + return QString(); - return success; + return defs.at(0); } -void CSMSettings::UserSettings::mergeMap (const CSMSettings::SectionMap §ionSettings) +CSMSettings::UserSettings& CSMSettings::UserSettings::instance() { - foreach (QString key, sectionSettings.uniqueKeys()) - { - // insert entire section if it does not already exist in the loaded files - if (mSectionSettings.find(key) == mSectionSettings.end()) - mSectionSettings.insert(key, sectionSettings.value(key)); - else - { - SettingMap *passedSettings = sectionSettings.value(key); - SettingMap *settings = mSectionSettings.value(key); - - foreach (QString key2, passedSettings->uniqueKeys()) - { - //insert section settings individially if they do not already exist - if (settings->find(key2) == settings->end()) - settings->insert(key2, passedSettings->value(key2)); - else - { - settings->value(key2)->update(passedSettings->value(key2)->getValue()); - } - } - } - } + assert(mUserSettingsInstance); + return *mUserSettingsInstance; } -void CSMSettings::UserSettings::loadSettings (const QString &fileName) +void CSMSettings::UserSettings::updateUserSetting(const QString &settingKey, + const QStringList &list) { - mSectionSettings.clear(); - - //global - QString globalFilePath = QString::fromStdString(mCfgMgr.getGlobalPath().string()) + fileName; - bool globalOk = loadFromFile(globalFilePath); - + mSettingDefinitions->setValue (settingKey ,list); - //local - QString localFilePath = QString::fromStdString(mCfgMgr.getLocalPath().string()) + fileName; - bool localOk = loadFromFile(localFilePath); - - //user - mUserFilePath = QString::fromStdString(mCfgMgr.getUserConfigPath().string()) + fileName; - loadFromFile(mUserFilePath); + emit userSettingUpdated (settingKey, list); +} - if (!(localOk || globalOk)) +CSMSettings::Setting *CSMSettings::UserSettings::findSetting + (const QString &pageName, const QString &settingName) +{ + foreach (Setting *setting, mSettings) { - QString message = QObject::tr("
Could not open user settings files for reading

\ - Global and local settings files could not be read.\ - You may have incorrect file permissions or the OpenCS installation may be corrupted.
"); - - message += QObject::tr("
Global filepath: ") + globalFilePath; - message += QObject::tr("
Local filepath: ") + localFilePath; - - displayFileErrorMessage ( message, true); + if (setting->name() == settingName) + { + if (setting->page() == pageName) + return setting; + } } + return 0; } -void CSMSettings::UserSettings::updateSettings (const QString §ionName, const QString &settingName) +void CSMSettings::UserSettings::removeSetting + (const QString &pageName, const QString &settingName) { - - SettingMap *settings = getValidSettings(sectionName); - - if (!settings) + if (mSettings.isEmpty()) return; - if (settingName.isEmpty()) - { - foreach (const SettingContainer *setting, *settings) - emit signalUpdateEditorSetting (setting->objectName(), setting->getValue()); - } - else + QList ::iterator removeIterator = mSettings.begin(); + + while (removeIterator != mSettings.end()) { - if (settings->find(settingName) != settings->end()) + if ((*removeIterator)->name() == settingName) { - const SettingContainer *setting = settings->value(settingName); - emit signalUpdateEditorSetting (setting->objectName(), setting->getValue()); + if ((*removeIterator)->page() == pageName) + { + mSettings.erase (removeIterator); + break; + } } + removeIterator++; } } -QString CSMSettings::UserSettings::getSetting (const QString §ion, const QString &setting) const -{ - SettingMap *settings = getValidSettings(section); - QString retVal = ""; +CSMSettings::SettingPageMap CSMSettings::UserSettings::settingPageMap() const +{ + SettingPageMap pageMap; - if (settings->find(setting) != settings->end()) - retVal = settings->value(setting)->getValue(); + foreach (Setting *setting, mSettings) + pageMap[setting->page()].append (setting); - return retVal; + return pageMap; } -CSMSettings::UserSettings& CSMSettings::UserSettings::instance() +CSMSettings::Setting *CSMSettings::UserSettings::createSetting + (CSMSettings::SettingType typ, const QString &page, const QString &name) { - assert(mUserSettingsInstance); - return *mUserSettingsInstance; -} + //get list of all settings for the current setting name + if (findSetting (page, name)) + { + qWarning() << "Duplicate declaration encountered: " + << (name + '/' + page); + return 0; + } -void CSMSettings::UserSettings::displayFileErrorMessage(const QString &message, bool isReadOnly) -{ - // File cannot be opened or created - QMessageBox msgBox; - msgBox.setWindowTitle(QObject::tr("OpenCS configuration file I/O error")); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - - if (!isReadOnly) - msgBox.setText (mReadWriteMessage + message); - else - msgBox.setText (message); - - msgBox.exec(); -} + Setting *setting = new Setting (typ, name, page); -CSMSettings::SettingMap * -CSMSettings::UserSettings::getValidSettings (const QString §ionName) const -{ - SettingMap *settings = 0; - //copy the default values for the entire section if it's not found - if (mSectionSettings.find(sectionName) == mSectionSettings.end()) - { - if (mEditorSettingDefaults.find(sectionName) != mEditorSettingDefaults.end()) - settings = mEditorSettingDefaults.value (sectionName); - } - //otherwise, iterate the section's settings, looking for missing values and replacing them with defaults. - else - { - SettingMap *loadedSettings = mSectionSettings[sectionName]; - SettingMap *defaultSettings = mEditorSettingDefaults[sectionName]; + //add declaration to the model + mSettings.append (setting); - foreach (QString key, defaultSettings->uniqueKeys()) - { - //write the default value to the loaded settings - if (loadedSettings->find((key))==loadedSettings->end()) - loadedSettings->insert(key, defaultSettings->value(key)); - } + return setting; +} - settings = mSectionSettings.value (sectionName); - } +QStringList CSMSettings::UserSettings::definitions (const QString &viewKey) const +{ + if (mSettingDefinitions->contains (viewKey)) + return mSettingDefinitions->value (viewKey).toStringList(); - return settings; + return QStringList(); } diff --git a/apps/opencs/model/settings/usersettings.hpp b/apps/opencs/model/settings/usersettings.hpp index 63e78bd61..7e553caf6 100644 --- a/apps/opencs/model/settings/usersettings.hpp +++ b/apps/opencs/model/settings/usersettings.hpp @@ -1,13 +1,12 @@ #ifndef USERSETTINGS_HPP #define USERSETTINGS_HPP -#include +#include #include #include #include #include - #include "support.hpp" #ifndef Q_MOC_RUN @@ -18,77 +17,79 @@ namespace Files { typedef std::vector PathContainer; struct ConfigurationManager;} class QFile; +class QSettings; namespace CSMSettings { - struct UserSettings: public QObject + class Setting; + typedef QMap > SettingPageMap; + + class UserSettings: public QObject { Q_OBJECT - SectionMap mSectionSettings; - SectionMap mEditorSettingDefaults; - static UserSettings *mUserSettingsInstance; - QString mUserFilePath; - Files::ConfigurationManager mCfgMgr; - QString mReadOnlyMessage; - QString mReadWriteMessage; + const Files::ConfigurationManager& mCfgMgr; + + QSettings *mSettingDefinitions; + QList mSettings; public: /// Singleton implementation static UserSettings& instance(); - UserSettings(); + UserSettings (const Files::ConfigurationManager& configurationManager); ~UserSettings(); - UserSettings (UserSettings const &); //not implemented - void operator= (UserSettings const &); //not implemented - - /// Writes settings to the last loaded settings file - bool writeSettings(QMap §ions); - - /// Called from editor to trigger signal to update the specified setting. - /// If no setting name is specified, all settings found in the specified section are updated. - void updateSettings (const QString §ionName, const QString &settingName = ""); + UserSettings (UserSettings const &); //not implemented + UserSettings& operator= (UserSettings const &); //not implemented /// Retrieves the settings file at all three levels (global, local and user). - - /// \todo Multi-valued settings are not fully implemented. Setting values - /// \todo loaded in later files will always overwrite previously loaded values. void loadSettings (const QString &fileName); - /// Returns the entire map of settings across all sections - const SectionMap &getSectionMap () const; + /// Updates QSettings and syncs with the ini file + void setDefinitions (const QString &key, const QStringList &defs); - const SettingMap *getSettings (const QString §ionName) const; + QString settingValue (const QString &settingKey); - /// Retrieves the value as a QString of the specified setting in the specified section - QString getSetting(const QString §ion, const QString &setting) const; + ///retrieve a setting object from a given page and setting name + Setting *findSetting + (const QString &pageName, const QString &settingName = QString()); - private: + ///remove a setting from the list + void removeSetting + (const QString &pageName, const QString &settingName); + ///Retreive a map of the settings, keyed by page name + SettingPageMap settingPageMap() const; - /// Opens a QTextStream from the provided path as read-only or read-write. - QTextStream *openFileStream (const QString &filePath, bool isReadOnly = false) const; + ///Returns a string list of defined vlaues for the specified setting + ///in "page/name" format. + QStringList definitions (const QString &viewKey) const; - /// Parses a setting file specified in filePath from the provided text stream. - bool loadFromFile (const QString &filePath = ""); + ///Test to indicate whether or not a setting has any definitions + bool hasSettingDefinitions (const QString &viewKey) const; - /// merge the passed map into mSectionSettings - void mergeMap (const SectionMap &); + ///Save any unsaved changes in the QSettings object + void saveDefinitions() const; - void displayFileErrorMessage(const QString &message, bool isReadOnly); + private: - void buildEditorSettingDefaults(); + void buildSettingModelDefaults(); - SettingMap *getValidSettings (const QString §ionName) const; + ///add a new setting to the model and return it + Setting *createSetting (CSMSettings::SettingType typ, + const QString &page, const QString &name); signals: - void signalUpdateEditorSetting (const QString &settingName, const QString &settingValue); + void userSettingUpdated (const QString &, const QStringList &); + + public slots: + void updateUserSetting (const QString &, const QStringList &); }; } #endif // USERSETTINGS_HPP diff --git a/apps/opencs/model/tools/birthsigncheck.cpp b/apps/opencs/model/tools/birthsigncheck.cpp index 59c65086e..db20ce4bc 100644 --- a/apps/opencs/model/tools/birthsigncheck.cpp +++ b/apps/opencs/model/tools/birthsigncheck.cpp @@ -17,7 +17,7 @@ int CSMTools::BirthsignCheckStage::setup() return mBirthsigns.getSize(); } -void CSMTools::BirthsignCheckStage::perform (int stage, std::vector& messages) +void CSMTools::BirthsignCheckStage::perform (int stage, Messages& messages) { const CSMWorld::Record& record = mBirthsigns.getRecord (stage); @@ -30,13 +30,13 @@ void CSMTools::BirthsignCheckStage::perform (int stage, std::vector // test for empty name, description and texture if (birthsign.mName.empty()) - messages.push_back (id.toString() + "|" + birthsign.mId + " has an empty name"); + messages.push_back (std::make_pair (id, birthsign.mId + " has an empty name")); if (birthsign.mDescription.empty()) - messages.push_back (id.toString() + "|" + birthsign.mId + " has an empty description"); + messages.push_back (std::make_pair (id, birthsign.mId + " has an empty description")); if (birthsign.mTexture.empty()) - messages.push_back (id.toString() + "|" + birthsign.mId + " is missing a texture"); + messages.push_back (std::make_pair (id, birthsign.mId + " is missing a texture")); /// \todo test if the texture exists diff --git a/apps/opencs/model/tools/birthsigncheck.hpp b/apps/opencs/model/tools/birthsigncheck.hpp index bdd65b44a..1030e5c02 100644 --- a/apps/opencs/model/tools/birthsigncheck.hpp +++ b/apps/opencs/model/tools/birthsigncheck.hpp @@ -21,7 +21,7 @@ namespace CSMTools virtual int setup(); ///< \return number of steps - virtual void perform (int stage, std::vector& messages); + virtual void perform (int stage, Messages& messages); ///< Messages resulting from this tage will be appended to \a messages. }; } diff --git a/apps/opencs/model/tools/classcheck.cpp b/apps/opencs/model/tools/classcheck.cpp index 6923b3153..cea4f3a68 100644 --- a/apps/opencs/model/tools/classcheck.cpp +++ b/apps/opencs/model/tools/classcheck.cpp @@ -18,7 +18,7 @@ int CSMTools::ClassCheckStage::setup() return mClasses.getSize(); } -void CSMTools::ClassCheckStage::perform (int stage, std::vector& messages) +void CSMTools::ClassCheckStage::perform (int stage, Messages& messages) { const CSMWorld::Record& record = mClasses.getRecord (stage); @@ -31,10 +31,10 @@ void CSMTools::ClassCheckStage::perform (int stage, std::vector& me // test for empty name and description if (class_.mName.empty()) - messages.push_back (id.toString() + "|" + class_.mId + " has an empty name"); + messages.push_back (std::make_pair (id, class_.mId + " has an empty name")); if (class_.mDescription.empty()) - messages.push_back (id.toString() + "|" + class_.mId + " has an empty description"); + messages.push_back (std::make_pair (id, class_.mId + " has an empty description")); // test for invalid attributes for (int i=0; i<2; ++i) @@ -42,18 +42,14 @@ void CSMTools::ClassCheckStage::perform (int stage, std::vector& me { std::ostringstream stream; - stream << id.toString() << "|Attribute #" << i << " of " << class_.mId << " is not set"; + stream << "Attribute #" << i << " of " << class_.mId << " is not set"; - messages.push_back (stream.str()); + messages.push_back (std::make_pair (id, stream.str())); } if (class_.mData.mAttribute[0]==class_.mData.mAttribute[1] && class_.mData.mAttribute[0]!=-1) { - std::ostringstream stream; - - stream << id.toString() << "|Class lists same attribute twice"; - - messages.push_back (stream.str()); + messages.push_back (std::make_pair (id, "Class lists same attribute twice")); } // test for non-unique skill @@ -66,12 +62,7 @@ void CSMTools::ClassCheckStage::perform (int stage, std::vector& me for (std::map::const_iterator iter (skills.begin()); iter!=skills.end(); ++iter) if (iter->second>1) { - std::ostringstream stream; - - stream - << id.toString() << "|" - << ESM::Skill::indexToId (iter->first) << " is listed more than once"; - - messages.push_back (stream.str()); + messages.push_back (std::make_pair (id, + ESM::Skill::indexToId (iter->first) + " is listed more than once")); } } \ No newline at end of file diff --git a/apps/opencs/model/tools/classcheck.hpp b/apps/opencs/model/tools/classcheck.hpp index 3604b451c..ec50ba35d 100644 --- a/apps/opencs/model/tools/classcheck.hpp +++ b/apps/opencs/model/tools/classcheck.hpp @@ -21,7 +21,7 @@ namespace CSMTools virtual int setup(); ///< \return number of steps - virtual void perform (int stage, std::vector& messages); + virtual void perform (int stage, Messages& messages); ///< Messages resulting from this tage will be appended to \a messages. }; } diff --git a/apps/opencs/model/tools/factioncheck.cpp b/apps/opencs/model/tools/factioncheck.cpp index c219e5610..42d577163 100644 --- a/apps/opencs/model/tools/factioncheck.cpp +++ b/apps/opencs/model/tools/factioncheck.cpp @@ -18,7 +18,7 @@ int CSMTools::FactionCheckStage::setup() return mFactions.getSize(); } -void CSMTools::FactionCheckStage::perform (int stage, std::vector& messages) +void CSMTools::FactionCheckStage::perform (int stage, Messages& messages) { const CSMWorld::Record& record = mFactions.getRecord (stage); @@ -31,16 +31,12 @@ void CSMTools::FactionCheckStage::perform (int stage, std::vector& // test for empty name if (faction.mName.empty()) - messages.push_back (id.toString() + "|" + faction.mId + " has an empty name"); + messages.push_back (std::make_pair (id, faction.mId + " has an empty name")); // test for invalid attributes if (faction.mData.mAttribute[0]==faction.mData.mAttribute[1] && faction.mData.mAttribute[0]!=-1) { - std::ostringstream stream; - - stream << id.toString() << "|Faction lists same attribute twice"; - - messages.push_back (stream.str()); + messages.push_back (std::make_pair (id , "Faction lists same attribute twice")); } // test for non-unique skill @@ -53,13 +49,8 @@ void CSMTools::FactionCheckStage::perform (int stage, std::vector& for (std::map::const_iterator iter (skills.begin()); iter!=skills.end(); ++iter) if (iter->second>1) { - std::ostringstream stream; - - stream - << id.toString() << "|" - << ESM::Skill::indexToId (iter->first) << " is listed more than once"; - - messages.push_back (stream.str()); + messages.push_back (std::make_pair (id, + ESM::Skill::indexToId (iter->first) + " is listed more than once")); } /// \todo check data members that can't be edited in the table view diff --git a/apps/opencs/model/tools/factioncheck.hpp b/apps/opencs/model/tools/factioncheck.hpp index 7cd80347d..ccc44e6a9 100644 --- a/apps/opencs/model/tools/factioncheck.hpp +++ b/apps/opencs/model/tools/factioncheck.hpp @@ -21,7 +21,7 @@ namespace CSMTools virtual int setup(); ///< \return number of steps - virtual void perform (int stage, std::vector& messages); + virtual void perform (int stage, Messages& messages); ///< Messages resulting from this tage will be appended to \a messages. }; } diff --git a/apps/opencs/model/tools/mandatoryid.cpp b/apps/opencs/model/tools/mandatoryid.cpp index b99abec6d..412e9f2f0 100644 --- a/apps/opencs/model/tools/mandatoryid.cpp +++ b/apps/opencs/model/tools/mandatoryid.cpp @@ -15,9 +15,10 @@ int CSMTools::MandatoryIdStage::setup() return mIds.size(); } -void CSMTools::MandatoryIdStage::perform (int stage, std::vector& messages) +void CSMTools::MandatoryIdStage::perform (int stage, Messages& messages) { if (mIdCollection.searchId (mIds.at (stage))==-1 || mIdCollection.getRecord (mIds.at (stage)).isDeleted()) - messages.push_back (mCollectionId.toString() + "|Missing mandatory record: " + mIds.at (stage)); + messages.push_back (std::make_pair (mCollectionId, + "Missing mandatory record: " + mIds.at (stage))); } \ No newline at end of file diff --git a/apps/opencs/model/tools/mandatoryid.hpp b/apps/opencs/model/tools/mandatoryid.hpp index 5fddf08d3..a8afea62a 100644 --- a/apps/opencs/model/tools/mandatoryid.hpp +++ b/apps/opencs/model/tools/mandatoryid.hpp @@ -30,7 +30,7 @@ namespace CSMTools virtual int setup(); ///< \return number of steps - virtual void perform (int stage, std::vector& messages); + virtual void perform (int stage, Messages& messages); ///< Messages resulting from this tage will be appended to \a messages. }; } diff --git a/apps/opencs/model/tools/racecheck.cpp b/apps/opencs/model/tools/racecheck.cpp index 413de5ef0..47aeda1e6 100644 --- a/apps/opencs/model/tools/racecheck.cpp +++ b/apps/opencs/model/tools/racecheck.cpp @@ -7,7 +7,7 @@ #include "../world/universalid.hpp" -void CSMTools::RaceCheckStage::performPerRecord (int stage, std::vector& messages) +void CSMTools::RaceCheckStage::performPerRecord (int stage, Messages& messages) { const CSMWorld::Record& record = mRaces.getRecord (stage); @@ -20,24 +20,24 @@ void CSMTools::RaceCheckStage::performPerRecord (int stage, std::vector& messages) +void CSMTools::RaceCheckStage::performFinal (Messages& messages) { CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Races); if (!mPlayable) - messages.push_back (id.toString() + "|No playable race"); + messages.push_back (std::make_pair (id, "No playable race")); } CSMTools::RaceCheckStage::RaceCheckStage (const CSMWorld::IdCollection& races) @@ -64,7 +64,7 @@ int CSMTools::RaceCheckStage::setup() return mRaces.getSize()+1; } -void CSMTools::RaceCheckStage::perform (int stage, std::vector& messages) +void CSMTools::RaceCheckStage::perform (int stage, Messages& messages) { if (stage==mRaces.getSize()) performFinal (messages); diff --git a/apps/opencs/model/tools/racecheck.hpp b/apps/opencs/model/tools/racecheck.hpp index ff9948bf6..c68b283be 100644 --- a/apps/opencs/model/tools/racecheck.hpp +++ b/apps/opencs/model/tools/racecheck.hpp @@ -15,9 +15,9 @@ namespace CSMTools const CSMWorld::IdCollection& mRaces; bool mPlayable; - void performPerRecord (int stage, std::vector& messages); + void performPerRecord (int stage, Messages& messages); - void performFinal (std::vector& messages); + void performFinal (Messages& messages); public: @@ -26,7 +26,7 @@ namespace CSMTools virtual int setup(); ///< \return number of steps - virtual void perform (int stage, std::vector& messages); + virtual void perform (int stage, Messages& messages); ///< Messages resulting from this tage will be appended to \a messages. }; } diff --git a/apps/opencs/model/tools/referenceablecheck.cpp b/apps/opencs/model/tools/referenceablecheck.cpp index dab61bfff..1816d0808 100644 --- a/apps/opencs/model/tools/referenceablecheck.cpp +++ b/apps/opencs/model/tools/referenceablecheck.cpp @@ -1,7 +1,9 @@ #include "referenceablecheck.hpp" + +#include + #include "../world/record.hpp" #include "../world/universalid.hpp" -#include CSMTools::ReferenceableCheckStage::ReferenceableCheckStage( const CSMWorld::RefIdData& referenceable, const CSMWorld::IdCollection& races, @@ -16,7 +18,7 @@ CSMTools::ReferenceableCheckStage::ReferenceableCheckStage( { } -void CSMTools::ReferenceableCheckStage::perform(int stage, std::vector< std::string >& messages) +void CSMTools::ReferenceableCheckStage::perform (int stage, Messages& messages) { //Checks for books, than, when stage is above mBooksSize goes to other checks, with (stage - PrevSum) as stage. const int bookSize(mReferencables.getBooks().getSize()); @@ -206,11 +208,11 @@ void CSMTools::ReferenceableCheckStage::perform(int stage, std::vector< std::str staticCheck(stage, mReferencables.getStatics(), messages); return; } - + stage -= staticSize; const int creatureSize(mReferencables.getCreatures().getSize()); - + if (stage < creatureSize) { creatureCheck(stage, mReferencables.getCreatures(), messages); @@ -230,7 +232,7 @@ int CSMTools::ReferenceableCheckStage::setup() void CSMTools::ReferenceableCheckStage::bookCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::Book >& records, - std::vector< std::string >& messages) + Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -248,7 +250,7 @@ void CSMTools::ReferenceableCheckStage::bookCheck( void CSMTools::ReferenceableCheckStage::activatorCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::Activator >& records, - std::vector< std::string >& messages) + Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -262,15 +264,13 @@ void CSMTools::ReferenceableCheckStage::activatorCheck( //Checking for model, IIRC all activators should have a model if (activator.mModel.empty()) - { - messages.push_back(id.toString() + "|" + activator.mId + " has no model"); - } + messages.push_back (std::make_pair (id, activator.mId + " has no model")); } void CSMTools::ReferenceableCheckStage::potionCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::Potion >& records, - std::vector< std::string >& messages) + Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -290,7 +290,7 @@ void CSMTools::ReferenceableCheckStage::potionCheck( void CSMTools::ReferenceableCheckStage::apparatusCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::Apparatus >& records, - std::vector< std::string >& messages) + Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -310,7 +310,7 @@ void CSMTools::ReferenceableCheckStage::apparatusCheck( void CSMTools::ReferenceableCheckStage::armorCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::Armor >& records, - std::vector< std::string >& messages) + Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -326,21 +326,17 @@ void CSMTools::ReferenceableCheckStage::armorCheck( //checking for armor class, armor should have poistive armor class, but 0 is considered legal if (armor.mData.mArmor < 0) - { - messages.push_back(id.toString() + "|" + armor.mId + " has negative armor class"); - } + messages.push_back (std::make_pair (id, armor.mId + " has negative armor class")); //checking for health. Only positive numbers are allowed, or 0 is illegal if (armor.mData.mHealth <= 0) - { - messages.push_back(id.toString() + "|" + armor.mId + " has non positive health"); - } + messages.push_back (std::make_pair (id, armor.mId + " has non positive health")); } void CSMTools::ReferenceableCheckStage::clothingCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::Clothing >& records, - std::vector< std::string >& messages) + Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -357,7 +353,7 @@ void CSMTools::ReferenceableCheckStage::clothingCheck( void CSMTools::ReferenceableCheckStage::containerCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::Container >& records, - std::vector< std::string >& messages) + Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -371,153 +367,109 @@ void CSMTools::ReferenceableCheckStage::containerCheck( //Checking for model, IIRC all containers should have a model if (container.mModel.empty()) - { - messages.push_back(id.toString() + "|" + container.mId + " has no model"); - } + messages.push_back (std::make_pair (id, container.mId + " has no model")); //Checking for capacity (weight) if (container.mWeight < 0) //0 is allowed - { - messages.push_back(id.toString() + "|" + container.mId + " has negative weight (capacity)"); - } + messages.push_back (std::make_pair (id, + container.mId + " has negative weight (capacity)")); //checking for name if (container.mName.empty()) - { - messages.push_back(id.toString() + "|" + container.mId + " has an empty name"); - } + messages.push_back (std::make_pair (id, container.mId + " has an empty name")); } -void CSMTools::ReferenceableCheckStage::creatureCheck( - int stage, - const CSMWorld::RefIdDataContainer< ESM::Creature >& records, - std::vector< std::string >& messages) +void CSMTools::ReferenceableCheckStage::creatureCheck ( + int stage, const CSMWorld::RefIdDataContainer< ESM::Creature >& records, + Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); if (baseRecord.isDeleted()) - { return; - } const ESM::Creature& creature = (dynamic_cast&>(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Creature, creature.mId); if (creature.mModel.empty()) - { - messages.push_back(id.toString() + "|" + creature.mId + " has no model"); - } + messages.push_back (std::make_pair (id, creature.mId + " has no model")); if (creature.mName.empty()) - { - messages.push_back(id.toString() + "|" + creature.mId + " has an empty name"); - } + messages.push_back (std::make_pair (id, creature.mId + " has an empty name")); //stats checks if (creature.mData.mLevel < 1) - { - messages.push_back(id.toString() + "|" + creature.mId + " has non-postive level"); - } + messages.push_back (std::make_pair (id, creature.mId + " has non-postive level")); if (creature.mData.mStrength < 0) - { - messages.push_back(id.toString() + "|" + creature.mId + " has negative strength"); - } + messages.push_back (std::make_pair (id, creature.mId + " has negative strength")); if (creature.mData.mIntelligence < 0) - { - messages.push_back(id.toString() + "|" + creature.mId + " has negative intelligence"); - } + messages.push_back (std::make_pair (id, creature.mId + " has negative intelligence")); if (creature.mData.mWillpower < 0) - { - messages.push_back(id.toString() + "|" + creature.mId + " has negative willpower"); - } + messages.push_back (std::make_pair (id, creature.mId + " has negative willpower")); if (creature.mData.mAgility < 0) - { - messages.push_back(id.toString() + "|" + creature.mId + " has negative agility"); - } + messages.push_back (std::make_pair (id, creature.mId + " has negative agility")); if (creature.mData.mSpeed < 0) - { - messages.push_back(id.toString() + "|" + creature.mId + " has negative speed"); - } + messages.push_back (std::make_pair (id, creature.mId + " has negative speed")); if (creature.mData.mEndurance < 0) - { - messages.push_back(id.toString() + "|" + creature.mId + " has negative endurance"); - } + messages.push_back (std::make_pair (id, creature.mId + " has negative endurance")); if (creature.mData.mPersonality < 0) - { - messages.push_back(id.toString() + "|" + creature.mId + " has negative personality"); - } + messages.push_back (std::make_pair (id, creature.mId + " has negative personality")); if (creature.mData.mLuck < 0) - { - messages.push_back(id.toString() + "|" + creature.mId + " has negative luck"); - } + messages.push_back (std::make_pair (id, creature.mId + " has negative luck")); if (creature.mData.mHealth < 0) - { - messages.push_back(id.toString() + "|" + creature.mId + " has negative health"); - } + messages.push_back (std::make_pair (id, creature.mId + " has negative health")); if (creature.mData.mSoul < 0) - { - messages.push_back(id.toString() + "|" + creature.mId + " has negative soul value"); - } + messages.push_back (std::make_pair (id, creature.mId + " has negative soul value")); for (int i = 0; i < 6; ++i) { if (creature.mData.mAttack[i] < 0) { - messages.push_back(id.toString() + "|" + creature.mId + " has negative attack strength"); + messages.push_back (std::make_pair (id, + creature.mId + " has negative attack strength")); break; } } //TODO, find meaning of other values if (creature.mData.mGold < 0) //It seems that this is for gold in merchant creatures - { - messages.push_back(id.toString() + "|" + creature.mId + " has negative gold "); - } + messages.push_back (std::make_pair (id, creature.mId + " has negative gold ")); } void CSMTools::ReferenceableCheckStage::doorCheck( - int stage, - const CSMWorld::RefIdDataContainer< ESM::Door >& records, - std::vector< std::string >& messages) + int stage, const CSMWorld::RefIdDataContainer< ESM::Door >& records, + Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); if (baseRecord.isDeleted()) - { return; - } const ESM::Door& Door = (dynamic_cast&>(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Door, Door.mId); //usual, name or model if (Door.mName.empty()) - { - messages.push_back(id.toString() + "|" + Door.mId + " has an empty name"); - } + messages.push_back (std::make_pair (id, Door.mId + " has an empty name")); if (Door.mModel.empty()) - { - messages.push_back(id.toString() + "|" + Door.mId + " has no model"); - } - - //TODO, check what static unsigned int sRecordId; is for + messages.push_back (std::make_pair (id, Door.mId + " has no model")); } void CSMTools::ReferenceableCheckStage::ingredientCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::Ingredient >& records, - std::vector< std::string >& messages) + Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -535,7 +487,7 @@ void CSMTools::ReferenceableCheckStage::ingredientCheck( void CSMTools::ReferenceableCheckStage::creaturesLevListCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::CreatureLevList >& records, - std::vector< std::string >& messages) + Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -553,7 +505,7 @@ void CSMTools::ReferenceableCheckStage::creaturesLevListCheck( void CSMTools::ReferenceableCheckStage::itemLevelledListCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::ItemLevList >& records, - std::vector< std::string >& messages) + Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -569,40 +521,33 @@ void CSMTools::ReferenceableCheckStage::itemLevelledListCheck( } void CSMTools::ReferenceableCheckStage::lightCheck( - int stage, - const CSMWorld::RefIdDataContainer< ESM::Light >& records, - std::vector< std::string >& messages) + int stage, const CSMWorld::RefIdDataContainer< ESM::Light >& records, + Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); if (baseRecord.isDeleted()) - { return; - } const ESM::Light& light = (dynamic_cast& >(baseRecord)).get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Light, light.mId); if (light.mData.mRadius < 0) - { - messages.push_back(id.toString() + "|" + light.mId + " has negative light radius"); - } + messages.push_back (std::make_pair (id, light.mId + " has negative light radius")); if (light.mData.mFlags & ESM::Light::Carry) { inventoryItemCheck(light, messages, id.toString()); if (light.mData.mTime == 0) - { - messages.push_back(id.toString() + "|" + light.mId + " has zero duration"); - } + messages.push_back (std::make_pair (id, light.mId + " has zero duration")); } } void CSMTools::ReferenceableCheckStage::lockpickCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::Lockpick >& records, - std::vector< std::string >& messages) + Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -622,7 +567,7 @@ void CSMTools::ReferenceableCheckStage::lockpickCheck( void CSMTools::ReferenceableCheckStage::miscCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::Miscellaneous >& records, - std::vector< std::string >& messages) + Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -637,20 +582,17 @@ void CSMTools::ReferenceableCheckStage::miscCheck( inventoryItemCheck(miscellaneous, messages, id.toString()); } -void CSMTools::ReferenceableCheckStage::npcCheck( - int stage, - const CSMWorld::RefIdDataContainer< ESM::NPC >& records, - std::vector< std::string >& messages) +void CSMTools::ReferenceableCheckStage::npcCheck ( + int stage, const CSMWorld::RefIdDataContainer< ESM::NPC >& records, + Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); if (baseRecord.isDeleted()) - { return; - } const ESM::NPC& npc = (dynamic_cast& >(baseRecord)).get(); - CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Npc, npc.mId); + CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Npc, npc.mId); short level(npc.mNpdt52.mLevel); char disposition(npc.mNpdt52.mDisposition); @@ -661,15 +603,13 @@ void CSMTools::ReferenceableCheckStage::npcCheck( //Detect if player is present if (Misc::StringUtils::ciEqual(npc.mId, "player")) //Happy now, scrawl? - { mPlayerPresent = true; - } if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) //12 = autocalculated { if ((npc.mFlags & ESM::NPC::Autocalc) == 0) //0x0008 = autocalculated flag { - messages.push_back(id.toString() + "|" + npc.mId + " mNpdtType or flags mismatch!"); //should not happend? + messages.push_back (std::make_pair (id, npc.mId + " mNpdtType or flags mismatch!")); //should not happend? return; } @@ -681,146 +621,95 @@ void CSMTools::ReferenceableCheckStage::npcCheck( } else { - if (npc.mNpdt52.mMana < 0) - { - messages.push_back(id.toString() + "|" + npc.mId + " mana has negative value"); - } - - if (npc.mNpdt52.mFatigue < 0) - { - messages.push_back(id.toString() + "|" + npc.mId + " fatigue has negative value"); - } - if (npc.mNpdt52.mAgility == 0) - { - messages.push_back(id.toString() + "|" + npc.mId + " agility has zero value"); - } + messages.push_back (std::make_pair (id, npc.mId + " agility has zero value")); if (npc.mNpdt52.mEndurance == 0) - { - messages.push_back(id.toString() + "|" + npc.mId + " endurance has zero value"); - } + messages.push_back (std::make_pair (id, npc.mId + " endurance has zero value")); if (npc.mNpdt52.mIntelligence == 0) - { - messages.push_back(id.toString() + "|" + npc.mId + " intelligence has zero value"); - } + messages.push_back (std::make_pair (id, npc.mId + " intelligence has zero value")); if (npc.mNpdt52.mLuck == 0) - { - messages.push_back(id.toString() + "|" + npc.mId + " luck has zero value"); - } + messages.push_back (std::make_pair (id, npc.mId + " luck has zero value")); if (npc.mNpdt52.mPersonality == 0) - { - messages.push_back(id.toString() + "|" + npc.mId + " personality has zero value"); - } + messages.push_back (std::make_pair (id, npc.mId + " personality has zero value")); if (npc.mNpdt52.mStrength == 0) - { - messages.push_back(id.toString() + "|" + npc.mId + " strength has zero value"); - } + messages.push_back (std::make_pair (id, npc.mId + " strength has zero value")); if (npc.mNpdt52.mSpeed == 0) - { - messages.push_back(id.toString() + "|" + npc.mId + " speed has zero value"); - } + messages.push_back (std::make_pair (id, npc.mId + " speed has zero value")); if (npc.mNpdt52.mWillpower == 0) - { - messages.push_back(id.toString() + "|" + npc.mId + " willpower has zero value"); - } + messages.push_back (std::make_pair (id, npc.mId + " willpower has zero value")); } if (level < 1) - { - messages.push_back(id.toString() + "|" + npc.mId + " level is non positive"); - } + messages.push_back (std::make_pair (id, npc.mId + " level is non positive")); if (gold < 0) - { - messages.push_back(id.toString() + "|" + npc.mId + " gold has negative value"); - } + messages.push_back (std::make_pair (id, npc.mId + " gold has negative value")); if (npc.mName.empty()) - { - messages.push_back(id.toString() + "|" + npc.mId + " has any empty name"); - } + messages.push_back (std::make_pair (id, npc.mId + " has any empty name")); if (npc.mClass.empty()) { - messages.push_back(id.toString() + "|" + npc.mId + " has any empty class"); + messages.push_back (std::make_pair (id, npc.mId + " has any empty class")); } - else //checking if there is such class + else if (mClasses.searchId (npc.mClass) == -1) { - if (mClasses.searchId(npc.mClass) == -1) - { - messages.push_back(id.toString() + "|" + npc.mId + " has invalid class"); - } + messages.push_back (std::make_pair (id, npc.mId + " has invalid class")); } if (npc.mRace.empty()) { - messages.push_back(id.toString() + "|" + npc.mId + " has any empty race"); + messages.push_back (std::make_pair (id, npc.mId + " has any empty race")); } - else //checking if there is a such race + else if (mRaces.searchId (npc.mRace) == -1) { - if (mRaces.searchId(npc.mRace) == -1) - { - messages.push_back(id.toString() + "|" + npc.mId + " has invalid race"); - } + messages.push_back (std::make_pair (id, npc.mId + " has invalid race")); } if (disposition < 0) - { - messages.push_back(id.toString() + "|" + npc.mId + " has negative disposition"); - } + messages.push_back (std::make_pair (id, npc.mId + " has negative disposition")); if (reputation < 0) //It seems that no character in Morrowind.esm have negative reputation. I'm assuming that negative reputation is invalid { - messages.push_back(id.toString() + "|" + npc.mId + " has negative reputation"); + messages.push_back (std::make_pair (id, npc.mId + " has negative reputation")); } - if (npc.mFaction.empty() == false) + if (!npc.mFaction.empty()) { if (rank < 0) - { - messages.push_back(id.toString() + "|" + npc.mId + " has negative rank"); - } + messages.push_back (std::make_pair (id, npc.mId + " has negative rank")); if (mFactions.searchId(npc.mFaction) == -1) - { - messages.push_back(id.toString() + "|" + npc.mId + " has invalid faction"); - } + messages.push_back (std::make_pair (id, npc.mId + " has invalid faction")); } if (npc.mHead.empty()) - { - messages.push_back(id.toString() + "|" + npc.mId + " has no head"); - } + messages.push_back (std::make_pair (id, npc.mId + " has no head")); if (npc.mHair.empty()) - { - messages.push_back(id.toString() + "|" + npc.mId + " has no hair"); - } + messages.push_back (std::make_pair (id, npc.mId + " has no hair")); //TODO: reputation, Disposition, rank, everything else } void CSMTools::ReferenceableCheckStage::weaponCheck( - int stage, - const CSMWorld::RefIdDataContainer< ESM::Weapon >& records, - std::vector< std::string >& messages) + int stage, const CSMWorld::RefIdDataContainer< ESM::Weapon >& records, + Messages& messages) { - const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); + const CSMWorld::RecordBase& baseRecord = records.getRecord (stage); if (baseRecord.isDeleted()) - { return; - } const ESM::Weapon& weapon = (dynamic_cast& >(baseRecord)).get(); - CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Weapon, weapon.mId); + CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Weapon, weapon.mId); //TODO, It seems that this stuff for spellcasting is obligatory and In fact We should check if records are present if @@ -860,20 +749,17 @@ void CSMTools::ReferenceableCheckStage::weaponCheck( weapon.mData.mType == ESM::Weapon::Bolt)) { if (weapon.mData.mSlash[0] > weapon.mData.mSlash[1]) - { - messages.push_back(id.toString() + "|" + weapon.mId + " has minimum slash damage higher than maximum"); - } + messages.push_back (std::make_pair (id, + weapon.mId + " has minimum slash damage higher than maximum")); if (weapon.mData.mThrust[0] > weapon.mData.mThrust[1]) - { - messages.push_back(id.toString() + "|" + weapon.mId + " has minimum thrust damage higher than maximum"); - } + messages.push_back (std::make_pair (id, + weapon.mId + " has minimum thrust damage higher than maximum")); } if (weapon.mData.mChop[0] > weapon.mData.mChop[1]) - { - messages.push_back(id.toString() + "|" + weapon.mId + " has minimum chop damage higher than maximum"); - } + messages.push_back (std::make_pair (id, + weapon.mId + " has minimum chop damage higher than maximum")); if (!(weapon.mData.mType == ESM::Weapon::Arrow || weapon.mData.mType == ESM::Weapon::Bolt || @@ -881,14 +767,10 @@ void CSMTools::ReferenceableCheckStage::weaponCheck( { //checking of health if (weapon.mData.mHealth <= 0) - { - messages.push_back(id.toString() + "|" + weapon.mId + " has non-positivie health"); - } + messages.push_back (std::make_pair (id, weapon.mId + " has non-positivie health")); if (weapon.mData.mReach < 0) - { - messages.push_back(id.toString() + "|" + weapon.mId + " has negative reach"); - } + messages.push_back (std::make_pair (id, weapon.mId + " has negative reach")); } } } @@ -896,7 +778,7 @@ void CSMTools::ReferenceableCheckStage::weaponCheck( void CSMTools::ReferenceableCheckStage::probeCheck( int stage, const CSMWorld::RefIdDataContainer< ESM::Probe >& records, - std::vector< std::string >& messages) + Messages& messages) { const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); @@ -912,184 +794,128 @@ void CSMTools::ReferenceableCheckStage::probeCheck( toolCheck(probe, messages, id.toString(), true); } -void CSMTools::ReferenceableCheckStage::repairCheck( - int stage, - const CSMWorld::RefIdDataContainer< ESM::Repair >& records, - std::vector< std::string >& messages) +void CSMTools::ReferenceableCheckStage::repairCheck ( + int stage, const CSMWorld::RefIdDataContainer< ESM::Repair >& records, + Messages& messages) { - const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); + const CSMWorld::RecordBase& baseRecord = records.getRecord (stage); if (baseRecord.isDeleted()) - { return; - } const ESM::Repair& repair = (dynamic_cast& >(baseRecord)).get(); - CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Repair, repair.mId); + CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Repair, repair.mId); - inventoryItemCheck(repair, messages, id.toString()); - toolCheck(repair, messages, id.toString(), true); + inventoryItemCheck (repair, messages, id.toString()); + toolCheck (repair, messages, id.toString(), true); } -void CSMTools::ReferenceableCheckStage::staticCheck( - int stage, - const CSMWorld::RefIdDataContainer< ESM::Static >& records, - std::vector< std::string >& messages) +void CSMTools::ReferenceableCheckStage::staticCheck ( + int stage, const CSMWorld::RefIdDataContainer< ESM::Static >& records, + Messages& messages) { - const CSMWorld::RecordBase& baseRecord = records.getRecord(stage); + const CSMWorld::RecordBase& baseRecord = records.getRecord (stage); if (baseRecord.isDeleted()) - { return; - } const ESM::Static& staticElement = (dynamic_cast& >(baseRecord)).get(); - CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Static, staticElement.mId); + CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Static, staticElement.mId); if (staticElement.mModel.empty()) - { - messages.push_back(id.toString() + "|" + staticElement.mId + " has no model"); - } + messages.push_back (std::make_pair (id, staticElement.mId + " has no model")); } //final check -void CSMTools::ReferenceableCheckStage::finalCheck(std::vector< std::string >& messages) +void CSMTools::ReferenceableCheckStage::finalCheck (Messages& messages) { if (!mPlayerPresent) - { - CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Npc); - messages.push_back(id.toString() + "| There is no player record"); - } + messages.push_back (std::make_pair (CSMWorld::UniversalId::Type_Referenceables, + "There is no player record")); } //Templates begins here -template void CSMTools::ReferenceableCheckStage::inventoryItemCheck( - const ITEM& someItem, - std::vector< std::string >& messages, - const std::string& someID, bool enchantable) +template void CSMTools::ReferenceableCheckStage::inventoryItemCheck ( + const Item& someItem, Messages& messages, const std::string& someID, bool enchantable) { if (someItem.mName.empty()) - { - messages.push_back(someID + "|" + someItem.mId + " has an empty name"); - } + messages.push_back (std::make_pair (someID, someItem.mId + " has an empty name")); //Checking for weight if (someItem.mData.mWeight < 0) - { - messages.push_back(someID + "|" + someItem.mId + " has negative weight"); - } + messages.push_back (std::make_pair (someID, someItem.mId + " has negative weight")); //Checking for value if (someItem.mData.mValue < 0) - { - messages.push_back(someID + "|" + someItem.mId + " has negative value"); - } + messages.push_back (std::make_pair (someID, someItem.mId + " has negative value")); -//checking for model + //checking for model if (someItem.mModel.empty()) - { - messages.push_back(someID + "|" + someItem.mId + " has no model"); - } + messages.push_back (std::make_pair (someID, someItem.mId + " has no model")); //checking for icon if (someItem.mIcon.empty()) - { - messages.push_back(someID + "|" + someItem.mId + " has no icon"); - } + messages.push_back (std::make_pair (someID, someItem.mId + " has no icon")); - if (enchantable) - { - if (someItem.mData.mEnchant < 0) - { - messages.push_back(someID + "|" + someItem.mId + " has negative enchantment"); - } - } + if (enchantable && someItem.mData.mEnchant < 0) + messages.push_back (std::make_pair (someID, someItem.mId + " has negative enchantment")); } -template void CSMTools::ReferenceableCheckStage::inventoryItemCheck( - const ITEM& someItem, - std::vector< std::string >& messages, - const std::string& someID) +template void CSMTools::ReferenceableCheckStage::inventoryItemCheck ( + const Item& someItem, Messages& messages, const std::string& someID) { if (someItem.mName.empty()) - { - messages.push_back(someID + "|" + someItem.mId + " has an empty name"); - } + messages.push_back (std::make_pair (someID, someItem.mId + " has an empty name")); //Checking for weight if (someItem.mData.mWeight < 0) - { - messages.push_back(someID + "|" + someItem.mId + " has negative weight"); - } + messages.push_back (std::make_pair (someID, someItem.mId + " has negative weight")); //Checking for value if (someItem.mData.mValue < 0) - { - messages.push_back(someID + "|" + someItem.mId + " has negative value"); - } + messages.push_back (std::make_pair (someID, someItem.mId + " has negative value")); //checking for model if (someItem.mModel.empty()) - { - messages.push_back(someID + "|" + someItem.mId + " has no model"); - } + messages.push_back (std::make_pair (someID, someItem.mId + " has no model")); //checking for icon if (someItem.mIcon.empty()) - { - messages.push_back(someID + "|" + someItem.mId + " has no icon"); - } + messages.push_back (std::make_pair (someID, someItem.mId + " has no icon")); } -template void CSMTools::ReferenceableCheckStage::toolCheck( - const TOOL& someTool, - std::vector< std::string >& messages, - const std::string& someID, bool canBeBroken) +template void CSMTools::ReferenceableCheckStage::toolCheck ( + const Tool& someTool, Messages& messages, const std::string& someID, bool canBeBroken) { if (someTool.mData.mQuality <= 0) - { - messages.push_back(someID + "|" + someTool.mId + " has non-positive quality"); - } + messages.push_back (std::make_pair (someID, someTool.mId + " has non-positive quality")); - if (canBeBroken) - { - if (someTool.mData.mUses <= 0) - { - messages.push_back(someID + "|" + someTool.mId + " has non-positive uses count"); - } - } + if (canBeBroken && someTool.mData.mUses<=0) + messages.push_back (std::make_pair (someID, + someTool.mId + " has non-positive uses count")); } -template void CSMTools::ReferenceableCheckStage::toolCheck( - const TOOL& someTool, - std::vector< std::string >& messages, - const std::string& someID) +template void CSMTools::ReferenceableCheckStage::toolCheck ( + const Tool& someTool, Messages& messages, const std::string& someID) { if (someTool.mData.mQuality <= 0) - { - messages.push_back(someID + "|" + someTool.mId + " has non-positive quality"); - } + messages.push_back (std::make_pair (someID, someTool.mId + " has non-positive quality")); } -template void CSMTools::ReferenceableCheckStage::listCheck( - const LIST& someList, - std::vector< std::string >& messages, - const std::string& someID) +template void CSMTools::ReferenceableCheckStage::listCheck ( + const List& someList, Messages& messages, const std::string& someID) { for (unsigned i = 0; i < someList.mList.size(); ++i) { if (mReferencables.searchId(someList.mList[i].mId).first == -1) - { - messages.push_back(someID + "|" + someList.mId + " contains item without referencable"); - } + messages.push_back (std::make_pair (someID, + someList.mId + " contains item without referencable")); if (someList.mList[i].mLevel < 1) - { - messages.push_back(someID + "|" + someList.mId + " contains item with non-positive level"); - } + messages.push_back (std::make_pair (someID, + someList.mId + " contains item with non-positive level")); } } -// kate: indent-mode cstyle; indent-width 4; replace-tabs on; diff --git a/apps/opencs/model/tools/referenceablecheck.hpp b/apps/opencs/model/tools/referenceablecheck.hpp index 338983cc7..b0129fc2a 100644 --- a/apps/opencs/model/tools/referenceablecheck.hpp +++ b/apps/opencs/model/tools/referenceablecheck.hpp @@ -11,63 +11,64 @@ namespace CSMTools class ReferenceableCheckStage : public CSMDoc::Stage { public: - ReferenceableCheckStage(const CSMWorld::RefIdData& referenceable, - const CSMWorld::IdCollection& races, - const CSMWorld::IdCollection& classes, - const CSMWorld::IdCollection& factions); - virtual void perform(int stage, std::vector< std::string >& messages); + ReferenceableCheckStage (const CSMWorld::RefIdData& referenceable, + const CSMWorld::IdCollection& races, + const CSMWorld::IdCollection& classes, + const CSMWorld::IdCollection& factions); + + virtual void perform(int stage, Messages& messages); virtual int setup(); private: //CONCRETE CHECKS - void bookCheck(int stage, const CSMWorld::RefIdDataContainer< ESM::Book >& records, std::vector< std::string >& messages); - void activatorCheck(int stage, const CSMWorld::RefIdDataContainer< ESM::Activator >& records, std::vector< std::string >& messages); - void potionCheck(int stage, const CSMWorld::RefIdDataContainer& records, std::vector& messages); - void apparatusCheck(int stage, const CSMWorld::RefIdDataContainer& records, std::vector& messages); - void armorCheck(int stage, const CSMWorld::RefIdDataContainer& records, std::vector& messages); - void clothingCheck(int stage, const CSMWorld::RefIdDataContainer& records, std::vector& messages); - void containerCheck(int stage, const CSMWorld::RefIdDataContainer& records, std::vector& messages); - void creatureCheck(int stage, const CSMWorld::RefIdDataContainer& records, std::vector& messages); - void doorCheck(int stage, const CSMWorld::RefIdDataContainer& records, std::vector& messages); - void ingredientCheck(int stage, const CSMWorld::RefIdDataContainer& records, std::vector& messages); - void creaturesLevListCheck(int stage, const CSMWorld::RefIdDataContainer& records, std::vector& messages); - void itemLevelledListCheck(int stage, const CSMWorld::RefIdDataContainer& records, std::vector& messages); - void lightCheck(int stage, const CSMWorld::RefIdDataContainer& records, std::vector& messages); - void lockpickCheck(int stage, const CSMWorld::RefIdDataContainer& records, std::vector& messages); - void miscCheck(int stage, const CSMWorld::RefIdDataContainer& records, std::vector& messages); - void npcCheck(int stage, const CSMWorld::RefIdDataContainer& records, std::vector& messages); - void weaponCheck(int stage, const CSMWorld::RefIdDataContainer& records, std::vector& messages); - void probeCheck(int stage, const CSMWorld::RefIdDataContainer& records, std::vector& messages); - void repairCheck(int stage, const CSMWorld::RefIdDataContainer& records, std::vector& messages); - void staticCheck(int stage, const CSMWorld::RefIdDataContainer& records, std::vector& messages); - + void bookCheck(int stage, const CSMWorld::RefIdDataContainer< ESM::Book >& records, Messages& messages); + void activatorCheck(int stage, const CSMWorld::RefIdDataContainer< ESM::Activator >& records, Messages& messages); + void potionCheck(int stage, const CSMWorld::RefIdDataContainer& records, Messages& messages); + void apparatusCheck(int stage, const CSMWorld::RefIdDataContainer& records, Messages& messages); + void armorCheck(int stage, const CSMWorld::RefIdDataContainer& records, Messages& messages); + void clothingCheck(int stage, const CSMWorld::RefIdDataContainer& records, Messages& messages); + void containerCheck(int stage, const CSMWorld::RefIdDataContainer& records, Messages& messages); + void creatureCheck(int stage, const CSMWorld::RefIdDataContainer& records, Messages& messages); + void doorCheck(int stage, const CSMWorld::RefIdDataContainer& records, Messages& messages); + void ingredientCheck(int stage, const CSMWorld::RefIdDataContainer& records, Messages& messages); + void creaturesLevListCheck(int stage, const CSMWorld::RefIdDataContainer& records, Messages& messages); + void itemLevelledListCheck(int stage, const CSMWorld::RefIdDataContainer& records, Messages& messages); + void lightCheck(int stage, const CSMWorld::RefIdDataContainer& records, Messages& messages); + void lockpickCheck(int stage, const CSMWorld::RefIdDataContainer& records, Messages& messages); + void miscCheck(int stage, const CSMWorld::RefIdDataContainer& records, Messages& messages); + void npcCheck(int stage, const CSMWorld::RefIdDataContainer& records, Messages& messages); + void weaponCheck(int stage, const CSMWorld::RefIdDataContainer& records, Messages& messages); + void probeCheck(int stage, const CSMWorld::RefIdDataContainer& records, Messages& messages); + void repairCheck(int stage, const CSMWorld::RefIdDataContainer& records, Messages& messages); + void staticCheck(int stage, const CSMWorld::RefIdDataContainer& records, Messages& messages); + //FINAL CHECK - void finalCheck(std::vector& messages); - + void finalCheck (Messages& messages); + //TEMPLATE CHECKS template void inventoryItemCheck(const ITEM& someItem, - std::vector& messages, + Messages& messages, const std::string& someID, bool enchantable); //for all enchantable items. - + template void inventoryItemCheck(const ITEM& someItem, - std::vector& messages, + Messages& messages, const std::string& someID); //for non-enchantable items. - + template void toolCheck(const TOOL& someTool, - std::vector& messages, + Messages& messages, const std::string& someID, bool canbebroken); //for tools with uses. - + template void toolCheck(const TOOL& someTool, - std::vector& messages, + Messages& messages, const std::string& someID); //for tools without uses. template void listCheck(const LIST& someList, - std::vector< std::string >& messages, + Messages& messages, const std::string& someID); - + const CSMWorld::RefIdData& mReferencables; const CSMWorld::IdCollection& mRaces; const CSMWorld::IdCollection& mClasses; diff --git a/apps/opencs/model/tools/regioncheck.cpp b/apps/opencs/model/tools/regioncheck.cpp index 4398e00ef..07df20470 100644 --- a/apps/opencs/model/tools/regioncheck.cpp +++ b/apps/opencs/model/tools/regioncheck.cpp @@ -17,7 +17,7 @@ int CSMTools::RegionCheckStage::setup() return mRegions.getSize(); } -void CSMTools::RegionCheckStage::perform (int stage, std::vector& messages) +void CSMTools::RegionCheckStage::perform (int stage, Messages& messages) { const CSMWorld::Record& record = mRegions.getRecord (stage); @@ -30,7 +30,7 @@ void CSMTools::RegionCheckStage::perform (int stage, std::vector& m // test for empty name if (region.mName.empty()) - messages.push_back (id.toString() + "|" + region.mId + " has an empty name"); + messages.push_back (std::make_pair (id, region.mId + " has an empty name")); /// \todo test that the ID in mSleeplist exists diff --git a/apps/opencs/model/tools/regioncheck.hpp b/apps/opencs/model/tools/regioncheck.hpp index c8c437cbd..a12903e7d 100644 --- a/apps/opencs/model/tools/regioncheck.hpp +++ b/apps/opencs/model/tools/regioncheck.hpp @@ -21,7 +21,7 @@ namespace CSMTools virtual int setup(); ///< \return number of steps - virtual void perform (int stage, std::vector& messages); + virtual void perform (int stage, Messages& messages); ///< Messages resulting from this tage will be appended to \a messages. }; } diff --git a/apps/opencs/model/tools/reportmodel.cpp b/apps/opencs/model/tools/reportmodel.cpp index d88361746..75545a7c7 100644 --- a/apps/opencs/model/tools/reportmodel.cpp +++ b/apps/opencs/model/tools/reportmodel.cpp @@ -51,16 +51,11 @@ bool CSMTools::ReportModel::removeRows (int row, int count, const QModelIndex& p return true; } -void CSMTools::ReportModel::add (const std::string& row) +void CSMTools::ReportModel::add (const CSMWorld::UniversalId& id, const std::string& message) { - std::string::size_type index = row.find ('|'); - - if (index==std::string::npos) - throw std::logic_error ("invalid report message"); - beginInsertRows (QModelIndex(), mRows.size(), mRows.size()); - mRows.push_back (std::make_pair (row.substr (0, index), row.substr (index+1))); + mRows.push_back (std::make_pair (id, message)); endInsertRows(); } diff --git a/apps/opencs/model/tools/reportmodel.hpp b/apps/opencs/model/tools/reportmodel.hpp index 55c25d907..0f000245e 100644 --- a/apps/opencs/model/tools/reportmodel.hpp +++ b/apps/opencs/model/tools/reportmodel.hpp @@ -28,7 +28,7 @@ namespace CSMTools virtual bool removeRows (int row, int count, const QModelIndex& parent = QModelIndex()); - void add (const std::string& row); + void add (const CSMWorld::UniversalId& id, const std::string& message); const CSMWorld::UniversalId& getUniversalId (int row) const; }; diff --git a/apps/opencs/model/tools/scriptcheck.cpp b/apps/opencs/model/tools/scriptcheck.cpp index a5154d292..b989e22a2 100644 --- a/apps/opencs/model/tools/scriptcheck.cpp +++ b/apps/opencs/model/tools/scriptcheck.cpp @@ -16,8 +16,6 @@ void CSMTools::ScriptCheckStage::report (const std::string& message, const Compi CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Script, mId); - stream << id.toString() << "|"; - if (type==ErrorMessage) stream << "error "; else @@ -28,25 +26,15 @@ void CSMTools::ScriptCheckStage::report (const std::string& message, const Compi << ", line " << loc.mLine << ", column " << loc.mColumn << " (" << loc.mLiteral << "): " << message; - mMessages->push_back (stream.str()); + mMessages->push_back (std::make_pair (id, stream.str())); } void CSMTools::ScriptCheckStage::report (const std::string& message, Type type) { - std::ostringstream stream; - CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Script, mId); - stream << id.toString() << "|"; - - if (type==ErrorMessage) - stream << "error: "; - else - stream << "warning: "; - - stream << message; - - mMessages->push_back (stream.str()); + mMessages->push_back (std::make_pair (id, + (type==ErrorMessage ? "error: " : "warning: ") + message)); } CSMTools::ScriptCheckStage::ScriptCheckStage (const CSMWorld::Data& data) @@ -68,7 +56,7 @@ int CSMTools::ScriptCheckStage::setup() return mData.getScripts().getSize(); } -void CSMTools::ScriptCheckStage::perform (int stage, std::vector& messages) +void CSMTools::ScriptCheckStage::perform (int stage, Messages& messages) { mMessages = &messages; mId = mData.getScripts().getId (stage); @@ -90,13 +78,10 @@ void CSMTools::ScriptCheckStage::perform (int stage, std::vector& m } catch (const std::exception& error) { - std::ostringstream stream; - CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Script, mId); - stream << id.toString() << "|Critical compile error: " << error.what(); - - messages.push_back (stream.str()); + messages.push_back (std::make_pair (id, + std::string ("Critical compile error: ") + error.what())); } mMessages = 0; diff --git a/apps/opencs/model/tools/scriptcheck.hpp b/apps/opencs/model/tools/scriptcheck.hpp index 8de8e1a66..ecf8d61b7 100644 --- a/apps/opencs/model/tools/scriptcheck.hpp +++ b/apps/opencs/model/tools/scriptcheck.hpp @@ -18,7 +18,7 @@ namespace CSMTools CSMWorld::ScriptContext mContext; std::string mId; std::string mFile; - std::vector *mMessages; + Messages *mMessages; virtual void report (const std::string& message, const Compiler::TokenLoc& loc, Type type); ///< Report error to the user. @@ -33,7 +33,7 @@ namespace CSMTools virtual int setup(); ///< \return number of steps - virtual void perform (int stage, std::vector& messages); + virtual void perform (int stage, Messages& messages); ///< Messages resulting from this tage will be appended to \a messages. }; } diff --git a/apps/opencs/model/tools/skillcheck.cpp b/apps/opencs/model/tools/skillcheck.cpp index 28fc24fd3..630516c72 100644 --- a/apps/opencs/model/tools/skillcheck.cpp +++ b/apps/opencs/model/tools/skillcheck.cpp @@ -16,7 +16,7 @@ int CSMTools::SkillCheckStage::setup() return mSkills.getSize(); } -void CSMTools::SkillCheckStage::perform (int stage, std::vector& messages) +void CSMTools::SkillCheckStage::perform (int stage, Messages& messages) { const CSMWorld::Record& record = mSkills.getRecord (stage); @@ -32,11 +32,11 @@ void CSMTools::SkillCheckStage::perform (int stage, std::vector& me { std::ostringstream stream; - stream << id.toString() << "|Use value #" << i << " of " << skill.mId << " is negative"; + stream << "Use value #" << i << " of " << skill.mId << " is negative"; - messages.push_back (stream.str()); + messages.push_back (std::make_pair (id, stream.str())); } if (skill.mDescription.empty()) - messages.push_back (id.toString() + "|" + skill.mId + " has an empty description"); + messages.push_back (std::make_pair (id, skill.mId + " has an empty description")); } \ No newline at end of file diff --git a/apps/opencs/model/tools/skillcheck.hpp b/apps/opencs/model/tools/skillcheck.hpp index 662bdadee..cf5d53b5c 100644 --- a/apps/opencs/model/tools/skillcheck.hpp +++ b/apps/opencs/model/tools/skillcheck.hpp @@ -21,7 +21,7 @@ namespace CSMTools virtual int setup(); ///< \return number of steps - virtual void perform (int stage, std::vector& messages); + virtual void perform (int stage, Messages& messages); ///< Messages resulting from this tage will be appended to \a messages. }; } diff --git a/apps/opencs/model/tools/soundcheck.cpp b/apps/opencs/model/tools/soundcheck.cpp index dce2d2b6f..3d222e909 100644 --- a/apps/opencs/model/tools/soundcheck.cpp +++ b/apps/opencs/model/tools/soundcheck.cpp @@ -16,7 +16,7 @@ int CSMTools::SoundCheckStage::setup() return mSounds.getSize(); } -void CSMTools::SoundCheckStage::perform (int stage, std::vector& messages) +void CSMTools::SoundCheckStage::perform (int stage, Messages& messages) { const CSMWorld::Record& record = mSounds.getRecord (stage); @@ -28,7 +28,7 @@ void CSMTools::SoundCheckStage::perform (int stage, std::vector& me CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Sound, sound.mId); if (sound.mData.mMinRange>sound.mData.mMaxRange) - messages.push_back (id.toString() + "|Maximum range larger than minimum range"); + messages.push_back (std::make_pair (id, "Maximum range larger than minimum range")); /// \todo check, if the sound file exists } \ No newline at end of file diff --git a/apps/opencs/model/tools/soundcheck.hpp b/apps/opencs/model/tools/soundcheck.hpp index 00b45cd93..a82a0eb6d 100644 --- a/apps/opencs/model/tools/soundcheck.hpp +++ b/apps/opencs/model/tools/soundcheck.hpp @@ -21,7 +21,7 @@ namespace CSMTools virtual int setup(); ///< \return number of steps - virtual void perform (int stage, std::vector& messages); + virtual void perform (int stage, Messages& messages); ///< Messages resulting from this tage will be appended to \a messages. }; } diff --git a/apps/opencs/model/tools/spellcheck.cpp b/apps/opencs/model/tools/spellcheck.cpp index a2cc7c8d2..3d0be46fd 100644 --- a/apps/opencs/model/tools/spellcheck.cpp +++ b/apps/opencs/model/tools/spellcheck.cpp @@ -17,7 +17,7 @@ int CSMTools::SpellCheckStage::setup() return mSpells.getSize(); } -void CSMTools::SpellCheckStage::perform (int stage, std::vector& messages) +void CSMTools::SpellCheckStage::perform (int stage, Messages& messages) { const CSMWorld::Record& record = mSpells.getRecord (stage); @@ -30,11 +30,11 @@ void CSMTools::SpellCheckStage::perform (int stage, std::vector& me // test for empty name and description if (spell.mName.empty()) - messages.push_back (id.toString() + "|" + spell.mId + " has an empty name"); + messages.push_back (std::make_pair (id, spell.mId + " has an empty name")); // test for invalid cost values if (spell.mData.mCost<0) - messages.push_back (id.toString() + "|" + spell.mId + " has a negative spell costs"); + messages.push_back (std::make_pair (id, spell.mId + " has a negative spell costs")); /// \todo check data members that can't be edited in the table view } \ No newline at end of file diff --git a/apps/opencs/model/tools/spellcheck.hpp b/apps/opencs/model/tools/spellcheck.hpp index 880ddafcd..182f1888b 100644 --- a/apps/opencs/model/tools/spellcheck.hpp +++ b/apps/opencs/model/tools/spellcheck.hpp @@ -21,7 +21,7 @@ namespace CSMTools virtual int setup(); ///< \return number of steps - virtual void perform (int stage, std::vector& messages); + virtual void perform (int stage, Messages& messages); ///< Messages resulting from this tage will be appended to \a messages. }; } diff --git a/apps/opencs/model/tools/tools.cpp b/apps/opencs/model/tools/tools.cpp index d3d8f5fad..2f93db48e 100644 --- a/apps/opencs/model/tools/tools.cpp +++ b/apps/opencs/model/tools/tools.cpp @@ -45,8 +45,9 @@ CSMDoc::Operation *CSMTools::Tools::getVerifier() connect (mVerifier, SIGNAL (progress (int, int, int)), this, SIGNAL (progress (int, int, int))); connect (mVerifier, SIGNAL (done (int)), this, SIGNAL (done (int))); - connect (mVerifier, SIGNAL (reportMessage (const QString&, int)), - this, SLOT (verifierMessage (const QString&, int))); + connect (mVerifier, + SIGNAL (reportMessage (const CSMWorld::UniversalId&, const std::string&, int)), + this, SLOT (verifierMessage (const CSMWorld::UniversalId&, const std::string&, int))); std::vector mandatoryIds; // I want C++11, damn it! mandatoryIds.push_back ("Day"); @@ -87,13 +88,17 @@ CSMDoc::Operation *CSMTools::Tools::getVerifier() CSMTools::Tools::Tools (CSMWorld::Data& data) : mData (data), mVerifier (0), mNextReportNumber (0) { - for (std::map::iterator iter (mReports.begin()); iter!=mReports.end(); ++iter) - delete iter->second; + // index 0: load error log + mReports.insert (std::make_pair (mNextReportNumber++, new ReportModel)); + mActiveReports.insert (std::make_pair (CSMDoc::State_Loading, 0)); } CSMTools::Tools::~Tools() { delete mVerifier; + + for (std::map::iterator iter (mReports.begin()); iter!=mReports.end(); ++iter) + delete iter->second; } CSMWorld::UniversalId CSMTools::Tools::runVerifier() @@ -132,17 +137,19 @@ int CSMTools::Tools::getRunningOperations() const CSMTools::ReportModel *CSMTools::Tools::getReport (const CSMWorld::UniversalId& id) { - if (id.getType()!=CSMWorld::UniversalId::Type_VerificationResults) + if (id.getType()!=CSMWorld::UniversalId::Type_VerificationResults && + id.getType()!=CSMWorld::UniversalId::Type_LoadErrorLog) throw std::logic_error ("invalid request for report model: " + id.toString()); return mReports.at (id.getIndex()); } -void CSMTools::Tools::verifierMessage (const QString& message, int type) +void CSMTools::Tools::verifierMessage (const CSMWorld::UniversalId& id, const std::string& message, + int type) { std::map::iterator iter = mActiveReports.find (type); if (iter!=mActiveReports.end()) - mReports[iter->second]->add (message.toUtf8().constData()); + mReports[iter->second]->add (id, message); } diff --git a/apps/opencs/model/tools/tools.hpp b/apps/opencs/model/tools/tools.hpp index 0079fab34..3394d3f62 100644 --- a/apps/opencs/model/tools/tools.hpp +++ b/apps/opencs/model/tools/tools.hpp @@ -61,7 +61,8 @@ namespace CSMTools private slots: - void verifierMessage (const QString& message, int type); + void verifierMessage (const CSMWorld::UniversalId& id, const std::string& message, + int type); signals: diff --git a/apps/opencs/model/world/cell.cpp b/apps/opencs/model/world/cell.cpp index cd58fca1e..40520a9ba 100644 --- a/apps/opencs/model/world/cell.cpp +++ b/apps/opencs/model/world/cell.cpp @@ -18,8 +18,3 @@ void CSMWorld::Cell::load (ESM::ESMReader &esm) mId = stream.str(); } } - -void CSMWorld::Cell::addRef (const std::string& id) -{ - mRefs.push_back (std::make_pair (id, false)); -} \ No newline at end of file diff --git a/apps/opencs/model/world/cell.hpp b/apps/opencs/model/world/cell.hpp index e6f3c8c35..8a2781590 100644 --- a/apps/opencs/model/world/cell.hpp +++ b/apps/opencs/model/world/cell.hpp @@ -1,7 +1,7 @@ #ifndef CSM_WOLRD_CELL_H #define CSM_WOLRD_CELL_H -#include +#include #include #include @@ -15,12 +15,12 @@ namespace CSMWorld struct Cell : public ESM::Cell { std::string mId; - std::vector > mRefs; // ID, modified - std::vector mDeletedRefs; - void load (ESM::ESMReader &esm); + /// These are the references modified by the edited content file. These are stored in + /// mModified only. + std::set mTouchedRefs; - void addRef (const std::string& id); + void load (ESM::ESMReader &esm); }; } diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index 6976b454d..6b276d151 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -10,6 +10,7 @@ #include "columnbase.hpp" #include "columns.hpp" #include "info.hpp" +#include "cell.hpp" namespace CSMWorld { @@ -87,6 +88,45 @@ namespace CSMWorld } }; + /// \brief Specialisation that takes care of the modified reference tracking + template<> + struct RecordStateColumn : public Column + { + RecordStateColumn() + : Column (Columns::ColumnId_Modification, ColumnBase::Display_RecordState) + {} + + virtual QVariant get (const Record& record) const + { + if (record.mState==Record::State_Erased) + return static_cast (Record::State_Deleted); + + if (!record.mModified.mTouchedRefs.empty() && + !record.mState==Record::State_Deleted && + !record.mState==Record::State_ModifiedOnly) + { + static_cast (Record::State_Modified); + } + + return static_cast (record.mState); + } + + virtual void set (Record& record, const QVariant& data) + { + record.mState = static_cast (data.toInt()); + } + + virtual bool isEditable() const + { + return true; + } + + virtual bool isUserEditable() const + { + return false; + } + }; + template struct FixedRecordTypeColumn : public Column { diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index d60dcae11..442e73e51 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -55,7 +55,8 @@ int CSMWorld::Data::count (RecordBase::State state, const CollectionBase& collec return number; } -CSMWorld::Data::Data() : mRefs (mCells) +CSMWorld::Data::Data (ToUTF8::FromType encoding) +: mEncoder (encoding), mRefs (mCells), mReader (0), mDialogue (0) { mGlobals.addColumn (new StringIdColumn); mGlobals.addColumn (new RecordStateColumn); @@ -260,6 +261,8 @@ CSMWorld::Data::~Data() { for (std::vector::iterator iter (mModels.begin()); iter!=mModels.end(); ++iter) delete *iter; + + delete mReader; } const CSMWorld::IdCollection& CSMWorld::Data::getGlobals() const @@ -453,7 +456,7 @@ CSMWorld::IdCollection& CSMWorld::Data::getFilters() return mFilters; } -QAbstractItemModel *CSMWorld::Data::getTableModel (const UniversalId& id) +QAbstractItemModel *CSMWorld::Data::getTableModel (const CSMWorld::UniversalId& id) { std::map::iterator iter = mModelIndex.find (id.getType()); @@ -481,148 +484,171 @@ void CSMWorld::Data::merge() mGlobals.merge(); } -void CSMWorld::Data::loadFile (const boost::filesystem::path& path, bool base, bool project) +int CSMWorld::Data::startLoading (const boost::filesystem::path& path, bool base, bool project) { - ESM::ESMReader reader; + delete mReader; + mReader = 0; + mDialogue = 0; + mRefLoadCache.clear(); - /// \todo set encoding properly, once config implementation has been fixed. - ToUTF8::Utf8Encoder encoder (ToUTF8::calculateEncoding ("win1252")); - reader.setEncoder (&encoder); + mReader = new ESM::ESMReader; + mReader->setEncoder (&mEncoder); + mReader->open (path.string()); - reader.open (path.string()); + mBase = base; + mProject = project; - const ESM::Dialogue *dialogue = 0; + mAuthor = mReader->getAuthor(); + mDescription = mReader->getDesc(); - mAuthor = reader.getAuthor(); - mDescription = reader.getDesc(); + return mReader->getRecordCount(); +} - // Note: We do not need to send update signals here, because at this point the model is not connected - // to any view. - while (reader.hasMoreRecs()) +bool CSMWorld::Data::continueLoading (CSMDoc::Stage::Messages& messages) +{ + if (!mReader) + throw std::logic_error ("can't continue loading, because no load has been started"); + + if (!mReader->hasMoreRecs()) { - ESM::NAME n = reader.getRecName(); - reader.getRecHeader(); + delete mReader; + mReader = 0; + mDialogue = 0; + mRefLoadCache.clear(); + return true; + } - switch (n.val) + ESM::NAME n = mReader->getRecName(); + mReader->getRecHeader(); + + switch (n.val) + { + case ESM::REC_GLOB: mGlobals.load (*mReader, mBase); break; + case ESM::REC_GMST: mGmsts.load (*mReader, mBase); break; + case ESM::REC_SKIL: mSkills.load (*mReader, mBase); break; + case ESM::REC_CLAS: mClasses.load (*mReader, mBase); break; + case ESM::REC_FACT: mFactions.load (*mReader, mBase); break; + case ESM::REC_RACE: mRaces.load (*mReader, mBase); break; + case ESM::REC_SOUN: mSounds.load (*mReader, mBase); break; + case ESM::REC_SCPT: mScripts.load (*mReader, mBase); break; + case ESM::REC_REGN: mRegions.load (*mReader, mBase); break; + case ESM::REC_BSGN: mBirthsigns.load (*mReader, mBase); break; + case ESM::REC_SPEL: mSpells.load (*mReader, mBase); break; + + case ESM::REC_CELL: { - case ESM::REC_GLOB: mGlobals.load (reader, base); break; - case ESM::REC_GMST: mGmsts.load (reader, base); break; - case ESM::REC_SKIL: mSkills.load (reader, base); break; - case ESM::REC_CLAS: mClasses.load (reader, base); break; - case ESM::REC_FACT: mFactions.load (reader, base); break; - case ESM::REC_RACE: mRaces.load (reader, base); break; - case ESM::REC_SOUN: mSounds.load (reader, base); break; - case ESM::REC_SCPT: mScripts.load (reader, base); break; - case ESM::REC_REGN: mRegions.load (reader, base); break; - case ESM::REC_BSGN: mBirthsigns.load (reader, base); break; - case ESM::REC_SPEL: mSpells.load (reader, base); break; - - case ESM::REC_CELL: - mCells.load (reader, base); - mRefs.load (reader, mCells.getSize()-1, base); - break; + mCells.load (*mReader, mBase); + std::string cellId = Misc::StringUtils::lowerCase (mCells.getId (mCells.getSize()-1)); + mRefs.load (*mReader, mCells.getSize()-1, mBase, mRefLoadCache[cellId], messages); + break; + } - case ESM::REC_ACTI: mReferenceables.load (reader, base, UniversalId::Type_Activator); break; - case ESM::REC_ALCH: mReferenceables.load (reader, base, UniversalId::Type_Potion); break; - case ESM::REC_APPA: mReferenceables.load (reader, base, UniversalId::Type_Apparatus); break; - case ESM::REC_ARMO: mReferenceables.load (reader, base, UniversalId::Type_Armor); break; - case ESM::REC_BOOK: mReferenceables.load (reader, base, UniversalId::Type_Book); break; - case ESM::REC_CLOT: mReferenceables.load (reader, base, UniversalId::Type_Clothing); break; - case ESM::REC_CONT: mReferenceables.load (reader, base, UniversalId::Type_Container); break; - case ESM::REC_CREA: mReferenceables.load (reader, base, UniversalId::Type_Creature); break; - case ESM::REC_DOOR: mReferenceables.load (reader, base, UniversalId::Type_Door); break; - case ESM::REC_INGR: mReferenceables.load (reader, base, UniversalId::Type_Ingredient); break; - case ESM::REC_LEVC: - mReferenceables.load (reader, base, UniversalId::Type_CreatureLevelledList); break; - case ESM::REC_LEVI: - mReferenceables.load (reader, base, UniversalId::Type_ItemLevelledList); break; - case ESM::REC_LIGH: mReferenceables.load (reader, base, UniversalId::Type_Light); break; - case ESM::REC_LOCK: mReferenceables.load (reader, base, UniversalId::Type_Lockpick); break; - case ESM::REC_MISC: - mReferenceables.load (reader, base, UniversalId::Type_Miscellaneous); break; - case ESM::REC_NPC_: mReferenceables.load (reader, base, UniversalId::Type_Npc); break; - case ESM::REC_PROB: mReferenceables.load (reader, base, UniversalId::Type_Probe); break; - case ESM::REC_REPA: mReferenceables.load (reader, base, UniversalId::Type_Repair); break; - case ESM::REC_STAT: mReferenceables.load (reader, base, UniversalId::Type_Static); break; - case ESM::REC_WEAP: mReferenceables.load (reader, base, UniversalId::Type_Weapon); break; - - case ESM::REC_DIAL: - { - std::string id = reader.getHNOString ("NAME"); + case ESM::REC_ACTI: mReferenceables.load (*mReader, mBase, UniversalId::Type_Activator); break; + case ESM::REC_ALCH: mReferenceables.load (*mReader, mBase, UniversalId::Type_Potion); break; + case ESM::REC_APPA: mReferenceables.load (*mReader, mBase, UniversalId::Type_Apparatus); break; + case ESM::REC_ARMO: mReferenceables.load (*mReader, mBase, UniversalId::Type_Armor); break; + case ESM::REC_BOOK: mReferenceables.load (*mReader, mBase, UniversalId::Type_Book); break; + case ESM::REC_CLOT: mReferenceables.load (*mReader, mBase, UniversalId::Type_Clothing); break; + case ESM::REC_CONT: mReferenceables.load (*mReader, mBase, UniversalId::Type_Container); break; + case ESM::REC_CREA: mReferenceables.load (*mReader, mBase, UniversalId::Type_Creature); break; + case ESM::REC_DOOR: mReferenceables.load (*mReader, mBase, UniversalId::Type_Door); break; + case ESM::REC_INGR: mReferenceables.load (*mReader, mBase, UniversalId::Type_Ingredient); break; + case ESM::REC_LEVC: + mReferenceables.load (*mReader, mBase, UniversalId::Type_CreatureLevelledList); break; + case ESM::REC_LEVI: + mReferenceables.load (*mReader, mBase, UniversalId::Type_ItemLevelledList); break; + case ESM::REC_LIGH: mReferenceables.load (*mReader, mBase, UniversalId::Type_Light); break; + case ESM::REC_LOCK: mReferenceables.load (*mReader, mBase, UniversalId::Type_Lockpick); break; + case ESM::REC_MISC: + mReferenceables.load (*mReader, mBase, UniversalId::Type_Miscellaneous); break; + case ESM::REC_NPC_: mReferenceables.load (*mReader, mBase, UniversalId::Type_Npc); break; + case ESM::REC_PROB: mReferenceables.load (*mReader, mBase, UniversalId::Type_Probe); break; + case ESM::REC_REPA: mReferenceables.load (*mReader, mBase, UniversalId::Type_Repair); break; + case ESM::REC_STAT: mReferenceables.load (*mReader, mBase, UniversalId::Type_Static); break; + case ESM::REC_WEAP: mReferenceables.load (*mReader, mBase, UniversalId::Type_Weapon); break; + + case ESM::REC_DIAL: + { + std::string id = mReader->getHNOString ("NAME"); - ESM::Dialogue record; - record.mId = id; - record.load (reader); + ESM::Dialogue record; + record.mId = id; + record.load (*mReader); - if (record.mType==ESM::Dialogue::Journal) + if (record.mType==ESM::Dialogue::Journal) + { + mJournals.load (record, mBase); + mDialogue = &mJournals.getRecord (id).get(); + } + else if (record.mType==ESM::Dialogue::Deleted) + { + mDialogue = 0; // record vector can be shuffled around which would make pointer + // to record invalid + + if (mJournals.tryDelete (id)) { - mJournals.load (record, base); - dialogue = &mJournals.getRecord (id).get(); + /// \todo handle info records } - else if (record.mType==ESM::Dialogue::Deleted) + else if (mTopics.tryDelete (id)) { - dialogue = 0; // record vector can be shuffled around which would make pointer - // to record invalid - - if (mJournals.tryDelete (id)) - { - /// \todo handle info records - } - else if (mTopics.tryDelete (id)) - { - /// \todo handle info records - } - else - { - /// \todo report deletion of non-existing record - } + /// \todo handle info records } else { - mTopics.load (record, base); - dialogue = &mTopics.getRecord (id).get(); + messages.push_back (std::make_pair (UniversalId::Type_None, + "Trying to delete dialogue record " + id + " which does not exist")); } + } + else + { + mTopics.load (record, mBase); + mDialogue = &mTopics.getRecord (id).get(); + } + break; + } + + case ESM::REC_INFO: + { + if (!mDialogue) + { + messages.push_back (std::make_pair (UniversalId::Type_None, + "Found info record not following a dialogue record")); + + mReader->skipRecord(); break; } - case ESM::REC_INFO: - { - if (!dialogue) - { - /// \todo INFO record without matching DIAL record -> report to user - reader.skipRecord(); - break; - } + if (mDialogue->mType==ESM::Dialogue::Journal) + mJournalInfos.load (*mReader, mBase, *mDialogue); + else + mTopicInfos.load (*mReader, mBase, *mDialogue); - if (dialogue->mType==ESM::Dialogue::Journal) - mJournalInfos.load (reader, base, *dialogue); - else - mTopicInfos.load (reader, base, *dialogue); + break; + } + + case ESM::REC_FILT: + if (mProject) + { + mFilters.load (*mReader, mBase); + mFilters.setData (mFilters.getSize()-1, + mFilters.findColumnIndex (CSMWorld::Columns::ColumnId_Scope), + static_cast (CSMFilter::Filter::Scope_Project)); break; } - case ESM::REC_FILT: - - if (project) - { - mFilters.load (reader, base); - mFilters.setData (mFilters.getSize()-1, - mFilters.findColumnIndex (CSMWorld::Columns::ColumnId_Scope), - static_cast (CSMFilter::Filter::Scope_Project)); - break; - } + // fall through (filter record in a content file is an error with format 0) - // fall through (filter record in a content file is an error with format 0) + default: - default: + messages.push_back (std::make_pair (UniversalId::Type_None, + "Unsupported record type: " + n.toString())); - /// \todo throw an exception instead, once all records are implemented - /// or maybe report error and continue? - reader.skipRecord(); - } + mReader->skipRecord(); } + + return false; } bool CSMWorld::Data::hasId (const std::string& id) const diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index 152c3ac41..9a842878a 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -22,8 +22,12 @@ #include #include +#include + #include "../filter/filter.hpp" +#include "../doc/stage.hpp" + #include "idcollection.hpp" #include "universalid.hpp" #include "cell.hpp" @@ -33,12 +37,19 @@ class QAbstractItemModel; +namespace ESM +{ + class ESMReader; + struct Dialogue; +} + namespace CSMWorld { class Data : public QObject { Q_OBJECT + ToUTF8::Utf8Encoder mEncoder; IdCollection mGlobals; IdCollection mGmsts; IdCollection mSkills; @@ -62,6 +73,11 @@ namespace CSMWorld std::map mModelIndex; std::string mAuthor; std::string mDescription; + ESM::ESMReader *mReader; + const ESM::Dialogue *mDialogue; // last loaded dialogue + bool mBase; + bool mProject; + std::map > mRefLoadCache; // not implemented Data (const Data&); @@ -78,7 +94,7 @@ namespace CSMWorld public: - Data(); + Data (ToUTF8::FromType encoding); virtual ~Data(); @@ -167,10 +183,15 @@ namespace CSMWorld void merge(); ///< Merge modified into base. - void loadFile (const boost::filesystem::path& path, bool base, bool project); - ///< Merging content of a file into base or modified. + int startLoading (const boost::filesystem::path& path, bool base, bool project); + ///< Begin merging content of a file into base or modified. /// /// \param project load project file instead of content file + /// + ///< \return estimated number of records + + bool continueLoading (CSMDoc::Stage::Messages& messages); + ///< \return Finished? bool hasId (const std::string& id) const; diff --git a/apps/opencs/model/world/ref.cpp b/apps/opencs/model/world/ref.cpp index cf9e496ee..d1bd771f8 100644 --- a/apps/opencs/model/world/ref.cpp +++ b/apps/opencs/model/world/ref.cpp @@ -1,12 +1,10 @@ #include "ref.hpp" -#include "cell.hpp" - -void CSMWorld::CellRef::load (ESM::ESMReader &esm, Cell& cell, const std::string& id) +CSMWorld::CellRef::CellRef() { - mId = id; - mCell = cell.mId; + mRefNum.mIndex = 0; - cell.addRef (mId); + // special marker: This reference does not have a RefNum assign to it yet. + mRefNum.mContentFile = -2; } \ No newline at end of file diff --git a/apps/opencs/model/world/ref.hpp b/apps/opencs/model/world/ref.hpp index fcf016ee2..eb62434cf 100644 --- a/apps/opencs/model/world/ref.hpp +++ b/apps/opencs/model/world/ref.hpp @@ -3,11 +3,6 @@ #include -namespace ESM -{ - class ESMReader; -} - namespace CSMWorld { class Cell; @@ -18,8 +13,7 @@ namespace CSMWorld std::string mId; std::string mCell; - void load (ESM::ESMReader &esm, Cell& cell, const std::string& id); - ///< Load cell ref and register it with \a cell. + CellRef(); }; } diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index 9ee59bd1e..36518f0fa 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -3,12 +3,15 @@ #include +#include + #include "ref.hpp" #include "cell.hpp" #include "universalid.hpp" #include "record.hpp" -void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool base) +void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool base, + std::map& cache, CSMDoc::Stage::Messages& messages) { Record cell = mCells.getRecord (cellIndex); @@ -17,19 +20,79 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool CellRef ref; bool deleted = false; - while (cell2.getNextRef (reader, ref, deleted)) + + while (ESM::Cell::getNextRef (reader, ref, deleted)) { - /// \todo handle deleted and moved references - ref.load (reader, cell2, getNewId()); + ref.mCell = cell2.mId; - Record record2; - record2.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; - (base ? record2.mBase : record2.mModified) = ref; + /// \todo handle moved references - appendRecord (record2); - } + std::map::iterator iter = cache.find (ref.mRefNum); + + if (deleted) + { + if (iter==cache.end()) + { + CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Cell, + mCells.getId (cellIndex)); + + messages.push_back (std::make_pair (id, + "Attempt to delete a non-existing reference")); + + continue; + } + + int index = getIndex (iter->second); + + Record record = getRecord (index); + + if (record.mState==RecordBase::State_BaseOnly) + { + removeRows (index, 1); + cache.erase (iter); + } + else + { + cell.mModified.mTouchedRefs.insert (Misc::StringUtils::lowerCase ( + mCells.getId (cellIndex))); + record.mState = RecordBase::State_Deleted; + setRecord (index, record); + } + + continue; + } - mCells.setRecord (cellIndex, cell); + if (!base) + cell.mModified.mTouchedRefs.insert (Misc::StringUtils::lowerCase ( + mCells.getId (cellIndex))); + + if (iter==cache.end()) + { + // new reference + ref.mId = getNewId(); + + Record record; + record.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; + (base ? record.mBase : record.mModified) = ref; + + appendRecord (record); + + cache.insert (std::make_pair (ref.mRefNum, ref.mId)); + } + else + { + // old reference -> merge + ref.mId = iter->second; + + int index = getIndex (ref.mId); + + Record record = getRecord (index); + record.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_Modified; + (base ? record.mBase : record.mModified) = ref; + + setRecord (index, record); + } + } } std::string CSMWorld::RefCollection::getNewId() @@ -38,14 +101,3 @@ std::string CSMWorld::RefCollection::getNewId() stream << "ref#" << mNextId++; return stream.str(); } - -void CSMWorld::RefCollection::cloneRecord(const std::string& origin, - const std::string& destination, - const CSMWorld::UniversalId::Type type, - const CSMWorld::UniversalId::ArgumentType argumentType) -{ - Record clone(getRecord(origin)); - clone.mState = CSMWorld::RecordBase::State_ModifiedOnly; - clone.get().mId = destination; - insertRecord(clone, getAppendIndex(destination, type), type); -} \ No newline at end of file diff --git a/apps/opencs/model/world/refcollection.hpp b/apps/opencs/model/world/refcollection.hpp index dcfd2036c..63d369ed9 100644 --- a/apps/opencs/model/world/refcollection.hpp +++ b/apps/opencs/model/world/refcollection.hpp @@ -1,6 +1,10 @@ #ifndef CSM_WOLRD_REFCOLLECTION_H #define CSM_WOLRD_REFCOLLECTION_H +#include + +#include "../doc/stage.hpp" + #include "collection.hpp" #include "ref.hpp" #include "record.hpp" @@ -22,15 +26,12 @@ namespace CSMWorld : mCells (cells), mNextId (0) {} - void load (ESM::ESMReader& reader, int cellIndex, bool base); + void load (ESM::ESMReader& reader, int cellIndex, bool base, + std::map& cache, + CSMDoc::Stage::Messages& messages); ///< Load a sequence of references. std::string getNewId(); - - void cloneRecord(const std::string& origin, - const std::string& destination, - const CSMWorld::UniversalId::Type type, - const CSMWorld::UniversalId::ArgumentType argumentType); }; } diff --git a/apps/opencs/model/world/tablemimedata.cpp b/apps/opencs/model/world/tablemimedata.cpp index d7b7728a5..6d65d0ff8 100644 --- a/apps/opencs/model/world/tablemimedata.cpp +++ b/apps/opencs/model/world/tablemimedata.cpp @@ -1,6 +1,9 @@ #include "tablemimedata.hpp" + #include +#include + #include "universalid.hpp" #include "columnbase.hpp" @@ -11,7 +14,7 @@ mDocument(document) mObjectsFormats << QString::fromUtf8 (("tabledata/" + id.getTypeName()).c_str()); } -CSMWorld::TableMimeData::TableMimeData (std::vector< CSMWorld::UniversalId >& id, const CSMDoc::Document& document) : +CSMWorld::TableMimeData::TableMimeData (const std::vector< CSMWorld::UniversalId >& id, const CSMDoc::Document& document) : mUniversalId (id), mDocument(document) { for (std::vector::iterator it (mUniversalId.begin()); it != mUniversalId.end(); ++it) @@ -33,7 +36,8 @@ std::string CSMWorld::TableMimeData::getIcon() const { if (mUniversalId.empty()) { - throw ("TableMimeData holds no UniversalId"); + qDebug()<<"TableMimeData object does not hold any records!"; //because throwing in the event loop tends to be problematic + throw("TableMimeData object does not hold any records!"); } std::string tmpIcon; @@ -50,7 +54,7 @@ std::string CSMWorld::TableMimeData::getIcon() const if (tmpIcon != mUniversalId[i].getIcon()) { - return ":/multitype.png"; //icon stolen from gnome + return ":/multitype.png"; //icon stolen from gnome TODO: get new icon } tmpIcon = mUniversalId[i].getIcon(); @@ -531,4 +535,4 @@ CSMWorld::ColumnBase::Display CSMWorld::TableMimeData::convertEnums (CSMWorld::U const CSMDoc::Document* CSMWorld::TableMimeData::getDocumentPtr() const { return &mDocument; -} \ No newline at end of file +} diff --git a/apps/opencs/model/world/tablemimedata.hpp b/apps/opencs/model/world/tablemimedata.hpp index adcb147c1..85c243944 100644 --- a/apps/opencs/model/world/tablemimedata.hpp +++ b/apps/opencs/model/world/tablemimedata.hpp @@ -33,7 +33,7 @@ namespace CSMWorld public: TableMimeData(UniversalId id, const CSMDoc::Document& document); - TableMimeData(std::vector& id, const CSMDoc::Document& document); + TableMimeData(const std::vector& id, const CSMDoc::Document& document); ~TableMimeData(); @@ -56,6 +56,7 @@ namespace CSMWorld UniversalId returnMatching(CSMWorld::ColumnBase::Display type) const; static CSMWorld::UniversalId::Type convertEnums(CSMWorld::ColumnBase::Display type); + static CSMWorld::ColumnBase::Display convertEnums(CSMWorld::UniversalId::Type type); private: diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp index a62acc02b..140a410c0 100644 --- a/apps/opencs/model/world/universalid.cpp +++ b/apps/opencs/model/world/universalid.cpp @@ -18,7 +18,7 @@ namespace static const TypeData sNoArg[] = { - { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, "empty", 0 }, + { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, "-", 0 }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Globals, "Global Variables", 0 }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Gmsts, "Game Settings", 0 }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Skills, "Skills", 0 }, @@ -64,6 +64,7 @@ namespace { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_TopicInfo, "TopicInfo", 0 }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_JournalInfo, "JournalInfo", 0 }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Cell, "Cell", ":./cell.png" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Cell_Missing, "Cell", ":./cell.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Referenceable, "Referenceables", 0 }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Activator, "Activator", ":./activator.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Potion, "Potion", ":./potion.png" }, @@ -100,6 +101,7 @@ namespace static const TypeData sIndexArg[] = { { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_VerificationResults, "Verification Results", 0 }, + { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_LoadErrorLog, "Load Error Log", 0 }, { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0, 0 } // end marker }; } diff --git a/apps/opencs/model/world/universalid.hpp b/apps/opencs/model/world/universalid.hpp index 34167cd85..22779b263 100644 --- a/apps/opencs/model/world/universalid.hpp +++ b/apps/opencs/model/world/universalid.hpp @@ -60,6 +60,7 @@ namespace CSMWorld Type_Spell, Type_Cells, Type_Cell, + Type_Cell_Missing, //For cells that does not exist yet. Type_Referenceables, Type_Referenceable, Type_Activator, @@ -96,10 +97,11 @@ namespace CSMWorld Type_JournalInfos, Type_JournalInfo, Type_Scene, - Type_Preview + Type_Preview, + Type_LoadErrorLog }; - enum { NumberOfTypes = Type_Scene+1 }; + enum { NumberOfTypes = Type_LoadErrorLog+1 }; private: diff --git a/apps/opencs/view/doc/loader.cpp b/apps/opencs/view/doc/loader.cpp new file mode 100644 index 000000000..7e4754ddf --- /dev/null +++ b/apps/opencs/view/doc/loader.cpp @@ -0,0 +1,193 @@ + +#include "loader.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "../../model/doc/document.hpp" + +void CSVDoc::LoadingDocument::closeEvent (QCloseEvent *event) +{ + event->ignore(); + cancel(); +} + +CSVDoc::LoadingDocument::LoadingDocument (CSMDoc::Document *document) +: mDocument (document), mAborted (false), mMessages (0) +{ + setWindowTitle (("Opening " + document->getSavePath().filename().string()).c_str()); + + setMinimumWidth (400); + + mLayout = new QVBoxLayout (this); + + // file progress + mFile = new QLabel (this); + + mLayout->addWidget (mFile); + + mFileProgress = new QProgressBar (this); + + mLayout->addWidget (mFileProgress); + + int size = static_cast (document->getContentFiles().size())+1; + if (document->isNew()) + --size; + + mFileProgress->setMinimum (0); + mFileProgress->setMaximum (size); + mFileProgress->setTextVisible (true); + mFileProgress->setValue (0); + + // record progress + mLayout->addWidget (new QLabel ("Records", this)); + + mRecordProgress = new QProgressBar (this); + + mLayout->addWidget (mRecordProgress); + + mRecordProgress->setMinimum (0); + mRecordProgress->setTextVisible (true); + mRecordProgress->setValue (0); + + // error message + mError = new QLabel (this); + mError->setWordWrap (true); + + mLayout->addWidget (mError); + + // buttons + mButtons = new QDialogButtonBox (QDialogButtonBox::Cancel, Qt::Horizontal, this); + + mLayout->addWidget (mButtons); + + setLayout (mLayout); + + move (QCursor::pos()); + + show(); + + connect (mButtons, SIGNAL (rejected()), this, SLOT (cancel())); +} + +void CSVDoc::LoadingDocument::nextStage (const std::string& name, int steps) +{ + mFile->setText (QString::fromUtf8 (("Loading: " + name).c_str())); + + mFileProgress->setValue (mFileProgress->value()+1); + + mRecordProgress->setValue (0); + mRecordProgress->setMaximum (steps>0 ? steps : 1); +} + +void CSVDoc::LoadingDocument::nextRecord() +{ + int value = mRecordProgress->value()+1; + + if (value<=mRecordProgress->maximum()) + mRecordProgress->setValue (value); +} + +void CSVDoc::LoadingDocument::abort (const std::string& error) +{ + mAborted = true; + mError->setText (QString::fromUtf8 (("Loading failed: " + error).c_str())); + mButtons->setStandardButtons (QDialogButtonBox::Close); +} + +void CSVDoc::LoadingDocument::addMessage (const std::string& message) +{ + if (!mMessages) + { + mMessages = new QListWidget (this); + mLayout->insertWidget (4, mMessages); + } + + new QListWidgetItem (QString::fromUtf8 (message.c_str()), mMessages); +} + +void CSVDoc::LoadingDocument::cancel() +{ + if (!mAborted) + emit cancel (mDocument); + else + { + emit close (mDocument); + deleteLater(); + } +} + + +CSVDoc::Loader::Loader() {} + +CSVDoc::Loader::~Loader() +{ + for (std::map::iterator iter (mDocuments.begin()); + iter!=mDocuments.end(); ++iter) + delete iter->second; +} + +void CSVDoc::Loader::add (CSMDoc::Document *document) +{ + LoadingDocument *loading = new LoadingDocument (document); + mDocuments.insert (std::make_pair (document, loading)); + + connect (loading, SIGNAL (cancel (CSMDoc::Document *)), + this, SIGNAL (cancel (CSMDoc::Document *))); + connect (loading, SIGNAL (close (CSMDoc::Document *)), + this, SIGNAL (close (CSMDoc::Document *))); +} + +void CSVDoc::Loader::loadingStopped (CSMDoc::Document *document, bool completed, + const std::string& error) +{ + std::map::iterator iter = mDocuments.begin(); + + for (; iter!=mDocuments.end(); ++iter) + if (iter->first==document) + break; + + if (iter==mDocuments.end()) + return; + + if (completed || error.empty()) + { + delete iter->second; + mDocuments.erase (iter); + } + else if (!completed && !error.empty()) + { + iter->second->abort (error); + // Leave the window open for now (wait for the user to close it) + mDocuments.erase (iter); + } +} + +void CSVDoc::Loader::nextStage (CSMDoc::Document *document, const std::string& name, int steps) +{ + std::map::iterator iter = mDocuments.find (document); + + if (iter!=mDocuments.end()) + iter->second->nextStage (name, steps); +} + +void CSVDoc::Loader::nextRecord (CSMDoc::Document *document) +{ + std::map::iterator iter = mDocuments.find (document); + + if (iter!=mDocuments.end()) + iter->second->nextRecord(); +} + +void CSVDoc::Loader::loadMessage (CSMDoc::Document *document, const std::string& message) +{ + std::map::iterator iter = mDocuments.find (document); + + if (iter!=mDocuments.end()) + iter->second->addMessage (message); +} \ No newline at end of file diff --git a/apps/opencs/view/doc/loader.hpp b/apps/opencs/view/doc/loader.hpp new file mode 100644 index 000000000..69a8b48ba --- /dev/null +++ b/apps/opencs/view/doc/loader.hpp @@ -0,0 +1,99 @@ +#ifndef CSV_DOC_LOADER_H +#define CSV_DOC_LOADER_H + +#include + +#include +#include +#include + +class QLabel; +class QProgressBar; +class QDialogButtonBox; +class QListWidget; +class QVBoxLayout; + +namespace CSMDoc +{ + class Document; +} + +namespace CSVDoc +{ + class LoadingDocument : public QWidget + { + Q_OBJECT + + CSMDoc::Document *mDocument; + QLabel *mFile; + QProgressBar *mFileProgress; + QProgressBar *mRecordProgress; + bool mAborted; + QDialogButtonBox *mButtons; + QLabel *mError; + QListWidget *mMessages; + QVBoxLayout *mLayout; + + private: + + void closeEvent (QCloseEvent *event); + + public: + + LoadingDocument (CSMDoc::Document *document); + + void nextStage (const std::string& name, int steps); + + void nextRecord(); + + void abort (const std::string& error); + + void addMessage (const std::string& message); + + private slots: + + void cancel(); + + signals: + + void cancel (CSMDoc::Document *document); + ///< Stop loading process. + + void close (CSMDoc::Document *document); + ///< Close stopped loading process. + }; + + class Loader : public QObject + { + Q_OBJECT + + std::map mDocuments; + + public: + + Loader(); + + virtual ~Loader(); + + signals: + + void cancel (CSMDoc::Document *document); + + void close (CSMDoc::Document *document); + + public slots: + + void add (CSMDoc::Document *document); + + void loadingStopped (CSMDoc::Document *document, bool completed, + const std::string& error); + + void nextStage (CSMDoc::Document *document, const std::string& name, int steps); + + void nextRecord (CSMDoc::Document *document); + + void loadMessage (CSMDoc::Document *document, const std::string& message); + }; +} + +#endif diff --git a/apps/opencs/view/doc/opendialog.cpp b/apps/opencs/view/doc/opendialog.cpp deleted file mode 100644 index d107b198c..000000000 --- a/apps/opencs/view/doc/opendialog.cpp +++ /dev/null @@ -1,62 +0,0 @@ -#include -#include - -#include - -#include "opendialog.hpp" - -OpenDialog::OpenDialog(QWidget * parent) : QDialog(parent) -{ - QVBoxLayout *layout = new QVBoxLayout(this); - mFileSelector = new DataFilesList(mCfgMgr, this); - layout->addWidget(mFileSelector); - - /// \todo move config to Editor class and add command line options. - // We use the Configuration Manager to retrieve the configuration values - boost::program_options::variables_map variables; - boost::program_options::options_description desc; - - desc.add_options() - ("data", boost::program_options::value()->default_value(Files::PathContainer(), "data")->multitoken()) - ("data-local", boost::program_options::value()->default_value("")) - ("fs-strict", boost::program_options::value()->implicit_value(true)->default_value(false)) - ("encoding", boost::program_options::value()->default_value("win1252")); - - boost::program_options::notify(variables); - - mCfgMgr.readConfiguration(variables, desc); - - Files::PathContainer mDataDirs, mDataLocal; - if (!variables["data"].empty()) { - mDataDirs = Files::PathContainer(variables["data"].as()); - } - - std::string local = variables["data-local"].as(); - if (!local.empty()) { - mDataLocal.push_back(Files::PathContainer::value_type(local)); - } - - mCfgMgr.processPaths(mDataDirs); - mCfgMgr.processPaths(mDataLocal); - - // Set the charset for reading the esm/esp files - QString encoding = QString::fromUtf8 (variables["encoding"].as().c_str()); - - Files::PathContainer dataDirs; - dataDirs.insert(dataDirs.end(), mDataDirs.begin(), mDataDirs.end()); - dataDirs.insert(dataDirs.end(), mDataLocal.begin(), mDataLocal.end()); - mFileSelector->setupDataFiles(dataDirs, encoding); - - buttonBox = new QDialogButtonBox(QDialogButtonBox::Open | QDialogButtonBox::Cancel, Qt::Horizontal, this); - connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); - connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); - layout->addWidget(buttonBox); - - setLayout(layout); - setWindowTitle(tr("Open")); -} - -void OpenDialog::getFileList(std::vector& paths) -{ - mFileSelector->selectedFiles(paths); -} diff --git a/apps/opencs/view/doc/opendialog.hpp b/apps/opencs/view/doc/opendialog.hpp deleted file mode 100644 index 6355aea44..000000000 --- a/apps/opencs/view/doc/opendialog.hpp +++ /dev/null @@ -1,17 +0,0 @@ -#include -#include - -class DataFilesList; -class QDialogButtonBox; - -class OpenDialog : public QDialog { - Q_OBJECT -public: - OpenDialog(QWidget * parent = 0); - - void getFileList(std::vector& paths); -private: - DataFilesList * mFileSelector; - QDialogButtonBox * buttonBox; - Files::ConfigurationManager mCfgMgr; -}; \ No newline at end of file diff --git a/apps/opencs/view/doc/subview.cpp b/apps/opencs/view/doc/subview.cpp index 7fd005717..a80d21cb2 100644 --- a/apps/opencs/view/doc/subview.cpp +++ b/apps/opencs/view/doc/subview.cpp @@ -12,16 +12,15 @@ CSMWorld::UniversalId CSVDoc::SubView::getUniversalId() const return mUniversalId; } -void CSVDoc::SubView::updateEditorSetting (const QString &settingName, const QString &settingValue) -{ -} - void CSVDoc::SubView::setStatusBar (bool show) {} void CSVDoc::SubView::useHint (const std::string& hint) {} +void CSVDoc::SubView::updateUserSetting (const QString &, const QStringList &) +{} + void CSVDoc::SubView::setUniversalId (const CSMWorld::UniversalId& id) { mUniversalId = id; setWindowTitle (mUniversalId.toString().c_str()); -} \ No newline at end of file +} diff --git a/apps/opencs/view/doc/subview.hpp b/apps/opencs/view/doc/subview.hpp index 85274a18d..733a75bb0 100644 --- a/apps/opencs/view/doc/subview.hpp +++ b/apps/opencs/view/doc/subview.hpp @@ -37,7 +37,6 @@ namespace CSVDoc CSMWorld::UniversalId getUniversalId() const; virtual void setEditLock (bool locked) = 0; - virtual void updateEditorSetting (const QString &, const QString &); virtual void setStatusBar (bool show); ///< Default implementation: ignored @@ -48,6 +47,10 @@ namespace CSVDoc signals: void focusId (const CSMWorld::UniversalId& universalId, const std::string& hint); + + public slots: + virtual void updateUserSetting + (const QString &, const QStringList &); }; } diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index bc34c6118..e71b8435a 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -10,9 +10,12 @@ #include #include "../../model/doc/document.hpp" +#include "../../model/settings/usersettings.hpp" + #include "../world/subviews.hpp" + #include "../tools/subviews.hpp" -#include "../settings/usersettingsdialog.hpp" + #include "viewmanager.hpp" #include "operations.hpp" #include "subview.hpp" @@ -47,6 +50,10 @@ void CSVDoc::View::setupFileMenu() connect (mVerify, SIGNAL (triggered()), this, SLOT (verify())); file->addAction (mVerify); + QAction *loadErrors = new QAction (tr ("Load Error Log"), this); + connect (loadErrors, SIGNAL (triggered()), this, SLOT (loadErrorLog())); + file->addAction (loadErrors); + QAction *close = new QAction (tr ("&Close"), this); connect (close, SIGNAL (triggered()), this, SLOT (close())); file->addAction(close); @@ -235,8 +242,11 @@ CSVDoc::View::View (ViewManager& viewManager, CSMDoc::Document *document, int to : mViewManager (viewManager), mDocument (document), mViewIndex (totalViews-1), mViewTotal (totalViews) { - QString width = CSMSettings::UserSettings::instance().getSetting(QString("Window Size"), QString("Width")); - QString height = CSMSettings::UserSettings::instance().getSetting(QString("Window Size"), QString("Height")); + QString width = CSMSettings::UserSettings::instance().settingValue + ("Window Size/Width"); + + QString height = CSMSettings::UserSettings::instance().settingValue + ("Window Size/Height"); resize (width.toInt(), height.toInt()); @@ -336,7 +346,10 @@ void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id, const std::strin connect (view, SIGNAL (focusId (const CSMWorld::UniversalId&, const std::string&)), this, SLOT (addSubView (const CSMWorld::UniversalId&, const std::string&))); - CSMSettings::UserSettings::instance().updateSettings("Display Format"); + connect (&CSMSettings::UserSettings::instance(), + SIGNAL (userSettingUpdated (const QString &, const QStringList &)), + view, + SLOT (updateUserSetting (const QString &, const QStringList &))); view->show(); } @@ -484,25 +497,9 @@ void CSVDoc::View::resizeViewHeight (int height) resize (geometry().width(), height); } -void CSVDoc::View::updateEditorSetting (const QString &settingName, const QString &settingValue) -{ - if ( (settingName == "Record Status Display") || (settingName == "Referenceable ID Type Display") ) - { - foreach (QObject *view, mSubViewWindow.children()) - { - // not all mSubviewWindow children are CSVDoc::Subview objects - CSVDoc::SubView *subview = dynamic_cast(view); - - if (subview) - subview->updateEditorSetting (settingName, settingValue); - } - } - else if (settingName == "Width") - resizeViewWidth (settingValue.toInt()); - - else if (settingName == "Height") - resizeViewHeight (settingValue.toInt()); -} +void CSVDoc::View::updateUserSetting + (const QString &name, const QStringList &list) +{} void CSVDoc::View::toggleShowStatusBar (bool show) { @@ -512,3 +509,8 @@ void CSVDoc::View::toggleShowStatusBar (bool show) subView->setStatusBar (show); } } + +void CSVDoc::View::loadErrorLog() +{ + addSubView (CSMWorld::UniversalId (CSMWorld::UniversalId::Type_LoadErrorLog, 0)); +} diff --git a/apps/opencs/view/doc/view.hpp b/apps/opencs/view/doc/view.hpp index ee7380e2b..686c001dc 100644 --- a/apps/opencs/view/doc/view.hpp +++ b/apps/opencs/view/doc/view.hpp @@ -126,6 +126,8 @@ namespace CSVDoc void abortOperation (int type); + void updateUserSetting (const QString &, const QStringList &); + private slots: void newView(); @@ -177,6 +179,8 @@ namespace CSVDoc void addJournalInfosSubView(); void toggleShowStatusBar (bool show); + + void loadErrorLog(); }; } diff --git a/apps/opencs/view/doc/viewmanager.cpp b/apps/opencs/view/doc/viewmanager.cpp index 4a4dbc124..02f6a5467 100644 --- a/apps/opencs/view/doc/viewmanager.cpp +++ b/apps/opencs/view/doc/viewmanager.cpp @@ -15,7 +15,8 @@ #include "../world/vartypedelegate.hpp" #include "../world/recordstatusdelegate.hpp" #include "../world/idtypedelegate.hpp" -#include "../settings/usersettingsdialog.hpp" + +#include "../../model/settings/usersettings.hpp" #include "view.hpp" @@ -84,8 +85,32 @@ CSVDoc::ViewManager::ViewManager (CSMDoc::DocumentManager& documentManager) mDelegateFactories->add (sMapping[i].mDisplay, new CSVWorld::EnumDelegateFactory ( CSMWorld::Columns::getEnums (sMapping[i].mColumnId), sMapping[i].mAllowNone)); - connect (&CSMSettings::UserSettings::instance(), SIGNAL (signalUpdateEditorSetting (const QString &, const QString &)), - this, SLOT (slotUpdateEditorSetting (const QString &, const QString &))); + connect (&mDocumentManager, SIGNAL (loadRequest (CSMDoc::Document *)), + &mLoader, SLOT (add (CSMDoc::Document *))); + + connect ( + &mDocumentManager, SIGNAL (loadingStopped (CSMDoc::Document *, bool, const std::string&)), + &mLoader, SLOT (loadingStopped (CSMDoc::Document *, bool, const std::string&))); + + connect ( + &mDocumentManager, SIGNAL (nextStage (CSMDoc::Document *, const std::string&, int)), + &mLoader, SLOT (nextStage (CSMDoc::Document *, const std::string&, int))); + + connect ( + &mDocumentManager, SIGNAL (nextRecord (CSMDoc::Document *)), + &mLoader, SLOT (nextRecord (CSMDoc::Document *))); + + connect ( + &mDocumentManager, SIGNAL (loadMessage (CSMDoc::Document *, const std::string&)), + &mLoader, SLOT (loadMessage (CSMDoc::Document *, const std::string&))); + + connect ( + &mLoader, SIGNAL (cancel (CSMDoc::Document *)), + &mDocumentManager, SIGNAL (cancelLoading (CSMDoc::Document *))); + + connect ( + &mLoader, SIGNAL (close (CSMDoc::Document *)), + &mDocumentManager, SLOT (removeDocument (CSMDoc::Document *))); } CSVDoc::ViewManager::~ViewManager() @@ -119,6 +144,11 @@ CSVDoc::View *CSVDoc::ViewManager::addView (CSMDoc::Document *document) connect (view, SIGNAL (loadDocumentRequest ()), this, SIGNAL (loadDocumentRequest())); connect (view, SIGNAL (editSettingsRequest()), this, SIGNAL (editSettingsRequest())); + connect (&CSMSettings::UserSettings::instance(), + SIGNAL (userSettingUpdated(const QString &, const QStringList &)), + view, + SLOT (updateUserSetting (const QString &, const QStringList &))); + updateIndices(); return view; @@ -313,9 +343,3 @@ void CSVDoc::ViewManager::exitApplication (CSVDoc::View *view) if (notifySaveOnClose (view)) QApplication::instance()->exit(); } - -void CSVDoc::ViewManager::slotUpdateEditorSetting (const QString &settingName, const QString &settingValue) -{ - foreach (CSVDoc::View *view, mViews) - view->updateEditorSetting (settingName, settingValue); -} diff --git a/apps/opencs/view/doc/viewmanager.hpp b/apps/opencs/view/doc/viewmanager.hpp index 01f495186..8cc92774b 100644 --- a/apps/opencs/view/doc/viewmanager.hpp +++ b/apps/opencs/view/doc/viewmanager.hpp @@ -5,6 +5,8 @@ #include +#include "loader.hpp" + namespace CSMDoc { class Document; @@ -29,6 +31,7 @@ namespace CSVDoc CSVWorld::CommandDelegateFactoryCollection *mDelegateFactories; bool mExitOnSaveStateChange; bool mUserWarned; + Loader mLoader; // not implemented ViewManager (const ViewManager&); @@ -76,9 +79,6 @@ namespace CSVDoc void progress (int current, int max, int type, int threads, CSMDoc::Document *document); void onExitWarningHandler(int state, CSMDoc::Document* document); - - /// connected to update signal in UserSettings - void slotUpdateEditorSetting (const QString &, const QString &); }; } diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index 0f23dfe7b..a3f34d218 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -3,8 +3,12 @@ #include -CSVRender::PagedWorldspaceWidget::PagedWorldspaceWidget (QWidget *parent) -: WorldspaceWidget (parent) +#include + +#include + +CSVRender::PagedWorldspaceWidget::PagedWorldspaceWidget (QWidget* parent, CSMDoc::Document& document) +: WorldspaceWidget (document, parent) {} void CSVRender::PagedWorldspaceWidget::useViewHint (const std::string& hint) @@ -44,4 +48,45 @@ void CSVRender::PagedWorldspaceWidget::setCellSelection (const CSMWorld::CellSel { mSelection = selection; emit cellSelectionChanged (mSelection); +} + +std::pair< int, int > CSVRender::PagedWorldspaceWidget::getCoordinatesFromId (const std::string& record) const +{ + std::istringstream stream (record.c_str()); + char ignore; + int x, y; + stream >> ignore >> x >> y; + return std::make_pair(x, y); +} + +void CSVRender::PagedWorldspaceWidget::handleDrop (const std::vector< CSMWorld::UniversalId >& data) +{ + bool selectionChanged = false; + for (unsigned i = 0; i < data.size(); ++i) + { + std::pair coordinates(getCoordinatesFromId(data[i].getId())); + if (mSelection.add(CSMWorld::CellCoordinates(coordinates.first, coordinates.second))) + { + selectionChanged = true; + } + } + if (selectionChanged) + { + emit cellSelectionChanged(mSelection); + } +} + +CSVRender::WorldspaceWidget::dropRequirments CSVRender::PagedWorldspaceWidget::getDropRequirements (CSVRender::WorldspaceWidget::dropType type) const +{ + switch (type) + { + case cellsExterior: + return canHandle; + + case cellsInterior: + return needUnpaged; + + default: + return ignored; + } } \ No newline at end of file diff --git a/apps/opencs/view/render/pagedworldspacewidget.hpp b/apps/opencs/view/render/pagedworldspacewidget.hpp index f6ff32dc1..0a73c791c 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.hpp +++ b/apps/opencs/view/render/pagedworldspacewidget.hpp @@ -13,17 +13,25 @@ namespace CSVRender CSMWorld::CellSelection mSelection; + private: + + std::pair getCoordinatesFromId(const std::string& record) const; + public: - PagedWorldspaceWidget (QWidget *parent); + PagedWorldspaceWidget (QWidget *parent, CSMDoc::Document& document); ///< \note Sets the cell area selection to an invalid value to indicate that currently /// no cells are displayed. The cells to be displayed will be specified later through /// hint system. - virtual void useViewHint (const std::string& hint); + void useViewHint (const std::string& hint); void setCellSelection (const CSMWorld::CellSelection& selection); + virtual void handleDrop(const std::vector& data); + + virtual dropRequirments getDropRequirements(dropType type) const; + signals: void cellSelectionChanged (const CSMWorld::CellSelection& selection); diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index 1aee421ed..52e7afefd 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -86,8 +86,11 @@ namespace CSVRender } std::stringstream windowHandle; +#ifdef WIN32 + windowHandle << Ogre::StringConverter::toString((unsigned long)(this->winId())); +#else windowHandle << this->winId(); - +#endif std::stringstream windowTitle; static int count=0; windowTitle << ++count; @@ -320,7 +323,7 @@ namespace CSVRender } - if (mUpdate) + if (mUpdate && mWindow) { mUpdate = false; mWindow->update(); diff --git a/apps/opencs/view/render/scenewidget.hpp b/apps/opencs/view/render/scenewidget.hpp index 8df9cf347..7f8f104f1 100644 --- a/apps/opencs/view/render/scenewidget.hpp +++ b/apps/opencs/view/render/scenewidget.hpp @@ -36,7 +36,7 @@ namespace CSVRender SceneWidget(QWidget *parent); virtual ~SceneWidget(); - QPaintEngine* paintEngine() const; + QPaintEngine* paintEngine() const; CSVWorld::SceneToolMode *makeLightingSelector (CSVWorld::SceneToolbar *parent); ///< \attention The created tool is not added to the toolbar (via addTool). Doing that diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index fb74788cc..166c85f44 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -3,10 +3,13 @@ #include +#include + #include "../../model/doc/document.hpp" #include "../../model/world/data.hpp" #include "../../model/world/idtable.hpp" +#include "../../model/world/tablemimedata.hpp" void CSVRender::UnpagedWorldspaceWidget::update() { @@ -20,9 +23,8 @@ void CSVRender::UnpagedWorldspaceWidget::update() /// \todo deal with mSunlight and mFog/mForDensity } -CSVRender::UnpagedWorldspaceWidget::UnpagedWorldspaceWidget (const std::string& cellId, - CSMDoc::Document& document, QWidget *parent) -: WorldspaceWidget (parent), mCellId (cellId) +CSVRender::UnpagedWorldspaceWidget::UnpagedWorldspaceWidget (const std::string& cellId, CSMDoc::Document& document, QWidget* parent) +: WorldspaceWidget (document, parent), mCellId (cellId) { mCellsModel = &dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); @@ -63,4 +65,26 @@ void CSVRender::UnpagedWorldspaceWidget::cellRowsAboutToBeRemoved (const QModelI if (cellIndex.row()>=start && cellIndex.row()<=end) emit closeRequest(); -} \ No newline at end of file +} + +void CSVRender::UnpagedWorldspaceWidget::handleDrop (const std::vector< CSMWorld::UniversalId >& data) +{ + mCellId = data.begin()->getId(); + update(); + emit cellChanged(*data.begin()); +} + +CSVRender::WorldspaceWidget::dropRequirments CSVRender::UnpagedWorldspaceWidget::getDropRequirements (CSVRender::WorldspaceWidget::dropType type) const +{ + switch(type) + { + case cellsInterior: + return canHandle; + + case cellsExterior: + return needPaged; + + default: + return ignored; + } +} diff --git a/apps/opencs/view/render/unpagedworldspacewidget.hpp b/apps/opencs/view/render/unpagedworldspacewidget.hpp index 17dc46918..bb5340845 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.hpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.hpp @@ -31,13 +31,21 @@ namespace CSVRender public: UnpagedWorldspaceWidget (const std::string& cellId, CSMDoc::Document& document, - QWidget *parent); + QWidget *parent); + + virtual dropRequirments getDropRequirements(dropType type) const; + + virtual void handleDrop(const std::vector& data); private slots: void cellDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); void cellRowsAboutToBeRemoved (const QModelIndex& parent, int start, int end); + + signals: + + void cellChanged(const CSMWorld::UniversalId& id); }; } diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index 4d2442c89..59b82bb67 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -5,15 +5,20 @@ #include #include +#include + #include "../world/scenetoolmode.hpp" +#include -CSVRender::WorldspaceWidget::WorldspaceWidget (QWidget *parent) -: SceneWidget (parent) +CSVRender::WorldspaceWidget::WorldspaceWidget (const CSMDoc::Document& document, QWidget* parent) +: SceneWidget (parent), mDocument(document) { Ogre::Entity* ent = getSceneManager()->createEntity("cube", Ogre::SceneManager::PT_CUBE); ent->setMaterialName("BaseWhite"); getSceneManager()->getRootSceneNode()->attachObject(ent); + + setAcceptDrops(true); } void CSVRender::WorldspaceWidget::selectNavigationMode (const std::string& mode) @@ -46,4 +51,78 @@ CSVWorld::SceneToolMode *CSVRender::WorldspaceWidget::makeNavigationSelector ( this, SLOT (selectNavigationMode (const std::string&))); return tool; +} + +CSVRender::WorldspaceWidget::dropType CSVRender::WorldspaceWidget::getDropType ( + const std::vector< CSMWorld::UniversalId >& data) +{ + dropType output = notCells; + bool firstIteration = true; + + for (unsigned i = 0; i < data.size(); ++i) + { + if (data[i].getType() == CSMWorld::UniversalId::Type_Cell || + data[i].getType() == CSMWorld::UniversalId::Type_Cell_Missing) + { + if (*(data[i].getId().begin()) == '#') //exterior + { + if (firstIteration) + { + output = cellsExterior; + firstIteration = false; + continue; + } + + if (output == cellsInterior) + { + output = cellsMixed; + break; + } else { + output = cellsInterior; + } + } else //interior + { + if (firstIteration) + { + output = cellsInterior; + firstIteration = false; + continue; + } + + if (output == cellsExterior) + { + output = cellsMixed; + break; + } else { + output = cellsInterior; + } + } + } else { + output = notCells; + break; + } + } + + return output; +} + +void CSVRender::WorldspaceWidget::dragEnterEvent (QDragEnterEvent* event) +{ + event->accept(); +} + +void CSVRender::WorldspaceWidget::dragMoveEvent(QDragMoveEvent *event) +{ + event->accept(); +} + + +void CSVRender::WorldspaceWidget::dropEvent (QDropEvent* event) +{ + const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); + + if (mime->fromDocument (mDocument)) + { + emit dataDropped(mime->getData()); + } //not handling drops from different documents at the moment } \ No newline at end of file diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index f7208d7a1..a14e03915 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -6,7 +6,13 @@ #include "navigation1st.hpp" #include "navigationfree.hpp" #include "navigationorbit.hpp" +#include +#include +namespace CSMWorld +{ + class UniversalId; +} namespace CSVWorld { class SceneToolMode; @@ -25,7 +31,23 @@ namespace CSVRender public: - WorldspaceWidget (QWidget *parent = 0); + enum dropType + { + cellsMixed, + cellsInterior, + cellsExterior, + notCells + }; + + enum dropRequirments + { + canHandle, + needPaged, + needUnpaged, + ignored //either mixed cells, or not cells + }; + + WorldspaceWidget (const CSMDoc::Document& document, QWidget *parent = 0); CSVWorld::SceneToolMode *makeNavigationSelector (CSVWorld::SceneToolbar *parent); ///< \attention The created tool is not added to the toolbar (via addTool). Doing that @@ -33,9 +55,26 @@ namespace CSVRender void selectDefaultNavigationMode(); + static dropType getDropType(const std::vector& data); + + virtual dropRequirments getDropRequirements(dropType type) const = 0; + virtual void useViewHint (const std::string& hint); ///< Default-implementation: ignored. + virtual void handleDrop(const std::vector& data) = 0; + + protected: + const CSMDoc::Document& mDocument; //for checking if drop comes from same document + + private: + + void dragEnterEvent(QDragEnterEvent *event); + + void dropEvent(QDropEvent* event); + + void dragMoveEvent(QDragMoveEvent *event); + private slots: void selectNavigationMode (const std::string& mode); @@ -43,6 +82,7 @@ namespace CSVRender signals: void closeRequest(); + void dataDropped(const std::vector& data); }; } diff --git a/apps/opencs/view/settings/abstractblock.cpp b/apps/opencs/view/settings/abstractblock.cpp deleted file mode 100644 index 65825ce8b..000000000 --- a/apps/opencs/view/settings/abstractblock.cpp +++ /dev/null @@ -1,112 +0,0 @@ -#include "abstractblock.hpp" - -CSVSettings::AbstractBlock::AbstractBlock(QWidget* parent) - : QObject (parent), mBox ( new GroupBox (parent) ), mWidgetParent (parent) -{} - -CSVSettings::AbstractBlock::AbstractBlock(bool isVisible, QWidget* parent) - : QObject (parent), mBox ( new GroupBox (isVisible, parent)), mWidgetParent (parent) -{} - -QLayout *CSVSettings::AbstractBlock::createLayout (Orientation direction, - bool isZeroMargin, QWidget* parent) -{ - QLayout *layout = 0; - - if (direction == Orient_Vertical) - layout = new QVBoxLayout (parent); - else - layout = new QHBoxLayout (parent); - - if (isZeroMargin) - layout->setContentsMargins(0, 0, 0, 0); - - return layout; -} - -QGroupBox *CSVSettings::AbstractBlock::getGroupBox() -{ - return mBox; -} - -CSVSettings::AbstractWidget *CSVSettings::AbstractBlock::buildWidget (const QString& widgetName, WidgetDef &def, - QLayout *layout, bool isConnected) const -{ - AbstractWidget *widg = 0; - - switch (def.type) - { - - case Widget_RadioButton: - widg = new SettingWidget (def, layout, mBox); - break; - - case Widget_SpinBox: - widg = new SettingWidget (def, layout, mBox); - break; - - case Widget_CheckBox: - widg = new SettingWidget (def, layout, mBox); - break; - - case Widget_LineEdit: - widg = new SettingWidget (def, layout, mBox); - break; - - case Widget_ListBox: - widg = new SettingWidget (def, layout, mBox); - break; - - case Widget_ComboBox: - widg = new SettingWidget (def, layout, mBox); - break; - - default: - break; - }; - - if (!mBox->layout()) - mBox->setLayout(widg->getLayout()); - - widg->widget()->setObjectName(widgetName); - - if (isConnected) - connect (widg, SIGNAL (signalUpdateItem (const QString &)), this, SLOT (slotUpdate (const QString &))); - connect (this, SIGNAL (signalUpdateWidget (const QString &)), widg, SLOT (slotUpdateWidget (const QString &) )); - - return widg; -} - -void CSVSettings::AbstractBlock::setVisible (bool isVisible) -{ - mBox->setBorderVisibility (isVisible); -} - -bool CSVSettings::AbstractBlock::isVisible () const -{ - return mBox->borderVisibile(); -} - -QWidget *CSVSettings::AbstractBlock::getParent() const -{ - return mWidgetParent; -} - -void CSVSettings::AbstractBlock::slotUpdate (const QString &value) -{ - slotUpdateSetting (objectName(), value); -} - -void CSVSettings::AbstractBlock::slotSetEnabled(bool value) -{ - mBox->setEnabled(value); -} - -void CSVSettings::AbstractBlock::slotUpdateSetting (const QString &settingName, const QString &settingValue) -{ - bool doEmit = true; - updateBySignal (settingName, settingValue, doEmit); - - if (doEmit) - emit signalUpdateSetting (settingName, settingValue); -} diff --git a/apps/opencs/view/settings/abstractblock.hpp b/apps/opencs/view/settings/abstractblock.hpp deleted file mode 100644 index 361339fe2..000000000 --- a/apps/opencs/view/settings/abstractblock.hpp +++ /dev/null @@ -1,82 +0,0 @@ -#ifndef ABSTRACTBLOCK_HPP -#define ABSTRACTBLOCK_HPP - -#include -#include - -#include "settingwidget.hpp" -#include "../../model/settings/settingsitem.hpp" -#include "groupbox.hpp" - -namespace CSVSettings -{ - - /// Abstract base class for all blocks - class AbstractBlock : public QObject - { - Q_OBJECT - - protected: - - typedef QMap SettingsItemMap; - GroupBox *mBox; - QWidget *mWidgetParent; - - public: - - explicit AbstractBlock (QWidget *parent = 0); - explicit AbstractBlock (bool isVisible, QWidget *parent = 0); - - QGroupBox *getGroupBox(); - void setVisible (bool isVisible); - bool isVisible() const; - - virtual CSMSettings::SettingList *getSettings() = 0; - - /// update settings found in the passed map and are encapsulated by the block - virtual bool updateSettings (const CSMSettings::SettingMap &settings) = 0; - - /// update callback function called from update slot - /// used for updating application-level settings in the editor - virtual bool updateBySignal (const QString &name, const QString &value, bool &doEmit) - { return false; } - - protected: - - /// Creates the layout for the block's QGroupBox - QLayout *createLayout (Orientation direction, bool isZeroMargin, QWidget* parent = 0); - - /// Creates widgets that exist as direct children of the block - AbstractWidget *buildWidget (const QString &widgetName, WidgetDef &wDef, - QLayout *layout = 0, bool isConnected = true) const; - - QWidget *getParent() const; - - public slots: - - /// enables / disables block-level widgets based on signals from other widgets - /// used in ToggleBlock - void slotSetEnabled (bool value); - - /// receives updates to applicaion-level settings in the Editor - void slotUpdateSetting (const QString &settingName, const QString &settingValue); - - private slots: - - /// receives updates to a setting in the block pushed from the application level - void slotUpdate (const QString &value); - - signals: - - /// signal to UserSettings instance - void signalUpdateSetting (const QString &propertyName, const QString &propertyValue); - - /// signal to widget for updating widget value - void signalUpdateWidget (const QString & value); - - /// ProxyBlock use only. - /// Name and value correspond to settings for which the block is a proxy. - void signalUpdateProxySetting (const QString &propertyName, const QString &propertyValue); - }; -} -#endif // ABSTRACTBLOCK_HPP diff --git a/apps/opencs/view/settings/abstractpage.cpp b/apps/opencs/view/settings/abstractpage.cpp deleted file mode 100644 index e6c605275..000000000 --- a/apps/opencs/view/settings/abstractpage.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#include "abstractpage.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -CSVSettings::AbstractPage::AbstractPage(QWidget *parent): - QWidget(parent) -{ - QGridLayout *pageLayout = new QGridLayout(this); - setLayout (pageLayout); -} - -CSVSettings::AbstractPage::AbstractPage(const QString &pageName, QWidget *parent): - QWidget(parent) -{ - QWidget::setObjectName (pageName); - - QGridLayout *pageLayout = new QGridLayout(this); - setLayout (pageLayout); -} - -CSVSettings::AbstractPage::~AbstractPage() -{ -} - -CSMSettings::SettingList *CSVSettings::AbstractPage::getSettings() -{ - CSMSettings::SettingList *settings = new CSMSettings::SettingList(); - - foreach (AbstractBlock *block, mAbstractBlocks) - { - CSMSettings::SettingList *groupSettings = block->getSettings(); - settings->append (*groupSettings); - } - - return settings; -} diff --git a/apps/opencs/view/settings/abstractpage.hpp b/apps/opencs/view/settings/abstractpage.hpp deleted file mode 100644 index 77ef4524f..000000000 --- a/apps/opencs/view/settings/abstractpage.hpp +++ /dev/null @@ -1,70 +0,0 @@ -#ifndef ABSTRACTPAGE_HPP -#define ABSTRACTPAGE_HPP - -#include -#include -#include - -#include "abstractblock.hpp" - -class SettingMap; -class SettingList; - -namespace CSVSettings { - - typedef QList AbstractBlockList; - - /// Abstract base class for all setting pages in the dialog - - /// \todo Scripted implementation of settings should eliminate the need - /// \todo derive page classes. - /// \todo AbstractPage should be replaced with a general page construction class. - class AbstractPage: public QWidget - { - - protected: - - AbstractBlockList mAbstractBlocks; - - public: - - AbstractPage(QWidget *parent = 0); - AbstractPage (const QString &pageName, QWidget* parent = 0); - - ~AbstractPage(); - - virtual void setupUi() = 0; - - /// triggers widgiet initialization at the page level. All widgets updated to - /// current setting values - virtual void initializeWidgets (const CSMSettings::SettingMap &settings) = 0; - - /// retrieve the list of settings local to the page. - CSMSettings::SettingList *getSettings(); - - void setObjectName(); - - protected: - - /// Create a block for the page. - /// Block is constructed using passed definition struct - /// Page level-layout is created and assigned - template - AbstractBlock *buildBlock (T *def) - { - S *block = new S (this); - int ret = block->build (def); - - if (ret < 0) - return 0; - - QGroupBox *box = block->getGroupBox(); - QWidget::layout()->addWidget (box); - - return block; - } - - }; -} - -#endif // ABSTRACTPAGE_HPP diff --git a/apps/opencs/view/settings/abstractwidget.cpp b/apps/opencs/view/settings/abstractwidget.cpp deleted file mode 100644 index f268d3b27..000000000 --- a/apps/opencs/view/settings/abstractwidget.cpp +++ /dev/null @@ -1,78 +0,0 @@ -#include "abstractwidget.hpp" - -#include -#include - -void CSVSettings::AbstractWidget::build(QWidget *widget, WidgetDef &def, bool noLabel) -{ - if (!mLayout) - createLayout(def.orientation, true); - - buildLabelAndWidget (widget, def, noLabel); - -} - -void CSVSettings::AbstractWidget::buildLabelAndWidget (QWidget *widget, WidgetDef &def, bool noLabel) -{ - if (def.widgetWidth > -1) - widget->setFixedWidth (def.widgetWidth); - - if (!(def.caption.isEmpty() || noLabel) ) - { - QLabel *label = new QLabel (def.caption, &dynamic_cast( *parent())); - label->setBuddy (widget); - mLayout->addWidget (label); - - if (def.labelWidth > -1) - label->setFixedWidth(def.labelWidth); - } - - mLayout->addWidget (widget); - mLayout->setAlignment (widget, getAlignment (def.widgetAlignment)); -} - -void CSVSettings::AbstractWidget::createLayout - (Orientation direction, bool isZeroMargin) -{ - if (direction == Orient_Vertical) - mLayout = new QVBoxLayout (); - else - mLayout = new QHBoxLayout (); - - if (isZeroMargin) - mLayout->setContentsMargins(0, 0, 0, 0); -} - -QFlags CSVSettings::AbstractWidget::getAlignment (CSVSettings::Alignment flag) -{ - return QFlags(static_cast(flag)); -} - -QLayout *CSVSettings::AbstractWidget::getLayout() -{ - return mLayout; -} - -void CSVSettings::AbstractWidget::slotUpdateWidget (const QString &value) -{ - updateWidget (value); -} - -void CSVSettings::AbstractWidget::slotUpdateItem(const QString &value) -{ - emit signalUpdateItem (value); -} - -void CSVSettings::AbstractWidget::slotUpdateItem(bool value) -{ - if (value) - emit signalUpdateItem (widget()->objectName()); -} - -void CSVSettings::AbstractWidget::slotUpdateItem(int value) -{ - emit signalUpdateItem (QString::number(value)); -} - -void CSVSettings::AbstractWidget::slotUpdateItem (QListWidgetItem* current, QListWidgetItem* previous) -{} diff --git a/apps/opencs/view/settings/abstractwidget.hpp b/apps/opencs/view/settings/abstractwidget.hpp deleted file mode 100644 index 325de2bd2..000000000 --- a/apps/opencs/view/settings/abstractwidget.hpp +++ /dev/null @@ -1,69 +0,0 @@ -#ifndef ABSTRACTWIDGET_HPP -#define ABSTRACTWIDGET_HPP - -#include -#include "support.hpp" - -class QLayout; - -namespace CSVSettings -{ - /// Abstract base class for widgets which are used in user preferences dialog - class AbstractWidget : public QObject - { - Q_OBJECT - - QLayout *mLayout; - - public: - - /// Passed layout is assigned the constructed widget. - /// if no layout is passed, one is created. - explicit AbstractWidget (QLayout *layout = 0, QWidget* parent = 0) - : QObject (parent), mLayout (layout) - {} - - /// retrieve layout for insertion into itemblock - QLayout *getLayout(); - - /// create the derived widget instance - void build (QWidget* widget, WidgetDef &def, bool noLabel = false); - - /// reference to the derived widget instance - virtual QWidget *widget() = 0; - - protected: - - /// Callback called by receiving slot for widget udpates - virtual void updateWidget (const QString &value) = 0; - - /// Converts user-defined enum to Qt equivalents - QFlags getAlignment (Alignment flag); - - private: - - /// Creates layout and assigns label and widget as appropriate - void createLayout (Orientation direction, bool isZeroMargin); - - /// Creates label and widget according to passed definition - void buildLabelAndWidget (QWidget *widget, WidgetDef &def, bool noLabel); - - - signals: - - /// outbound update signal - void signalUpdateItem (const QString &value); - - public slots: - - /// receives inbound updates - void slotUpdateWidget (const QString &value); - - /// Overloads for outbound updates from derived widget signal - void slotUpdateItem (const QString &value); - void slotUpdateItem (bool value); - void slotUpdateItem (int value); - void slotUpdateItem (QListWidgetItem* current, QListWidgetItem* previous); - }; -} -#endif // ABSTRACTWIDGET_HPP diff --git a/apps/opencs/view/settings/blankpage.cpp b/apps/opencs/view/settings/blankpage.cpp deleted file mode 100644 index 837a31bee..000000000 --- a/apps/opencs/view/settings/blankpage.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "blankpage.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef Q_OS_MAC -#include -#endif - -#include "../../model/settings/usersettings.hpp" -#include "groupblock.hpp" -#include "toggleblock.hpp" - -CSVSettings::BlankPage::BlankPage(QWidget *parent): - AbstractPage("Blank", parent) -{ - -} - -CSVSettings::BlankPage::BlankPage(const QString &title, QWidget *parent): - AbstractPage(title, parent) -{ - // Hacks to get the stylesheet look properly -#ifdef Q_OS_MAC - QPlastiqueStyle *style = new QPlastiqueStyle; - //profilesComboBox->setStyle(style); -#endif - - setupUi(); -} - -void CSVSettings::BlankPage::setupUi() -{ - QGroupBox *pageBox = new QGroupBox(this); - layout()->addWidget(pageBox); -} - -void CSVSettings::BlankPage::initializeWidgets (const CSMSettings::SettingMap &settings) -{ - //iterate each item in each blocks in this section - //validate the corresponding setting against the defined valuelist if any. - foreach (AbstractBlock *block, mAbstractBlocks) - block->updateSettings (settings); -} diff --git a/apps/opencs/view/settings/blankpage.hpp b/apps/opencs/view/settings/blankpage.hpp deleted file mode 100644 index 07049fb71..000000000 --- a/apps/opencs/view/settings/blankpage.hpp +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef BLANKPAGE_HPP -#define BLANKPAGE_HPP - -#include "abstractpage.hpp" - -class QGroupBox; - -namespace CSVSettings { - - class UserSettings; - class AbstractBlock; - - /// Derived page with no widgets - /// Reference use only. - class BlankPage : public AbstractPage - { - - public: - - BlankPage (QWidget *parent = 0); - BlankPage (const QString &title, QWidget *parent); - - void setupUi(); - void initializeWidgets (const CSMSettings::SettingMap &settings); - }; -} - -#endif // BLANKPAGE_HPP diff --git a/apps/opencs/view/settings/booleanview.cpp b/apps/opencs/view/settings/booleanview.cpp new file mode 100644 index 000000000..2a3f0cba6 --- /dev/null +++ b/apps/opencs/view/settings/booleanview.cpp @@ -0,0 +1,100 @@ +#include +#include + +#include +#include +#include + +#include + +#include "booleanview.hpp" +#include "../../model/settings/setting.hpp" + +CSVSettings::BooleanView::BooleanView (CSMSettings::Setting *setting, + Page *parent) + : View (setting, parent) +{ + foreach (const QString &value, setting->declaredValues()) + { + QAbstractButton *button = 0; + + switch (setting->type()) + { + case CSMSettings::Type_CheckBox: + button = new QCheckBox (value, this); + break; + + case CSMSettings::Type_RadioButton: + button = new QRadioButton (value, this); + break; + + default: + break; + } + + connect (button, SIGNAL (clicked (bool)), + this, SLOT (slotToggled (bool))); + + button->setObjectName (value); + + addWidget (button); + + mButtons[value] = button; + } +} + +void CSVSettings::BooleanView::slotToggled (bool state) +{ + //test only for true to avoid multiple selection updates with radiobuttons + if (!isMultiValue() && !state) + return; + + QStringList values; + + foreach (QString key, mButtons.keys()) + { + if (mButtons.value(key)->isChecked()) + values.append (key); + } + setSelectedValues (values, false); + + View::updateView(); +} + +void CSVSettings::BooleanView::updateView (bool signalUpdate) const +{ + + QStringList values = selectedValues(); + + foreach (const QString &buttonName, mButtons.keys()) + { + QAbstractButton *button = mButtons[buttonName]; + + //if the value is not found in the list, the widget is checked false + bool buttonValue = values.contains(buttonName); + + //skip if the butotn value will not change + if (button->isChecked() == buttonValue) + continue; + + //disable autoexclusive if it's enabled and we're setting + //the button value to false + bool switchExclusive = (!buttonValue && button->autoExclusive()); + + if (switchExclusive) + button->setAutoExclusive (false); + + button->setChecked (buttonValue); + + if (switchExclusive) + button->setAutoExclusive(true); + } + View::updateView (signalUpdate); +} + +CSVSettings::BooleanView *CSVSettings::BooleanViewFactory::createView + (CSMSettings::Setting *setting, + Page *parent) +{ + return new BooleanView (setting, parent); +} diff --git a/apps/opencs/view/settings/booleanview.hpp b/apps/opencs/view/settings/booleanview.hpp new file mode 100644 index 000000000..55ef0bb08 --- /dev/null +++ b/apps/opencs/view/settings/booleanview.hpp @@ -0,0 +1,44 @@ +#ifndef CSVSETTINGS_BOOLEANVIEW_HPP +#define CSVSETTINGS_BOOLEANVIEW_HPP + +#include +#include + +#include "view.hpp" +#include "../../model/settings/support.hpp" + +class QStringListModel; + +namespace CSVSettings +{ + class BooleanView : public View + { + Q_OBJECT + + QMap mButtons; + + public: + explicit BooleanView (CSMSettings::Setting *setting, + Page *parent); + + protected: + void updateView (bool signalUpdate = true) const; + + private slots: + void slotToggled (bool state); + }; + + class BooleanViewFactory : public QObject, public IViewFactory + { + Q_OBJECT + + public: + explicit BooleanViewFactory (QWidget *parent = 0) + : QObject (parent) + {} + + BooleanView *createView (CSMSettings::Setting *setting, + Page *parent); + }; +} +#endif // CSVSETTINGS_BOOLEANVIEW_HPP diff --git a/apps/opencs/view/settings/customblock.cpp b/apps/opencs/view/settings/customblock.cpp deleted file mode 100644 index bbceafabe..000000000 --- a/apps/opencs/view/settings/customblock.cpp +++ /dev/null @@ -1,121 +0,0 @@ -#include "customblock.hpp" -#include "groupblock.hpp" -#include "itemblock.hpp" -#include "proxyblock.hpp" - -CSVSettings::CustomBlock::CustomBlock (QWidget *parent) : AbstractBlock (parent) -{ -} - -int CSVSettings::CustomBlock::build(GroupBlockDefList &defList, GroupBlockDefList::iterator *it) -{ - int retVal = 0; - - GroupBlockDefList::iterator defaultIt; - GroupBlockDefList::iterator listIt = defList.begin(); - GroupBlockDefList::iterator proxyIt = defaultIt; - - if (it) - listIt = *it; - - ProxyBlock *proxyBlock = new ProxyBlock(getParent()); - - for (; listIt != defList.end(); ++listIt) - { - if (!(*listIt)->isProxy) - retVal = buildGroupBlock (*listIt); - else - { - mGroupList << proxyBlock; - proxyIt = listIt; - } - } - - if (proxyIt != defaultIt) - retVal = buildProxyBlock (*proxyIt, proxyBlock); - - return retVal; -} - -CSVSettings::GroupBox *CSVSettings::CustomBlock::buildGroupBox (Orientation orientation) -{ - GroupBox *box = new GroupBox (false, mBox); - createLayout (orientation, true, box); - - return box; -} - -int CSVSettings::CustomBlock::buildGroupBlock(GroupBlockDef *def) -{ - GroupBlock *block = new GroupBlock (getParent()); - - mGroupList << block; - - connect (block, SIGNAL (signalUpdateSetting(const QString &, const QString &)), - this, SLOT (slotUpdateSetting (const QString &, const QString &))); - - return block->build(def); -} - -int CSVSettings::CustomBlock::buildProxyBlock(GroupBlockDef *def, ProxyBlock *block) -{ - if (def->settingItems.size() != 1) - return -1; - - int retVal = block->build(def); - - if (retVal != 0) - return retVal; - - // The first settingItem is the proxy setting, containing the list of settings bound to it. - foreach (QStringList *list, *(def->settingItems.at(0)->proxyList)) - { - QString proxiedBlockName = list->at(0); - - //iterate each group in the custom block, matching it to each proxied setting - //and connecting it appropriately - foreach (GroupBlock *groupBlock, mGroupList) - { - ItemBlock *proxiedBlock = groupBlock->getItemBlock (proxiedBlockName); - - if (proxiedBlock) - { - block->addSetting(proxiedBlock, list); - - //connect the proxy block's update signal to the custom block's slot - connect (block, SIGNAL (signalUpdateSetting (const QString &, const QString &)), - this, SLOT (slotUpdateSetting (const QString &, const QString &))); - } - } - } - - return 0; -} - -CSMSettings::SettingList *CSVSettings::CustomBlock::getSettings() -{ - CSMSettings::SettingList *settings = new CSMSettings::SettingList(); - - foreach (GroupBlock *block, mGroupList) - { - CSMSettings::SettingList *groupSettings = block->getSettings(); - - if (groupSettings) - settings->append(*groupSettings); - } - - return settings; -} - -bool CSVSettings::CustomBlock::updateSettings (const CSMSettings::SettingMap &settings) -{ - bool success = true; - - foreach (GroupBlock *block, mGroupList) - { - bool success2 = block->updateSettings (settings); - success = success && success2; - } - - return success; -} diff --git a/apps/opencs/view/settings/customblock.hpp b/apps/opencs/view/settings/customblock.hpp deleted file mode 100644 index 54c50f395..000000000 --- a/apps/opencs/view/settings/customblock.hpp +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef CUSTOMBLOCK_HPP -#define CUSTOMBLOCK_HPP - -#include "abstractblock.hpp" - -namespace CSVSettings -{ - - class ProxyBlock; - - /// Base class for customized user preference setting blocks - /// Special block classes should be derived from CustomBlock - class CustomBlock : public AbstractBlock - { - - protected: - - GroupBlockList mGroupList; - - public: - - explicit CustomBlock (QWidget *parent = 0); - - /// Update settings local to the block - bool updateSettings (const CSMSettings::SettingMap &settings); - - /// Retrieve settings local to the block - CSMSettings::SettingList *getSettings(); - - /// construct the block using the passed definition - int build (GroupBlockDefList &defList, GroupBlockDefList::Iterator *it = 0); - - protected: - - /// construct the block groupbox - GroupBox *buildGroupBox (Orientation orientation); - - private: - - /// Construction function for creating a standard GroupBlock child - int buildGroupBlock(GroupBlockDef *def); - - /// Construction function for creating a standard ProxyBlock child - int buildProxyBlock(GroupBlockDef *def, ProxyBlock *block); - }; -} -#endif // CUSTOMBLOCK_HPP diff --git a/apps/opencs/view/settings/datadisplayformatpage.cpp b/apps/opencs/view/settings/datadisplayformatpage.cpp deleted file mode 100755 index 332b68f5c..000000000 --- a/apps/opencs/view/settings/datadisplayformatpage.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#include "datadisplayformatpage.hpp" -#include "groupblock.hpp" -#include "../../model/settings/usersettings.hpp" - -CSVSettings::DataDisplayFormatPage::DataDisplayFormatPage(QWidget* parent) : - AbstractPage("Display Format", parent) -{ - setupUi(); -} - -CSVSettings::GroupBlockDef *CSVSettings::DataDisplayFormatPage::setupDataDisplay( const QString &title) -{ - GroupBlockDef *statusBlock = new GroupBlockDef(QString(title)); - - SettingsItemDef *statusItem = new SettingsItemDef (statusBlock->title, "Icon Only"); - *(statusItem->valueList) << QString("Icon and Text") << QString("Icon Only") << QString("Text Only"); - - WidgetDef statusWidget (Widget_RadioButton); - statusWidget.valueList = statusItem->valueList; - - statusItem->widget = statusWidget; - - statusBlock->settingItems << statusItem; - - statusBlock->isZeroMargin = false; - - return statusBlock; -} - - -void CSVSettings::DataDisplayFormatPage::setupUi() -{ - - mAbstractBlocks << buildBlock (setupDataDisplay ("Record Status Display")); - mAbstractBlocks << buildBlock (setupDataDisplay ("Referenceable ID Type Display")); - - foreach (AbstractBlock *block, mAbstractBlocks) - { - connect (block, SIGNAL (signalUpdateSetting (const QString &, const QString &)), - this, SIGNAL (signalUpdateEditorSetting (const QString &, const QString &)) ); - } - - connect ( this, - SIGNAL ( signalUpdateEditorSetting (const QString &, const QString &)), - &(CSMSettings::UserSettings::instance()), - SIGNAL ( signalUpdateEditorSetting (const QString &, const QString &))); - -} - -void CSVSettings::DataDisplayFormatPage::initializeWidgets (const CSMSettings::SettingMap &settings) -{ - //iterate each item in each blocks in this section - //validate the corresponding setting against the defined valuelist if any. - for (AbstractBlockList::Iterator it_block = mAbstractBlocks.begin(); - it_block != mAbstractBlocks.end(); ++it_block) - (*it_block)->updateSettings (settings); -} diff --git a/apps/opencs/view/settings/datadisplayformatpage.hpp b/apps/opencs/view/settings/datadisplayformatpage.hpp deleted file mode 100755 index b785bbd23..000000000 --- a/apps/opencs/view/settings/datadisplayformatpage.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef EDITORPAGE_HPP -#define EDITORPAGE_HPP - -#include "support.hpp" -#include "abstractpage.hpp" - -namespace CSVSettings -{ - class DataDisplayFormatPage : public AbstractPage - { - Q_OBJECT - - public: - explicit DataDisplayFormatPage(QWidget *parent = 0); - - void initializeWidgets (const CSMSettings::SettingMap &settings); - void setupUi(); - - private: - - /// User preference view of the record status delegate's icon / text setting - GroupBlockDef *setupDataDisplay(const QString &); - - signals: - - /// Signals up for changes to editor application-level settings - void signalUpdateEditorSetting (const QString &settingName, const QString &settingValue); - - public slots: - }; -} - -#endif // EDITORPAGE_HPP diff --git a/apps/opencs/view/settings/dialog.cpp b/apps/opencs/view/settings/dialog.cpp new file mode 100644 index 000000000..56bc1fdfe --- /dev/null +++ b/apps/opencs/view/settings/dialog.cpp @@ -0,0 +1,121 @@ +#include "dialog.hpp" + +#include +#include +#include +#include +#include + +#include "../../model/settings/usersettings.hpp" + +#include "page.hpp" + +#include + +#include + +#include +#include +#include + +#include +#include + +CSVSettings::Dialog::Dialog(QMainWindow *parent) + : mStackedWidget (0), mDebugMode (false), SettingWindow (parent) +{ + setWindowTitle(QString::fromUtf8 ("User Settings")); + + setupDialog(); + + connect (mPageListWidget, + SIGNAL (currentItemChanged(QListWidgetItem*, QListWidgetItem*)), + this, + SLOT (slotChangePage (QListWidgetItem*, QListWidgetItem*))); +} + +void CSVSettings::Dialog::slotChangePage + (QListWidgetItem *cur, QListWidgetItem *prev) +{ + mStackedWidget->changePage + (mPageListWidget->row (cur), mPageListWidget->row (prev)); + + layout()->activate(); + setFixedSize(minimumSizeHint()); +} + +void CSVSettings::Dialog::setupDialog() +{ + //create central widget with it's layout and immediate children + QWidget *centralWidget = new QGroupBox (this); + + centralWidget->setLayout (new QHBoxLayout()); + centralWidget->setSizePolicy (QSizePolicy::Expanding, QSizePolicy::Preferred); + setCentralWidget (centralWidget); + setDockOptions (QMainWindow::AllowNestedDocks); + + buildPageListWidget (centralWidget); + buildStackedWidget (centralWidget); +} + +void CSVSettings::Dialog::buildPages() +{ + SettingWindow::createPages (); + + QFontMetrics fm (QApplication::font()); + + foreach (Page *page, SettingWindow::pages()) + { + QString pageName = page->objectName(); + + int textWidth = fm.width(pageName); + + new QListWidgetItem (pageName, mPageListWidget); + mPageListWidget->setFixedWidth (textWidth + 50); + + mStackedWidget->addWidget (&dynamic_cast(*(page))); + } + + resize (mStackedWidget->sizeHint()); +} + +void CSVSettings::Dialog::buildPageListWidget (QWidget *centralWidget) +{ + mPageListWidget = new QListWidget (centralWidget); + mPageListWidget->setMinimumWidth(50); + mPageListWidget->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Expanding); + + mPageListWidget->setSelectionBehavior (QAbstractItemView::SelectItems); + + centralWidget->layout()->addWidget(mPageListWidget); +} + +void CSVSettings::Dialog::buildStackedWidget (QWidget *centralWidget) +{ + mStackedWidget = new ResizeableStackedWidget (centralWidget); + + centralWidget->layout()->addWidget (mStackedWidget); +} + +void CSVSettings::Dialog::closeEvent (QCloseEvent *event) +{ + //SettingWindow::closeEvent() must be called first to ensure + //model is updated + SettingWindow::closeEvent (event); + + saveSettings(); +} + +void CSVSettings::Dialog::show() +{ + if (pages().isEmpty()) + { + buildPages(); + setViewValues(); + } + + QPoint screenCenter = QApplication::desktop()->screenGeometry().center(); + + move (screenCenter - geometry().center()); + QWidget::show(); +} diff --git a/apps/opencs/view/settings/dialog.hpp b/apps/opencs/view/settings/dialog.hpp new file mode 100644 index 000000000..b0e12c461 --- /dev/null +++ b/apps/opencs/view/settings/dialog.hpp @@ -0,0 +1,54 @@ +#ifndef CSVSETTINGS_DIALOG_H +#define CSVSETTINGS_DIALOG_H + +#include "settingwindow.hpp" +#include "resizeablestackedwidget.hpp" +#include + +class QStackedWidget; +class QListWidget; +class QListWidgetItem; + +namespace CSVSettings { + + class Page; + + class Dialog : public SettingWindow + { + Q_OBJECT + + QListWidget *mPageListWidget; + ResizeableStackedWidget *mStackedWidget; + bool mDebugMode; + + public: + + explicit Dialog (QMainWindow *parent = 0); + + ///Enables setting debug mode. When the dialog opens, a page is created + ///which displays the SettingModel's contents in a Tree view. + void enableDebugMode (bool state, QStandardItemModel *model = 0); + + protected: + + /// Settings are written on close + void closeEvent (QCloseEvent *event); + + void setupDialog(); + + private: + + void buildPages(); + void buildPageListWidget (QWidget *centralWidget); + void buildStackedWidget (QWidget *centralWidget); + + public slots: + + void show(); + + private slots: + + void slotChangePage (QListWidgetItem *, QListWidgetItem *); + }; +} +#endif // CSVSETTINGS_DIALOG_H diff --git a/apps/opencs/view/settings/editorpage.cpp b/apps/opencs/view/settings/editorpage.cpp deleted file mode 100644 index 153ac1551..000000000 --- a/apps/opencs/view/settings/editorpage.cpp +++ /dev/null @@ -1,53 +0,0 @@ -#include "editorpage.hpp" -#include "groupblock.hpp" -#include "../../model/settings/usersettings.hpp" - -CSVSettings::EditorPage::EditorPage(QWidget* parent) : - AbstractPage("Display Format", parent) -{ - setupUi(); -} - -CSVSettings::GroupBlockDef *CSVSettings::EditorPage::setupRecordStatusDisplay() -{ - GroupBlockDef *statusBlock = new GroupBlockDef(QString("Record Status Display")); - - SettingsItemDef *statusItem = new SettingsItemDef (statusBlock->title, "Icon and Text"); - *(statusItem->valueList) << QString("Icon and Text") << QString("Icon Only") << QString("Text Only"); - - WidgetDef statusWidget (Widget_RadioButton); - statusWidget.valueList = statusItem->valueList; - - statusItem->widget = statusWidget; - - statusBlock->settingItems << statusItem; - - return statusBlock; -} - -void CSVSettings::EditorPage::setupUi() -{ - - mAbstractBlocks << buildBlock(setupRecordStatusDisplay()); - - foreach (AbstractBlock *block, mAbstractBlocks) - { - connect (block, SIGNAL (signalUpdateSetting (const QString &, const QString &)), - this, SIGNAL (signalUpdateEditorSetting (const QString &, const QString &)) ); - } - - connect ( this, - SIGNAL ( signalUpdateEditorSetting (const QString &, const QString &)), - &(CSMSettings::UserSettings::instance()), - SIGNAL ( signalUpdateEditorSetting (const QString &, const QString &))); - -} - -void CSVSettings::EditorPage::initializeWidgets (const CSMSettings::SettingMap &settings) -{ - //iterate each item in each blocks in this section - //validate the corresponding setting against the defined valuelist if any. - for (AbstractBlockList::Iterator it_block = mAbstractBlocks.begin(); - it_block != mAbstractBlocks.end(); ++it_block) - (*it_block)->updateSettings (settings); -} diff --git a/apps/opencs/view/settings/editorpage.hpp b/apps/opencs/view/settings/editorpage.hpp deleted file mode 100644 index 85215edab..000000000 --- a/apps/opencs/view/settings/editorpage.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef EDITORPAGE_HPP -#define EDITORPAGE_HPP - -#include "support.hpp" -#include "abstractpage.hpp" - -namespace CSVSettings -{ - class EditorPage : public AbstractPage - { - Q_OBJECT - - public: - explicit EditorPage(QWidget *parent = 0); - - void initializeWidgets (const CSMSettings::SettingMap &settings); - void setupUi(); - - private: - - /// User preference view of the record status delegate's icon / text setting - GroupBlockDef *setupRecordStatusDisplay(); - - signals: - - /// Signals up for changes to editor application-level settings - void signalUpdateEditorSetting (const QString &settingName, const QString &settingValue); - - public slots: - }; -} - -#endif // EDITORPAGE_HPP diff --git a/apps/opencs/view/settings/frame.cpp b/apps/opencs/view/settings/frame.cpp new file mode 100644 index 000000000..019024776 --- /dev/null +++ b/apps/opencs/view/settings/frame.cpp @@ -0,0 +1,105 @@ +#include "frame.hpp" + +#include + +const QString CSVSettings::Frame::sInvisibleBoxStyle = + QString::fromUtf8("Frame { border:2px; padding 2px; margin: 2px;}"); + +CSVSettings::Frame::Frame (bool isVisible, const QString &title, + QWidget *parent) + : mIsHorizontal (true), mLayout (new SettingLayout()), + QGroupBox (title, parent) +{ + setFlat (true); + mVisibleBoxStyle = styleSheet(); + + if (!isVisible) + setStyleSheet (sInvisibleBoxStyle); + + setLayout (mLayout); +} + +void CSVSettings::Frame::hideWidgets() +{ + for (int i = 0; i < children().size(); i++) + { + QObject *obj = children().at(i); + + Frame *widgFrame = dynamic_cast (obj); + + if (widgFrame) + { + widgFrame->hideWidgets(); + continue; + } + + QWidget *widg = static_cast (obj); + if (widg->property("sizePolicy").isValid()) + widg->setSizePolicy (QSizePolicy::Ignored, QSizePolicy::Ignored); + } + + layout()->activate(); + setFixedSize(minimumSizeHint()); + +} + +void CSVSettings::Frame::showWidgets() +{ + for (int i = 0; i < children().size(); i++) + { + QObject *obj = children().at(i); + + Frame *widgFrame = dynamic_cast (obj); + + if (widgFrame) + { + widgFrame->showWidgets(); + continue; + } + + QWidget *widg = static_cast (obj); + + if (widg->property("sizePolicy").isValid()) + { + widg->setSizePolicy + (QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + } + } + layout()->activate(); + setFixedSize(minimumSizeHint()); +} + +void CSVSettings::Frame::addWidget (QWidget *widget, int row, int column, + int rowSpan, int columnSpan) +{ + if (row == -1) + row = getNextRow(); + + if (column == -1) + column = getNextColumn(); + + mLayout->addWidget (widget, row, column, rowSpan, columnSpan); + //, Qt::AlignLeft | Qt::AlignTop); + + widget->setSizePolicy (QSizePolicy::Ignored, QSizePolicy::Ignored); +} + +int CSVSettings::Frame::getNextRow () const +{ + int row = mLayout->rowCount(); + + if (mIsHorizontal && row > 0) + row--; + + return row; +} + +int CSVSettings::Frame::getNextColumn () const +{ + int column = 0; + + if (mIsHorizontal) + column = mLayout->columnCount(); + + return column; +} diff --git a/apps/opencs/view/settings/frame.hpp b/apps/opencs/view/settings/frame.hpp new file mode 100644 index 000000000..bbb92f34f --- /dev/null +++ b/apps/opencs/view/settings/frame.hpp @@ -0,0 +1,60 @@ +#ifndef CSVSETTINGS_FRAME_HPP +#define CSVSETTINGS_FRAME_HPP + +#include +#include +#include +#include "../../model/settings/support.hpp" + +namespace CSVSettings +{ + class SettingLayout : public QGridLayout + { + public: + explicit SettingLayout (QWidget *parent = 0) + : QGridLayout (parent) + { + setContentsMargins(0,0,0,0); + setAlignment(Qt::AlignLeft | Qt::AlignTop); + } + }; + + /// Custom implementation of QGroupBox to act as a base for view classes + class Frame : public QGroupBox + { + static const QString sInvisibleBoxStyle; + + QString mVisibleBoxStyle; + + bool mIsHorizontal; + + SettingLayout *mLayout; + + public: + explicit Frame (bool isVisible, const QString &title = "", + QWidget *parent = 0); + + ///Adds a widget to the grid layout, setting the position + ///relative to the last added widgets, or absolutely for positive + ///row / column values + void addWidget (QWidget *widget, int row = -1, int column = -1, + int rowSpan = 1, int columnSpan = 1); + + ///Force the grid to lay out in horizontal or vertical alignments + void setHLayout() { mIsHorizontal = true; } + void setVLayout() { mIsHorizontal = false; } + + ///show / hide widgets (when stacked widget page changes) + void showWidgets(); + void hideWidgets(); + + private: + + ///functions which return the index for the next layout row / column + int getNextColumn() const; + int getNextRow() const; + + }; +} + +#endif // CSVSETTINGS_FRAME_HPP diff --git a/apps/opencs/view/settings/groupblock.cpp b/apps/opencs/view/settings/groupblock.cpp deleted file mode 100644 index e31e526c0..000000000 --- a/apps/opencs/view/settings/groupblock.cpp +++ /dev/null @@ -1,108 +0,0 @@ -#include "groupblock.hpp" -#include "itemblock.hpp" - -CSVSettings::GroupBlock::GroupBlock (QWidget* parent) - : AbstractBlock (parent) -{} - -CSVSettings::GroupBlock::GroupBlock (bool isVisible, QWidget *parent) - : AbstractBlock (isVisible, parent) -{} - -int CSVSettings::GroupBlock::build (GroupBlockDef *def) -{ - - if (def->settingItems.size() == 0) - return -1; - - int retVal = 0; - - setVisible (def->isVisible); - - mBox->setLayout(createLayout (def->widgetOrientation, def->isZeroMargin)); - - setObjectName (def->title); - mBox->setTitle (def->title); - - foreach (SettingsItemDef *itemDef, def->settingItems) - { - ItemBlock *block = new ItemBlock (mBox); - - if (block->build (*itemDef) < 0) - { - retVal = -2; - break; - } - - mItemBlockList << block; - mBox->layout()->addWidget (block->getGroupBox()); - - connect (block, SIGNAL (signalUpdateSetting (const QString &, const QString &)), - this, SLOT (slotUpdateSetting (const QString &, const QString &) )); - } - - return retVal; -} - -CSMSettings::SettingList *CSVSettings::GroupBlock::getSettings() -{ - CSMSettings::SettingList *settings = 0; - - foreach (ItemBlock *block, mItemBlockList) - { - if (!settings) - settings = new CSMSettings::SettingList(); - - settings->append(*(block->getSettings ())); - } - - return settings; -} - -CSVSettings::ItemBlock *CSVSettings::GroupBlock::getItemBlock (const QString &name, ItemBlockList *blockList) -{ - ItemBlock *retBlock = 0; - - if (!blockList) - blockList = &mItemBlockList; - - foreach (ItemBlock *block, *blockList) - { - if (block->objectName() == name) - { - retBlock = block; - break; - } - } - - return retBlock; -} - -CSVSettings::ItemBlock *CSVSettings::GroupBlock::getItemBlock (int index) -{ - ItemBlock *retBlock = 0; - - if (mItemBlockList.size() > index) - retBlock = mItemBlockList.at(index); - - return retBlock; -} - -bool CSVSettings::GroupBlock::updateSettings (const CSMSettings::SettingMap &settings) -{ - bool success = true; - - //update all non-proxy settings - foreach (ItemBlock *block, mItemBlockList) - { - CSMSettings::SettingContainer *setting = settings[block->objectName()]; - - if (setting) - { - bool success2 = block->update (setting->getValue()); - success = success && success2; - } - } - - return success; -} diff --git a/apps/opencs/view/settings/groupblock.hpp b/apps/opencs/view/settings/groupblock.hpp deleted file mode 100644 index 5c0754193..000000000 --- a/apps/opencs/view/settings/groupblock.hpp +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef GROUPBLOCK_HPP -#define GROUPBLOCK_HPP - -#include -#include "abstractblock.hpp" - -namespace CSVSettings -{ - class ItemBlock; - - /// Base class for group blocks. - /// Derived block classes should use CustomBlock - class GroupBlock : public AbstractBlock - { - ItemBlockList mItemBlockList; - - public: - GroupBlock (QWidget* parent = 0); - GroupBlock (bool isVisible, QWidget *parent = 0); - - /// build the gorup block based on passed definition - int build (GroupBlockDef *def); - - /// update settings local to the group block - bool updateSettings (const CSMSettings::SettingMap &settings); - - /// retrieve setting list local to the group block - CSMSettings::SettingList *getSettings(); - - /// retrieve item block by name from the passed list or local list - ItemBlock *getItemBlock (const QString &name, ItemBlockList *blockList = 0); - - /// retrieve the item block by index from the local list - ItemBlock *getItemBlock (int index); - - protected: - - /// create block layout based on passed definition - int buildLayout (GroupBlockDef &def); - - }; -} -#endif // GROUPBLOCK_HPP diff --git a/apps/opencs/view/settings/groupbox.cpp b/apps/opencs/view/settings/groupbox.cpp deleted file mode 100644 index da2cc2571..000000000 --- a/apps/opencs/view/settings/groupbox.cpp +++ /dev/null @@ -1,56 +0,0 @@ -#include "groupbox.hpp" - -const QString CSVSettings::GroupBox::INVISIBLE_BOX_STYLE = - QString::fromUtf8("QGroupBox { border: 0px; padding 0px; margin: 0px;}"); - -CSVSettings::GroupBox::GroupBox(QWidget *parent) : - QGroupBox (parent) -{ - initBox(); -} - -CSVSettings::GroupBox::GroupBox (bool isVisible, QWidget *parent) : - QGroupBox (parent) -{ - initBox(isVisible); -} - -void CSVSettings::GroupBox::initBox(bool isVisible) -{ - setFlat (true); - VISIBLE_BOX_STYLE = styleSheet(); - - if (!isVisible) - setStyleSheet (INVISIBLE_BOX_STYLE); -} - -bool CSVSettings::GroupBox::borderVisibile() const -{ - return (styleSheet() != INVISIBLE_BOX_STYLE); -} - -void CSVSettings::GroupBox::setTitle (const QString &title) -{ - if (borderVisibile() ) - { - QGroupBox::setTitle (title); - setMinimumWidth(); - } -} - -void CSVSettings::GroupBox::setBorderVisibility (bool value) -{ - if (value) - setStyleSheet(VISIBLE_BOX_STYLE); - else - setStyleSheet(INVISIBLE_BOX_STYLE); -} - -void CSVSettings::GroupBox::setMinimumWidth() -{ - //set minimum width to accommodate title, if needed - //1.5 multiplier to account for bold title. - QFontMetrics fm (font()); - int minWidth = fm.width(title()); - QGroupBox::setMinimumWidth (minWidth * 1.5); -} diff --git a/apps/opencs/view/settings/groupbox.hpp b/apps/opencs/view/settings/groupbox.hpp deleted file mode 100644 index 9d3a01936..000000000 --- a/apps/opencs/view/settings/groupbox.hpp +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef GROUPBOX_HPP -#define GROUPBOX_HPP - -#include - -namespace CSVSettings -{ - /// Custom implementation of QGroupBox to be used with block classes - class GroupBox : public QGroupBox - { - static const QString INVISIBLE_BOX_STYLE; - QString VISIBLE_BOX_STYLE; //not a const... - - public: - explicit GroupBox (QWidget *parent = 0); - explicit GroupBox (bool isVisible, QWidget *parent = 0); - - void setTitle (const QString &title); - void setBorderVisibility (bool value); - bool borderVisibile() const; - - private: - void setMinimumWidth(); - void initBox(bool isVisible = true); - }; -} - -#endif // GROUPBOX_HPP diff --git a/apps/opencs/view/settings/itemblock.cpp b/apps/opencs/view/settings/itemblock.cpp deleted file mode 100644 index 9cb0ae1a1..000000000 --- a/apps/opencs/view/settings/itemblock.cpp +++ /dev/null @@ -1,115 +0,0 @@ -#include "itemblock.hpp" - -#include - -CSVSettings::ItemBlock::ItemBlock (QWidget* parent) - : mSetting (0), AbstractBlock (false, parent) -{ -} - -int CSVSettings::ItemBlock::build(SettingsItemDef &iDef) -{ - buildItemBlock (iDef); - buildItemBlockWidgets (iDef); - - return 0; -} - -void CSVSettings::ItemBlock::buildItemBlockWidgets (SettingsItemDef &iDef) -{ - WidgetDef wDef = iDef.widget; - QLayout *blockLayout = 0; - QString defaultValue = iDef.defaultValue; - - switch (wDef.type) - { - - case Widget_CheckBox: - case Widget_RadioButton: - - foreach (QString item, *(iDef.valueList)) - { - wDef.caption = item; - wDef.isDefault = (item == defaultValue); - - blockLayout = buildWidget (item, wDef, blockLayout)->getLayout(); - } - - break; - - case Widget_ComboBox: - case Widget_ListBox: - - //assign the item's value list to the widget's value list. - //pass through to default to finish widget construction. - if (!wDef.valueList) - wDef.valueList = iDef.valueList; - - default: - //only one instance of this non-list widget type. - //Set it's value to the default value for the item and build the widget. - - if (wDef.value.isEmpty()) - wDef.value = iDef.defaultValue; - - buildWidget (iDef.name, wDef); - } -} - -void CSVSettings::ItemBlock::buildItemBlock (SettingsItemDef &iDef) -{ - QString defaultValue = iDef.defaultValue; - - setObjectName(iDef.name); - - mSetting = new CSMSettings::SettingsItem (objectName(), - iDef.hasMultipleValues, iDef.defaultValue, - parent()); - - if (iDef.valueList) - mSetting->setValueList(iDef.valueList); - - if (!iDef.minMax.isEmpty()) - mSetting->setValuePair(iDef.minMax); -} - - -bool CSVSettings::ItemBlock::update (const QString &value) -{ - bool success = updateItem (value); - - if (success) - signalUpdateWidget (value); - - return success; -} - - -bool CSVSettings::ItemBlock::updateItem (const QString &value) -{ - return mSetting->updateItem(value); -} - - -bool CSVSettings::ItemBlock::updateBySignal(const QString &name, const QString &value, bool &doEmit) -{ - bool success = (mSetting->getValue() != value); - - if (success) - success = updateItem(value); - - return success; -} - -CSMSettings::SettingList *CSVSettings::ItemBlock::getSettings () -{ - CSMSettings::SettingList *list = new CSMSettings::SettingList(); - list->push_back(mSetting); - - return list; -} - -QString CSVSettings::ItemBlock::getValue() const -{ - return mSetting->getValue(); -} diff --git a/apps/opencs/view/settings/itemblock.hpp b/apps/opencs/view/settings/itemblock.hpp deleted file mode 100644 index 2d1d45d41..000000000 --- a/apps/opencs/view/settings/itemblock.hpp +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef ITEMBLOCK_HPP -#define ITEMBLOCK_HPP - -#include "abstractblock.hpp" - -namespace CSVSettings -{ - - class ItemBlock : public AbstractBlock - { - CSMSettings::SettingsItem *mSetting; - WidgetList mWidgetList; - - public: - - ItemBlock (QWidget* parent = 0); - - /// pure virtual function not implemented - bool updateSettings (const CSMSettings::SettingMap &settings) { return false; } - - CSMSettings::SettingList *getSettings (); - - QString getValue () const; - - /// item blocks encapsulate only one setting - int getSettingCount(); - - /// update setting value and corresponding widget - bool update (const QString &value); - - /// virtual construction function - int build(SettingsItemDef &iDef); - - private: - - /// custom construction function - void buildItemBlock (SettingsItemDef& iDef); - void buildItemBlockWidgets (SettingsItemDef& iDef); - - /// update the setting value - bool updateItem (const QString &); - - /// callback function triggered when update to application level is signalled - bool updateBySignal (const QString &name, const QString &value, bool &doEmit); - }; -} - -#endif // ITEMBLOCK_HPP diff --git a/apps/opencs/view/settings/listview.cpp b/apps/opencs/view/settings/listview.cpp new file mode 100644 index 000000000..36cdbb0ae --- /dev/null +++ b/apps/opencs/view/settings/listview.cpp @@ -0,0 +1,106 @@ +#include "listview.hpp" +#include "../../model/settings/setting.hpp" + +#include +#include +#include + +CSVSettings::ListView::ListView(CSMSettings::Setting *setting, + Page *parent) + : mComboBox (0), mAbstractItemView (0), View(setting, parent) +{ + QWidget *widget = + buildWidget(setting->isMultiLine(), setting->widgetWidth()); + + addWidget (widget, setting->viewRow(), setting->viewColumn()); + + if (mComboBox) + buildComboBoxModel(); + + else if (mAbstractItemView) + buildAbstractItemViewModel(); +} + +void CSVSettings::ListView::buildComboBoxModel() +{ + mComboBox->setModel (dataModel()); + mComboBox->setModelColumn (0); + mComboBox->view()->setSelectionModel (selectionModel()); + + int curIdx = -1; + + if (!selectionModel()->selection().isEmpty()) + curIdx = selectionModel()->selectedIndexes().at(0).row(); + + mComboBox->setCurrentIndex (curIdx); + + connect (mComboBox, SIGNAL(currentIndexChanged(int)), + this, SLOT(emitItemViewUpdate(int))); +} + +void CSVSettings::ListView::buildAbstractItemViewModel() +{ + mAbstractItemView->setModel (dataModel()); + mAbstractItemView->setSelectionModel (selectionModel()); + + //connection needs to go here for list view update to signal to + //the outside +} + +void CSVSettings::ListView::emitItemViewUpdate (int idx) +{ + updateView(); +} + +QWidget *CSVSettings::ListView::buildWidget(bool isMultiLine, int width) +{ + QWidget *widget = 0; + + if (isMultiLine) + { + mAbstractItemView = new QListView (this); + widget = mAbstractItemView; + + if (width > 0) + widget->setFixedWidth (widgetWidth (width)); + } + else + { + mComboBox = new QComboBox (this); + widget = mComboBox; + + if (width > 0) + mComboBox->setMinimumContentsLength (width); + } + + return widget; +} + +void CSVSettings::ListView::showEvent ( QShowEvent * event ) +{ + View::showEvent (event); +} + +void CSVSettings::ListView::updateView (bool signalUpdate) const +{ + QStringList values = selectedValues(); + + if (mComboBox) + { + int idx = -1; + + if (values.size() > 0) + idx = (mComboBox->findText(values.at(0))); + + mComboBox->setCurrentIndex (idx); + } + + View::updateView (signalUpdate); +} + +CSVSettings::ListView *CSVSettings::ListViewFactory::createView + (CSMSettings::Setting *setting, + Page *parent) +{ + return new ListView(setting, parent); +} diff --git a/apps/opencs/view/settings/listview.hpp b/apps/opencs/view/settings/listview.hpp new file mode 100644 index 000000000..c2860d769 --- /dev/null +++ b/apps/opencs/view/settings/listview.hpp @@ -0,0 +1,63 @@ +#ifndef CSVSETTINGS_LISTVIEW_HPP +#define CSVSETTINGS_LISTVIEW_HPP + +#include "view.hpp" + + +class QStringListModel; +class QComboBox; +class QAbstractItemView; + +namespace CSVSettings +{ + class ListView : public View + { + Q_OBJECT + + QAbstractItemView *mAbstractItemView; + QComboBox *mComboBox; + + public: + explicit ListView (CSMSettings::Setting *setting, + Page *parent); + + protected: + + void updateView (bool signalUpdate = true) const; + void showEvent ( QShowEvent * event ); + + ///Receives signal from widget and signals viwUpdated() + void slotTextEdited (QString value); + + private: + + ///Helper function to construct a model for an AbstractItemView + void buildAbstractItemViewModel(); + + ///Helper function to construct a model for a combobox + void buildComboBoxModel(); + + ///Helper function to build the view widget + QWidget *buildWidget (bool isMultiLine, int width); + + private slots: + + ///Receives updates from single-select widgets (like combobox) and + ///signals viewUpdated with the selected values. + void emitItemViewUpdate (int idx); + }; + + class ListViewFactory : public QObject, public IViewFactory + { + Q_OBJECT + + public: + explicit ListViewFactory (QWidget *parent = 0) + : QObject (parent) + {} + + ListView *createView (CSMSettings::Setting *setting, + Page *parent); + }; +} +#endif // CSVSETTINGS_LISTVIEW_HPP diff --git a/apps/opencs/view/settings/page.cpp b/apps/opencs/view/settings/page.cpp new file mode 100644 index 000000000..afd4bff5e --- /dev/null +++ b/apps/opencs/view/settings/page.cpp @@ -0,0 +1,92 @@ +#include "page.hpp" +#include "view.hpp" +#include "booleanview.hpp" +#include "textview.hpp" +#include "listview.hpp" +#include "rangeview.hpp" + +#include "../../model/settings/usersettings.hpp" +#include "../../model/settings/connector.hpp" +#include "../../model/settings/support.hpp" + +#include "settingwindow.hpp" + +QMap + CSVSettings::Page::mViewFactories; + +CSVSettings::Page::Page(const QString &pageName, + QList settingList, + SettingWindow *parent) : + mParent(parent), mIsEditorPage (false), Frame(false, "", parent) +{ + setObjectName (pageName); + + if (mViewFactories.size() == 0) + buildFactories(); + + setVLayout(); + setupViews (settingList); +} + +void CSVSettings::Page::setupViews + (QList &settingList) +{ + foreach (CSMSettings::Setting *setting, settingList) + addView (setting); +} + +void CSVSettings::Page::addView (CSMSettings::Setting *setting) +{ + if (setting->viewType() == ViewType_Undefined) + return; + + View *view = mViewFactories[setting->viewType()]->createView(setting, this); + + if (!view) + return; + + mViews.append (view); + + addWidget (view, setting->viewRow(), setting->viewColumn(), + setting->rowSpan(), setting->columnSpan() ); + + //if this page is an editor page, connect each of it's views up to the + //UserSettings singleton for signaling back to OpenCS + if (setting->isEditorSetting()) { + connect (view, SIGNAL (viewUpdated(const QString&, const QStringList&)), + &CSMSettings::UserSettings::instance(), + SLOT (updateUserSetting (const QString &, const QStringList &))); + } +} + +CSVSettings::View *CSVSettings::Page::findView (const QString &page, + const QString &setting) const +{ + + //if this is not the page we're looking for, + //appeal to the parent setting window to find the appropriate view + if (page != objectName()) + return mParent->findView (page, setting); + + //otherwise, return the matching view + for (int i = 0; i < mViews.size(); i++) + { + View *view = mViews.at(i); + + if (view->parentPage()->objectName() != page) + continue; + + if (view->objectName() == setting) + return view; + } + + return 0; +} + +void CSVSettings::Page::buildFactories() +{ + mViewFactories[ViewType_Boolean] = new BooleanViewFactory (this); + mViewFactories[ViewType_Text] = new TextViewFactory (this); + mViewFactories[ViewType_List] = new ListViewFactory (this); + mViewFactories[ViewType_Range] = new RangeViewFactory (this); +} diff --git a/apps/opencs/view/settings/page.hpp b/apps/opencs/view/settings/page.hpp new file mode 100644 index 000000000..877d4bef8 --- /dev/null +++ b/apps/opencs/view/settings/page.hpp @@ -0,0 +1,54 @@ +#ifndef CSVSETTINGS_PAGE_HPP +#define CSVSETTINGS_PAGE_HPP + +#include +#include +#include + +#include "frame.hpp" + +#include "../../model/settings/support.hpp" + +namespace CSMSettings { class Setting; } + +namespace CSVSettings +{ + class View; + class IViewFactory; + class SettingWindow; + + class Page : public Frame + { + Q_OBJECT + + QList mViews; + SettingWindow *mParent; + static QMap mViewFactories; + bool mIsEditorPage; + + public: + explicit Page(const QString &pageName, + QList settingList, + SettingWindow *parent); + + ///Creates a new view based on the passed setting and adds it to + ///the page. + void addView (CSMSettings::Setting *setting); + + ///Iterates the views created for this page based on the passed setting + ///and returns it. + View *findView (const QString &page, const QString &setting) const; + + ///returns the list of views associated with the page + const QList &views () const { return mViews; } + + private: + + ///Creates views based on the passed setting list + void setupViews (QList &settingList); + + ///Creates factory objects for view construction + void buildFactories(); + }; +} +#endif // CSVSETTINGS_PAGE_HPP diff --git a/apps/opencs/view/settings/proxyblock.cpp b/apps/opencs/view/settings/proxyblock.cpp deleted file mode 100644 index 81cc54fca..000000000 --- a/apps/opencs/view/settings/proxyblock.cpp +++ /dev/null @@ -1,152 +0,0 @@ -#include "proxyblock.hpp" -#include "itemblock.hpp" - -CSVSettings::ProxyBlock::ProxyBlock (QWidget *parent) - : GroupBlock (parent) -{ -} -int CSVSettings::ProxyBlock::build (GroupBlockDef *proxyDef) -{ - //get the list of pre-defined values for the proxy - mValueList = proxyDef->settingItems.at(0)->valueList; - - bool success = GroupBlock::build(proxyDef); - - //connect the item block of the proxy setting to the proxy-update slot - connect (getItemBlock(0), SIGNAL (signalUpdateSetting(const QString &, const QString &)), - this, SLOT (slotUpdateProxySetting (const QString &, const QString &))); - - return success; -} - -void CSVSettings::ProxyBlock::addSetting (ItemBlock *settingBlock, QStringList *proxyList) -{ - //connect the item block of the proxied seting to the generic update slot - connect (settingBlock, SIGNAL (signalUpdateSetting(const QString &, const QString &)), - this, SLOT (slotUpdateProxySetting(const QString &, const QString &))); - - mProxiedItemBlockList << settingBlock; - mProxyList << proxyList; -} - -bool CSVSettings::ProxyBlock::updateSettings (const CSMSettings::SettingMap &settings) -{ - return updateByProxiedSettings(&settings); -} - -bool CSVSettings::ProxyBlock::updateBySignal(const QString &name, const QString &value, bool &doEmit) -{ - doEmit = false; - return updateProxiedSettings(); -} - -void CSVSettings::ProxyBlock::slotUpdateProxySetting (const QString &name, const QString &value) -{ - updateByProxiedSettings(); -} - -bool CSVSettings::ProxyBlock::updateProxiedSettings() -{ - foreach (ItemBlock *block, mProxiedItemBlockList) - { - QString value = getItemBlock(0)->getValue(); - - bool success = false; - int i = 0; - - //find the value index of the selected value in the proxy setting - for (; i < mValueList->size(); ++i) - { - success = (value == mValueList->at(i)); - - if (success) - break; - } - - if (!success) - return false; - - // update the containing the proxied item's name - foreach (QStringList *list, mProxyList) - { - if ( list->at(0) == block->objectName()) - block->update (list->at(++i)); - } - } - - return true; -} - -bool CSVSettings::ProxyBlock::updateByProxiedSettings(const CSMSettings::SettingMap *settings) -{ - bool success = false; - int commonIndex = -1; - - //update all proxy settings based on values from non-proxies - foreach (QStringList *list, mProxyList) - { - //Iterate each proxy item's proxied setting list, getting the current values - //Compare those value indices. - //If indices match, they correlate to one of the proxy's values in it's value list - - //first value is always the name of the setting the proxy setting manages - QStringList::Iterator itProxyValue = list->begin(); - QString proxiedSettingName = (*itProxyValue); - QString proxiedSettingValue = ""; - itProxyValue++; - - if (!settings) - { - //get the actual setting value - ItemBlock *block = getProxiedItemBlock (proxiedSettingName); - - if (block) - proxiedSettingValue = block->getValue(); - } - else - proxiedSettingValue = (*settings)[proxiedSettingName]->getValue(); - - int j = 0; - - //iterate each value in the proxy string list - for (; itProxyValue != (list)->end(); ++itProxyValue) - { - success = ((*itProxyValue) == proxiedSettingValue); - - if (success) - break; - - j++; - } - - //break if no match was found - if ( !success ) - break; - - if (commonIndex != -1) - success = (commonIndex == j); - else - commonIndex = j; - - //break if indices were found, but mismatch - if (!success) - break; - } - - //if successful, the proxied setting values match a pre-defined value in the - //proxy's value list. Set the proxy to that value index - if (success) - { - ItemBlock *block = getItemBlock(0); - - if (block) - block->update (mValueList->at(commonIndex)); - } - - return success; -} - -CSVSettings::ItemBlock *CSVSettings::ProxyBlock::getProxiedItemBlock (const QString &name) -{ - return getItemBlock (name, &mProxiedItemBlockList); -} diff --git a/apps/opencs/view/settings/proxyblock.hpp b/apps/opencs/view/settings/proxyblock.hpp deleted file mode 100644 index 90fb9bc97..000000000 --- a/apps/opencs/view/settings/proxyblock.hpp +++ /dev/null @@ -1,52 +0,0 @@ -#ifndef PROXYBLOCK_HPP -#define PROXYBLOCK_HPP - -#include "groupblock.hpp" - -namespace CSVSettings -{ - class ProxyBlock : public GroupBlock - { - Q_OBJECT - - /// TODO: Combine mProxyItemBlockList and mProxyList. - ItemBlockList mProxiedItemBlockList; - ProxyList mProxyList; - QStringList *mValueList; - - public: - - explicit ProxyBlock (QWidget *parent = 0); - explicit ProxyBlock (ItemBlock *proxyItemBlock, QWidget *parent = 0); - - /// Add a block that contains a proxied setting to the proxy block. - void addSetting (ItemBlock* settingBlock, QStringList *proxyList); - - int build (GroupBlockDef *def); - - CSMSettings::SettingList *getSettings() { return 0; } - - /// Update settings local to the proxy block pushed from application level - bool updateSettings (const CSMSettings::SettingMap &settings); - - /// callback function triggered when update to the application level is signaled. - bool updateBySignal (const QString &name, const QString &value, bool &doEmit); - - private: - - /// return the item block of a proxied setting - ItemBlock *getProxiedItemBlock (const QString &name); - - /// update the proxy setting with data from the proxied settings - bool updateByProxiedSettings(const CSMSettings::SettingMap *settings = 0); - - /// update proxied settings with data from the proxy setting - bool updateProxiedSettings(); - - private slots: - - void slotUpdateProxySetting (const QString &name, const QString &value); - - }; -} -#endif // PROXYBLOCK_HPP diff --git a/apps/opencs/view/settings/rangeview.cpp b/apps/opencs/view/settings/rangeview.cpp new file mode 100644 index 000000000..8ae6caca0 --- /dev/null +++ b/apps/opencs/view/settings/rangeview.cpp @@ -0,0 +1,201 @@ +#include +#include +#include +#include +#include +#include + +#include "rangeview.hpp" +#include "spinbox.hpp" +#include "../../model/settings/setting.hpp" +#include "../../model/settings/support.hpp" + +CSVSettings::RangeView::RangeView (CSMSettings::Setting *setting, + Page *parent) + : mRangeWidget (0), mRangeType (setting->type()), View (setting, parent) +{ + + mRangeWidget = 0; + + if (isMultiValue()) + return; + + switch (mRangeType) + { + case CSMSettings::Type_SpinBox: + case CSMSettings::Type_DoubleSpinBox: + buildSpinBox (setting); + break; + + case CSMSettings::Type_Dial: + case CSMSettings::Type_Slider: + buildSlider (setting); + break; + + default: + break; + } + + mRangeWidget->setFixedWidth (widgetWidth (setting->widgetWidth())); + mRangeWidget->setObjectName (setting->name()); + + addWidget (mRangeWidget); +} + +void CSVSettings::RangeView::buildSlider (CSMSettings::Setting *setting) +{ + switch (setting->type()) + { + case CSMSettings::Type_Slider: + mRangeWidget = new QSlider (Qt::Horizontal, this); + mRangeWidget->setProperty ("tickInterval", setting->tickInterval()); + + if (setting->ticksAbove()) + { + if (setting->ticksBelow()) + mRangeWidget->setProperty ("tickPosition", QSlider::TicksBothSides); + else + mRangeWidget->setProperty ("tickPosition", QSlider::TicksAbove); + } + else if (setting->ticksBelow()) + mRangeWidget->setProperty ("tickPosition", QSlider::TicksBelow); + else + mRangeWidget->setProperty ("tickPosition", QSlider::NoTicks); + + break; + + case CSMSettings::Type_Dial: + mRangeWidget = new QDial (this); + mRangeWidget->setProperty ("wrapping", setting->wrapping()); + mRangeWidget->setProperty ("notchesVisible", + (setting->ticksAbove() || setting->ticksBelow())); + break; + + default: + break; + } + + mRangeWidget->setProperty ("minimum", setting->minimum()); + mRangeWidget->setProperty ("maximum", setting->maximum()); + mRangeWidget->setProperty ("tracking", false); + mRangeWidget->setProperty ("singleStep", setting->singleStep()); + + connect (mRangeWidget, SIGNAL (valueChanged (int)), + this, SLOT (slotUpdateView (int))); +} + +void CSVSettings::RangeView::buildSpinBox (CSMSettings::Setting *setting) +{ + SpinBox *sb = 0; + + switch (setting->type()) + { + case CSMSettings::Type_SpinBox: + + sb = new SpinBox (this); + + if (!setting->declaredValues().isEmpty()) + sb->setValueList (setting->declaredValues()); + + mRangeWidget = sb; + + connect (mRangeWidget, SIGNAL (valueChanged (int)), + this, SLOT (slotUpdateView (int))); + break; + + case CSMSettings::Type_DoubleSpinBox: + mRangeWidget = new QDoubleSpinBox (this); + + connect (mRangeWidget, SIGNAL (valueChanged (double)), + this, SLOT (slotUpdateView (double))); + break; + + default: + break; + } + + //min / max values are set automatically in AlphaSpinBox + if (setting->declaredValues().isEmpty()) + { + mRangeWidget->setProperty ("minimum", setting->minimum()); + mRangeWidget->setProperty ("maximum", setting->maximum()); + mRangeWidget->setProperty ("singleStep", setting->singleStep()); + mRangeWidget->setProperty ("specialValueText", + setting->specialValueText()); + } + + mRangeWidget->setProperty ("prefix", setting->prefix()); + mRangeWidget->setProperty ("suffix", setting->suffix()); + mRangeWidget->setProperty ("wrapping", setting->wrapping()); + +} + +void CSVSettings::RangeView::slotUpdateView (int value) +{ + QString textValue = ""; + QStringList list; + + switch (mRangeType) + { + case CSMSettings::Type_SpinBox: + list = static_cast (mRangeWidget)->valueList(); + if (!list.isEmpty()) + textValue = list.at(value); + break; + + default: + break; + } + + if (textValue.isEmpty()) + textValue = QVariant (value).toString(); + + setSelectedValue (textValue, false); + + View::updateView(); +} + +void CSVSettings::RangeView::slotUpdateView (double value) +{ + setSelectedValue (QVariant(value).toString(), false); + + View::updateView(); +} + +void CSVSettings::RangeView::updateView (bool signalUpdate) const +{ + QString value; + + if (!selectedValues().isEmpty()) + value = selectedValues().at(0); + + switch (mRangeType) + { + case CSMSettings::Type_SpinBox: + static_cast (mRangeWidget)->setValue (value); + break; + + case CSMSettings::Type_DoubleSpinBox: + static_cast (mRangeWidget)->setValue (value.toDouble()); + break; + + case CSMSettings::Type_Slider: + case CSMSettings::Type_Dial: + mRangeWidget->setProperty ("value", value.toInt()); + mRangeWidget->setProperty ("sliderPosition", value.toInt()); + break; + + default: + break; + + } + + View::updateView (signalUpdate); +} + +CSVSettings::RangeView *CSVSettings::RangeViewFactory::createView + (CSMSettings::Setting *setting, + Page *parent) +{ + return new RangeView (setting, parent); +} diff --git a/apps/opencs/view/settings/rangeview.hpp b/apps/opencs/view/settings/rangeview.hpp new file mode 100644 index 000000000..2ab343f1f --- /dev/null +++ b/apps/opencs/view/settings/rangeview.hpp @@ -0,0 +1,55 @@ +#ifndef CSVSETTINGS_RANGEVIEW_HPP +#define CSVSETTINGS_RANGEVIEW_HPP + +#include "view.hpp" +#include "../../model/settings/support.hpp" + +class QStringListModel; +class QAbstractSpinBox; + +namespace CSVSettings +{ + class RangeView : public View + { + Q_OBJECT + + QWidget *mRangeWidget; + CSMSettings::SettingType mRangeType; + + public: + explicit RangeView (CSMSettings::Setting *setting, + Page *parent); + + protected: + + ///virtual function called through View + void updateView (bool signalUpdate = true) const; + + ///construct a slider-based view + void buildSlider (CSMSettings::Setting *setting); + + ///construct a spinbox-based view + void buildSpinBox (CSMSettings::Setting *setting); + + private slots: + + ///responds to valueChanged signals + void slotUpdateView (int value); + void slotUpdateView (double value); + + }; + + class RangeViewFactory : public QObject, public IViewFactory + { + Q_OBJECT + + public: + explicit RangeViewFactory (QWidget *parent = 0) + : QObject (parent) + {} + + RangeView *createView (CSMSettings::Setting *setting, + Page *parent); + }; +} +#endif // CSVSETTINGS_RANGEVIEW_HPP diff --git a/apps/opencs/view/settings/resizeablestackedwidget.cpp b/apps/opencs/view/settings/resizeablestackedwidget.cpp new file mode 100644 index 000000000..cb127cb76 --- /dev/null +++ b/apps/opencs/view/settings/resizeablestackedwidget.cpp @@ -0,0 +1,40 @@ +#include "resizeablestackedwidget.hpp" +#include "page.hpp" + +#include + +CSVSettings::ResizeableStackedWidget::ResizeableStackedWidget(QWidget *parent) : + QStackedWidget(parent) +{} + +void CSVSettings::ResizeableStackedWidget::addWidget(QWidget* pWidget) +{ + QStackedWidget::addWidget(pWidget); +} + +void CSVSettings::ResizeableStackedWidget::changePage + (int current, int previous) +{ + if (current == previous) + return; + + Page *prevPage = 0; + Page *curPage = 0; + + if (previous > -1) + prevPage = static_cast (widget (previous)); + + if (current > -1) + curPage = static_cast (widget (current)); + + if (prevPage) + prevPage->hideWidgets(); + + if (curPage) + curPage->showWidgets(); + + layout()->activate(); + setFixedSize(minimumSizeHint()); + + setCurrentIndex (current); +} diff --git a/apps/opencs/view/settings/resizeablestackedwidget.hpp b/apps/opencs/view/settings/resizeablestackedwidget.hpp new file mode 100644 index 000000000..2d0c71a23 --- /dev/null +++ b/apps/opencs/view/settings/resizeablestackedwidget.hpp @@ -0,0 +1,25 @@ +#ifndef CSVSETTINGS_RESIZEABLESTACKEDWIDGET_HPP +#define CSVSETTINGS_RESIZEABLESTACKEDWIDGET_HPP + +#include + +class QListWidgetItem; + +namespace CSVSettings +{ + class ResizeableStackedWidget : public QStackedWidget + { + Q_OBJECT + + public: + explicit ResizeableStackedWidget(QWidget *parent = 0); + + ///add a widget to the stacked widget + void addWidget(QWidget* pWidget); + + ///called whenever the stacked widget page is changed + void changePage (int, int); + }; +} + +#endif // CSVSETTINGS_RESIZEABLESTACKEDWIDGET_HPP diff --git a/apps/opencs/view/settings/settingwidget.cpp b/apps/opencs/view/settings/settingwidget.cpp deleted file mode 100644 index 2c93986e7..000000000 --- a/apps/opencs/view/settings/settingwidget.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "settingwidget.hpp" diff --git a/apps/opencs/view/settings/settingwidget.hpp b/apps/opencs/view/settings/settingwidget.hpp deleted file mode 100644 index 9f4513671..000000000 --- a/apps/opencs/view/settings/settingwidget.hpp +++ /dev/null @@ -1,214 +0,0 @@ -#ifndef SETTINGWIDGET_HPP -#define SETTINGWIDGET_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "abstractwidget.hpp" - -namespace CSVSettings -{ - - /// Generic template for radiobuttons / checkboxes - template - class SettingWidget : public AbstractWidget - { - - T1 *mWidget; - - public: - - explicit SettingWidget (WidgetDef &def, QLayout *layout, QWidget* parent = 0) - : AbstractWidget (layout, parent), mWidget (new T1 (parent)) - { - mWidget->setText(def.caption); - build (mWidget, def, true); - mWidget->setChecked(def.isDefault); - - connect (mWidget, SIGNAL (toggled (bool)), - this, SLOT (slotUpdateItem (bool))); - } - - QWidget *widget() { return mWidget; } - - private: - - void updateWidget (const QString &value) - { - if ( value == mWidget->objectName() && !mWidget->isChecked() ) - mWidget->setChecked (true); - } - }; - - /// spin box template - template <> - class SettingWidget : public AbstractWidget - { - - QSpinBox *mWidget; - - public: - - SettingWidget (WidgetDef &def, QLayout *layout, QWidget *parent = 0) - : AbstractWidget (layout, parent), mWidget (new QSpinBox (parent)) - { - def.caption += tr(" (%1 to %2)").arg(def.minMax->left).arg(def.minMax->right); - - mWidget->setMaximum (def.minMax->right.toInt()); - mWidget->setMinimum (def.minMax->left.toInt()); - mWidget->setValue (def.value.toInt()); - - build (mWidget, def); - - connect (mWidget, SIGNAL (valueChanged (int)), - this, SLOT (slotUpdateItem (int))); - - mWidget->setAlignment (getAlignment(def.valueAlignment)); - - - } - - QWidget *widget() { return mWidget; } - - private: - - void updateWidget (const QString &value) - { - int intVal = value.toInt(); - - if (intVal >= mWidget->minimum() && intVal <= mWidget->maximum() && intVal != mWidget->value()) - mWidget->setValue (intVal); - } - - signals: - - }; - - /// combo box template - template <> - class SettingWidget : public CSVSettings::AbstractWidget - { - - QComboBox *mWidget; - - - public: - - explicit SettingWidget(WidgetDef &def, QLayout *layout, QWidget *parent = 0) - : AbstractWidget (layout, parent), mWidget (new QComboBox (parent)) - { - int i = 0; - - foreach (QString item, *(def.valueList)) - { - mWidget->addItem (item); - - if (item == def.value) - mWidget->setCurrentIndex(i); - - i++; - } - - build (mWidget, def); - - connect (mWidget, SIGNAL (currentIndexChanged (const QString &)), - this, SLOT (slotUpdateItem (const QString &))); - - //center the combo box items - mWidget->setEditable (true); - mWidget->lineEdit()->setReadOnly (true); - mWidget->lineEdit()->setAlignment (getAlignment(def.valueAlignment)); - - QFlags alignment = mWidget->lineEdit()->alignment(); - - for (int j = 0; j < mWidget->count(); j++) - mWidget->setItemData (j, QVariant(alignment), Qt::TextAlignmentRole); - } - - QWidget *widget() { return mWidget; } - - private: - - void updateWidget (const QString &value) - { - if (mWidget->currentText() != value) - mWidget->setCurrentIndex(mWidget->findText(value)); - } - - }; - - /// line edit template - template <> - class SettingWidget : public CSVSettings::AbstractWidget - { - - QLineEdit *mWidget; - - public: - - explicit SettingWidget(WidgetDef &def, QLayout *layout, QWidget *parent = 0) - : AbstractWidget (layout, parent), mWidget (new QLineEdit (parent)) - { - if (!def.inputMask.isEmpty()) - mWidget->setInputMask (def.inputMask); - - mWidget->setText (def.value); - - build (mWidget, def); - - connect (mWidget, SIGNAL (textChanged (const QString &)), - this, SLOT (slotUpdateItem (const QString &))); - - mWidget->setAlignment (getAlignment(def.valueAlignment)); - } - - QWidget *widget() { return mWidget; } - - void updateWidget (const QString &value) - { - if (mWidget->text() != value) - mWidget->setText(value); - } - }; - - /// list widget template - /// \todo Not fully implemented. Only widget supporting multi-valued settings - template <> - class SettingWidget : public CSVSettings::AbstractWidget - { - - QListWidget *mWidget; - - public: - - explicit SettingWidget(WidgetDef &def, QLayout *layout, QWidget *parent = 0 ) - : AbstractWidget (layout, parent), mWidget (new QListWidget (parent)) - { - int i = 0; - - foreach (QString item, *(def.valueList)) - { - mWidget->addItem (item); - - if (item == def.value) {} - i++; - } - build (mWidget, def); - } - - QWidget *widget() { return mWidget; } - - private: - void updateWidget (const QString &value) {} - }; - -} -#endif // SETTINGWIDGET_HPP diff --git a/apps/opencs/view/settings/settingwindow.cpp b/apps/opencs/view/settings/settingwindow.cpp new file mode 100644 index 000000000..7cdf2bded --- /dev/null +++ b/apps/opencs/view/settings/settingwindow.cpp @@ -0,0 +1,135 @@ +#include +#include + +#include "../../model/settings/setting.hpp" +#include "../../model/settings/connector.hpp" +#include "../../model/settings/usersettings.hpp" +#include "settingwindow.hpp" +#include "page.hpp" +#include "view.hpp" + +CSVSettings::SettingWindow::SettingWindow(QWidget *parent) + : QMainWindow(parent) +{} + +void CSVSettings::SettingWindow::createPages() +{ + CSMSettings::SettingPageMap pageMap = mModel->settingPageMap(); + + QList connectedSettings; + + foreach (const QString &pageName, pageMap.keys()) + { + QList pageSettings = pageMap.value (pageName); + + mPages.append (new Page (pageName, pageSettings, this)); + + for (int i = 0; i < pageSettings.size(); i++) + { + CSMSettings::Setting *setting = pageSettings.at(i); + + if (!setting->proxyLists().isEmpty()) + connectedSettings.append (setting); + } + } + + if (!connectedSettings.isEmpty()) + createConnections(connectedSettings); +} + +void CSVSettings::SettingWindow::createConnections + (const QList &list) +{ + foreach (const CSMSettings::Setting *setting, list) + { + View *masterView = findView (setting->page(), setting->name()); + + CSMSettings::Connector *connector = + new CSMSettings::Connector (masterView, this); + + connect (masterView, + SIGNAL (viewUpdated(const QString &, const QStringList &)), + connector, + SLOT (slotUpdateSlaves()) + ); + + const CSMSettings::ProxyValueMap &proxyMap = setting->proxyLists(); + + foreach (const QString &key, proxyMap.keys()) + { + QStringList keyPair = key.split('/'); + + if (keyPair.size() != 2) + continue; + + View *slaveView = findView (keyPair.at(0), keyPair.at(1)); + + if (!slaveView) + { + qWarning () << "Unable to create connection for view " + << key; + continue; + } + + QList proxyList = proxyMap.value (key); + connector->addSlaveView (slaveView, proxyList); + + connect (slaveView, + SIGNAL (viewUpdated(const QString &, const QStringList &)), + connector, + SLOT (slotUpdateMaster())); + } + } +} + +void CSVSettings::SettingWindow::setViewValues() +{ + //iterate each page and view, setting their definintions + //if they exist in the model + foreach (const Page *page, mPages) + { + foreach (const View *view, page->views()) + { + //testing beforehand prevents overwriting a proxy setting + if (!mModel->hasSettingDefinitions (view->viewKey())) + continue; + + QStringList defs = mModel->definitions (view->viewKey()); + + view->setSelectedValues(defs); + } + } +} + +CSVSettings::View *CSVSettings::SettingWindow::findView + (const QString &pageName, const QString &setting) +{ + foreach (const Page *page, mPages) + { + if (page->objectName() == pageName) + return page->findView (pageName, setting); + } + return 0; +} + +void CSVSettings::SettingWindow::saveSettings() +{ + //setting the definition in the model automatically syncs with the file + foreach (const Page *page, mPages) + { + foreach (const View *view, page->views()) + { + if (!view->serializable()) + continue; + + mModel->setDefinitions (view->viewKey(), view->selectedValues()); + } + } + + mModel->saveDefinitions(); +} + +void CSVSettings::SettingWindow::closeEvent (QCloseEvent *event) +{ + QApplication::focusWidget()->clearFocus(); +} diff --git a/apps/opencs/view/settings/settingwindow.hpp b/apps/opencs/view/settings/settingwindow.hpp new file mode 100644 index 000000000..2266f130d --- /dev/null +++ b/apps/opencs/view/settings/settingwindow.hpp @@ -0,0 +1,60 @@ +#ifndef CSVSETTINGS_SETTINGWINDOW_HPP +#define CSVSETTINGS_SETTINGWINDOW_HPP + +#include +#include + +#include "../../model/settings/support.hpp" + +namespace CSMSettings { + class Setting; + class UserSettings; +} + +namespace CSVSettings { + + class Page; + class View; + + typedef QList PageList; + + class SettingWindow : public QMainWindow + { + Q_OBJECT + + PageList mPages; + CSMSettings::UserSettings *mModel; + + public: + explicit SettingWindow(QWidget *parent = 0); + + ///retrieve a reference to a view based on it's page and setting name + View *findView (const QString &pageName, const QString &setting); + + ///set the model the view uses (instance of UserSettings) + void setModel (CSMSettings::UserSettings &model) { mModel = &model; } + + protected: + + virtual void closeEvent (QCloseEvent *event); + + ///construct the pages to be displayed in the dialog + void createPages(); + + ///return the list of constructed pages + const PageList &pages() const { return mPages; } + + ///save settings from the GUI to file + void saveSettings(); + + ///sets the defined values for the views that have been created + void setViewValues(); + + private: + + ///create connections between settings (used for proxy settings) + void createConnections (const QList &list); + }; +} + +#endif // CSVSETTINGS_SETTINGWINDOW_HPP diff --git a/apps/opencs/view/settings/spinbox.cpp b/apps/opencs/view/settings/spinbox.cpp new file mode 100644 index 000000000..4b1447f8f --- /dev/null +++ b/apps/opencs/view/settings/spinbox.cpp @@ -0,0 +1,50 @@ +#include "spinbox.hpp" + +#include + +CSVSettings::SpinBox::SpinBox(QWidget *parent) + : mValueList(QStringList()), QSpinBox(parent) +{ + setRange (0, 0); +} + +QString CSVSettings::SpinBox::textFromValue(int val) const +{ + if (mValueList.isEmpty()) + return QVariant (val).toString(); + + QString value; + + if (val < mValueList.size()) + value = mValueList.at (val); + + return value; +} + +int CSVSettings::SpinBox::valueFromText(const QString &text) const +{ + if (mValueList.isEmpty()) + return -1; + + if (mValueList.contains (text)) + return mValueList.indexOf(text); + + return -1; +} + +void CSVSettings::SpinBox::setValue (const QString &value) +{ + if (!mValueList.isEmpty()) + { + lineEdit()->setText (value); + QSpinBox::setValue(valueFromText(value)); + } + else + QSpinBox::setValue (value.toInt()); +} + +void CSVSettings::SpinBox::setValueList (const QStringList &list) +{ + mValueList = list; + setMaximum (list.size() - 1); +} diff --git a/apps/opencs/view/settings/spinbox.hpp b/apps/opencs/view/settings/spinbox.hpp new file mode 100644 index 000000000..e887e8c93 --- /dev/null +++ b/apps/opencs/view/settings/spinbox.hpp @@ -0,0 +1,38 @@ +#ifndef CSVSETTINGS_SPINBOX_HPP +#define CSVSETTINGS_SPINBOX_HPP + +#include +#include +#include + +namespace CSVSettings +{ + class SpinBox : public QSpinBox + { + Q_OBJECT + + QStringList mValueList; + + public: + explicit SpinBox(QWidget *parent = 0); + + ///set the value displayed in the spin box + void setValue (const QString &value); + + ///set the stringlist that's used as a list of pre-defined values + ///to be displayed as the user scrolls + void setValueList (const QStringList &list); + + ///returns the pre-defined value list. + const QStringList &valueList() const { return mValueList; } + + protected: + + ///converts an index value to corresponding text to be displayed + QString textFromValue (int val) const; + + ///converts a text value to a corresponding index + int valueFromText (const QString &text) const; + }; +} +#endif // CSVSETTINGS_SPINBOX_HPP diff --git a/apps/opencs/view/settings/support.cpp b/apps/opencs/view/settings/support.cpp deleted file mode 100644 index d79edfdb3..000000000 --- a/apps/opencs/view/settings/support.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "support.hpp" diff --git a/apps/opencs/view/settings/support.hpp b/apps/opencs/view/settings/support.hpp deleted file mode 100644 index 5d954505c..000000000 --- a/apps/opencs/view/settings/support.hpp +++ /dev/null @@ -1,206 +0,0 @@ -#ifndef VIEW_SUPPORT_HPP -#define VIEW_SUPPORT_HPP - -#include -#include - -#include "../../model/settings/support.hpp" - -namespace CSVSettings -{ - struct WidgetDef; - class ItemBlock; - class GroupBlock; - struct GroupBlockDef; - - typedef QList GroupBlockDefList; - typedef QList GroupBlockList; - typedef QList ItemBlockList; - typedef QList ProxyList; - typedef QList WidgetList; - typedef QMap ItemBlockMap; - - enum Orientation - { - Orient_Horizontal, - Orient_Vertical - }; - - enum WidgetType - { - Widget_CheckBox, - Widget_ComboBox, - Widget_LineEdit, - Widget_ListBox, - Widget_RadioButton, - Widget_SpinBox, - Widget_Undefined - }; - - enum Alignment - { - Align_Left = Qt::AlignLeft, - Align_Center = Qt::AlignHCenter, - Align_Right = Qt::AlignRight - }; - - /// definition struct for widgets - struct WidgetDef - { - /// type of widget providing input - WidgetType type; - - /// width of caption label - int labelWidth; - - /// width of input widget - int widgetWidth; - - /// label / widget orientation (horizontal / vertical) - Orientation orientation; - - /// input mask (line edit only) - QString inputMask; - - /// label caption. Leave empty for multiple items. See BlockDef::captionList - QString caption; - - /// widget value. Leave empty for multiple items. See BlockDef::valueList - QString value; - - /// Min/Max QString value pair. If empty, assigned to property item value pair. - CSMSettings::QStringPair *minMax; - - /// value list for list widgets. If left empty, is assigned to property item value list during block build(). - QStringList *valueList; - - /// determined at runtime - bool isDefault; - - /// left / center / right-justify text in widget - Alignment valueAlignment; - - /// left / center / right-justify widget in group box - Alignment widgetAlignment; - - - WidgetDef() : labelWidth (-1), widgetWidth (-1), - orientation (Orient_Horizontal), - isDefault (true), valueAlignment (Align_Center), - widgetAlignment (Align_Right), - inputMask (""), value (""), - caption (""), valueList (0) - {} - - WidgetDef (WidgetType widgType) - : type (widgType), orientation (Orient_Horizontal), - caption (""), value (""), valueAlignment (Align_Center), - widgetAlignment (Align_Right), - labelWidth (-1), widgetWidth (-1), - valueList (0), isDefault (true) - {} - - }; - - /// Defines the attributes of the setting as it is represented in the config file - /// as well as the UI elements (group box and widget) that serve it. - /// Only one widget may serve as the input widget for the setting. - struct SettingsItemDef - { - /// setting name - QString name; - - /// list of valid values for the setting - QStringList *valueList; - - /// Used to populate option widget captions or list widget item lists (see WidgetDef::caption / value) - QString defaultValue; - - /// flag indicating multi-valued setting - bool hasMultipleValues; - - /// minimum / maximum value pair - CSMSettings::QStringPair minMax; - - /// definition of the input widget for this setting - WidgetDef widget; - - /// general orientation of the widget / label for this setting - Orientation orientation; - - /// list of settings and corresponding default values for proxy widget - ProxyList *proxyList; - - SettingsItemDef() : name (""), defaultValue (""), orientation (Orient_Vertical), hasMultipleValues (false) - {} - - SettingsItemDef (QString propName, QString propDefault, Orientation propOrient = Orient_Vertical) - : name (propName), defaultValue (propDefault), orientation (propOrient), - hasMultipleValues(false), valueList (new QStringList), proxyList ( new ProxyList) - {} - }; - - - /// Generic container block - struct GroupBlockDef - { - /// block title - QString title; - - /// list of captions for widgets at the block level (not associated with any particular setting) - QStringList captions; - - /// list of widgets at the block level (not associated with any particular setting) - WidgetList widgets; - - /// list of the settings which are subordinate to the setting block. - QList settingItems; - - /// general orientation of widgets in group block - Orientation widgetOrientation; - - /// determines whether or not box border/title are visible - bool isVisible; - - /// indicates whether or not this block defines a proxy block - bool isProxy; - - /// generic default value attribute - QString defaultValue; - - /// shows / hides margins - bool isZeroMargin; - - GroupBlockDef (): title(""), widgetOrientation (Orient_Vertical), isVisible (true), isProxy (false), defaultValue (""), isZeroMargin (true) - {} - - GroupBlockDef (QString blockTitle) - : title (blockTitle), widgetOrientation (Orient_Vertical), isProxy (false), isVisible (true), defaultValue (""), isZeroMargin (true) - {} - }; - - /// used to create unique, complex blocks - struct CustomBlockDef - { - /// block title - QString title; - - /// default value for widgets unique to the custom block - QString defaultValue; - - /// list of settings groups that comprise the settings within the custom block - GroupBlockDefList blockDefList; - - /// orientation of the widgets within the block - Orientation blockOrientation; - - CustomBlockDef (): title (""), defaultValue (""), blockOrientation (Orient_Horizontal) - {} - - CustomBlockDef (const QString &blockTitle) - : title (blockTitle), defaultValue (""), blockOrientation (Orient_Horizontal) - {} - }; -} - -#endif // VIEW_SUPPORT_HPP diff --git a/apps/opencs/view/settings/textview.cpp b/apps/opencs/view/settings/textview.cpp new file mode 100644 index 000000000..6886732db --- /dev/null +++ b/apps/opencs/view/settings/textview.cpp @@ -0,0 +1,63 @@ +#include +#include + +#include "textview.hpp" +#include "../../model/settings/setting.hpp" + +CSVSettings::TextView::TextView(CSMSettings::Setting *setting, Page *parent) + : mDelimiter (setting->delimiter()), View (setting, parent) + +{ + if (setting->isMultiLine()) + mTextWidget = new QTextEdit ("", this); + else + mTextWidget = new QLineEdit ("", this); + + if (setting->widgetWidth() > 0) + mTextWidget->setFixedWidth (widgetWidth (setting->widgetWidth())); + + connect (mTextWidget, SIGNAL (textEdited (QString)), + this, SLOT (slotTextEdited (QString))); + + addWidget (mTextWidget, setting->viewRow(), setting->viewColumn()); +} + +bool CSVSettings::TextView::isEquivalent + (const QString &lhs, const QString &rhs) const +{ + return (lhs.trimmed() == rhs.trimmed()); +} + +void CSVSettings::TextView::slotTextEdited (QString value) +{ + QStringList values = value.split (mDelimiter, QString::SkipEmptyParts); + + QStringList returnValues; + + foreach (const QString &splitValue, values) + returnValues.append (splitValue.trimmed()); + + setSelectedValues (returnValues, false); + + View::updateView(); +} + +void CSVSettings::TextView::updateView(bool signalUpdate) const +{ + QString values = selectedValues().join (mDelimiter); + + if (isEquivalent (mTextWidget->property("text").toString(), values)) + return; + + mTextWidget->setProperty("text", values); + + View::updateView (signalUpdate); +} + +CSVSettings::TextView *CSVSettings::TextViewFactory::createView + (CSMSettings::Setting *setting, + Page *parent) +{ + return new TextView (setting, parent); +} + diff --git a/apps/opencs/view/settings/textview.hpp b/apps/opencs/view/settings/textview.hpp new file mode 100644 index 000000000..f4cd03d2f --- /dev/null +++ b/apps/opencs/view/settings/textview.hpp @@ -0,0 +1,51 @@ +#ifndef CSVSETTINGS_TEXTVIEW_HPP +#define CSVSETTINGS_TEXTVIEW_HPP + +#include "view.hpp" +#include "../../model/settings/setting.hpp" + +namespace CSVSettings +{ + class TextView : public View + { + Q_OBJECT + + QWidget *mTextWidget; + + QString mDelimiter; + + public: + explicit TextView (CSMSettings::Setting *setting, + Page *parent = 0); + + protected: + + /// virtual function called through View + void updateView (bool signalUpdate = true) const; + + protected slots: + + ///Receives updates to the widget for signalling + void slotTextEdited (QString value); + + private: + + ///Comparison function that returns true if the trimmed() strings + ///are equal + bool isEquivalent (const QString &lhs, const QString &rhs) const; + }; + + class TextViewFactory : public QObject, public IViewFactory + { + Q_OBJECT + + public: + explicit TextViewFactory (QWidget *parent = 0) + : QObject (parent) + {} + + TextView *createView (CSMSettings::Setting *setting, + Page *parent); + }; +} +#endif // CSVSETTINGS_TEXTVIEW_HPP diff --git a/apps/opencs/view/settings/toggleblock.cpp b/apps/opencs/view/settings/toggleblock.cpp deleted file mode 100644 index 3406a62c4..000000000 --- a/apps/opencs/view/settings/toggleblock.cpp +++ /dev/null @@ -1,80 +0,0 @@ -#include "toggleblock.hpp" -#include "groupblock.hpp" -#include "groupbox.hpp" -#include "itemblock.hpp" - -CSVSettings::ToggleBlock::ToggleBlock(QWidget *parent) : - CustomBlock(parent) -{} - -int CSVSettings::ToggleBlock::build(CustomBlockDef *def) -{ - if (def->blockDefList.size()==0) - return -1; - - QList::Iterator it = def->blockDefList.begin(); - - //first def in the list is the def for the toggle block - GroupBlockDef *toggleDef = *it++; - - if (toggleDef->captions.size() != def->blockDefList.size()-1 ) - return -2; - - if (toggleDef->widgets.size() == 0) - return -3; - - //create the toogle block UI structure - QLayout *blockLayout = createLayout (def->blockOrientation, true); - GroupBox *propertyBox = buildGroupBox (toggleDef->widgetOrientation); - - mBox->setLayout(blockLayout); - mBox->setTitle (toggleDef->title); - - //build the blocks contained in the def list - //this manages proxy block construction. - //Any settings managed by the proxy setting - //must be included in the blocks defined in the list. - CustomBlock::build (def->blockDefList, &it); - - for (GroupBlockList::iterator it = mGroupList.begin(); it != mGroupList.end(); ++it) - propertyBox->layout()->addWidget ((*it)->getGroupBox()); - - //build togle widgets, linking them to the settings - GroupBox *toggleBox = buildToggleWidgets (toggleDef, def->defaultValue); - - blockLayout->addWidget(toggleBox); - blockLayout->addWidget(propertyBox); - blockLayout->setAlignment (propertyBox, Qt::AlignRight); - - return 0; -} - -CSVSettings::GroupBox *CSVSettings::ToggleBlock::buildToggleWidgets (GroupBlockDef *def, QString &defaultToggle) -{ - GroupBox *box = new GroupBox (false, getParent()); - - QLayout *layout = createLayout (def->widgetOrientation, true, static_cast(box)); - - for (int i = 0; i < def->widgets.size(); ++i) - { - QString caption = def->captions.at(i); - WidgetDef *wDef = def->widgets.at(i); - - wDef->caption = caption; - wDef->widgetAlignment = Align_Left; - - AbstractWidget *widg = buildWidget (caption, *wDef, layout, false); - - GroupBlock *block = mGroupList.at(i); - - //connect widget's update to the property block's enabled status - connect (widg->widget(), SIGNAL (toggled (bool)), block, SLOT (slotSetEnabled(bool))); - - //enable the default toggle option - block->getGroupBox()->setEnabled( caption == defaultToggle ); - - layout = widg->getLayout(); - } - - return box; -} diff --git a/apps/opencs/view/settings/toggleblock.hpp b/apps/opencs/view/settings/toggleblock.hpp deleted file mode 100644 index 4b6e8e344..000000000 --- a/apps/opencs/view/settings/toggleblock.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef TOGGLEBLOCK_HPP -#define TOGGLEBLOCK_HPP - -#include - -#include "customblock.hpp" - -namespace CSVSettings -{ - class GroupBlock; - class GroupBox; - class ToggleWidget; - class ItemBlock; - - class ToggleBlock : public CustomBlock - { - - public: - explicit ToggleBlock(QWidget *parent = 0); - - int build (CustomBlockDef *def); - - private: - /// Constructor for toggle widgets that are specific to toggle block - /// Widgets are not a part of the user preference settings - GroupBox *buildToggleWidgets (GroupBlockDef *def, QString &defaultToggle); - }; -} -#endif // TOGGLEBLOCK_HPP diff --git a/apps/opencs/view/settings/usersettingsdialog.cpp b/apps/opencs/view/settings/usersettingsdialog.cpp deleted file mode 100644 index e73e24dcb..000000000 --- a/apps/opencs/view/settings/usersettingsdialog.cpp +++ /dev/null @@ -1,119 +0,0 @@ -#include "usersettingsdialog.hpp" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../../model/settings/support.hpp" - -#include "datadisplayformatpage.hpp" -#include "windowpage.hpp" -#include "settingwidget.hpp" - -CSVSettings::UserSettingsDialog::UserSettingsDialog(QMainWindow *parent) : - QMainWindow (parent), mStackedWidget (0) -{ - setWindowTitle(QString::fromUtf8 ("User Settings")); - buildPages(); - setWidgetStates (); - - connect (mListWidget, - SIGNAL (currentItemChanged(QListWidgetItem*, QListWidgetItem*)), - this, - SLOT (slotChangePage (QListWidgetItem*, QListWidgetItem*))); - - QRect scr = QApplication::desktop()->screenGeometry(); - QRect rect = geometry(); - move (scr.center().x() - rect.center().x(), scr.center().y() - rect.center().y()); -} - -CSVSettings::UserSettingsDialog::~UserSettingsDialog() -{ -} - -void CSVSettings::UserSettingsDialog::closeEvent (QCloseEvent *event) -{ - writeSettings(); -} - -void CSVSettings::UserSettingsDialog::setWidgetStates () -{ - CSMSettings::UserSettings::instance().loadSettings("opencs.cfg"); - - //iterate the tabWidget's pages (sections) - for (int i = 0; i < mStackedWidget->count(); i++) - { - //get the settings defined for the entire section - //and update widget - QString pageName = mStackedWidget->widget(i)->objectName(); - - const CSMSettings::SettingMap *settings = CSMSettings::UserSettings::instance().getSettings(pageName); - AbstractPage &page = getAbstractPage (i); - page.initializeWidgets(*settings); - } -} - -void CSVSettings::UserSettingsDialog::buildPages() -{ - //craete central widget with it's layout and immediate children - QWidget *centralWidget = new QWidget (this); - - mListWidget = new QListWidget (centralWidget); - mStackedWidget = new QStackedWidget (centralWidget); - - QGridLayout* dialogLayout = new QGridLayout(); - - mListWidget->setMinimumWidth(0); - mListWidget->setSizePolicy (QSizePolicy::Preferred, QSizePolicy::Expanding); - - mStackedWidget->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed); - - dialogLayout->addWidget (mListWidget,0,0); - dialogLayout->addWidget (mStackedWidget,0,1, Qt::AlignTop); - - centralWidget->setLayout (dialogLayout); - - setCentralWidget (centralWidget); - setDockOptions (QMainWindow::AllowNestedDocks); - - createPage(); - createPage(); - -} - -void CSVSettings::UserSettingsDialog::writeSettings() -{ - QMap settings; - - for (int i = 0; i < mStackedWidget->count(); ++i) - { - AbstractPage &page = getAbstractPage (i); - settings [page.objectName()] = page.getSettings(); - } - CSMSettings::UserSettings::instance().writeSettings(settings); -} - -CSVSettings::AbstractPage &CSVSettings::UserSettingsDialog::getAbstractPage (int index) -{ - return dynamic_cast (*(mStackedWidget->widget (index))); -} - -void CSVSettings::UserSettingsDialog::slotChangePage(QListWidgetItem *current, QListWidgetItem *previous) -{ - if (!current) - current = previous; - - if (!(current == previous)) - mStackedWidget->setCurrentIndex (mListWidget->row(current)); -} diff --git a/apps/opencs/view/settings/usersettingsdialog.hpp b/apps/opencs/view/settings/usersettingsdialog.hpp deleted file mode 100644 index 3b3fa5b79..000000000 --- a/apps/opencs/view/settings/usersettingsdialog.hpp +++ /dev/null @@ -1,71 +0,0 @@ -#ifndef USERSETTINGSDIALOG_H -#define USERSETTINGSDIALOG_H - -#include -#include -#include -#include - -#include "../../model/settings/usersettings.hpp" -#include "../../model/settings/support.hpp" - -class QHBoxLayout; -class AbstractWidget; -class QStackedWidget; -class QListWidget; - -namespace CSVSettings { - - class AbstractPage; - - class UserSettingsDialog : public QMainWindow - { - Q_OBJECT - - QListWidget *mListWidget; - QStackedWidget *mStackedWidget; - - public: - UserSettingsDialog(QMainWindow *parent = 0); - ~UserSettingsDialog(); - - private: - - /// Settings are written on close - void closeEvent (QCloseEvent *event); - - /// return the setting page by name - /// performs dynamic cast to AbstractPage * - AbstractPage &getAbstractPage (int index); - void setWidgetStates (); - void buildPages(); - void writeSettings(); - - /// Templated function to create a custom user preference page - template - void createPage () - { - T *page = new T(mStackedWidget); - - mStackedWidget->addWidget (&dynamic_cast(*page)); - - new QListWidgetItem (page->objectName(), mListWidget); - - //finishing touches - QFontMetrics fm (QApplication::font()); - int textWidth = fm.width(page->objectName()); - - if ((textWidth + 50) > mListWidget->minimumWidth()) - mListWidget->setMinimumWidth(textWidth + 50); - - resize (mStackedWidget->sizeHint()); - } - - public slots: - - /// Called when a different page is selected in the left-hand list widget - void slotChangePage (QListWidgetItem*, QListWidgetItem*); - }; - -} -#endif // USERSETTINGSDIALOG_H diff --git a/apps/opencs/view/settings/view.cpp b/apps/opencs/view/settings/view.cpp new file mode 100644 index 000000000..69109e2b3 --- /dev/null +++ b/apps/opencs/view/settings/view.cpp @@ -0,0 +1,216 @@ +#include +#include +#include +#include +#include + +#include "view.hpp" +#include "../../model/settings/support.hpp" +#include "../../model/settings/setting.hpp" +#include "page.hpp" + +CSVSettings::View::View(CSMSettings::Setting *setting, + Page *parent) + + : mDataModel(0), mParentPage (parent), + mHasFixedValues (!setting->declaredValues().isEmpty()), + mIsMultiValue (setting->isMultiValue()), + mViewKey (setting->page() + '/' + setting->name()), + mSerializable (setting->serializable()), + Frame(true, setting->name(), parent) +{ + setObjectName (setting->name()); + buildView(); + buildModel (setting); +} + +void CSVSettings::View::buildModel (const CSMSettings::Setting *setting) +{ + QStringList values = setting->defaultValues(); + + if (mHasFixedValues) + buildFixedValueModel (setting->declaredValues()); + else + buildUpdatableValueModel (values); + + mSelectionModel = new QItemSelectionModel (mDataModel, this); + + setSelectedValues (values, false); +} + +void CSVSettings::View::buildFixedValueModel (const QStringList &values) +{ + //fixed value models are simple string list models, since they are read-only + mDataModel = new QStringListModel (values, this); +} + +void CSVSettings::View::buildUpdatableValueModel (const QStringList &values) +{ + //updateable models are standard item models because they support + //replacing entire columns + QList itemList; + + foreach (const QString &value, values) + itemList.append (new QStandardItem(value)); + + QStandardItemModel *model = new QStandardItemModel (this); + model->appendColumn (itemList); + + mDataModel = model; +} + +void CSVSettings::View::buildView() +{ + setFlat (true); + setHLayout(); +} + +int CSVSettings::View::currentIndex () const +{ + if (selectedValues().isEmpty()) + return -1; + + QString currentValue = selectedValues().at(0); + + for (int i = 0; i < mDataModel->rowCount(); i++) + if (value(i) == currentValue) + return i; + + return -1; +} + +void CSVSettings::View::refresh() const +{ + select (mSelectionModel->selection()); + updateView(); +} + +int CSVSettings::View::rowCount() const +{ + return mDataModel->rowCount(); +} + +void CSVSettings::View::select (const QItemSelection &selection) const +{ + mSelectionModel->clear(); + mSelectionModel->select(selection, QItemSelectionModel::Select); +} + +QStringList CSVSettings::View::selectedValues() const +{ + QStringList selValues; + + foreach (const QModelIndex &idx, mSelectionModel->selectedIndexes()) + selValues.append (value(idx.row())); + + return selValues; +} + +void CSVSettings::View::setSelectedValue (const QString &value, + bool doViewUpdate, bool signalUpdate) +{ + setSelectedValues (QStringList() << value, doViewUpdate, signalUpdate); +} + +void CSVSettings::View::setSelectedValues (const QStringList &list, + bool doViewUpdate, bool signalUpdate) const +{ + QItemSelection selection; + + if (stringListsMatch (list, selectedValues())) + return; + + if (!mHasFixedValues) + { + QStandardItemModel *model = + static_cast (mDataModel); + + model->clear(); + model->appendColumn (toStandardItemList (list)); + + for (int i = 0; i < model->rowCount(); i++) + { + QModelIndex idx = model->index(i, 0); + selection.append (QItemSelectionRange (idx, idx)); + } + } + else + { + for (int i = 0; i < mDataModel->rowCount(); i++) + { + if (list.contains(value(i))) + { + QModelIndex idx = mDataModel->index(i, 0); + selection.append(QItemSelectionRange (idx, idx)); + } + } + } + select (selection); + + //update the view if the selection was set from the model side, not by the + //user + if (doViewUpdate) + updateView (signalUpdate); +} + +void CSVSettings::View::showEvent ( QShowEvent * event ) +{ + refresh(); +} + +bool CSVSettings::View::stringListsMatch ( + const QStringList &list1, + const QStringList &list2) const +{ + //returns a "sloppy" match, verifying that each list contains all the same + //items, though not necessarily in the same order. + + if (list1.size() != list2.size()) + return false; + + QStringList tempList(list2); + + //iterate each value in the list, removing one occurrence of the value in + //the other list. If no corresponding value is found, test fails + foreach (const QString &value, list1) + { + if (!tempList.contains(value)) + return false; + + tempList.removeOne(value); + } + return true; +} + +QList CSVSettings::View::toStandardItemList + (const QStringList &list) const +{ + QList itemList; + + foreach (const QString &value, list) + itemList.append (new QStandardItem (value)); + + return itemList; +} + +void CSVSettings::View::updateView (bool signalUpdate) const +{ + if (signalUpdate) + emit viewUpdated(viewKey(), selectedValues()); +} + +QString CSVSettings::View::value (int row) const +{ + if (row > -1 && row < mDataModel->rowCount()) + return mDataModel->data (mDataModel->index(row, 0)).toString(); + + return QString(); +} + +int CSVSettings::View::widgetWidth(int characterCount) const +{ + QString widthToken = QString().fill ('m', characterCount); + QFontMetrics fm (QApplication::font()); + + return (fm.width (widthToken)); +} diff --git a/apps/opencs/view/settings/view.hpp b/apps/opencs/view/settings/view.hpp new file mode 100644 index 000000000..84ad62759 --- /dev/null +++ b/apps/opencs/view/settings/view.hpp @@ -0,0 +1,160 @@ +#ifndef CSVSETTINGS_VIEW_HPP +#define CSVSETTINGS_VIEW_HPP + +#include +#include + +#include "frame.hpp" +#include "../../model/settings/support.hpp" + +class QGroupBox; +class QStringList; +class QStandardItem; +class QItemSelection; +class QAbstractItemModel; +class QItemSelectionModel; + +namespace CSMSettings { class Setting; } + +namespace CSVSettings +{ + class Page; + + class View : public Frame + { + Q_OBJECT + + ///Pointer to the owning Page instance + Page *mParentPage; + + ///Pointer to the selection model for the view + QItemSelectionModel *mSelectionModel; + + ///Pointer to the data model for the view's selection model + QAbstractItemModel *mDataModel; + + ///State indicating whether or not the setting has a pre-defined list + ///of values, limiting possible definitions + bool mHasFixedValues; + + ///State indicating whether the view will allow multiple values + bool mIsMultiValue; + + ///'pagename.settingname' form of the view's id + QString mViewKey; + + ///indicates whether or not the setting is written to file + bool mSerializable; + + public: + + explicit View (CSMSettings::Setting *setting, Page *parent); + + ///Returns the index / row of the passed value, -1 if not found. + int currentIndex () const; + + ///Returns the number of rows in the view's data model + int rowCount() const; + + ///Returns bool indicating the data in this view should / should not + ///be serialized to a config file + bool serializable() const { return mSerializable; } + + ///Returns a pointer to the view's owning parent page + const Page *parentPage() const { return mParentPage; } + + ///Returns the selected items in the selection model as a QStringList + QStringList selectedValues() const; + + ///Sets the selected items in the selection model based on passed list. + ///Bools allow opt-out of updating the view + ///or signaling the view was updatedto avoid viscious cylcing. + void setSelectedValues (const QStringList &values, + bool updateView = true, + bool signalUpdate = true) const; + + void setSelectedValue (const QString &value, + bool updateView = true, + bool signalUpdate = true); + + + ///Returns the value of the data model at the specified row + QString value (int row) const; + + QString viewKey() const { return mViewKey; } + + protected: + + /// Returns the model which provides data for the selection model + QAbstractItemModel *dataModel() { return mDataModel; } + + ///Accessor function for subclasses + bool isMultiValue() { return mIsMultiValue; } + + ///Returns the view selection model + QItemSelectionModel *selectionModel() { return mSelectionModel;} + + ///Global callback for basic view initialization + void showEvent ( QShowEvent * event ); + + ///Virtual for updating a specific View subclass + ///bool indicates whether viewUpdated() signal is emitted + virtual void updateView (bool signalUpdate = true) const; + + ///Returns the pixel width corresponding to the specified number of + ///characters. + int widgetWidth(int characterCount) const; + + private: + + ///Constructs the view layout + void buildView(); + + ///Constructs the data and selection models + void buildModel (const CSMSettings::Setting *setting); + + ///In cases where the view has a pre-defined list of possible values, + ///a QStringListModel is created using those values. + ///View changes operate on the selection model only. + void buildFixedValueModel (const QStringList &definitions); + + ///In cases where the view does not have a pre-defined list of possible + ///values, a QStandardItemModel is created, containing the actual + ///setting definitions. View changes first update the data in the + ///model to match the data in the view. The selection model always + ///selects all values. + void buildUpdatableValueModel (const QStringList &definitions); + + ///Refreshes the view + void refresh() const; + + ///Convenince function for selection model's select() method. Also + ///clears out the model beforehand to ensure complete selection. + void select (const QItemSelection &selection) const; + + ///Compares two string lists "loosely", ensuring that all values in + ///one list are contained entirely in the other, and that neither list + ///has more values than the other. List order is not considered. + bool stringListsMatch (const QStringList &list1, + const QStringList &list2) const; + + ///Converts a string list to a list of QStandardItem pointers. + QList toStandardItemList(const QStringList &) const; + + signals: + + ///Signals that the view has been changed. + void viewUpdated(const QString &, const QStringList &) const; + + }; + + class IViewFactory + { + public: + + ///Creation interface for view factories + virtual View *createView (CSMSettings::Setting *setting, + Page *parent) = 0; + }; +} +#endif // CSVSETTINGS_VIEW_HPP diff --git a/apps/opencs/view/settings/windowpage.cpp b/apps/opencs/view/settings/windowpage.cpp deleted file mode 100644 index ae42623b7..000000000 --- a/apps/opencs/view/settings/windowpage.cpp +++ /dev/null @@ -1,144 +0,0 @@ -#include "windowpage.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef Q_OS_MAC -#include -#endif - -#include "../../model/settings/usersettings.hpp" -#include "groupblock.hpp" -#include "toggleblock.hpp" -#include "../../view/settings/abstractblock.hpp" - -CSVSettings::WindowPage::WindowPage(QWidget *parent): - AbstractPage("Window Size", parent) -{ - // Hacks to get the stylesheet look properly -#ifdef Q_OS_MAC - QPlastiqueStyle *style = new QPlastiqueStyle; - //profilesComboBox->setStyle(style); -#endif - - setupUi(); -} - -CSVSettings::GroupBlockDef * CSVSettings::WindowPage::buildDefinedWindowSize() -{ - GroupBlockDef *block = new GroupBlockDef ( "Defined Size"); - - SettingsItemDef *widthByHeightItem = new SettingsItemDef ("Window Size", "640x480"); - WidgetDef widthByHeightWidget = WidgetDef (Widget_ComboBox); - widthByHeightWidget.widgetWidth = 90; - *(widthByHeightItem->valueList) << "640x480" << "800x600" << "1024x768" << "1440x900"; - - QStringList *widthProxy = new QStringList; - QStringList *heightProxy = new QStringList; - - (*widthProxy) << "Width" << "640" << "800" << "1024" << "1440"; - (*heightProxy) << "Height" << "480" << "600" << "768" << "900"; - - *(widthByHeightItem->proxyList) << widthProxy << heightProxy; - - widthByHeightItem->widget = widthByHeightWidget; - - block->settingItems << widthByHeightItem; - block->isProxy = true; - block->isVisible = false; - - return block; -} - -CSVSettings::GroupBlockDef *CSVSettings::WindowPage::buildCustomWindowSize() -{ - GroupBlockDef *block = new GroupBlockDef ("Custom Size"); - - //custom width - SettingsItemDef *widthItem = new SettingsItemDef ("Width", "640"); - widthItem->widget = WidgetDef (Widget_LineEdit); - widthItem->widget.widgetWidth = 45; - widthItem->widget.inputMask = "9999"; - - //custom height - SettingsItemDef *heightItem = new SettingsItemDef ("Height", "480"); - heightItem->widget = WidgetDef (Widget_LineEdit); - heightItem->widget.widgetWidth = 45; - heightItem->widget.caption = "x"; - heightItem->widget.inputMask = "9999"; - - block->settingItems << widthItem << heightItem; - block->widgetOrientation = Orient_Horizontal; - block->isVisible = false; - - return block; -} - -CSVSettings::GroupBlockDef *CSVSettings::WindowPage::buildWindowSizeToggle() -{ - GroupBlockDef *block = new GroupBlockDef (objectName()); - - // window size toggle - block->captions << "Pre-Defined" << "Custom"; - block->widgetOrientation = Orient_Vertical; - block->isVisible = false; - - //define a widget for each group in the toggle - for (int i = 0; i < 2; i++) - block->widgets << new WidgetDef (Widget_RadioButton); - - block->widgets.at(0)->isDefault = false; - - return block; -} - -CSVSettings::CustomBlockDef *CSVSettings::WindowPage::buildWindowSize(GroupBlockDef *toggle_def, - GroupBlockDef *defined_def, - GroupBlockDef *custom_def) -{ - CustomBlockDef *block = new CustomBlockDef(QString ("Window Size")); - - block->blockDefList << toggle_def << defined_def << custom_def; - block->defaultValue = "Custom"; - - return block; - -} - -void CSVSettings::WindowPage::setupUi() -{ - CustomBlockDef *windowSize = buildWindowSize(buildWindowSizeToggle(), - buildDefinedWindowSize(), - buildCustomWindowSize() - ); - - mAbstractBlocks << buildBlock (windowSize); - - foreach (AbstractBlock *block, mAbstractBlocks) - { - connect (block, SIGNAL (signalUpdateSetting (const QString &, const QString &)), - this, SIGNAL (signalUpdateEditorSetting (const QString &, const QString &)) ); - } - - connect ( this, - SIGNAL ( signalUpdateEditorSetting (const QString &, const QString &)), - &(CSMSettings::UserSettings::instance()), - SIGNAL ( signalUpdateEditorSetting (const QString &, const QString &))); - -} - - -void CSVSettings::WindowPage::initializeWidgets (const CSMSettings::SettingMap &settings) -{ - //iterate each item in each blocks in this section - //validate the corresponding setting against the defined valuelist if any. - for (AbstractBlockList::Iterator it_block = mAbstractBlocks.begin(); - it_block != mAbstractBlocks.end(); ++it_block) - (*it_block)->updateSettings (settings); -} diff --git a/apps/opencs/view/settings/windowpage.hpp b/apps/opencs/view/settings/windowpage.hpp deleted file mode 100644 index 2f2830625..000000000 --- a/apps/opencs/view/settings/windowpage.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef WINDOWPAGE_H -#define WINDOWPAGE_H - -#include "abstractpage.hpp" - -class QGroupBox; - -namespace CSVSettings { - - class UserSettings; - class AbstractBlock; - - class WindowPage : public AbstractPage - { - Q_OBJECT - - public: - - WindowPage(QWidget *parent = 0); - - void setupUi(); - void initializeWidgets (const CSMSettings::SettingMap &settings); - - /// - GroupBlockDef *buildCustomWindowSize(); - GroupBlockDef *buildDefinedWindowSize(); - GroupBlockDef *buildWindowSizeToggle(); - CustomBlockDef *buildWindowSize (GroupBlockDef *, GroupBlockDef *, GroupBlockDef *); - - signals: - void signalUpdateEditorSetting (const QString &settingName, const QString &settingValue); - }; -} -#endif //WINDOWPAGE_H diff --git a/apps/opencs/view/tools/reportsubview.cpp b/apps/opencs/view/tools/reportsubview.cpp index d59f0c234..e84f5cf4b 100644 --- a/apps/opencs/view/tools/reportsubview.cpp +++ b/apps/opencs/view/tools/reportsubview.cpp @@ -33,12 +33,13 @@ void CSVTools::ReportSubView::setEditLock (bool locked) // ignored. We don't change document state anyway. } -void CSVTools::ReportSubView::updateEditorSetting (const QString& key, const QString& value) +void CSVTools::ReportSubView::updateUserSetting + (const QString &name, const QStringList &list) { - mIdTypeDelegate->updateEditorSetting (key, value); + mIdTypeDelegate->updateUserSetting (name, list); } void CSVTools::ReportSubView::show (const QModelIndex& index) { focusId (mModel->getUniversalId (index.row()), ""); -} \ No newline at end of file +} diff --git a/apps/opencs/view/tools/reportsubview.hpp b/apps/opencs/view/tools/reportsubview.hpp index 6503ebd27..9f6a4c1da 100644 --- a/apps/opencs/view/tools/reportsubview.hpp +++ b/apps/opencs/view/tools/reportsubview.hpp @@ -39,7 +39,8 @@ namespace CSVTools virtual void setEditLock (bool locked); - virtual void updateEditorSetting (const QString&, const QString&); + virtual void updateUserSetting + (const QString &, const QStringList &); private slots: @@ -47,4 +48,4 @@ namespace CSVTools }; } -#endif \ No newline at end of file +#endif diff --git a/apps/opencs/view/tools/subviews.cpp b/apps/opencs/view/tools/subviews.cpp index 781cf602e..8b04aca50 100644 --- a/apps/opencs/view/tools/subviews.cpp +++ b/apps/opencs/view/tools/subviews.cpp @@ -9,4 +9,6 @@ void CSVTools::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) { manager.add (CSMWorld::UniversalId::Type_VerificationResults, new CSVDoc::SubViewFactory); + manager.add (CSMWorld::UniversalId::Type_LoadErrorLog, + new CSVDoc::SubViewFactory); } \ No newline at end of file diff --git a/apps/opencs/view/world/datadisplaydelegate.cpp b/apps/opencs/view/world/datadisplaydelegate.cpp old mode 100755 new mode 100644 index d838395f6..31ec18d52 --- a/apps/opencs/view/world/datadisplaydelegate.cpp +++ b/apps/opencs/view/world/datadisplaydelegate.cpp @@ -1,21 +1,32 @@ #include "datadisplaydelegate.hpp" +#include "../../model/settings/usersettings.hpp" + #include #include CSVWorld::DataDisplayDelegate::DataDisplayDelegate(const ValueList &values, const IconList &icons, - QUndoStack &undoStack, QObject *parent) - : EnumDelegate (values, undoStack, parent), mDisplayMode (Mode_TextOnly), mIcons (icons) - , mIconSize (QSize(16, 16)), mIconLeftOffset(3), mTextLeftOffset(8) + QUndoStack &undoStack, + const QString &pageName, + const QString &settingName, + QObject *parent) + : EnumDelegate (values, undoStack, parent), mDisplayMode (Mode_TextOnly), + mIcons (icons), mIconSize (QSize(16, 16)), mIconLeftOffset(3), + mTextLeftOffset(8), mSettingKey (pageName + '/' + settingName) { mTextAlignment.setAlignment (Qt::AlignLeft | Qt::AlignVCenter ); buildPixmaps(); + + QString value = + CSMSettings::UserSettings::instance().settingValue (mSettingKey); + + updateDisplayMode(value); } void CSVWorld::DataDisplayDelegate::buildPixmaps () { - if (mPixmaps.size() > 0) + if (!mPixmaps.empty()) mPixmaps.clear(); IconList::iterator it = mIcons.begin(); @@ -23,7 +34,7 @@ void CSVWorld::DataDisplayDelegate::buildPixmaps () while (it != mIcons.end()) { mPixmaps.push_back (std::make_pair (it->first, it->second.pixmap (mIconSize) ) ); - it++; + ++it; } } @@ -89,6 +100,30 @@ void CSVWorld::DataDisplayDelegate::paintIcon (QPainter *painter, const QStyleOp painter->drawPixmap (iconRect, mPixmaps.at(index).second); } +void CSVWorld::DataDisplayDelegate::updateUserSetting (const QString &name, + const QStringList &list) +{ + if (list.isEmpty()) + return; + + QString value = list.at(0); + + if (name == mSettingKey) + updateDisplayMode (value); +} + +void CSVWorld::DataDisplayDelegate::updateDisplayMode (const QString &mode) +{ + if (mode == "Icon and Text") + mDisplayMode = Mode_IconAndText; + + else if (mode == "Icon Only") + mDisplayMode = Mode_IconOnly; + + else if (mode == "Text Only") + mDisplayMode = Mode_TextOnly; +} + CSVWorld::DataDisplayDelegate::~DataDisplayDelegate() { mIcons.clear(); @@ -106,5 +141,7 @@ CSVWorld::CommandDelegate *CSVWorld::DataDisplayDelegateFactory::makeDelegate (Q QObject *parent) const { - return new DataDisplayDelegate (mValues, mIcons, undoStack, parent); + return new DataDisplayDelegate (mValues, mIcons, undoStack, "", "", parent); } + + diff --git a/apps/opencs/view/world/datadisplaydelegate.hpp b/apps/opencs/view/world/datadisplaydelegate.hpp index d23b86631..ef453c58f 100755 --- a/apps/opencs/view/world/datadisplaydelegate.hpp +++ b/apps/opencs/view/world/datadisplaydelegate.hpp @@ -35,10 +35,15 @@ namespace CSVWorld int mIconLeftOffset; int mTextLeftOffset; + QString mSettingKey; + public: explicit DataDisplayDelegate (const ValueList & values, const IconList & icons, - QUndoStack& undoStack, QObject *parent); + QUndoStack& undoStack, + const QString &pageName, + const QString &settingName, + QObject *parent); ~DataDisplayDelegate(); @@ -53,8 +58,14 @@ namespace CSVWorld /// offset the horizontal position of the text from the right edge of the icon. Default is 8 pixels. void setTextLeftOffset (int offset); + ///update the display mode for the delegate + void updateUserSetting (const QString &name, const QStringList &list); + private: + /// update the display mode based on a passed string + void updateDisplayMode (const QString &); + /// custom paint function for painting the icon. Mode_IconAndText and Mode_Icon only. void paintIcon (QPainter *painter, const QStyleOptionViewItem &option, int i) const; diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index abdc33103..d03bf3f80 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -183,7 +183,7 @@ CSVWorld::CommandDelegate* CSVWorld::DialogueDelegateDispatcher::makeDelegate(CS { delegate = CommandDelegateFactoryCollection::get().makeDelegate ( display, mUndoStack, mParent); - mDelegates.insert(std::make_pair(display, delegate)); + mDelegates.insert(std::make_pair(display, delegate)); } else { delegate = delegateIt->second; diff --git a/apps/opencs/view/world/dragrecordtable.cpp b/apps/opencs/view/world/dragrecordtable.cpp new file mode 100644 index 000000000..c33fa58ad --- /dev/null +++ b/apps/opencs/view/world/dragrecordtable.cpp @@ -0,0 +1,38 @@ +#include + +#include "../../model/world/tablemimedata.hpp" +#include "dragrecordtable.hpp" + +void CSVWorld::DragRecordTable::startDrag (const CSVWorld::DragRecordTable& table) +{ + CSMWorld::TableMimeData* mime = new CSMWorld::TableMimeData (table.getDraggedRecords(), mDocument); + + if (mime) + { + QDrag* drag = new QDrag (this); + drag->setMimeData (mime); + drag->setPixmap (QString::fromUtf8 (mime->getIcon().c_str())); + drag->exec (Qt::CopyAction); + } +} + +CSVWorld::DragRecordTable::DragRecordTable (CSMDoc::Document& document, QWidget* parent) : +mDocument(document), +QTableView(parent), +mEditLock(false) +{} + +void CSVWorld::DragRecordTable::setEditLock (bool locked) +{ + mEditLock = locked; +} + +void CSVWorld::DragRecordTable::dragEnterEvent(QDragEnterEvent *event) +{ + event->acceptProposedAction(); +} + +void CSVWorld::DragRecordTable::dragMoveEvent(QDragMoveEvent *event) +{ + event->accept(); +} diff --git a/apps/opencs/view/world/dragrecordtable.hpp b/apps/opencs/view/world/dragrecordtable.hpp new file mode 100644 index 000000000..8c5f1b841 --- /dev/null +++ b/apps/opencs/view/world/dragrecordtable.hpp @@ -0,0 +1,45 @@ +#ifndef CSV_WORLD_DRAGRECORDTABLE_H +#define CSV_WORLD_DRAGRECORDTABLE_H + +#include +#include + +class QWidget; +class QAction; + +namespace CSMDoc +{ + class Document; +} + +namespace CSMWorld +{ + class UniversalId; +} + +namespace CSVWorld +{ + class DragRecordTable : public QTableView + { + protected: + CSMDoc::Document& mDocument; + bool mEditLock; + + public: + DragRecordTable(CSMDoc::Document& document, QWidget* parent = NULL); + + virtual std::vector getDraggedRecords() const = 0; + + void setEditLock(bool locked); + + protected: + void startDrag(const DragRecordTable& table); + + void dragEnterEvent(QDragEnterEvent *event); + + void dragMoveEvent(QDragMoveEvent *event); + }; +} + +#endif + diff --git a/apps/opencs/view/world/idtypedelegate.cpp b/apps/opencs/view/world/idtypedelegate.cpp index ce4e8f014..6b4d442f3 100755 --- a/apps/opencs/view/world/idtypedelegate.cpp +++ b/apps/opencs/view/world/idtypedelegate.cpp @@ -4,30 +4,11 @@ CSVWorld::IdTypeDelegate::IdTypeDelegate (const ValueList &values, const IconList &icons, QUndoStack& undoStack, QObject *parent) - : DataDisplayDelegate (values, icons, undoStack, parent) + : DataDisplayDelegate (values, icons, undoStack, + "Display Format", "Referenceable ID Type Display", + parent) {} -bool CSVWorld::IdTypeDelegate::updateEditorSetting (const QString &settingName, const QString &settingValue) -{ - /// \todo make the setting key a member variable, that is initialised from a constructor argument - if (settingName == "Referenceable ID Type Display") - { - if (settingValue == "Icon and Text") - mDisplayMode = Mode_IconAndText; - - else if (settingValue == "Icon Only") - mDisplayMode = Mode_IconOnly; - - else if (settingValue == "Text Only") - mDisplayMode = Mode_TextOnly; - - return true; - } - - return false; -} - - CSVWorld::IdTypeDelegateFactory::IdTypeDelegateFactory() { for (int i=0; i enums = diff --git a/apps/opencs/view/world/recordstatusdelegate.hpp b/apps/opencs/view/world/recordstatusdelegate.hpp index d9126fee0..1b42223af 100644 --- a/apps/opencs/view/world/recordstatusdelegate.hpp +++ b/apps/opencs/view/world/recordstatusdelegate.hpp @@ -20,9 +20,6 @@ namespace CSVWorld explicit RecordStatusDelegate(const ValueList& values, const IconList& icons, QUndoStack& undoStack, QObject *parent = 0); - - virtual bool updateEditorSetting (const QString &settingName, const QString &settingValue); - }; class RecordStatusDelegateFactory : public DataDisplayDelegateFactory diff --git a/apps/opencs/view/world/regionmap.cpp b/apps/opencs/view/world/regionmap.cpp index 738de89ae..849a1988a 100644 --- a/apps/opencs/view/world/regionmap.cpp +++ b/apps/opencs/view/world/regionmap.cpp @@ -17,6 +17,7 @@ #include "../../model/world/idtable.hpp" #include "../../model/world/commands.hpp" #include "../../model/world/columns.hpp" +#include "../../model/world/tablemimedata.hpp" void CSVWorld::RegionMap::contextMenuEvent (QContextMenuEvent *event) { @@ -180,7 +181,7 @@ void CSVWorld::RegionMap::setRegion (const std::string& regionId) CSVWorld::RegionMap::RegionMap (const CSMWorld::UniversalId& universalId, CSMDoc::Document& document, QWidget *parent) -: QTableView (parent), mEditLock (false), mDocument (document) +: DragRecordTable(document, parent) { verticalHeader()->hide(); horizontalHeader()->hide(); @@ -223,11 +224,8 @@ CSVWorld::RegionMap::RegionMap (const CSMWorld::UniversalId& universalId, mViewInTableAction = new QAction (tr ("View Cells in Table"), this); connect (mViewInTableAction, SIGNAL (triggered()), this, SLOT (viewInTable())); addAction (mViewInTableAction); -} -void CSVWorld::RegionMap::setEditLock (bool locked) -{ - mEditLock = locked; + setAcceptDrops(true); } void CSVWorld::RegionMap::selectAll() @@ -343,4 +341,65 @@ void CSVWorld::RegionMap::viewInTable() hint << ")"; emit editRequest (CSMWorld::UniversalId::Type_Cells, hint.str()); +} + +void CSVWorld::RegionMap::mouseMoveEvent (QMouseEvent* event) +{ + startDrag(*this); +} + +std::vector< CSMWorld::UniversalId > CSVWorld::RegionMap::getDraggedRecords() const +{ + QModelIndexList selected(getSelectedCells(true, false)); + std::vector ids; + foreach (QModelIndex it, selected) + { + ids.push_back( + CSMWorld::UniversalId( + CSMWorld::UniversalId::Type_Cell, + model()->data(it, CSMWorld::RegionMap::Role_CellId).toString().toUtf8().constData())); + } + selected = getSelectedCells(false, true); + foreach (QModelIndex it, selected) + { + ids.push_back( + CSMWorld::UniversalId( + CSMWorld::UniversalId::Type_Cell_Missing, + model()->data(it, CSMWorld::RegionMap::Role_CellId).toString().toUtf8().constData())); + } + return ids; +} + +void CSVWorld::RegionMap::dropEvent (QDropEvent* event) +{ + QModelIndex index = indexAt (event->pos()); + + bool exists = QTableView::model()->data(index, Qt::BackgroundRole)!=QBrush (Qt::DiagCrossPattern); + + if (!index.isValid() || !exists) + { + return; + } + + const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); + if (mime->fromDocument(mDocument) && mime->holdsType(CSMWorld::UniversalId::Type_Region)) + { + CSMWorld::UniversalId record (mime->returnMatching (CSMWorld::UniversalId::Type_Region)); + + QAbstractItemModel *regionModel = model(); + + CSMWorld::IdTable *cellsModel = &dynamic_cast (* + mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); + + std::string cellId(regionModel->data (index, CSMWorld::RegionMap::Role_CellId). + toString().toUtf8().constData()); + + QModelIndex index2(cellsModel->getModelIndex (cellId, + cellsModel->findColumnIndex (CSMWorld::Columns::ColumnId_Region))); + + mDocument.getUndoStack().push(new CSMWorld::ModifyCommand + (*cellsModel, index2, QString::fromUtf8(record.getId().c_str()))); + + mRegionId = record.getId(); + } } \ No newline at end of file diff --git a/apps/opencs/view/world/regionmap.hpp b/apps/opencs/view/world/regionmap.hpp index c3757fe45..0097a16dc 100644 --- a/apps/opencs/view/world/regionmap.hpp +++ b/apps/opencs/view/world/regionmap.hpp @@ -1,8 +1,14 @@ #ifndef CSV_WORLD_REGIONMAP_H #define CSV_WORLD_REGIONMAP_H +#include +#include + +#include #include +#include "./dragrecordtable.hpp" + class QAction; namespace CSMDoc @@ -17,7 +23,7 @@ namespace CSMWorld namespace CSVWorld { - class RegionMap : public QTableView + class RegionMap : public DragRecordTable { Q_OBJECT @@ -29,8 +35,6 @@ namespace CSVWorld QAction *mUnsetRegionAction; QAction *mViewAction; QAction *mViewInTableAction; - bool mEditLock; - CSMDoc::Document& mDocument; std::string mRegionId; private: @@ -50,12 +54,16 @@ namespace CSVWorld void setRegion (const std::string& regionId); ///< Set region Id of selected cells. + void mouseMoveEvent(QMouseEvent *event); + + void dropEvent(QDropEvent* event); + public: RegionMap (const CSMWorld::UniversalId& universalId, CSMDoc::Document& document, QWidget *parent = 0); - void setEditLock (bool locked); + virtual std::vector getDraggedRecords() const; signals: diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index 0bb11ce8c..36cce9ecd 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "../../model/doc/document.hpp" @@ -18,11 +19,10 @@ #include "tablebottombox.hpp" #include "creator.hpp" -#include "scenetoolbar.hpp" #include "scenetoolmode.hpp" CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) -: SubView (id) +: SubView (id), mLayout(new QHBoxLayout), mDocument(document), mScene(NULL), mToolbar(NULL) { QVBoxLayout *layout = new QVBoxLayout; @@ -32,33 +32,35 @@ CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::D new TableBottomBox (NullCreatorFactory(), document.getData(), document.getUndoStack(), id, this), 0); - QHBoxLayout *layout2 = new QHBoxLayout; + mLayout->setContentsMargins (QMargins (0, 0, 0, 0)); - layout2->setContentsMargins (QMargins (0, 0, 0, 0)); - - SceneToolbar *toolbar = new SceneToolbar (48+6, this); + CSVRender::WorldspaceWidget* wordspaceWidget = NULL; + widgetType whatWidget; if (id.getId()=="sys::default") { - CSVRender::PagedWorldspaceWidget *widget = new CSVRender::PagedWorldspaceWidget (this); - mScene = widget; - connect (widget, SIGNAL (cellSelectionChanged (const CSMWorld::CellSelection&)), - this, SLOT (cellSelectionChanged (const CSMWorld::CellSelection&))); + whatWidget = widget_Paged; + + CSVRender::PagedWorldspaceWidget *newWidget = new CSVRender::PagedWorldspaceWidget (this, document); + + wordspaceWidget = newWidget; + + makeConnections(newWidget); } else - mScene = new CSVRender::UnpagedWorldspaceWidget (id.getId(), document, this); + { + whatWidget = widget_Unpaged; - SceneToolMode *navigationTool = mScene->makeNavigationSelector (toolbar); - toolbar->addTool (navigationTool); + CSVRender::UnpagedWorldspaceWidget *newWidget = new CSVRender::UnpagedWorldspaceWidget (id.getId(), document, this); - SceneToolMode *lightingTool = mScene->makeLightingSelector (toolbar); - toolbar->addTool (lightingTool); + wordspaceWidget = newWidget; - layout2->addWidget (toolbar, 0); + makeConnections(newWidget); + } - layout2->addWidget (mScene, 1); + replaceToolbarAndWorldspace(wordspaceWidget, makeToolbar(wordspaceWidget, whatWidget)); - layout->insertLayout (0, layout2, 1); + layout->insertLayout (0, mLayout, 1); CSVFilter::FilterBox *filterBox = new CSVFilter::FilterBox (document.getData(), this); @@ -69,10 +71,53 @@ CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::D widget->setLayout (layout); setWidget (widget); +} - mScene->selectDefaultNavigationMode(); +void CSVWorld::SceneSubView::makeConnections (CSVRender::UnpagedWorldspaceWidget* widget) +{ + connect (widget, SIGNAL (closeRequest()), this, SLOT (closeRequest())); + + connect(widget, SIGNAL(dataDropped(const std::vector&)), + this, SLOT(handleDrop(const std::vector&))); + + connect(widget, SIGNAL(cellChanged(const CSMWorld::UniversalId&)), + this, SLOT(cellSelectionChanged(const CSMWorld::UniversalId&))); +} + +void CSVWorld::SceneSubView::makeConnections (CSVRender::PagedWorldspaceWidget* widget) +{ + connect (widget, SIGNAL (closeRequest()), this, SLOT (closeRequest())); + + connect(widget, SIGNAL(dataDropped(const std::vector&)), + this, SLOT(handleDrop(const std::vector&))); + + connect (widget, SIGNAL (cellSelectionChanged (const CSMWorld::CellSelection&)), + this, SLOT (cellSelectionChanged (const CSMWorld::CellSelection&))); +} + +CSVWorld::SceneToolbar* CSVWorld::SceneSubView::makeToolbar (CSVRender::WorldspaceWidget* widget, widgetType type) +{ + CSVWorld::SceneToolbar* toolbar = new SceneToolbar (48+6, this); + + SceneToolMode *navigationTool = widget->makeNavigationSelector (toolbar); + toolbar->addTool (navigationTool); + + SceneToolMode *lightingTool = widget->makeLightingSelector (toolbar); + toolbar->addTool (lightingTool); + +/* Add buttons specific to the type. For now no need for it. + * + switch (type) + { + case widget_Paged: + break; + + case widget_Unpaged: + break; - connect (mScene, SIGNAL (closeRequest()), this, SLOT (closeRequest())); + } +*/ + return toolbar; } void CSVWorld::SceneSubView::setEditLock (bool locked) @@ -102,8 +147,19 @@ void CSVWorld::SceneSubView::closeRequest() deleteLater(); } +void CSVWorld::SceneSubView::cellSelectionChanged (const CSMWorld::UniversalId& id) +{ + setUniversalId(id); + std::ostringstream stream; + stream << "Scene: " << getUniversalId().getId(); + + setWindowTitle (QString::fromUtf8 (stream.str().c_str())); +} + + void CSVWorld::SceneSubView::cellSelectionChanged (const CSMWorld::CellSelection& selection) { + setUniversalId(CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Scene, "sys::default")); int size = selection.getSize(); std::ostringstream stream; @@ -126,4 +182,62 @@ void CSVWorld::SceneSubView::cellSelectionChanged (const CSMWorld::CellSelection } setWindowTitle (QString::fromUtf8 (stream.str().c_str())); +} + +void CSVWorld::SceneSubView::handleDrop (const std::vector< CSMWorld::UniversalId >& data) +{ + CSVRender::PagedWorldspaceWidget* pagedNewWidget = NULL; + CSVRender::UnpagedWorldspaceWidget* unPagedNewWidget = NULL; + SceneToolbar* toolbar = NULL; + + switch (mScene->getDropRequirements(CSVRender::WorldspaceWidget::getDropType(data))) + { + case CSVRender::WorldspaceWidget::canHandle: + mScene->handleDrop(data); + break; + + case CSVRender::WorldspaceWidget::needPaged: + pagedNewWidget = new CSVRender::PagedWorldspaceWidget(this, mDocument); + toolbar = makeToolbar(pagedNewWidget, widget_Paged); + makeConnections(pagedNewWidget); + replaceToolbarAndWorldspace(pagedNewWidget, toolbar); + mScene->handleDrop(data); + break; + + case CSVRender::WorldspaceWidget::needUnpaged: + unPagedNewWidget = new CSVRender::UnpagedWorldspaceWidget(data.begin()->getId(), mDocument, this); + toolbar = makeToolbar(unPagedNewWidget, widget_Unpaged); + makeConnections(unPagedNewWidget); + replaceToolbarAndWorldspace(unPagedNewWidget, toolbar); + cellSelectionChanged(*(data.begin())); + break; + + case CSVRender::WorldspaceWidget::ignored: + return; + } +} + +void CSVWorld::SceneSubView::replaceToolbarAndWorldspace (CSVRender::WorldspaceWidget* widget, CSVWorld::SceneToolbar* toolbar) +{ + assert(mLayout); + + if (mScene) + { + mLayout->removeWidget(mScene); + mScene->deleteLater(); + } + + if (mToolbar) + { + mLayout->removeWidget(mToolbar); + mToolbar->deleteLater(); + } + + mScene = widget; + mToolbar = toolbar; + + mLayout->addWidget (mToolbar, 0); + mLayout->addWidget (mScene, 1); + + mScene->selectDefaultNavigationMode(); } \ No newline at end of file diff --git a/apps/opencs/view/world/scenesubview.hpp b/apps/opencs/view/world/scenesubview.hpp index 0b15ea541..b9ecbe931 100644 --- a/apps/opencs/view/world/scenesubview.hpp +++ b/apps/opencs/view/world/scenesubview.hpp @@ -1,7 +1,10 @@ #ifndef CSV_WORLD_SCENESUBVIEW_H #define CSV_WORLD_SCENESUBVIEW_H +#include + #include "../doc/subview.hpp" +#include "scenetoolbar.hpp" class QModelIndex; @@ -18,6 +21,8 @@ namespace CSMDoc namespace CSVRender { class WorldspaceWidget; + class PagedWorldspaceWidget; + class UnpagedWorldspaceWidget; } namespace CSVWorld @@ -32,6 +37,9 @@ namespace CSVWorld TableBottomBox *mBottom; CSVRender::WorldspaceWidget *mScene; + QHBoxLayout* mLayout; + CSMDoc::Document& mDocument; + SceneToolbar* mToolbar; public: @@ -45,11 +53,30 @@ namespace CSVWorld virtual void useHint (const std::string& hint); + private: + + void makeConnections(CSVRender::PagedWorldspaceWidget* widget); + + void makeConnections(CSVRender::UnpagedWorldspaceWidget* widget); + + void replaceToolbarAndWorldspace(CSVRender::WorldspaceWidget* widget, SceneToolbar* toolbar); + + enum widgetType + { + widget_Paged, + widget_Unpaged + }; + SceneToolbar* makeToolbar(CSVRender::WorldspaceWidget* widget, widgetType type); + private slots: void closeRequest(); void cellSelectionChanged (const CSMWorld::CellSelection& selection); + + void cellSelectionChanged (const CSMWorld::UniversalId& id); + + void handleDrop(const std::vector& data); }; } diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 712b8f556..3d4b02c9c 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -188,8 +188,8 @@ std::vector CSVWorld::Table::listDeletableSelectedIds() const CSVWorld::Table::Table (const CSMWorld::UniversalId& id, bool createAndDelete, bool sorting, CSMDoc::Document& document) -: mCreateAction (0), mCloneAction(0), mEditLock (false), mRecordStatusDisplay (0), - mDocument (document) +: mCreateAction (0), mCloneAction(0), mRecordStatusDisplay (0), + DragRecordTable(document) { mModel = &dynamic_cast (*mDocument.getData().getTableModel (id)); @@ -282,7 +282,7 @@ void CSVWorld::Table::setEditLock (bool locked) for (std::vector::iterator iter (mDelegates.begin()); iter!=mDelegates.end(); ++iter) (*iter)->setEditLock (locked); - mEditLock = locked; + DragRecordTable::setEditLock(locked); } CSMWorld::UniversalId CSVWorld::Table::getUniversalId (int row) const @@ -298,7 +298,7 @@ void CSVWorld::Table::revertRecord() { std::vector revertableIds = listRevertableSelectedIds(); - if (revertableIds.size()>0) + if (!revertableIds.empty()) { if (revertableIds.size()>1) mDocument.getUndoStack().beginMacro (tr ("Revert multiple records")); @@ -318,7 +318,7 @@ void CSVWorld::Table::deleteRecord() { std::vector deletableIds = listDeletableSelectedIds(); - if (deletableIds.size()>0) + if (!deletableIds.empty()) { if (deletableIds.size()>1) mDocument.getUndoStack().beginMacro (tr ("Delete multiple records")); @@ -449,15 +449,21 @@ void CSVWorld::Table::previewRecord() } } -void CSVWorld::Table::updateEditorSetting (const QString &settingName, const QString &settingValue) +void CSVWorld::Table::updateUserSetting + (const QString &name, const QStringList &list) { int columns = mModel->columnCount(); for (int i=0; i (*delegate). - updateEditorSetting (settingName, settingValue)) - emit dataChanged (mModel->index (0, i), mModel->index (mModel->rowCount()-1, i)); + { + dynamic_cast + (*delegate).updateUserSetting (name, list); + { + emit dataChanged (mModel->index (0, i), + mModel->index (mModel->rowCount()-1, i)); + } + } } void CSVWorld::Table::tableSizeUpdate() @@ -512,42 +518,8 @@ void CSVWorld::Table::mouseMoveEvent (QMouseEvent* event) { if (event->buttons() & Qt::LeftButton) { - QModelIndexList selectedRows = selectionModel()->selectedRows(); - - if (selectedRows.size() == 0) - { - return; - } - - QDrag* drag = new QDrag (this); - CSMWorld::TableMimeData* mime = NULL; - - if (selectedRows.size() == 1) - { - mime = new CSMWorld::TableMimeData (getUniversalId (selectedRows.begin()->row()), mDocument); - } - else - { - std::vector idToDrag; - - foreach (QModelIndex it, selectedRows) //I had a dream. Dream where you could use C++11 in OpenMW. - { - idToDrag.push_back (getUniversalId (it.row())); - } - - mime = new CSMWorld::TableMimeData (idToDrag, mDocument); - } - - drag->setMimeData (mime); - drag->setPixmap (QString::fromUtf8 (mime->getIcon().c_str())); - drag->exec(Qt::CopyAction); + startDrag(*this); } - -} - -void CSVWorld::Table::dragEnterEvent(QDragEnterEvent *event) -{ - event->acceptProposedAction(); } void CSVWorld::Table::dropEvent(QDropEvent *event) @@ -577,11 +549,6 @@ void CSVWorld::Table::dropEvent(QDropEvent *event) } //TODO handle drops from different document } -void CSVWorld::Table::dragMoveEvent(QDragMoveEvent *event) -{ - event->accept(); -} - std::vector CSVWorld::Table::getColumnsWithDisplay(CSMWorld::ColumnBase::Display display) const { const int count = mModel->columnCount(); @@ -598,4 +565,19 @@ std::vector CSVWorld::Table::getColumnsWithDisplay(CSMWorld::Column } } return titles; -} \ No newline at end of file +} + +std::vector< CSMWorld::UniversalId > CSVWorld::Table::getDraggedRecords() const +{ + + QModelIndexList selectedRows = selectionModel()->selectedRows(); + std::vector idToDrag; + + foreach (QModelIndex it, selectedRows) //I had a dream. Dream where you could use C++11 in OpenMW. + { + idToDrag.push_back (getUniversalId (it.row())); + } + + return idToDrag; +} + diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp index 4231a4a43..3b1d40e78 100644 --- a/apps/opencs/view/world/table.hpp +++ b/apps/opencs/view/world/table.hpp @@ -4,11 +4,11 @@ #include #include -#include #include #include "../../model/filter/node.hpp" #include "../../model/world/columnbase.hpp" +#include "dragrecordtable.hpp" class QUndoStack; class QAction; @@ -31,7 +31,7 @@ namespace CSVWorld class CommandDelegate; ///< Table widget - class Table : public QTableView + class Table : public DragRecordTable { Q_OBJECT @@ -47,9 +47,7 @@ namespace CSVWorld QAction *mPreviewAction; CSMWorld::IdTableProxyModel *mProxyModel; CSMWorld::IdTable *mModel; - bool mEditLock; int mRecordStatusDisplay; - CSMDoc::Document& mDocument; private: @@ -61,10 +59,6 @@ namespace CSVWorld void mouseMoveEvent(QMouseEvent *event); - void dragEnterEvent(QDragEnterEvent *event); - - void dragMoveEvent(QDragMoveEvent *event); - void dropEvent(QDropEvent *event); public: @@ -74,14 +68,14 @@ namespace CSVWorld ///< \param createAndDelete Allow creation and deletion of records. /// \param sorting Allow changing order of rows in the view via column headers. - void setEditLock (bool locked); + virtual void setEditLock (bool locked); CSMWorld::UniversalId getUniversalId (int row) const; - void updateEditorSetting (const QString &settingName, const QString &settingValue); - std::vector getColumnsWithDisplay(CSMWorld::ColumnBase::Display display) const; + virtual std::vector getDraggedRecords() const; + signals: void editRequest (const CSMWorld::UniversalId& id, const std::string& hint); @@ -94,6 +88,7 @@ namespace CSVWorld /// \param modified Number of added and modified records void createRequest(); + void cloneRequest(const CSMWorld::UniversalId&); private slots: @@ -123,6 +118,8 @@ namespace CSVWorld void requestFocus (const std::string& id); void recordFilterChanged (boost::shared_ptr filter); + + void updateUserSetting (const QString &name, const QStringList &list); }; } diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp index a5a7e8252..a0bef366b 100644 --- a/apps/opencs/view/world/tablesubview.cpp +++ b/apps/opencs/view/world/tablesubview.cpp @@ -82,9 +82,10 @@ void CSVWorld::TableSubView::editRequest (const CSMWorld::UniversalId& id, const focusId (id, hint); } -void CSVWorld::TableSubView::updateEditorSetting(const QString &settingName, const QString &settingValue) +void CSVWorld::TableSubView::updateUserSetting + (const QString &name, const QStringList &list) { - mTable->updateEditorSetting(settingName, settingValue); + mTable->updateUserSetting(name, list); } void CSVWorld::TableSubView::setStatusBar (bool show) @@ -110,13 +111,17 @@ void CSVWorld::TableSubView::createFilterRequest (std::vector< CSMWorld::Univers { std::vector > > filterSource; - for (std::vector::iterator it = types.begin(); it != types.end(); ++it) + for (std::vector::iterator it(types.begin()); it != types.end(); ++it) { std::pair > pair( //splited long line - std::make_pair(it->getId(), mTable->getColumnsWithDisplay(CSMWorld::TableMimeData::convertEnums(it->getType())))); + std::make_pair(it->getId(), mTable->getColumnsWithDisplay(CSMWorld::TableMimeData::convertEnums(it->getType())))); - filterSource.push_back(pair); + if(!pair.second.empty()) + { + filterSource.push_back(pair); + } } + mFilterBox->createFilterRequest(filterSource, action); } @@ -134,4 +139,4 @@ bool CSVWorld::TableSubView::eventFilter (QObject* object, QEvent* event) return handled; } return false; -} \ No newline at end of file +} diff --git a/apps/opencs/view/world/tablesubview.hpp b/apps/opencs/view/world/tablesubview.hpp index b344ba1ad..9d86c32e4 100644 --- a/apps/opencs/view/world/tablesubview.hpp +++ b/apps/opencs/view/world/tablesubview.hpp @@ -43,7 +43,8 @@ namespace CSVWorld virtual void setEditLock (bool locked); - virtual void updateEditorSetting (const QString& key, const QString& value); + virtual void updateUserSetting + (const QString& name, const QStringList &list); virtual void setStatusBar (bool show); diff --git a/apps/opencs/view/world/util.cpp b/apps/opencs/view/world/util.cpp index b2a32b551..ea8a7c541 100644 --- a/apps/opencs/view/world/util.cpp +++ b/apps/opencs/view/world/util.cpp @@ -196,12 +196,6 @@ bool CSVWorld::CommandDelegate::isEditLocked() const return mEditLock; } -bool CSVWorld::CommandDelegate::updateEditorSetting (const QString &settingName, - const QString &settingValue) -{ - return false; -} - void CSVWorld::CommandDelegate::setEditorData (QWidget *editor, const QModelIndex& index, bool tryDisplay) const { QVariant v = index.data(Qt::EditRole); @@ -263,4 +257,4 @@ void CSVWorld::DropLineEdit::dropEvent(QDropEvent *event) const CSMWorld::TableMimeData* data(dynamic_cast(event->mimeData())); emit tableMimeDataDropped(data->getData(), data->getDocumentPtr()); //WIP -} \ No newline at end of file +} diff --git a/apps/opencs/view/world/util.hpp b/apps/opencs/view/world/util.hpp index 7664f3eae..1c7e37818 100644 --- a/apps/opencs/view/world/util.hpp +++ b/apps/opencs/view/world/util.hpp @@ -136,15 +136,15 @@ namespace CSVWorld bool isEditLocked() const; - virtual bool updateEditorSetting (const QString &settingName, const QString &settingValue); ///< \return Does column require update? virtual void setEditorData (QWidget *editor, const QModelIndex& index, bool tryDisplay = false) const; - private slots: + public slots: - virtual void slotUpdateEditorSetting (const QString &settingName, const QString &settingValue) {} + virtual void updateUserSetting + (const QString &name, const QStringList &list) {} }; } diff --git a/apps/opencs/view/world/vartypedelegate.cpp b/apps/opencs/view/world/vartypedelegate.cpp index 15ce2dbaf..fc00f4491 100644 --- a/apps/opencs/view/world/vartypedelegate.cpp +++ b/apps/opencs/view/world/vartypedelegate.cpp @@ -79,7 +79,7 @@ void CSVWorld::VarTypeDelegateFactory::add (ESM::VarType type) std::vector enums = CSMWorld::Columns::getEnums (CSMWorld::Columns::ColumnId_ValueType); - if (type<0 && type>=enums.size()) + if (static_cast(type) >= enums.size()) throw std::logic_error ("Unsupported variable type"); mValues.push_back (std::make_pair (type, QString::fromUtf8 (enums[type].c_str()))); diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 9fabb2080..8496b47a4 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -48,7 +48,7 @@ add_openmw_dir (mwscript ) add_openmw_dir (mwsound - soundmanagerimp openal_output audiere_decoder mpgsnd_decoder ffmpeg_decoder + soundmanagerimp openal_output ffmpeg_decoder sound ) add_openmw_dir (mwworld @@ -57,7 +57,7 @@ add_openmw_dir (mwworld cells localscripts customdata weather inventorystore ptr actionopen actionread actionequip timestamp actionalchemy cellstore actionapply actioneat esmstore store recordcmp fallback actionrepair actionsoulgem livecellref actiondoor - contentloader esmloader omwloader actiontrap cellreflist + contentloader esmloader omwloader actiontrap cellreflist projectilemanager cellref ) add_openmw_dir (mwclass @@ -67,9 +67,9 @@ add_openmw_dir (mwclass add_openmw_dir (mwmechanics mechanicsmanagerimp stat character creaturestats magiceffects movement actors objects - drawstate spells activespells npcstats aipackage aisequence aipersue alchemy aiwander aitravel aifollow + drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor aiescort aiactivate aicombat repair enchanting pathfinding pathgrid security spellsuccess spellcasting - disease pickpocket levelledlist combat steering + disease pickpocket levelledlist combat steering obstacle ) add_openmw_dir (mwstate @@ -83,6 +83,10 @@ add_openmw_dir (mwbase # Main executable set(BOOST_COMPONENTS system filesystem program_options thread wave) +if(WIN32) + set(BOOST_COMPONENTS ${BOOST_COMPONENTS} locale) +endif(WIN32) + find_package(Boost REQUIRED COMPONENTS ${BOOST_COMPONENTS}) IF(OGRE_STATIC) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 508b195e9..3647f8ccb 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -59,9 +59,6 @@ void OMW::Engine::executeLocalScripts() MWScript::InterpreterContext interpreterContext ( &script.second.getRefData().getLocals(), script.second); MWBase::Environment::get().getScriptManager()->run (script.first, interpreterContext); - - if (MWBase::Environment::get().getWorld()->hasCellChanged()) - break; } localScripts.setIgnore (MWWorld::Ptr()); @@ -69,9 +66,13 @@ void OMW::Engine::executeLocalScripts() bool OMW::Engine::frameStarted (const Ogre::FrameEvent& evt) { - bool paused = MWBase::Environment::get().getWindowManager()->isGuiMode(); - MWBase::Environment::get().getWorld()->frameStarted(evt.timeSinceLastFrame, paused); - MWBase::Environment::get().getWindowManager ()->frameStarted(evt.timeSinceLastFrame); + if (MWBase::Environment::get().getStateManager()->getState()!= + MWBase::StateManager::State_NoGame) + { + bool paused = MWBase::Environment::get().getWindowManager()->isGuiMode(); + MWBase::Environment::get().getWorld()->frameStarted(evt.timeSinceLastFrame, paused); + MWBase::Environment::get().getWindowManager ()->frameStarted(evt.timeSinceLastFrame); + } return true; } @@ -101,15 +102,10 @@ bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt) // global scripts MWBase::Environment::get().getScriptManager()->getGlobalScripts().run(); - bool changed = MWBase::Environment::get().getWorld()->hasCellChanged(); - // local scripts - executeLocalScripts(); // This does not handle the case where a global script causes a - // cell change, followed by a cell change in a local script during - // the same frame. + executeLocalScripts(); - if (changed) // keep change flag for another frame, if cell changed happened in local script - MWBase::Environment::get().getWorld()->markCellAsUnchanged(); + MWBase::Environment::get().getWorld()->markCellAsUnchanged(); if (!paused) MWBase::Environment::get().getWorld()->advanceTime( @@ -118,8 +114,12 @@ bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt) // update actors - MWBase::Environment::get().getMechanicsManager()->update(frametime, - paused); + if (MWBase::Environment::get().getStateManager()->getState()!= + MWBase::StateManager::State_NoGame) + { + MWBase::Environment::get().getMechanicsManager()->update(frametime, + paused); + } if (MWBase::Environment::get().getStateManager()->getState()== MWBase::StateManager::State_Running) @@ -130,16 +130,24 @@ bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt) } // update world - MWBase::Environment::get().getWorld()->update(frametime, paused); + if (MWBase::Environment::get().getStateManager()->getState()!= + MWBase::StateManager::State_NoGame) + { + MWBase::Environment::get().getWorld()->update(frametime, paused); + } // update GUI - Ogre::RenderWindow* window = mOgre->getWindow(); - unsigned int tri, batch; - MWBase::Environment::get().getWorld()->getTriangleBatchCount(tri, batch); - MWBase::Environment::get().getWindowManager()->wmUpdateFps(window->getLastFPS(), tri, batch); - MWBase::Environment::get().getWindowManager()->onFrame(frametime); - MWBase::Environment::get().getWindowManager()->update(); + if (MWBase::Environment::get().getStateManager()->getState()!= + MWBase::StateManager::State_NoGame) + { + Ogre::RenderWindow* window = mOgre->getWindow(); + unsigned int tri, batch; + MWBase::Environment::get().getWorld()->getTriangleBatchCount(tri, batch); + MWBase::Environment::get().getWindowManager()->wmUpdateFps(window->getLastFPS(), tri, batch); + + MWBase::Environment::get().getWindowManager()->update(); + } } catch (const std::exception& e) { @@ -228,7 +236,7 @@ void OMW::Engine::addArchive (const std::string& archive) { // Set resource dir void OMW::Engine::setResourceDir (const boost::filesystem::path& parResDir) { - mResDir = boost::filesystem::system_complete(parResDir); + mResDir = parResDir; } // Set start cell name (only interiors for now) @@ -372,7 +380,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) // Create the world mEnvironment.setWorld( new MWWorld::World (*mOgre, mFileCollections, mContentFiles, mResDir, mCfgMgr.getCachePath(), mEncoder, mFallbackMap, - mActivationDistanceOverride, mCellName)); + mActivationDistanceOverride, mCellName, mStartupScript)); MWBase::Environment::get().getWorld()->setupPlayer(); input->setPlayer(&mEnvironment.getWorld()->getPlayer()); @@ -401,10 +409,6 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) mEnvironment.setJournal (new MWDialogue::Journal); mEnvironment.setDialogueManager (new MWDialogue::DialogueManager (mExtensions, mVerboseScripts, mTranslationDataStorage)); - mEnvironment.getWorld()->renderPlayer(); - mechanics->buildPlayer(); - window->updatePlayer(); - mOgre->getRoot()->addFrameListener (this); // scripts @@ -458,10 +462,9 @@ void OMW::Engine::go() catch (...) {} } else + { MWBase::Environment::get().getStateManager()->newGame (true); - - if (!mStartupScript.empty()) - MWBase::Environment::get().getWindowManager()->executeInConsole (mStartupScript); + } // Start the main rendering loop while (!mEnvironment.get().getStateManager()->hasQuitRequest()) @@ -488,12 +491,9 @@ void OMW::Engine::activate() MWScript::InterpreterContext interpreterContext (&ptr.getRefData().getLocals(), ptr); - boost::shared_ptr action = - MWWorld::Class::get (ptr).activate (ptr, MWBase::Environment::get().getWorld()->getPlayerPtr()); - - interpreterContext.activate (ptr, action); + interpreterContext.activate (ptr); - std::string script = MWWorld::Class::get (ptr).getScript (ptr); + std::string script = ptr.getClass().getScript (ptr); MWBase::Environment::get().getWorld()->breakInvisibility(MWBase::Environment::get().getWorld()->getPlayerPtr()); @@ -505,7 +505,7 @@ void OMW::Engine::activate() if (!interpreterContext.hasActivationBeenHandled()) { - interpreterContext.executeActivation(); + interpreterContext.executeActivation(ptr); } } diff --git a/apps/openmw/mwbase/dialoguemanager.hpp b/apps/openmw/mwbase/dialoguemanager.hpp index 3d70fdc6a..cab6809aa 100644 --- a/apps/openmw/mwbase/dialoguemanager.hpp +++ b/apps/openmw/mwbase/dialoguemanager.hpp @@ -5,6 +5,11 @@ #include +namespace Loading +{ + class Listener; +} + namespace ESM { class ESMReader; @@ -45,9 +50,6 @@ namespace MWBase virtual void goodbye() = 0; - virtual MWWorld::Ptr getActor() const = 0; - ///< Return the actor the player is currently talking to. - virtual void say(const MWWorld::Ptr &actor, const std::string &topic) const = 0; //calbacks for the GUI @@ -63,9 +65,15 @@ namespace MWBase virtual int countSavedGameRecords() const = 0; - virtual void write (ESM::ESMWriter& writer) const = 0; + virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) const = 0; virtual void readRecord (ESM::ESMReader& reader, int32_t type) = 0; + + /// Changes faction1's opinion of faction2 by \a diff. + virtual void modFactionReaction (const std::string& faction1, const std::string& faction2, int diff) = 0; + + /// @return faction1's opinion of faction2 + virtual int getFactionReaction (const std::string& faction1, const std::string& faction2) const = 0; }; } diff --git a/apps/openmw/mwbase/journal.hpp b/apps/openmw/mwbase/journal.hpp index 8e4e9703f..a49ebb9bc 100644 --- a/apps/openmw/mwbase/journal.hpp +++ b/apps/openmw/mwbase/journal.hpp @@ -11,6 +11,11 @@ #include "../mwdialogue/topic.hpp" #include "../mwdialogue/quest.hpp" +namespace Loading +{ + class Listener; +} + namespace ESM { class ESMReader; @@ -32,7 +37,7 @@ namespace MWBase typedef std::deque TEntryContainer; typedef TEntryContainer::const_iterator TEntryIter; - typedef std::map TQuestContainer; // topc, quest + typedef std::map TQuestContainer; // topic, quest typedef TQuestContainer::const_iterator TQuestIter; typedef std::map TTopicContainer; // topic-id, topic-content typedef TTopicContainer::const_iterator TTopicIter; @@ -80,7 +85,7 @@ namespace MWBase virtual int countSavedGameRecords() const = 0; - virtual void write (ESM::ESMWriter& writer) const = 0; + virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) const = 0; virtual void readRecord (ESM::ESMReader& reader, int32_t type) = 0; }; diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index e4c480a8c..f31241bdb 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -96,6 +96,9 @@ namespace MWBase /// Check if \a observer is potentially aware of \a ptr. Does not do a line of sight check! virtual bool awarenessCheck (const MWWorld::Ptr& ptr, const MWWorld::Ptr& observer) = 0; + /// Makes \a ptr fight \a target. Also shouts a combat taunt. + virtual void startCombat (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target) = 0; + enum OffenseType { OT_Theft, // Taking items owned by an NPC or a faction you are not a member of @@ -160,10 +163,16 @@ namespace MWBase virtual bool isAIActive() = 0; virtual void getObjectsInRange (const Ogre::Vector3& position, float radius, std::vector& objects) = 0; + virtual void getActorsInRange(const Ogre::Vector3 &position, float radius, std::vector &objects) = 0; - ///return the list of actors which are following the given actor (ie AiFollow is active and the target is the actor) + ///return the list of actors which are following the given actor + /**ie AiFollow is active and the target is the actor**/ virtual std::list getActorsFollowing(const MWWorld::Ptr& actor) = 0; + ///Returns a list of actors who are fighting the given actor within the fAlarmDistance + /** ie AiCombat is active and the target is the actor **/ + virtual std::list getActorsFighting(const MWWorld::Ptr& actor) = 0; + virtual void playerLoaded() = 0; }; } diff --git a/apps/openmw/mwbase/soundmanager.hpp b/apps/openmw/mwbase/soundmanager.hpp index f3973153a..15739730b 100644 --- a/apps/openmw/mwbase/soundmanager.hpp +++ b/apps/openmw/mwbase/soundmanager.hpp @@ -110,18 +110,25 @@ namespace MWBase ///< Play a sound, independently of 3D-position ///< @param offset Value from [0,1] meaning from which fraction the sound the playback starts. - virtual SoundPtr playSound3D(const MWWorld::Ptr &reference, const std::string& soundId, - float volume, float pitch, PlayType type=Play_TypeSfx, - PlayMode mode=Play_Normal, float offset=0) = 0; - ///< Play a sound from an object + virtual MWBase::SoundPtr playSound3D(const MWWorld::Ptr &reference, const std::string& soundId, + float volume, float pitch, PlayType type=Play_TypeSfx, + PlayMode mode=Play_Normal, float offset=0) = 0; + ///< Play a 3D sound attached to an MWWorld::Ptr. Will be updated automatically with the Ptr's position, unless Play_NoTrack is specified. ///< @param offset Value from [0,1] meaning from which fraction the sound the playback starts. + virtual MWBase::SoundPtr playManualSound3D(const Ogre::Vector3& initialPos, const std::string& soundId, + float volume, float pitch, PlayType type, PlayMode mode, float offset=0) = 0; + ///< Play a 3D sound at \a initialPos. If the sound should be moving, it must be updated manually using Sound::setPosition. + virtual void stopSound3D(const MWWorld::Ptr &reference, const std::string& soundId) = 0; ///< Stop the given object from playing the given sound, virtual void stopSound3D(const MWWorld::Ptr &reference) = 0; ///< Stop the given object from playing all sounds. + virtual void stopSound(MWBase::SoundPtr sound) = 0; + ///< Stop the given sound handle + virtual void stopSound(const MWWorld::CellStore *cell) = 0; ///< Stop all sounds for the given cell. diff --git a/apps/openmw/mwbase/statemanager.hpp b/apps/openmw/mwbase/statemanager.hpp index cd907408a..121a73a48 100644 --- a/apps/openmw/mwbase/statemanager.hpp +++ b/apps/openmw/mwbase/statemanager.hpp @@ -55,6 +55,8 @@ namespace MWBase virtual void endGame() = 0; + virtual void deleteGame (const MWState::Character *character, const MWState::Slot *slot) = 0; + virtual void saveGame (const std::string& description, const MWState::Slot *slot = 0) = 0; ///< Write a saved game to \a slot or create a new slot if \a slot == 0. /// @@ -65,6 +67,14 @@ namespace MWBase /// /// \note \a slot must belong to \a character. + ///Simple saver, writes over the file if already existing + /** Used for quick save and autosave **/ + virtual void quickSave(std::string = "Quicksave")=0; + + ///Simple loader, loads the last saved file + /** Used for quickload **/ + virtual void quickLoad()=0; + virtual MWState::Character *getCurrentCharacter (bool create = true) = 0; ///< \param create Create a new character, if there is no current character. diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index e3bd428e2..2dfa50eb5 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -57,6 +57,7 @@ namespace MWGui class InventoryWindow; class ContainerWindow; class DialogueWindow; + class WindowModal; enum ShowInDialogueMode { ShowInDialogueMode_IfPossible, @@ -156,8 +157,9 @@ namespace MWBase virtual void setValue (const std::string& id, int value) = 0; /// Set time left for the player to start drowning (update the drowning bar) - /// @param time value from [0,20] - virtual void setDrowningTimeLeft (float time) =0; + /// @param time time left to start drowning + /// @param maxTime how long we can be underwater (in total) until drowning starts + virtual void setDrowningTimeLeft (float time, float maxTime) = 0; virtual void setPlayerClass (const ESM::Class &class_) = 0; ///< set current class of player @@ -192,15 +194,15 @@ namespace MWBase virtual void setDragDrop(bool dragDrop) = 0; virtual bool getWorldMouseOver() = 0; - virtual void toggleFogOfWar() = 0; + virtual bool toggleFogOfWar() = 0; - virtual void toggleFullHelp() = 0; + virtual bool toggleFullHelp() = 0; ///< show extra info in item tooltips (owner, script) virtual bool getFullHelp() const = 0; - virtual void setInteriorMapTexture(const int x, const int y) = 0; - ///< set the index of the map texture that should be used (for interiors) + virtual void setActiveMap(int x, int y, bool interior) = 0; + ///< set the indices of the map texture that should be used /// sets the visibility of the drowning bar virtual void setDrowningBarVisibility(bool visible) = 0; @@ -233,14 +235,19 @@ namespace MWBase virtual void addVisitedLocation(const std::string& name, int x, int y) = 0; + /// Hides dialog and schedules dialog to be deleted. virtual void removeDialog(OEngine::GUI::Layout* dialog) = 0; - ///< Hides dialog and schedules dialog to be deleted. + + ///Gracefully attempts to exit the topmost GUI mode + /** No guarentee of actually closing the window **/ + virtual void exitCurrentGuiMode() = 0; virtual void messageBox (const std::string& message, const std::vector& buttons = std::vector(), enum MWGui::ShowInDialogueMode showInDialogueMode = MWGui::ShowInDialogueMode_IfPossible) = 0; virtual void staticMessageBox(const std::string& message) = 0; virtual void removeStaticMessageBox() = 0; + + /// returns the index of the pressed button or -1 if no button was pressed (->MessageBoxmanager->InteractiveMessageBox) virtual int readPressedButton() = 0; - ///< returns the index of the pressed button or -1 if no button was pressed (->MessageBoxmanager->InteractiveMessageBox) virtual void onFrame (float frameDuration) = 0; @@ -302,8 +309,25 @@ namespace MWBase /// Clear all savegame-specific data virtual void clear() = 0; - virtual void write (ESM::ESMWriter& writer) = 0; + virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) = 0; virtual void readRecord (ESM::ESMReader& reader, int32_t type) = 0; + virtual int countSavedGameRecords() const = 0; + + /// Does the current stack of GUI-windows permit saving? + virtual bool isSavingAllowed() const = 0; + + /// Returns the current Modal + /** Used to send exit command to active Modal when Esc is pressed **/ + virtual MWGui::WindowModal* getCurrentModal() const = 0; + + /// Sets the current Modal + /** Used to send exit command to active Modal when Esc is pressed **/ + virtual void addCurrentModal(MWGui::WindowModal* input) = 0; + + /// Removes the top Modal + /** Used when one Modal adds another Modal + \param input Pointer to the current modal, to ensure proper modal is removed **/ + virtual void removeCurrentModal(MWGui::WindowModal* input) = 0; }; } diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index f03a9197d..81bec6fe8 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -108,7 +108,7 @@ namespace MWBase virtual int countSavedGameRecords() const = 0; - virtual void write (ESM::ESMWriter& writer) const = 0; + virtual void write (ESM::ESMWriter& writer, Loading::Listener& listener) const = 0; virtual void readRecord (ESM::ESMReader& reader, int32_t type, const std::map& contentFileMap) = 0; @@ -126,7 +126,7 @@ namespace MWBase virtual void setWaterHeight(const float height) = 0; - virtual void toggleWater() = 0; + virtual bool toggleWater() = 0; virtual void adjustSky() = 0; @@ -200,6 +200,9 @@ namespace MWBase virtual MWWorld::Ptr searchPtrViaHandle (const std::string& handle) = 0; ///< Return a pointer to a liveCellRef with the given Ogre handle or Ptr() if not found + virtual MWWorld::Ptr searchPtrViaActorId (int actorId) = 0; + ///< Search is limited to the active cells. + /// \todo enable reference in the OGRE scene virtual void enable (const MWWorld::Ptr& ptr) = 0; @@ -252,7 +255,8 @@ namespace MWBase virtual void changeToExteriorCell (const ESM::Position& position) = 0; ///< Move to exterior cell. - virtual void changeToCell (const ESM::CellId& cellId, const ESM::Position& position) = 0; + virtual void changeToCell (const ESM::CellId& cellId, const ESM::Position& position, bool detectWorldSpaceChange=true) = 0; + ///< @param detectWorldSpaceChange if true, clean up worldspace-specific data when the world space changes virtual const ESM::Cell *getExterior (const std::string& cellName) const = 0; ///< Return a cell matching the given name or a 0-pointer, if there is no such cell. @@ -351,15 +355,14 @@ namespace MWBase virtual void update (float duration, bool paused) = 0; - virtual bool placeObject (const MWWorld::Ptr& object, float cursorX, float cursorY, int amount) = 0; + virtual MWWorld::Ptr placeObject (const MWWorld::Ptr& object, float cursorX, float cursorY, int amount) = 0; ///< copy and place an object into the gameworld at the specified cursor position /// @param object /// @param cursor X (relative 0-1) /// @param cursor Y (relative 0-1) /// @param number of objects to place - /// @return true if the object was placed, or false if it was rejected because the position is too far away - virtual void dropObjectOnGround (const MWWorld::Ptr& actor, const MWWorld::Ptr& object, int amount) = 0; + virtual MWWorld::Ptr dropObjectOnGround (const MWWorld::Ptr& actor, const MWWorld::Ptr& object, int amount) = 0; ///< copy and place an object into the gameworld at the given actor's position /// @param actor giving the dropped object position /// @param object @@ -390,10 +393,10 @@ namespace MWBase virtual void setupPlayer() = 0; virtual void renderPlayer() = 0; - virtual bool getOpenOrCloseDoor(const MWWorld::Ptr& door) = 0; - ///< if activated, should this door be opened or closed? + /// open or close a non-teleport door (depending on current state) virtual void activateDoor(const MWWorld::Ptr& door) = 0; - ///< activate (open or close) an non-teleport door + /// open or close a non-teleport door as specified + virtual void activateDoor(const MWWorld::Ptr& door, bool open) = 0; virtual bool getPlayerStandingOn (const MWWorld::Ptr& object) = 0; ///< @return true if the player is standing on \a object virtual bool getActorStandingOn (const MWWorld::Ptr& object) = 0; ///< @return true if any actor is standing on \a object @@ -407,6 +410,8 @@ namespace MWBase virtual bool getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc) = 0; ///< get Line of Sight (morrowind stupid implementation) + virtual float getDistToNearestRayHit(const Ogre::Vector3& from, const Ogre::Vector3& dir, float maxDist) = 0; + virtual void enableActorCollision(const MWWorld::Ptr& actor, bool enable) = 0; virtual int canRest() = 0; @@ -463,7 +468,8 @@ namespace MWBase virtual void castSpell (const MWWorld::Ptr& actor) = 0; - virtual void launchMagicBolt (const std::string& id, bool stack, const ESM::EffectList& effects, + virtual void launchMagicBolt (const std::string& model, const std::string& sound, const std::string& spellId, + float speed, bool stack, const ESM::EffectList& effects, const MWWorld::Ptr& actor, const std::string& sourceName) = 0; virtual void launchProjectile (MWWorld::Ptr actor, MWWorld::Ptr projectile, const Ogre::Vector3& worldPos, const Ogre::Quaternion& orient, MWWorld::Ptr bow, float speed) = 0; @@ -509,7 +515,7 @@ namespace MWBase /// Spawn a blood effect for \a ptr at \a worldPosition virtual void spawnBloodEffect (const MWWorld::Ptr& ptr, const Ogre::Vector3& worldPosition) = 0; - virtual void explodeSpell (const Ogre::Vector3& origin, const MWWorld::Ptr& object, const ESM::EffectList& effects, + virtual void explodeSpell (const Ogre::Vector3& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const std::string& id, const std::string& sourceName) = 0; }; } diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index 16ab6321d..043aadd35 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -97,8 +97,8 @@ namespace MWClass std::string text; if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); - text += MWGui::ToolTips::getMiscString(ref->mRef.mFaction, "Faction"); + text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); + text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } info.text = text; @@ -108,7 +108,7 @@ namespace MWClass boost::shared_ptr Activator::activate(const MWWorld::Ptr &ptr, const MWWorld::Ptr &actor) const { - if(get(actor).isNpc() && get(actor).getNpcStats(actor).isWerewolf()) + if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Sound *sound = store.get().searchRandom("WolfActivator"); diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index 8580d61ce..947a9cb94 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -127,8 +127,8 @@ namespace MWClass text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); - text += MWGui::ToolTips::getMiscString(ref->mRef.mFaction, "Faction"); + text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); + text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } info.text = text; diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index 550151e43..825b14978 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -168,10 +168,7 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - if (ptr.getCellRef().mCharge == -1) - return ref->mBase->mData.mValue; - else - return ref->mBase->mData.mValue * (static_cast(ptr.getCellRef().mCharge) / getItemMaxHealth(ptr)); + return ref->mBase->mData.mValue * (static_cast(getItemHealth(ptr)) / getItemMaxHealth(ptr)); } void Armor::registerSelf() @@ -242,7 +239,7 @@ namespace MWClass text += "\n#{sArmorRating}: " + MWGui::ToolTips::toString(ref->mBase->mData.mArmor); - int remainingHealth = (ptr.getCellRef().mCharge != -1) ? ptr.getCellRef().mCharge : ref->mBase->mData.mHealth; + int remainingHealth = getItemHealth(ptr); text += "\n#{sCondition}: " + MWGui::ToolTips::toString(remainingHealth) + "/" + MWGui::ToolTips::toString(ref->mBase->mData.mHealth); @@ -250,14 +247,14 @@ namespace MWClass text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); - text += MWGui::ToolTips::getMiscString(ref->mRef.mFaction, "Faction"); + text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); + text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } info.enchant = ref->mBase->mEnchant; if (!info.enchant.empty()) - info.remainingEnchantCharge = ptr.getCellRef().mEnchantmentCharge; + info.remainingEnchantCharge = ptr.getCellRef().getEnchantmentCharge(); info.text = text; @@ -272,7 +269,7 @@ namespace MWClass return ref->mBase->mEnchant; } - void Armor::applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const + std::string Armor::applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const { MWWorld::LiveCellRef *ref = ptr.get(); @@ -283,19 +280,18 @@ namespace MWClass newItem.mData.mEnchant=enchCharge; newItem.mEnchant=enchId; const ESM::Armor *record = MWBase::Environment::get().getWorld()->createRecord (newItem); - ref->mBase = record; - ref->mRef.mRefID = record->mId; + return record->mId; } std::pair Armor::canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const { MWWorld::InventoryStore& invStore = npc.getClass().getInventoryStore(npc); - if (ptr.getCellRef().mCharge == 0) + if (ptr.getCellRef().getCharge() == 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); + std::pair, bool> slots_ = ptr.getClass().getEquipmentSlots(ptr); if (slots_.first.empty()) return std::make_pair(0, ""); diff --git a/apps/openmw/mwclass/armor.hpp b/apps/openmw/mwclass/armor.hpp index 17cfca453..e9164f920 100644 --- a/apps/openmw/mwclass/armor.hpp +++ b/apps/openmw/mwclass/armor.hpp @@ -67,7 +67,8 @@ namespace MWClass virtual std::string getEnchantment (const MWWorld::Ptr& ptr) const; ///< @return the enchantment ID if the object is enchanted, otherwise an empty string - virtual void applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const; + virtual std::string applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const; + ///< Creates a new record using \a ptr as template, with the given name and the given enchantment applied to it. virtual std::pair canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const; ///< Return 0 if player cannot equip item. 1 if can equip. 2 if it's twohanded weapon. 3 if twohanded weapon conflicts with that. \n diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index ebc3b18e6..0cc2e6020 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -61,7 +61,7 @@ namespace MWClass boost::shared_ptr Book::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { - if(get(actor).isNpc() && get(actor).getNpcStats(actor).isWerewolf()) + if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Sound *sound = store.get().searchRandom("WolfItem"); @@ -139,8 +139,8 @@ namespace MWClass text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); - text += MWGui::ToolTips::getMiscString(ref->mRef.mFaction, "Faction"); + text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); + text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } @@ -159,7 +159,7 @@ namespace MWClass return ref->mBase->mEnchant; } - void Book::applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const + std::string Book::applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const { MWWorld::LiveCellRef *ref = ptr.get(); @@ -171,8 +171,7 @@ namespace MWClass newItem.mData.mEnchant=enchCharge; newItem.mEnchant=enchId; const ESM::Book *record = MWBase::Environment::get().getWorld()->createRecord (newItem); - ref->mBase = record; - ref->mRef.mRefID = record->mId; + return record->mId; } boost::shared_ptr Book::use (const MWWorld::Ptr& ptr) const diff --git a/apps/openmw/mwclass/book.hpp b/apps/openmw/mwclass/book.hpp index 79b823fa9..b60ef41d6 100644 --- a/apps/openmw/mwclass/book.hpp +++ b/apps/openmw/mwclass/book.hpp @@ -51,7 +51,8 @@ namespace MWClass virtual std::string getEnchantment (const MWWorld::Ptr& ptr) const; ///< @return the enchantment ID if the object is enchanted, otherwise an empty string - virtual void applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const; + virtual std::string applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const; + ///< Creates a new record using \a ptr as template, with the given name and the given enchantment applied to it. virtual boost::shared_ptr use (const MWWorld::Ptr& ptr) const; ///< Generate action for using via inventory menu diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index 18a40d5d3..c0362188b 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -193,14 +193,14 @@ namespace MWClass text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); - text += MWGui::ToolTips::getMiscString(ref->mRef.mFaction, "Faction"); + text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); + text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } info.enchant = ref->mBase->mEnchant; if (!info.enchant.empty()) - info.remainingEnchantCharge = ptr.getCellRef().mEnchantmentCharge; + info.remainingEnchantCharge = ptr.getCellRef().getEnchantmentCharge(); info.text = text; @@ -215,7 +215,7 @@ namespace MWClass return ref->mBase->mEnchant; } - void Clothing::applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const + std::string Clothing::applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const { MWWorld::LiveCellRef *ref = ptr.get(); @@ -226,14 +226,13 @@ namespace MWClass newItem.mData.mEnchant=enchCharge; newItem.mEnchant=enchId; const ESM::Clothing *record = MWBase::Environment::get().getWorld()->createRecord (newItem); - ref->mBase = record; - ref->mRef.mRefID = record->mId; + return record->mId; } std::pair Clothing::canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const { // slots that this item can be equipped in - std::pair, bool> slots_ = MWWorld::Class::get(ptr).getEquipmentSlots(ptr); + std::pair, bool> slots_ = ptr.getClass().getEquipmentSlots(ptr); if (slots_.first.empty()) return std::make_pair(0, ""); diff --git a/apps/openmw/mwclass/clothing.hpp b/apps/openmw/mwclass/clothing.hpp index a73b2c190..052928238 100644 --- a/apps/openmw/mwclass/clothing.hpp +++ b/apps/openmw/mwclass/clothing.hpp @@ -59,7 +59,8 @@ namespace MWClass virtual std::string getEnchantment (const MWWorld::Ptr& ptr) const; ///< @return the enchantment ID if the object is enchanted, otherwise an empty string - virtual void applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const; + virtual std::string applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const; + ///< Creates a new record using \a ptr as template, with the given name and the given enchantment applied to it. virtual std::pair canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const; ///< Return 0 if player cannot equip item. 1 if can equip. 2 if it's twohanded weapon. 3 if twohanded weapon conflicts with that. diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 604b51990..9498ea52d 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -53,13 +53,31 @@ namespace MWClass ptr.get(); data->mContainerStore.fill( - ref->mBase->mInventory, ptr.getCellRef().mOwner, ptr.getCellRef().mFaction, MWBase::Environment::get().getWorld()->getStore()); + ref->mBase->mInventory, ptr.getCellRef().getOwner(), ptr.getCellRef().getFaction(), MWBase::Environment::get().getWorld()->getStore()); // store ptr.getRefData().setCustomData (data.release()); } } + void Container::respawn(const MWWorld::Ptr &ptr) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + if (ref->mBase->mFlags & ESM::Container::Respawn) + { + ptr.getRefData().setCustomData(NULL); + } + } + + void Container::restock(const MWWorld::Ptr& ptr) const + { + MWWorld::LiveCellRef *ref = ptr.get(); + const ESM::InventoryList& list = ref->mBase->mInventory; + MWWorld::ContainerStore& store = getContainerStore(ptr); + store.restock(list, ptr, ptr.getCellRef().getOwner(), ptr.getCellRef().getFaction()); + } + void Container::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { const std::string model = getModel(ptr); @@ -94,7 +112,7 @@ namespace MWClass if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) return boost::shared_ptr (new MWWorld::NullAction ()); - if(get(actor).isNpc() && get(actor).getNpcStats(actor).isWerewolf()) + if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Sound *sound = store.get().searchRandom("WolfContainer"); @@ -109,38 +127,38 @@ namespace MWClass const std::string trapActivationSound = "Disarm Trap Fail"; MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); - MWWorld::InventoryStore& invStore = MWWorld::Class::get(player).getInventoryStore(player); + MWWorld::InventoryStore& invStore = player.getClass().getInventoryStore(player); - bool needKey = ptr.getCellRef().mLockLevel>0; + bool needKey = ptr.getCellRef().getLockLevel() > 0; bool hasKey = false; std::string keyName; // make key id lowercase - std::string keyId = ptr.getCellRef().mKey; + std::string keyId = ptr.getCellRef().getKey(); Misc::StringUtils::toLower(keyId); for (MWWorld::ContainerStoreIterator it = invStore.begin(); it != invStore.end(); ++it) { - std::string refId = it->getCellRef().mRefID; + std::string refId = it->getCellRef().getRefId(); Misc::StringUtils::toLower(refId); if (refId == keyId) { hasKey = true; - keyName = MWWorld::Class::get(*it).getName(*it); + keyName = it->getClass().getName(*it); } } if (needKey && hasKey) { MWBase::Environment::get().getWindowManager ()->messageBox (keyName + " #{sKeyUsed}"); - ptr.getCellRef().mLockLevel = 0; + unlock(ptr); // using a key disarms the trap - ptr.getCellRef().mTrap = ""; + ptr.getCellRef().setTrap(""); } if (!needKey || hasKey) { - if(ptr.getCellRef().mTrap.empty()) + if(ptr.getCellRef().getTrap().empty()) { boost::shared_ptr action (new MWWorld::ActionOpen(ptr)); return action; @@ -148,7 +166,7 @@ namespace MWClass else { // Activate trap - boost::shared_ptr action(new MWWorld::ActionTrap(actor, ptr.getCellRef().mTrap, ptr)); + boost::shared_ptr action(new MWWorld::ActionTrap(actor, ptr.getCellRef().getTrap(), ptr)); action->setSound(trapActivationSound); return action; } @@ -209,14 +227,16 @@ namespace MWClass info.caption = ref->mBase->mName; std::string text; - if (ref->mRef.mLockLevel > 0) - text += "\n#{sLockLevel}: " + MWGui::ToolTips::toString(ref->mRef.mLockLevel); - if (ref->mRef.mTrap != "") + if (ptr.getCellRef().getLockLevel() > 0) + text += "\n#{sLockLevel}: " + MWGui::ToolTips::toString(ptr.getCellRef().getLockLevel()); + else if (ptr.getCellRef().getLockLevel() < 0) + text += "\n#{sUnlocked}"; + if (ptr.getCellRef().getTrap() != "") text += "\n#{sTrapped}"; if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); - text += MWGui::ToolTips::getMiscString(ref->mRef.mFaction, "Faction"); + text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); + text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } @@ -240,17 +260,18 @@ namespace MWClass void Container::lock (const MWWorld::Ptr& ptr, int lockLevel) const { - if (lockLevel<0) - lockLevel = 0; - - ptr.getCellRef().mLockLevel = lockLevel; + if(lockLevel!=0) + ptr.getCellRef().setLockLevel(abs(lockLevel)); //Changes lock to locklevel, in positive + else + ptr.getCellRef().setLockLevel(abs(ptr.getCellRef().getLockLevel())); //No locklevel given, just flip the original one } void Container::unlock (const MWWorld::Ptr& ptr) const { - ptr.getCellRef().mLockLevel = 0; + ptr.getCellRef().setLockLevel(-abs(ptr.getCellRef().getLockLevel())); //Makes lockLevel negative } + MWWorld::Ptr Container::copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const { diff --git a/apps/openmw/mwclass/container.hpp b/apps/openmw/mwclass/container.hpp index c97867d35..9fc013e45 100644 --- a/apps/openmw/mwclass/container.hpp +++ b/apps/openmw/mwclass/container.hpp @@ -48,7 +48,7 @@ namespace MWClass ///< Returns total weight of objects inside this object (including modifications from magic /// effects). Throws an exception, if the object can't hold other objects. - virtual void lock (const MWWorld::Ptr& ptr, int lockLevel) const; + virtual void lock (const MWWorld::Ptr& ptr, int lockLevel = 0) const; ///< Lock object virtual void unlock (const MWWorld::Ptr& ptr) const; @@ -64,6 +64,10 @@ namespace MWClass static void registerSelf(); + virtual void respawn (const MWWorld::Ptr& ptr) const; + + virtual void restock (const MWWorld::Ptr &ptr) const; + virtual std::string getModel(const MWWorld::Ptr &ptr) const; }; } diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 94238e6d5..1a6e4e321 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -123,9 +123,6 @@ namespace MWClass else data->mContainerStore = new MWWorld::ContainerStore(); - // Relates to NPC gold reset delay - data->mCreatureStats.setTradeTime(MWWorld::TimeStamp(0.0, 0)); - data->mCreatureStats.setGoldPool(ref->mBase->mData.mGold); // store @@ -164,7 +161,11 @@ namespace MWClass { const std::string model = getModel(ptr); if(!model.empty()) + { physics.addActor(ptr); + if (getCreatureStats(ptr).isDead()) + MWBase::Environment::get().getWorld()->enableActorCollision(ptr, false); + } MWBase::Environment::get().getMechanicsManager()->add(ptr); } @@ -276,7 +277,7 @@ namespace MWClass if (!weapon.isEmpty()) { - const bool weaphashealth = get(weapon).hasItemHealth(weapon); + const bool weaphashealth = weapon.getClass().hasItemHealth(weapon); const unsigned char *attack = NULL; if(type == ESM::Weapon::AT_Chop) attack = weapon.get()->mBase->mData.mChop; @@ -290,20 +291,24 @@ namespace MWClass weaponDamage *= 0.5f + (stats.getAttribute(ESM::Attribute::Luck).getModified() / 100.0f); if(weaphashealth) { - int weapmaxhealth = weapon.get()->mBase->mData.mHealth; - if(weapon.getCellRef().mCharge == -1) - weapon.getCellRef().mCharge = weapmaxhealth; - weaponDamage *= float(weapon.getCellRef().mCharge) / weapmaxhealth; + int weapmaxhealth = weapon.getClass().getItemMaxHealth(weapon); + int weaphealth = weapon.getClass().getItemHealth(weapon); + weaponDamage *= float(weaphealth) / weapmaxhealth; + + if (!MWBase::Environment::get().getWorld()->getGodModeState()) + { + // Reduce weapon charge by at least one, but cap at 0 + weaphealth -= std::min(std::max(1, + (int)(damage * gmst.find("fWeaponDamageMult")->getFloat())), weaphealth); + + weapon.getCellRef().setCharge(weaphealth); + } + + // Weapon broken? unequip it + if (weapon.getCellRef().getCharge() == 0) + weapon = *getInventoryStore(ptr).unequipItem(weapon, ptr); } - 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 = *getInventoryStore(ptr).unequipItem(weapon, ptr); - damage += weaponDamage; } @@ -337,6 +342,12 @@ namespace MWClass { // NOTE: 'object' and/or 'attacker' may be empty. + getCreatureStats(ptr).setAttacked(true); + + // Self defense + if (!attacker.isEmpty() && ptr.getClass().getCreatureStats(ptr).getAiSetting(MWMechanics::CreatureStats::AI_Fight).getModified() < 80) + MWBase::Environment::get().getMechanicsManager()->startCombat(ptr, attacker); + if(!successful) { // TODO: Handle HitAttemptOnMe script function @@ -347,7 +358,7 @@ namespace MWClass } if(!object.isEmpty()) - getCreatureStats(ptr).setLastHitObject(MWWorld::Class::get(object).getId(object)); + getCreatureStats(ptr).setLastHitObject(object.getClass().getId(object)); if(!attacker.isEmpty() && attacker.getRefData().getHandle() == "player") { @@ -437,7 +448,7 @@ namespace MWClass boost::shared_ptr Creature::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { - if(get(actor).isNpc() && get(actor).getNpcStats(actor).isWerewolf()) + if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Sound *sound = store.get().searchRandom("WolfCreature"); @@ -812,6 +823,34 @@ namespace MWClass return ptr.get()->mBase->mData.mGold; } + void Creature::respawn(const MWWorld::Ptr &ptr) const + { + if (ptr.get()->mBase->mFlags & ESM::Creature::Respawn) + { + // Note we do not respawn moved references in the cell they were moved to. Instead they are respawned in the original cell. + // This also means we cannot respawn dynamically placed references with no content file connection. + if (ptr.getCellRef().getRefNum().mContentFile != -1) + { + if (ptr.getRefData().getCount() == 0) + ptr.getRefData().setCount(1); + + // Reset to original position + ESM::Position& pos = ptr.getRefData().getPosition(); + pos = ptr.getCellRef().getPosition(); + + ptr.getRefData().setCustomData(NULL); + } + } + } + + void Creature::restock(const MWWorld::Ptr& ptr) const + { + MWWorld::LiveCellRef *ref = ptr.get(); + const ESM::InventoryList& list = ref->mBase->mInventory; + MWWorld::ContainerStore& store = getContainerStore(ptr); + store.restock(list, ptr, ptr.getCellRef().getRefId(), ptr.getCellRef().getFaction()); + } + const ESM::GameSetting* Creature::fMinWalkSpeedCreature; const ESM::GameSetting* Creature::fMaxWalkSpeedCreature; const ESM::GameSetting *Creature::fEncumberedMoveEffect; diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index 04c010c83..30573cd15 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -143,6 +143,10 @@ namespace MWClass ///< Write additional state from \a ptr into \a state. virtual int getBaseGold(const MWWorld::Ptr& ptr) const; + + virtual void respawn (const MWWorld::Ptr& ptr) const; + + virtual void restock (const MWWorld::Ptr &ptr) const; }; } diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp index 732038b2f..784304804 100644 --- a/apps/openmw/mwclass/creaturelevlist.cpp +++ b/apps/openmw/mwclass/creaturelevlist.cpp @@ -2,6 +2,7 @@ #include "creaturelevlist.hpp" #include +#include #include "../mwmechanics/levelledlist.hpp" @@ -11,7 +12,10 @@ namespace { struct CreatureLevListCustomData : public MWWorld::CustomData { - // TODO: save the creature we spawned here + // actorId of the creature we spawned + int mSpawnActorId; + bool mSpawn; // Should a new creature be spawned? + virtual MWWorld::CustomData *clone() const; }; @@ -28,6 +32,14 @@ namespace MWClass return ""; } + void CreatureLevList::respawn(const MWWorld::Ptr &ptr) const + { + ensureCustomData(ptr); + + CreatureLevListCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); + customData.mSpawn = true; + } + void CreatureLevList::registerSelf() { boost::shared_ptr instance (new CreatureLevList); @@ -38,6 +50,34 @@ namespace MWClass void CreatureLevList::insertObjectRendering(const MWWorld::Ptr &ptr, MWRender::RenderingInterface &renderingInterface) const { ensureCustomData(ptr); + + CreatureLevListCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); + if (!customData.mSpawn) + return; + + MWWorld::LiveCellRef *ref = + ptr.get(); + + std::string id = MWMechanics::getLevelledItem(ref->mBase, true); + + if (!id.empty()) + { + // Delete the previous creature + if (customData.mSpawnActorId != -1) + { + MWWorld::Ptr creature = MWBase::Environment::get().getWorld()->searchPtrViaActorId(customData.mSpawnActorId); + if (!creature.isEmpty()) + MWBase::Environment::get().getWorld()->deleteObject(creature); + customData.mSpawnActorId = -1; + } + + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + MWWorld::ManualRef ref(store, id); + ref.getPtr().getCellRef().setPosition(ptr.getCellRef().getPosition()); + MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(), ptr.getCell() , ptr.getCellRef().getPosition()); + customData.mSpawnActorId = placed.getClass().getCreatureStats(placed).getActorId(); + customData.mSpawn = false; + } } void CreatureLevList::ensureCustomData(const MWWorld::Ptr &ptr) const @@ -45,22 +85,32 @@ namespace MWClass if (!ptr.getRefData().getCustomData()) { std::auto_ptr data (new CreatureLevListCustomData); + data->mSpawnActorId = -1; + data->mSpawn = true; - MWWorld::LiveCellRef *ref = - ptr.get(); + ptr.getRefData().setCustomData(data.release()); + } + } - std::string id = MWMechanics::getLevelledItem(ref->mBase, true); + void CreatureLevList::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) + const + { + const ESM::CreatureLevListState& state2 = dynamic_cast (state); - if (!id.empty()) - { - const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); - MWWorld::ManualRef ref(store, id); - ref.getPtr().getCellRef().mPos = ptr.getCellRef().mPos; - // TODO: hold on to this for respawn purposes later - MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(), ptr.getCell() , ptr.getCellRef().mPos); - } + ensureCustomData(ptr); + CreatureLevListCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); + customData.mSpawnActorId = state2.mSpawnActorId; + customData.mSpawn = state2.mSpawn; + } - ptr.getRefData().setCustomData(data.release()); - } + void CreatureLevList::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) + const + { + ESM::CreatureLevListState& state2 = dynamic_cast (state); + + ensureCustomData(ptr); + CreatureLevListCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); + state2.mSpawnActorId = customData.mSpawnActorId; + state2.mSpawn = customData.mSpawn; } } diff --git a/apps/openmw/mwclass/creaturelevlist.hpp b/apps/openmw/mwclass/creaturelevlist.hpp index d2c02043e..6c51a3189 100644 --- a/apps/openmw/mwclass/creaturelevlist.hpp +++ b/apps/openmw/mwclass/creaturelevlist.hpp @@ -19,6 +19,16 @@ namespace MWClass virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering + + virtual void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) + const; + ///< Read additional state from \a state into \a ptr. + + virtual void writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) + const; + ///< Write additional state from \a ptr into \a state. + + virtual void respawn (const MWWorld::Ptr& ptr) const; }; } diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 3cd8237e7..12645c9f3 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -2,6 +2,7 @@ #include "door.hpp" #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -17,12 +18,28 @@ #include "../mwworld/physicssystem.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/actiontrap.hpp" +#include "../mwworld/customdata.hpp" #include "../mwgui/tooltips.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" +namespace +{ + struct DoorCustomData : public MWWorld::CustomData + { + int mDoorState; // 0 = nothing, 1 = opening, 2 = closing + + virtual MWWorld::CustomData *clone() const; + }; + + MWWorld::CustomData *DoorCustomData::clone() const + { + return new DoorCustomData (*this); + } +} + namespace MWClass { void Door::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const @@ -38,6 +55,14 @@ namespace MWClass const std::string model = getModel(ptr); if(!model.empty()) physics.addObject(ptr); + + // Resume the door's opening/closing animation if it wasn't finished + ensureCustomData(ptr); + const DoorCustomData& customData = dynamic_cast(*ptr.getRefData().getCustomData()); + if (customData.mDoorState > 0) + { + MWBase::Environment::get().getWorld()->activateDoor(ptr, customData.mDoorState == 1 ? true : false); + } } std::string Door::getModel(const MWWorld::Ptr &ptr) const @@ -58,8 +83,8 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - if (ref->mRef.mTeleport && !ref->mRef.mDestCell.empty()) // TODO doors that lead to exteriors - return ref->mRef.mDestCell; + if (ptr.getCellRef().getTeleport() && !ptr.getCellRef().getDestCell().empty()) // TODO doors that lead to exteriors + return ptr.getCellRef().getDestCell(); return ref->mBase->mName; } @@ -74,23 +99,23 @@ namespace MWClass const std::string lockedSound = "LockedDoor"; const std::string trapActivationSound = "Disarm Trap Fail"; - MWWorld::ContainerStore &invStore = get(actor).getContainerStore(actor); + MWWorld::ContainerStore &invStore = actor.getClass().getContainerStore(actor); - bool needKey = ptr.getCellRef().mLockLevel>0; + bool needKey = ptr.getCellRef().getLockLevel() > 0; bool hasKey = false; std::string keyName; // make key id lowercase - std::string keyId = ptr.getCellRef().mKey; + std::string keyId = ptr.getCellRef().getKey(); Misc::StringUtils::toLower(keyId); for (MWWorld::ContainerStoreIterator it = invStore.begin(); it != invStore.end(); ++it) { - std::string refId = it->getCellRef().mRefID; + std::string refId = it->getCellRef().getRefId(); Misc::StringUtils::toLower(refId); if (refId == keyId) { hasKey = true; - keyName = get(*it).getName(*it); + keyName = it->getClass().getName(*it); } } @@ -98,24 +123,24 @@ namespace MWClass { if(actor == MWBase::Environment::get().getWorld()->getPlayerPtr()) MWBase::Environment::get().getWindowManager()->messageBox(keyName + " #{sKeyUsed}"); - ptr.getCellRef().mLockLevel = 0; + unlock(ptr); //Call the function here. because that makes sense. // using a key disarms the trap - ptr.getCellRef().mTrap = ""; + ptr.getCellRef().getTrap() = ""; } if (!needKey || hasKey) { - if(!ptr.getCellRef().mTrap.empty()) + if(!ptr.getCellRef().getTrap().empty()) { // Trap activation - boost::shared_ptr action(new MWWorld::ActionTrap(actor, ptr.getCellRef().mTrap, ptr)); + boost::shared_ptr action(new MWWorld::ActionTrap(actor, ptr.getCellRef().getTrap(), ptr)); action->setSound(trapActivationSound); return action; } - if (ref->mRef.mTeleport) + if (ptr.getCellRef().getTeleport()) { - boost::shared_ptr action(new MWWorld::ActionTeleport (ref->mRef.mDestCell, ref->mRef.mDoorDest)); + boost::shared_ptr action(new MWWorld::ActionTeleport (ptr.getCellRef().getDestCell(), ptr.getCellRef().getDoorDest())); action->setSound(openSound); @@ -125,7 +150,14 @@ namespace MWClass { // animated door boost::shared_ptr action(new MWWorld::ActionDoor(ptr)); - if (MWBase::Environment::get().getWorld()->getOpenOrCloseDoor(ptr)) + int doorstate = getDoorState(ptr); + bool opening = true; + if (doorstate == 1) + opening = false; + if (doorstate == 0 && ptr.getRefData().getLocalRotation().rot[2] != 0) + opening = false; + + if (opening) { MWBase::Environment::get().getSoundManager()->fadeOutSound3D(ptr, closeSound, 0.5); @@ -158,15 +190,15 @@ namespace MWClass void Door::lock (const MWWorld::Ptr& ptr, int lockLevel) const { - if (lockLevel<0) - lockLevel = 0; - - ptr.getCellRef().mLockLevel = lockLevel; + if(lockLevel!=0) + ptr.getCellRef().setLockLevel(abs(lockLevel)); //Changes lock to locklevel, in positive + else + ptr.getCellRef().setLockLevel(abs(ptr.getCellRef().getLockLevel())); //No locklevel given, just flip the origional one } void Door::unlock (const MWWorld::Ptr& ptr) const { - ptr.getCellRef().mLockLevel = 0; + ptr.getCellRef().setLockLevel(-abs(ptr.getCellRef().getLockLevel())); //Makes lockLevel negative } std::string Door::getScript (const MWWorld::Ptr& ptr) const @@ -202,15 +234,17 @@ namespace MWClass std::string text; - if (ref->mRef.mTeleport) + if (ptr.getCellRef().getTeleport()) { text += "\n#{sTo}"; text += "\n" + getDestination(*ref); } - if (ref->mRef.mLockLevel > 0) - text += "\n#{sLockLevel}: " + MWGui::ToolTips::toString(ref->mRef.mLockLevel); - if (ref->mRef.mTrap != "") + if (ptr.getCellRef().getLockLevel() > 0) + text += "\n#{sLockLevel}: " + MWGui::ToolTips::toString(ptr.getCellRef().getLockLevel()); + else if (ptr.getCellRef().getLockLevel() < 0) + text += "\n#{sUnlocked}"; + if (ptr.getCellRef().getTrap() != "") text += "\n#{sTrapped}"; if (MWBase::Environment::get().getWindowManager()->getFullHelp()) @@ -226,16 +260,16 @@ namespace MWClass const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); std::string dest; - if (door.mRef.mDestCell != "") + if (door.mRef.getDestCell() != "") { // door leads to an interior, use interior name as tooltip - dest = door.mRef.mDestCell; + dest = door.mRef.getDestCell(); } else { // door leads to exterior, use cell name (if any), otherwise translated region name int x,y; - MWBase::Environment::get().getWorld()->positionToIndex (door.mRef.mDoorDest.pos[0], door.mRef.mDoorDest.pos[1], x, y); + MWBase::Environment::get().getWorld()->positionToIndex (door.mRef.getDoorDest().pos[0], door.mRef.getDoorDest().pos[1], x, y); const ESM::Cell* cell = store.get().find(x,y); if (cell->mName != "") dest = cell->mName; @@ -260,4 +294,48 @@ namespace MWClass return MWWorld::Ptr(&cell.get().insert(*ref), &cell); } + + void Door::ensureCustomData(const MWWorld::Ptr &ptr) const + { + if (!ptr.getRefData().getCustomData()) + { + std::auto_ptr data(new DoorCustomData); + + data->mDoorState = 0; + ptr.getRefData().setCustomData(data.release()); + } + } + + int Door::getDoorState (const MWWorld::Ptr &ptr) const + { + ensureCustomData(ptr); + const DoorCustomData& customData = dynamic_cast(*ptr.getRefData().getCustomData()); + return customData.mDoorState; + } + + void Door::setDoorState (const MWWorld::Ptr &ptr, int state) const + { + ensureCustomData(ptr); + DoorCustomData& customData = dynamic_cast(*ptr.getRefData().getCustomData()); + customData.mDoorState = state; + } + + void Door::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const + { + ensureCustomData(ptr); + DoorCustomData& customData = dynamic_cast(*ptr.getRefData().getCustomData()); + + const ESM::DoorState& state2 = dynamic_cast(state); + customData.mDoorState = state2.mDoorState; + } + + void Door::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) const + { + ensureCustomData(ptr); + const DoorCustomData& customData = dynamic_cast(*ptr.getRefData().getCustomData()); + + ESM::DoorState& state2 = dynamic_cast(state); + state2.mDoorState = customData.mDoorState; + } + } diff --git a/apps/openmw/mwclass/door.hpp b/apps/openmw/mwclass/door.hpp index 2ac342a61..12b360aa8 100644 --- a/apps/openmw/mwclass/door.hpp +++ b/apps/openmw/mwclass/door.hpp @@ -9,6 +9,8 @@ namespace MWClass { class Door : public MWWorld::Class { + void ensureCustomData (const MWWorld::Ptr& ptr) const; + virtual MWWorld::Ptr copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; @@ -36,7 +38,7 @@ namespace MWClass static std::string getDestination (const MWWorld::LiveCellRef& door); ///< @return destination cell name or token - virtual void lock (const MWWorld::Ptr& ptr, int lockLevel) const; + virtual void lock (const MWWorld::Ptr& ptr, int lockLevel = 0) const; ///< Lock object virtual void unlock (const MWWorld::Ptr& ptr) const; @@ -48,6 +50,20 @@ namespace MWClass static void registerSelf(); virtual std::string getModel(const MWWorld::Ptr &ptr) const; + + /// 0 = nothing, 1 = opening, 2 = closing + virtual int getDoorState (const MWWorld::Ptr &ptr) const; + /// This does not actually cause the door to move. Use World::activateDoor instead. + virtual void setDoorState (const MWWorld::Ptr &ptr, int state) const; + + + virtual void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) + const; + ///< Read additional state from \a state into \a ptr. + + virtual void writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) + const; + ///< Write additional state from \a ptr into \a state. }; } diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index e15424c38..60c0efeb8 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -147,13 +147,13 @@ namespace MWClass text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); - text += MWGui::ToolTips::getMiscString(ref->mRef.mFaction, "Faction"); + text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); + text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); - MWMechanics::NpcStats& npcStats = MWWorld::Class::get(player).getNpcStats (player); + MWMechanics::NpcStats& npcStats = player.getClass().getNpcStats (player); int alchemySkill = npcStats.getSkill (ESM::Skill::Alchemy).getBase(); static const float fWortChanceValue = diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index bd25b66b2..ef4549268 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -50,9 +50,9 @@ namespace MWClass void Light::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { const std::string model = getModel(ptr); - if(!model.empty()) { - renderingInterface.getObjects().insertModel(ptr, model); - } + + // Insert even if model is empty, so that the light is added + renderingInterface.getObjects().insertModel(ptr, model); } void Light::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const @@ -187,8 +187,8 @@ namespace MWClass text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); - text += MWGui::ToolTips::getMiscString(ref->mRef.mFaction, "Faction"); + text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); + text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } @@ -250,7 +250,7 @@ namespace MWClass std::pair Light::canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const { - MWWorld::InventoryStore& invStore = MWWorld::Class::get(npc).getInventoryStore(npc); + MWWorld::InventoryStore& invStore = npc.getClass().getInventoryStore(npc); MWWorld::ContainerStoreIterator weapon = invStore.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); if(weapon == invStore.end()) diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index 60ffec7b9..19381a3fd 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -86,10 +86,7 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - if (ptr.getCellRef().mCharge == -1) - return ref->mBase->mData.mValue; - else - return ref->mBase->mData.mValue * (static_cast(ptr.getCellRef().mCharge) / getItemMaxHealth(ptr)); + return ref->mBase->mData.mValue * (static_cast(getItemHealth(ptr)) / getItemMaxHealth(ptr)); } void Lockpick::registerSelf() @@ -136,7 +133,7 @@ namespace MWClass std::string text; - int remainingUses = (ptr.getCellRef().mCharge != -1) ? ptr.getCellRef().mCharge : ref->mBase->mData.mUses; + int remainingUses = getItemHealth(ptr); text += "\n#{sUses}: " + MWGui::ToolTips::toString(remainingUses); text += "\n#{sQuality}: " + MWGui::ToolTips::toString(ref->mBase->mData.mQuality); @@ -144,8 +141,8 @@ namespace MWClass text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); - text += MWGui::ToolTips::getMiscString(ref->mRef.mFaction, "Faction"); + text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); + text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index e568bf869..1044fb01d 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -28,11 +28,11 @@ namespace { bool isGold (const MWWorld::Ptr& ptr) { - return Misc::StringUtils::ciEqual(ptr.getCellRef().mRefID, "gold_001") - || Misc::StringUtils::ciEqual(ptr.getCellRef().mRefID, "gold_005") - || Misc::StringUtils::ciEqual(ptr.getCellRef().mRefID, "gold_010") - || Misc::StringUtils::ciEqual(ptr.getCellRef().mRefID, "gold_025") - || Misc::StringUtils::ciEqual(ptr.getCellRef().mRefID, "gold_100"); + return Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_001") + || Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_005") + || Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_010") + || Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_025") + || Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_100"); } } @@ -94,12 +94,12 @@ namespace MWClass ptr.get(); int value = ref->mBase->mData.mValue; - if (ptr.getCellRef().mGoldValue > 1 && ptr.getRefData().getCount() == 1) - value = ptr.getCellRef().mGoldValue; + if (ptr.getCellRef().getGoldValue() > 1 && ptr.getRefData().getCount() == 1) + value = ptr.getCellRef().getGoldValue(); - if (ptr.getCellRef().mSoul != "") + if (ptr.getCellRef().getSoul() != "") { - const ESM::Creature *creature = MWBase::Environment::get().getWorld()->getStore().get().find(ref->mRef.mSoul); + const ESM::Creature *creature = MWBase::Environment::get().getWorld()->getStore().get().find(ref->mRef.getSoul()); value *= creature->mData.mSoul; } @@ -167,9 +167,9 @@ namespace MWClass info.caption = ref->mBase->mName + countString; info.icon = ref->mBase->mIcon; - if (ref->mRef.mSoul != "") + if (ref->mRef.getSoul() != "") { - const ESM::Creature *creature = store.get().find(ref->mRef.mSoul); + const ESM::Creature *creature = store.get().find(ref->mRef.getSoul()); info.caption += " (" + creature->mName + ")"; } @@ -182,8 +182,8 @@ namespace MWClass } if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); - text += MWGui::ToolTips::getMiscString(ref->mRef.mFaction, "Faction"); + text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); + text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } @@ -219,7 +219,7 @@ namespace MWClass MWWorld::LiveCellRef *ref = newRef.getPtr().get(); newPtr = MWWorld::Ptr(&cell.get().insert(*ref), &cell); - newPtr.getCellRef().mGoldValue = goldAmount; + newPtr.getCellRef().setGoldValue(goldAmount); newPtr.getRefData().setCount(1); } else { MWWorld::LiveCellRef *ref = @@ -231,7 +231,7 @@ namespace MWClass boost::shared_ptr Miscellaneous::use (const MWWorld::Ptr& ptr) const { - if (ptr.getCellRef().mSoul == "") + if (ptr.getCellRef().getSoul().empty()) return boost::shared_ptr(new MWWorld::NullAction()); else return boost::shared_ptr(new MWWorld::ActionSoulgem(ptr)); @@ -242,12 +242,7 @@ namespace MWClass MWWorld::LiveCellRef *ref = 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_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"); + return !ref->mBase->mData.mIsKey && (npcServices & ESM::NPC::Misc) && !isGold(item); } float Miscellaneous::getWeight(const MWWorld::Ptr &ptr) const diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 5a3ff10de..7405292b4 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -357,9 +357,6 @@ namespace MWClass data->mInventoryStore.fill(ref->mBase->mInventory, getId(ptr), "", MWBase::Environment::get().getWorld()->getStore()); - // Relates to NPC gold reset delay - data->mNpcStats.setTradeTime(MWWorld::TimeStamp(0.0, 0)); - data->mNpcStats.setGoldPool(gold); // store @@ -391,6 +388,8 @@ namespace MWClass { physics.addActor(ptr); MWBase::Environment::get().getMechanicsManager()->add(ptr); + if (getCreatureStats(ptr).isDead()) + MWBase::Environment::get().getWorld()->enableActorCollision(ptr, false); } bool Npc::isPersistent(const MWWorld::Ptr &actor) const @@ -485,10 +484,10 @@ namespace MWClass if(victim.isEmpty()) // Didn't hit anything return; - const MWWorld::Class &othercls = MWWorld::Class::get(victim); + const MWWorld::Class &othercls = victim.getClass(); if(!othercls.isActor()) // Can't hit non-actors return; - MWMechanics::CreatureStats &otherstats = victim.getClass().getCreatureStats(victim); + MWMechanics::CreatureStats &otherstats = othercls.getCreatureStats(victim); if(otherstats.isDead()) // Can't hit dead actors return; @@ -497,7 +496,7 @@ namespace MWClass int weapskill = ESM::Skill::HandToHand; if(!weapon.isEmpty()) - weapskill = get(weapon).getEquipmentSkill(weapon); + weapskill = weapon.getClass().getEquipmentSkill(weapon); float hitchance = MWMechanics::getHitChance(ptr, victim, ptr.getClass().getSkill(ptr, weapskill)); @@ -512,7 +511,7 @@ namespace MWClass MWMechanics::NpcStats &stats = getNpcStats(ptr); if(!weapon.isEmpty()) { - const bool weaphashealth = get(weapon).hasItemHealth(weapon); + const bool weaphashealth = weapon.getClass().hasItemHealth(weapon); const unsigned char *attack = NULL; if(type == ESM::Weapon::AT_Chop) attack = weapon.get()->mBase->mData.mChop; @@ -527,20 +526,24 @@ namespace MWClass (stats.getAttribute(ESM::Attribute::Strength).getModified() * fDamageStrengthMult->getFloat() * 0.1); if(weaphashealth) { - int weapmaxhealth = weapon.get()->mBase->mData.mHealth; - if(weapon.getCellRef().mCharge == -1) - weapon.getCellRef().mCharge = weapmaxhealth; - damage *= float(weapon.getCellRef().mCharge) / weapmaxhealth; - } + int weapmaxhealth = weapon.getClass().getItemMaxHealth(weapon); + int weaphealth = weapon.getClass().getItemHealth(weapon); - if (!MWBase::Environment::get().getWorld()->getGodModeState()) - weapon.getCellRef().mCharge -= std::min(std::max(1, - (int)(damage * gmst.find("fWeaponDamageMult")->getFloat())), weapon.getCellRef().mCharge); + damage *= float(weaphealth) / weapmaxhealth; - // Weapon broken? unequip it - if (weapon.getCellRef().mCharge == 0) - weapon = *inv.unequipItem(weapon, ptr); + if (!MWBase::Environment::get().getWorld()->getGodModeState()) + { + // Reduce weapon charge by at least one, but cap at 0 + weaphealth -= std::min(std::max(1, + (int)(damage * gmst.find("fWeaponDamageMult")->getFloat())), weaphealth); + weapon.getCellRef().setCharge(weaphealth); + } + + // Weapon broken? unequip it + if (weaphealth == 0) + weapon = *inv.unequipItem(weapon, ptr); + } } healthdmg = true; } @@ -621,9 +624,13 @@ namespace MWClass // NOTE: 'object' and/or 'attacker' may be empty. // Attacking peaceful NPCs is a crime - if (!attacker.isEmpty() && ptr.getClass().isNpc() && ptr.getClass().getCreatureStats(ptr).getAiSetting(MWMechanics::CreatureStats::AI_Fight).getModified() <= 30) + // anything below 80 is considered peaceful (see Actors::updateActor) + if (!attacker.isEmpty() && !ptr.getClass().getCreatureStats(ptr).isHostile() && + ptr.getClass().getCreatureStats(ptr).getAiSetting(MWMechanics::CreatureStats::AI_Fight).getModified() < 80) MWBase::Environment::get().getMechanicsManager()->commitCrime(attacker, ptr, MWBase::MechanicsManager::OT_Assault); + getCreatureStats(ptr).setAttacked(true); + if(!successful) { // TODO: Handle HitAttemptOnMe script function @@ -634,7 +641,7 @@ namespace MWClass } if(!object.isEmpty()) - getCreatureStats(ptr).setLastHitObject(get(object).getId(object)); + getCreatureStats(ptr).setLastHitObject(object.getClass().getId(object)); if(!attacker.isEmpty() && attacker.getRefData().getHandle() == "player") { @@ -659,7 +666,6 @@ namespace MWClass { MWBase::Environment::get().getDialogueManager()->say(ptr, "hit"); } - getCreatureStats(ptr).setAttacked(true); // Check for knockdown float agilityTerm = getCreatureStats(ptr).getAttribute(ESM::Attribute::Agility).getModified() * fKnockDownMult->getFloat(); @@ -703,20 +709,19 @@ namespace MWClass MWWorld::Ptr armor = ((armorslot != inv.end()) ? *armorslot : MWWorld::Ptr()); if(!armor.isEmpty() && armor.getTypeName() == typeid(ESM::Armor).name()) { - ESM::CellRef &armorref = armor.getCellRef(); - if(armorref.mCharge == -1) - armorref.mCharge = armor.get()->mBase->mData.mHealth; - armorref.mCharge -= std::min(std::max(1, (int)damagediff), - armorref.mCharge); + int armorhealth = armor.getClass().getItemHealth(armor); + armorhealth -= std::min(std::max(1, (int)damagediff), + armorhealth); + armor.getCellRef().setCharge(armorhealth); // Armor broken? unequip it - if (armorref.mCharge == 0) + if (armorhealth == 0) inv.unequipItem(armor, ptr); if (ptr.getRefData().getHandle() == "player") - skillUsageSucceeded(ptr, get(armor).getEquipmentSkill(armor), 0); + skillUsageSucceeded(ptr, armor.getClass().getEquipmentSkill(armor), 0); - switch(get(armor).getEquipmentSkill(armor)) + switch(armor.getClass().getEquipmentSkill(armor)) { case ESM::Skill::LightArmor: sndMgr->playSound3D(ptr, "Light Armor Hit", 1.0f, 1.0f); @@ -796,7 +801,11 @@ namespace MWClass boost::shared_ptr Npc::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { - if(get(actor).isNpc() && get(actor).getNpcStats(actor).isWerewolf()) + // player got activated by another NPC + if(ptr.getRefData().getHandle() == "player") + return boost::shared_ptr(new MWWorld::ActionTalk(actor)); + + if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Sound *sound = store.get().searchRandom("WolfNPC"); @@ -808,15 +817,11 @@ namespace MWClass } if(getCreatureStats(ptr).isDead()) return boost::shared_ptr(new MWWorld::ActionOpen(ptr, true)); - if(get(ptr).getCreatureStats(ptr).isHostile()) + if(ptr.getClass().getCreatureStats(ptr).isHostile()) return boost::shared_ptr(new MWWorld::FailedAction("#{sActorInCombat}")); if(getCreatureStats(actor).getStance(MWMechanics::CreatureStats::Stance_Sneak)) return boost::shared_ptr(new MWWorld::ActionOpen(ptr)); // stealing - // player got activated by another NPC - if(ptr.getRefData().getHandle() == "player") - return boost::shared_ptr(new MWWorld::ActionTalk(actor)); - return boost::shared_ptr(new MWWorld::ActionTalk(ptr)); } @@ -941,7 +946,7 @@ namespace MWClass if (fallHeight >= fallDistanceMin) { - const float acrobaticsSkill = MWWorld::Class::get(ptr).getNpcStats (ptr).getSkill(ESM::Skill::Acrobatics).getModified(); + const float acrobaticsSkill = ptr.getClass().getNpcStats (ptr).getSkill(ESM::Skill::Acrobatics).getModified(); const NpcCustomData *npcdata = static_cast(ptr.getRefData().getCustomData()); const float jumpSpellBonus = npcdata->mNpcStats.getMagicEffects().get(ESM::MagicEffect::Jump).mMagnitude; const float fallAcroBase = gmst.find("fFallAcroBase")->getFloat(); @@ -1105,7 +1110,7 @@ namespace MWClass { MWWorld::LiveCellRef *ref = it->get(); - int armorSkillType = MWWorld::Class::get(*it).getEquipmentSkill(*it); + int armorSkillType = it->getClass().getEquipmentSkill(*it); int armorSkill = stats.getSkill(armorSkillType).getModified(); if(ref->mBase->mData.mWeight == 0) @@ -1175,7 +1180,7 @@ namespace MWClass if(boots == inv.end() || boots->getTypeName() != typeid(ESM::Armor).name()) return "FootBareLeft"; - switch(Class::get(*boots).getEquipmentSkill(*boots)) + switch(boots->getClass().getEquipmentSkill(*boots)) { case ESM::Skill::LightArmor: return "FootLightLeft"; @@ -1202,7 +1207,7 @@ namespace MWClass if(boots == inv.end() || boots->getTypeName() != typeid(ESM::Armor).name()) return "FootBareRight"; - switch(Class::get(*boots).getEquipmentSkill(*boots)) + switch(boots->getClass().getEquipmentSkill(*boots)) { case ESM::Skill::LightArmor: return "FootLightRight"; @@ -1305,7 +1310,35 @@ namespace MWClass bool Npc::isClass(const MWWorld::Ptr& ptr, const std::string &className) const { - return ptr.get()->mBase->mClass == className; + return Misc::StringUtils::ciEqual(ptr.get()->mBase->mClass, className); + } + + void Npc::respawn(const MWWorld::Ptr &ptr) const + { + if (ptr.get()->mBase->mFlags & ESM::NPC::Respawn) + { + // Note we do not respawn moved references in the cell they were moved to. Instead they are respawned in the original cell. + // This also means we cannot respawn dynamically placed references with no content file connection. + if (ptr.getCellRef().getRefNum().mContentFile != -1) + { + if (ptr.getRefData().getCount() == 0) + ptr.getRefData().setCount(1); + + // Reset to original position + ESM::Position& pos = ptr.getRefData().getPosition(); + pos = ptr.getCellRef().getPosition(); + + ptr.getRefData().setCustomData(NULL); + } + } + } + + void Npc::restock(const MWWorld::Ptr& ptr) const + { + MWWorld::LiveCellRef *ref = ptr.get(); + const ESM::InventoryList& list = ref->mBase->mInventory; + MWWorld::ContainerStore& store = getContainerStore(ptr); + store.restock(list, ptr, ptr.getCellRef().getRefId(), ptr.getCellRef().getFaction()); } const ESM::GameSetting *Npc::fMinWalkSpeed; diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index 596bf0e56..356e358b9 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -170,6 +170,18 @@ namespace MWClass virtual int getBaseGold(const MWWorld::Ptr& ptr) const; virtual bool isClass(const MWWorld::Ptr& ptr, const std::string &className) const; + + virtual bool canSwim (const MWWorld::Ptr &ptr) const { + return true; + } + + virtual bool canWalk (const MWWorld::Ptr &ptr) const { + return true; + } + + virtual void respawn (const MWWorld::Ptr& ptr) const; + + virtual void restock (const MWWorld::Ptr& ptr) const; }; } diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 216f815cd..7440617c2 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -133,7 +133,7 @@ namespace MWClass // hide effects the player doesnt know about MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); - MWMechanics::NpcStats& npcStats = MWWorld::Class::get(player).getNpcStats (player); + MWMechanics::NpcStats& npcStats = player.getClass().getNpcStats (player); int alchemySkill = npcStats.getSkill (ESM::Skill::Alchemy).getBase(); int i=0; static const float fWortChanceValue = @@ -151,8 +151,8 @@ namespace MWClass info.isPotion = true; if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); - text += MWGui::ToolTips::getMiscString(ref->mRef.mFaction, "Faction"); + text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); + text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index d376270cb..5d076a3c5 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -85,10 +85,7 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - if (ptr.getCellRef().mCharge == -1) - return ref->mBase->mData.mValue; - else - return ref->mBase->mData.mValue * (static_cast(ptr.getCellRef().mCharge) / getItemMaxHealth(ptr)); + return ref->mBase->mData.mValue * (static_cast(getItemHealth(ptr)) / getItemMaxHealth(ptr)); } void Probe::registerSelf() @@ -135,7 +132,7 @@ namespace MWClass std::string text; - int remainingUses = (ptr.getCellRef().mCharge != -1) ? ptr.getCellRef().mCharge : ref->mBase->mData.mUses; + int remainingUses = getItemHealth(ptr); text += "\n#{sUses}: " + MWGui::ToolTips::toString(remainingUses); text += "\n#{sQuality}: " + MWGui::ToolTips::toString(ref->mBase->mData.mQuality); @@ -143,8 +140,8 @@ namespace MWClass text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); - text += MWGui::ToolTips::getMiscString(ref->mRef.mFaction, "Faction"); + text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); + text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index af79a9691..9b528a4fc 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -76,10 +76,7 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - if (ptr.getCellRef().mCharge == -1) - return ref->mBase->mData.mValue; - else - return ref->mBase->mData.mValue * (static_cast(ptr.getCellRef().mCharge) / getItemMaxHealth(ptr)); + return ref->mBase->mData.mValue * (static_cast(getItemHealth(ptr)) / getItemMaxHealth(ptr)); } void Repair::registerSelf() @@ -139,7 +136,7 @@ namespace MWClass std::string text; - int remainingUses = (ptr.getCellRef().mCharge != -1) ? ptr.getCellRef().mCharge : ref->mBase->mData.mUses; + int remainingUses = getItemHealth(ptr); text += "\n#{sUses}: " + MWGui::ToolTips::toString(remainingUses); text += "\n#{sQuality}: " + MWGui::ToolTips::toString(ref->mBase->mData.mQuality); @@ -147,8 +144,8 @@ namespace MWClass text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); - text += MWGui::ToolTips::getMiscString(ref->mRef.mFaction, "Faction"); + text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); + text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index e9b0c8f3c..26618c021 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -154,10 +154,7 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - if (ptr.getCellRef().mCharge == -1) - return ref->mBase->mData.mValue; - else - return ref->mBase->mData.mValue * (static_cast(ptr.getCellRef().mCharge) / getItemMaxHealth(ptr)); + return ref->mBase->mData.mValue * (static_cast(getItemHealth(ptr)) / getItemMaxHealth(ptr)); } void Weapon::registerSelf() @@ -340,7 +337,7 @@ namespace MWClass if (ref->mBase->mData.mType < 11) // thrown weapons and arrows/bolts don't have health, only quantity { - int remainingHealth = (ptr.getCellRef().mCharge != -1) ? ptr.getCellRef().mCharge : ref->mBase->mData.mHealth; + int remainingHealth = getItemHealth(ptr); text += "\n#{sCondition}: " + MWGui::ToolTips::toString(remainingHealth) + "/" + MWGui::ToolTips::toString(ref->mBase->mData.mHealth); } @@ -351,11 +348,11 @@ namespace MWClass info.enchant = ref->mBase->mEnchant; if (!info.enchant.empty()) - info.remainingEnchantCharge = ptr.getCellRef().mEnchantmentCharge; + info.remainingEnchantCharge = ptr.getCellRef().getEnchantmentCharge(); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); - text += MWGui::ToolTips::getMiscString(ref->mRef.mFaction, "Faction"); + text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getOwner(), "Owner"); + text += MWGui::ToolTips::getMiscString(ptr.getCellRef().getFaction(), "Faction"); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script"); } @@ -372,7 +369,7 @@ namespace MWClass return ref->mBase->mEnchant; } - void Weapon::applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const + std::string Weapon::applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const { MWWorld::LiveCellRef *ref = ptr.get(); @@ -383,16 +380,15 @@ namespace MWClass newItem.mData.mEnchant=enchCharge; newItem.mEnchant=enchId; const ESM::Weapon *record = MWBase::Environment::get().getWorld()->createRecord (newItem); - ref->mBase = record; - ref->mRef.mRefID = record->mId; + return record->mId; } std::pair Weapon::canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const { - if (ptr.getCellRef().mCharge == 0) + if (ptr.getCellRef().getCharge() == 0) return std::make_pair(0, "#{sInventoryMessage1}"); - std::pair, bool> slots_ = MWWorld::Class::get(ptr).getEquipmentSlots(ptr); + std::pair, bool> slots_ = ptr.getClass().getEquipmentSlots(ptr); if (slots_.first.empty()) return std::make_pair (0, ""); diff --git a/apps/openmw/mwclass/weapon.hpp b/apps/openmw/mwclass/weapon.hpp index db44cd2b7..97ee10291 100644 --- a/apps/openmw/mwclass/weapon.hpp +++ b/apps/openmw/mwclass/weapon.hpp @@ -68,7 +68,8 @@ namespace MWClass virtual std::string getEnchantment (const MWWorld::Ptr& ptr) const; ///< @return the enchantment ID if the object is enchanted, otherwise an empty string - virtual void applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const; + virtual std::string applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const; + ///< Creates a new record using \a ptr as template, with the given name and the given enchantment applied to it. virtual std::pair canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const; ///< Return 0 if player cannot equip item. 1 if can equip. 2 if it's twohanded weapon. 3 if twohanded weapon conflicts with that. diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 88f1302bb..b6cef2fe7 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -134,17 +134,16 @@ namespace MWDialogue mActor = actor; - MWMechanics::CreatureStats& creatureStats = MWWorld::Class::get (actor).getCreatureStats (actor); + MWMechanics::CreatureStats& creatureStats = actor.getClass().getCreatureStats (actor); mTalkedTo = creatureStats.hasTalkedToPlayer(); mActorKnownTopics.clear(); MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow(); - win->startDialogue(actor, MWWorld::Class::get (actor).getName (actor)); + win->startDialogue(actor, actor.getClass().getName (actor)); //setup the list of topics known by the actor. Topics who are also on the knownTopics list will be added to the GUI updateTopics(); - updateGlobals(); //greeting const MWWorld::Store &dialogs = @@ -195,7 +194,7 @@ namespace MWDialogue Compiler::Locals locals; - std::string actorScript = MWWorld::Class::get (mActor).getScript (mActor); + std::string actorScript = mActor.getClass().getScript (mActor); if (!actorScript.empty()) { @@ -287,7 +286,18 @@ namespace MWDialogue MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor); win->addResponse (Interpreter::fixDefinesDialog(info->mResponse, interpreterContext), title); - MWBase::Environment::get().getJournal()->addTopic (topic, info->mId, mActor.getClass().getName(mActor)); + + // Make sure the returned DialInfo is from the Dialogue we supplied. If could also be from the Info refusal group, + // in which case it should not be added to the journal. + for (ESM::Dialogue::InfoContainer::const_iterator iter = dialogue.mInfo.begin(); + iter!=dialogue.mInfo.end(); ++iter) + { + if (iter->mId == info->mId) + { + MWBase::Environment::get().getJournal()->addTopic (topic, info->mId, mActor.getClass().getName(mActor)); + break; + } + } executeScript (info->mResultScript); @@ -392,6 +402,8 @@ namespace MWDialogue win->setKeywords(keywordList); mChoice = choice; + + updateGlobals(); } void DialogueManager::keywordSelected (const std::string& keyword) @@ -423,7 +435,7 @@ namespace MWDialogue // Apply disposition change to NPC's base disposition if (mActor.getClass().isNpc()) { - MWMechanics::NpcStats& npcStats = MWWorld::Class::get(mActor).getNpcStats(mActor); + MWMechanics::NpcStats& npcStats = mActor.getClass().getNpcStats(mActor); npcStats.setBaseDisposition(npcStats.getBaseDisposition() + mPermanentDispositionChange); } mPermanentDispositionChange = 0; @@ -452,7 +464,19 @@ namespace MWDialogue MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor); MWBase::Environment::get().getWindowManager()->getDialogueWindow()->addResponse (Interpreter::fixDefinesDialog(text, interpreterContext)); - MWBase::Environment::get().getJournal()->addTopic (mLastTopic, info->mId, mActor.getClass().getName(mActor)); + + // Make sure the returned DialInfo is from the Dialogue we supplied. If could also be from the Info refusal group, + // in which case it should not be added to the journal. + for (ESM::Dialogue::InfoContainer::const_iterator iter = mDialogueMap[mLastTopic].mInfo.begin(); + iter!=mDialogueMap[mLastTopic].mInfo.end(); ++iter) + { + if (iter->mId == info->mId) + { + MWBase::Environment::get().getJournal()->addTopic (mLastTopic, info->mId, mActor.getClass().getName(mActor)); + break; + } + } + executeScript (info->mResultScript); } } @@ -468,11 +492,6 @@ namespace MWDialogue mIsInChoice = true; } - MWWorld::Ptr DialogueManager::getActor() const - { - return mActor; - } - void DialogueManager::goodbye() { mIsInChoice = true; @@ -614,7 +633,7 @@ namespace MWDialogue return 1; // known topics } - void DialogueManager::write (ESM::ESMWriter& writer) const + void DialogueManager::write (ESM::ESMWriter& writer, Loading::Listener& progress) const { ESM::DialogueState state; @@ -623,9 +642,12 @@ namespace MWDialogue if (iter->second) state.mKnownTopics.push_back (iter->first); + state.mModFactionReaction = mModFactionReaction; + writer.startRecord (ESM::REC_DIAS); state.save (writer); writer.endRecord (ESM::REC_DIAS); + progress.increaseProgress(); } void DialogueManager::readRecord (ESM::ESMReader& reader, int32_t type) @@ -641,9 +663,46 @@ namespace MWDialogue iter!=state.mKnownTopics.end(); ++iter) if (store.get().search (*iter)) mKnownTopics.insert (std::make_pair (*iter, true)); + + mModFactionReaction = state.mModFactionReaction; } } + void DialogueManager::modFactionReaction(const std::string &faction1, const std::string &faction2, int diff) + { + std::string fact1 = Misc::StringUtils::lowerCase(faction1); + std::string fact2 = Misc::StringUtils::lowerCase(faction2); + + // Make sure the factions exist + MWBase::Environment::get().getWorld()->getStore().get().find(fact1); + MWBase::Environment::get().getWorld()->getStore().get().find(fact2); + + std::map& map = mModFactionReaction[fact1]; + if (map.find(fact2) == map.end()) + map[fact2] = 0; + map[fact2] += diff; + } + + int DialogueManager::getFactionReaction(const std::string &faction1, const std::string &faction2) const + { + std::string fact1 = Misc::StringUtils::lowerCase(faction1); + std::string fact2 = Misc::StringUtils::lowerCase(faction2); + + ModFactionReactionMap::const_iterator map = mModFactionReaction.find(fact1); + int diff = 0; + if (map != mModFactionReaction.end() && map->second.find(fact2) != map->second.end()) + diff = map->second.at(fact2); + + const ESM::Faction* faction = MWBase::Environment::get().getWorld()->getStore().get().find(fact1); + + std::map::const_iterator it = faction->mReactions.begin(); + for (; it != faction->mReactions.end(); ++it) + { + if (Misc::StringUtils::ciEqual(it->first, fact2)) + return it->second + diff; + } + return diff; + } std::vector ParseHyperText(const std::string& text) { diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp index b9284dc1a..db0b78d59 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp @@ -24,6 +24,11 @@ namespace MWDialogue { std::map mDialogueMap; std::map mKnownTopics;// Those are the topics the player knows. + + // Modified faction reactions. > + typedef std::map > ModFactionReactionMap; + ModFactionReactionMap mModFactionReaction; + std::list mActorKnownTopics; Translation::Storage& mTranslationDataStorage; @@ -68,9 +73,6 @@ namespace MWDialogue virtual void goodbye(); - virtual MWWorld::Ptr getActor() const; - ///< Return the actor the player is currently talking to. - virtual bool checkServiceRefused (); virtual void say(const MWWorld::Ptr &actor, const std::string &topic) const; @@ -86,9 +88,15 @@ namespace MWDialogue virtual int countSavedGameRecords() const; - virtual void write (ESM::ESMWriter& writer) const; + virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) const; virtual void readRecord (ESM::ESMReader& reader, int32_t type); + + /// Changes faction1's opinion of faction2 by \a diff. + virtual void modFactionReaction (const std::string& faction1, const std::string& faction2, int diff); + + /// @return faction1's opinion of faction2 + virtual int getFactionReaction (const std::string& faction1, const std::string& faction2) const; }; diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp index 18ae7dd1b..d301e88aa 100644 --- a/apps/openmw/mwdialogue/filter.cpp +++ b/apps/openmw/mwdialogue/filter.cpp @@ -25,7 +25,7 @@ bool MWDialogue::Filter::testActor (const ESM::DialInfo& info) const // actor id if (!info.mActor.empty()) { - if ( !Misc::StringUtils::ciEqual(info.mActor, MWWorld::Class::get (mActor).getId (mActor))) + if ( !Misc::StringUtils::ciEqual(info.mActor, mActor.getClass().getId (mActor))) return false; } else if (isCreature) @@ -64,7 +64,7 @@ bool MWDialogue::Filter::testActor (const ESM::DialInfo& info) const if (isCreature) return false; - MWMechanics::NpcStats& stats = MWWorld::Class::get (mActor).getNpcStats (mActor); + MWMechanics::NpcStats& stats = mActor.getClass().getNpcStats (mActor); std::map::iterator iter = stats.getFactionRanks().find ( Misc::StringUtils::lowerCase (info.mFaction)); if (iter==stats.getFactionRanks().end()) @@ -98,7 +98,7 @@ bool MWDialogue::Filter::testPlayer (const ESM::DialInfo& info) const // check player faction if (!info.mPcFaction.empty()) { - MWMechanics::NpcStats& stats = MWWorld::Class::get (player).getNpcStats (player); + MWMechanics::NpcStats& stats = player.getClass().getNpcStats (player); std::map::iterator iter = stats.getFactionRanks().find (Misc::StringUtils::lowerCase (info.mPcFaction)); if(iter==stats.getFactionRanks().end()) @@ -111,8 +111,14 @@ bool MWDialogue::Filter::testPlayer (const ESM::DialInfo& info) const // check cell if (!info.mCell.empty()) - if (!Misc::StringUtils::ciEqual(player.getCell()->getCell()->mName, info.mCell)) + { + // supports partial matches, just like getPcCell + const std::string& playerCell = player.getCell()->getCell()->mName; + bool match = playerCell.length()>=info.mCell.length() && + Misc::StringUtils::ciEqual(playerCell.substr (0, info.mCell.length()), info.mCell); + if (!match) return false; + } return true; } @@ -176,7 +182,7 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c case SelectWrapper::Function_Local: { - std::string scriptName = MWWorld::Class::get (mActor).getScript (mActor); + std::string scriptName = mActor.getClass().getScript (mActor); if (scriptName.empty()) return false; // no script @@ -214,8 +220,8 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - float ratio = MWWorld::Class::get (player).getCreatureStats (player).getHealth().getCurrent() / - MWWorld::Class::get (player).getCreatureStats (player).getHealth().getModified(); + float ratio = player.getClass().getCreatureStats (player).getHealth().getCurrent() / + player.getClass().getCreatureStats (player).getHealth().getModified(); return select.selectCompare (static_cast(ratio*100)); } @@ -224,7 +230,7 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - float value = MWWorld::Class::get (player).getCreatureStats (player). + float value = player.getClass().getCreatureStats (player). getDynamic (select.getArgument()).getCurrent(); return select.selectCompare (value); @@ -232,8 +238,8 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c case SelectWrapper::Function_HealthPercent: { - float ratio = MWWorld::Class::get (mActor).getCreatureStats (mActor).getHealth().getCurrent() / - MWWorld::Class::get (mActor).getCreatureStats (mActor).getHealth().getModified(); + float ratio = mActor.getClass().getCreatureStats (mActor).getHealth().getCurrent() / + mActor.getClass().getCreatureStats (mActor).getHealth().getModified(); return select.selectCompare (ratio); } @@ -256,17 +262,9 @@ int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) con case SelectWrapper::Function_Item: { - MWWorld::ContainerStore& store = MWWorld::Class::get (player).getContainerStore (player); - - int sum = 0; - - std::string name = select.getName(); + MWWorld::ContainerStore& store = player.getClass().getContainerStore (player); - for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end(); ++iter) - if (Misc::StringUtils::ciEqual(iter->getCellRef().mRefID, name)) - sum += iter->getRefData().getCount(); - - return sum; + return store.count(select.getName()); } case SelectWrapper::Function_Dead: @@ -279,29 +277,29 @@ int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) con case SelectWrapper::Function_AiSetting: - return MWWorld::Class::get (mActor).getCreatureStats (mActor).getAiSetting ( + return mActor.getClass().getCreatureStats (mActor).getAiSetting ( (MWMechanics::CreatureStats::AiSetting)select.getArgument()).getModified(); case SelectWrapper::Function_PcAttribute: - return MWWorld::Class::get (player).getCreatureStats (player). + return player.getClass().getCreatureStats (player). getAttribute (select.getArgument()).getModified(); case SelectWrapper::Function_PcSkill: - return static_cast (MWWorld::Class::get (player). + return static_cast (player.getClass(). getNpcStats (player).getSkill (select.getArgument()).getModified()); case SelectWrapper::Function_FriendlyHit: { - int hits = MWWorld::Class::get (mActor).getCreatureStats (mActor).getFriendlyHits(); + int hits = mActor.getClass().getCreatureStats (mActor).getFriendlyHits(); return hits>4 ? 4 : hits; } case SelectWrapper::Function_PcLevel: - return MWWorld::Class::get (player).getCreatureStats (player).getLevel(); + return player.getClass().getCreatureStats (player).getLevel(); case SelectWrapper::Function_PcGender: @@ -309,7 +307,7 @@ int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) con case SelectWrapper::Function_PcClothingModifier: { - MWWorld::InventoryStore& store = MWWorld::Class::get (player).getInventoryStore (player); + MWWorld::InventoryStore& store = player.getClass().getInventoryStore (player); int value = 0; @@ -318,7 +316,7 @@ int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) con MWWorld::ContainerStoreIterator slot = store.getSlot (i); if (slot!=store.end()) - value += MWWorld::Class::get (*slot).getValue (*slot); + value += slot->getClass().getValue (*slot); } return value; @@ -326,15 +324,15 @@ int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) con case SelectWrapper::Function_PcCrimeLevel: - return MWWorld::Class::get (player).getNpcStats (player).getBounty(); + return player.getClass().getNpcStats (player).getBounty(); case SelectWrapper::Function_RankRequirement: { - if (MWWorld::Class::get (mActor).getNpcStats (mActor).getFactionRanks().empty()) + if (mActor.getClass().getNpcStats (mActor).getFactionRanks().empty()) return 0; std::string faction = - MWWorld::Class::get (mActor).getNpcStats (mActor).getFactionRanks().begin()->first; + mActor.getClass().getNpcStats (mActor).getFactionRanks().begin()->first; int rank = getFactionRank (player, faction); @@ -354,11 +352,11 @@ int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) con case SelectWrapper::Function_Level: - return MWWorld::Class::get (mActor).getCreatureStats (mActor).getLevel(); + return mActor.getClass().getCreatureStats (mActor).getLevel(); case SelectWrapper::Function_PCReputation: - return MWWorld::Class::get (player).getNpcStats (player).getReputation(); + return player.getClass().getNpcStats (player).getReputation(); case SelectWrapper::Function_Weather: @@ -366,15 +364,15 @@ int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) con case SelectWrapper::Function_Reputation: - return MWWorld::Class::get (mActor).getNpcStats (mActor).getReputation(); + return mActor.getClass().getNpcStats (mActor).getReputation(); case SelectWrapper::Function_FactionRankDiff: { - if (MWWorld::Class::get (mActor).getNpcStats (mActor).getFactionRanks().empty()) + if (mActor.getClass().getNpcStats (mActor).getFactionRanks().empty()) return 0; std::pair faction = - *MWWorld::Class::get (mActor).getNpcStats (mActor).getFactionRanks().begin(); + *mActor.getClass().getNpcStats (mActor).getFactionRanks().begin(); int rank = getFactionRank (player, faction.first); @@ -383,31 +381,30 @@ int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) con case SelectWrapper::Function_WerewolfKills: - return MWWorld::Class::get (player).getNpcStats (player).getWerewolfKills(); + return player.getClass().getNpcStats (player).getWerewolfKills(); case SelectWrapper::Function_RankLow: case SelectWrapper::Function_RankHigh: { bool low = select.getFunction()==SelectWrapper::Function_RankLow; - if (MWWorld::Class::get (mActor).getNpcStats (mActor).getFactionRanks().empty()) + if (mActor.getClass().getNpcStats (mActor).getFactionRanks().empty()) return 0; std::string factionId = - MWWorld::Class::get (mActor).getNpcStats (mActor).getFactionRanks().begin()->first; + mActor.getClass().getNpcStats (mActor).getFactionRanks().begin()->first; int value = 0; - const ESM::Faction& faction = - *MWBase::Environment::get().getWorld()->getStore().get().find (factionId); - - MWMechanics::NpcStats& playerStats = MWWorld::Class::get (player).getNpcStats (player); + MWMechanics::NpcStats& playerStats = player.getClass().getNpcStats (player); - for (std::vector::const_iterator iter (faction.mReactions.begin()); - iter!=faction.mReactions.end(); ++iter) - if (playerStats.getFactionRanks().find (iter->mFaction)!=playerStats.getFactionRanks().end()) - if (low ? iter->mReactionmReaction>value) - value = iter->mReaction; + std::map::const_iterator playerFactionIt = playerStats.getFactionRanks().begin(); + for (; playerFactionIt != playerStats.getFactionRanks().end(); ++playerFactionIt) + { + int reaction = MWBase::Environment::get().getDialogueManager()->getFactionReaction(factionId, playerFactionIt->first); + if (low ? reaction < value : reaction > value) + value = reaction; + } return value; } @@ -430,7 +427,7 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co case SelectWrapper::Function_NotId: - return !Misc::StringUtils::ciEqual(MWWorld::Class::get (mActor).getId (mActor), select.getName()); + return !Misc::StringUtils::ciEqual(mActor.getClass().getId (mActor), select.getName()); case SelectWrapper::Function_NotFaction: @@ -450,7 +447,7 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co case SelectWrapper::Function_NotLocal: { - std::string scriptName = MWWorld::Class::get (mActor).getScript (mActor); + std::string scriptName = mActor.getClass().getScript (mActor); if (scriptName.empty()) // This actor has no attached script, so there is no local variable @@ -483,36 +480,36 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co case SelectWrapper::Function_SameFaction: - return MWWorld::Class::get (mActor).getNpcStats (mActor).isSameFaction ( - MWWorld::Class::get (player).getNpcStats (player)); + return mActor.getClass().getNpcStats (mActor).isSameFaction ( + player.getClass().getNpcStats (player)); case SelectWrapper::Function_PcCommonDisease: - return MWWorld::Class::get (player).getCreatureStats (player).hasCommonDisease(); + return player.getClass().getCreatureStats (player).hasCommonDisease(); case SelectWrapper::Function_PcBlightDisease: - return MWWorld::Class::get (player).getCreatureStats (player).hasBlightDisease(); + return player.getClass().getCreatureStats (player).hasBlightDisease(); case SelectWrapper::Function_PcCorprus: - return MWWorld::Class::get (player).getCreatureStats (player). + return player.getClass().getCreatureStats (player). getMagicEffects().get (ESM::MagicEffect::Corprus).mMagnitude!=0; case SelectWrapper::Function_PcExpelled: { - if (MWWorld::Class::get (mActor).getNpcStats (mActor).getFactionRanks().empty()) + if (mActor.getClass().getNpcStats (mActor).getFactionRanks().empty()) return false; std::string faction = - MWWorld::Class::get (mActor).getNpcStats (mActor).getFactionRanks().begin()->first; + mActor.getClass().getNpcStats (mActor).getFactionRanks().begin()->first; return player.getClass().getNpcStats(player).getExpelled(faction); } case SelectWrapper::Function_PcVampire: - return MWWorld::Class::get (player).getCreatureStats(player).getMagicEffects(). + return player.getClass().getCreatureStats(player).getMagicEffects(). get(ESM::MagicEffect::Vampirism).mMagnitude > 0; case SelectWrapper::Function_TalkedToPc: @@ -521,7 +518,7 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co case SelectWrapper::Function_Alarmed: - return MWWorld::Class::get (mActor).getCreatureStats (mActor).isAlarmed(); + return mActor.getClass().getCreatureStats (mActor).isAlarmed(); case SelectWrapper::Function_Detected: @@ -529,11 +526,11 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co case SelectWrapper::Function_Attacked: - return MWWorld::Class::get (mActor).getCreatureStats (mActor).getAttacked(); + return mActor.getClass().getCreatureStats (mActor).getAttacked(); case SelectWrapper::Function_ShouldAttack: - return MWWorld::Class::get (mActor).getCreatureStats (mActor).isHostile(); + return mActor.getClass().getCreatureStats(mActor).getAiSetting(MWMechanics::CreatureStats::AI_Fight).getModified() >= 80; case SelectWrapper::Function_CreatureTargetted: @@ -541,7 +538,7 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co case SelectWrapper::Function_Werewolf: - return MWWorld::Class::get (mActor).getNpcStats (mActor).isWerewolf(); + return mActor.getClass().getNpcStats (mActor).isWerewolf(); default: @@ -551,7 +548,7 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co int MWDialogue::Filter::getFactionRank (const MWWorld::Ptr& actor, const std::string& factionId) const { - MWMechanics::NpcStats& stats = MWWorld::Class::get (actor).getNpcStats (actor); + MWMechanics::NpcStats& stats = actor.getClass().getNpcStats (actor); std::map::const_iterator iter = stats.getFactionRanks().find (factionId); @@ -567,13 +564,13 @@ bool MWDialogue::Filter::hasFactionRankSkillRequirements (const MWWorld::Ptr& ac if (rank<0 || rank>=10) throw std::runtime_error ("rank index out of range"); - if (!MWWorld::Class::get (actor).getNpcStats (actor).hasSkillsForRank (factionId, rank)) + if (!actor.getClass().getNpcStats (actor).hasSkillsForRank (factionId, rank)) return false; const ESM::Faction& faction = *MWBase::Environment::get().getWorld()->getStore().get().find (factionId); - MWMechanics::CreatureStats& stats = MWWorld::Class::get (actor).getCreatureStats (actor); + MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats (actor); return stats.getAttribute (faction.mData.mAttribute[0]).getBase()>=faction.mData.mRankData[rank].mAttribute1 && stats.getAttribute (faction.mData.mAttribute[1]).getBase()>=faction.mData.mRankData[rank].mAttribute2; @@ -585,7 +582,7 @@ bool MWDialogue::Filter::hasFactionRankReputationRequirements (const MWWorld::Pt if (rank<0 || rank>=10) throw std::runtime_error ("rank index out of range"); - MWMechanics::NpcStats& stats = MWWorld::Class::get (actor).getNpcStats (actor); + MWMechanics::NpcStats& stats = actor.getClass().getNpcStats (actor); const ESM::Faction& faction = *MWBase::Environment::get().getWorld()->getStore().get().find (factionId); @@ -615,7 +612,7 @@ std::vector MWDialogue::Filter::list (const ESM::Dialogue bool infoRefusal = false; // Iterate over topic responses to find a matching one - for (std::vector::const_iterator iter = dialogue.mInfo.begin(); + for (ESM::Dialogue::InfoContainer::const_iterator iter = dialogue.mInfo.begin(); iter!=dialogue.mInfo.end(); ++iter) { if (testActor (*iter) && testPlayer (*iter) && testSelectStructs (*iter)) @@ -640,7 +637,7 @@ std::vector MWDialogue::Filter::list (const ESM::Dialogue const ESM::Dialogue& infoRefusalDialogue = *dialogues.find ("Info Refusal"); - for (std::vector::const_iterator iter = infoRefusalDialogue.mInfo.begin(); + for (ESM::Dialogue::InfoContainer::const_iterator iter = infoRefusalDialogue.mInfo.begin(); iter!=infoRefusalDialogue.mInfo.end(); ++iter) if (testActor (*iter) && testPlayer (*iter) && testSelectStructs (*iter) && testDisposition(*iter, invertDisposition)) { infos.push_back(&*iter); @@ -654,7 +651,7 @@ std::vector MWDialogue::Filter::list (const ESM::Dialogue bool MWDialogue::Filter::responseAvailable (const ESM::Dialogue& dialogue) const { - for (std::vector::const_iterator iter = dialogue.mInfo.begin(); + for (ESM::Dialogue::InfoContainer::const_iterator iter = dialogue.mInfo.begin(); iter!=dialogue.mInfo.end(); ++iter) { if (testActor (*iter) && testPlayer (*iter) && testSelectStructs (*iter)) diff --git a/apps/openmw/mwdialogue/filter.hpp b/apps/openmw/mwdialogue/filter.hpp index 5c6d092ad..7e7f2b6f5 100644 --- a/apps/openmw/mwdialogue/filter.hpp +++ b/apps/openmw/mwdialogue/filter.hpp @@ -55,6 +55,7 @@ namespace MWDialogue std::vector list (const ESM::Dialogue& dialogue, bool fallbackToInfoRefusal, bool searchAll, bool invertDisposition=false) const; + ///< \note If fallbackToInfoRefusal is used, the returned DialInfo might not be from the supplied ESM::Dialogue. const ESM::DialInfo* search (const ESM::Dialogue& dialogue, const bool fallbackToInfoRefusal) const; ///< Get a matching response for the requested dialogue. diff --git a/apps/openmw/mwdialogue/journalentry.cpp b/apps/openmw/mwdialogue/journalentry.cpp index 9463e4c45..55574bf3e 100644 --- a/apps/openmw/mwdialogue/journalentry.cpp +++ b/apps/openmw/mwdialogue/journalentry.cpp @@ -20,7 +20,7 @@ namespace MWDialogue const ESM::Dialogue *dialogue = MWBase::Environment::get().getWorld()->getStore().get().find (topic); - for (std::vector::const_iterator iter (dialogue->mInfo.begin()); + for (ESM::Dialogue::InfoContainer::const_iterator iter (dialogue->mInfo.begin()); iter!=dialogue->mInfo.end(); ++iter) if (iter->mId == mInfoId) { @@ -73,7 +73,7 @@ namespace MWDialogue const ESM::Dialogue *dialogue = MWBase::Environment::get().getWorld()->getStore().get().find (topic); - for (std::vector::const_iterator iter (dialogue->mInfo.begin()); + for (ESM::Dialogue::InfoContainer::const_iterator iter (dialogue->mInfo.begin()); iter!=dialogue->mInfo.end(); ++iter) if (iter->mData.mDisposition==index) /// \todo cleanup info structure { diff --git a/apps/openmw/mwdialogue/journalimp.cpp b/apps/openmw/mwdialogue/journalimp.cpp index 26383b3a7..04aa0534d 100644 --- a/apps/openmw/mwdialogue/journalimp.cpp +++ b/apps/openmw/mwdialogue/journalimp.cpp @@ -56,7 +56,7 @@ namespace MWDialogue if (infoId.empty()) return true; - for (std::vector::const_iterator iter (dialogue->mInfo.begin()); + for (ESM::Dialogue::InfoContainer::const_iterator iter (dialogue->mInfo.begin()); iter!=dialogue->mInfo.end(); ++iter) if (iter->mId == infoId) return true; @@ -167,7 +167,7 @@ namespace MWDialogue return count; } - void Journal::write (ESM::ESMWriter& writer) const + void Journal::write (ESM::ESMWriter& writer, Loading::Listener& progress) const { for (TQuestIter iter (mQuests.begin()); iter!=mQuests.end(); ++iter) { @@ -178,6 +178,7 @@ namespace MWDialogue writer.startRecord (ESM::REC_QUES); state.save (writer); writer.endRecord (ESM::REC_QUES); + progress.increaseProgress(); for (Topic::TEntryIter iter (quest.begin()); iter!=quest.end(); ++iter) { @@ -188,6 +189,7 @@ namespace MWDialogue writer.startRecord (ESM::REC_JOUR); entry.save (writer); writer.endRecord (ESM::REC_JOUR); + progress.increaseProgress(); } } @@ -199,6 +201,7 @@ namespace MWDialogue writer.startRecord (ESM::REC_JOUR); entry.save (writer); writer.endRecord (ESM::REC_JOUR); + progress.increaseProgress(); } for (TTopicIter iter (mTopics.begin()); iter!=mTopics.end(); ++iter) @@ -214,6 +217,7 @@ namespace MWDialogue writer.startRecord (ESM::REC_JOUR); entry.save (writer); writer.endRecord (ESM::REC_JOUR); + progress.increaseProgress(); } } } diff --git a/apps/openmw/mwdialogue/journalimp.hpp b/apps/openmw/mwdialogue/journalimp.hpp index 1b4803ba2..00511f47c 100644 --- a/apps/openmw/mwdialogue/journalimp.hpp +++ b/apps/openmw/mwdialogue/journalimp.hpp @@ -64,7 +64,7 @@ namespace MWDialogue virtual int countSavedGameRecords() const; - virtual void write (ESM::ESMWriter& writer) const; + virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) const; virtual void readRecord (ESM::ESMReader& reader, int32_t type); }; diff --git a/apps/openmw/mwdialogue/quest.cpp b/apps/openmw/mwdialogue/quest.cpp index a5411b747..a699286a1 100644 --- a/apps/openmw/mwdialogue/quest.cpp +++ b/apps/openmw/mwdialogue/quest.cpp @@ -27,7 +27,7 @@ namespace MWDialogue const ESM::Dialogue *dialogue = MWBase::Environment::get().getWorld()->getStore().get().find (mTopic); - for (std::vector::const_iterator iter (dialogue->mInfo.begin()); + for (ESM::Dialogue::InfoContainer::const_iterator iter (dialogue->mInfo.begin()); iter!=dialogue->mInfo.end(); ++iter) if (iter->mQuestStatus==ESM::DialInfo::QS_Name) return iter->mResponse; @@ -45,8 +45,7 @@ namespace MWDialogue const ESM::Dialogue *dialogue = MWBase::Environment::get().getWorld()->getStore().get().find (mTopic); - bool found=false; - for (std::vector::const_iterator iter (dialogue->mInfo.begin()); + for (ESM::Dialogue::InfoContainer::const_iterator iter (dialogue->mInfo.begin()); iter!=dialogue->mInfo.end(); ++iter) if (iter->mData.mDisposition==index && iter->mQuestStatus!=ESM::DialInfo::QS_Name) { @@ -54,15 +53,10 @@ namespace MWDialogue mFinished = true; else if (iter->mQuestStatus==ESM::DialInfo::QS_Restart) mFinished = false; - - found = true; - // Don't return here. Quest status may actually be in a different info record, since we don't merge these (yet?) } - if (found) - mIndex = index; - else - throw std::runtime_error ("unknown journal index for topic " + mTopic); + // The index must be set even if no related journal entry was found + mIndex = index; } bool Quest::isFinished() const @@ -77,7 +71,7 @@ namespace MWDialogue const ESM::Dialogue *dialogue = MWBase::Environment::get().getWorld()->getStore().get().find (entry.mTopic); - for (std::vector::const_iterator iter (dialogue->mInfo.begin()); + for (ESM::Dialogue::InfoContainer::const_iterator iter (dialogue->mInfo.begin()); iter!=dialogue->mInfo.end(); ++iter) if (iter->mId == entry.mInfoId) { diff --git a/apps/openmw/mwgui/alchemywindow.cpp b/apps/openmw/mwgui/alchemywindow.cpp index a6880ffcb..ab04189a6 100644 --- a/apps/openmw/mwgui/alchemywindow.cpp +++ b/apps/openmw/mwgui/alchemywindow.cpp @@ -19,7 +19,7 @@ namespace std::string getIconPath(MWWorld::Ptr ptr) { std::string path = std::string("icons\\"); - path += MWWorld::Class::get(ptr).getInventoryIcon(ptr); + path += ptr.getClass().getInventoryIcon(ptr); int pos = path.rfind("."); path.erase(pos); path.append(".dds"); @@ -66,10 +66,7 @@ namespace MWGui void AlchemyWindow::onCancelButtonClicked(MyGUI::Widget* _sender) { - mAlchemy.clear(); - - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Alchemy); - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Inventory); + exit(); } void AlchemyWindow::onCreateButtonClicked(MyGUI::Widget* _sender) @@ -159,6 +156,12 @@ namespace MWGui update(); } + void AlchemyWindow::exit() { + mAlchemy.clear(); + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Alchemy); + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Inventory); + } + void AlchemyWindow::onIngredientSelected(MyGUI::Widget* _sender) { removeIngredient(_sender); @@ -174,7 +177,7 @@ namespace MWGui { update(); - std::string sound = MWWorld::Class::get(item).getUpSoundId(item); + std::string sound = item.getClass().getUpSoundId(item); MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); } } diff --git a/apps/openmw/mwgui/alchemywindow.hpp b/apps/openmw/mwgui/alchemywindow.hpp index 3a58ebf06..4fd4b825c 100644 --- a/apps/openmw/mwgui/alchemywindow.hpp +++ b/apps/openmw/mwgui/alchemywindow.hpp @@ -19,6 +19,7 @@ namespace MWGui AlchemyWindow(); virtual void open(); + virtual void exit(); private: ItemView* mItemView; diff --git a/apps/openmw/mwgui/bookpage.cpp b/apps/openmw/mwgui/bookpage.cpp index 52682342f..2cebc8e19 100644 --- a/apps/openmw/mwgui/bookpage.cpp +++ b/apps/openmw/mwgui/bookpage.cpp @@ -93,7 +93,10 @@ struct TypesetBookImpl : TypesetBook typedef std::vector
Sections; + // Holds "top" and "bottom" vertical coordinates in the source text. + // A page is basically a "window" into a portion of the source text, similar to a ScrollView. typedef std::pair Page; + typedef std::vector Pages; Pages mPages; @@ -374,6 +377,29 @@ struct TypesetBookImpl::Typesetter : BookTypesetter else { //split section + int sectionHeightLeft = sectionHeight; + while (sectionHeightLeft > mPageHeight) + { + spaceLeft = mPageHeight - (curPageStop - curPageStart); + + // Adjust to the top of the first line that does not fit on the current page anymore + int splitPos = curPageStop; + for (Lines::iterator j = i->mLines.begin (); j != i->mLines.end (); ++j) + { + if (j->mRect.bottom > curPageStart + mPageHeight) + { + splitPos = j->mRect.top; + break; + } + } + + mBook->mPages.push_back (Page (curPageStart, splitPos)); + curPageStart = splitPos; + curPageStop = splitPos; + + sectionHeightLeft = (i->mRect.bottom - splitPos); + } + curPageStop = i->mRect.bottom; } } @@ -695,6 +721,8 @@ protected: typedef TypesetBookImpl::Section Section; typedef TypesetBookImpl::Line Line; typedef TypesetBookImpl::Run Run; + bool mIsPageReset; + size_t mPage; struct TextFormat : ISubWidget { @@ -745,6 +773,23 @@ protected: void destroyDrawItem() {}; }; + void resetPage() + { + mIsPageReset = true; + mPage = 0; + } + + void setPage(size_t page) + { + mIsPageReset = false; + mPage = page; + } + + bool isPageDifferent(size_t page) + { + return mIsPageReset || (mPage != page); + } + public: typedef TypesetBookImpl::StyleImpl Style; @@ -760,14 +805,13 @@ public: boost::shared_ptr mBook; - size_t mPage; MyGUI::ILayerNode* mNode; ActiveTextFormats mActiveTextFormats; PageDisplay () { - mPage = -1; + resetPage (); mViewTop = 0; mViewBottom = 0; mFocusItem = NULL; @@ -783,7 +827,8 @@ public: ActiveTextFormats::iterator i = mActiveTextFormats.find (Font); - mNode->outOfDate (i->second->mRenderItem); + if (mNode) + mNode->outOfDate (i->second->mRenderItem); } } @@ -901,7 +946,7 @@ public: createActiveFormats (newBook); mBook = newBook; - mPage = newPage; + setPage (newPage); if (newPage < mBook->mPages.size ()) { @@ -917,19 +962,19 @@ public: else { mBook.reset (); - mPage = -1; + resetPage (); mViewTop = 0; mViewBottom = 0; } } else - if (mBook && mPage != newPage) + if (mBook && isPageDifferent (newPage)) { if (mNode != NULL) for (ActiveTextFormats::iterator i = mActiveTextFormats.begin (); i != mActiveTextFormats.end (); ++i) mNode->outOfDate(i->second->mRenderItem); - mPage = newPage; + setPage (newPage); if (newPage < mBook->mPages.size ()) { @@ -1125,6 +1170,8 @@ public: protected: void onMouseLostFocus(Widget* _new) { + // NOTE: MyGUI also fires eventMouseLostFocus for widgets that are about to be destroyed (if they had focus). + // Child widgets may already be destroyed! So be careful. if (PageDisplay* pd = dynamic_cast (getSubWidgetText ())) { pd->onMouseLostFocus (); diff --git a/apps/openmw/mwgui/bookwindow.cpp b/apps/openmw/mwgui/bookwindow.cpp index 884d567c5..32a5255c9 100644 --- a/apps/openmw/mwgui/bookwindow.cpp +++ b/apps/openmw/mwgui/bookwindow.cpp @@ -114,6 +114,14 @@ namespace MWGui setTakeButtonShow(true); } + void BookWindow::exit() + { + // no 3d sounds because the object could be in a container. + MWBase::Environment::get().getSoundManager()->playSound ("book close", 1.0, 1.0); + + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Book); + } + void BookWindow::setTakeButtonShow(bool show) { mTakeButtonShow = show; @@ -128,10 +136,7 @@ namespace MWGui void BookWindow::onCloseButtonClicked (MyGUI::Widget* sender) { - // no 3d sounds because the object could be in a container. - MWBase::Environment::get().getSoundManager()->playSound ("book close", 1.0, 1.0); - - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Book); + exit(); } void BookWindow::onTakeButtonClicked (MyGUI::Widget* sender) diff --git a/apps/openmw/mwgui/bookwindow.hpp b/apps/openmw/mwgui/bookwindow.hpp index f8821ab50..a944f56e2 100644 --- a/apps/openmw/mwgui/bookwindow.hpp +++ b/apps/openmw/mwgui/bookwindow.hpp @@ -14,6 +14,8 @@ namespace MWGui public: BookWindow(); + virtual void exit(); + void open(MWWorld::Ptr book); void setTakeButtonShow(bool show); void nextPage(); diff --git a/apps/openmw/mwgui/charactercreation.cpp b/apps/openmw/mwgui/charactercreation.cpp index 5526bd26d..85f57a1a8 100644 --- a/apps/openmw/mwgui/charactercreation.cpp +++ b/apps/openmw/mwgui/charactercreation.cpp @@ -47,7 +47,7 @@ namespace void updatePlayerHealth() { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - MWMechanics::NpcStats& npcStats = MWWorld::Class::get(player).getNpcStats(player); + MWMechanics::NpcStats& npcStats = player.getClass().getNpcStats(player); npcStats.updateHealth(); } } @@ -188,12 +188,13 @@ namespace MWGui break; case GM_ClassCreate: - MWBase::Environment::get().getWindowManager()->removeDialog(mCreateClassDialog); - mCreateClassDialog = 0; - mCreateClassDialog = new CreateClassDialog(); + if (!mCreateClassDialog) + { + mCreateClassDialog = new CreateClassDialog(); + mCreateClassDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onCreateClassDialogDone); + mCreateClassDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onCreateClassDialogBack); + } mCreateClassDialog->setNextButtonShow(mCreationStage >= CSE_ClassChosen); - mCreateClassDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onCreateClassDialogDone); - mCreateClassDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onCreateClassDialogBack); mCreateClassDialog->setVisible(true); if (mCreationStage < CSE_RaceChosen) mCreationStage = CSE_RaceChosen; @@ -219,7 +220,7 @@ namespace MWGui { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - const MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); + const MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); mReviewDialog->setHealth ( stats.getHealth() ); mReviewDialog->setMagicka( stats.getMagicka() ); @@ -531,8 +532,8 @@ namespace MWGui mPlayerClass = klass; MWBase::Environment::get().getWindowManager()->setPlayerClass(klass); - MWBase::Environment::get().getWindowManager()->removeDialog(mCreateClassDialog); - mCreateClassDialog = 0; + // Do not delete dialog, so that choices are rembered in case we want to go back and adjust them later + mCreateClassDialog->setVisible(false); } updatePlayerHealth(); @@ -554,8 +555,8 @@ namespace MWGui void CharacterCreation::onCreateClassDialogBack() { - MWBase::Environment::get().getWindowManager()->removeDialog(mCreateClassDialog); - mCreateClassDialog = 0; + // Do not delete dialog, so that choices are rembered in case we want to go back and adjust them later + mCreateClassDialog->setVisible(false); MWBase::Environment::get().getWindowManager()->popGuiMode(); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Class); diff --git a/apps/openmw/mwgui/class.cpp b/apps/openmw/mwgui/class.cpp index 1c8cc7840..9f6306830 100644 --- a/apps/openmw/mwgui/class.cpp +++ b/apps/openmw/mwgui/class.cpp @@ -718,6 +718,11 @@ namespace MWGui } void SelectSpecializationDialog::onCancelClicked(MyGUI::Widget* _sender) + { + exit(); + } + + void SelectSpecializationDialog::exit() { eventCancel(); } @@ -764,6 +769,11 @@ namespace MWGui } void SelectAttributeDialog::onCancelClicked(MyGUI::Widget* _sender) + { + exit(); + } + + void SelectAttributeDialog::exit() { eventCancel(); } @@ -855,6 +865,11 @@ namespace MWGui } void SelectSkillDialog::onCancelClicked(MyGUI::Widget* _sender) + { + exit(); + } + + void SelectSkillDialog::exit() { eventCancel(); } diff --git a/apps/openmw/mwgui/class.hpp b/apps/openmw/mwgui/class.hpp index f78f7541b..5c23c834d 100644 --- a/apps/openmw/mwgui/class.hpp +++ b/apps/openmw/mwgui/class.hpp @@ -136,6 +136,8 @@ namespace MWGui SelectSpecializationDialog(); ~SelectSpecializationDialog(); + virtual void exit(); + ESM::Class::Specialization getSpecializationId() const { return mSpecializationId; } // Events @@ -167,6 +169,8 @@ namespace MWGui SelectAttributeDialog(); ~SelectAttributeDialog(); + virtual void exit(); + ESM::Attribute::AttributeID getAttributeId() const { return mAttributeId; } // Events @@ -196,6 +200,8 @@ namespace MWGui SelectSkillDialog(); ~SelectSkillDialog(); + virtual void exit(); + ESM::Skill::SkillEnum getSkillId() const { return mSkillId; } // Events diff --git a/apps/openmw/mwgui/companionitemmodel.cpp b/apps/openmw/mwgui/companionitemmodel.cpp index bb6cf2800..b8be9dcb8 100644 --- a/apps/openmw/mwgui/companionitemmodel.cpp +++ b/apps/openmw/mwgui/companionitemmodel.cpp @@ -10,23 +10,23 @@ namespace MWGui { } - void CompanionItemModel::copyItem (const ItemStack& item, size_t count) + MWWorld::Ptr CompanionItemModel::copyItem (const ItemStack& item, size_t count, bool setNewOwner=false) { if (mActor.getClass().isNpc()) { - MWMechanics::NpcStats& stats = MWWorld::Class::get(mActor).getNpcStats(mActor); - stats.modifyProfit(MWWorld::Class::get(item.mBase).getValue(item.mBase) * count); + MWMechanics::NpcStats& stats = mActor.getClass().getNpcStats(mActor); + stats.modifyProfit(item.mBase.getClass().getValue(item.mBase) * count); } - InventoryItemModel::copyItem(item, count); + return InventoryItemModel::copyItem(item, count, setNewOwner); } void CompanionItemModel::removeItem (const ItemStack& item, size_t count) { if (mActor.getClass().isNpc()) { - MWMechanics::NpcStats& stats = MWWorld::Class::get(mActor).getNpcStats(mActor); - stats.modifyProfit(-MWWorld::Class::get(item.mBase).getValue(item.mBase) * count); + MWMechanics::NpcStats& stats = mActor.getClass().getNpcStats(mActor); + stats.modifyProfit(-item.mBase.getClass().getValue(item.mBase) * count); } InventoryItemModel::removeItem(item, count); diff --git a/apps/openmw/mwgui/companionitemmodel.hpp b/apps/openmw/mwgui/companionitemmodel.hpp index c11e11c32..172fa9508 100644 --- a/apps/openmw/mwgui/companionitemmodel.hpp +++ b/apps/openmw/mwgui/companionitemmodel.hpp @@ -13,7 +13,7 @@ namespace MWGui public: CompanionItemModel (const MWWorld::Ptr& actor); - virtual void copyItem (const ItemStack& item, size_t count); + virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool setNewOwner); virtual void removeItem (const ItemStack& item, size_t count); }; diff --git a/apps/openmw/mwgui/companionwindow.cpp b/apps/openmw/mwgui/companionwindow.cpp index a0a34108e..d0ac3e7c3 100644 --- a/apps/openmw/mwgui/companionwindow.cpp +++ b/apps/openmw/mwgui/companionwindow.cpp @@ -61,7 +61,7 @@ void CompanionWindow::onItemSelected(int index) if (count > 1 && !shift) { CountDialog* dialog = MWBase::Environment::get().getWindowManager()->getCountDialog(); - dialog->open(MWWorld::Class::get(object).getName(object), "#{sTake}", count); + dialog->open(object.getClass().getName(object), "#{sTake}", count); dialog->eventOkClicked.clear(); dialog->eventOkClicked += MyGUI::newDelegate(this, &CompanionWindow::dragItem); } @@ -92,7 +92,7 @@ void CompanionWindow::open(const MWWorld::Ptr& npc) mSortModel = new SortFilterItemModel(mModel); mItemView->setModel(mSortModel); - setTitle(MWWorld::Class::get(npc).getName(npc)); + setTitle(npc.getClass().getName(npc)); } void CompanionWindow::onFrame() @@ -104,22 +104,27 @@ void CompanionWindow::updateEncumbranceBar() { if (mPtr.isEmpty()) return; - float capacity = MWWorld::Class::get(mPtr).getCapacity(mPtr); - float encumbrance = MWWorld::Class::get(mPtr).getEncumbrance(mPtr); + float capacity = mPtr.getClass().getCapacity(mPtr); + float encumbrance = mPtr.getClass().getEncumbrance(mPtr); mEncumbranceBar->setValue(encumbrance, capacity); if (mPtr.getTypeName() != typeid(ESM::NPC).name()) mProfitLabel->setCaption(""); else { - MWMechanics::NpcStats& stats = MWWorld::Class::get(mPtr).getNpcStats(mPtr); + MWMechanics::NpcStats& stats = mPtr.getClass().getNpcStats(mPtr); mProfitLabel->setCaptionWithReplacing("#{sProfitValue} " + boost::lexical_cast(stats.getProfit())); } } void CompanionWindow::onCloseButtonClicked(MyGUI::Widget* _sender) { - if (mPtr.getTypeName() == typeid(ESM::NPC).name() && MWWorld::Class::get(mPtr).getNpcStats(mPtr).getProfit() < 0) + exit(); +} + +void CompanionWindow::exit() +{ + if (mPtr.getTypeName() == typeid(ESM::NPC).name() && mPtr.getClass().getNpcStats(mPtr).getProfit() < 0) { std::vector buttons; buttons.push_back("#{sCompanionWarningButtonOne}"); @@ -135,8 +140,8 @@ void CompanionWindow::onMessageBoxButtonClicked(int button) { if (button == 0) { - mPtr.getRefData().getLocals().setVarByInt(MWWorld::Class::get(mPtr).getScript(mPtr), - "minimumProfit", MWWorld::Class::get(mPtr).getNpcStats(mPtr).getProfit()); + mPtr.getRefData().getLocals().setVarByInt(mPtr.getClass().getScript(mPtr), + "minimumProfit", mPtr.getClass().getNpcStats(mPtr).getProfit()); MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Companion); MWBase::Environment::get().getDialogueManager()->startDialogue (mPtr); diff --git a/apps/openmw/mwgui/companionwindow.hpp b/apps/openmw/mwgui/companionwindow.hpp index 7fdfc069f..006d0a3c3 100644 --- a/apps/openmw/mwgui/companionwindow.hpp +++ b/apps/openmw/mwgui/companionwindow.hpp @@ -18,6 +18,8 @@ namespace MWGui public: CompanionWindow(DragAndDrop* dragAndDrop, MessageBoxManager* manager); + virtual void exit(); + void open(const MWWorld::Ptr& npc); void onFrame (); @@ -25,7 +27,7 @@ namespace MWGui ItemView* mItemView; SortFilterItemModel* mSortModel; CompanionItemModel* mModel; - size_t mSelectedItem; + int mSelectedItem; DragAndDrop* mDragAndDrop; diff --git a/apps/openmw/mwgui/confirmationdialog.cpp b/apps/openmw/mwgui/confirmationdialog.cpp index f431f2f64..89f477598 100644 --- a/apps/openmw/mwgui/confirmationdialog.cpp +++ b/apps/openmw/mwgui/confirmationdialog.cpp @@ -28,13 +28,18 @@ namespace MWGui center(); } - void ConfirmationDialog::onCancelButtonClicked(MyGUI::Widget* _sender) + void ConfirmationDialog::exit() { eventCancelClicked(); setVisible(false); } + void ConfirmationDialog::onCancelButtonClicked(MyGUI::Widget* _sender) + { + exit(); + } + void ConfirmationDialog::onOkButtonClicked(MyGUI::Widget* _sender) { eventOkClicked(); diff --git a/apps/openmw/mwgui/confirmationdialog.hpp b/apps/openmw/mwgui/confirmationdialog.hpp index 47b256017..c50873c74 100644 --- a/apps/openmw/mwgui/confirmationdialog.hpp +++ b/apps/openmw/mwgui/confirmationdialog.hpp @@ -10,6 +10,7 @@ namespace MWGui public: ConfirmationDialog(); void open(const std::string& message); + virtual void exit(); typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp index 01eb770f7..2ae4d6ed1 100644 --- a/apps/openmw/mwgui/console.cpp +++ b/apps/openmw/mwgui/console.cpp @@ -1,5 +1,8 @@ #include "console.hpp" +#include +#include + #include #include @@ -58,7 +61,7 @@ namespace MWGui } catch (const std::exception& error) { - printError (std::string ("An exception has been thrown: ") + error.what()); + printError (std::string ("Error: ") + error.what()); } return false; @@ -140,6 +143,11 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(NULL); } + void Console::exit() + { + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Console); + } + void Console::setFont(const std::string &fntName) { mHistory->setFontName(fntName); @@ -187,14 +195,15 @@ namespace MWGui } catch (const std::exception& error) { - printError (std::string ("An exception has been thrown: ") + error.what()); + printError (std::string ("Error: ") + error.what()); } } } void Console::executeFile (const std::string& path) { - std::ifstream stream (path.c_str()); + namespace bfs = boost::filesystem; + bfs::ifstream stream ((bfs::path(path))); if (!stream.is_open()) printError ("failed to open file: " + path); @@ -215,16 +224,22 @@ namespace MWGui { std::vector matches; listNames(); - mCommandLine->setCaption(complete( mCommandLine->getOnlyText(), matches )); -#if 0 - int i = 0; - for(std::vector::iterator it=matches.begin(); it < matches.end(); ++it,++i ) + std::string oldCaption = mCommandLine->getCaption(); + std::string newCaption = complete( mCommandLine->getOnlyText(), matches ); + mCommandLine->setCaption(newCaption); + + // List candidates if repeatedly pressing tab + if (oldCaption == newCaption && matches.size()) { - printOK( *it ); - if( i == 50 ) - break; + int i = 0; + printOK(""); + for(std::vector::iterator it=matches.begin(); it < matches.end(); ++it,++i ) + { + printOK( *it ); + if( i == 50 ) + break; + } } -#endif } if(mCommandHistory.empty()) return; @@ -403,7 +418,7 @@ namespace MWGui } else { - setTitle("#{sConsoleTitle} (" + object.getCellRef().mRefID + ")"); + setTitle("#{sConsoleTitle} (" + object.getCellRef().getRefId() + ")"); mPtr = object; } // User clicked on an object. Restore focus to the console command line. diff --git a/apps/openmw/mwgui/console.hpp b/apps/openmw/mwgui/console.hpp index 890176363..ec699b317 100644 --- a/apps/openmw/mwgui/console.hpp +++ b/apps/openmw/mwgui/console.hpp @@ -21,86 +21,83 @@ namespace MWGui { - class Console : public WindowBase, private Compiler::ErrorHandler, public ReferenceInterface - { - private: + class Console : public WindowBase, private Compiler::ErrorHandler, public ReferenceInterface + { + public: + /// Set the implicit object for script execution + void setSelectedObject(const MWWorld::Ptr& object); - Compiler::Extensions mExtensions; - MWScript::CompilerContext mCompilerContext; - std::vector mNames; - bool mConsoleOnlyScripts; + MyGUI::EditBox* mCommandLine; + MyGUI::EditBox* mHistory; - bool compile (const std::string& cmd, Compiler::Output& output); + typedef std::list StringList; - /// Report error to the user. - virtual void report (const std::string& message, const Compiler::TokenLoc& loc, Type type); + // History of previous entered commands + StringList mCommandHistory; + StringList::iterator mCurrent; + std::string mEditString; - /// Report a file related error - virtual void report (const std::string& message, Type type); + Console(int w, int h, bool consoleOnlyScripts); - void listNames(); - ///< Write all valid identifiers and keywords into mNames and sort them. - /// \note If mNames is not empty, this function is a no-op. - /// \note The list may contain duplicates (if a name is a keyword and an identifier at the same - /// time). + virtual void open(); + virtual void close(); - public: + virtual void exit(); - void setSelectedObject(const MWWorld::Ptr& object); - ///< Set the implicit object for script execution + void setFont(const std::string &fntName); - protected: + void onResChange(int width, int height); - virtual void onReferenceUnavailable(); + void clearHistory(); + // Print a message to the console. Messages may contain color + // code, eg. "#FFFFFF this is white". + void print(const std::string &msg); - public: - MyGUI::EditBox* mCommandLine; - MyGUI::EditBox* mHistory; + // These are pre-colored versions that you should use. - typedef std::list StringList; + /// Output from successful console command + void printOK(const std::string &msg); - // History of previous entered commands - StringList mCommandHistory; - StringList::iterator mCurrent; - std::string mEditString; + /// Error message + void printError(const std::string &msg); - Console(int w, int h, bool consoleOnlyScripts); + void execute (const std::string& command); - virtual void open(); - virtual void close(); + void executeFile (const std::string& path); - void setFont(const std::string &fntName); + protected: - void onResChange(int width, int height); + virtual void onReferenceUnavailable(); - void clearHistory(); + private: - // Print a message to the console. Messages may contain color - // code, eg. "#FFFFFF this is white". - void print(const std::string &msg); + void keyPress(MyGUI::Widget* _sender, + MyGUI::KeyCode key, + MyGUI::Char _char); - // These are pre-colored versions that you should use. + void acceptCommand(MyGUI::EditBox* _sender); - /// Output from successful console command - void printOK(const std::string &msg); + std::string complete( std::string input, std::vector &matches ); - /// Error message - void printError(const std::string &msg); + Compiler::Extensions mExtensions; + MWScript::CompilerContext mCompilerContext; + std::vector mNames; + bool mConsoleOnlyScripts; - void execute (const std::string& command); + bool compile (const std::string& cmd, Compiler::Output& output); - void executeFile (const std::string& path); + /// Report error to the user. + virtual void report (const std::string& message, const Compiler::TokenLoc& loc, Type type); - private: + /// Report a file related error + virtual void report (const std::string& message, Type type); - void keyPress(MyGUI::Widget* _sender, - MyGUI::KeyCode key, - MyGUI::Char _char); - - void acceptCommand(MyGUI::EditBox* _sender); - - std::string complete( std::string input, std::vector &matches ); + /// Write all valid identifiers and keywords into mNames and sort them. + /// \note If mNames is not empty, this function is a no-op. + /// \note The list may contain duplicates (if a name is a keyword and an identifier at the same + /// time). + void listNames(); }; } #endif diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index 34ac8d9f4..609dea385 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -13,6 +13,7 @@ #include "../mwworld/containerstore.hpp" #include "../mwmechanics/pickpocket.hpp" +#include "../mwmechanics/creaturestats.hpp" #include "countdialog.hpp" #include "tradewindow.hpp" @@ -36,7 +37,7 @@ namespace MWGui mIsOnDragAndDrop = true; mDragAndDropWidget->setVisible(true); - std::string sound = MWWorld::Class::get(mItem.mBase).getUpSoundId(mItem.mBase); + std::string sound = mItem.mBase.getClass().getUpSoundId(mItem.mBase); MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); if (mSourceSortModel) @@ -46,7 +47,7 @@ namespace MWGui } std::string path = std::string("icons\\"); - path += MWWorld::Class::get(mItem.mBase).getInventoryIcon(mItem.mBase); + path += mItem.mBase.getClass().getInventoryIcon(mItem.mBase); MyGUI::ImageBox* baseWidget = mDragAndDropWidget->createWidget ("ImageBox", MyGUI::IntCoord(0, 0, 42, 42), MyGUI::Align::Default); mDraggedWidget = baseWidget; @@ -75,7 +76,7 @@ namespace MWGui void DragAndDrop::drop(ItemModel *targetModel, ItemView *targetView) { - std::string sound = MWWorld::Class::get(mItem.mBase).getDownSoundId(mItem.mBase); + std::string sound = mItem.mBase.getClass().getDownSoundId(mItem.mBase); MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); mDragAndDropWidget->setVisible(false); @@ -84,14 +85,14 @@ namespace MWGui // otherwise, do the transfer if (targetModel != mSourceModel) { - targetModel->copyItem(mItem, mDraggedCount); - mSourceModel->removeItem(mItem, mDraggedCount); + mSourceModel->moveItem(mItem, mDraggedCount, targetModel); } mSourceModel->update(); finish(); - targetView->update(); + if (targetView) + targetView->update(); // We need to update the view since an other item could be auto-equipped. mSourceView->update(); @@ -154,7 +155,7 @@ namespace MWGui if (count > 1 && !shift) { CountDialog* dialog = MWBase::Environment::get().getWindowManager()->getCountDialog(); - dialog->open(MWWorld::Class::get(object).getName(object), "#{sTake}", count); + dialog->open(object.getClass().getName(object), "#{sTake}", count); dialog->eventOkClicked.clear(); dialog->eventOkClicked += MyGUI::newDelegate(this, &ContainerWindow::dragItem); } @@ -176,8 +177,8 @@ namespace MWGui { // check that we don't exceed container capacity MWWorld::Ptr item = mDragAndDrop->mItem.mBase; - float weight = MWWorld::Class::get(item).getWeight(item) * mDragAndDrop->mDraggedCount; - if (MWWorld::Class::get(mPtr).getCapacity(mPtr) < MWWorld::Class::get(mPtr).getEncumbrance(mPtr) + weight) + float weight = item.getClass().getWeight(item) * mDragAndDrop->mDraggedCount; + if (mPtr.getClass().getCapacity(mPtr) < mPtr.getClass().getEncumbrance(mPtr) + weight) { MWBase::Environment::get().getWindowManager()->messageBox("#{sContentsMessage3}"); return; @@ -226,7 +227,7 @@ namespace MWGui // Careful here. setTitle may cause size updates, causing itemview redraw, so make sure to do it last // or we end up using a possibly invalid model. - setTitle(MWWorld::Class::get(container).getName(container)); + setTitle(container.getClass().getName(container)); } void ContainerWindow::onKeyPressed(MyGUI::Widget *_sender, MyGUI::KeyCode _key, MyGUI::Char _char) @@ -262,7 +263,7 @@ namespace MWGui } } - void ContainerWindow::onCloseButtonClicked(MyGUI::Widget* _sender) + void ContainerWindow::exit() { if(mDragAndDrop == NULL || !mDragAndDrop->mIsOnDragAndDrop) { @@ -270,6 +271,11 @@ namespace MWGui } } + void ContainerWindow::onCloseButtonClicked(MyGUI::Widget* _sender) + { + exit(); + } + void ContainerWindow::onTakeAllButtonClicked(MyGUI::Widget* _sender) { if(mDragAndDrop == NULL || !mDragAndDrop->mIsOnDragAndDrop) @@ -283,7 +289,7 @@ namespace MWGui { // play the sound of the first object MWWorld::Ptr item = mModel->getItem(i).mBase; - std::string sound = MWWorld::Class::get(item).getUpSoundId(item); + std::string sound = item.getClass().getUpSoundId(item); MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); } @@ -292,8 +298,7 @@ namespace MWGui if (!onTakeItem(item, item.mCount)) break; - playerModel->copyItem(item, item.mCount); - mModel->removeItem(item, item.mCount); + mModel->moveItem(item, item.mCount, playerModel); } MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Container); @@ -306,7 +311,7 @@ namespace MWGui { onTakeAllButtonClicked(mTakeButton); - if (MWWorld::Class::get(mPtr).isPersistent(mPtr)) + if (mPtr.getClass().isPersistent(mPtr)) MWBase::Environment::get().getWindowManager()->messageBox("#{sDisposeCorpseFail}"); else MWBase::Environment::get().getWorld()->deleteObject(mPtr); @@ -341,7 +346,11 @@ namespace MWGui } else { - MWBase::Environment::get().getMechanicsManager()->itemTaken(player, item.mBase, count); + // Looting a dead corpse is considered OK + if (mPtr.getClass().isActor() && mPtr.getClass().getCreatureStats(mPtr).isDead()) + return true; + else + MWBase::Environment::get().getMechanicsManager()->itemTaken(player, item.mBase, count); } return true; } diff --git a/apps/openmw/mwgui/container.hpp b/apps/openmw/mwgui/container.hpp index ce4707af6..5446a4ab7 100644 --- a/apps/openmw/mwgui/container.hpp +++ b/apps/openmw/mwgui/container.hpp @@ -54,6 +54,8 @@ namespace MWGui void open(const MWWorld::Ptr& container, bool loot=false); virtual void close(); + virtual void exit(); + private: DragAndDrop* mDragAndDrop; @@ -62,7 +64,7 @@ namespace MWGui MWGui::ItemView* mItemView; SortFilterItemModel* mSortModel; ItemModel* mModel; - size_t mSelectedItem; + int mSelectedItem; MyGUI::Button* mDisposeCorpseButton; MyGUI::Button* mTakeButton; diff --git a/apps/openmw/mwgui/containeritemmodel.cpp b/apps/openmw/mwgui/containeritemmodel.cpp index bcb8440bf..b2befc3ba 100644 --- a/apps/openmw/mwgui/containeritemmodel.cpp +++ b/apps/openmw/mwgui/containeritemmodel.cpp @@ -71,12 +71,12 @@ ItemModel::ModelIndex ContainerItemModel::getIndex (ItemStack item) return -1; } -void ContainerItemModel::copyItem (const ItemStack& item, size_t count) +MWWorld::Ptr ContainerItemModel::copyItem (const ItemStack& item, size_t count, bool setNewOwner) { 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!"); - source.getClass().getContainerStore(source).add(item.mBase, count, source); + return *source.getClass().getContainerStore(source).add(item.mBase, count, source); } void ContainerItemModel::removeItem (const ItemStack& item, size_t count) @@ -85,7 +85,7 @@ void ContainerItemModel::removeItem (const ItemStack& item, size_t count) for (std::vector::iterator source = mItemSources.begin(); source != mItemSources.end(); ++source) { - MWWorld::ContainerStore& store = MWWorld::Class::get(*source).getContainerStore(*source); + MWWorld::ContainerStore& store = source->getClass().getContainerStore(*source); for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { @@ -120,7 +120,7 @@ void ContainerItemModel::update() mItems.clear(); for (std::vector::iterator source = mItemSources.begin(); source != mItemSources.end(); ++source) { - MWWorld::ContainerStore& store = MWWorld::Class::get(*source).getContainerStore(*source); + MWWorld::ContainerStore& store = source->getClass().getContainerStore(*source); for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { diff --git a/apps/openmw/mwgui/containeritemmodel.hpp b/apps/openmw/mwgui/containeritemmodel.hpp index 22595582e..7ced6ae34 100644 --- a/apps/openmw/mwgui/containeritemmodel.hpp +++ b/apps/openmw/mwgui/containeritemmodel.hpp @@ -21,7 +21,7 @@ namespace MWGui virtual ModelIndex getIndex (ItemStack item); virtual size_t getItemCount(); - virtual void copyItem (const ItemStack& item, size_t count); + virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool setNewOwner=false); virtual void removeItem (const ItemStack& item, size_t count); virtual void update(); diff --git a/apps/openmw/mwgui/countdialog.cpp b/apps/openmw/mwgui/countdialog.cpp index 02ccbbc05..53c33b3c4 100644 --- a/apps/openmw/mwgui/countdialog.cpp +++ b/apps/openmw/mwgui/countdialog.cpp @@ -49,7 +49,12 @@ namespace MWGui mItemEdit->setCaption(boost::lexical_cast(maxCount)); } - void CountDialog::cancel() + void CountDialog::cancel() //Keeping this here as I don't know if anything else relies on it. + { + exit(); + } + + void CountDialog::exit() { setVisible(false); } @@ -65,16 +70,16 @@ namespace MWGui setVisible(false); } - + // essentially duplicating what the OK button does if user presses // Enter key void CountDialog::onEnterKeyPressed(MyGUI::EditBox* _sender) { eventOkClicked(NULL, mSlider->getScrollPosition()+1); - + setVisible(false); } - + void CountDialog::onEditTextChange(MyGUI::EditBox* _sender) { if (_sender->getCaption() == "") diff --git a/apps/openmw/mwgui/countdialog.hpp b/apps/openmw/mwgui/countdialog.hpp index 06de3eb88..a00b0b223 100644 --- a/apps/openmw/mwgui/countdialog.hpp +++ b/apps/openmw/mwgui/countdialog.hpp @@ -11,6 +11,7 @@ namespace MWGui CountDialog(); void open(const std::string& item, const std::string& message, const int maxCount); void cancel(); + virtual void exit(); typedef MyGUI::delegates::CMultiDelegate2 EventHandle_WidgetInt; diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index 6c43f47b4..5e3f93ab9 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -51,7 +51,7 @@ namespace MWGui void PersuasionDialog::onCancel(MyGUI::Widget *sender) { - setVisible(false); + exit(); } void PersuasionDialog::onPersuade(MyGUI::Widget *sender) @@ -87,6 +87,11 @@ namespace MWGui mGoldLabel->setCaptionWithReplacing("#{sGold}: " + boost::lexical_cast(playerGold)); } + void PersuasionDialog::exit() + { + setVisible(false); + } + // -------------------------------------------------------------------------------------------------- Response::Response(const std::string &text, const std::string &title) @@ -162,7 +167,8 @@ namespace MWGui { std::string::const_iterator i = text.begin (); KeywordSearchT::Match match; - while (i != text.end () && keywordSearch->search (i, text.end (), match)) + + while (i != text.end () && keywordSearch->search (i, text.end (), match, text.begin ())) { if (i != match.mBeg) addTopicLink (typesetter, 0, i - text.begin (), match.mBeg - text.begin ()); @@ -263,6 +269,13 @@ namespace MWGui static_cast(mMainWidget)->eventWindowChangeCoord += MyGUI::newDelegate(this, &DialogueWindow::onWindowResize); } + void DialogueWindow::exit() + { + if (!mEnabled || MWBase::Environment::get().getDialogueManager()->isInChoice()) + return; + MWBase::Environment::get().getDialogueManager()->goodbyeSelected(); + } + void DialogueWindow::onWindowResize(MyGUI::Window* _sender) { mTopicsList->adjustSize(); @@ -280,9 +293,7 @@ namespace MWGui void DialogueWindow::onByeClicked(MyGUI::Widget* _sender) { - if (!mEnabled || MWBase::Environment::get().getDialogueManager()->isInChoice()) - return; - MWBase::Environment::get().getDialogueManager()->goodbyeSelected(); + exit(); } void DialogueWindow::onSelectTopic(const std::string& topic, int id) @@ -362,6 +373,8 @@ namespace MWGui mTopicsList->setEnabled(true); setTitle(npcName); + clearChoices(); + mTopicsList->clear(); for (std::vector::iterator it = mHistoryContents.begin(); it != mHistoryContents.end(); ++it) @@ -383,8 +396,8 @@ namespace MWGui mTopicLinks.clear(); mKeywordSearch.clear(); - bool isCompanion = !MWWorld::Class::get(mPtr).getScript(mPtr).empty() - && mPtr.getRefData().getLocals().getIntVar(MWWorld::Class::get(mPtr).getScript(mPtr), "companion"); + bool isCompanion = !mPtr.getClass().getScript(mPtr).empty() + && mPtr.getRefData().getLocals().getIntVar(mPtr.getClass().getScript(mPtr), "companion"); bool anyService = mServices > 0 || isCompanion || mPtr.getTypeName() == typeid(ESM::NPC).name(); @@ -501,6 +514,15 @@ namespace MWGui // no scrollbar onScrollbarMoved(mScrollBar, 0); } + + MyGUI::Button* byeButton; + getWidget(byeButton, "ByeButton"); + if(MWBase::Environment::get().getDialogueManager()->isInChoice()) { + byeButton->setEnabled(false); + } + else { + byeButton->setEnabled(true); + } } void DialogueWindow::notifyLinkClicked (TypesetBook::InteractiveId link) @@ -510,7 +532,7 @@ namespace MWGui void DialogueWindow::onScrollbarMoved(MyGUI::ScrollBar *sender, size_t pos) { - mHistory->setPosition(0,-pos); + mHistory->setPosition(0, pos * -1); } void DialogueWindow::addResponse(const std::string &text, const std::string &title) diff --git a/apps/openmw/mwgui/dialogue.hpp b/apps/openmw/mwgui/dialogue.hpp index befbd6eee..fcb1338b5 100644 --- a/apps/openmw/mwgui/dialogue.hpp +++ b/apps/openmw/mwgui/dialogue.hpp @@ -34,6 +34,7 @@ namespace MWGui PersuasionDialog(); virtual void open(); + virtual void exit(); private: MyGUI::Button* mCancelButton; @@ -103,6 +104,8 @@ namespace MWGui public: DialogueWindow(); + virtual void exit(); + // Events typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index ada004612..ccb1fab68 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -61,6 +61,11 @@ namespace MWGui onRemoveSoul(NULL); } + void EnchantingDialog::exit() + { + MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Enchanting); + } + void EnchantingDialog::updateLabels() { std::stringstream enchantCost; @@ -119,7 +124,7 @@ namespace MWGui MyGUI::ImageBox* image = mSoulBox->createWidget("ImageBox", MyGUI::IntCoord(0, 0, 32, 32), MyGUI::Align::Default); std::string path = std::string("icons\\"); - path += MWWorld::Class::get(soulgem).getInventoryIcon(soulgem); + path += soulgem.getClass().getInventoryIcon(soulgem); int pos = path.rfind("."); path.erase(pos); path.append(".dds"); @@ -141,7 +146,7 @@ namespace MWGui void EnchantingDialog::onCancelButtonClicked(MyGUI::Widget* sender) { - MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Enchanting); + exit(); } void EnchantingDialog::onSelectItem(MyGUI::Widget *sender) @@ -164,7 +169,7 @@ namespace MWGui MyGUI::ImageBox* image = mItemBox->createWidget("ImageBox", MyGUI::IntCoord(0, 0, 32, 32), MyGUI::Align::Default); std::string path = std::string("icons\\"); - path += MWWorld::Class::get(item).getInventoryIcon(item); + path += item.getClass().getInventoryIcon(item); int pos = path.rfind("."); path.erase(pos); path.append(".dds"); @@ -207,7 +212,7 @@ namespace MWGui MyGUI::ImageBox* image = mSoulBox->createWidget("ImageBox", MyGUI::IntCoord(0, 0, 32, 32), MyGUI::Align::Default); std::string path = std::string("icons\\"); - path += MWWorld::Class::get(item).getInventoryIcon(item); + path += item.getClass().getInventoryIcon(item); int pos = path.rfind("."); path.erase(pos); path.append(".dds"); @@ -306,7 +311,7 @@ namespace MWGui for (int i=0; i<2; ++i) { MWWorld::Ptr item = (i == 0) ? mEnchanting.getOldItem() : mEnchanting.getGem(); - if (Misc::StringUtils::ciEqual(item.getCellRef().mOwner, mPtr.getCellRef().mRefID)) + if (Misc::StringUtils::ciEqual(item.getCellRef().getOwner(), mPtr.getCellRef().getRefId())) { std::string msg = MWBase::Environment::get().getWorld()->getStore().get().find("sNotifyMessage49")->getString(); if (msg.find("%s") != std::string::npos) diff --git a/apps/openmw/mwgui/enchantingdialog.hpp b/apps/openmw/mwgui/enchantingdialog.hpp index 8bad60c8e..7e641702e 100644 --- a/apps/openmw/mwgui/enchantingdialog.hpp +++ b/apps/openmw/mwgui/enchantingdialog.hpp @@ -19,6 +19,9 @@ namespace MWGui virtual ~EnchantingDialog(); virtual void open(); + + virtual void exit(); + void startEnchanting(MWWorld::Ptr actor); void startSelfEnchanting(MWWorld::Ptr soulgem); diff --git a/apps/openmw/mwgui/fontloader.cpp b/apps/openmw/mwgui/fontloader.cpp index 7a6317c16..59c2e7ca6 100644 --- a/apps/openmw/mwgui/fontloader.cpp +++ b/apps/openmw/mwgui/fontloader.cpp @@ -196,6 +196,16 @@ namespace MWGui bitmapFile->read(&textureData[0], width*height*4); bitmapFile->close(); + std::string resourceName; + if (name.size() >= 5 && Misc::StringUtils::ciEqual(name.substr(0, 5), "magic")) + resourceName = "Magic Cards"; + else if (name.size() >= 7 && Misc::StringUtils::ciEqual(name.substr(0, 7), "century")) + resourceName = "Century Gothic"; + else if (name.size() >= 7 && Misc::StringUtils::ciEqual(name.substr(0, 7), "daedric")) + resourceName = "Daedric"; + else + return; // no point in loading it, since there is no way of using additional fonts + std::string textureName = name; Ogre::Image image; image.loadDynamicImage(&textureData[0], width, height, Ogre::PF_BYTE_RGBA); @@ -208,18 +218,11 @@ namespace MWGui // Register the font with MyGUI MyGUI::ResourceManualFont* font = static_cast( MyGUI::FactoryManager::getInstance().createObject("Resource", "ResourceManualFont")); + // We need to emulate loading from XML because the data members are private as of mygui 3.2.0 MyGUI::xml::Document xmlDocument; MyGUI::xml::ElementPtr root = xmlDocument.createRoot("ResourceManualFont"); - - if (name.size() >= 5 && Misc::StringUtils::ciEqual(name.substr(0, 5), "magic")) - root->addAttribute("name", "Magic Cards"); - else if (name.size() >= 7 && Misc::StringUtils::ciEqual(name.substr(0, 7), "century")) - root->addAttribute("name", "Century Gothic"); - else if (name.size() >= 7 && Misc::StringUtils::ciEqual(name.substr(0, 7), "daedric")) - root->addAttribute("name", "Daedric"); - else - return; // no point in loading it, since there is no way of using additional fonts + root->addAttribute("name", resourceName); MyGUI::xml::ElementPtr defaultHeight = root->createChild("Property"); defaultHeight->addAttribute("key", "DefaultHeight"); @@ -285,6 +288,7 @@ namespace MWGui font->deserialization(root, MyGUI::Version(3,2,0)); + MyGUI::ResourceManager::getInstance().removeByName(font->getResourceName()); MyGUI::ResourceManager::getInstance().addResource(font); } diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index 06a228a1f..ede5750a5 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -17,9 +17,47 @@ #include "itemmodel.hpp" #include "container.hpp" +#include "itemmodel.hpp" + namespace MWGui { + /** + * Makes it possible to use ItemModel::moveItem to move an item from an inventory to the world. + */ + class WorldItemModel : public ItemModel + { + public: + WorldItemModel(float left, float top) : mLeft(left), mTop(top) {} + virtual ~WorldItemModel() {} + virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool setNewOwner=false) + { + MWBase::World* world = MWBase::Environment::get().getWorld(); + + MWWorld::Ptr dropped; + if (world->canPlaceObject(mLeft, mTop)) + dropped = world->placeObject(item.mBase, mLeft, mTop, count); + else + dropped = world->dropObjectOnGround(world->getPlayerPtr(), item.mBase, count); + if (setNewOwner) + dropped.getCellRef().setOwner(""); + + return dropped; + } + + virtual void removeItem (const ItemStack& item, size_t count) { throw std::runtime_error("removeItem not implemented"); } + virtual ModelIndex getIndex (ItemStack item) { throw std::runtime_error("getIndex not implemented"); } + virtual void update() {} + virtual size_t getItemCount() { return 0; } + virtual ItemStack getItem (ModelIndex index) { throw std::runtime_error("getItem not implemented"); } + + private: + // Where to drop the item + float mLeft; + float mTop; + }; + + HUD::HUD(int width, int height, int fpsLevel, DragAndDrop* dragAndDrop) : Layout("openmw_hud.layout") , mHealth(NULL) @@ -52,7 +90,7 @@ namespace MWGui , mWeaponVisible(true) , mSpellVisible(true) , mWorldMouseOver(false) - , mEnemyHealthTimer(0) + , mEnemyHealthTimer(-1) , mIsDrowning(false) , mWeaponSpellTimer(0.f) , mDrowningFlashTheta(0.f) @@ -203,9 +241,9 @@ namespace MWGui } } - void HUD::setDrowningTimeLeft(float time) + void HUD::setDrowningTimeLeft(float time, float maxTime) { - size_t progress = time/20.0*200.0; + size_t progress = time/maxTime*200.0; mDrowning->setProgressPosition(progress); bool isDrowning = (progress == 0); @@ -229,29 +267,18 @@ namespace MWGui if (mDragAndDrop->mIsOnDragAndDrop) { // drop item into the gameworld - MWWorld::Ptr object = mDragAndDrop->mItem.mBase; - - MWBase::World* world = MWBase::Environment::get().getWorld(); + MWBase::Environment::get().getWorld()->breakInvisibility( + MWBase::Environment::get().getWorld()->getPlayerPtr()); MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); MyGUI::IntPoint cursorPosition = MyGUI::InputManager::getInstance().getMousePosition(); float mouseX = cursorPosition.left / float(viewSize.width); float mouseY = cursorPosition.top / float(viewSize.height); - if (world->canPlaceObject(mouseX, mouseY)) - world->placeObject(object, mouseX, mouseY, mDragAndDrop->mDraggedCount); - else - world->dropObjectOnGround(world->getPlayerPtr(), object, mDragAndDrop->mDraggedCount); + WorldItemModel drop (mouseX, mouseY); + mDragAndDrop->drop(&drop, NULL); MWBase::Environment::get().getWindowManager()->changePointer("arrow"); - - std::string sound = MWWorld::Class::get(object).getDownSoundId(object); - MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); - - // remove object from the container it was coming from - mDragAndDrop->mSourceModel->removeItem(mDragAndDrop->mItem, mDragAndDrop->mDraggedCount); - mDragAndDrop->finish(); - mDragAndDrop->mSourceModel->update(); } else { @@ -321,7 +348,7 @@ namespace MWGui void HUD::onWeaponClicked(MyGUI::Widget* _sender) { const MWWorld::Ptr& player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - if (MWWorld::Class::get(player).getNpcStats(player).isWerewolf()) + if (player.getClass().getNpcStats(player).isWerewolf()) { MWBase::Environment::get().getWindowManager()->messageBox("#{sWerewolfRefusal}"); return; @@ -333,7 +360,7 @@ namespace MWGui void HUD::onMagicClicked(MyGUI::Widget* _sender) { const MWWorld::Ptr& player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - if (MWWorld::Class::get(player).getNpcStats(player).isWerewolf()) + if (player.getClass().getNpcStats(player).isWerewolf()) { MWBase::Environment::get().getWindowManager()->messageBox("#{sWerewolfRefusal}"); return; @@ -416,7 +443,7 @@ namespace MWGui void HUD::setSelectedEnchantItem(const MWWorld::Ptr& item, int chargePercent) { - std::string itemName = MWWorld::Class::get(item).getName(item); + std::string itemName = item.getClass().getName(item); if (itemName != mSpellName && mSpellVisible) { mWeaponSpellTimer = 5.0f; @@ -439,7 +466,7 @@ namespace MWGui , MyGUI::Align::Stretch); std::string path = std::string("icons\\"); - path+=MWWorld::Class::get(item).getInventoryIcon(item); + path+=item.getClass().getInventoryIcon(item); Widgets::fixTexturePath(path); itemBox->setImageTexture(path); itemBox->setNeedMouseFocus(false); @@ -447,7 +474,7 @@ namespace MWGui void HUD::setSelectedWeapon(const MWWorld::Ptr& item, int durabilityPercent) { - std::string itemName = MWWorld::Class::get(item).getName(item); + std::string itemName = item.getClass().getName(item); if (itemName != mWeaponName && mWeaponVisible) { mWeaponSpellTimer = 5.0f; @@ -466,10 +493,10 @@ namespace MWGui MyGUI::Gui::getInstance().destroyWidget(mWeapImage->getChildAt(0)); std::string path = std::string("icons\\"); - path+=MWWorld::Class::get(item).getInventoryIcon(item); + path+=item.getClass().getInventoryIcon(item); Widgets::fixTexturePath(path); - if (MWWorld::Class::get(item).getEnchantment(item) != "") + if (item.getClass().getEnchantment(item) != "") { mWeapImage->setImageTexture("textures\\menu_icon_magic_mini.dds"); MyGUI::ImageBox* itemBox = mWeapImage->createWidgetReal("ImageBox", MyGUI::FloatCoord(0,0,1,1) @@ -518,7 +545,7 @@ namespace MWGui MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = world->getPlayerPtr(); - if (MWWorld::Class::get(player).getNpcStats(player).isWerewolf()) + if (player.getClass().getNpcStats(player).isWerewolf()) mWeapImage->setImageTexture("icons\\k\\tx_werewolf_hand.dds"); else mWeapImage->setImageTexture("icons\\k\\stealth_handtohand.dds"); @@ -601,12 +628,15 @@ namespace MWGui effectsDx = (viewSize.width - mMinimapBoxBaseRight) - (viewSize.width - mEffectBoxBaseRight); mMapVisible = mMinimapBox->getVisible (); + if (!mMapVisible) + mCellNameBox->setVisible(false); + mEffectBox->setPosition((viewSize.width - mEffectBoxBaseRight) - mEffectBox->getWidth() + effectsDx, mEffectBox->getTop()); } void HUD::updateEnemyHealthBar() { - MWMechanics::CreatureStats& stats = MWWorld::Class::get(mEnemy).getCreatureStats(mEnemy); + MWMechanics::CreatureStats& stats = mEnemy.getClass().getCreatureStats(mEnemy); mEnemyHealth->setProgressRange(100); // Health is usually cast to int before displaying. Actors die whenever they are < 1 health. // Therefore any value < 1 should show as an empty health bar. We do the same in statswindow :) @@ -625,7 +655,7 @@ namespace MWGui if (mIsDrowning) { float intensity = (cos(mDrowningFlashTheta) + 1.0f) / 2.0f; - mDrowningFlash->setColour(MyGUI::Colour(intensity, intensity, intensity)); + mDrowningFlash->setColour(MyGUI::Colour(intensity, 0, 0)); } } @@ -639,4 +669,10 @@ namespace MWGui updateEnemyHealthBar(); } + void HUD::resetEnemy() + { + mEnemy = MWWorld::Ptr(); + mEnemyHealthTimer = -1; + } + } diff --git a/apps/openmw/mwgui/hud.hpp b/apps/openmw/mwgui/hud.hpp index 1645d8db0..973ac0745 100644 --- a/apps/openmw/mwgui/hud.hpp +++ b/apps/openmw/mwgui/hud.hpp @@ -22,8 +22,9 @@ namespace MWGui void setBatchCount(unsigned int count); /// Set time left for the player to start drowning - /// @param time value from [0,20] - void setDrowningTimeLeft(float time); + /// @param time time left to start drowning + /// @param maxTime how long we can be underwater (in total) until drowning starts + void setDrowningTimeLeft(float time, float maxTime); void setDrowningBarVisible(bool visible); void setHmsVisible(bool visible); @@ -56,6 +57,7 @@ namespace MWGui void update(); void setEnemy(const MWWorld::Ptr& enemy); + void resetEnemy(); private: MyGUI::ProgressBar *mHealth, *mMagicka, *mStamina, *mEnemyHealth, *mDrowning; diff --git a/apps/openmw/mwgui/inventoryitemmodel.cpp b/apps/openmw/mwgui/inventoryitemmodel.cpp index 97e1e9a2b..ad1a4e953 100644 --- a/apps/openmw/mwgui/inventoryitemmodel.cpp +++ b/apps/openmw/mwgui/inventoryitemmodel.cpp @@ -4,6 +4,8 @@ #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" +#include "../mwmechanics/creaturestats.hpp" + namespace MWGui { @@ -38,17 +40,17 @@ ItemModel::ModelIndex InventoryItemModel::getIndex (ItemStack item) return -1; } -void InventoryItemModel::copyItem (const ItemStack& item, size_t count) +MWWorld::Ptr InventoryItemModel::copyItem (const ItemStack& item, size_t count, bool setNewOwner) { if (item.mBase.getContainerStore() == &mActor.getClass().getContainerStore(mActor)) throw std::runtime_error("Item to copy needs to be from a different container!"); - mActor.getClass().getContainerStore(mActor).add(item.mBase, count, mActor); + return *mActor.getClass().getContainerStore(mActor).add(item.mBase, count, mActor, setNewOwner); } void InventoryItemModel::removeItem (const ItemStack& item, size_t count) { - MWWorld::ContainerStore& store = MWWorld::Class::get(mActor).getContainerStore(mActor); + MWWorld::ContainerStore& store = mActor.getClass().getContainerStore(mActor); int removed = store.remove(item.mBase, count, mActor); if (removed == 0) @@ -57,9 +59,25 @@ void InventoryItemModel::removeItem (const ItemStack& item, size_t count) throw std::runtime_error("Not enough items in the stack to remove"); } +MWWorld::Ptr InventoryItemModel::moveItem(const ItemStack &item, size_t count, ItemModel *otherModel) +{ + bool setNewOwner = false; + + // Are you dead? Then you wont need that anymore + if (mActor.getClass().isActor() && mActor.getClass().getCreatureStats(mActor).isDead() + // Make sure that the item is actually owned by the dead actor + // Prevents a potential exploit for resetting the owner of any item, by placing the item in a corpse + && Misc::StringUtils::ciEqual(item.mBase.getCellRef().getOwner(), mActor.getCellRef().getRefId())) + setNewOwner = true; + + MWWorld::Ptr ret = otherModel->copyItem(item, count, setNewOwner); + removeItem(item, count); + return ret; +} + void InventoryItemModel::update() { - MWWorld::ContainerStore& store = MWWorld::Class::get(mActor).getContainerStore(mActor); + MWWorld::ContainerStore& store = mActor.getClass().getContainerStore(mActor); mItems.clear(); @@ -69,7 +87,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().getRefId() == "werewolfrobe") continue; ItemStack newItem (item, this, item.getRefData().getCount()); diff --git a/apps/openmw/mwgui/inventoryitemmodel.hpp b/apps/openmw/mwgui/inventoryitemmodel.hpp index 23856395e..f58ee2939 100644 --- a/apps/openmw/mwgui/inventoryitemmodel.hpp +++ b/apps/openmw/mwgui/inventoryitemmodel.hpp @@ -15,9 +15,12 @@ namespace MWGui virtual ModelIndex getIndex (ItemStack item); virtual size_t getItemCount(); - virtual void copyItem (const ItemStack& item, size_t count); + virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool setNewOwner=false); virtual void removeItem (const ItemStack& item, size_t count); + /// Move items from this model to \a otherModel. + virtual MWWorld::Ptr moveItem (const ItemStack& item, size_t count, ItemModel* otherModel); + virtual void update(); protected: diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index e9efe75e7..b1e8052d8 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -35,7 +35,7 @@ namespace MWGui , mTrading(false) , mLastXSize(0) , mLastYSize(0) - , mPreview(MWBase::Environment::get().getWorld ()->getPlayerPtr()) + , mPreview(new MWRender::InventoryPreview(MWBase::Environment::get().getWorld ()->getPlayerPtr())) , mPreviewDirty(true) , mDragAndDrop(dragAndDrop) , mSelectedItem(-1) @@ -91,8 +91,8 @@ namespace MWGui mTradeModel = new TradeItemModel(new InventoryItemModel(mPtr), MWWorld::Ptr()); mSortModel = new SortFilterItemModel(mTradeModel); mItemView->setModel(mSortModel); - mPreview = MWRender::InventoryPreview(mPtr); - mPreview.setup(); + mPreview.reset(new MWRender::InventoryPreview(mPtr)); + mPreview->setup(); } void InventoryWindow::setGuiMode(GuiMode mode) @@ -162,49 +162,20 @@ namespace MWGui } const ItemStack& item = mTradeModel->getItem(index); - std::string sound = MWWorld::Class::get(item.mBase).getDownSoundId(item.mBase); + std::string sound = item.mBase.getClass().getDownSoundId(item.mBase); 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_") + if (item.mBase.getCellRef().getRefId().size() > 6 + && item.mBase.getCellRef().getRefId().substr(0,6) == "bound_") { MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); MWBase::Environment::get().getWindowManager()->messageBox("#{sBarterDialog12}"); return; } - if (item.mType == ItemStack::Type_Equipped) - { - MWWorld::InventoryStore& invStore = MWWorld::Class::get(mPtr).getInventoryStore(mPtr); - MWWorld::Ptr newStack = *invStore.unequipItem(item.mBase, mPtr); - - // The unequipped item was re-stacked. We have to update the index - // since the item pointed does not exist anymore. - if (item.mBase != newStack) - { - // newIndex will store the index of the ItemStack the item was stacked on - int newIndex = -1; - for (size_t i=0; i < mTradeModel->getItemCount(); ++i) - { - if (mTradeModel->getItem(i).mBase == newStack) - { - newIndex = i; - break; - } - } - - if (newIndex == -1) - throw std::runtime_error("Can't find restacked item"); - - index = newIndex; - object = mTradeModel->getItem(index).mBase; - } - - } - bool shift = MyGUI::InputManager::getInstance().isShiftPressed(); if (MyGUI::InputManager::getInstance().isControlPressed()) count = 1; @@ -213,7 +184,7 @@ namespace MWGui { // check if merchant accepts item int services = MWBase::Environment::get().getWindowManager()->getTradeWindow()->getMerchantServices(); - if (!MWWorld::Class::get(object).canSell(object, services)) + if (!object.getClass().canSell(object, services)) { MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); MWBase::Environment::get().getWindowManager()-> @@ -226,7 +197,7 @@ namespace MWGui { CountDialog* dialog = MWBase::Environment::get().getWindowManager()->getCountDialog(); std::string message = mTrading ? "#{sQuanityMenuMessage01}" : "#{sTake}"; - dialog->open(MWWorld::Class::get(object).getName(object), message, count); + dialog->open(object.getClass().getName(object), message, count); dialog->eventOkClicked.clear(); if (mTrading) dialog->eventOkClicked += MyGUI::newDelegate(this, &InventoryWindow::sellItem); @@ -247,15 +218,48 @@ namespace MWGui notifyContentChanged(); } + void InventoryWindow::ensureSelectedItemUnequipped() + { + const ItemStack& item = mTradeModel->getItem(mSelectedItem); + if (item.mType == ItemStack::Type_Equipped) + { + MWWorld::InventoryStore& invStore = mPtr.getClass().getInventoryStore(mPtr); + MWWorld::Ptr newStack = *invStore.unequipItem(item.mBase, mPtr); + + // The unequipped item was re-stacked. We have to update the index + // since the item pointed does not exist anymore. + if (item.mBase != newStack) + { + // newIndex will store the index of the ItemStack the item was stacked on + int newIndex = -1; + for (size_t i=0; i < mTradeModel->getItemCount(); ++i) + { + if (mTradeModel->getItem(i).mBase == newStack) + { + newIndex = i; + break; + } + } + + if (newIndex == -1) + throw std::runtime_error("Can't find restacked item"); + + mSelectedItem = newIndex; + } + } + } + void InventoryWindow::dragItem(MyGUI::Widget* sender, int count) { + ensureSelectedItemUnequipped(); mDragAndDrop->startDrag(mSelectedItem, mSortModel, mTradeModel, mItemView, count); } void InventoryWindow::sellItem(MyGUI::Widget* sender, int count) { + ensureSelectedItemUnequipped(); const ItemStack& item = mTradeModel->getItem(mSelectedItem); - std::string sound = MWWorld::Class::get(item.mBase).getDownSoundId(item.mBase); + std::string sound = item.mBase.getClass().getDownSoundId(item.mBase); MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); if (item.mType == ItemStack::Type_Barter) @@ -378,7 +382,7 @@ namespace MWGui if (script.empty() || ptr.getRefData().getLocals().getIntVar(script, "pcskipequip") == 0) { - boost::shared_ptr action = MWWorld::Class::get(ptr).use(ptr); + boost::shared_ptr action = ptr.getClass().use(ptr); action->execute (MWBase::Environment::get().getWorld()->getPlayerPtr()); @@ -408,14 +412,8 @@ namespace MWGui if (mDragAndDrop->mSourceModel != mTradeModel) { - // add item to the player's inventory - MWWorld::ContainerStore& invStore = MWWorld::Class::get(mPtr).getContainerStore(mPtr); - MWWorld::ContainerStoreIterator it = invStore.begin(); - - it = invStore.add(ptr, mDragAndDrop->mDraggedCount, mPtr); - - mDragAndDrop->mSourceModel->removeItem(mDragAndDrop->mItem, mDragAndDrop->mDraggedCount); - ptr = *it; + // Move item to the player's inventory + ptr = mDragAndDrop->mSourceModel->moveItem(mDragAndDrop->mItem, mDragAndDrop->mDraggedCount, mTradeModel); } useItem(ptr); } @@ -444,19 +442,19 @@ namespace MWGui MWWorld::Ptr InventoryWindow::getAvatarSelectedItem(int x, int y) { - int slot = mPreview.getSlotSelected (x, y); + int slot = mPreview->getSlotSelected (x, y); if (slot == -1) return MWWorld::Ptr(); - MWWorld::InventoryStore& invStore = MWWorld::Class::get(mPtr).getInventoryStore(mPtr); + MWWorld::InventoryStore& invStore = mPtr.getClass().getInventoryStore(mPtr); if(invStore.getSlot(slot) != invStore.end()) { MWWorld::Ptr item = *invStore.getSlot(slot); // 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().getRefId() == "werewolfrobe") return MWWorld::Ptr(); return item; } @@ -468,8 +466,8 @@ namespace MWGui { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - float capacity = MWWorld::Class::get(player).getCapacity(player); - float encumbrance = MWWorld::Class::get(player).getEncumbrance(player); + float capacity = player.getClass().getCapacity(player); + float encumbrance = player.getClass().getEncumbrance(player); mEncumbranceBar->setValue(encumbrance, capacity); } @@ -493,16 +491,16 @@ namespace MWGui mPreviewDirty = false; MyGUI::IntSize size = mAvatarImage->getSize(); - mPreview.update (size.width, size.height); + mPreview->update (size.width, size.height); mAvatarImage->setImageTexture("CharacterPreview"); mAvatarImage->setImageCoord(MyGUI::IntCoord(0, 0, std::min(512, size.width), std::min(1024, size.height))); mAvatarImage->setImageTile(MyGUI::IntSize(std::min(512, size.width), std::min(1024, size.height))); mArmorRating->setCaptionWithReplacing ("#{sArmor}: " - + boost::lexical_cast(static_cast(MWWorld::Class::get(mPtr).getArmorRating(mPtr)))); + + boost::lexical_cast(static_cast(mPtr.getClass().getArmorRating(mPtr)))); if (mArmorRating->getTextSize().width > mArmorRating->getSize().width) - mArmorRating->setCaptionWithReplacing (boost::lexical_cast(static_cast(MWWorld::Class::get(mPtr).getArmorRating(mPtr)))); + mArmorRating->setCaptionWithReplacing (boost::lexical_cast(static_cast(mPtr.getClass().getArmorRating(mPtr)))); } } @@ -536,14 +534,16 @@ namespace MWGui && (type != typeid(ESM::Potion).name())) return; - if (MWWorld::Class::get(object).getName(object) == "") // objects without name presented to user can never be picked up + if (object.getClass().getName(object) == "") // objects without name presented to user can never be picked up return; int count = object.getRefData().getCount(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWBase::Environment::get().getWorld()->breakInvisibility(player); + // 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()->getPlayerPtr(); 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/inventorywindow.hpp b/apps/openmw/mwgui/inventorywindow.hpp index 7ef168e98..df563b3d4 100644 --- a/apps/openmw/mwgui/inventorywindow.hpp +++ b/apps/openmw/mwgui/inventorywindow.hpp @@ -34,7 +34,7 @@ namespace MWGui MWWorld::Ptr getAvatarSelectedItem(int x, int y); void rebuildAvatar() { - mPreview.rebuild(); + mPreview->rebuild(); } TradeItemModel* getTradeModel(); @@ -52,7 +52,7 @@ namespace MWGui DragAndDrop* mDragAndDrop; bool mPreviewDirty; - size_t mSelectedItem; + int mSelectedItem; MWWorld::Ptr mPtr; @@ -81,7 +81,7 @@ namespace MWGui int mLastXSize; int mLastYSize; - MWRender::InventoryPreview mPreview; + std::auto_ptr mPreview; bool mTrading; @@ -102,6 +102,9 @@ namespace MWGui void notifyContentChanged(); void adjustPanes(); + + /// Unequips mSelectedItem, if it is equipped, and then updates mSelectedItem in case it was re-stacked + void ensureSelectedItemUnequipped(); }; } diff --git a/apps/openmw/mwgui/itemmodel.cpp b/apps/openmw/mwgui/itemmodel.cpp index 58e89c4fc..55317724e 100644 --- a/apps/openmw/mwgui/itemmodel.cpp +++ b/apps/openmw/mwgui/itemmodel.cpp @@ -13,7 +13,7 @@ namespace MWGui , mType(Type_Normal) , mBase(base) { - if (MWWorld::Class::get(base).getEnchantment(base) != "") + if (base.getClass().getEnchantment(base) != "") mFlags |= Flag_Enchanted; } @@ -71,16 +71,22 @@ namespace MWGui { } + MWWorld::Ptr ItemModel::moveItem(const ItemStack &item, size_t count, ItemModel *otherModel) + { + MWWorld::Ptr ret = otherModel->copyItem(item, count); + removeItem(item, count); + return ret; + } + ProxyItemModel::~ProxyItemModel() { delete mSourceModel; } - void ProxyItemModel::copyItem (const ItemStack& item, size_t count) + MWWorld::Ptr ProxyItemModel::copyItem (const ItemStack& item, size_t count, bool setNewOwner) { - // no need to use mapToSource since itemIndex refers to an index in the sourceModel - mSourceModel->copyItem (item, count); + return mSourceModel->copyItem (item, count, setNewOwner); } void ProxyItemModel::removeItem (const ItemStack& item, size_t count) diff --git a/apps/openmw/mwgui/itemmodel.hpp b/apps/openmw/mwgui/itemmodel.hpp index 47aaf79e8..21c5477d0 100644 --- a/apps/openmw/mwgui/itemmodel.hpp +++ b/apps/openmw/mwgui/itemmodel.hpp @@ -55,7 +55,11 @@ namespace MWGui virtual void update() = 0; - virtual void copyItem (const ItemStack& item, size_t count) = 0; + /// Move items from this model to \a otherModel. + virtual MWWorld::Ptr moveItem (const ItemStack& item, size_t count, ItemModel* otherModel); + + /// @param setNewOwner Set the copied item's owner to the actor we are copying to, or keep the original owner? + virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool setNewOwner=false) = 0; virtual void removeItem (const ItemStack& item, size_t count) = 0; private: @@ -69,7 +73,7 @@ namespace MWGui { public: virtual ~ProxyItemModel(); - virtual void copyItem (const ItemStack& item, size_t count); + virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool setNewOwner=false); virtual void removeItem (const ItemStack& item, size_t count); virtual ModelIndex getIndex (ItemStack item); diff --git a/apps/openmw/mwgui/itemselection.cpp b/apps/openmw/mwgui/itemselection.cpp index 01ea3429c..1b197f6d8 100644 --- a/apps/openmw/mwgui/itemselection.cpp +++ b/apps/openmw/mwgui/itemselection.cpp @@ -26,6 +26,11 @@ namespace MWGui center(); } + void ItemSelectionDialog::exit() + { + eventDialogCanceled(); + } + void ItemSelectionDialog::openContainer(const MWWorld::Ptr& container) { mModel = new InventoryItemModel(container); @@ -53,7 +58,7 @@ namespace MWGui void ItemSelectionDialog::onCancelButtonClicked(MyGUI::Widget* sender) { - eventDialogCanceled(); + exit(); } } diff --git a/apps/openmw/mwgui/itemselection.hpp b/apps/openmw/mwgui/itemselection.hpp index c9ec23cfa..28c45c605 100644 --- a/apps/openmw/mwgui/itemselection.hpp +++ b/apps/openmw/mwgui/itemselection.hpp @@ -14,6 +14,8 @@ namespace MWGui public: ItemSelectionDialog(const std::string& label); + virtual void exit(); + typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; typedef MyGUI::delegates::CMultiDelegate1 EventHandle_Item; diff --git a/apps/openmw/mwgui/itemview.cpp b/apps/openmw/mwgui/itemview.cpp index 027c3201f..24bc3fd63 100644 --- a/apps/openmw/mwgui/itemview.cpp +++ b/apps/openmw/mwgui/itemview.cpp @@ -81,7 +81,7 @@ void ItemView::update() /// \todo performance improvement: don't create/destroy all the widgets everytime the container window changes size, only reposition them std::string path = std::string("icons\\"); - path += MWWorld::Class::get(item.mBase).getInventoryIcon(item.mBase); + path += item.mBase.getClass().getInventoryIcon(item.mBase); // background widget (for the "equipped" frame and magic item background image) bool isMagic = (item.mFlags & ItemStack::Flag_Enchanted); diff --git a/apps/openmw/mwgui/journalviewmodel.cpp b/apps/openmw/mwgui/journalviewmodel.cpp index c6bd6d15d..5994c6e21 100644 --- a/apps/openmw/mwgui/journalviewmodel.cpp +++ b/apps/openmw/mwgui/journalviewmodel.cpp @@ -178,7 +178,7 @@ struct JournalViewModelImpl : JournalViewModel KeywordSearchT::Match match; - while (i != utf8text.end () && mModel->mKeywordSearch.search (i, utf8text.end (), match)) + while (i != utf8text.end () && mModel->mKeywordSearch.search (i, utf8text.end (), match, utf8text.begin())) { if (i != match.mBeg) visitor (0, i - utf8text.begin (), match.mBeg - utf8text.begin ()); diff --git a/apps/openmw/mwgui/keywordsearch.hpp b/apps/openmw/mwgui/keywordsearch.hpp index a9fb6daab..4f4459b35 100644 --- a/apps/openmw/mwgui/keywordsearch.hpp +++ b/apps/openmw/mwgui/keywordsearch.hpp @@ -66,10 +66,19 @@ public: return false; } - bool search (Point beg, Point end, Match & match) + bool search (Point beg, Point end, Match & match, Point start) { for (Point i = beg; i != end; ++i) { + // check if previous character marked start of new word + if (i != start) + { + Point prev = i; + --prev; + if(isalpha(*prev)) + continue; + } + // check first character typename Entry::childen_t::iterator candidate = mRoot.mChildren.find (std::tolower (*i, mLocale)); diff --git a/apps/openmw/mwgui/levelupdialog.cpp b/apps/openmw/mwgui/levelupdialog.cpp index 5a3fc2855..38995ac32 100644 --- a/apps/openmw/mwgui/levelupdialog.cpp +++ b/apps/openmw/mwgui/levelupdialog.cpp @@ -61,8 +61,8 @@ namespace MWGui void LevelupDialog::setAttributeValues() { MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); - MWMechanics::CreatureStats& creatureStats = MWWorld::Class::get(player).getCreatureStats (player); - MWMechanics::NpcStats& pcStats = MWWorld::Class::get(player).getNpcStats (player); + MWMechanics::CreatureStats& creatureStats = player.getClass().getCreatureStats (player); + MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats (player); for (int i=0; i<8; ++i) { @@ -117,8 +117,8 @@ namespace MWGui { MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = world->getPlayerPtr(); - MWMechanics::CreatureStats& creatureStats = MWWorld::Class::get(player).getCreatureStats (player); - MWMechanics::NpcStats& pcStats = MWWorld::Class::get(player).getNpcStats (player); + MWMechanics::CreatureStats& creatureStats = player.getClass().getCreatureStats (player); + MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats (player); mSpentAttributes.clear(); resetCoins(); @@ -172,7 +172,7 @@ namespace MWGui void LevelupDialog::onOkButtonClicked (MyGUI::Widget* sender) { MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); - MWMechanics::NpcStats& pcStats = MWWorld::Class::get(player).getNpcStats (player); + MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats (player); if (mSpentAttributes.size() < 3) MWBase::Environment::get().getWindowManager ()->messageBox("#{sNotifyMessage36}"); diff --git a/apps/openmw/mwgui/list.cpp b/apps/openmw/mwgui/list.cpp index 8dda041ca..19f20eeee 100644 --- a/apps/openmw/mwgui/list.cpp +++ b/apps/openmw/mwgui/list.cpp @@ -26,7 +26,7 @@ namespace MWGui if (mClient == 0) mClient = this; - mScrollView = mClient->createWidgetReal( + mScrollView = mClient->createWidgetReal( "MW_ScrollView", MyGUI::FloatCoord(0.0, 0.0, 1.0, 1.0), MyGUI::Align::Top | MyGUI::Align::Left | MyGUI::Align::Stretch, getName() + "_ScrollView"); } @@ -48,10 +48,10 @@ namespace MWGui void MWList::redraw(bool scrollbarShown) { - const int _scrollBarWidth = 24; // fetch this from skin? + const int _scrollBarWidth = 20; // fetch this from skin? const int scrollBarWidth = scrollbarShown ? _scrollBarWidth : 0; const int spacing = 3; - size_t scrollbarPosition = mScrollView->getScrollPosition(); + size_t viewPosition = -mScrollView->getViewOffset().top; while (mScrollView->getChildCount()) { @@ -83,7 +83,7 @@ namespace MWGui else { MyGUI::ImageBox* separator = mScrollView->createWidget("MW_HLine", - MyGUI::IntCoord(2, mItemHeight, mScrollView->getWidth()-4, 18), + MyGUI::IntCoord(2, mItemHeight, mScrollView->getWidth() - scrollBarWidth - 4, 18), MyGUI::Align::Left | MyGUI::Align::Top | MyGUI::Align::HStretch); separator->setNeedMouseFocus(false); @@ -91,15 +91,15 @@ namespace MWGui } ++i; } - mScrollView->setCanvasSize(mClient->getSize().width + (_scrollBarWidth-scrollBarWidth), std::max(mItemHeight, mClient->getSize().height)); + mScrollView->setCanvasSize(mClient->getSize().width, std::max(mItemHeight, mClient->getSize().height)); if (!scrollbarShown && mItemHeight > mClient->getSize().height) redraw(true); - size_t scrollbarRange = mScrollView->getScrollRange(); - if(scrollbarPosition > scrollbarRange) - scrollbarPosition = scrollbarRange; - mScrollView->setScrollPosition(scrollbarPosition); + size_t viewRange = mScrollView->getCanvasSize().height; + if(viewPosition > viewRange) + viewPosition = viewRange; + mScrollView->setViewOffset(MyGUI::IntPoint(0, -viewPosition)); } bool MWList::hasItem(const std::string& name) @@ -151,19 +151,5 @@ namespace MWGui return mScrollView->findWidget (getName() + "_item_" + name); } - size_t MWScrollView::getScrollPosition() - { - return getVScroll()->getScrollPosition(); - } - - void MWScrollView::setScrollPosition(size_t position) - { - getVScroll()->setScrollPosition(position); - } - size_t MWScrollView::getScrollRange() - { - return getVScroll()->getScrollRange(); - } - } } diff --git a/apps/openmw/mwgui/list.hpp b/apps/openmw/mwgui/list.hpp index 956523c0d..dcfe7931a 100644 --- a/apps/openmw/mwgui/list.hpp +++ b/apps/openmw/mwgui/list.hpp @@ -7,18 +7,6 @@ namespace MWGui { namespace Widgets { - /** - * \brief a custom ScrollView which has access to scrollbar properties - */ - class MWScrollView : public MyGUI::ScrollView - { - MYGUI_RTTI_DERIVED(MWScrollView) - public: - size_t getScrollPosition(); - void setScrollPosition(size_t); - size_t getScrollRange(); - }; - /** * \brief a very simple list widget that supports word-wrapping entries * \note if the width or height of the list changes, you must call adjustSize() method @@ -70,7 +58,7 @@ namespace MWGui void onItemSelected(MyGUI::Widget* _sender); private: - MWGui::Widgets::MWScrollView* mScrollView; + MyGUI::ScrollView* mScrollView; MyGUI::Widget* mClient; std::vector mItems; diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 7917c75f3..7c915ebeb 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -160,7 +160,6 @@ namespace MWGui void LoadingScreen::setProgress (size_t value) { - assert(value < mProgressBar->getScrollRange()); if (value - mProgress < mProgressBar->getScrollRange()/100.f) return; mProgress = value; @@ -174,7 +173,6 @@ namespace MWGui mProgressBar->setScrollPosition(0); size_t value = mProgress + increase; mProgress = value; - assert(mProgress < mProgressBar->getScrollRange()); mProgressBar->setTrackSize(value / (float)(mProgressBar->getScrollRange()) * mProgressBar->getLineSize()); draw(); } diff --git a/apps/openmw/mwgui/loadingscreen.hpp b/apps/openmw/mwgui/loadingscreen.hpp index 55235173f..96e0e1ed4 100644 --- a/apps/openmw/mwgui/loadingscreen.hpp +++ b/apps/openmw/mwgui/loadingscreen.hpp @@ -25,7 +25,7 @@ namespace MWGui virtual void setProgressRange (size_t range); virtual void setProgress (size_t value); - virtual void increaseProgress (size_t increase); + virtual void increaseProgress (size_t increase=1); virtual void setVisible(bool visible); diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index b6e3915bb..53ce8f488 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -1,5 +1,7 @@ #include "mainmenu.hpp" +#include + #include #include "../mwbase/environment.hpp" @@ -16,6 +18,7 @@ #include "confirmationdialog.hpp" #include "imagebutton.hpp" #include "backgroundimage.hpp" +#include "videowidget.hpp" namespace MWGui { @@ -25,10 +28,11 @@ namespace MWGui , mButtonBox(0), mWidth (w), mHeight (h) , mSaveGameDialog(NULL) , mBackground(NULL) + , mVideo(NULL) { getWidget(mVersionText, "VersionText"); std::stringstream sstream; - sstream << "OpenMW version: " << OPENMW_VERSION; + sstream << "OpenMW Version: " << OPENMW_VERSION; // adding info about git hash if available std::string rev = OPENMW_VERSION_COMMITHASH; @@ -36,12 +40,14 @@ namespace MWGui if (!rev.empty() && !tag.empty()) { rev = rev.substr(0,10); - sstream << "\nrevision: " << rev; + sstream << "\nRevision: " << rev; } std::string output = sstream.str(); mVersionText->setCaption(output); + mHasAnimatedMenu = (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("video\\menu_background.bik")); + updateMenu(); } @@ -134,14 +140,66 @@ namespace MWGui void MainMenu::showBackground(bool show) { - if (show && !mBackground) + if (mVideo && !show) + { + MyGUI::Gui::getInstance().destroyWidget(mVideo); + mVideo = NULL; + } + if (mBackground && !show) { - mBackground = MyGUI::Gui::getInstance().createWidgetReal("ImageBox", 0,0,1,1, - MyGUI::Align::Stretch, "Menu"); - mBackground->setBackgroundImage("textures\\menu_morrowind.dds"); + MyGUI::Gui::getInstance().destroyWidget(mBackground); + mBackground = NULL; + } + + if (!show) + return; + + if (mHasAnimatedMenu) + { + if (!mVideo) + { + // Use black background to correct aspect ratio + mVideoBackground = MyGUI::Gui::getInstance().createWidgetReal("ImageBox", 0,0,1,1, + MyGUI::Align::Default, "Menu"); + mVideoBackground->setImageTexture("black.png"); + + mVideo = mVideoBackground->createWidget("ImageBox", 0,0,1,1, + MyGUI::Align::Stretch, "Menu"); + + mVideo->playVideo("video\\menu_background.bik", false); + } + + MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); + int screenWidth = viewSize.width; + int screenHeight = viewSize.height; + mVideoBackground->setSize(screenWidth, screenHeight); + + double imageaspect = static_cast(mVideo->getVideoWidth())/mVideo->getVideoHeight(); + + int leftPadding = std::max(0.0, (screenWidth - screenHeight * imageaspect) / 2); + int topPadding = std::max(0.0, (screenHeight - screenWidth / imageaspect) / 2); + + mVideo->setCoord(leftPadding, topPadding, + screenWidth - leftPadding*2, screenHeight - topPadding*2); + + mVideo->setVisible(true); + } + else + { + if (!mBackground) + { + mBackground = MyGUI::Gui::getInstance().createWidgetReal("ImageBox", 0,0,1,1, + MyGUI::Align::Stretch, "Menu"); + mBackground->setBackgroundImage("textures\\menu_morrowind.dds"); + } + mBackground->setVisible(true); } - if (mBackground) - mBackground->setVisible(show); + } + + void MainMenu::update(float dt) + { + if (mVideo) + mVideo->update(); } void MainMenu::updateMenu() @@ -165,14 +223,15 @@ namespace MWGui buttons.push_back("newgame"); + if (state==MWBase::StateManager::State_Running && + MWBase::Environment::get().getWorld()->getGlobalInt ("chargenstate")==-1 && + MWBase::Environment::get().getWindowManager()->isSavingAllowed()) + buttons.push_back("savegame"); + if (MWBase::Environment::get().getStateManager()->characterBegin()!= MWBase::Environment::get().getStateManager()->characterEnd()) buttons.push_back("loadgame"); - if (state==MWBase::StateManager::State_Running && - MWBase::Environment::get().getWorld()->getGlobalInt ("chargenstate")==-1) - buttons.push_back("savegame"); - buttons.push_back("options"); if (state==MWBase::StateManager::State_NoGame) @@ -228,7 +287,7 @@ namespace MWGui if (state == MWBase::StateManager::State_NoGame) { // Align with the background image - int bottomPadding=48; + int bottomPadding=24; mButtonBox->setCoord (mWidth/2 - maxwidth/2, mHeight - curH - bottomPadding, maxwidth, curH); } else diff --git a/apps/openmw/mwgui/mainmenu.hpp b/apps/openmw/mwgui/mainmenu.hpp index c27442536..ccd8df4b0 100644 --- a/apps/openmw/mwgui/mainmenu.hpp +++ b/apps/openmw/mwgui/mainmenu.hpp @@ -9,12 +9,15 @@ namespace MWGui class ImageButton; class BackgroundImage; class SaveGameDialog; + class VideoWidget; class MainMenu : public OEngine::GUI::Layout { int mWidth; int mHeight; + bool mHasAnimatedMenu; + public: MainMenu(int w, int h); @@ -24,6 +27,8 @@ namespace MWGui virtual void setVisible (bool visible); + void update(float dt); + private: MyGUI::Widget* mButtonBox; @@ -31,6 +36,9 @@ namespace MWGui BackgroundImage* mBackground; + MyGUI::ImageBox* mVideoBackground; + VideoWidget* mVideo; // For animated main menus + std::map mButtons; void onButtonClicked (MyGUI::Widget* sender); diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index 1cc9610df..29c065f3d 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -14,6 +14,8 @@ #include "../mwrender/globalmap.hpp" +#include "../components/esm/globalmap.hpp" + #include "widgets.hpp" namespace MWGui @@ -39,6 +41,13 @@ namespace MWGui LocalMapBase::~LocalMapBase() { + // Clear our "lost focus" delegate for marker widgets first, otherwise it will + // fire when the widget is about to be destroyed and the mouse cursor is over it. + // At that point, other widgets may already be destroyed, so applyFogOfWar (which is called by the delegate) would crash. + for (std::vector::iterator it = mDoorMarkerWidgets.begin(); it != mDoorMarkerWidgets.end(); ++it) + (*it)->eventMouseLostFocus.clear(); + for (std::vector::iterator it = mMarkerWidgets.begin(); it != mMarkerWidgets.end(); ++it) + (*it)->eventMouseLostFocus.clear(); } void LocalMapBase::init(MyGUI::ScrollView* widget, MyGUI::ImageBox* compass, OEngine::GUI::Layout* layout, bool mapDragAndDrop) @@ -56,11 +65,11 @@ namespace MWGui { MyGUI::ImageBox* map = mLocalMap->createWidget("ImageBox", MyGUI::IntCoord(mx*widgetSize, my*widgetSize, widgetSize, widgetSize), - MyGUI::Align::Top | MyGUI::Align::Left, "Map_" + boost::lexical_cast(mx) + "_" + boost::lexical_cast(my)); + MyGUI::Align::Top | MyGUI::Align::Left); MyGUI::ImageBox* fog = map->createWidget("ImageBox", MyGUI::IntCoord(0, 0, widgetSize, widgetSize), - MyGUI::Align::Top | MyGUI::Align::Left, "Map_" + boost::lexical_cast(mx) + "_" + boost::lexical_cast(my) + "_fog"); + MyGUI::Align::Top | MyGUI::Align::Left); if (!mMapDragAndDrop) { @@ -80,10 +89,11 @@ namespace MWGui mChanged = true; } - void LocalMapBase::toggleFogOfWar() + bool LocalMapBase::toggleFogOfWar() { mFogOfWar = !mFogOfWar; applyFogOfWar(); + return mFogOfWar; } void LocalMapBase::applyFogOfWar() @@ -151,8 +161,9 @@ namespace MWGui markerPos.cellX = cellX; markerPos.cellY = cellY; - widgetPos = MyGUI::IntPoint(nX * 512 + (1+cellX-mCurX) * 512, - nY * 512 + (1+cellY-mCurY) * 512); + // Image space is -Y up, cells are Y up + widgetPos = MyGUI::IntPoint(nX * 512 + (1+(cellX-mCurX)) * 512, + nY * 512 + (1-(cellY-mCurY)) * 512); } markerPos.nX = nX; @@ -170,14 +181,10 @@ namespace MWGui mInterior = interior; mChanged = false; - // clear all previous markers - for (unsigned int i=0; i< mLocalMap->getChildCount(); ++i) - { - if (mLocalMap->getChildAt(i)->getName ().substr (0, 4) == "Door") - { - MyGUI::Gui::getInstance ().destroyWidget (mLocalMap->getChildAt(i)); - } - } + // clear all previous door markers + for (std::vector::iterator it = mDoorMarkerWidgets.begin(); it != mDoorMarkerWidgets.end(); ++it) + MyGUI::Gui::getInstance().destroyWidget(*it); + mDoorMarkerWidgets.clear(); // Update the map textures for (int mx=0; mx<3; ++mx) @@ -231,7 +238,7 @@ namespace MWGui 8, 8); ++counter; MyGUI::Button* markerWidget = mLocalMap->createWidget("ButtonImage", - widgetCoord, MyGUI::Align::Default, "Door" + boost::lexical_cast(counter)); + widgetCoord, MyGUI::Align::Default); markerWidget->setImageResource("DoorMarker"); markerWidget->setUserString("ToolTipType", "Layout"); markerWidget->setUserString("ToolTipLayout", "TextToolTipOneLine"); @@ -241,6 +248,8 @@ namespace MWGui // Used by tooltips to not show the tooltip if marker is hidden by fog of war markerWidget->setUserString("IsMarker", "true"); markerWidget->setUserData(markerPos); + + mDoorMarkerWidgets.push_back(markerWidget); } updateMarkers(); @@ -331,7 +340,7 @@ namespace MWGui 8, 8); ++counter; MyGUI::ImageBox* markerWidget = mLocalMap->createWidget("ImageBox", - widgetCoord, MyGUI::Align::Default, "Marker" + boost::lexical_cast(counter)); + widgetCoord, MyGUI::Align::Default); markerWidget->setImageTexture(markerTexture); markerWidget->setUserString("IsMarker", "true"); markerWidget->setUserData(markerPos); @@ -342,13 +351,9 @@ namespace MWGui 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)); - } - } + for (std::vector::iterator it = mMarkerWidgets.begin(); it != mMarkerWidgets.end(); ++it) + MyGUI::Gui::getInstance().destroyWidget(*it); + mMarkerWidgets.clear(); addDetectionMarkers(MWBase::World::Detect_Creature); addDetectionMarkers(MWBase::World::Detect_Key); @@ -367,10 +372,11 @@ namespace MWGui widgetPos.top - 4, 8, 8); MyGUI::ImageBox* markerWidget = mLocalMap->createWidget("ImageBox", - widgetCoord, MyGUI::Align::Default, "MarkerMarked"); + widgetCoord, MyGUI::Align::Default); markerWidget->setImageTexture("textures\\menu_map_smark.dds"); markerWidget->setUserString("IsMarker", "true"); markerWidget->setUserData(markerPos); + mMarkerWidgets.push_back(markerWidget); } } @@ -436,10 +442,9 @@ namespace MWGui worldY * mGlobalMapRender->getHeight()+6, 12, 12); - static int _counter=0; MyGUI::Button* markerWidget = mGlobalMapOverlay->createWidget("ButtonImage", - widgetCoord, MyGUI::Align::Default, "Door" + boost::lexical_cast(_counter)); + widgetCoord, MyGUI::Align::Default); markerWidget->setImageResource("DoorMarker"); markerWidget->setUserString("ToolTipType", "Layout"); markerWidget->setUserString("ToolTipLayout", "TextToolTipOneLine"); @@ -452,11 +457,27 @@ namespace MWGui markerWidget->setUserString("ToolTipType", "Layout"); markerWidget->setUserString("ToolTipLayout", "TextToolTipOneLine"); markerWidget->setUserString("Caption_TextOneLine", name); + + CellId cell; + cell.first = x; + cell.second = y; + mMarkers.push_back(cell); } void MapWindow::cellExplored(int x, int y) { - mGlobalMapRender->exploreCell(x,y); + mQueuedToExplore.push_back(std::make_pair(x,y)); + } + + void MapWindow::onFrame(float dt) + { + for (std::vector::iterator it = mQueuedToExplore.begin(); it != mQueuedToExplore.end(); ++it) + { + mGlobalMapRender->exploreCell(it->first, it->second); + } + mQueuedToExplore.clear(); + + NoDrop::onFrame(dt); } void MapWindow::onDragStart(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) @@ -580,6 +601,7 @@ namespace MWGui void MapWindow::clear() { + mMarkers.clear(); mGlobalMapRender->clear(); while (mEventBoxGlobal->getChildCount()) @@ -588,21 +610,34 @@ namespace MWGui MyGUI::Gui::getInstance().destroyWidget(mGlobalMapOverlay->getChildAt(0)); } - void MapWindow::write(ESM::ESMWriter &writer) + void MapWindow::write(ESM::ESMWriter &writer, Loading::Listener& progress) { - mGlobalMapRender->write(writer); + ESM::GlobalMap map; + mGlobalMapRender->write(map); + + map.mMarkers = mMarkers; + + writer.startRecord(ESM::REC_GMAP); + map.save(writer); + writer.endRecord(ESM::REC_GMAP); + progress.increaseProgress(); } void MapWindow::readRecord(ESM::ESMReader &reader, int32_t type) { - std::vector > exploredCells; - mGlobalMapRender->readRecord(reader, type, exploredCells); - - for (std::vector >::iterator it = exploredCells.begin(); it != exploredCells.end(); ++it) + if (type == ESM::REC_GMAP) { - const ESM::Cell* cell = MWBase::Environment::get().getWorld()->getStore().get().search(it->first, it->second); - if (cell && !cell->mName.empty()) - addVisitedLocation(cell->mName, it->first, it->second); + ESM::GlobalMap map; + map.load(reader); + + mGlobalMapRender->read(map); + + for (std::vector::iterator it = map.mMarkers.begin(); it != map.mMarkers.end(); ++it) + { + const ESM::Cell* cell = MWBase::Environment::get().getWorld()->getStore().get().search(it->first, it->second); + if (cell && !cell->mName.empty()) + addVisitedLocation(cell->mName, it->first, it->second); + } } } } diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index 249477551..d23b0c228 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -35,7 +35,7 @@ namespace MWGui void setPlayerDir(const float x, const float y); void setPlayerPos(const float x, const float y); - void toggleFogOfWar(); + bool toggleFogOfWar(); struct MarkerPosition { @@ -58,6 +58,10 @@ namespace MWGui std::vector mMapWidgets; std::vector mFogWidgets; + // Keep track of created marker widgets, just to easily remove them later. + std::vector mDoorMarkerWidgets; // Doors + std::vector mMarkerWidgets; // Other markers + void applyFogOfWar(); void onMarkerFocused(MyGUI::Widget* w1, MyGUI::Widget* w2); @@ -93,19 +97,22 @@ namespace MWGui void renderGlobalMap(Loading::Listener* loadingListener); - void addVisitedLocation(const std::string& name, int x, int y); // adds the marker to the global map + // adds the marker to the global map + void addVisitedLocation(const std::string& name, int x, int y); + + // reveals this cell's map on the global map void cellExplored(int x, int y); void setGlobalMapPlayerPosition (float worldX, float worldY); virtual void open(); - void onFrame(float dt) { NoDrop::onFrame(dt); } + void onFrame(float dt); /// Clear all savegame-specific data void clear(); - void write (ESM::ESMWriter& writer); + void write (ESM::ESMWriter& writer, Loading::Listener& progress); void readRecord (ESM::ESMReader& reader, int32_t type); private: @@ -124,6 +131,14 @@ namespace MWGui MyGUI::IntPoint mLastDragPos; bool mGlobal; + // Markers on global map + typedef std::pair CellId; + std::vector mMarkers; + + // Cells that should be explored in the next frame (i.e. their map revealed on the global map) + // We can't do this immediately, because the map update is not immediate either (see mNeedMapUpdate in scene.cpp) + std::vector mQueuedToExplore; + MyGUI::Button* mEventBoxGlobal; MyGUI::Button* mEventBoxLocal; diff --git a/apps/openmw/mwgui/merchantrepair.cpp b/apps/openmw/mwgui/merchantrepair.cpp index 2a48e62a9..49cc60d8a 100644 --- a/apps/openmw/mwgui/merchantrepair.cpp +++ b/apps/openmw/mwgui/merchantrepair.cpp @@ -39,19 +39,19 @@ void MerchantRepair::startRepair(const MWWorld::Ptr &actor) MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); - MWWorld::ContainerStore& store = MWWorld::Class::get(player).getContainerStore(player); + MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); int categories = MWWorld::ContainerStore::Type_Weapon | MWWorld::ContainerStore::Type_Armor; for (MWWorld::ContainerStoreIterator iter (store.begin(categories)); iter!=store.end(); ++iter) { - if (MWWorld::Class::get(*iter).hasItemHealth(*iter)) + if (iter->getClass().hasItemHealth(*iter)) { - int maxDurability = MWWorld::Class::get(*iter).getItemMaxHealth(*iter); - int durability = (iter->getCellRef().mCharge == -1) ? maxDurability : iter->getCellRef().mCharge; + int maxDurability = iter->getClass().getItemMaxHealth(*iter); + int durability = iter->getClass().getItemHealth(*iter); if (maxDurability == durability) continue; - int basePrice = MWWorld::Class::get(*iter).getValue(*iter); + int basePrice = iter->getClass().getValue(*iter); float fRepairMult = MWBase::Environment::get().getWorld()->getStore().get() .find("fRepairMult")->getFloat(); @@ -64,7 +64,7 @@ void MerchantRepair::startRepair(const MWWorld::Ptr &actor) int price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mActor, x, true); - std::string name = MWWorld::Class::get(*iter).getName(*iter) + std::string name = iter->getClass().getName(*iter) + " - " + boost::lexical_cast(price) + MWBase::Environment::get().getWorld()->getStore().get() .find("sgp")->getString();; @@ -110,11 +110,16 @@ void MerchantRepair::open() center(); } +void MerchantRepair::exit() +{ + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_MerchantRepair); +} + void MerchantRepair::onRepairButtonClick(MyGUI::Widget *sender) { // repair MWWorld::Ptr item = *sender->getUserData(); - item.getCellRef().mCharge = MWWorld::Class::get(item).getItemMaxHealth(item); + item.getCellRef().setCharge(item.getClass().getItemMaxHealth(item)); MWBase::Environment::get().getSoundManager()->playSound("Repair",1,1); @@ -128,7 +133,7 @@ void MerchantRepair::onRepairButtonClick(MyGUI::Widget *sender) void MerchantRepair::onOkButtonClick(MyGUI::Widget *sender) { - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_MerchantRepair); + exit(); } } diff --git a/apps/openmw/mwgui/merchantrepair.hpp b/apps/openmw/mwgui/merchantrepair.hpp index 4cb39fe01..2f1387365 100644 --- a/apps/openmw/mwgui/merchantrepair.hpp +++ b/apps/openmw/mwgui/merchantrepair.hpp @@ -16,6 +16,8 @@ public: virtual void open(); + virtual void exit(); + void startRepair(const MWWorld::Ptr& actor); private: diff --git a/apps/openmw/mwgui/pickpocketitemmodel.cpp b/apps/openmw/mwgui/pickpocketitemmodel.cpp index 0196bf02d..230282f15 100644 --- a/apps/openmw/mwgui/pickpocketitemmodel.cpp +++ b/apps/openmw/mwgui/pickpocketitemmodel.cpp @@ -42,8 +42,8 @@ namespace MWGui 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_") + if (item.mBase.getCellRef().getRefId().size() > 6 + && item.mBase.getCellRef().getRefId().substr(0,6) == "bound_") { continue; } diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 4c0faeac1..af4e20ca4 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -2,6 +2,8 @@ #include +#include + #include "../mwworld/inventorystore.hpp" #include "../mwworld/class.hpp" @@ -55,6 +57,19 @@ namespace MWGui } } + void QuickKeysMenu::exit() + { + MWBase::Environment::get().getWindowManager()->removeGuiMode (MWGui::GM_QuickKeysMenu); + } + + void QuickKeysMenu::clear() + { + for (int i=0; i<10; ++i) + { + unassign(mQuickKeyButtons[i], i); + } + } + QuickKeysMenu::~QuickKeysMenu() { delete mAssignDialog; @@ -154,18 +169,17 @@ namespace MWGui frame->setUserString ("ToolTipType", "ItemPtr"); frame->setUserData(item); frame->eventMouseButtonClick += MyGUI::newDelegate(this, &QuickKeysMenu::onQuickKeyButtonClicked); - - MyGUI::ImageBox* image = frame->createWidget("ImageBox", MyGUI::IntCoord(5, 5, 32, 32), MyGUI::Align::Default); std::string path = std::string("icons\\"); - path += MWWorld::Class::get(item).getInventoryIcon(item); + path += item.getClass().getInventoryIcon(item); int pos = path.rfind("."); path.erase(pos); path.append(".dds"); image->setImageTexture (path); image->setNeedMouseFocus (false); - mItemSelectionDialog->setVisible(false); + if (mItemSelectionDialog) + mItemSelectionDialog->setVisible(false); } void QuickKeysMenu::onAssignItemCancel() @@ -191,14 +205,15 @@ namespace MWGui MyGUI::ImageBox* image = frame->createWidget("ImageBox", MyGUI::IntCoord(5, 5, 32, 32), MyGUI::Align::Default); std::string path = std::string("icons\\"); - path += MWWorld::Class::get(item).getInventoryIcon(item); + path += item.getClass().getInventoryIcon(item); int pos = path.rfind("."); path.erase(pos); path.append(".dds"); image->setImageTexture (path); image->setNeedMouseFocus (false); - mMagicSelectionDialog->setVisible(false); + if (mMagicSelectionDialog) + mMagicSelectionDialog->setVisible(false); } void QuickKeysMenu::onAssignMagic (const std::string& spellId) @@ -239,7 +254,8 @@ namespace MWGui image->setImageTexture (path); image->setNeedMouseFocus (false); - mMagicSelectionDialog->setVisible(false); + if (mMagicSelectionDialog) + mMagicSelectionDialog->setVisible(false); } void QuickKeysMenu::onAssignMagicCancel () @@ -254,7 +270,7 @@ namespace MWGui QuickKeyType type = *button->getUserData(); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player); + MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); if (type == Type_Item || type == Type_MagicItem) { @@ -263,11 +279,11 @@ namespace MWGui if (item.getRefData ().getCount() < 1) { // Try searching for a compatible replacement - std::string id = item.getCellRef().mRefID; + std::string id = item.getCellRef().getRefId(); for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { - if (Misc::StringUtils::ciEqual(it->getCellRef().mRefID, id)) + if (Misc::StringUtils::ciEqual(it->getCellRef().getRefId(), id)) { item = *it; button->getChildAt(0)->setUserData(item); @@ -279,7 +295,7 @@ namespace MWGui { // No replacement was found MWBase::Environment::get().getWindowManager ()->messageBox ( - "#{sQuickMenu5} " + MWWorld::Class::get(item).getName(item)); + "#{sQuickMenu5} " + item.getClass().getName(item)); return; } } @@ -319,13 +335,12 @@ namespace MWGui assert(it != store.end()); // equip, if it can be equipped - if (!MWWorld::Class::get(item).getEquipmentSlots(item).first.empty()) + if (!item.getClass().getEquipmentSlots(item).first.empty()) { MWBase::Environment::get().getWindowManager()->getInventoryWindow()->useItem(item); } store.setSelectedEnchantItem(it); - MWBase::Environment::get().getWindowManager()->setSelectedEnchantItem(item); } } @@ -375,6 +390,116 @@ namespace MWGui center(); } + void QuickKeysMenuAssign::exit() + { + setVisible(false); + } + + void QuickKeysMenu::write(ESM::ESMWriter &writer) + { + writer.startRecord(ESM::REC_KEYS); + + ESM::QuickKeys keys; + + for (int i=0; i<10; ++i) + { + MyGUI::Button* button = mQuickKeyButtons[i]; + + int type = *button->getUserData(); + + ESM::QuickKeys::QuickKey key; + key.mType = type; + + switch (type) + { + case Type_Unassigned: + break; + case Type_Item: + case Type_MagicItem: + { + MWWorld::Ptr item = *button->getChildAt(0)->getUserData(); + key.mId = item.getCellRef().getRefId(); + break; + } + case Type_Magic: + std::string spellId = button->getChildAt(0)->getUserString("Spell"); + key.mId = spellId; + break; + } + + keys.mKeys.push_back(key); + } + + keys.save(writer); + + writer.endRecord(ESM::REC_KEYS); + } + + void QuickKeysMenu::readRecord(ESM::ESMReader &reader, int32_t type) + { + if (type != ESM::REC_KEYS) + return; + + ESM::QuickKeys keys; + keys.load(reader); + + int i=0; + for (std::vector::const_iterator it = keys.mKeys.begin(); it != keys.mKeys.end(); ++it) + { + if (i >= 10) + return; + + mSelectedIndex = i; + int keyType = it->mType; + std::string id = it->mId; + MyGUI::Button* button = mQuickKeyButtons[i]; + + switch (keyType) + { + case Type_Magic: + onAssignMagic(id); + break; + case Type_Item: + case Type_MagicItem: + { + // Find the item by id + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); + MWWorld::Ptr item; + for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) + { + if (Misc::StringUtils::ciEqual(it->getCellRef().getRefId(), id)) + { + if (item.isEmpty() || + // Prefer the stack with the lowest remaining uses + !item.getClass().hasItemHealth(*it) || + it->getClass().getItemHealth(*it) < item.getClass().getItemHealth(item)) + { + item = *it; + } + } + } + + if (item.isEmpty()) + unassign(button, i); + else + { + if (keyType == Type_Item) + onAssignItem(item); + else if (keyType == Type_MagicItem) + onAssignMagicItem(item); + } + + break; + } + case Type_Unassigned: + unassign(button, i); + break; + } + + ++i; + } + } // --------------------------------------------------------------------------------------------------------- @@ -393,7 +518,12 @@ namespace MWGui void MagicSelectionDialog::onCancelButtonClicked (MyGUI::Widget *sender) { - mParent->onAssignMagicCancel (); + exit(); + } + + void MagicSelectionDialog::exit() + { + mParent->onAssignMagicCancel(); } void MagicSelectionDialog::open () @@ -408,8 +538,8 @@ namespace MWGui const int spellHeight = 18; MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player); - MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); + MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); + MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); MWMechanics::Spells& spells = stats.getSpells(); /// \todo lots of copy&pasted code from SpellWindow @@ -452,7 +582,7 @@ namespace MWGui std::vector items; for (MWWorld::ContainerStoreIterator it(store.begin()); it != store.end(); ++it) { - std::string enchantId = MWWorld::Class::get(*it).getEnchantment(*it); + std::string enchantId = it->getClass().getEnchantment(*it); if (enchantId != "") { // only add items with "Cast once" or "Cast on use" @@ -531,7 +661,7 @@ namespace MWGui MyGUI::Button* t = mMagicList->createWidget(equipped ? "SpellText" : "SpellTextUnequipped", MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); - t->setCaption(MWWorld::Class::get(item).getName(item)); + t->setCaption(item.getClass().getName(item)); t->setTextAlign(MyGUI::Align::Left); t->setUserData(item); t->setUserString("ToolTipType", "ItemPtr"); diff --git a/apps/openmw/mwgui/quickkeysmenu.hpp b/apps/openmw/mwgui/quickkeysmenu.hpp index 058519ece..40c5dab56 100644 --- a/apps/openmw/mwgui/quickkeysmenu.hpp +++ b/apps/openmw/mwgui/quickkeysmenu.hpp @@ -18,6 +18,7 @@ namespace MWGui QuickKeysMenu(); ~QuickKeysMenu(); + virtual void exit(); void onItemButtonClicked(MyGUI::Widget* sender); void onMagicButtonClicked(MyGUI::Widget* sender); @@ -41,6 +42,11 @@ namespace MWGui }; + void write (ESM::ESMWriter& writer); + void readRecord (ESM::ESMReader& reader, int32_t type); + void clear(); + + private: MyGUI::EditBox* mInstructionLabel; MyGUI::Button* mOkButton; @@ -64,6 +70,7 @@ namespace MWGui { public: QuickKeysMenuAssign(QuickKeysMenu* parent); + virtual void exit(); private: MyGUI::TextBox* mLabel; @@ -81,6 +88,7 @@ namespace MWGui MagicSelectionDialog(QuickKeysMenu* parent); virtual void open(); + virtual void exit(); private: MyGUI::Button* mCancelButton; diff --git a/apps/openmw/mwgui/recharge.cpp b/apps/openmw/mwgui/recharge.cpp index 683406d9e..e5ea54591 100644 --- a/apps/openmw/mwgui/recharge.cpp +++ b/apps/openmw/mwgui/recharge.cpp @@ -38,10 +38,15 @@ void Recharge::open() center(); } +void Recharge::exit() +{ + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Recharge); +} + void Recharge::start (const MWWorld::Ptr &item) { std::string path = std::string("icons\\"); - path += MWWorld::Class::get(item).getInventoryIcon(item); + path += item.getClass().getInventoryIcon(item); int pos = path.rfind("."); path.erase(pos); path.append(".dds"); @@ -56,7 +61,7 @@ void Recharge::updateView() { MWWorld::Ptr gem = *mGemIcon->getUserData(); - std::string soul = gem.getCellRef().mSoul; + std::string soul = gem.getCellRef().getSoul(); const ESM::Creature *creature = MWBase::Environment::get().getWorld()->getStore().get().find(soul); mChargeLabel->setCaptionWithReplacing("#{sCharges} " + boost::lexical_cast(creature->mData.mSoul)); @@ -85,7 +90,7 @@ void Recharge::updateView() int currentY = 0; MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - MWWorld::ContainerStore& store = MWWorld::Class::get(player).getContainerStore(player); + MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end(); ++iter) { @@ -93,20 +98,20 @@ void Recharge::updateView() if (enchantmentName.empty()) continue; const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find(enchantmentName); - if (iter->getCellRef().mEnchantmentCharge >= enchantment->mData.mCharge - || iter->getCellRef().mEnchantmentCharge == -1) + if (iter->getCellRef().getEnchantmentCharge() >= enchantment->mData.mCharge + || iter->getCellRef().getEnchantmentCharge() == -1) continue; MyGUI::TextBox* text = mView->createWidget ( "SandText", MyGUI::IntCoord(8, currentY, mView->getWidth()-8, 18), MyGUI::Align::Default); - text->setCaption(MWWorld::Class::get(*iter).getName(*iter)); + text->setCaption(iter->getClass().getName(*iter)); text->setNeedMouseFocus(false); currentY += 19; MyGUI::ImageBox* icon = mView->createWidget ( "ImageBox", MyGUI::IntCoord(16, currentY, 32, 32), MyGUI::Align::Default); std::string path = std::string("icons\\"); - path += MWWorld::Class::get(*iter).getInventoryIcon(*iter); + path += iter->getClass().getInventoryIcon(*iter); int pos = path.rfind("."); path.erase(pos); path.append(".dds"); @@ -118,7 +123,7 @@ void Recharge::updateView() Widgets::MWDynamicStatPtr chargeWidget = mView->createWidget ("MW_ChargeBar", MyGUI::IntCoord(72, currentY+2, 199, 20), MyGUI::Align::Default); - chargeWidget->setValue(iter->getCellRef().mEnchantmentCharge, enchantment->mData.mCharge); + chargeWidget->setValue(iter->getCellRef().getEnchantmentCharge(), enchantment->mData.mCharge); chargeWidget->setNeedMouseFocus(false); currentY += 32 + 4; @@ -128,7 +133,7 @@ void Recharge::updateView() void Recharge::onCancel(MyGUI::Widget *sender) { - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Recharge); + exit(); } void Recharge::onItemClicked(MyGUI::Widget *sender) @@ -159,15 +164,15 @@ void Recharge::onItemClicked(MyGUI::Widget *sender) int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] if (roll < x) { - std::string soul = gem.getCellRef().mSoul; + std::string soul = gem.getCellRef().getSoul(); const ESM::Creature *creature = MWBase::Environment::get().getWorld()->getStore().get().find(soul); float restored = creature->mData.mSoul * (roll / x); const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find( item.getClass().getEnchantment(item)); - item.getCellRef().mEnchantmentCharge = - std::min(item.getCellRef().mEnchantmentCharge + restored, static_cast(enchantment->mData.mCharge)); + item.getCellRef().setEnchantmentCharge( + std::min(item.getCellRef().getEnchantmentCharge() + restored, static_cast(enchantment->mData.mCharge))); player.getClass().skillUsageSucceeded (player, ESM::Skill::Enchant, 0); } diff --git a/apps/openmw/mwgui/recharge.hpp b/apps/openmw/mwgui/recharge.hpp index 2ffc5e10f..5558b197e 100644 --- a/apps/openmw/mwgui/recharge.hpp +++ b/apps/openmw/mwgui/recharge.hpp @@ -15,6 +15,8 @@ public: virtual void open(); + virtual void exit(); + void start (const MWWorld::Ptr& gem); protected: diff --git a/apps/openmw/mwgui/referenceinterface.hpp b/apps/openmw/mwgui/referenceinterface.hpp index 39574d0f7..df53a42b7 100644 --- a/apps/openmw/mwgui/referenceinterface.hpp +++ b/apps/openmw/mwgui/referenceinterface.hpp @@ -17,6 +17,8 @@ namespace MWGui void checkReferenceAvailable(); ///< closes the window, if the MW-reference has become unavailable + void resetReference() { mPtr = MWWorld::Ptr(); mCurrentPlayerCell = NULL; } + protected: virtual void onReferenceUnavailable() = 0; ///< called when reference has become unavailable diff --git a/apps/openmw/mwgui/repair.cpp b/apps/openmw/mwgui/repair.cpp index de96bcacd..29ec62887 100644 --- a/apps/openmw/mwgui/repair.cpp +++ b/apps/openmw/mwgui/repair.cpp @@ -35,12 +35,17 @@ void Repair::open() center(); } +void Repair::exit() +{ + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Repair); +} + void Repair::startRepairItem(const MWWorld::Ptr &item) { mRepair.setTool(item); std::string path = std::string("icons\\"); - path += MWWorld::Class::get(item).getInventoryIcon(item); + path += item.getClass().getInventoryIcon(item); int pos = path.rfind("."); path.erase(pos); path.append(".dds"); @@ -56,7 +61,7 @@ void Repair::updateRepairView() MWWorld::LiveCellRef *ref = mRepair.getTool().get(); - int uses = (mRepair.getTool().getCellRef().mCharge != -1) ? mRepair.getTool().getCellRef().mCharge : ref->mBase->mData.mUses; + int uses = mRepair.getTool().getClass().getItemHealth(mRepair.getTool()); float quality = ref->mBase->mData.mQuality; @@ -90,28 +95,28 @@ void Repair::updateRepairView() int currentY = 0; MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - MWWorld::ContainerStore& store = MWWorld::Class::get(player).getContainerStore(player); + MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); int categories = MWWorld::ContainerStore::Type_Weapon | MWWorld::ContainerStore::Type_Armor; for (MWWorld::ContainerStoreIterator iter (store.begin(categories)); iter!=store.end(); ++iter) { - if (MWWorld::Class::get(*iter).hasItemHealth(*iter)) + if (iter->getClass().hasItemHealth(*iter)) { - int maxDurability = MWWorld::Class::get(*iter).getItemMaxHealth(*iter); - int durability = (iter->getCellRef().mCharge == -1) ? maxDurability : iter->getCellRef().mCharge; + int maxDurability = iter->getClass().getItemMaxHealth(*iter); + int durability = iter->getClass().getItemHealth(*iter); if (maxDurability == durability) continue; MyGUI::TextBox* text = mRepairView->createWidget ( "SandText", MyGUI::IntCoord(8, currentY, mRepairView->getWidth()-8, 18), MyGUI::Align::Default); - text->setCaption(MWWorld::Class::get(*iter).getName(*iter)); + text->setCaption(iter->getClass().getName(*iter)); text->setNeedMouseFocus(false); currentY += 19; MyGUI::ImageBox* icon = mRepairView->createWidget ( "ImageBox", MyGUI::IntCoord(16, currentY, 32, 32), MyGUI::Align::Default); std::string path = std::string("icons\\"); - path += MWWorld::Class::get(*iter).getInventoryIcon(*iter); + path += iter->getClass().getInventoryIcon(*iter); int pos = path.rfind("."); path.erase(pos); path.append(".dds"); @@ -134,7 +139,7 @@ void Repair::updateRepairView() void Repair::onCancel(MyGUI::Widget *sender) { - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Repair); + exit(); } void Repair::onRepairItem(MyGUI::Widget *sender) diff --git a/apps/openmw/mwgui/repair.hpp b/apps/openmw/mwgui/repair.hpp index d0f5c54c4..42539ad9e 100644 --- a/apps/openmw/mwgui/repair.hpp +++ b/apps/openmw/mwgui/repair.hpp @@ -15,6 +15,8 @@ public: virtual void open(); + virtual void exit(); + void startRepairItem (const MWWorld::Ptr& item); protected: diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp index bb4373cba..348b7c1df 100644 --- a/apps/openmw/mwgui/savegamedialog.cpp +++ b/apps/openmw/mwgui/savegamedialog.cpp @@ -23,6 +23,7 @@ namespace MWGui : WindowModal("openmw_savegame_dialog.layout") , mSaving(true) , mCurrentCharacter(NULL) + , mCurrentSlot(NULL) { getWidget(mScreenshot, "Screenshot"); getWidget(mCharacterSelection, "SelectCharacter"); @@ -36,6 +37,7 @@ namespace MWGui mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onCancelButtonClicked); mCharacterSelection->eventComboChangePosition += MyGUI::newDelegate(this, &SaveGameDialog::onCharacterSelected); mSaveList->eventListChangePosition += MyGUI::newDelegate(this, &SaveGameDialog::onSlotSelected); + mSaveList->eventListMouseItemActivate += MyGUI::newDelegate(this, &SaveGameDialog::onSlotMouseClick); mSaveList->eventListSelectAccept += MyGUI::newDelegate(this, &SaveGameDialog::onSlotActivated); mSaveNameEdit->eventEditSelectAccept += MyGUI::newDelegate(this, &SaveGameDialog::onEditSelectAccept); mSaveNameEdit->eventEditTextChange += MyGUI::newDelegate(this, &SaveGameDialog::onSaveNameChanged); @@ -47,6 +49,37 @@ namespace MWGui accept(); } + void SaveGameDialog::onSlotMouseClick(MyGUI::ListBox* sender, size_t pos) + { + onSlotSelected(sender, pos); + + if (pos != MyGUI::ITEM_NONE && MyGUI::InputManager::getInstance().isShiftPressed()) + { + ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog(); + dialog->open("#{sMessage3}"); + dialog->eventOkClicked.clear(); + dialog->eventOkClicked += MyGUI::newDelegate(this, &SaveGameDialog::onDeleteSlotConfirmed); + dialog->eventCancelClicked.clear(); + } + } + + void SaveGameDialog::onDeleteSlotConfirmed() + { + MWBase::Environment::get().getStateManager()->deleteGame (mCurrentCharacter, mCurrentSlot); + mSaveList->removeItemAt(mSaveList->getIndexSelected()); + onSlotSelected(mSaveList, MyGUI::ITEM_NONE); + + // The character might be deleted now + size_t previousIndex = mCharacterSelection->getIndexSelected(); + open(); + if (mCharacterSelection->getItemCount()) + { + size_t nextCharacter = std::min(previousIndex, mCharacterSelection->getItemCount()-1); + mCharacterSelection->setIndexSelected(nextCharacter); + onCharacterSelected(mCharacterSelection, nextCharacter); + } + } + void SaveGameDialog::onSaveNameChanged(MyGUI::EditBox *sender) { // This might have previously been a save slot from the list. If so, that is no longer the case @@ -69,6 +102,12 @@ namespace MWGui center(); + mCharacterSelection->setCaption(""); + mCharacterSelection->removeAllItems(); + mCurrentCharacter = NULL; + mCurrentSlot = NULL; + mSaveList->removeAllItems(); + MWBase::StateManager* mgr = MWBase::Environment::get().getStateManager(); if (mgr->characterBegin() == mgr->characterEnd()) return; @@ -78,8 +117,6 @@ namespace MWGui std::string directory = Misc::StringUtils::lowerCase (Settings::Manager::getString ("character", "Saves")); - mCharacterSelection->removeAllItems(); - int selectedIndex = MyGUI::ITEM_NONE; for (MWBase::StateManager::CharacterIterator it = mgr->characterBegin(); it != mgr->characterEnd(); ++it) @@ -97,9 +134,12 @@ namespace MWGui else { // Find the localised name for this class from the store - const ESM::Class* class_ = MWBase::Environment::get().getWorld()->getStore().get().find( + const ESM::Class* class_ = MWBase::Environment::get().getWorld()->getStore().get().search( it->getSignature().mPlayerClassId); - className = class_->mName; + if (class_) + className = class_->mName; + else + className = "?"; // From an older savegame format that did not support custom classes properly. } title << " (Level " << it->getSignature().mPlayerLevel << " " << className << ")"; @@ -122,6 +162,11 @@ namespace MWGui } + void SaveGameDialog::exit() + { + setVisible(false); + } + void SaveGameDialog::setLoadOrSave(bool load) { mSaving = !load; @@ -140,7 +185,7 @@ namespace MWGui void SaveGameDialog::onCancelButtonClicked(MyGUI::Widget *sender) { - setVisible(false); + exit(); } void SaveGameDialog::onConfirmationGiven() @@ -152,23 +197,10 @@ namespace MWGui { MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(NULL); - // Get the selected slot, if any - unsigned int i=0; - const MWState::Slot* slot = NULL; - - if (mCurrentCharacter) - { - for (MWState::Character::SlotIterator it = mCurrentCharacter->begin(); it != mCurrentCharacter->end(); ++it,++i) - { - if (i == mSaveList->getIndexSelected()) - slot = &*it; - } - } - if (mSaving) { // If overwriting an existing slot, ask for confirmation first - if (slot != NULL && !reallySure) + if (mCurrentSlot != NULL && !reallySure) { ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog(); dialog->open("#{sMessage4}"); @@ -182,23 +214,21 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage65}"); return; } - MWBase::Environment::get().getStateManager()->saveGame (mSaveNameEdit->getCaption(), slot); - } - else - { - if (mCurrentCharacter && slot) - { - MWBase::Environment::get().getStateManager()->loadGame (mCurrentCharacter, slot); - MWBase::Environment::get().getWindowManager()->removeGuiMode (MWGui::GM_MainMenu); - } } setVisible(false); + MWBase::Environment::get().getWindowManager()->removeGuiMode (MWGui::GM_MainMenu); - if (MWBase::Environment::get().getStateManager()->getState()== - MWBase::StateManager::State_NoGame) + if (mSaving) { - MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); + MWBase::Environment::get().getStateManager()->saveGame (mSaveNameEdit->getCaption(), mCurrentSlot); + } + else + { + if (mCurrentCharacter && mCurrentSlot) + { + MWBase::Environment::get().getStateManager()->loadGame (mCurrentCharacter, mCurrentSlot); + } } } @@ -221,6 +251,7 @@ namespace MWGui assert(character && "Can't find selected character"); mCurrentCharacter = character; + mCurrentSlot = NULL; fillSaveList(); } @@ -233,13 +264,23 @@ namespace MWGui { mSaveList->addItem(it->mProfile.mDescription); } - onSlotSelected(mSaveList, MyGUI::ITEM_NONE); + // When loading, Auto-select the first save, if there is one + if (mSaveList->getItemCount() && !mSaving) + { + mSaveList->setIndexSelected(0); + onSlotSelected(mSaveList, 0); + // Give key focus to save list so we can confirm the selection with Enter + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mSaveList); + } + else + onSlotSelected(mSaveList, MyGUI::ITEM_NONE); } void SaveGameDialog::onSlotSelected(MyGUI::ListBox *sender, size_t pos) { if (pos == MyGUI::ITEM_NONE) { + mCurrentSlot = NULL; mInfoText->setCaption(""); mScreenshot->setImageTexture(""); return; @@ -248,17 +289,17 @@ namespace MWGui if (mSaving) mSaveNameEdit->setCaption(sender->getItemNameAt(pos)); - const MWState::Slot* slot = NULL; + mCurrentSlot = NULL; unsigned int i=0; for (MWState::Character::SlotIterator it = mCurrentCharacter->begin(); it != mCurrentCharacter->end(); ++it, ++i) { if (i == pos) - slot = &*it; + mCurrentSlot = &*it; } - assert(slot && "Can't find selected slot"); + assert(mCurrentSlot && "Can't find selected slot"); std::stringstream text; - time_t time = slot->mTimeStamp; + time_t time = mCurrentSlot->mTimeStamp; struct tm* timeinfo; timeinfo = localtime(&time); @@ -269,24 +310,24 @@ namespace MWGui char buffer[size]; if (std::strftime(buffer, size, "%x %X", timeinfo) > 0) text << buffer << "\n"; - text << "Level " << slot->mProfile.mPlayerLevel << "\n"; - text << slot->mProfile.mPlayerCell << "\n"; + text << "Level " << mCurrentSlot->mProfile.mPlayerLevel << "\n"; + text << mCurrentSlot->mProfile.mPlayerCell << "\n"; // text << "Time played: " << slot->mProfile.mTimePlayed << "\n"; - int hour = int(slot->mProfile.mInGameTime.mGameHour); + int hour = int(mCurrentSlot->mProfile.mInGameTime.mGameHour); bool pm = hour >= 12; if (hour >= 13) hour -= 12; if (hour == 0) hour = 12; text - << slot->mProfile.mInGameTime.mDay << " " - << MWBase::Environment::get().getWorld()->getMonthName(slot->mProfile.mInGameTime.mMonth) + << mCurrentSlot->mProfile.mInGameTime.mDay << " " + << MWBase::Environment::get().getWorld()->getMonthName(mCurrentSlot->mProfile.mInGameTime.mMonth) << " " << hour << " " << (pm ? "#{sSaveMenuHelp05}" : "#{sSaveMenuHelp04}"); mInfoText->setCaptionWithReplacing(text.str()); // Decode screenshot - std::vector data = slot->mProfile.mScreenshot; // MemoryDataStream doesn't work with const data :( + std::vector data = mCurrentSlot->mProfile.mScreenshot; // MemoryDataStream doesn't work with const data :( Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(&data[0], data.size())); Ogre::Image image; image.load(stream, "jpg"); diff --git a/apps/openmw/mwgui/savegamedialog.hpp b/apps/openmw/mwgui/savegamedialog.hpp index 8d09a1cbc..9f44d5370 100644 --- a/apps/openmw/mwgui/savegamedialog.hpp +++ b/apps/openmw/mwgui/savegamedialog.hpp @@ -6,6 +6,7 @@ namespace MWState { class Character; + class Slot; } namespace MWGui @@ -18,14 +19,23 @@ namespace MWGui virtual void open(); + virtual void exit(); + void setLoadOrSave(bool load); private: void onCancelButtonClicked (MyGUI::Widget* sender); void onOkButtonClicked (MyGUI::Widget* sender); void onCharacterSelected (MyGUI::ComboBox* sender, size_t pos); + // Slot selected (mouse click or arrow keys) void onSlotSelected (MyGUI::ListBox* sender, size_t pos); + // Slot activated (double click or enter key) void onSlotActivated (MyGUI::ListBox* sender, size_t pos); + // Slot clicked with mouse + void onSlotMouseClick(MyGUI::ListBox* sender, size_t pos); + + void onDeleteSlotConfirmed(); + void onEditSelectAccept (MyGUI::EditBox* sender); void onSaveNameChanged (MyGUI::EditBox* sender); void onConfirmationGiven(); @@ -46,6 +56,7 @@ namespace MWGui MyGUI::Widget* mSpacer; const MWState::Character* mCurrentCharacter; + const MWState::Slot* mCurrentSlot; }; diff --git a/apps/openmw/mwgui/scrollwindow.cpp b/apps/openmw/mwgui/scrollwindow.cpp index a084e6453..3d0751114 100644 --- a/apps/openmw/mwgui/scrollwindow.cpp +++ b/apps/openmw/mwgui/scrollwindow.cpp @@ -67,6 +67,13 @@ namespace MWGui setTakeButtonShow(true); } + void ScrollWindow::exit() + { + MWBase::Environment::get().getSoundManager()->playSound ("scroll", 1.0, 1.0); + + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Scroll); + } + void ScrollWindow::setTakeButtonShow(bool show) { mTakeButtonShow = show; @@ -81,9 +88,7 @@ namespace MWGui void ScrollWindow::onCloseButtonClicked (MyGUI::Widget* _sender) { - MWBase::Environment::get().getSoundManager()->playSound ("scroll", 1.0, 1.0); - - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Scroll); + exit(); } void ScrollWindow::onTakeButtonClicked (MyGUI::Widget* _sender) diff --git a/apps/openmw/mwgui/scrollwindow.hpp b/apps/openmw/mwgui/scrollwindow.hpp index 5feaff7bf..17e56f334 100644 --- a/apps/openmw/mwgui/scrollwindow.hpp +++ b/apps/openmw/mwgui/scrollwindow.hpp @@ -14,6 +14,7 @@ namespace MWGui ScrollWindow (); void open (MWWorld::Ptr scroll); + virtual void exit(); void setTakeButtonShow(bool show); void setInventoryAllowed(bool allowed); diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 78adecd3e..749616490 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -73,17 +73,6 @@ namespace return (Ogre::Root::getSingleton ().getRenderSystem ()->getName ().find("OpenGL") != std::string::npos) ? "glsl" : "hlsl"; } - bool cgAvailable () - { - Ogre::Root::PluginInstanceList list = Ogre::Root::getSingleton ().getInstalledPlugins (); - for (Ogre::Root::PluginInstanceList::const_iterator it = list.begin(); it != list.end(); ++it) - { - if ((*it)->getName() == "Cg Program Manager") - return true; - } - return false; - } - const char* checkButtonType = "CheckButton"; const char* sliderType = "Slider"; @@ -242,7 +231,7 @@ namespace MWGui void SettingsWindow::onOkButtonClicked(MyGUI::Widget* _sender) { - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Settings); + exit(); } void SettingsWindow::onResolutionSelected(MyGUI::ListBox* _sender, size_t index) @@ -366,12 +355,7 @@ namespace MWGui void SettingsWindow::onShaderModeToggled(MyGUI::Widget* _sender) { std::string val = static_cast(_sender)->getCaption(); - if (val == "cg") - { - val = hlslGlsl(); - } - else if (cgAvailable ()) - val = "cg"; + val = hlslGlsl(); static_cast(_sender)->setCaption(val); @@ -510,4 +494,9 @@ namespace MWGui { updateControlsBox (); } + + void SettingsWindow::exit() + { + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Settings); + } } diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index 7a6c1a5ed..37f2c8af0 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -17,6 +17,8 @@ namespace MWGui virtual void open(); + virtual void exit(); + void updateControlsBox(); protected: diff --git a/apps/openmw/mwgui/sortfilteritemmodel.cpp b/apps/openmw/mwgui/sortfilteritemmodel.cpp index 74a4f88e7..b8dcbcbbb 100644 --- a/apps/openmw/mwgui/sortfilteritemmodel.cpp +++ b/apps/openmw/mwgui/sortfilteritemmodel.cpp @@ -47,8 +47,8 @@ namespace if (left.mBase.getTypeName() == right.mBase.getTypeName()) { - int cmp = MWWorld::Class::get(left.mBase).getName(left.mBase).compare( - MWWorld::Class::get(right.mBase).getName(right.mBase)); + int cmp = left.mBase.getClass().getName(left.mBase).compare( + right.mBase.getClass().getName(right.mBase)); return cmp < 0; } else @@ -114,7 +114,7 @@ namespace MWGui if ((mFilter & Filter_OnlyEnchanted) && !(item.mFlags & ItemStack::Flag_Enchanted)) return false; if ((mFilter & Filter_OnlyChargedSoulstones) && (base.getTypeName() != typeid(ESM::Miscellaneous).name() - || base.getCellRef().mSoul == "")) + || base.getCellRef().getSoul() == "")) return false; if ((mFilter & Filter_OnlyEnchantable) && (item.mFlags & ItemStack::Flag_Enchanted || (base.getTypeName() != typeid(ESM::Armor).name() diff --git a/apps/openmw/mwgui/spellbuyingwindow.cpp b/apps/openmw/mwgui/spellbuyingwindow.cpp index 77df46514..8d9f35daa 100644 --- a/apps/openmw/mwgui/spellbuyingwindow.cpp +++ b/apps/openmw/mwgui/spellbuyingwindow.cpp @@ -33,6 +33,11 @@ namespace MWGui mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellBuyingWindow::onCancelButtonClicked); } + void SpellBuyingWindow::exit() + { + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_SpellBuying); + } + void SpellBuyingWindow::addSpell(const std::string& spellId) { const MWWorld::ESMStore &store = @@ -83,7 +88,7 @@ namespace MWGui mPtr = actor; clearSpells(); - MWMechanics::Spells& merchantSpells = MWWorld::Class::get (actor).getCreatureStats (actor).getSpells(); + MWMechanics::Spells& merchantSpells = actor.getClass().getCreatureStats (actor).getSpells(); for (MWMechanics::Spells::TIterator iter = merchantSpells.begin(); iter!=merchantSpells.end(); ++iter) { @@ -107,7 +112,7 @@ namespace MWGui bool SpellBuyingWindow::playerHasSpell(const std::string &id) { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - MWMechanics::Spells& playerSpells = MWWorld::Class::get (player).getCreatureStats (player).getSpells(); + MWMechanics::Spells& playerSpells = player.getClass().getCreatureStats (player).getSpells(); for (MWMechanics::Spells::TIterator it = playerSpells.begin(); it != playerSpells.end(); ++it) { if (Misc::StringUtils::ciEqual(id, it->first)) @@ -121,7 +126,7 @@ namespace MWGui int price = *_sender->getUserData(); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); + MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); MWMechanics::Spells& spells = stats.getSpells(); spells.add (mSpellsWidgetMap.find(_sender)->second); player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); @@ -132,7 +137,7 @@ namespace MWGui void SpellBuyingWindow::onCancelButtonClicked(MyGUI::Widget* _sender) { - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_SpellBuying); + exit(); } void SpellBuyingWindow::updateLabels() diff --git a/apps/openmw/mwgui/spellbuyingwindow.hpp b/apps/openmw/mwgui/spellbuyingwindow.hpp index f7ea54c89..2a6dcfdcc 100644 --- a/apps/openmw/mwgui/spellbuyingwindow.hpp +++ b/apps/openmw/mwgui/spellbuyingwindow.hpp @@ -25,6 +25,8 @@ namespace MWGui void startSpellBuying(const MWWorld::Ptr& actor); + virtual void exit(); + protected: MyGUI::Button* mCancelButton; MyGUI::TextBox* mPlayerGold; diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index 469d7188e..030b8bf37 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -75,6 +75,15 @@ namespace MWGui center(); } + void EditEffectDialog::exit() + { + setVisible(false); + if(mEditing) + eventEffectModified(mOldEffect); + else + eventEffectRemoved(mEffect); + } + void EditEffectDialog::newEffect (const ESM::MagicEffect *effect) { setMagicEffect(effect); @@ -222,11 +231,7 @@ namespace MWGui void EditEffectDialog::onCancelButtonClicked (MyGUI::Widget* sender) { - setVisible(false); - if(mEditing) - eventEffectModified(mOldEffect); - else - eventEffectRemoved(mEffect); + exit(); } void EditEffectDialog::setSkill (int skill) @@ -313,7 +318,7 @@ namespace MWGui void SpellCreationDialog::onCancelButtonClicked (MyGUI::Widget* sender) { - MWBase::Environment::get().getWindowManager()->removeGuiMode (MWGui::GM_SpellCreation); + exit(); } void SpellCreationDialog::onBuyButtonClicked (MyGUI::Widget* sender) @@ -353,7 +358,7 @@ namespace MWGui const ESM::Spell* spell = MWBase::Environment::get().getWorld()->createRecord(mSpell); - MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); + MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); MWMechanics::Spells& spells = stats.getSpells(); spells.add (spell->mId); @@ -367,6 +372,11 @@ namespace MWGui center(); } + void SpellCreationDialog::exit() + { + MWBase::Environment::get().getWindowManager()->removeGuiMode (MWGui::GM_SpellCreation); + } + void SpellCreationDialog::onReferenceUnavailable () { MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Dialogue); @@ -445,7 +455,7 @@ namespace MWGui // get the list of magic effects that are known to the player MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); + MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); MWMechanics::Spells& spells = stats.getSpells(); std::vector knownEffects; diff --git a/apps/openmw/mwgui/spellcreationdialog.hpp b/apps/openmw/mwgui/spellcreationdialog.hpp index e424d7395..25c615678 100644 --- a/apps/openmw/mwgui/spellcreationdialog.hpp +++ b/apps/openmw/mwgui/spellcreationdialog.hpp @@ -18,6 +18,7 @@ namespace MWGui EditEffectDialog(); virtual void open(); + virtual void exit(); void setSkill(int skill); void setAttribute(int attribute); @@ -127,6 +128,7 @@ namespace MWGui SpellCreationDialog(); virtual void open(); + virtual void exit(); void startSpellMaking(MWWorld::Ptr actor); diff --git a/apps/openmw/mwgui/spellicons.cpp b/apps/openmw/mwgui/spellicons.cpp index 0cd665a87..1a9e418de 100644 --- a/apps/openmw/mwgui/spellicons.cpp +++ b/apps/openmw/mwgui/spellicons.cpp @@ -21,7 +21,7 @@ namespace MWGui { void EffectSourceVisitor::visit (MWMechanics::EffectKey key, - const std::string& sourceName, const std::string& casterHandle, + const std::string& sourceName, int casterActorId, float magnitude, float remainingTime) { MagicEffectInfo newEffectSource; @@ -40,14 +40,14 @@ namespace MWGui // TODO: Tracking add/remove/expire would be better than force updating every frame MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - const MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); + const MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); EffectSourceVisitor visitor; // permanent item enchantments & permanent spells visitor.mIsPermanent = true; - MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player); + MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); store.visitEffectSources(visitor); stats.getSpells().visitEffectSources(visitor); diff --git a/apps/openmw/mwgui/spellicons.hpp b/apps/openmw/mwgui/spellicons.hpp index 1bb80f3d4..7df9ad8b9 100644 --- a/apps/openmw/mwgui/spellicons.hpp +++ b/apps/openmw/mwgui/spellicons.hpp @@ -42,8 +42,10 @@ namespace MWGui std::map > mEffectSources; + virtual ~EffectSourceVisitor() {} + virtual void visit (MWMechanics::EffectKey key, - const std::string& sourceName, const std::string& casterHandle, + const std::string& sourceName, int casterActorId, float magnitude, float remainingTime = -1); }; diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index b052739bd..fb5a80cc7 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -84,8 +84,8 @@ namespace MWGui // retrieve all player spells, divide them into Powers and Spells and sort them std::vector spellList; MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player); - MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); + MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); + MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); MWMechanics::Spells& spells = stats.getSpells(); for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it) @@ -122,7 +122,7 @@ namespace MWGui std::vector items; for (MWWorld::ContainerStoreIterator it(store.begin()); it != store.end(); ++it) { - std::string enchantId = MWWorld::Class::get(*it).getEnchantment(*it); + std::string enchantId = it->getClass().getEnchantment(*it); if (enchantId != "") { // only add items with "Cast once" or "Cast on use" @@ -203,7 +203,7 @@ namespace MWGui MWWorld::Ptr item = *it; const ESM::Enchantment* enchant = - esmStore.get().find(MWWorld::Class::get(item).getEnchantment(item)); + esmStore.get().find(item.getClass().getEnchantment(item)); // check if the item is currently equipped (will display in a different color) bool equipped = false; @@ -218,7 +218,7 @@ namespace MWGui MyGUI::Button* t = mSpellView->createWidget(equipped ? "SpellText" : "SpellTextUnequipped", MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); - t->setCaption(MWWorld::Class::get(item).getName(item)); + t->setCaption(item.getClass().getName(item)); t->setTextAlign(MyGUI::Align::Left); t->setUserData(item); t->setUserString("ToolTipType", "ItemPtr"); @@ -238,7 +238,7 @@ namespace MWGui int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10)); std::string cost = boost::lexical_cast(castCost); - int currentCharge = int(item.getCellRef().mEnchantmentCharge); + int currentCharge = int(item.getCellRef().getEnchantmentCharge()); if (currentCharge == -1) currentCharge = enchant->mData.mCharge; std::string charge = boost::lexical_cast(currentCharge); @@ -300,7 +300,7 @@ namespace MWGui void SpellWindow::onEnchantedItemSelected(MyGUI::Widget* _sender) { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player); + MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); MWWorld::Ptr item = *_sender->getUserData(); // retrieve ContainerStoreIterator to the item @@ -316,13 +316,12 @@ namespace MWGui // equip, if it can be equipped and is not already equipped if (_sender->getUserString("Equipped") == "false" - && !MWWorld::Class::get(item).getEquipmentSlots(item).first.empty()) + && !item.getClass().getEquipmentSlots(item).first.empty()) { MWBase::Environment::get().getWindowManager()->getInventoryWindow()->useItem(item); } store.setSelectedEnchantItem(it); - MWBase::Environment::get().getWindowManager()->setSelectedEnchantItem(item); updateSpells(); } @@ -331,7 +330,7 @@ namespace MWGui { std::string spellId = _sender->getUserString("Spell"); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player); + MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); if (MyGUI::InputManager::getInstance().isShiftPressed()) { @@ -385,7 +384,7 @@ namespace MWGui void SpellWindow::onDeleteSpellAccept() { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); + MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); MWMechanics::Spells& spells = stats.getSpells(); if (MWBase::Environment::get().getWindowManager()->getSelectedSpell() == mSpellToDelete) diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index 3d4c741a3..246ade7bf 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -228,7 +228,7 @@ namespace MWGui NoDrop::onFrame(dt); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - const MWMechanics::NpcStats &PCstats = MWWorld::Class::get(player).getNpcStats(player); + const MWMechanics::NpcStats &PCstats = player.getClass().getNpcStats(player); // level progress MyGUI::Widget* levelWidget; @@ -454,20 +454,30 @@ namespace MWGui if (!mFactions.empty()) { - // Add a line separator if there are items above - if (!mSkillWidgets.empty()) - addSeparator(coord1, coord2); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - const MWMechanics::NpcStats &PCstats = MWWorld::Class::get(player).getNpcStats(player); + const MWMechanics::NpcStats &PCstats = player.getClass().getNpcStats(player); const std::set &expelled = PCstats.getExpelled(); - addGroup(MWBase::Environment::get().getWindowManager()->getGameSettingString("sFaction", "Faction"), coord1, coord2); + bool firstFaction=true; FactionList::const_iterator end = mFactions.end(); for (FactionList::const_iterator it = mFactions.begin(); it != end; ++it) { const ESM::Faction *faction = store.get().find(it->first); + if (faction->mData.mIsHidden == 1) + continue; + + if (firstFaction) + { + // Add a line separator if there are items above + if (!mSkillWidgets.empty()) + addSeparator(coord1, coord2); + + addGroup(MWBase::Environment::get().getWindowManager()->getGameSettingString("sFaction", "Faction"), coord1, coord2); + + firstFaction = false; + } + MyGUI::Widget* w = addItem(faction->mName, coord1, coord2); std::string text; @@ -475,7 +485,7 @@ namespace MWGui text += std::string("#DDC79E") + faction->mName; if (expelled.find(it->first) != expelled.end()) - text += "\n#{sExpelled}"; + text += "\n#BF9959#{sExpelled}"; else { text += std::string("\n#BF9959") + faction->mRanks[it->second]; diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index f941c699b..aeb79a938 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -89,7 +89,7 @@ namespace MWGui if (mFocusObject.isEmpty ()) return; - const MWWorld::Class& objectclass = MWWorld::Class::get (mFocusObject); + const MWWorld::Class& objectclass = mFocusObject.getClass(); MyGUI::IntSize tooltipSize; if ((!objectclass.hasToolTip(mFocusObject))&&(MWBase::Environment::get().getWindowManager()->getMode() == GM_Console)) @@ -97,7 +97,7 @@ namespace MWGui setCoord(0, 0, 300, 300); mDynamicToolTipBox->setVisible(true); ToolTipInfo info; - info.caption=mFocusObject.getCellRef().mRefID; + info.caption=mFocusObject.getCellRef().getRefId(); info.icon=""; tooltipSize = createToolTip(info); } @@ -305,7 +305,7 @@ namespace MWGui MyGUI::IntSize tooltipSize; - const MWWorld::Class& object = MWWorld::Class::get (mFocusObject); + const MWWorld::Class& object = mFocusObject.getClass(); if (!object.hasToolTip(mFocusObject)) { mDynamicToolTipBox->setVisible(false); @@ -547,9 +547,10 @@ namespace MWGui return " (" + boost::lexical_cast(value) + ")"; } - void ToolTips::toggleFullHelp() + bool ToolTips::toggleFullHelp() { mFullHelp = !mFullHelp; + return mFullHelp; } bool ToolTips::getFullHelp() const diff --git a/apps/openmw/mwgui/tooltips.hpp b/apps/openmw/mwgui/tooltips.hpp index be5c63191..4e73cc555 100644 --- a/apps/openmw/mwgui/tooltips.hpp +++ b/apps/openmw/mwgui/tooltips.hpp @@ -45,7 +45,7 @@ namespace MWGui void setEnabled(bool enabled); - void toggleFullHelp(); ///< show extra info in item tooltips (owner, script) + bool toggleFullHelp(); ///< show extra info in item tooltips (owner, script) bool getFullHelp() const; void setDelay(float delay); diff --git a/apps/openmw/mwgui/tradeitemmodel.cpp b/apps/openmw/mwgui/tradeitemmodel.cpp index c9c65a152..fe43eb548 100644 --- a/apps/openmw/mwgui/tradeitemmodel.cpp +++ b/apps/openmw/mwgui/tradeitemmodel.cpp @@ -120,14 +120,11 @@ namespace MWGui if (i == sourceModel->getItemCount()) throw std::runtime_error("The borrowed item disappeared"); - // reset owner before copying + // reset owner while copying, but only for items bought by the player + bool setNewOwner = (mMerchant.isEmpty()); const ItemStack& item = sourceModel->getItem(i); - std::string owner = item.mBase.getCellRef().mOwner; - if (mMerchant.isEmpty()) // only for items bought by player - item.mBase.getCellRef().mOwner = ""; // copy the borrowed items to our model - copyItem(item, it->mCount); - item.mBase.getCellRef().mOwner = owner; + copyItem(item, it->mCount, setNewOwner); // then remove them from the source model sourceModel->removeItem(item, it->mCount); } @@ -141,7 +138,7 @@ namespace MWGui int services = 0; if (!mMerchant.isEmpty()) - services = MWWorld::Class::get(mMerchant).getServices(mMerchant); + services = mMerchant.getClass().getServices(mMerchant); mItems.clear(); // add regular items @@ -151,14 +148,14 @@ namespace MWGui if(!mMerchant.isEmpty()) { MWWorld::Ptr base = item.mBase; - if(Misc::StringUtils::ciEqual(base.getCellRef().mRefID, MWWorld::ContainerStore::sGoldId)) + if(Misc::StringUtils::ciEqual(base.getCellRef().getRefId(), MWWorld::ContainerStore::sGoldId)) continue; - if(!MWWorld::Class::get(base).canSell(base, services)) + if(!base.getClass().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_") + if (item.mBase.getCellRef().getRefId().size() > 6 + && item.mBase.getCellRef().getRefId().substr(0,6) == "bound_") { continue; } @@ -167,7 +164,7 @@ namespace MWGui if(mMerchant.getClass().hasInventoryStore(mMerchant)) { bool isEquipped = false; - MWWorld::InventoryStore& store = MWWorld::Class::get(mMerchant).getInventoryStore(mMerchant); + MWWorld::InventoryStore& store = mMerchant.getClass().getInventoryStore(mMerchant); for (int slot=0; slot itemSources; MWBase::Environment::get().getWorld()->getContainersOwnedBy(actor, itemSources); @@ -102,7 +102,7 @@ namespace MWGui // Careful here. setTitle may cause size updates, causing itemview redraw, so make sure to do it last // or we end up using a possibly invalid model. - setTitle(MWWorld::Class::get(actor).getName(actor)); + setTitle(actor.getClass().getName(actor)); onFilterChanged(mFilterAll); } @@ -133,7 +133,14 @@ namespace MWGui int TradeWindow::getMerchantServices() { - return MWWorld::Class::get(mPtr).getServices(mPtr); + return mPtr.getClass().getServices(mPtr); + } + + void TradeWindow::exit() + { + mTradeModel->abort(); + MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getTradeModel()->abort(); + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Barter); } void TradeWindow::onItemSelected (int index) @@ -150,7 +157,7 @@ namespace MWGui { CountDialog* dialog = MWBase::Environment::get().getWindowManager()->getCountDialog(); std::string message = "#{sQuanityMenuMessage02}"; - dialog->open(MWWorld::Class::get(object).getName(object), message, count); + dialog->open(object.getClass().getName(object), message, count); dialog->eventOkClicked.clear(); dialog->eventOkClicked += MyGUI::newDelegate(this, &TradeWindow::sellItem); mItemToSell = mSortModel->mapToSource(index); @@ -165,7 +172,7 @@ namespace MWGui void TradeWindow::sellItem(MyGUI::Widget* sender, int count) { const ItemStack& item = mTradeModel->getItem(mItemToSell); - std::string sound = MWWorld::Class::get(item.mBase).getDownSoundId(item.mBase); + std::string sound = item.mBase.getClass().getDownSoundId(item.mBase); MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); TradeItemModel* playerTradeModel = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getTradeModel(); @@ -208,7 +215,7 @@ namespace MWGui void TradeWindow::addOrRemoveGold(int amount, const MWWorld::Ptr& actor) { - MWWorld::ContainerStore& store = MWWorld::Class::get(actor).getContainerStore(actor); + MWWorld::ContainerStore& store = actor.getClass().getContainerStore(actor); if (amount > 0) { @@ -245,7 +252,7 @@ namespace MWGui // were there any items traded at all? std::vector playerBought = playerItemModel->getItemsBorrowedToUs(); std::vector merchantBought = mTradeModel->getItemsBorrowedToUs(); - if (!playerBought.size() && !merchantBought.size()) + if (playerBought.empty() && merchantBought.empty()) { // user notification MWBase::Environment::get().getWindowManager()-> @@ -277,7 +284,7 @@ namespace MWGui // check if the player is attempting to sell back an item stolen from this actor for (std::vector::iterator it = merchantBought.begin(); it != merchantBought.end(); ++it) { - if (Misc::StringUtils::ciEqual(it->mBase.getCellRef().mOwner, mPtr.getCellRef().mRefID)) + if (Misc::StringUtils::ciEqual(it->mBase.getCellRef().getOwner(), mPtr.getCellRef().getRefId())) { std::string msg = gmst.find("sNotifyMessage49")->getString(); if (msg.find("%s") != std::string::npos) @@ -364,8 +371,6 @@ namespace MWGui mPtr.getClass().getCreatureStats(mPtr).getGoldPool() - mCurrentBalance ); } - updateTradeTime(); - MWBase::Environment::get().getWindowManager()->getDialogueWindow()->addResponse( MWBase::Environment::get().getWorld()->getStore().get().find("sBarterDialog5")->getString()); @@ -377,9 +382,7 @@ namespace MWGui void TradeWindow::onCancelButtonClicked(MyGUI::Widget* _sender) { - mTradeModel->abort(); - MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getTradeModel()->abort(); - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Barter); + exit(); } void TradeWindow::onMaxSaleButtonClicked(MyGUI::Widget* _sender) @@ -444,7 +447,7 @@ namespace MWGui void TradeWindow::sellToNpc(const MWWorld::Ptr& item, int count, bool boughtItem) { - int diff = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, MWWorld::Class::get(item).getValue(item) * count, boughtItem); + int diff = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, item.getClass().getValue(item) * count, boughtItem); mCurrentBalance += diff; mCurrentMerchantOffer += diff; @@ -454,7 +457,7 @@ namespace MWGui void TradeWindow::buyFromNpc(const MWWorld::Ptr& item, int count, bool soldItem) { - int diff = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, MWWorld::Class::get(item).getValue(item) * count, !soldItem); + int diff = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, item.getClass().getValue(item) * count, !soldItem); mCurrentBalance -= diff; mCurrentMerchantOffer -= diff; @@ -475,30 +478,26 @@ namespace MWGui return merchantGold; } - // Relates to NPC gold reset delay - void TradeWindow::checkTradeTime() + void TradeWindow::restock() { MWMechanics::CreatureStats &sellerStats = mPtr.getClass().getCreatureStats(mPtr); - double delay = boost::lexical_cast(MWBase::Environment::get().getWorld()->getStore().get().find("fBarterGoldResetDelay")->getInt()); + float delay = MWBase::Environment::get().getWorld()->getStore().get().find("fBarterGoldResetDelay")->getFloat(); - // if time stamp longer than gold reset delay, reset gold. - if (MWBase::Environment::get().getWorld()->getTimeStamp() >= sellerStats.getTradeTime() + delay) + if (MWBase::Environment::get().getWorld()->getTimeStamp() >= sellerStats.getLastRestockTime() + delay) { sellerStats.setGoldPool(mPtr.getClass().getBaseGold(mPtr)); - } - } - void TradeWindow::updateTradeTime() - { - MWWorld::ContainerStore store = mPtr.getClass().getContainerStore(mPtr); - MWMechanics::CreatureStats &sellerStats = mPtr.getClass().getCreatureStats(mPtr); - double delay = boost::lexical_cast(MWBase::Environment::get().getWorld()->getStore().get().find("fBarterGoldResetDelay")->getInt()); + mPtr.getClass().restock(mPtr); - // If trade timestamp is within reset delay don't set - if ( ! (MWBase::Environment::get().getWorld()->getTimeStamp() >= sellerStats.getTradeTime() && - MWBase::Environment::get().getWorld()->getTimeStamp() < sellerStats.getTradeTime() + delay) ) - { - sellerStats.setTradeTime(MWBase::Environment::get().getWorld()->getTimeStamp()); + // Also restock any containers owned by this merchant, which are also available to buy in the trade window + std::vector itemSources; + MWBase::Environment::get().getWorld()->getContainersOwnedBy(mPtr, itemSources); + for (std::vector::iterator it = itemSources.begin(); it != itemSources.end(); ++it) + { + it->getClass().restock(*it); + } + + sellerStats.setLastRestockTime(MWBase::Environment::get().getWorld()->getTimeStamp()); } } } diff --git a/apps/openmw/mwgui/tradewindow.hpp b/apps/openmw/mwgui/tradewindow.hpp index 5c154d425..420a642e8 100644 --- a/apps/openmw/mwgui/tradewindow.hpp +++ b/apps/openmw/mwgui/tradewindow.hpp @@ -35,6 +35,8 @@ namespace MWGui int getMerchantServices(); + virtual void exit(); + private: ItemView* mItemView; @@ -101,9 +103,7 @@ namespace MWGui int getMerchantGold(); - // Relates to NPC gold reset delay - void checkTradeTime(); - void updateTradeTime(); + void restock(); }; } diff --git a/apps/openmw/mwgui/trainingwindow.cpp b/apps/openmw/mwgui/trainingwindow.cpp index bee76992a..9a2c3b805 100644 --- a/apps/openmw/mwgui/trainingwindow.cpp +++ b/apps/openmw/mwgui/trainingwindow.cpp @@ -35,6 +35,11 @@ namespace MWGui center(); } + void TrainingWindow::exit() + { + MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Training); + } + void TrainingWindow::startTraining (MWWorld::Ptr actor) { mPtr = actor; @@ -44,7 +49,7 @@ namespace MWGui mPlayerGold->setCaptionWithReplacing("#{sGold}: " + boost::lexical_cast(playerGold)); - MWMechanics::NpcStats& npcStats = MWWorld::Class::get(actor).getNpcStats (actor); + MWMechanics::NpcStats& npcStats = actor.getClass().getNpcStats (actor); // NPC can train you in his best 3 skills std::vector< std::pair > bestSkills; @@ -73,7 +78,7 @@ namespace MWGui MyGUI::EnumeratorWidgetPtr widgets = mTrainingOptions->getEnumerator (); MyGUI::Gui::getInstance ().destroyWidgets (widgets); - MWMechanics::NpcStats& pcStats = MWWorld::Class::get(player).getNpcStats (player); + MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats (player); const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); @@ -107,7 +112,7 @@ namespace MWGui void TrainingWindow::onCancelButtonClicked (MyGUI::Widget *sender) { - MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Training); + exit(); } void TrainingWindow::onTrainingSelected (MyGUI::Widget *sender) @@ -115,7 +120,7 @@ namespace MWGui int skillId = *sender->getUserData(); MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); - MWMechanics::NpcStats& pcStats = MWWorld::Class::get(player).getNpcStats (player); + MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats (player); const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); @@ -123,7 +128,7 @@ namespace MWGui int price = pcStats.getSkill (skillId).getBase() * store.get().find("iTrainingMod")->getInt (); price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr,price,true); - MWMechanics::NpcStats& npcStats = MWWorld::Class::get(mPtr).getNpcStats (mPtr); + MWMechanics::NpcStats& npcStats = mPtr.getClass().getNpcStats (mPtr); if (npcStats.getSkill (skillId).getBase () <= pcStats.getSkill (skillId).getBase ()) { MWBase::Environment::get().getWindowManager()->messageBox ("#{sServiceTrainingWords}"); diff --git a/apps/openmw/mwgui/trainingwindow.hpp b/apps/openmw/mwgui/trainingwindow.hpp index 740115cdf..1fc902b20 100644 --- a/apps/openmw/mwgui/trainingwindow.hpp +++ b/apps/openmw/mwgui/trainingwindow.hpp @@ -14,6 +14,8 @@ namespace MWGui virtual void open(); + virtual void exit(); + void startTraining(MWWorld::Ptr actor); void onFrame(float dt); diff --git a/apps/openmw/mwgui/travelwindow.cpp b/apps/openmw/mwgui/travelwindow.cpp index 89d73215d..79d50cdc5 100644 --- a/apps/openmw/mwgui/travelwindow.cpp +++ b/apps/openmw/mwgui/travelwindow.cpp @@ -45,6 +45,11 @@ namespace MWGui mSelect->getHeight()); } + void TravelWindow::exit() + { + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Travel); + } + void TravelWindow::addDestination(const std::string& travelId,ESM::Position pos,bool interior) { int price = 0; @@ -161,7 +166,7 @@ namespace MWGui MWBase::Environment::get().getWorld()->changeToExteriorCell(pos); } - MWWorld::Class::get(player).adjustPosition(player); + player.getClass().adjustPosition(player); MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Travel); MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Dialogue); MWBase::Environment::get().getWorld ()->getFader ()->fadeOut(0); @@ -170,7 +175,7 @@ namespace MWGui void TravelWindow::onCancelButtonClicked(MyGUI::Widget* _sender) { - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Travel); + exit(); } void TravelWindow::updateLabels() diff --git a/apps/openmw/mwgui/travelwindow.hpp b/apps/openmw/mwgui/travelwindow.hpp index f2a23b048..5387bd690 100644 --- a/apps/openmw/mwgui/travelwindow.hpp +++ b/apps/openmw/mwgui/travelwindow.hpp @@ -22,6 +22,8 @@ namespace MWGui public: TravelWindow(); + virtual void exit(); + void startTravel(const MWWorld::Ptr& actor); protected: diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp index 9417d2a4b..9c7757af9 100644 --- a/apps/openmw/mwgui/waitdialog.cpp +++ b/apps/openmw/mwgui/waitdialog.cpp @@ -8,6 +8,7 @@ #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/statemanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" @@ -15,6 +16,7 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwstate/charactermanager.hpp" namespace MWGui { @@ -67,6 +69,12 @@ namespace MWGui mProgressBar.setVisible (false); } + void WaitDialog::exit() + { + if(!mProgressBar.isVisible()) //Only exit if not currently waiting + MWBase::Environment::get().getWindowManager()->popGuiMode(); + } + void WaitDialog::open() { if (!MWBase::Environment::get().getWindowManager ()->getRestEnabled ()) @@ -116,6 +124,9 @@ namespace MWGui void WaitDialog::startWaiting(int hoursToWait) { + if(Settings::Manager::getBool("autosave","Saves") && mSleeping) //autosaves when enabled and sleeping + MWBase::Environment::get().getStateManager()->quickSave("Autosave"); + MWBase::World* world = MWBase::Environment::get().getWorld(); world->getFader ()->fadeOut(0.2); setVisible(false); @@ -155,7 +166,7 @@ namespace MWGui void WaitDialog::onCancelButtonClicked(MyGUI::Widget* sender) { - MWBase::Environment::get().getWindowManager()->popGuiMode (); + exit(); } void WaitDialog::onHourSliderChangedPosition(MyGUI::ScrollBar* sender, size_t position) @@ -167,11 +178,11 @@ namespace MWGui void WaitDialog::setCanRest (bool canRest) { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); + MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); bool full = (stats.getFatigue().getCurrent() >= stats.getFatigue().getModified()) && (stats.getHealth().getCurrent() >= stats.getHealth().getModified()) && (stats.getMagicka().getCurrent() >= stats.getMagicka().getModified()); - MWMechanics::NpcStats& npcstats = MWWorld::Class::get(player).getNpcStats(player); + MWMechanics::NpcStats& npcstats = player.getClass().getNpcStats(player); bool werewolf = npcstats.isWerewolf(); mUntilHealedButton->setVisible(canRest && !full); @@ -214,8 +225,20 @@ namespace MWGui } if (mCurHour > mHours) + { stopWaiting(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + const MWMechanics::NpcStats &pcstats = player.getClass().getNpcStats(player); + + // trigger levelup if possible + const MWWorld::Store &gmst = + MWBase::Environment::get().getWorld()->getStore().get(); + if (mSleeping && pcstats.getLevelProgress () >= gmst.find("iLevelUpTotal")->getInt()) + { + MWBase::Environment::get().getWindowManager()->pushGuiMode (GM_Levelup); + } + } } void WaitDialog::stopWaiting () @@ -225,19 +248,9 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Rest); MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_RestBed); mWaiting = false; - - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - const MWMechanics::NpcStats &pcstats = MWWorld::Class::get(player).getNpcStats(player); - - // trigger levelup if possible - const MWWorld::Store &gmst = - MWBase::Environment::get().getWorld()->getStore().get(); - if (mSleeping && pcstats.getLevelProgress () >= gmst.find("iLevelUpTotal")->getInt()) - { - MWBase::Environment::get().getWindowManager()->pushGuiMode (GM_Levelup); - } } + void WaitDialog::wakeUp () { mSleeping = false; diff --git a/apps/openmw/mwgui/waitdialog.hpp b/apps/openmw/mwgui/waitdialog.hpp index d96649af6..1cf05bb06 100644 --- a/apps/openmw/mwgui/waitdialog.hpp +++ b/apps/openmw/mwgui/waitdialog.hpp @@ -28,12 +28,15 @@ namespace MWGui virtual void open(); + virtual void exit(); + void onFrame(float dt); void bedActivated() { setCanRest(true); } bool getSleeping() { return mWaiting && mSleeping; } void wakeUp(); + void autosave(); protected: MyGUI::TextBox* mDateTimeText; diff --git a/apps/openmw/mwgui/windowbase.cpp b/apps/openmw/mwgui/windowbase.cpp index 87b26b814..cc18e6694 100644 --- a/apps/openmw/mwgui/windowbase.cpp +++ b/apps/openmw/mwgui/windowbase.cpp @@ -2,6 +2,8 @@ #include "../mwbase/windowmanager.hpp" #include "container.hpp" +#include "../mwbase/environment.hpp" +#include "../mwgui/windowmanagerimp.hpp" using namespace MWGui; @@ -21,6 +23,11 @@ void WindowBase::setVisible(bool visible) close(); } +bool WindowBase::isVisible() +{ + return mMainWidget->getVisible(); +} + void WindowBase::center() { // Centre dialog @@ -45,11 +52,13 @@ WindowModal::WindowModal(const std::string& parLayout) void WindowModal::open() { MyGUI::InputManager::getInstance ().addWidgetModal (mMainWidget); + MWBase::Environment::get().getWindowManager()->addCurrentModal(this); //Set so we can escape it if needed } void WindowModal::close() { MyGUI::InputManager::getInstance ().removeWidgetModal (mMainWidget); + MWBase::Environment::get().getWindowManager()->removeCurrentModal(this); } NoDrop::NoDrop(DragAndDrop *drag, MyGUI::Widget *widget) diff --git a/apps/openmw/mwgui/windowbase.hpp b/apps/openmw/mwgui/windowbase.hpp index 48de9ea87..81073d419 100644 --- a/apps/openmw/mwgui/windowbase.hpp +++ b/apps/openmw/mwgui/windowbase.hpp @@ -21,9 +21,16 @@ namespace MWGui // Events typedef MyGUI::delegates::CMultiDelegate1 EventHandle_WindowBase; + ///Unhides the window virtual void open() {} + ///Hides the window virtual void close () {} + ///Gracefully exits the window + virtual void exit() {} + ///Sets the visibility of the window virtual void setVisible(bool visible); + ///Returns the visibility state of the window + virtual bool isVisible(); void center(); /** Event : Dialog finished, OK button clicked.\n @@ -42,6 +49,7 @@ namespace MWGui WindowModal(const std::string& parLayout); virtual void open(); virtual void close(); + virtual void exit() {} }; /// A window that cannot be the target of a drag&drop action. diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index db19070a6..4de3028ed 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -23,6 +23,8 @@ #include "../mwworld/player.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwsound/soundmanagerimp.hpp" + #include "console.hpp" #include "journalwindow.hpp" #include "journalviewmodel.hpp" @@ -134,6 +136,7 @@ namespace MWGui , mFPS(0.0f) , mTriangleCount(0) , mBatchCount(0) + , mCurrentModals() { // Set up the GUI system mGuiManager = new OEngine::GUI::MyGUIManager(mRendering->getWindow(), mRendering->getScene(), false, logpath); @@ -158,7 +161,6 @@ namespace MWGui MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); - MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); @@ -187,7 +189,7 @@ namespace MWGui MyGUI::InputManager::getInstance().eventChangeKeyFocus += MyGUI::newDelegate(this, &WindowManager::onKeyFocusChanged); onCursorChange(MyGUI::PointerManager::getInstance().getDefaultPointer()); - SDL_ShowCursor(false); + //SDL_ShowCursor(false); mCursorManager->setEnabled(true); @@ -289,12 +291,17 @@ namespace MWGui void WindowManager::setNewGame(bool newgame) { + // This method will always be called after loading a savegame or starting a new game + // Reset enemy, it could be a dangling pointer from a previous game + mHud->resetEnemy(); + if (newgame) { disallowAll(); delete mCharGen; mCharGen = new CharacterCreation(); mGuiModes.clear(); + MWBase::Environment::get().getInputManager()->changeInputMode(false); mHud->unsetSelectedWeapon(); mHud->unsetSelectedSpell(); unsetForceHide(GW_ALL); @@ -621,9 +628,9 @@ namespace MWGui mStatsWindow->setValue (id, value); } - void WindowManager::setDrowningTimeLeft (float time) + void WindowManager::setDrowningTimeLeft (float time, float maxTime) { - mHud->setDrowningTimeLeft(time); + mHud->setDrowningTimeLeft(time, maxTime); } void WindowManager::setPlayerClass (const ESM::Class &class_) @@ -662,6 +669,93 @@ namespace MWGui mGarbageDialogs.push_back(dialog); } + void WindowManager::exitCurrentGuiMode() { + switch(mGuiModes.back()) { + case GM_QuickKeysMenu: + mQuickKeysMenu->exit(); + break; + case GM_MainMenu: + removeGuiMode(GM_MainMenu); //Simple way to remove it + break; + case GM_Settings: + mSettingsWindow->exit(); + break; + case GM_Console: + mConsole->exit(); + break; + case GM_Scroll: + mScrollWindow->exit(); + break; + case GM_Book: + mBookWindow->exit(); + break; + case GM_Alchemy: + mAlchemyWindow->exit(); + break; + case GM_Rest: + mWaitDialog->exit(); + break; + case GM_RestBed: + mWaitDialog->exit(); + break; + case GM_Name: + case GM_Race: + case GM_Class: + case GM_ClassPick: + case GM_ClassCreate: + case GM_Birth: + case GM_ClassGenerate: + case GM_Review: + break; + case GM_Inventory: + removeGuiMode(GM_Inventory); //Simple way to remove it + break; + case GM_Container: + mContainerWindow->exit(); + break; + case GM_Companion: + mCompanionWindow->exit(); + break; + case GM_Dialogue: + mDialogueWindow->exit(); + break; + case GM_Barter: + mTradeWindow->exit(); + break; + case GM_SpellBuying: + mSpellBuyingWindow->exit(); + break; + case GM_Travel: + mTravelWindow->exit(); + break; + case GM_SpellCreation: + mSpellCreationDialog->exit(); + break; + case GM_Recharge: + mRecharge->exit(); + break; + case GM_Enchanting: + mEnchantingDialog->exit(); + break; + case GM_Training: + mTrainingWindow->exit(); + break; + case GM_MerchantRepair: + mMerchantRepair->exit(); + break; + case GM_Repair: + mRepair->exit(); + break; + case GM_Journal: + MWBase::Environment::get().getSoundManager()->playSound ("book close", 1.0, 1.0); + removeGuiMode(GM_Journal); //Simple way to remove it + break; + default: + // Unsupported mode, switch back to game + break; + } + } + void WindowManager::messageBox (const std::string& message, const std::vector& buttons, enum MWGui::ShowInDialogueMode showInDialogueMode) { if (buttons.empty()) { @@ -709,6 +803,8 @@ namespace MWGui mToolTips->onFrame(frameDuration); + mMenu->update(frameDuration); + if (MWBase::Environment::get().getStateManager()->getState()== MWBase::StateManager::State_NoGame) return; @@ -762,8 +858,6 @@ namespace MWGui mMap->setCellPrefix("Cell"); mHud->setCellPrefix("Cell"); - mMap->setActiveCell (cell->getCell()->getGridX(), cell->getCell()->getGridY()); - mHud->setActiveCell (cell->getCell()->getGridX(), cell->getCell()->getGridY()); } else { @@ -779,10 +873,10 @@ namespace MWGui } } - void WindowManager::setInteriorMapTexture(const int x, const int y) + void WindowManager::setActiveMap(int x, int y, bool interior) { - mMap->setActiveCell(x,y, true); - mHud->setActiveCell(x,y, true); + mMap->setActiveCell(x,y, interior); + mHud->setActiveCell(x,y, interior); } void WindowManager::setPlayerPos(const float x, const float y) @@ -812,10 +906,10 @@ namespace MWGui mHud->setMinimapVisible (visible); } - void WindowManager::toggleFogOfWar() + bool WindowManager::toggleFogOfWar() { mMap->toggleFogOfWar(); - mHud->toggleFogOfWar(); + return mHud->toggleFogOfWar(); } void WindowManager::setFocusObject(const MWWorld::Ptr& focus) @@ -828,9 +922,9 @@ namespace MWGui mToolTips->setFocusObjectScreenCoords(min_x, min_y, max_x, max_y); } - void WindowManager::toggleFullHelp() + bool WindowManager::toggleFullHelp() { - mToolTips->toggleFullHelp(); + return mToolTips->toggleFullHelp(); } bool WindowManager::getFullHelp() const @@ -1025,20 +1119,20 @@ namespace MWGui { mSelectedSpell = ""; const ESM::Enchantment* ench = MWBase::Environment::get().getWorld()->getStore().get() - .find(MWWorld::Class::get(item).getEnchantment(item)); + .find(item.getClass().getEnchantment(item)); - int chargePercent = (item.getCellRef().mEnchantmentCharge == -1) ? 100 - : (item.getCellRef().mEnchantmentCharge / static_cast(ench->mData.mCharge) * 100); + int chargePercent = (item.getCellRef().getEnchantmentCharge() == -1) ? 100 + : (item.getCellRef().getEnchantmentCharge() / static_cast(ench->mData.mCharge) * 100); mHud->setSelectedEnchantItem(item, chargePercent); - mSpellWindow->setTitle(MWWorld::Class::get(item).getName(item)); + mSpellWindow->setTitle(item.getClass().getName(item)); } void WindowManager::setSelectedWeapon(const MWWorld::Ptr& item) { - int durabilityPercent = (item.getCellRef().mCharge == -1) ? 100 - : (item.getCellRef().mCharge / static_cast(MWWorld::Class::get(item).getItemMaxHealth(item)) * 100); + int durabilityPercent = + (item.getClass().getItemHealth(item) / static_cast(item.getClass().getItemMaxHealth(item)) * 100); mHud->setSelectedWeapon(item, durabilityPercent); - mInventoryWindow->setTitle(MWWorld::Class::get(item).getName(item)); + mInventoryWindow->setTitle(item.getClass().getName(item)); } void WindowManager::unsetSelectedSpell() @@ -1324,9 +1418,6 @@ namespace MWGui void WindowManager::updatePlayer() { - unsetSelectedSpell(); - unsetSelectedWeapon(); - mInventoryWindow->updatePlayer(); } @@ -1401,16 +1492,64 @@ namespace MWGui void WindowManager::clear() { mMap->clear(); + mQuickKeysMenu->clear(); + + mTrainingWindow->resetReference(); + mDialogueWindow->resetReference(); + mTradeWindow->resetReference(); + mSpellBuyingWindow->resetReference(); + mSpellCreationDialog->resetReference(); + mEnchantingDialog->resetReference(); + mContainerWindow->resetReference(); + mCompanionWindow->resetReference(); + mConsole->resetReference(); + + mGuiModes.clear(); + MWBase::Environment::get().getInputManager()->changeInputMode(false); + updateVisible(); } - void WindowManager::write(ESM::ESMWriter &writer) + void WindowManager::write(ESM::ESMWriter &writer, Loading::Listener& progress) { - mMap->write(writer); + mMap->write(writer, progress); + + mQuickKeysMenu->write(writer); + progress.increaseProgress(); + + if (!mSelectedSpell.empty()) + { + writer.startRecord(ESM::REC_ASPL); + writer.writeHNString("ID__", mSelectedSpell); + writer.endRecord(ESM::REC_ASPL); + progress.increaseProgress(); + } } void WindowManager::readRecord(ESM::ESMReader &reader, int32_t type) { - mMap->readRecord(reader, type); + if (type == ESM::REC_GMAP) + mMap->readRecord(reader, type); + else if (type == ESM::REC_KEYS) + mQuickKeysMenu->readRecord(reader, type); + else if (type == ESM::REC_ASPL) + { + reader.getSubNameIs("ID__"); + mSelectedSpell = reader.getHString(); + } + } + + int WindowManager::countSavedGameRecords() const + { + return 1 // Global map + + 1 // QuickKeysMenu + + (!mSelectedSpell.empty() ? 1 : 0); + } + + bool WindowManager::isSavingAllowed() const + { + return !MyGUI::InputManager::getInstance().isModalAny() + // TODO: remove this, once we have properly serialized the state of open windows + && (!isGuiMode() || (mGuiModes.size() == 1 && (getMode() == GM_MainMenu || getMode() == GM_Rest || getMode() == GM_RestBed))); } void WindowManager::playVideo(const std::string &name, bool allowSkipping) @@ -1466,4 +1605,21 @@ namespace MWGui mVideoWidget->setCoord(leftPadding, topPadding, screenWidth - leftPadding*2, screenHeight - topPadding*2); } + + WindowModal* WindowManager::getCurrentModal() const + { + if(mCurrentModals.size() > 0) + return mCurrentModals.top(); + else + return NULL; + } + + void WindowManager::removeCurrentModal(WindowModal* input) + { + // Only remove the top if it matches the current pointer. A lot of things hide their visibility before showing it, + //so just popping the top would cause massive issues. + if(mCurrentModals.size() > 0) + if(input == mCurrentModals.top()) + mCurrentModals.pop(); + } } diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index ab9770a41..b1dbf3a24 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -82,6 +82,7 @@ namespace MWGui class Recharge; class CompanionWindow; class VideoWidget; + class WindowModal; class WindowManager : public MWBase::WindowManager { @@ -132,10 +133,10 @@ namespace MWGui virtual void forceHide(MWGui::GuiWindow wnd); virtual void unsetForceHide(MWGui::GuiWindow wnd); - // Disallow all inventory mode windows + /// Disallow all inventory mode windows virtual void disallowAll(); - // Allow one or more windows + /// Allow one or more windows virtual void allow(GuiWindow wnd); virtual bool isAllowed(GuiWindow wnd) const; @@ -166,8 +167,9 @@ namespace MWGui virtual void setValue (const std::string& id, int value); /// Set time left for the player to start drowning (update the drowning bar) - /// @param time value from [0,20] - virtual void setDrowningTimeLeft (float time); + /// @param time time left to start drowning + /// @param maxTime how long we can be underwater (in total) until drowning starts + virtual void setDrowningTimeLeft (float time, float maxTime); virtual void setPlayerClass (const ESM::Class &class_); ///< set current class of player virtual void configureSkills (const SkillList& major, const SkillList& minor); ///< configure skill groups, each set contains the skill ID for that group. @@ -187,12 +189,12 @@ namespace MWGui virtual void setDragDrop(bool dragDrop); virtual bool getWorldMouseOver(); - virtual void toggleFogOfWar(); - virtual void toggleFullHelp(); ///< show extra info in item tooltips (owner, script) + virtual bool toggleFogOfWar(); + virtual bool toggleFullHelp(); ///< show extra info in item tooltips (owner, script) virtual bool getFullHelp() const; - virtual void setInteriorMapTexture(const int x, const int y); - ///< set the index of the map texture that should be used (for interiors) + virtual void setActiveMap(int x, int y, bool interior); + ///< set the indices of the map texture that should be used /// sets the visibility of the drowning bar virtual void setDrowningBarVisibility(bool visible); @@ -224,7 +226,11 @@ namespace MWGui virtual void addVisitedLocation(const std::string& name, int x, int y); - virtual void removeDialog(OEngine::GUI::Layout* dialog); ///< Hides dialog and schedules dialog to be deleted. + ///Hides dialog and schedules dialog to be deleted. + virtual void removeDialog(OEngine::GUI::Layout* dialog); + + ///Gracefully attempts to exit the topmost GUI mode + virtual void exitCurrentGuiMode(); virtual void messageBox (const std::string& message, const std::vector& buttons = std::vector(), enum MWGui::ShowInDialogueMode showInDialogueMode = MWGui::ShowInDialogueMode_IfPossible); virtual void staticMessageBox(const std::string& message); @@ -290,8 +296,25 @@ namespace MWGui /// Clear all savegame-specific data virtual void clear(); - virtual void write (ESM::ESMWriter& writer); + virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress); virtual void readRecord (ESM::ESMReader& reader, int32_t type); + virtual int countSavedGameRecords() const; + + /// Does the current stack of GUI-windows permit saving? + virtual bool isSavingAllowed() const; + + /// Returns the current Modal + /** Used to send exit command to active Modal when Esc is pressed **/ + virtual WindowModal* getCurrentModal() const; + + /// Sets the current Modal + /** Used to send exit command to active Modal when Esc is pressed **/ + virtual void addCurrentModal(WindowModal* input) {mCurrentModals.push(input);} + + /// Removes the top Modal + /** Used when one Modal adds another Modal + \param input Pointer to the current modal, to ensure proper modal is removed **/ + virtual void removeCurrentModal(WindowModal* input); private: bool mConsoleOnlyScripts; @@ -302,6 +325,8 @@ namespace MWGui std::string mSelectedSpell; + std::stack mCurrentModals; + OEngine::GUI::MyGUIManager *mGuiManager; OEngine::Render::OgreRenderer *mRendering; HUD *mHud; diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 73399ee78..80cd834d5 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -19,6 +19,7 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/statemanager.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/player.hpp" #include "../mwworld/class.hpp" @@ -27,6 +28,10 @@ #include "../mwmechanics/creaturestats.hpp" +#include "../mwdialogue/dialoguemanagerimp.hpp" + +#include "../mwgui/windowbase.hpp" + using namespace ICS; namespace @@ -114,6 +119,7 @@ namespace MWInput , mOverencumberedMessageDelay(0.f) , mAlwaysRunActive(false) , mAttemptJump(false) + , mControlsDisabled(false) { Ogre::RenderWindow* window = ogre.getWindow (); @@ -185,7 +191,7 @@ namespace MWInput if (action == A_Use) { - MWWorld::Class::get(mPlayer->getPlayer()).getCreatureStats(mPlayer->getPlayer()).setAttackingOrSpell(currentValue); + mPlayer->getPlayer().getClass().getCreatureStats(mPlayer->getPlayer()).setAttackingOrSpell(currentValue); } if (action == A_Jump) @@ -272,25 +278,32 @@ namespace MWInput case A_ToggleHUD: MWBase::Environment::get().getWindowManager()->toggleHud(); break; + case A_QuickSave: + quickSave(); + break; + case A_QuickLoad: + quickLoad(); + break; } } } void InputManager::update(float dt, bool disableControls, bool disableEvents) { + mControlsDisabled = disableControls; + mInputManager->setMouseVisible(MWBase::Environment::get().getWindowManager()->getCursorVisible()); mInputManager->capture(disableEvents); // inject some fake mouse movement to force updating MyGUI's widget states MyGUI::InputManager::getInstance().injectMouseMove( int(mMouseX), int(mMouseY), mMouseWheel); - // update values of channels (as a result of pressed keys) - if (!disableControls) - mInputBinder->update(dt); - - if (disableControls) + if (mControlsDisabled) return; + // update values of channels (as a result of pressed keys) + mInputBinder->update(dt); + bool grab = !MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu) && MWBase::Environment::get().getWindowManager()->getMode() != MWGui::GM_Console; @@ -368,7 +381,7 @@ namespace MWInput { MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); mOverencumberedMessageDelay -= dt; - if (MWWorld::Class::get(player).getEncumbrance(player) >= MWWorld::Class::get(player).getCapacity(player)) + if (player.getClass().getEncumbrance(player) >= player.getClass().getCapacity(player)) { mPlayer->setAutoMove (false); if (mOverencumberedMessageDelay <= 0) @@ -497,13 +510,14 @@ namespace MWInput if (text) { - edit->addText(MyGUI::UString(text)); + edit->insertText(MyGUI::UString(text), edit->getTextCursor()); SDL_free(text); } } if (arg.keysym.sym == SDLK_x && (arg.keysym.mod & SDL_Keymod(KMOD_CTRL))) { - std::string text = edit->getTextSelection(); + // Discard color codes and other escape characters + std::string text = MyGUI::TextIterator::getOnlyText(edit->getTextSelection()); if (text.length()) { SDL_SetClipboardText(text.c_str()); @@ -515,7 +529,8 @@ namespace MWInput { if (arg.keysym.sym == SDLK_c && (arg.keysym.mod & SDL_Keymod(KMOD_CTRL))) { - std::string text = edit->getTextSelection(); + // Discard color codes and other escape characters + std::string text = MyGUI::TextIterator::getOnlyText(edit->getTextSelection()); if (text.length()) SDL_SetClipboardText(text.c_str()); } @@ -529,7 +544,8 @@ namespace MWInput bool guiFocus = MyGUI::InputManager::getInstance().injectKeyPress(MyGUI::KeyCode::Enum(kc), 0); setPlayerControlsEnabled(!guiFocus); } - mInputBinder->keyPressed (arg); + if (!mControlsDisabled) + mInputBinder->keyPressed (arg); } void InputManager::textInput(const SDL_TextInputEvent &arg) @@ -645,21 +661,38 @@ namespace MWInput void InputManager::toggleMainMenu() { - if (MyGUI::InputManager::getInstance ().isModalAny()) + if (MyGUI::InputManager::getInstance().isModalAny()) { + MWBase::Environment::get().getWindowManager()->getCurrentModal()->exit(); return; + } - if (MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu)) - { - MWBase::Environment::get().getWindowManager()->popGuiMode(); - MWBase::Environment::get().getSoundManager()->resumeSounds (MWBase::SoundManager::Play_TypeSfx); + if(MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_Dialogue) { //Give access to the main menu when at a choice in dialogue + if(MWBase::Environment::get().getDialogueManager()->isInChoice()) { + MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); + MWBase::Environment::get().getSoundManager()->pauseSounds (MWBase::SoundManager::Play_TypeSfx); + return; + } } - else + + if(!MWBase::Environment::get().getWindowManager()->isGuiMode()) //No open GUIs, open up the MainMenu { MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); MWBase::Environment::get().getSoundManager()->pauseSounds (MWBase::SoundManager::Play_TypeSfx); } + else //Close current GUI + { + MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); + MWBase::Environment::get().getSoundManager()->resumeSounds (MWBase::SoundManager::Play_TypeSfx); + } + } + + void InputManager::quickLoad() { + MWBase::Environment::get().getStateManager()->quickLoad(); } + void InputManager::quickSave() { + MWBase::Environment::get().getStateManager()->quickSave(); + } void InputManager::toggleSpell() { if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return; @@ -669,7 +702,7 @@ namespace MWInput return; // Not allowed if no spell selected - MWWorld::InventoryStore& inventory = MWWorld::Class::get(mPlayer->getPlayer()).getInventoryStore(mPlayer->getPlayer()); + MWWorld::InventoryStore& inventory = mPlayer->getPlayer().getClass().getInventoryStore(mPlayer->getPlayer()); if (MWBase::Environment::get().getWindowManager()->getSelectedSpell().empty() && inventory.getSelectedEnchantItem() == inventory.end()) return; @@ -701,8 +734,12 @@ namespace MWInput if (!MWBase::Environment::get().getWindowManager()->getRestEnabled () || MWBase::Environment::get().getWindowManager()->isGuiMode ()) return; - /// \todo check if resting is currently allowed (enemies nearby?) - MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_Rest); + if(mPlayer->isInCombat()) {//Check if in combat + MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage2}"); //Nope, + return; + } + MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_Rest); //Open rest GUI + } void InputManager::screenshot() @@ -762,8 +799,7 @@ namespace MWInput } else if(MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_Journal) { - MWBase::Environment::get().getSoundManager()->playSound ("book close", 1.0, 1.0); - MWBase::Environment::get().getWindowManager()->popGuiMode(); + MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); } // .. but don't touch any other mode. } @@ -779,8 +815,12 @@ namespace MWInput if (!MWBase::Environment::get().getWindowManager()->isGuiMode () && MWBase::Environment::get().getWorld()->getGlobalFloat ("chargenstate")==-1) MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_QuickKeysMenu); - else if (MWBase::Environment::get().getWindowManager()->getMode () == MWGui::GM_QuickKeysMenu) - MWBase::Environment::get().getWindowManager()->removeGuiMode (MWGui::GM_QuickKeysMenu); + else if (MWBase::Environment::get().getWindowManager()->getMode () == MWGui::GM_QuickKeysMenu) { + while(MyGUI::InputManager::getInstance().isModalAny()) { //Handle any open Modal windows + MWBase::Environment::get().getWindowManager()->getCurrentModal()->exit(); + } + MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); //And handle the actual main window + } } void InputManager::activate() @@ -833,36 +873,39 @@ namespace MWInput // across different versions of OpenMW (in the case where another input action is added) std::map defaultKeyBindings; - defaultKeyBindings[A_Activate] = SDLK_SPACE; - defaultKeyBindings[A_MoveBackward] = SDLK_s; - defaultKeyBindings[A_MoveForward] = SDLK_w; - defaultKeyBindings[A_MoveLeft] = SDLK_a; - defaultKeyBindings[A_MoveRight] = SDLK_d; - defaultKeyBindings[A_ToggleWeapon] = SDLK_f; - defaultKeyBindings[A_ToggleSpell] = SDLK_r; - defaultKeyBindings[A_QuickKeysMenu] = SDLK_F1; - defaultKeyBindings[A_Console] = SDLK_F2; - defaultKeyBindings[A_Run] = SDLK_LSHIFT; - defaultKeyBindings[A_Sneak] = SDLK_LCTRL; - defaultKeyBindings[A_AutoMove] = SDLK_q; - defaultKeyBindings[A_Jump] = SDLK_e; - defaultKeyBindings[A_Journal] = SDLK_j; - defaultKeyBindings[A_Rest] = SDLK_t; - defaultKeyBindings[A_GameMenu] = SDLK_ESCAPE; - defaultKeyBindings[A_TogglePOV] = SDLK_TAB; - defaultKeyBindings[A_QuickKey1] = SDLK_1; - defaultKeyBindings[A_QuickKey2] = SDLK_2; - defaultKeyBindings[A_QuickKey3] = SDLK_3; - defaultKeyBindings[A_QuickKey4] = SDLK_4; - defaultKeyBindings[A_QuickKey5] = SDLK_5; - defaultKeyBindings[A_QuickKey6] = SDLK_6; - defaultKeyBindings[A_QuickKey7] = SDLK_7; - defaultKeyBindings[A_QuickKey8] = SDLK_8; - defaultKeyBindings[A_QuickKey9] = SDLK_9; - defaultKeyBindings[A_QuickKey10] = SDLK_0; - defaultKeyBindings[A_Screenshot] = SDLK_F12; - defaultKeyBindings[A_ToggleHUD] = SDLK_F11; - defaultKeyBindings[A_AlwaysRun] = SDLK_y; + //Gets the Keyvalue from the Scancode; gives the button in the same place reguardless of keyboard format + defaultKeyBindings[A_Activate] = SDL_GetKeyFromScancode(SDL_SCANCODE_SPACE); + defaultKeyBindings[A_MoveBackward] = SDL_GetKeyFromScancode(SDL_SCANCODE_S); + defaultKeyBindings[A_MoveForward] = SDL_GetKeyFromScancode(SDL_SCANCODE_W); + defaultKeyBindings[A_MoveLeft] = SDL_GetKeyFromScancode(SDL_SCANCODE_A); + defaultKeyBindings[A_MoveRight] = SDL_GetKeyFromScancode(SDL_SCANCODE_D); + defaultKeyBindings[A_ToggleWeapon] = SDL_GetKeyFromScancode(SDL_SCANCODE_F); + defaultKeyBindings[A_ToggleSpell] = SDL_GetKeyFromScancode(SDL_SCANCODE_R); + defaultKeyBindings[A_QuickKeysMenu] = SDL_GetKeyFromScancode(SDL_SCANCODE_F1); + defaultKeyBindings[A_Console] = SDL_GetKeyFromScancode(SDL_SCANCODE_F2); + defaultKeyBindings[A_Run] = SDL_GetKeyFromScancode(SDL_SCANCODE_LSHIFT); + defaultKeyBindings[A_Sneak] = SDL_GetKeyFromScancode(SDL_SCANCODE_LCTRL); + defaultKeyBindings[A_AutoMove] = SDL_GetKeyFromScancode(SDL_SCANCODE_Q); + defaultKeyBindings[A_Jump] = SDL_GetKeyFromScancode(SDL_SCANCODE_E); + defaultKeyBindings[A_Journal] = SDL_GetKeyFromScancode(SDL_SCANCODE_J); + defaultKeyBindings[A_Rest] = SDL_GetKeyFromScancode(SDL_SCANCODE_T); + defaultKeyBindings[A_GameMenu] = SDL_GetKeyFromScancode(SDL_SCANCODE_ESCAPE); + defaultKeyBindings[A_TogglePOV] = SDL_GetKeyFromScancode(SDL_SCANCODE_TAB); + defaultKeyBindings[A_QuickKey1] = SDL_GetKeyFromScancode(SDL_SCANCODE_1); + defaultKeyBindings[A_QuickKey2] = SDL_GetKeyFromScancode(SDL_SCANCODE_2); + defaultKeyBindings[A_QuickKey3] = SDL_GetKeyFromScancode(SDL_SCANCODE_3); + defaultKeyBindings[A_QuickKey4] = SDL_GetKeyFromScancode(SDL_SCANCODE_4); + defaultKeyBindings[A_QuickKey5] = SDL_GetKeyFromScancode(SDL_SCANCODE_5); + defaultKeyBindings[A_QuickKey6] = SDL_GetKeyFromScancode(SDL_SCANCODE_6); + defaultKeyBindings[A_QuickKey7] = SDL_GetKeyFromScancode(SDL_SCANCODE_7); + defaultKeyBindings[A_QuickKey8] = SDL_GetKeyFromScancode(SDL_SCANCODE_8); + defaultKeyBindings[A_QuickKey9] = SDL_GetKeyFromScancode(SDL_SCANCODE_9); + defaultKeyBindings[A_QuickKey10] = SDL_GetKeyFromScancode(SDL_SCANCODE_0); + defaultKeyBindings[A_Screenshot] = SDL_GetKeyFromScancode(SDL_SCANCODE_F12); + defaultKeyBindings[A_ToggleHUD] = SDL_GetKeyFromScancode(SDL_SCANCODE_F11); + defaultKeyBindings[A_AlwaysRun] = SDL_GetKeyFromScancode(SDL_SCANCODE_Y); + defaultKeyBindings[A_QuickSave] = SDL_GetKeyFromScancode(SDL_SCANCODE_F5); + defaultKeyBindings[A_QuickLoad] = SDL_GetKeyFromScancode(SDL_SCANCODE_F9); std::map defaultMouseButtonBindings; defaultMouseButtonBindings[A_Inventory] = SDL_BUTTON_RIGHT; @@ -939,6 +982,8 @@ namespace MWInput descriptions[A_QuickKey9] = "sQuick9Cmd"; descriptions[A_QuickKey10] = "sQuick10Cmd"; descriptions[A_AlwaysRun] = "sAlways_Run"; + descriptions[A_QuickSave] = "sQuickSaveCmd"; + descriptions[A_QuickLoad] = "sQuickLoadCmd"; if (descriptions[action] == "") return ""; // not configurable @@ -982,6 +1027,8 @@ namespace MWInput ret.push_back(A_Journal); ret.push_back(A_Rest); ret.push_back(A_Console); + ret.push_back(A_QuickSave); + ret.push_back(A_QuickLoad); ret.push_back(A_Screenshot); ret.push_back(A_QuickKeysMenu); ret.push_back(A_QuickKey1); diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index dbd13e793..6bf1ad6b0 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -68,7 +68,7 @@ namespace MWInput /// Clear all savegame-specific data virtual void clear(); - virtual void update(float dt, bool disableControls, bool disableEvents=false); + virtual void update(float dt, bool disableControls=false, bool disableEvents=false); void setPlayer (MWWorld::Player* player) { mPlayer = player; } @@ -145,6 +145,8 @@ namespace MWInput bool mInvertY; + bool mControlsDisabled; + float mCameraSensitivity; float mUISensitivity; float mCameraYMultiplier; @@ -186,6 +188,8 @@ namespace MWInput void toggleWalking(); void toggleAutoMove(); void rest(); + void quickLoad(); + void quickSave(); void quickKey (int index); void showQuickKeysMenu(); diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index 0c5bd9afa..e64a736c3 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -53,9 +53,9 @@ namespace MWMechanics { const MWWorld::TimeStamp& start = iter->second.mTimeStamp; - const std::vector& effects = iter->second.mEffects; + const std::vector& effects = iter->second.mEffects; - for (std::vector::const_iterator effectIt = effects.begin(); effectIt != effects.end(); ++effectIt) + for (std::vector::const_iterator effectIt = effects.begin(); effectIt != effects.end(); ++effectIt) { int duration = effectIt->mDuration; MWWorld::TimeStamp end = start; @@ -63,7 +63,7 @@ namespace MWMechanics MWBase::Environment::get().getWorld()->getTimeScaleFactor()/(60*60); if (end>now) - mEffects.add(effectIt->mKey, MWMechanics::EffectParam(effectIt->mMagnitude)); + mEffects.add(MWMechanics::EffectKey(effectIt->mEffectId, effectIt->mArg), MWMechanics::EffectParam(effectIt->mMagnitude)); } } } @@ -91,11 +91,11 @@ namespace MWMechanics double ActiveSpells::timeToExpire (const TIterator& iterator) const { - const std::vector& effects = iterator->second.mEffects; + const std::vector& effects = iterator->second.mEffects; int duration = 0; - for (std::vector::const_iterator iter (effects.begin()); + for (std::vector::const_iterator iter (effects.begin()); iter!=effects.end(); ++iter) { if (iter->mDuration > duration) @@ -132,8 +132,8 @@ namespace MWMechanics return mSpells; } - void ActiveSpells::addSpell(const std::string &id, bool stack, std::vector effects, - const std::string &displayName, const std::string& casterHandle) + void ActiveSpells::addSpell(const std::string &id, bool stack, std::vector effects, + const std::string &displayName, int casterActorId) { bool exists = false; for (TContainer::const_iterator it = begin(); it != end(); ++it) @@ -146,7 +146,7 @@ namespace MWMechanics params.mTimeStamp = MWBase::Environment::get().getWorld()->getTimeStamp(); params.mEffects = effects; params.mDisplayName = displayName; - params.mCasterHandle = casterHandle; + params.mCasterActorId = casterActorId; if (!exists || stack) mSpells.insert (std::make_pair(id, params)); @@ -168,7 +168,7 @@ namespace MWMechanics { float timeScale = MWBase::Environment::get().getWorld()->getTimeScaleFactor(); - for (std::vector::const_iterator effectIt = it->second.mEffects.begin(); + for (std::vector::const_iterator effectIt = it->second.mEffects.begin(); effectIt != it->second.mEffects.end(); ++effectIt) { std::string name = it->second.mDisplayName; @@ -178,7 +178,7 @@ namespace MWMechanics float magnitude = effectIt->mMagnitude; if (magnitude) - visitor.visit(effectIt->mKey, name, it->second.mCasterHandle, magnitude, remainingTime); + visitor.visit(MWMechanics::EffectKey(effectIt->mEffectId, effectIt->mArg), name, it->second.mCasterActorId, magnitude, remainingTime); } } } @@ -200,33 +200,70 @@ namespace MWMechanics { for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it) { - for (std::vector::iterator effectIt = it->second.mEffects.begin(); + for (std::vector::iterator effectIt = it->second.mEffects.begin(); effectIt != it->second.mEffects.end();) { - if (effectIt->mKey.mId == effectId) + if (effectIt->mEffectId == effectId) effectIt = it->second.mEffects.erase(effectIt); else - effectIt++; + ++effectIt; } } mSpellsChanged = true; } - void ActiveSpells::purge(const std::string &actorHandle) + void ActiveSpells::purge(int casterActorId) { for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it) { - for (std::vector::iterator effectIt = it->second.mEffects.begin(); + for (std::vector::iterator effectIt = it->second.mEffects.begin(); effectIt != it->second.mEffects.end();) { - const ESM::MagicEffect* effect = MWBase::Environment::get().getWorld()->getStore().get().find(effectIt->mKey.mId); + const ESM::MagicEffect* effect = MWBase::Environment::get().getWorld()->getStore().get().find(effectIt->mEffectId); if (effect->mData.mFlags & ESM::MagicEffect::CasterLinked - && it->second.mCasterHandle == actorHandle) + && it->second.mCasterActorId == casterActorId) effectIt = it->second.mEffects.erase(effectIt); else - effectIt++; + ++effectIt; } } mSpellsChanged = true; } + + void ActiveSpells::clear() + { + mSpells.clear(); + mSpellsChanged = true; + } + + void ActiveSpells::writeState(ESM::ActiveSpells &state) const + { + for (TContainer::const_iterator it = mSpells.begin(); it != mSpells.end(); ++it) + { + // Stupid copying of almost identical structures. ESM::TimeStamp <-> MWWorld::TimeStamp + ESM::ActiveSpells::ActiveSpellParams params; + params.mEffects = it->second.mEffects; + params.mCasterActorId = it->second.mCasterActorId; + params.mDisplayName = it->second.mDisplayName; + params.mTimeStamp = it->second.mTimeStamp.toEsm(); + + state.mSpells.insert (std::make_pair(it->first, params)); + } + } + + void ActiveSpells::readState(const ESM::ActiveSpells &state) + { + for (ESM::ActiveSpells::TContainer::const_iterator it = state.mSpells.begin(); it != state.mSpells.end(); ++it) + { + // Stupid copying of almost identical structures. ESM::TimeStamp <-> MWWorld::TimeStamp + ActiveSpellParams params; + params.mEffects = it->second.mEffects; + params.mCasterActorId = it->second.mCasterActorId; + params.mDisplayName = it->second.mDisplayName; + params.mTimeStamp = MWWorld::TimeStamp(it->second.mTimeStamp); + + mSpells.insert (std::make_pair(it->first, params)); + mSpellsChanged = true; + } + } } diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index 7a40afb4c..6c4c93128 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -10,6 +10,7 @@ #include "magiceffects.hpp" #include +#include namespace MWMechanics { @@ -21,29 +22,24 @@ namespace MWMechanics { public: - // Parameters of an effect concerning lasting effects. - // Note we are not using ENAMstruct since the magnitude may be modified by magic resistance, etc. - // It could also be a negative magnitude, in case of inversing an effect, e.g. Absorb spell causes damage on target, but heals the caster. - struct Effect - { - float mMagnitude; - EffectKey mKey; - float mDuration; - }; + typedef ESM::ActiveEffect ActiveEffect; struct ActiveSpellParams { - std::vector mEffects; + std::vector mEffects; MWWorld::TimeStamp mTimeStamp; std::string mDisplayName; - // Handle to the caster that that inflicted this spell on us - std::string mCasterHandle; + // The caster that inflicted this spell on us + int mCasterActorId; }; typedef std::multimap TContainer; typedef TContainer::const_iterator TIterator; + void readState (const ESM::ActiveSpells& state); + void writeState (ESM::ActiveSpells& state) const; + private: mutable TContainer mSpells; @@ -76,10 +72,9 @@ 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, const std::string& casterHandle); + void addSpell (const std::string& id, bool stack, std::vector effects, + const std::string& displayName, int casterActorId); /// Removes the active effects from this spell/potion/.. with \a id void removeEffects (const std::string& id); @@ -90,8 +85,11 @@ namespace MWMechanics /// Remove all active effects, if roll succeeds (for each effect) void purgeAll (float chance); - /// Remove all effects with CASTER_LINKED flag that were cast by \a actorHandle - void purge (const std::string& actorHandle); + /// Remove all effects with CASTER_LINKED flag that were cast by \a casterActorId + void purge (int casterActorId); + + /// Remove all spells + void clear(); bool isSpellActive (std::string id) const; ///< case insensitive diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index f7478e22c..8dcccfdb3 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -29,7 +29,7 @@ #include "aicombat.hpp" #include "aifollow.hpp" -#include "aipersue.hpp" +#include "aipursue.hpp" namespace { @@ -38,9 +38,12 @@ void adjustBoundItem (const std::string& item, bool bound, const MWWorld::Ptr& a { if (bound) { - MWWorld::Ptr newPtr = *actor.getClass().getContainerStore(actor).add(item, 1, actor); - MWWorld::ActionEquip action(newPtr); - action.execute(actor); + if (actor.getClass().getContainerStore(actor).count(item) == 0) + { + MWWorld::Ptr newPtr = *actor.getClass().getContainerStore(actor).add(item, 1, actor); + MWWorld::ActionEquip action(newPtr); + action.execute(actor); + } } else { @@ -59,17 +62,17 @@ bool disintegrateSlot (MWWorld::Ptr ptr, int slot, float disintegrate) { if (!item->getClass().hasItemHealth(*item)) return false; - if (item->getCellRef().mCharge == -1) - item->getCellRef().mCharge = item->getClass().getItemMaxHealth(*item); + int charge = item->getClass().getItemHealth(*item); - if (item->getCellRef().mCharge == 0) + if (charge == 0) return false; - item->getCellRef().mCharge -= + charge -= std::min(disintegrate, - static_cast(item->getCellRef().mCharge)); + static_cast(charge)); + item->getCellRef().setCharge(charge); - if (item->getCellRef().mCharge == 0) + if (charge == 0) { // Will unequip the broken item and try to find a replacement if (ptr.getRefData().getHandle() != "player") @@ -116,7 +119,7 @@ namespace MWMechanics : mCreature(trappedCreature) {} virtual void visit (MWMechanics::EffectKey key, - const std::string& sourceName, const std::string& casterHandle, + const std::string& sourceName, int casterActorId, float magnitude, float remainingTime = -1) { if (key.mId != ESM::MagicEffect::Soultrap) @@ -126,7 +129,7 @@ namespace MWMechanics MWBase::World* world = MWBase::Environment::get().getWorld(); - MWWorld::Ptr caster = world->searchPtrViaHandle(casterHandle); + MWWorld::Ptr caster = world->searchPtrViaActorId(casterActorId); if (caster.isEmpty() || !caster.getClass().isActor()) return; @@ -144,13 +147,13 @@ namespace MWMechanics for (MWWorld::ContainerStoreIterator it = container.begin(MWWorld::ContainerStore::Type_Miscellaneous); it != container.end(); ++it) { - const std::string& id = it->getCellRef().mRefID; + const std::string& id = it->getCellRef().getRefId(); 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()) + && it->getCellRef().getSoul().empty()) { gem = it; gemCapacity = thisGemCapacity; @@ -163,7 +166,7 @@ namespace MWMechanics // Set the soul on just one of the gems, not the whole stack gem->getContainerStore()->unstack(*gem, caster); - gem->getCellRef().mSoul = mCreature.getCellRef().mRefID; + gem->getCellRef().setSoul(mCreature.getCellRef().getRefId()); if (caster.getRefData().getHandle() == "player") MWBase::Environment::get().getWindowManager()->messageBox("#{sSoultrapSuccess}"); @@ -176,78 +179,86 @@ namespace MWMechanics adjustMagicEffects (ptr); if (ptr.getClass().getCreatureStats(ptr).needToRecalcDynamicStats()) calculateDynamicStats (ptr); - + calculateCreatureStatModifiers (ptr, duration); + // fatigue restoration + calculateRestoration(ptr, duration, false); + } + + void Actors::engageCombat (const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, bool againstPlayer) + { + CreatureStats& creatureStats = actor1.getClass().getCreatureStats(actor1); + + if (againstPlayer && creatureStats.isHostile()) return; // already fighting against player + + // pure water creatures won't try to fight with the target on the ground + // except that creature is already hostile + if ((againstPlayer || !creatureStats.isHostile()) + && ((actor1.getClass().canSwim(actor1) && !actor1.getClass().canWalk(actor1) // pure water creature + && !MWBase::Environment::get().getWorld()->isSwimming(actor2)) + || (!actor1.getClass().canSwim(actor1) && MWBase::Environment::get().getWorld()->isSwimming(actor2)))) // creature can't swim to target + return; + + float fight; - // AI - if(MWBase::Environment::get().getMechanicsManager()->isAIActive()) + if (againstPlayer) + fight = actor1.getClass().getCreatureStats(actor1).getAiSetting(CreatureStats::AI_Fight).getModified(); + else { - CreatureStats& creatureStats = MWWorld::Class::get(ptr).getCreatureStats(ptr); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + fight = 0; + // if one of actors is creature then we should make a decision to start combat or not + // NOTE: function doesn't take into account combat between 2 creatures + if (!actor1.getClass().isNpc()) + { + // if creature is hostile then it is necessarily to start combat + if (creatureStats.isHostile()) fight = 100; + else fight = creatureStats.getAiSetting(CreatureStats::AI_Fight).getModified(); + } + } + + ESM::Position actor1Pos = actor1.getRefData().getPosition(); + ESM::Position actor2Pos = actor2.getRefData().getPosition(); + float d = Ogre::Vector3(actor1Pos.pos).distance(Ogre::Vector3(actor2Pos.pos)); - //engage combat or not? - if(ptr != player && !creatureStats.isHostile()) + if( (fight == 100 && d <= 5000) + || (fight >= 95 && d <= 3000) + || (fight >= 90 && d <= 2000) + || (fight >= 80 && d <= 1000)) + { + if (againstPlayer || actor2.getClass().getCreatureStats(actor2).getAiSequence().canAddTarget(actor2Pos, d)) { - ESM::Position playerpos = player.getRefData().getPosition(); - ESM::Position actorpos = ptr.getRefData().getPosition(); - 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(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()) - { - disp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(ptr); - } + bool LOS = MWBase::Environment::get().getWorld()->getLOS(actor1, actor2); - if( (fight == 100 ) - || (fight >= 95 && d <= 3000) - || (fight >= 90 && d <= 2000) - || (fight >= 80 && d <= 1000) - || (fight >= 80 && disp <= 40) - || (fight >= 70 && disp <= 35 && d <= 1000) - || (fight >= 60 && disp <= 30 && d <= 1000) - || (fight >= 50 && disp == 0) - || (fight >= 40 && disp <= 10 && d <= 500) - ) - { - bool LOS = MWBase::Environment::get().getWorld()->getLOS(ptr,player) - && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, ptr); + if (againstPlayer) LOS &= MWBase::Environment::get().getMechanicsManager()->awarenessCheck(actor2, actor1); - if (LOS) + if (LOS) + { + MWBase::Environment::get().getMechanicsManager()->startCombat(actor1, actor2); + if (!againstPlayer) // start combat between each other { - creatureStats.getAiSequence().stack(AiCombat(MWBase::Environment::get().getWorld()->getPlayerPtr())); - creatureStats.setHostile(true); + MWBase::Environment::get().getMechanicsManager()->startCombat(actor2, actor1); } } } - updateCrimePersuit(ptr, duration); - creatureStats.getAiSequence().execute (ptr,duration); } - - // fatigue restoration - calculateRestoration(ptr, duration, false); } - void Actors::updateNpc (const MWWorld::Ptr& ptr, float duration, bool paused) + void Actors::updateNpc (const MWWorld::Ptr& ptr, float duration) { - if(!paused) - { - updateDrowning(ptr, duration); - calculateNpcStatModifiers(ptr); - updateEquippedLight(ptr, duration); - } + updateDrowning(ptr, duration); + calculateNpcStatModifiers(ptr); + updateEquippedLight(ptr, duration); } void Actors::adjustMagicEffects (const MWWorld::Ptr& creature) { - CreatureStats& creatureStats = MWWorld::Class::get (creature).getCreatureStats (creature); + CreatureStats& creatureStats = creature.getClass().getCreatureStats (creature); MagicEffects now = creatureStats.getSpells().getMagicEffects(); if (creature.getTypeName()==typeid (ESM::NPC).name()) { - MWWorld::InventoryStore& store = MWWorld::Class::get (creature).getInventoryStore (creature); + MWWorld::InventoryStore& store = creature.getClass().getInventoryStore (creature); now += store.getMagicEffects(); } @@ -262,7 +273,7 @@ namespace MWMechanics void Actors::calculateDynamicStats (const MWWorld::Ptr& ptr) { - CreatureStats& creatureStats = MWWorld::Class::get (ptr).getCreatureStats (ptr); + CreatureStats& creatureStats = ptr.getClass().getCreatureStats (ptr); int strength = creatureStats.getAttribute(ESM::Attribute::Strength).getBase(); int intelligence = creatureStats.getAttribute(ESM::Attribute::Intelligence).getBase(); @@ -330,7 +341,7 @@ namespace MWMechanics void Actors::calculateCreatureStatModifiers (const MWWorld::Ptr& ptr, float duration) { - CreatureStats &creatureStats = MWWorld::Class::get(ptr).getCreatureStats(ptr); + CreatureStats &creatureStats = ptr.getClass().getCreatureStats(ptr); const MagicEffects &effects = creatureStats.getMagicEffects(); // attributes @@ -543,35 +554,61 @@ namespace MWMechanics { MWWorld::CellStore* store = ptr.getCell(); MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), creatureID, 1); - ref.getPtr().getCellRef().mPos = ipos; + ref.getPtr().getCellRef().setPosition(ipos); + + MWMechanics::CreatureStats& summonedCreatureStats = ref.getPtr().getClass().getCreatureStats(ref.getPtr()); - // TODO: Add AI to follow player and fight for him + // Make the summoned creature follow its master and help in fights AiFollow package(ptr.getRefData().getHandle()); - MWWorld::Class::get (ref.getPtr()).getCreatureStats (ref.getPtr()).getAiSequence().stack(package); + summonedCreatureStats.getAiSequence().stack(package, ref.getPtr()); + int creatureActorId = summonedCreatureStats.getActorId(); + + MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,ipos); + // TODO: VFX_SummonStart, VFX_SummonEnd - creatureStats.mSummonedCreatures.insert(std::make_pair(it->first, - MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,ipos).getRefData().getHandle())); + creatureStats.mSummonedCreatures.insert(std::make_pair(it->first, creatureActorId)); } } else { - std::string handle = creatureStats.mSummonedCreatures[it->first]; - // TODO: Show death animation before deleting? We shouldn't allow looting the corpse while the animation - // plays though, which is a rather lame exploit in vanilla. - MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaHandle(handle); + // Summon lifetime has expired. Try to delete the creature. + int actorId = creatureStats.mSummonedCreatures[it->first]; + creatureStats.mSummonedCreatures.erase(it->first); + + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(actorId); if (!ptr.isEmpty()) { + // TODO: Show death animation before deleting? We shouldn't allow looting the corpse while the animation + // plays though, which is a rather lame exploit in vanilla. MWBase::Environment::get().getWorld()->deleteObject(ptr); creatureStats.mSummonedCreatures.erase(it->first); } + else + { + // We didn't find the creature. It's probably in an inactive cell. + // Add to graveyard so we can delete it when the cell becomes active. + creatureStats.mSummonGraveyard.push_back(actorId); + } } } } + + for (std::vector::iterator it = creatureStats.mSummonGraveyard.begin(); it != creatureStats.mSummonGraveyard.end(); ) + { + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(*it); + if (!ptr.isEmpty()) + { + it = creatureStats.mSummonGraveyard.erase(it); + MWBase::Environment::get().getWorld()->deleteObject(ptr); + } + else + ++it; + } } void Actors::calculateNpcStatModifiers (const MWWorld::Ptr& ptr) { - NpcStats &npcStats = MWWorld::Class::get(ptr).getNpcStats(ptr); + NpcStats &npcStats = ptr.getClass().getNpcStats(ptr); const MagicEffects &effects = npcStats.getMagicEffects(); // skills @@ -627,7 +664,7 @@ namespace MWMechanics { bool isPlayer = ptr.getRefData().getHandle()=="player"; - MWWorld::InventoryStore &inventoryStore = MWWorld::Class::get(ptr).getInventoryStore(ptr); + MWWorld::InventoryStore &inventoryStore = ptr.getClass().getInventoryStore(ptr); MWWorld::ContainerStoreIterator heldIter = inventoryStore.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); /** @@ -649,7 +686,7 @@ namespace MWMechanics { if (torch != inventoryStore.end()) { - if (!MWWorld::Class::get (ptr).getCreatureStats (ptr).isHostile()) + if (!ptr.getClass().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()) @@ -726,63 +763,57 @@ namespace MWMechanics if (ptr != player && ptr.getClass().isNpc()) { // get stats of witness - CreatureStats& creatureStats = MWWorld::Class::get(ptr).getCreatureStats(ptr); - NpcStats& npcStats = MWWorld::Class::get(ptr).getNpcStats(ptr); + CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); + NpcStats& npcStats = ptr.getClass().getNpcStats(ptr); - // If I'm a guard and I'm not hostile - if (ptr.getClass().isClass(ptr, "Guard") && !creatureStats.isHostile()) + if (ptr.getClass().isClass(ptr, "Guard") && creatureStats.getAiSequence().getTypeId() != AiPackage::TypeIdPursue && !creatureStats.isHostile()) { /// \todo Move me! I shouldn't be here... const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); - float cutoff = float(esmStore.get().find("iCrimeThreshold")->getInt()) * - float(esmStore.get().find("iCrimeThresholdMultiplier")->getInt()) * - esmStore.get().find("fCrimeGoldDiscountMult")->getFloat(); - // Attack on sight if bounty is greater than the cutoff + float cutoff = float(esmStore.get().find("iCrimeThreshold")->getInt()); + // Force dialogue on sight if bounty is greater than the cutoff + // In vanilla morrowind, the greeting dialogue is scripted to either arrest the player (< 5000 bounty) or attack (>= 5000 bounty) if ( player.getClass().getNpcStats(player).getBounty() >= cutoff + // TODO: do not run these two every frame. keep an Aware state for each actor and update it every 0.2 s or so? && MWBase::Environment::get().getWorld()->getLOS(ptr, player) && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, ptr)) { - creatureStats.getAiSequence().stack(AiCombat(player)); - creatureStats.setHostile(true); - npcStats.setCrimeId( MWBase::Environment::get().getWorld()->getPlayer().getCrimeId() ); + creatureStats.getAiSequence().stack(AiPursue(player), ptr); + creatureStats.setAlarmed(true); + npcStats.setCrimeId(MWBase::Environment::get().getWorld()->getPlayer().getNewCrimeId()); } } // if I was a witness to a crime if (npcStats.getCrimeId() != -1) { - // if you've payed for your crimes and I havent noticed + // if you've paid for your crimes and I havent noticed if( npcStats.getCrimeId() <= MWBase::Environment::get().getWorld()->getPlayer().getCrimeId() ) { // Calm witness down if (ptr.getClass().isClass(ptr, "Guard")) - creatureStats.getAiSequence().stopPersue(); + creatureStats.getAiSequence().stopPursuit(); creatureStats.getAiSequence().stopCombat(); // Reset factors to attack // TODO: Not a complete list, disposition changes? creatureStats.setHostile(false); creatureStats.setAttacked(false); - + creatureStats.setAlarmed(false); + // Update witness crime id npcStats.setCrimeId(-1); } - else if (!creatureStats.isHostile()) + else if (!creatureStats.isHostile() && creatureStats.getAiSequence().getTypeId() != AiPackage::TypeIdPursue) { if (ptr.getClass().isClass(ptr, "Guard")) - creatureStats.getAiSequence().stack(AiPersue(player.getClass().getId(player))); + creatureStats.getAiSequence().stack(AiPursue(player), ptr); else - creatureStats.getAiSequence().stack(AiCombat(player)); - creatureStats.setHostile(true); + { + MWBase::Environment::get().getMechanicsManager()->startCombat(ptr, player); + } } } - - // if I didn't report a crime was I attacked? - else if (creatureStats.getAttacked() && !creatureStats.isHostile()) - { - creatureStats.getAiSequence().stack(AiCombat(player)); - creatureStats.setHostile(true); - } } } @@ -798,15 +829,17 @@ namespace MWMechanics } } - void Actors::addActor (const MWWorld::Ptr& ptr) + void Actors::addActor (const MWWorld::Ptr& ptr, bool updateImmediately) { // erase previous death events since we are currently only tracking them while in an active cell - MWWorld::Class::get(ptr).getCreatureStats(ptr).clearHasDied(); + ptr.getClass().getCreatureStats(ptr).clearHasDied(); removeActor(ptr); MWRender::Animation *anim = MWBase::Environment::get().getWorld()->getAnimation(ptr); mActors.insert(std::make_pair(ptr, new CharacterController(ptr, anim))); + if (updateImmediately) + mActors[ptr]->update(0); } void Actors::removeActor (const MWWorld::Ptr& ptr) @@ -847,10 +880,24 @@ namespace MWMechanics } } + static Ogre::Vector3 sBasePoint; + bool comparePtrDist (const MWWorld::Ptr& ptr1, const MWWorld::Ptr& ptr2) + { + return (sBasePoint.squaredDistance(Ogre::Vector3(ptr1.getRefData().getPosition().pos)) + < sBasePoint.squaredDistance(Ogre::Vector3(ptr2.getRefData().getPosition().pos))); + } + void Actors::update (float duration, bool paused) { if(!paused) { + std::list listGuards; // at the moment only guards certainly will fight with creatures + + static float timerUpdateAITargets = 0; + + // target lists get updated once every 1.0 sec + if (timerUpdateAITargets >= 1.0f) timerUpdateAITargets = 0; + // Reset data from previous frame for (PtrControllerMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { @@ -858,29 +905,63 @@ namespace MWMechanics // Note, the new hit object for this frame may be set by CharacterController::update -> Animation::runAnimation // (below) iter->first.getClass().getCreatureStats(iter->first).setLastHitObject(std::string()); + + // add guards to list to later make them fight with creatures + if (timerUpdateAITargets == 0 && iter->first.getClass().isClass(iter->first, "Guard")) + listGuards.push_back(iter->first); } - // AI and magic effects update - for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter) + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + + listGuards.push_back(player); + + // AI and magic effects update + for(PtrControllerMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { if (!iter->first.getClass().getCreatureStats(iter->first).isDead()) { updateActor(iter->first, duration); + + if (MWBase::Environment::get().getMechanicsManager()->isAIActive()) + { + // make guards and creatures fight each other + if (timerUpdateAITargets == 0 && iter->first.getTypeName() == typeid(ESM::Creature).name() && !listGuards.empty()) + { + sBasePoint = Ogre::Vector3(iter->first.getRefData().getPosition().pos); + listGuards.sort(comparePtrDist); // try to engage combat starting from the nearest creature + + for (std::list::iterator it = listGuards.begin(); it != listGuards.end(); ++it) + { + engageCombat(iter->first, *it, false); + } + } + + if (iter->first != player) engageCombat(iter->first, player, true); + + if (iter->first.getClass().isNpc() && iter->first != player) + updateCrimePersuit(iter->first, duration); + + if (iter->first != player) + iter->first.getClass().getCreatureStats(iter->first).getAiSequence().execute(iter->first, duration); + } + if(iter->first.getTypeName() == typeid(ESM::NPC).name()) - updateNpc(iter->first, duration, paused); + updateNpc(iter->first, duration); } } + timerUpdateAITargets += duration; + // Looping magic VFX update // Note: we need to do this before any of the animations are updated. // Reaching the text keys may trigger Hit / Spellcast (and as such, particles), // so updating VFX immediately after that would just remove the particle effects instantly. // There needs to be a magic effect update in between. - for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter) + for(PtrControllerMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) iter->second->updateContinuousVfx(); // Animation/movement update - for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter) + for(PtrControllerMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { if (iter->first.getClass().getCreatureStats(iter->first).getMagicEffects().get( ESM::MagicEffect::Paralyze).mMagnitude > 0) @@ -888,12 +969,23 @@ namespace MWMechanics iter->second->update(duration); } - // Kill dead actors - for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();iter++) + // Kill dead actors, update some variables + for(PtrControllerMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { - const MWWorld::Class &cls = MWWorld::Class::get(iter->first); + const MWWorld::Class &cls = iter->first.getClass(); CreatureStats &stats = cls.getCreatureStats(iter->first); + //KnockedOutOneFrameLogic + //Used for "OnKnockedOut" command + //Put here to ensure that it's run for PRECISELY one frame. + if (stats.getKnockedDown() && !stats.getKnockedDownOneFrame() && !stats.getKnockedDownOverOneFrame()) { //Start it for one frame if nessesary + stats.setKnockedDownOneFrame(true); + } + else if (stats.getKnockedDownOneFrame() && !stats.getKnockedDownOverOneFrame()) { //Turn off KnockedOutOneframe + stats.setKnockedDownOneFrame(false); + stats.setKnockedDownOverOneFrame(true); + } + if(!stats.isDead()) { if(iter->second->isDead()) @@ -904,7 +996,7 @@ namespace MWMechanics } // If it's the player and God Mode is turned on, keep it alive - if(iter->first.getRefData().getHandle()=="player" && + if (iter->first.getRefData().getHandle()=="player" && MWBase::Environment::get().getWorld()->getGodModeState()) { MWMechanics::DynamicStat stat (stats.getHealth()); @@ -918,21 +1010,17 @@ namespace MWMechanics continue; } - // Make sure spell effects with CasterLinked flag are removed - // TODO: would be nice not to do this all the time... - for(PtrControllerMap::iterator iter2(mActors.begin());iter2 != mActors.end();++iter2) - { - MWMechanics::ActiveSpells& spells = iter2->first.getClass().getCreatureStats(iter2->first).getActiveSpells(); - spells.purge(iter->first.getRefData().getHandle()); - } - - // FIXME: see http://bugs.openmw.org/issues/869 - MWBase::Environment::get().getWorld()->enableActorCollision(iter->first, false); - if (iter->second->kill()) { ++mDeathCount[cls.getId(iter->first)]; + // Make sure spell effects with CasterLinked flag are removed + for (PtrControllerMap::iterator iter2(mActors.begin());iter2 != mActors.end();++iter2) + { + MWMechanics::ActiveSpells& spells = iter2->first.getClass().getCreatureStats(iter2->first).getActiveSpells(); + spells.purge(stats.getActorId()); + } + // Apply soultrap if (iter->first.getTypeName() == typeid(ESM::Creature).name()) { @@ -943,12 +1031,44 @@ namespace MWMechanics // 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()); + stats.getActiveSpells().clear(); calculateCreatureStatModifiers(iter->first, 0); - if(cls.isEssential(iter->first)) + MWBase::Environment::get().getWorld()->enableActorCollision(iter->first, false); + + if (cls.isEssential(iter->first)) MWBase::Environment::get().getWindowManager()->messageBox("#{sKilledEssential}"); } } + + // if player is in sneak state see if anyone detects him + if (player.getClass().getCreatureStats(player).getMovementFlag(MWMechanics::CreatureStats::Flag_Sneak)) + { + const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); + const int radius = esmStore.get().find("fSneakUseDist")->getInt(); + bool detected = false; + + for (PtrControllerMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) + { + if (iter->first == player) // not the player + continue; + + // is the player in range and can they be detected + if ( (Ogre::Vector3(iter->first.getRefData().getPosition().pos).squaredDistance(Ogre::Vector3(player.getRefData().getPosition().pos)) <= radius*radius) + && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, iter->first) + && MWBase::Environment::get().getWorld()->getLOS(player, iter->first)) + { + detected = true; + MWBase::Environment::get().getWindowManager()->setSneakVisibility(false); + break; + } + } + + if (!detected) + MWBase::Environment::get().getWindowManager()->setSneakVisibility(true); + } + else + MWBase::Environment::get().getWindowManager()->setSneakVisibility(false); } } void Actors::restoreDynamicStats(bool sleep) @@ -1025,16 +1145,36 @@ namespace MWMechanics std::list list; for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();iter++) { - const MWWorld::Class &cls = MWWorld::Class::get(iter->first); + const MWWorld::Class &cls = iter->first.getClass(); CreatureStats &stats = cls.getCreatureStats(iter->first); - - if(stats.getAiSequence().getTypeId() == AiPackage::TypeIdFollow) + if(!stats.isDead() && stats.getAiSequence().getTypeId() == AiPackage::TypeIdFollow) { MWMechanics::AiFollow* package = static_cast(stats.getAiSequence().getActivePackage()); - if(package->getFollowedActor() == actor.getCellRef().mRefID) + if(package->getFollowedActor() == actor.getCellRef().getRefId()) list.push_front(iter->first); } } return list; } + + std::list Actors::getActorsFighting(const MWWorld::Ptr& actor) { + std::list list; + std::vector neighbors; + Ogre::Vector3 position = Ogre::Vector3(actor.getRefData().getPosition().pos); + getObjectsInRange(position, + MWBase::Environment::get().getWorld()->getStore().get().find("fAlarmRadius")->getFloat(), + neighbors); //only care about those within the alarm disance + for(std::vector::iterator iter(neighbors.begin());iter != neighbors.end();iter++) + { + const MWWorld::Class &cls = iter->getClass(); + CreatureStats &stats = cls.getCreatureStats(*iter); + if(!stats.isDead() && stats.getAiSequence().getTypeId() == AiPackage::TypeIdCombat) + { + MWMechanics::AiCombat* package = static_cast(stats.getAiSequence().getActivePackage()); + if(package->getTarget() == actor) + list.push_front(*iter); + } + } + return list; + } } diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index d61d74258..cc95660dc 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -27,7 +27,7 @@ namespace MWMechanics { std::map mDeathCount; - void updateNpc(const MWWorld::Ptr &ptr, float duration, bool paused); + void updateNpc(const MWWorld::Ptr &ptr, float duration); void adjustMagicEffects (const MWWorld::Ptr& creature); @@ -58,7 +58,7 @@ namespace MWMechanics /// paused we may want to do it manually (after equipping permanent enchantment) void updateMagicEffects (const MWWorld::Ptr& ptr) { adjustMagicEffects(ptr); } - void addActor (const MWWorld::Ptr& ptr); + void addActor (const MWWorld::Ptr& ptr, bool updateImmediately=false); ///< Register an actor for stats management /// /// \note Dead actors are ignored. @@ -81,6 +81,12 @@ namespace MWMechanics ///< This function is normally called automatically during the update process, but it can /// also be called explicitly at any time to force an update. + /** Start combat between two actors + @Notes: If againstPlayer = true then actor2 should be the Player. + If one of the combatants is creature it should be actor1. + */ + void engageCombat(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, bool againstPlayer); + void restoreDynamicStats(bool sleep); ///< If the player is sleeping, this should be called every hour. @@ -98,8 +104,13 @@ namespace MWMechanics void getObjectsInRange(const Ogre::Vector3& position, float radius, std::vector& out); + ///Returns the list of actors which are following the given actor + /**ie AiFollow is active and the target is the actor **/ std::list getActorsFollowing(const MWWorld::Ptr& actor); - /// getActorsFighting(const MWWorld::Ptr& actor); private: PtrControllerMap mActors; diff --git a/apps/openmw/mwmechanics/aiactivate.cpp b/apps/openmw/mwmechanics/aiactivate.cpp index eeedc0d7a..3dfacb853 100644 --- a/apps/openmw/mwmechanics/aiactivate.cpp +++ b/apps/openmw/mwmechanics/aiactivate.cpp @@ -19,83 +19,26 @@ MWMechanics::AiActivate *MWMechanics::AiActivate::clone() const return new AiActivate(*this); } bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor,float duration) -{ - MWBase::World *world = MWBase::Environment::get().getWorld(); - ESM::Position pos = actor.getRefData().getPosition(); - Movement &movement = actor.getClass().getMovementSettings(actor); - const ESM::Cell *cell = actor.getCell()->getCell(); - - MWWorld::Ptr player = world->getPlayerPtr(); - if(cell->mData.mX != player.getCell()->getCell()->mData.mX) - { - int sideX = PathFinder::sgn(cell->mData.mX - player.getCell()->getCell()->mData.mX); - //check if actor is near the border of an inactive cell. If so, stop walking. - if(sideX * (pos.pos[0] - cell->mData.mX*ESM::Land::REAL_SIZE) > - sideX * (ESM::Land::REAL_SIZE/2.0f - 200.0f)) - { - movement.mPosition[1] = 0; - return false; - } - } - if(cell->mData.mY != player.getCell()->getCell()->mData.mY) - { - int sideY = PathFinder::sgn(cell->mData.mY - player.getCell()->getCell()->mData.mY); - //check if actor is near the border of an inactive cell. If so, stop walking. - if(sideY * (pos.pos[1] - cell->mData.mY*ESM::Land::REAL_SIZE) > - sideY * (ESM::Land::REAL_SIZE/2.0f - 200.0f)) - { - movement.mPosition[1] = 0; - return false; - } - } - - MWWorld::Ptr target = world->searchPtr(mObjectId,false); - if(target == MWWorld::Ptr()) return true; - - ESM::Position targetPos = target.getRefData().getPosition(); - - bool cellChange = cell->mData.mX != mCellX || cell->mData.mY != mCellY; - if(!mPathFinder.isPathConstructed() || cellChange) - { - mCellX = cell->mData.mX; - mCellY = cell->mData.mY; - - ESM::Pathgrid::Point dest; - dest.mX = targetPos.pos[0]; - dest.mY = targetPos.pos[1]; - dest.mZ = targetPos.pos[2]; - - ESM::Pathgrid::Point start; - start.mX = pos.pos[0]; - start.mY = pos.pos[1]; - start.mZ = pos.pos[2]; - - mPathFinder.buildPath(start, dest, actor.getCell(), true); - } - - if((pos.pos[0]-targetPos.pos[0])*(pos.pos[0]-targetPos.pos[0])+ - (pos.pos[1]-targetPos.pos[1])*(pos.pos[1]-targetPos.pos[1])+ - (pos.pos[2]-targetPos.pos[2])*(pos.pos[2]-targetPos.pos[2]) < 200*200) - { - movement.mPosition[1] = 0; - MWWorld::Ptr target = world->getPtr(mObjectId,false); - MWWorld::Class::get(target).activate(target,actor).get()->execute(actor); - return true; - } - - if(mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2])) - { - movement.mPosition[1] = 0; - MWWorld::Ptr target = world->getPtr(mObjectId,false); - MWWorld::Class::get(target).activate(target,actor).get()->execute(actor); - return true; - } - - float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); - zTurn(actor, Ogre::Degree(zAngle)); - MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; - movement.mPosition[1] = 1; - +{ + ESM::Position pos = actor.getRefData().getPosition(); //position of the actor + const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mObjectId, false); //The target to follow + + if(target == MWWorld::Ptr()) + return true; //Target doesn't exist + + //Set the target desition from the actor + ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; + + if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < 200) { //Stop when you get close + actor.getClass().getMovementSettings(actor).mPosition[1] = 0; + MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(mObjectId,false); + target.getClass().activate(target,actor).get()->execute(actor); //Arrest player + return true; + } + else { + pathTo(actor, dest, duration); //Go to the destination + } + return false; } diff --git a/apps/openmw/mwmechanics/aiactivate.hpp b/apps/openmw/mwmechanics/aiactivate.hpp index fd54869f6..0e660e967 100644 --- a/apps/openmw/mwmechanics/aiactivate.hpp +++ b/apps/openmw/mwmechanics/aiactivate.hpp @@ -7,21 +7,21 @@ #include "pathfinding.hpp" namespace MWMechanics -{ - +{ + /// \brief Causes actor to walk to activatable object and activate it + /** Will activate when close to object **/ class AiActivate : public AiPackage { - public: + public: + /// Constructor + /** \param objectId Reference to object to activate **/ AiActivate(const std::string &objectId); virtual AiActivate *clone() const; virtual bool execute (const MWWorld::Ptr& actor,float duration); - ///< \return Package completed? virtual int getTypeId() const; private: std::string mObjectId; - - PathFinder mPathFinder; int mCellX; int mCellY; }; diff --git a/apps/openmw/mwmechanics/aiavoiddoor.cpp b/apps/openmw/mwmechanics/aiavoiddoor.cpp new file mode 100644 index 000000000..ea6f296cc --- /dev/null +++ b/apps/openmw/mwmechanics/aiavoiddoor.cpp @@ -0,0 +1,85 @@ +#include "aiavoiddoor.hpp" +#include +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/cellstore.hpp" +#include "creaturestats.hpp" +#include "movement.hpp" +#include "mechanicsmanagerimp.hpp" + +#include + +#include "steering.hpp" + +MWMechanics::AiAvoidDoor::AiAvoidDoor(const MWWorld::Ptr& doorPtr) +: AiPackage(), mDoorPtr(doorPtr), mDuration(1), mAdjAngle(0) +{ + +} + +bool MWMechanics::AiAvoidDoor::execute (const MWWorld::Ptr& actor,float duration) +{ + + ESM::Position pos = actor.getRefData().getPosition(); + if(mDuration == 1) //If it just started, get the actor position as the stuck detection thing + mLastPos = pos; + + mDuration -= duration; //Update timer + + if(mDuration < 0) { + float x = pos.pos[0] - mLastPos.pos[0]; + float y = pos.pos[1] - mLastPos.pos[1]; + float z = pos.pos[2] - mLastPos.pos[2]; + int distance = x * x + y * y + z * z; + if(distance < 10 * 10) { //Got stuck, didn't move + if(mAdjAngle == 0) //Try going in various directions + mAdjAngle = 1.57079632679f; //pi/2 + else if (mAdjAngle == 1.57079632679f) + mAdjAngle = -1.57079632679; + else + mAdjAngle = 0; + mDuration = 1; //reset timer + } + else //Not stuck + return true; // We have tried backing up for more than one second, we've probably cleared it + } + + if (!mDoorPtr.getClass().getDoorState(mDoorPtr)) + return true; //Door is no longer opening + + ESM::Position tPos = mDoorPtr.getRefData().getPosition(); //Position of the door + float x = pos.pos[0] - tPos.pos[0]; + float y = pos.pos[1] - tPos.pos[1]; + float dirToDoor = std::atan2(x,y) + pos.rot[2] + mAdjAngle; //Calculates the direction to the door, relative to the direction of the NPC + // For example, if the NPC is directly facing the door this will be pi/2 + + // Make actor move away from the door + actor.getClass().getMovementSettings(actor).mPosition[1] = -1 * std::sin(dirToDoor); //I knew I'd use trig someday + actor.getClass().getMovementSettings(actor).mPosition[0] = -1 * std::cos(dirToDoor); + + //Make all nearby actors also avoid the door + std::vector actors; + MWBase::Environment::get().getMechanicsManager()->getActorsInRange(Ogre::Vector3(pos.pos[0],pos.pos[1],pos.pos[2]),100,actors); + for(std::vector::iterator it = actors.begin(); it != actors.end(); it++) { + if(*it != MWBase::Environment::get().getWorld()->getPlayerPtr()) { //Not the player + MWMechanics::AiSequence& seq = it->getClass().getCreatureStats(*it).getAiSequence(); + if(seq.getTypeId() != MWMechanics::AiPackage::TypeIdAvoidDoor) { //Only add it once + seq.stack(MWMechanics::AiAvoidDoor(mDoorPtr),*it); + } + } + } + + return false; +} + +MWMechanics::AiAvoidDoor *MWMechanics::AiAvoidDoor::clone() const +{ + return new AiAvoidDoor(*this); +} + + int MWMechanics::AiAvoidDoor::getTypeId() const +{ + return TypeIdAvoidDoor; +} + diff --git a/apps/openmw/mwmechanics/aiavoiddoor.hpp b/apps/openmw/mwmechanics/aiavoiddoor.hpp new file mode 100644 index 000000000..d2a2e33a1 --- /dev/null +++ b/apps/openmw/mwmechanics/aiavoiddoor.hpp @@ -0,0 +1,35 @@ +#ifndef GAME_MWMECHANICS_AIAVOIDDOOR_H +#define GAME_MWMECHANICS_AIAVOIDDOOR_H + +#include "aipackage.hpp" +#include +#include "pathfinding.hpp" +#include +#include "../mwworld/class.hpp" + +namespace MWMechanics +{ + /// \brief AiPackage to have an actor avoid an opening door + /** The AI will retreat from the door until it has finished opening, walked far away from it, or one second has passed, in an attempt to avoid it + **/ + class AiAvoidDoor : public AiPackage + { + public: + /// Avoid door until the door is fully open + AiAvoidDoor(const MWWorld::Ptr& doorPtr); + + virtual AiAvoidDoor *clone() const; + + virtual bool execute (const MWWorld::Ptr& actor,float duration); + + virtual int getTypeId() const; + + private: + float mDuration; + MWWorld::Ptr mDoorPtr; + ESM::Position mLastPos; + float mAdjAngle; + }; +} +#endif + diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index fd2fa61ba..9ec770c9d 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -31,35 +31,145 @@ namespace //chooses an attack depending on probability to avoid uniformity void chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement); + + float getZAngleToDir(const Ogre::Vector3& dir, float dirLen = 0.0f) + { + float len = (dirLen > 0.0f)? dirLen : dir.length(); + return Ogre::Radian( Ogre::Math::ACos(dir.y / len) * sgn(Ogre::Math::ASin(dir.x / len)) ).valueDegrees(); + } + + float getXAngleToDir(const Ogre::Vector3& dir, float dirLen = 0.0f) + { + float len = (dirLen > 0.0f)? dirLen : dir.length(); + return Ogre::Radian(-Ogre::Math::ASin(dir.z / len)).valueDegrees(); + } + + + const float PATHFIND_Z_REACH = 50.0f; + // distance at which actor pays more attention to decide whether to shortcut or stick to pathgrid + const float PATHFIND_CAUTION_DIST = 500.0f; + // distance after which actor (failed previously to shortcut) will try again + const float PATHFIND_SHORTCUT_RETRY_DIST = 300.0f; + + // cast up-down ray with some offset from actor position to check for pits/obstacles on the way to target; + // magnitude of pits/obstacles is defined by PATHFIND_Z_REACH + bool checkWayIsClear(const Ogre::Vector3& from, const Ogre::Vector3& to, float offsetXY) + { + if((to - from).length() >= PATHFIND_CAUTION_DIST || abs(from.z - to.z) <= PATHFIND_Z_REACH) + { + Ogre::Vector3 dir = to - from; + dir.z = 0; + dir.normalise(); + float verticalOffset = 200; // instead of '200' here we want the height of the actor + Ogre::Vector3 _from = from + dir*offsetXY + Ogre::Vector3::UNIT_Z * verticalOffset; + + // cast up-down ray and find height in world space of hit + float h = _from.z - MWBase::Environment::get().getWorld()->getDistToNearestRayHit(_from, -Ogre::Vector3::UNIT_Z, verticalOffset + PATHFIND_Z_REACH + 1); + + if(abs(from.z - h) <= PATHFIND_Z_REACH) + return true; + } + + return false; + } } namespace MWMechanics { + static const float MAX_ATTACK_DURATION = 0.35f; + static const float DOOR_CHECK_INTERVAL = 1.5f; // same as AiWander + // NOTE: MIN_DIST_TO_DOOR_SQUARED is defined in obstacle.hpp + AiCombat::AiCombat(const MWWorld::Ptr& actor) : - mTarget(actor), + mTargetActorId(actor.getClass().getCreatureStats(actor).getActorId()), mTimerAttack(0), mTimerReact(0), mTimerCombatMove(0), mFollowTarget(false), mReadyToAttack(false), - mStrike(false), + mAttack(false), mCombatMove(false), - mRotate(false), mMovement(), - mTargetAngle(0) + mForceNoShortcut(false), + mShortcutFailPos(), + mBackOffDoor(false), + mCell(NULL), + mDoorIter(actor.getCell()->get().mList.end()), + mDoors(actor.getCell()->get()), + mDoorCheckDuration(0) { } + /* + * Current AiCombat movement states (as of 0.29.0), ignoring the details of the + * attack states such as CombatMove, Strike and ReadyToAttack: + * + * +----(within strike range)----->attack--(beyond strike range)-->follow + * | | ^ | | + * | | | | | + * pursue<---(beyond follow range)-----+ +----(within strike range)---+ | + * ^ | + * | | + * +-------------------------(beyond follow range)--------------------+ + * + * + * Below diagram is high level only, the code detail is a little different + * (but including those detail will just complicate the diagram w/o adding much) + * + * +----------(same)-------------->attack---------(same)---------->follow + * | |^^ ||| + * | ||| ||| + * | +--(same)-----------------+|+----------(same)------------+|| + * | | | || + * | | | (in range) || + * | <---+ (too far) | || + * pursue<-------------------------[door open]<-----+ || + * ^^^ | || + * ||| | || + * ||+----------evade-----+ | || + * || | [closed door] | || + * |+----> maybe stuck, check --------------> back up, check door || + * | ^ | ^ | ^ || + * | | | | | | || + * | | +---+ +---+ || + * | +-------------------------------------------------------+| + * | | + * +---------------------------(same)---------------------------------+ + * + * FIXME: + * + * The new scheme is way too complicated, should really be implemented as a + * proper state machine. + * + * TODO: + * + * Use the Observer Pattern to co-ordinate attacks, provide intelligence on + * whether the target was hit, etc. + */ bool AiCombat::execute (const MWWorld::Ptr& actor,float duration) { //General description - if(!actor.getClass().getCreatureStats(actor).isHostile() - || actor.getClass().getCreatureStats(actor).getHealth().getCurrent() <= 0) + if(actor.getClass().getCreatureStats(actor).isDead()) return true; - if(mTarget.getClass().getCreatureStats(mTarget).isDead()) + MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); + + if(target.getClass().getCreatureStats(target).isDead()) return true; + const MWWorld::Class& actorClass = actor.getClass(); + MWBase::World& world = *MWBase::Environment::get().getWorld(); + + if ((!actorClass.isNpc() && target == world.getPlayerPtr() && + actorClass.canSwim(actor) && !actorClass.canWalk(actor) // pure water creature + && !world.isSwimming(target)) // Player moved out of water + || (!actorClass.canSwim(actor) && world.isSwimming(target))) // creature can't swim to Player + { + actorClass.getCreatureStats(actor).setHostile(false); + actorClass.getCreatureStats(actor).setAttackingOrSpell(false); + return true; + } + //Update every frame if(mCombatMove) { @@ -73,16 +183,21 @@ namespace MWMechanics } actor.getClass().getMovementSettings(actor) = mMovement; + actor.getClass().getMovementSettings(actor).mRotation[0] = 0; + actor.getClass().getMovementSettings(actor).mRotation[2] = 0; - if (mRotate) + if(mMovement.mRotation[2] != 0) { - if (zTurn(actor, Ogre::Degree(mTargetAngle))) - mRotate = false; + if(zTurn(actor, Ogre::Degree(mMovement.mRotation[2]))) mMovement.mRotation[2] = 0; } + if(mMovement.mRotation[0] != 0) + { + if(smoothTurn(actor, Ogre::Degree(mMovement.mRotation[0]), 0)) mMovement.mRotation[0] = 0; + } mTimerAttack -= duration; - actor.getClass().getCreatureStats(actor).setAttackingOrSpell(mStrike); + actor.getClass().getCreatureStats(actor).setAttackingOrSpell(mAttack); float tReaction = 0.25f; if(mTimerReact < tReaction) @@ -95,18 +210,24 @@ namespace MWMechanics mTimerReact = 0; + bool cellChange = mCell && (actor.getCell() != mCell); + if(!mCell || cellChange) + { + mCell = actor.getCell(); + } + //actual attacking logic //TODO: Some skills affect period of strikes.For berserk-like style period ~ 0.25f - float attackPeriod = 1.0f; + float attacksPeriod = 1.0f; if(mReadyToAttack) { - if(mTimerAttack <= -attackPeriod) + if(mTimerAttack <= -attacksPeriod) { //TODO: should depend on time between 'start' to 'min attack' //for better controlling of NPCs' attack strength. //Also it seems that this time is different for slash/thrust/chop - mTimerAttack = 0.35f * static_cast(rand())/RAND_MAX; - mStrike = true; + mTimerAttack = MAX_ATTACK_DURATION * static_cast(rand())/RAND_MAX; + mAttack = true; //say a provoking combat phrase if (actor.getClass().isNpc()) @@ -121,30 +242,34 @@ namespace MWMechanics } } else if (mTimerAttack <= 0) - mStrike = false; + mAttack = false; } else { - mTimerAttack = -attackPeriod; - mStrike = false; + mTimerAttack = -attacksPeriod; + mAttack = false; } - const MWWorld::Class &cls = actor.getClass(); + const MWWorld::Class &actorCls = actor.getClass(); const ESM::Weapon *weapon = NULL; MWMechanics::WeaponType weaptype; float weapRange, weapSpeed = 1.0f; - actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); + actorCls.getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); - if (actor.getClass().hasInventoryStore(actor)) + // Get weapon characteristics + if (actorCls.hasInventoryStore(actor)) { - MWMechanics::DrawState_ state = actor.getClass().getCreatureStats(actor).getDrawState(); + MWMechanics::DrawState_ state = actorCls.getCreatureStats(actor).getDrawState(); if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing) - actor.getClass().getCreatureStats(actor).setDrawState(MWMechanics::DrawState_Weapon); + actorCls.getCreatureStats(actor).setDrawState(MWMechanics::DrawState_Weapon); + + // TODO: Check equipped weapon and equip a different one if we can't attack with it + // (e.g. no ammunition, or wrong type of ammunition equipped, etc. autoEquip is not very smart in this regard)) //Get weapon speed and range MWWorld::ContainerStoreIterator weaponSlot = - MWMechanics::getActiveWeapon(cls.getCreatureStats(actor), cls.getInventoryStore(actor), &weaptype); + MWMechanics::getActiveWeapon(actorCls.getCreatureStats(actor), actorCls.getInventoryStore(actor), &weaptype); if (weaptype == WeapType_HandToHand) { @@ -152,8 +277,9 @@ namespace MWMechanics MWBase::Environment::get().getWorld()->getStore().get(); weapRange = gmst.find("fHandToHandReach")->getFloat(); } - else + else if (weaptype != WeapType_PickProbe && weaptype != WeapType_Spell) { + // All other WeapTypes are actually weapons, so get is safe. weapon = weaponSlot->get()->mBase; weapRange = weapon->mData.mReach; weapSpeed = weapon->mData.mSpeed; @@ -166,58 +292,101 @@ namespace MWMechanics weapRange = 150; //TODO: use true attack range (the same problem in Creature::hit) } - ESM::Position pos = actor.getRefData().getPosition(); - float rangeMelee; - float rangeCloseUp; + /* + * Some notes on meanings of variables: + * + * rangeAttack: + * + * - Distance where attack using the actor's weapon is possible: + * longer for ranged weapons (obviously?) vs. melee weapons + * - Determined by weapon's reach parameter; hardcoded value + * for ranged weapon and for creatures + * - Once within this distance mFollowTarget is triggered + * + * rangeFollow: + * + * - Applies to melee weapons or hand to hand only (or creatures without + * weapons) + * - Distance a little further away than the actor's weapon reach + * i.e. rangeFollow > rangeAttack for melee weapons + * - Hardcoded value (0 for ranged weapons) + * - Once the target gets beyond this distance mFollowTarget is cleared + * and a path to the target needs to be found + * + * mFollowTarget: + * + * - Once triggered, the actor follows the target with LOS shortcut + * (the shortcut really only applies to cells where pathgrids are + * available, since the default path without pathgrids is direct to + * target even if LOS is not achieved) + */ + + float rangeAttack; + float rangeFollow; bool distantCombat = false; - if (weaptype==WeapType_BowAndArrow || weaptype==WeapType_Crossbow || weaptype==WeapType_Thrown) + if (weaptype == WeapType_BowAndArrow || weaptype == WeapType_Crossbow || weaptype == WeapType_Thrown) { - rangeMelee = 1000; // TODO: should depend on archer skill - rangeCloseUp = 0; //doesn't needed when attacking from distance + rangeAttack = 1000; // TODO: should depend on archer skill + rangeFollow = 0; // not needed in ranged combat distantCombat = true; } else { - rangeMelee = weapRange; - rangeCloseUp = 300; + rangeAttack = weapRange; + rangeFollow = 300; } - Ogre::Vector3 vStart(pos.pos[0], pos.pos[1], pos.pos[2]); - ESM::Position targetPos = mTarget.getRefData().getPosition(); - Ogre::Vector3 vDest(targetPos.pos[0], targetPos.pos[1], targetPos.pos[2]); - Ogre::Vector3 vDir = vDest - vStart; - float distBetween = vDir.length(); + ESM::Position pos = actor.getRefData().getPosition(); + Ogre::Vector3 vActorPos(pos.pos); + Ogre::Vector3 vTargetPos(target.getRefData().getPosition().pos); + Ogre::Vector3 vDirToTarget = vTargetPos - vActorPos; + float distToTarget = vDirToTarget.length(); + + bool isStuck = false; + float speed = 0.0f; + if(mMovement.mPosition[1] && (Ogre::Vector3(mLastPos.pos) - vActorPos).length() < (speed = actorCls.getSpeed(actor)) * tReaction / 2) + isStuck = true; + + mLastPos = pos; + + // check if actor can move along z-axis + bool canMoveByZ = (actorCls.canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor)) + || MWBase::Environment::get().getWorld()->isFlying(actor); - if(distBetween < rangeMelee || (distBetween <= rangeCloseUp && mFollowTarget) ) + // determine vertical angle to target + // if actor can move along z-axis it will control movement dir + // if can't - it will control correct aiming + mMovement.mRotation[0] = getXAngleToDir(vDirToTarget, distToTarget); + + // (within strike dist) || (not quite strike dist while following) + if(distToTarget < rangeAttack || (distToTarget <= rangeFollow && mFollowTarget && !isStuck) ) { //Melee and Close-up combat - vDir.z = 0; - float dirLen = vDir.length(); - mTargetAngle = Ogre::Radian( Ogre::Math::ACos(vDir.y / dirLen) * sgn(Ogre::Math::ASin(vDir.x / dirLen)) ).valueDegrees(); - mRotate = true; - //bool LOS = MWBase::Environment::get().getWorld()->getLOS(actor, mTarget); - if (mFollowTarget && distBetween > rangeMelee) + // if we preserve dir.z then horizontal angle can be inaccurate + mMovement.mRotation[2] = getZAngleToDir(Ogre::Vector3(vDirToTarget.x, vDirToTarget.y, 0)); + + // (not quite strike dist while following) + if (mFollowTarget && distToTarget > rangeAttack) { //Close-up combat: just run up on target mMovement.mPosition[1] = 1; } - else + else // (within strike dist) { - //Melee: stop running and attack mMovement.mPosition[1] = 0; - // When attacking with a weapon, choose between slash, thrust or chop - if (actor.getClass().hasInventoryStore(actor)) - chooseBestAttack(weapon, mMovement); + // set slash/thrust/chop attack + if (mAttack && !distantCombat) chooseBestAttack(weapon, mMovement); if(mMovement.mPosition[0] || mMovement.mPosition[1]) { mTimerCombatMove = 0.1f + 0.1f * static_cast(rand())/RAND_MAX; mCombatMove = true; } - else if(actor.getClass().isNpc() && (!distantCombat || (distantCombat && rangeMelee/5))) + // only NPCs are smart enough to use dodge movements + else if(actorCls.isNpc() && (!distantCombat || (distantCombat && distToTarget < rangeAttack/2))) { //apply sideway movement (kind of dodging) with some probability if(static_cast(rand())/RAND_MAX < 0.25) @@ -228,7 +397,7 @@ namespace MWMechanics } } - if(distantCombat && distBetween < rangeMelee/4) + if(distantCombat && distToTarget < rangeAttack/4) { mMovement.mPosition[1] = -1; } @@ -238,93 +407,198 @@ namespace MWMechanics mFollowTarget = true; } } - else + else // remote pathfinding { - //target is at far distance: build path to target OR follow target (if previously actor had reached it once) - mFollowTarget = false; + bool preferShortcut = false; + bool inLOS = MWBase::Environment::get().getWorld()->getLOS(actor, target); - buildNewPath(actor); //may fail to build a path, check before use + // check if shortcut is available + if(inLOS && (!isStuck || mReadyToAttack) + && (!mForceNoShortcut || (Ogre::Vector3(mShortcutFailPos.pos) - vActorPos).length() >= PATHFIND_SHORTCUT_RETRY_DIST)) + { + if(speed == 0.0f) speed = actorCls.getSpeed(actor); + // maximum dist before pit/obstacle for actor to avoid them depending on his speed + float maxAvoidDist = tReaction * speed + speed / MAX_VEL_ANGULAR.valueRadians() * 2; // *2 - for reliability + preferShortcut = checkWayIsClear(vActorPos, vTargetPos, Ogre::Vector3(vDirToTarget.x, vDirToTarget.y, 0).length() > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2); + } - //delete visited path node - mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]); + // don't use pathgrid when actor can move in 3 dimensions + if(canMoveByZ) preferShortcut = true; - //if no new path leave mTargetAngle unchanged - if(!mPathFinder.getPath().empty()) + if(preferShortcut) + { + mMovement.mRotation[2] = getZAngleToDir(vDirToTarget, distToTarget); + mForceNoShortcut = false; + mShortcutFailPos.pos[0] = mShortcutFailPos.pos[1] = mShortcutFailPos.pos[2] = 0; + mPathFinder.clearPath(); + } + else // if shortcut failed stick to path grid { - //try shortcut - if(vDir.length() < mPathFinder.getDistToNext(pos.pos[0],pos.pos[1],pos.pos[2]) && MWBase::Environment::get().getWorld()->getLOS(actor, mTarget)) - mTargetAngle = Ogre::Radian( Ogre::Math::ACos(vDir.y / vDir.length()) * sgn(Ogre::Math::ASin(vDir.x / vDir.length())) ).valueDegrees(); - else - mTargetAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); - mRotate = true; + if(!isStuck && mShortcutFailPos.pos[0] == 0.0f && mShortcutFailPos.pos[1] == 0.0f && mShortcutFailPos.pos[2] == 0.0f) + { + mForceNoShortcut = true; + mShortcutFailPos = pos; + } + + mFollowTarget = false; + + buildNewPath(actor, target); //may fail to build a path, check before use + + //delete visited path node + mPathFinder.checkWaypoint(pos.pos[0],pos.pos[1],pos.pos[2]); + + // This works on the borders between the path grid and areas with no waypoints. + if(inLOS && mPathFinder.getPath().size() > 1) + { + // get point just before target + std::list::const_iterator pntIter = --mPathFinder.getPath().end(); + --pntIter; + Ogre::Vector3 vBeforeTarget = Ogre::Vector3(pntIter->mX, pntIter->mY, pntIter->mZ); + + // if current actor pos is closer to target then last point of path (excluding target itself) then go straight on target + if(distToTarget <= (vTargetPos - vBeforeTarget).length()) + { + mMovement.mRotation[2] = getZAngleToDir(vDirToTarget, distToTarget); + preferShortcut = true; + } + } + + // if there is no new path, then go straight on target + if(!preferShortcut) + { + if(!mPathFinder.getPath().empty()) + mMovement.mRotation[2] = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); + else + mMovement.mRotation[2] = getZAngleToDir(vDirToTarget, distToTarget); + } } mMovement.mPosition[1] = 1; mReadyToAttack = false; } - if(distBetween > rangeMelee) + if(!isStuck && distToTarget > rangeAttack && !distantCombat) { //special run attack; it shouldn't affect melee combat tactics - if(actor.getClass().getMovementSettings(actor).mPosition[1] == 1) + if(actorCls.getMovementSettings(actor).mPosition[1] == 1) { //check if actor can overcome the distance = distToTarget - attackerWeapRange //less than in time of playing weapon anim from 'start' to 'hit' tags (t_swing) //then start attacking - float speed1 = cls.getSpeed(actor); - float speed2 = mTarget.getClass().getSpeed(mTarget); - if(mTarget.getClass().getMovementSettings(mTarget).mPosition[0] == 0 - && mTarget.getClass().getMovementSettings(mTarget).mPosition[1] == 0) + float speed1 = actorCls.getSpeed(actor); + float speed2 = target.getClass().getSpeed(target); + if(target.getClass().getMovementSettings(target).mPosition[0] == 0 + && target.getClass().getMovementSettings(target).mPosition[1] == 0) speed2 = 0; - float s1 = distBetween - weapRange; + float s1 = distToTarget - weapRange; float t = s1/speed1; float s2 = speed2 * t; - float t_swing = 0.17f/weapSpeed;//instead of 0.17 should be the time of playing weapon anim from 'start' to 'hit' tags + float t_swing = (MAX_ATTACK_DURATION/2) / weapSpeed;//instead of 0.17 should be the time of playing weapon anim from 'start' to 'hit' tags if (t + s2/speed1 <= t_swing) { mReadyToAttack = true; - if(mTimerAttack <= -attackPeriod) + if(mTimerAttack <= -attacksPeriod) { - mTimerAttack = 0.3f*static_cast(rand())/RAND_MAX; - mStrike = true; + mTimerAttack = MAX_ATTACK_DURATION * static_cast(rand())/RAND_MAX; + mAttack = true; } } } } - actor.getClass().getMovementSettings(actor) = mMovement; + // NOTE: This section gets updated every tReaction, which is currently hard + // coded at 250ms or 1/4 second + // + // TODO: Add a parameter to vary DURATION_SAME_SPOT? + MWWorld::CellStore *cell = actor.getCell(); + if((distToTarget > rangeAttack || mFollowTarget) && + mObstacleCheck.check(actor, tReaction)) // check if evasive action needed + { + // first check if we're walking into a door + mDoorCheckDuration += 1.0f; // add time taken for obstacle check + if(mDoorCheckDuration >= DOOR_CHECK_INTERVAL && !cell->getCell()->isExterior()) + { + mDoorCheckDuration = 0; + // Check all the doors in this cell + mDoors = cell->get(); // update + mDoorIter = mDoors.mList.begin(); + for (; mDoorIter != mDoors.mList.end(); ++mDoorIter) + { + MWWorld::LiveCellRef& ref = *mDoorIter; + float minSqr = 1.3*1.3*MIN_DIST_TO_DOOR_SQUARED; // for legibility + if(vActorPos.squaredDistance(Ogre::Vector3(ref.mRef.getPosition().pos)) < minSqr && + ref.mData.getLocalRotation().rot[2] < 0.4f) // even small opening + { + //std::cout<<"closed door id \""<getCell()->isExterior() && !mDoors.mList.empty()) + { + MWWorld::LiveCellRef& ref = *mDoorIter; + float minSqr = 1.6 * 1.6 * MIN_DIST_TO_DOOR_SQUARED; // for legibility + // TODO: add reaction to checking open doors + if(mBackOffDoor && + vActorPos.squaredDistance(Ogre::Vector3(ref.mRef.getPosition().pos)) < minSqr) + { + mMovement.mPosition[1] = -0.2; // back off, but slowly + } + else if(mBackOffDoor && + mDoorIter != mDoors.mList.end() && + ref.mData.getLocalRotation().rot[2] >= 1) + { + mDoorIter = mDoors.mList.end(); + mBackOffDoor = false; + //std::cout<<"open door id \""<getCell()->isExterior(); - if (isOutside) - targetPosThreshold = 300; - else - targetPosThreshold = 100; + float targetPosThreshold = (actor.getCell()->getCell()->isExterior())? 300 : 100; - if((dist < 0) || (dist > targetPosThreshold)) + //construct new path only if target has moved away more than on [targetPosThreshold] + if(dist > targetPosThreshold) { - //construct new path only if target has moved away more than on ESM::Position pos = actor.getRefData().getPosition(); ESM::Pathgrid::Point start; @@ -332,17 +606,18 @@ namespace MWMechanics start.mY = pos.pos[1]; start.mZ = pos.pos[2]; + ESM::Pathgrid::Point dest; + dest.mX = newPathTarget.x; + dest.mY = newPathTarget.y; + dest.mZ = newPathTarget.z; + if(!mPathFinder.isPathConstructed()) - mPathFinder.buildPath(start, dest, actor.getCell(), isOutside); + mPathFinder.buildPath(start, dest, actor.getCell(), false); else { PathFinder newPathFinder; - newPathFinder.buildPath(start, dest, actor.getCell(), isOutside); + newPathFinder.buildPath(start, dest, actor.getCell(), false); - //TO EXPLORE: - //maybe here is a mistake (?): PathFinder::getPathSize() returns number of grid points in the path, - //not the actual path length. Here we should know if the new path is actually more effective. - //if(pathFinder2.getPathSize() < mPathFinder.getPathSize()) if(!mPathFinder.getPath().empty()) { newPathFinder.syncStart(mPathFinder.getPath()); @@ -362,9 +637,9 @@ namespace MWMechanics return 1; } - const std::string &AiCombat::getTargetId() const + MWWorld::Ptr AiCombat::getTarget() const { - return mTarget.getRefData().getHandle(); + return MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); } diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 767a36292..4b728ff22 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -6,26 +6,32 @@ #include "pathfinding.hpp" #include "movement.hpp" +#include "obstacle.hpp" + +#include "../mwworld/cellstore.hpp" // for Doors #include "../mwbase/world.hpp" namespace MWMechanics { + /// \brief Causes the actor to fight another actor class AiCombat : public AiPackage { public: + ///Constructor + /** \param actor Actor to fight **/ AiCombat(const MWWorld::Ptr& actor); virtual AiCombat *clone() const; virtual bool execute (const MWWorld::Ptr& actor,float duration); - ///< \return Package completed? virtual int getTypeId() const; virtual unsigned int getPriority() const; - const std::string &getTargetId() const; + ///Returns target ID + MWWorld::Ptr getTarget() const; private: PathFinder mPathFinder; @@ -36,19 +42,28 @@ namespace MWMechanics // when mCombatMove is true float mTimerCombatMove; - // the z rotation angle (degrees) we want to reach - // used every frame when mRotate is true - float mTargetAngle; - - bool mReadyToAttack, mStrike; + // AiCombat states + bool mReadyToAttack, mAttack; bool mFollowTarget; bool mCombatMove; - bool mRotate; + bool mBackOffDoor; + bool mForceNoShortcut; + ESM::Position mShortcutFailPos; + + ESM::Position mLastPos; MWMechanics::Movement mMovement; - MWWorld::Ptr mTarget; + int mTargetActorId; + + const MWWorld::CellStore* mCell; + ObstacleCheck mObstacleCheck; + float mDoorCheckDuration; + // TODO: for some reason mDoors.searchViaHandle() returns + // null pointers, workaround by keeping an iterator + MWWorld::CellRefList::List::iterator mDoorIter; + MWWorld::CellRefList& mDoors; - void buildNewPath(const MWWorld::Ptr& actor); + void buildNewPath(const MWWorld::Ptr& actor, const MWWorld::Ptr& target); }; } diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index f27fada39..07bb9726e 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -75,58 +75,6 @@ namespace MWMechanics return true; } - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - ESM::Position pos = actor.getRefData().getPosition(); - bool cellChange = actor.getCell()->getCell()->mData.mX != mCellX || actor.getCell()->getCell()->mData.mY != mCellY; - - if(actor.getCell()->getCell()->mData.mX != player.getCell()->getCell()->mData.mX) - { - int sideX = PathFinder::sgn(actor.getCell()->getCell()->mData.mX - player.getCell()->getCell()->mData.mX); - // Check if actor is near the border of an inactive cell. If so, pause walking. - if(sideX * (pos.pos[0] - actor.getCell()->getCell()->mData.mX * ESM::Land::REAL_SIZE) > sideX * (ESM::Land::REAL_SIZE / - 2.0 - 200)) - { - MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0; - return false; - } - } - if(actor.getCell()->getCell()->mData.mY != player.getCell()->getCell()->mData.mY) - { - int sideY = PathFinder::sgn(actor.getCell()->getCell()->mData.mY - player.getCell()->getCell()->mData.mY); - // Check if actor is near the border of an inactive cell. If so, pause walking. - if(sideY*(pos.pos[1] - actor.getCell()->getCell()->mData.mY * ESM::Land::REAL_SIZE) > sideY * (ESM::Land::REAL_SIZE / - 2.0 - 200)) - { - MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0; - return false; - } - } - - - if(!mPathFinder.isPathConstructed() || cellChange) - { - mCellX = actor.getCell()->getCell()->mData.mX; - mCellY = actor.getCell()->getCell()->mData.mY; - - ESM::Pathgrid::Point dest; - dest.mX = mX; - dest.mY = mY; - dest.mZ = mZ; - - ESM::Pathgrid::Point start; - start.mX = pos.pos[0]; - start.mY = pos.pos[1]; - start.mZ = pos.pos[2]; - - mPathFinder.buildPath(start, dest, actor.getCell(), true); - } - - if(mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2])) - { - MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0; - return true; - } - const MWWorld::Ptr follower = MWBase::Environment::get().getWorld()->getPtr(mActorId, false); const float* const leaderPos = actor.getRefData().getPosition().pos; const float* const followerPos = follower.getRefData().getPosition().pos; @@ -141,16 +89,15 @@ namespace MWMechanics if(distanceBetweenResult <= mMaxDist * mMaxDist) { - float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); - zTurn(actor, Ogre::Degree(zAngle)); - MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; + if(pathTo(actor,ESM::Pathgrid::Point(mX,mY,mZ),duration)) //Returns true on path complete + return true; mMaxDist = 470; } else { // Stop moving if the player is to far away MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle3", 0, 1); - MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0; + actor.getClass().getMovementSettings(actor).mPosition[1] = 0; mMaxDist = 330; } diff --git a/apps/openmw/mwmechanics/aiescort.hpp b/apps/openmw/mwmechanics/aiescort.hpp index 53b57c058..3771417fa 100644 --- a/apps/openmw/mwmechanics/aiescort.hpp +++ b/apps/openmw/mwmechanics/aiescort.hpp @@ -8,18 +8,22 @@ namespace MWMechanics { + /// \brief AI Package to have an NPC lead the player to a specific point class AiEscort : public AiPackage { public: + /// Implementation of AiEscort + /** The Actor will escort the specified actor to the world position x, y, z until they reach their position, or they run out of time + \implement AiEscort **/ AiEscort(const std::string &actorId,int duration, float x, float y, float z); - ///< \implement AiEscort + /// Implementation of AiEscortCell + /** The Actor will escort the specified actor to the cell position x, y, z until they reach their position, or they run out of time + \implement AiEscortCell **/ AiEscort(const std::string &actorId,const std::string &cellId,int duration, float x, float y, float z); - ///< \implement AiEscortCell virtual AiEscort *clone() const; virtual bool execute (const MWWorld::Ptr& actor,float duration); - ///< \return Package completed? virtual int getTypeId() const; diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index c3b36516c..f1296a949 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -4,6 +4,7 @@ #include "../mwbase/environment.hpp" #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" +#include "creaturestats.hpp" #include "movement.hpp" #include @@ -11,104 +12,63 @@ #include "steering.hpp" MWMechanics::AiFollow::AiFollow(const std::string &actorId,float duration, float x, float y, float z) -: mAlwaysFollow(false), mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(""), mTimer(0), mStuckTimer(0) +: mAlwaysFollow(false), mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(""), AiPackage() { } MWMechanics::AiFollow::AiFollow(const std::string &actorId,const std::string &cellId,float duration, float x, float y, float z) -: mAlwaysFollow(false), mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(cellId), mTimer(0), mStuckTimer(0) +: mAlwaysFollow(false), mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(cellId), AiPackage() { } MWMechanics::AiFollow::AiFollow(const std::string &actorId) -: mAlwaysFollow(true), mDuration(0), mX(0), mY(0), mZ(0), mActorId(actorId), mCellId(""), mTimer(0), mStuckTimer(0) +: mAlwaysFollow(true), mDuration(0), mX(0), mY(0), mZ(0), mActorId(actorId), mCellId(""), AiPackage() { } bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration) { - const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mActorId, false); - - if(target == MWWorld::Ptr()) return true; + const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mActorId, false); //The target to follow - mTimer = mTimer + duration; - mStuckTimer = mStuckTimer + duration; - mTotalTime = mTotalTime + duration; + if(target == MWWorld::Ptr()) return true; //Target doesn't exist - ESM::Position pos = actor.getRefData().getPosition(); + ESM::Position pos = actor.getRefData().getPosition(); //position of the actor - if(!mAlwaysFollow) + if(!mAlwaysFollow) //Update if you only follow for a bit { - if(mTotalTime > mDuration && mDuration != 0) + if(mTotalTime > mDuration && mDuration != 0) //Check if we've run out of time return true; if((pos.pos[0]-mX)*(pos.pos[0]-mX) + (pos.pos[1]-mY)*(pos.pos[1]-mY) + - (pos.pos[2]-mZ)*(pos.pos[2]-mZ) < 100*100) + (pos.pos[2]-mZ)*(pos.pos[2]-mZ) < 100*100) //Close-ish to final position { - if(actor.getCell()->isExterior()) + if(actor.getCell()->isExterior()) //Outside? { - if(mCellId == "") + if(mCellId == "") //No cell to travel to return true; } else { - if(mCellId == actor.getCell()->getCell()->mName) + if(mCellId == actor.getCell()->getCell()->mName) //Cell to travel to return true; } } } - ESM::Pathgrid::Point dest; - dest.mX = target.getRefData().getPosition().pos[0]; - dest.mY = target.getRefData().getPosition().pos[1]; - dest.mZ = target.getRefData().getPosition().pos[2]; + //Set the target desition from the actor + ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; - ESM::Pathgrid::Point start; - start.mX = pos.pos[0]; - start.mY = pos.pos[1]; - start.mZ = pos.pos[2]; - - if(mPathFinder.getPath().empty()) - mPathFinder.buildPath(start, dest, actor.getCell(), true); - - - if(mTimer > 0.25) - { - if(!mPathFinder.getPath().empty()) - { - ESM::Pathgrid::Point lastPos = mPathFinder.getPath().back(); - - if((dest.mX - lastPos.mX)*(dest.mX - lastPos.mX) - +(dest.mY - lastPos.mY)*(dest.mY - lastPos.mY) - +(dest.mZ - lastPos.mZ)*(dest.mZ - lastPos.mZ) - > 100*100) - mPathFinder.addPointToPath(dest); - } - - mTimer = 0; - } - - if(mStuckTimer>0.5) - { - if((mStuckPos.pos[0] - pos.pos[0])*(mStuckPos.pos[0] - pos.pos[0]) - +(mStuckPos.pos[1] - pos.pos[1])*(mStuckPos.pos[1] - pos.pos[1]) - +(mStuckPos.pos[2] - pos.pos[2])*(mStuckPos.pos[2] - pos.pos[2]) < 100) //NPC is stuck - mPathFinder.buildPath(start, dest, actor.getCell(), true); - - mStuckTimer = 0; - mStuckPos = pos; - } - - if(!mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2])) - { - zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); + if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < 100) //Stop when you get close + actor.getClass().getMovementSettings(actor).mPosition[1] = 0; + else { + pathTo(actor, dest, duration); //Go to the destination } - if((dest.mX - pos.pos[0])*(dest.mX - pos.pos[0])+(dest.mY - pos.pos[1])*(dest.mY - pos.pos[1])+(dest.mZ - pos.pos[2])*(dest.mZ - pos.pos[2]) - < 100*100) - actor.getClass().getMovementSettings(actor).mPosition[1] = 0; - else - actor.getClass().getMovementSettings(actor).mPosition[1] = 1; + //Check if you're far away + if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) > 1000) + actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, true); //Make NPC run + else if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < 800) //Have a bit of a dead zone, otherwise npc will constantly flip between running and not when right on the edge of the running threshhold + actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, false); //make NPC walk return false; } diff --git a/apps/openmw/mwmechanics/aifollow.hpp b/apps/openmw/mwmechanics/aifollow.hpp index 48a8eb4c2..10a381410 100644 --- a/apps/openmw/mwmechanics/aifollow.hpp +++ b/apps/openmw/mwmechanics/aifollow.hpp @@ -1,43 +1,45 @@ -#ifndef GAME_MWMECHANICS_AIFALLOW_H -#define GAME_MWMECHANICS_AIFALLOW_H +#ifndef GAME_MWMECHANICS_AIFOLLOW_H +#define GAME_MWMECHANICS_AIFOLLOW_H #include "aipackage.hpp" #include #include "pathfinding.hpp" -#include "../../../components/esm/defs.hpp" +#include namespace MWMechanics { - + /// \brief AiPackage for an actor to follow another actor/the PC + /** The AI will follow the target until a condition (time, or position) are set. Both can be disabled to cause the actor to follow the other indefinitely + **/ class AiFollow : public AiPackage { public: + /// Follow Actor for duration or until you arrive at a world position AiFollow(const std::string &ActorId,float duration, float X, float Y, float Z); + /// Follow Actor for duration or until you arrive at a position in a cell AiFollow(const std::string &ActorId,const std::string &CellId,float duration, float X, float Y, float Z); + /// Follow Actor indefinitively AiFollow(const std::string &ActorId); + virtual AiFollow *clone() const; + virtual bool execute (const MWWorld::Ptr& actor,float duration); - ///< \return Package completed? + virtual int getTypeId() const; + /// Returns the actor being followed std::string getFollowedActor(); private: - bool mAlwaysFollow; //this will make the actor always follow, thus ignoring mDuration and mX,mY,mZ (used for summoned creatures). + /// This will make the actor always follow. + /** Thus ignoring mDuration and mX,mY,mZ (used for summoned creatures). **/ + bool mAlwaysFollow; float mDuration; float mX; float mY; float mZ; std::string mActorId; std::string mCellId; - - float mTimer; - float mStuckTimer; - float mTotalTime; - - ESM::Position mStuckPos; - - PathFinder mPathFinder; }; } #endif diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 8193a670b..2144aa11d 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -1,4 +1,131 @@ #include "aipackage.hpp" +#include +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/cellstore.hpp" +#include "creaturestats.hpp" +#include "movement.hpp" +#include "../mwworld/action.hpp" + +#include + +#include "steering.hpp" + MWMechanics::AiPackage::~AiPackage() {} + +MWMechanics::AiPackage::AiPackage() : mLastDoorChecked(MWWorld::Ptr()), mTimer(.26), mStuckTimer(0) { //mTimer starts at .26 to force initial pathbuild + +} + + +bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Point dest, float duration) +{ + //Update various Timers + mTimer += duration; //Update timer + mStuckTimer += duration; //Update stuck timer + mTotalTime += duration; //Update total time following + + + ESM::Position pos = actor.getRefData().getPosition(); //position of the actor + + /// Stops the actor when it gets too close to a unloaded cell + { + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + const ESM::Cell *cell = actor.getCell()->getCell(); + Movement &movement = actor.getClass().getMovementSettings(actor); + + //Ensure pursuer doesn't leave loaded cells + if(cell->mData.mX != player.getCell()->getCell()->mData.mX) + { + int sideX = PathFinder::sgn(cell->mData.mX - player.getCell()->getCell()->mData.mX); + //check if actor is near the border of an inactive cell. If so, stop walking. + if(sideX * (pos.pos[0] - cell->mData.mX*ESM::Land::REAL_SIZE) > sideX * (ESM::Land::REAL_SIZE/2.0f - 200.0f)) + { + movement.mPosition[1] = 0; + return false; + } + } + if(cell->mData.mY != player.getCell()->getCell()->mData.mY) + { + int sideY = PathFinder::sgn(cell->mData.mY - player.getCell()->getCell()->mData.mY); + //check if actor is near the border of an inactive cell. If so, stop walking. + if(sideY * (pos.pos[1] - cell->mData.mY*ESM::Land::REAL_SIZE) > sideY * (ESM::Land::REAL_SIZE/2.0f - 200.0f)) + { + movement.mPosition[1] = 0; + return false; + } + } + } + + //Start position + ESM::Pathgrid::Point start = pos.pos; + + //*********************** + /// Checks if you can't get to the end position at all, adds end position to end of path + /// Rebuilds path every quarter of a second, in case the target has moved + //*********************** + if(mTimer > 0.25) + { + if(distance(mPrevDest, dest) > 10) { //Only rebuild path if it's moved + mPathFinder.buildPath(start, dest, actor.getCell(), true); //Rebuild path, in case the target has moved + mPrevDest = dest; + } + + if(!mPathFinder.getPath().empty()) //Path has points in it + { + ESM::Pathgrid::Point lastPos = mPathFinder.getPath().back(); //Get the end of the proposed path + + if(distance(dest, lastPos) > 100) //End of the path is far from the destination + mPathFinder.addPointToPath(dest); //Adds the final destination to the path, to try to get to where you want to go + } + + mTimer = 0; + } + + //************************ + /// Checks if you aren't moving; attempts to unstick you + //************************ + if(mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2])) //Path finished? + return true; + else if(mStuckTimer>0.5) //Every half second see if we need to take action to avoid something + { +/// TODO (tluppi#1#): Use ObstacleCheck here. Not working for some reason + //if(mObstacleCheck.check(actor, duration)) { + if(distance(start, mStuckPos.pos[0], mStuckPos.pos[1], mStuckPos.pos[2]) < 10 && distance(dest, start) > 20) { //Actually stuck, and far enough away from destination to care + // first check if we're walking into a door + MWWorld::Ptr door = getNearbyDoor(actor); + if(door != MWWorld::Ptr()) // NOTE: checks interior cells only + { + if(door.getCellRef().getTrap().empty() && mLastDoorChecked != door) { //Open the door if untrapped + door.getClass().activate(door, actor).get()->execute(actor); + mLastDoorChecked = door; + } + } + else // probably walking into another NPC + { + // TODO: diagonal should have same animation as walk forward + // but doesn't seem to do that? + actor.getClass().getMovementSettings(actor).mPosition[0] = 1; + actor.getClass().getMovementSettings(actor).mPosition[1] = 0.1f; + // change the angle a bit, too + zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0] + 1, pos.pos[1]))); + } + } + else { //Not stuck, so reset things + mStuckTimer = 0; + mStuckPos = pos; + mLastDoorChecked = MWWorld::Ptr(); //Resets it, in case he gets stuck behind the door again + actor.getClass().getMovementSettings(actor).mPosition[1] = 1; //Just run forward + } + } + else { + actor.getClass().getMovementSettings(actor).mPosition[1] = 1; //Just run forward the rest of the time + } + + zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); + + return false; +} diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 8e015da15..055958384 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -1,6 +1,12 @@ #ifndef GAME_MWMECHANICS_AIPACKAGE_H #define GAME_MWMECHANICS_AIPACKAGE_H +#include "pathfinding.hpp" +#include +#include "../mwbase/world.hpp" + +#include "obstacle.hpp" + namespace MWWorld { class Ptr; @@ -12,6 +18,7 @@ namespace MWMechanics class AiPackage { public: + ///Enumerates the various AITypes availible. enum TypeId { TypeIdNone = -1, TypeIdWander = 0, @@ -20,21 +27,47 @@ namespace MWMechanics TypeIdFollow = 3, TypeIdActivate = 4, TypeIdCombat = 5, - TypeIdPersue = 6 + TypeIdPursue = 6, + TypeIdAvoidDoor = 7 }; + ///Default constructor + AiPackage(); + + ///Default Deconstructor virtual ~AiPackage(); - + + ///Clones the package virtual AiPackage *clone() const = 0; - + + /// Updates and runs the package (Should run every frame) + /// \return Package completed? virtual bool execute (const MWWorld::Ptr& actor,float duration) = 0; - ///< \return Package completed? - + + /// Returns the TypeID of the AiPackage + /// \see enum TypeId virtual int getTypeId() const = 0; - ///< @see enum TypeId + /// Higher number is higher priority (0 being the lowest) virtual unsigned int getPriority() const {return 0;} - ///< higher number is higher priority (0 beeing the lowest) + + protected: + /// Causes the actor to attempt to walk to the specified location + /** \return If the actor has arrived at his destination **/ + bool pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Point dest, float duration); + + PathFinder mPathFinder; + ObstacleCheck mObstacleCheck; + + float mDoorCheckDuration; + float mTimer; + float mStuckTimer; + float mTotalTime; + + MWWorld::Ptr mLastDoorChecked; //Used to ensure we don't try to CONSTANTLY open a door + + ESM::Position mStuckPos; + ESM::Pathgrid::Point mPrevDest; }; } diff --git a/apps/openmw/mwmechanics/aipersue.cpp b/apps/openmw/mwmechanics/aipersue.cpp deleted file mode 100644 index 36e18946c..000000000 --- a/apps/openmw/mwmechanics/aipersue.cpp +++ /dev/null @@ -1,106 +0,0 @@ -#include "aipersue.hpp" - -#include "../mwbase/world.hpp" -#include "../mwbase/environment.hpp" - -#include "../mwworld/class.hpp" -#include "../mwworld/action.hpp" -#include "../mwworld/cellstore.hpp" - -#include "steering.hpp" -#include "movement.hpp" -#include "creaturestats.hpp" - -MWMechanics::AiPersue::AiPersue(const std::string &objectId) - : mObjectId(objectId) -{ -} -MWMechanics::AiPersue *MWMechanics::AiPersue::clone() const -{ - return new AiPersue(*this); -} -bool MWMechanics::AiPersue::execute (const MWWorld::Ptr& actor, float duration) -{ - MWBase::World *world = MWBase::Environment::get().getWorld(); - ESM::Position pos = actor.getRefData().getPosition(); - Movement &movement = actor.getClass().getMovementSettings(actor); - const ESM::Cell *cell = actor.getCell()->getCell(); - - actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); - - MWWorld::Ptr player = world->getPlayerPtr(); - if(cell->mData.mX != player.getCell()->getCell()->mData.mX) - { - int sideX = PathFinder::sgn(cell->mData.mX - player.getCell()->getCell()->mData.mX); - //check if actor is near the border of an inactive cell. If so, stop walking. - if(sideX * (pos.pos[0] - cell->mData.mX*ESM::Land::REAL_SIZE) > - sideX * (ESM::Land::REAL_SIZE/2.0f - 200.0f)) - { - movement.mPosition[1] = 0; - return false; - } - } - if(cell->mData.mY != player.getCell()->getCell()->mData.mY) - { - int sideY = PathFinder::sgn(cell->mData.mY - player.getCell()->getCell()->mData.mY); - //check if actor is near the border of an inactive cell. If so, stop walking. - if(sideY * (pos.pos[1] - cell->mData.mY*ESM::Land::REAL_SIZE) > - sideY * (ESM::Land::REAL_SIZE/2.0f - 200.0f)) - { - movement.mPosition[1] = 0; - return false; - } - } - - MWWorld::Ptr target = world->getPtr(mObjectId,false); - ESM::Position targetPos = target.getRefData().getPosition(); - - bool cellChange = cell->mData.mX != mCellX || cell->mData.mY != mCellY; - if(!mPathFinder.isPathConstructed() || cellChange) - { - mCellX = cell->mData.mX; - mCellY = cell->mData.mY; - - ESM::Pathgrid::Point dest; - dest.mX = targetPos.pos[0]; - dest.mY = targetPos.pos[1]; - dest.mZ = targetPos.pos[2]; - - ESM::Pathgrid::Point start; - start.mX = pos.pos[0]; - start.mY = pos.pos[1]; - start.mZ = pos.pos[2]; - - mPathFinder.buildPath(start, dest, actor.getCell(), true); - } - - if((pos.pos[0]-targetPos.pos[0])*(pos.pos[0]-targetPos.pos[0])+ - (pos.pos[1]-targetPos.pos[1])*(pos.pos[1]-targetPos.pos[1])+ - (pos.pos[2]-targetPos.pos[2])*(pos.pos[2]-targetPos.pos[2]) < 200*200) - { - movement.mPosition[1] = 0; - MWWorld::Ptr target = world->getPtr(mObjectId,false); - MWWorld::Class::get(target).activate(target,actor).get()->execute(actor); - return true; - } - - if(mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2])) - { - movement.mPosition[1] = 0; - MWWorld::Ptr target = world->getPtr(mObjectId,false); - MWWorld::Class::get(target).activate(target,actor).get()->execute(actor); - return true; - } - - float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); - zTurn(actor, Ogre::Degree(zAngle)); - MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; - movement.mPosition[1] = 1; - - return false; -} - -int MWMechanics::AiPersue::getTypeId() const -{ - return TypeIdPersue; -} diff --git a/apps/openmw/mwmechanics/aipersue.hpp b/apps/openmw/mwmechanics/aipersue.hpp deleted file mode 100644 index 3fd708ab3..000000000 --- a/apps/openmw/mwmechanics/aipersue.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef GAME_MWMECHANICS_AIPERSUE_H -#define GAME_MWMECHANICS_AIPERSUE_H - -#include "aipackage.hpp" -#include - -#include "pathfinding.hpp" - -namespace MWMechanics -{ - - class AiPersue : public AiPackage - { - public: - AiPersue(const std::string &objectId); - virtual AiPersue *clone() const; - virtual bool execute (const MWWorld::Ptr& actor,float duration); - ///< \return Package completed? - virtual int getTypeId() const; - - private: - std::string mObjectId; - - PathFinder mPathFinder; - int mCellX; - int mCellY; - }; -} -#endif diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp new file mode 100644 index 000000000..cd9d34e08 --- /dev/null +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -0,0 +1,60 @@ +#include "aipursue.hpp" + +#include "../mwbase/environment.hpp" + +#include "../mwworld/class.hpp" +#include "../mwworld/action.hpp" +#include "../mwworld/cellstore.hpp" + +#include "../mwmechanics/creaturestats.hpp" + +#include "movement.hpp" +#include "creaturestats.hpp" + +namespace MWMechanics +{ + +AiPursue::AiPursue(const MWWorld::Ptr& actor) + : mTargetActorId(actor.getClass().getCreatureStats(actor).getActorId()) +{ +} +AiPursue *MWMechanics::AiPursue::clone() const +{ + return new AiPursue(*this); +} +bool AiPursue::execute (const MWWorld::Ptr& actor, float duration) +{ + ESM::Position pos = actor.getRefData().getPosition(); //position of the actor + const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); //The target to follow + + if(target == MWWorld::Ptr()) + return true; //Target doesn't exist + + //Set the target desition from the actor + ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; + + if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < 100) { //Stop when you get close + actor.getClass().getMovementSettings(actor).mPosition[1] = 0; + target.getClass().activate(target,actor).get()->execute(actor); //Arrest player + return true; + } + else { + pathTo(actor, dest, duration); //Go to the destination + } + + actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, true); //Make NPC run + + return false; +} + +int AiPursue::getTypeId() const +{ + return TypeIdPursue; +} + +MWWorld::Ptr AiPursue::getTarget() const +{ + return MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); +} + +} // namespace MWMechanics diff --git a/apps/openmw/mwmechanics/aipursue.hpp b/apps/openmw/mwmechanics/aipursue.hpp new file mode 100644 index 000000000..27affc9ac --- /dev/null +++ b/apps/openmw/mwmechanics/aipursue.hpp @@ -0,0 +1,36 @@ +#ifndef GAME_MWMECHANICS_AIPURSUE_H +#define GAME_MWMECHANICS_AIPURSUE_H + +#include "aipackage.hpp" + +#include "../mwbase/world.hpp" + +#include "pathfinding.hpp" + +namespace MWMechanics +{ + /// \brief Makes the actor very closely follow the actor + /** Used for arresting players. Causes the actor to run to the pursued actor and activate them, to arrest them. + Note that while very similar to AiActivate, it will ONLY activate when evry close to target (Not also when the + path is completed). **/ + class AiPursue : public AiPackage + { + public: + ///Constructor + /** \param actor Actor to pursue **/ + AiPursue(const MWWorld::Ptr& actor); + + virtual AiPursue *clone() const; + virtual bool execute (const MWWorld::Ptr& actor,float duration); + virtual int getTypeId() const; + + MWWorld::Ptr getTarget() const; + + private: + + int mTargetActorId; // The actor to pursue + int mCellX; + int mCellY; + }; +} +#endif diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index c67367a6c..8a6a69b27 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -9,6 +9,7 @@ #include "aifollow.hpp" #include "aiactivate.hpp" #include "aicombat.hpp" +#include "aipursue.hpp" #include "../mwworld/class.hpp" #include "creaturestats.hpp" @@ -16,21 +17,24 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" -void MWMechanics::AiSequence::copy (const AiSequence& sequence) +namespace MWMechanics +{ + +void AiSequence::copy (const AiSequence& sequence) { for (std::list::const_iterator iter (sequence.mPackages.begin()); iter!=sequence.mPackages.end(); ++iter) mPackages.push_back ((*iter)->clone()); } -MWMechanics::AiSequence::AiSequence() : mDone (false), mLastAiPackage(-1) {} +AiSequence::AiSequence() : mDone (false), mLastAiPackage(-1) {} -MWMechanics::AiSequence::AiSequence (const AiSequence& sequence) : mDone (false) +AiSequence::AiSequence (const AiSequence& sequence) : mDone (false) { copy (sequence); } -MWMechanics::AiSequence& MWMechanics::AiSequence::operator= (const AiSequence& sequence) +AiSequence& AiSequence::operator= (const AiSequence& sequence) { if (this!=&sequence) { @@ -38,33 +42,62 @@ MWMechanics::AiSequence& MWMechanics::AiSequence::operator= (const AiSequence& s copy (sequence); mDone = sequence.mDone; } - + return *this; } -MWMechanics::AiSequence::~AiSequence() +AiSequence::~AiSequence() { clear(); } -int MWMechanics::AiSequence::getTypeId() const +int AiSequence::getTypeId() const { if (mPackages.empty()) return -1; - + return mPackages.front()->getTypeId(); } -bool MWMechanics::AiSequence::getCombatTarget(std::string &targetActorId) const +bool AiSequence::getCombatTarget(MWWorld::Ptr &targetActor) const { if (getTypeId() != AiPackage::TypeIdCombat) return false; const AiCombat *combat = static_cast(mPackages.front()); - targetActorId = combat->getTargetId(); + + targetActor = combat->getTarget(); + + return true; +} + +bool AiSequence::canAddTarget(const ESM::Position& actorPos, float distToTarget) const +{ + bool firstCombatFound = false; + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + + for(std::list::const_iterator it = mPackages.begin(); it != mPackages.end(); ++it) + { + if ((*it)->getTypeId() == AiPackage::TypeIdCombat) + { + firstCombatFound = true; + + const AiCombat *combat = static_cast(*it); + if (combat->getTarget() != player ) return false; // only 1 non-player target allowed + else + { + // add new target only if current target (player) is farther + ESM::Position &targetPos = combat->getTarget().getRefData().getPosition(); + + float distToCurrTarget = (Ogre::Vector3(targetPos.pos) - Ogre::Vector3(actorPos.pos)).length(); + return (distToCurrTarget > distToTarget); + } + } + else if (firstCombatFound) break; // assumes combat packages go one-by-one in packages list + } return true; } -void MWMechanics::AiSequence::stopCombat() +void AiSequence::stopCombat() { while (getTypeId() == AiPackage::TypeIdCombat) { @@ -73,42 +106,100 @@ void MWMechanics::AiSequence::stopCombat() } } -void MWMechanics::AiSequence::stopPersue() +void AiSequence::stopPursuit() { - while (getTypeId() == AiPackage::TypeIdPersue) + while (getTypeId() == AiPackage::TypeIdPursue) { delete *mPackages.begin(); mPackages.erase (mPackages.begin()); } } -bool MWMechanics::AiSequence::isPackageDone() const +bool AiSequence::isPackageDone() const { return mDone; } -void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor,float duration) +void AiSequence::execute (const MWWorld::Ptr& actor,float duration) { if(actor != MWBase::Environment::get().getWorld()->getPlayerPtr()) { if (!mPackages.empty()) { - mLastAiPackage = mPackages.front()->getTypeId(); - if (mPackages.front()->execute (actor,duration)) + MWMechanics::AiPackage* package = mPackages.front(); + mLastAiPackage = package->getTypeId(); + + // if active package is combat one, choose nearest target + if (mLastAiPackage == AiPackage::TypeIdCombat) + { + std::list::iterator itActualCombat; + + float nearestDist = std::numeric_limits::max(); + Ogre::Vector3 vActorPos = Ogre::Vector3(actor.getRefData().getPosition().pos); + + for(std::list::iterator it = mPackages.begin(); it != mPackages.end();) + { + if ((*it)->getTypeId() != AiPackage::TypeIdCombat) break; + + MWWorld::Ptr target = static_cast(*it)->getTarget(); + + // target disappeared (e.g. summoned creatures) + if (target.isEmpty()) + { + delete *it; + it = mPackages.erase(it); + } + else + { + ESM::Position &targetPos = target.getRefData().getPosition(); + + float distTo = (Ogre::Vector3(targetPos.pos) - vActorPos).length(); + if (distTo < nearestDist) + { + nearestDist = distTo; + itActualCombat = it; + } + ++it; + } + } + + if (!mPackages.empty()) + { + if (nearestDist < std::numeric_limits::max() && mPackages.begin() != itActualCombat) + { + // move combat package with nearest target to the front + mPackages.splice(mPackages.begin(), mPackages, itActualCombat); + } + + package = mPackages.front(); + mLastAiPackage = package->getTypeId(); + } + else + { + mDone = true; + return; + } + } + + if (package->execute (actor,duration)) { - delete *mPackages.begin(); - mPackages.erase (mPackages.begin()); + // To account for the rare case where AiPackage::execute() queued another AI package + // (e.g. AiPursue executing a dialogue script that uses startCombat) + std::list::iterator toRemove = + std::find(mPackages.begin(), mPackages.end(), package); + mPackages.erase(toRemove); + delete package; mDone = true; } else { - mDone = false; + mDone = false; } } } } -void MWMechanics::AiSequence::clear() +void AiSequence::clear() { for (std::list::const_iterator iter (mPackages.begin()); iter!=mPackages.end(); ++iter) delete *iter; @@ -116,9 +207,24 @@ void MWMechanics::AiSequence::clear() mPackages.clear(); } -void MWMechanics::AiSequence::stack (const AiPackage& package) +void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor) { - for(std::list::iterator it = mPackages.begin(); it != mPackages.end(); it++) + if (package.getTypeId() == AiPackage::TypeIdCombat || package.getTypeId() == AiPackage::TypeIdPursue) + { + // Notify AiWander of our current position so we can return to it after combat finished + for (std::list::const_iterator iter (mPackages.begin()); iter!=mPackages.end(); ++iter) + { + if((*iter)->getTypeId() == AiPackage::TypeIdPursue && package.getTypeId() == AiPackage::TypeIdPursue + && static_cast(*iter)->getTarget() == static_cast(&package)->getTarget()) + { + return; // target is already pursued + } + else if ((*iter)->getTypeId() == AiPackage::TypeIdWander) + static_cast(*iter)->setReturnPosition(Ogre::Vector3(actor.getRefData().getPosition().pos)); + } + } + + for(std::list::iterator it = mPackages.begin(); it != mPackages.end(); ++it) { if(mPackages.front()->getPriority() <= package.getPriority()) { @@ -131,12 +237,12 @@ void MWMechanics::AiSequence::stack (const AiPackage& package) mPackages.push_front (package.clone()); } -void MWMechanics::AiSequence::queue (const AiPackage& package) +void AiSequence::queue (const AiPackage& package) { mPackages.push_back (package.clone()); } -MWMechanics::AiPackage* MWMechanics::AiSequence::getActivePackage() +AiPackage* MWMechanics::AiSequence::getActivePackage() { if(mPackages.empty()) throw std::runtime_error(std::string("No AI Package!")); @@ -144,7 +250,7 @@ MWMechanics::AiPackage* MWMechanics::AiSequence::getActivePackage() return mPackages.front(); } -void MWMechanics::AiSequence::fill(const ESM::AIPackageList &list) +void AiSequence::fill(const ESM::AIPackageList &list) { for (std::vector::const_iterator it = list.mList.begin(); it != list.mList.end(); ++it) { @@ -180,3 +286,5 @@ void MWMechanics::AiSequence::fill(const ESM::AIPackageList &list) mPackages.push_back(package); } } + +} // namespace MWMechanics diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index 07b7c898c..41a280da8 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -13,65 +13,82 @@ namespace MWWorld namespace MWMechanics { class AiPackage; - + /// \brief Sequence of AI-packages for a single actor + /** The top-most AI package is run each frame. When completed, it is removed from the stack. **/ class AiSequence { + ///AiPackages to run though std::list mPackages; + ///Finished with top AIPackage, set for one frame bool mDone; + ///Copy AiSequence void copy (const AiSequence& sequence); - // The type of AI package that ran last + /// The type of AI package that ran last int mLastAiPackage; public: - + ///Default constructor AiSequence(); - + + /// Copy Constructor AiSequence (const AiSequence& sequence); - + + /// Assignment operator AiSequence& operator= (const AiSequence& sequence); - + virtual ~AiSequence(); + /// Returns currently executing AiPackage type + /** \see enum AiPackage::TypeId **/ int getTypeId() const; - ///< @see enum AiPackage::TypeId + /// Get the typeid of the Ai package that ran last + /** NOT the currently "active" Ai package that will be run in the next frame. + This difference is important when an Ai package has just finished and been removed. + \see enum AiPackage::TypeId **/ int getLastRunTypeId() const { return mLastAiPackage; } - ///< Get the typeid of the Ai package that ran last, NOT the currently "active" Ai package that will be run in the next frame. - /// This difference is important when an Ai package has just finished and been removed. - bool getCombatTarget (std::string &targetActorId) const; - ///< Return true and assign target if combat package is currently - /// active, return false otherwise + /// Return true and assign target if combat package is currently active, return false otherwise + bool getCombatTarget (MWWorld::Ptr &targetActor) const; + + bool canAddTarget(const ESM::Position& actorPos, float distToTarget) const; + ///< Function assumes that actor can have only 1 target apart player + /// Removes all combat packages until first non-combat or stack empty. void stopCombat(); - ///< Removes all combat packages until first non-combat or stack empty. - void stopPersue(); - ///< Removes all persue packages until first non-persue or stack empty. - + /// Has a package been completed during the last update? bool isPackageDone() const; - ///< Has a package been completed during the last update? - + + /// Removes all pursue packages until first non-pursue or stack empty. + void stopPursuit(); + + /// Execute current package, switching if needed. void execute (const MWWorld::Ptr& actor,float duration); - ///< Execute package. - + + /// Remove all packages. void clear(); - ///< Remove all packages. - void stack (const AiPackage& package); - ///< Add \a package to the front of the sequence (suspends current package) - + ///< Add \a package to the front of the sequence + /** Suspends current package + @param actor The actor that owns this AiSequence **/ + void stack (const AiPackage& package, const MWWorld::Ptr& actor); + + /// Add \a package to the end of the sequence + /** Executed after all other packages have been completed **/ void queue (const AiPackage& package); - ///< Add \a package to the end of the sequence (executed after all other packages have been - /// completed) + /// Return the current active package. + /** If there is no active package, it will throw an exception **/ AiPackage* getActivePackage(); - ///< return the current active package. If there is no active package, throw an exeption + /// Fills the AiSequence with packages + /** Typically used for loading from the ESM + \see ESM::AIPackageList **/ void fill (const ESM::AIPackageList& list); }; } diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index c62c4e970..024656b38 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -8,6 +8,7 @@ #include "steering.hpp" #include "movement.hpp" +#include "creaturestats.hpp" namespace MWMechanics { @@ -30,6 +31,8 @@ namespace MWMechanics Movement &movement = actor.getClass().getMovementSettings(actor); const ESM::Cell *cell = actor.getCell()->getCell(); + actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, false); + MWWorld::Ptr player = world->getPlayerPtr(); if(cell->mData.mX != player.getCell()->getCell()->mData.mX) { diff --git a/apps/openmw/mwmechanics/aitravel.hpp b/apps/openmw/mwmechanics/aitravel.hpp index 72f3e0298..ea7f1dc32 100644 --- a/apps/openmw/mwmechanics/aitravel.hpp +++ b/apps/openmw/mwmechanics/aitravel.hpp @@ -6,15 +6,16 @@ #include "pathfinding.hpp" namespace MWMechanics -{ +{ + /// \brief Causes the AI to travel to the specified point class AiTravel : public AiPackage { - public: + public: + /// Default constructor AiTravel(float x, float y, float z); virtual AiTravel *clone() const; virtual bool execute (const MWWorld::Ptr& actor,float duration); - ///< \return Package completed? virtual int getTypeId() const; diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index e89d43ca4..1c870bda4 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -1,6 +1,7 @@ #include "aiwander.hpp" #include +#include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" @@ -17,11 +18,8 @@ namespace MWMechanics { - // NOTE: determined empirically but probably need further tweaking - static const int COUNT_BEFORE_RESET = 200; - static const float DIST_SAME_SPOT = 1.8f; - static const float DURATION_SAME_SPOT = 1.0f; - static const float DURATION_TO_EVADE = 0.4f; + static const int COUNT_BEFORE_RESET = 200; // TODO: maybe no longer needed + static const float DOOR_CHECK_INTERVAL = 1.5f; AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat): mDistance(distance), mDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle), mRepeat(repeat) @@ -29,16 +27,19 @@ namespace MWMechanics , mCellY(std::numeric_limits::max()) , mXCell(0) , mYCell(0) - , mX(0) - , mY(0) - , mZ(0) - , mPrevX(0) - , mPrevY(0) - , mWalkState(State_Norm) - , mStuckCount(0) - , mEvadeDuration(0) - , mStuckDuration(0) + , mCell(NULL) + , mStuckCount(0) // TODO: maybe no longer needed + , mDoorCheckDuration(0) + , mTrimCurrentNode(false) + , mReaction(0) + , mGreetDistanceMultiplier(0) + , mGreetDistanceReset(0) + , mChance(0) + , mRotate(false) + , mTargetAngle(0) , mSaidGreeting(false) + , mHasReturnPosition(false) + , mReturnPosition(0,0,0) { for(unsigned short counter = 0; counter < mIdle.size(); counter++) { @@ -55,9 +56,15 @@ namespace MWMechanics mStartTime = MWBase::Environment::get().getWorld()->getTimeStamp(); mPlayedIdle = 0; - mPathgrid = NULL; + const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); mIdleChanceMultiplier = - MWBase::Environment::get().getWorld()->getStore().get().find("fIdleChanceMultiplier")->getFloat(); + store.get().find("fIdleChanceMultiplier")->getFloat(); + + mGreetDistanceMultiplier = + store.get().find("iGreetDistanceMultiplier")->getInt(); + mGreetDistanceReset = + store.get().find("fGreetDistanceReset")->getFloat(); + mChance = store.get().find("fVoiceIdleOdds")->getFloat(); mStoredAvailableNodes = false; mChooseAction = true; @@ -71,11 +78,155 @@ namespace MWMechanics return new AiWander(*this); } - // TODO: duration is passed in but never used, check if it is needed + /* + * AiWander high level states (0.29.0). Not entirely accurate in some cases + * e.g. non-NPC actors do not greet and some creatures may be moving even in + * the IdleNow state. + * + * [select node, + * build path] + * +---------->MoveNow----------->Walking + * | | + * [allowed | | + * nodes] | [hello if near] | + * start--->ChooseAction----->IdleNow | + * ^ ^ | | + * | | | | + * | +-----------+ | + * | | + * +----------------------------------+ + * + * + * New high level states. Not exactly as per vanilla (e.g. door stuff) + * but the differences are required because our physics does not work like + * vanilla and therefore have to compensate/work around. + * + * [select node, [if stuck evade + * build path] or remove nodes if near door] + * +---------->MoveNow<---------->Walking + * | ^ | | + * | |(near door) | | + * [allowed | | | | + * nodes] | [hello if near] | | + * start--->ChooseAction----->IdleNow | | + * ^ ^ | ^ | | + * | | | | (stuck near | | + * | +-----------+ +---------------+ | + * | player) | + * +----------------------------------+ + * + * NOTE: non-time critical operations are run once every 250ms or so. + * + * TODO: It would be great if door opening/closing can be detected and pathgrid + * links dynamically updated. Currently (0.29.0) AiWander allows choosing a + * destination beyond closed doors which sometimes makes the actors stuck at the + * door and impossible for the player to open the door. + * + * For now detect being stuck at the door and simply delete the nodes from the + * allowed set. The issue is when the door opens the allowed set is not + * re-calculated. However this would not be an issue in most cases since hostile + * actors will enter combat (i.e. no longer wandering) and different pathfinding + * will kick in. + */ bool AiWander::execute (const MWWorld::Ptr& actor,float duration) { - actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); - actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, false); + MWMechanics::CreatureStats& cStats = actor.getClass().getCreatureStats(actor); + if(cStats.isDead() || cStats.getHealth().getCurrent() <= 0) + return true; // Don't bother with dead actors + + bool cellChange = mCell && (actor.getCell() != mCell); + if(!mCell || cellChange) + { + mCell = actor.getCell(); + mStoredAvailableNodes = false; // prob. not needed since mDistance = 0 + } + const ESM::Cell *cell = mCell->getCell(); + + cStats.setDrawState(DrawState_Nothing); + cStats.setMovementFlag(CreatureStats::Flag_Run, false); + + ESM::Position pos = actor.getRefData().getPosition(); + + // Check if an idle actor is too close to a door - if so start walking + mDoorCheckDuration += duration; + if(mDoorCheckDuration >= DOOR_CHECK_INTERVAL) + { + mDoorCheckDuration = 0; // restart timer + if(mDistance && // actor is not intended to be stationary + mIdleNow && // but is in idle + !mWalking && // FIXME: some actors are idle while walking + proximityToDoor(actor, MIN_DIST_TO_DOOR_SQUARED*1.6*1.6)) // NOTE: checks interior cells only + { + mIdleNow = false; + mMoveNow = true; + mTrimCurrentNode = false; // just in case + } + } + + if(mWalking) // have not yet reached the destination + { + // turn towards the next point in mPath + zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); + actor.getClass().getMovementSettings(actor).mPosition[1] = 1; + + // Returns true if evasive action needs to be taken + if(mObstacleCheck.check(actor, duration)) + { + // first check if we're walking into a door + if(proximityToDoor(actor)) // NOTE: checks interior cells only + { + // remove allowed points then select another random destination + mTrimCurrentNode = true; + trimAllowedNodes(mAllowedNodes, mPathFinder); + mObstacleCheck.clear(); + mPathFinder.clearPath(); + mWalking = false; + mMoveNow = true; + } + else // probably walking into another NPC + { + // TODO: diagonal should have same animation as walk forward + // but doesn't seem to do that? + actor.getClass().getMovementSettings(actor).mPosition[0] = 1; + actor.getClass().getMovementSettings(actor).mPosition[1] = 0.1f; + // change the angle a bit, too + zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0] + 1, pos.pos[1]))); + } + mStuckCount++; // TODO: maybe no longer needed + } +//#if 0 + // TODO: maybe no longer needed + if(mStuckCount >= COUNT_BEFORE_RESET) // something has gone wrong, reset + { + //std::cout << "Reset \""<< cls.getName(actor) << "\"" << std::endl; + mObstacleCheck.clear(); + + stopWalking(actor); + mMoveNow = false; + mWalking = false; + mChooseAction = true; + } +//#endif + } + + if (mRotate) + { + // Reduce the turning animation glitch by using a *HUGE* value of + // epsilon... TODO: a proper fix might be in either the physics or the + // animation subsystem + if (zTurn(actor, Ogre::Degree(mTargetAngle), Ogre::Degree(5))) + mRotate = false; + } + + mReaction += duration; + if(mReaction > 0.25f) // FIXME: hard coded constant + { + mReaction = 0; + return false; + } + + // NOTE: everything below get updated every 0.25 seconds + MWBase::World *world = MWBase::Environment::get().getWorld(); if(mDuration) { @@ -103,46 +254,55 @@ namespace MWMechanics } } - ESM::Position pos = actor.getRefData().getPosition(); - - // Once off initialization to discover & store allowed node points for this actor. + // Initialization to discover & store allowed node points for this actor. if(!mStoredAvailableNodes) { - mPathgrid = world->getStore().get().search(*actor.getCell()->getCell()); - - mCellX = actor.getCell()->getCell()->mData.mX; - mCellY = actor.getCell()->getCell()->mData.mY; - - // TODO: If there is no path does this actor get stuck forever? - if(!mPathgrid) - mDistance = 0; - else if(mPathgrid->mPoints.empty()) + // infrequently used, therefore no benefit in caching it as a member + const ESM::Pathgrid * + pathgrid = world->getStore().get().search(*cell); + + // cache the current cell location + mCellX = cell->mData.mX; + mCellY = cell->mData.mY; + + // If there is no path this actor doesn't go anywhere. See: + // https://forum.openmw.org/viewtopic.php?t=1556 + // http://www.fliggerty.com/phpBB3/viewtopic.php?f=30&t=5833 + if(!pathgrid || pathgrid->mPoints.empty()) mDistance = 0; - if(mDistance) // A distance value is initially passed into the constructor. + // A distance value passed into the constructor indicates how far the + // actor can wander from the spawn position. AiWander assumes that + // pathgrid points are available, and uses them to randomly select wander + // destinations within the allowed set of pathgrid points (nodes). + if(mDistance) { mXCell = 0; mYCell = 0; - if(actor.getCell()->getCell()->isExterior()) + if(cell->isExterior()) { mXCell = mCellX * ESM::Land::REAL_SIZE; mYCell = mCellY * ESM::Land::REAL_SIZE; } + // FIXME: There might be a bug here. The allowed node points are + // based on the actor's current position rather than the actor's + // spawn point. As a result the allowed nodes for wander can change + // between saves, for example. + // // convert npcPos to local (i.e. cell) co-ordinates - Ogre::Vector3 npcPos(actor.getRefData().getPosition().pos); + Ogre::Vector3 npcPos(pos.pos); npcPos[0] = npcPos[0] - mXCell; npcPos[1] = npcPos[1] - mYCell; - // populate mAllowedNodes for this actor with pathgrid point indexes based on mDistance - // NOTE: mPoints and mAllowedNodes contain points in local co-ordinates - for(unsigned int counter = 0; counter < mPathgrid->mPoints.size(); counter++) + // mAllowedNodes for this actor with pathgrid point indexes based on mDistance + // NOTE: mPoints and mAllowedNodes are in local co-ordinates + for(unsigned int counter = 0; counter < pathgrid->mPoints.size(); counter++) { - Ogre::Vector3 nodePos(mPathgrid->mPoints[counter].mX, - mPathgrid->mPoints[counter].mY, - mPathgrid->mPoints[counter].mZ); + Ogre::Vector3 nodePos(pathgrid->mPoints[counter].mX, pathgrid->mPoints[counter].mY, + pathgrid->mPoints[counter].mZ); if(npcPos.squaredDistance(nodePos) <= mDistance * mDistance) - mAllowedNodes.push_back(mPathgrid->mPoints[counter]); + mAllowedNodes.push_back(pathgrid->mPoints[counter]); } if(!mAllowedNodes.empty()) { @@ -151,9 +311,8 @@ namespace MWMechanics unsigned int index = 0; for(unsigned int counterThree = 1; counterThree < mAllowedNodes.size(); counterThree++) { - Ogre::Vector3 nodePos(mAllowedNodes[counterThree].mX, - mAllowedNodes[counterThree].mY, - mAllowedNodes[counterThree].mZ); + Ogre::Vector3 nodePos(mAllowedNodes[counterThree].mX, mAllowedNodes[counterThree].mY, + mAllowedNodes[counterThree].mZ); float tempDist = npcPos.squaredDistance(nodePos); if(tempDist < closestNode) index = counterThree; @@ -166,29 +325,52 @@ namespace MWMechanics } } - // TODO: Does this actor stay in one spot forever while in AiWander? + // Actor becomes stationary - see above URL's for previous research if(mAllowedNodes.empty()) mDistance = 0; // Don't try to move if you are in a new cell (ie: positioncell command called) but still play idles. - if(mDistance && (mCellX != actor.getCell()->getCell()->mData.mX || mCellY != actor.getCell()->getCell()->mData.mY)) + if(mDistance && cellChange) mDistance = 0; - if(mChooseAction) // Initially set true by the constructor. + // For stationary NPCs, move back to the starting location if another AiPackage moved us elsewhere + if (cellChange) + mHasReturnPosition = false; + if (mDistance == 0 && mHasReturnPosition && Ogre::Vector3(pos.pos).squaredDistance(mReturnPosition) > 20*20) { - mPlayedIdle = 0; - unsigned short idleRoll = 0; + mChooseAction = false; + mIdleNow = false; - for(unsigned int counter = 0; counter < mIdle.size(); counter++) + if (!mPathFinder.isPathConstructed()) { - unsigned short idleChance = mIdleChanceMultiplier * mIdle[counter]; - unsigned short randSelect = (int)(rand() / ((double)RAND_MAX + 1) * int(100 / mIdleChanceMultiplier)); - if(randSelect < idleChance && randSelect > idleRoll) + Ogre::Vector3 destNodePos = mReturnPosition; + + ESM::Pathgrid::Point dest; + dest.mX = destNodePos[0]; + dest.mY = destNodePos[1]; + dest.mZ = destNodePos[2]; + + // actor position is already in world co-ordinates + ESM::Pathgrid::Point start; + start.mX = pos.pos[0]; + start.mY = pos.pos[1]; + start.mZ = pos.pos[2]; + + // don't take shortcuts for wandering + mPathFinder.buildPath(start, dest, actor.getCell(), false); + + if(mPathFinder.isPathConstructed()) { - mPlayedIdle = counter+2; - idleRoll = randSelect; + mMoveNow = false; + mWalking = true; } } + } + + if(mChooseAction) + { + mPlayedIdle = 0; + getRandomIdle(); // NOTE: sets mPlayedIdle with a random selection if(!mPlayedIdle && mDistance) { @@ -205,65 +387,81 @@ namespace MWMechanics mIdleNow = true; // Play idle voiced dialogue entries randomly - int hello = actor.getClass().getCreatureStats(actor).getAiSetting(CreatureStats::AI_Hello).getModified(); + int hello = cStats.getAiSetting(CreatureStats::AI_Hello).getModified(); if (hello > 0) { - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - float chance = store.get().find("fVoiceIdleOdds")->getFloat(); int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); // Don't bother if the player is out of hearing range - if (roll < chance && Ogre::Vector3(player.getRefData().getPosition().pos).distance(Ogre::Vector3(actor.getRefData().getPosition().pos)) < 1500) + if (roll < mChance && Ogre::Vector3(player.getRefData().getPosition().pos).squaredDistance(Ogre::Vector3(pos.pos)) < 1500*1500) MWBase::Environment::get().getDialogueManager()->say(actor, "idle"); } } } // Allow interrupting a walking actor to trigger a greeting - if(mIdleNow || (mWalking && (mWalkState != State_Norm))) + if(mIdleNow || (mWalking && !mObstacleCheck.isNormalState() && mDistance)) { // Play a random voice greeting if the player gets too close - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - - int hello = actor.getClass().getCreatureStats(actor).getAiSetting(CreatureStats::AI_Hello).getModified(); + int hello = cStats.getAiSetting(CreatureStats::AI_Hello).getModified(); float helloDistance = hello; - int iGreetDistanceMultiplier = store.get().find("iGreetDistanceMultiplier")->getInt(); - helloDistance *= iGreetDistanceMultiplier; + helloDistance *= mGreetDistanceMultiplier; MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - float playerDist = Ogre::Vector3(player.getRefData().getPosition().pos).distance( - Ogre::Vector3(actor.getRefData().getPosition().pos)); + Ogre::Vector3 playerPos(player.getRefData().getPosition().pos); + Ogre::Vector3 actorPos(actor.getRefData().getPosition().pos); + float playerDistSqr = playerPos.squaredDistance(actorPos); - if(mWalking && playerDist <= helloDistance) + if(playerDistSqr <= helloDistance*helloDistance) { - stopWalking(actor); - mMoveNow = false; - mWalking = false; - mWalkState = State_Norm; + if(mWalking) + { + stopWalking(actor); + mMoveNow = false; + mWalking = false; + mObstacleCheck.clear(); + mIdleNow = true; + getRandomIdle(); + } + + if(!mRotate) + { + Ogre::Vector3 dir = playerPos - actorPos; + float length = dir.length(); + + float faceAngle = Ogre::Radian(Ogre::Math::ACos(dir.y / length) * + ((Ogre::Math::ASin(dir.x / length).valueRadians()>0)?1.0:-1.0)).valueDegrees(); + float actorAngle = actor.getRefData().getBaseNode()->getOrientation().getRoll().valueDegrees(); + // an attempt at reducing the turning animation glitch + if(abs(abs(faceAngle) - abs(actorAngle)) >= 5) // TODO: is there a better way? + { + mTargetAngle = faceAngle; + mRotate = true; + } + } } if (!mSaidGreeting) { // TODO: check if actor is aware / has line of sight - if (playerDist <= helloDistance + if (playerDistSqr <= helloDistance*helloDistance // Only play a greeting if the player is not moving && Ogre::Vector3(player.getClass().getMovementSettings(player).mPosition).squaredLength() == 0) { mSaidGreeting = true; MWBase::Environment::get().getDialogueManager()->say(actor, "hello"); - // TODO: turn to face player and interrupt the idle animation? } } else { - float fGreetDistanceReset = store.get().find("fGreetDistanceReset")->getFloat(); - if (playerDist >= fGreetDistanceReset * iGreetDistanceMultiplier) + if (playerDistSqr >= mGreetDistanceReset*mGreetDistanceReset * mGreetDistanceMultiplier*mGreetDistanceMultiplier) mSaidGreeting = false; } // Check if idle animation finished - if(!checkIdle(actor, mPlayedIdle)) + // FIXME: don't stay forever + if(!checkIdle(actor, mPlayedIdle) && playerDistSqr > helloDistance*helloDistance) { mPlayedIdle = 0; mIdleNow = false; @@ -273,12 +471,17 @@ namespace MWMechanics if(mMoveNow && mDistance) { + // Construct a new path if there isn't one if(!mPathFinder.isPathConstructed()) { assert(mAllowedNodes.size()); unsigned int randNode = (int)(rand() / ((double)RAND_MAX + 1) * mAllowedNodes.size()); - Ogre::Vector3 destNodePos(mAllowedNodes[randNode].mX, mAllowedNodes[randNode].mY, mAllowedNodes[randNode].mZ); + // NOTE: initially constructed with local (i.e. cell) co-ordinates + Ogre::Vector3 destNodePos(mAllowedNodes[randNode].mX, + mAllowedNodes[randNode].mY, + mAllowedNodes[randNode].mZ); + // convert dest to use world co-ordinates ESM::Pathgrid::Point dest; dest.mX = destNodePos[0] + mXCell; dest.mY = destNodePos[1] + mYCell; @@ -295,16 +498,21 @@ namespace MWMechanics if(mPathFinder.isPathConstructed()) { - // buildPath inserts dest in case it is not a pathgraph point index - // which is a duplicate for AiWander + // buildPath inserts dest in case it is not a pathgraph point + // index which is a duplicate for AiWander. However below code + // does not work since getPath() returns a copy of path not a + // reference //if(mPathFinder.getPathSize() > 1) //mPathFinder.getPath().pop_back(); - // Remove this node as an option and add back the previously used node - // (stops NPC from picking the same node): + // Remove this node as an option and add back the previously used node (stops NPC from picking the same node): ESM::Pathgrid::Point temp = mAllowedNodes[randNode]; mAllowedNodes.erase(mAllowedNodes.begin() + randNode); - mAllowedNodes.push_back(mCurrentNode); + // check if mCurrentNode was taken out of mAllowedNodes + if(mTrimCurrentNode && mAllowedNodes.size() > 1) + mTrimCurrentNode = false; + else + mAllowedNodes.push_back(mCurrentNode); mCurrentNode = temp; mMoveNow = false; @@ -316,118 +524,44 @@ namespace MWMechanics } } - if(mWalking) + // Are we there yet? + if(mWalking && + mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2])) { - if(mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2])) - { - stopWalking(actor); - mMoveNow = false; - mWalking = false; - mChooseAction = true; - } - else - { - /* f t - * State_Norm <---> State_CheckStuck --> State_Evade - * ^ ^ | ^ | ^ | | - * | | | | | | | | - * | +---+ +---+ +---+ | u - * | any < t < u | - * +--------------------------------------------+ - * - * f = one frame - * t = how long before considered stuck - * u = how long to move sideways - */ - bool samePosition = (abs(pos.pos[0] - mPrevX) < DIST_SAME_SPOT) && - (abs(pos.pos[1] - mPrevY) < DIST_SAME_SPOT); - - switch(mWalkState) - { - case State_Norm: - { - if(!samePosition) - break; - else - mWalkState = State_CheckStuck; - } - /* FALL THROUGH */ - case State_CheckStuck: - { - if(!samePosition) - { - mWalkState = State_Norm; - mStuckDuration = 0; - break; - } - else - { - mStuckDuration += duration; - // consider stuck only if position unchanges for a period - if(mStuckDuration > DURATION_SAME_SPOT) - { - mWalkState = State_Evade; - mStuckDuration = 0; - mStuckCount++; - } - else - break; // still in the same state, but duration added to timer - } - } - /* FALL THROUGH */ - case State_Evade: - { - mEvadeDuration += duration; - if(mEvadeDuration < DURATION_TO_EVADE) - break; - else - { - mWalkState = State_Norm; // tried to evade, assume all is ok and start again - mEvadeDuration = 0; - } - } - /* NO DEFAULT CASE */ - } + stopWalking(actor); + mMoveNow = false; + mWalking = false; + mChooseAction = true; + mHasReturnPosition = false; + } - if(mWalkState == State_Evade) - { - //std::cout << "Stuck \""<& nodes, + const PathFinder& pathfinder) + { + // TODO: how to add these back in once the door opens? + // Idea: keep a list of detected closed doors (see aicombat.cpp) + // Every now and then check whether one of the doors is opened. (maybe + // at the end of playing idle?) If the door is opened then re-calculate + // allowed nodes starting from the spawn point. + std::list paths = pathfinder.getPath(); + while(paths.size() >= 2) + { + ESM::Pathgrid::Point pt = paths.back(); + for(unsigned int j = 0; j < nodes.size(); j++) + { + // FIXME: doesn't hadle a door with the same X/Y + // co-ordinates but with a different Z + if(nodes[j].mX == pt.mX && nodes[j].mY == pt.mY) { - // normal walk forward - actor.getClass().getMovementSettings(actor).mPosition[1] = 1; - // turn towards the next point in mPath - // TODO: possibly no need to check every frame, maybe every 30 should be ok? - zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); + nodes.erase(nodes.begin() + j); + break; } - - if(mStuckCount >= COUNT_BEFORE_RESET) // something has gone wrong, reset - { - //std::cout << "Reset \""< idleRoll) + { + mPlayedIdle = counter+2; + idleRoll = randSelect; + } + } + } } diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 75621911e..6481b2a01 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -2,66 +2,73 @@ #define GAME_MWMECHANICS_AIWANDER_H #include "aipackage.hpp" + #include +#include + #include "pathfinding.hpp" +#include "obstacle.hpp" #include "../mwworld/timestamp.hpp" namespace MWMechanics { + /// \brief Causes the Actor to wander within a specified range class AiWander : public AiPackage { public: - + /// Constructor + /** \param distance Max distance the ACtor will wander + \param duration Time, in hours, that this package will be preformed + \param timeOfDay Start time of the package, if it has a duration. Currently unimplemented + \param idle Chances of each idle to play (9 in total) + \param repeat Repeat wander or not **/ AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat); + virtual AiPackage *clone() const; + virtual bool execute (const MWWorld::Ptr& actor,float duration); - ///< \return Package completed? + virtual int getTypeId() const; - ///< 0: Wander + + /// Set the position to return to for a stationary (non-wandering) actor + /** In case another AI package moved the actor elsewhere **/ + void setReturnPosition (const Ogre::Vector3& position); private: void stopWalking(const MWWorld::Ptr& actor); void playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect); bool checkIdle(const MWWorld::Ptr& actor, unsigned short idleSelect); + void getRandomIdle(); - int mDistance; + int mDistance; // how far the actor can wander from the spawn point int mDuration; int mTimeOfDay; std::vector mIdle; bool mRepeat; bool mSaidGreeting; + int mGreetDistanceMultiplier; + float mGreetDistanceReset; + float mChance; - float mX; - float mY; - float mZ; + bool mHasReturnPosition; // NOTE: Could be removed if mReturnPosition was initialized to actor position, + // if we had the actor in the AiWander constructor... + Ogre::Vector3 mReturnPosition; - // Cell location + // Cached current cell location int mCellX; int mCellY; // Cell location multiplied by ESM::Land::REAL_SIZE float mXCell; float mYCell; - // for checking if we're stuck (but don't check Z axis) - float mPrevX; - float mPrevY; - - enum WalkState - { - State_Norm, - State_CheckStuck, - State_Evade - }; - WalkState mWalkState; - - int mStuckCount; - float mStuckDuration; - float mEvadeDuration; + const MWWorld::CellStore* mCell; // for detecting cell change + // if false triggers calculating allowed nodes based on mDistance bool mStoredAvailableNodes; + // AiWander states bool mChooseAction; bool mIdleNow; bool mMoveNow; @@ -72,12 +79,24 @@ namespace MWMechanics MWWorld::TimeStamp mStartTime; + // allowed pathgrid nodes based on mDistance from the spawn point std::vector mAllowedNodes; ESM::Pathgrid::Point mCurrentNode; + bool mTrimCurrentNode; + void trimAllowedNodes(std::vector& nodes, + const PathFinder& pathfinder); PathFinder mPathFinder; - const ESM::Pathgrid *mPathgrid; + ObstacleCheck mObstacleCheck; + float mDoorCheckDuration; + int mStuckCount; + + // the z rotation angle (degrees) we want to reach + // used every frame when mRotate is true + float mTargetAngle; + bool mRotate; + float mReaction; // update some actions infrequently }; } diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index af58e9ee0..64b358b96 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -292,13 +292,13 @@ void MWMechanics::Alchemy::addPotion (const std::string& name) void MWMechanics::Alchemy::increaseSkill() { - MWWorld::Class::get (mAlchemist).skillUsageSucceeded (mAlchemist, ESM::Skill::Alchemy, 0); + mAlchemist.getClass().skillUsageSucceeded (mAlchemist, ESM::Skill::Alchemy, 0); } float MWMechanics::Alchemy::getChance() const { - const CreatureStats& creatureStats = MWWorld::Class::get (mAlchemist).getCreatureStats (mAlchemist); - const NpcStats& npcStats = MWWorld::Class::get (mAlchemist).getNpcStats (mAlchemist); + const CreatureStats& creatureStats = mAlchemist.getClass().getCreatureStats (mAlchemist); + const NpcStats& npcStats = mAlchemist.getClass().getNpcStats (mAlchemist); return (npcStats.getSkill (ESM::Skill::Alchemy).getModified() + @@ -331,7 +331,7 @@ void MWMechanics::Alchemy::setAlchemist (const MWWorld::Ptr& npc) mEffects.clear(); - MWWorld::ContainerStore& store = MWWorld::Class::get (npc).getContainerStore (npc); + MWWorld::ContainerStore& store = npc.getClass().getContainerStore (npc); for (MWWorld::ContainerStoreIterator iter (store.begin (MWWorld::ContainerStore::Type_Apparatus)); iter!=store.end(); ++iter) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 93c789af1..c9da912dd 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -407,32 +407,56 @@ MWWorld::ContainerStoreIterator getActiveWeapon(CreatureStats &stats, MWWorld::I return inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); } +void CharacterController::playDeath(float startpoint, CharacterState death) +{ + switch (death) + { + case CharState_SwimDeath: + mCurrentDeath = "swimdeath"; + break; + case CharState_DeathKnockDown: + mCurrentDeath = "deathknockdown"; + break; + case CharState_DeathKnockOut: + mCurrentDeath = "deathknockout"; + break; + default: + mCurrentDeath = "death" + Ogre::StringConverter::toString(death - CharState_Death1 + 1); + } + mDeathState = death; + + mPtr.getClass().getCreatureStats(mPtr).setDeathAnimation(mDeathState - CharState_Death1); + + // For dead actors, refreshCurrentAnims is no longer called, so we need to disable the movement state manually. + mMovementState = CharState_None; + mAnimation->disable(mCurrentMovement); + mCurrentMovement = ""; + + mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::Group_All, + false, 1.0f, "start", "stop", startpoint, 0); +} + void CharacterController::playRandomDeath(float startpoint) { if(MWBase::Environment::get().getWorld()->isSwimming(mPtr) && mAnimation->hasAnimation("swimdeath")) { mDeathState = CharState_SwimDeath; - mCurrentDeath = "swimdeath"; } else if (mHitState == CharState_KnockDown) { mDeathState = CharState_DeathKnockDown; - mCurrentDeath = "deathknockdown"; } else if (mHitState == CharState_KnockOut) { mDeathState = CharState_DeathKnockOut; - mCurrentDeath = "deathknockout"; } else { int selected=0; - mCurrentDeath = chooseRandomGroup("death", &selected); + chooseRandomGroup("death", &selected); mDeathState = static_cast(CharState_Death1 + (selected-1)); } - - mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::Group_All, - false, 1.0f, "start", "stop", startpoint, 0); + playDeath(startpoint, mDeathState); } CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim) @@ -454,7 +478,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim if(!mAnimation) return; - const MWWorld::Class &cls = MWWorld::Class::get(mPtr); + const MWWorld::Class &cls = mPtr.getClass(); if(cls.isActor()) { /* Accumulate along X/Y only for now, until we can figure out how we should @@ -489,12 +513,14 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim mIdleState = CharState_Idle; } - refreshCurrentAnims(mIdleState, mMovementState, true); if(mDeathState != CharState_None) { - playRandomDeath(1.0f); + int deathindex = mPtr.getClass().getCreatureStats(mPtr).getDeathAnimation(); + playDeath(1.0f, CharacterState(CharState_Death1 + deathindex)); } + else + refreshCurrentAnims(mIdleState, mMovementState, true); } CharacterController::~CharacterController() @@ -543,7 +569,7 @@ bool CharacterController::updateCreatureState() bool CharacterController::updateWeaponState() { - const MWWorld::Class &cls = MWWorld::Class::get(mPtr); + const MWWorld::Class &cls = mPtr.getClass(); CreatureStats &stats = cls.getCreatureStats(mPtr); WeaponType weaptype = WeapType_None; MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr); @@ -595,8 +621,8 @@ bool CharacterController::updateWeaponState() if(weapon != inv.end() && !(weaptype == WeapType_None && mWeaponType == WeapType_Spell)) { std::string soundid = (weaptype == WeapType_None) ? - MWWorld::Class::get(*weapon).getDownSoundId(*weapon) : - MWWorld::Class::get(*weapon).getUpSoundId(*weapon); + weapon->getClass().getDownSoundId(*weapon) : + weapon->getClass().getUpSoundId(*weapon); if(!soundid.empty()) { MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); @@ -740,10 +766,6 @@ bool CharacterController::updateWeaponState() MWBase::Environment::get().getWindowManager()->messageBox(resultMessage); if(!resultSound.empty()) MWBase::Environment::get().getSoundManager()->playSound(resultSound, 1.0f, 1.0f); - - // Set again, just to update the charge bar - if(item.getRefData().getCount()) - MWBase::Environment::get().getWindowManager()->setSelectedWeapon(item); } else if (ammunition) { @@ -981,7 +1003,7 @@ bool CharacterController::updateWeaponState() void CharacterController::update(float duration) { MWBase::World *world = MWBase::Environment::get().getWorld(); - const MWWorld::Class &cls = MWWorld::Class::get(mPtr); + const MWWorld::Class &cls = mPtr.getClass(); Ogre::Vector3 movement(0.0f); updateVisibility(); @@ -1010,10 +1032,20 @@ void CharacterController::update(float duration) bool flying = world->isFlying(mPtr); //Ogre::Vector3 vec = cls.getMovementVector(mPtr); Ogre::Vector3 vec(cls.getMovementSettings(mPtr).mPosition); - vec.normalise(); + if(vec.z > 0.0f) // to avoid slow-down when jumping + { + Ogre::Vector2 vecXY = Ogre::Vector2(vec.x, vec.y); + vecXY.normalise(); + vec.x = vecXY.x; + vec.y = vecXY.y; + } + else + vec.normalise(); + if(mHitState != CharState_None && mJumpState == JumpState_None) vec = Ogre::Vector3(0.0f); Ogre::Vector3 rot = cls.getRotationVector(mPtr); + mMovementSpeed = cls.getSpeed(mPtr); vec.x *= mMovementSpeed; @@ -1110,9 +1142,12 @@ void CharacterController::update(float duration) if(cls.isNpc()) { const NpcStats &stats = cls.getNpcStats(mPtr); - mult = gmst.find("fJumpMoveBase")->getFloat() + + static const float fJumpMoveBase = gmst.find("fJumpMoveBase")->getFloat(); + static const float fJumpMoveMult = gmst.find("fJumpMoveMult")->getFloat(); + + mult = fJumpMoveBase + (stats.getSkill(ESM::Skill::Acrobatics).getModified()/100.0f * - gmst.find("fJumpMoveMult")->getFloat()); + fJumpMoveMult); } vec.x *= mult; @@ -1122,14 +1157,7 @@ void CharacterController::update(float duration) else if(vec.z > 0.0f && mJumpState == JumpState_None) { // Started a jump. - float z = cls.getJump(mPtr); - if(vec.x == 0 && vec.y == 0) - vec = Ogre::Vector3(0.0f, 0.0f, z); - else - { - Ogre::Vector3 lat = Ogre::Vector3(vec.x, vec.y, 0.0f).normalisedCopy(); - vec = Ogre::Vector3(lat.x, lat.y, 1.0f) * z * 0.707f; - } + vec.z = cls.getJump(mPtr); // advance acrobatics if (mPtr.getRefData().getHandle() == "player") @@ -1179,7 +1207,7 @@ void CharacterController::update(float duration) } else { - if(!(vec.z > 0.0f)) + if(!(vec.z > 0.0f)) mJumpState = JumpState_None; vec.z = 0.0f; @@ -1369,9 +1397,9 @@ bool CharacterController::kill() { if( isDead() ) { - //player's death animation is over if( mPtr.getRefData().getHandle()=="player" && !isAnimPlaying(mCurrentDeath) ) { + //player's death animation is over MWBase::Environment::get().getStateManager()->askLoadRecent(); } return false; @@ -1441,14 +1469,14 @@ void CharacterController::updateVisibility() void CharacterController::determineAttackType() { - float * move = mPtr.getClass().getMovementSettings(mPtr).mPosition; + float *move = mPtr.getClass().getMovementSettings(mPtr).mPosition; if(mPtr.getClass().hasInventoryStore(mPtr)) { - if (move[0] && !move[1]) //sideway - mAttackType = "slash"; - else if (move[1]) //forward + if (move[1]) // forward-backward mAttackType = "thrust"; + else if (move[0]) //sideway + mAttackType = "slash"; else mAttackType = "chop"; } diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 5aea0210f..5cefe13bc 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -181,6 +181,7 @@ class CharacterController void updateVisibility(); + void playDeath(float startpoint, CharacterState death); void playRandomDeath(float startpoint = 0.0f); /// choose a random animation group with \a prefix and numeric suffix @@ -202,7 +203,9 @@ public: void skipAnim(); bool isAnimPlaying(const std::string &groupName); + /// @return false if the character has already been killed before bool kill(); + void resurrect(); bool isDead() const { return mDeathState != CharState_None; } diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index cdc12e210..69c3c08f7 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -102,10 +102,11 @@ namespace MWMechanics if (roll < x) { // Reduce shield durability by incoming damage - if (shield->getCellRef().mCharge == -1) - shield->getCellRef().mCharge = shield->getClass().getItemMaxHealth(*shield); - shield->getCellRef().mCharge -= std::min(shield->getCellRef().mCharge, int(damage)); - if (!shield->getCellRef().mCharge) + int shieldhealth = shield->getClass().getItemHealth(*shield); + + shieldhealth -= std::min(shieldhealth, int(damage)); + shield->getCellRef().setCharge(shieldhealth); + if (shieldhealth == 0) inv.unequipItem(*shield, blocker); // Reduce blocker fatigue @@ -146,8 +147,8 @@ namespace MWMechanics || weapon.get()->mBase->mData.mFlags & ESM::Weapon::Magical)) damage *= multiplier; - if (weapon.get()->mBase->mData.mFlags & ESM::Weapon::Silver - & actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) + if ((weapon.get()->mBase->mData.mFlags & ESM::Weapon::Silver) + && actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) damage *= MWBase::Environment::get().getWorld()->getStore().get().find("fWereWolfSilverWeaponDamageMult")->getFloat(); if (damage == 0 && attacker.getRefData().getHandle() == "player") diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index f81613ed1..7fd26c25c 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -11,14 +11,19 @@ namespace MWMechanics { + int CreatureStats::sActorId = 0; + CreatureStats::CreatureStats() : mLevel (0), mDead (false), mDied (false), mFriendlyHits (0), mTalkedTo (false), mAlarmed (false), mAttacked (false), mHostile (false), mAttackingOrSpell(false), mIsWerewolf(false), - mFallHeight(0), mRecalcDynamicStats(false), mKnockdown(false), mHitRecovery(false), mBlock(false), - mMovementFlags(0), mDrawState (DrawState_Nothing), mAttackStrength(0.f) + mFallHeight(0), mRecalcDynamicStats(false), mKnockdown(false), mKnockdownOneFrame(false), + mKnockdownOverOneFrame(false), mHitRecovery(false), mBlock(false), + mMovementFlags(0), mDrawState (DrawState_Nothing), mAttackStrength(0.f), + mLastRestock(0,0), mGoldPool(0), mActorId(-1), + mDeathAnimation(0) { for (int i=0; i<4; ++i) mAiSettings[i] = 0; @@ -98,7 +103,6 @@ namespace MWMechanics Stat CreatureStats::getAiSetting (AiSetting index) const { - assert (index>=0 && index<4); return mAiSettings[index]; } @@ -216,7 +220,6 @@ namespace MWMechanics void CreatureStats::setAiSetting (AiSetting index, Stat value) { - assert (index>=0 && index<4); mAiSettings[index] = value; } @@ -318,11 +321,9 @@ namespace MWMechanics bool CreatureStats::getCreatureTargetted() const { - std::string target; - if (mAiSequence.getCombatTarget(target)) + MWWorld::Ptr targetPtr; + if (mAiSequence.getCombatTarget(targetPtr)) { - MWWorld::Ptr targetPtr; - targetPtr = MWBase::Environment::get().getWorld()->getPtr(target, true); return targetPtr.getTypeName() == typeid(ESM::Creature).name(); } return false; @@ -348,20 +349,6 @@ namespace MWMechanics return mLastHitObject; } - bool CreatureStats::canUsePower(const std::string &power) const - { - std::map::const_iterator it = mUsedPowers.find(power); - if (it == mUsedPowers.end() || it->second + 24 <= MWBase::Environment::get().getWorld()->getTimeStamp()) - return true; - else - return false; - } - - void CreatureStats::usePower(const std::string &power) - { - mUsedPowers[power] = MWBase::Environment::get().getWorld()->getTimeStamp(); - } - void CreatureStats::addToFallHeight(float height) { mFallHeight += height; @@ -387,6 +374,8 @@ namespace MWMechanics void CreatureStats::setKnockedDown(bool value) { mKnockdown = value; + if(!value) //Resets the "OverOneFrame" flag + setKnockedDownOverOneFrame(false); } bool CreatureStats::getKnockedDown() const @@ -394,6 +383,23 @@ namespace MWMechanics return mKnockdown; } + void CreatureStats::setKnockedDownOneFrame(bool value) + { + mKnockdownOneFrame = value; + } + + bool CreatureStats::getKnockedDownOneFrame() const + { + return mKnockdownOneFrame; + } + + void CreatureStats::setKnockedDownOverOneFrame(bool value) { + mKnockdownOverOneFrame = value; + } + bool CreatureStats::getKnockedDownOverOneFrame() const { + return mKnockdownOverOneFrame; + } + void CreatureStats::setHitRecovery(bool value) { mHitRecovery = value; @@ -462,39 +468,139 @@ namespace MWMechanics void CreatureStats::writeState (ESM::CreatureStats& state) const { - for (int i=0; i<8; ++i) + for (int i=0; i mDynamic[3]; // health, magicka, fatigue @@ -41,6 +42,8 @@ namespace MWMechanics bool mHostile; bool mAttackingOrSpell; bool mKnockdown; + bool mKnockdownOneFrame; + bool mKnockdownOverOneFrame; bool mHitRecovery; bool mBlock; unsigned int mMovementFlags; @@ -53,15 +56,22 @@ namespace MWMechanics // Do we need to recalculate stats derived from attributes or other factors? bool mRecalcDynamicStats; - std::map mUsedPowers; + // For merchants: the last time items were restocked and gold pool refilled. + MWWorld::TimeStamp mLastRestock; - MWWorld::TimeStamp mTradeTime; // Relates to NPC gold reset delay + // The pool of merchant gold (not in inventory) + int mGoldPool; - int mGoldPool; // the pool of merchant gold not in inventory + int mActorId; + + // The index of the death animation that was played + unsigned char mDeathAnimation; protected: + // These two are only set by NpcStats, but they are declared in CreatureStats to prevent using virtual methods. bool mIsWerewolf; AttributeValue mWerewolfAttributes[8]; + int mLevel; public: @@ -82,9 +92,6 @@ namespace MWMechanics /// @return total fall height float land(); - bool canUsePower (const std::string& power) const; - void usePower (const std::string& power); - const AttributeValue & getAttribute(int index) const; const DynamicStat & getHealth() const; @@ -188,7 +195,14 @@ namespace MWMechanics float getEvasion() const; void setKnockedDown(bool value); + ///Returns true for the entire duration of the actor being knocked down bool getKnockedDown() const; + void setKnockedDownOneFrame(bool value); + ///Returns true only for the first frame of the actor being knocked out; used for "onKnockedOut" command + bool getKnockedDownOneFrame() const; + void setKnockedDownOverOneFrame(bool value); + ///Returns true for all but the first frame of being knocked out; used to know to not reset mKnockedDownOneFrame + bool getKnockedDownOverOneFrame() const; void setHitRecovery(bool value); bool getHitRecovery() const; void setBlock(bool value); @@ -215,21 +229,41 @@ namespace MWMechanics void setLastHitObject(const std::string &objectid); const std::string &getLastHitObject() const; - // Note, this is just a cache to avoid checking the whole container store every frame TODO: Put it somewhere else? + // Note, this is just a cache to avoid checking the whole container store every frame. We don't need to store it in saves. + // TODO: Put it somewhere else? std::set mBoundItems; - // Same as above - std::map mSummonedCreatures; + + // TODO: store in savegame + // TODO: encapsulate? + // + std::map mSummonedCreatures; + // Contains summoned creatures with an expired lifetime that have not been deleted yet. + std::vector mSummonGraveyard; void writeState (ESM::CreatureStats& state) const; void readState (const ESM::CreatureStats& state); - // Relates to NPC gold reset delay - void setTradeTime(MWWorld::TimeStamp tradeTime); - MWWorld::TimeStamp getTradeTime() const; + static void writeActorIdCounter (ESM::ESMWriter& esm); + static void readActorIdCounter (ESM::ESMReader& esm); + + void setLastRestockTime(MWWorld::TimeStamp tradeTime); + MWWorld::TimeStamp getLastRestockTime() const; void setGoldPool(int pool); int getGoldPool() const; + + unsigned char getDeathAnimation() const; + void setDeathAnimation(unsigned char index); + + int getActorId(); + ///< Will generate an actor ID, if the actor does not have one yet. + + bool matchesActorId (int id) const; + ///< Check if \a id matches the actor ID of *this (if the actor does not have an ID + /// assigned this function will return false). + + static void cleanup(); }; } diff --git a/apps/openmw/mwmechanics/drawstate.hpp b/apps/openmw/mwmechanics/drawstate.hpp index 5be00505c..7f59d8d78 100644 --- a/apps/openmw/mwmechanics/drawstate.hpp +++ b/apps/openmw/mwmechanics/drawstate.hpp @@ -6,9 +6,9 @@ namespace MWMechanics /// \note The _ suffix is required to avoid a collision with a Windoze macro. Die, Microsoft! Die! enum DrawState_ { - DrawState_Weapon = 0, - DrawState_Spell = 1, - DrawState_Nothing = 2 + DrawState_Nothing = 0, + DrawState_Weapon = 1, + DrawState_Spell = 2 }; } diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index 87337cdd7..f3f6795db 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -20,12 +20,10 @@ namespace MWMechanics if(!itemEmpty()) { mObjectType = mOldItemPtr.getTypeName(); - mOldItemId = mOldItemPtr.getCellRef().mRefID; } else { mObjectType=""; - mOldItemId=""; } } @@ -52,7 +50,7 @@ namespace MWMechanics bool Enchanting::create() { const MWWorld::Ptr& player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - MWWorld::ContainerStore& store = MWWorld::Class::get(player).getContainerStore(player); + MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); ESM::Enchantment enchantment; enchantment.mData.mCharge = getGemCharge(); @@ -67,7 +65,7 @@ namespace MWMechanics if(getEnchantChance() (RAND_MAX)*100) return false; - MWWorld::Class::get (mEnchanter).skillUsageSucceeded (mEnchanter, ESM::Skill::Enchant, 2); + mEnchanter.getClass().skillUsageSucceeded (mEnchanter, ESM::Skill::Enchant, 2); } if(mCastStyle==ESM::Enchantment::ConstantEffect) @@ -78,17 +76,13 @@ namespace MWMechanics enchantment.mData.mCost = getEnchantPoints(); enchantment.mEffects = mEffectList; - // Create a new item - MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), mOldItemId, 1); - const MWWorld::Ptr& newItemPtr = ref.getPtr(); - // Apply the enchantment const ESM::Enchantment *enchantmentPtr = MWBase::Environment::get().getWorld()->createRecord (enchantment); - MWWorld::Class::get(newItemPtr).applyEnchantment(newItemPtr, enchantmentPtr->mId, getGemCharge(), mNewItemName); + std::string newItemId = mOldItemPtr.getClass().applyEnchantment(mOldItemPtr, enchantmentPtr->mId, getGemCharge(), mNewItemName); // Add the new item to player inventory and remove the old one store.remove(mOldItemPtr, 1, player); - store.add(newItemPtr, 1, player); + store.add(newItemId, 1, player); if(!mSelfEnchanting) payForEnchantment(); @@ -212,7 +206,7 @@ namespace MWMechanics const float enchantCost = getEnchantPoints(); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - MWMechanics::NpcStats &stats = MWWorld::Class::get(player).getNpcStats(player); + MWMechanics::NpcStats &stats = player.getClass().getNpcStats(player); int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified(); /* @@ -240,9 +234,9 @@ namespace MWMechanics const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); if(soulEmpty()) return 0; - if(mSoulGemPtr.getCellRef().mSoul=="") + if(mSoulGemPtr.getCellRef().getSoul()=="") return 0; - const ESM::Creature* soul = store.get().find(mSoulGemPtr.getCellRef().mSoul); + const ESM::Creature* soul = store.get().find(mSoulGemPtr.getCellRef().getSoul()); return soul->mData.mSoul; } @@ -277,7 +271,7 @@ namespace MWMechanics float Enchanting::getEnchantChance() const { - const NpcStats& npcStats = MWWorld::Class::get (mEnchanter).getNpcStats (mEnchanter); + const NpcStats& npcStats = mEnchanter.getClass().getNpcStats (mEnchanter); float chance1 = (npcStats.getSkill (ESM::Skill::Enchant).getModified() + (0.25 * npcStats.getAttribute (ESM::Attribute::Intelligence).getModified()) @@ -295,7 +289,7 @@ namespace MWMechanics void Enchanting::payForEnchantment() const { const MWWorld::Ptr& player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - MWWorld::ContainerStore& store = MWWorld::Class::get(player).getContainerStore(player); + MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); store.remove(MWWorld::ContainerStore::sGoldId, getEnchantPrice(), player); } diff --git a/apps/openmw/mwmechanics/enchanting.hpp b/apps/openmw/mwmechanics/enchanting.hpp index ae0b25a4a..01ca1e0e1 100644 --- a/apps/openmw/mwmechanics/enchanting.hpp +++ b/apps/openmw/mwmechanics/enchanting.hpp @@ -21,7 +21,6 @@ namespace MWMechanics std::string mNewItemName; std::string mObjectType; - std::string mOldItemId; public: Enchanting(); diff --git a/apps/openmw/mwmechanics/levelledlist.hpp b/apps/openmw/mwmechanics/levelledlist.hpp index 120616f9f..5d9e29118 100644 --- a/apps/openmw/mwmechanics/levelledlist.hpp +++ b/apps/openmw/mwmechanics/levelledlist.hpp @@ -53,28 +53,26 @@ namespace MWMechanics return std::string(); std::string item = candidates[std::rand()%candidates.size()]; + // Vanilla doesn't fail on nonexistent items in levelled lists + if (!MWBase::Environment::get().getWorld()->getStore().find(Misc::StringUtils::lowerCase(item))) + { + std::cerr << "Warning: ignoring nonexistent item '" << item << "' in levelled list '" << levItem->mId << "'" << std::endl; + return std::string(); + } + // Is this another levelled item or a real item? - try + MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), item, 1); + if (ref.getPtr().getTypeName() != typeid(ESM::ItemLevList).name() + && ref.getPtr().getTypeName() != typeid(ESM::CreatureLevList).name()) { - MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), item, 1); - if (ref.getPtr().getTypeName() != typeid(ESM::ItemLevList).name() - && ref.getPtr().getTypeName() != typeid(ESM::CreatureLevList).name()) - { - return item; - } - else - { - if (ref.getPtr().getTypeName() == typeid(ESM::ItemLevList).name()) - return getLevelledItem(ref.getPtr().get()->mBase, failChance); - else - return getLevelledItem(ref.getPtr().get()->mBase, failChance); - } + return item; } - catch (std::logic_error& e) + else { - // Vanilla doesn't fail on nonexistent items in levelled lists - std::cerr << "Warning: ignoring nonexistent item '" << item << "'" << std::endl; - return std::string(); + if (ref.getPtr().getTypeName() == typeid(ESM::ItemLevList).name()) + return getLevelledItem(ref.getPtr().get()->mBase, failChance); + else + return getLevelledItem(ref.getPtr().get()->mBase, failChance); } } diff --git a/apps/openmw/mwmechanics/magiceffects.hpp b/apps/openmw/mwmechanics/magiceffects.hpp index 45abda21d..4fd5e159a 100644 --- a/apps/openmw/mwmechanics/magiceffects.hpp +++ b/apps/openmw/mwmechanics/magiceffects.hpp @@ -56,7 +56,7 @@ namespace MWMechanics struct EffectSourceVisitor { virtual void visit (MWMechanics::EffectKey key, - const std::string& sourceName, const std::string& casterHandle, + const std::string& sourceName, int casterActorId, float magnitude, float remainingTime = -1) = 0; }; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 3a26ea7dc..900ea72ca 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -13,6 +13,8 @@ #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" +#include "../mwmechanics/aicombat.hpp" + #include #include "spellcasting.hpp" @@ -22,20 +24,20 @@ namespace /// @return is \a ptr allowed to take/use \a item or is it a crime? bool isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, MWWorld::Ptr& victim) { - const std::string& owner = item.getCellRef().mOwner; - bool isOwned = !owner.empty(); + const std::string& owner = item.getCellRef().getOwner(); + bool isOwned = !owner.empty() && owner != "player"; - const std::string& faction = item.getCellRef().mFaction; + const std::string& faction = item.getCellRef().getFaction(); bool isFactionOwned = false; - if (!faction.empty()) + if (!faction.empty() && ptr.getClass().isNpc()) { const std::map& factions = ptr.getClass().getNpcStats(ptr).getFactionRanks(); if (factions.find(Misc::StringUtils::lowerCase(faction)) == factions.end()) isFactionOwned = true; } - if (!item.getCellRef().mOwner.empty()) - victim = MWBase::Environment::get().getWorld()->searchPtr(item.getCellRef().mOwner, true); + if (!item.getCellRef().getOwner().empty()) + victim = MWBase::Environment::get().getWorld()->searchPtr(item.getCellRef().getOwner(), true); return (!isOwned && !isFactionOwned); } @@ -47,8 +49,8 @@ namespace MWMechanics { MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayerPtr(); - MWMechanics::CreatureStats& creatureStats = MWWorld::Class::get (ptr).getCreatureStats (ptr); - MWMechanics::NpcStats& npcStats = MWWorld::Class::get (ptr).getNpcStats (ptr); + MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats (ptr); + MWMechanics::NpcStats& npcStats = ptr.getClass().getNpcStats (ptr); const ESM::NPC *player = ptr.get()->mBase; @@ -213,7 +215,7 @@ namespace MWMechanics void MechanicsManager::add(const MWWorld::Ptr& ptr) { - if(MWWorld::Class::get(ptr).isActor()) + if(ptr.getClass().isActor()) mActors.addActor(ptr); else mObjects.addObject(ptr); @@ -232,7 +234,7 @@ namespace MWMechanics if(old == mWatched) mWatched = ptr; - if(MWWorld::Class::get(ptr).isActor()) + if(ptr.getClass().isActor()) mActors.updateActor(old, ptr); else mObjects.updateObject(old, ptr); @@ -298,13 +300,15 @@ namespace MWMechanics if(stats.getTimeToStartDrowning() != mWatchedStats.getTimeToStartDrowning()) { + const float fHoldBreathTime = MWBase::Environment::get().getWorld()->getStore().get() + .find("fHoldBreathTime")->getFloat(); mWatchedStats.setTimeToStartDrowning(stats.getTimeToStartDrowning()); - if(stats.getTimeToStartDrowning() >= 20.0f) + if(stats.getTimeToStartDrowning() >= fHoldBreathTime) winMgr->setDrowningBarVisibility(false); else { winMgr->setDrowningBarVisibility(true); - winMgr->setDrowningTimeLeft(stats.getTimeToStartDrowning()); + winMgr->setDrowningTimeLeft(stats.getTimeToStartDrowning(), fHoldBreathTime); } } @@ -325,6 +329,23 @@ namespace MWMechanics winMgr->updateSkillArea(); winMgr->setValue("level", stats.getLevel()); + + // Update the equipped weapon icon + MWWorld::InventoryStore& inv = mWatched.getClass().getInventoryStore(mWatched); + MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (weapon == inv.end()) + winMgr->unsetSelectedWeapon(); + else + winMgr->setSelectedWeapon(*weapon); + + // Update the selected spell icon + MWWorld::ContainerStoreIterator enchantItem = inv.getSelectedEnchantItem(); + if (enchantItem != inv.end()) + winMgr->setSelectedEnchantItem(*enchantItem); + else if (!winMgr->getSelectedSpell().empty()) + winMgr->setSelectedSpell(winMgr->getSelectedSpell(), int(MWMechanics::getSpellSuccessChance(winMgr->getSelectedSpell(), mWatched))); + else + winMgr->unsetSelectedSpell(); } if (mUpdatePlayer) @@ -364,7 +385,7 @@ namespace MWMechanics // have been made for them. Make sure they're properly updated. MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayerPtr(); mActors.removeActor(ptr); - mActors.addActor(ptr); + mActors.addActor(ptr, true); } mActors.update(duration, paused); @@ -454,13 +475,13 @@ namespace MWMechanics int MechanicsManager::getDerivedDisposition(const MWWorld::Ptr& ptr) { - const MWMechanics::NpcStats& npcSkill = MWWorld::Class::get(ptr).getNpcStats(ptr); + const MWMechanics::NpcStats& npcSkill = ptr.getClass().getNpcStats(ptr); float x = npcSkill.getBaseDisposition(); MWWorld::LiveCellRef* npc = ptr.get(); MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWWorld::LiveCellRef* player = playerPtr.get(); - const MWMechanics::NpcStats &playerStats = MWWorld::Class::get(playerPtr).getNpcStats(playerPtr); + const MWMechanics::NpcStats &playerStats = playerPtr.getClass().getNpcStats(playerPtr); if (Misc::StringUtils::ciEqual(npc->mBase->mRace, player->mBase->mRace)) x += MWBase::Environment::get().getWorld()->getStore().get().find("fDispRaceMod")->getFloat(); @@ -477,27 +498,24 @@ namespace MWMechanics if (playerStats.getFactionRanks().find(npcFaction) != playerStats.getFactionRanks().end()) { - for(std::vector::const_iterator it = MWBase::Environment::get().getWorld()->getStore().get().find(npcFaction)->mReactions.begin(); - it != MWBase::Environment::get().getWorld()->getStore().get().find(npcFaction)->mReactions.end(); ++it) + if (!playerStats.getExpelled(npcFaction)) { - if(Misc::StringUtils::ciEqual(it->mFaction, npcFaction) - && !playerStats.getExpelled(it->mFaction)) - reaction = it->mReaction; + reaction = playerStats.getFactionReputation(npcFaction); + + rank = playerStats.getFactionRanks().find(npcFaction)->second; } - rank = playerStats.getFactionRanks().find(npcFaction)->second; } - else if (npcFaction != "") + else if (!npcFaction.empty()) { - for(std::vector::const_iterator it = MWBase::Environment::get().getWorld()->getStore().get().find(npcFaction)->mReactions.begin(); - it != MWBase::Environment::get().getWorld()->getStore().get().find(npcFaction)->mReactions.end();++it) + std::map::const_iterator playerFactionIt = playerStats.getFactionRanks().begin(); + for (; playerFactionIt != playerStats.getFactionRanks().end(); ++playerFactionIt) { - if(playerStats.getFactionRanks().find(Misc::StringUtils::lowerCase(it->mFaction)) != playerStats.getFactionRanks().end() ) - { - if(it->mReaction < reaction) - reaction = it->mReaction; - } + std::string itFaction = playerFactionIt->first; + + int itReaction = MWBase::Environment::get().getDialogueManager()->getFactionReaction(npcFaction, itFaction); + if (playerFactionIt == playerStats.getFactionRanks().begin() || itReaction < reaction) + reaction = itReaction; } - rank = 0; } else { @@ -526,10 +544,10 @@ namespace MWMechanics if (ptr.getTypeName() == typeid(ESM::Creature).name()) return basePrice; - const MWMechanics::NpcStats &sellerStats = MWWorld::Class::get(ptr).getNpcStats(ptr); + const MWMechanics::NpcStats &sellerStats = ptr.getClass().getNpcStats(ptr); MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld()->getPlayerPtr(); - const MWMechanics::NpcStats &playerStats = MWWorld::Class::get(playerPtr).getNpcStats(playerPtr); + const MWMechanics::NpcStats &playerStats = playerPtr.getClass().getNpcStats(playerPtr); // I suppose the temporary disposition change _has_ to be considered here, // otherwise one would get different prices when exiting and re-entering the dialogue window... @@ -571,10 +589,10 @@ namespace MWMechanics const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); - MWMechanics::NpcStats& npcStats = MWWorld::Class::get(npc).getNpcStats(npc); + MWMechanics::NpcStats& npcStats = npc.getClass().getNpcStats(npc); MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld()->getPlayerPtr(); - const MWMechanics::NpcStats &playerStats = MWWorld::Class::get(playerPtr).getNpcStats(playerPtr); + const MWMechanics::NpcStats &playerStats = playerPtr.getClass().getNpcStats(playerPtr); float persTerm = playerStats.getAttribute(ESM::Attribute::Personality).getModified() / gmst.find("fPersonalityMod")->getFloat(); @@ -645,8 +663,6 @@ namespace MWMechanics 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))))); - // TODO: initiate combat and quit dialogue if fight rating is too high - // or should setAiSetting handle this? npcStats.setAiSetting (MWMechanics::CreatureStats::AI_Fight, std::max(0, std::min(100, fight + int(std::min(-iPerMinChange, -s))))); } @@ -720,27 +736,27 @@ namespace MWMechanics void MechanicsManager::forceStateUpdate(const MWWorld::Ptr &ptr) { - if(MWWorld::Class::get(ptr).isActor()) + if(ptr.getClass().isActor()) mActors.forceStateUpdate(ptr); } void MechanicsManager::playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number) { - if(MWWorld::Class::get(ptr).isActor()) + if(ptr.getClass().isActor()) mActors.playAnimationGroup(ptr, groupName, mode, number); else mObjects.playAnimationGroup(ptr, groupName, mode, number); } void MechanicsManager::skipAnimation(const MWWorld::Ptr& ptr) { - if(MWWorld::Class::get(ptr).isActor()) + if(ptr.getClass().isActor()) mActors.skipAnimation(ptr); else mObjects.skipAnimation(ptr); } bool MechanicsManager::checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string &groupName) { - if(MWWorld::Class::get(ptr).isActor()) + if(ptr.getClass().isActor()) return mActors.checkAnimationPlaying(ptr, groupName); else return false; @@ -772,6 +788,11 @@ namespace MWMechanics bool MechanicsManager::sleepInBed(const MWWorld::Ptr &ptr, const MWWorld::Ptr &bed) { + if(MWBase::Environment::get().getWorld()->getPlayer().isInCombat()) { + MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage2}"); + return true; + } + MWWorld::Ptr victim; if (isAllowedToUse(ptr, bed, victim)) return false; @@ -801,13 +822,13 @@ namespace MWMechanics commitCrime(ptr, victim, OT_Theft, item.getClass().getValue(item) * count); } - bool MechanicsManager::commitCrime(const MWWorld::Ptr &ptr, const MWWorld::Ptr &victim, OffenseType type, int arg) + bool MechanicsManager::commitCrime(const MWWorld::Ptr &player, const MWWorld::Ptr &victim, OffenseType type, int arg) { // NOTE: int arg can be from itemTaken() so DON'T modify it, since it is // passed to reportCrime later on in this function. - // Only player can commit crime and no victimless crimes - if (ptr.getRefData().getHandle() != "player" || victim.isEmpty()) + // Only player can commit crime + if (player.getRefData().getHandle() != "player") return false; const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); @@ -824,62 +845,80 @@ namespace MWMechanics alarm = esmStore.get().find("iAlarmKilling")->getInt(); else if (type == OT_Theft) alarm = esmStore.get().find("iAlarmStealing")->getInt(); + else + return false; // Innocent until proven guilty bool reported = false; - // Find all the NPCs within the alarm radius + // Find all the actors within the alarm radius std::vector neighbors; - mActors.getObjectsInRange(Ogre::Vector3(ptr.getRefData().getPosition().pos), + mActors.getObjectsInRange(Ogre::Vector3(player.getRefData().getPosition().pos), esmStore.get().find("fAlarmRadius")->getInt(), neighbors); - // Find an actor who witnessed the crime + int id = MWBase::Environment::get().getWorld()->getPlayer().getNewCrimeId(); + + // Find actors who witnessed the crime for (std::vector::iterator it = neighbors.begin(); it != neighbors.end(); ++it) - { - if (*it == ptr) continue; // not the player + { + if (*it == player) continue; // not the player // Was the crime seen? - if ( ( MWBase::Environment::get().getWorld()->getLOS(ptr, *it) && awarenessCheck(ptr, *it) ) || - type == OT_Assault ) + if (MWBase::Environment::get().getWorld()->getLOS(player, *it) && awarenessCheck(player, *it) ) { + // TODO: Add more messages + if (type == OT_Theft) + MWBase::Environment::get().getDialogueManager()->say(*it, "thief"); + + if (*it == victim) + { + // Self-defense + // The victim is aware of the criminal/assailant. If being assaulted, fight back now + // (regardless of whether the assault is reported or not) + // This applies to both NPCs and creatures + + // ... except if this is a guard: then the player is given a chance to pay a fine / go to jail instead + if (type == OT_Assault && !it->getClass().isClass(*it, "guard")) + MWBase::Environment::get().getMechanicsManager()->startCombat(victim, player); + } + + // Crime reporting only applies to NPCs + if (!it->getClass().isNpc()) + continue; // Will the witness report the crime? if (it->getClass().getCreatureStats(*it).getAiSetting(CreatureStats::AI_Alarm).getBase() >= alarm) { reported = true; - int id = MWBase::Environment::get().getWorld()->getPlayer().getNewCrimeId(); // Tell everyone, including yourself for (std::vector::iterator it1 = neighbors.begin(); it1 != neighbors.end(); ++it1) - { - if (*it1 == ptr) continue; // not the player - - // TODO: Add more messages - if (type == OT_Theft) - MWBase::Environment::get().getDialogueManager()->say(*it1, "thief"); - else if (type == OT_Assault) - MWBase::Environment::get().getDialogueManager()->say(*it1, "attack"); - + { + if ( *it1 == player + || !it1->getClass().isNpc()) continue; // not the player and is an NPC + // Will other witnesses paticipate in crime - if ( it1->getClass().getCreatureStats(*it1).getAiSetting(CreatureStats::AI_Alarm).getBase() >= alarm + if ( it1->getClass().getCreatureStats(*it1).getAiSetting(CreatureStats::AI_Alarm).getBase() >= alarm || type == OT_Assault ) { it1->getClass().getNpcStats(*it1).setCrimeId(id); } + + // Mark as Alarmed for dialogue + it1->getClass().getCreatureStats(*it1).setAlarmed(true); } - break; // Someone saw the crime and everyone has been told } } } if (reported) - reportCrime(ptr, victim, type, arg); + reportCrime(player, victim, type, arg); return reported; } void MechanicsManager::reportCrime(const MWWorld::Ptr &ptr, const MWWorld::Ptr &victim, OffenseType type, int arg) { const MWWorld::Store& store = MWBase::Environment::get().getWorld()->getStore().get(); - + // Bounty for each type of crime if (type == OT_Trespassing || type == OT_SleepingInOwnedBed) arg = store.find("iCrimeTresspass")->getInt(); @@ -978,14 +1017,34 @@ namespace MWMechanics return (roll >= target); } + void MechanicsManager::startCombat(const MWWorld::Ptr &ptr, const MWWorld::Ptr &target) + { + ptr.getClass().getCreatureStats(ptr).getAiSequence().stack(MWMechanics::AiCombat(target), ptr); + if (target == MWBase::Environment::get().getWorld()->getPlayerPtr()) + ptr.getClass().getCreatureStats(ptr).setHostile(true); + + // Must be done after the target is set up, so that CreatureTargetted dialogue filter works properly + if (ptr.getClass().isNpc()) + MWBase::Environment::get().getDialogueManager()->say(ptr, "attack"); + } + void MechanicsManager::getObjectsInRange(const Ogre::Vector3 &position, float radius, std::vector &objects) { mActors.getObjectsInRange(position, radius, objects); mObjects.getObjectsInRange(position, radius, objects); } + void MechanicsManager::getActorsInRange(const Ogre::Vector3 &position, float radius, std::vector &objects) + { + mActors.getObjectsInRange(position, radius, objects); + } + std::list MechanicsManager::getActorsFollowing(const MWWorld::Ptr& actor) { return mActors.getActorsFollowing(actor); } + + std::list MechanicsManager::getActorsFighting(const MWWorld::Ptr& actor) { + return mActors.getActorsFighting(actor); + } } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 5dd758377..dcd12ee14 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -105,6 +105,9 @@ namespace MWMechanics /// Check if \a observer is potentially aware of \a ptr. Does not do a line of sight check! virtual bool awarenessCheck (const MWWorld::Ptr& ptr, const MWWorld::Ptr& observer); + /// Makes \a ptr fight \a target. Also shouts a combat taunt. + virtual void startCombat (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target); + /** * @brief Commit a crime. If any actors witness the crime and report it, * reportCrime will be called automatically. @@ -120,7 +123,7 @@ namespace MWMechanics /// Utility to check if opening (i.e. unlocking) this object is illegal and calling commitCrime if so virtual void objectOpened (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item); /// Attempt sleeping in a bed. If this is illegal, call commitCrime. - /// @return was it illegal, and someone saw you doing it? + /// @return was it illegal, and someone saw you doing it? Also returns fail when enemies are nearby virtual bool sleepInBed (const MWWorld::Ptr& ptr, const MWWorld::Ptr& bed); virtual void forceStateUpdate(const MWWorld::Ptr &ptr); @@ -134,9 +137,12 @@ namespace MWMechanics virtual void updateMagicEffects (const MWWorld::Ptr& ptr); virtual void getObjectsInRange (const Ogre::Vector3& position, float radius, std::vector& objects); + virtual void getActorsInRange(const Ogre::Vector3 &position, float radius, std::vector &objects); virtual std::list getActorsFollowing(const MWWorld::Ptr& actor); + virtual std::list getActorsFighting(const MWWorld::Ptr& actor); + virtual bool toggleAI(); virtual bool isAIActive(); diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index e11e3b0c4..d50f2c5ae 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -432,9 +432,9 @@ float MWMechanics::NpcStats::getTimeToStartDrowning() const { return mTimeToStartDrowning; } + void MWMechanics::NpcStats::setTimeToStartDrowning(float time) { - assert(time>=0 && time<=20); mTimeToStartDrowning=time; } @@ -446,11 +446,16 @@ void MWMechanics::NpcStats::writeState (ESM::NpcStats& state) const state.mDisposition = mDisposition; - for (int i=0; i<27; ++i) + for (int i=0; isecond.mExpelled) mExpelled.insert (iter->first); - if (iter->second.mRank) - mFactionRank.insert (std::make_pair (iter->first, iter->second.mRank)); + if (iter->second.mRank >= 0) + mFactionRank[iter->first] = iter->second.mRank; if (iter->second.mReputation) - mFactionReputation.insert (std::make_pair (iter->first, iter->second.mReputation)); + mFactionReputation[iter->first] = iter->second.mReputation; } mDisposition = state.mDisposition; - for (int i=0; i<27; ++i) + for (int i=0; i::const_iterator iter (state.mUsedIds.begin()); diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp index 0ae596a54..185a58b94 100644 --- a/apps/openmw/mwmechanics/npcstats.hpp +++ b/apps/openmw/mwmechanics/npcstats.hpp @@ -31,8 +31,8 @@ namespace MWMechanics std::map mFactionRank; int mDisposition; - SkillValue mSkill[27]; - SkillValue mWerewolfSkill[27]; + SkillValue mSkill[ESM::Skill::Length]; + SkillValue mWerewolfSkill[ESM::Skill::Length]; int mBounty; std::set mExpelled; std::map mFactionReputation; @@ -40,7 +40,6 @@ namespace MWMechanics int mCrimeId; int mWerewolfKills; int mProfit; - float mAttackStrength; int mLevelProgress; // 0-10 diff --git a/apps/openmw/mwmechanics/obstacle.cpp b/apps/openmw/mwmechanics/obstacle.cpp new file mode 100644 index 000000000..55ebfeab5 --- /dev/null +++ b/apps/openmw/mwmechanics/obstacle.cpp @@ -0,0 +1,172 @@ +#include "obstacle.hpp" + +#include + +#include "../mwbase/world.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/cellstore.hpp" + +namespace MWMechanics +{ + // NOTE: determined empirically but probably need further tweaking + static const float DIST_SAME_SPOT = 1.8f; + static const float DURATION_SAME_SPOT = 1.0f; + static const float DURATION_TO_EVADE = 0.4f; + + // Proximity check function for interior doors. Given that most interior cells + // do not have many doors performance shouldn't be too much of an issue. + // + // Limitation: there can be false detections, and does not test whether the + // actor is facing the door. + bool proximityToDoor(const MWWorld::Ptr& actor, float minSqr, bool closed) + { + if(getNearbyDoor(actor, minSqr, closed)!=MWWorld::Ptr()) + return true; + else + return false; + } + + MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor, float minSqr, bool closed) + { + MWWorld::CellStore *cell = actor.getCell(); + + if(cell->getCell()->isExterior()) + return MWWorld::Ptr(); // check interior cells only + + // Check all the doors in this cell + MWWorld::CellRefList& doors = cell->get(); + MWWorld::CellRefList::List& refList = doors.mList; + MWWorld::CellRefList::List::iterator it = refList.begin(); + Ogre::Vector3 pos(actor.getRefData().getPosition().pos); + + /// TODO: How to check whether the actor is facing a door? Below code is for + /// the player, perhaps it can be adapted. + //MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getFacedObject(); + //if(!ptr.isEmpty()) + //std::cout << "faced door " << ptr.getClass().getName(ptr) << std::endl; + + /// TODO: The in-game observation of rot[2] value seems to be the + /// opposite of the code in World::activateDoor() ::confused:: + for (; it != refList.end(); ++it) + { + MWWorld::LiveCellRef& ref = *it; + if(pos.squaredDistance(Ogre::Vector3(ref.mData.getPosition().pos)) < minSqr) + if((closed && ref.mData.getLocalRotation().rot[2] == 0) || + (!closed && ref.mData.getLocalRotation().rot[2] >= 1)) + { + return MWWorld::Ptr(&ref, actor.getCell()); // found, stop searching + } + } + return MWWorld::Ptr(); // none found + } + + ObstacleCheck::ObstacleCheck(): + mPrevX(0) // to see if the moved since last time + , mPrevY(0) + , mDistSameSpot(-1) // avoid calculating it each time + , mWalkState(State_Norm) + , mStuckDuration(0) + , mEvadeDuration(0) + { + } + + void ObstacleCheck::clear() + { + mWalkState = State_Norm; + mStuckDuration = 0; + mEvadeDuration = 0; + } + + bool ObstacleCheck::isNormalState() const + { + return mWalkState == State_Norm; + } + + /* + * input - actor, duration (time since last check) + * output - true if evasive action needs to be taken + * + * Walking state transitions (player greeting check not shown): + * + * MoveNow <------------------------------------+ + * | d| + * | | + * +-> State_Norm <---> State_CheckStuck --> State_Evade + * ^ ^ | f ^ | t ^ | | + * | | | | | | | | + * | +---+ +---+ +---+ | u + * | any < t < u | + * +--------------------------------------------+ + * + * f = one reaction time + * d = proximity to a closed door + * t = how long before considered stuck + * u = how long to move sideways + * + * DIST_SAME_SPOT is calibrated for movement speed of around 150. + * A rat has walking speed of around 30, so we need to adjust for + * that. + */ + bool ObstacleCheck::check(const MWWorld::Ptr& actor, float duration) + { + const MWWorld::Class& cls = actor.getClass(); + ESM::Position pos = actor.getRefData().getPosition(); + + if(mDistSameSpot == -1) + mDistSameSpot = DIST_SAME_SPOT * (cls.getSpeed(actor) / 150); + + bool samePosition = (abs(pos.pos[0] - mPrevX) < mDistSameSpot) && + (abs(pos.pos[1] - mPrevY) < mDistSameSpot); + // update position + mPrevX = pos.pos[0]; + mPrevY = pos.pos[1]; + + switch(mWalkState) + { + case State_Norm: + { + if(!samePosition) + break; + else + mWalkState = State_CheckStuck; + } + /* FALL THROUGH */ + case State_CheckStuck: + { + if(!samePosition) + { + mWalkState = State_Norm; + mStuckDuration = 0; + break; + } + else + { + mStuckDuration += duration; + // consider stuck only if position unchanges for a period + if(mStuckDuration < DURATION_SAME_SPOT) + break; // still checking, note duration added to timer + else + { + mStuckDuration = 0; + mWalkState = State_Evade; + } + } + } + /* FALL THROUGH */ + case State_Evade: + { + mEvadeDuration += duration; + if(mEvadeDuration < DURATION_TO_EVADE) + return true; + else + { + // tried to evade, assume all is ok and start again + mWalkState = State_Norm; + mEvadeDuration = 0; + } + } + /* NO DEFAULT CASE */ + } + return false; // no obstacles to evade (yet) + } +} diff --git a/apps/openmw/mwmechanics/obstacle.hpp b/apps/openmw/mwmechanics/obstacle.hpp new file mode 100644 index 000000000..76ab9d029 --- /dev/null +++ b/apps/openmw/mwmechanics/obstacle.hpp @@ -0,0 +1,63 @@ +#ifndef OPENMW_MECHANICS_OBSTACLE_H +#define OPENMW_MECHANICS_OBSTACLE_H + +//#include "../mwbase/world.hpp" +//#include "../mwworld/class.hpp" +#include "../mwworld/cellstore.hpp" + +namespace MWWorld +{ + class Ptr; +} + +namespace MWMechanics +{ + /// NOTE: determined empirically based on in-game behaviour + static const float MIN_DIST_TO_DOOR_SQUARED = 128*128; + + /// tests actor's proximity to a closed door by default + bool proximityToDoor(const MWWorld::Ptr& actor, + float minSqr = MIN_DIST_TO_DOOR_SQUARED, + bool closed = true); + + /// Returns door pointer within range. No guarentee is given as too which one + /** \return Pointer to the door, or NULL if none exists **/ + MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor, + float minSqr = MIN_DIST_TO_DOOR_SQUARED, + bool closed = true); + + class ObstacleCheck + { + public: + ObstacleCheck(); + + // Clear the timers and set the state machine to default + void clear(); + + bool isNormalState() const; + + // Returns true if there is an obstacle and an evasive action + // should be taken + bool check(const MWWorld::Ptr& actor, float duration); + + private: + + // for checking if we're stuck (ignoring Z axis) + float mPrevX; + float mPrevY; + + enum WalkState + { + State_Norm, + State_CheckStuck, + State_Evade + }; + WalkState mWalkState; + + float mStuckDuration; // accumulate time here while in same spot + float mEvadeDuration; + float mDistSameSpot; // take account of actor's speed + }; +} + +#endif diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 730b8cb92..d77a35ea4 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -11,30 +11,6 @@ namespace { - float distanceZCorrected(ESM::Pathgrid::Point point, float x, float y, float z) - { - x -= point.mX; - y -= point.mY; - z -= point.mZ; - return sqrt(x * x + y * y + 0.1 * z * z); - } - - float distance(ESM::Pathgrid::Point point, float x, float y, float z) - { - x -= point.mX; - y -= point.mY; - z -= point.mZ; - return sqrt(x * x + y * y + z * z); - } - - float distance(ESM::Pathgrid::Point a, ESM::Pathgrid::Point b) - { - float x = a.mX - b.mX; - float y = a.mY - b.mY; - float z = a.mZ - b.mZ; - return sqrt(x * x + y * y + z * z); - } - // Slightly cheaper version for comparisons. // Caller needs to be careful for very short distances (i.e. less than 1) // or when accumuating the results i.e. (a + b)^2 != a^2 + b^2 @@ -100,8 +76,11 @@ namespace } } } - if(start == closestReachableIndex) - closestReachableIndex = -1; // couldn't find anyting other than start + // AiWander has logic that depends on whether a path was created, deleting + // allowed nodes if not. Hence a path needs to be created even if the start + // and the end points are the same. + //if(start == closestReachableIndex) + //closestReachableIndex = -1; // couldn't find anyting other than start return std::pair (closestReachableIndex, closestReachableIndex == closestIndex); @@ -111,6 +90,30 @@ namespace namespace MWMechanics { + float distanceZCorrected(ESM::Pathgrid::Point point, float x, float y, float z) + { + x -= point.mX; + y -= point.mY; + z -= point.mZ; + return sqrt(x * x + y * y + 0.1 * z * z); + } + + float distance(ESM::Pathgrid::Point point, float x, float y, float z) + { + x -= point.mX; + y -= point.mY; + z -= point.mZ; + return sqrt(x * x + y * y + z * z); + } + + float distance(ESM::Pathgrid::Point a, ESM::Pathgrid::Point b) + { + float x = a.mX - b.mX; + float y = a.mY - b.mY; + float z = a.mZ - b.mZ; + return sqrt(x * x + y * y + z * z); + } + PathFinder::PathFinder() : mIsPathConstructed(false), mPathgrid(NULL), @@ -224,6 +227,18 @@ namespace MWMechanics // this shouldn't really happen, but just in case if(endNode.first != -1) { + // AiWander has logic that depends on whether a path was created, + // deleting allowed nodes if not. Hence a path needs to be created + // even if the start and the end points are the same. + // NOTE: aStarSearch will return an empty path if the start and end + // nodes are the same + if(startNode == endNode.first) + { + mPath.push_back(endPoint); + mIsPathConstructed = true; + return; + } + mPath = mCell->aStarSearch(startNode, endNode.first); if(!mPath.empty()) @@ -310,22 +325,22 @@ namespace MWMechanics } // used by AiCombat, see header for the rationale - void PathFinder::syncStart(const std::list &path) + bool PathFinder::syncStart(const std::list &path) { if (mPath.size() < 2) - return; //nothing to pop + return false; //nothing to pop + std::list::const_iterator oldStart = path.begin(); std::list::iterator iter = ++mPath.begin(); if( (*iter).mX == oldStart->mX && (*iter).mY == oldStart->mY - && (*iter).mZ == oldStart->mZ - && (*iter).mAutogenerated == oldStart->mAutogenerated - && (*iter).mConnectionNum == oldStart->mConnectionNum ) + && (*iter).mZ == oldStart->mZ) { mPath.pop_front(); + return true; } - + return false; } } diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index eb093ad69..603a04f8c 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -13,6 +13,8 @@ namespace MWWorld namespace MWMechanics { + float distance(ESM::Pathgrid::Point point, float x, float y, float); + float distance(ESM::Pathgrid::Point a, ESM::Pathgrid::Point b); class PathFinder { public: @@ -57,16 +59,20 @@ namespace MWMechanics return mPath.size(); } - std::list getPath() const + const std::list& getPath() const { return mPath; } - // When first point of newly created path is the nearest to actor point, - // then a situation can occure when this point is undesirable - // (if the 2nd point of new path == the 1st point of old path) - // This functions deletes that point. - void syncStart(const std::list &path); + /** Synchronize new path with old one to avoid visiting 1 waypoint 2 times + @note + If the first point is chosen as the nearest one + the situation can occur when the 1st point of the new path is undesirable + (i.e. the 2nd point of new path == the 1st point of old path). + @param path - old path + @return true if such point was found and deleted + */ + bool syncStart(const std::list &path); void addPointToPath(ESM::Pathgrid::Point &point) { diff --git a/apps/openmw/mwmechanics/pathgrid.cpp b/apps/openmw/mwmechanics/pathgrid.cpp index ec647c1cb..c3fa0a662 100644 --- a/apps/openmw/mwmechanics/pathgrid.cpp +++ b/apps/openmw/mwmechanics/pathgrid.cpp @@ -197,11 +197,11 @@ namespace MWMechanics // both of these are set to zero in the constructor //mSCCId = 0; // how many strongly connected components in this cell //mSCCIndex = 0; - int pointsSize = mPathgrid->mPoints.size(); + int pointsSize = static_cast (mPathgrid->mPoints.size()); mSCCPoint.resize(pointsSize, std::pair (-1, -1)); mSCCStack.reserve(pointsSize); - for(int v = 0; v < static_cast (pointsSize); v++) + for(int v = 0; v < pointsSize; v++) { if(mSCCPoint[v].first == -1) // undefined (haven't visited) recursiveStrongConnect(v); @@ -249,7 +249,7 @@ namespace MWMechanics return path; // there is no path, return an empty path } - int graphSize = mGraph.size(); + int graphSize = static_cast (mGraph.size()); std::vector gScore (graphSize, -1); std::vector fScore (graphSize, -1); std::vector graphParent (graphSize, -1); @@ -296,7 +296,7 @@ namespace MWMechanics // add this edge to openset, lowest cost goes to the front // TODO: if this causes performance problems a hash table may help std::list::iterator it = openset.begin(); - for(it = openset.begin(); it!= openset.end(); it++) + for(it = openset.begin(); it!= openset.end(); ++it) { if(fScore[*it] > fScore[dest]) break; @@ -328,6 +328,12 @@ namespace MWMechanics path.push_front(pt); current = graphParent[current]; } + + // add first node to path explicitly + ESM::Pathgrid::Point pt = mPathgrid->mPoints[start]; + pt.mX += xCell; + pt.mY += yCell; + path.push_front(pt); return path; } } diff --git a/apps/openmw/mwmechanics/pathgrid.hpp b/apps/openmw/mwmechanics/pathgrid.hpp index ac545efbc..5d01dca00 100644 --- a/apps/openmw/mwmechanics/pathgrid.hpp +++ b/apps/openmw/mwmechanics/pathgrid.hpp @@ -30,6 +30,8 @@ namespace MWMechanics // the input parameters are pathgrid point indexes // the output list is in local (internal cells) or world (external // cells) co-ordinates + // + // NOTE: if start equals end an empty path is returned std::list aStarSearch(const int start, const int end) const; private: diff --git a/apps/openmw/mwmechanics/repair.cpp b/apps/openmw/mwmechanics/repair.cpp index 48179d344..9f2c851cf 100644 --- a/apps/openmw/mwmechanics/repair.cpp +++ b/apps/openmw/mwmechanics/repair.cpp @@ -28,11 +28,11 @@ void Repair::repair(const MWWorld::Ptr &itemToRepair) 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; + int uses = mTool.getClass().getItemHealth(mTool); + mTool.getCellRef().setCharge(uses-1); - MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); - MWMechanics::NpcStats& npcStats = MWWorld::Class::get(player).getNpcStats(player); + MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); + MWMechanics::NpcStats& npcStats = player.getClass().getNpcStats(player); float fatigueTerm = stats.getFatigueTerm(); int pcStrength = stats.getAttribute(ESM::Attribute::Strength).getModified(); @@ -53,17 +53,17 @@ void Repair::repair(const MWWorld::Ptr &itemToRepair) y = std::max(1, y); // repair by 'y' points - itemToRepair.getCellRef().mCharge += y; - itemToRepair.getCellRef().mCharge = std::min(itemToRepair.getCellRef().mCharge, - MWWorld::Class::get(itemToRepair).getItemMaxHealth(itemToRepair)); + int charge = itemToRepair.getClass().getItemHealth(itemToRepair); + charge = std::min(charge + y, itemToRepair.getClass().getItemMaxHealth(itemToRepair)); + itemToRepair.getCellRef().setCharge(charge); // set the OnPCRepair variable on the item's script - std::string script = MWWorld::Class::get(itemToRepair).getScript(itemToRepair); + std::string script = itemToRepair.getClass().getScript(itemToRepair); if(script != "") itemToRepair.getRefData().getLocals().setVarByInt(script, "onpcrepair", 1); // increase skill - MWWorld::Class::get(player).skillUsageSucceeded(player, ESM::Skill::Armorer, 0); + player.getClass().skillUsageSucceeded(player, ESM::Skill::Armorer, 0); MWBase::Environment::get().getSoundManager()->playSound("Repair",1,1); MWBase::Environment::get().getWindowManager()->messageBox("#{sRepairSuccess}"); @@ -75,23 +75,23 @@ void Repair::repair(const MWWorld::Ptr &itemToRepair) } // tool used up? - if (mTool.getCellRef().mCharge == 0) + if (mTool.getCellRef().getCharge() == 0) { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - MWWorld::ContainerStore& store = MWWorld::Class::get(player).getContainerStore(player); + MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); store.remove(mTool, 1, player); std::string message = MWBase::Environment::get().getWorld()->getStore().get() .find("sNotifyMessage51")->getString(); - MWBase::Environment::get().getWindowManager()->messageBox((boost::format(message) % MWWorld::Class::get(mTool).getName(mTool)).str()); + MWBase::Environment::get().getWindowManager()->messageBox((boost::format(message) % mTool.getClass().getName(mTool)).str()); // try to find a new tool of the same ID for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end(); ++iter) { - if (Misc::StringUtils::ciEqual(iter->getCellRef().mRefID, mTool.getCellRef().mRefID)) + if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), mTool.getCellRef().getRefId())) { mTool = *iter; break; diff --git a/apps/openmw/mwmechanics/security.cpp b/apps/openmw/mwmechanics/security.cpp index edec45e15..4a049d60f 100644 --- a/apps/openmw/mwmechanics/security.cpp +++ b/apps/openmw/mwmechanics/security.cpp @@ -18,8 +18,8 @@ namespace MWMechanics Security::Security(const MWWorld::Ptr &actor) : mActor(actor) { - CreatureStats& creatureStats = MWWorld::Class::get(actor).getCreatureStats(actor); - NpcStats& npcStats = MWWorld::Class::get(actor).getNpcStats(actor); + CreatureStats& creatureStats = actor.getClass().getCreatureStats(actor); + NpcStats& npcStats = actor.getClass().getNpcStats(actor); mAgility = creatureStats.getAttribute(ESM::Attribute::Agility).getModified(); mLuck = creatureStats.getAttribute(ESM::Attribute::Luck).getModified(); mSecuritySkill = npcStats.getSkill(ESM::Skill::Security).getModified(); @@ -29,10 +29,10 @@ namespace MWMechanics void Security::pickLock(const MWWorld::Ptr &lock, const MWWorld::Ptr &lockpick, std::string& resultMessage, std::string& resultSound) { - if (lock.getCellRef().mLockLevel <= 0) + if (!(lock.getCellRef().getLockLevel() > 0)) //If it's unlocked back out immediately return; - int lockStrength = lock.getCellRef().mLockLevel; + int lockStrength = lock.getCellRef().getLockLevel(); float pickQuality = lockpick.get()->mBase->mData.mQuality; @@ -51,31 +51,31 @@ namespace MWMechanics int roll = static_cast (std::rand()) / RAND_MAX * 100; if (roll <= x) { - MWWorld::Class::get(lock).unlock(lock); + lock.getClass().unlock(lock); resultMessage = "#{sLockSuccess}"; resultSound = "Open Lock"; - MWWorld::Class::get(mActor).skillUsageSucceeded(mActor, ESM::Skill::Security, 1); + mActor.getClass().skillUsageSucceeded(mActor, ESM::Skill::Security, 1); } else resultMessage = "#{sLockFail}"; } - if (lockpick.getCellRef().mCharge == -1) - lockpick.getCellRef().mCharge = lockpick.get()->mBase->mData.mUses; - --lockpick.getCellRef().mCharge; - if (!lockpick.getCellRef().mCharge) + int uses = lockpick.getClass().getItemHealth(lockpick); + --uses; + lockpick.getCellRef().setCharge(uses); + if (!uses) lockpick.getContainerStore()->remove(lockpick, 1, mActor); } void Security::probeTrap(const MWWorld::Ptr &trap, const MWWorld::Ptr &probe, std::string& resultMessage, std::string& resultSound) { - if (trap.getCellRef().mTrap == "") + if (trap.getCellRef().getTrap() == "") return; float probeQuality = probe.get()->mBase->mData.mQuality; - const ESM::Spell* trapSpell = MWBase::Environment::get().getWorld()->getStore().get().find(trap.getCellRef().mTrap); + const ESM::Spell* trapSpell = MWBase::Environment::get().getWorld()->getStore().get().find(trap.getCellRef().getTrap()); float trapSpellPoints = trapSpell->mData.mCost; float fTrapCostMult = MWBase::Environment::get().getWorld()->getStore().get().find("fTrapCostMult")->getFloat(); @@ -93,20 +93,20 @@ namespace MWMechanics int roll = static_cast (std::rand()) / RAND_MAX * 100; if (roll <= x) { - trap.getCellRef().mTrap = ""; + trap.getCellRef().setTrap(""); resultSound = "Disarm Trap"; resultMessage = "#{sTrapSuccess}"; - MWWorld::Class::get(mActor).skillUsageSucceeded(mActor, ESM::Skill::Security, 0); + mActor.getClass().skillUsageSucceeded(mActor, ESM::Skill::Security, 0); } else resultMessage = "#{sTrapFail}"; } - if (probe.getCellRef().mCharge == -1) - probe.getCellRef().mCharge = probe.get()->mBase->mData.mUses; - --probe.getCellRef().mCharge; - if (!probe.getCellRef().mCharge) + int uses = probe.getClass().getItemHealth(probe); + --uses; + probe.getCellRef().setCharge(uses); + if (!uses) probe.getContainerStore()->remove(probe, 1, mActor); } diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 7f5a7fac5..c996e90d6 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -21,6 +21,41 @@ #include "magiceffects.hpp" #include "npcstats.hpp" +namespace +{ + + /// Get projectile properties (model, sound and speed) for a spell with the given effects + /// If \a model is empty, the spell has no ranged effects and should not spawn a projectile. + void getProjectileInfo (const ESM::EffectList& effects, std::string& model, std::string& sound, float& speed) + { + for (std::vector::const_iterator iter (effects.mList.begin()); + iter!=effects.mList.end(); ++iter) + { + if (iter->mRange != ESM::RT_Target) + continue; + + const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find ( + iter->mEffectID); + + model = magicEffect->mBolt; + if (model.empty()) + model = "VFX_DefaultBolt"; + + static const std::string schools[] = { + "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" + }; + if (!magicEffect->mBoltSound.empty()) + sound = magicEffect->mBoltSound; + else + sound = schools[magicEffect->mData.mSchool] + " bolt"; + + speed = magicEffect->mData.mSpeed; + break; + } + } + +} + namespace MWMechanics { @@ -140,7 +175,7 @@ namespace MWMechanics float x = (willpower + 0.1 * luck) * stats.getFatigueTerm(); // This makes spells that are easy to cast harder to resist and vice versa - if (spell != NULL && caster.getClass().isActor()) + if (spell != NULL && !caster.isEmpty() && caster.getClass().isActor()) { float castChance = getSpellSuccessChance(spell, caster); if (castChance > 0) @@ -189,6 +224,9 @@ namespace MWMechanics void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, const ESM::EffectList &effects, ESM::RangeType range, bool reflected, bool exploded) { + if (target.getClass().isActor() && target.getClass().getCreatureStats(target).isDead()) + return; + // If none of the effects need to apply, we can early-out bool found = false; for (std::vector::const_iterator iter (effects.mList.begin()); @@ -219,10 +257,12 @@ namespace MWMechanics } ESM::EffectList reflectedEffects; - std::vector appliedLastingEffects; + std::vector appliedLastingEffects; bool firstAppliedEffect = true; bool anyHarmfulEffect = false; + bool castByPlayer = (!caster.isEmpty() && caster.getRefData().getHandle() == "player"); + for (std::vector::const_iterator effectIt (effects.mList.begin()); effectIt!=effects.mList.end(); ++effectIt) { @@ -235,7 +275,7 @@ namespace MWMechanics if (!MWBase::Environment::get().getWorld()->isLevitationEnabled() && effectIt->mEffectID == ESM::MagicEffect::Levitate) { - if (caster.getRefData().getHandle() == "player") + if (castByPlayer) MWBase::Environment::get().getWindowManager()->messageBox("#{sLevitateDisabled}"); continue; } @@ -246,13 +286,13 @@ namespace MWMechanics effectIt->mEffectID == ESM::MagicEffect::Mark || effectIt->mEffectID == ESM::MagicEffect::Recall)) { - if (caster.getRefData().getHandle() == "player") + if (castByPlayer) MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); continue; } // If player is healing someone, show the target's HP bar - if (caster.getRefData().getHandle() == "player" && target != caster + if (castByPlayer && target != caster && effectIt->mEffectID == ESM::MagicEffect::RestoreHealth && target.getClass().isActor()) MWBase::Environment::get().getWindowManager()->setEnemy(target); @@ -263,7 +303,7 @@ namespace MWMechanics anyHarmfulEffect = true; // If player is attempting to cast a harmful spell, show the target's HP bar - if (caster.getRefData().getHandle() == "player" && target != caster) + if (castByPlayer && target != caster) MWBase::Environment::get().getWindowManager()->setEnemy(target); // Try absorbing if it's a spell @@ -329,8 +369,9 @@ namespace MWMechanics bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration); if (target.getClass().isActor() && hasDuration) { - ActiveSpells::Effect effect; - effect.mKey = MWMechanics::EffectKey(*effectIt); + ActiveSpells::ActiveEffect effect; + effect.mEffectId = effectIt->mEffectID; + effect.mArg = MWMechanics::EffectKey(*effectIt).mArg; effect.mDuration = effectIt->mDuration; effect.mMagnitude = magnitude; @@ -338,17 +379,20 @@ namespace MWMechanics // For absorb effects, also apply the effect to the caster - but with a negative // magnitude, since we're transfering stats from the target to the caster - for (int i=0; i<5; ++i) + if (!caster.isEmpty() && caster.getClass().isActor()) { - if (effectIt->mEffectID == ESM::MagicEffect::AbsorbAttribute+i) + for (int i=0; i<5; ++i) { - std::vector effects; - ActiveSpells::Effect effect_ = effect; - effect_.mMagnitude *= -1; - effects.push_back(effect_); - // Also make sure to set casterHandle = target, so that the effect on the caster gets purged when the target dies - caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell("", true, - effects, mSourceName, target.getRefData().getHandle()); + if (effectIt->mEffectID == ESM::MagicEffect::AbsorbAttribute+i) + { + std::vector effects; + ActiveSpells::ActiveEffect effect_ = effect; + effect_.mMagnitude *= -1; + effects.push_back(effect_); + // Also make sure to set casterActorId = target, so that the effect on the caster gets purged when the target dies + caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell("", true, + effects, mSourceName, target.getClass().getCreatureStats(target).getActorId()); + } } } } @@ -389,6 +433,7 @@ namespace MWMechanics else castStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_DefaultHit"); + // TODO: VFX are no longer active after saving/reloading the game bool loop = magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx; // Note: in case of non actor, a free effect should be fine as well MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(target); @@ -399,18 +444,23 @@ namespace MWMechanics } if (!exploded) - MWBase::Environment::get().getWorld()->explodeSpell(mHitPosition, mTarget, effects, caster, mId, mSourceName); + MWBase::Environment::get().getWorld()->explodeSpell(mHitPosition, effects, caster, mId, mSourceName); - if (reflectedEffects.mList.size()) + if (!reflectedEffects.mList.empty()) inflict(caster, target, reflectedEffects, range, true); - if (appliedLastingEffects.size()) + if (!appliedLastingEffects.empty()) + { + int casterActorId = -1; + if (caster.getClass().isActor()) + casterActorId = caster.getClass().getCreatureStats(caster).getActorId(); target.getClass().getCreatureStats(target).getActiveSpells().addSpell(mId, mStack, appliedLastingEffects, - mSourceName, caster.getRefData().getHandle()); + mSourceName, casterActorId); + } - if (anyHarmfulEffect && target.getClass().isActor() && target != caster - && target.getClass().getCreatureStats(target).getAiSetting(MWMechanics::CreatureStats::AI_Fight).getModified() <= 30) - MWBase::Environment::get().getMechanicsManager()->commitCrime(caster, target, MWBase::MechanicsManager::OT_Assault); + // Notify the target actor they've been hit + if (anyHarmfulEffect && target.getClass().isActor() && target != caster && caster.getClass().isActor()) + target.getClass().onHit(target, 0.f, true, MWWorld::Ptr(), caster, true); } void CastSpell::applyInstantEffect(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, const MWMechanics::EffectKey& effect, float magnitude) @@ -420,19 +470,21 @@ namespace MWMechanics { if (effectId == ESM::MagicEffect::Lock) { - if (target.getCellRef().mLockLevel < magnitude) - target.getCellRef().mLockLevel = magnitude; + if (target.getCellRef().getLockLevel() < magnitude) //If the door is not already locked to a higher value, lock it to spell magnitude + target.getCellRef().setLockLevel(magnitude); } else if (effectId == ESM::MagicEffect::Open) { - if (target.getCellRef().mLockLevel <= magnitude) + if (target.getCellRef().getLockLevel() <= magnitude) { - if (target.getCellRef().mLockLevel > 0) + if (target.getCellRef().getLockLevel() > 0) { + //Door not already unlocked MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock", 1.f, 1.f); - MWBase::Environment::get().getMechanicsManager()->objectOpened(caster, target); + if (!caster.isEmpty() && caster.getClass().isActor()) + MWBase::Environment::get().getMechanicsManager()->objectOpened(caster, target); } - target.getCellRef().mLockLevel = 0; + target.getCellRef().setLockLevel(-abs(target.getCellRef().getLockLevel())); //unlocks the door } else MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock Fail", 1.f, 1.f); @@ -536,7 +588,7 @@ namespace MWMechanics throw std::runtime_error("can't cast an item without an enchantment"); mSourceName = item.getClass().getName(item); - mId = item.getCellRef().mRefID; + mId = item.getCellRef().getRefId(); const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find(enchantmentName); @@ -549,10 +601,10 @@ namespace MWMechanics int eSkill = mCaster.getClass().getSkill(mCaster, ESM::Skill::Enchant); const int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10)); - if (item.getCellRef().mEnchantmentCharge == -1) - item.getCellRef().mEnchantmentCharge = enchantment->mData.mCharge; + if (item.getCellRef().getEnchantmentCharge() == -1) + item.getCellRef().setEnchantmentCharge(enchantment->mData.mCharge); - if (item.getCellRef().mEnchantmentCharge < castCost) + if (item.getCellRef().getEnchantmentCharge() < castCost) { // TODO: Should there be a sound here? if (mCaster.getRefData().getHandle() == "player") @@ -560,7 +612,7 @@ namespace MWMechanics return false; } // Reduce charge - item.getCellRef().mEnchantmentCharge -= castCost; + item.getCellRef().setEnchantmentCharge(item.getCellRef().getEnchantmentCharge() - castCost); } if (enchantment->mData.mType == ESM::Enchantment::WhenUsed) @@ -574,7 +626,6 @@ namespace MWMechanics { if (mCaster.getRefData().getHandle() == "player") { - MWBase::Environment::get().getWindowManager()->setSelectedEnchantItem(item); // Set again to show the modified charge mCaster.getClass().skillUsageSucceeded (mCaster, ESM::Skill::Enchant, 3); } } @@ -587,7 +638,13 @@ namespace MWMechanics inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Touch); } - MWBase::Environment::get().getWorld()->launchMagicBolt(mId, false, enchantment->mEffects, mCaster, mSourceName); + std::string projectileModel; + std::string sound; + float speed = 0; + getProjectileInfo(enchantment->mEffects, projectileModel, sound, speed); + if (!projectileModel.empty()) + MWBase::Environment::get().getWorld()->launchMagicBolt(projectileModel, sound, mId, speed, + false, enchantment->mEffects, mCaster, mSourceName); return true; } @@ -666,7 +723,15 @@ namespace MWMechanics } } - MWBase::Environment::get().getWorld()->launchMagicBolt(mId, false, spell->mEffects, mCaster, mSourceName); + + std::string projectileModel; + std::string sound; + float speed = 0; + getProjectileInfo(spell->mEffects, projectileModel, sound, speed); + if (!projectileModel.empty()) + MWBase::Environment::get().getWorld()->launchMagicBolt(projectileModel, sound, mId, speed, + false, spell->mEffects, mCaster, mSourceName); + return true; } @@ -714,7 +779,7 @@ namespace MWMechanics effect.mDuration = 1; if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)) { - if (!magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) + if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) magnitude = int((0.05 * y) / (0.1 * magicEffect->mData.mBaseCost)); else magnitude = int(y / (0.1 * magicEffect->mData.mBaseCost)); diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index 44402fe7b..dce4b792e 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -61,9 +61,12 @@ namespace MWMechanics /// @note Auto detects if spell, ingredient or potion bool cast (const std::string& id); + /// @note \a target can be any type of object, not just actors. + /// @note \a caster can be any type of object, or even an empty object. void inflict (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, const ESM::EffectList& effects, ESM::RangeType range, bool reflected=false, bool exploded=false); + /// @note \a caster can be any type of object, or even an empty object. void applyInstantEffect (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, const MWMechanics::EffectKey& effect, float magnitude); }; diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index 21781c530..dee1a1b05 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -4,6 +4,7 @@ #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -30,10 +31,19 @@ namespace MWMechanics { const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(spellId); - std::vector random; - random.resize(spell->mEffects.mList.size()); - for (unsigned int i=0; i (std::rand()) / RAND_MAX; + std::map random; + + // Determine the random magnitudes (unless this is a castable spell, in which case + // they will be determined when the spell is cast) + if (spell->mData.mType != ESM::Spell::ST_Power && spell->mData.mType != ESM::Spell::ST_Spell) + { + for (unsigned int i=0; imEffects.mList.size();++i) + { + if (spell->mEffects.mList[i].mMagnMin != spell->mEffects.mList[i].mMagnMax) + random[i] = static_cast (std::rand()) / RAND_MAX; + } + } + mSpells.insert (std::make_pair (Misc::StringUtils::lowerCase(spellId), random)); } } @@ -67,7 +77,11 @@ namespace MWMechanics int i=0; for (std::vector::const_iterator it = spell->mEffects.mList.begin(); it != spell->mEffects.mList.end(); ++it) { - effects.add (*it, it->mMagnMin + (it->mMagnMax - it->mMagnMin) * iter->second[i]); + float random = 1.f; + if (iter->second.find(i) != iter->second.end()) + random = iter->second.at(i); + + effects.add (*it, it->mMagnMin + (it->mMagnMax - it->mMagnMin) * random); ++i; } } @@ -129,7 +143,7 @@ namespace MWMechanics if (spell->mData.mType == ESM::Spell::ST_Disease) mSpells.erase(iter++); else - iter++; + ++iter; } } @@ -143,7 +157,7 @@ namespace MWMechanics if (spell->mData.mType == ESM::Spell::ST_Blight) mSpells.erase(iter++); else - iter++; + ++iter; } } @@ -157,7 +171,7 @@ namespace MWMechanics if (Misc::StringUtils::ciEqual(spell->mId, "corprus")) mSpells.erase(iter++); else - iter++; + ++iter; } } @@ -171,7 +185,7 @@ namespace MWMechanics if (spell->mData.mType == ESM::Spell::ST_Curse) mSpells.erase(iter++); else - iter++; + ++iter; } } @@ -192,9 +206,60 @@ namespace MWMechanics for (std::vector::const_iterator effectIt = list.mList.begin(); 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); + float random = 1.f; + if (it->second.find(i) != it->second.end()) + random = it->second.at(i); + + float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * random; + visitor.visit(MWMechanics::EffectKey(*effectIt), spell->mName, -1, magnitude); + } + } + } + + bool Spells::canUsePower(const std::string &power) const + { + std::map::const_iterator it = mUsedPowers.find(power); + if (it == mUsedPowers.end() || it->second + 24 <= MWBase::Environment::get().getWorld()->getTimeStamp()) + return true; + else + return false; + } + + void Spells::usePower(const std::string &power) + { + mUsedPowers[power] = MWBase::Environment::get().getWorld()->getTimeStamp(); + } + + void Spells::readState(const ESM::SpellState &state) + { + mSpells = state.mSpells; + mSelectedSpell = state.mSelectedSpell; + + // Discard spells that are no longer available due to changed content files + for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) + { + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(iter->first); + if (!spell) + { + if (iter->first == mSelectedSpell) + mSelectedSpell = ""; + mSpells.erase(iter++); } + else + ++iter; } + + // No need to discard spells here (doesn't really matter if non existent ids are kept) + for (std::map::const_iterator it = state.mUsedPowers.begin(); it != state.mUsedPowers.end(); ++it) + mUsedPowers[it->first] = MWWorld::TimeStamp(it->second); + } + + void Spells::writeState(ESM::SpellState &state) const + { + state.mSpells = mSpells; + state.mSelectedSpell = mSelectedSpell; + + for (std::map::const_iterator it = mUsedPowers.begin(); it != mUsedPowers.end(); ++it) + state.mUsedPowers[it->first] = it->second.toEsm(); } } diff --git a/apps/openmw/mwmechanics/spells.hpp b/apps/openmw/mwmechanics/spells.hpp index 354b1fd0b..6997a9d7a 100644 --- a/apps/openmw/mwmechanics/spells.hpp +++ b/apps/openmw/mwmechanics/spells.hpp @@ -7,12 +7,16 @@ #include #include "../mwworld/ptr.hpp" +#include "../mwworld/timestamp.hpp" #include "magiceffects.hpp" + namespace ESM { struct Spell; + + struct SpellState; } namespace MWMechanics @@ -22,21 +26,29 @@ namespace MWMechanics /// \brief Spell list /// /// This class manages known spells as well as abilities, powers and permanent negative effects like - /// diseases. + /// diseases. It also keeps track of used powers (which can only be used every 24h). class Spells { public: - typedef std::map > TContainer; // ID, normalised magnitudes + + typedef std::map > TContainer; // ID, typedef TContainer::const_iterator TIterator; private: TContainer mSpells; + + // Note: this is the spell that's about to be cast, *not* the spell selected in the GUI (which may be different) std::string mSelectedSpell; + std::map mUsedPowers; + public: + bool canUsePower (const std::string& power) const; + void usePower (const std::string& power); + void purgeCommonDisease(); void purgeBlightDisease(); void purgeCorprusDisease(); @@ -72,6 +84,9 @@ namespace MWMechanics bool hasBlightDisease() const; void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor) const; + + void readState (const ESM::SpellState& state); + void writeState (ESM::SpellState& state) const; }; } diff --git a/apps/openmw/mwmechanics/steering.cpp b/apps/openmw/mwmechanics/steering.cpp index d911fd81b..9f887f5ca 100644 --- a/apps/openmw/mwmechanics/steering.cpp +++ b/apps/openmw/mwmechanics/steering.cpp @@ -10,9 +10,9 @@ namespace MWMechanics { -bool zTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle) +bool smoothTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle, int axis, Ogre::Degree epsilon) { - Ogre::Radian currentAngle (actor.getRefData().getPosition().rot[2]); + Ogre::Radian currentAngle (actor.getRefData().getPosition().rot[axis]); Ogre::Radian diff (targetAngle - currentAngle); if (diff >= Ogre::Degree(180)) { @@ -27,17 +27,20 @@ bool zTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle) // The turning animation actually moves you slightly, so the angle will be wrong again. // Use epsilon to prevent jerkiness. - const Ogre::Degree epsilon (0.5); if (absDiff < epsilon) return true; - // Max. speed of 10 radian per sec - Ogre::Radian limit = Ogre::Radian(10) * MWBase::Environment::get().getFrameDuration(); + Ogre::Radian limit = MAX_VEL_ANGULAR * MWBase::Environment::get().getFrameDuration(); if (absDiff > limit) diff = Ogre::Math::Sign(diff) * limit; - actor.getClass().getMovementSettings(actor).mRotation[2] = diff.valueRadians(); + actor.getClass().getMovementSettings(actor).mRotation[axis] = diff.valueRadians(); return false; } +bool zTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle, Ogre::Degree epsilon) +{ + return smoothTurn(actor, targetAngle, 2, epsilon); +} + } diff --git a/apps/openmw/mwmechanics/steering.hpp b/apps/openmw/mwmechanics/steering.hpp index 504dc3ac3..91df49f0d 100644 --- a/apps/openmw/mwmechanics/steering.hpp +++ b/apps/openmw/mwmechanics/steering.hpp @@ -10,9 +10,16 @@ class Ptr; namespace MWMechanics { +// Max rotating speed, radian/sec +const Ogre::Radian MAX_VEL_ANGULAR(10); + /// configure rotation settings for an actor to reach this target angle (eventually) /// @return have we reached the target angle? -bool zTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle); +bool zTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle, + Ogre::Degree epsilon = Ogre::Degree(0.5)); + +bool smoothTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle, int axis, + Ogre::Degree epsilon = Ogre::Degree(0.5)); } diff --git a/apps/openmw/mwrender/actors.cpp b/apps/openmw/mwrender/actors.cpp index 820ba8acc..b7e9f5730 100644 --- a/apps/openmw/mwrender/actors.cpp +++ b/apps/openmw/mwrender/actors.cpp @@ -49,22 +49,17 @@ void Actors::insertBegin(const MWWorld::Ptr &ptr) Ogre::SceneNode* insert = cellnode->createChildSceneNode(); const float *f = ptr.getRefData().getPosition().pos; insert->setPosition(f[0], f[1], f[2]); - insert->setScale(ptr.getCellRef().mScale, ptr.getCellRef().mScale, ptr.getCellRef().mScale); + insert->setScale(ptr.getCellRef().getScale(), ptr.getCellRef().getScale(), ptr.getCellRef().getScale()); // Convert MW rotation to a quaternion: - f = ptr.getCellRef().mPos.rot; + f = ptr.getCellRef().getPosition().rot; - // Rotate around X axis - Ogre::Quaternion xr(Ogre::Radian(-f[0]), Ogre::Vector3::UNIT_X); - - // Rotate around Y axis - Ogre::Quaternion yr(Ogre::Radian(-f[1]), Ogre::Vector3::UNIT_Y); - - // Rotate around Z axis + // For rendering purposes, actors should only rotate around the Z axis. + // X rotation is used for camera rotation (for the player) and for + // ranged magic / ranged weapon aiming. Ogre::Quaternion zr(Ogre::Radian(-f[2]), Ogre::Vector3::UNIT_Z); - // Rotates first around z, then y, then x - insert->setOrientation(xr*yr*zr); + insert->setOrientation(zr); ptr.getRefData().setBaseNode(insert); } diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index e62fee6e2..9ed4cb6d3 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -290,7 +290,7 @@ void Animation::addAnimSource(const std::string &model) mAccumRoot = mNonAccumRoot->getParent(); if(!mAccumRoot) { - std::cerr<< "Non-Accum root for "<getParent(); if(!mAccumRoot) { - std::cerr<< "Non-Accum root for "<second.compare(0, groupname.size(), groupname) == 0 && iter->second.compare(groupname.size(), 2, ": ") == 0) @@ -424,7 +424,7 @@ NifOgre::TextKeyMap::const_iterator Animation::findGroupStart(const NifOgre::Tex bool Animation::hasAnimation(const std::string &anim) { AnimSourceList::const_iterator iter(mAnimSources.begin()); - for(;iter != mAnimSources.end();iter++) + for(;iter != mAnimSources.end();++iter) { const NifOgre::TextKeyMap &keys = (*iter)->mTextKeys; if(findGroupStart(keys, anim) != keys.end()) @@ -465,7 +465,7 @@ float Animation::calcAnimVelocity(const NifOgre::TextKeyMap &keys, NifOgre::Node stoptime = keyiter->first; break; } - keyiter++; + ++keyiter; } if(stoptime > starttime) @@ -585,13 +585,13 @@ bool Animation::reset(AnimState &state, const NifOgre::TextKeyMap &keys, const s std::string starttag = groupname+": "+start; NifOgre::TextKeyMap::const_iterator startkey(groupstart); while(startkey != keys.end() && startkey->second != starttag) - startkey++; + ++startkey; if(startkey == keys.end() && start == "loop start") { starttag = groupname+": start"; startkey = groupstart; while(startkey != keys.end() && startkey->second != starttag) - startkey++; + ++startkey; } if(startkey == keys.end()) return false; @@ -603,7 +603,7 @@ bool Animation::reset(AnimState &state, const NifOgre::TextKeyMap &keys, const s // The Scrib's idle3 animation has "Idle3: Stop." instead of "Idle3: Stop". // Why, just why? :( && (stopkey->second.size() < stoptag.size() || stopkey->second.substr(0,stoptag.size()) != stoptag)) - stopkey++; + ++stopkey; if(stopkey == keys.end()) return false; @@ -627,7 +627,7 @@ bool Animation::reset(AnimState &state, const NifOgre::TextKeyMap &keys, const s state.mLoopStartTime = key->first; else if(key->second == loopstoptag) state.mLoopStopTime = key->first; - key++; + ++key; } } @@ -775,11 +775,11 @@ void Animation::play(const std::string &groupname, int priority, int groups, boo } /* Look in reverse; last-inserted source has priority. */ + AnimState state; AnimSourceList::reverse_iterator iter(mAnimSources.rbegin()); - for(;iter != mAnimSources.rend();iter++) + for(;iter != mAnimSources.rend();++iter) { const NifOgre::TextKeyMap &textkeys = (*iter)->mTextKeys; - AnimState state; if(reset(state, textkeys, groupname, start, stop, startpoint)) { state.mSource = *iter; @@ -795,7 +795,7 @@ void Animation::play(const std::string &groupname, int priority, int groups, boo while(textkey != textkeys.end() && textkey->first <= state.mTime) { handleTextKey(state, groupname, textkey); - textkey++; + ++textkey; } if(state.mTime >= state.mLoopStopTime && state.mLoopCount > 0) @@ -810,7 +810,7 @@ void Animation::play(const std::string &groupname, int priority, int groups, boo while(textkey != textkeys.end() && textkey->first <= state.mTime) { handleTextKey(state, groupname, textkey); - textkey++; + ++textkey; } } @@ -818,9 +818,16 @@ void Animation::play(const std::string &groupname, int priority, int groups, boo } } if(iter == mAnimSources.rend()) - std::cerr<< "Failed to find animation "<setPosition(-mNonAccumCtrl->getTranslation(state.mTime)*mAccumulate); + } } bool Animation::isPlaying(const std::string &groupname) const @@ -965,7 +972,7 @@ Ogre::Vector3 Animation::runAnimation(float duration) while(textkey != textkeys.end() && textkey->first <= state.mTime) { handleTextKey(state, stateiter->first, textkey); - textkey++; + ++textkey; } if(state.mTime >= state.mLoopStopTime && state.mLoopCount > 0) @@ -979,7 +986,7 @@ Ogre::Vector3 Animation::runAnimation(float duration) while(textkey != textkeys.end() && textkey->first <= state.mTime) { handleTextKey(state, stateiter->first, textkey); - textkey++; + ++textkey; } if(state.mTime >= state.mLoopStopTime) @@ -1250,22 +1257,30 @@ Ogre::Vector3 Animation::getEnchantmentColor(MWWorld::Ptr item) ObjectAnimation::ObjectAnimation(const MWWorld::Ptr& ptr, const std::string &model) : Animation(ptr, ptr.getRefData().getBaseNode()) { - setObjectRoot(model, false); - - Ogre::Vector3 extents = getWorldBounds().getSize(); - float size = std::max(std::max(extents.x, extents.y), extents.z); - - bool small = (size < Settings::Manager::getInt("small object size", "Viewing distance")) && - Settings::Manager::getBool("limit small object distance", "Viewing distance"); - // do not fade out doors. that will cause holes and look stupid - if(ptr.getTypeName().find("Door") != std::string::npos) - small = false; - - float dist = small ? Settings::Manager::getInt("small object distance", "Viewing distance") : 0.0f; - Ogre::Vector3 col = getEnchantmentColor(ptr); - setRenderProperties(mObjectRoot, (mPtr.getTypeName() == typeid(ESM::Static).name()) ? - (small ? RV_StaticsSmall : RV_Statics) : RV_Misc, - RQG_Main, RQG_Alpha, dist, !ptr.getClass().getEnchantment(ptr).empty(), &col); + if (!model.empty()) + { + setObjectRoot(model, false); + + Ogre::Vector3 extents = getWorldBounds().getSize(); + float size = std::max(std::max(extents.x, extents.y), extents.z); + + bool small = (size < Settings::Manager::getInt("small object size", "Viewing distance")) && + Settings::Manager::getBool("limit small object distance", "Viewing distance"); + // do not fade out doors. that will cause holes and look stupid + if(ptr.getTypeName().find("Door") != std::string::npos) + small = false; + + float dist = small ? Settings::Manager::getInt("small object distance", "Viewing distance") : 0.0f; + Ogre::Vector3 col = getEnchantmentColor(ptr); + setRenderProperties(mObjectRoot, (mPtr.getTypeName() == typeid(ESM::Static).name()) ? + (small ? RV_StaticsSmall : RV_Statics) : RV_Misc, + RQG_Main, RQG_Alpha, dist, !ptr.getClass().getEnchantment(ptr).empty(), &col); + } + else + { + // No model given. Create an object root anyway, so that lights can be added to it if needed. + mObjectRoot = NifOgre::ObjectScenePtr (new NifOgre::ObjectScene(mInsert->getCreator())); + } } void ObjectAnimation::addLight(const ESM::Light *light) diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 013e3daf4..5e88b2250 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -118,7 +118,7 @@ namespace MWRender 0, true, (renderHeadOnly() ? NpcAnimation::VM_HeadOnly : NpcAnimation::VM_Normal)); float scale=1.f; - MWWorld::Class::get(mCharacter).adjustScale(mCharacter, scale); + mCharacter.getClass().adjustScale(mCharacter, scale); mNode->setScale(Ogre::Vector3(scale)); mCamera->setPosition(mPosition * mNode->getScale()); diff --git a/apps/openmw/mwrender/characterpreview.hpp b/apps/openmw/mwrender/characterpreview.hpp index 16e6ab017..60312455f 100644 --- a/apps/openmw/mwrender/characterpreview.hpp +++ b/apps/openmw/mwrender/characterpreview.hpp @@ -34,6 +34,10 @@ namespace MWRender virtual void rebuild(); + private: + CharacterPreview(const CharacterPreview&); + CharacterPreview& operator=(const CharacterPreview&); + protected: virtual bool renderHeadOnly() { return false; } diff --git a/apps/openmw/mwrender/debugging.cpp b/apps/openmw/mwrender/debugging.cpp index ba39d10d5..4f5536ca3 100644 --- a/apps/openmw/mwrender/debugging.cpp +++ b/apps/openmw/mwrender/debugging.cpp @@ -106,7 +106,7 @@ ManualObject *Debugging::createPathgridPoints(const ESM::Pathgrid *pathgrid) uint32 startIndex = 0; for(ESM::Pathgrid::PointList::const_iterator it = pathgrid->mPoints.begin(); it != pathgrid->mPoints.end(); - it++, startIndex += 6) + ++it, startIndex += 6) { Vector3 pointPos(it->mX, it->mY, it->mZ); diff --git a/apps/openmw/mwrender/effectmanager.cpp b/apps/openmw/mwrender/effectmanager.cpp index 1e6119daa..968be0f9e 100644 --- a/apps/openmw/mwrender/effectmanager.cpp +++ b/apps/openmw/mwrender/effectmanager.cpp @@ -11,18 +11,6 @@ namespace MWRender { -class EffectAnimationTime : public Ogre::ControllerValue -{ -private: - float mTime; -public: - EffectAnimationTime() : mTime(0) { } - void addTime(float time) { mTime += time; } - - virtual Ogre::Real getValue() const { return mTime; } - virtual void setValue(Ogre::Real value) {} -}; - EffectManager::EffectManager(Ogre::SceneManager *sceneMgr) : mSceneMgr(sceneMgr) { diff --git a/apps/openmw/mwrender/effectmanager.hpp b/apps/openmw/mwrender/effectmanager.hpp index bc9e68d26..eb6863655 100644 --- a/apps/openmw/mwrender/effectmanager.hpp +++ b/apps/openmw/mwrender/effectmanager.hpp @@ -5,6 +5,20 @@ namespace MWRender { + + class EffectAnimationTime : public Ogre::ControllerValue + { + private: + float mTime; + public: + EffectAnimationTime() : mTime(0) { } + void addTime(float time) { mTime += time; } + + virtual Ogre::Real getValue() const { return mTime; } + virtual void setValue(Ogre::Real value) {} + }; + + // Note: effects attached to another object should be managed by MWRender::Animation::addEffect. // This class manages "free" effects, i.e. attached to a dedicated scene node in the world. class EffectManager diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index 76ad1890f..0537ed516 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -231,9 +231,8 @@ namespace MWRender mOverlayTexture->getBuffer()->blitFromMemory(pb); } - void GlobalMap::write(ESM::ESMWriter &writer) + void GlobalMap::write(ESM::GlobalMap& map) { - ESM::GlobalMap map; map.mBounds.mMinX = mMinX; map.mBounds.mMaxX = mMaxX; map.mBounds.mMinY = mMinY; @@ -244,95 +243,68 @@ namespace MWRender Ogre::DataStreamPtr encoded = image.encode("png"); map.mImageData.resize(encoded->size()); encoded->read(&map.mImageData[0], encoded->size()); - - writer.startRecord(ESM::REC_GMAP); - map.save(writer); - writer.endRecord(ESM::REC_GMAP); } - void GlobalMap::readRecord(ESM::ESMReader &reader, int32_t type, std::vector >& exploredCells) + void GlobalMap::read(ESM::GlobalMap& map) { - if (type == ESM::REC_GMAP) - { - ESM::GlobalMap map; - map.load(reader); - - const ESM::GlobalMap::Bounds& bounds = map.mBounds; - - if (bounds.mMaxX-bounds.mMinX <= 0) - return; - if (bounds.mMaxY-bounds.mMinY <= 0) - return; - - if (bounds.mMinX > bounds.mMaxX - || bounds.mMinY > bounds.mMaxY) - throw std::runtime_error("invalid map bounds"); + const ESM::GlobalMap::Bounds& bounds = map.mBounds; - Ogre::Image image; - Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(&map.mImageData[0], map.mImageData.size())); - image.load(stream, "png"); - - int xLength = (bounds.mMaxX-bounds.mMinX+1); - int yLength = (bounds.mMaxY-bounds.mMinY+1); - - // Size of one cell in image space - int cellImageSizeSrc = image.getWidth() / xLength; - if (int(image.getHeight() / yLength) != cellImageSizeSrc) - throw std::runtime_error("cell size must be quadratic"); - - // Determine which cells were explored by reading the image data - for (int x=0; x < xLength; ++x) - { - for (int y=0; y < yLength; ++y) - { - unsigned int imageX = (x) * cellImageSizeSrc; - // NB y + 1, because we want the top left corner, not bottom left where the origin of the cell is - unsigned int imageY = (yLength - (y + 1)) * cellImageSizeSrc; + if (bounds.mMaxX-bounds.mMinX <= 0) + return; + if (bounds.mMaxY-bounds.mMinY <= 0) + return; - assert(imageX < image.getWidth()); - assert(imageY < image.getHeight()); + if (bounds.mMinX > bounds.mMaxX + || bounds.mMinY > bounds.mMaxY) + throw std::runtime_error("invalid map bounds"); - if (image.getColourAt(imageX, imageY, 0).a > 0) - exploredCells.push_back(std::make_pair(x+bounds.mMinX,y+bounds.mMinY)); - } - } + Ogre::Image image; + Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(&map.mImageData[0], map.mImageData.size())); + image.load(stream, "png"); + + int xLength = (bounds.mMaxX-bounds.mMinX+1); + int yLength = (bounds.mMaxY-bounds.mMinY+1); + + // Size of one cell in image space + int cellImageSizeSrc = image.getWidth() / xLength; + if (int(image.getHeight() / yLength) != cellImageSizeSrc) + throw std::runtime_error("cell size must be quadratic"); + + // If cell bounds of the currently loaded content and the loaded savegame do not match, + // we need to resize source/dest boxes to accommodate + // This means nonexisting cells will be dropped silently + int cellImageSizeDst = 24; + + // Completely off-screen? -> no need to blit anything + if (bounds.mMaxX < mMinX + || bounds.mMaxY < mMinY + || bounds.mMinX > mMaxX + || bounds.mMinY > mMaxY) + return; - // If cell bounds of the currently loaded content and the loaded savegame do not match, - // we need to resize source/dest boxes to accommodate - // This means nonexisting cells will be dropped silently - int cellImageSizeDst = 24; - - // Completely off-screen? -> no need to blit anything - if (bounds.mMaxX < mMinX - || bounds.mMaxY < mMinY - || bounds.mMinX > mMaxX - || bounds.mMinY > mMaxY) - return; - - int leftDiff = (mMinX - bounds.mMinX); - int topDiff = (bounds.mMaxY - mMaxY); - int rightDiff = (bounds.mMaxX - mMaxX); - int bottomDiff = (mMinY - bounds.mMinY); - Ogre::Image::Box srcBox ( std::max(0, leftDiff * cellImageSizeSrc), - std::max(0, topDiff * cellImageSizeSrc), - std::min(image.getWidth(), image.getWidth() - rightDiff * cellImageSizeSrc), - std::min(image.getHeight(), image.getHeight() - bottomDiff * cellImageSizeSrc)); - - Ogre::Image::Box destBox ( std::max(0, -leftDiff * cellImageSizeDst), - std::max(0, -topDiff * cellImageSizeDst), - std::min(mOverlayTexture->getWidth(), mOverlayTexture->getWidth() + rightDiff * cellImageSizeDst), - std::min(mOverlayTexture->getHeight(), mOverlayTexture->getHeight() + bottomDiff * cellImageSizeDst)); - - // Looks like there is no interface for blitting from memory with src/dst boxes. - // So we create a temporary texture for blitting. - Ogre::TexturePtr tex = Ogre::TextureManager::getSingleton().createManual("@temp", - Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, image.getWidth(), - image.getHeight(), 0, Ogre::PF_A8B8G8R8); - tex->loadImage(image); - - mOverlayTexture->getBuffer()->blit(tex->getBuffer(), srcBox, destBox); - - Ogre::TextureManager::getSingleton().remove("@temp"); - } + int leftDiff = (mMinX - bounds.mMinX); + int topDiff = (bounds.mMaxY - mMaxY); + int rightDiff = (bounds.mMaxX - mMaxX); + int bottomDiff = (mMinY - bounds.mMinY); + Ogre::Image::Box srcBox ( std::max(0, leftDiff * cellImageSizeSrc), + std::max(0, topDiff * cellImageSizeSrc), + std::min(image.getWidth(), image.getWidth() - rightDiff * cellImageSizeSrc), + std::min(image.getHeight(), image.getHeight() - bottomDiff * cellImageSizeSrc)); + + Ogre::Image::Box destBox ( std::max(0, -leftDiff * cellImageSizeDst), + std::max(0, -topDiff * cellImageSizeDst), + std::min(mOverlayTexture->getWidth(), mOverlayTexture->getWidth() + rightDiff * cellImageSizeDst), + std::min(mOverlayTexture->getHeight(), mOverlayTexture->getHeight() + bottomDiff * cellImageSizeDst)); + + // Looks like there is no interface for blitting from memory with src/dst boxes. + // So we create a temporary texture for blitting. + Ogre::TexturePtr tex = Ogre::TextureManager::getSingleton().createManual("@temp", + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, image.getWidth(), + image.getHeight(), 0, Ogre::PF_A8B8G8R8); + tex->loadImage(image); + + mOverlayTexture->getBuffer()->blit(tex->getBuffer(), srcBox, destBox); + + Ogre::TextureManager::getSingleton().remove("@temp"); } } diff --git a/apps/openmw/mwrender/globalmap.hpp b/apps/openmw/mwrender/globalmap.hpp index 5fe878cd4..6075d042e 100644 --- a/apps/openmw/mwrender/globalmap.hpp +++ b/apps/openmw/mwrender/globalmap.hpp @@ -12,8 +12,7 @@ namespace Loading namespace ESM { - class ESMWriter; - class ESMReader; + class GlobalMap; } namespace MWRender @@ -40,8 +39,8 @@ namespace MWRender /// Clears the overlay void clear(); - void write (ESM::ESMWriter& writer); - void readRecord (ESM::ESMReader& reader, int32_t type, std::vector >& exploredCells); + void write (ESM::GlobalMap& map); + void read (ESM::GlobalMap& map); private: std::string mCacheDir; diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 0f6d782a6..2b0323675 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -9,6 +9,8 @@ #include #include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" @@ -42,11 +44,28 @@ LocalMap::LocalMap(OEngine::Render::OgreRenderer* rend, MWRender::RenderingManag mLight->setDirection (Ogre::Vector3(0.3, 0.3, -0.7)); mLight->setVisible (false); mLight->setDiffuseColour (ColourValue(0.7,0.7,0.7)); + + mRenderTexture = TextureManager::getSingleton().createManual( + "localmap/rtt", + ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + TEX_TYPE_2D, + sMapResolution, sMapResolution, + 0, + PF_R8G8B8, + TU_RENDERTARGET); + + mRenderTarget = mRenderTexture->getBuffer()->getRenderTarget(); + mRenderTarget->setAutoUpdated(false); + Viewport* vp = mRenderTarget->addViewport(mCellCamera); + vp->setOverlaysEnabled(false); + vp->setShadowsEnabled(false); + vp->setBackgroundColour(ColourValue(0, 0, 0)); + vp->setVisibilityMask(RV_Map); + vp->setMaterialScheme("local_map"); } LocalMap::~LocalMap() { - deleteBuffers(); } const Ogre::Vector2 LocalMap::rotatePoint(const Ogre::Vector2& p, const Ogre::Vector2& c, const float angle) @@ -55,59 +74,83 @@ const Ogre::Vector2 LocalMap::rotatePoint(const Ogre::Vector2& p, const Ogre::Ve Math::Sin(angle) * (p.x - c.x) + Math::Cos(angle) * (p.y - c.y) + c.y); } -void LocalMap::deleteBuffers() -{ - mBuffers.clear(); -} - -void LocalMap::saveTexture(const std::string& texname, const std::string& filename) +std::string LocalMap::coordStr(const int x, const int y) { - TexturePtr tex = TextureManager::getSingleton().getByName(texname); - if (tex.isNull()) return; - HardwarePixelBufferSharedPtr readbuffer = tex->getBuffer(); - readbuffer->lock(HardwareBuffer::HBL_NORMAL ); - const PixelBox &readrefpb = readbuffer->getCurrentLock(); - uchar *readrefdata = static_cast(readrefpb.data); - - Image img; - img = img.loadDynamicImage (readrefdata, tex->getWidth(), - tex->getHeight(), tex->getFormat()); - img.save("./" + filename); - - readbuffer->unlock(); + return StringConverter::toString(x) + "_" + StringConverter::toString(y); } -std::string LocalMap::coordStr(const int x, const int y) +void LocalMap::clear() { - return StringConverter::toString(x) + "_" + StringConverter::toString(y); + // Not actually removing the Textures here. That doesnt appear to work properly. It seems MyGUI still keeps some pointers. + mBuffers.clear(); } void LocalMap::saveFogOfWar(MWWorld::CellStore* cell) { if (!mInterior) { - /*saveTexture("Cell_"+coordStr(mCellX, mCellY)+"_fog", - "Cell_"+coordStr(mCellX, mCellY)+"_fog.png");*/ + std::string textureName = "Cell_"+coordStr(cell->getCell()->getGridX(), cell->getCell()->getGridY())+"_fog"; + std::auto_ptr fog (new ESM::FogState()); + fog->mFogTextures.push_back(ESM::FogTexture()); + + TexturePtr tex = TextureManager::getSingleton().getByName(textureName); + if (tex.isNull()) + return; + + Ogre::Image image; + tex->convertToImage(image); + + Ogre::DataStreamPtr encoded = image.encode("tga"); + fog->mFogTextures.back().mImageData.resize(encoded->size()); + encoded->read(&fog->mFogTextures.back().mImageData[0], encoded->size()); + + cell->setFog(fog.release()); } else { Vector2 min(mBounds.getMinimum().x, mBounds.getMinimum().y); Vector2 max(mBounds.getMaximum().x, mBounds.getMaximum().y); Vector2 length = max-min; - - // divide into segments const int segsX = std::ceil( length.x / sSize ); const int segsY = std::ceil( length.y / sSize ); + mInteriorName = cell->getCell()->mName; + + std::auto_ptr fog (new ESM::FogState()); + + fog->mBounds.mMinX = mBounds.getMinimum().x; + fog->mBounds.mMaxX = mBounds.getMaximum().x; + fog->mBounds.mMinY = mBounds.getMinimum().y; + fog->mBounds.mMaxY = mBounds.getMaximum().y; + fog->mNorthMarkerAngle = mAngle; + + fog->mFogTextures.reserve(segsX*segsY); + for (int x=0; xgetCell()->mName + "_" + coordStr(x,y) + "_fog"; + + TexturePtr tex = TextureManager::getSingleton().getByName(textureName); + if (tex.isNull()) + return; + + Ogre::Image image; + tex->convertToImage(image); + + fog->mFogTextures.push_back(ESM::FogTexture()); + + Ogre::DataStreamPtr encoded = image.encode("tga"); + fog->mFogTextures.back().mImageData.resize(encoded->size()); + encoded->read(&fog->mFogTextures.back().mImageData[0], encoded->size()); + + fog->mFogTextures.back().mX = x; + fog->mFogTextures.back().mY = y; } } + + cell->setFog(fog.release()); } } @@ -125,30 +168,38 @@ void LocalMap::requestMap(MWWorld::CellStore* cell, float zMin, float zMax) mCameraPosNode->setPosition(Vector3(0,0,0)); - render((x+0.5)*sSize, (y+0.5)*sSize, zMin, zMax, sSize, sSize, name); + // Note: using force=true for exterior cell maps. + // They must be updated even if they were visited before, because the set of surrounding active cells might be different + // (and objects in a different cell can "bleed" into another cell's map if they cross the border) + render((x+0.5)*sSize, (y+0.5)*sSize, zMin, zMax, sSize, sSize, name, true); + + if (mBuffers.find(name) == mBuffers.end()) + { + if (cell->getFog()) + loadFogOfWar(name, cell->getFog()->mFogTextures.back()); + else + createFogOfWar(name); + } } void LocalMap::requestMap(MWWorld::CellStore* cell, AxisAlignedBox bounds) { - // if we're in an empty cell, don't bother rendering anything + // If we're in an empty cell, bail out + // The operations in this function are only valid for finite bounds if (bounds.isNull ()) return; mInterior = true; - mBounds = bounds; - float zMin = mBounds.getMinimum().z; - float zMax = mBounds.getMaximum().z; + mBounds = bounds; + // Get the cell's NorthMarker rotation. This is used to rotate the entire map. const Vector2& north = MWBase::Environment::get().getWorld()->getNorthVector(cell); - Radian angle = Ogre::Math::ATan2 (north.x, north.y); + Radian angle = Ogre::Math::ATan2 (north.x, north.y) + Ogre::Degree(2); mAngle = angle.valueRadians(); - mCellCamera->setOrientation(Quaternion::IDENTITY); - mCameraRotNode->setOrientation(Quaternion(Math::Cos(mAngle/2.f), 0, 0, -Math::Sin(mAngle/2.f))); - - // rotate the cell and merge the rotated corners to the bounding box + // Rotate the cell and merge the rotated corners to the bounding box Vector2 _center(bounds.getCenter().x, bounds.getCenter().y); Vector3 _c1 = bounds.getCorner(AxisAlignedBox::FAR_LEFT_BOTTOM); Vector3 _c2 = bounds.getCorner(AxisAlignedBox::FAR_RIGHT_BOTTOM); @@ -168,9 +219,48 @@ void LocalMap::requestMap(MWWorld::CellStore* cell, mBounds.merge(Vector3(c3.x, c3.y, 0)); mBounds.merge(Vector3(c4.x, c4.y, 0)); - // apply a little padding - mBounds.setMinimum (mBounds.getMinimum() - Vector3(500,500,0)); - mBounds.setMaximum (mBounds.getMaximum() + Vector3(500,500,0)); + // Do NOT change padding! This will break older savegames. + // If the padding really needs to be changed, then it must be saved in the ESM::FogState and + // assume the old (500) value as default for older savegames. + const int padding = 500; + + // Apply a little padding + mBounds.setMinimum (mBounds.getMinimum() - Vector3(padding,padding,0)); + mBounds.setMaximum (mBounds.getMaximum() + Vector3(padding,padding,0)); + + float zMin = mBounds.getMinimum().z; + float zMax = mBounds.getMaximum().z; + + // If there is fog state in the CellStore (e.g. when it came from a savegame) we need to do some checks + // to see if this state is still valid. + // Both the cell bounds and the NorthMarker rotation could be changed by the content files or exchanged models. + // If they changed by too much (for bounds, < padding is considered acceptable) then parts of the interior might not + // be covered by the map anymore. + // The following code detects this, and discards the CellStore's fog state if it needs to. + if (cell->getFog()) + { + ESM::FogState* fog = cell->getFog(); + + Ogre::Vector3 newMin (fog->mBounds.mMinX, fog->mBounds.mMinY, zMin); + Ogre::Vector3 newMax (fog->mBounds.mMaxX, fog->mBounds.mMaxY, zMax); + + Ogre::Vector3 minDiff = newMin - mBounds.getMinimum(); + Ogre::Vector3 maxDiff = newMax - mBounds.getMaximum(); + + if (std::abs(minDiff.x) > 500 || std::abs(minDiff.y) > 500 + || std::abs(maxDiff.x) > 500 || std::abs(maxDiff.y) > 500 + || std::abs(mAngle - fog->mNorthMarkerAngle) > Ogre::Degree(5).valueRadians()) + { + // Nuke it + cell->setFog(NULL); + } + else + { + // Looks sane, use it + mBounds = Ogre::AxisAlignedBox(newMin, newMax); + mAngle = fog->mNorthMarkerAngle; + } + } Vector2 center(mBounds.getCenter().x, mBounds.getCenter().y); @@ -179,6 +269,9 @@ void LocalMap::requestMap(MWWorld::CellStore* cell, Vector2 length = max-min; + mCellCamera->setOrientation(Quaternion::IDENTITY); + mCameraRotNode->setOrientation(Quaternion(Math::Cos(mAngle/2.f), 0, 0, -Math::Sin(mAngle/2.f))); + mCameraPosNode->setPosition(Vector3(center.x, center.y, 0)); // divide into segments @@ -187,22 +280,100 @@ void LocalMap::requestMap(MWWorld::CellStore* cell, mInteriorName = cell->getCell()->mName; + int i=0; for (int x=0; xgetCell()->mName + "_" + coordStr(x,y); + + render(newcenter.x - center.x, newcenter.y - center.y, zMin, zMax, sSize, sSize, texturePrefix); - render(newcenter.x - center.x, newcenter.y - center.y, zMin, zMax, sSize, sSize, - cell->getCell()->mName + "_" + coordStr(x,y)); + if (!cell->getFog()) + createFogOfWar(texturePrefix); + else + { + ESM::FogState* fog = cell->getFog(); + + // We are using the same bounds and angle as we were using when the textures were originally made. Segments should come out the same. + if (i >= int(fog->mFogTextures.size())) + throw std::runtime_error("fog texture count mismatch"); + + ESM::FogTexture& esm = fog->mFogTextures[i]; + loadFogOfWar(texturePrefix, esm); + } + ++i; } } } +void LocalMap::createFogOfWar(const std::string& texturePrefix) +{ + const std::string texName = texturePrefix + "_fog"; + TexturePtr tex = createFogOfWarTexture(texName); + + // create a buffer to use for dynamic operations + std::vector buffer; + + // initialize to (0, 0, 0, 1) + buffer.resize(sFogOfWarResolution*sFogOfWarResolution, (255 << 24)); + + // upload to the texture + memcpy(tex->getBuffer()->lock(HardwareBuffer::HBL_DISCARD), &buffer[0], sFogOfWarResolution*sFogOfWarResolution*4); + tex->getBuffer()->unlock(); + + mBuffers[texturePrefix] = buffer; +} + +Ogre::TexturePtr LocalMap::createFogOfWarTexture(const std::string &texName) +{ + TexturePtr tex = TextureManager::getSingleton().getByName(texName); + if (tex.isNull()) + { + tex = TextureManager::getSingleton().createManual( + texName, + ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + TEX_TYPE_2D, + sFogOfWarResolution, sFogOfWarResolution, + 0, + PF_A8R8G8B8, + TU_DYNAMIC_WRITE_ONLY); + } + + return tex; +} + +void LocalMap::loadFogOfWar (const std::string& texturePrefix, ESM::FogTexture& esm) +{ + std::vector& data = esm.mImageData; + Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(&data[0], data.size())); + Ogre::Image image; + image.load(stream, "tga"); + + if (int(image.getWidth()) != sFogOfWarResolution || int(image.getHeight()) != sFogOfWarResolution) + throw std::runtime_error("fog texture size mismatch"); + + std::string texName = texturePrefix + "_fog"; + + Ogre::TexturePtr tex = createFogOfWarTexture(texName); + + tex->unload(); + tex->loadImage(image); + + // create a buffer to use for dynamic operations + std::vector buffer; + buffer.resize(sFogOfWarResolution*sFogOfWarResolution); + memcpy(&buffer[0], image.getData(), image.getSize()); + + mBuffers[texturePrefix] = buffer; +} + void LocalMap::render(const float x, const float y, const float zlow, const float zhigh, - const float xw, const float yw, const std::string& texture) + const float xw, const float yw, const std::string& texture, bool force) { mCellCamera->setFarClipDistance( (zhigh-zlow) + 2000 ); mCellCamera->setNearClipDistance(50); @@ -229,51 +400,22 @@ void LocalMap::render(const float x, const float y, if (tex.isNull()) { // 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(); + mRenderTarget->update(); - 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", + // create a new texture and blit to it + Ogre::TexturePtr tex = TextureManager::getSingleton().createManual( + texture, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, TEX_TYPE_2D, - xw*sFogOfWarResolution/sSize, yw*sFogOfWarResolution/sSize, + sMapResolution, sMapResolution, 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(); - - mBuffers[texture] = buffer; + PF_R8G8B8); + tex->getBuffer()->blit(mRenderTexture->getBuffer()); + } + else if (force) + { + mRenderTarget->update(); + tex->getBuffer()->blit(mRenderTexture->getBuffer()); } mRenderingManager->enableLights(true); @@ -304,6 +446,9 @@ bool LocalMap::isPositionExplored (float nX, float nY, int x, int y, bool interi if (mBuffers.find(texName) == mBuffers.end()) return false; + nX = std::max(0.f, std::min(1.f, nX)); + nY = std::max(0.f, std::min(1.f, nY)); + int texU = (sFogOfWarResolution-1) * nX; int texV = (sFogOfWarResolution-1) * nY; @@ -339,10 +484,7 @@ void LocalMap::updatePlayer (const Ogre::Vector3& position, const Ogre::Quaterni mCellX = x; mCellY = y; } - else - { - MWBase::Environment::get().getWindowManager()->setInteriorMapTexture(x,y); - } + MWBase::Environment::get().getWindowManager()->setActiveMap(x,y,mInterior); // convert from world coordinates to texture UV coordinates std::string texBaseName; @@ -414,6 +556,8 @@ void LocalMap::updatePlayer (const Ogre::Vector3& position, const Ogre::Quaterni } // copy to the texture + // NOTE: Could be optimized later. We actually only need to update the region that changed. + // Not a big deal at the moment, the FoW is only 32x32 anyway. memcpy(tex->getBuffer()->lock(HardwareBuffer::HBL_DISCARD), &aBuffer[0], sFogOfWarResolution*sFogOfWarResolution*4); tex->getBuffer()->unlock(); } diff --git a/apps/openmw/mwrender/localmap.hpp b/apps/openmw/mwrender/localmap.hpp index 638469d2d..babf7224e 100644 --- a/apps/openmw/mwrender/localmap.hpp +++ b/apps/openmw/mwrender/localmap.hpp @@ -11,6 +11,11 @@ namespace MWWorld class CellStore; } +namespace ESM +{ + class FogTexture; +} + namespace MWRender { class RenderingManager; @@ -24,6 +29,11 @@ namespace MWRender LocalMap(OEngine::Render::OgreRenderer*, MWRender::RenderingManager* rendering); ~LocalMap(); + /** + * Clear all savegame-specific data (i.e. fog of war textures) + */ + void clear(); + /** * Request the local map for an exterior cell. * @remarks It will either be loaded from a disk cache, @@ -54,10 +64,8 @@ namespace MWRender void updatePlayer (const Ogre::Vector3& position, const Ogre::Quaternion& orientation); /** - * Save the fog of war for the current cell to disk. - * @remarks This should be called before loading a - * new cell, as well as when the game is quit. - * @param current cell + * Save the fog of war for this cell to its CellStore. + * @remarks This should be called when unloading a cell, and for all active cells prior to saving the game. */ void saveFogOfWar(MWWorld::CellStore* cell); @@ -76,7 +84,6 @@ namespace MWRender OEngine::Render::OgreRenderer* mRendering; MWRender::RenderingManager* mRenderingManager; - // 1024*1024 pixels for a cell static const int sMapResolution = 512; // the dynamic texture is a bottleneck, so don't set this too high @@ -99,21 +106,29 @@ namespace MWRender float mAngle; const Ogre::Vector2 rotatePoint(const Ogre::Vector2& p, const Ogre::Vector2& c, const float angle); + /// @param force Always render, even if we already have a cached map void render(const float x, const float y, const float zlow, const float zhigh, const float xw, const float yw, - const std::string& texture); + const std::string& texture, bool force=false); + + // Creates a fog of war texture and initializes it to full black + void createFogOfWar(const std::string& texturePrefix); + + // Loads a fog of war texture from its ESM struct + void loadFogOfWar(const std::string& texturePrefix, ESM::FogTexture& esm); // FogTexture not const because MemoryDataStream doesn't accept it - void saveTexture(const std::string& texname, const std::string& filename); + Ogre::TexturePtr createFogOfWarTexture(const std::string& name); std::string coordStr(const int x, const int y); - // a buffer for the "fog of war" texture of the current cell. - // interior cells could be divided into multiple textures, - // so we store in a map. + // A buffer for the "fog of war" textures of the current cell. + // Both interior and exterior maps are possibly divided into multiple textures. std::map > mBuffers; - void deleteBuffers(); + // The render texture we will use to create the map images + Ogre::TexturePtr mRenderTexture; + Ogre::RenderTarget* mRenderTarget; bool mInterior; int mCellX, mCellY; diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index a09a58191..03ccde388 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -68,7 +68,8 @@ namespace MWRender float HeadAnimationTime::getValue() const { - // TODO: Handle eye blinking (time is in the text keys) + // TODO use time from text keys (Talk Start/Stop, Blink Start/Stop) + // TODO: Handle eye blinking if (MWBase::Environment::get().getSoundManager()->sayDone(mReference)) return 0; else @@ -236,7 +237,7 @@ void NpcAnimation::updateNpcBase() void NpcAnimation::updateParts() { mAlpha = 1.f; - const MWWorld::Class &cls = MWWorld::Class::get(mPtr); + const MWWorld::Class &cls = mPtr.getClass(); MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr); NpcType curType = Type_Normal; @@ -667,12 +668,12 @@ void NpcAnimation::showWeapons(bool showWeapon) mShowWeapons = showWeapon; if(showWeapon) { - MWWorld::InventoryStore &inv = MWWorld::Class::get(mPtr).getInventoryStore(mPtr); + MWWorld::InventoryStore &inv = mPtr.getClass().getInventoryStore(mPtr); MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); if(weapon != inv.end()) { Ogre::Vector3 glowColor = getEnchantmentColor(*weapon); - std::string mesh = MWWorld::Class::get(*weapon).getModel(*weapon); + std::string mesh = weapon->getClass().getModel(*weapon); addOrReplaceIndividualPart(ESM::PRT_Weapon, MWWorld::InventoryStore::Slot_CarriedRight, 1, mesh, !weapon->getClass().getEnchantment(*weapon).empty(), &glowColor); @@ -700,13 +701,13 @@ void NpcAnimation::showWeapons(bool showWeapon) void NpcAnimation::showCarriedLeft(bool show) { mShowCarriedLeft = show; - MWWorld::InventoryStore &inv = MWWorld::Class::get(mPtr).getInventoryStore(mPtr); + MWWorld::InventoryStore &inv = mPtr.getClass().getInventoryStore(mPtr); MWWorld::ContainerStoreIterator iter = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); if(show && iter != inv.end()) { Ogre::Vector3 glowColor = getEnchantmentColor(*iter); - std::string mesh = MWWorld::Class::get(*iter).getModel(*iter); + std::string mesh = iter->getClass().getModel(*iter); if (addOrReplaceIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, 1, mesh, !iter->getClass().getEnchantment(*iter).empty(), &glowColor)) { diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index 9c10ca84b..7953a3117 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -52,11 +52,11 @@ void Objects::insertBegin(const MWWorld::Ptr& ptr) const float *f = ptr.getRefData().getPosition().pos; insert->setPosition(f[0], f[1], f[2]); - insert->setScale(ptr.getCellRef().mScale, ptr.getCellRef().mScale, ptr.getCellRef().mScale); + insert->setScale(ptr.getCellRef().getScale(), ptr.getCellRef().getScale(), ptr.getCellRef().getScale()); // Convert MW rotation to a quaternion: - f = ptr.getCellRef().mPos.rot; + f = ptr.getCellRef().getPosition().rot; // Rotate around X axis Ogre::Quaternion xr(Ogre::Radian(-f[0]), Ogre::Vector3::UNIT_X); @@ -79,79 +79,82 @@ void Objects::insertModel(const MWWorld::Ptr &ptr, const std::string &mesh) std::auto_ptr anim(new ObjectAnimation(ptr, mesh)); - Ogre::AxisAlignedBox bounds = anim->getWorldBounds(); - Ogre::Vector3 extents = bounds.getSize(); - extents *= ptr.getRefData().getBaseNode()->getScale(); - float size = std::max(std::max(extents.x, extents.y), extents.z); - - bool small = (size < Settings::Manager::getInt("small object size", "Viewing distance")) && - Settings::Manager::getBool("limit small object distance", "Viewing distance"); - // do not fade out doors. that will cause holes and look stupid - if(ptr.getTypeName().find("Door") != std::string::npos) - small = false; - - if (mBounds.find(ptr.getCell()) == mBounds.end()) - mBounds[ptr.getCell()] = Ogre::AxisAlignedBox::BOX_NULL; - mBounds[ptr.getCell()].merge(bounds); - if(ptr.getTypeName() == typeid(ESM::Light).name()) anim->addLight(ptr.get()->mBase); - if(ptr.getTypeName() == typeid(ESM::Static).name() && - Settings::Manager::getBool("use static geometry", "Objects") && - anim->canBatch()) + if (!mesh.empty()) { - Ogre::StaticGeometry* sg = 0; - - if (small) + Ogre::AxisAlignedBox bounds = anim->getWorldBounds(); + Ogre::Vector3 extents = bounds.getSize(); + extents *= ptr.getRefData().getBaseNode()->getScale(); + float size = std::max(std::max(extents.x, extents.y), extents.z); + + bool small = (size < Settings::Manager::getInt("small object size", "Viewing distance")) && + Settings::Manager::getBool("limit small object distance", "Viewing distance"); + // do not fade out doors. that will cause holes and look stupid + if(ptr.getTypeName().find("Door") != std::string::npos) + small = false; + + if (mBounds.find(ptr.getCell()) == mBounds.end()) + mBounds[ptr.getCell()] = Ogre::AxisAlignedBox::BOX_NULL; + mBounds[ptr.getCell()].merge(bounds); + + if(ptr.getTypeName() == typeid(ESM::Static).name() && + Settings::Manager::getBool("use static geometry", "Objects") && + anim->canBatch()) { - if(mStaticGeometrySmall.find(ptr.getCell()) == mStaticGeometrySmall.end()) - { - uniqueID = uniqueID+1; - sg = mRenderer.getScene()->createStaticGeometry("sg" + Ogre::StringConverter::toString(uniqueID)); - sg->setOrigin(ptr.getRefData().getBaseNode()->getPosition()); - mStaticGeometrySmall[ptr.getCell()] = sg; + Ogre::StaticGeometry* sg = 0; - sg->setRenderingDistance(Settings::Manager::getInt("small object distance", "Viewing distance")); + if (small) + { + if(mStaticGeometrySmall.find(ptr.getCell()) == mStaticGeometrySmall.end()) + { + uniqueID = uniqueID+1; + sg = mRenderer.getScene()->createStaticGeometry("sg" + Ogre::StringConverter::toString(uniqueID)); + sg->setOrigin(ptr.getRefData().getBaseNode()->getPosition()); + mStaticGeometrySmall[ptr.getCell()] = sg; + + sg->setRenderingDistance(Settings::Manager::getInt("small object distance", "Viewing distance")); + } + else + sg = mStaticGeometrySmall[ptr.getCell()]; } else - sg = mStaticGeometrySmall[ptr.getCell()]; - } - else - { - if(mStaticGeometry.find(ptr.getCell()) == mStaticGeometry.end()) { - uniqueID = uniqueID+1; - sg = mRenderer.getScene()->createStaticGeometry("sg" + Ogre::StringConverter::toString(uniqueID)); - sg->setOrigin(ptr.getRefData().getBaseNode()->getPosition()); - mStaticGeometry[ptr.getCell()] = sg; + if(mStaticGeometry.find(ptr.getCell()) == mStaticGeometry.end()) + { + uniqueID = uniqueID+1; + sg = mRenderer.getScene()->createStaticGeometry("sg" + Ogre::StringConverter::toString(uniqueID)); + sg->setOrigin(ptr.getRefData().getBaseNode()->getPosition()); + mStaticGeometry[ptr.getCell()] = sg; + } + else + sg = mStaticGeometry[ptr.getCell()]; } - else - sg = mStaticGeometry[ptr.getCell()]; - } - // This specifies the size of a single batch region. - // If it is set too high: - // - there will be problems choosing the correct lights - // - the culling will be more inefficient - // If it is set too low: - // - there will be too many batches. - if(ptr.getCell()->isExterior()) - sg->setRegionDimensions(Ogre::Vector3(2048,2048,2048)); - else - sg->setRegionDimensions(Ogre::Vector3(1024,1024,1024)); + // This specifies the size of a single batch region. + // If it is set too high: + // - there will be problems choosing the correct lights + // - the culling will be more inefficient + // If it is set too low: + // - there will be too many batches. + if(ptr.getCell()->isExterior()) + sg->setRegionDimensions(Ogre::Vector3(2048,2048,2048)); + else + sg->setRegionDimensions(Ogre::Vector3(1024,1024,1024)); - sg->setVisibilityFlags(small ? RV_StaticsSmall : RV_Statics); + sg->setVisibilityFlags(small ? RV_StaticsSmall : RV_Statics); - sg->setCastShadows(true); + sg->setCastShadows(true); - sg->setRenderQueueGroup(RQG_Main); + sg->setRenderQueueGroup(RQG_Main); - anim->fillBatch(sg); - /* TODO: We could hold on to this and just detach it from the scene graph, so if the Ptr - * ever needs to modify we can reattach it and rebuild the StaticGeometry object without - * it. Would require associating the Ptr with the StaticGeometry. */ - anim.reset(); + anim->fillBatch(sg); + /* TODO: We could hold on to this and just detach it from the scene graph, so if the Ptr + * ever needs to modify we can reattach it and rebuild the StaticGeometry object without + * it. Would require associating the Ptr with the StaticGeometry. */ + anim.reset(); + } } if(anim.get() != NULL) @@ -240,25 +243,25 @@ Ogre::AxisAlignedBox Objects::getDimensions(MWWorld::CellStore* cell) void Objects::enableLights() { PtrAnimationMap::const_iterator it = mObjects.begin(); - for(;it != mObjects.end();it++) + for(;it != mObjects.end();++it) it->second->enableLights(true); } void Objects::disableLights() { PtrAnimationMap::const_iterator it = mObjects.begin(); - for(;it != mObjects.end();it++) + for(;it != mObjects.end();++it) it->second->enableLights(false); } void Objects::update(float dt, Ogre::Camera* camera) { PtrAnimationMap::const_iterator it = mObjects.begin(); - for(;it != mObjects.end();it++) + for(;it != mObjects.end();++it) it->second->runAnimation(dt); it = mObjects.begin(); - for(;it != mObjects.end();it++) + for(;it != mObjects.end();++it) it->second->preRender(camera); } diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index 67bc75e02..92a49acc0 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -35,7 +35,7 @@ OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNod mSupported = (mSunTotalAreaQuery != 0) && (mSunVisibleAreaQuery != 0); } - catch (Ogre::Exception& e) + catch (Ogre::Exception&) { mSupported = false; } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 97283d065..8a22c63c6 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -216,13 +216,14 @@ OEngine::Render::Fader* RenderingManager::getFader() return mRendering.getFader(); } - MWRender::Camera* RenderingManager::getCamera() const +MWRender::Camera* RenderingManager::getCamera() const { return mCamera; } void RenderingManager::removeCell (MWWorld::CellStore *store) { + mLocalMap->saveFogOfWar(store); mObjects->removeCell(store); mActors->removeCell(store); mDebugging->cellRemoved(store); @@ -233,9 +234,9 @@ void RenderingManager::removeWater () mWater->setActive(false); } -void RenderingManager::toggleWater() +bool RenderingManager::toggleWater() { - mWater->toggle(); + return mWater->toggle(); } void RenderingManager::cellAdded (MWWorld::CellStore *store) @@ -248,7 +249,7 @@ void RenderingManager::cellAdded (MWWorld::CellStore *store) void RenderingManager::addObject (const MWWorld::Ptr& ptr){ const MWWorld::Class& class_ = - MWWorld::Class::get (ptr); + ptr.getClass(); class_.insertObjectRendering(ptr, *this); } @@ -278,7 +279,7 @@ void RenderingManager::rotateObject(const MWWorld::Ptr &ptr) mCamera->rotateCamera(-rot, false); Ogre::Quaternion newo = Ogre::Quaternion(Ogre::Radian(rot.z), Ogre::Vector3::NEGATIVE_UNIT_Z); - if(!MWWorld::Class::get(ptr).isActor()) + if(!ptr.getClass().isActor()) newo = Ogre::Quaternion(Ogre::Radian(rot.x), Ogre::Vector3::NEGATIVE_UNIT_X) * Ogre::Quaternion(Ogre::Radian(rot.y), Ogre::Vector3::NEGATIVE_UNIT_Y) * newo; ptr.getRefData().getBaseNode()->setOrientation(newo); @@ -293,7 +294,7 @@ RenderingManager::updateObjectCell(const MWWorld::Ptr &old, const MWWorld::Ptr & Ogre::SceneNode *parent = child->getParentSceneNode(); parent->removeChild(child); - if (MWWorld::Class::get(old).isActor()) { + if (old.getClass().isActor()) { mActors->updateObjectCell(old, cur); } else { mObjects->updateObjectCell(old, cur); @@ -313,7 +314,7 @@ void RenderingManager::rebuildPtr(const MWWorld::Ptr &ptr) NpcAnimation *anim = NULL; if(ptr.getRefData().getHandle() == "player") anim = mPlayerAnimation; - else if(MWWorld::Class::get(ptr).isActor()) + else if(ptr.getClass().isActor()) anim = dynamic_cast(mActors->getAnimation(ptr)); if(anim) { @@ -336,7 +337,7 @@ void RenderingManager::update (float duration, bool paused) MWWorld::Ptr player = world->getPlayerPtr(); - int blind = MWWorld::Class::get(player).getCreatureStats(player).getMagicEffects().get(ESM::MagicEffect::Blind).mMagnitude; + int blind = player.getClass().getCreatureStats(player).getMagicEffects().get(ESM::MagicEffect::Blind).mMagnitude; mRendering.getFader()->setFactor(std::max(0.f, 1.f-(blind / 100.f))); setAmbientMode(); @@ -379,6 +380,10 @@ void RenderingManager::update (float duration, bool paused) mCamera->update(duration, paused); + Ogre::SceneNode *node = data.getBaseNode(); + Ogre::Quaternion orient = node->_getDerivedOrientation(); + mLocalMap->updatePlayer(playerPos, orient); + if(paused) return; @@ -392,10 +397,6 @@ void RenderingManager::update (float duration, bool paused) mSkyManager->setGlare(mOcclusionQuery->getSunVisibility()); - Ogre::SceneNode *node = data.getBaseNode(); - Ogre::Quaternion orient = node->_getDerivedOrientation(); - - mLocalMap->updatePlayer(playerPos, orient); mWater->updateUnderwater(world->isUnderwater(player.getCell(), cam)); @@ -563,7 +564,8 @@ void RenderingManager::configureAmbient(MWWorld::CellStore &mCell) Ogre::ColourValue colour; colour.setAsABGR (mCell.getCell()->mAmbi.mSunlight); mSun->setDiffuseColour (colour); - mSun->setDirection(0,-1,0); + mSun->setDirection(1,-1,-1); + sunEnable(false); } } // Switch through lighting modes. @@ -597,7 +599,7 @@ void RenderingManager::setAmbientColour(const Ogre::ColourValue& colour) mAmbientColor = colour; MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - int nightEye = MWWorld::Class::get(player).getCreatureStats(player).getMagicEffects().get(ESM::MagicEffect::NightEye).mMagnitude; + int nightEye = player.getClass().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)); @@ -670,7 +672,7 @@ void RenderingManager::requestMap(MWWorld::CellStore* cell) mLocalMap->requestMap(cell, mObjects->getDimensions(cell)); } -void RenderingManager::preCellChange(MWWorld::CellStore* cell) +void RenderingManager::writeFog(MWWorld::CellStore* cell) { mLocalMap->saveFogOfWar(cell); } @@ -692,15 +694,8 @@ Shadows* RenderingManager::getShadows() return mShadows; } -void RenderingManager::switchToInterior() +void RenderingManager::notifyWorldSpaceChanged() { - // TODO: also do this when switching worldspace - mEffectManager->clear(); -} - -void RenderingManager::switchToExterior() -{ - // TODO: also do this when switching worldspace mEffectManager->clear(); } @@ -756,7 +751,8 @@ void RenderingManager::processChangedSettings(const Settings::CategorySettingVec } else if (it->second == "max viewing distance" && it->first == "Viewing distance") { - if (!MWBase::Environment::get().getWorld()->isCellExterior() && !MWBase::Environment::get().getWorld()->isCellQuasiExterior()) + if (!MWBase::Environment::get().getWorld()->isCellExterior() && !MWBase::Environment::get().getWorld()->isCellQuasiExterior() + && MWBase::Environment::get().getWorld()->getPlayerPtr().mCell) configureFog(*MWBase::Environment::get().getWorld()->getPlayerPtr().getCell()); } else if (it->first == "Video" && ( @@ -1056,4 +1052,10 @@ void RenderingManager::spawnEffect(const std::string &model, const std::string & mEffectManager->addEffect(model, texture, worldPosition, scale); } +void RenderingManager::clear() +{ + mLocalMap->clear(); + notifyWorldSpaceChanged(); +} + } // namespace diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 423a7078a..f539f9270 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -107,12 +107,15 @@ public: void cellAdded (MWWorld::CellStore *store); void waterAdded(MWWorld::CellStore *store); + /// Clear all savegame-specific data (i.e. fog of war textures) + void clear(); + void enableTerrain(bool enable); void removeWater(); - void preCellChange (MWWorld::CellStore* store); - ///< this event is fired immediately before changing cell + /// Write current fog of war for this cell to the CellStore + void writeFog (MWWorld::CellStore* store); void addObject (const MWWorld::Ptr& ptr); void removeObject (const MWWorld::Ptr& ptr); @@ -124,7 +127,7 @@ public: void rotateObject (const MWWorld::Ptr& ptr); void setWaterHeight(const float height); - void toggleWater(); + bool toggleWater(); /// Updates object rendering after cell change /// \param old Object reference in previous cell @@ -160,8 +163,7 @@ public: Shadows* getShadows(); - void switchToInterior(); - void switchToExterior(); + void notifyWorldSpaceChanged(); void getTriangleBatchCount(unsigned int &triangles, unsigned int &batches); diff --git a/apps/openmw/mwrender/ripplesimulation.cpp b/apps/openmw/mwrender/ripplesimulation.cpp index f52deedcc..74216c1de 100644 --- a/apps/openmw/mwrender/ripplesimulation.cpp +++ b/apps/openmw/mwrender/ripplesimulation.cpp @@ -28,7 +28,9 @@ RippleSimulation::RippleSimulation(Ogre::SceneManager* mainSceneManager) mRippleAreaLength(1000), mImpulseSize(20), mTexelOffset(0,0), - mFirstUpdate(true) + mFirstUpdate(true), + mRectangle(NULL), + mImpulse(NULL) { Ogre::AxisAlignedBox aabInf; aabInf.setInfinite(); @@ -105,6 +107,7 @@ RippleSimulation::RippleSimulation(Ogre::SceneManager* mainSceneManager) RippleSimulation::~RippleSimulation() { delete mRectangle; + delete mImpulse; Ogre::Root::getSingleton().destroySceneManager(mSceneMgr); } diff --git a/apps/openmw/mwrender/videoplayer.cpp b/apps/openmw/mwrender/videoplayer.cpp index 80704ca7c..79f2fa948 100644 --- a/apps/openmw/mwrender/videoplayer.cpp +++ b/apps/openmw/mwrender/videoplayer.cpp @@ -6,14 +6,9 @@ #include #include -#include #include -#include #include -#include -#include -#include -#include +#include #include @@ -21,9 +16,6 @@ #include "../mwbase/soundmanager.hpp" #include "../mwsound/sound_decoder.hpp" #include "../mwsound/sound.hpp" -#include "../mwbase/inputmanager.hpp" - -#include "renderconst.hpp" #ifdef _WIN32 #include @@ -58,6 +50,29 @@ extern "C" #endif } +#ifdef _WIN32 + // Decide whether to play binkaudio. + #include + // libavcodec versions 54.10.100 (or maybe earlier) to 54.54.100 potentially crashes Windows 64bit. + // From version 54.56 or higher, there's no sound due to the encoding format changing from S16 to FLTP + // (see https://gitorious.org/ffmpeg/ffmpeg/commit/7bfd1766d1c18f07b0a2dd042418a874d49ea60d and + // http://git.videolan.org/?p=ffmpeg.git;a=commitdiff;h=3049d5b9b32845c86aa5588bb3352bdeb2edfdb2;hp=43c6b45a53a186a187f7266e4d6bd3c2620519f1), + // but does not crash (or at least no known crash). + #if (LIBAVCODEC_VERSION_MAJOR > 54) + #define FFMPEG_PLAY_BINKAUDIO + #else + #ifdef _WIN64 + #if ((LIBAVCODEC_VERSION_MAJOR == 54) && (LIBAVCODEC_VERSION_MINOR >= 55)) + #define FFMPEG_PLAY_BINKAUDIO + #endif + #else + #if ((LIBAVCODEC_VERSION_MAJOR == 54) && (LIBAVCODEC_VERSION_MINOR >= 10)) + #define FFMPEG_PLAY_BINKAUDIO + #endif + #endif + #endif +#endif + #define MAX_AUDIOQ_SIZE (5 * 16 * 1024) #define MAX_VIDEOQ_SIZE (5 * 256 * 1024) #define AV_SYNC_THRESHOLD 0.01 @@ -978,8 +993,14 @@ void VideoState::init(const std::string& resourceName) MWBase::Environment::get().getSoundManager()->pauseSounds(); this->external_clock_base = av_gettime(); + +#if !defined(_WIN32) || defined(FFMPEG_PLAY_BINKAUDIO) if(audio_index >= 0) this->stream_open(audio_index, this->format_ctx); +#else + std::cout<<"FFmpeg sound disabled for \""+resourceName+"\""<= 0) { this->stream_open(video_index, this->format_ctx); diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 5368cbe68..2cbc4462c 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -326,10 +326,11 @@ void Water::setHeight(const float height) sh::Factory::getInstance ().setSharedParameter ("waterLevel", sh::makeProperty(new sh::FloatValue(height))); } -void Water::toggle() +bool Water::toggle() { mToggled = !mToggled; updateVisible(); + return mToggled; } void diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp index 481a41297..6a7b05a3a 100644 --- a/apps/openmw/mwrender/water.hpp +++ b/apps/openmw/mwrender/water.hpp @@ -150,7 +150,7 @@ namespace MWRender { void setActive(bool active); - void toggle(); + bool toggle(); void update(float dt, Ogre::Vector3 player); void frameStarted(float dt); diff --git a/apps/openmw/mwrender/weaponanimation.hpp b/apps/openmw/mwrender/weaponanimation.hpp index c09aa65d9..cbe910c71 100644 --- a/apps/openmw/mwrender/weaponanimation.hpp +++ b/apps/openmw/mwrender/weaponanimation.hpp @@ -33,6 +33,7 @@ namespace MWRender { public: WeaponAnimation() : mPitchFactor(0) {} + virtual ~WeaponAnimation() {} virtual void attachArrow(MWWorld::Ptr actor); virtual void releaseArrow(MWWorld::Ptr actor); diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index a34c5476c..cc17905df 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -16,7 +16,6 @@ #include "../mwmechanics/aifollow.hpp" #include "../mwmechanics/aitravel.hpp" #include "../mwmechanics/aiwander.hpp" -#include "../mwmechanics/aicombat.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -48,7 +47,7 @@ namespace MWScript for (unsigned int i=0; i @@ -299,7 +298,7 @@ namespace MWScript for (unsigned int i=0; igetPtr(targetID, true) )); + MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(targetID, true); + MWBase::Environment::get().getMechanicsManager()->startCombat(actor, target); } }; @@ -451,7 +447,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { MWWorld::Ptr actor = R()(runtime); - MWMechanics::CreatureStats& creatureStats = MWWorld::Class::get(actor).getCreatureStats(actor); + MWMechanics::CreatureStats& creatureStats = actor.getClass().getCreatureStats(actor); creatureStats.getAiSequence().stopCombat(); creatureStats.setHostile(false); } diff --git a/apps/openmw/mwscript/cellextensions.cpp b/apps/openmw/mwscript/cellextensions.cpp index 903612258..2e2e9b698 100644 --- a/apps/openmw/mwscript/cellextensions.cpp +++ b/apps/openmw/mwscript/cellextensions.cpp @@ -88,6 +88,12 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { + if (!MWBase::Environment::get().getWorld()->getPlayerPtr().isInCell()) + { + runtime.push (0); + return; + } + bool interior = !MWBase::Environment::get().getWorld()->getPlayerPtr().getCell()->getCell()->isExterior(); @@ -104,6 +110,11 @@ namespace MWScript std::string name = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); + if (!MWBase::Environment::get().getWorld()->getPlayerPtr().isInCell()) + { + runtime.push(0); + return; + } const ESM::Cell *cell = MWBase::Environment::get().getWorld()->getPlayerPtr().getCell()->getCell(); std::string current = cell->mName; @@ -131,6 +142,11 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { + if (!MWBase::Environment::get().getWorld()->getPlayerPtr().isInCell()) + { + runtime.push(0.f); + return; + } MWWorld::CellStore *cell = MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(); if (cell->getCell()->hasWater()) runtime.push (cell->getWaterLevel()); @@ -147,6 +163,11 @@ namespace MWScript { Interpreter::Type_Float level = runtime[0].mFloat; + if (!MWBase::Environment::get().getWorld()->getPlayerPtr().isInCell()) + { + return; + } + MWWorld::CellStore *cell = MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(); if (cell->getCell()->isExterior()) @@ -165,6 +186,11 @@ namespace MWScript { Interpreter::Type_Float level = runtime[0].mFloat; + if (!MWBase::Environment::get().getWorld()->getPlayerPtr().isInCell()) + { + return; + } + MWWorld::CellStore *cell = MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(); if (cell->getCell()->isExterior()) diff --git a/apps/openmw/mwscript/compilercontext.cpp b/apps/openmw/mwscript/compilercontext.cpp index 1b3e769bf..3ff75eeae 100644 --- a/apps/openmw/mwscript/compilercontext.cpp +++ b/apps/openmw/mwscript/compilercontext.cpp @@ -45,7 +45,7 @@ namespace MWScript { MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPtr (id, false); - script = MWWorld::Class::get (ptr).getScript (ptr); + script = ptr.getClass().getScript (ptr); reference = true; } diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp index 66c8d4468..93711d036 100644 --- a/apps/openmw/mwscript/containerextensions.cpp +++ b/apps/openmw/mwscript/containerextensions.cpp @@ -117,11 +117,11 @@ namespace MWScript if (count == 0) return; - MWWorld::ContainerStore& store = MWWorld::Class::get (ptr).getContainerStore (ptr); + MWWorld::ContainerStore& store = ptr.getClass().getContainerStore (ptr); std::string itemName; for (MWWorld::ContainerStoreIterator iter(store.begin()); iter != store.end(); ++iter) - if (::Misc::StringUtils::ciEqual(iter->getCellRef().mRefID, item)) + if (::Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), item)) itemName = iter->getClass().getName(*iter); int numRemoved = store.remove(item, count, ptr); @@ -165,7 +165,7 @@ namespace MWScript MWWorld::ContainerStoreIterator it = invStore.begin(); for (; it != invStore.end(); ++it) { - if (::Misc::StringUtils::ciEqual(it->getCellRef().mRefID, item)) + if (::Misc::StringUtils::ciEqual(it->getCellRef().getRefId(), item)) break; } if (it == invStore.end()) @@ -231,7 +231,7 @@ namespace MWScript throw std::runtime_error ("armor index out of range"); } - MWWorld::InventoryStore& invStore = MWWorld::Class::get(ptr).getInventoryStore (ptr); + MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore (ptr); MWWorld::ContainerStoreIterator it = invStore.getSlot (slot); if (it == invStore.end() || it->getTypeName () != typeid(ESM::Armor).name()) @@ -240,7 +240,7 @@ namespace MWScript return; } - int skill = MWWorld::Class::get(*it).getEquipmentSkill (*it) ; + int skill = it->getClass().getEquipmentSkill (*it) ; if (skill == ESM::Skill::HeavyArmor) runtime.push(2); else if (skill == ESM::Skill::MediumArmor) @@ -264,11 +264,11 @@ namespace MWScript std::string item = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - MWWorld::InventoryStore& invStore = MWWorld::Class::get(ptr).getInventoryStore (ptr); + MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore (ptr); for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) { MWWorld::ContainerStoreIterator it = invStore.getSlot (slot); - if (it != invStore.end() && ::Misc::StringUtils::ciEqual(it->getCellRef().mRefID, item)) + if (it != invStore.end() && ::Misc::StringUtils::ciEqual(it->getCellRef().getRefId(), item)) { runtime.push(1); return; @@ -290,12 +290,12 @@ namespace MWScript const std::string &name = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - MWWorld::InventoryStore& invStore = MWWorld::Class::get(ptr).getInventoryStore (ptr); + MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore (ptr); for (MWWorld::ContainerStoreIterator it = invStore.begin(MWWorld::ContainerStore::Type_Miscellaneous); it != invStore.end(); ++it) { - if (::Misc::StringUtils::ciEqual(it->getCellRef().mSoul, name)) + if (::Misc::StringUtils::ciEqual(it->getCellRef().getSoul(), name)) { runtime.push(1); return; @@ -314,7 +314,7 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - MWWorld::InventoryStore& invStore = MWWorld::Class::get(ptr).getInventoryStore (ptr); + MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore (ptr); MWWorld::ContainerStoreIterator it = invStore.getSlot (MWWorld::InventoryStore::Slot_CarriedRight); if (it == invStore.end() || it->getTypeName () != typeid(ESM::Weapon).name()) { diff --git a/apps/openmw/mwscript/dialogueextensions.cpp b/apps/openmw/mwscript/dialogueextensions.cpp index a882ae05e..9dde65ab2 100644 --- a/apps/openmw/mwscript/dialogueextensions.cpp +++ b/apps/openmw/mwscript/dialogueextensions.cpp @@ -34,7 +34,15 @@ namespace MWScript Interpreter::Type_Integer index = runtime[0].mInteger; runtime.pop(); - MWBase::Environment::get().getJournal()->addEntry (quest, index); + // Invoking Journal with a non-existing index is allowed, and triggers no errors. Seriously? :( + try + { + MWBase::Environment::get().getJournal()->addEntry (quest, index); + } + catch (...) + { + MWBase::Environment::get().getJournal()->setJournalIndex(quest, index); + } } }; @@ -141,7 +149,7 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - MWWorld::Class::get(ptr).getNpcStats (ptr).setReputation (MWWorld::Class::get(ptr).getNpcStats (ptr).getReputation () + value); + ptr.getClass().getNpcStats (ptr).setReputation (ptr.getClass().getNpcStats (ptr).getReputation () + value); } }; @@ -156,7 +164,7 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - MWWorld::Class::get(ptr).getNpcStats (ptr).setReputation (value); + ptr.getClass().getNpcStats (ptr).setReputation (value); } }; @@ -169,7 +177,7 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - runtime.push (MWWorld::Class::get(ptr).getNpcStats (ptr).getReputation ()); + runtime.push (ptr.getClass().getNpcStats (ptr).getReputation ()); } }; @@ -184,10 +192,49 @@ namespace MWScript MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); - runtime.push (MWWorld::Class::get(ptr).getNpcStats (ptr).isSameFaction (MWWorld::Class::get(player).getNpcStats (player))); + runtime.push (ptr.getClass().getNpcStats (ptr).isSameFaction (player.getClass().getNpcStats (player))); } }; + class OpModFactionReaction : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + std::string faction1 = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + std::string faction2 = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + int modReaction = runtime[0].mInteger; + runtime.pop(); + + MWBase::Environment::get().getDialogueManager()->modFactionReaction(faction1, faction2, modReaction); + } + }; + + class OpGetFactionReaction : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + std::string faction1 = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + std::string faction2 = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + // ignore extra garbage argument + runtime.pop(); + + runtime.push(MWBase::Environment::get().getDialogueManager() + ->getFactionReaction(faction1, faction2)); + } + }; + void installOpcodes (Interpreter::Interpreter& interpreter) { @@ -207,6 +254,8 @@ namespace MWScript interpreter.installSegment5 (Compiler::Dialogue::opcodeGetReputationExplicit, new OpGetReputation); interpreter.installSegment5 (Compiler::Dialogue::opcodeSameFaction, new OpSameFaction); interpreter.installSegment5 (Compiler::Dialogue::opcodeSameFactionExplicit, new OpSameFaction); + interpreter.installSegment5 (Compiler::Dialogue::opcodeModFactionReaction, new OpModFactionReaction); + interpreter.installSegment5 (Compiler::Dialogue::opcodeGetFactionReaction, new OpGetFactionReaction); } } diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index 70186a089..24b0b6f7a 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -54,7 +54,10 @@ op 0x20025: AiFollowCell, explicit reference op 0x20026: ModRegion op 0x20027: RemoveSoulGem op 0x20028: RemoveSoulGem, explicit reference -opcodes 0x20029-0x3ffff unused +op 0x20029: PCRaiseRank, explicit reference +op 0x2002a: PCLowerRank, explicit reference +op 0x2002b: PCJoinFaction, explicit reference +opcodes 0x2002c-0x3ffff unused Segment 4: (not implemented yet) @@ -385,5 +388,10 @@ op 0x200023c: StopCombat op 0x200023d: StopCombatExplicit op 0x200023e: GetPcInJail op 0x200023f: GetPcTraveling +op 0x2000240: onKnockout +op 0x2000241: onKnockoutExplicit +op 0x2000242: ModFactionReaction +op 0x2000243: GetFactionReaction +op 0x2000244: Activate, explicit -opcodes 0x2000240-0x3ffffff unused +opcodes 0x2000245-0x3ffffff unused diff --git a/apps/openmw/mwscript/globalscripts.cpp b/apps/openmw/mwscript/globalscripts.cpp index 527c576cc..8e118e2f8 100644 --- a/apps/openmw/mwscript/globalscripts.cpp +++ b/apps/openmw/mwscript/globalscripts.cpp @@ -97,7 +97,7 @@ namespace MWScript return mScripts.size(); } - void GlobalScripts::write (ESM::ESMWriter& writer) const + void GlobalScripts::write (ESM::ESMWriter& writer, Loading::Listener& progress) const { for (std::map >::const_iterator iter (mScripts.begin()); iter!=mScripts.end(); ++iter) @@ -113,6 +113,7 @@ namespace MWScript writer.startRecord (ESM::REC_GSCR); script.save (writer); writer.endRecord (ESM::REC_GSCR); + progress.increaseProgress(); } } diff --git a/apps/openmw/mwscript/globalscripts.hpp b/apps/openmw/mwscript/globalscripts.hpp index de63b9906..97584a5b8 100644 --- a/apps/openmw/mwscript/globalscripts.hpp +++ b/apps/openmw/mwscript/globalscripts.hpp @@ -14,6 +14,11 @@ namespace ESM class ESMReader; } +namespace Loading +{ + class Listener; +} + namespace MWWorld { struct ESMStore; @@ -46,7 +51,7 @@ namespace MWScript int countSavedGameRecords() const; - void write (ESM::ESMWriter& writer) const; + void write (ESM::ESMWriter& writer, Loading::Listener& progress) const; bool readRecord (ESM::ESMReader& reader, int32_t type); ///< Records for variables that do not exist are dropped silently. diff --git a/apps/openmw/mwscript/guiextensions.cpp b/apps/openmw/mwscript/guiextensions.cpp index 57fc2d470..be241a564 100644 --- a/apps/openmw/mwscript/guiextensions.cpp +++ b/apps/openmw/mwscript/guiextensions.cpp @@ -92,7 +92,8 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { - MWBase::Environment::get().getWindowManager()->toggleFogOfWar(); + runtime.getContext().report(MWBase::Environment::get().getWindowManager()->toggleFogOfWar() ? "Fog of war -> On" + : "Fog of war -> Off"); } }; @@ -102,7 +103,8 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { - MWBase::Environment::get().getWindowManager()->toggleFullHelp(); + runtime.getContext().report(MWBase::Environment::get().getWindowManager()->toggleFullHelp() ? "Full help -> On" + : "Full help -> Off"); } }; diff --git a/apps/openmw/mwscript/interpretercontext.cpp b/apps/openmw/mwscript/interpretercontext.cpp index b79808d08..6bf50371b 100644 --- a/apps/openmw/mwscript/interpretercontext.cpp +++ b/apps/openmw/mwscript/interpretercontext.cpp @@ -66,7 +66,7 @@ namespace MWScript { const MWWorld::Ptr ptr = getReference (id, false); - id = MWWorld::Class::get (ptr).getScript (ptr); + id = ptr.getClass().getScript (ptr); ptr.getRefData().setLocals ( *MWBase::Environment::get().getWorld()->getStore().get().find (id)); @@ -86,7 +86,7 @@ namespace MWScript { const MWWorld::Ptr ptr = getReference (id, false); - id = MWWorld::Class::get (ptr).getScript (ptr); + id = ptr.getClass().getScript (ptr); ptr.getRefData().setLocals ( *MWBase::Environment::get().getWorld()->getStore().get().find (id)); @@ -263,7 +263,7 @@ namespace MWScript std::string InterpreterContext::getNPCRank() const { - std::map ranks = MWWorld::Class::get (mReference).getNpcStats (mReference).getFactionRanks(); + std::map ranks = mReference.getClass().getNpcStats (mReference).getFactionRanks(); std::map::const_iterator it = ranks.begin(); MWBase::World *world = MWBase::Environment::get().getWorld(); @@ -299,18 +299,26 @@ namespace MWScript MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = world->getPlayerPtr(); - std::string factionId = MWWorld::Class::get (mReference).getNpcStats (mReference).getFactionRanks().begin()->first; + std::string factionId = mReference.getClass().getNpcStats (mReference).getFactionRanks().begin()->first; - std::map ranks = MWWorld::Class::get (player).getNpcStats (player).getFactionRanks(); - std::map::const_iterator it = ranks.begin(); + std::map ranks = player.getClass().getNpcStats (player).getFactionRanks(); + std::map::const_iterator it = ranks.find(factionId); + int rank = -1; + if (it != ranks.end()) + rank = it->second; + + // If you are not in the faction, PcRank returns the first rank, for whatever reason. + // This is used by the dialogue when joining the Thieves Guild in Balmora. + if (rank == -1) + rank = 0; const MWWorld::ESMStore &store = world->getStore(); const ESM::Faction *faction = store.get().find(factionId); - if(it->second < 0 || it->second > 9) // there are only 10 ranks + if(rank < 0 || rank > 9) // there are only 10 ranks return ""; - return faction->mRanks[it->second]; + return faction->mRanks[rank]; } std::string InterpreterContext::getPCNextRank() const @@ -318,34 +326,34 @@ namespace MWScript MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = world->getPlayerPtr(); - std::string factionId = MWWorld::Class::get (mReference).getNpcStats (mReference).getFactionRanks().begin()->first; + std::string factionId = mReference.getClass().getNpcStats (mReference).getFactionRanks().begin()->first; - const MWWorld::ESMStore &store = world->getStore(); - const ESM::Faction *faction = store.get().find(factionId); + std::map ranks = player.getClass().getNpcStats (player).getFactionRanks(); + std::map::const_iterator it = ranks.find(factionId); + int rank = -1; + if (it != ranks.end()) + rank = it->second; - std::map ranks = MWWorld::Class::get (player).getNpcStats (player).getFactionRanks(); + ++rank; // Next rank - if (!ranks.empty()) - { - std::map::const_iterator it = ranks.begin(); + // if we are already at max rank, there is no next rank + if (rank > 9) + rank = 9; + + const MWWorld::ESMStore &store = world->getStore(); + const ESM::Faction *faction = store.get().find(factionId); - if(it->second < -1 || it->second > 9) - return ""; + if(rank < 0 || rank > 9) + return ""; - if(it->second <= 8) // If player is at max rank, there is no next rank - return faction->mRanks[it->second + 1]; - else - return faction->mRanks[it->second]; - } - else - return faction->mRanks[0]; + return faction->mRanks[rank]; } int InterpreterContext::getPCBounty() const { MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = world->getPlayerPtr(); - return MWWorld::Class::get (player).getNpcStats (player).getBounty(); + return player.getClass().getNpcStats (player).getBounty(); } std::string InterpreterContext::getCurrentCellName() const @@ -370,10 +378,14 @@ namespace MWScript float InterpreterContext::getDistance (const std::string& name, const std::string& id) const { - // TODO handle exterior cells (when ref and ref2 are located in different cells) - const MWWorld::Ptr ref2 = getReference (id, false); + const MWWorld::Ptr ref2 = getReference (id, false, false); + // If either actor is in a non-active cell, return a large value (just like vanilla) + if (ref2.isEmpty()) + return std::numeric_limits().max(); - const MWWorld::Ptr ref = MWBase::Environment::get().getWorld()->getPtr (name, true); + const MWWorld::Ptr ref = getReference (name, false, false); + if (ref.isEmpty()) + return std::numeric_limits().max(); double diff[3]; @@ -401,28 +413,25 @@ namespace MWScript return mActivationHandled; } - void InterpreterContext::activate (const MWWorld::Ptr& ptr, - boost::shared_ptr action) + void InterpreterContext::activate (const MWWorld::Ptr& ptr) { mActivated = ptr; mActivationHandled = false; - mAction = action; } - void InterpreterContext::executeActivation() + void InterpreterContext::executeActivation(MWWorld::Ptr ptr) { - if (!mAction.get()) - throw std::runtime_error ("activation failed, because no action to perform"); - - mAction->execute (MWBase::Environment::get().getWorld()->getPlayerPtr()); - mActivationHandled = true; + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + boost::shared_ptr action = (ptr.getClass().activate(ptr, player)); + action->execute (player); + if (mActivated == ptr) + mActivationHandled = true; } void InterpreterContext::clearActivation() { mActivated = MWWorld::Ptr(); mActivationHandled = false; - mAction.reset(); } float InterpreterContext::getSecondsPassed() const diff --git a/apps/openmw/mwscript/interpretercontext.hpp b/apps/openmw/mwscript/interpretercontext.hpp index 9fb7fa2bf..1137efed3 100644 --- a/apps/openmw/mwscript/interpretercontext.hpp +++ b/apps/openmw/mwscript/interpretercontext.hpp @@ -31,7 +31,6 @@ namespace MWScript MWWorld::Ptr mActivated; bool mActivationHandled; - boost::shared_ptr mAction; MWWorld::Ptr getReference (const std::string& id, bool activeOnly, bool doThrow=true); @@ -126,12 +125,12 @@ namespace MWScript bool hasActivationBeenHandled() const; - void activate (const MWWorld::Ptr& ptr, boost::shared_ptr action); - ///< Store reference acted upon and action. The actual execution of the action does not + void activate (const MWWorld::Ptr& ptr); + ///< Store reference acted upon. The actual execution of the action does not /// take place here. - void executeActivation(); - ///< Execute the action defined by the last activate call. + void executeActivation(MWWorld::Ptr ptr); + ///< Execute the activation action for this ptr. If ptr is mActivated, mark activation as handled. void clearActivation(); ///< Discard the action defined by the last activate call. diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 831ec7736..9d6d5e50d 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -109,6 +109,7 @@ namespace MWScript } }; + template class OpActivate : public Interpreter::Opcode0 { public: @@ -118,7 +119,9 @@ namespace MWScript InterpreterContext& context = static_cast (runtime.getContext()); - context.executeActivation(); + MWWorld::Ptr ptr = R()(runtime); + + context.executeActivation(ptr); } }; @@ -131,7 +134,10 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - Interpreter::Type_Integer lockLevel = 100; + Interpreter::Type_Integer lockLevel = ptr.getCellRef().getLockLevel(); + if(lockLevel==0) { //no lock level was ever set, set to 100 as default + lockLevel = 100; + } if (arg0==1) { @@ -139,7 +145,7 @@ namespace MWScript runtime.pop(); } - MWWorld::Class::get (ptr).lock (ptr, lockLevel); + ptr.getClass().lock (ptr, lockLevel); } }; @@ -152,7 +158,7 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - MWWorld::Class::get (ptr).unlock (ptr); + ptr.getClass().unlock (ptr); } }; @@ -272,7 +278,8 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { - MWBase::Environment::get().getWorld()->toggleWater(); + runtime.getContext().report(MWBase::Environment::get().getWorld()->toggleWater() ? "Water -> On" + : "Water -> Off"); } }; @@ -283,7 +290,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { // We are ignoring the DontSaveObject statement for now. Probably not worth - /// bothering with. The incompatibility we are creating should be marginal at most. + // bothering with. The incompatibility we are creating should be marginal at most. } }; @@ -320,7 +327,7 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - runtime.push (ptr.getCellRef ().mLockLevel > 0); + runtime.push (ptr.getCellRef().getLockLevel() > 0); } }; @@ -341,7 +348,7 @@ namespace MWScript if(key < 0 || key > 32767 || *end != '\0') key = ESM::MagicEffect::effectStringToId(effect); - runtime.push(MWWorld::Class::get(ptr).getCreatureStats(ptr).getMagicEffects().get( + runtime.push(ptr.getClass().getCreatureStats(ptr).getMagicEffects().get( MWMechanics::EffectKey(key)).mMagnitude > 0); } }; @@ -365,7 +372,7 @@ namespace MWScript store.get().find(creature); // This line throws an exception if it can't find the creature MWWorld::Ptr item = *ptr.getClass().getContainerStore(ptr).add(gem, 1, ptr); - item.getCellRef().mSoul = creature; + item.getCellRef().setSoul(creature); } }; @@ -385,10 +392,10 @@ namespace MWScript for (unsigned int i=0; igetCellRef().mSoul, soul)) + if (::Misc::StringUtils::ciEqual(it->getCellRef().getSoul(), soul)) { store.remove(*it, 1, ptr); return; @@ -420,13 +427,13 @@ namespace MWScript if (amount == 0) return; - MWWorld::ContainerStore& store = MWWorld::Class::get (ptr).getContainerStore (ptr); + MWWorld::ContainerStore& store = ptr.getClass().getContainerStore (ptr); int toRemove = amount; for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end(); ++iter) { - if (::Misc::StringUtils::ciEqual(iter->getCellRef().mRefID, item)) + if (::Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), item)) { int removed = store.remove(*iter, toRemove, ptr); MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, *iter, removed); @@ -453,12 +460,12 @@ namespace MWScript std::string soul = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - MWWorld::ContainerStore& store = MWWorld::Class::get (ptr).getContainerStore (ptr); + MWWorld::ContainerStore& store = ptr.getClass().getContainerStore (ptr); for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end(); ++iter) { - if (::Misc::StringUtils::ciEqual(iter->getCellRef().mSoul, soul)) + if (::Misc::StringUtils::ciEqual(iter->getCellRef().getSoul(), soul)) { MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, *iter, 1); store.remove(*iter, 1, ptr); @@ -477,7 +484,7 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - runtime.push(MWWorld::Class::get(ptr).getCreatureStats (ptr).getAttacked ()); + runtime.push(ptr.getClass().getCreatureStats (ptr).getAttacked ()); } }; @@ -518,7 +525,7 @@ namespace MWScript std::string id = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); - runtime.push(MWWorld::Class::get(ptr).getCreatureStats(ptr).getActiveSpells().isSpellActive(id)); + runtime.push(ptr.getClass().getCreatureStats(ptr).getActiveSpells().isSpellActive(id)); } }; @@ -617,7 +624,7 @@ namespace MWScript std::string objectID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - MWMechanics::CreatureStats &stats = MWWorld::Class::get(ptr).getCreatureStats(ptr); + MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr); runtime.push(::Misc::StringUtils::ciEqual(objectID, stats.getLastHitObject())); } }; @@ -653,12 +660,12 @@ namespace MWScript { std::stringstream str; - const std::string script = MWWorld::Class::get(ptr).getScript(ptr); + const std::string script = ptr.getClass().getScript(ptr); if(script.empty()) - str<< ptr.getCellRef().mRefID<<" ("<getLocals(script); @@ -814,6 +821,7 @@ namespace MWScript { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); player.getClass().getNpcStats(player).setBounty(0); + MWBase::Environment::get().getWorld()->confiscateStolenItems(player); MWBase::Environment::get().getWorld()->getPlayer().recordCrimeId(); } }; @@ -825,7 +833,6 @@ namespace MWScript { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); player.getClass().getNpcStats(player).setBounty(0); - MWBase::Environment::get().getWorld()->confiscateStolenItems(player); MWBase::Environment::get().getWorld()->getPlayer().recordCrimeId(); } }; @@ -856,7 +863,8 @@ namespace MWScript { interpreter.installSegment5 (Compiler::Misc::opcodeXBox, new OpXBox); interpreter.installSegment5 (Compiler::Misc::opcodeOnActivate, new OpOnActivate); - interpreter.installSegment5 (Compiler::Misc::opcodeActivate, new OpActivate); + interpreter.installSegment5 (Compiler::Misc::opcodeActivate, new OpActivate); + interpreter.installSegment5 (Compiler::Misc::opcodeActivateExplicit, new OpActivate); interpreter.installSegment3 (Compiler::Misc::opcodeLock, new OpLock); interpreter.installSegment3 (Compiler::Misc::opcodeLockExplicit, new OpLock); interpreter.installSegment5 (Compiler::Misc::opcodeUnlock, new OpUnlock); diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 80467f58a..8b61237a1 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -29,11 +29,9 @@ namespace { - std::string getDialogueActorFaction() + std::string getDialogueActorFaction(MWWorld::Ptr actor) { - MWWorld::Ptr actor = MWBase::Environment::get().getDialogueManager()->getActor(); - - const MWMechanics::NpcStats &stats = MWWorld::Class::get (actor).getNpcStats (actor); + const MWMechanics::NpcStats &stats = actor.getClass().getNpcStats (actor); if (stats.getFactionRanks().empty()) throw std::runtime_error ( @@ -57,7 +55,7 @@ namespace MWScript MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Integer value = - MWWorld::Class::get (ptr) + ptr.getClass() .getCreatureStats (ptr) .getLevel(); @@ -77,7 +75,7 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - MWWorld::Class::get (ptr) + ptr.getClass() .getCreatureStats (ptr) .setLevel(value); } @@ -97,7 +95,7 @@ namespace MWScript MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Integer value = - MWWorld::Class::get (ptr) + ptr.getClass() .getCreatureStats (ptr) .getAttribute(mIndex) .getModified(); @@ -144,7 +142,7 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - MWMechanics::AttributeValue attribute = MWWorld::Class::get(ptr) + MWMechanics::AttributeValue attribute = ptr.getClass() .getCreatureStats(ptr) .getAttribute(mIndex); @@ -167,13 +165,13 @@ namespace MWScript MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Float value; - if (mIndex==0 && MWWorld::Class::get (ptr).hasItemHealth (ptr)) + if (mIndex==0 && ptr.getClass().hasItemHealth (ptr)) { // health is a special case - value = MWWorld::Class::get (ptr).getItemMaxHealth (ptr); + value = ptr.getClass().getItemMaxHealth (ptr); } else { value = - MWWorld::Class::get(ptr) + ptr.getClass() .getCreatureStats(ptr) .getDynamic(mIndex) .getCurrent(); @@ -198,13 +196,13 @@ namespace MWScript Interpreter::Type_Float value = runtime[0].mFloat; runtime.pop(); - MWMechanics::DynamicStat stat (MWWorld::Class::get (ptr).getCreatureStats (ptr) + MWMechanics::DynamicStat stat (ptr.getClass().getCreatureStats (ptr) .getDynamic (mIndex)); stat.setModified (value, 0); stat.setCurrent(value); - MWWorld::Class::get (ptr).getCreatureStats (ptr).setDynamic (mIndex, stat); + ptr.getClass().getCreatureStats (ptr).setDynamic (mIndex, stat); } }; @@ -224,18 +222,18 @@ namespace MWScript Interpreter::Type_Float diff = runtime[0].mFloat; runtime.pop(); - MWMechanics::CreatureStats& stats = MWWorld::Class::get (ptr).getCreatureStats (ptr); + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); Interpreter::Type_Float current = stats.getDynamic(mIndex).getCurrent(); - MWMechanics::DynamicStat stat (MWWorld::Class::get (ptr).getCreatureStats (ptr) + MWMechanics::DynamicStat stat (ptr.getClass().getCreatureStats (ptr) .getDynamic (mIndex)); stat.setModified (diff + stat.getModified(), 0); stat.setCurrent (diff + current); - MWWorld::Class::get (ptr).getCreatureStats (ptr).setDynamic (mIndex, stat); + ptr.getClass().getCreatureStats (ptr).setDynamic (mIndex, stat); } }; @@ -255,16 +253,16 @@ namespace MWScript Interpreter::Type_Float diff = runtime[0].mFloat; runtime.pop(); - MWMechanics::CreatureStats& stats = MWWorld::Class::get (ptr).getCreatureStats (ptr); + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); Interpreter::Type_Float current = stats.getDynamic(mIndex).getCurrent(); - MWMechanics::DynamicStat stat (MWWorld::Class::get (ptr).getCreatureStats (ptr) + MWMechanics::DynamicStat stat (ptr.getClass().getCreatureStats (ptr) .getDynamic (mIndex)); stat.setCurrent (diff + current); - MWWorld::Class::get (ptr).getCreatureStats (ptr).setDynamic (mIndex, stat); + ptr.getClass().getCreatureStats (ptr).setDynamic (mIndex, stat); } }; @@ -281,7 +279,7 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - MWMechanics::CreatureStats& stats = MWWorld::Class::get (ptr).getCreatureStats (ptr); + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); Interpreter::Type_Float value = 0; @@ -329,7 +327,7 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - MWMechanics::NpcStats& stats = MWWorld::Class::get (ptr).getNpcStats (ptr); + MWMechanics::NpcStats& stats = ptr.getClass().getNpcStats (ptr); MWWorld::LiveCellRef *ref = ptr.get(); @@ -388,7 +386,7 @@ namespace MWScript { MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = world->getPlayerPtr(); - runtime.push (static_cast (MWWorld::Class::get (player).getNpcStats (player).getBounty())); + runtime.push (static_cast (player.getClass().getNpcStats (player).getBounty())); } }; @@ -401,7 +399,7 @@ namespace MWScript MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = world->getPlayerPtr(); - MWWorld::Class::get (player).getNpcStats (player).setBounty(runtime[0].mFloat); + player.getClass().getNpcStats (player).setBounty(runtime[0].mFloat); runtime.pop(); } }; @@ -415,7 +413,7 @@ namespace MWScript MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = world->getPlayerPtr(); - MWWorld::Class::get (player).getNpcStats (player).setBounty(runtime[0].mFloat + MWWorld::Class::get (player).getNpcStats (player).getBounty()); + player.getClass().getNpcStats (player).setBounty(runtime[0].mFloat + player.getClass().getNpcStats (player).getBounty()); runtime.pop(); } }; @@ -435,7 +433,7 @@ namespace MWScript // make sure a spell with this ID actually exists. MWBase::Environment::get().getWorld()->getStore().get().find (id); - MWWorld::Class::get (ptr).getCreatureStats (ptr).getSpells().add (id); + ptr.getClass().getCreatureStats (ptr).getSpells().add (id); } }; @@ -451,7 +449,7 @@ namespace MWScript std::string id = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - MWWorld::Class::get (ptr).getCreatureStats (ptr).getSpells().remove (id); + ptr.getClass().getCreatureStats (ptr).getSpells().remove (id); MWBase::WindowManager *wm = MWBase::Environment::get().getWindowManager(); @@ -475,7 +473,7 @@ namespace MWScript std::string spellid = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - MWWorld::Class::get (ptr).getCreatureStats (ptr).getActiveSpells().removeEffects(spellid); + ptr.getClass().getCreatureStats (ptr).getActiveSpells().removeEffects(spellid); } }; @@ -491,7 +489,7 @@ namespace MWScript Interpreter::Type_Integer effectId = runtime[0].mInteger; runtime.pop(); - MWWorld::Class::get (ptr).getCreatureStats (ptr).getActiveSpells().purgeEffect(effectId); + ptr.getClass().getCreatureStats (ptr).getActiveSpells().purgeEffect(effectId); } }; @@ -511,8 +509,8 @@ namespace MWScript Interpreter::Type_Integer value = 0; for (MWMechanics::Spells::TIterator iter ( - MWWorld::Class::get (ptr).getCreatureStats (ptr).getSpells().begin()); - iter!=MWWorld::Class::get (ptr).getCreatureStats (ptr).getSpells().end(); ++iter) + ptr.getClass().getCreatureStats (ptr).getSpells().begin()); + iter!=ptr.getClass().getCreatureStats (ptr).getSpells().end(); ++iter) if (iter->first==id) { value = 1; @@ -523,6 +521,7 @@ namespace MWScript } }; + template class OpPCJoinFaction : public Interpreter::Opcode1 { public: @@ -533,7 +532,8 @@ namespace MWScript if(arg0==0) { - factionID = getDialogueActorFaction(); + MWWorld::Ptr actor = R()(runtime); + factionID = getDialogueActorFaction(actor); } else { @@ -541,17 +541,21 @@ namespace MWScript runtime.pop(); } ::Misc::StringUtils::toLower(factionID); + // Make sure this faction exists + MWBase::Environment::get().getWorld()->getStore().get().find(factionID); + if(factionID != "") { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - if(MWWorld::Class::get(player).getNpcStats(player).getFactionRanks().find(factionID) == MWWorld::Class::get(player).getNpcStats(player).getFactionRanks().end()) + if(player.getClass().getNpcStats(player).getFactionRanks().find(factionID) == player.getClass().getNpcStats(player).getFactionRanks().end()) { - MWWorld::Class::get(player).getNpcStats(player).getFactionRanks()[factionID] = 0; + player.getClass().getNpcStats(player).getFactionRanks()[factionID] = 0; } } } }; + template class OpPCRaiseRank : public Interpreter::Opcode1 { public: @@ -562,7 +566,8 @@ namespace MWScript if(arg0==0) { - factionID = getDialogueActorFaction(); + MWWorld::Ptr actor = R()(runtime); + factionID = getDialogueActorFaction(actor); } else { @@ -570,21 +575,25 @@ namespace MWScript runtime.pop(); } ::Misc::StringUtils::toLower(factionID); + // Make sure this faction exists + MWBase::Environment::get().getWorld()->getStore().get().find(factionID); + if(factionID != "") { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - if(MWWorld::Class::get(player).getNpcStats(player).getFactionRanks().find(factionID) == MWWorld::Class::get(player).getNpcStats(player).getFactionRanks().end()) + if(player.getClass().getNpcStats(player).getFactionRanks().find(factionID) == player.getClass().getNpcStats(player).getFactionRanks().end()) { - MWWorld::Class::get(player).getNpcStats(player).getFactionRanks()[factionID] = 0; + player.getClass().getNpcStats(player).getFactionRanks()[factionID] = 0; } else { - MWWorld::Class::get(player).getNpcStats(player).getFactionRanks()[factionID] = MWWorld::Class::get(player).getNpcStats(player).getFactionRanks()[factionID] +1; + player.getClass().getNpcStats(player).getFactionRanks()[factionID] = player.getClass().getNpcStats(player).getFactionRanks()[factionID] +1; } } } }; + template class OpPCLowerRank : public Interpreter::Opcode1 { public: @@ -595,7 +604,8 @@ namespace MWScript if(arg0==0) { - factionID = getDialogueActorFaction(); + MWWorld::Ptr actor = R()(runtime); + factionID = getDialogueActorFaction(actor); } else { @@ -603,12 +613,15 @@ namespace MWScript runtime.pop(); } ::Misc::StringUtils::toLower(factionID); + // Make sure this faction exists + MWBase::Environment::get().getWorld()->getStore().get().find(factionID); + if(factionID != "") { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - if(MWWorld::Class::get(player).getNpcStats(player).getFactionRanks().find(factionID) != MWWorld::Class::get(player).getNpcStats(player).getFactionRanks().end()) + if(player.getClass().getNpcStats(player).getFactionRanks().find(factionID) != player.getClass().getNpcStats(player).getFactionRanks().end()) { - MWWorld::Class::get(player).getNpcStats(player).getFactionRanks()[factionID] = MWWorld::Class::get(player).getNpcStats(player).getFactionRanks()[factionID] -1; + player.getClass().getNpcStats(player).getFactionRanks()[factionID] = player.getClass().getNpcStats(player).getFactionRanks()[factionID] -1; } } } @@ -631,22 +644,25 @@ namespace MWScript } else { - if(MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().empty()) + if(ptr.getClass().getNpcStats(ptr).getFactionRanks().empty()) { factionID = ""; } else { - factionID = MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().begin()->first; + factionID = ptr.getClass().getNpcStats(ptr).getFactionRanks().begin()->first; } } ::Misc::StringUtils::toLower(factionID); + // Make sure this faction exists + MWBase::Environment::get().getWorld()->getStore().get().find(factionID); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); if(factionID!="") { - if(MWWorld::Class::get(player).getNpcStats(player).getFactionRanks().find(factionID) != MWWorld::Class::get(player).getNpcStats(player).getFactionRanks().end()) + if(player.getClass().getNpcStats(player).getFactionRanks().find(factionID) != player.getClass().getNpcStats(player).getFactionRanks().end()) { - runtime.push(MWWorld::Class::get(player).getNpcStats(player).getFactionRanks()[factionID]); + runtime.push(player.getClass().getNpcStats(player).getFactionRanks()[factionID]); } else { @@ -672,8 +688,8 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - MWWorld::Class::get (ptr).getNpcStats (ptr).setBaseDisposition - (MWWorld::Class::get (ptr).getNpcStats (ptr).getBaseDisposition() + value); + ptr.getClass().getNpcStats (ptr).setBaseDisposition + (ptr.getClass().getNpcStats (ptr).getBaseDisposition() + value); } }; @@ -689,7 +705,7 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - MWWorld::Class::get (ptr).getNpcStats (ptr).setBaseDisposition (value); + ptr.getClass().getNpcStats (ptr).setBaseDisposition (value); } }; @@ -735,8 +751,8 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - if (!MWWorld::Class::get (ptr).getNpcStats (ptr).getFactionRanks().empty()) - factionId = MWWorld::Class::get (ptr).getNpcStats (ptr).getFactionRanks().begin()->first; + if (!ptr.getClass().getNpcStats (ptr).getFactionRanks().empty()) + factionId = ptr.getClass().getNpcStats (ptr).getFactionRanks().begin()->first; } if (factionId.empty()) @@ -746,7 +762,7 @@ namespace MWScript MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); runtime.push ( - MWWorld::Class::get (player).getNpcStats (player).getFactionReputation (factionId)); + player.getClass().getNpcStats (player).getFactionReputation (factionId)); } }; @@ -771,8 +787,8 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - if (!MWWorld::Class::get (ptr).getNpcStats (ptr).getFactionRanks().empty()) - factionId = MWWorld::Class::get (ptr).getNpcStats (ptr).getFactionRanks().begin()->first; + if (!ptr.getClass().getNpcStats (ptr).getFactionRanks().empty()) + factionId = ptr.getClass().getNpcStats (ptr).getFactionRanks().begin()->first; } if (factionId.empty()) @@ -781,7 +797,7 @@ namespace MWScript ::Misc::StringUtils::toLower (factionId); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - MWWorld::Class::get (player).getNpcStats (player).setFactionReputation (factionId, value); + player.getClass().getNpcStats (player).setFactionReputation (factionId, value); } }; @@ -806,8 +822,8 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - if (!MWWorld::Class::get (ptr).getNpcStats (ptr).getFactionRanks().empty()) - factionId = MWWorld::Class::get (ptr).getNpcStats (ptr).getFactionRanks().begin()->first; + if (!ptr.getClass().getNpcStats (ptr).getFactionRanks().empty()) + factionId = ptr.getClass().getNpcStats (ptr).getFactionRanks().begin()->first; } if (factionId.empty()) @@ -816,8 +832,8 @@ namespace MWScript ::Misc::StringUtils::toLower (factionId); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - MWWorld::Class::get (player).getNpcStats (player).setFactionReputation (factionId, - MWWorld::Class::get (player).getNpcStats (player).getFactionReputation (factionId)+ + player.getClass().getNpcStats (player).setFactionReputation (factionId, + player.getClass().getNpcStats (player).getFactionReputation (factionId)+ value); } }; @@ -831,7 +847,7 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - runtime.push (MWWorld::Class::get (ptr).getCreatureStats (ptr).hasCommonDisease()); + runtime.push (ptr.getClass().getCreatureStats (ptr).hasCommonDisease()); } }; @@ -844,7 +860,7 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - runtime.push (MWWorld::Class::get (ptr).getCreatureStats (ptr).hasBlightDisease()); + runtime.push (ptr.getClass().getCreatureStats (ptr).hasBlightDisease()); } }; @@ -876,7 +892,7 @@ namespace MWScript { MWWorld::Ptr ptr = MWBase::Environment::get().getWorld ()->getPlayerPtr(); - runtime.push (MWWorld::Class::get(ptr).getNpcStats (ptr).getWerewolfKills ()); + runtime.push (ptr.getClass().getNpcStats (ptr).getWerewolfKills ()); } }; @@ -897,13 +913,13 @@ namespace MWScript } else { - if(MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().empty()) + if(ptr.getClass().getNpcStats(ptr).getFactionRanks().empty()) { factionID = ""; } else { - factionID = MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().begin()->first; + factionID = ptr.getClass().getNpcStats(ptr).getFactionRanks().begin()->first; } } ::Misc::StringUtils::toLower(factionID); @@ -935,13 +951,13 @@ namespace MWScript else { MWWorld::Ptr ptr = R()(runtime); - if(MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().empty()) + if(ptr.getClass().getNpcStats(ptr).getFactionRanks().empty()) { factionID = ""; } else { - factionID = MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().begin()->first; + factionID = ptr.getClass().getNpcStats(ptr).getFactionRanks().begin()->first; } } MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); @@ -968,13 +984,13 @@ namespace MWScript else { MWWorld::Ptr ptr = R()(runtime); - if(MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().empty()) + if(ptr.getClass().getNpcStats(ptr).getFactionRanks().empty()) { factionID = ""; } else { - factionID = MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().begin()->first; + factionID = ptr.getClass().getNpcStats(ptr).getFactionRanks().begin()->first; } } MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); @@ -993,11 +1009,11 @@ namespace MWScript MWWorld::Ptr ptr = R()(runtime); std::string factionID = ""; - if(MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().empty()) + if(ptr.getClass().getNpcStats(ptr).getFactionRanks().empty()) return; else { - factionID = MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().begin()->first; + factionID = ptr.getClass().getNpcStats(ptr).getFactionRanks().begin()->first; } MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); @@ -1005,7 +1021,7 @@ namespace MWScript if (ptr == player) return; - std::map& ranks = MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks (); + std::map& ranks = ptr.getClass().getNpcStats(ptr).getFactionRanks (); ranks[factionID] = ranks[factionID]+1; } }; @@ -1020,11 +1036,11 @@ namespace MWScript MWWorld::Ptr ptr = R()(runtime); std::string factionID = ""; - if(MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().empty()) + if(ptr.getClass().getNpcStats(ptr).getFactionRanks().empty()) return; else { - factionID = MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().begin()->first; + factionID = ptr.getClass().getNpcStats(ptr).getFactionRanks().begin()->first; } MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); @@ -1032,7 +1048,7 @@ namespace MWScript if (ptr == player) return; - std::map& ranks = MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks (); + std::map& ranks = ptr.getClass().getNpcStats(ptr).getFactionRanks (); ranks[factionID] = ranks[factionID]-1; } }; @@ -1047,10 +1063,26 @@ namespace MWScript MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Integer value = - MWWorld::Class::get (ptr).getCreatureStats (ptr).hasDied(); + ptr.getClass().getCreatureStats (ptr).hasDied(); if (value) - MWWorld::Class::get (ptr).getCreatureStats (ptr).clearHasDied(); + ptr.getClass().getCreatureStats (ptr).clearHasDied(); + + runtime.push (value); + } + }; + + template + class OpOnKnockout : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + Interpreter::Type_Integer value = + ptr.getClass().getCreatureStats (ptr).getKnockedDownOneFrame(); runtime.push (value); } @@ -1064,7 +1096,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { MWWorld::Ptr ptr = R()(runtime); - runtime.push(MWWorld::Class::get(ptr).getNpcStats(ptr).isWerewolf()); + runtime.push(ptr.getClass().getNpcStats(ptr).isWerewolf()); } }; @@ -1180,9 +1212,12 @@ namespace MWScript interpreter.installSegment5 (Compiler::Stats::opcodeGetSpell, new OpGetSpell); interpreter.installSegment5 (Compiler::Stats::opcodeGetSpellExplicit, new OpGetSpell); - interpreter.installSegment3(Compiler::Stats::opcodePCRaiseRank,new OpPCRaiseRank); - interpreter.installSegment3(Compiler::Stats::opcodePCLowerRank,new OpPCLowerRank); - interpreter.installSegment3(Compiler::Stats::opcodePCJoinFaction,new OpPCJoinFaction); + interpreter.installSegment3(Compiler::Stats::opcodePCRaiseRank,new OpPCRaiseRank); + interpreter.installSegment3(Compiler::Stats::opcodePCLowerRank,new OpPCLowerRank); + interpreter.installSegment3(Compiler::Stats::opcodePCJoinFaction,new OpPCJoinFaction); + interpreter.installSegment3(Compiler::Stats::opcodePCRaiseRankExplicit,new OpPCRaiseRank); + interpreter.installSegment3(Compiler::Stats::opcodePCLowerRankExplicit,new OpPCLowerRank); + interpreter.installSegment3(Compiler::Stats::opcodePCJoinFactionExplicit,new OpPCJoinFaction); interpreter.installSegment3(Compiler::Stats::opcodeGetPCRank,new OpGetPCRank); interpreter.installSegment3(Compiler::Stats::opcodeGetPCRankExplicit,new OpGetPCRank); @@ -1229,6 +1264,8 @@ namespace MWScript interpreter.installSegment5 (Compiler::Stats::opcodeOnDeath, new OpOnDeath); interpreter.installSegment5 (Compiler::Stats::opcodeOnDeathExplicit, new OpOnDeath); + interpreter.installSegment5 (Compiler::Stats::opcodeOnKnockout, new OpOnKnockout); + interpreter.installSegment5 (Compiler::Stats::opcodeOnKnockoutExplicit, new OpOnKnockout); interpreter.installSegment5 (Compiler::Stats::opcodeIsWerewolf, new OpIsWerewolf); interpreter.installSegment5 (Compiler::Stats::opcodeIsWerewolfExplicit, new OpIsWerewolf); @@ -1238,7 +1275,7 @@ namespace MWScript interpreter.installSegment5 (Compiler::Stats::opcodeUndoWerewolf, new OpSetWerewolf); interpreter.installSegment5 (Compiler::Stats::opcodeUndoWerewolfExplicit, new OpSetWerewolf); interpreter.installSegment5 (Compiler::Stats::opcodeSetWerewolfAcrobatics, new OpSetWerewolfAcrobatics); - interpreter.installSegment5 (Compiler::Stats::opcodeSetWerewolfAcrobaticsExplicit, new OpSetWerewolfAcrobatics); + interpreter.installSegment5 (Compiler::Stats::opcodeSetWerewolfAcrobaticsExplicit, new OpSetWerewolfAcrobatics); } } } diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 1efc79643..a944a31b8 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -47,7 +47,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { MWWorld::Ptr ptr = R()(runtime); - runtime.push(ptr.getCellRef().mScale); + runtime.push(ptr.getCellRef().getScale()); } }; @@ -64,7 +64,7 @@ namespace MWScript runtime.pop(); // add the parameter to the object's scale. - MWBase::Environment::get().getWorld()->scaleObject(ptr,ptr.getCellRef().mScale + scale); + MWBase::Environment::get().getWorld()->scaleObject(ptr,ptr.getCellRef().getScale() + scale); } }; @@ -117,15 +117,15 @@ namespace MWScript if (axis == "x") { - runtime.push(Ogre::Radian(ptr.getCellRef().mPos.rot[0]).valueDegrees()); + runtime.push(Ogre::Radian(ptr.getCellRef().getPosition().rot[0]).valueDegrees()); } else if (axis == "y") { - runtime.push(Ogre::Radian(ptr.getCellRef().mPos.rot[1]).valueDegrees()); + runtime.push(Ogre::Radian(ptr.getCellRef().getPosition().rot[1]).valueDegrees()); } else if (axis == "z") { - runtime.push(Ogre::Radian(ptr.getCellRef().mPos.rot[2]).valueDegrees()); + runtime.push(Ogre::Radian(ptr.getCellRef().getPosition().rot[2]).valueDegrees()); } else throw std::runtime_error ("invalid rotation axis: " + axis); @@ -247,15 +247,15 @@ namespace MWScript if(axis == "x") { - runtime.push(ptr.getCellRef().mPos.pos[0]); + runtime.push(ptr.getCellRef().getPosition().pos[0]); } else if(axis == "y") { - runtime.push(ptr.getCellRef().mPos.pos[1]); + runtime.push(ptr.getCellRef().getPosition().pos[1]); } else if(axis == "z") { - runtime.push(ptr.getCellRef().mPos.pos[2]); + runtime.push(ptr.getCellRef().getPosition().pos[2]); } else throw std::runtime_error ("invalid axis: " + axis); @@ -271,7 +271,7 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - if (!ptr.isInCell()) + if (ptr.getContainerStore()) return; if (ptr.getRefData().getHandle() == "player") @@ -295,7 +295,7 @@ namespace MWScript { store = MWBase::Environment::get().getWorld()->getInterior(cellID); } - catch(std::exception &e) + catch(std::exception&) { const ESM::Cell* cell = MWBase::Environment::get().getWorld()->getExterior(cellID); if(cell) @@ -308,6 +308,7 @@ namespace MWScript if(store) { MWBase::Environment::get().getWorld()->moveObject(ptr,store,x,y,z); + ptr = MWWorld::Ptr(ptr.getBase(), store); float ax = Ogre::Radian(ptr.getRefData().getPosition().rot[0]).valueDegrees(); float ay = Ogre::Radian(ptr.getRefData().getPosition().rot[1]).valueDegrees(); if(ptr.getTypeName() == typeid(ESM::NPC).name())//some morrowind oddity @@ -318,7 +319,7 @@ namespace MWScript } MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,zRot); - MWWorld::Class::get(ptr).adjustPosition(ptr); + ptr.getClass().adjustPosition(ptr); } else { @@ -365,7 +366,7 @@ namespace MWScript zRot = zRot/60.; } MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,zRot); - MWWorld::Class::get(ptr).adjustPosition(ptr); + ptr.getClass().adjustPosition(ptr); } }; @@ -395,7 +396,7 @@ namespace MWScript { store = MWBase::Environment::get().getWorld()->getInterior(cellID); } - catch(std::exception &e) + catch(std::exception&) { const ESM::Cell* cell = MWBase::Environment::get().getWorld()->getExterior(cellID); if(cell) @@ -414,7 +415,7 @@ namespace MWScript pos.rot[0] = pos.rot[1] = 0; pos.rot[2] = zRot; MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(),itemID); - ref.getPtr().getCellRef().mPos = pos; + ref.getPtr().getCellRef().setPosition(pos); MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,pos); } else @@ -455,7 +456,7 @@ namespace MWScript pos.rot[0] = pos.rot[1] = 0; pos.rot[2] = zRot; MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(),itemID); - ref.getPtr().getCellRef().mPos = pos; + ref.getPtr().getCellRef().setPosition(pos); MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,pos); } else @@ -522,7 +523,7 @@ namespace MWScript // create item MWWorld::CellStore* store = actor.getCell(); MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), itemID, count); - ref.getPtr().getCellRef().mPos = ipos; + ref.getPtr().getCellRef().setPosition(ipos); MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,ipos); } @@ -616,8 +617,8 @@ namespace MWScript ptr.getRefData().getLocalRotation().rot[1] = 0; ptr.getRefData().getLocalRotation().rot[2] = 0; MWBase::Environment::get().getWorld()->rotateObject(ptr, 0,0,0,true); - MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().mPos.pos[0], - ptr.getCellRef().mPos.pos[1], ptr.getCellRef().mPos.pos[2]); + MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().pos[0], + ptr.getCellRef().getPosition().pos[1], ptr.getCellRef().getPosition().pos[2]); } }; diff --git a/apps/openmw/mwsound/audiere_decoder.cpp b/apps/openmw/mwsound/audiere_decoder.cpp deleted file mode 100644 index 3f3e3a62d..000000000 --- a/apps/openmw/mwsound/audiere_decoder.cpp +++ /dev/null @@ -1,137 +0,0 @@ -#ifdef OPENMW_USE_AUDIERE - -#include -#include - -#include "audiere_decoder.hpp" - - -static void fail(const std::string &msg) -{ throw std::runtime_error("Audiere exception: "+msg); } - -namespace MWSound -{ - -class OgreFile : public audiere::File -{ - Ogre::DataStreamPtr mStream; - - ADR_METHOD(int) read(void* buffer, int size) - { - return mStream->read(buffer, size); - } - - ADR_METHOD(bool) seek(int position, SeekMode mode) - { - if(mode == CURRENT) - mStream->seek(mStream->tell()+position); - else if(mode == BEGIN) - mStream->seek(position); - else if(mode == END) - mStream->seek(mStream->size()+position); - else - return false; - - return true; - } - - ADR_METHOD(int) tell() - { - return mStream->tell(); - } - - size_t refs; - ADR_METHOD(void) ref() { ++refs; } - ADR_METHOD(void) unref() - { - if(--refs == 0) - delete this; - } - -public: - OgreFile(const Ogre::DataStreamPtr &stream) - : mStream(stream), refs(1) - { } - virtual ~OgreFile() { } - - Ogre::String getName() - { return mStream->getName(); } -}; - - -void Audiere_Decoder::open(const std::string &fname) -{ - close(); - - mSoundFile = audiere::FilePtr(new OgreFile(mResourceMgr.openResource(fname))); - mSoundSource = audiere::OpenSampleSource(mSoundFile); - mSoundFileName = fname; - - int channels, srate; - audiere::SampleFormat format; - - mSoundSource->getFormat(channels, srate, format); - if(format == audiere::SF_S16) - mSampleType = SampleType_Int16; - else if(format == audiere::SF_U8) - mSampleType = SampleType_UInt8; - else - fail("Unsupported sample type"); - - if(channels == 1) - mChannelConfig = ChannelConfig_Mono; - else if(channels == 2) - mChannelConfig = ChannelConfig_Stereo; - else - fail("Unsupported channel count"); - - mSampleRate = srate; -} - -void Audiere_Decoder::close() -{ - mSoundFile = NULL; - mSoundSource = NULL; -} - -std::string Audiere_Decoder::getName() -{ - return mSoundFileName; -} - -void Audiere_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) -{ - *samplerate = mSampleRate; - *chans = mChannelConfig; - *type = mSampleType; -} - -size_t Audiere_Decoder::read(char *buffer, size_t bytes) -{ - int size = bytesToFrames(bytes, mChannelConfig, mSampleType); - size = mSoundSource->read(size, buffer); - return framesToBytes(size, mChannelConfig, mSampleType); -} - -void Audiere_Decoder::rewind() -{ - mSoundSource->reset(); -} - -size_t Audiere_Decoder::getSampleOffset() -{ - return 0; -} - -Audiere_Decoder::Audiere_Decoder() -{ -} - -Audiere_Decoder::~Audiere_Decoder() -{ - close(); -} - -} - -#endif diff --git a/apps/openmw/mwsound/audiere_decoder.hpp b/apps/openmw/mwsound/audiere_decoder.hpp deleted file mode 100644 index f432c32ec..000000000 --- a/apps/openmw/mwsound/audiere_decoder.hpp +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef GAME_SOUND_AUDIERE_DECODER_H -#define GAME_SOUND_AUDIERE_DECODER_H - -#include - -#include "audiere.h" - -#include "sound_decoder.hpp" - - -namespace MWSound -{ - class Audiere_Decoder : public Sound_Decoder - { - std::string mSoundFileName; - audiere::FilePtr mSoundFile; - audiere::SampleSourcePtr mSoundSource; - int mSampleRate; - SampleType mSampleType; - ChannelConfig mChannelConfig; - - virtual void open(const std::string &fname); - virtual void close(); - - virtual std::string getName(); - virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type); - - virtual size_t read(char *buffer, size_t bytes); - virtual void rewind(); - virtual size_t getSampleOffset(); - - Audiere_Decoder& operator=(const Audiere_Decoder &rhs); - Audiere_Decoder(const Audiere_Decoder &rhs); - - Audiere_Decoder(); - public: - virtual ~Audiere_Decoder(); - - friend class SoundManager; - }; -#ifndef DEFAULT_DECODER -#define DEFAULT_DECODER (::MWSound::Audiere_Decoder) -#endif -}; - -#endif diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index c595de5ae..75f7ccae4 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -204,7 +204,7 @@ void FFmpeg_Decoder::open(const std::string &fname) mFrame = avcodec_alloc_frame(); } - catch(std::exception &e) + catch(std::exception&) { if (mFormatCtx->pb->buffer != NULL) { diff --git a/apps/openmw/mwsound/mpgsnd_decoder.cpp b/apps/openmw/mwsound/mpgsnd_decoder.cpp deleted file mode 100644 index fb187f844..000000000 --- a/apps/openmw/mwsound/mpgsnd_decoder.cpp +++ /dev/null @@ -1,247 +0,0 @@ -#ifdef OPENMW_USE_MPG123 - -#include -#include - -#include "mpgsnd_decoder.hpp" - - -static void fail(const std::string &msg) -{ throw std::runtime_error("MpgSnd exception: "+msg); } - -namespace MWSound -{ - -// -// libSndFile io callbacks -// -sf_count_t MpgSnd_Decoder::ogresf_get_filelen(void *user_data) -{ - Ogre::DataStreamPtr stream = static_cast(user_data)->mDataStream; - return stream->size(); -} - -sf_count_t MpgSnd_Decoder::ogresf_seek(sf_count_t offset, int whence, void *user_data) -{ - Ogre::DataStreamPtr stream = static_cast(user_data)->mDataStream; - - if(whence == SEEK_CUR) - stream->seek(stream->tell()+offset); - else if(whence == SEEK_SET) - stream->seek(offset); - else if(whence == SEEK_END) - stream->seek(stream->size()+offset); - else - return -1; - - return stream->tell(); -} - -sf_count_t MpgSnd_Decoder::ogresf_read(void *ptr, sf_count_t count, void *user_data) -{ - Ogre::DataStreamPtr stream = static_cast(user_data)->mDataStream; - return stream->read(ptr, count); -} - -sf_count_t MpgSnd_Decoder::ogresf_write(const void*, sf_count_t, void*) -{ return -1; } - -sf_count_t MpgSnd_Decoder::ogresf_tell(void *user_data) -{ - Ogre::DataStreamPtr stream = static_cast(user_data)->mDataStream; - return stream->tell(); -} - -// -// libmpg13 io callbacks -// -ssize_t MpgSnd_Decoder::ogrempg_read(void *user_data, void *ptr, size_t count) -{ - Ogre::DataStreamPtr stream = static_cast(user_data)->mDataStream; - return stream->read(ptr, count); -} - -off_t MpgSnd_Decoder::ogrempg_lseek(void *user_data, off_t offset, int whence) -{ - Ogre::DataStreamPtr stream = static_cast(user_data)->mDataStream; - - if(whence == SEEK_CUR) - stream->seek(stream->tell()+offset); - else if(whence == SEEK_SET) - stream->seek(offset); - else if(whence == SEEK_END) - stream->seek(stream->size()+offset); - else - return -1; - - return stream->tell(); -} - - -void MpgSnd_Decoder::open(const std::string &fname) -{ - close(); - mDataStream = mResourceMgr.openResource(fname); - - SF_VIRTUAL_IO streamIO = { - ogresf_get_filelen, ogresf_seek, - ogresf_read, ogresf_write, ogresf_tell - }; - mSndFile = sf_open_virtual(&streamIO, SFM_READ, &mSndInfo, this); - if(mSndFile) - { - if(mSndInfo.channels == 1) - mChanConfig = ChannelConfig_Mono; - else if(mSndInfo.channels == 2) - mChanConfig = ChannelConfig_Stereo; - else - { - sf_close(mSndFile); - mSndFile = NULL; - fail("Unsupported channel count in "+fname); - } - mSampleRate = mSndInfo.samplerate; - return; - } - mDataStream->seek(0); - - mMpgFile = mpg123_new(NULL, NULL); - if(mMpgFile && mpg123_replace_reader_handle(mMpgFile, ogrempg_read, ogrempg_lseek, NULL) == MPG123_OK && - mpg123_open_handle(mMpgFile, this) == MPG123_OK) - { - try - { - int encoding, channels; - long rate; - if(mpg123_getformat(mMpgFile, &rate, &channels, &encoding) != MPG123_OK) - fail("Failed to get audio format"); - if(encoding != MPG123_ENC_SIGNED_16) - fail("Unsupported encoding in "+fname); - if(channels != 1 && channels != 2) - fail("Unsupported channel count in "+fname); - mChanConfig = ((channels==2)?ChannelConfig_Stereo:ChannelConfig_Mono); - mSampleRate = rate; - return; - } - catch(std::exception &e) - { - mpg123_close(mMpgFile); - mpg123_delete(mMpgFile); - mMpgFile = NULL; - throw; - } - mpg123_close(mMpgFile); - } - if(mMpgFile) - mpg123_delete(mMpgFile); - mMpgFile = NULL; - - fail("Unsupported file type: "+fname); -} - -void MpgSnd_Decoder::close() -{ - if(mSndFile) - sf_close(mSndFile); - mSndFile = NULL; - - if(mMpgFile) - { - mpg123_close(mMpgFile); - mpg123_delete(mMpgFile); - mMpgFile = NULL; - } - - mDataStream.setNull(); -} - -std::string MpgSnd_Decoder::getName() -{ - return mDataStream->getName(); -} - -void MpgSnd_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) -{ - if(!mSndFile && !mMpgFile) - fail("No open file"); - - *samplerate = mSampleRate; - *chans = mChanConfig; - *type = SampleType_Int16; -} - -size_t MpgSnd_Decoder::read(char *buffer, size_t bytes) -{ - size_t got = 0; - - if(mSndFile) - { - got = sf_read_short(mSndFile, (short*)buffer, bytes/2)*2; - } - else if(mMpgFile) - { - int err; - err = mpg123_read(mMpgFile, (unsigned char*)buffer, bytes, &got); - if(err != MPG123_OK && err != MPG123_DONE) - fail("Failed to read from file"); - } - return got; -} - -void MpgSnd_Decoder::readAll(std::vector &output) -{ - if(mSndFile && mSndInfo.frames > 0) - { - size_t pos = output.size(); - output.resize(pos + mSndInfo.frames*mSndInfo.channels*2); - sf_readf_short(mSndFile, (short*)(&output[0]+pos), mSndInfo.frames); - return; - } - // Fallback in case we don't know the total already - Sound_Decoder::readAll(output); -} - -void MpgSnd_Decoder::rewind() -{ - if(!mSndFile && !mMpgFile) - fail("No open file"); - - if(mSndFile) - { - if(sf_seek(mSndFile, 0, SEEK_SET) == -1) - fail("seek failed"); - } - else if(mMpgFile) - { - if(mpg123_seek(mMpgFile, 0, SEEK_SET) < 0) - fail("seek failed"); - } -} - -size_t MpgSnd_Decoder::getSampleOffset() -{ - return 0; -} - -MpgSnd_Decoder::MpgSnd_Decoder() - : mSndInfo() - , mSndFile(NULL) - , mMpgFile(NULL) - , mDataStream() - , mChanConfig(ChannelConfig_Stereo) - , mSampleRate(0) -{ - static bool initdone = false; - if(!initdone) - mpg123_init(); - initdone = true; -} - -MpgSnd_Decoder::~MpgSnd_Decoder() -{ - close(); -} - -} - -#endif diff --git a/apps/openmw/mwsound/mpgsnd_decoder.hpp b/apps/openmw/mwsound/mpgsnd_decoder.hpp deleted file mode 100644 index be52f6f49..000000000 --- a/apps/openmw/mwsound/mpgsnd_decoder.hpp +++ /dev/null @@ -1,59 +0,0 @@ -#ifndef GAME_SOUND_MPGSND_DECODER_H -#define GAME_SOUND_MPGSND_DECODER_H - -#include - -#include - -#include "mpg123.h" -#include "sndfile.h" - -#include "sound_decoder.hpp" - - -namespace MWSound -{ - class MpgSnd_Decoder : public Sound_Decoder - { - SF_INFO mSndInfo; - SNDFILE *mSndFile; - mpg123_handle *mMpgFile; - - Ogre::DataStreamPtr mDataStream; - static sf_count_t ogresf_get_filelen(void *user_data); - static sf_count_t ogresf_seek(sf_count_t offset, int whence, void *user_data); - static sf_count_t ogresf_read(void *ptr, sf_count_t count, void *user_data); - static sf_count_t ogresf_write(const void*, sf_count_t, void*); - static sf_count_t ogresf_tell(void *user_data); - static ssize_t ogrempg_read(void*, void*, size_t); - static off_t ogrempg_lseek(void*, off_t, int); - - ChannelConfig mChanConfig; - int mSampleRate; - - virtual void open(const std::string &fname); - virtual void close(); - - virtual std::string getName(); - virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type); - - virtual size_t read(char *buffer, size_t bytes); - virtual void readAll(std::vector &output); - virtual void rewind(); - virtual size_t getSampleOffset(); - - MpgSnd_Decoder& operator=(const MpgSnd_Decoder &rhs); - MpgSnd_Decoder(const MpgSnd_Decoder &rhs); - - MpgSnd_Decoder(); - public: - virtual ~MpgSnd_Decoder(); - - friend class SoundManager; - }; -#ifndef DEFAULT_DECODER -#define DEFAULT_DECODER (::MWSound::MpgSnd_Decoder) -#endif -} - -#endif diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 9a3dd7342..b245b9241 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -288,7 +288,7 @@ OpenAL_SoundStream::OpenAL_SoundStream(OpenAL_Output &output, ALuint src, Decode mOutput.mActiveSounds.push_back(this); } - catch(std::exception &e) + catch(std::exception&) { alDeleteBuffers(sNumBuffers, mBuffers); alGetError(); @@ -471,7 +471,7 @@ bool OpenAL_SoundStream::process() mIsFinished = finished; } - catch(std::exception &e) { + catch(std::exception&) { std::cout<< "Error updating stream \""<getName()<<"\"" <open(fname); } - catch(Ogre::FileNotFoundException &e) + catch(Ogre::FileNotFoundException&) { std::string::size_type pos = fname.rfind('.'); if(pos == std::string::npos) @@ -859,7 +859,7 @@ MWBase::SoundPtr OpenAL_Output::playSound(const std::string &fname, float vol, f buf = getBuffer(fname); sound.reset(new OpenAL_Sound(*this, src, buf, Ogre::Vector3(0.0f), vol, basevol, pitch, 1.0f, 1000.0f, flags)); } - catch(std::exception &e) + catch(std::exception&) { mFreeSources.push_back(src); if(buf && alIsBuffer(buf)) @@ -898,7 +898,7 @@ MWBase::SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre buf = getBuffer(fname); sound.reset(new OpenAL_Sound3D(*this, src, buf, pos, vol, basevol, pitch, min, max, flags)); } - catch(std::exception &e) + catch(std::exception&) { mFreeSources.push_back(src); if(buf && alIsBuffer(buf)) @@ -940,7 +940,7 @@ MWBase::SoundPtr OpenAL_Output::streamSound(DecoderPtr decoder, float volume, fl { sound.reset(new OpenAL_SoundStream(*this, src, decoder, volume, pitch, flags)); } - catch(std::exception &e) + catch(std::exception&) { mFreeSources.push_back(src); throw; diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 0720e798a..4a3093b10 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -17,8 +17,8 @@ #include "openal_output.hpp" #define SOUND_OUT "OpenAL" -/* Set up the sound manager to use FFMPEG, MPG123+libsndfile, or Audiere for - * input. The OPENMW_USE_x macros are set in CMakeLists.txt. +/* Set up the sound manager to use FFMPEG for input. + * The OPENMW_USE_x macros are set in CMakeLists.txt. */ #ifdef OPENMW_USE_FFMPEG #include "ffmpeg_decoder.hpp" @@ -27,20 +27,6 @@ #endif #endif -#ifdef OPENMW_USE_AUDIERE -#include "audiere_decoder.hpp" -#ifndef SOUND_IN -#define SOUND_IN "Audiere" -#endif -#endif - -#ifdef OPENMW_USE_MPG123 -#include "mpgsnd_decoder.hpp" -#ifndef SOUND_IN -#define SOUND_IN "mpg123,sndfile" -#endif -#endif - namespace MWSound { @@ -335,7 +321,7 @@ namespace MWSound sound = mOutput->playSound(file, volume, basevol, pitch, mode|type, offset); mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), soundId); } - catch(std::exception &e) + catch(std::exception&) { //std::cout <<"Sound Error: "<isInitialized()) + return sound; + try + { + // Look up the sound in the ESM data + float basevol = volumeFromType(type); + float min, max; + std::string file = lookup(soundId, volume, min, max); + + sound = mOutput->playSound3D(file, initialPos, volume, basevol, pitch, min, max, mode|type, offset); + mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), soundId); + } catch(std::exception &e) { //std::cout <<"Sound Error: "<first == sound) + { + snditer->first->stop(); + mActiveSounds.erase(snditer++); + } + else + ++snditer; + } + } + void SoundManager::stopSound3D(const MWWorld::Ptr &ptr, const std::string& soundId) { SoundMap::iterator snditer = mActiveSounds.begin(); @@ -442,7 +466,7 @@ namespace MWSound { snditer->first->setFadeout(duration); } - snditer++; + ++snditer; } } diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index 1454240b4..ab9dcf734 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -115,6 +115,13 @@ namespace MWSound virtual MWBase::SoundPtr playSound3D(const MWWorld::Ptr &reference, const std::string& soundId, float volume, float pitch, PlayType type=Play_TypeSfx, PlayMode mode=Play_Normal, float offset=0); + ///< Play a 3D sound attached to an MWWorld::Ptr. Will be updated automatically with the Ptr's position, unless Play_NoTrack is specified. + ///< @param offset Value from [0,1] meaning from which fraction the sound the playback starts. + + virtual MWBase::SoundPtr playManualSound3D(const Ogre::Vector3& initialPos, const std::string& soundId, + float volume, float pitch, PlayType type, PlayMode mode, float offset=0); + ///< Play a 3D sound at \a initialPos. If the sound should be moving, it must be updated manually using Sound::setPosition. + ///< Play a sound from an object ///< @param offset value from [0,1], when to start playback. 0 is beginning, 1 is end. @@ -124,6 +131,9 @@ namespace MWSound virtual void stopSound3D(const MWWorld::Ptr &reference); ///< Stop the given object from playing all sounds. + virtual void stopSound(MWBase::SoundPtr sound); + ///< Stop the given sound handle + virtual void stopSound(const MWWorld::CellStore *cell); ///< Stop all sounds for the given cell. diff --git a/apps/openmw/mwstate/character.cpp b/apps/openmw/mwstate/character.cpp index 304eaddd3..5fe80ce0c 100644 --- a/apps/openmw/mwstate/character.cpp +++ b/apps/openmw/mwstate/character.cpp @@ -95,6 +95,21 @@ MWState::Character::Character (const boost::filesystem::path& saves, const std:: } } +void MWState::Character::cleanup() +{ + if (mSlots.size() == 0) + { + // All slots are gone, no need to keep the empty directory + if (boost::filesystem::is_directory (mPath)) + { + // Extra safety check to make sure the directory is empty (e.g. slots failed to parse header) + boost::filesystem::directory_iterator it(mPath); + if (it == boost::filesystem::directory_iterator()) + boost::filesystem::remove_all(mPath); + } + } +} + const MWState::Slot *MWState::Character::createSlot (const ESM::SavedGame& profile) { addSlot (profile); @@ -102,6 +117,21 @@ const MWState::Slot *MWState::Character::createSlot (const ESM::SavedGame& profi return &mSlots.back(); } +void MWState::Character::deleteSlot (const Slot *slot) +{ + int index = slot - &mSlots[0]; + + if (index<0 || index>=static_cast (mSlots.size())) + { + // sanity check; not entirely reliable + throw std::logic_error ("slot not found"); + } + + boost::filesystem::remove(slot->mPath); + + mSlots.erase (mSlots.begin()+index); +} + const MWState::Slot *MWState::Character::updateSlot (const Slot *slot, const ESM::SavedGame& profile) { int index = slot - &mSlots[0]; @@ -150,4 +180,4 @@ ESM::SavedGame MWState::Character::getSignature() const slot = *iter; return slot.mProfile; -} \ No newline at end of file +} diff --git a/apps/openmw/mwstate/character.hpp b/apps/openmw/mwstate/character.hpp index 61e4e5b25..874533289 100644 --- a/apps/openmw/mwstate/character.hpp +++ b/apps/openmw/mwstate/character.hpp @@ -36,11 +36,19 @@ namespace MWState Character (const boost::filesystem::path& saves, const std::string& game); + void cleanup(); + ///< Delete the directory we used, if it is empty + const Slot *createSlot (const ESM::SavedGame& profile); ///< Create new slot. /// /// \attention The ownership of the slot is not transferred. + /// \note Slot must belong to this character. + /// + /// \attention The \a slot pointer will be invalidated by this call. + void deleteSlot (const Slot *slot); + const Slot *updateSlot (const Slot *slot, const ESM::SavedGame& profile); /// \note Slot must belong to this character. /// diff --git a/apps/openmw/mwstate/charactermanager.cpp b/apps/openmw/mwstate/charactermanager.cpp index 2a40fb1cc..822e2d88e 100644 --- a/apps/openmw/mwstate/charactermanager.cpp +++ b/apps/openmw/mwstate/charactermanager.cpp @@ -47,6 +47,25 @@ MWState::Character *MWState::CharacterManager::getCurrentCharacter (bool create) return mCurrent; } +void MWState::CharacterManager::deleteSlot(const MWState::Character *character, const MWState::Slot *slot) +{ + int index = character - &mCharacters[0]; + + if (index<0 || index>=static_cast (mCharacters.size())) + throw std::logic_error ("invalid character"); + + mCharacters[index].deleteSlot(slot); + + if (mCharacters[index].begin() == mCharacters[index].end()) + { + // All slots deleted, cleanup and remove this character + mCharacters[index].cleanup(); + if (character == mCurrent) + mCurrent = NULL; + mCharacters.erase(mCharacters.begin() + index); + } +} + void MWState::CharacterManager::createCharacter() { std::ostringstream stream; diff --git a/apps/openmw/mwstate/charactermanager.hpp b/apps/openmw/mwstate/charactermanager.hpp index bc2e23f89..869d34f21 100644 --- a/apps/openmw/mwstate/charactermanager.hpp +++ b/apps/openmw/mwstate/charactermanager.hpp @@ -30,6 +30,8 @@ namespace MWState Character *getCurrentCharacter (bool create = true); ///< \param create Create a new character, if there is no current character. + void deleteSlot(const MWState::Character *character, const MWState::Slot *slot); + void createCharacter(); ///< Create new character within saved game management diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 6b0871012..dafb8323a 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -12,6 +12,8 @@ #include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/journal.hpp" @@ -28,6 +30,7 @@ #include "../mwworld/inventorystore.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/creaturestats.hpp" #include "../mwscript/globalscripts.hpp" @@ -46,6 +49,8 @@ void MWState::StateManager::cleanup (bool force) mState = State_NoGame; mCharacterManager.clearCurrentCharacter(); mTimePlayed = 0; + + MWMechanics::CreatureStats::cleanup(); } } @@ -133,8 +138,6 @@ void MWState::StateManager::newGame (bool bypass) else MWBase::Environment::get().getWorld()->setGlobalInt ("chargenstate", -1); - MWBase::Environment::get().getScriptManager()->getGlobalScripts().addStartup(); - mState = State_Running; } @@ -184,7 +187,7 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot else slot = mCharacterManager.getCurrentCharacter()->updateSlot (slot, profile); - std::ofstream stream (slot->mPath.string().c_str(), std::ios::binary); + boost::filesystem::ofstream stream (slot->mPath, std::ios::binary); ESM::ESMWriter writer; @@ -196,26 +199,36 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot writer.addMaster (*iter, 0); // not using the size information anyway -> use value of 0 writer.setFormat (ESM::Header::CurrentFormat); - writer.setRecordCount ( - 1 // saved game header - +MWBase::Environment::get().getJournal()->countSavedGameRecords() - +MWBase::Environment::get().getWorld()->countSavedGameRecords() - +MWBase::Environment::get().getScriptManager()->getGlobalScripts().countSavedGameRecords() - +MWBase::Environment::get().getDialogueManager()->countSavedGameRecords() - +1 // global map - ); + int recordCount = 1 // saved game header + +MWBase::Environment::get().getJournal()->countSavedGameRecords() + +MWBase::Environment::get().getWorld()->countSavedGameRecords() + +MWBase::Environment::get().getScriptManager()->getGlobalScripts().countSavedGameRecords() + +MWBase::Environment::get().getDialogueManager()->countSavedGameRecords() + +MWBase::Environment::get().getWindowManager()->countSavedGameRecords(); + writer.setRecordCount (recordCount); writer.save (stream); + Loading::Listener& listener = *MWBase::Environment::get().getWindowManager()->getLoadingScreen(); + listener.setProgressRange(recordCount); + listener.setLabel("#{sNotifyMessage4}"); + + Loading::ScopedLoad load(&listener); + writer.startRecord (ESM::REC_SAVE); slot->mProfile.save (writer); writer.endRecord (ESM::REC_SAVE); + listener.increaseProgress(); + + MWBase::Environment::get().getJournal()->write (writer, listener); + MWBase::Environment::get().getDialogueManager()->write (writer, listener); + MWBase::Environment::get().getWorld()->write (writer, listener); + MWBase::Environment::get().getScriptManager()->getGlobalScripts().write (writer, listener); + MWBase::Environment::get().getWindowManager()->write(writer, listener); - MWBase::Environment::get().getJournal()->write (writer); - MWBase::Environment::get().getDialogueManager()->write (writer); - MWBase::Environment::get().getWorld()->write (writer); - MWBase::Environment::get().getScriptManager()->getGlobalScripts().write (writer); - MWBase::Environment::get().getWindowManager()->write(writer); + // Ensure we have written the number of records that was estimated + if (writer.getRecordCount() != recordCount+1) // 1 extra for TES3 record + std::cerr << "Warning: number of written savegame records does not match. Estimated: " << recordCount+1 << ", written: " << writer.getRecordCount() << std::endl; writer.close(); @@ -223,6 +236,30 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot slot->mPath.parent_path().filename().string()); } +void MWState::StateManager::quickSave (std::string name) +{ + if (!(mState==State_Running && + MWBase::Environment::get().getWorld()->getGlobalInt ("chargenstate")==-1 // char gen + && MWBase::Environment::get().getWindowManager()->isSavingAllowed())) + { + //You can not save your game right now + MWBase::Environment::get().getWindowManager()->messageBox("#{sSaveGameDenied}"); + return; + } + + const Slot* slot = NULL; + Character* mCurrentCharacter = getCurrentCharacter(true); //Get current character + + //Find quicksave slot + for (Character::SlotIterator it = mCurrentCharacter->begin(); it != mCurrentCharacter->end(); ++it) + { + if (it->mProfile.mDescription == name) + slot = &*it; + } + + saveGame(name, slot); +} + void MWState::StateManager::loadGame (const Character *character, const Slot *slot) { try @@ -236,6 +273,13 @@ void MWState::StateManager::loadGame (const Character *character, const Slot *sl std::map contentFileMap = buildContentFileIndexMap (reader); + Loading::Listener& listener = *MWBase::Environment::get().getWindowManager()->getLoadingScreen(); + + listener.setProgressRange(reader.getRecordCount()); + listener.setLabel("#{sLoadingMessage14}"); + + Loading::ScopedLoad load(&listener); + while (reader.hasMoreRecs()) { ESM::NAME n = reader.getRecName(); @@ -273,6 +317,10 @@ void MWState::StateManager::loadGame (const Character *character, const Slot *sl case ESM::REC_PLAY: case ESM::REC_CSTA: case ESM::REC_WTHR: + case ESM::REC_DYNA: + case ESM::REC_ACTC: + case ESM::REC_PROJ: + case ESM::REC_MPRJ: MWBase::Environment::get().getWorld()->readRecord (reader, n.val, contentFileMap); break; @@ -283,6 +331,8 @@ void MWState::StateManager::loadGame (const Character *character, const Slot *sl break; case ESM::REC_GMAP: + case ESM::REC_KEYS: + case ESM::REC_ASPL: MWBase::Environment::get().getWindowManager()->readRecord(reader, n.val); break; @@ -293,6 +343,7 @@ void MWState::StateManager::loadGame (const Character *character, const Slot *sl /// \todo log error reader.skipRecord(); } + listener.increaseProgress(); } mCharacterManager.setCurrentCharacter(character); @@ -309,25 +360,36 @@ void MWState::StateManager::loadGame (const Character *character, const Slot *sl MWBase::Environment::get().getMechanicsManager()->playerLoaded(); MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayerPtr(); - - //Update the weapon icon in the hud with whatever the player is currently holding. - MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr); - MWWorld::ContainerStoreIterator item = invStore.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - - if (item != invStore.end()) - MWBase::Environment::get().getWindowManager()->setSelectedWeapon(*item); ESM::CellId cellId = ptr.getCell()->getCell()->getCellId(); - MWBase::Environment::get().getWorld()->changeToCell (cellId, ptr.getRefData().getPosition()); + // Use detectWorldSpaceChange=false, otherwise some of the data we just loaded would be cleared again + MWBase::Environment::get().getWorld()->changeToCell (cellId, ptr.getRefData().getPosition(), false); + + // Do not trigger erroneous cellChanged events + MWBase::Environment::get().getWorld()->markCellAsUnchanged(); } catch (const std::exception& e) { std::cerr << "failed to load saved game: " << e.what() << std::endl; cleanup (true); + + MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); } } +void MWState::StateManager::quickLoad() +{ + if (Character* mCurrentCharacter = getCurrentCharacter (false)) + if (const MWState::Slot* slot = &*mCurrentCharacter->begin()) //Get newest save + loadGame (mCurrentCharacter, slot); +} + +void MWState::StateManager::deleteGame(const MWState::Character *character, const MWState::Slot *slot) +{ + mCharacterManager.deleteSlot(character, slot); +} + MWState::Character *MWState::StateManager::getCurrentCharacter (bool create) { return mCharacterManager.getCurrentCharacter (create); diff --git a/apps/openmw/mwstate/statemanagerimp.hpp b/apps/openmw/mwstate/statemanagerimp.hpp index 46ade236b..40c36deb5 100644 --- a/apps/openmw/mwstate/statemanagerimp.hpp +++ b/apps/openmw/mwstate/statemanagerimp.hpp @@ -44,11 +44,23 @@ namespace MWState virtual void endGame(); + virtual void deleteGame (const MWState::Character *character, const MWState::Slot *slot); + ///< Delete a saved game slot from this character. If all save slots are deleted, the character will be deleted too. + virtual void saveGame (const std::string& description, const Slot *slot = 0); ///< Write a saved game to \a slot or create a new slot if \a slot == 0. /// /// \note Slot must belong to the current character. + ///Saves a file, using supplied filename, overwritting if needed + /** This is mostly used for quicksaving and autosaving, for they use the same name over and over again + \param name Name of save, defaults to "Quicksave"**/ + virtual void quickSave(std::string name = "Quicksave"); + + ///Loads the last saved file + /** Used for quickload **/ + virtual void quickLoad(); + virtual void loadGame (const Character *character, const Slot *slot); ///< Load a saved game file from \a slot. /// diff --git a/apps/openmw/mwworld/actionalchemy.cpp b/apps/openmw/mwworld/actionalchemy.cpp index bba75bc49..bbba1081c 100644 --- a/apps/openmw/mwworld/actionalchemy.cpp +++ b/apps/openmw/mwworld/actionalchemy.cpp @@ -2,11 +2,19 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" + +#include "../mwworld/player.hpp" namespace MWWorld { void ActionAlchemy::executeImp (const Ptr& actor) { + if(MWBase::Environment::get().getWorld()->getPlayer().isInCombat()) { //Ensure we're not in combat + MWBase::Environment::get().getWindowManager()->messageBox("#{sInventoryMessage3}"); + return; + } + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Alchemy); } } diff --git a/apps/openmw/mwworld/actionapply.cpp b/apps/openmw/mwworld/actionapply.cpp index f78b8f798..6b12cc3e6 100644 --- a/apps/openmw/mwworld/actionapply.cpp +++ b/apps/openmw/mwworld/actionapply.cpp @@ -3,6 +3,9 @@ #include "class.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + namespace MWWorld { ActionApply::ActionApply (const Ptr& target, const std::string& id) @@ -11,7 +14,9 @@ namespace MWWorld void ActionApply::executeImp (const Ptr& actor) { - MWWorld::Class::get (getTarget()).apply (getTarget(), mId, actor); + MWBase::Environment::get().getWorld()->breakInvisibility(actor); + + getTarget().getClass().apply (getTarget(), mId, actor); } @@ -22,7 +27,9 @@ namespace MWWorld void ActionApplyWithSkill::executeImp (const Ptr& actor) { - if (MWWorld::Class::get (getTarget()).apply (getTarget(), mId, actor) && mUsageType!=-1) - MWWorld::Class::get (getTarget()).skillUsageSucceeded (actor, mSkillIndex, mUsageType); + MWBase::Environment::get().getWorld()->breakInvisibility(actor); + + if (getTarget().getClass().apply (getTarget(), mId, actor) && mUsageType!=-1) + getTarget().getClass().skillUsageSucceeded (actor, mSkillIndex, mUsageType); } } diff --git a/apps/openmw/mwworld/actioneat.cpp b/apps/openmw/mwworld/actioneat.cpp index 020bdb0ff..660915523 100644 --- a/apps/openmw/mwworld/actioneat.cpp +++ b/apps/openmw/mwworld/actioneat.cpp @@ -20,10 +20,10 @@ namespace MWWorld getTarget().getContainerStore()->remove(getTarget(), 1, actor); // apply to actor - std::string id = Class::get (getTarget()).getId (getTarget()); + std::string id = getTarget().getClass().getId (getTarget()); - if (Class::get (actor).apply (actor, id, actor)) - Class::get (actor).skillUsageSucceeded (actor, ESM::Skill::Alchemy, 1); + if (actor.getClass().apply (actor, id, actor)) + actor.getClass().skillUsageSucceeded (actor, ESM::Skill::Alchemy, 1); } ActionEat::ActionEat (const MWWorld::Ptr& object) : Action (false, object) {} diff --git a/apps/openmw/mwworld/actionequip.cpp b/apps/openmw/mwworld/actionequip.cpp index 2a1b7a3aa..05677cdc7 100644 --- a/apps/openmw/mwworld/actionequip.cpp +++ b/apps/openmw/mwworld/actionequip.cpp @@ -40,7 +40,7 @@ namespace MWWorld } // slots that this item can be equipped in - std::pair, bool> slots_ = MWWorld::Class::get(getTarget()).getEquipmentSlots(getTarget()); + std::pair, bool> slots_ = getTarget().getClass().getEquipmentSlots(getTarget()); // retrieve ContainerStoreIterator to the item MWWorld::ContainerStoreIterator it = invStore.begin(); diff --git a/apps/openmw/mwworld/actionread.cpp b/apps/openmw/mwworld/actionread.cpp index 67755259e..0a4e2d6c9 100644 --- a/apps/openmw/mwworld/actionread.cpp +++ b/apps/openmw/mwworld/actionread.cpp @@ -4,6 +4,8 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" +#include "../mwworld/player.hpp" + #include "../mwmechanics/npcstats.hpp" #include "../mwgui/bookwindow.hpp" @@ -19,8 +21,18 @@ namespace MWWorld { } - void ActionRead::executeImp (const MWWorld::Ptr& actor) - { + void ActionRead::executeImp (const MWWorld::Ptr& actor) { + + //Ensure we're not in combat + if(MWBase::Environment::get().getWorld()->getPlayer().isInCombat() + // Reading in combat is still allowed if the scroll/book is not in the player inventory yet + // (since otherwise, there would be no way to pick it up) + && getTarget().getContainerStore() == &actor.getClass().getContainerStore(actor) + ) { + MWBase::Environment::get().getWindowManager()->messageBox("#{sInventoryMessage4}"); + return; + } + LiveCellRef *ref = getTarget().get(); if (ref->mBase->mData.mIsScroll) @@ -35,7 +47,7 @@ namespace MWWorld } MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); - MWMechanics::NpcStats& npcStats = MWWorld::Class::get(player).getNpcStats (player); + MWMechanics::NpcStats& npcStats = player.getClass().getNpcStats (player); // Skill gain from books if (ref->mBase->mData.mSkillID >= 0 && ref->mBase->mData.mSkillID < ESM::Skill::Length diff --git a/apps/openmw/mwworld/actionrepair.cpp b/apps/openmw/mwworld/actionrepair.cpp index bd5642116..a86dc38b1 100644 --- a/apps/openmw/mwworld/actionrepair.cpp +++ b/apps/openmw/mwworld/actionrepair.cpp @@ -2,6 +2,8 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" +#include "../mwworld/player.hpp" namespace MWWorld { @@ -12,6 +14,11 @@ namespace MWWorld void ActionRepair::executeImp (const Ptr& actor) { + if(MWBase::Environment::get().getWorld()->getPlayer().isInCombat()) { + MWBase::Environment::get().getWindowManager()->messageBox("#{sInventoryMessage2}"); + return; + } + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Repair); MWBase::Environment::get().getWindowManager()->startRepairItem(getTarget()); } diff --git a/apps/openmw/mwworld/actionsoulgem.cpp b/apps/openmw/mwworld/actionsoulgem.cpp index 6746f692f..7237fd334 100644 --- a/apps/openmw/mwworld/actionsoulgem.cpp +++ b/apps/openmw/mwworld/actionsoulgem.cpp @@ -2,20 +2,26 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" +#include "../mwworld/player.hpp" namespace MWWorld { -ActionSoulgem::ActionSoulgem(const Ptr &object) - : Action(false, object) -{ + ActionSoulgem::ActionSoulgem(const Ptr &object) + : Action(false, object) + { -} + } -void ActionSoulgem::executeImp(const Ptr &actor) -{ - MWBase::Environment::get().getWindowManager()->showSoulgemDialog(getTarget()); -} + void ActionSoulgem::executeImp(const Ptr &actor) + { + if(MWBase::Environment::get().getWorld()->getPlayer().isInCombat()) { //Ensure we're not in combat + MWBase::Environment::get().getWindowManager()->messageBox("#{sInventoryMessage5}"); + return; + } + MWBase::Environment::get().getWindowManager()->showSoulgemDialog(getTarget()); + } } diff --git a/apps/openmw/mwworld/actionteleport.cpp b/apps/openmw/mwworld/actionteleport.cpp index 150f0bed2..4378e179d 100644 --- a/apps/openmw/mwworld/actionteleport.cpp +++ b/apps/openmw/mwworld/actionteleport.cpp @@ -20,9 +20,8 @@ namespace MWWorld //find any NPC that is following the actor and teleport him too std::list followers = MWBase::Environment::get().getMechanicsManager()->getActorsFollowing(actor); - for(std::list::iterator it = followers.begin();it != followers.end();it++) + for(std::list::iterator it = followers.begin();it != followers.end();++it) { - std::cout << "teleporting someone!" << (*it).getCellRef().mRefID; executeImp(*it); } diff --git a/apps/openmw/mwworld/actiontrap.cpp b/apps/openmw/mwworld/actiontrap.cpp index bcefb0181..1472afc08 100644 --- a/apps/openmw/mwworld/actiontrap.cpp +++ b/apps/openmw/mwworld/actiontrap.cpp @@ -11,7 +11,7 @@ namespace MWWorld cast.mHitPosition = Ogre::Vector3(actor.getRefData().getPosition().pos); cast.cast(mSpellId); - mTrapSource.getCellRef().mTrap = ""; + mTrapSource.getCellRef().setTrap(""); } } diff --git a/apps/openmw/mwworld/cellref.cpp b/apps/openmw/mwworld/cellref.cpp new file mode 100644 index 000000000..f16d8e3d1 --- /dev/null +++ b/apps/openmw/mwworld/cellref.cpp @@ -0,0 +1,185 @@ +#include "cellref.hpp" + +#include + +namespace MWWorld +{ + + ESM::RefNum CellRef::getRefNum() const + { + return mCellRef.mRefNum; + } + + std::string CellRef::getRefId() const + { + return mCellRef.mRefID; + } + + bool CellRef::getTeleport() const + { + return mCellRef.mTeleport; + } + + ESM::Position CellRef::getDoorDest() const + { + return mCellRef.mDoorDest; + } + + std::string CellRef::getDestCell() const + { + return mCellRef.mDestCell; + } + + float CellRef::getScale() const + { + return mCellRef.mScale; + } + + void CellRef::setScale(float scale) + { + if (scale != mCellRef.mScale) + { + mChanged = true; + mCellRef.mScale = scale; + } + } + + ESM::Position CellRef::getPosition() const + { + return mCellRef.mPos; + } + + void CellRef::setPosition(const ESM::Position &position) + { + mChanged = true; + mCellRef.mPos = position; + } + + float CellRef::getEnchantmentCharge() const + { + return mCellRef.mEnchantmentCharge; + } + + void CellRef::setEnchantmentCharge(float charge) + { + if (charge != mCellRef.mEnchantmentCharge) + { + mChanged = true; + mCellRef.mEnchantmentCharge = charge; + } + } + + int CellRef::getCharge() const + { + return mCellRef.mCharge; + } + + void CellRef::setCharge(int charge) + { + if (charge != mCellRef.mCharge) + { + mChanged = true; + mCellRef.mCharge = charge; + } + } + + std::string CellRef::getOwner() const + { + return mCellRef.mOwner; + } + + void CellRef::setOwner(const std::string &owner) + { + if (owner != mCellRef.mOwner) + { + mChanged = true; + mCellRef.mOwner = owner; + } + } + + std::string CellRef::getSoul() const + { + return mCellRef.mSoul; + } + + void CellRef::setSoul(const std::string &soul) + { + if (soul != mCellRef.mSoul) + { + mChanged = true; + mCellRef.mSoul = soul; + } + } + + std::string CellRef::getFaction() const + { + return mCellRef.mFaction; + } + + void CellRef::setFaction(const std::string &faction) + { + if (faction != mCellRef.mFaction) + { + mChanged = true; + mCellRef.mFaction = faction; + } + } + + int CellRef::getLockLevel() const + { + return mCellRef.mLockLevel; + } + + void CellRef::setLockLevel(int lockLevel) + { + if (lockLevel != mCellRef.mLockLevel) + { + mChanged = true; + mCellRef.mLockLevel = lockLevel; + } + } + + std::string CellRef::getKey() const + { + return mCellRef.mKey; + } + + std::string CellRef::getTrap() const + { + return mCellRef.mTrap; + } + + void CellRef::setTrap(const std::string& trap) + { + if (trap != mCellRef.mTrap) + { + mChanged = true; + mCellRef.mTrap = trap; + } + } + + int CellRef::getGoldValue() const + { + return mCellRef.mGoldValue; + } + + void CellRef::setGoldValue(int value) + { + if (value != mCellRef.mGoldValue) + { + mChanged = true; + mCellRef.mGoldValue = value; + } + } + + void CellRef::writeState(ESM::ObjectState &state) const + { + state.mRef = mCellRef; + } + + bool CellRef::hasChanged() const + { + return mChanged; + } + +} diff --git a/apps/openmw/mwworld/cellref.hpp b/apps/openmw/mwworld/cellref.hpp new file mode 100644 index 000000000..4db362b1e --- /dev/null +++ b/apps/openmw/mwworld/cellref.hpp @@ -0,0 +1,99 @@ +#ifndef OPENMW_MWWORLD_CELLREF_H +#define OPENMW_MWWORLD_CELLREF_H + +#include + +namespace ESM +{ + struct ObjectState; +} + +namespace MWWorld +{ + + /// \brief Encapsulated variant of ESM::CellRef with change tracking + class CellRef + { + public: + + CellRef (const ESM::CellRef& ref) + : mCellRef(ref) + { + mChanged = false; + } + + // Note: Currently unused for items in containers + ESM::RefNum getRefNum() const; + + // Id of object being referenced + std::string getRefId() const; + + // For doors - true if this door teleports to somewhere else, false + // if it should open through animation. + bool getTeleport() const; + + // Teleport location for the door, if this is a teleporting door. + ESM::Position getDoorDest() const; + + // Destination cell for doors (optional) + std::string getDestCell() const; + + // Scale applied to mesh + float getScale() const; + void setScale(float scale); + + // Position and rotation of this object within the cell + ESM::Position getPosition() const; + void setPosition (const ESM::Position& position); + + // Remaining enchantment charge. This could be -1 if the charge was not touched yet (i.e. full). + float getEnchantmentCharge() const; + + void setEnchantmentCharge(float charge); + + // For weapon or armor, this is the remaining item health. + // For tools (lockpicks, probes, repair hammer) it is the remaining uses. + int getCharge() const; + void setCharge(int charge); + + // The NPC that owns this object (and will get angry if you steal it) + std::string getOwner() const; + void setOwner(const std::string& owner); + + // ID of creature trapped in this soul gem + std::string getSoul() const; + void setSoul(const std::string& soul); + + // The faction that owns this object (and will get angry if + // you take it and are not a faction member) + std::string getFaction() const; + void setFaction (const std::string& faction); + + // Lock level for doors and containers + // Positive for a locked door. 0 for a door that was never locked. + // For an unlocked door, it is set to -(previous locklevel) + int getLockLevel() const; + void setLockLevel(int lockLevel); + // Key and trap ID names, if any + std::string getKey() const; + std::string getTrap() const; + void setTrap(const std::string& trap); + + // This is 5 for Gold_005 references, 100 for Gold_100 and so on. + int getGoldValue() const; + void setGoldValue(int value); + + // Write the content of this CellRef into the given ObjectState + void writeState (ESM::ObjectState& state) const; + + // Has this CellRef changed since it was originally loaded? + bool hasChanged() const; + + private: + bool mChanged; + ESM::CellRef mCellRef; + }; + +} + +#endif diff --git a/apps/openmw/mwworld/cellreflist.hpp b/apps/openmw/mwworld/cellreflist.hpp index 264929bfb..9c3370f08 100644 --- a/apps/openmw/mwworld/cellreflist.hpp +++ b/apps/openmw/mwworld/cellreflist.hpp @@ -27,7 +27,7 @@ namespace MWWorld LiveRef *find (const std::string& name) { for (typename List::iterator iter (mList.begin()); iter!=mList.end(); ++iter) - if (iter->mData.getCount() > 0 && iter->mRef.mRefID == name) + if (iter->mData.getCount() > 0 && iter->mRef.getRefId() == name) return &*iter; return 0; diff --git a/apps/openmw/mwworld/cells.cpp b/apps/openmw/mwworld/cells.cpp index acffe20f3..3b758f866 100644 --- a/apps/openmw/mwworld/cells.cpp +++ b/apps/openmw/mwworld/cells.cpp @@ -78,6 +78,7 @@ void MWWorld::Cells::writeCell (ESM::ESMWriter& writer, CellStore& cell) const writer.startRecord (ESM::REC_CSTA); cellState.mId.save (writer); cellState.save (writer); + cell.writeFog(writer); cell.writeReferences (writer); writer.endRecord (ESM::REC_CSTA); } @@ -277,17 +278,23 @@ int MWWorld::Cells::countSavedGameRecords() const return count; } -void MWWorld::Cells::write (ESM::ESMWriter& writer) const +void MWWorld::Cells::write (ESM::ESMWriter& writer, Loading::Listener& progress) const { for (std::map, CellStore>::iterator iter (mExteriors.begin()); iter!=mExteriors.end(); ++iter) if (iter->second.hasState()) + { writeCell (writer, iter->second); + progress.increaseProgress(); // Assumes that each cell writes one record + } for (std::map::iterator iter (mInteriors.begin()); iter!=mInteriors.end(); ++iter) if (iter->second.hasState()) + { writeCell (writer, iter->second); + progress.increaseProgress(); // Assumes that each cell writes one record + } } bool MWWorld::Cells::readRecord (ESM::ESMReader& reader, int32_t type, @@ -313,6 +320,9 @@ bool MWWorld::Cells::readRecord (ESM::ESMReader& reader, int32_t type, state.load (reader); cellStore->loadState (state); + if (state.mHasFogOfWar) + cellStore->readFog(reader); + if (cellStore->getState()!=CellStore::State_Loaded) cellStore->load (mStore, mReader); diff --git a/apps/openmw/mwworld/cells.hpp b/apps/openmw/mwworld/cells.hpp index 5209aa51a..a9c17fa93 100644 --- a/apps/openmw/mwworld/cells.hpp +++ b/apps/openmw/mwworld/cells.hpp @@ -15,6 +15,11 @@ namespace ESM struct Cell; } +namespace Loading +{ + class Listener; +} + namespace MWWorld { class ESMStore; @@ -69,7 +74,7 @@ namespace MWWorld int countSavedGameRecords() const; - void write (ESM::ESMWriter& writer) const; + void write (ESM::ESMWriter& writer, Loading::Listener& progress) const; bool readRecord (ESM::ESMReader& reader, int32_t type, const std::map& contentFileMap); diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 6bc7657e4..63cdbfb1a 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -11,10 +11,15 @@ #include #include #include +#include +#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "../mwmechanics/creaturestats.hpp" + #include "ptr.hpp" #include "esmstore.hpp" #include "class.hpp" @@ -31,7 +36,7 @@ namespace MWWorld::Ptr container (&*iter, 0); MWWorld::Ptr ptr = - MWWorld::Class::get (container).getContainerStore (container).search (id); + container.getClass().getContainerStore (container).search (id); if (!ptr.isEmpty()) return ptr; @@ -40,6 +45,22 @@ namespace return MWWorld::Ptr(); } + template + MWWorld::Ptr searchViaActorId (MWWorld::CellRefList& actorList, int actorId, + MWWorld::CellStore *cell) + { + for (typename MWWorld::CellRefList::List::iterator iter (actorList.mList.begin()); + iter!=actorList.mList.end(); ++iter) + { + MWWorld::Ptr actor (&*iter, cell); + + if (actor.getClass().getCreatureStats (actor).matchesActorId (actorId) && actor.getRefData().getCount() > 0) + return actor; + } + + return MWWorld::Ptr(); + } + template void writeReferenceCollection (ESM::ESMWriter& writer, const MWWorld::CellRefList& collection) @@ -51,8 +72,16 @@ namespace iter (collection.mList.begin()); iter!=collection.mList.end(); ++iter) { - if (iter->mData.getCount()==0 && iter->mRef.mRefNum.mContentFile==-1) - continue; // deleted file that did not came from a content file -> ignore + if (!iter->mData.hasChanged() && !iter->mRef.hasChanged() && iter->mRef.getRefNum().mContentFile != -1) + { + // Reference that came from a content file and has not been changed -> ignore + continue; + } + if (iter->mData.getCount()==0 && iter->mRef.getRefNum().mContentFile==-1) + { + // Deleted reference that did not come from a content file -> ignore + continue; + } RecordType state; iter->save (state); @@ -72,13 +101,17 @@ namespace RecordType state; state.load (reader); - std::map::const_iterator iter = - contentFileMap.find (state.mRef.mRefNum.mContentFile); + // If the reference came from a content file, make sure this content file is loaded + if (state.mRef.mRefNum.mContentFile != -1) + { + std::map::const_iterator iter = + contentFileMap.find (state.mRef.mRefNum.mContentFile); - if (iter==contentFileMap.end()) - return; // content file has been removed -> skip + if (iter==contentFileMap.end()) + return; // content file has been removed -> skip - state.mRef.mRefNum.mContentFile = iter->second; + state.mRef.mRefNum.mContentFile = iter->second; + } if (!MWWorld::LiveCellRef::checkState (state)) return; // not valid anymore with current content files -> skip @@ -88,14 +121,17 @@ namespace if (!record) return; - for (typename MWWorld::CellRefList::List::iterator iter (collection.mList.begin()); - iter!=collection.mList.end(); ++iter) - if (iter->mRef.mRefNum==state.mRef.mRefNum) - { - // overwrite existing reference - iter->load (state); - return; - } + if (state.mRef.mRefNum.mContentFile != -1) + { + for (typename MWWorld::CellRefList::List::iterator iter (collection.mList.begin()); + iter!=collection.mList.end(); ++iter) + if (iter->mRef.getRefNum()==state.mRef.mRefNum) + { + // overwrite existing reference + iter->load (state); + return; + } + } // new reference MWWorld::LiveCellRef ref (record); @@ -141,7 +177,7 @@ namespace MWWorld } CellStore::CellStore (const ESM::Cell *cell) - : mCell (cell), mState (State_Unloaded), mHasState (false) + : mCell (cell), mState (State_Unloaded), mHasState (false), mLastRespawn(0,0) { mWaterLevel = cell->mWater; } @@ -315,6 +351,17 @@ namespace MWWorld return Ptr(); } + Ptr CellStore::searchViaActorId (int id) + { + if (Ptr ptr = ::searchViaActorId (mNpcs, id, this)) + return ptr; + + if (Ptr ptr = ::searchViaActorId (mCreatures, id, this)) + return ptr; + + return Ptr(); + } + float CellStore::getWaterLevel() const { return mWaterLevel; @@ -429,7 +476,6 @@ namespace MWWorld while(mCell->getNextRef(esm[index], ref, deleted)) { // Don't load reference if it was moved to a different cell. - std::string lowerCase = Misc::StringUtils::lowerCase(ref.mRefID); ESM::MovedCellRefTracker::const_iterator iter = std::find(mCell->mMovedRefs.begin(), mCell->mMovedRefs.end(), ref.mRefNum); if (iter != mCell->mMovedRefs.end()) { @@ -517,6 +563,7 @@ namespace MWWorld mWaterLevel = state.mWaterLevel; mWaterLevel = state.mWaterLevel; + mLastRespawn = MWWorld::TimeStamp(state.mLastRespawn); } void CellStore::saveState (ESM::CellState& state) const @@ -527,6 +574,22 @@ namespace MWWorld state.mWaterLevel = mWaterLevel; state.mWaterLevel = mWaterLevel; + state.mHasFogOfWar = (mFogState.get() ? 1 : 0); + state.mLastRespawn = mLastRespawn.toEsm(); + } + + void CellStore::writeFog(ESM::ESMWriter &writer) const + { + if (mFogState.get()) + { + mFogState->save(writer, mCell->mData.mFlags & ESM::Cell::Interior); + } + } + + void CellStore::readFog(ESM::ESMReader &reader) + { + mFogState.reset(new ESM::FogState()); + mFogState->load(reader); } void CellStore::writeReferences (ESM::ESMWriter& writer) const @@ -539,9 +602,9 @@ namespace MWWorld writeReferenceCollection (writer, mClothes); writeReferenceCollection (writer, mContainers); writeReferenceCollection (writer, mCreatures); - writeReferenceCollection (writer, mDoors); + writeReferenceCollection (writer, mDoors); writeReferenceCollection (writer, mIngreds); - writeReferenceCollection (writer, mCreatureLists); + writeReferenceCollection (writer, mCreatureLists); writeReferenceCollection (writer, mItemLists); writeReferenceCollection (writer, mLights); writeReferenceCollection (writer, mLockpicks); @@ -607,7 +670,7 @@ namespace MWWorld case ESM::REC_DOOR: - readReferenceCollection (reader, mDoors, contentFileMap); + readReferenceCollection (reader, mDoors, contentFileMap); break; case ESM::REC_INGR: @@ -617,7 +680,7 @@ namespace MWWorld case ESM::REC_LEVC: - readReferenceCollection (reader, mCreatureLists, contentFileMap); + readReferenceCollection (reader, mCreatureLists, contentFileMap); break; case ESM::REC_LEVI: @@ -691,4 +754,46 @@ namespace MWWorld { return mPathgridGraph.aStarSearch(start, end); } + + void CellStore::setFog(ESM::FogState *fog) + { + mFogState.reset(fog); + } + + ESM::FogState* CellStore::getFog() const + { + return mFogState.get(); + } + + void CellStore::respawn() + { + if (mState == State_Loaded) + { + static int iMonthsToRespawn = MWBase::Environment::get().getWorld()->getStore().get().find("iMonthsToRespawn")->getInt(); + if (MWBase::Environment::get().getWorld()->getTimeStamp() - mLastRespawn > 24*30*iMonthsToRespawn) + { + mLastRespawn = MWBase::Environment::get().getWorld()->getTimeStamp(); + for (CellRefList::List::iterator it (mContainers.mList.begin()); it!=mContainers.mList.end(); ++it) + { + Ptr ptr (&*it, this); + ptr.getClass().respawn(ptr); + } + for (CellRefList::List::iterator it (mCreatures.mList.begin()); it!=mCreatures.mList.end(); ++it) + { + Ptr ptr (&*it, this); + ptr.getClass().respawn(ptr); + } + for (CellRefList::List::iterator it (mNpcs.mList.begin()); it!=mNpcs.mList.end(); ++it) + { + Ptr ptr (&*it, this); + ptr.getClass().respawn(ptr); + } + for (CellRefList::List::iterator it (mCreatureLists.mList.begin()); it!=mCreatureLists.mList.end(); ++it) + { + Ptr ptr (&*it, this); + ptr.getClass().respawn(ptr); + } + } + } + } } diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index b970afe1b..ba6fad7ba 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -3,22 +3,30 @@ #include #include +#include #include "livecellref.hpp" #include "esmstore.hpp" #include "cellreflist.hpp" -#include "../mwmechanics/pathgrid.hpp" +#include + +#include "../mwmechanics/pathgrid.hpp" // TODO: maybe belongs in mwworld + +#include "timestamp.hpp" namespace ESM { struct CellState; + struct FogState; } namespace MWWorld { class Ptr; + + /// \brief Mutable state of a cell class CellStore { @@ -31,12 +39,19 @@ namespace MWWorld private: + // Even though fog actually belongs to the player and not cells, + // it makes sense to store it here since we need it once for each cell. + // Note this is NULL until the cell is explored to save some memory + boost::shared_ptr mFogState; + const ESM::Cell *mCell; State mState; bool mHasState; std::vector mIds; float mWaterLevel; + MWWorld::TimeStamp mLastRespawn; + CellRefList mActivators; CellRefList mPotions; CellRefList mAppas; @@ -80,10 +95,18 @@ namespace MWWorld Ptr searchViaHandle (const std::string& handle); ///< Will return an empty Ptr if cell is not loaded. + Ptr searchViaActorId (int id); + ///< Will return an empty Ptr if cell is not loaded. + float getWaterLevel() const; void setWaterLevel (float level); + void setFog (ESM::FogState* fog); + ///< \note Takes ownership of the pointer + + ESM::FogState* getFog () const; + int count() const; ///< Return total number of references, including deleted ones. @@ -134,10 +157,17 @@ namespace MWWorld void saveState (ESM::CellState& state) const; + void writeFog (ESM::ESMWriter& writer) const; + + void readFog (ESM::ESMReader& reader); + void writeReferences (ESM::ESMWriter& writer) const; void readReferences (ESM::ESMReader& reader, const std::map& contentFileMap); + void respawn (); + ///< Check mLastRespawn and respawn references if necessary. This is a no-op if the cell is not loaded. + template CellRefList& get() { throw std::runtime_error ("Storage for this type not exist in cells"); diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 99dbcc66c..c13ecfab5 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -82,6 +82,14 @@ namespace MWWorld return false; } + int Class::getItemHealth(const Ptr &ptr) const + { + if (ptr.getCellRef().getCharge() == -1) + return getItemMaxHealth(ptr); + else + return ptr.getCellRef().getCharge(); + } + int Class::getItemMaxHealth (const Ptr& ptr) const { throw std::runtime_error ("class does not have item health"); @@ -304,7 +312,7 @@ namespace MWWorld return ""; } - void Class::applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const + std::string Class::applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const { throw std::runtime_error ("class can't be enchanted"); } @@ -323,7 +331,7 @@ namespace MWWorld if(!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) return boost::shared_ptr(new NullAction()); - if(get(actor).isNpc() && get(actor).getNpcStats(actor).isWerewolf()) + if(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Sound *sound = store.get().searchRandom("WolfItem"); @@ -397,7 +405,7 @@ namespace MWWorld void Class::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) const {} - int Class::getBaseGold(const MWWorld::Ptr& ptr) const + int Class::getBaseGold(const MWWorld::Ptr& ptr) const { throw std::runtime_error("class does not support base gold"); } @@ -406,4 +414,14 @@ namespace MWWorld { return false; } + + int Class::getDoorState (const MWWorld::Ptr &ptr) const + { + throw std::runtime_error("this is not a door"); + } + + void Class::setDoorState (const MWWorld::Ptr &ptr, int state) const + { + throw std::runtime_error("this is not a door"); + } } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 27b842348..c3f94d7f1 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -116,9 +116,12 @@ namespace MWWorld virtual bool hasItemHealth (const Ptr& ptr) const; ///< \return Item health data available? (default implementation: false) + virtual int getItemHealth (const Ptr& ptr) const; + ///< Return current item health or throw an exception if class does not have item health + virtual int getItemMaxHealth (const Ptr& ptr) const; ///< Return item max health or throw an exception, if class does not have item health - /// (default implementation: throw an exceoption) + /// (default implementation: throw an exception) virtual void hit(const Ptr& ptr, int type=-1) const; ///< Execute a melee hit, using the current weapon. This will check the relevant skills @@ -278,7 +281,8 @@ namespace MWWorld virtual std::string getModel(const MWWorld::Ptr &ptr) const; - virtual void applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const; + virtual std::string applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const; + ///< Creates a new record using \a ptr as template, with the given name and the given enchantment applied to it. virtual std::pair canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const; ///< Return 0 if player cannot equip item. 1 if can equip. 2 if it's twohanded weapon. 3 if twohanded weapon conflicts with that. @@ -325,17 +329,20 @@ namespace MWWorld static const Class& get (const std::string& key); ///< If there is no class for this \a key, an exception is thrown. - static const Class& get (const Ptr& ptr) - { - return ptr.getClass(); - } - ///< If there is no class for this pointer, an exception is thrown. - static void registerClass (const std::string& key, boost::shared_ptr instance); virtual int getBaseGold(const MWWorld::Ptr& ptr) const; virtual bool isClass(const MWWorld::Ptr& ptr, const std::string &className) const; + + /// 0 = nothing, 1 = opening, 2 = closing + virtual int getDoorState (const MWWorld::Ptr &ptr) const; + /// This does not actually cause the door to move. Use World::activateDoor instead. + virtual void setDoorState (const MWWorld::Ptr &ptr, int state) const; + + virtual void respawn (const MWWorld::Ptr& ptr) const {} + + virtual void restock (const MWWorld::Ptr& ptr) const {} }; } diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index bd0704724..8a076c3fc 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -74,7 +74,6 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::getState (CellRefList ref (record); ref.load (state); - ref.mRef.mRefNum.mContentFile = -1; collection.mList.push_back (ref); return ContainerStoreIterator (this, --collection.mList.end()); @@ -127,7 +126,7 @@ int MWWorld::ContainerStore::count(const std::string &id) { int total=0; for (MWWorld::ContainerStoreIterator iter (begin()); iter!=end(); ++iter) - if (Misc::StringUtils::ciEqual(iter->getCellRef().mRefID, id)) + if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), id)) total += iter->getRefData().getCount(); return total; } @@ -142,10 +141,10 @@ void MWWorld::ContainerStore::unstack(const Ptr &ptr, const Ptr& container) bool MWWorld::ContainerStore::stacks(const Ptr& ptr1, const Ptr& ptr2) { - const MWWorld::Class& cls1 = MWWorld::Class::get(ptr1); - const MWWorld::Class& cls2 = MWWorld::Class::get(ptr2); + const MWWorld::Class& cls1 = ptr1.getClass(); + const MWWorld::Class& cls2 = ptr2.getClass(); - if (!Misc::StringUtils::ciEqual(ptr1.getCellRef().mRefID, ptr2.getCellRef().mRefID)) + if (!Misc::StringUtils::ciEqual(ptr1.getCellRef().getRefId(), ptr2.getCellRef().getRefId())) return false; // If it has an enchantment, don't stack when some of the charge is already used @@ -154,25 +153,24 @@ bool MWWorld::ContainerStore::stacks(const Ptr& ptr1, const Ptr& ptr2) const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find( ptr1.getClass().getEnchantment(ptr1)); float maxCharge = enchantment->mData.mCharge; - float enchantCharge1 = ptr1.getCellRef().mEnchantmentCharge == -1 ? maxCharge : ptr1.getCellRef().mEnchantmentCharge; - float enchantCharge2 = ptr2.getCellRef().mEnchantmentCharge == -1 ? maxCharge : ptr2.getCellRef().mEnchantmentCharge; + float enchantCharge1 = ptr1.getCellRef().getEnchantmentCharge() == -1 ? maxCharge : ptr1.getCellRef().getEnchantmentCharge(); + float enchantCharge2 = ptr2.getCellRef().getEnchantmentCharge() == -1 ? maxCharge : ptr2.getCellRef().getEnchantmentCharge(); if (enchantCharge1 != maxCharge || enchantCharge2 != maxCharge) return false; } return ptr1 != ptr2 // an item never stacks onto itself - && ptr1.getCellRef().mOwner == ptr2.getCellRef().mOwner - && ptr1.getCellRef().mSoul == ptr2.getCellRef().mSoul + && ptr1.getCellRef().getOwner() == ptr2.getCellRef().getOwner() + && ptr1.getCellRef().getSoul() == ptr2.getCellRef().getSoul() && ptr1.getClass().getRemainingUsageTime(ptr1) == ptr2.getClass().getRemainingUsageTime(ptr2) && cls1.getScript(ptr1) == cls2.getScript(ptr2) // item that is already partly used up never stacks - && (!cls1.hasItemHealth(ptr1) || ptr1.getCellRef().mCharge == -1 - || cls1.getItemMaxHealth(ptr1) == ptr1.getCellRef().mCharge) - && (!cls2.hasItemHealth(ptr2) || ptr2.getCellRef().mCharge == -1 - || cls2.getItemMaxHealth(ptr2) == ptr2.getCellRef().mCharge); + && (!cls1.hasItemHealth(ptr1) || ( + cls1.getItemHealth(ptr1) == cls1.getItemMaxHealth(ptr1) + && cls2.getItemHealth(ptr2) == cls2.getItemMaxHealth(ptr2))); } MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add(const std::string &id, int count, const Ptr &actorPtr) @@ -187,41 +185,70 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add(const std::string & MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr, int count, const Ptr& actorPtr, bool setOwner) { - MWWorld::ContainerStoreIterator it = addImp(itemPtr, count); - MWWorld::Ptr item = *it; + Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); - // 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; + MWWorld::ContainerStoreIterator it = end(); if (setOwner && actorPtr.getClass().isActor()) - item.getCellRef().mOwner = actorPtr.getCellRef().mRefID; + { + // HACK: Set owner on the original item, then reset it after we have copied it + // If we set the owner on the copied item, it would not stack correctly... + std::string oldOwner = itemPtr.getCellRef().getOwner(); + if (actorPtr == player) + { + // No point in setting owner to the player - NPCs will not respect this anyway + // Additionally, setting it to "player" would make those items not stack with items that don't have an owner + itemPtr.getCellRef().setOwner(""); + } + else + itemPtr.getCellRef().setOwner(actorPtr.getCellRef().getRefId()); - std::string script = MWWorld::Class::get(item).getScript(item); - if(script != "") + it = addImp(itemPtr, count); + + itemPtr.getCellRef().setOwner(oldOwner); + } + else { - CellStore *cell; + it = addImp(itemPtr, count); + } - Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); + // The copy of the original item we just made + MWWorld::Ptr item = *it; - if(&(MWWorld::Class::get (player).getContainerStore (player)) == this) + // we may have copied an item from the world, so reset a few things first + item.getRefData().setBaseNode(NULL); // Especially important, otherwise scripts on the item could think that it's actually in a cell + ESM::Position pos; + pos.rot[0] = 0; + pos.rot[1] = 0; + pos.rot[2] = 0; + pos.pos[0] = 0; + pos.pos[1] = 0; + pos.pos[2] = 0; + item.getCellRef().setPosition(pos); + + std::string script = item.getClass().getScript(item); + if(script != "") + { + if (actorPtr == player) { - cell = 0; // Items in player's inventory have cell set to 0, so their scripts will never be removed - - // Set OnPCAdd special variable, if it is declared - item.getRefData().getLocals().setVarByInt(script, "onpcadd", 1); + // Items in player's inventory have cell set to 0, so their scripts will never be removed + item.mCell = 0; } else - cell = player.getCell(); + { + // Set mCell to the cell of the container/actor, so that the scripts are removed properly when + // the cell of the container/actor goes inactive + item.mCell = actorPtr.getCell(); + } - item.mCell = cell; item.mContainerStore = 0; + MWBase::Environment::get().getWorld()->getLocalScripts().add(script, item); + + // Set OnPCAdd special variable, if it is declared + // Make sure to do this *after* we have added the script to LocalScripts + if (actorPtr == player) + item.getRefData().getLocals().setVarByInt(script, "onpcadd", 1); } return it; @@ -236,17 +263,17 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr, // gold needs special handling: when it is inserted into a container, the base object automatically becomes Gold_001 // this ensures that gold piles of different sizes stack with each other (also, several scripts rely on Gold_001 for detecting player gold) - if (Misc::StringUtils::ciEqual(ptr.getCellRef().mRefID, "gold_001") - || Misc::StringUtils::ciEqual(ptr.getCellRef().mRefID, "gold_005") - || Misc::StringUtils::ciEqual(ptr.getCellRef().mRefID, "gold_010") - || Misc::StringUtils::ciEqual(ptr.getCellRef().mRefID, "gold_025") - || Misc::StringUtils::ciEqual(ptr.getCellRef().mRefID, "gold_100")) + if (Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_001") + || Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_005") + || Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_010") + || Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_025") + || Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), "gold_100")) { int realCount = count * ptr.getClass().getValue(ptr); for (MWWorld::ContainerStoreIterator iter (begin(type)); iter!=end(); ++iter) { - if (Misc::StringUtils::ciEqual((*iter).getCellRef().mRefID, MWWorld::ContainerStore::sGoldId)) + if (Misc::StringUtils::ciEqual((*iter).getCellRef().getRefId(), MWWorld::ContainerStore::sGoldId)) { iter->getRefData().setCount(iter->getRefData().getCount() + realCount); flagAsModified(); @@ -305,7 +332,7 @@ int MWWorld::ContainerStore::remove(const std::string& itemId, int count, const int toRemove = count; for (ContainerStoreIterator iter(begin()); iter != end() && toRemove > 0; ++iter) - if (Misc::StringUtils::ciEqual(iter->getCellRef().mRefID, itemId)) + if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), itemId)) toRemove -= remove(*iter, toRemove, actor); flagAsModified(); @@ -343,7 +370,7 @@ void MWWorld::ContainerStore::fill (const ESM::InventoryList& items, const std:: for (std::vector::const_iterator iter (items.mList.begin()); iter!=items.mList.end(); ++iter) { - std::string id = iter->mItem.toString(); + std::string id = Misc::StringUtils::lowerCase(iter->mItem.toString()); addInitialItem(id, owner, faction, iter->mCount); } @@ -351,20 +378,18 @@ void MWWorld::ContainerStore::fill (const ESM::InventoryList& items, const std:: } void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std::string& owner, const std::string& faction, - int count, bool topLevel) + int count, bool topLevel, const std::string& levItem) { - count = std::abs(count); /// \todo implement item restocking (indicated by negative count) - ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), id, count); if (ref.getPtr().getTypeName()==typeid (ESM::ItemLevList).name()) { const ESM::ItemLevList* levItem = ref.getPtr().get()->mBase; - if (topLevel && count > 1 && levItem->mFlags & ESM::ItemLevList::Each) + if (topLevel && std::abs(count) > 1 && levItem->mFlags & ESM::ItemLevList::Each) { - for (int i=0; i 0 ? 1 : -1, true, levItem->mId); return; } else @@ -372,17 +397,58 @@ void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std:: std::string id = MWMechanics::getLevelledItem(ref.getPtr().get()->mBase, false); if (id.empty()) return; - addInitialItem(id, owner, faction, count, false); + addInitialItem(id, owner, faction, count, false, levItem->mId); } } else { - ref.getPtr().getCellRef().mOwner = owner; - ref.getPtr().getCellRef().mFaction = faction; + // A negative count indicates restocking items + // For a restocking levelled item, remember what we spawned so we can delete it later when the merchant restocks + if (!levItem.empty() && count < 0) + { + if (mLevelledItemMap.find(id) == mLevelledItemMap.end()) + mLevelledItemMap[id] = 0; + mLevelledItemMap[id] += std::abs(count); + } + count = std::abs(count); + + ref.getPtr().getCellRef().setOwner(owner); + ref.getPtr().getCellRef().setFaction(faction); addImp (ref.getPtr(), count); } } +void MWWorld::ContainerStore::restock (const ESM::InventoryList& items, const MWWorld::Ptr& ptr, const std::string& owner, const std::string& faction) +{ + // Remove the items already spawned by levelled items that will restock + for (std::map::iterator it = mLevelledItemMap.begin(); it != mLevelledItemMap.end(); ++it) + { + if (count(it->first) >= it->second) + remove(it->first, it->second, ptr); + } + mLevelledItemMap.clear(); + + for (std::vector::const_iterator it = items.mList.begin(); it != items.mList.end(); ++it) + { + if (it->mCount >= 0) + continue; + + std::string item = Misc::StringUtils::lowerCase(it->mItem.toString()); + + if (MWBase::Environment::get().getWorld()->getStore().get().search(it->mItem.toString())) + { + addInitialItem(item, owner, faction, it->mCount, true); + } + else + { + int currentCount = count(item); + if (currentCount < std::abs(it->mCount)) + add (item, std::abs(it->mCount) - currentCount, ptr); + } + } + flagAsModified(); +} + void MWWorld::ContainerStore::clear() { for (ContainerStoreIterator iter (begin()); iter!=end(); ++iter) @@ -561,6 +627,8 @@ void MWWorld::ContainerStore::writeState (ESM::InventoryState& state) const state.mLights.clear(); + state.mLevelledItemMap = mLevelledItemMap; + for (MWWorld::CellRefList::List::const_iterator iter (lights.mList.begin()); iter!=lights.mList.end(); ++iter) { @@ -604,6 +672,8 @@ void MWWorld::ContainerStore::readState (const ESM::InventoryState& state) { getState (lights, iter->first); } + + mLevelledItemMap = state.mLevelledItemMap; } diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index dd0c1b002..7c81bdb6e 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -2,6 +2,7 @@ #define GAME_MWWORLD_CONTAINERSTORE_H #include +#include #include #include @@ -65,10 +66,15 @@ namespace MWWorld MWWorld::CellRefList probes; MWWorld::CellRefList repairs; MWWorld::CellRefList weapons; + + std::map mLevelledItemMap; + ///< Stores result of levelled item spawns. + /// This is used to remove the spawned item(s) if the levelled item is restocked. + mutable float mCachedWeight; mutable bool mWeightUpToDate; ContainerStoreIterator addImp (const Ptr& ptr, int count); - void addInitialItem (const std::string& id, const std::string& owner, const std::string& faction, int count, bool topLevel=true); + void addInitialItem (const std::string& id, const std::string& owner, const std::string& faction, int count, bool topLevel=true, const std::string& levItem = ""); template ContainerStoreIterator getState (CellRefList& collection, @@ -145,6 +151,8 @@ namespace MWWorld void fill (const ESM::InventoryList& items, const std::string& owner, const std::string& faction, const MWWorld::ESMStore& store); ///< Insert items into *this. + void restock (const ESM::InventoryList& items, const MWWorld::Ptr& ptr, const std::string& owner, const std::string& faction); + virtual void clear(); ///< Empty container. diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index c5c826d47..03d928d2a 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -27,24 +27,22 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) { listener->setProgressRange(1000); - std::set missing; - ESM::Dialogue *dialogue = 0; /// \todo Move this to somewhere else. ESMReader? // Cache parent esX files by tracking their indices in the global list of // all files/readers used by the engine. This will greaty accelerate // refnumber mangling, as required for handling moved references. - int index = ~0; const std::vector &masters = esm.getGameFiles(); std::vector *allPlugins = esm.getGlobalReaderList(); for (size_t j = 0; j < masters.size(); j++) { ESM::Header::MasterData &mast = const_cast(masters[j]); std::string fname = mast.name; + int index = ~0; for (int i = 0; i < esm.getIndex(); i++) { const std::string &candidate = allPlugins->at(i).getContext().filename; std::string fnamecandidate = boost::filesystem::path(candidate).filename().string(); - if (fname == fnamecandidate) { + if (Misc::StringUtils::ciEqual(fname, fnamecandidate)) { index = i; break; } @@ -72,9 +70,10 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) if (n.val == ESM::REC_INFO) { std::string id = esm.getHNOString("INAM"); if (dialogue) { - dialogue->mInfo.push_back(ESM::DialInfo()); - dialogue->mInfo.back().mId = id; - dialogue->mInfo.back().load(esm); + ESM::DialInfo info; + info.mId = id; + info.load(esm); + dialogue->addInfo(info, esm.getIndex() != 0); } else { std::cerr << "error: info record without dialog" << std::endl; esm.skipRecord(); @@ -84,9 +83,9 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) } else if (n.val == ESM::REC_SKIL) { mSkills.load (esm); } else { - // Not found (this would be an error later) - esm.skipRecord(); - missing.insert(n.toString()); + std::stringstream error; + error << "Unknown record: " << n.toString(); + throw std::runtime_error(error.str()); } } else { // Load it @@ -113,19 +112,6 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) } listener->setProgress(esm.getFileOffset() / (float)esm.getFileSize() * 1000); } - - /* This information isn't needed on screen. But keep the code around - for debugging purposes later. - - cout << "\n" << mStores.size() << " record types:\n"; - for(RecListList::iterator it = mStores.begin(); it != mStores.end(); it++) - cout << " " << toStr(it->first) << ": " << it->second->getSize() << endl; - cout << "\nNot implemented yet: "; - for(set::iterator it = missing.begin(); - it != missing.end(); it++ ) - cout << *it << " "; - cout << endl; - */ } void ESMStore::setUp() @@ -141,8 +127,8 @@ void ESMStore::setUp() int ESMStore::countSavedGameRecords() const { - return - mPotions.getDynamicSize() + return 1 // DYNA (dynamic name counter) + +mPotions.getDynamicSize() +mArmors.getDynamicSize() +mBooks.getDynamicSize() +mClasses.getDynamicSize() @@ -153,17 +139,24 @@ void ESMStore::setUp() +mWeapons.getDynamicSize(); } - void ESMStore::write (ESM::ESMWriter& writer) const + void ESMStore::write (ESM::ESMWriter& writer, Loading::Listener& progress) const { - mPotions.write (writer); - mArmors.write (writer); - mBooks.write (writer); - mClasses.write (writer); - mClothes.write (writer); - mEnchants.write (writer); - mSpells.write (writer); - mWeapons.write (writer); - mNpcs.write (writer); + writer.startRecord(ESM::REC_DYNA); + writer.startSubRecord("COUN"); + writer.writeT(mDynamicCount); + writer.endRecord("COUN"); + writer.endRecord(ESM::REC_DYNA); + progress.increaseProgress(); + + mPotions.write (writer, progress); + mArmors.write (writer, progress); + mBooks.write (writer, progress); + mClasses.write (writer, progress); + mClothes.write (writer, progress); + mEnchants.write (writer, progress); + mSpells.write (writer, progress); + mWeapons.write (writer, progress); + mNpcs.write (writer, progress); } bool ESMStore::readRecord (ESM::ESMReader& reader, int32_t type) @@ -192,11 +185,16 @@ void ESMStore::setUp() if (!mRaces.find (player->mRace) || !mClasses.find (player->mClass)) - throw std::runtime_error ("Invalid player record (race or class unavilable"); + throw std::runtime_error ("Invalid player record (race or class unavailable"); } return true; + case ESM::REC_DYNA: + reader.getSubNameIs("COUN"); + reader.getHT(mDynamicCount); + return true; + default: return false; diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index e6730c307..90786acd4 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -85,7 +85,8 @@ namespace MWWorld return mStores.end(); } - // Look up the given ID in 'all'. Returns 0 if not found. + /// Look up the given ID in 'all'. Returns 0 if not found. + /// \note id must be in lower case. int find(const std::string &id) const { std::map::const_iterator it = mIds.find(id); @@ -166,16 +167,17 @@ namespace MWWorld template const T *insert(const T &x) { + std::ostringstream id; + id << "$dynamic" << mDynamicCount++; + Store &store = const_cast &>(get()); - if (store.search(x.mId) != 0) { + if (store.search(id.str()) != 0) { std::ostringstream msg; - msg << "Try to override existing record '" << x.mId << "'"; + msg << "Try to override existing record '" << id.str() << "'"; throw std::runtime_error(msg.str()); } T record = x; - std::ostringstream id; - id << "$dynamic" << mDynamicCount++; record.mId = id.str(); T *ptr = store.insert(record); @@ -189,10 +191,13 @@ namespace MWWorld template const T *insertStatic(const T &x) { + std::ostringstream id; + id << "$dynamic" << mDynamicCount++; + Store &store = const_cast &>(get()); - if (store.search(x.mId) != 0) { + if (store.search(id.str()) != 0) { std::ostringstream msg; - msg << "Try to override existing record '" << x.mId << "'"; + msg << "Try to override existing record '" << id.str() << "'"; throw std::runtime_error(msg.str()); } T record = x; @@ -212,7 +217,7 @@ namespace MWWorld int countSavedGameRecords() const; - void write (ESM::ESMWriter& writer) const; + void write (ESM::ESMWriter& writer, Loading::Listener& progress) const; bool readRecord (ESM::ESMReader& reader, int32_t type); ///< \return Known type? @@ -225,17 +230,18 @@ namespace MWWorld template <> inline const ESM::NPC *ESMStore::insert(const ESM::NPC &npc) { + std::ostringstream id; + id << "$dynamic" << mDynamicCount++; + if (Misc::StringUtils::ciEqual(npc.mId, "player")) { return mNpcs.insert(npc); - } else if (mNpcs.search(npc.mId) != 0) { + } else if (mNpcs.search(id.str()) != 0) { std::ostringstream msg; - msg << "Try to override existing record '" << npc.mId << "'"; + msg << "Try to override existing record '" << id.str() << "'"; throw std::runtime_error(msg.str()); } ESM::NPC record = npc; - std::ostringstream id; - id << "$dynamic" << mDynamicCount++; record.mId = id.str(); ESM::NPC *ptr = mNpcs.insert(record); diff --git a/apps/openmw/mwworld/fallback.cpp b/apps/openmw/mwworld/fallback.cpp index 569a6b50c..c0b21b2ef 100644 --- a/apps/openmw/mwworld/fallback.cpp +++ b/apps/openmw/mwworld/fallback.cpp @@ -41,8 +41,9 @@ namespace MWWorld unsigned int j=0; for(unsigned int i=0;i(ret[0])/255.f,boost::lexical_cast(ret[1])/255.f,boost::lexical_cast(ret[2])/255.f); } } diff --git a/apps/openmw/mwworld/globals.cpp b/apps/openmw/mwworld/globals.cpp index 879ffa8e3..663af640b 100644 --- a/apps/openmw/mwworld/globals.cpp +++ b/apps/openmw/mwworld/globals.cpp @@ -77,7 +77,7 @@ namespace MWWorld return mVariables.size(); } - void Globals::write (ESM::ESMWriter& writer) const + void Globals::write (ESM::ESMWriter& writer, Loading::Listener& progress) const { for (Collection::const_iterator iter (mVariables.begin()); iter!=mVariables.end(); ++iter) { @@ -85,6 +85,7 @@ namespace MWWorld writer.writeHNString ("NAME", iter->first); iter->second.write (writer, ESM::Variant::Format_Global); writer.endRecord (ESM::REC_GLOB); + progress.increaseProgress(); } } diff --git a/apps/openmw/mwworld/globals.hpp b/apps/openmw/mwworld/globals.hpp index d8d2cefbf..3ff4a5d6e 100644 --- a/apps/openmw/mwworld/globals.hpp +++ b/apps/openmw/mwworld/globals.hpp @@ -16,6 +16,11 @@ namespace ESM class ESMReader; } +namespace Loading +{ + class Listener; +} + namespace MWWorld { class ESMStore; @@ -46,7 +51,7 @@ namespace MWWorld int countSavedGameRecords() const; - void write (ESM::ESMWriter& writer) const; + void write (ESM::ESMWriter& writer, Loading::Listener& progress) const; bool readRecord (ESM::ESMReader& reader, int32_t type); ///< Records for variables that do not exist are dropped silently. diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index e00276293..2eb8aeb46 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -8,7 +8,6 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" -#include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwmechanics/npcstats.hpp" @@ -130,7 +129,7 @@ void MWWorld::InventoryStore::equip (int slot, const ContainerStoreIterator& ite std::pair, bool> slots_; - slots_ = Class::get (*iterator).getEquipmentSlots (*iterator); + slots_ = iterator->getClass().getEquipmentSlots (*iterator); if (std::find (slots_.first.begin(), slots_.first.end(), slot)==slots_.first.end()) throw std::runtime_error ("invalid slot"); @@ -150,10 +149,6 @@ void MWWorld::InventoryStore::equip (int slot, const ContainerStoreIterator& ite fireEquipmentChangedEvent(); updateMagicEffects(actor); - - // Update HUD icon for player weapon - if (slot == MWWorld::InventoryStore::Slot_CarriedRight) - MWBase::Environment::get().getWindowManager()->setSelectedWeapon(*getSlot(slot)); } void MWWorld::InventoryStore::unequipAll(const MWWorld::Ptr& actor) @@ -197,16 +192,21 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor) { Ptr test = *iter; - // Don't autoEquip lights + // Don't autoEquip lights. Handled in Actors::updateEquippedLight based on environment light. if (test.getTypeName() == typeid(ESM::Light).name()) { continue; } + // Don't auto-equip probes or lockpicks. NPCs can't use them (yet). And AiCombat would attempt to "attack" with them. + // NOTE: In the future AiCombat should handle equipping appropriate weapons + if (test.getTypeName() == typeid(ESM::Lockpick).name() || test.getTypeName() == typeid(ESM::Probe).name()) + continue; + // Only autoEquip if we are the original owner of the item. // This stops merchants from auto equipping anything you sell to them. // ...unless this is a companion, he should always equip items given to him. - if (!Misc::StringUtils::ciEqual(test.getCellRef().mOwner, actor.getCellRef().mRefID) && + if (!Misc::StringUtils::ciEqual(test.getCellRef().getOwner(), actor.getCellRef().getRefId()) && (actor.getClass().getScript(actor).empty() || !actor.getRefData().getLocals().getIntVar(actor.getClass().getScript(actor), "companion"))) continue; @@ -258,7 +258,7 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor) } } - switch(MWWorld::Class::get (test).canBeEquipped (test, actor).first) + switch(test.getClass().canBeEquipped (test, actor).first) { case 0: continue; @@ -325,7 +325,7 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) if (*iter==end()) continue; - std::string enchantmentId = MWWorld::Class::get (**iter).getEnchantment (**iter); + std::string enchantmentId = (*iter)->getClass().getEnchantment (**iter); if (!enchantmentId.empty()) { @@ -337,7 +337,7 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) std::vector params; - bool existed = (mPermanentMagicEffectMagnitudes.find((**iter).getCellRef().mRefID) != mPermanentMagicEffectMagnitudes.end()); + bool existed = (mPermanentMagicEffectMagnitudes.find((**iter).getCellRef().getRefId()) != mPermanentMagicEffectMagnitudes.end()); if (!existed) { // Roll some dice, one for each effect @@ -358,10 +358,10 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) // Consider equipping the same item twice (e.g. a ring) // However, permanent enchantments with a random magnitude are kind of an exploit anyway, // so it doesn't really matter if both items will get the same magnitude. *Extreme* edge case. - mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID] = params; + mPermanentMagicEffectMagnitudes[(**iter).getCellRef().getRefId()] = params; } else - params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID]; + params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().getRefId()]; int i=0; for (std::vector::const_iterator effectIt (enchantment.mEffects.mList.begin()); @@ -407,7 +407,7 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) { if (*iter == end()) continue; - if ((**iter).getCellRef().mRefID == it->first) + if ((**iter).getCellRef().getRefId() == it->first) { found = true; } @@ -441,7 +441,7 @@ bool MWWorld::InventoryStore::stacks(const Ptr& ptr1, const Ptr& ptr2) { if (*iter != end() && (ptr1 == **iter || ptr2 == **iter)) { - bool stackWhenEquipped = MWWorld::Class::get(**iter).getEquipmentSlots(**iter).second; + bool stackWhenEquipped = (*iter)->getClass().getEquipmentSlots(**iter).second; if (!stackWhenEquipped) return false; } @@ -494,7 +494,6 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor && *mSelectedEnchantItem == item && actor.getRefData().getHandle() == "player") { mSelectedEnchantItem = end(); - MWBase::Environment::get().getWindowManager()->unsetSelectedSpell(); } updateRechargingItems(); @@ -528,22 +527,13 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, c if (actor.getRefData().getHandle() == "player") { // Unset OnPCEquip Variable on item's script, if it has a script with that variable declared - const std::string& script = Class::get(*it).getScript(*it); + const std::string& script = it->getClass().getScript(*it); if (script != "") (*it).getRefData().getLocals().setVarByInt(script, "onpcequip", 0); - // Update HUD icon when removing player weapon or selected enchanted item. - // We have to check for both as the weapon could also be the enchanted item. - if (slot == MWWorld::InventoryStore::Slot_CarriedRight) - { - // weapon - MWBase::Environment::get().getWindowManager()->unsetSelectedWeapon(); - } if ((mSelectedEnchantItem != end()) && (mSelectedEnchantItem == it)) { - // enchanted item mSelectedEnchantItem = end(); - MWBase::Environment::get().getWindowManager()->unsetSelectedSpell(); } } @@ -589,7 +579,7 @@ void MWWorld::InventoryStore::visitEffectSources(MWMechanics::EffectSourceVisito if (*iter==end()) continue; - std::string enchantmentId = MWWorld::Class::get (**iter).getEnchantment (**iter); + std::string enchantmentId = (*iter)->getClass().getEnchantment (**iter); if (enchantmentId.empty()) continue; @@ -599,17 +589,17 @@ void MWWorld::InventoryStore::visitEffectSources(MWMechanics::EffectSourceVisito if (enchantment.mData.mType != ESM::Enchantment::ConstantEffect) continue; - if (mPermanentMagicEffectMagnitudes.find((**iter).getCellRef().mRefID) == mPermanentMagicEffectMagnitudes.end()) + if (mPermanentMagicEffectMagnitudes.find((**iter).getCellRef().getRefId()) == mPermanentMagicEffectMagnitudes.end()) continue; int i=0; for (std::vector::const_iterator effectIt (enchantment.mEffects.mList.begin()); effectIt!=enchantment.mEffects.mList.end(); ++effectIt) { - const EffectParams& params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID][i]; + const EffectParams& params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().getRefId()][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), -1, magnitude); ++i; } @@ -636,15 +626,15 @@ void MWWorld::InventoryStore::rechargeItems(float duration) { for (TRechargingItems::iterator it = mRechargingItems.begin(); it != mRechargingItems.end(); ++it) { - if (it->first->getCellRef().mEnchantmentCharge == -1 - || it->first->getCellRef().mEnchantmentCharge == it->second) + if (it->first->getCellRef().getEnchantmentCharge() == -1 + || it->first->getCellRef().getEnchantmentCharge() == it->second) continue; static float fMagicItemRechargePerSecond = MWBase::Environment::get().getWorld()->getStore().get().find( "fMagicItemRechargePerSecond")->getFloat(); - it->first->getCellRef().mEnchantmentCharge = std::min (it->first->getCellRef().mEnchantmentCharge + fMagicItemRechargePerSecond * duration, - it->second); + it->first->getCellRef().setEnchantmentCharge(std::min (it->first->getCellRef().getEnchantmentCharge() + fMagicItemRechargePerSecond * duration, + it->second)); } } diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index b4b275b6a..95b956907 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -87,6 +87,7 @@ namespace MWWorld float mMultiplier; }; + // TODO: store in savegame typedef std::map > TEffectMagnitudes; TEffectMagnitudes mPermanentMagicEffectMagnitudes; diff --git a/apps/openmw/mwworld/livecellref.cpp b/apps/openmw/mwworld/livecellref.cpp index 0fbb26c84..0921d3a1b 100644 --- a/apps/openmw/mwworld/livecellref.cpp +++ b/apps/openmw/mwworld/livecellref.cpp @@ -10,6 +10,11 @@ #include "class.hpp" #include "esmstore.hpp" +MWWorld::LiveCellRefBase::LiveCellRefBase(std::string type, const ESM::CellRef &cref) + : mClass(&Class::get(type)), mRef(cref), mData(cref) +{ +} + void MWWorld::LiveCellRefBase::loadImp (const ESM::ObjectState& state) { mRef = state.mRef; @@ -20,10 +25,15 @@ void MWWorld::LiveCellRefBase::loadImp (const ESM::ObjectState& state) if (state.mHasLocals) { std::string scriptId = mClass->getScript (ptr); - - mData.setLocals (*MWBase::Environment::get().getWorld()->getStore(). - get().search (scriptId)); - mData.getLocals().read (state.mLocals, scriptId); + // Make sure we still have a script. It could have been coming from a content file that is no longer active. + if (!scriptId.empty()) + { + if (const ESM::Script* script = MWBase::Environment::get().getWorld()->getStore().get().search (scriptId)) + { + mData.setLocals (*script); + mData.getLocals().read (state.mLocals, scriptId); + } + } } mClass->readAdditionalState (ptr, state); @@ -31,7 +41,7 @@ void MWWorld::LiveCellRefBase::loadImp (const ESM::ObjectState& state) void MWWorld::LiveCellRefBase::saveImp (ESM::ObjectState& state) const { - state.mRef = mRef; + mRef.writeState(state); /// \todo get rid of this cast once const-correct Ptr are available Ptr ptr (const_cast (this)); @@ -44,4 +54,4 @@ void MWWorld::LiveCellRefBase::saveImp (ESM::ObjectState& state) const bool MWWorld::LiveCellRefBase::checkStateImp (const ESM::ObjectState& state) { return true; -} \ No newline at end of file +} diff --git a/apps/openmw/mwworld/livecellref.hpp b/apps/openmw/mwworld/livecellref.hpp index b2e4d6d56..3994d8a24 100644 --- a/apps/openmw/mwworld/livecellref.hpp +++ b/apps/openmw/mwworld/livecellref.hpp @@ -3,7 +3,7 @@ #include -#include +#include "cellref.hpp" #include "refdata.hpp" @@ -26,7 +26,7 @@ namespace MWWorld /** Information about this instance, such as 3D location and rotation * and individual type-dependent data. */ - ESM::CellRef mRef; + MWWorld::CellRef mRef; /** runtime-data */ RefData mData; @@ -62,9 +62,9 @@ namespace MWWorld /// \note Does not check if the RefId exists. }; - inline bool operator== (const LiveCellRefBase& cellRef, const ESM::CellRef::RefNum refNum) + inline bool operator== (const LiveCellRefBase& cellRef, const ESM::RefNum refNum) { - return cellRef.mRef.mRefNum==refNum; + return cellRef.mRef.getRefNum()==refNum; } /// A reference to one object (of any type) in a cell. diff --git a/apps/openmw/mwworld/localscripts.cpp b/apps/openmw/mwworld/localscripts.cpp index 844e2b18b..8a671cea8 100644 --- a/apps/openmw/mwworld/localscripts.cpp +++ b/apps/openmw/mwworld/localscripts.cpp @@ -36,10 +36,10 @@ namespace MWWorld::Ptr containerPtr (&*iter, cell); - MWWorld::ContainerStore& container = MWWorld::Class::get(containerPtr).getContainerStore(containerPtr); + MWWorld::ContainerStore& container = containerPtr.getClass().getContainerStore(containerPtr); for(MWWorld::ContainerStoreIterator it3 = container.begin(); it3 != container.end(); ++it3) { - std::string script = MWWorld::Class::get(*it3).getScript(*it3); + std::string script = it3->getClass().getScript(*it3); if(script != "") { MWWorld::Ptr item = *it3; diff --git a/apps/openmw/mwworld/manualref.hpp b/apps/openmw/mwworld/manualref.hpp index 0e21c55ac..3842e7ff1 100644 --- a/apps/openmw/mwworld/manualref.hpp +++ b/apps/openmw/mwworld/manualref.hpp @@ -19,56 +19,14 @@ namespace MWWorld ManualRef& operator= (const ManualRef&); template - bool create (const MWWorld::Store& list, const std::string& name) + void create (const MWWorld::Store& list, const std::string& name) { - if (const T *instance = list.search (name)) - { - LiveCellRef ref; - ref.mBase = instance; - ref.mRef.mRefNum.mIndex = 0; - ref.mRef.mRefNum.mContentFile = -1; - - mRef = ref; - mPtr = Ptr (&boost::any_cast&> (mRef), 0); - - return true; - } - - return false; - } + const T* base = list.find(name); - public: - - ManualRef (const MWWorld::ESMStore& store, const std::string& name, const int count=1) - { - // create - if (!create (store.get(), name) && - !create (store.get(), name) && - !create (store.get(), name) && - !create (store.get(), name) && - !create (store.get(), name) && - !create (store.get(), name) && - !create (store.get(), name) && - !create (store.get(), name) && - !create (store.get(), name) && - !create (store.get(), name) && - !create (store.get(), name) && - !create (store.get(), name) && - !create (store.get(), name) && - !create (store.get(), name) && - !create (store.get(), name) && - !create (store.get(), name) && - !create (store.get(), name) && - !create (store.get(), name) && - !create (store.get(), name) && - !create (store.get(), name)) - throw std::logic_error ("failed to create manual cell ref for " + name); - - // initialise - ESM::CellRef& cellRef = mPtr.getCellRef(); - cellRef.mRefID = Misc::StringUtils::lowerCase (name); + ESM::CellRef cellRef; cellRef.mRefNum.mIndex = 0; cellRef.mRefNum.mContentFile = -1; + cellRef.mRefID = name; cellRef.mScale = 1; cellRef.mFactIndex = 0; cellRef.mCharge = -1; @@ -77,6 +35,48 @@ namespace MWWorld cellRef.mTeleport = false; cellRef.mLockLevel = 0; cellRef.mReferenceBlocked = 0; + + LiveCellRef ref(cellRef, base); + + mRef = ref; + mPtr = Ptr (&boost::any_cast&> (mRef), 0); + } + + public: + + ManualRef (const MWWorld::ESMStore& store, const std::string& name, const int count=1) + { + std::string lowerName = Misc::StringUtils::lowerCase (name); + switch (store.find (lowerName)) + { + case ESM::REC_ACTI: create (store.get(), lowerName); break; + case ESM::REC_ALCH: create (store.get(), lowerName); break; + case ESM::REC_APPA: create (store.get(), lowerName); break; + case ESM::REC_ARMO: create (store.get(), lowerName); break; + case ESM::REC_BOOK: create (store.get(), lowerName); break; + case ESM::REC_CLOT: create (store.get(), lowerName); break; + case ESM::REC_CONT: create (store.get(), lowerName); break; + case ESM::REC_CREA: create (store.get(), lowerName); break; + case ESM::REC_DOOR: create (store.get(), lowerName); break; + case ESM::REC_INGR: create (store.get(), lowerName); break; + case ESM::REC_LEVC: create (store.get(), lowerName); break; + case ESM::REC_LEVI: create (store.get(), lowerName); break; + case ESM::REC_LIGH: create (store.get(), lowerName); break; + case ESM::REC_LOCK: create (store.get(), lowerName); break; + case ESM::REC_MISC: create (store.get(), lowerName); break; + case ESM::REC_NPC_: create (store.get(), lowerName); break; + case ESM::REC_PROB: create (store.get(), lowerName); break; + case ESM::REC_REPA: create (store.get(), lowerName); break; + case ESM::REC_STAT: create (store.get(), lowerName); break; + case ESM::REC_WEAP: create (store.get(), lowerName); break; + + case 0: + throw std::logic_error ("failed to create manual cell ref for " + lowerName + " (unknown ID)"); + + default: + throw std::logic_error ("failed to create manual cell ref for " + lowerName + " (unknown type)"); + } + mPtr.getRefData().setCount(count); } diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index 7d531d6d3..e93d9e640 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -33,7 +33,7 @@ using namespace Ogre; namespace MWWorld { - static const float sMaxSlope = 60.0f; + static const float sMaxSlope = 49.0f; static const float sStepSize = 32.0f; // Arbitrary number. To prevent infinite loops. They shouldn't happen but it's good to be prepared. static const int sMaxIterations = 8; @@ -227,10 +227,6 @@ namespace MWWorld Ogre::Vector3 inertia(0.0f); Ogre::Vector3 velocity; - bool canWalk = ptr.getClass().canWalk(ptr); - bool isBipedal = ptr.getClass().isBipedal(ptr); - bool isNpc = ptr.getClass().isNpc(); - if(position.z < waterlevel || isFlying) // under water by 3/4 or can fly { // TODO: Shouldn't water have higher drag in calculating velocity? @@ -246,6 +242,15 @@ namespace MWWorld // If falling, add part of the incoming velocity with the current inertia // TODO: but we could be jumping up? velocity = velocity * time + physicActor->getInertialForce(); + + // avoid getting infinite inertia in air + float actorSpeed = ptr.getClass().getSpeed(ptr); + float speedXY = Ogre::Vector2(velocity.x, velocity.y).length(); + if (speedXY > actorSpeed) + { + velocity.x *= actorSpeed / speedXY; + velocity.y *= actorSpeed / speedXY; + } } inertia = velocity; // NOTE: velocity is for z axis only in this code block @@ -277,14 +282,11 @@ namespace MWWorld // NOTE: velocity is either z axis only or x & z axis Ogre::Vector3 nextpos = newPosition + velocity * remainingTime; - // If not able to fly, walk or bipedal don't allow to move out of water + // If not able to fly, don't allow to swim up into the air // TODO: this if condition may not work for large creatures or situations // where the creature gets above the waterline for some reason if(newPosition.z < waterlevel && // started 3/4 under water !isFlying && // can't fly - !canWalk && // can't walk - !isBipedal && // not bipedal (assume bipedals can walk) - !isNpc && // FIXME: shouldn't really need this nextpos.z > waterlevel && // but about to go above water newPosition.z <= waterlevel) { @@ -313,8 +315,9 @@ namespace MWWorld // NOTE: stepMove modifies newPosition if successful if(stepMove(colobj, newPosition, velocity, remainingTime, engine)) { - // don't let slaughterfish move out of water after stepMove - if(ptr.getClass().canSwim(ptr) && newPosition.z > (waterlevel - halfExtents.z * 0.5)) + // don't let pure water creatures move out of water after stepMove + if((ptr.getClass().canSwim(ptr) && !ptr.getClass().canWalk(ptr)) + && newPosition.z > (waterlevel - halfExtents.z * 0.5)) newPosition = oldPosition; else // Only on the ground if there's gravity isOnGround = !(newPosition.z < waterlevel || isFlying); @@ -489,7 +492,7 @@ namespace MWWorld return std::make_pair(true, ray.getPoint(len * test.second)); } - std::pair PhysicsSystem::castRay(float mouseX, float mouseY) + std::pair PhysicsSystem::castRay(float mouseX, float mouseY, Ogre::Vector3* normal) { Ogre::Ray ray = mRender.getCamera()->getCameraToViewportRay( mouseX, @@ -501,7 +504,7 @@ namespace MWWorld _from = btVector3(from.x, from.y, from.z); _to = btVector3(to.x, to.y, to.z); - std::pair result = mEngine->rayTest(_from, _to); + std::pair result = mEngine->rayTest(_from, _to, true, false, normal); if (result.first == "") return std::make_pair(false, Ogre::Vector3()); @@ -535,7 +538,7 @@ namespace MWWorld void PhysicsSystem::addObject (const Ptr& ptr, bool placeable) { - std::string mesh = MWWorld::Class::get(ptr).getModel(ptr); + std::string mesh = ptr.getClass().getModel(ptr); Ogre::SceneNode* node = ptr.getRefData().getBaseNode(); handleToMesh[node->getName()] = mesh; OEngine::Physic::RigidBody* body = mEngine->createAndAdjustRigidBody( @@ -547,7 +550,7 @@ namespace MWWorld void PhysicsSystem::addActor (const Ptr& ptr) { - std::string mesh = MWWorld::Class::get(ptr).getModel(ptr); + std::string mesh = ptr.getClass().getModel(ptr); Ogre::SceneNode* node = ptr.getRefData().getBaseNode(); //TODO:optimize this. Searching the std::map isn't very efficient i think. mEngine->addCharacter(node->getName(), mesh, node->getPosition(), node->getScale().x, node->getOrientation()); @@ -632,12 +635,12 @@ namespace MWWorld bool cmode = act->getCollisionMode(); if(cmode) { - act->enableCollisions(false); + act->enableCollisionMode(false); return false; } else { - act->enableCollisions(true); + act->enableCollisionMode(true); return true; } } @@ -648,12 +651,12 @@ namespace MWWorld bool PhysicsSystem::getObjectAABB(const MWWorld::Ptr &ptr, Ogre::Vector3 &min, Ogre::Vector3 &max) { - std::string model = MWWorld::Class::get(ptr).getModel(ptr); + std::string model = ptr.getClass().getModel(ptr); if (model.empty()) { return false; } btVector3 btMin, btMax; - float scale = ptr.getCellRef().mScale; + float scale = ptr.getCellRef().getScale(); mEngine->getObjectAABB(model, scale, btMin, btMax); min.x = btMin.x(); @@ -671,7 +674,7 @@ namespace MWWorld void PhysicsSystem::queueObjectMovement(const Ptr &ptr, const Ogre::Vector3 &movement) { PtrVelocityList::iterator iter = mMovementQueue.begin(); - for(;iter != mMovementQueue.end();iter++) + for(;iter != mMovementQueue.end();++iter) { if(iter->first == ptr) { @@ -692,7 +695,7 @@ namespace MWWorld { const MWBase::World *world = MWBase::Environment::get().getWorld(); PtrVelocityList::iterator iter = mMovementQueue.begin(); - for(;iter != mMovementQueue.end();iter++) + for(;iter != mMovementQueue.end();++iter) { float waterlevel = -std::numeric_limits::max(); const ESM::Cell *cell = iter->first.getCell()->getCell(); diff --git a/apps/openmw/mwworld/physicssystem.hpp b/apps/openmw/mwworld/physicssystem.hpp index 3dcd088f5..899d7144d 100644 --- a/apps/openmw/mwworld/physicssystem.hpp +++ b/apps/openmw/mwworld/physicssystem.hpp @@ -70,8 +70,9 @@ namespace MWWorld std::pair castRay(const Ogre::Vector3 &orig, const Ogre::Vector3 &dir, float len); - std::pair castRay(float mouseX, float mouseY); - ///< cast ray from the mouse, return true if it hit something and the first result (in OGRE coordinates) + std::pair castRay(float mouseX, float mouseY, Ogre::Vector3* normal = NULL); + ///< cast ray from the mouse, return true if it hit something and the first result + /// @param normal if non-NULL, the hit normal will be written there (if there is a hit) OEngine::Physic::PhysicEngine* getEngine(); diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index a4a4e9568..9913b888b 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -9,13 +9,18 @@ #include #include +#include "../mwworld/esmstore.hpp" + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwmechanics/movement.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/actors.hpp" +#include "../mwmechanics/mechanicsmanagerimp.hpp" #include "class.hpp" #include "ptr.hpp" @@ -32,10 +37,12 @@ namespace MWWorld mTeleported(false), mMarkedCell(NULL), mCurrentCrimeId(-1), - mPayedCrimeId(-1) + mPaidCrimeId(-1) { - mPlayer.mBase = player; - mPlayer.mRef.mRefID = "player"; + ESM::CellRef cellRef; + cellRef.blank(); + cellRef.mRefID = "player"; + mPlayer = LiveCellRef(cellRef, player); float* playerPos = mPlayer.mData.getPosition().pos; playerPos[0] = playerPos[1] = playerPos[2] = 0; @@ -70,7 +77,7 @@ namespace MWWorld void Player::setDrawState (MWMechanics::DrawState_ state) { MWWorld::Ptr ptr = getPlayer(); - MWWorld::Class::get(ptr).getNpcStats(ptr).setDrawState (state); + ptr.getClass().getNpcStats(ptr).setDrawState (state); } bool Player::getAutoMove() const @@ -89,14 +96,13 @@ namespace MWWorld if (mAutoMove) value = 1; - MWWorld::Class::get (ptr).getMovementSettings (ptr).mPosition[1] = value; + ptr.getClass().getMovementSettings (ptr).mPosition[1] = value; } void Player::setLeftRight (int value) { MWWorld::Ptr ptr = getPlayer(); - - MWWorld::Class::get (ptr).getMovementSettings (ptr).mPosition[0] = value; + ptr.getClass().getMovementSettings (ptr).mPosition[0] = value; } void Player::setForwardBackward (int value) @@ -108,14 +114,13 @@ namespace MWWorld if (mAutoMove) value = 1; - MWWorld::Class::get (ptr).getMovementSettings (ptr).mPosition[1] = value; + ptr.getClass().getMovementSettings (ptr).mPosition[1] = value; } void Player::setUpDown(int value) { MWWorld::Ptr ptr = getPlayer(); - - MWWorld::Class::get (ptr).getMovementSettings (ptr).mPosition[2] = value; + ptr.getClass().getMovementSettings (ptr).mPosition[2] = value; } void Player::setRunState(bool run) @@ -127,33 +132,29 @@ namespace MWWorld void Player::setSneak(bool sneak) { MWWorld::Ptr ptr = getPlayer(); - ptr.getClass().getCreatureStats(ptr).setMovementFlag(MWMechanics::CreatureStats::Flag_Sneak, sneak); - - // TODO show sneak indicator only when the player is not detected by any actor - MWBase::Environment::get().getWindowManager()->setSneakVisibility(sneak); } void Player::yaw(float yaw) { MWWorld::Ptr ptr = getPlayer(); - MWWorld::Class::get(ptr).getMovementSettings(ptr).mRotation[2] += yaw; + ptr.getClass().getMovementSettings(ptr).mRotation[2] += yaw; } void Player::pitch(float pitch) { MWWorld::Ptr ptr = getPlayer(); - MWWorld::Class::get(ptr).getMovementSettings(ptr).mRotation[0] += pitch; + ptr.getClass().getMovementSettings(ptr).mRotation[0] += pitch; } void Player::roll(float roll) { MWWorld::Ptr ptr = getPlayer(); - MWWorld::Class::get(ptr).getMovementSettings(ptr).mRotation[1] += roll; + ptr.getClass().getMovementSettings(ptr).mRotation[1] += roll; } MWMechanics::DrawState_ Player::getDrawState() { MWWorld::Ptr ptr = getPlayer(); - return MWWorld::Class::get(ptr).getNpcStats(ptr).getDrawState(); + return ptr.getClass().getNpcStats(ptr).getDrawState(); } bool Player::wasTeleported() const @@ -166,6 +167,10 @@ namespace MWWorld mTeleported = teleported; } + bool Player::isInCombat() { + return MWBase::Environment::get().getMechanicsManager()->getActorsFighting(getPlayer()).size() != 0; + } + void Player::markPosition(CellStore *markedCell, ESM::Position markedPosition) { mMarkedCell = markedCell; @@ -189,7 +194,7 @@ namespace MWWorld mTeleported = false; } - void Player::write (ESM::ESMWriter& writer) const + void Player::write (ESM::ESMWriter& writer, Loading::Listener& progress) const { ESM::Player player; @@ -197,7 +202,7 @@ namespace MWWorld player.mCellId = mCellStore->getCell()->getCellId(); player.mCurrentCrimeId = mCurrentCrimeId; - player.mPayedCrimeId = mPayedCrimeId; + player.mPaidCrimeId = mPaidCrimeId; player.mBirthsign = mSign; @@ -219,6 +224,8 @@ namespace MWWorld writer.startRecord (ESM::REC_PLAY); player.save (writer); writer.endRecord (ESM::REC_PLAY); + + progress.increaseProgress(); } bool Player::readRecord (ESM::ESMReader& reader, int32_t type) @@ -245,7 +252,7 @@ namespace MWWorld throw std::runtime_error ("invalid player state record (birthsign)"); mCurrentCrimeId = player.mCurrentCrimeId; - mPayedCrimeId = player.mPayedCrimeId; + mPaidCrimeId = player.mPaidCrimeId; mSign = player.mBirthsign; @@ -290,11 +297,11 @@ namespace MWWorld void Player::recordCrimeId() { - mPayedCrimeId = mCurrentCrimeId; + mPaidCrimeId = mCurrentCrimeId; } int Player::getCrimeId() const { - return mPayedCrimeId; + return mPaidCrimeId; } } diff --git a/apps/openmw/mwworld/player.hpp b/apps/openmw/mwworld/player.hpp index 7dbaaddb4..d8cde5952 100644 --- a/apps/openmw/mwworld/player.hpp +++ b/apps/openmw/mwworld/player.hpp @@ -21,6 +21,11 @@ namespace MWBase class Ptr; } +namespace Loading +{ + class Listener; +} + namespace MWWorld { class CellStore; @@ -43,8 +48,8 @@ namespace MWWorld bool mTeleported; int mCurrentCrimeId; // the id assigned witnesses - int mPayedCrimeId; // the last id payed off (0 bounty) - + int mPaidCrimeId; // the last id paid off (0 bounty) + public: Player(const ESM::NPC *player, const MWBase::World& world); @@ -90,15 +95,18 @@ namespace MWWorld bool wasTeleported() const; void setTeleported(bool teleported); + ///Checks all nearby actors to see if anyone has an aipackage against you + bool isInCombat(); + void clear(); - void write (ESM::ESMWriter& writer) const; + void write (ESM::ESMWriter& writer, Loading::Listener& progress) const; bool readRecord (ESM::ESMReader& reader, int32_t type); int getNewCrimeId(); // get new id for witnesses - void recordCrimeId(); // record the payed crime id when bounty is 0 - int getCrimeId() const; // get the last payed crime id + void recordCrimeId(); // record the paid crime id when bounty is 0 + int getCrimeId() const; // get the last paid crime id }; } #endif diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp new file mode 100644 index 000000000..4e4f0b271 --- /dev/null +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -0,0 +1,405 @@ +#include "projectilemanager.hpp" + +#include +#include + +#include + +#include + +#include "../mwworld/manualref.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/inventorystore.hpp" + +#include "../mwbase/soundmanager.hpp" +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" + +#include "../mwmechanics/combat.hpp" +#include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/spellcasting.hpp" + +#include "../mwrender/effectmanager.hpp" + +#include "../mwsound/sound.hpp" + +namespace MWWorld +{ + + ProjectileManager::ProjectileManager(Ogre::SceneManager* sceneMgr, OEngine::Physic::PhysicEngine &engine) + : mPhysEngine(engine) + , mSceneMgr(sceneMgr) + { + + } + + void ProjectileManager::createModel(State &state, const std::string &model) + { + state.mObject = NifOgre::Loader::createObjects(state.mNode, model); + for(size_t i = 0;i < state.mObject->mControllers.size();i++) + { + if(state.mObject->mControllers[i].getSource().isNull()) + state.mObject->mControllers[i].setSource(Ogre::SharedPtr (new MWRender::EffectAnimationTime())); + } + } + + void ProjectileManager::update(NifOgre::ObjectScenePtr object, float duration) + { + for(size_t i = 0; i < object->mControllers.size() ;i++) + { + MWRender::EffectAnimationTime* value = dynamic_cast(object->mControllers[i].getSource().get()); + if (value) + value->addTime(duration); + + object->mControllers[i].update(); + } + } + + void ProjectileManager::launchMagicBolt(const std::string &model, const std::string &sound, + const std::string &spellId, float speed, bool stack, + const ESM::EffectList &effects, const Ptr &actor, const std::string &sourceName) + { + // Spawn at 0.75 * ActorHeight + float height = mPhysEngine.getCharacter(actor.getRefData().getHandle())->getHalfExtents().z * 2 * 0.75; + + Ogre::Vector3 pos(actor.getRefData().getPosition().pos); + pos.z += height; + + Ogre::Quaternion orient = Ogre::Quaternion(Ogre::Radian(actor.getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * + Ogre::Quaternion(Ogre::Radian(actor.getRefData().getPosition().rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X); + + MagicBoltState state; + state.mSourceName = sourceName; + state.mId = model; + state.mSpellId = spellId; + state.mActorId = actor.getClass().getCreatureStats(actor).getActorId(); + state.mSpeed = speed; + state.mStack = stack; + state.mSoundId = sound; + + // Only interested in "on target" effects + for (std::vector::const_iterator iter (effects.mList.begin()); + iter!=effects.mList.end(); ++iter) + { + if (iter->mRange == ESM::RT_Target) + state.mEffects.mList.push_back(*iter); + } + + MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), model); + MWWorld::Ptr ptr = ref.getPtr(); + + state.mNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(pos, orient); + createModel(state, ptr.getClass().getModel(ptr)); + + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + state.mSound = sndMgr->playManualSound3D(pos, sound, 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop); + + mMagicBolts.push_back(state); + } + + void ProjectileManager::launchProjectile(Ptr actor, Ptr projectile, const Ogre::Vector3 &pos, + const Ogre::Quaternion &orient, Ptr bow, float speed) + { + ProjectileState state; + state.mActorId = actor.getClass().getCreatureStats(actor).getActorId(); + state.mBowId = bow.getCellRef().getRefId(); + state.mVelocity = orient.yAxis() * speed; + state.mId = projectile.getCellRef().getRefId(); + + MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), projectile.getCellRef().getRefId()); + MWWorld::Ptr ptr = ref.getPtr(); + + state.mNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(pos, orient); + createModel(state, ptr.getClass().getModel(ptr)); + + mProjectiles.push_back(state); + } + + void ProjectileManager::update(float dt) + { + moveProjectiles(dt); + moveMagicBolts(dt); + } + + void ProjectileManager::moveMagicBolts(float duration) + { + for (std::vector::iterator it = mMagicBolts.begin(); it != mMagicBolts.end();) + { + Ogre::Quaternion orient = it->mNode->getOrientation(); + static float fTargetSpellMaxSpeed = MWBase::Environment::get().getWorld()->getStore().get() + .find("fTargetSpellMaxSpeed")->getFloat(); + float speed = fTargetSpellMaxSpeed * it->mSpeed; + + Ogre::Vector3 direction = orient.yAxis(); + direction.normalise(); + Ogre::Vector3 pos(it->mNode->getPosition()); + Ogre::Vector3 newPos = pos + direction * duration * speed; + + it->mSound->setPosition(newPos); + + it->mNode->setPosition(newPos); + + update(it->mObject, duration); + + // Check for impact + // TODO: use a proper btRigidBody / btGhostObject? + btVector3 from(pos.x, pos.y, pos.z); + btVector3 to(newPos.x, newPos.y, newPos.z); + std::vector > collisions = mPhysEngine.rayTest2(from, to); + bool hit=false; + + for (std::vector >::iterator cIt = collisions.begin(); cIt != collisions.end() && !hit; ++cIt) + { + MWWorld::Ptr obstacle = MWBase::Environment::get().getWorld()->searchPtrViaHandle(cIt->second); + + MWWorld::Ptr caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->mActorId); + + if (!obstacle.isEmpty() && obstacle == caster) + continue; + + if (caster.isEmpty()) + caster = obstacle; + + if (obstacle.isEmpty()) + { + // Terrain + } + else + { + MWMechanics::CastSpell cast(caster, obstacle); + cast.mHitPosition = pos; + cast.mId = it->mSpellId; + cast.mSourceName = it->mSourceName; + cast.mStack = it->mStack; + cast.inflict(obstacle, caster, it->mEffects, ESM::RT_Target, false, true); + } + + hit = true; + } + if (hit) + { + MWWorld::Ptr caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->mActorId); + MWBase::Environment::get().getWorld()->explodeSpell(pos, it->mEffects, caster, it->mSpellId, it->mSourceName); + + MWBase::Environment::get().getSoundManager()->stopSound(it->mSound); + + mSceneMgr->destroySceneNode(it->mNode); + + it = mMagicBolts.erase(it); + continue; + } + else + ++it; + } + } + + void ProjectileManager::moveProjectiles(float duration) + { + for (std::vector::iterator it = mProjectiles.begin(); it != mProjectiles.end();) + { + // gravity constant - must be way lower than the gravity affecting actors, since we're not + // simulating aerodynamics at all + it->mVelocity -= Ogre::Vector3(0, 0, 627.2f * 0.1f) * duration; + + Ogre::Vector3 pos(it->mNode->getPosition()); + Ogre::Vector3 newPos = pos + it->mVelocity * duration; + + Ogre::Quaternion orient = Ogre::Vector3::UNIT_Y.getRotationTo(it->mVelocity); + it->mNode->setOrientation(orient); + it->mNode->setPosition(newPos); + + update(it->mObject, duration); + + // Check for impact + // TODO: use a proper btRigidBody / btGhostObject? + btVector3 from(pos.x, pos.y, pos.z); + btVector3 to(newPos.x, newPos.y, newPos.z); + std::vector > collisions = mPhysEngine.rayTest2(from, to); + bool hit=false; + + for (std::vector >::iterator cIt = collisions.begin(); cIt != collisions.end() && !hit; ++cIt) + { + MWWorld::Ptr obstacle = MWBase::Environment::get().getWorld()->searchPtrViaHandle(cIt->second); + + MWWorld::Ptr caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->mActorId); + + // Arrow intersects with player immediately after shooting :/ + if (obstacle == caster) + continue; + + if (obstacle.isEmpty()) + { + // Terrain + } + else if (obstacle.getClass().isActor()) + { + MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), it->mId); + + // Try to get a Ptr to the bow that was used. It might no longer exist. + MWWorld::Ptr bow = projectileRef.getPtr(); + if (!caster.isEmpty()) + { + MWWorld::InventoryStore& inv = caster.getClass().getInventoryStore(caster); + MWWorld::ContainerStoreIterator invIt = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), it->mBowId)) + bow = *invIt; + } + + if (caster.isEmpty()) + caster = obstacle; + + MWMechanics::projectileHit(caster, obstacle, bow, projectileRef.getPtr(), pos + (newPos - pos) * cIt->first); + } + hit = true; + } + if (hit) + { + mSceneMgr->destroySceneNode(it->mNode); + + it = mProjectiles.erase(it); + continue; + } + else + ++it; + } + } + + void ProjectileManager::clear() + { + for (std::vector::iterator it = mProjectiles.begin(); it != mProjectiles.end(); ++it) + { + mSceneMgr->destroySceneNode(it->mNode); + } + mProjectiles.clear(); + for (std::vector::iterator it = mMagicBolts.begin(); it != mMagicBolts.end(); ++it) + { + MWBase::Environment::get().getSoundManager()->stopSound(it->mSound); + mSceneMgr->destroySceneNode(it->mNode); + } + mMagicBolts.clear(); + } + + void ProjectileManager::write(ESM::ESMWriter &writer, Loading::Listener &progress) const + { + for (std::vector::const_iterator it = mProjectiles.begin(); it != mProjectiles.end(); ++it) + { + writer.startRecord(ESM::REC_PROJ); + + ESM::ProjectileState state; + state.mId = it->mId; + state.mPosition = it->mNode->getPosition(); + state.mOrientation = it->mNode->getOrientation(); + state.mActorId = it->mActorId; + + state.mBowId = it->mBowId; + state.mVelocity = it->mVelocity; + + state.save(writer); + + writer.endRecord(ESM::REC_PROJ); + + progress.increaseProgress(); + } + + for (std::vector::const_iterator it = mMagicBolts.begin(); it != mMagicBolts.end(); ++it) + { + writer.startRecord(ESM::REC_MPRJ); + + ESM::MagicBoltState state; + state.mId = it->mId; + state.mPosition = it->mNode->getPosition(); + state.mOrientation = it->mNode->getOrientation(); + state.mActorId = it->mActorId; + + state.mSpellId = it->mSpellId; + state.mEffects = it->mEffects; + state.mSound = it->mSoundId; + state.mSourceName = it->mSourceName; + state.mSpeed = it->mSpeed; + state.mStack = it->mStack; + + state.save(writer); + + writer.endRecord(ESM::REC_MPRJ); + + progress.increaseProgress(); + } + } + + bool ProjectileManager::readRecord(ESM::ESMReader &reader, int32_t type) + { + if (type == ESM::REC_PROJ) + { + ESM::ProjectileState esm; + esm.load(reader); + + ProjectileState state; + state.mActorId = esm.mActorId; + state.mBowId = esm.mBowId; + state.mVelocity = esm.mVelocity; + state.mId = esm.mId; + + std::string model; + try + { + MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), esm.mId); + MWWorld::Ptr ptr = ref.getPtr(); + model = ptr.getClass().getModel(ptr); + } + catch(...) + { + return true; + } + + state.mNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(esm.mPosition, esm.mOrientation); + createModel(state, model); + + mProjectiles.push_back(state); + return true; + } + else if (type == ESM::REC_MPRJ) + { + ESM::MagicBoltState esm; + esm.load(reader); + + MagicBoltState state; + state.mSourceName = esm.mSourceName; + state.mId = esm.mId; + state.mSpellId = esm.mSpellId; + state.mActorId = esm.mActorId; + state.mSpeed = esm.mSpeed; + state.mStack = esm.mStack; + state.mEffects = esm.mEffects; + + std::string model; + try + { + MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), esm.mId); + MWWorld::Ptr ptr = ref.getPtr(); + model = ptr.getClass().getModel(ptr); + } + catch(...) + { + return true; + } + + state.mNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(esm.mPosition, esm.mOrientation); + createModel(state, model); + + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + state.mSound = sndMgr->playManualSound3D(esm.mPosition, esm.mSound, 1.0f, 1.0f, + MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop); + + mMagicBolts.push_back(state); + return true; + } + + return false; + } + + int ProjectileManager::countSavedGameRecords() const + { + return mMagicBolts.size() + mProjectiles.size(); + } + +} diff --git a/apps/openmw/mwworld/projectilemanager.hpp b/apps/openmw/mwworld/projectilemanager.hpp new file mode 100644 index 000000000..da965a4cf --- /dev/null +++ b/apps/openmw/mwworld/projectilemanager.hpp @@ -0,0 +1,111 @@ +#ifndef OPENMW_MWWORLD_PROJECTILEMANAGER_H +#define OPENMW_MWWORLD_PROJECTILEMANAGER_H + +#include + +#include + +#include +#include + +#include "../mwbase/soundmanager.hpp" + +#include "ptr.hpp" + +namespace OEngine +{ +namespace Physic +{ + class PhysicEngine; +} +} + +namespace Loading +{ + class Listener; +} + +namespace Ogre +{ + class SceneManager; +} + +namespace MWWorld +{ + + class ProjectileManager + { + public: + ProjectileManager (Ogre::SceneManager* sceneMgr, + OEngine::Physic::PhysicEngine& engine); + + void launchMagicBolt (const std::string& model, const std::string &sound, const std::string &spellId, + float speed, bool stack, const ESM::EffectList& effects, + const MWWorld::Ptr& actor, const std::string& sourceName); + + void launchProjectile (MWWorld::Ptr actor, MWWorld::Ptr projectile, + const Ogre::Vector3& pos, const Ogre::Quaternion& orient, MWWorld::Ptr bow, float speed); + + void update(float dt); + + /// Removes all current projectiles. Should be called when switching to a new worldspace. + void clear(); + + void write (ESM::ESMWriter& writer, Loading::Listener& progress) const; + bool readRecord (ESM::ESMReader& reader, int32_t type); + int countSavedGameRecords() const; + + private: + OEngine::Physic::PhysicEngine& mPhysEngine; + Ogre::SceneManager* mSceneMgr; + + struct State + { + NifOgre::ObjectScenePtr mObject; + Ogre::SceneNode* mNode; + + // Actor who shot this projectile + int mActorId; + + // MW-id of this projectile + std::string mId; + }; + + struct MagicBoltState : public State + { + std::string mSpellId; + + // Name of item to display as effect source in magic menu (in case we casted an enchantment) + std::string mSourceName; + + ESM::EffectList mEffects; + + float mSpeed; + + bool mStack; + + MWBase::SoundPtr mSound; + std::string mSoundId; + }; + + struct ProjectileState : public State + { + // RefID of the bow or crossbow the actor was using when this projectile was fired (may be empty) + std::string mBowId; + + Ogre::Vector3 mVelocity; + }; + + std::vector mMagicBolts; + std::vector mProjectiles; + + void moveProjectiles(float dt); + void moveMagicBolts(float dt); + + void createModel (State& state, const std::string& model); + void update (NifOgre::ObjectScenePtr object, float duration); + }; + +} + +#endif diff --git a/apps/openmw/mwworld/ptr.cpp b/apps/openmw/mwworld/ptr.cpp index 3920a3e79..1cf22744a 100644 --- a/apps/openmw/mwworld/ptr.cpp +++ b/apps/openmw/mwworld/ptr.cpp @@ -5,14 +5,7 @@ #include "containerstore.hpp" #include "class.hpp" - - -/* This shouldn't really be here. */ -MWWorld::LiveCellRefBase::LiveCellRefBase(std::string type, const ESM::CellRef &cref) - : mClass(&Class::get(type)), mRef(cref), mData(mRef) -{ -} - +#include "livecellref.hpp" const std::string& MWWorld::Ptr::getTypeName() const { @@ -29,7 +22,7 @@ MWWorld::LiveCellRefBase *MWWorld::Ptr::getBase() const return mRef; } -ESM::CellRef& MWWorld::Ptr::getCellRef() const +MWWorld::CellRef& MWWorld::Ptr::getCellRef() const { assert(mRef); @@ -59,4 +52,4 @@ MWWorld::ContainerStore *MWWorld::Ptr::getContainerStore() const MWWorld::Ptr::operator const void *() { return mRef; -} \ No newline at end of file +} diff --git a/apps/openmw/mwworld/ptr.hpp b/apps/openmw/mwworld/ptr.hpp index b83069283..2f37a1cfd 100644 --- a/apps/openmw/mwworld/ptr.hpp +++ b/apps/openmw/mwworld/ptr.hpp @@ -7,12 +7,12 @@ #include #include "cellreflist.hpp" -#include "livecellref.hpp" namespace MWWorld { class ContainerStore; class CellStore; + class LiveCellRefBase; /// \brief Pointer to a LiveCellRef @@ -60,7 +60,7 @@ namespace MWWorld MWWorld::LiveCellRefBase *getBase() const; - ESM::CellRef& getCellRef() const; + MWWorld::CellRef& getCellRef() const; RefData& getRefData() const; diff --git a/apps/openmw/mwworld/refdata.cpp b/apps/openmw/mwworld/refdata.cpp index 008782130..2e267b37c 100644 --- a/apps/openmw/mwworld/refdata.cpp +++ b/apps/openmw/mwworld/refdata.cpp @@ -22,6 +22,7 @@ namespace MWWorld mCount = refData.mCount; mPosition = refData.mPosition; mLocalRotation = refData.mLocalRotation; + mChanged = refData.mChanged; mCustomData = refData.mCustomData ? refData.mCustomData->clone() : 0; } @@ -35,7 +36,7 @@ namespace MWWorld } RefData::RefData() - : mBaseNode(0), mHasLocals (false), mEnabled (true), mCount (1), mCustomData (0) + : mBaseNode(0), mHasLocals (false), mEnabled (true), mCount (1), mCustomData (0), mChanged(false) { for (int i=0; i<3; ++i) { @@ -47,7 +48,8 @@ namespace MWWorld RefData::RefData (const ESM::CellRef& cellRef) : mBaseNode(0), mHasLocals (false), mEnabled (true), mCount (1), mPosition (cellRef.mPos), - mCustomData (0) + mCustomData (0), + mChanged(false) // Loading from ESM/ESP files -> assume unchanged { mLocalRotation.rot[0]=0; mLocalRotation.rot[1]=0; @@ -56,8 +58,9 @@ namespace MWWorld RefData::RefData (const ESM::ObjectState& objectState) : mBaseNode (0), mHasLocals (false), mEnabled (objectState.mEnabled), - mCount (objectState.mCount), mPosition (objectState.mPosition), mCustomData (0) - { + mCount (objectState.mCount), mPosition (objectState.mPosition), mCustomData (0), + mChanged(true) // Loading from a savegame -> assume changed + { for (int i=0; i<3; ++i) mLocalRotation.rot[i] = objectState.mLocalRotation[i]; } @@ -149,6 +152,7 @@ namespace MWWorld { mLocals.configure (script); mHasLocals = true; + mChanged = true; } } @@ -157,6 +161,8 @@ namespace MWWorld if(count == 0) MWBase::Environment::get().getWorld()->removeRefScript(this); + mChanged = true; + mCount = count; } @@ -172,26 +178,31 @@ namespace MWWorld void RefData::enable() { + mChanged = !mEnabled; mEnabled = true; } void RefData::disable() { + mChanged = mEnabled; mEnabled = false; } ESM::Position& RefData::getPosition() { + mChanged = true; return mPosition; } LocalRotation& RefData::getLocalRotation() { + mChanged = true; return mLocalRotation; } void RefData::setCustomData (CustomData *data) { + mChanged = true; // We do not currently track CustomData, so assume anything with a CustomData is changed delete mCustomData; mCustomData = data; } @@ -200,4 +211,9 @@ namespace MWWorld { return mCustomData; } + + bool RefData::hasChanged() const + { + return mChanged; + } } diff --git a/apps/openmw/mwworld/refdata.hpp b/apps/openmw/mwworld/refdata.hpp index 82371b056..a8ffad684 100644 --- a/apps/openmw/mwworld/refdata.hpp +++ b/apps/openmw/mwworld/refdata.hpp @@ -47,6 +47,8 @@ namespace MWWorld void cleanup(); + bool mChanged; + public: RefData(); @@ -108,6 +110,9 @@ namespace MWWorld CustomData *getCustomData(); ///< May return a 0-pointer. The ownership of the return data object is not transferred. + + bool hasChanged() const; + ///< Has this RefData changed since it was originally loaded? }; } diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 3d4413a35..32bf773bd 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -47,10 +47,10 @@ namespace { if (mRescale) { - if (ptr.getCellRef().mScale<0.5) - ptr.getCellRef().mScale = 0.5; - else if (ptr.getCellRef().mScale>2) - ptr.getCellRef().mScale = 2; + if (ptr.getCellRef().getScale()<0.5) + ptr.getCellRef().setScale(0.5); + else if (ptr.getCellRef().getScale()>2) + ptr.getCellRef().setScale(2); } if (ptr.getRefData().getCount() && ptr.getRefData().isEnabled()) @@ -65,7 +65,7 @@ namespace float az = Ogre::Radian(ptr.getRefData().getLocalRotation().rot[2]).valueDegrees(); MWBase::Environment::get().getWorld()->localRotateObject (ptr, ax, ay, az); - MWBase::Environment::get().getWorld()->scaleObject (ptr, ptr.getCellRef().mScale); + MWBase::Environment::get().getWorld()->scaleObject (ptr, ptr.getCellRef().getScale()); ptr.getClass().adjustPosition (ptr); } catch (const std::exception& e) @@ -85,7 +85,17 @@ namespace namespace MWWorld { - void Scene::update (float duration, bool paused){ + void Scene::update (float duration, bool paused) + { + if (mNeedMapUpdate) + { + // Note: exterior cell maps must be updated, even if they were visited before, because the set of surrounding cells might be different + // (and objects in a different cell can "bleed" into another cells map if they cross the border) + for (CellStoreCollection::iterator active = mActiveCells.begin(); active!=mActiveCells.end(); ++active) + mRendering.requestMap(*active); + mNeedMapUpdate = false; + } + mRendering.update (duration, paused); } @@ -155,6 +165,8 @@ namespace MWWorld } } + cell->respawn(); + // ... then references. This is important for adjustPosition to work correctly. /// \todo rescale depending on the state of a new GMST insertCell (*cell, true, loadingListener); @@ -186,7 +198,7 @@ namespace MWWorld float z = Ogre::Radian(pos.rot[2]).valueDegrees(); world->rotateObject(player, x, y, z); - MWWorld::Class::get(player).adjustPosition(player); + player.getClass().adjustPosition(player); } MWBase::MechanicsManager *mechMgr = @@ -197,8 +209,9 @@ namespace MWWorld mRendering.updateTerrain(); - for (CellStoreCollection::iterator active = mActiveCells.begin(); active!=mActiveCells.end(); ++active) - mRendering.requestMap(*active); + // Delay the map update until scripts have been given a chance to run. + // If we don't do this, objects that should be disabled will still appear on the map. + mNeedMapUpdate = true; MWBase::Environment::get().getWindowManager()->changeCell(mCurrentCell); } @@ -333,8 +346,6 @@ namespace MWWorld // Sky system MWBase::Environment::get().getWorld()->adjustSky(); - mRendering.switchToExterior(); - mCellChanged = true; loadingListener->removeWallpaper(); @@ -342,7 +353,7 @@ namespace MWWorld //We need the ogre renderer and a scene node. Scene::Scene (MWRender::RenderingManager& rendering, PhysicsSystem *physics) - : mCurrentCell (0), mCellChanged (false), mPhysics(physics), mRendering(rendering) + : mCurrentCell (0), mCellChanged (false), mPhysics(physics), mRendering(rendering), mNeedMapUpdate(false) { } @@ -388,7 +399,7 @@ namespace MWWorld float z = Ogre::Radian(position.rot[2]).valueDegrees(); world->rotateObject(world->getPlayerPtr(), x, y, z); - MWWorld::Class::get(world->getPlayerPtr()).adjustPosition(world->getPlayerPtr()); + world->getPlayerPtr().getClass().adjustPosition(world->getPlayerPtr()); world->getFader()->fadeIn(0.5f); return; } @@ -428,7 +439,6 @@ namespace MWWorld mCurrentCell = cell; // adjust fog - mRendering.switchToInterior(); mRendering.configureFog(*mCurrentCell); // adjust player @@ -472,9 +482,9 @@ namespace MWWorld void Scene::addObjectToScene (const Ptr& ptr) { mRendering.addObject(ptr); - MWWorld::Class::get(ptr).insertObject(ptr, *mPhysics); + ptr.getClass().insertObject(ptr, *mPhysics); MWBase::Environment::get().getWorld()->rotateObject(ptr, 0, 0, 0, true); - MWBase::Environment::get().getWorld()->scaleObject(ptr, ptr.getCellRef().mScale); + MWBase::Environment::get().getWorld()->scaleObject(ptr, ptr.getCellRef().getScale()); } void Scene::removeObjectFromScene (const Ptr& ptr) @@ -496,4 +506,24 @@ namespace MWWorld } return false; } + + Ptr Scene::searchPtrViaHandle (const std::string& handle) + { + for (CellStoreCollection::const_iterator iter (mActiveCells.begin()); + iter!=mActiveCells.end(); ++iter) + if (Ptr ptr = (*iter)->searchViaHandle (handle)) + return ptr; + + return Ptr(); + } + + Ptr Scene::searchPtrViaActorId (int actorId) + { + for (CellStoreCollection::const_iterator iter (mActiveCells.begin()); + iter!=mActiveCells.end(); ++iter) + if (Ptr ptr = (*iter)->searchViaActorId (actorId)) + return ptr; + + return Ptr(); + } } diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index 16d4877a9..449644754 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -58,6 +58,8 @@ namespace MWWorld PhysicsSystem *mPhysics; MWRender::RenderingManager& mRendering; + bool mNeedMapUpdate; + void playerCellChange (CellStore *cell, const ESM::Position& position, bool adjustPlayerPos = true); @@ -102,6 +104,10 @@ namespace MWWorld ///< Remove an object from the scene, but not from the world model. bool isCellActive(const CellStore &cell); + + Ptr searchPtrViaHandle (const std::string& handle); + + Ptr searchPtrViaActorId (int actorId); }; } diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 0fc2d547c..1dfb2f976 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -8,6 +8,8 @@ #include +#include + #include "recordcmp.hpp" namespace MWWorld @@ -313,7 +315,7 @@ namespace MWWorld return erase(item.mId); } - void write (ESM::ESMWriter& writer) const + void write (ESM::ESMWriter& writer, Loading::Listener& progress) const { for (typename Dynamic::const_iterator iter (mDynamic.begin()); iter!=mDynamic.end(); ++iter) @@ -322,6 +324,7 @@ namespace MWWorld writer.writeHNString ("NAME", iter->second.mId); iter->second.save (writer); writer.endRecord (T::sRecordId); + progress.increaseProgress(); } } @@ -682,12 +685,14 @@ namespace MWWorld typedef std::map::iterator IntIterator; //std::sort(mInt.begin(), mInt.end(), RecordCmp()); + mSharedInt.clear(); mSharedInt.reserve(mInt.size()); for (IntIterator it = mInt.begin(); it != mInt.end(); ++it) { mSharedInt.push_back(&(it->second)); } //std::sort(mExt.begin(), mExt.end(), ExtCmp()); + mSharedExt.clear(); mSharedExt.reserve(mExt.size()); for (ExtIterator it = mExt.begin(); it != mExt.end(); ++it) { mSharedExt.push_back(&(it->second)); diff --git a/apps/openmw/mwworld/timestamp.cpp b/apps/openmw/mwworld/timestamp.cpp index 126d5490c..a73ed7ca5 100644 --- a/apps/openmw/mwworld/timestamp.cpp +++ b/apps/openmw/mwworld/timestamp.cpp @@ -1,10 +1,10 @@ - #include "timestamp.hpp" #include - #include +#include + namespace MWWorld { TimeStamp::TimeStamp (float hour, int day) @@ -105,4 +105,18 @@ namespace MWWorld return hours + 24*days; } + + ESM::TimeStamp TimeStamp::toEsm() const + { + ESM::TimeStamp ret; + ret.mDay = mDay; + ret.mHour = mHour; + return ret; + } + + TimeStamp::TimeStamp(const ESM::TimeStamp &esm) + { + mDay = esm.mDay; + mHour = esm.mHour; + } } diff --git a/apps/openmw/mwworld/timestamp.hpp b/apps/openmw/mwworld/timestamp.hpp index e2d8b242a..54cd40baf 100644 --- a/apps/openmw/mwworld/timestamp.hpp +++ b/apps/openmw/mwworld/timestamp.hpp @@ -1,6 +1,11 @@ #ifndef GAME_MWWORLD_TIMESTAMP_H #define GAME_MWWORLD_TIMESTAMP_H +namespace ESM +{ + class TimeStamp; +} + namespace MWWorld { /// \brief In-game time stamp @@ -14,9 +19,12 @@ namespace MWWorld public: explicit TimeStamp (float hour = 0, int day = 0); - ///< \oaram hour [0, 23) + ///< \param hour [0, 23) /// \param day >=0 + explicit TimeStamp (const ESM::TimeStamp& esm); + ESM::TimeStamp toEsm () const; + float getHour() const; int getDay() const; diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 335702c66..25f523bee 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -329,7 +329,6 @@ void WeatherManager::update(float duration) const bool exterior = (world->isCellExterior() || world->isCellQuasiExterior()); if (!exterior) { - mRendering->sunDisable(false); mRendering->skyDisable(); mRendering->getSkyManager()->setLightningStrength(0.f); stopSounds(true); @@ -686,7 +685,7 @@ bool WeatherManager::isDark() const return exterior && (mHour < mSunriseTime || mHour > mNightStart - 1); } -void WeatherManager::write(ESM::ESMWriter& writer) +void WeatherManager::write(ESM::ESMWriter& writer, Loading::Listener& progress) { ESM::WeatherState state; state.mHour = mHour; @@ -701,6 +700,7 @@ void WeatherManager::write(ESM::ESMWriter& writer) writer.startRecord(ESM::REC_WTHR); state.save(writer); writer.endRecord(ESM::REC_WTHR); + progress.increaseProgress(); } bool WeatherManager::readRecord(ESM::ESMReader& reader, int32_t type) diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index cad3a4492..3e9df504b 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -18,6 +18,11 @@ namespace MWRender class RenderingManager; } +namespace Loading +{ + class Listener; +} + namespace MWWorld { class Fallback; @@ -158,7 +163,7 @@ namespace MWWorld /// @see World::isDark bool isDark() const; - void write(ESM::ESMWriter& writer); + void write(ESM::ESMWriter& writer, Loading::Listener& progress); bool readRecord(ESM::ESMReader& reader, int32_t type); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index e3c632512..548a871ce 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -6,7 +6,8 @@ #else #include #endif - +#include "../mwbase/scriptmanager.hpp" +#include "../mwscript/globalscripts.hpp" #include #include @@ -30,6 +31,7 @@ #include "../mwmechanics/spellcasting.hpp" #include "../mwmechanics/levelledlist.hpp" #include "../mwmechanics/combat.hpp" +#include "../mwmechanics/aiavoiddoor.hpp" //Used to tell actors to avoid doors #include "../mwrender/sky.hpp" #include "../mwrender/animation.hpp" @@ -42,6 +44,7 @@ #include "containerstore.hpp" #include "inventorystore.hpp" #include "actionteleport.hpp" +#include "projectilemanager.hpp" #include "contentloader.hpp" #include "esmloader.hpp" @@ -123,18 +126,20 @@ namespace MWWorld const std::vector& contentFiles, const boost::filesystem::path& resDir, const boost::filesystem::path& cacheDir, ToUTF8::Utf8Encoder* encoder, const std::map& fallbackMap, - int activationDistanceOverride, const std::string& startCell) + int activationDistanceOverride, const std::string& startCell, const std::string& startupScript) : mPlayer (0), mLocalScripts (mStore), mSky (true), mCells (mStore, mEsm), mActivationDistanceOverride (activationDistanceOverride), - mFallback(fallbackMap), mPlayIntro(0), mTeleportEnabled(true), mLevitationEnabled(true), + mFallback(fallbackMap), mTeleportEnabled(true), mLevitationEnabled(true), mFacedDistance(FLT_MAX), mGodMode(false), mContentFiles (contentFiles), mGoToJail(false), - mStartCell (startCell) + mStartCell (startCell), mStartupScript(startupScript) { mPhysics = new PhysicsSystem(renderer); mPhysEngine = mPhysics->getEngine(); + mProjectileManager.reset(new ProjectileManager(renderer.getScene(), *mPhysEngine)); + mRendering = new MWRender::RenderingManager(renderer, resDir, cacheDir, mPhysEngine,&mFallback); mPhysEngine->setSceneManager(renderer.getScene()); @@ -184,6 +189,15 @@ namespace MWWorld MWBase::Environment::get().getWindowManager()->updatePlayer(); + if (!bypass) + { + // set new game mark + mGlobalVariables["chargenstate"].setInteger (1); + mGlobalVariables["pcrace"].setInteger (3); + + MWBase::Environment::get().getScriptManager()->getGlobalScripts().addStartup(); + } + if (bypass && !mStartCell.empty()) { ESM::Position pos; @@ -200,43 +214,44 @@ namespace MWWorld } else { - /// \todo if !bypass, do not add player location to global map for the duration of one - /// frame - ESM::Position pos; - const int cellSize = 8192; - pos.pos[0] = cellSize/2; - pos.pos[1] = cellSize/2; - pos.pos[2] = 0; - pos.rot[0] = 0; - pos.rot[1] = 0; - pos.rot[2] = 0; - mWorldScene->changeToExteriorCell(pos); + for (int i=0; i<5; ++i) + MWBase::Environment::get().getScriptManager()->getGlobalScripts().run(); + if (!getPlayerPtr().isInCell()) + { + ESM::Position pos; + const int cellSize = 8192; + pos.pos[0] = cellSize/2; + pos.pos[1] = cellSize/2; + pos.pos[2] = 0; + pos.rot[0] = 0; + pos.rot[1] = 0; + pos.rot[2] = 0; + mWorldScene->changeToExteriorCell(pos); + } } if (!bypass) - { - // FIXME: should be set to 1, but the sound manager won't pause newly started sounds - mPlayIntro = 2; + MWBase::Environment::get().getWindowManager()->playVideo(mFallback.getFallbackString("Movies_New_Game"), true); - // set new game mark - mGlobalVariables["chargenstate"].setInteger (1); - mGlobalVariables["pcrace"].setInteger (3); - } + // enable collision + if (!mPhysics->toggleCollisionMode()) + mPhysics->toggleCollisionMode(); // we don't want old weather to persist on a new game delete mWeatherManager; mWeatherManager = 0; mWeatherManager = new MWWorld::WeatherManager(mRendering,&mFallback); + + MWBase::Environment::get().getWindowManager()->executeInConsole(mStartupScript); } void World::clear() { - mLocalScripts.clear(); - mPlayer->clear(); + mRendering->clear(); - // enable collision - if (!mPhysics->toggleCollisionMode()) - mPhysics->toggleCollisionMode(); + mProjectileManager->clear(); + + mLocalScripts.clear(); mWorldScene->changeToVoid(); @@ -245,21 +260,19 @@ namespace MWWorld if (mPlayer) { - mPlayer->setCell (0); + mPlayer->clear(); + mPlayer->setCell(0); mPlayer->getPlayer().getRefData() = RefData(); - mPlayer->set (mStore.get().find ("player")); + mPlayer->set(mStore.get().find ("player")); } mCells.clear(); - mMagicBolts.clear(); - mProjectiles.clear(); mDoorStates.clear(); mGodMode = false; mSky = true; mTeleportEnabled = true; - mPlayIntro = 0; mFacedDistance = FLT_MAX; mGlobalVariables.fill (mStore); @@ -268,29 +281,51 @@ namespace MWWorld int World::countSavedGameRecords() const { return - mStore.countSavedGameRecords() + mCells.countSavedGameRecords() + +mStore.countSavedGameRecords() +mGlobalVariables.countSavedGameRecords() + +mProjectileManager->countSavedGameRecords() +1 // player record - +mCells.countSavedGameRecords(); + +1 // weather record + +1; // actorId counter } - void World::write (ESM::ESMWriter& writer) const + void World::write (ESM::ESMWriter& writer, Loading::Listener& progress) const { - mStore.write (writer); - mGlobalVariables.write (writer); - mCells.write (writer); - mPlayer->write (writer); - mWeatherManager->write (writer); + // Active cells could have a dirty fog of war, sync it to the CellStore first + for (Scene::CellStoreCollection::const_iterator iter (mWorldScene->getActiveCells().begin()); + iter!=mWorldScene->getActiveCells().end(); ++iter) + { + CellStore* cellstore = *iter; + mRendering->writeFog(cellstore); + } + + MWMechanics::CreatureStats::writeActorIdCounter(writer); + progress.increaseProgress(); + + mCells.write (writer, progress); + mStore.write (writer, progress); + mGlobalVariables.write (writer, progress); + mPlayer->write (writer, progress); + mWeatherManager->write (writer, progress); + mProjectileManager->write (writer, progress); } void World::readRecord (ESM::ESMReader& reader, int32_t type, const std::map& contentFileMap) { + if (type == ESM::REC_ACTC) + { + MWMechanics::CreatureStats::readActorIdCounter(reader); + return; + } + if (!mStore.readRecord (reader, type) && !mGlobalVariables.readRecord (reader, type) && !mPlayer->readRecord (reader, type) && !mWeatherManager->readRecord (reader, type) && - !mCells.readRecord (reader, type, contentFileMap)) + !mCells.readRecord (reader, type, contentFileMap) && + !mProjectileManager->readRecord (reader, type)) { throw std::runtime_error ("unknown record in saved game"); } @@ -502,8 +537,8 @@ namespace MWWorld return ptr; } - Ptr ptr = Class::get (mPlayer->getPlayer()). - getContainerStore (mPlayer->getPlayer()).search (lowerCaseName); + Ptr ptr = mPlayer->getPlayer().getClass() + .getContainerStore(mPlayer->getPlayer()).search(lowerCaseName); if (!ptr.isEmpty()) return ptr; @@ -535,17 +570,17 @@ namespace MWWorld { if (mPlayer->getPlayer().getRefData().getHandle()==handle) return mPlayer->getPlayer(); - for (Scene::CellStoreCollection::const_iterator iter (mWorldScene->getActiveCells().begin()); - iter!=mWorldScene->getActiveCells().end(); ++iter) - { - CellStore* cellstore = *iter; - Ptr ptr = cellstore->searchViaHandle (handle); - if (!ptr.isEmpty()) - return ptr; - } + return mWorldScene->searchPtrViaHandle (handle); + } - return MWWorld::Ptr(); + Ptr World::searchPtrViaActorId (int actorId) + { + // The player is not registered in any CellStore so must be checked manually + if (actorId == getPlayerPtr().getClass().getCreatureStats(getPlayerPtr()).getActorId()) + return getPlayerPtr(); + // Now search cells + return mWorldScene->searchPtrViaActorId (actorId); } void World::addContainerScripts(const Ptr& reference, CellStore * cell) @@ -554,10 +589,10 @@ namespace MWWorld reference.getTypeName()==typeid (ESM::NPC).name() || reference.getTypeName()==typeid (ESM::Creature).name()) { - MWWorld::ContainerStore& container = MWWorld::Class::get(reference).getContainerStore(reference); + MWWorld::ContainerStore& container = reference.getClass().getContainerStore(reference); for(MWWorld::ContainerStoreIterator it = container.begin(); it != container.end(); ++it) { - std::string script = MWWorld::Class::get(*it).getScript(*it); + std::string script = it->getClass().getScript(*it); if(script != "") { MWWorld::Ptr item = *it; @@ -589,10 +624,10 @@ namespace MWWorld reference.getTypeName()==typeid (ESM::NPC).name() || reference.getTypeName()==typeid (ESM::Creature).name()) { - MWWorld::ContainerStore& container = MWWorld::Class::get(reference).getContainerStore(reference); + MWWorld::ContainerStore& container = reference.getClass().getContainerStore(reference); for(MWWorld::ContainerStoreIterator it = container.begin(); it != container.end(); ++it) { - std::string script = MWWorld::Class::get(*it).getScript(*it); + std::string script = it->getClass().getScript(*it); if(script != "") { MWWorld::Ptr item = *it; @@ -785,6 +820,14 @@ namespace MWWorld void World::changeToInteriorCell (const std::string& cellName, const ESM::Position& position) { + if (mCurrentWorldSpace != cellName) + { + // changed worldspace + mProjectileManager->clear(); + mRendering->notifyWorldSpaceChanged(); + mCurrentWorldSpace = cellName; + } + removeContainerScripts(getPlayerPtr()); mWorldScene->changeToInteriorCell(cellName, position); addContainerScripts(getPlayerPtr(), getPlayerPtr().getCell()); @@ -792,13 +835,22 @@ namespace MWWorld void World::changeToExteriorCell (const ESM::Position& position) { + if (mCurrentWorldSpace != "sys::default") // FIXME + { + // changed worldspace + mProjectileManager->clear(); + mRendering->notifyWorldSpaceChanged(); + } removeContainerScripts(getPlayerPtr()); mWorldScene->changeToExteriorCell(position); addContainerScripts(getPlayerPtr(), getPlayerPtr().getCell()); } - void World::changeToCell (const ESM::CellId& cellId, const ESM::Position& position) + void World::changeToCell (const ESM::CellId& cellId, const ESM::Position& position, bool detectWorldSpaceChange) { + if (!detectWorldSpaceChange) + mCurrentWorldSpace = cellId.mWorldspace; + if (cellId.mPaged) changeToExteriorCell (position); else @@ -893,7 +945,7 @@ namespace MWWorld Ogre::Vector3 vec(x, y, z); - CellStore *currCell = ptr.getCell(); + CellStore *currCell = ptr.isInCell() ? ptr.getCell() : NULL; bool isPlayer = ptr == mPlayer->getPlayer(); bool haveToMove = isPlayer || mWorldScene->isCellActive(*currCell); @@ -924,16 +976,14 @@ namespace MWWorld removeContainerScripts (ptr); haveToMove = false; - MWWorld::Ptr newPtr = MWWorld::Class::get(ptr) + MWWorld::Ptr newPtr = ptr.getClass() .copyToCell(ptr, *newCell); newPtr.getRefData().setBaseNode(0); - - objectLeftActiveCell(ptr, newPtr); } else { MWWorld::Ptr copy = - MWWorld::Class::get(ptr).copyToCell(ptr, *newCell, pos); + ptr.getClass().copyToCell(ptr, *newCell, pos); mRendering->updateObjectCell(ptr, copy); MWBase::Environment::get().getSoundManager()->updatePtr (ptr, copy); @@ -942,7 +992,7 @@ namespace MWWorld mechMgr->updateCell(ptr, copy); std::string script = - MWWorld::Class::get(ptr).getScript(ptr); + ptr.getClass().getScript(ptr); if (!script.empty()) { mLocalScripts.remove(ptr); @@ -984,8 +1034,8 @@ namespace MWWorld void World::scaleObject (const Ptr& ptr, float scale) { - ptr.getCellRef().mScale = scale; - MWWorld::Class::get(ptr).adjustScale(ptr,scale); + ptr.getCellRef().setScale(scale); + ptr.getClass().adjustScale(ptr,scale); if(ptr.getRefData().getBaseNode() == 0) return; @@ -1012,7 +1062,7 @@ namespace MWWorld objRot[2] = rot.z; } - if(Class::get(ptr).isActor()) + if(ptr.getClass().isActor()) { /* HACK? Actors shouldn't really be rotating around X (or Y), but * currently it's done so for rotating the camera, which needs @@ -1066,13 +1116,15 @@ namespace MWWorld while(ptr.getRefData().getLocalRotation().rot[2]<=-fullRotateRad) ptr.getRefData().getLocalRotation().rot[2]+=fullRotateRad; - Ogre::Quaternion worldRotQuat(Ogre::Quaternion(Ogre::Radian(ptr.getRefData().getPosition().rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X)* - Ogre::Quaternion(Ogre::Radian(ptr.getRefData().getPosition().rot[1]), Ogre::Vector3::NEGATIVE_UNIT_Y)* - Ogre::Quaternion(Ogre::Radian(ptr.getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z)); + Ogre::Quaternion worldRotQuat(Ogre::Radian(ptr.getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z); + if (!ptr.getClass().isActor()) + worldRotQuat = Ogre::Quaternion(Ogre::Radian(ptr.getRefData().getPosition().rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X)* + Ogre::Quaternion(Ogre::Radian(ptr.getRefData().getPosition().rot[1]), Ogre::Vector3::NEGATIVE_UNIT_Y)* worldRotQuat; - Ogre::Quaternion rot(Ogre::Quaternion(Ogre::Degree(x), Ogre::Vector3::NEGATIVE_UNIT_X)* - Ogre::Quaternion(Ogre::Degree(y), Ogre::Vector3::NEGATIVE_UNIT_Y)* - Ogre::Quaternion(Ogre::Degree(z), Ogre::Vector3::NEGATIVE_UNIT_Z)); + Ogre::Quaternion rot(Ogre::Degree(z), Ogre::Vector3::NEGATIVE_UNIT_Z); + if (!ptr.getClass().isActor()) + rot = Ogre::Quaternion(Ogre::Degree(x), Ogre::Vector3::NEGATIVE_UNIT_X)* + Ogre::Quaternion(Ogre::Degree(y), Ogre::Vector3::NEGATIVE_UNIT_Y)*rot; ptr.getRefData().getBaseNode()->setOrientation(worldRotQuat*rot); mPhysics->rotateObject(ptr); @@ -1150,8 +1202,7 @@ namespace MWWorld { processDoors(duration); - moveMagicBolts(duration); - moveProjectiles(duration); + mProjectileManager->update(duration); const PtrVelocityList &results = mPhysics->applyQueuedMovement(duration); PtrVelocityList::const_iterator player(results.end()); @@ -1184,29 +1235,48 @@ namespace MWWorld while (it != mDoorStates.end()) { if (!mWorldScene->isCellActive(*it->first.getCell()) || !it->first.getRefData().getBaseNode()) + { + // The door is no longer in an active cell, or it was disabled. + // Erase from mDoorStates, since we no longer need to move it. + // Once we load the door's cell again (or re-enable the door), Door::insertObject will reinsert to mDoorStates. mDoorStates.erase(it++); + } else { float oldRot = Ogre::Radian(it->first.getRefData().getLocalRotation().rot[2]).valueDegrees(); float diff = duration * 90; - float targetRot = std::min(std::max(0.f, oldRot + diff * (it->second ? 1 : -1)), 90.f); + float targetRot = std::min(std::max(0.f, oldRot + diff * (it->second == 1 ? 1 : -1)), 90.f); localRotateObject(it->first, 0, 0, targetRot); + bool reached = (targetRot == 90.f && it->second) || targetRot == 0.f; + /// \todo should use convexSweepTest here std::vector collisions = mPhysics->getCollisions(it->first); for (std::vector::iterator cit = collisions.begin(); cit != collisions.end(); ++cit) { MWWorld::Ptr ptr = getPtrViaHandle(*cit); - if (MWWorld::Class::get(ptr).isActor()) + if (ptr.getClass().isActor()) { - // we collided with an actor, we need to undo the rotation + // Collided with actor, ask actor to try to avoid door + if(ptr != MWBase::Environment::get().getWorld()->getPlayerPtr() ) { + MWMechanics::AiSequence& seq = ptr.getClass().getCreatureStats(ptr).getAiSequence(); + if(seq.getTypeId() != MWMechanics::AiPackage::TypeIdAvoidDoor) //Only add it once + seq.stack(MWMechanics::AiAvoidDoor(it->first),ptr); + } + + // we need to undo the rotation localRotateObject(it->first, 0, 0, oldRot); - break; + reached = false; + //break; //Removed in case multiple actors are touching } } - if ((targetRot == 90.f && it->second) || targetRot == 0.f) + if (reached) + { + // Mark as non-moving + it->first.getClass().setDoorState(it->first, 0); mDoorStates.erase(it++); + } else ++it; } @@ -1302,23 +1372,16 @@ namespace MWWorld void World::update (float duration, bool paused) { - if (mPlayIntro) - { - --mPlayIntro; - if (mPlayIntro == 0) - MWBase::Environment::get().getWindowManager()->playVideo(mFallback.getFallbackString("Movies_New_Game"), true); - } - if (mGoToJail && !paused) goToJail(); updateWeather(duration); - mWorldScene->update (duration, paused); - if (!paused) doPhysics (duration); + mWorldScene->update (duration, paused); + performUpdateSceneQueries (); updateWindowManager (); @@ -1480,7 +1543,10 @@ namespace MWWorld { MWWorld::LiveCellRef& ref = *it; - if (ref.mRef.mTeleport) + if (!ref.mData.isEnabled()) + continue; + + if (ref.mRef.getTeleport()) { World::DoorMarker newMarker; newMarker.name = MWClass::Door::getDestination(ref); @@ -1509,26 +1575,26 @@ namespace MWWorld mRendering->setWaterHeight(height); } - void World::toggleWater() + bool World::toggleWater() { - mRendering->toggleWater(); + return mRendering->toggleWater(); } void World::PCDropped (const Ptr& item) { - std::string script = MWWorld::Class::get(item).getScript(item); + std::string script = item.getClass().getScript(item); // Set OnPCDrop Variable on item's script, if it has a script with that variable declared if(script != "") item.getRefData().getLocals().setVarByInt(script, "onpcdrop", 1); } - bool World::placeObject (const MWWorld::Ptr& object, float cursorX, float cursorY, int amount) + MWWorld::Ptr World::placeObject (const MWWorld::Ptr& object, float cursorX, float cursorY, int amount) { std::pair result = mPhysics->castRay(cursorX, cursorY); if (!result.first) - return false; + return MWWorld::Ptr(); CellStore* cell = getPlayerPtr().getCell(); @@ -1549,30 +1615,44 @@ namespace MWWorld // only the player place items in the world, so no need to check actor PCDropped(dropped); - return true; + return dropped; } bool World::canPlaceObject(float cursorX, float cursorY) { - std::pair result = mPhysics->castRay(cursorX, cursorY); - - /// \todo also check if the wanted position is on a flat surface, and not e.g. against a vertical wall! + Ogre::Vector3 normal(0,0,0); + std::pair result = mPhysics->castRay(cursorX, cursorY, &normal); - if (!result.first) + if (result.first) + { + // check if the wanted position is on a flat surface, and not e.g. against a vertical wall + return (normal.angleBetween(Ogre::Vector3(0.f,0.f,1.f)).valueDegrees() < 30); + } + else return false; - return true; } Ptr World::copyObjectToCell(const Ptr &object, CellStore* cell, ESM::Position pos, bool adjustPos) { - if (object.getClass().isActor() || adjustPos) + if (!object.getClass().isActor() && adjustPos) { + // Adjust position so the location we wanted ends up in the middle of the object bounding box Ogre::Vector3 min, max; if (mPhysics->getObjectAABB(object, min, max)) { - pos.pos[0] -= (min.x + max.x) / 2; - pos.pos[1] -= (min.y + max.y) / 2; - pos.pos[2] -= min.z; + Ogre::Quaternion xr(Ogre::Radian(-pos.rot[0]), Ogre::Vector3::UNIT_X); + Ogre::Quaternion yr(Ogre::Radian(-pos.rot[1]), Ogre::Vector3::UNIT_Y); + Ogre::Quaternion zr(Ogre::Radian(-pos.rot[2]), Ogre::Vector3::UNIT_Z); + + Ogre::Vector3 adjust ( + (min.x + max.x) / 2, + (min.y + max.y) / 2, + min.z + ); + adjust = (xr*yr*zr) * adjust; + pos.pos[0] -= adjust.x; + pos.pos[1] -= adjust.y; + pos.pos[2] -= adjust.z; } } @@ -1584,13 +1664,13 @@ namespace MWWorld } MWWorld::Ptr dropped = - MWWorld::Class::get(object).copyToCell(object, *cell, pos); + object.getClass().copyToCell(object, *cell, pos); if (mWorldScene->isCellActive(*cell)) { if (dropped.getRefData().isEnabled()) { mWorldScene->addObjectToScene(dropped); } - std::string script = MWWorld::Class::get(dropped).getScript(dropped); + std::string script = dropped.getClass().getScript(dropped); if (!script.empty()) { mLocalScripts.add(script, dropped); } @@ -1600,7 +1680,7 @@ namespace MWWorld return dropped; } - void World::dropObjectOnGround (const Ptr& actor, const Ptr& object, int amount) + MWWorld::Ptr World::dropObjectOnGround (const Ptr& actor, const Ptr& object, int amount) { MWWorld::CellStore* cell = actor.getCell(); @@ -1629,6 +1709,7 @@ namespace MWWorld if(actor == mPlayer->getPlayer()) // Only call if dropped by player PCDropped(dropped); + return dropped; } void World::processChangedSettings(const Settings::CategorySettingVector& settings) @@ -1798,7 +1879,7 @@ namespace MWWorld if((!physactor->getOnGround()&&physactor->getCollisionMode()) || isUnderwater(currentCell, playerPos)) return 2; if((currentCell->getCell()->mData.mFlags&ESM::Cell::NoSleep) || - Class::get(player).getNpcStats(player).isWerewolf()) + player.getClass().getNpcStats(player).isWerewolf()) return 1; return 0; @@ -1821,25 +1902,32 @@ namespace MWWorld void World::activateDoor(const MWWorld::Ptr& door) { - if (mDoorStates.find(door) != mDoorStates.end()) - { - // if currently opening, then close, if closing, then open - mDoorStates[door] = !mDoorStates[door]; - } - else + int state = door.getClass().getDoorState(door); + switch (state) { + case 0: if (door.getRefData().getLocalRotation().rot[2] == 0) - mDoorStates[door] = 1; // open + state = 1; // if closed, then open else - mDoorStates[door] = 0; // close + state = 2; // if open, then close + break; + case 2: + state = 1; // if closing, then open + break; + case 1: + default: + state = 2; // if opening, then close + break; } + door.getClass().setDoorState(door, state); + mDoorStates[door] = state; } - bool World::getOpenOrCloseDoor(const Ptr &door) + void World::activateDoor(const Ptr &door, bool open) { - if (mDoorStates.find(door) != mDoorStates.end()) - return !mDoorStates[door]; // if currently opening or closing, then do the opposite - return door.getRefData().getLocalRotation().rot[2] == 0; + int state = open ? 1 : 2; + door.getClass().setDoorState(door, state); + mDoorStates[door] = state; } bool World::getPlayerStandingOn (const MWWorld::Ptr& object) @@ -1876,7 +1964,7 @@ namespace MWWorld for (CellRefList::List::iterator container = refList.begin(); container != refList.end(); ++container) { MWWorld::Ptr ptr (&*container, *cellIt); - if (Misc::StringUtils::ciEqual(ptr.getCellRef().mOwner, npc.getCellRef().mRefID)) + if (Misc::StringUtils::ciEqual(ptr.getCellRef().getOwner(), npc.getCellRef().getRefId())) out.push_back(ptr); } } @@ -1904,7 +1992,7 @@ namespace MWWorld (*cellIt)->forEach(functor); for (std::vector::iterator it = functor.mHandles.begin(); it != functor.mHandles.end(); ++it) - if (Misc::StringUtils::ciEqual(searchPtrViaHandle(*it).getCellRef().mOwner, npc.getCellRef().mRefID)) + if (Misc::StringUtils::ciEqual(searchPtrViaHandle(*it).getCellRef().getOwner(), npc.getCellRef().getRefId())) out.push_back(searchPtrViaHandle(*it)); } } @@ -1926,11 +2014,24 @@ namespace MWWorld return false; } + float World::getDistToNearestRayHit(const Ogre::Vector3& from, const Ogre::Vector3& dir, float maxDist) + { + btVector3 btFrom(from.x, from.y, from.z); + btVector3 btTo = btVector3(dir.x, dir.y, dir.z); + btTo.normalize(); + btTo = btFrom + btTo * maxDist; + + std::pair result = mPhysEngine->rayTest(btFrom, btTo, false); + + if(result.second == -1) return maxDist; + else return result.second*(btTo-btFrom).length(); + } + void World::enableActorCollision(const MWWorld::Ptr& actor, bool enable) { OEngine::Physic::PhysicActor *physicActor = mPhysEngine->getCharacter(actor.getRefData().getHandle()); - physicActor->enableCollisions(enable); + physicActor->enableCollisionBody(enable); } bool World::findInteriorPosition(const std::string &name, ESM::Position &pos) @@ -1947,34 +2048,34 @@ namespace MWWorld } const DoorList &doors = cellStore->get().mList; for (DoorList::const_iterator it = doors.begin(); it != doors.end(); ++it) { - if (!it->mRef.mTeleport) { + if (!it->mRef.getTeleport()) { continue; } MWWorld::CellStore *source = 0; // door to exterior - if (it->mRef.mDestCell.empty()) { + if (it->mRef.getDestCell().empty()) { int x, y; - const float *pos = it->mRef.mDoorDest.pos; + const float *pos = it->mRef.getDoorDest().pos; positionToIndex(pos[0], pos[1], x, y); source = getExterior(x, y); } // door to interior else { - source = getInterior(it->mRef.mDestCell); + source = getInterior(it->mRef.getDestCell()); } if (0 != source) { // Find door leading to our current teleport door // and use it destination to position inside cell. const DoorList &doors = source->get().mList; for (DoorList::const_iterator jt = doors.begin(); jt != doors.end(); ++jt) { - if (it->mRef.mTeleport && - Misc::StringUtils::ciEqual(name, jt->mRef.mDestCell)) + if (it->mRef.getTeleport() && + Misc::StringUtils::ciEqual(name, jt->mRef.getDestCell())) { /// \note Using _any_ door pointed to the interior, /// not the one pointed to current door. - pos = jt->mRef.mDoorDest; + pos = jt->mRef.getDoorDest(); return true; } } @@ -2022,7 +2123,7 @@ namespace MWWorld void World::setWerewolf(const MWWorld::Ptr& actor, bool werewolf) { - MWMechanics::NpcStats& npcStats = Class::get(actor).getNpcStats(actor); + MWMechanics::NpcStats& npcStats = actor.getClass().getNpcStats(actor); // The actor does not have to change state if (npcStats.isWerewolf() == werewolf) @@ -2034,7 +2135,7 @@ namespace MWWorld // 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); + MWWorld::InventoryStore& invStore = actor.getClass().getInventoryStore(actor); invStore.unequipAll(actor); if(werewolf) @@ -2056,7 +2157,6 @@ namespace MWWorld { // Update the GUI only when called on the player MWBase::WindowManager* windowManager = MWBase::Environment::get().getWindowManager(); - windowManager->unsetSelectedWeapon(); if (werewolf) { @@ -2074,7 +2174,7 @@ namespace MWWorld void World::applyWerewolfAcrobatics(const Ptr &actor) { const Store &gmst = getStore().get(); - MWMechanics::NpcStats &stats = Class::get(actor).getNpcStats(actor); + MWMechanics::NpcStats &stats = actor.getClass().getNpcStats(actor); stats.getSkill(ESM::Skill::Acrobatics).setBase(gmst.find("fWerewolfAcrobatics")->getFloat()); } @@ -2138,8 +2238,8 @@ namespace MWWorld // 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); + if (stats.getSpells().canUsePower(spell->mId)) + stats.getSpells().usePower(spell->mId); else { message = "#{sPowerAlreadyUsed}"; @@ -2190,306 +2290,14 @@ namespace MWWorld void World::launchProjectile (MWWorld::Ptr actor, MWWorld::Ptr projectile, const Ogre::Vector3& worldPos, const Ogre::Quaternion& orient, MWWorld::Ptr bow, float speed) { - ProjectileState state; - state.mActorHandle = actor.getRefData().getHandle(); - state.mBow = bow; - state.mVelocity = orient.yAxis() * speed; - - MWWorld::ManualRef ref(getStore(), projectile.getCellRef().mRefID); - - ESM::Position pos; - pos.pos[0] = worldPos.x; - pos.pos[1] = worldPos.y; - pos.pos[2] = worldPos.z; - - // Do NOT copy actor rotation! actors use a different rotation order, and this will not produce the same facing direction. - Ogre::Matrix3 mat; - orient.ToRotationMatrix(mat); - Ogre::Radian xr,yr,zr; - mat.ToEulerAnglesXYZ(xr, yr, zr); - pos.rot[0] = -xr.valueRadians(); - pos.rot[1] = -yr.valueRadians(); - pos.rot[2] = -zr.valueRadians(); - - MWWorld::Ptr ptr = copyObjectToCell(ref.getPtr(), actor.getCell(), pos, false); - ptr.getRefData().setCount(1); - - mProjectiles[ptr] = state; + mProjectileManager->launchProjectile(actor, projectile, worldPos, orient, bow, speed); } - void World::launchMagicBolt (const std::string& id, bool stack, const ESM::EffectList& effects, + void World::launchMagicBolt (const std::string& model, const std::string &sound, const std::string &spellId, + float speed, bool stack, const ESM::EffectList& effects, const MWWorld::Ptr& actor, const std::string& sourceName) { - std::string projectileModel; - std::string sound; - float speed = 0; - for (std::vector::const_iterator iter (effects.mList.begin()); - iter!=effects.mList.end(); ++iter) - { - if (iter->mRange != ESM::RT_Target) - continue; - - const ESM::MagicEffect *magicEffect = getStore().get().find ( - iter->mEffectID); - - projectileModel = magicEffect->mBolt; - if (projectileModel.empty()) - projectileModel = "VFX_DefaultBolt"; - - static const std::string schools[] = { - "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" - }; - - if (!magicEffect->mBoltSound.empty()) - sound = magicEffect->mBoltSound; - else - sound = schools[magicEffect->mData.mSchool] + " bolt"; - - speed = magicEffect->mData.mSpeed; - break; - } - if (projectileModel.empty()) - return; - - // Spawn at 0.75 * ActorHeight - float height = mPhysEngine->getCharacter(actor.getRefData().getHandle())->getHalfExtents().z * 2 * 0.75; - - MWWorld::ManualRef ref(getStore(), projectileModel); - ESM::Position pos; - pos.pos[0] = actor.getRefData().getPosition().pos[0]; - pos.pos[1] = actor.getRefData().getPosition().pos[1]; - pos.pos[2] = actor.getRefData().getPosition().pos[2] + height; - - // Do NOT copy rotation directly! actors use a different rotation order, and this will not produce the same facing direction. - Ogre::Quaternion orient = Ogre::Quaternion(Ogre::Radian(actor.getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * - Ogre::Quaternion(Ogre::Radian(actor.getRefData().getPosition().rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X); - Ogre::Matrix3 mat; - orient.ToRotationMatrix(mat); - Ogre::Radian xr,yr,zr; - mat.ToEulerAnglesXYZ(xr, yr, zr); - pos.rot[0] = -xr.valueRadians(); - pos.rot[1] = -yr.valueRadians(); - pos.rot[2] = -zr.valueRadians(); - - ref.getPtr().getCellRef().mPos = pos; - CellStore* cell = actor.getCell(); - MWWorld::Ptr ptr = copyObjectToCell(ref.getPtr(), cell, pos); - - MagicBoltState state; - state.mSourceName = sourceName; - state.mId = id; - state.mActorHandle = actor.getRefData().getHandle(); - state.mSpeed = speed; - state.mStack = stack; - - // Only interested in "on target" effects - for (std::vector::const_iterator iter (effects.mList.begin()); - iter!=effects.mList.end(); ++iter) - { - if (iter->mRange == ESM::RT_Target) - state.mEffects.mList.push_back(*iter); - } - - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - sndMgr->playSound3D(ptr, sound, 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop); - - mMagicBolts[ptr] = state; - } - - void World::moveProjectiles(float duration) - { - std::map moved; - for (std::map::iterator it = mProjectiles.begin(); it != mProjectiles.end();) - { - if (!mWorldScene->isCellActive(*it->first.getCell())) - { - deleteObject(it->first); - mProjectiles.erase(it++); - continue; - } - - MWWorld::Ptr ptr = it->first; - - // gravity constant - must be way lower than the gravity affecting actors, since we're not - // simulating aerodynamics at all - it->second.mVelocity -= Ogre::Vector3(0, 0, 627.2f * 0.1f) * duration; - - Ogre::Vector3 pos(ptr.getRefData().getPosition().pos); - Ogre::Vector3 newPos = pos + it->second.mVelocity * duration; - - Ogre::Quaternion orient = Ogre::Vector3::UNIT_Y.getRotationTo(it->second.mVelocity); - Ogre::Matrix3 mat; - orient.ToRotationMatrix(mat); - Ogre::Radian xr,yr,zr; - mat.ToEulerAnglesXYZ(xr, yr, zr); - rotateObject(ptr, -xr.valueDegrees(), -yr.valueDegrees(), -zr.valueDegrees()); - - // Check for impact - btVector3 from(pos.x, pos.y, pos.z); - btVector3 to(newPos.x, newPos.y, newPos.z); - std::vector > collisions = mPhysEngine->rayTest2(from, to); - bool hit=false; - - // HACK: query against the shape as well, since the ray does not take the volume into account - // really, this should be a convex cast, but the whole physics system needs a rewrite - std::vector col2 = mPhysEngine->getCollisions(ptr.getRefData().getHandle()); - for (std::vector::iterator ci = col2.begin(); ci != col2.end(); ++ci) - collisions.push_back(std::make_pair(0.f,*ci)); - - for (std::vector >::iterator cIt = collisions.begin(); cIt != collisions.end() && !hit; ++cIt) - { - MWWorld::Ptr obstacle = searchPtrViaHandle(cIt->second); - if (obstacle == ptr) - continue; - - MWWorld::Ptr caster = searchPtrViaHandle(it->second.mActorHandle); - - // Arrow intersects with player immediately after shooting :/ - if (obstacle == caster) - continue; - - if (caster.isEmpty()) - caster = obstacle; - - if (obstacle.isEmpty()) - { - // Terrain - } - else if (obstacle.getClass().isActor()) - { - MWMechanics::projectileHit(caster, obstacle, it->second.mBow, ptr, pos + (newPos - pos) * cIt->first); - } - hit = true; - } - if (hit) - { - deleteObject(ptr); - mProjectiles.erase(it++); - continue; - } - - std::string handle = ptr.getRefData().getHandle(); - - moveObject(ptr, newPos.x, newPos.y, newPos.z); - - // HACK: Re-fetch Ptrs if necessary, since the cell might have changed - if (!ptr.getRefData().getCount()) - { - moved[handle] = it->second; - mProjectiles.erase(it++); - } - else - ++it; - } - - // HACK: Re-fetch Ptrs if necessary, since the cell might have changed - for (std::map::iterator it = moved.begin(); it != moved.end(); ++it) - { - MWWorld::Ptr newPtr = searchPtrViaHandle(it->first); - if (newPtr.isEmpty()) // The projectile went into an inactive cell and was deleted - continue; - mProjectiles[getPtrViaHandle(it->first)] = it->second; - } - } - - void World::moveMagicBolts(float duration) - { - std::map moved; - for (std::map::iterator it = mMagicBolts.begin(); it != mMagicBolts.end();) - { - if (!mWorldScene->isCellActive(*it->first.getCell())) - { - deleteObject(it->first); - mMagicBolts.erase(it++); - continue; - } - - MWWorld::Ptr ptr = it->first; - - Ogre::Vector3 rot(ptr.getRefData().getPosition().rot); - - Ogre::Quaternion orient = ptr.getRefData().getBaseNode()->getOrientation(); - static float fTargetSpellMaxSpeed = getStore().get().find("fTargetSpellMaxSpeed")->getFloat(); - float speed = fTargetSpellMaxSpeed * it->second.mSpeed; - - Ogre::Vector3 direction = orient.yAxis(); - direction.normalise(); - Ogre::Vector3 pos(ptr.getRefData().getPosition().pos); - Ogre::Vector3 newPos = pos + direction * duration * speed; - - // Check for impact - btVector3 from(pos.x, pos.y, pos.z); - btVector3 to(newPos.x, newPos.y, newPos.z); - std::vector > collisions = mPhysEngine->rayTest2(from, to); - bool explode = false; - for (std::vector >::iterator cIt = collisions.begin(); cIt != collisions.end() && !explode; ++cIt) - { - MWWorld::Ptr obstacle = searchPtrViaHandle(cIt->second); - if (obstacle == ptr) - continue; - - MWWorld::Ptr caster = searchPtrViaHandle(it->second.mActorHandle); - if (caster.isEmpty()) - caster = obstacle; - - if (obstacle.isEmpty()) - { - // Terrain - } - else - { - MWMechanics::CastSpell cast(caster, obstacle); - cast.mHitPosition = pos; - cast.mId = it->second.mId; - cast.mSourceName = it->second.mSourceName; - cast.mStack = it->second.mStack; - cast.inflict(obstacle, caster, it->second.mEffects, ESM::RT_Target, false, true); - } - - explode = true; - } - - if (explode) - { - MWWorld::Ptr caster = searchPtrViaHandle(it->second.mActorHandle); - explodeSpell(Ogre::Vector3(ptr.getRefData().getPosition().pos), ptr, it->second.mEffects, caster, it->second.mId, it->second.mSourceName); - - deleteObject(ptr); - mMagicBolts.erase(it++); - continue; - } - - std::string handle = ptr.getRefData().getHandle(); - - moveObject(ptr, newPos.x, newPos.y, newPos.z); - - // HACK: Re-fetch Ptrs if necessary, since the cell might have changed - if (!ptr.getRefData().getCount()) - { - moved[handle] = it->second; - mMagicBolts.erase(it++); - } - else - ++it; - } - - // HACK: Re-fetch Ptrs if necessary, since the cell might have changed - for (std::map::iterator it = moved.begin(); it != moved.end(); ++it) - { - MWWorld::Ptr newPtr = searchPtrViaHandle(it->first); - if (newPtr.isEmpty()) // The projectile went into an inactive cell and was deleted - continue; - mMagicBolts[getPtrViaHandle(it->first)] = it->second; - } - } - - void World::objectLeftActiveCell(Ptr object, Ptr movedPtr) - { - // For now, projectiles moved to an inactive cell are just deleted, because there's no reliable way to hold on to the meta information - if (mMagicBolts.find(object) != mMagicBolts.end()) - deleteObject(movedPtr); - if (mProjectiles.find(object) != mProjectiles.end()) - deleteObject(movedPtr); + mProjectileManager->launchMagicBolt(model, sound, spellId, speed, stack, effects, actor, sourceName); } const std::vector& World::getContentFiles() const @@ -2506,7 +2314,17 @@ namespace MWWorld bool World::isDark() const { - return mWeatherManager->isDark(); + MWWorld::CellStore* cell = mPlayer->getPlayer().getCell(); + if (cell->isExterior()) + return mWeatherManager->isDark(); + else + { + uint32_t ambient = cell->getCell()->mAmbi.mAmbient; + int ambientTotal = (ambient & 0xff) + + ((ambient>>8) & 0xff) + + ((ambient>>16) & 0xff); + return !(cell->getCell()->mData.mFlags & ESM::Cell::NoSleep) && ambientTotal <= 201; + } } bool World::findInteriorPositionInWorldSpace(MWWorld::CellStore* cell, Ogre::Vector3& result) @@ -2520,9 +2338,9 @@ namespace MWWorld for (CellRefList::List::iterator it = refList.begin(); it != refList.end(); ++it) { MWWorld::LiveCellRef& ref = *it; - if (ref.mRef.mTeleport && ref.mRef.mDestCell.empty()) + if (ref.mRef.getTeleport() && ref.mRef.getDestCell().empty()) { - ESM::Position pos = ref.mRef.mDoorDest; + ESM::Position pos = ref.mRef.getDoorDest(); result = Ogre::Vector3(pos.pos); return true; } @@ -2695,6 +2513,7 @@ namespace MWWorld MWWorld::Ptr closestChest; float closestDistance = FLT_MAX; + //Find closest stolen_goods chest std::vector chests; mCells.getInteriorPtrs("stolen_goods", chests); @@ -2712,17 +2531,18 @@ namespace MWWorld } } - if (!closestChest.isEmpty()) + if (!closestChest.isEmpty()) //Found a close chest { ContainerStore& store = ptr.getClass().getContainerStore(ptr); - for (ContainerStoreIterator it = store.begin(); it != store.end(); ++it) + for (ContainerStoreIterator it = store.begin(); it != store.end(); ++it) //Move all stolen stuff into chest { - if (!it->getCellRef().mOwner.empty() && it->getCellRef().mOwner != "player") + if (!it->getCellRef().getOwner().empty() && it->getCellRef().getOwner() != "player") //Not owned by no one/player? { closestChest.getClass().getContainerStore(closestChest).add(*it, it->getRefData().getCount(), closestChest); store.remove(*it, it->getRefData().getCount(), ptr); } } + closestChest.getClass().lock(closestChest,50); } } @@ -2791,7 +2611,7 @@ namespace MWWorld message += "\n" + skillMsg; } - // TODO: Sleep the player + // TODO: Sleep the player std::vector buttons; buttons.push_back("#{sOk}"); @@ -2826,7 +2646,7 @@ namespace MWWorld MWWorld::CellStore* cell = mPlayer->getPlayer().getCell(); MWWorld::ManualRef ref(getStore(), selectedCreature, 1); - ref.getPtr().getCellRef().mPos = ipos; + ref.getPtr().getCellRef().setPosition(ipos); safePlaceObject(ref.getPtr(), cell, ipos); } @@ -2859,7 +2679,7 @@ namespace MWWorld mRendering->spawnEffect(model, texture, worldPosition); } - void World::explodeSpell(const Vector3 &origin, const MWWorld::Ptr& object, const ESM::EffectList &effects, const Ptr &caster, + void World::explodeSpell(const Vector3 &origin, const ESM::EffectList &effects, const Ptr &caster, const std::string& id, const std::string& sourceName) { std::map > toApply; @@ -2884,12 +2704,13 @@ namespace MWWorld static const std::string schools[] = { "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" }; - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - if(!effect->mAreaSound.empty()) - sndMgr->playSound3D(object, effect->mAreaSound, 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_NoTrack); - else - sndMgr->playSound3D(object, schools[effect->mData.mSchool]+" area", 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_NoTrack); - + { + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + if(!effect->mAreaSound.empty()) + sndMgr->playManualSound3D(origin, effect->mAreaSound, 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_NoTrack); + else + sndMgr->playManualSound3D(origin, schools[effect->mData.mSchool]+" area", 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_NoTrack); + } // Get the actors in range of the effect std::vector objects; MWBase::Environment::get().getMechanicsManager()->getObjectsInRange( diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index f1e89bf6b..0a396ef5c 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -3,6 +3,8 @@ #include "../mwrender/debugging.hpp" +#include + #include "ptr.hpp" #include "scene.hpp" #include "esmstore.hpp" @@ -51,6 +53,7 @@ namespace MWWorld { class WeatherManager; class Player; + class ProjectileManager; /// \brief The game world and its visual representation @@ -72,8 +75,12 @@ namespace MWWorld Cells mCells; + std::string mCurrentWorldSpace; + OEngine::Physic::PhysicEngine* mPhysEngine; + boost::shared_ptr mProjectileManager; + bool mGodMode; std::vector mContentFiles; @@ -87,39 +94,10 @@ namespace MWWorld std::string mFacedHandle; float mFacedDistance; - std::map mDoorStates; - ///< only holds doors that are currently moving. 0 means closing, 1 opening - - struct MagicBoltState - { - // Id of spell or enchantment to apply when it hits - std::string mId; - - // Actor who casted this projectile - std::string mActorHandle; - - // Name of item to display as effect source in magic menu (in case we casted an enchantment) - std::string mSourceName; - - ESM::EffectList mEffects; - - float mSpeed; - - bool mStack; - }; + std::string mStartupScript; - struct ProjectileState - { - // Actor who shot this projectile - std::string mActorHandle; - - MWWorld::Ptr mBow; // bow or crossbow the projectile was fired from - - Ogre::Vector3 mVelocity; - }; - - std::map mMagicBolts; - std::map mProjectiles; + std::map mDoorStates; + ///< only holds doors that are currently moving. 1 = opening, 2 = closing std::string mStartCell; @@ -148,9 +126,6 @@ namespace MWWorld void processDoors(float duration); ///< Run physics simulation and modify \a world accordingly. - void moveMagicBolts(float duration); - void moveProjectiles(float duration); - void doPhysics(float duration); ///< Run physics simulation and modify \a world accordingly. @@ -165,15 +140,10 @@ namespace MWWorld void loadContentFiles(const Files::Collections& fileCollections, const std::vector& content, ContentLoader& contentLoader); - int mPlayIntro; - bool mTeleportEnabled; bool mLevitationEnabled; bool mGoToJail; - /// Called when \a object is moved to an inactive cell - void objectLeftActiveCell (MWWorld::Ptr object, MWWorld::Ptr movedPtr); - float feetToGameUnits(float feet); public: @@ -183,7 +153,7 @@ namespace MWWorld const std::vector& contentFiles, const boost::filesystem::path& resDir, const boost::filesystem::path& cacheDir, ToUTF8::Utf8Encoder* encoder, const std::map& fallbackMap, - int activationDistanceOverride, const std::string& startCell); + int activationDistanceOverride, const std::string& startCell, const std::string& startupScript); virtual ~World(); @@ -194,7 +164,7 @@ namespace MWWorld virtual int countSavedGameRecords() const; - virtual void write (ESM::ESMWriter& writer) const; + virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) const; virtual void readRecord (ESM::ESMReader& reader, int32_t type, const std::map& contentFileMap); @@ -213,7 +183,7 @@ namespace MWWorld virtual void setWaterHeight(const float height); - virtual void toggleWater(); + virtual bool toggleWater(); virtual void adjustSky(); @@ -287,6 +257,9 @@ namespace MWWorld virtual Ptr searchPtrViaHandle (const std::string& handle); ///< Return a pointer to a liveCellRef with the given Ogre handle or Ptr() if not found + virtual Ptr searchPtrViaActorId (int actorId); + ///< Search is limited to the active cells. + virtual void adjustPosition (const Ptr& ptr); ///< Adjust position after load to be on ground. Must be called after model load. @@ -340,7 +313,8 @@ namespace MWWorld virtual void changeToExteriorCell (const ESM::Position& position); ///< Move to exterior cell. - virtual void changeToCell (const ESM::CellId& cellId, const ESM::Position& position); + virtual void changeToCell (const ESM::CellId& cellId, const ESM::Position& position, bool detectWorldSpaceChange=true); + ///< @param detectWorldSpaceChange if true, clean up worldspace-specific data when the world space changes virtual const ESM::Cell *getExterior (const std::string& cellName) const; ///< Return a cell matching the given name or a 0-pointer, if there is no such cell. @@ -436,15 +410,14 @@ namespace MWWorld virtual void update (float duration, bool paused); - virtual bool placeObject (const MWWorld::Ptr& object, float cursorX, float cursorY, int amount); + virtual MWWorld::Ptr placeObject (const MWWorld::Ptr& object, float cursorX, float cursorY, int amount); ///< copy and place an object into the gameworld at the specified cursor position /// @param object /// @param cursor X (relative 0-1) /// @param cursor Y (relative 0-1) /// @param number of objects to place - /// @return true if the object was placed, or false if it was rejected because the position is too far away - virtual void dropObjectOnGround (const MWWorld::Ptr& actor, const MWWorld::Ptr& object, int amount); + virtual MWWorld::Ptr dropObjectOnGround (const MWWorld::Ptr& actor, const MWWorld::Ptr& object, int amount); ///< copy and place an object into the gameworld at the given actor's position /// @param actor giving the dropped object position /// @param object @@ -493,10 +466,11 @@ namespace MWWorld virtual void setupPlayer(); virtual void renderPlayer(); - virtual bool getOpenOrCloseDoor(const MWWorld::Ptr& door); - ///< if activated, should this door be opened or closed? + /// open or close a non-teleport door (depending on current state) virtual void activateDoor(const MWWorld::Ptr& door); - ///< activate (open or close) an non-teleport door + + /// open or close a non-teleport door as specified + virtual void activateDoor(const MWWorld::Ptr& door, bool open); virtual bool getPlayerStandingOn (const MWWorld::Ptr& object); ///< @return true if the player is standing on \a object virtual bool getActorStandingOn (const MWWorld::Ptr& object); ///< @return true if any actor is standing on \a object @@ -510,6 +484,8 @@ namespace MWWorld virtual bool getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc); ///< get Line of Sight (morrowind stupid implementation) + virtual float getDistToNearestRayHit(const Ogre::Vector3& from, const Ogre::Vector3& dir, float maxDist); + virtual void enableActorCollision(const MWWorld::Ptr& actor, bool enable); virtual int canRest(); @@ -567,7 +543,8 @@ namespace MWWorld */ virtual void castSpell (const MWWorld::Ptr& actor); - virtual void launchMagicBolt (const std::string& id, bool stack, const ESM::EffectList& effects, + virtual void launchMagicBolt (const std::string& model, const std::string& sound, const std::string& spellId, + float speed, bool stack, const ESM::EffectList& effects, const MWWorld::Ptr& actor, const std::string& sourceName); virtual void launchProjectile (MWWorld::Ptr actor, MWWorld::Ptr projectile, const Ogre::Vector3& worldPos, const Ogre::Quaternion& orient, MWWorld::Ptr bow, float speed); @@ -607,7 +584,7 @@ namespace MWWorld /// Spawn a blood effect for \a ptr at \a worldPosition virtual void spawnBloodEffect (const MWWorld::Ptr& ptr, const Ogre::Vector3& worldPosition); - virtual void explodeSpell (const Ogre::Vector3& origin, const MWWorld::Ptr& object, const ESM::EffectList& effects, + virtual void explodeSpell (const Ogre::Vector3& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const std::string& id, const std::string& sourceName); }; } diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 682010340..9fe7890ac 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -12,7 +12,6 @@ if (GTEST_FOUND AND GMOCK_FOUND) file(GLOB UNITTEST_SRC_FILES components/misc/test_*.cpp - components/file_finder/test_*.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) diff --git a/apps/openmw_test_suite/components/file_finder/test_filefinder.cpp b/apps/openmw_test_suite/components/file_finder/test_filefinder.cpp deleted file mode 100644 index 2d151988b..000000000 --- a/apps/openmw_test_suite/components/file_finder/test_filefinder.cpp +++ /dev/null @@ -1,66 +0,0 @@ -#include -#include -#include "components/file_finder/file_finder.hpp" - -struct FileFinderTest : public ::testing::Test -{ - protected: - FileFinderTest() - : mTestDir("./filefinder_test_dir/") - , mTestFile("test.txt") - , mTestFileUppercase("TEST.TXT") - , mTestFileNotFound("foobarbaz.txt") - { - } - - virtual void SetUp() - { - boost::filesystem::create_directory(boost::filesystem::path(mTestDir)); - - std::ofstream ofs(std::string(mTestDir + mTestFile).c_str(), std::ofstream::out); - ofs << std::endl; - ofs.close(); - } - - virtual void TearDown() - { - boost::filesystem::remove_all(boost::filesystem::path(mTestDir)); - } - - std::string mTestDir; - std::string mTestFile; - std::string mTestFileUppercase; - std::string mTestFileNotFound; -}; - -TEST_F(FileFinderTest, FileFinder_has_file) -{ - FileFinder::FileFinder fileFinder(mTestDir); - ASSERT_TRUE(fileFinder.has(mTestFile)); - ASSERT_TRUE(fileFinder.has(mTestFileUppercase)); - ASSERT_TRUE(fileFinder.lookup(mTestFile) == std::string(mTestDir + mTestFile)); - ASSERT_TRUE(fileFinder.lookup(mTestFileUppercase) == std::string(mTestDir + mTestFile)); -} - -TEST_F(FileFinderTest, FileFinder_does_not_have_file) -{ - FileFinder::FileFinder fileFinder(mTestDir); - ASSERT_FALSE(fileFinder.has(mTestFileNotFound)); - ASSERT_TRUE(fileFinder.lookup(mTestFileNotFound).empty()); -} - -TEST_F(FileFinderTest, FileFinderStrict_has_file) -{ - FileFinder::FileFinderStrict fileFinder(mTestDir); - ASSERT_TRUE(fileFinder.has(mTestFile)); - ASSERT_FALSE(fileFinder.has(mTestFileUppercase)); - ASSERT_TRUE(fileFinder.lookup(mTestFile) == std::string(mTestDir + mTestFile)); - ASSERT_FALSE(fileFinder.lookup(mTestFileUppercase) == std::string(mTestDir + mTestFile)); -} - -TEST_F(FileFinderTest, FileFinderStrict_does_not_have_file) -{ - FileFinder::FileFinderStrict fileFinder(mTestDir); - ASSERT_FALSE(fileFinder.has(mTestFileNotFound)); - ASSERT_TRUE(fileFinder.lookup(mTestFileNotFound).empty()); -} diff --git a/apps/openmw_test_suite/components/file_finder/test_search.cpp b/apps/openmw_test_suite/components/file_finder/test_search.cpp deleted file mode 100644 index 63745b625..000000000 --- a/apps/openmw_test_suite/components/file_finder/test_search.cpp +++ /dev/null @@ -1,74 +0,0 @@ -#include -#include -#include - -#include "components/file_finder/search.hpp" - -struct SearchTest : public ::testing::Test -{ - protected: - SearchTest() - : mTestDir("./search_test_dir/") - { - } - - virtual void SetUp() - { - boost::filesystem::create_directory(boost::filesystem::path(mTestDir)); - - std::ofstream ofs(std::string(mTestDir + "test2.txt").c_str(), std::ofstream::out); - ofs << std::endl; - ofs.close(); - } - - virtual void TearDown() - { - boost::filesystem::remove_all(boost::filesystem::path(mTestDir)); - } - - std::string mTestDir; -}; - -TEST_F(SearchTest, file_not_found) -{ - struct Result : public FileFinder::ReturnPath - { - Result(const boost::filesystem::path& expectedPath) - : mExpectedPath(expectedPath) - { - } - - void add(const boost::filesystem::path& p) - { - ASSERT_FALSE(p == mExpectedPath); - } - - private: - boost::filesystem::path mExpectedPath; - - } r(boost::filesystem::path(mTestDir + "test.txt")); - - FileFinder::find(mTestDir, r, false); -} - -TEST_F(SearchTest, file_found) -{ - struct Result : public FileFinder::ReturnPath - { - Result(const boost::filesystem::path& expectedPath) - : mExpectedPath(expectedPath) - { - } - - void add(const boost::filesystem::path& p) - { - ASSERT_TRUE(p == mExpectedPath); - } - - private: - boost::filesystem::path mExpectedPath; - - } r(boost::filesystem::path(mTestDir + "test2.txt")); - - FileFinder::find(mTestDir, r, false); -} diff --git a/apps/openmw_test_suite/components/misc/test_slicearray.cpp b/apps/openmw_test_suite/components/misc/test_slicearray.cpp deleted file mode 100644 index ab63e56c4..000000000 --- a/apps/openmw_test_suite/components/misc/test_slicearray.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include -#include "components/misc/slice_array.hpp" - -struct SliceArrayTest : public ::testing::Test -{ - protected: - virtual void SetUp() - { - } - - virtual void TearDown() - { - } -}; - -TEST_F(SliceArrayTest, hello_string) -{ - Misc::SString s("hello"); - ASSERT_EQ(sizeof("hello") - 1, s.length); - ASSERT_FALSE(s=="hel"); - ASSERT_FALSE(s=="hell"); - ASSERT_TRUE(s=="hello"); -} - -TEST_F(SliceArrayTest, othello_string_with_offset_2_and_size_4) -{ - Misc::SString s("othello" + 2, 4); - ASSERT_EQ(sizeof("hell") - 1, s.length); - ASSERT_FALSE(s=="hel"); - ASSERT_TRUE(s=="hell"); - ASSERT_FALSE(s=="hello"); -} - diff --git a/cmake/FindAudiere.cmake b/cmake/FindAudiere.cmake deleted file mode 100644 index 79427309c..000000000 --- a/cmake/FindAudiere.cmake +++ /dev/null @@ -1,60 +0,0 @@ -# Locate Audiere -# This module defines -# AUDIERE_LIBRARY -# AUDIERE_FOUND, if false, do not try to link to Audiere -# AUDIERE_INCLUDE_DIR, where to find the headers -# -# Created by Nicolay Korslund for OpenMW (http://openmw.com) -# -# More or less a direct ripoff of FindOpenAL.cmake by Eric Wing. - -#============================================================================= -# Copyright 2005-2009 Kitware, Inc. -# -# Distributed under the OSI-approved BSD License (the "License"); -# see accompanying file Copyright.txt for details. -# -# This software is distributed WITHOUT ANY WARRANTY; without even the -# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the License for more information. -#============================================================================= -# (To distributed this file outside of CMake, substitute the full -# License text for the above reference.) - - -FIND_PATH(AUDIERE_INCLUDE_DIR audiere.h - HINTS - PATH_SUFFIXES include - PATHS - $ENV{AUDIERE_DIR} - ~/Library/Frameworks - /Library/Frameworks - /usr/local - /usr - /sw # Fink - /opt/local # DarwinPorts - /opt/csw # Blastwave - /opt -) - -FIND_LIBRARY(AUDIERE_LIBRARY - NAMES audiere - HINTS - PATH_SUFFIXES lib64 lib libs64 libs libs/Win32 libs/Win64 - PATHS - $ENV{AUDIERE_DIR} - ~/Library/Frameworks - /Library/Frameworks - /usr/local - /usr - /sw - /opt/local - /opt/csw - /opt -) - -SET(AUDIERE_FOUND "NO") -IF(AUDIERE_LIBRARY AND AUDIERE_INCLUDE_DIR) - SET(AUDIERE_FOUND "YES") -ENDIF(AUDIERE_LIBRARY AND AUDIERE_INCLUDE_DIR) - diff --git a/cmake/FindLIBUNSHIELD.cmake b/cmake/FindLIBUNSHIELD.cmake index 4f4e98a1c..f0fa4cc82 100644 --- a/cmake/FindLIBUNSHIELD.cmake +++ b/cmake/FindLIBUNSHIELD.cmake @@ -4,7 +4,7 @@ # LIBUNSHIELD_FOUND, if false, do not try to link to LibUnshield # LIBUNSHIELD_INCLUDE_DIR, where to find the headers # -# Created by Tom Mason (wheybags) for OpenMW (http://openmw.com), based on FindMPG123.cmake +# Created by Tom Mason (wheybags) for OpenMW (http://openmw.org), based on FindMPG123.cmake # # Ripped off from other sources. In fact, this file is so generic (I # just did a search and replace on another file) that I wonder why the diff --git a/cmake/FindMPG123.cmake b/cmake/FindMPG123.cmake deleted file mode 100644 index 51e562c91..000000000 --- a/cmake/FindMPG123.cmake +++ /dev/null @@ -1,47 +0,0 @@ -# Locate MPG123 -# This module defines -# MPG123_LIBRARY -# MPG123_FOUND, if false, do not try to link to Mpg123 -# MPG123_INCLUDE_DIR, where to find the headers -# -# Created by Nicolay Korslund for OpenMW (http://openmw.com) -# -# Ripped off from other sources. In fact, this file is so generic (I -# just did a search and replace on another file) that I wonder why the -# CMake guys haven't wrapped this entire thing in a single -# function. Do we really need to repeat this stuff for every single -# library when they all work the same? - -FIND_PATH(MPG123_INCLUDE_DIR mpg123.h - HINTS - PATHS - ~/Library/Frameworks - /Library/Frameworks - /usr/local - /usr - /sw # Fink - /opt/local # DarwinPorts - /opt/csw # Blastwave - /opt -) - -FIND_LIBRARY(MPG123_LIBRARY - NAMES mpg123 - HINTS - PATH_SUFFIXES lib64 lib libs64 libs libs/Win32 libs/Win64 - PATHS - ~/Library/Frameworks - /Library/Frameworks - /usr/local - /usr - /sw - /opt/local - /opt/csw - /opt -) - -SET(MPG123_FOUND "NO") -IF(MPG123_LIBRARY AND MPG123_INCLUDE_DIR) - SET(MPG123_FOUND "YES") -ENDIF(MPG123_LIBRARY AND MPG123_INCLUDE_DIR) - diff --git a/cmake/FindSNDFILE.cmake b/cmake/FindSNDFILE.cmake deleted file mode 100644 index 5c7664b50..000000000 --- a/cmake/FindSNDFILE.cmake +++ /dev/null @@ -1,41 +0,0 @@ -# Locate SNDFILE -# This module defines -# SNDFILE_LIBRARY -# SNDFILE_FOUND, if false, do not try to link to Sndfile -# SNDFILE_INCLUDE_DIR, where to find the headers -# -# Created by Nicolay Korslund for OpenMW (http://openmw.com) - -FIND_PATH(SNDFILE_INCLUDE_DIR sndfile.h - HINTS - PATHS - ~/Library/Frameworks - /Library/Frameworks - /usr/local - /usr - /sw # Fink - /opt/local # DarwinPorts - /opt/csw # Blastwave - /opt -) - -FIND_LIBRARY(SNDFILE_LIBRARY - NAMES sndfile - HINTS - PATH_SUFFIXES lib64 lib libs64 libs libs/Win32 libs/Win64 - PATHS - ~/Library/Frameworks - /Library/Frameworks - /usr/local - /usr - /sw - /opt/local - /opt/csw - /opt -) - -SET(SNDFILE_FOUND "NO") -IF(SNDFILE_LIBRARY AND SNDFILE_INCLUDE_DIR) - SET(SNDFILE_FOUND "YES") -ENDIF(SNDFILE_LIBRARY AND SNDFILE_INCLUDE_DIR) - diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 2b06babe7..e0166138e 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -34,10 +34,6 @@ add_component_dir (to_utf8 to_utf8 ) -add_component_dir (file_finder - file_finder filename_less search - ) - add_component_dir (esm attr defs esmcommon esmreader esmwriter loadacti loadalch loadappa loadarmo loadbody loadbook loadbsgn loadcell loadclas loadclot loadcont loadcrea loadcrec loaddial loaddoor loadench loadfact loadglob loadgmst @@ -45,11 +41,11 @@ add_component_dir (esm loadnpc loadpgrd loadrace loadregn loadscpt loadskil loadsndg loadsoun loadspel loadsscr loadstat loadweap records aipackage effectlist spelllist variant variantimp loadtes3 cellref filter savedgame journalentry queststate locals globalscript player objectstate cellid cellstate globalmap lightstate inventorystate containerstate npcstate creaturestate dialoguestate statstate - npcstats creaturestats weatherstate + npcstats creaturestats weatherstate quickkeys fogstate spellstate activespells creaturelevliststate doorstate projectilestate ) add_component_dir (misc - slice_array stringops + utf8stream stringops ) add_component_dir (files diff --git a/components/bsa/bsa_file.cpp b/components/bsa/bsa_file.cpp index 25b006fb3..0958c8f0c 100644 --- a/components/bsa/bsa_file.cpp +++ b/components/bsa/bsa_file.cpp @@ -25,6 +25,9 @@ #include +#include +#include + #include "../files/constrainedfiledatastream.hpp" using namespace std; @@ -72,7 +75,8 @@ void BSAFile::readHeader() */ assert(!isLoaded); - std::ifstream input(filename.c_str(), std::ios_base::binary); + namespace bfs = boost::filesystem; + bfs::ifstream input(bfs::path(filename), std::ios_base::binary); // Total archive size size_t fsize = 0; diff --git a/components/compiler/exprparser.cpp b/components/compiler/exprparser.cpp index 0c013b18f..ed628278b 100644 --- a/components/compiler/exprparser.cpp +++ b/components/compiler/exprparser.cpp @@ -98,7 +98,7 @@ namespace Compiler else if (t1=='f' || t2=='f') mOperands.push_back ('f'); else - std::logic_error ("failed to determine result operand type"); + throw std::logic_error ("failed to determine result operand type"); } void ExprParser::pop() @@ -730,7 +730,7 @@ namespace Compiler } int ExprParser::parseArguments (const std::string& arguments, Scanner& scanner, - std::vector& code, bool invert) + std::vector& code) { bool optional = false; int optionalCount = 0; @@ -762,15 +762,10 @@ namespace Compiler if (*iter!='x') { - if (invert) - { - std::vector tmp; - stringParser.append (tmp); + std::vector tmp; + stringParser.append (tmp); - stack.push (tmp); - } - else - stringParser.append (code); + stack.push (tmp); if (optional) ++optionalCount; @@ -795,10 +790,7 @@ namespace Compiler if (type!=*iter) Generator::convert (tmp, type, *iter); - if (invert) - stack.push (tmp); - else - std::copy (tmp.begin(), tmp.end(), std::back_inserter (code)); + stack.push (tmp); if (optional) ++optionalCount; diff --git a/components/compiler/exprparser.hpp b/components/compiler/exprparser.hpp index 6a4e1be2f..e4e385ff0 100644 --- a/components/compiler/exprparser.hpp +++ b/components/compiler/exprparser.hpp @@ -96,12 +96,10 @@ namespace Compiler /// \return Type ('l': integer, 'f': float) int parseArguments (const std::string& arguments, Scanner& scanner, - std::vector& code, bool invert = false); + std::vector& code); ///< Parse sequence of arguments specified by \a arguments. - /// \param arguments Each character represents one arguments ('l': integer, - /// 'f': float, 'S': string, 'c': string (case smashed), '/': following arguments are - /// optional) - /// 'x': optional string that will be ignored (die in a fire, MW script compiler!) + /// \param arguments Uses ScriptArgs typedef + /// \see Compiler::ScriptArgs /// \param invert Store arguments in reverted order. /// \return number of optional arguments }; diff --git a/components/compiler/extensions.cpp b/components/compiler/extensions.cpp index c09abcbaf..c2b11c615 100644 --- a/components/compiler/extensions.cpp +++ b/components/compiler/extensions.cpp @@ -21,7 +21,7 @@ namespace Compiler return iter->second; } - bool Extensions::isFunction (int keyword, char& returnType, std::string& argumentType, + bool Extensions::isFunction (int keyword, ScriptReturn& returnType, ScriptArgs& argumentType, bool& explicitReference) const { std::map::const_iterator iter = mFunctions.find (keyword); @@ -37,7 +37,7 @@ namespace Compiler return true; } - bool Extensions::isInstruction (int keyword, std::string& argumentType, + bool Extensions::isInstruction (int keyword, ScriptArgs& argumentType, bool& explicitReference) const { std::map::const_iterator iter = mInstructions.find (keyword); @@ -52,8 +52,8 @@ namespace Compiler return true; } - void Extensions::registerFunction (const std::string& keyword, char returnType, - const std::string& argumentType, int code, int codeExplicit) + void Extensions::registerFunction (const std::string& keyword, ScriptReturn returnType, + const ScriptArgs& argumentType, int code, int codeExplicit) { Function function; @@ -83,7 +83,7 @@ namespace Compiler } void Extensions::registerInstruction (const std::string& keyword, - const std::string& argumentType, int code, int codeExplicit) + const ScriptArgs& argumentType, int code, int codeExplicit) { Instruction instruction; diff --git a/components/compiler/extensions.hpp b/components/compiler/extensions.hpp index 18bb24ed0..3f91ca357 100644 --- a/components/compiler/extensions.hpp +++ b/components/compiler/extensions.hpp @@ -11,14 +11,35 @@ namespace Compiler { class Literals; - /// \brief Collection of compiler extensions + /// Typedef for script arguments string + /** Every character reperesents an argument to the command. All arguments are required until a /, after which + every argument is optional.
+ Eg: fff/f represents 3 required floats followed by one optional float
+ f - Float
+ c - String, case smashed
+ l - Integer
+ s - Short
+ S - String, case preserved
+ x - Optional, ignored argument + **/ + typedef std::string ScriptArgs; + + /// Typedef for script return char + /** The character represents the type of data being returned.
+ f - float
+ S - String (Cell names)
+ l - Integer + **/ + typedef char ScriptReturn; + /// \brief Collection of compiler extensions class Extensions { + struct Function { char mReturn; - std::string mArguments; + ScriptArgs mArguments; int mCode; int mCodeExplicit; int mSegment; @@ -26,7 +47,7 @@ namespace Compiler struct Instruction { - std::string mArguments; + ScriptArgs mArguments; int mCode; int mCodeExplicit; int mSegment; @@ -46,21 +67,21 @@ namespace Compiler /// - if no match is found 0 is returned. /// - keyword must be all lower case. - bool isFunction (int keyword, char& returnType, std::string& argumentType, + bool isFunction (int keyword, ScriptReturn& returnType, ScriptArgs& argumentType, bool& explicitReference) const; ///< Is this keyword registered with a function? If yes, return return and argument /// types. /// \param explicitReference In: has explicit reference; Out: set to false, if /// explicit reference is not available for this instruction. - bool isInstruction (int keyword, std::string& argumentType, + bool isInstruction (int keyword, ScriptArgs& argumentType, bool& explicitReference) const; ///< Is this keyword registered with a function? If yes, return argument types. /// \param explicitReference In: has explicit reference; Out: set to false, if /// explicit reference is not available for this instruction. - void registerFunction (const std::string& keyword, char returnType, - const std::string& argumentType, int code, int codeExplicit = -1); + void registerFunction (const std::string& keyword, ScriptReturn returnType, + const ScriptArgs& argumentType, int code, int codeExplicit = -1); ///< Register a custom function /// - keyword must be all lower case. /// - keyword must be unique @@ -68,7 +89,7 @@ namespace Compiler /// \note Currently only segment 3 and segment 5 opcodes are supported. void registerInstruction (const std::string& keyword, - const std::string& argumentType, int code, int codeExplicit = -1); + const ScriptArgs& argumentType, int code, int codeExplicit = -1); ///< Register a custom instruction /// - keyword must be all lower case. /// - keyword must be unique diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index 78b6409f2..0f726a52d 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -179,6 +179,8 @@ namespace Compiler opcodeGetReputationExplicit); extensions.registerFunction("samefaction", 'l', "", opcodeSameFaction, opcodeSameFactionExplicit); + extensions.registerInstruction("modfactionreaction", "ccl", opcodeModFactionReaction); + extensions.registerFunction("getfactionreaction", 'l', "ccl", opcodeGetFactionReaction); } } @@ -222,7 +224,7 @@ namespace Compiler { extensions.registerFunction ("xbox", 'l', "", opcodeXBox); extensions.registerFunction ("onactivate", 'l', "", opcodeOnActivate); - extensions.registerInstruction ("activate", "", opcodeActivate); + extensions.registerInstruction ("activate", "", opcodeActivate, opcodeActivateExplicit); extensions.registerInstruction ("lock", "/l", opcodeLock, opcodeLockExplicit); extensions.registerInstruction ("unlock", "", opcodeUnlock, opcodeUnlockExplicit); extensions.registerInstruction ("cast", "SS", opcodeCast, opcodeCastExplicit); @@ -409,9 +411,9 @@ namespace Compiler opcodeResurrectExplicit); extensions.registerFunction ("getspell", 'l', "c", opcodeGetSpell, opcodeGetSpellExplicit); - extensions.registerInstruction("pcraiserank","/S",opcodePCRaiseRank); - extensions.registerInstruction("pclowerrank","/S",opcodePCLowerRank); - extensions.registerInstruction("pcjoinfaction","/S",opcodePCJoinFaction); + extensions.registerInstruction("pcraiserank","/S",opcodePCRaiseRank, opcodePCRaiseRankExplicit); + extensions.registerInstruction("pclowerrank","/S",opcodePCLowerRank, opcodePCLowerRankExplicit); + extensions.registerInstruction("pcjoinfaction","/S",opcodePCJoinFaction, opcodePCJoinFactionExplicit); extensions.registerInstruction ("moddisposition","l",opcodeModDisposition, opcodeModDispositionExplicit); extensions.registerInstruction ("setdisposition","l",opcodeSetDisposition, @@ -444,6 +446,7 @@ namespace Compiler extensions.registerInstruction ("lowerrank", "", opcodeLowerRank, opcodeLowerRankExplicit); extensions.registerFunction ("ondeath", 'l', "", opcodeOnDeath, opcodeOnDeathExplicit); + extensions.registerFunction ("onknockout", 'l', "", opcodeOnKnockout, opcodeOnKnockoutExplicit); extensions.registerFunction ("iswerewolf", 'l', "", opcodeIsWerewolf, opcodeIsWerewolfExplicit); diff --git a/components/compiler/lineparser.cpp b/components/compiler/lineparser.cpp index 98bd63ba1..f7d2726e3 100644 --- a/components/compiler/lineparser.cpp +++ b/components/compiler/lineparser.cpp @@ -166,7 +166,7 @@ namespace Compiler if (!arguments.empty()) { mExprParser.reset(); - mExprParser.parseArguments (arguments, scanner, mCode, true); + mExprParser.parseArguments (arguments, scanner, mCode); } mName = name; @@ -278,7 +278,7 @@ namespace Compiler mExplicit.clear(); } - int optionals = mExprParser.parseArguments (argumentType, scanner, mCode, true); + int optionals = mExprParser.parseArguments (argumentType, scanner, mCode); extensions->generateInstructionCode (keyword, mCode, mLiterals, mExplicit, optionals); mState = EndState; @@ -363,14 +363,14 @@ namespace Compiler case Scanner::K_startscript: - mExprParser.parseArguments ("c", scanner, mCode, true); + mExprParser.parseArguments ("c", scanner, mCode); Generator::startScript (mCode); mState = EndState; return true; case Scanner::K_stopscript: - mExprParser.parseArguments ("c", scanner, mCode, true); + mExprParser.parseArguments ("c", scanner, mCode); Generator::stopScript (mCode); mState = EndState; return true; diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp index 1dbdbf7e7..8796c53c5 100644 --- a/components/compiler/opcodes.hpp +++ b/components/compiler/opcodes.hpp @@ -152,6 +152,8 @@ namespace Compiler const int opcodeGetReputationExplicit = 0x20001b2; const int opcodeSameFaction = 0x20001b5; const int opcodeSameFactionExplicit = 0x20001b6; + const int opcodeModFactionReaction = 0x2000242; + const int opcodeGetFactionReaction = 0x2000243; } namespace Gui @@ -180,6 +182,7 @@ namespace Compiler const int opcodeXBox = 0x200000c; const int opcodeOnActivate = 0x200000d; const int opcodeActivate = 0x2000075; + const int opcodeActivateExplicit = 0x2000244; const int opcodeLock = 0x20004; const int opcodeLockExplicit = 0x20005; const int opcodeUnlock = 0x200008c; @@ -330,6 +333,10 @@ namespace Compiler const int opcodePCRaiseRank = 0x2000b; const int opcodePCLowerRank = 0x2000c; const int opcodePCJoinFaction = 0x2000d; + const int opcodePCRaiseRankExplicit = 0x20029; + const int opcodePCLowerRankExplicit = 0x2002a; + const int opcodePCJoinFactionExplicit = 0x2002b; + const int opcodeGetPCRank = 0x2000e; const int opcodeGetPCRankExplicit = 0x2000f; const int opcodeModDisposition = 0x200014d; @@ -373,6 +380,8 @@ namespace Compiler const int opcodeLowerRankExplicit = 0x20001eb; const int opcodeOnDeath = 0x20001fc; const int opcodeOnDeathExplicit = 0x2000205; + const int opcodeOnKnockout = 0x2000240; + const int opcodeOnKnockoutExplicit = 0x2000241; const int opcodeBecomeWerewolf = 0x2000217; const int opcodeBecomeWerewolfExplicit = 0x2000218; diff --git a/components/esm/activespells.cpp b/components/esm/activespells.cpp new file mode 100644 index 000000000..4f51a619e --- /dev/null +++ b/components/esm/activespells.cpp @@ -0,0 +1,56 @@ +#include "activespells.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" + +namespace ESM +{ + + void ActiveSpells::save(ESMWriter &esm) const + { + for (TContainer::const_iterator it = mSpells.begin(); it != mSpells.end(); ++it) + { + esm.writeHNString ("ID__", it->first); + + const ActiveSpellParams& params = it->second; + + esm.writeHNT ("CAST", params.mCasterActorId); + esm.writeHNString ("DISP", params.mDisplayName); + esm.writeHNT ("TIME", params.mTimeStamp); + + for (std::vector::const_iterator effectIt = params.mEffects.begin(); effectIt != params.mEffects.end(); ++effectIt) + { + esm.writeHNT ("MGEF", effectIt->mEffectId); + if (effectIt->mArg != -1) + esm.writeHNT ("ARG_", effectIt->mArg); + esm.writeHNT ("MAGN", effectIt->mMagnitude); + esm.writeHNT ("DURA", effectIt->mDuration); + } + } + } + + void ActiveSpells::load(ESMReader &esm) + { + while (esm.isNextSub("ID__")) + { + std::string spellId = esm.getHString(); + + ActiveSpellParams params; + esm.getHNT (params.mCasterActorId, "CAST"); + params.mDisplayName = esm.getHNString ("DISP"); + esm.getHNT (params.mTimeStamp, "TIME"); + + while (esm.isNextSub("MGEF")) + { + ActiveEffect effect; + esm.getHT(effect.mEffectId); + effect.mArg = -1; + esm.getHNOT(effect.mArg, "ARG_"); + esm.getHNT (effect.mMagnitude, "MAGN"); + esm.getHNT (effect.mDuration, "DURA"); + params.mEffects.push_back(effect); + } + mSpells.insert(std::make_pair(spellId, params)); + } + } +} diff --git a/components/esm/activespells.hpp b/components/esm/activespells.hpp new file mode 100644 index 000000000..d9e9a8c63 --- /dev/null +++ b/components/esm/activespells.hpp @@ -0,0 +1,45 @@ +#ifndef OPENMW_ESM_ACTIVESPELLS_H +#define OPENMW_ESM_ACTIVESPELLS_H + +#include "effectlist.hpp" +#include "defs.hpp" + +#include +#include + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + // Parameters of an effect concerning lasting effects. + // Note we are not using ENAMstruct since the magnitude may be modified by magic resistance, etc. + // It could also be a negative magnitude, in case of inversing an effect, e.g. Absorb spell causes damage on target, but heals the caster. + struct ActiveEffect + { + int mEffectId; + float mMagnitude; + int mArg; // skill or attribute + float mDuration; + }; + + // format 0, saved games only + struct ActiveSpells + { + struct ActiveSpellParams + { + std::vector mEffects; + ESM::TimeStamp mTimeStamp; + std::string mDisplayName; + int mCasterActorId; + }; + + typedef std::multimap TContainer; + TContainer mSpells; + + void load (ESMReader &esm); + void save (ESMWriter &esm) const; + }; +} + +#endif diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp index 00b15f4a3..d785fb2b6 100644 --- a/components/esm/cellref.cpp +++ b/components/esm/cellref.cpp @@ -54,8 +54,9 @@ void ESM::CellRef::load (ESMReader& esm, bool wideRefNum) else mTeleport = false; - mLockLevel = -1; + mLockLevel = 0; //Set to 0 to indicate no lock esm.getHNOT (mLockLevel, "FLTV"); + mKey = esm.getHNOString ("KNAM"); mTrap = esm.getHNOString ("TNAM"); @@ -113,8 +114,9 @@ void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory) cons esm.writeHNOCString("DNAM", mDestCell); } - if (mLockLevel != -1 && !inInventory) - esm.writeHNT("FLTV", mLockLevel); + if (mLockLevel != 0 && !inInventory) { + esm.writeHNT("FLTV", mLockLevel); + } if (!inInventory) esm.writeHNOCString ("KNAM", mKey); @@ -166,7 +168,18 @@ void ESM::CellRef::blank() } } -bool ESM::operator== (const CellRef::RefNum& left, const CellRef::RefNum& right) +bool ESM::operator== (const RefNum& left, const RefNum& right) { return left.mIndex==right.mIndex && left.mContentFile==right.mContentFile; } + +bool ESM::operator< (const RefNum& left, const RefNum& right) +{ + if (left.mIndexright.mIndex) + return false; + + return left.mContentFile mAttributes[8]; StatState mDynamic[3]; + ESM::TimeStamp mTradeTime; + int mGoldPool; + int mActorId; + + bool mDead; + bool mDied; + int mFriendlyHits; + bool mTalkedTo; + bool mAlarmed; + bool mAttacked; + bool mHostile; + bool mAttackingOrSpell; + bool mKnockdown; + bool mKnockdownOneFrame; + bool mKnockdownOverOneFrame; + bool mHitRecovery; + bool mBlock; + unsigned int mMovementFlags; + float mAttackStrength; + float mFallHeight; + std::string mLastHitObject; + bool mRecalcDynamicStats; + int mDrawState; + unsigned char mDeathAnimation; + + int mLevel; + + SpellState mSpells; + ActiveSpells mActiveSpells; + void load (ESMReader &esm); void save (ESMWriter &esm) const; }; } -#endif \ No newline at end of file +#endif diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index 4d5b36c74..bdeb95291 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -6,6 +6,12 @@ namespace ESM { +struct TimeStamp +{ + float mHour; + int mDay; +}; + // Pixel color value. Standard four-byte rr,gg,bb,aa format. typedef int32_t Color; @@ -34,6 +40,12 @@ struct Position }; #pragma pack(pop) +template +struct FourCC +{ + static const unsigned int value = (((((d << 8) | c) << 8) | b) << 8) | a; +}; + enum RecNameInts { // format 0 / legacy @@ -93,6 +105,12 @@ enum RecNameInts REC_GMAP = 0x50414d47, REC_DIAS = 0x53414944, REC_WTHR = 0x52485457, + REC_KEYS = FourCC<'K','E','Y','S'>::value, + REC_DYNA = FourCC<'D','Y','N','A'>::value, + REC_ASPL = FourCC<'A','S','P','L'>::value, + REC_ACTC = FourCC<'A','C','T','C'>::value, + REC_MPRJ = FourCC<'M','P','R','J'>::value, + REC_PROJ = FourCC<'P','R','O','J'>::value, // format 1 REC_FILT = 0x544C4946 diff --git a/components/esm/dialoguestate.cpp b/components/esm/dialoguestate.cpp index b3544c85c..14301ac19 100644 --- a/components/esm/dialoguestate.cpp +++ b/components/esm/dialoguestate.cpp @@ -8,6 +8,20 @@ void ESM::DialogueState::load (ESMReader &esm) { while (esm.isNextSub ("TOPI")) mKnownTopics.push_back (esm.getHString()); + + while (esm.isNextSub ("FACT")) + { + std::string faction = esm.getHString(); + + while (esm.isNextSub ("REAC")) + { + std::string faction2 = esm.getHString(); + int reaction; + esm.getHNT(reaction, "INTV"); + + mModFactionReaction[faction][faction2] = reaction; + } + } } void ESM::DialogueState::save (ESMWriter &esm) const @@ -16,6 +30,18 @@ void ESM::DialogueState::save (ESMWriter &esm) const iter!=mKnownTopics.end(); ++iter) { esm.writeHNString ("TOPI", *iter); + } + + for (std::map >::const_iterator iter = mModFactionReaction.begin(); + iter != mModFactionReaction.end(); ++iter) + { + esm.writeHNString ("FACT", iter->first); + for (std::map::const_iterator reactIter = iter->second.begin(); + reactIter != iter->second.end(); ++reactIter) + { + esm.writeHNString ("REAC", reactIter->first); + esm.writeHNT ("INTV", reactIter->second); + } } -} \ No newline at end of file +} diff --git a/components/esm/dialoguestate.hpp b/components/esm/dialoguestate.hpp index 9aa9eaefd..5e5f602a3 100644 --- a/components/esm/dialoguestate.hpp +++ b/components/esm/dialoguestate.hpp @@ -3,6 +3,7 @@ #include #include +#include namespace ESM { @@ -15,9 +16,11 @@ namespace ESM { std::vector mKnownTopics; + std::map > mModFactionReaction; + void load (ESMReader &esm); void save (ESMWriter &esm) const; }; } -#endif \ No newline at end of file +#endif diff --git a/components/esm/doorstate.cpp b/components/esm/doorstate.cpp new file mode 100644 index 000000000..0487be7a4 --- /dev/null +++ b/components/esm/doorstate.cpp @@ -0,0 +1,25 @@ +#include "doorstate.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" + +namespace ESM +{ + + void DoorState::load(ESMReader &esm) + { + ObjectState::load(esm); + + mDoorState = 0; + esm.getHNOT (mDoorState, "ANIM"); + } + + void DoorState::save(ESMWriter &esm, bool inInventory) const + { + ObjectState::save(esm, inInventory); + + if (mDoorState != 0) + esm.writeHNT ("ANIM", mDoorState); + } + +} diff --git a/components/esm/doorstate.hpp b/components/esm/doorstate.hpp new file mode 100644 index 000000000..0df30afb0 --- /dev/null +++ b/components/esm/doorstate.hpp @@ -0,0 +1,19 @@ +#ifndef OPENMW_ESM_DOORSTATE_H +#define OPENMW_ESM_DOORSTATE_H + +#include "objectstate.hpp" + +namespace ESM +{ + // format 0, saved games only + + struct DoorState : public ObjectState + { + int mDoorState; + + virtual void load (ESMReader &esm); + virtual void save (ESMWriter &esm, bool inInventory = false) const; + }; +} + +#endif diff --git a/components/esm/esmreader.hpp b/components/esm/esmreader.hpp index b6c0ebc70..6b0bb9a27 100644 --- a/components/esm/esmreader.hpp +++ b/components/esm/esmreader.hpp @@ -31,6 +31,7 @@ public: *************************************************************************/ int getVer() const { return mHeader.mData.version; } + int getRecordCount() const { return mHeader.mData.records; } float getFVer() const { if(mHeader.mData.version == VER_12) return 1.2; else return 1.3; } const std::string getAuthor() const { return mHeader.mData.author.toString(); } const std::string getDesc() const { return mHeader.mData.desc.toString(); } diff --git a/components/esm/esmwriter.cpp b/components/esm/esmwriter.cpp index 91f123eb7..9d8d943d9 100644 --- a/components/esm/esmwriter.cpp +++ b/components/esm/esmwriter.cpp @@ -100,6 +100,9 @@ namespace ESM void ESMWriter::startSubRecord(const std::string& name) { + // Sub-record hierarchies are not properly supported in ESMReader. This should be fixed later. + assert (mRecords.size() <= 1); + writeName(name); RecordData rec; rec.name = name; diff --git a/components/esm/esmwriter.hpp b/components/esm/esmwriter.hpp index 33650e678..ca4f42217 100644 --- a/components/esm/esmwriter.hpp +++ b/components/esm/esmwriter.hpp @@ -29,7 +29,12 @@ class ESMWriter void setEncoder(ToUTF8::Utf8Encoder *encoding); void setAuthor(const std::string& author); void setDescription(const std::string& desc); + // Set the record count for writing it in the file header void setRecordCount (int count); + // Counts how many records we have actually written. + // It is a good idea to compare this with the value you wrote into the header (setRecordCount) + // It should be the record count you set + 1 (1 additional record for the TES3 header) + int getRecordCount() { return mRecordCount; } void setFormat (int format); void clearMaster(); @@ -69,6 +74,18 @@ class ESMWriter endRecord(name); } +private: + // Prevent using writeHNT with strings. This already happened by accident and results in + // state being discarded without any error on writing or reading it. :( + // writeHNString and friends must be used instead. + void writeHNT(const std::string &name, std::string data) + { + } + void writeT(const std::string& data) + { + } +public: + template void writeHNT(const std::string& name, const T& data, int size) { @@ -91,6 +108,7 @@ class ESMWriter void startRecord(const std::string& name, uint32_t flags = 0); void startRecord(uint32_t name, uint32_t flags = 0); + /// @note Sub-record hierarchies are not properly supported in ESMReader. This should be fixed later. void startSubRecord(const std::string& name); void endRecord(const std::string& name); void endRecord(uint32_t name); diff --git a/components/esm/fogstate.cpp b/components/esm/fogstate.cpp new file mode 100644 index 000000000..18235066d --- /dev/null +++ b/components/esm/fogstate.cpp @@ -0,0 +1,40 @@ +#include "fogstate.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" + +void ESM::FogState::load (ESMReader &esm) +{ + esm.getHNOT(mBounds, "BOUN"); + esm.getHNOT(mNorthMarkerAngle, "ANGL"); + while (esm.isNextSub("FTEX")) + { + esm.getSubHeader(); + FogTexture tex; + + esm.getT(tex.mX); + esm.getT(tex.mY); + + size_t imageSize = esm.getSubSize()-sizeof(int)*2; + tex.mImageData.resize(imageSize); + esm.getExact(&tex.mImageData[0], imageSize); + mFogTextures.push_back(tex); + } +} + +void ESM::FogState::save (ESMWriter &esm, bool interiorCell) const +{ + if (interiorCell) + { + esm.writeHNT("BOUN", mBounds); + esm.writeHNT("ANGL", mNorthMarkerAngle); + } + for (std::vector::const_iterator it = mFogTextures.begin(); it != mFogTextures.end(); ++it) + { + esm.startSubRecord("FTEX"); + esm.writeT(it->mX); + esm.writeT(it->mY); + esm.write(&it->mImageData[0], it->mImageData.size()); + esm.endRecord("FTEX"); + } +} diff --git a/components/esm/fogstate.hpp b/components/esm/fogstate.hpp new file mode 100644 index 000000000..4a5619e51 --- /dev/null +++ b/components/esm/fogstate.hpp @@ -0,0 +1,38 @@ +#ifndef OPENMW_ESM_FOGSTATE_H +#define OPENMW_ESM_FOGSTATE_H + +#include + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + struct FogTexture + { + int mX, mY; // Only used for interior cells + std::vector mImageData; + }; + + // format 0, saved games only + // Fog of war state + struct FogState + { + // Only used for interior cells + float mNorthMarkerAngle; + struct Bounds + { + float mMinX; + float mMinY; + float mMaxX; + float mMaxY; + } mBounds; + + std::vector mFogTextures; + + void load (ESMReader &esm); + void save (ESMWriter &esm, bool interiorCell) const; + }; +} + +#endif diff --git a/components/esm/globalmap.cpp b/components/esm/globalmap.cpp index 1fa5f907e..f17c071ff 100644 --- a/components/esm/globalmap.cpp +++ b/components/esm/globalmap.cpp @@ -14,6 +14,15 @@ void ESM::GlobalMap::load (ESMReader &esm) esm.getSubHeader(); mImageData.resize(esm.getSubSize()); esm.getExact(&mImageData[0], mImageData.size()); + + while (esm.isNextSub("MRK_")) + { + esm.getSubHeader(); + CellId cell; + esm.getT(cell.first); + esm.getT(cell.second); + mMarkers.push_back(cell); + } } void ESM::GlobalMap::save (ESMWriter &esm) const @@ -23,4 +32,12 @@ void ESM::GlobalMap::save (ESMWriter &esm) const esm.startSubRecord("DATA"); esm.write(&mImageData[0], mImageData.size()); esm.endRecord("DATA"); + + for (std::vector::const_iterator it = mMarkers.begin(); it != mMarkers.end(); ++it) + { + esm.startSubRecord("MRK_"); + esm.writeT(it->first); + esm.writeT(it->second); + esm.endRecord("MRK_"); + } } diff --git a/components/esm/globalmap.hpp b/components/esm/globalmap.hpp index 5d036c736..158f70a6e 100644 --- a/components/esm/globalmap.hpp +++ b/components/esm/globalmap.hpp @@ -25,6 +25,9 @@ namespace ESM std::vector mImageData; + typedef std::pair CellId; + std::vector mMarkers; + void load (ESMReader &esm); void save (ESMWriter &esm) const; }; diff --git a/components/esm/inventorystate.cpp b/components/esm/inventorystate.cpp index 4d8cbc622..1b0bc772e 100644 --- a/components/esm/inventorystate.cpp +++ b/components/esm/inventorystate.cpp @@ -47,6 +47,14 @@ void ESM::InventoryState::load (ESMReader &esm) mItems.push_back (std::make_pair (state, std::make_pair (id, slot))); } } + + while (esm.isNextSub("LEVM")) + { + std::string id = esm.getHString(); + int count; + esm.getHNT (count, "COUN"); + mLevelledItemMap[id] = count; + } } void ESM::InventoryState::save (ESMWriter &esm) const @@ -57,4 +65,10 @@ void ESM::InventoryState::save (ESMWriter &esm) const for (std::vector >::const_iterator iter (mLights.begin()); iter!=mLights.end(); ++iter) write (esm, iter->first, ESM::REC_LIGH, iter->second); -} \ No newline at end of file + + for (std::map::const_iterator it = mLevelledItemMap.begin(); it != mLevelledItemMap.end(); ++it) + { + esm.writeHNString ("LEVM", it->first); + esm.writeHNT ("COUN", it->second); + } +} diff --git a/components/esm/inventorystate.hpp b/components/esm/inventorystate.hpp index 3cfffbccc..4aa79f575 100644 --- a/components/esm/inventorystate.hpp +++ b/components/esm/inventorystate.hpp @@ -1,6 +1,8 @@ #ifndef OPENMW_ESM_INVENTORYSTATE_H #define OPENMW_ESM_INVENTORYSTATE_H +#include + #include "objectstate.hpp" #include "lightstate.hpp" @@ -20,6 +22,10 @@ namespace ESM // lights (slot) std::vector > mLights; + std::map mLevelledItemMap; + + virtual ~InventoryState() {} + virtual void load (ESMReader &esm); virtual void save (ESMWriter &esm) const; }; diff --git a/components/esm/loadcell.cpp b/components/esm/loadcell.cpp index 55d043c8a..0830c5de6 100644 --- a/components/esm/loadcell.cpp +++ b/components/esm/loadcell.cpp @@ -16,7 +16,7 @@ namespace { ///< Translate 8bit/24bit code (stored in refNum.mIndex) into a proper refNum - void adjustRefNum (ESM::CellRef::RefNum& refNum, ESM::ESMReader& reader) + void adjustRefNum (ESM::RefNum& refNum, ESM::ESMReader& reader) { int local = (refNum.mIndex & 0xff000000) >> 24; @@ -40,12 +40,12 @@ namespace ESM unsigned int Cell::sRecordId = REC_CELL; // Some overloaded compare operators. - bool operator== (const MovedCellRef& ref, const CellRef::RefNum& refNum) + bool operator== (const MovedCellRef& ref, const RefNum& refNum) { return ref.mRefNum == refNum; } - bool operator== (const CellRef& ref, const CellRef::RefNum& refNum) + bool operator== (const CellRef& ref, const RefNum& refNum) { return ref.mRefNum == refNum; } diff --git a/components/esm/loadcell.hpp b/components/esm/loadcell.hpp index f01c88c65..28204c9ee 100644 --- a/components/esm/loadcell.hpp +++ b/components/esm/loadcell.hpp @@ -28,7 +28,7 @@ class ESMWriter; class MovedCellRef { public: - CellRef::RefNum mRefNum; + RefNum mRefNum; // Target cell (if exterior) int mTarget[2]; @@ -39,8 +39,8 @@ public: }; /// Overloaded compare operator used to search inside a list of cell refs. -bool operator==(const MovedCellRef& ref, const CellRef::RefNum& refNum); -bool operator==(const CellRef& ref, const CellRef::RefNum& refNum); +bool operator==(const MovedCellRef& ref, const RefNum& refNum); +bool operator==(const CellRef& ref, const RefNum& refNum); typedef std::list MovedCellRefTracker; typedef std::list CellRefTracker; diff --git a/components/esm/loadclot.cpp b/components/esm/loadclot.cpp index d64564d77..17ecdf3ae 100644 --- a/components/esm/loadclot.cpp +++ b/components/esm/loadclot.cpp @@ -8,33 +8,34 @@ namespace ESM { unsigned int Clothing::sRecordId = REC_CLOT; -void Clothing::load(ESMReader &esm) -{ - mModel = esm.getHNString("MODL"); - mName = esm.getHNOString("FNAM"); - esm.getHNT(mData, "CTDT", 12); + void Clothing::load(ESMReader &esm) + { + mModel = esm.getHNString("MODL"); + mName = esm.getHNOString("FNAM"); + esm.getHNT(mData, "CTDT", 12); - mScript = esm.getHNOString("SCRI"); - mIcon = esm.getHNOString("ITEX"); + mScript = esm.getHNOString("SCRI"); + mIcon = esm.getHNOString("ITEX"); - mParts.load(esm); + mParts.load(esm); - mEnchant = esm.getHNOString("ENAM"); -} -void Clothing::save(ESMWriter &esm) const -{ - esm.writeHNCString("MODL", mModel); - esm.writeHNOCString("FNAM", mName); - esm.writeHNT("CTDT", mData, 12); + mEnchant = esm.getHNOString("ENAM"); + } - esm.writeHNOCString("SCRI", mScript); - esm.writeHNOCString("ITEX", mIcon); + void Clothing::save(ESMWriter &esm) const + { + esm.writeHNCString("MODL", mModel); + esm.writeHNOCString("FNAM", mName); + esm.writeHNT("CTDT", mData, 12); - mParts.save(esm); + esm.writeHNOCString("SCRI", mScript); + esm.writeHNOCString("ITEX", mIcon); - esm.writeHNOCString("ENAM", mEnchant); -} + mParts.save(esm); + + esm.writeHNOCString("ENAM", mEnchant); + } void Clothing::blank() { @@ -46,7 +47,6 @@ void Clothing::save(ESMWriter &esm) const mName.clear(); mModel.clear(); mIcon.clear(); - mIcon.clear(); mEnchant.clear(); mScript.clear(); } diff --git a/components/esm/loadcrea.hpp b/components/esm/loadcrea.hpp index 817c0e43c..e459dded7 100644 --- a/components/esm/loadcrea.hpp +++ b/components/esm/loadcrea.hpp @@ -33,7 +33,7 @@ struct Creature Respawn = 0x002, Weapon = 0x004, // Has weapon and shield - None = 0x008, // ?? + None = 0x008, // ?? This flag appears set for every creature in Morrowind.esm Essential = 0x080, // Blood types diff --git a/components/esm/loaddial.cpp b/components/esm/loaddial.cpp index fb43ee858..ee7ddbfad 100644 --- a/components/esm/loaddial.cpp +++ b/components/esm/loaddial.cpp @@ -1,5 +1,7 @@ #include "loaddial.hpp" +#include + #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" @@ -43,4 +45,50 @@ void Dialogue::save(ESMWriter &esm) const mInfo.clear(); } +void Dialogue::addInfo(const ESM::DialInfo& info, bool merge) +{ + if (!merge || mInfo.empty() || info.mNext.empty()) + { + mLookup[info.mId] = mInfo.insert(mInfo.end(), info); + return; + } + if (info.mPrev.empty()) + { + mLookup[info.mId] = mInfo.insert(mInfo.begin(), info); + return; + } + + ESM::Dialogue::InfoContainer::iterator it = mInfo.end(); + + std::map::iterator lookup; + + lookup = mLookup.find(info.mId); + if (lookup != mLookup.end()) + { + it = lookup->second; + *it = info; + return; + } + + lookup = mLookup.find(info.mPrev); + if (lookup != mLookup.end()) + { + it = lookup->second; + + mLookup[info.mId] = mInfo.insert(++it, info); + return; + } + + lookup = mLookup.find(info.mNext); + if (lookup != mLookup.end()) + { + it = lookup->second; + + mLookup[info.mId] = mInfo.insert(it, info); + return; + } + + std::cerr << "Failed to insert info " << info.mId << std::endl; +} + } diff --git a/components/esm/loaddial.hpp b/components/esm/loaddial.hpp index 63d78833e..6ec5527f9 100644 --- a/components/esm/loaddial.hpp +++ b/components/esm/loaddial.hpp @@ -2,7 +2,8 @@ #define OPENMW_ESM_DIAL_H #include -#include +#include +#include #include "loadinfo.hpp" @@ -33,11 +34,22 @@ struct Dialogue std::string mId; signed char mType; - std::vector mInfo; + + typedef std::list InfoContainer; + + typedef std::map LookupMap; + + InfoContainer mInfo; + + // This is only used during the loading phase to speed up DialInfo merging. + LookupMap mLookup; void load(ESMReader &esm); void save(ESMWriter &esm) const; + /// @param merge Merge with existing list, or just push each record to the end of the list? + void addInfo (const ESM::DialInfo& info, bool merge); + void blank(); ///< Set record to default state (does not touch the ID and does not change the type). }; diff --git a/components/esm/loadfact.cpp b/components/esm/loadfact.cpp index 84be21938..0924efb17 100644 --- a/components/esm/loadfact.cpp +++ b/components/esm/loadfact.cpp @@ -44,10 +44,10 @@ void Faction::load(ESMReader &esm) // Read faction response values while (esm.hasMoreSubs()) { - Reaction r; - r.mFaction = esm.getHNString("ANAM"); - esm.getHNT(r.mReaction, "INTV"); - mReactions.push_back(r); + std::string faction = esm.getHNString("ANAM"); + int reaction; + esm.getHNT(reaction, "INTV"); + mReactions[faction] = reaction; } } void Faction::save(ESMWriter &esm) const @@ -64,10 +64,10 @@ void Faction::save(ESMWriter &esm) const esm.writeHNT("FADT", mData, 240); - for (std::vector::const_iterator it = mReactions.begin(); it != mReactions.end(); ++it) + for (std::map::const_iterator it = mReactions.begin(); it != mReactions.end(); ++it) { - esm.writeHNString("ANAM", it->mFaction); - esm.writeHNT("INTV", it->mReaction); + esm.writeHNString("ANAM", it->first); + esm.writeHNT("INTV", it->second); } } diff --git a/components/esm/loadfact.hpp b/components/esm/loadfact.hpp index 9c257e068..75e30a5bf 100644 --- a/components/esm/loadfact.hpp +++ b/components/esm/loadfact.hpp @@ -2,7 +2,7 @@ #define OPENMW_ESM_FACT_H #include -#include +#include namespace ESM { @@ -53,13 +53,8 @@ struct Faction FADTstruct mData; - struct Reaction - { - std::string mFaction; - int mReaction; - }; - - std::vector mReactions; + // + std::map mReactions; // Name of faction ranks (may be empty for NPC factions) std::string mRanks[10]; diff --git a/components/esm/loadnpc.hpp b/components/esm/loadnpc.hpp index 08f678b45..fd3e45bdc 100644 --- a/components/esm/loadnpc.hpp +++ b/components/esm/loadnpc.hpp @@ -82,7 +82,7 @@ struct NPC char mSkills[Skill::Length]; char mReputation; - short mHealth, mMana, mFatigue; + unsigned short mHealth, mMana, mFatigue; char mDisposition, mFactionID, mRank; char mUnknown; int mGold; diff --git a/components/esm/loadpgrd.cpp b/components/esm/loadpgrd.cpp index 3b5330e9f..efdbdd86b 100644 --- a/components/esm/loadpgrd.cpp +++ b/components/esm/loadpgrd.cpp @@ -8,6 +8,20 @@ namespace ESM { unsigned int Pathgrid::sRecordId = REC_PGRD; + Pathgrid::Point& Pathgrid::Point::operator=(const float rhs[3]) { + mX = rhs[0]; + mY = rhs[1]; + mZ = rhs[2]; + return *this; + } + Pathgrid::Point::Point(const float rhs[3]) { + mX = rhs[0]; + mY = rhs[1]; + mZ = rhs[2]; + } + Pathgrid::Point::Point():mX(0),mY(0),mZ(0) { + } + void Pathgrid::load(ESMReader &esm) { esm.getHNT(mData, "DATA", 12); diff --git a/components/esm/loadpgrd.hpp b/components/esm/loadpgrd.hpp index 9ee49552d..60a736991 100644 --- a/components/esm/loadpgrd.hpp +++ b/components/esm/loadpgrd.hpp @@ -31,6 +31,10 @@ struct Pathgrid unsigned char mAutogenerated; // autogenerated vs. user coloring flag? unsigned char mConnectionNum; // number of connections for this point short mUnknown; + Point& operator=(const float[3]); + Point(const float[3]); + Point(); + Point(int x, int y, int z) : mX(x), mY(y), mZ(z) {} }; // 16 bytes struct Edge // path grid edge diff --git a/components/esm/loadscpt.cpp b/components/esm/loadscpt.cpp index de679e815..aee8628bd 100644 --- a/components/esm/loadscpt.cpp +++ b/components/esm/loadscpt.cpp @@ -38,6 +38,8 @@ void Script::load(ESMReader &esm) char* str = &tmp[0]; for (size_t i = 0; i < mVarNames.size(); i++) { + char *termsym = strchr(str, '\r'); + if(termsym) *termsym = '\0'; mVarNames[i] = std::string(str); str += mVarNames[i].size() + 1; diff --git a/components/esm/loadtes3.hpp b/components/esm/loadtes3.hpp index 5614d295f..eb5e14daf 100644 --- a/components/esm/loadtes3.hpp +++ b/components/esm/loadtes3.hpp @@ -28,7 +28,7 @@ namespace ESM int type; // 0=esp, 1=esm, 32=ess (unused) NAME32 author; // Author's name NAME256 desc; // File description - int records; // Number of records? Not used. + int records; // Number of records }; // Defines another files (esm or esp) that this file depends upon. @@ -52,4 +52,4 @@ namespace ESM } -#endif \ No newline at end of file +#endif diff --git a/components/esm/npcstats.cpp b/components/esm/npcstats.cpp index 80238ad68..3fa954182 100644 --- a/components/esm/npcstats.cpp +++ b/components/esm/npcstats.cpp @@ -36,6 +36,18 @@ void ESM::NpcStats::load (ESMReader &esm) mSkills[i].mWerewolf.load (esm); } + bool hasWerewolfAttributes = false; + esm.getHNOT (hasWerewolfAttributes, "HWAT"); + + if (hasWerewolfAttributes) + { + for (int i=0; i<8; ++i) + mWerewolfAttributes[i].load (esm); + } + + mIsWerewolf = false; + esm.getHNOT (mIsWerewolf, "WOLF"); + mBounty = 0; esm.getHNOT (mBounty, "BOUN"); @@ -48,8 +60,9 @@ void ESM::NpcStats::load (ESMReader &esm) mProfit = 0; esm.getHNOT (mProfit, "PROF"); - mAttackStrength = 0; - esm.getHNOT (mAttackStrength, "ASTR"); + // No longer used. Now part of CreatureStats. + float attackStrength = 0; + esm.getHNOT (attackStrength, "ASTR"); mLevelProgress = 0; esm.getHNOT (mLevelProgress, "LPRO"); @@ -101,6 +114,13 @@ void ESM::NpcStats::save (ESMWriter &esm) const mSkills[i].mWerewolf.save (esm); } + esm.writeHNT ("HWAT", true); + for (int i=0; i<8; ++i) + mWerewolfAttributes[i].save (esm); + + if (mIsWerewolf) + esm.writeHNT ("WOLF", mIsWerewolf); + if (mBounty) esm.writeHNT ("BOUN", mBounty); @@ -113,9 +133,6 @@ void ESM::NpcStats::save (ESMWriter &esm) const if (mProfit) esm.writeHNT ("PROF", mProfit); - if (mAttackStrength) - esm.writeHNT ("ASTR", mAttackStrength); - if (mLevelProgress) esm.writeHNT ("LPRO", mLevelProgress); @@ -123,7 +140,7 @@ void ESM::NpcStats::save (ESMWriter &esm) const for (std::vector::const_iterator iter (mUsedIds.begin()); iter!=mUsedIds.end(); ++iter) - esm.writeHNT ("USED", *iter); + esm.writeHNString ("USED", *iter); if (mTimeToStartDrowning) esm.writeHNT ("DRTI", mTimeToStartDrowning); @@ -136,4 +153,4 @@ void ESM::NpcStats::save (ESMWriter &esm) const if (mCrimeId != -1) esm.writeHNT ("CRID", mCrimeId); -} \ No newline at end of file +} diff --git a/components/esm/npcstats.hpp b/components/esm/npcstats.hpp index 504cd0163..ce7c75d2a 100644 --- a/components/esm/npcstats.hpp +++ b/components/esm/npcstats.hpp @@ -31,6 +31,9 @@ namespace ESM Faction(); }; + StatState mWerewolfAttributes[8]; + bool mIsWerewolf; + std::map mFactions; int mDisposition; Skill mSkills[27]; @@ -38,7 +41,6 @@ namespace ESM int mReputation; int mWerewolfKills; int mProfit; - float mAttackStrength; int mLevelProgress; int mSkillIncrease[8]; std::vector mUsedIds; @@ -52,4 +54,4 @@ namespace ESM }; } -#endif \ No newline at end of file +#endif diff --git a/components/esm/player.cpp b/components/esm/player.cpp index e41cc535e..52b44c945 100644 --- a/components/esm/player.cpp +++ b/components/esm/player.cpp @@ -26,8 +26,10 @@ void ESM::Player::load (ESMReader &esm) mBirthsign = esm.getHNString ("SIGN"); - esm.getHNT (mCurrentCrimeId, "CURD"); - esm.getHNT (mPayedCrimeId, "PAYD"); + mCurrentCrimeId = -1; + esm.getHNOT (mCurrentCrimeId, "CURD"); + mPaidCrimeId = -1; + esm.getHNOT (mPaidCrimeId, "PAYD"); } void ESM::Player::save (ESMWriter &esm) const @@ -50,5 +52,5 @@ void ESM::Player::save (ESMWriter &esm) const esm.writeHNString ("SIGN", mBirthsign); esm.writeHNT ("CURD", mCurrentCrimeId); - esm.writeHNT ("PAYD", mPayedCrimeId); -} \ No newline at end of file + esm.writeHNT ("PAYD", mPaidCrimeId); +} diff --git a/components/esm/player.hpp b/components/esm/player.hpp index 377c8547a..440d0a2d9 100644 --- a/components/esm/player.hpp +++ b/components/esm/player.hpp @@ -26,11 +26,11 @@ namespace ESM std::string mBirthsign; int mCurrentCrimeId; - int mPayedCrimeId; + int mPaidCrimeId; void load (ESMReader &esm); void save (ESMWriter &esm) const; }; } -#endif \ No newline at end of file +#endif diff --git a/components/esm/projectilestate.cpp b/components/esm/projectilestate.cpp new file mode 100644 index 000000000..85c00025b --- /dev/null +++ b/components/esm/projectilestate.cpp @@ -0,0 +1,65 @@ +#include "projectilestate.hpp" + +#include "esmwriter.hpp" +#include "esmreader.hpp" + +namespace ESM +{ + + void BaseProjectileState::save(ESMWriter &esm) const + { + esm.writeHNString ("ID__", mId); + esm.writeHNT ("VEC3", mPosition); + esm.writeHNT ("QUAT", mOrientation); + esm.writeHNT ("ACTO", mActorId); + } + + void BaseProjectileState::load(ESMReader &esm) + { + mId = esm.getHNString("ID__"); + esm.getHNT (mPosition, "VEC3"); + esm.getHNT (mOrientation, "QUAT"); + esm.getHNT (mActorId, "ACTO"); + } + + void MagicBoltState::save(ESMWriter &esm) const + { + BaseProjectileState::save(esm); + + esm.writeHNString ("SPEL", mSpellId); + esm.writeHNString ("SRCN", mSourceName); + mEffects.save(esm); + esm.writeHNT ("SPED", mSpeed); + esm.writeHNT ("STCK", mStack); + esm.writeHNString ("SOUN", mSound); + } + + void MagicBoltState::load(ESMReader &esm) + { + BaseProjectileState::load(esm); + + mSpellId = esm.getHNString("SPEL"); + mSourceName = esm.getHNString ("SRCN"); + mEffects.load(esm); + esm.getHNT (mSpeed, "SPED"); + esm.getHNT (mStack, "STCK"); + mSound = esm.getHNString ("SOUN"); + } + + void ProjectileState::save(ESMWriter &esm) const + { + BaseProjectileState::save(esm); + + esm.writeHNString ("BOW_", mBowId); + esm.writeHNT ("VEL_", mVelocity); + } + + void ProjectileState::load(ESMReader &esm) + { + BaseProjectileState::load(esm); + + mBowId = esm.getHNString ("BOW_"); + esm.getHNT (mVelocity, "VEL_"); + } + +} diff --git a/components/esm/projectilestate.hpp b/components/esm/projectilestate.hpp new file mode 100644 index 000000000..6e36efb5b --- /dev/null +++ b/components/esm/projectilestate.hpp @@ -0,0 +1,90 @@ +#ifndef OPENMW_ESM_PROJECTILESTATE_H +#define OPENMW_ESM_PROJECTILESTATE_H + +#include + +#include +#include + +#include "effectlist.hpp" + +namespace ESM +{ + + // format 0, savegames only + + struct Quaternion + { + float mValues[4]; + + Quaternion() {} + Quaternion (Ogre::Quaternion q) + { + mValues[0] = q.w; + mValues[1] = q.x; + mValues[2] = q.y; + mValues[3] = q.z; + } + + operator Ogre::Quaternion () const + { + return Ogre::Quaternion(mValues[0], mValues[1], mValues[2], mValues[3]); + } + }; + + struct Vector3 + { + float mValues[3]; + + Vector3() {} + Vector3 (Ogre::Vector3 v) + { + mValues[0] = v.x; + mValues[1] = v.y; + mValues[2] = v.z; + } + + operator Ogre::Vector3 () const + { + return Ogre::Vector3(&mValues[0]); + } + }; + + struct BaseProjectileState + { + std::string mId; + + Vector3 mPosition; + Quaternion mOrientation; + + int mActorId; + + void load (ESMReader &esm); + void save (ESMWriter &esm) const; + }; + + struct MagicBoltState : public BaseProjectileState + { + std::string mSpellId; + std::string mSourceName; + ESM::EffectList mEffects; + float mSpeed; + bool mStack; + std::string mSound; + + void load (ESMReader &esm); + void save (ESMWriter &esm) const; + }; + + struct ProjectileState : public BaseProjectileState + { + std::string mBowId; + Vector3 mVelocity; + + void load (ESMReader &esm); + void save (ESMWriter &esm) const; + }; + +} + +#endif diff --git a/components/esm/quickkeys.cpp b/components/esm/quickkeys.cpp new file mode 100644 index 000000000..42cd91c5b --- /dev/null +++ b/components/esm/quickkeys.cpp @@ -0,0 +1,42 @@ +#include "quickkeys.hpp" + +#include "esmwriter.hpp" +#include "esmreader.hpp" + +namespace ESM +{ + + void QuickKeys::load(ESMReader &esm) + { + if (esm.isNextSub("KEY_")) + esm.getSubHeader(); // no longer used, because sub-record hierachies do not work properly in esmreader + + while (esm.isNextSub("TYPE")) + { + int keyType; + esm.getHT(keyType); + std::string id; + id = esm.getHNString("ID__"); + + QuickKey key; + key.mType = keyType; + key.mId = id; + + mKeys.push_back(key); + + if (esm.isNextSub("KEY_")) + esm.getSubHeader(); // no longer used, because sub-record hierachies do not work properly in esmreader + } + } + + void QuickKeys::save(ESMWriter &esm) const + { + for (std::vector::const_iterator it = mKeys.begin(); it != mKeys.end(); ++it) + { + esm.writeHNT("TYPE", it->mType); + esm.writeHNString("ID__", it->mId); + } + } + + +} diff --git a/components/esm/quickkeys.hpp b/components/esm/quickkeys.hpp new file mode 100644 index 000000000..c52466b13 --- /dev/null +++ b/components/esm/quickkeys.hpp @@ -0,0 +1,28 @@ +#ifndef OPENMW_COMPONENTS_ESM_QUICKKEYS_H +#define OPENMW_COMPONENTS_ESM_QUICKKEYS_H + +#include +#include + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + struct QuickKeys + { + struct QuickKey + { + int mType; + std::string mId; // Spell or Item ID + }; + + std::vector mKeys; + + void load (ESMReader &esm); + void save (ESMWriter &esm) const; + }; + +} + +#endif diff --git a/components/esm/spellstate.cpp b/components/esm/spellstate.cpp new file mode 100644 index 000000000..2dca2dcec --- /dev/null +++ b/components/esm/spellstate.cpp @@ -0,0 +1,66 @@ +#include "spellstate.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" + +namespace ESM +{ + + void SpellState::load(ESMReader &esm) + { + while (esm.isNextSub("SPEL")) + { + std::string id = esm.getHString(); + + std::map random; + while (esm.isNextSub("INDX")) + { + int index; + esm.getHT(index); + + float magnitude; + esm.getHNT(magnitude, "RAND"); + + random[index] = magnitude; + } + + mSpells[id] = random; + } + + while (esm.isNextSub("USED")) + { + std::string id = esm.getHString(); + TimeStamp time; + esm.getHNT(time, "TIME"); + + mUsedPowers[id] = time; + } + + mSelectedSpell = esm.getHNOString("SLCT"); + } + + void SpellState::save(ESMWriter &esm) const + { + for (TContainer::const_iterator it = mSpells.begin(); it != mSpells.end(); ++it) + { + esm.writeHNString("SPEL", it->first); + + const std::map& random = it->second; + for (std::map::const_iterator rIt = random.begin(); rIt != random.end(); ++rIt) + { + esm.writeHNT("INDX", rIt->first); + esm.writeHNT("RAND", rIt->second); + } + } + + for (std::map::const_iterator it = mUsedPowers.begin(); it != mUsedPowers.end(); ++it) + { + esm.writeHNString("USED", it->first); + esm.writeHNT("TIME", it->second); + } + + if (!mSelectedSpell.empty()) + esm.writeHNString("SLCT", mSelectedSpell); + } + +} diff --git a/components/esm/spellstate.hpp b/components/esm/spellstate.hpp new file mode 100644 index 000000000..cb5c0ff0d --- /dev/null +++ b/components/esm/spellstate.hpp @@ -0,0 +1,29 @@ +#ifndef OPENMW_ESM_SPELLSTATE_H +#define OPENMW_ESM_SPELLSTATE_H + +#include +#include + +#include "defs.hpp" + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + struct SpellState + { + typedef std::map > TContainer; + TContainer mSpells; + + std::map mUsedPowers; + + std::string mSelectedSpell; + + void load (ESMReader &esm); + void save (ESMWriter &esm) const; + }; + +} + +#endif diff --git a/components/file_finder/file_finder.hpp b/components/file_finder/file_finder.hpp deleted file mode 100644 index 8a15af73a..000000000 --- a/components/file_finder/file_finder.hpp +++ /dev/null @@ -1,142 +0,0 @@ -#ifndef FILE_FINDER_MAIN_H -#define FILE_FINDER_MAIN_H - -#include - -#include "search.hpp" -#include "filename_less.hpp" -#include - -namespace FileFinder -{ - -template -class FileFinderT -{ - typedef std::map TableContainer; - TableContainer table; - - struct Inserter : ReturnPath - { - FileFinderT *owner; - int cut; - - void add(const boost::filesystem::path &pth) - { - std::string file = pth.string(); - std::string key = file.substr(cut); - owner->table[key] = file; - } - }; - - Inserter inserter; - -public: - FileFinderT(const boost::filesystem::path &path, bool recurse=true) - { - inserter.owner = this; - - // Remember the original path length, so we can cut it away from - // the relative paths used as keys - const std::string& pstring = path.string(); - inserter.cut = pstring.size(); - - // If the path does not end in a slash, then boost will add one - // later, which means one more character we have to remove. - char last = *pstring.rbegin(); - if(last != '\\' && last != '/') - inserter.cut++; - - // Fill the map - find(path, inserter, recurse); - } - - bool has(const std::string& file) const - { - return table.find(file) != table.end(); - } - - // Find the full path from a relative path. - const std::string &lookup(const std::string& file) const - { - static std::string empty; - typename TableContainer::const_iterator it = table.find(file); - return (it != table.end()) ? it->second : empty; - } -}; - -template -< - class LESS -> -struct TreeFileFinder -{ - typedef TreeFileFinder finder_t; - - TreeFileFinder(const Files::PathContainer& paths, bool recurse = true) - { - struct : ReturnPath - { - finder_t *owner; - int cut; - - void add(const boost::filesystem::path &pth) - { - std::string file = pth.string(); - std::string key = file.substr(cut); - owner->mTable[key] = file; - } - } inserter; - - inserter.owner = this; - - for (Files::PathContainer::const_iterator it = paths.begin(); it != paths.end(); ++it) - { - - // Remember the original path length, so we can cut it away from - // the relative paths used as keys - const std::string& pstring = it->string(); - inserter.cut = pstring.size(); - - // If the path does not end in a slash, then boost will add one - // later, which means one more character we have to remove. - char last = *pstring.rbegin(); - if (last != '\\' && last != '/') - { - inserter.cut++; - } - - // Fill the map - find(*it, inserter, recurse); - } - } - - bool has(const std::string& file) const - { - return mTable.find(file) != mTable.end(); - } - - const std::string& lookup(const std::string& file) const - { - static std::string empty; - typename TableContainer::const_iterator it = mTable.find(file); - return (it != mTable.end()) ? it->second : empty; - } - - private: - typedef std::map TableContainer; - TableContainer mTable; - -// Inserter inserter; -}; - - -// The default is to use path_less for equality checks -typedef FileFinderT FileFinder; -typedef FileFinderT FileFinderStrict; - -typedef TreeFileFinder LessTreeFileFinder; -typedef TreeFileFinder StrictTreeFileFinder; - -} /* namespace FileFinder */ -#endif /* FILE_FINDER_MAIN_H */ diff --git a/components/file_finder/filename_less.hpp b/components/file_finder/filename_less.hpp deleted file mode 100644 index bc3186ce9..000000000 --- a/components/file_finder/filename_less.hpp +++ /dev/null @@ -1,84 +0,0 @@ -#ifndef FILE_FINDER_LESS_H -#define FILE_FINDER_LESS_H - -#include -#include - -namespace FileFinder{ - -// Used for maps of file paths. Compares file paths, but ignores case -// AND treats \ and / as the same character. -struct path_less -{ - int compareChar(char a, char b) const - { - if(a>b) return 1; - else if(a= 'a' && a <= 'z') a += 'A'-'a'; - else if(a == '\\') a = '/'; - if(b >= 'a' && b <= 'z') b += 'A'-'a'; - else if(b == '\\') b = '/'; - return compareChar(a,b); - } - - int compareString(const char *a, const char *b) const - { - while(*a && *b) - { - int i = comparePathChar(*a,*b); - if(i != 0) return i; - a++; b++; - } - // At this point, one or both of the chars is a null terminator. - // Normal char comparison will get the correct final result here. - return compareChar(*a,*b); - } - - bool operator() (const std::string& a, const std::string& b) const - { - return compareString(a.c_str(), b.c_str()) < 0; - } -}; - -struct path_slash -{ - int compareChar(char a, char b) const - { - if(a>b) return 1; - else if(a - -void FileFinder::find(const boost::filesystem::path & dir_path, ReturnPath &ret, bool recurse) -{ - if (boost::filesystem::exists(dir_path)) - { - if (!recurse) - { - boost::filesystem::directory_iterator end_itr; // default construction yields past-the-end - for (boost::filesystem::directory_iterator itr(dir_path); itr != end_itr; ++itr) - { - if (!boost::filesystem::is_directory( *itr )) - { - ret.add(*itr); - } - } - } - else - { - boost::filesystem::recursive_directory_iterator end_itr; // default construction yields past-the-end - for (boost::filesystem::recursive_directory_iterator itr(dir_path); itr != end_itr; ++itr) - { - if (!boost::filesystem::is_directory(*itr)) - { - ret.add(*itr); - } - } - } - } - else - { - std::cout << "Path " << dir_path << " not found" << std::endl; - } -} diff --git a/components/file_finder/search.hpp b/components/file_finder/search.hpp deleted file mode 100644 index 4e16fb64a..000000000 --- a/components/file_finder/search.hpp +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef FILE_FINDER_SEARCH_H -#define FILE_FINDER_SEARCH_H - -#include -#include - -namespace FileFinder -{ - struct ReturnPath - { - virtual void add(const boost::filesystem::path &pth) = 0; - }; - - /** Search the given path and return all file paths through 'ret'. If - recurse==true, all files in subdirectories are returned as well. - */ - void find(const boost::filesystem::path & dir_path, ReturnPath &ret, bool recurse=true); -} - -#endif diff --git a/components/files/collections.cpp b/components/files/collections.cpp index c6195d88c..a933eb682 100644 --- a/components/files/collections.cpp +++ b/components/files/collections.cpp @@ -1,6 +1,7 @@ - #include "collections.hpp" +#include + namespace Files { Collections::Collections() @@ -36,9 +37,19 @@ namespace Files for (Files::PathContainer::const_iterator iter = mDirectories.begin(); iter != mDirectories.end(); ++iter) { - const boost::filesystem::path path = *iter / file; - if (boost::filesystem::exists(path)) - return path.string(); + for (boost::filesystem::directory_iterator iter2 (*iter); + iter2!=boost::filesystem::directory_iterator(); ++iter2) + { + boost::filesystem::path path = *iter2; + + if (mFoldCase) + { + if (Misc::StringUtils::ciEqual(file, path.filename().string())) + return path.string(); + } + else if (path.filename().string() == file) + return path.string(); + } } throw std::runtime_error ("file " + file + " not found"); @@ -49,9 +60,19 @@ namespace Files for (Files::PathContainer::const_iterator iter = mDirectories.begin(); iter != mDirectories.end(); ++iter) { - const boost::filesystem::path path = *iter / file; - if (boost::filesystem::exists(path)) - return true; + for (boost::filesystem::directory_iterator iter2 (*iter); + iter2!=boost::filesystem::directory_iterator(); ++iter2) + { + boost::filesystem::path path = *iter2; + + if (mFoldCase) + { + if (Misc::StringUtils::ciEqual(file, path.filename().string())) + return true; + } + else if (path.filename().string() == file) + return true; + } } return false; diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index 761b7ca5a..ffa911b44 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -1,12 +1,12 @@ #include "configurationmanager.hpp" #include -#include #include #include #include #include +#include /** * \namespace Files @@ -125,7 +125,7 @@ void ConfigurationManager::loadConfig(const boost::filesystem::path& path, { std::cout << "Loading config file: " << cfgFile.string() << "... "; - std::ifstream configFileStream(cfgFile.string().c_str()); + boost::filesystem::ifstream configFileStream(cfgFile); if (configFileStream.is_open()) { boost::program_options::store(boost::program_options::parse_config_file( diff --git a/components/files/lowlevelfile.cpp b/components/files/lowlevelfile.cpp index 06ee9fb4e..4cdeec043 100644 --- a/components/files/lowlevelfile.cpp +++ b/components/files/lowlevelfile.cpp @@ -206,6 +206,8 @@ size_t LowLevelFile::read (void * data, size_t size) } #elif FILE_API == FILE_API_WIN32 + +#include /* * * Implementation of LowLevelFile methods using Win32 API calls @@ -227,9 +229,10 @@ void LowLevelFile::open (char const * filename) { assert (mHandle == INVALID_HANDLE_VALUE); - HANDLE handle = CreateFileA (filename, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0); + std::wstring wname = boost::locale::conv::utf_to_utf(filename); + HANDLE handle = CreateFileW (wname.c_str(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0); - if (handle == NULL) + if (handle == INVALID_HANDLE_VALUE) { std::ostringstream os; os << "Failed to open '" << filename << "' for reading."; diff --git a/components/files/multidircollection.cpp b/components/files/multidircollection.cpp index 347de96a6..1abbae3ae 100644 --- a/components/files/multidircollection.cpp +++ b/components/files/multidircollection.cpp @@ -60,10 +60,10 @@ namespace Files { boost::filesystem::path path = *dirIter; - if (!equal (extension, boost::filesystem::path (path.extension()).string())) + if (!equal (extension, path.extension().string())) continue; - std::string filename = boost::filesystem::path (path.filename()).string(); + std::string filename = path.filename().string(); TIter result = mFiles.find (filename); diff --git a/components/files/windowspath.cpp b/components/files/windowspath.cpp index ea1ca56d3..6b58304a0 100644 --- a/components/files/windowspath.cpp +++ b/components/files/windowspath.cpp @@ -10,8 +10,12 @@ #pragma comment(lib, "Shlwapi.lib") +#include +namespace bconv = boost::locale::conv; + /** * FIXME: Someone with Windows system should check this and correct if necessary + * FIXME: MAX_PATH is irrelevant for extended-length paths, i.e. \\?\... */ /** @@ -23,22 +27,29 @@ namespace Files WindowsPath::WindowsPath(const std::string& application_name) : mName(application_name) { + /* Since on Windows boost::path.string() returns string of narrow + characters in local encoding, it is required to path::imbue() + with UTF-8 encoding (generated for empty name from boost::locale) + to handle Unicode in platform-agnostic way using std::string. + + See boost::filesystem and boost::locale reference for details. + */ + boost::filesystem::path::imbue(boost::locale::generator().generate("")); } boost::filesystem::path WindowsPath::getUserConfigPath() const { boost::filesystem::path userPath("."); - TCHAR path[MAX_PATH]; + WCHAR path[MAX_PATH + 1]; memset(path, 0, sizeof(path)); - if(SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL | CSIDL_FLAG_CREATE, NULL, 0, path))) + if(SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_PERSONAL | CSIDL_FLAG_CREATE, NULL, 0, path))) { - PathAppend(path, TEXT("My Games")); - userPath = boost::filesystem::path(path); + userPath = boost::filesystem::path(bconv::utf_to_utf(path)); } - return userPath / mName; + return userPath / "My Games" / mName; } boost::filesystem::path WindowsPath::getUserDataPath() const @@ -51,12 +62,12 @@ boost::filesystem::path WindowsPath::getGlobalConfigPath() const { boost::filesystem::path globalPath("."); - TCHAR path[MAX_PATH]; + WCHAR path[MAX_PATH + 1]; memset(path, 0, sizeof(path)); - if(SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PROGRAM_FILES | CSIDL_FLAG_CREATE, NULL, 0, path))) + if(SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_PROGRAM_FILES | CSIDL_FLAG_CREATE, NULL, 0, path))) { - globalPath = boost::filesystem::path(path); + globalPath = boost::filesystem::path(bconv::utf_to_utf(path)); } return globalPath / mName; diff --git a/components/loadinglistener/loadinglistener.hpp b/components/loadinglistener/loadinglistener.hpp index 483d52491..f6a7b71e9 100644 --- a/components/loadinglistener/loadinglistener.hpp +++ b/components/loadinglistener/loadinglistener.hpp @@ -17,7 +17,7 @@ namespace Loading virtual void setProgressRange (size_t range) = 0; virtual void setProgress (size_t value) = 0; - virtual void increaseProgress (size_t increase) = 0; + virtual void increaseProgress (size_t increase = 1) = 0; /// Indicate the scene is now ready to be shown virtual void removeWallpaper() = 0; diff --git a/components/misc/slice_array.hpp b/components/misc/slice_array.hpp deleted file mode 100644 index cd58e7bd6..000000000 --- a/components/misc/slice_array.hpp +++ /dev/null @@ -1,82 +0,0 @@ -/* - OpenMW - The completely unofficial reimplementation of Morrowind - Copyright (C) 2008-2010 Nicolay Korslund - Email: < korslund@gmail.com > - WWW: http://openmw.sourceforge.net/ - - This file (slice_array.h) is part of the OpenMW package. - - OpenMW is distributed as free software: you can redistribute it - and/or modify it under the terms of the GNU General Public License - version 3, as published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - version 3 along with this program. If not, see - http://www.gnu.org/licenses/ . - - */ - -#ifndef MISC_SLICE_ARRAY_H -#define MISC_SLICE_ARRAY_H - -// A simple array implementation containing a pointer and a -// length. Used for holding slices into a data buffer. -#include -#include - -namespace Misc -{ - -template -struct SliceArray -{ - const T* ptr; - size_t length; - - /// Initialize to zero length - SliceArray() : ptr(0), length(0) {} - - /// Initialize from pointer + length - SliceArray(const T* _ptr, size_t _length) - : ptr(_ptr), length(_length) {} - - /// Initialize from null-terminated string - SliceArray(const char* str) - { - ptr = str; - length = strlen(str); - } - - bool operator==(SliceArray &t) - { - return - length == t.length && - (memcmp(ptr,t.ptr, length*sizeof(T)) == 0); - } - - /// Only use this for stings - bool operator==(const char* str) - { - return - str[length] == 0 && - (strncmp(ptr, str, length) == 0); - } - - /** This allocates a copy of the data. Only use this for debugging - and error messages. */ - std::string toString() - { return std::string(ptr,length); } -}; - -typedef SliceArray SString; -typedef SliceArray IntArray; -typedef SliceArray FloatArray; - -} - -#endif diff --git a/components/nif/niffile.hpp b/components/nif/niffile.hpp index 79e1cc2a5..77e0acb9e 100644 --- a/components/nif/niffile.hpp +++ b/components/nif/niffile.hpp @@ -155,6 +155,7 @@ struct KeyListT { static const int sLinearInterpolation = 1; static const int sQuadraticInterpolation = 2; static const int sTBCInterpolation = 3; + static const int sXYZInterpolation = 4; int mInterpolationType; VecType mKeys; @@ -199,6 +200,43 @@ struct KeyListT { key.mContinuity = nif->getFloat(); } } + //\FIXME This now reads the correct amount of data in the file, but doesn't actually do anything with it. + else if(mInterpolationType == sXYZInterpolation) + { + if (count != 1) + { + nif->file->fail("count should always be '1' for XYZ_ROTATION_KEY. Retrieved Value: "+Ogre::StringConverter::toString(count)); + return; + } + //KeyGroup (see http://niftools.sourceforge.net/doc/nif/NiKeyframeData.html) + //Chomp unknown and possibly unused float + nif->getFloat(); + for(size_t i=0;i<3;++i) + { + unsigned int numKeys = nif->getInt(); + if(numKeys != 0) + { + int interpolationTypeAgain = nif->getInt(); + if( interpolationTypeAgain != sLinearInterpolation) + { + nif->file->fail("XYZ_ROTATION_KEY's KeyGroup keyType must be '1' (Linear Interpolation). Retrieved Value: "+Ogre::StringConverter::toString(interpolationTypeAgain)); + return; + } + for(size_t j = 0;j < numKeys;j++) + { + //For now just chomp these + nif->getFloat(); + nif->getFloat(); + } + } + nif->file->warn("XYZ_ROTATION_KEY read, but not used!"); + } + } + else if (mInterpolationType == 0) + { + if (count != 0) + nif->file->fail("Interpolation type 0 doesn't work with keys"); + } else nif->file->fail("Unhandled interpolation type: "+Ogre::StringConverter::toString(mInterpolationType)); } diff --git a/components/nifogre/material.cpp b/components/nifogre/material.cpp index 4dae1a93d..3a87e1d52 100644 --- a/components/nifogre/material.cpp +++ b/components/nifogre/material.cpp @@ -67,6 +67,10 @@ std::string NIFMaterialLoader::findTextureName(const std::string &filename) std::string texname = filename; Misc::StringUtils::toLower(texname); + // Apparently, leading separators are allowed + while (texname.size() && (texname[0] == '/' || texname[0] == '\\')) + texname.erase(0, 1); + if(texname.compare(0, sizeof(path)-1, path) != 0 && texname.compare(0, sizeof(path2)-1, path2) != 0) texname = path + texname; diff --git a/components/nifogre/ogrenifloader.cpp b/components/nifogre/ogrenifloader.cpp index ce8244619..36d750821 100644 --- a/components/nifogre/ogrenifloader.cpp +++ b/components/nifogre/ogrenifloader.cpp @@ -863,7 +863,8 @@ class NIFObjectLoader Nif::ControllerPtr ctrl = partnode->controller; while(!ctrl.empty()) { - if(ctrl->recType == Nif::RC_NiParticleSystemController && ctrl->flags & Nif::NiNode::ControllerFlag_Active) + if((ctrl->recType == Nif::RC_NiParticleSystemController || ctrl->recType == Nif::RC_NiBSPArrayController) + && ctrl->flags & Nif::NiNode::ControllerFlag_Active) { const Nif::NiParticleSystemController *partctrl = static_cast(ctrl.getPtr()); diff --git a/components/nifogre/skeleton.cpp b/components/nifogre/skeleton.cpp index c0482cf5e..26647e595 100644 --- a/components/nifogre/skeleton.cpp +++ b/components/nifogre/skeleton.cpp @@ -42,6 +42,7 @@ void NIFSkeletonLoader::buildBones(Ogre::Skeleton *skel, const Nif::Node *node, while(!ctrl.empty()) { if(!(ctrl->recType == Nif::RC_NiParticleSystemController || + ctrl->recType == Nif::RC_NiBSPArrayController || ctrl->recType == Nif::RC_NiVisController || ctrl->recType == Nif::RC_NiUVController || ctrl->recType == Nif::RC_NiKeyframeController || diff --git a/components/ogreinit/ogreinit.cpp b/components/ogreinit/ogreinit.cpp index 1b9a899a0..77dbcb1ee 100644 --- a/components/ogreinit/ogreinit.cpp +++ b/components/ogreinit/ogreinit.cpp @@ -1,11 +1,15 @@ #include "ogreinit.hpp" #include +#include +#include +#include #include #include #include #include +#include #if OGRE_PLATFORM == OGRE_PLATFORM_APPLE #include @@ -13,8 +17,54 @@ #include +#include +#include + #include "ogreplugin.hpp" +namespace bfs = boost::filesystem; + +namespace +{ + /** \brief Custom Ogre::LogListener interface implementation being + able to portably handle UTF-8 encoded path. + + Effectively this is used in conjunction with default listener, + but since on every message messageLogged() set 'skip' flag to + true, there should be no troubles sharing same file. + */ + class LogListener : public Ogre::LogListener + { + bfs::ofstream file; + char buffer[16]; + + + public: + + LogListener(const std::string &path) + : file((bfs::path(path))) + { + memset(buffer, sizeof(buffer), 0); + } + + void timestamp() + { + int local = time(0) % 86400; + int sec = local % 60; + int min = (local / 60) % 60; + int hrs = local / 3600; + sprintf(buffer, "%02d:%02d:%02d: ", hrs, min, sec); + } + + virtual void messageLogged(const std::string &msg, Ogre::LogMessageLevel lvl, bool mask, const std::string &logName, bool &skip) + { + timestamp(); + file << buffer << msg << std::endl; + skip = true; + } + }; +} + namespace OgreInit { @@ -43,6 +93,11 @@ namespace OgreInit new Ogre::LogManager; Ogre::Log *log = Ogre::LogManager::getSingleton().createLog(logPath); + #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 + // Use custom listener only on Windows + log->addListener(new LogListener(logPath)); + #endif + // Disable logging to cout/cerr log->setDebugOutputEnabled(false); @@ -62,6 +117,7 @@ namespace OgreInit OgreInit::~OgreInit() { delete mRoot; + delete Ogre::LogManager::getSingletonPtr(); std::vector::iterator ei; for(ei = mEmitterFactories.begin();ei != mEmitterFactories.end();++ei) @@ -140,11 +196,6 @@ namespace OgreInit pluginDir = OGRE_PLUGIN_DIR_REL; #endif } - - boost::filesystem::path absPluginPath = boost::filesystem::absolute(boost::filesystem::path(pluginDir)); - - pluginDir = absPluginPath.string(); - Files::loadOgrePlugin(pluginDir, "RenderSystem_GL", *mRoot); Files::loadOgrePlugin(pluginDir, "RenderSystem_GLES2", *mRoot); Files::loadOgrePlugin(pluginDir, "RenderSystem_GL3Plus", *mRoot); diff --git a/components/settings/settings.cpp b/components/settings/settings.cpp index 13015e39c..0def0afdb 100644 --- a/components/settings/settings.cpp +++ b/components/settings/settings.cpp @@ -1,13 +1,19 @@ #include "settings.hpp" -#include #include +#include +#include #include #include +#include + +#include using namespace Settings; +namespace bfs = boost::filesystem; + Ogre::ConfigFile Manager::mFile = Ogre::ConfigFile(); Ogre::ConfigFile Manager::mDefaultFile = Ogre::ConfigFile(); CategorySettingVector Manager::mChangedSettings = CategorySettingVector(); @@ -15,17 +21,19 @@ CategorySettingValueMap Manager::mNewSettings = CategorySettingValueMap(); void Manager::loadUser (const std::string& file) { - mFile.load(file); + Ogre::DataStreamPtr stream = openConstrainedFileDataStream(file.c_str()); + mFile.load(stream); } void Manager::loadDefault (const std::string& file) { - mDefaultFile.load(file); + Ogre::DataStreamPtr stream = openConstrainedFileDataStream(file.c_str()); + mDefaultFile.load(stream); } void Manager::saveUser(const std::string& file) { - std::fstream fout(file.c_str(), std::ios::out); + bfs::ofstream fout((bfs::path(file))); Ogre::ConfigFile::SectionIterator seci = mFile.getSectionIterator(); diff --git a/components/terrain/chunk.cpp b/components/terrain/chunk.cpp index 0820dcc73..7ecafda5e 100644 --- a/components/terrain/chunk.cpp +++ b/components/terrain/chunk.cpp @@ -55,6 +55,9 @@ namespace Terrain mVertexData->vertexBufferBinding->setBinding(2, uvBuffer); mVertexData->vertexBufferBinding->setBinding(3, colourBuffer); + // Assign a default material in case terrain material fails to be created + mMaterial = Ogre::MaterialManager::getSingleton().getByName("BaseWhite"); + mIndexData = OGRE_NEW Ogre::IndexData(); mIndexData->indexStart = 0; } @@ -67,11 +70,13 @@ namespace Terrain Chunk::~Chunk() { + if (!mMaterial.isNull()) + { #if TERRAIN_USE_SHADER - sh::Factory::getInstance().destroyMaterialInstance(mMaterial->getName()); + sh::Factory::getInstance().destroyMaterialInstance(mMaterial->getName()); #endif - Ogre::MaterialManager::getSingleton().remove(mMaterial->getName()); - + Ogre::MaterialManager::getSingleton().remove(mMaterial->getName()); + } OGRE_DELETE mVertexData; OGRE_DELETE mIndexData; } diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index faa73a986..b56f70680 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -181,7 +181,7 @@ namespace Terrain // shadow. TODO: repeated, put in function if (mShadows) { - for (Ogre::uint i = 0; i < (mSplitShadows ? 3 : 1); ++i) + for (int i = 0; i < (mSplitShadows ? 3 : 1); ++i) { sh::MaterialInstanceTextureUnit* shadowTex = p->createTextureUnit ("shadowMap" + Ogre::StringConverter::toString(i)); shadowTex->setProperty ("content_type", sh::makeProperty (new sh::StringValue("shadow"))); @@ -334,7 +334,7 @@ namespace Terrain // shadow if (shadows) { - for (Ogre::uint i = 0; i < (mSplitShadows ? 3 : 1); ++i) + for (int i = 0; i < (mSplitShadows ? 3 : 1); ++i) { sh::MaterialInstanceTextureUnit* shadowTex = p->createTextureUnit ("shadowMap" + Ogre::StringConverter::toString(i)); shadowTex->setProperty ("content_type", sh::makeProperty (new sh::StringValue("shadow"))); diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp index 844144d7c..3d968470f 100644 --- a/components/terrain/world.cpp +++ b/components/terrain/world.cpp @@ -337,6 +337,8 @@ namespace Terrain it->mTarget->loadLayers(*it); } + delete data; + mRootNode->loadMaterials(); mLayerLoadPending = false; diff --git a/components/translation/translation.cpp b/components/translation/translation.cpp index d0ea4b7fb..423c3971a 100644 --- a/components/translation/translation.cpp +++ b/components/translation/translation.cpp @@ -1,7 +1,8 @@ #include "translation.hpp" -#include -#include +#include + +#include namespace Translation { @@ -28,9 +29,8 @@ namespace Translation if (dataFileCollections.getCollection (extension).doesExist (fileName)) { - std::string path = dataFileCollections.getCollection (extension).getPath (fileName).string(); - - std::ifstream stream (path.c_str()); + boost::filesystem::ifstream stream ( + dataFileCollections.getCollection (extension).getPath (fileName)); if (!stream.is_open()) throw std::runtime_error ("failed to open translation file: " + fileName); diff --git a/credits.txt b/credits.txt index eb427a22b..da5417034 100644 --- a/credits.txt +++ b/credits.txt @@ -17,6 +17,7 @@ Alex McKibben (WeirdSexy) Alexander Nadeau (wareya) Alexander Olofsson (Ace) Artem Kotsynyak (greye) +Arthur Moore (EmperorArthur) athile Britt Mathis (galdor557) BrotherBrick @@ -33,6 +34,7 @@ Edmondo Tommasina (edmondo) Eduard Cot (trombonecot) Eli2 Emanuel Guével (potatoesmaster) +Fil Krynicki (filkry) gugus/gus Jacob Essex (Yacoby) Jannik Heller (scrawl) @@ -70,6 +72,7 @@ Sebastian Wick (swick) Sergey Shambir sir_herrbatka Sylvain Thesnieres (Garvek) +Thomas Luppi (Digmaster) Tom Mason (wheybags) Torben Leif Carrington (TorbenC) diff --git a/extern/oics/ICSInputControlSystem_mouse.cpp b/extern/oics/ICSInputControlSystem_mouse.cpp index be18ebbc0..fb1100528 100644 --- a/extern/oics/ICSInputControlSystem_mouse.cpp +++ b/extern/oics/ICSInputControlSystem_mouse.cpp @@ -333,11 +333,6 @@ namespace ICS } } } - else if(mDetectingBindingListener) - { - mDetectingBindingListener->mouseButtonBindingDetected(this, - mDetectingBindingControl, btn, mDetectingBindingDirection); - } } } @@ -345,11 +340,19 @@ namespace ICS { if(mActive) { - ControlsButtonBinderMapType::const_iterator it = mControlsMouseButtonBinderMap.find((int)btn); - if(it != mControlsMouseButtonBinderMap.end()) - { - it->second.control->setChangingDirection(Control::STOP); - } + if (!mDetectingBindingControl) + { + ControlsButtonBinderMapType::const_iterator it = mControlsMouseButtonBinderMap.find((int)btn); + if(it != mControlsMouseButtonBinderMap.end()) + { + it->second.control->setChangingDirection(Control::STOP); + } + } + else if(mDetectingBindingListener) + { + mDetectingBindingListener->mouseButtonBindingDetected(this, + mDetectingBindingControl, btn, mDetectingBindingDirection); + } } } diff --git a/extern/oics/tinyxml.cpp b/extern/oics/tinyxml.cpp index 29a4768aa..21b2d9c9a 100644 --- a/extern/oics/tinyxml.cpp +++ b/extern/oics/tinyxml.cpp @@ -31,18 +31,34 @@ distribution. #include "tinyxml.h" +#ifdef _WIN32 +#include // import MultiByteToWideChar +#endif + bool TiXmlBase::condenseWhiteSpace = true; // Microsoft compiler security FILE* TiXmlFOpen( const char* filename, const char* mode ) { - #if defined(_MSC_VER) && (_MSC_VER >= 1400 ) + #if defined(_WIN32) FILE* fp = 0; - errno_t err = fopen_s( &fp, filename, mode ); - if ( !err && fp ) - return fp; - return 0; + size_t len = strlen(filename); + wchar_t *wname = new wchar_t[len + 1]; + memset(wname, 0, sizeof(*wname) * (len + 1)); + wchar_t wmode[32] = { 0 }; + + MultiByteToWideChar(CP_UTF8, 0, filename, len, wname, len); + MultiByteToWideChar(CP_UTF8, 0, mode, -1, wmode, sizeof(wmode) / sizeof(*wmode)); + + #if defined(_MSC_VER) && (_MSC_VER >= 1400 ) + errno_t err = _wfopen_s( &fp, wname, wmode ); + #else + fp = _wfopen(wname, wmode); + #endif + delete[] wname; + + return fp; #else return fopen( filename, mode ); #endif diff --git a/extern/sdl4ogre/sdlinputwrapper.cpp b/extern/sdl4ogre/sdlinputwrapper.cpp index d48e43c01..82db5ea99 100644 --- a/extern/sdl4ogre/sdlinputwrapper.cpp +++ b/extern/sdl4ogre/sdlinputwrapper.cpp @@ -115,7 +115,7 @@ namespace SFO mWindowListener->windowClosed(); break; default: - std::cerr << "Unhandled SDL event of type " << evt.type << std::endl; + std::cerr << "Unhandled SDL event of type 0x" << std::hex << evt.type << std::endl; break; } } @@ -241,8 +241,8 @@ namespace SFO //eep, wrap the pointer manually if the input driver doesn't support //relative positioning natively - int success = SDL_SetRelativeMouseMode(relative ? SDL_TRUE : SDL_FALSE); - if(relative && success != 0) + bool success = SDL_SetRelativeMouseMode(relative ? SDL_TRUE : SDL_FALSE) == 0; + if(relative && !success) mWrapPointer = true; //now remove all mouse events using the old setting from the queue @@ -443,5 +443,10 @@ namespace SFO mKeyMap.insert( KeyMap::value_type(SDLK_PAGEDOWN, OIS::KC_PGDOWN) ); mKeyMap.insert( KeyMap::value_type(SDLK_INSERT, OIS::KC_INSERT) ); mKeyMap.insert( KeyMap::value_type(SDLK_DELETE, OIS::KC_DELETE) ); + mKeyMap.insert( KeyMap::value_type(SDLK_KP_ENTER, OIS::KC_NUMPADENTER) ); + mKeyMap.insert( KeyMap::value_type(SDLK_RCTRL, OIS::KC_RCONTROL) ); + mKeyMap.insert( KeyMap::value_type(SDLK_LGUI, OIS::KC_LWIN) ); + mKeyMap.insert( KeyMap::value_type(SDLK_RGUI, OIS::KC_RWIN) ); + mKeyMap.insert( KeyMap::value_type(SDLK_APPLICATION, OIS::KC_APPS) ); } } diff --git a/extern/shiny/Main/Factory.hpp b/extern/shiny/Main/Factory.hpp index 15c859958..e8562011c 100644 --- a/extern/shiny/Main/Factory.hpp +++ b/extern/shiny/Main/Factory.hpp @@ -206,7 +206,7 @@ namespace sh std::string getCacheFolder () { return mPlatform->getCacheFolder (); } bool getReadSourceCache() { return mReadSourceCache; } - bool getWriteSourceCache() { return mReadSourceCache; } + bool getWriteSourceCache() { return mWriteSourceCache; } public: bool getWriteMicrocodeCache() { return mWriteMicrocodeCache; } // Fixme diff --git a/extern/shiny/Main/Preprocessor.cpp b/extern/shiny/Main/Preprocessor.cpp index 1a97668bc..c03879d46 100644 --- a/extern/shiny/Main/Preprocessor.cpp +++ b/extern/shiny/Main/Preprocessor.cpp @@ -4,6 +4,59 @@ #include #include +#include + +/* + Almost exact copy of load_file_to_string policy found in + boost::wave headers with the only change that it uses + boost::filesystem facility to handle UTF-8 paths used + throughout OpenMW (bfs::fstream, bfs::path). + + Original namespace is used due to required bost::wave + internal symbols. +*/ +namespace boost { +namespace wave { +namespace iteration_context_policies { + + struct load_utf8_path_to_string + { + template + class inner + { + public: + template + static void init_iterators(IterContextT &iter_ctx, + PositionT const &act_pos, language_support language) + { + typedef typename IterContextT::iterator_type iterator_type; + namespace bfs = boost::filesystem; + + // read in the file + bfs::ifstream instream(bfs::path(iter_ctx.filename.c_str())); + if (!instream.is_open()) { + BOOST_WAVE_THROW_CTX(iter_ctx.ctx, preprocess_exception, + bad_include_file, iter_ctx.filename.c_str(), act_pos); + return; + } + instream.unsetf(std::ios::skipws); + + iter_ctx.instring.assign( + std::istreambuf_iterator(instream.rdbuf()), + std::istreambuf_iterator()); + + iter_ctx.first = iterator_type( + iter_ctx.instring.begin(), iter_ctx.instring.end(), + PositionT(iter_ctx.filename), language); + iter_ctx.last = iterator_type(); + } + + private: + std::string instring; + }; + }; +} } } + namespace sh { std::string Preprocessor::preprocess (std::string source, const std::string& includePath, std::vector definitions, const std::string& name) @@ -29,7 +82,7 @@ namespace sh // match the iterator type used during construction of the context // instance (see below). It is the type of the underlying input stream. typedef boost::wave::context context_type; diff --git a/files/mygui/CMakeLists.txt b/files/mygui/CMakeLists.txt index ef223a617..2135f4dde 100644 --- a/files/mygui/CMakeLists.txt +++ b/files/mygui/CMakeLists.txt @@ -4,7 +4,6 @@ set(SDIR ${CMAKE_CURRENT_SOURCE_DIR}) set(DDIR ${OpenMW_BINARY_DIR}/resources/mygui) set(MYGUI_FILES - bigbars.png black.png core.skin core.xml @@ -81,7 +80,6 @@ set(MYGUI_FILES openmw_companion_window.layout openmw_savegame_dialog.layout openmw_recharge_dialog.layout - smallbars.png DejaVuLGCSansMono.ttf markers.png ../launcher/images/openmw.png diff --git a/files/mygui/bigbars.png b/files/mygui/bigbars.png deleted file mode 100644 index ee91da19e..000000000 Binary files a/files/mygui/bigbars.png and /dev/null differ diff --git a/files/mygui/openmw_confirmation_dialog.layout b/files/mygui/openmw_confirmation_dialog.layout index 47e1fd2b8..edeed539c 100644 --- a/files/mygui/openmw_confirmation_dialog.layout +++ b/files/mygui/openmw_confirmation_dialog.layout @@ -4,7 +4,7 @@ - + @@ -13,18 +13,19 @@ - - - - - - - - - + + + + + + + + + + - + + - - + \ No newline at end of file diff --git a/files/mygui/openmw_edit_effect.layout b/files/mygui/openmw_edit_effect.layout index fa1e58b9d..5dc53e505 100644 --- a/files/mygui/openmw_edit_effect.layout +++ b/files/mygui/openmw_edit_effect.layout @@ -33,6 +33,7 @@ + @@ -41,6 +42,7 @@ + @@ -58,6 +60,7 @@ + @@ -74,6 +77,7 @@ + diff --git a/files/mygui/openmw_hud.layout b/files/mygui/openmw_hud.layout index 72d337e45..90fa1c8a5 100644 --- a/files/mygui/openmw_hud.layout +++ b/files/mygui/openmw_hud.layout @@ -3,10 +3,8 @@ - - - - + + @@ -42,7 +40,7 @@ - + @@ -54,6 +52,7 @@ + @@ -101,6 +100,7 @@ + diff --git a/files/mygui/openmw_hud_energybar.skin.xml b/files/mygui/openmw_hud_energybar.skin.xml index f10908d7b..d5078e994 100644 --- a/files/mygui/openmw_hud_energybar.skin.xml +++ b/files/mygui/openmw_hud_energybar.skin.xml @@ -19,24 +19,28 @@ - - - + + + + - - - + + + + - - - + + + + - - - + + + + diff --git a/files/mygui/openmw_list.skin.xml b/files/mygui/openmw_list.skin.xml index 7972527ac..4dbc3da45 100644 --- a/files/mygui/openmw_list.skin.xml +++ b/files/mygui/openmw_list.skin.xml @@ -10,6 +10,10 @@ + + + + @@ -47,6 +51,10 @@ + + + + diff --git a/files/mygui/openmw_loading_screen.layout b/files/mygui/openmw_loading_screen.layout index 19649cfd2..faa0d8637 100644 --- a/files/mygui/openmw_loading_screen.layout +++ b/files/mygui/openmw_loading_screen.layout @@ -4,13 +4,13 @@ - + - + - + diff --git a/files/mygui/openmw_mainmenu.layout b/files/mygui/openmw_mainmenu.layout index e8cb23b77..f4c7142ce 100644 --- a/files/mygui/openmw_mainmenu.layout +++ b/files/mygui/openmw_mainmenu.layout @@ -2,11 +2,11 @@ - - - + + + - + diff --git a/files/mygui/openmw_progress.skin.xml b/files/mygui/openmw_progress.skin.xml index f5418e3f8..5ef3d7304 100644 --- a/files/mygui/openmw_progress.skin.xml +++ b/files/mygui/openmw_progress.skin.xml @@ -2,36 +2,50 @@ - - - + + + + - - - + + + + - - - + + + + - - - + + + + + - - - + + + + + + + + + + + - + + @@ -39,7 +53,7 @@ - + @@ -47,7 +61,7 @@ - + @@ -55,15 +69,15 @@ - + - - + + @@ -82,7 +96,7 @@ - + diff --git a/files/mygui/openmw_scroll_skin.xml b/files/mygui/openmw_scroll_skin.xml index b6ed9155f..b5dfd333d 100644 --- a/files/mygui/openmw_scroll_skin.xml +++ b/files/mygui/openmw_scroll_skin.xml @@ -3,12 +3,12 @@ - + - + diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index adf9f1557..9ecae465c 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -3,10 +3,10 @@ - - + + - + @@ -95,6 +95,18 @@ + + + + + + + + + + + + @@ -405,7 +417,7 @@ - + @@ -428,8 +440,12 @@ + + + + diff --git a/files/mygui/openmw_text.skin.xml b/files/mygui/openmw_text.skin.xml index 15287bc74..d4c72c75b 100644 --- a/files/mygui/openmw_text.skin.xml +++ b/files/mygui/openmw_text.skin.xml @@ -49,22 +49,6 @@ - - - - - - - - - - - - - - - - @@ -147,29 +131,29 @@ - + - + - + - + - + diff --git a/files/mygui/smallbars.png b/files/mygui/smallbars.png deleted file mode 100644 index 3c007a55c..000000000 Binary files a/files/mygui/smallbars.png and /dev/null differ diff --git a/files/opencs.cfg b/files/opencs.cfg deleted file mode 100644 index 3faac7c8e..000000000 --- a/files/opencs.cfg +++ /dev/null @@ -1,5 +0,0 @@ -[Editor] -Record Status Display = Icon and Text -[Window Size] -Width = 640 -Height = 480 diff --git a/files/opencs.ini b/files/opencs.ini new file mode 100644 index 000000000..065f11a88 --- /dev/null +++ b/files/opencs.ini @@ -0,0 +1,7 @@ +[Display%20Format] +Record%20Status%20Display=Icon Only +Referenceable%20ID%20Type%20Display=Text Only + +[Window%20Size] +Height=900 +Width=1440 diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 6361476e3..9eed2c7d9 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -133,9 +133,9 @@ distant land = false shader = true [Water] -shader = true +shader = false -refraction = true +refraction = false rtt size = 512 reflect terrain = true @@ -150,7 +150,7 @@ device = master volume = 1.0 sfx volume = 1.0 music volume = 0.4 -footsteps volume = 0.6 +footsteps volume = 0.15 voice volume = 1.0 @@ -174,6 +174,8 @@ best attack = false [Saves] character = +# Save when resting +autosave = true [Windows] inventory x = 0 diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index 4484d9862..235300b43 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -18,6 +18,7 @@ namespace Physic PhysicActor::PhysicActor(const std::string &name, const std::string &mesh, PhysicEngine *engine, const Ogre::Vector3 &position, const Ogre::Quaternion &rotation, float scale) : mName(name), mEngine(engine), mMesh(mesh), mBoxScaledTranslation(0,0,0), mBoxRotationInverse(0,0,0,0) , mBody(0), mRaycastingBody(0), mOnGround(false), mCollisionMode(true), mBoxRotation(0,0,0,0) + , mCollisionBody(true) , mForce(0.0f) { mBody = mEngine->createAndAdjustRigidBody(mMesh, mName, scale, position, rotation, &mBoxScaledTranslation, &mBoxRotation); @@ -41,14 +42,18 @@ namespace Physic } } - void PhysicActor::enableCollisions(bool collision) + void PhysicActor::enableCollisionMode(bool collision) { - assert(mBody); - if(collision && !mCollisionMode) enableCollisionBody(); - if(!collision && mCollisionMode) disableCollisionBody(); mCollisionMode = collision; } + void PhysicActor::enableCollisionBody(bool collision) + { + assert(mBody); + if(collision && !mCollisionBody) enableCollisionBody(); + if(!collision && mCollisionBody) disableCollisionBody(); + mCollisionBody = collision; + } void PhysicActor::setPosition(const Ogre::Vector3 &pos) { @@ -105,7 +110,7 @@ namespace Physic //Create the newly scaled rigid body mBody = mEngine->createAndAdjustRigidBody(mMesh, mName, scale, pos, rot); mRaycastingBody = mEngine->createAndAdjustRigidBody(mMesh, mName, scale, pos, rot, 0, 0, true); - mEngine->addRigidBody(mBody, false, mRaycastingBody,true); //Add rigid body to dynamics world, but do not add to object map + mEngine->addRigidBody(mCollisionBody ? mBody : 0, false, mRaycastingBody,true); //Add rigid body to dynamics world, but do not add to object map } Ogre::Vector3 PhysicActor::getHalfExtents() const @@ -672,7 +677,7 @@ namespace Physic { } - std::pair PhysicEngine::rayTest(btVector3& from,btVector3& to,bool raycastingObjectOnly,bool ignoreHeightMap) + std::pair PhysicEngine::rayTest(btVector3& from,btVector3& to,bool raycastingObjectOnly,bool ignoreHeightMap, Ogre::Vector3* normal) { std::string name = ""; float d = -1; @@ -689,7 +694,11 @@ namespace Physic if (resultCallback1.hasHit()) { name = static_cast(*resultCallback1.m_collisionObject).mName; - d = resultCallback1.m_closestHitFraction;; + d = resultCallback1.m_closestHitFraction; + if (normal) + *normal = Ogre::Vector3(resultCallback1.m_hitNormalWorld.x(), + resultCallback1.m_hitNormalWorld.y(), + resultCallback1.m_hitNormalWorld.z()); } return std::pair(name,d); diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp index 4ef611dc8..803986d5b 100644 --- a/libs/openengine/bullet/physic.hpp +++ b/libs/openengine/bullet/physic.hpp @@ -99,7 +99,15 @@ namespace Physic */ void setRotation(const Ogre::Quaternion &quat); - void enableCollisions(bool collision); + /** + * Sets the collisionMode for this actor. If disabled, the actor can fly and clip geometry. + */ + void enableCollisionMode(bool collision); + + /** + * Enables or disables the *external* collision body. If disabled, other actors will not collide with this actor. + */ + void enableCollisionBody(bool collision); bool getCollisionMode() const { @@ -167,6 +175,7 @@ namespace Physic Ogre::Vector3 mForce; bool mOnGround; bool mCollisionMode; + bool mCollisionBody; std::string mMesh; std::string mName; @@ -299,8 +308,10 @@ namespace Physic /** * Return the closest object hit by a ray. If there are no objects, it will return ("",-1). + * If \a normal is non-NULL, the hit normal will be written there (if there is a hit) */ - std::pair rayTest(btVector3& from,btVector3& to,bool raycastingObjectOnly = true,bool ignoreHeightMap = false); + std::pair rayTest(btVector3& from,btVector3& to,bool raycastingObjectOnly = true, + bool ignoreHeightMap = false, Ogre::Vector3* normal = NULL); /** * Return all objects hit by a ray. diff --git a/libs/openengine/bullet/trace.cpp b/libs/openengine/bullet/trace.cpp index afda52448..6eab43a60 100644 --- a/libs/openengine/bullet/trace.cpp +++ b/libs/openengine/bullet/trace.cpp @@ -116,7 +116,7 @@ void ActorTracer::findGround(btCollisionObject *actor, const Ogre::Vector3 &star mFraction = newTraceCallback.m_closestHitFraction; mPlaneNormal = Ogre::Vector3(tracehitnormal.x(), tracehitnormal.y(), tracehitnormal.z()); mEndPos = (end-start)*mFraction + start; - mEndPos[2] -= 1.0f; + mEndPos[2] += 1.0f; } else { diff --git a/libs/openengine/gui/loglistener.cpp b/libs/openengine/gui/loglistener.cpp new file mode 100644 index 000000000..da36b90a2 --- /dev/null +++ b/libs/openengine/gui/loglistener.cpp @@ -0,0 +1,39 @@ +#include "loglistener.hpp" + +#include +#include + +#include + +namespace MyGUI +{ + void CustomLogListener::open() + { + mStream.open(boost::filesystem::path(mFileName), std::ios_base::out); + } + + void CustomLogListener::close() + { + if (mStream.is_open()) + mStream.close(); + } + + void CustomLogListener::flush() + { + if (mStream.is_open()) + mStream.flush(); + } + + void CustomLogListener::log(const std::string& _section, LogLevel _level, const struct tm* _time, const std::string& _message, const char* _file, int _line) + { + if (mStream.is_open()) + { + const char* separator = " | "; + mStream << std::setw(2) << std::setfill('0') << _time->tm_hour << ":" + << std::setw(2) << std::setfill('0') << _time->tm_min << ":" + << std::setw(2) << std::setfill('0') << _time->tm_sec << separator + << _section << separator << _level.print() << separator + << _message << separator << _file << separator << _line << std::endl; + } + } +} diff --git a/libs/openengine/gui/loglistener.hpp b/libs/openengine/gui/loglistener.hpp new file mode 100644 index 000000000..47978ba44 --- /dev/null +++ b/libs/openengine/gui/loglistener.hpp @@ -0,0 +1,37 @@ +#ifndef OPENENGINE_MYGUI_LOGLISTENER_H +#define OPENENGINE_MYGUI_LOGLISTENER_H + +#include +#include + +#include + +namespace MyGUI +{ + /// \brief Custom MyGUI::ILogListener interface implementation + /// being able to portably handle UTF-8 encoded path. + class CustomLogListener : public ILogListener + { + public: + CustomLogListener(const std::string &name) + : mFileName(name) + {} + + ~CustomLogListener() {} + + virtual void open(); + virtual void close(); + virtual void flush(); + + virtual void log(const std::string& _section, LogLevel _level, const struct tm* _time, const std::string& _message, const char* _file, int _line); + + const std::string& getFileName() const { return mFileName; } + + private: + boost::filesystem::ofstream mStream; + std::string mFileName; + }; + +} + +#endif diff --git a/libs/openengine/gui/manager.cpp b/libs/openengine/gui/manager.cpp index 91937d24b..383d37640 100644 --- a/libs/openengine/gui/manager.cpp +++ b/libs/openengine/gui/manager.cpp @@ -1,9 +1,14 @@ #include "manager.hpp" +#include "loglistener.hpp" #include #include #include +#include +#include +#include + #include #include @@ -554,8 +559,34 @@ public: } }; -} +/// \brief Helper class holding data that required during +/// MyGUI log creation +class LogFacility +{ + ConsoleLogListener mConsole; + CustomLogListener mFile; + LevelLogFilter mFilter; + LogSource mSource; + +public: + + LogFacility(const std::string &output, bool console) + : mFile(output) + { + mConsole.setEnabled(console); + mFilter.setLoggingLevel(LogLevel::Info); + mSource.addLogListener(&mFile); + mSource.addLogListener(&mConsole); + mSource.setLogFilter(&mFilter); + + mSource.open(); + } + + LogSource *getSource() { return &mSource; } +}; + +} void MyGUIManager::setup(Ogre::RenderWindow *wnd, Ogre::SceneManager *mgr, bool logging, const std::string& logDir) { @@ -586,10 +617,10 @@ void MyGUIManager::setup(Ogre::RenderWindow *wnd, Ogre::SceneManager *mgr, bool mRenderManager = new MyGUI::OgreRenderManager(); mDataManager = new MyGUI::FixedOgreDataManager(); - LogManager::getInstance().setSTDOutputEnabled(logging); - - if (!theLogFile.empty()) - LogManager::getInstance().createDefaultSource(theLogFile); + // Do not use default log since it don't support Unicode path on Windows. + // Instead, manually create log source using LogFacility and pass it. + mLogFacility = new MyGUI::LogFacility(theLogFile, logging); + LogManager::getInstance().addLogSource(mLogFacility->getSource()); if (mShaderRenderManager) mShaderRenderManager->initialise(wnd, mgr); @@ -648,5 +679,7 @@ void MyGUIManager::shutdown() delete mLogManager; mLogManager = NULL; } + delete mLogFacility; + mGui = NULL; } diff --git a/libs/openengine/gui/manager.hpp b/libs/openengine/gui/manager.hpp index cca70dfcf..8923a5cc8 100644 --- a/libs/openengine/gui/manager.hpp +++ b/libs/openengine/gui/manager.hpp @@ -10,6 +10,7 @@ namespace MyGUI class OgreDataManager; class OgreRenderManager; class ShaderBasedRenderManager; + class LogFacility; } namespace Ogre @@ -25,6 +26,7 @@ namespace GUI { MyGUI::Gui *mGui; MyGUI::LogManager* mLogManager; + MyGUI::LogFacility* mLogFacility; MyGUI::OgreDataManager* mDataManager; MyGUI::OgreRenderManager* mRenderManager; MyGUI::ShaderBasedRenderManager* mShaderRenderManager; diff --git a/libs/openengine/ogre/renderer.cpp b/libs/openengine/ogre/renderer.cpp index caf62546e..8fcf615ba 100644 --- a/libs/openengine/ogre/renderer.cpp +++ b/libs/openengine/ogre/renderer.cpp @@ -15,6 +15,9 @@ #include +#include +#include + #include #include @@ -48,7 +51,41 @@ void OgreRenderer::update(float dt) void OgreRenderer::screenshot(const std::string &file) { - mWindow->writeContentsToFile(file); + /* Since Ogre uses narrow character interfaces, it does not support + Unicode paths on Windows. Therefore we had to implement screenshot + saving manually. + */ + namespace bfs = boost::filesystem; + bfs::ofstream out(bfs::path(file), std::ios::binary); + + Ogre::Image image; + + Ogre::PixelFormat pf = mWindow->suggestPixelFormat(); + int w = mWindow->getWidth(); + int h = mWindow->getHeight(); + + image.loadDynamicImage( + OGRE_ALLOC_T(Ogre::uchar, w * h * Ogre::PixelUtil::getNumElemBytes(pf), Ogre::MEMCATEGORY_GENERAL), + w, h, 1, pf, true + ); + mWindow->copyContentsToMemory(image.getPixelBox()); + + Ogre::DataStreamPtr stream = image.encode("png"); + Ogre::MemoryDataStream *mem = dynamic_cast(stream.get()); + if (mem != 0) { // likely + const char *ptr = reinterpret_cast(mem->getCurrentPtr()); + out.write(ptr, mem->size()); + } + else { + char buf[4096]; + size_t size = stream->size(); + while (size > 0) { + size_t chunk = (size > sizeof(buf)) ? sizeof(buf) : size; + stream->read(buf, chunk); + out.write(buf, chunk); + size -= chunk; + } + } } float OgreRenderer::getFPS() diff --git a/readme.txt b/readme.txt index a054626dd..92cb35f31 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ OpenMW: A reimplementation of The Elder Scrolls III: Morrowind OpenMW is an attempt at recreating the engine for the popular role-playing game Morrowind by Bethesda Softworks. You need to own and install the original game for OpenMW to work. -Version: 0.29.0 +Version: 0.30.0 License: GPL (see GPL3.txt for more information) Website: http://www.openmw.org @@ -96,6 +96,88 @@ Allowed options: CHANGELOG +0.30.0 + +Bug #416: Extreme shaking can occur during cell transitions while moving +Bug #1003: Province Cyrodiil: Ogre Exception in Stirk +Bug #1071: Crash when given a non-existent content file +Bug #1080: OpenMW allows resting/using a bed while in combat +Bug #1097: Wrong punishment for stealing in Census and Excise Office at the start of a new game +Bug #1098: Unlocked evidence chests should get locked after new evidence is put into them +Bug #1099: NPCs that you attacked still fight you after you went to jail/paid your fine +Bug #1100: Taking items from a corpse is considered stealing +Bug #1126: Some creatures can't get close enough to attack +Bug #1144: Killed creatures seem to die again each time player transitions indoors/outdoors +Bug #1181: loading a saved game does not reset the player control status +Bug #1185: Collision issues in Addamasartus +Bug #1187: Athyn Sarethi mission, rescuing varvur sarethi from the doesnt end the mission +Bug #1189: Crash when entering interior cell "Gnisis, Arvs-Drelen" +Bug #1191: Picking up papers without inventory in new game +Bug #1195: NPCs do not equip torches in certain interiors +Bug #1197: mouse wheel makes things scroll too fast +Bug #1200: door blocked by monsters +Bug #1201: item's magical charges are only refreshed when they are used +Bug #1203: Scribs do not defend themselves +Bug #1204: creatures life is not empty when they are dead +Bug #1205: armor experience does not progress when hits are taken +Bug #1206: blood particules always red. Undeads and mechanicals should have a different one. +Bug #1209: Tarhiel never falls +Bug #1210: journal adding script is ran again after having saved/loaded +Bug #1224: Names of custom classes are not properly handled in save games +Bug #1227: Editor: Fixed case handling for broken localised versions of Morrowind.esm +Bug #1235: Indoors walk stutter +Bug #1236: Aborting intro movie brings up the menu +Bug #1239: NPCs get stuck when walking past each other +Bug #1240: BTB - Settings 14.1 and Health Bar. +Bug #1241: BTB - Character and Khajiit Prejudice +Bug #1248: GUI Weapon icon is changed to hand-to-hand after save load +Bug #1254: Guild ranks do not show in dialogue +Bug #1255: When opening a container and selecting "Take All", the screen flashes blue +Bug #1260: Level Up menu doesn't show image when using a custom class +Bug #1265: Quit Menu Has Misaligned Buttons +Bug #1270: Active weapon icon is not updated when weapon is repaired +Bug #1271: NPC Stuck in hovering "Jumping" animation +Bug #1272: Crash when attempting to load Big City esm file. +Bug #1276: Editor: Dropping a region into the filter of a cell subview fails +Bug #1286: Dialogue topic list clips with window frame +Bug #1291: Saved game: store faction membership +Bug #1293: Pluginless Khajiit Head Pack by ashiraniir makes OpenMW close. +Bug #1294: Pasting in console adds text to end, not at cursor +Bug #1295: Conversation loop when asking about "specific place" in Vivec +Bug #1296: Caius doesn't leave at start of quest "Mehra Milo and the Lost Prophecies" +Bug #1297: Saved game: map markers +Bug #1302: ring_keley script causes vector::_M_range_check exception +Bug #1309: Bug on "You violated the law" dialog +Bug #1319: Creatures sometimes rendered incorrectly +Feature #50: Ranged Combat +Feature #58: Sneaking Skill +Feature #73: Crime and Punishment +Feature #135: Editor: OGRE integration +Feature #541: Editor: Dialogue Sub-Views +Feature #853: Editor: Rework User Settings +Feature #944: Editor: lighting modes +Feature #945: Editor: Camera navigation mode +Feature #953: Trader gold +Feature #1140: AI: summoned creatures +Feature #1142: AI follow: Run stance +Feature #1154: Not all NPCs get aggressive when one is attacked +Feature #1169: Terrain threading +Feature #1172: Loading screen and progress bars during saved/loading game +Feature #1173: Saved Game: include weather state +Feature #1207: Class creation form does not remember +Feature #1220: Editor: Preview Subview +Feature #1223: Saved Game: Local Variables +Feature #1229: Quicksave, quickload, autosave +Feature #1230: Deleting saves +Feature #1233: Bribe gold is placed into NPCs inventory +Feature #1252: Saved Game: quick key bindings +Feature #1273: Editor: Region Map context menu +Feature #1274: Editor: Region Map drag & drop +Feature #1275: Editor: Scene subview drop +Feature #1282: Non-faction member crime recognition. +Feature #1289: NPCs return to default position +Task #941: Remove unused cmake files + 0.29.0 Bug #556: Video soundtrack not played when music volume is set to zero