diff --git a/.travis.yml b/.travis.yml index c461ca8540..2626094f56 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,8 +24,6 @@ addons: packages: [ # Dev cmake, clang-3.6, libunshield-dev, libtinyxml-dev, - # Tests - libgtest-dev, google-mock, # Boost libboost-filesystem-dev, libboost-program-options-dev, libboost-system-dev, # FFmpeg diff --git a/AUTHORS.md b/AUTHORS.md index d2fd051809..5320a24625 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -153,6 +153,7 @@ Programmers Siimacore sir_herrbatka smbas + Sophie Kirschner (pineapplemachine) spycrab Stefan Galowicz (bogglez) Stanislav Bobrov (Jiub) @@ -171,7 +172,9 @@ Programmers viadanna Vincent Heuken vocollapse + Yohaulticetl zelurker + James Carty (MrTopCat) Documentation ------------- @@ -180,9 +183,11 @@ Documentation Alejandro Sanchez (HiPhish) Bodillium Bret Curtis (psi29a) + David Walley (Loriel) Cramal Ryan Tucker (Ravenwing) sir_herrbatka + Diego Crespo Packagers --------- diff --git a/CHANGELOG.md b/CHANGELOG.md index 83682c5074..c11d92900b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Bug #1990: Sunrise/sunset not set correct Bug #2131: Lustidrike's spell misses the player every time Bug #2222: Fatigue's effect on selling price is backwards + Bug #2256: Landing sound not playing when jumping immediately after landing + Bug #2274: Thin platform clips through player character instead of lifting Bug #2326: After a bound item expires the last equipped item of that type is not automatically re-equipped Bug #2455: Creatures attacks degrade armor Bug #2562: Forcing AI to activate a teleport door sometimes causes a crash @@ -12,15 +14,25 @@ Bug #2835: Player able to slowly move when overencumbered Bug #2852: No murder bounty when a player follower commits murder Bug #2862: [macOS] Can't quit launcher using Command-Q or OpenMW->Quit + Bug #2872: Tab completion in console doesn't work with explicit reference Bug #2971: Compiler did not reject lines with naked expressions beginning with x.y + Bug #3049: Drain and Fortify effects are not properly applied on health, magicka and fatigue + Bug #3059: Unable to hit with marksman weapons when too close to an enemy + Bug #3072: Fatal error on AddItem that has a script containing Equip Bug #3249: Fixed revert function not updating views properly Bug #3374: Touch spells not hitting kwama foragers Bug #3486: [Mod] NPC Commands does not work + Bug #3533: GetSpellEffects should detect effects with zero duration Bug #3591: Angled hit distance too low Bug #3629: DB assassin attack never triggers creature spawning + Bug #3681: OpenMW-CS: Clicking Scripts in Preferences spawns many color pickers + Bug #3788: GetPCInJail and GetPCTraveling do not work as in vanilla + Bug #3836: Script fails to compile when command argument contains "\n" Bug #3876: Landscape texture painting is misaligned Bug #3897: Have Goodbye give all choices the effects of Goodbye Bug #3911: [macOS] Typing in the "Content List name" dialog box produces double characters + Bug #3920: RemoveSpellEffects doesn't remove constant effects + Bug #3948: AiCombat moving target aiming uses incorrect speed for magic projectiles Bug #3950: FLATTEN_STATIC_TRANSFORMS optimization breaks animated collision shapes Bug #3993: Terrain texture blending map is not upscaled Bug #3997: Almalexia doesn't pace @@ -31,15 +43,20 @@ Bug #4215: OpenMW shows book text after last EOL tag Bug #4221: Characters get stuck in V-shaped terrain Bug #4230: AiTravel package issues break some Tribunal quests + Bug #4231: Infected rats from the "Crimson Plague" quest rendered unconscious by change in Drain Fatigue functionality Bug #4251: Stationary NPCs do not return to their position after combat + Bug #4271: Scamp flickers when attacking Bug #4274: Pre-0.43 death animations are not forward-compatible with 0.43+ Bug #4286: Scripted animations can be interrupted Bug #4291: Non-persistent actors that started the game as dead do not play death animations Bug #4293: Faction members are not aware of faction ownerships in barter + Bug #4304: "Follow" not working as a second AI package Bug #4307: World cleanup should remove dead bodies only if death animation is finished + Bug #4311: OpenMW does not handle RootCollisionNode correctly Bug #4327: Missing animations during spell/weapon stance switching Bug #4358: Running animation is interrupted when magic mode is toggled Bug #4368: Settings window ok button doesn't have key focus by default + Bug #4378: On-self absorb spells restore stats Bug #4393: NPCs walk back to where they were after using ResetActors Bug #4416: Handle exception if we try to play non-music file Bug #4419: MRK NiStringExtraData is handled incorrectly @@ -59,6 +76,7 @@ Bug #4461: "Open" spell from non-player caster isn't a crime Bug #4464: OpenMW keeps AiState cached storages even after we cancel AI packages Bug #4469: Abot Silt Striders – Model turn 90 degrees on horizontal + Bug #4470: Non-bipedal creatures with Weapon & Shield flag have inconsistent behaviour Bug #4474: No fallback when getVampireHead fails Bug #4475: Scripted animations should not cause movement Bug #4479: "Game" category on Advanced page is getting too long @@ -72,10 +90,39 @@ Bug #4503: Cast and ExplodeSpell commands increase alteration skill Bug #4510: Division by zero in MWMechanics::CreatureStats::setAttribute Bug #4519: Knockdown does not discard movement in the 1st-person mode + Bug #4531: Movement does not reset idle animations + Bug #4539: Paper Doll is affected by GUI scaling + Bug #4545: Creatures flee from werewolves + Bug #4551: Replace 0 sound range with default range separately + Bug #4553: Forcegreeting on non-actor opens a dialogue window which cannot be closed + Bug #4557: Topics with reserved names are handled differently from vanilla + Bug #4558: Mesh optimizer: check for reserved node name is case-sensitive + Bug #4560: OpenMW does not update pinned windows properly + Bug #4563: Fast travel price logic checks destination cell instead of service actor cell + Bug #4565: Underwater view distance should be limited + Bug #4573: Player uses headtracking in the 1st-person mode + Bug #4574: Player turning animations are twitchy + Bug #4575: Weird result of attack animation blending with movement animations + Bug #4576: Reset of idle animations when attack can not be started + Bug #4591: Attack strength should be 0 if player did not hold the attack button + Bug #4597: <> operator causes a compile error + Bug #4604: Picking up gold from the ground only makes 1 grabbed + Bug #4607: Scaling for animated collision shapes is applied twice + Bug #4608: Falling damage is applied twice + Bug #4614: Crash due to division by zero when FlipController has no textures + Bug #4615: Flicker effects for light sources are handled incorrectly + Bug #4617: First person sneaking offset is not applied while the character is in air + Bug #4618: Sneaking is possible while the character is flying + Bug #4622: Recharging enchanted items with Soul Gems does not award experience if it fails + Bug #4628: NPC record reputation, disposition and faction rank should have unsigned char type + Feature #1645: Casting effects from objects Feature #2606: Editor: Implemented (optional) case sensitive global search Feature #3083: Play animation when NPC is casting spell via script - Feature #3276: Editor: Search- Show number of (remaining) search results and indicate a search without any results + Feature #3103: Provide option for disposition to get increased by successful trade + Feature #3276: Editor: Search - Show number of (remaining) search results and indicate a search without any results Feature #3641: Editor: Limit FPS in 3d preview window + Feature #3703: Ranged sneak attack criticals + Feature #4012: Editor: Write a log file if OpenCS crashes Feature #4222: 360° screenshots Feature #4256: Implement ToggleBorders (TB) console command Feature #4324: Add CFBundleIdentifier in Info.plist to allow for macOS function key shortcuts @@ -84,11 +131,23 @@ Feature #4444: Per-group KF-animation files support Feature #4466: Editor: Add option to ignore "Base" records when running verifier Feature #4488: Make water shader rougher during rain - Feature #4012: Editor: Write a log file if OpenCS crashes Feature #4509: Show count of enchanted items in stack in the spells list Feature #4512: Editor: Use markers for lights and creatures levelled lists + Feature #4548: Weapon priority: use the actual chance to hit the target instead of weapon skill + Feature #4549: Weapon priority: use the actual damage in weapon rating calculations + Feature #4550: Weapon priority: make ranged weapon bonus more sensible + Feature #4579: Add option for applying Strength into hand to hand damage + Feature #4581: Use proper logging system + Feature #4624: Spell priority: don't cast hit chance-affecting spells if the enemy is not in respective stance at the moment + Feature #4625: Weapon priority: use weighted mean for melee damage rating + Feature #4626: Weapon priority: account for weapon speed + Feature #4632: AI priority: utilize vanilla AI GMSTs for priority rating Task #2490: Don't open command prompt window on Release-mode builds automatically Task #4545: Enable is_pod string test + Task #4605: Optimize skinning + Task #4606: Support Rapture3D's OpenAL driver + Task #4613: Incomplete type errors when compiling with g++ on OSX 10.9 + Task #4621: Optimize combat AI 0.44.0 ------ diff --git a/CI/before_install.linux.sh b/CI/before_install.linux.sh index 2b0e73110d..25d05e619c 100755 --- a/CI/before_install.linux.sh +++ b/CI/before_install.linux.sh @@ -1,11 +1,3 @@ #!/bin/sh sudo ln -s /usr/bin/clang-3.6 /usr/local/bin/clang sudo ln -s /usr/bin/clang++-3.6 /usr/local/bin/clang++ - -# build libgtest & libgtest_main -sudo mkdir /usr/src/gtest/build -cd /usr/src/gtest/build -sudo cmake .. -DBUILD_SHARED_LIBS=1 -sudo make -j4 -sudo ln -s /usr/src/gtest/build/libgtest.so /usr/lib/libgtest.so -sudo ln -s /usr/src/gtest/build/libgtest_main.so /usr/lib/libgtest_main.so diff --git a/CI/before_script.linux.sh b/CI/before_script.linux.sh index 93be1cb48e..dd879989a8 100755 --- a/CI/before_script.linux.sh +++ b/CI/before_script.linux.sh @@ -1,8 +1,21 @@ -#!/bin/sh +#!/bin/sh -e free -m + +env GENERATOR='Unix Makefiles' CONFIGURATION=Release CI/build_googletest.sh +GOOGLETEST_DIR="$(pwd)/googletest/build" + mkdir build cd build export CODE_COVERAGE=1 if [ "${CC}" = "clang" ]; then export CODE_COVERAGE=0; fi -${ANALYZE}cmake .. -DBUILD_WITH_CODE_COVERAGE=${CODE_COVERAGE} -DBUILD_UNITTESTS=1 -DCMAKE_INSTALL_PREFIX=/usr -DBINDIR=/usr/games -DCMAKE_BUILD_TYPE="None" -DUSE_SYSTEM_TINYXML=TRUE +${ANALYZE}cmake \ + -DBUILD_WITH_CODE_COVERAGE=${CODE_COVERAGE} \ + -DBUILD_UNITTESTS=1 \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DBINDIR=/usr/games \ + -DCMAKE_BUILD_TYPE="None" \ + -DUSE_SYSTEM_TINYXML=TRUE \ + -DGTEST_ROOT="${GOOGLETEST_DIR}" \ + -DGMOCK_ROOT="${GOOGLETEST_DIR}" \ + .. diff --git a/CI/build_googletest.sh b/CI/build_googletest.sh new file mode 100755 index 0000000000..cd61379b59 --- /dev/null +++ b/CI/build_googletest.sh @@ -0,0 +1,13 @@ +#!/bin/sh -e + +git clone https://github.com/google/googletest.git +cd googletest +mkdir build +cd build +cmake \ + -D CMAKE_BUILD_TYPE="${CONFIGURATION}" \ + -D CMAKE_INSTALL_PREFIX=. \ + -G "${GENERATOR}" \ + .. +cmake --build . --config "${CONFIGURATION}" +cmake --build . --target install --config "${CONFIGURATION}" diff --git a/CMakeLists.txt b/CMakeLists.txt index 678f3e16b6..34e1a8f4bb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -624,7 +624,7 @@ if (WIN32) # Warnings that aren't enabled normally and don't need to be enabled # They're unneeded and sometimes completely retarded warnings that /Wall enables # Not going to bother commenting them as they tend to warn on every standard library file - 4061 4263 4264 4266 4350 4371 4435 4514 4548 4571 4610 4619 4623 4625 + 4061 4263 4264 4266 4350 4371 4435 4514 4548 4571 4610 4619 4623 4625 4626 4628 4640 4668 4710 4711 4768 4820 4826 4917 4946 5032 5039 5045 # Warnings that are thrown on standard libraries and not OpenMW @@ -643,6 +643,7 @@ if (WIN32) # caused by boost 4191 # 'type cast' : unsafe conversion (1.56, thread_primitives.hpp, normally off) + 4643 # Forward declaring 'X' in namespace std is not permitted by the C++ Standard. (in *_std_fwd.h files) # caused by MyGUI 4275 # non dll-interface class 'std::exception' used as base for dll-interface class 'MyGUI::Exception' diff --git a/apps/essimporter/importinventory.cpp b/apps/essimporter/importinventory.cpp index 2c87a9a19d..8c7d07f637 100644 --- a/apps/essimporter/importinventory.cpp +++ b/apps/essimporter/importinventory.cpp @@ -20,6 +20,7 @@ namespace ESSImport item.mId = contItem.mItem.toString(); item.mCount = contItem.mCount; item.mRelativeEquipmentSlot = -1; + item.mLockLevel = 0; unsigned int itemCount = std::abs(item.mCount); bool separateStacks = false; diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index ec2e963d12..bfc08a7d64 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -1,6 +1,7 @@ set(LAUNCHER datafilespage.cpp graphicspage.cpp + sdlinit.cpp main.cpp maindialog.cpp playpage.cpp @@ -19,6 +20,7 @@ set(LAUNCHER set(LAUNCHER_HEADER datafilespage.hpp graphicspage.hpp + sdlinit.hpp maindialog.hpp playpage.hpp textslotmsgbox.hpp diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index 3a4c50e9d9..102463085b 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -73,6 +73,12 @@ bool Launcher::AdvancedPage::loadSettings() loadSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game"); loadSettingBool(preventMerchantEquippingCheckBox, "prevent merchant equipping", "Game"); loadSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game"); + loadSettingBool(chargeForEveryFollowerCheckBox, "charge for every follower travelling", "Game"); + loadSettingBool(enchantedWeaponsMagicalCheckBox, "enchanted weapons are magical", "Game"); + loadSettingBool(permanentBarterDispositionChangeCheckBox, "barter disposition change is permanent", "Game"); + int unarmedFactorsStrengthIndex = mEngineSettings.getInt("strength influences hand to hand", "Game"); + if (unarmedFactorsStrengthIndex >= 0 && unarmedFactorsStrengthIndex <= 2) + unarmedFactorsStrengthComboBox->setCurrentIndex(unarmedFactorsStrengthIndex); // Input Settings loadSettingBool(allowThirdPersonZoomCheckBox, "allow third person zoom", "Input"); @@ -125,6 +131,12 @@ void Launcher::AdvancedPage::saveSettings() saveSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game"); saveSettingBool(preventMerchantEquippingCheckBox, "prevent merchant equipping", "Game"); saveSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game"); + saveSettingBool(chargeForEveryFollowerCheckBox, "charge for every follower travelling", "Game"); + saveSettingBool(enchantedWeaponsMagicalCheckBox, "enchanted weapons are magical", "Game"); + saveSettingBool(permanentBarterDispositionChangeCheckBox, "barter disposition change is permanent", "Game"); + int unarmedFactorsStrengthIndex = unarmedFactorsStrengthComboBox->currentIndex(); + if (unarmedFactorsStrengthIndex != mEngineSettings.getInt("strength influences hand to hand", "Game")) + mEngineSettings.setInt("strength influences hand to hand", "Game", unarmedFactorsStrengthIndex); // Input Settings saveSettingBool(allowThirdPersonZoomCheckBox, "allow third person zoom", "Input"); diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index 34442fe714..072f1f36f2 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -12,7 +12,6 @@ #define MAC_OS_X_VERSION_MIN_REQUIRED __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ #endif // MAC_OS_X_VERSION_MIN_REQUIRED -#include #include #include @@ -48,27 +47,15 @@ Launcher::GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, Settings: } -bool Launcher::GraphicsPage::connectToSdl() { - SDL_SetHint(SDL_HINT_RENDER_DRIVER, "software"); - SDL_SetMainReady(); - // Required for determining screen resolution and such on the Graphics tab - if (SDL_Init(SDL_INIT_VIDEO) != 0) - { - return false; - } - signal(SIGINT, SIG_DFL); // We don't want to use the SDL event loop in the launcher, - // so reset SIGINT which SDL wants to redirect to an SDL_Quit event. - - return true; -} - bool Launcher::GraphicsPage::setupSDL() { - bool sdlConnectSuccessful = connectToSdl(); +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + bool sdlConnectSuccessful = initSDL(); if (!sdlConnectSuccessful) { return false; } +#endif int displays = SDL_GetNumVideoDisplays(); @@ -89,8 +76,10 @@ bool Launcher::GraphicsPage::setupSDL() screenComboBox->addItem(QString(tr("Screen ")) + QString::number(i + 1)); } +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) // Disconnect from SDL processes - SDL_Quit(); + quitSDL(); +#endif return true; } diff --git a/apps/launcher/graphicspage.hpp b/apps/launcher/graphicspage.hpp index 0354e5202f..9d943d5e29 100644 --- a/apps/launcher/graphicspage.hpp +++ b/apps/launcher/graphicspage.hpp @@ -7,6 +7,8 @@ #include +#include "sdlinit.hpp" + namespace Files { struct ConfigurationManager; } namespace Launcher @@ -37,11 +39,6 @@ namespace Launcher QStringList getAvailableResolutions(int screen); QRect getMaximumResolution(); - /** - * Connect to the SDL so that we can use it to determine graphics - * @return whether or not connecting to SDL is successful - */ - bool connectToSdl(); bool setupSDL(); }; } diff --git a/apps/launcher/main.cpp b/apps/launcher/main.cpp index 866ae2aa9c..c4cf568c4b 100644 --- a/apps/launcher/main.cpp +++ b/apps/launcher/main.cpp @@ -12,11 +12,18 @@ #endif // MAC_OS_X_VERSION_MIN_REQUIRED #include "maindialog.hpp" +#include "sdlinit.hpp" int main(int argc, char *argv[]) { try { +// Note: we should init SDL2 before Qt4 to avoid crashes on Linux, +// but we should init SDL2 after Qt5 to avoid input issues on MacOS X. +#if QT_VERSION < QT_VERSION_CHECK(5,0,0) + initSDL(); +#endif + QApplication app(argc, argv); // Now we make sure the current dir is set to application path @@ -33,11 +40,18 @@ int main(int argc, char *argv[]) if (result == Launcher::FirstRunDialogResultContinue) mainWin.show(); - return app.exec(); + int exitCode = app.exec(); + +#if QT_VERSION < QT_VERSION_CHECK(5,0,0) + // Disconnect from SDL processes + quitSDL(); +#endif + + return exitCode; } catch (std::exception& e) { std::cerr << "ERROR: " << e.what() << std::endl; return 0; } -} \ No newline at end of file +} diff --git a/apps/launcher/sdlinit.cpp b/apps/launcher/sdlinit.cpp new file mode 100644 index 0000000000..1fe1fd4c2f --- /dev/null +++ b/apps/launcher/sdlinit.cpp @@ -0,0 +1,25 @@ +#include + +#include +#include + +bool initSDL() +{ + SDL_SetHint(SDL_HINT_RENDER_DRIVER, "software"); + SDL_SetMainReady(); + // Required for determining screen resolution and such on the Graphics tab + if (SDL_Init(SDL_INIT_VIDEO) != 0) + { + return false; + } + signal(SIGINT, SIG_DFL); // We don't want to use the SDL event loop in the launcher, + // so reset SIGINT which SDL wants to redirect to an SDL_Quit event. + + return true; +} + +void quitSDL() +{ + // Disconnect from SDL processes + SDL_Quit(); +} diff --git a/apps/launcher/sdlinit.hpp b/apps/launcher/sdlinit.hpp new file mode 100644 index 0000000000..e6718f0e9c --- /dev/null +++ b/apps/launcher/sdlinit.hpp @@ -0,0 +1,8 @@ +#ifndef SDLINIT_H +#define SDLINIT_H + +bool initSDL(); + +void quitSDL(); + +#endif diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 6ca10e0f66..e733d9924b 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -5,11 +5,8 @@ #include #include - -#include - +#include #include - #include #include "model/doc/document.hpp" @@ -27,10 +24,6 @@ CS::Editor::Editor (int argc, char **argv) mLock(), mMerge (mDocumentManager), mIpcServerName ("org.openmw.OpenCS"), mServer(NULL), mClientSocket(NULL) { - // install the crash handler as soon as possible. note that the log path - // does not depend on config being read. - crashCatcherInstall(argc, argv, (mCfgMgr.getLogPath() / "openmw-cs-crash.log").string()); - std::pair > config = readConfig(); setupDataFiles (config.first); @@ -301,7 +294,7 @@ bool CS::Editor::makeIPCServer() mLock = boost::interprocess::file_lock(mPid.string().c_str()); if(!mLock.try_lock()) { - std::cerr << "OpenCS already running." << std::endl; + Log(Debug::Error) << "Error: OpenMW-CS is already running."; return false; } @@ -324,17 +317,17 @@ bool CS::Editor::makeIPCServer() if(boost::filesystem::exists(fullPath.toUtf8().constData())) { // TODO: compare pid of the current process with that in the file - std::cout << "Detected unclean shutdown." << std::endl; + Log(Debug::Info) << "Detected unclean shutdown."; // delete the stale file if(remove(fullPath.toUtf8().constData())) - std::cerr << "ERROR removing stale connection file" << std::endl; + Log(Debug::Error) << "Error: can not remove stale connection file."; } } } catch(const std::exception& e) { - std::cerr << "ERROR " << e.what() << std::endl; + Log(Debug::Error) << "Error: " << e.what(); return false; } diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp index d599f1f961..0473291cea 100644 --- a/apps/opencs/main.cpp +++ b/apps/opencs/main.cpp @@ -1,15 +1,15 @@ #include "editor.hpp" #include -#include #include #include #include #include -#include "model/doc/messages.hpp" +#include +#include "model/doc/messages.hpp" #include "model/world/universalid.hpp" #ifdef Q_OS_MAC @@ -30,7 +30,7 @@ class Application : public QApplication } catch (const std::exception& exception) { - std::cerr << "An exception has been caught: " << exception.what() << std::endl; + Log(Debug::Error) << "An exception has been caught: " << exception.what(); } return false; @@ -41,45 +41,43 @@ class Application : public QApplication Application (int& argc, char *argv[]) : QApplication (argc, argv) {} }; -int main(int argc, char *argv[]) +int runApplication(int argc, char *argv[]) { - #ifdef Q_OS_MAC - setenv("OSG_GL_TEXTURE_STORAGE", "OFF", 0); - #endif +#ifdef Q_OS_MAC + setenv("OSG_GL_TEXTURE_STORAGE", "OFF", 0); +#endif - try - { - // To allow background thread drawing in OSG - QApplication::setAttribute(Qt::AA_X11InitThreads, true); + // To allow background thread drawing in OSG + QApplication::setAttribute(Qt::AA_X11InitThreads, true); - Q_INIT_RESOURCE (resources); + Q_INIT_RESOURCE (resources); - qRegisterMetaType ("std::string"); - qRegisterMetaType ("CSMWorld::UniversalId"); - qRegisterMetaType ("CSMDoc::Message"); + qRegisterMetaType ("std::string"); + qRegisterMetaType ("CSMWorld::UniversalId"); + qRegisterMetaType ("CSMDoc::Message"); - Application application (argc, argv); + Application application (argc, argv); - #ifdef Q_OS_MAC - QDir dir(QCoreApplication::applicationDirPath()); - QDir::setCurrent(dir.absolutePath()); - #endif +#ifdef Q_OS_MAC + QDir dir(QCoreApplication::applicationDirPath()); + QDir::setCurrent(dir.absolutePath()); +#endif - application.setWindowIcon (QIcon (":./openmw-cs.png")); + application.setWindowIcon (QIcon (":./openmw-cs.png")); - CS::Editor editor(argc, argv); + CS::Editor editor(argc, argv); - if(!editor.makeIPCServer()) - { - editor.connectToIPCServer(); - return 0; - } - return editor.run(); - } - catch (std::exception& e) + if(!editor.makeIPCServer()) { - std::cerr << "ERROR: " << e.what() << std::endl; + editor.connectToIPCServer(); return 0; } + return editor.run(); +} + + +int main(int argc, char *argv[]) +{ + return wrapApplication(&runApplication, argc, argv, "OpenMW-CS"); } diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index e45d13aa9b..233b3e439d 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -2,7 +2,6 @@ #include #include -#include #include #include @@ -13,6 +12,8 @@ #include #endif +#include + void CSMDoc::Document::addGmsts() { for (size_t i=0; i < CSMWorld::DefaultGmsts::FloatCount; ++i) @@ -435,7 +436,7 @@ void CSMDoc::Document::modificationStateChanged (bool clean) void CSMDoc::Document::reportMessage (const CSMDoc::Message& message, int type) { /// \todo find a better way to get these messages to the user. - std::cout << message.mMessage << std::endl; + Log(Debug::Info) << message.mMessage; } void CSMDoc::Document::operationDone2 (int type, bool failed) diff --git a/apps/opencs/model/tools/pathgridcheck.cpp b/apps/opencs/model/tools/pathgridcheck.cpp index 6427bb1199..c25845885e 100644 --- a/apps/opencs/model/tools/pathgridcheck.cpp +++ b/apps/opencs/model/tools/pathgridcheck.cpp @@ -109,7 +109,7 @@ void CSMTools::PathgridCheckStage::perform (int stage, CSMDoc::Messages& message pathgrid.mPoints[i].mY == pathgrid.mPoints[j].mY && pathgrid.mPoints[i].mZ == pathgrid.mPoints[j].mZ) { - std::vector::const_iterator it = find(duplList.begin(), duplList.end(), i); + std::vector::const_iterator it = find(duplList.begin(), duplList.end(), static_cast(i)); if (it == duplList.end()) { std::ostringstream ss; diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index 6b586dcec1..7767cca6dc 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -1,8 +1,8 @@ #include "refcollection.hpp" #include -#include +#include #include #include @@ -58,10 +58,10 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool // message if (index.first != mref.mTarget[0] || index.second != mref.mTarget[1]) { - std::cerr << "The Position of moved ref " - << ref.mRefID << " does not match the target cell" << std::endl; - std::cerr << "Position: #" << index.first << " " << index.second - <<", Target #"<< mref.mTarget[0] << " " << mref.mTarget[1] << std::endl; + Log(Debug::Warning) << "Warning: the Position of moved ref " + << ref.mRefID << " does not match the target cell"; + Log(Debug::Warning) << "Position: #" << index.first << " " << index.second + <<", Target #"<< mref.mTarget[0] << " " << mref.mTarget[1]; stream.clear(); stream << "#" << mref.mTarget[0] << " " << mref.mTarget[1]; diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index 961b6c1c1b..8301f4e9ed 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -1,7 +1,6 @@ #include "object.hpp" #include -#include #include #include @@ -24,6 +23,7 @@ #include "../../model/world/cellcoordinates.hpp" #include "../../model/prefs/state.hpp" +#include #include #include #include @@ -133,7 +133,7 @@ void CSVRender::Object::update() catch (std::exception& e) { // TODO: use error marker mesh - std::cerr << e.what() << std::endl; + Log(Debug::Error) << e.what(); } } diff --git a/apps/opencs/view/widget/coloreditor.cpp b/apps/opencs/view/widget/coloreditor.cpp index 5f82f8e26d..82ca2b0c9c 100644 --- a/apps/opencs/view/widget/coloreditor.cpp +++ b/apps/opencs/view/widget/coloreditor.cpp @@ -1,11 +1,9 @@ #include "coloreditor.hpp" #include -#include #include #include #include -#include #include #include "colorpickerpopup.hpp" @@ -27,9 +25,7 @@ CSVWidget::ColorEditor::ColorEditor(QWidget *parent, const bool popupOnStart) mColorPicker(new ColorPickerPopup(this)), mPopupOnStart(popupOnStart) { - setCheckable(true); connect(this, SIGNAL(clicked()), this, SLOT(showPicker())); - connect(mColorPicker, SIGNAL(hid()), this, SLOT(pickerHid())); connect(mColorPicker, SIGNAL(colorChanged(const QColor &)), this, SLOT(pickerColorChanged(const QColor &))); } @@ -85,20 +81,7 @@ void CSVWidget::ColorEditor::setColor(const int colorInt) void CSVWidget::ColorEditor::showPicker() { - if (isChecked()) - { - mColorPicker->showPicker(calculatePopupPosition(), mColor); - } - else - { - mColorPicker->hide(); - } -} - -void CSVWidget::ColorEditor::pickerHid() -{ - setChecked(false); - emit pickingFinished(); + mColorPicker->showPicker(calculatePopupPosition(), mColor); } void CSVWidget::ColorEditor::pickerColorChanged(const QColor &color) diff --git a/apps/opencs/view/widget/coloreditor.hpp b/apps/opencs/view/widget/coloreditor.hpp index 668f22cc94..368896e422 100644 --- a/apps/opencs/view/widget/coloreditor.hpp +++ b/apps/opencs/view/widget/coloreditor.hpp @@ -45,7 +45,6 @@ namespace CSVWidget private slots: void showPicker(); - void pickerHid(); void pickerColorChanged(const QColor &color); signals: diff --git a/apps/opencs/view/widget/colorpickerpopup.cpp b/apps/opencs/view/widget/colorpickerpopup.cpp index 8e71ce39e4..47aab89817 100644 --- a/apps/opencs/view/widget/colorpickerpopup.cpp +++ b/apps/opencs/view/widget/colorpickerpopup.cpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include @@ -19,7 +18,6 @@ CSVWidget::ColorPickerPopup::ColorPickerPopup(QWidget *parent) mColorPicker->setWindowFlags(Qt::Widget); mColorPicker->setOptions(QColorDialog::NoButtons | QColorDialog::DontUseNativeDialog); mColorPicker->installEventFilter(this); - mColorPicker->open(); connect(mColorPicker, SIGNAL(currentColorChanged(const QColor &)), this, @@ -39,8 +37,9 @@ void CSVWidget::ColorPickerPopup::showPicker(const QPoint &position, const QColo geometry.moveTo(position); setGeometry(geometry); - mColorPicker->setCurrentColor(initialColor); - show(); + // Calling getColor() creates a blocking dialog that will continue execution once the user chooses OK or Cancel + QColor color = mColorPicker->getColor(initialColor); + mColorPicker->setCurrentColor(color); } void CSVWidget::ColorPickerPopup::mousePressEvent(QMouseEvent *event) @@ -63,12 +62,6 @@ void CSVWidget::ColorPickerPopup::mousePressEvent(QMouseEvent *event) QFrame::mousePressEvent(event); } -void CSVWidget::ColorPickerPopup::hideEvent(QHideEvent *event) -{ - QFrame::hideEvent(event); - emit hid(); -} - bool CSVWidget::ColorPickerPopup::eventFilter(QObject *object, QEvent *event) { if (object == mColorPicker && event->type() == QEvent::KeyPress) diff --git a/apps/opencs/view/widget/colorpickerpopup.hpp b/apps/opencs/view/widget/colorpickerpopup.hpp index 602bbdb6da..eb5653f469 100644 --- a/apps/opencs/view/widget/colorpickerpopup.hpp +++ b/apps/opencs/view/widget/colorpickerpopup.hpp @@ -20,11 +20,9 @@ namespace CSVWidget protected: virtual void mousePressEvent(QMouseEvent *event); - virtual void hideEvent(QHideEvent *event); virtual bool eventFilter(QObject *object, QEvent *event); signals: - void hid(); void colorChanged(const QColor &color); }; } diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 8b6f9d0b3b..2941ede1b2 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -10,6 +10,8 @@ #include +#include + #include #include @@ -61,7 +63,7 @@ namespace void checkSDLError(int ret) { if (ret != 0) - std::cerr << "SDL error: " << SDL_GetError() << std::endl; + Log(Debug::Error) << "SDL error: " << SDL_GetError(); } } @@ -189,7 +191,7 @@ bool OMW::Engine::frame(float frametime) } catch (const std::exception& e) { - std::cerr << "Error in frame: " << e.what() << std::endl; + Log(Debug::Error) << "Error in frame: " << e.what(); } return true; } @@ -198,6 +200,7 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) : mWindow(NULL) , mEncoding(ToUTF8::WINDOWS_1252) , mEncoder(NULL) + , mScreenCaptureOperation(nullptr) , mSkipMenu (false) , mUseSound (true) , mCompileAll (false) @@ -363,7 +366,7 @@ void OMW::Engine::createWindow(Settings::Manager& settings) // Try with a lower AA if (antialiasing > 0) { - std::cout << "Note: " << antialiasing << "x antialiasing not supported, trying " << antialiasing/2 << std::endl; + Log(Debug::Warning) << "Warning: " << antialiasing << "x antialiasing not supported, trying " << antialiasing/2; antialiasing /= 2; Settings::Manager::setInt("antialiasing", "Video", antialiasing); checkSDLError(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, antialiasing)); @@ -372,7 +375,7 @@ void OMW::Engine::createWindow(Settings::Manager& settings) else { std::stringstream error; - error << "Failed to create SDL window: " << SDL_GetError() << std::endl; + error << "Failed to create SDL window: " << SDL_GetError(); throw std::runtime_error(error.str()); } } @@ -419,16 +422,16 @@ void OMW::Engine::setWindowIcon() std::string windowIcon = (mResDir / "mygui" / "openmw.png").string(); windowIconStream.open(windowIcon, std::ios_base::in | std::ios_base::binary); if (windowIconStream.fail()) - std::cerr << "Error: Failed to open " << windowIcon << std::endl; + Log(Debug::Error) << "Error: Failed to open " << windowIcon; osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension("png"); if (!reader) { - std::cerr << "Error: Failed to read window icon, no png readerwriter found" << std::endl; + Log(Debug::Error) << "Error: Failed to read window icon, no png readerwriter found"; return; } osgDB::ReaderWriter::ReadResult result = reader->readImage(windowIconStream); if (!result.success()) - std::cerr << "Error: Failed to read " << windowIcon << ": " << result.message() << " code " << result.status() << std::endl; + Log(Debug::Error) << "Error: Failed to read " << windowIcon << ": " << result.message() << " code " << result.status(); else { osg::ref_ptr image = result.getImage(); @@ -563,21 +566,19 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) { std::pair result = mEnvironment.getScriptManager()->compileAll(); if (result.first) - std::cout + Log(Debug::Info) << "compiled " << result.second << " of " << result.first << " scripts (" << 100*static_cast (result.second)/result.first - << "%)" - << std::endl; + << "%)"; } if (mCompileAllDialogue) { std::pair result = MWDialogue::ScriptTest::compileAll(&mExtensions, mWarningsMode); if (result.first) - std::cout + Log(Debug::Info) << "compiled " << result.second << " of " << result.first << " dialogue script/actor combinations a(" << 100*static_cast (result.second)/result.first - << "%)" - << std::endl; + << "%)"; } } @@ -613,14 +614,14 @@ public: osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension(mScreenshotFormat); if (!readerwriter) { - std::cerr << "Error: Can't write screenshot, no '" << mScreenshotFormat << "' readerwriter found" << std::endl; + Log(Debug::Error) << "Error: Can't write screenshot, no '" << mScreenshotFormat << "' readerwriter found"; return; } osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(image, outStream); if (!result.success()) { - std::cerr << "Error: Can't write screenshot: " << result.message() << " code " << result.status() << std::endl; + Log(Debug::Error) << "Error: Can't write screenshot: " << result.message() << " code " << result.status(); } } @@ -635,7 +636,7 @@ void OMW::Engine::go() { assert (!mContentFiles.empty()); - std::cout << "OSG version: " << osgGetVersion() << std::endl; + Log(Debug::Info) << "OSG version: " << osgGetVersion(); // Load settings Settings::Manager settings; @@ -737,7 +738,7 @@ void OMW::Engine::go() // Save user settings settings.saveUser(settingspath); - std::cout << "Quitting peacefully." << std::endl; + Log(Debug::Info) << "Quitting peacefully."; } void OMW::Engine::setCompileAll (bool all) diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index d13a731279..176adfad03 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -1,16 +1,11 @@ -#include - #include -#include #include #include #include +#include -#include #include "engine.hpp" -#include - #if defined(_WIN32) // For OutputDebugString #ifndef WIN32_LEAN_AND_MEAN @@ -205,8 +200,8 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat StringsVector content = variables["content"].as().toStdStringVector(); if (content.empty()) { - std::cout << "No content file given (esm/esp, nor omwgame/omwaddon). Aborting..." << std::endl; - return false; + Log(Debug::Error) << "No content file given (esm/esp, nor omwgame/omwaddon). Aborting..."; + return false; } StringsVector::const_iterator it(content.begin()); @@ -220,7 +215,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat engine.setCell(variables["start"].as().toStdString()); engine.setSkipMenu (variables["skip-menu"].as(), variables["new-game"].as()); if (!variables["skip-menu"].as() && variables["new-game"].as()) - std::cerr << "Warning: new-game used without skip-menu -> ignoring it" << std::endl; + Log(Debug::Warning) << "Warning: new-game used without skip-menu -> ignoring it"; // scripts engine.setCompileAll(variables["script-all"].as()); @@ -241,124 +236,33 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat return true; } -#if defined(_WIN32) && defined(_DEBUG) - -class DebugOutput : public boost::iostreams::sink +int runApplication(int argc, char *argv[]) { -public: - std::streamsize write(const char *str, std::streamsize size) - { - // Make a copy for null termination - std::string tmp (str, static_cast(size)); - // Write string to Visual Studio Debug output - OutputDebugString (tmp.c_str ()); - return size; - } -}; -#else -class Tee : public boost::iostreams::sink -{ -public: - Tee(std::ostream &stream, std::ostream &stream2) - : out(stream), out2(stream2) - { - } - - std::streamsize write(const char *str, std::streamsize size) - { - out.write (str, size); - out.flush(); - out2.write (str, size); - out2.flush(); - return size; - } - -private: - std::ostream &out; - std::ostream &out2; -}; +#ifdef __APPLE__ + boost::filesystem::path binary_path = boost::filesystem::system_complete(boost::filesystem::path(argv[0])); + boost::filesystem::current_path(binary_path.parent_path()); + setenv("OSG_GL_TEXTURE_STORAGE", "OFF", 0); #endif + Files::ConfigurationManager cfgMgr; + std::unique_ptr engine; + engine.reset(new OMW::Engine(cfgMgr)); + + if (parseOptions(argc, argv, *engine, cfgMgr)) + { + engine->go(); + } + + return 0; +} + #ifdef ANDROID extern "C" int SDL_main(int argc, char**argv) #else int main(int argc, char**argv) #endif { -#if defined(__APPLE__) - setenv("OSG_GL_TEXTURE_STORAGE", "OFF", 0); -#endif - - // Some objects used to redirect cout and cerr - // Scope must be here, so this still works inside the catch block for logging exceptions - std::streambuf* cout_rdbuf = std::cout.rdbuf (); - std::streambuf* cerr_rdbuf = std::cerr.rdbuf (); - -#if !(defined(_WIN32) && defined(_DEBUG)) - boost::iostreams::stream_buffer coutsb; - boost::iostreams::stream_buffer cerrsb; -#endif - - std::ostream oldcout(cout_rdbuf); - std::ostream oldcerr(cerr_rdbuf); - - boost::filesystem::ofstream logfile; - - std::unique_ptr engine; - - int ret = 0; - try - { - Files::ConfigurationManager cfgMgr; - -#if defined(_WIN32) && defined(_DEBUG) - // Redirect cout and cerr to VS debug output when running in debug mode - boost::iostreams::stream_buffer sb; - sb.open(DebugOutput()); - std::cout.rdbuf (&sb); - std::cerr.rdbuf (&sb); -#else - // Redirect cout and cerr to openmw.log - logfile.open (boost::filesystem::path(cfgMgr.getLogPath() / "/openmw.log")); - - coutsb.open (Tee(logfile, oldcout)); - cerrsb.open (Tee(logfile, oldcerr)); - - std::cout.rdbuf (&coutsb); - std::cerr.rdbuf (&cerrsb); -#endif - - crashCatcherInstall(argc, argv, (cfgMgr.getLogPath() / "crash.log").string()); - -#ifdef __APPLE__ - boost::filesystem::path binary_path = boost::filesystem::system_complete(boost::filesystem::path(argv[0])); - boost::filesystem::current_path(binary_path.parent_path()); -#endif - - engine.reset(new OMW::Engine(cfgMgr)); - - if (parseOptions(argc, argv, *engine, cfgMgr)) - { - engine->go(); - } - } - catch (std::exception &e) - { -#if (defined(__APPLE__) || defined(__linux) || defined(__unix) || defined(__posix)) - if (!isatty(fileno(stdin))) -#endif - SDL_ShowSimpleMessageBox(0, "OpenMW: Fatal error", e.what(), NULL); - - std::cerr << "\nERROR: " << e.what() << std::endl; - - ret = 1; - } - - // Restore cout and cerr - std::cout.rdbuf(cout_rdbuf); - std::cerr.rdbuf(cerr_rdbuf); - - return ret; + return wrapApplication(&runApplication, argc, argv, "OpenMW"); } // Platform specific for Windows when there is no console built into the executable. diff --git a/apps/openmw/mwbase/dialoguemanager.hpp b/apps/openmw/mwbase/dialoguemanager.hpp index 7bace3790b..2ecab1c4cc 100644 --- a/apps/openmw/mwbase/dialoguemanager.hpp +++ b/apps/openmw/mwbase/dialoguemanager.hpp @@ -75,8 +75,8 @@ namespace MWBase virtual void persuade (int type, ResponseCallback* callback) = 0; virtual int getTemporaryDispositionChange () const = 0; - /// @note This change is temporary and gets discarded when dialogue ends. - virtual void applyDispositionChange (int delta) = 0; + /// @note Controlled by an option, gets discarded when dialogue ends by default + virtual void applyBarterDispositionChange (int delta) = 0; virtual int countSavedGameRecords() const = 0; diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 995c8d736c..fe3fc5721b 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -257,7 +257,7 @@ namespace MWBase virtual void cleanupSummonedCreature(const MWWorld::Ptr& caster, int creatureActorId) = 0; virtual void confiscateStolenItemToOwner(const MWWorld::Ptr &player, const MWWorld::Ptr &item, const MWWorld::Ptr& victim, int count) = 0; - virtual bool isAttackPrepairing(const MWWorld::Ptr& ptr) = 0; + virtual bool isAttackPreparing(const MWWorld::Ptr& ptr) = 0; virtual bool isRunning(const MWWorld::Ptr& ptr) = 0; virtual bool isSneaking(const MWWorld::Ptr& ptr) = 0; }; diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index a886166253..72c4c3a2ab 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -412,6 +412,7 @@ namespace MWBase /// @note throws an exception when invoked on a teleport door virtual void activateDoor(const MWWorld::Ptr& door, int state) = 0; + virtual void getActorsStandingOn (const MWWorld::ConstPtr& object, std::vector &actors) = 0; ///< get a list of actors standing on \a object virtual bool getPlayerStandingOn (const MWWorld::ConstPtr& object) = 0; ///< @return true if the player is standing on \a object virtual bool getActorStandingOn (const MWWorld::ConstPtr& object) = 0; ///< @return true if any actor is standing on \a object virtual bool getPlayerCollidingWith(const MWWorld::ConstPtr& object) = 0; ///< @return true if the player is colliding with \a object @@ -490,8 +491,8 @@ namespace MWBase virtual void castSpell (const MWWorld::Ptr& actor, bool manualSpell=false) = 0; virtual void launchMagicBolt (const std::string& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection) = 0; - virtual void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile, - const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr bow, float speed, float attackStrength) = 0; + virtual void launchProjectile (MWWorld::Ptr& actor, MWWorld::Ptr& projectile, + const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr& bow, float speed, float attackStrength) = 0; virtual void applyLoopingParticles(const MWWorld::Ptr& ptr) = 0; @@ -536,7 +537,7 @@ namespace MWBase /// Spawn a blood effect for \a ptr at \a worldPosition virtual void spawnBloodEffect (const MWWorld::Ptr& ptr, const osg::Vec3f& worldPosition) = 0; - virtual void spawnEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos) = 0; + virtual void spawnEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos, float scale = 1.f, bool isMagicVFX = true) = 0; virtual void explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const MWWorld::Ptr& ignore, ESM::RangeType rangeType, const std::string& id, @@ -566,6 +567,9 @@ namespace MWBase virtual bool isPlayerInJail() const = 0; + virtual void setPlayerTraveling(bool traveling) = 0; + virtual bool isPlayerTraveling() const = 0; + virtual void rotateWorldObject (const MWWorld::Ptr& ptr, osg::Quat rotate) = 0; /// Return terrain height at \a worldPos position. diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index 0d267046fe..b90c1ec581 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -142,14 +142,14 @@ namespace MWClass const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); - float iWeight = floor(gmst.find(typeGmst)->getFloat()); + float iWeight = floor(gmst.find(typeGmst)->mValue.getFloat()); float epsilon = 0.0005f; - if (ref->mBase->mData.mWeight <= iWeight * gmst.find ("fLightMaxMod")->getFloat() + epsilon) + if (ref->mBase->mData.mWeight <= iWeight * gmst.find ("fLightMaxMod")->mValue.getFloat() + epsilon) return ESM::Skill::LightArmor; - if (ref->mBase->mData.mWeight <= iWeight * gmst.find ("fMedMaxMod")->getFloat() + epsilon) + if (ref->mBase->mData.mWeight <= iWeight * gmst.find ("fMedMaxMod")->mValue.getFloat() + epsilon) return ESM::Skill::MediumArmor; else @@ -285,7 +285,7 @@ namespace MWClass int armorSkill = actor.getClass().getSkill(actor, armorSkillType); const MWBase::World *world = MWBase::Environment::get().getWorld(); - int iBaseArmorSkill = world->getStore().get().find("iBaseArmorSkill")->getInt(); + int iBaseArmorSkill = world->getStore().get().find("iBaseArmorSkill")->mValue.getInteger(); if(ref->mBase->mData.mWeight == 0) return ref->mBase->mData.mArmor; diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 02a8a17ef0..6999558ae9 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -1,7 +1,7 @@ #include "creature.hpp" #include - +#include #include #include #include @@ -146,7 +146,7 @@ namespace MWClass if (const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(*iter)) data->mCreatureStats.getSpells().add (spell); else /// \todo add option to make this a fatal error message pop-up, but default to warning for vanilla compatibility - std::cerr << "Warning: ignoring nonexistent spell '" << *iter << "' on creature '" << ref->mBase->mId << "'" << std::endl; + Log(Debug::Warning) << "Warning: ignoring nonexistent spell '" << *iter << "' on creature '" << ref->mBase->mId << "'"; } // inventory @@ -247,13 +247,13 @@ namespace MWClass MWMechanics::applyFatigueLoss(ptr, weapon, attackStrength); - float dist = gmst.find("fCombatDistance")->getFloat(); + float dist = gmst.find("fCombatDistance")->mValue.getFloat(); if (!weapon.isEmpty()) dist *= weapon.get()->mBase->mData.mReach; // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. std::vector targetActors; - if (!ptr.isEmpty() && ptr.getClass().isActor() && ptr != MWMechanics::getPlayer()) + if (!ptr.isEmpty() && ptr.getClass().isActor()) ptr.getClass().getCreatureStats(ptr).getAiSequence().getCombatTargets(targetActors); std::pair result = MWBase::Environment::get().getWorld()->getHitContact(ptr, dist, targetActors); @@ -325,9 +325,6 @@ namespace MWClass if (MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage, attackStrength)) damage = 0; - if (victim == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState()) - damage = 0; - MWMechanics::diseaseContact(victim, ptr); victim.getClass().onHit(victim, damage, healthdmg, weapon, ptr, hitPosition, true); @@ -376,11 +373,6 @@ namespace MWClass ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1); } - bool godmode = object == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); - - if (godmode) - damage = 0; - if (!successful) { // Missed @@ -402,9 +394,9 @@ namespace MWClass if (!attacker.isEmpty()) { // Check for knockdown - float agilityTerm = stats.getAttribute(ESM::Attribute::Agility).getModified() * getGmst().fKnockDownMult->getFloat(); + float agilityTerm = stats.getAttribute(ESM::Attribute::Agility).getModified() * getGmst().fKnockDownMult->mValue.getFloat(); float knockdownTerm = stats.getAttribute(ESM::Attribute::Agility).getModified() - * getGmst().iKnockDownOddsMult->getInt() * 0.01f + getGmst().iKnockDownOddsBase->getInt(); + * getGmst().iKnockDownOddsMult->mValue.getInteger() * 0.01f + getGmst().iKnockDownOddsBase->mValue.getInteger(); if (ishealth && agilityTerm <= damage && knockdownTerm <= Misc::Rng::roll0to99()) stats.setKnockedDown(true); else @@ -415,7 +407,7 @@ namespace MWClass if(ishealth) { - if (!attacker.isEmpty() && !godmode) + if (!attacker.isEmpty()) { damage = scaleDamage(damage, attacker, ptr); MWBase::Environment::get().getWorld()->spawnBloodEffect(ptr, hitPosition); @@ -524,8 +516,8 @@ namespace MWClass const GMST& gmst = getGmst(); - float walkSpeed = gmst.fMinWalkSpeedCreature->getFloat() + 0.01f * stats.getAttribute(ESM::Attribute::Speed).getModified() - * (gmst.fMaxWalkSpeedCreature->getFloat() - gmst.fMinWalkSpeedCreature->getFloat()); + float walkSpeed = gmst.fMinWalkSpeedCreature->mValue.getFloat() + 0.01f * stats.getAttribute(ESM::Attribute::Speed).getModified() + * (gmst.fMaxWalkSpeedCreature->mValue.getFloat() - gmst.fMinWalkSpeedCreature->mValue.getFloat()); const MWBase::World *world = MWBase::Environment::get().getWorld(); const MWMechanics::MagicEffects &mageffects = stats.getMagicEffects(); @@ -544,9 +536,9 @@ namespace MWClass { float flySpeed = 0.01f*(stats.getAttribute(ESM::Attribute::Speed).getModified() + mageffects.get(ESM::MagicEffect::Levitate).getMagnitude()); - flySpeed = gmst.fMinFlySpeed->getFloat() + flySpeed*(gmst.fMaxFlySpeed->getFloat() - gmst.fMinFlySpeed->getFloat()); + flySpeed = gmst.fMinFlySpeed->mValue.getFloat() + flySpeed*(gmst.fMaxFlySpeed->mValue.getFloat() - gmst.fMinFlySpeed->mValue.getFloat()); const float normalizedEncumbrance = getNormalizedEncumbrance(ptr); - flySpeed *= 1.0f - gmst.fEncumberedMoveEffect->getFloat() * normalizedEncumbrance; + flySpeed *= 1.0f - gmst.fEncumberedMoveEffect->mValue.getFloat() * normalizedEncumbrance; flySpeed = std::max(0.0f, flySpeed); moveSpeed = flySpeed; } @@ -556,8 +548,8 @@ namespace MWClass if(running) swimSpeed = runSpeed; swimSpeed *= 1.0f + 0.01f * mageffects.get(ESM::MagicEffect::SwiftSwim).getMagnitude(); - swimSpeed *= gmst.fSwimRunBase->getFloat() + 0.01f*getSkill(ptr, ESM::Skill::Athletics) * - gmst.fSwimRunAthleticsMult->getFloat(); + swimSpeed *= gmst.fSwimRunBase->mValue.getFloat() + 0.01f*getSkill(ptr, ESM::Skill::Athletics) * + gmst.fSwimRunAthleticsMult->mValue.getFloat(); moveSpeed = swimSpeed; } else if(running) @@ -822,8 +814,8 @@ namespace MWClass return; const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); - static const float fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->getFloat(); - static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->getFloat(); + static const float fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->mValue.getFloat(); + static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->mValue.getFloat(); float delay = ptr.getRefData().getCount() == 0 ? fCorpseClearDelay : std::min(fCorpseRespawnDelay, fCorpseClearDelay); diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp index 4a4d9793ee..b8c4cbb620 100644 --- a/apps/openmw/mwclass/creaturelevlist.cpp +++ b/apps/openmw/mwclass/creaturelevlist.cpp @@ -56,8 +56,8 @@ namespace MWClass else if (creatureStats.isDead()) { const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); - static const float fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->getFloat(); - static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->getFloat(); + static const float fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->mValue.getFloat(); + static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->mValue.getFloat(); float delay = std::min(fCorpseRespawnDelay, fCorpseClearDelay); if (creatureStats.getTimeOfDeath() + delay <= MWBase::Environment::get().getWorld()->getTimeStamp()) diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index df7c61ebf1..c133b6a266 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -138,7 +138,7 @@ namespace MWClass int alchemySkill = npcStats.getSkill (ESM::Skill::Alchemy).getBase(); static const float fWortChanceValue = - MWBase::Environment::get().getWorld()->getStore().get().find("fWortChanceValue")->getFloat(); + MWBase::Environment::get().getWorld()->getStore().get().find("fWortChanceValue")->mValue.getFloat(); MWGui::Widgets::SpellEffectList list; for (int i=0; i<4; ++i) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 1c293f1e41..c247ee543e 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -4,6 +4,7 @@ #include +#include #include #include #include @@ -365,15 +366,15 @@ namespace MWClass if (const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(*iter)) data->mNpcStats.getSpells().add (spell); else - std::cerr << "Warning: ignoring nonexistent race power '" << *iter << "' on NPC '" << ref->mBase->mId << "'" << std::endl; + Log(Debug::Warning) << "Warning: ignoring nonexistent race power '" << *iter << "' on NPC '" << ref->mBase->mId << "'"; } if (!ref->mBase->mFaction.empty()) { static const int iAutoRepFacMod = MWBase::Environment::get().getWorld()->getStore().get() - .find("iAutoRepFacMod")->getInt(); + .find("iAutoRepFacMod")->mValue.getInteger(); static const int iAutoRepLevMod = MWBase::Environment::get().getWorld()->getStore().get() - .find("iAutoRepLevMod")->getInt(); + .find("iAutoRepLevMod")->mValue.getInteger(); int rank = ref->mBase->getFactionRank(); data->mNpcStats.setReputation(iAutoRepFacMod * (rank+1) + iAutoRepLevMod * (data->mNpcStats.getLevel()-1)); @@ -395,7 +396,7 @@ namespace MWClass else { /// \todo add option to make this a fatal error message pop-up, but default to warning for vanilla compatibility - std::cerr << "Warning: ignoring nonexistent spell '" << *iter << "' on NPC '" << ref->mBase->mId << "'" << std::endl; + Log(Debug::Warning) << "Warning: ignoring nonexistent spell '" << *iter << "' on NPC '" << ref->mBase->mId << "'"; } } @@ -527,7 +528,7 @@ namespace MWClass const MWBase::World *world = MWBase::Environment::get().getWorld(); const MWWorld::Store &store = world->getStore().get(); - return store.find("sWerewolfPopup")->getString(); + return store.find("sWerewolfPopup")->mValue.getString(); } const MWWorld::LiveCellRef *ref = ptr.get(); @@ -564,10 +565,10 @@ namespace MWClass MWMechanics::applyFatigueLoss(ptr, weapon, attackStrength); - const float fCombatDistance = store.find("fCombatDistance")->getFloat(); + const float fCombatDistance = store.find("fCombatDistance")->mValue.getFloat(); float dist = fCombatDistance * (!weapon.isEmpty() ? weapon.get()->mBase->mData.mReach : - store.find("fHandToHandReach")->getFloat()); + store.find("fHandToHandReach")->mValue.getFloat()); // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. std::vector targetActors; @@ -637,14 +638,14 @@ namespace MWClass && !MWBase::Environment::get().getMechanicsManager()->awarenessCheck(ptr, victim); if(unaware) { - damage *= store.find("fCombatCriticalStrikeMult")->getFloat(); + damage *= store.find("fCombatCriticalStrikeMult")->mValue.getFloat(); MWBase::Environment::get().getWindowManager()->messageBox("#{sTargetCriticalStrike}"); MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f); } } if (othercls.getCreatureStats(victim).getKnockedDown()) - damage *= store.find("fCombatKODamageMult")->getFloat(); + damage *= store.find("fCombatKODamageMult")->mValue.getFloat(); // Apply "On hit" enchanted weapons MWMechanics::applyOnStrikeEnchantment(ptr, victim, weapon, hitPosition); @@ -736,14 +737,14 @@ namespace MWClass const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const GMST& gmst = getGmst(); - int chance = store.get().find("iVoiceHitOdds")->getInt(); + int chance = store.get().find("iVoiceHitOdds")->mValue.getInteger(); if (Misc::Rng::roll0to99() < chance) MWBase::Environment::get().getDialogueManager()->say(ptr, "hit"); // Check for knockdown - float agilityTerm = stats.getAttribute(ESM::Attribute::Agility).getModified() * gmst.fKnockDownMult->getFloat(); + float agilityTerm = stats.getAttribute(ESM::Attribute::Agility).getModified() * gmst.fKnockDownMult->mValue.getFloat(); float knockdownTerm = stats.getAttribute(ESM::Attribute::Agility).getModified() - * gmst.iKnockDownOddsMult->getInt() * 0.01f + gmst.iKnockDownOddsBase->getInt(); + * gmst.iKnockDownOddsMult->mValue.getInteger() * 0.01f + gmst.iKnockDownOddsBase->mValue.getInteger(); if (ishealth && agilityTerm <= damage && knockdownTerm <= Misc::Rng::roll0to99()) stats.setKnockedDown(true); else @@ -771,7 +772,7 @@ namespace MWClass float unmitigatedDamage = damage; float x = damage / (damage + getArmorRating(ptr)); - damage *= std::max(gmst.fCombatArmorMinMult->getFloat(), x); + damage *= std::max(gmst.fCombatArmorMinMult->mValue.getFloat(), x); int damageDiff = static_cast(unmitigatedDamage - damage); damage = std::max(1.f, damage); damageDiff = std::max(1, damageDiff); @@ -940,15 +941,15 @@ namespace MWClass bool sneaking = stats.getStance(MWMechanics::CreatureStats::Stance_Sneak); bool running = stats.getStance(MWMechanics::CreatureStats::Stance_Run); - float walkSpeed = gmst.fMinWalkSpeed->getFloat() + 0.01f*npcdata->mNpcStats.getAttribute(ESM::Attribute::Speed).getModified()* - (gmst.fMaxWalkSpeed->getFloat() - gmst.fMinWalkSpeed->getFloat()); - walkSpeed *= 1.0f - gmst.fEncumberedMoveEffect->getFloat()*normalizedEncumbrance; + float walkSpeed = gmst.fMinWalkSpeed->mValue.getFloat() + 0.01f*npcdata->mNpcStats.getAttribute(ESM::Attribute::Speed).getModified()* + (gmst.fMaxWalkSpeed->mValue.getFloat() - gmst.fMinWalkSpeed->mValue.getFloat()); + walkSpeed *= 1.0f - gmst.fEncumberedMoveEffect->mValue.getFloat()*normalizedEncumbrance; walkSpeed = std::max(0.0f, walkSpeed); if(sneaking) - walkSpeed *= gmst.fSneakSpeedMultiplier->getFloat(); + walkSpeed *= gmst.fSneakSpeedMultiplier->mValue.getFloat(); float runSpeed = walkSpeed*(0.01f * npcdata->mNpcStats.getSkill(ESM::Skill::Athletics).getModified() * - gmst.fAthleticsRunBonus->getFloat() + gmst.fBaseRunMultiplier->getFloat()); + gmst.fAthleticsRunBonus->mValue.getFloat() + gmst.fBaseRunMultiplier->mValue.getFloat()); float moveSpeed; if(getEncumbrance(ptr) > getCapacity(ptr)) @@ -958,8 +959,8 @@ namespace MWClass { float flySpeed = 0.01f*(npcdata->mNpcStats.getAttribute(ESM::Attribute::Speed).getModified() + mageffects.get(ESM::MagicEffect::Levitate).getMagnitude()); - flySpeed = gmst.fMinFlySpeed->getFloat() + flySpeed*(gmst.fMaxFlySpeed->getFloat() - gmst.fMinFlySpeed->getFloat()); - flySpeed *= 1.0f - gmst.fEncumberedMoveEffect->getFloat() * normalizedEncumbrance; + flySpeed = gmst.fMinFlySpeed->mValue.getFloat() + flySpeed*(gmst.fMaxFlySpeed->mValue.getFloat() - gmst.fMinFlySpeed->mValue.getFloat()); + flySpeed *= 1.0f - gmst.fEncumberedMoveEffect->mValue.getFloat() * normalizedEncumbrance; flySpeed = std::max(0.0f, flySpeed); moveSpeed = flySpeed; } @@ -969,8 +970,8 @@ namespace MWClass if(running) swimSpeed = runSpeed; swimSpeed *= 1.0f + 0.01f * mageffects.get(ESM::MagicEffect::SwiftSwim).getMagnitude(); - swimSpeed *= gmst.fSwimRunBase->getFloat() + 0.01f*npcdata->mNpcStats.getSkill(ESM::Skill::Athletics).getModified()* - gmst.fSwimRunAthleticsMult->getFloat(); + swimSpeed *= gmst.fSwimRunBase->mValue.getFloat() + 0.01f*npcdata->mNpcStats.getSkill(ESM::Skill::Athletics).getModified()* + gmst.fSwimRunAthleticsMult->mValue.getFloat(); moveSpeed = swimSpeed; } else if(running && !sneaking) @@ -981,7 +982,7 @@ namespace MWClass moveSpeed *= 0.75f; if(npcdata->mNpcStats.isWerewolf() && running && npcdata->mNpcStats.getDrawState() == MWMechanics::DrawState_Nothing) - moveSpeed *= gmst.fWereWolfRunMult->getFloat(); + moveSpeed *= gmst.fWereWolfRunMult->mValue.getFloat(); return moveSpeed; } @@ -991,11 +992,15 @@ namespace MWClass if(getEncumbrance(ptr) > getCapacity(ptr)) return 0.f; + const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); + if (stats.isParalyzed() || stats.getKnockedDown() || stats.isDead()) + return 0.f; + const NpcCustomData *npcdata = static_cast(ptr.getRefData().getCustomData()); const GMST& gmst = getGmst(); const MWMechanics::MagicEffects &mageffects = npcdata->mNpcStats.getMagicEffects(); - const float encumbranceTerm = gmst.fJumpEncumbranceBase->getFloat() + - gmst.fJumpEncumbranceMultiplier->getFloat() * + const float encumbranceTerm = gmst.fJumpEncumbranceBase->mValue.getFloat() + + gmst.fJumpEncumbranceMultiplier->mValue.getFloat() * (1.0f - Npc::getNormalizedEncumbrance(ptr)); float a = static_cast(npcdata->mNpcStats.getSkill(ESM::Skill::Acrobatics).getModified()); @@ -1006,14 +1011,14 @@ namespace MWClass a = 50.0f; } - float x = gmst.fJumpAcrobaticsBase->getFloat() + - std::pow(a / 15.0f, gmst.fJumpAcroMultiplier->getFloat()); - x += 3.0f * b * gmst.fJumpAcroMultiplier->getFloat(); + float x = gmst.fJumpAcrobaticsBase->mValue.getFloat() + + std::pow(a / 15.0f, gmst.fJumpAcroMultiplier->mValue.getFloat()); + x += 3.0f * b * gmst.fJumpAcroMultiplier->mValue.getFloat(); x += mageffects.get(ESM::MagicEffect::Jump).getMagnitude() * 64; x *= encumbranceTerm; - if(ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run)) - x *= gmst.fJumpRunMultiplier->getFloat(); + if(stats.getStance(MWMechanics::CreatureStats::Stance_Run)) + x *= gmst.fJumpRunMultiplier->mValue.getFloat(); x *= npcdata->mNpcStats.getFatigueTerm(); x -= -627.2f;/*gravity constant*/ x /= 3.0f; @@ -1078,7 +1083,7 @@ namespace MWClass float Npc::getCapacity (const MWWorld::Ptr& ptr) const { const MWMechanics::CreatureStats& stats = getCreatureStats (ptr); - static const float fEncumbranceStrMult = MWBase::Environment::get().getWorld()->getStore().get().find("fEncumbranceStrMult")->getFloat(); + static const float fEncumbranceStrMult = MWBase::Environment::get().getWorld()->getStore().get().find("fEncumbranceStrMult")->mValue.getFloat(); return stats.getAttribute(ESM::Attribute::Strength).getModified()*fEncumbranceStrMult; } @@ -1121,8 +1126,8 @@ namespace MWClass MWMechanics::NpcStats &stats = getNpcStats(ptr); const MWWorld::InventoryStore &invStore = getInventoryStore(ptr); - float fUnarmoredBase1 = store.find("fUnarmoredBase1")->getFloat(); - float fUnarmoredBase2 = store.find("fUnarmoredBase2")->getFloat(); + float fUnarmoredBase1 = store.find("fUnarmoredBase1")->mValue.getFloat(); + float fUnarmoredBase2 = store.find("fUnarmoredBase2")->mValue.getFloat(); int unarmoredSkill = stats.getSkill(ESM::Skill::Unarmored).getModified(); float ratings[MWWorld::InventoryStore::Slots]; @@ -1240,11 +1245,10 @@ namespace MWClass { MWBase::World *world = MWBase::Environment::get().getWorld(); osg::Vec3f pos(ptr.getRefData().getPosition().asVec3()); - if(world->isUnderwater(ptr.getCell(), pos) || world->isWalkingOnWater(ptr)) + if (world->isUnderwater(ptr.getCell(), pos) || world->isWalkingOnWater(ptr)) return "DefaultLandWater"; - if(world->isOnGround(ptr)) - return "DefaultLand"; - return ""; + + return "DefaultLand"; } if(name == "swimleft") return "Swim Left"; @@ -1363,8 +1367,8 @@ namespace MWClass return; const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); - static const float fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->getFloat(); - static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->getFloat(); + static const float fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->mValue.getFloat(); + static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->mValue.getFloat(); float delay = ptr.getRefData().getCount() == 0 ? fCorpseClearDelay : std::min(fCorpseRespawnDelay, fCorpseClearDelay); diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index bb1ca09a34..466ae47162 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -268,27 +268,30 @@ namespace MWClass { text += "\n#{sType} "; - std::map > mapping; - mapping[ESM::Weapon::ShortBladeOneHand] = std::make_pair("sSkillShortblade", "sOneHanded"); - mapping[ESM::Weapon::LongBladeOneHand] = std::make_pair("sSkillLongblade", "sOneHanded"); - mapping[ESM::Weapon::LongBladeTwoHand] = std::make_pair("sSkillLongblade", "sTwoHanded"); - mapping[ESM::Weapon::BluntOneHand] = std::make_pair("sSkillBluntweapon", "sOneHanded"); - mapping[ESM::Weapon::BluntTwoClose] = std::make_pair("sSkillBluntweapon", "sTwoHanded"); - mapping[ESM::Weapon::BluntTwoWide] = std::make_pair("sSkillBluntweapon", "sTwoHanded"); - mapping[ESM::Weapon::SpearTwoWide] = std::make_pair("sSkillSpear", "sTwoHanded"); - mapping[ESM::Weapon::AxeOneHand] = std::make_pair("sSkillAxe", "sOneHanded"); - mapping[ESM::Weapon::AxeTwoHand] = std::make_pair("sSkillAxe", "sTwoHanded"); - mapping[ESM::Weapon::MarksmanBow] = std::make_pair("sSkillMarksman", ""); - mapping[ESM::Weapon::MarksmanCrossbow] = std::make_pair("sSkillMarksman", ""); - mapping[ESM::Weapon::MarksmanThrown] = std::make_pair("sSkillMarksman", ""); - mapping[ESM::Weapon::Arrow] = std::make_pair("sSkillMarksman", ""); - mapping[ESM::Weapon::Bolt] = std::make_pair("sSkillMarksman", ""); + static std::map > mapping; + if (mapping.empty()) + { + mapping[ESM::Weapon::ShortBladeOneHand] = std::make_pair("sSkillShortblade", "sOneHanded"); + mapping[ESM::Weapon::LongBladeOneHand] = std::make_pair("sSkillLongblade", "sOneHanded"); + mapping[ESM::Weapon::LongBladeTwoHand] = std::make_pair("sSkillLongblade", "sTwoHanded"); + mapping[ESM::Weapon::BluntOneHand] = std::make_pair("sSkillBluntweapon", "sOneHanded"); + mapping[ESM::Weapon::BluntTwoClose] = std::make_pair("sSkillBluntweapon", "sTwoHanded"); + mapping[ESM::Weapon::BluntTwoWide] = std::make_pair("sSkillBluntweapon", "sTwoHanded"); + mapping[ESM::Weapon::SpearTwoWide] = std::make_pair("sSkillSpear", "sTwoHanded"); + mapping[ESM::Weapon::AxeOneHand] = std::make_pair("sSkillAxe", "sOneHanded"); + mapping[ESM::Weapon::AxeTwoHand] = std::make_pair("sSkillAxe", "sTwoHanded"); + mapping[ESM::Weapon::MarksmanBow] = std::make_pair("sSkillMarksman", ""); + mapping[ESM::Weapon::MarksmanCrossbow] = std::make_pair("sSkillMarksman", ""); + mapping[ESM::Weapon::MarksmanThrown] = std::make_pair("sSkillMarksman", ""); + mapping[ESM::Weapon::Arrow] = std::make_pair("sSkillMarksman", ""); + mapping[ESM::Weapon::Bolt] = std::make_pair("sSkillMarksman", ""); + } - std::string type = mapping[ref->mBase->mData.mType].first; - std::string oneOrTwoHanded = mapping[ref->mBase->mData.mType].second; + const std::string type = mapping[ref->mBase->mData.mType].first; + const std::string oneOrTwoHanded = mapping[ref->mBase->mData.mType].second; - text += store.get().find(type)->getString() + - ((oneOrTwoHanded != "") ? ", " + store.get().find(oneOrTwoHanded)->getString() : ""); + text += store.get().find(type)->mValue.getString() + + ((oneOrTwoHanded != "") ? ", " + store.get().find(oneOrTwoHanded)->mValue.getString() : ""); // weapon damage if (ref->mBase->mData.mType >= 9) @@ -326,7 +329,7 @@ namespace MWClass if (ref->mBase->mData.mType < 9 && Settings::Manager::getBool("show melee info", "Game")) { // 64 game units = 1 yard = 3 ft, display value in feet - const float combatDistance = store.get().find("fCombatDistance")->getFloat() * ref->mBase->mData.mReach; + const float combatDistance = store.get().find("fCombatDistance")->mValue.getFloat() * ref->mBase->mData.mReach; text += MWGui::ToolTips::getWeightString(combatDistance*3/64, "#{sRange}"); text += " #{sFeet}"; diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index fb492ff3b2..dcc33d1c32 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -5,7 +5,8 @@ #include #include #include -#include + +#include #include #include @@ -22,6 +23,8 @@ #include #include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/journal.hpp" @@ -201,16 +204,14 @@ namespace MWDialogue } catch (const std::exception& error) { - std::cerr << std::string ("Dialogue error: An exception has been thrown: ") + error.what() << std::endl; + Log(Debug::Error) << std::string ("Dialogue error: An exception has been thrown: ") + error.what(); success = false; } if (!success) { - std::cerr - << "Warning: compiling failed (dialogue script)" << std::endl - << cmd - << std::endl << std::endl; + Log(Debug::Warning) + << "Warning: compiling failed (dialogue script)\n" << cmd << "\n\n"; } return success; @@ -230,7 +231,7 @@ namespace MWDialogue } catch (const std::exception& error) { - std::cerr << std::string ("Dialogue error: An exception has been thrown: ") + error.what() << std::endl; + Log(Debug::Error) << std::string ("Dialogue error: An exception has been thrown: ") + error.what(); } } } @@ -260,7 +261,7 @@ namespace MWDialogue const MWWorld::Store& gmsts = MWBase::Environment::get().getWorld()->getStore().get(); - title = gmsts.find (modifiedTopic)->getString(); + title = gmsts.find (modifiedTopic)->mValue.getString(); } else title = topic; @@ -508,9 +509,11 @@ namespace MWDialogue return static_cast(mTemporaryDispositionChange); } - void DialogueManager::applyDispositionChange(int delta) + void DialogueManager::applyBarterDispositionChange(int delta) { mTemporaryDispositionChange += delta; + if (Settings::Manager::getBool("barter disposition change is permanent", "Game")) + mPermanentDispositionChange += delta; } bool DialogueManager::checkServiceRefused(ResponseCallback* callback) @@ -534,7 +537,7 @@ namespace MWDialogue MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor); - callback->addResponse(gmsts.find ("sServiceRefusal")->getString(), Interpreter::fixDefinesDialog(info->mResponse, interpreterContext)); + callback->addResponse(gmsts.find ("sServiceRefusal")->mValue.getString(), Interpreter::fixDefinesDialog(info->mResponse, interpreterContext)); executeScript (info->mResultScript, mActor); return true; diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp index f267f7542d..29a90082c7 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp @@ -94,8 +94,8 @@ namespace MWDialogue virtual void persuade (int type, ResponseCallback* callback); virtual int getTemporaryDispositionChange () const; - /// @note This change is temporary and gets discarded when dialogue ends. - virtual void applyDispositionChange (int delta); + /// @note Controlled by an option, gets discarded when dialogue ends by default + virtual void applyBarterDispositionChange (int delta); virtual int countSavedGameRecords() const; diff --git a/apps/openmw/mwdialogue/scripttest.cpp b/apps/openmw/mwdialogue/scripttest.cpp index 54b4d8cd9f..4f0b7422c7 100644 --- a/apps/openmw/mwdialogue/scripttest.cpp +++ b/apps/openmw/mwdialogue/scripttest.cpp @@ -1,7 +1,5 @@ #include "scripttest.hpp" -#include - #include "../mwworld/manualref.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/class.hpp" @@ -12,6 +10,7 @@ #include "../mwscript/compilercontext.hpp" +#include #include #include #include @@ -80,16 +79,14 @@ void test(const MWWorld::Ptr& actor, int &compiled, int &total, const Compiler:: } catch (const std::exception& error) { - std::cerr << std::string ("Dialogue error: An exception has been thrown: ") + error.what() << std::endl; + Log(Debug::Error) << std::string ("Dialogue error: An exception has been thrown: ") + error.what(); success = false; } if (!success) { - std::cerr - << "compiling failed (dialogue script)" << std::endl - << info->mResultScript - << std::endl << std::endl; + Log(Debug::Warning) + << "compiling failed (dialogue script)\n" << info->mResultScript << "\n\n"; } } } diff --git a/apps/openmw/mwgui/charactercreation.cpp b/apps/openmw/mwgui/charactercreation.cpp index 2deb622151..e3effa995b 100644 --- a/apps/openmw/mwgui/charactercreation.cpp +++ b/apps/openmw/mwgui/charactercreation.cpp @@ -1,5 +1,6 @@ #include "charactercreation.hpp" +#include #include #include "../mwbase/environment.hpp" @@ -284,7 +285,7 @@ namespace MWGui } catch (std::exception& e) { - std::cerr << "Error: Failed to create chargen window: " << e.what() << std::endl; + Log(Debug::Error) << "Error: Failed to create chargen window: " << e.what(); } } @@ -602,7 +603,7 @@ namespace MWGui mGenerateClass = "Mage"; else { - std::cout << "Failed to deduce class from chosen answers in generate class dialog" << std::endl; + Log(Debug::Warning) << "Failed to deduce class from chosen answers in generate class dialog."; mGenerateClass = "Thief"; } } diff --git a/apps/openmw/mwgui/class.cpp b/apps/openmw/mwgui/class.cpp index 4d2a15c820..6ed7a44915 100644 --- a/apps/openmw/mwgui/class.cpp +++ b/apps/openmw/mwgui/class.cpp @@ -12,6 +12,8 @@ #include "../mwworld/esmstore.hpp" +#include + #include "tooltips.hpp" namespace @@ -924,7 +926,7 @@ namespace MWGui std::string classImage = std::string("textures\\levelup\\") + classId + ".dds"; if (!MWBase::Environment::get().getWindowManager()->textureExists(classImage)) { - std::cout << "No class image for " << classId << ", falling back to default" << std::endl; + Log(Debug::Warning) << "No class image for " << classId << ", falling back to default"; classImage = "textures\\levelup\\warrior.dds"; } imageBox->setImageTexture(classImage); diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp index e8ac33f6db..b367c6f492 100644 --- a/apps/openmw/mwgui/console.cpp +++ b/apps/openmw/mwgui/console.cpp @@ -303,6 +303,14 @@ namespace MWGui bool has_front_quote = false; /* Does the input string contain things that don't have to be completed? If yes erase them. */ + + /* Erase a possible call to an explicit reference. */ + size_t explicitPos = tmp.find("->"); + if (explicitPos != std::string::npos) + { + tmp.erase(0, explicitPos+2); + } + /* Are there quotation marks? */ if( tmp.find('"') != std::string::npos ) { int numquotes=0; @@ -339,6 +347,7 @@ namespace MWGui } } } + /* Erase the input from the output string so we can easily append the completed form later. */ output.erase(output.end()-tmp.length(), output.end()); diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index 9222f1d020..703ba309e5 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -52,7 +52,7 @@ namespace MWGui void ContainerWindow::onItemSelected(int index) { - if (mDragAndDrop->mIsOnDragAndDrop && mModel) + if (mDragAndDrop->mIsOnDragAndDrop) { dropItem(); return; @@ -88,6 +88,9 @@ namespace MWGui void ContainerWindow::dragItem(MyGUI::Widget* sender, int count) { + if (!mModel) + return; + if (!onTakeItem(mModel->getItem(mSelectedItem), count)) return; @@ -96,6 +99,9 @@ namespace MWGui void ContainerWindow::dropItem() { + if (!mModel) + return; + bool success = mModel->onDropItem(mDragAndDrop->mItem.mBase, mDragAndDrop->mDraggedCount); if (success) @@ -104,7 +110,7 @@ namespace MWGui void ContainerWindow::onBackgroundSelected() { - if (mDragAndDrop->mIsOnDragAndDrop && mModel) + if (mDragAndDrop->mIsOnDragAndDrop) dropItem(); } diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index f4fe549172..6aceccaff5 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -362,52 +363,59 @@ namespace MWGui if (mGoodbye || MWBase::Environment::get().getDialogueManager()->isInChoice()) return; - int separatorPos = 0; - for (unsigned int i=0; igetItemCount(); ++i) - { - if (mTopicsList->getItemNameAt(i) == "") - separatorPos = i; - } + const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); - if (id >= separatorPos) + const std::string sPersuasion = gmst.find("sPersuasion")->mValue.getString(); + const std::string sCompanionShare = gmst.find("sCompanionShare")->mValue.getString(); + const std::string sBarter = gmst.find("sBarter")->mValue.getString(); + const std::string sSpells = gmst.find("sSpells")->mValue.getString(); + const std::string sTravel = gmst.find("sTravel")->mValue.getString(); + const std::string sSpellMakingMenuTitle = gmst.find("sSpellMakingMenuTitle")->mValue.getString(); + const std::string sEnchanting = gmst.find("sEnchanting")->mValue.getString(); + const std::string sServiceTrainingTitle = gmst.find("sServiceTrainingTitle")->mValue.getString(); + const std::string sRepair = gmst.find("sRepair")->mValue.getString(); + + if (topic != sPersuasion && topic != sCompanionShare && topic != sBarter + && topic != sSpells && topic != sTravel && topic != sSpellMakingMenuTitle + && topic != sEnchanting && topic != sServiceTrainingTitle && topic != sRepair) { onTopicActivated(topic); if (mGoodbyeButton->getEnabled()) MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mGoodbyeButton); } - else + else if (topic == sPersuasion) + mPersuasionDialog.setVisible(true); + else if (topic == sCompanionShare) + MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Companion, mPtr); + else if (!MWBase::Environment::get().getDialogueManager()->checkServiceRefused(mCallback.get())) { - const MWWorld::Store &gmst = - MWBase::Environment::get().getWorld()->getStore().get(); - - if (topic == gmst.find("sPersuasion")->getString()) - mPersuasionDialog.setVisible(true); - else if (topic == gmst.find("sCompanionShare")->getString()) - MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Companion, mPtr); - else if (!MWBase::Environment::get().getDialogueManager()->checkServiceRefused(mCallback.get())) - { - if (topic == gmst.find("sBarter")->getString()) - MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Barter, mPtr); - else if (topic == gmst.find("sSpells")->getString()) - MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_SpellBuying, mPtr); - else if (topic == gmst.find("sTravel")->getString()) - MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Travel, mPtr); - else if (topic == gmst.find("sSpellMakingMenuTitle")->getString()) - MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_SpellCreation, mPtr); - else if (topic == gmst.find("sEnchanting")->getString()) - MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Enchanting, mPtr); - else if (topic == gmst.find("sServiceTrainingTitle")->getString()) - MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Training, mPtr); - else if (topic == gmst.find("sRepair")->getString()) - MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_MerchantRepair, mPtr); - } - else - updateTopics(); + if (topic == sBarter) + MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Barter, mPtr); + else if (topic == sSpells) + MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_SpellBuying, mPtr); + else if (topic == sTravel) + MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Travel, mPtr); + else if (topic == sSpellMakingMenuTitle) + MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_SpellCreation, mPtr); + else if (topic == sEnchanting) + MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Enchanting, mPtr); + else if (topic == sServiceTrainingTitle) + MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Training, mPtr); + else if (topic == sRepair) + MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_MerchantRepair, mPtr); } + else + updateTopics(); } void DialogueWindow::setPtr(const MWWorld::Ptr& actor) { + if (!actor.getClass().isActor()) + { + Log(Debug::Warning) << "Warning: can not talk with non-actor object."; + return; + } + bool sameActor = (mPtr == actor); if (!sameActor) { @@ -450,7 +458,7 @@ namespace MWGui void DialogueWindow::restock() { MWMechanics::CreatureStats &sellerStats = mPtr.getClass().getCreatureStats(mPtr); - float delay = MWBase::Environment::get().getWorld()->getStore().get().find("fBarterGoldResetDelay")->getFloat(); + float delay = MWBase::Environment::get().getWorld()->getStore().get().find("fBarterGoldResetDelay")->mValue.getFloat(); // Gold is restocked every 24h if (MWBase::Environment::get().getWorld()->getTimeStamp() >= sellerStats.getLastRestockTime() + delay) @@ -495,31 +503,31 @@ namespace MWGui MWBase::Environment::get().getWorld()->getStore().get(); if (mPtr.getTypeName() == typeid(ESM::NPC).name()) - mTopicsList->addItem(gmst.find("sPersuasion")->getString()); + mTopicsList->addItem(gmst.find("sPersuasion")->mValue.getString()); if (services & ESM::NPC::AllItems) - mTopicsList->addItem(gmst.find("sBarter")->getString()); + mTopicsList->addItem(gmst.find("sBarter")->mValue.getString()); if (services & ESM::NPC::Spells) - mTopicsList->addItem(gmst.find("sSpells")->getString()); + mTopicsList->addItem(gmst.find("sSpells")->mValue.getString()); if (travel) - mTopicsList->addItem(gmst.find("sTravel")->getString()); + mTopicsList->addItem(gmst.find("sTravel")->mValue.getString()); if (services & ESM::NPC::Spellmaking) - mTopicsList->addItem(gmst.find("sSpellmakingMenuTitle")->getString()); + mTopicsList->addItem(gmst.find("sSpellmakingMenuTitle")->mValue.getString()); if (services & ESM::NPC::Enchanting) - mTopicsList->addItem(gmst.find("sEnchanting")->getString()); + mTopicsList->addItem(gmst.find("sEnchanting")->mValue.getString()); if (services & ESM::NPC::Training) - mTopicsList->addItem(gmst.find("sServiceTrainingTitle")->getString()); + mTopicsList->addItem(gmst.find("sServiceTrainingTitle")->mValue.getString()); if (services & ESM::NPC::Repair) - mTopicsList->addItem(gmst.find("sRepair")->getString()); + mTopicsList->addItem(gmst.find("sRepair")->mValue.getString()); if (isCompanion()) - mTopicsList->addItem(gmst.find("sCompanionShare")->getString()); + mTopicsList->addItem(gmst.find("sCompanionShare")->mValue.getString()); if (mTopicsList->getItemCount() > 0) mTopicsList->addSeparator(); @@ -584,7 +592,7 @@ namespace MWGui Goodbye* link = new Goodbye(); link->eventActivated += MyGUI::newDelegate(this, &DialogueWindow::onGoodbyeActivated); mLinks.push_back(link); - std::string goodbye = MWBase::Environment::get().getWorld()->getStore().get().find("sGoodbye")->getString(); + std::string goodbye = MWBase::Environment::get().getWorld()->getStore().get().find("sGoodbye")->mValue.getString(); BookTypesetter::Style* questionStyle = typesetter->createHotStyle(body, textColours.answer, textColours.answerOver, textColours.answerPressed, TypesetBook::InteractiveId(link)); diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index f7764e0f14..8fbfa65c68 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -341,7 +341,7 @@ namespace MWGui MWWorld::Ptr item = (i == 0) ? mEnchanting.getOldItem() : mEnchanting.getGem(); if (MWBase::Environment::get().getMechanicsManager()->isItemStolenFrom(item.getCellRef().getRefId(), mPtr)) { - std::string msg = MWBase::Environment::get().getWorld()->getStore().get().find("sNotifyMessage49")->getString(); + std::string msg = MWBase::Environment::get().getWorld()->getStore().get().find("sNotifyMessage49")->mValue.getString(); if (msg.find("%s") != std::string::npos) msg.replace(msg.find("%s"), 2, item.getClass().getName(item)); MWBase::Environment::get().getWindowManager()->messageBox(msg); diff --git a/apps/openmw/mwgui/formatting.cpp b/apps/openmw/mwgui/formatting.cpp index edcb94eed9..663bd73380 100644 --- a/apps/openmw/mwgui/formatting.cpp +++ b/apps/openmw/mwgui/formatting.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -300,7 +301,7 @@ namespace MWGui if (!exists) { - std::cerr << "Warning: Could not find \"" << src << "\" referenced by an tag." << std::endl; + Log(Debug::Warning) << "Warning: Could not find \"" << src << "\" referenced by an tag."; break; } diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index 1841303f25..91467d490e 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -611,7 +611,7 @@ namespace MWGui // Therefore any value < 1 should show as an empty health bar. We do the same in statswindow :) mEnemyHealth->setProgressPosition(static_cast(stats.getHealth().getCurrent() / stats.getHealth().getModified() * 100)); - static const float fNPCHealthBarFade = MWBase::Environment::get().getWorld()->getStore().get().find("fNPCHealthBarFade")->getFloat(); + static const float fNPCHealthBarFade = MWBase::Environment::get().getWorld()->getStore().get().find("fNPCHealthBarFade")->mValue.getFloat(); if (fNPCHealthBarFade > 0.f) mEnemyHealth->setAlpha(std::max(0.f, std::min(1.f, mEnemyHealthTimer/fNPCHealthBarFade))); @@ -620,7 +620,7 @@ namespace MWGui void HUD::setEnemy(const MWWorld::Ptr &enemy) { mEnemyActorId = enemy.getClass().getCreatureStats(enemy).getActorId(); - mEnemyHealthTimer = MWBase::Environment::get().getWorld()->getStore().get().find("fNPCHealthBarTime")->getFloat(); + mEnemyHealthTimer = MWBase::Environment::get().getWorld()->getStore().get().find("fNPCHealthBarTime")->mValue.getFloat(); if (!mEnemyHealth->getVisible()) mWeaponSpellBox->setPosition(mWeaponSpellBox->getPosition() - MyGUI::IntPoint(0,20)); mEnemyHealth->setVisible(true); diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index e909ecbba1..0d37bbff3b 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -67,7 +67,13 @@ namespace MWGui , mLastYSize(0) , mPreview(new MWRender::InventoryPreview(parent, resourceSystem, MWMechanics::getPlayer())) , mTrading(false) + , mScaleFactor(1.0f) + , mUpdateTimer(0.f) { + float uiScale = Settings::Manager::getFloat("scaling factor", "GUI"); + if (uiScale > 1.0) + mScaleFactor = uiScale; + mPreviewTexture.reset(new osgMyGUI::OSGTexture(mPreview->getTexture())); mPreview->rebuild(); @@ -431,10 +437,10 @@ namespace MWGui MyGUI::IntSize size = mAvatarImage->getSize(); int width = std::min(mPreview->getTextureWidth(), size.width); int height = std::min(mPreview->getTextureHeight(), size.height); - mPreview->setViewport(width, height); + mPreview->setViewport(int(width*mScaleFactor), int(height*mScaleFactor)); mAvatarImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, - width/float(mPreview->getTextureWidth()), height/float(mPreview->getTextureHeight()))); + width*mScaleFactor/float(mPreview->getTextureWidth()), height*mScaleFactor/float(mPreview->getTextureHeight()))); } void InventoryWindow::onFilterChanged(MyGUI::Widget* _sender) @@ -512,7 +518,7 @@ namespace MWGui // Give the script a chance to run once before we do anything else // this is important when setting pcskipequip as a reaction to onpcequip being set (bk_treasuryreport does this) - if (!script.empty() && MWBase::Environment::get().getWorld()->getScriptsEnabled()) + if (!force && !script.empty() && MWBase::Environment::get().getWorld()->getScriptsEnabled()) { MWScript::InterpreterContext interpreterContext (&ptr.getRefData().getLocals(), ptr); MWBase::Environment::get().getScriptManager()->run (script, interpreterContext); @@ -591,6 +597,11 @@ namespace MWGui { // convert to OpenGL lower-left origin y = (mAvatarImage->getHeight()-1) - y; + + // Scale coordinates + x = int(x*mScaleFactor); + y = int(y*mScaleFactor); + int slot = mPreview->getSlotSelected (x, y); if (slot == -1) @@ -621,6 +632,22 @@ namespace MWGui void InventoryWindow::onFrame(float dt) { updateEncumbranceBar(); + + if (mPinned) + { + mUpdateTimer += dt; + if (0.1f < mUpdateTimer) + { + mUpdateTimer = 0; + + // Update pinned inventory in-game + if (!MWBase::Environment::get().getWindowManager()->isGuiMode()) + { + mItemView->update(); + notifyContentChanged(); + } + } + } } void InventoryWindow::setTrading(bool trading) @@ -671,6 +698,8 @@ namespace MWGui return; int count = object.getRefData().getCount(); + if (object.getClass().isGold(object)) + count *= object.getClass().getValue(object); MWWorld::Ptr player = MWMechanics::getPlayer(); MWBase::Environment::get().getWorld()->breakInvisibility(player); diff --git a/apps/openmw/mwgui/inventorywindow.hpp b/apps/openmw/mwgui/inventorywindow.hpp index 124fe7b0ed..c60e373630 100644 --- a/apps/openmw/mwgui/inventorywindow.hpp +++ b/apps/openmw/mwgui/inventorywindow.hpp @@ -101,6 +101,8 @@ namespace MWGui std::unique_ptr mPreview; bool mTrading; + float mScaleFactor; + float mUpdateTimer; void onItemSelected(int index); void onItemSelectedFromSourceModel(int index); diff --git a/apps/openmw/mwgui/jailscreen.cpp b/apps/openmw/mwgui/jailscreen.cpp index 9fa5927ac9..1761e13460 100644 --- a/apps/openmw/mwgui/jailscreen.cpp +++ b/apps/openmw/mwgui/jailscreen.cpp @@ -99,9 +99,9 @@ namespace MWGui std::string message; if (mDays == 1) - message = gmst.find("sNotifyMessage42")->getString(); + message = gmst.find("sNotifyMessage42")->mValue.getString(); else - message = gmst.find("sNotifyMessage43")->getString(); + message = gmst.find("sNotifyMessage43")->mValue.getString(); std::stringstream dayStr; dayStr << mDays; @@ -110,12 +110,12 @@ namespace MWGui for (std::set::iterator it = skills.begin(); it != skills.end(); ++it) { - std::string skillName = gmst.find(ESM::Skill::sSkillNameIds[*it])->getString(); + std::string skillName = gmst.find(ESM::Skill::sSkillNameIds[*it])->mValue.getString(); std::stringstream skillValue; skillValue << player.getClass().getNpcStats(player).getSkill(*it).getBase(); - std::string skillMsg = gmst.find("sNotifyMessage44")->getString(); + std::string skillMsg = gmst.find("sNotifyMessage44")->mValue.getString(); if (*it == ESM::Skill::Sneak || *it == ESM::Skill::Security) - skillMsg = gmst.find("sNotifyMessage39")->getString(); + skillMsg = gmst.find("sNotifyMessage39")->mValue.getString(); if (skillMsg.find("%s") != std::string::npos) skillMsg.replace(skillMsg.find("%s"), 2, skillName); diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 99fe777aa7..be3d477e1e 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -10,9 +10,8 @@ #include #include - +#include #include - #include #include @@ -37,6 +36,7 @@ namespace MWGui , mLastRenderTime(0.0) , mLoadingOnTime(0.0) , mImportantLabel(false) + , mVisible(false) , mProgress(0) , mShowWallpaper(true) { @@ -93,7 +93,7 @@ namespace MWGui ++found; } if (mSplashScreens.empty()) - std::cerr << "No splash screens found!" << std::endl; + Log(Debug::Warning) << "Warning: no splash screens found!"; } void LoadingScreen::setLabel(const std::string &label, bool important) @@ -169,12 +169,18 @@ namespace MWGui // We are already using node masks to avoid the scene from being updated/rendered, but node masks don't work for computeBound() mViewer->getSceneData()->setComputeBoundingSphereCallback(new DontComputeBoundCallback); + mShowWallpaper = visible && (MWBase::Environment::get().getStateManager()->getState() + == MWBase::StateManager::State_NoGame); + + if (!visible) + { + draw(); + return; + } + mVisible = visible; mLoadingBox->setVisible(mVisible); - mShowWallpaper = mVisible && (MWBase::Environment::get().getStateManager()->getState() - == MWBase::StateManager::State_NoGame); - setVisible(true); if (mShowWallpaper) @@ -183,9 +189,6 @@ namespace MWGui } MWBase::Environment::get().getWindowManager()->pushGuiMode(mShowWallpaper ? GM_LoadingWallpaper : GM_Loading); - - if (!mVisible) - draw(); } void LoadingScreen::loadingOff() diff --git a/apps/openmw/mwgui/merchantrepair.cpp b/apps/openmw/mwgui/merchantrepair.cpp index d3504ebc3a..9974c6d16e 100644 --- a/apps/openmw/mwgui/merchantrepair.cpp +++ b/apps/openmw/mwgui/merchantrepair.cpp @@ -57,7 +57,7 @@ void MerchantRepair::setPtr(const MWWorld::Ptr &actor) int basePrice = iter->getClass().getValue(*iter); float fRepairMult = MWBase::Environment::get().getWorld()->getStore().get() - .find("fRepairMult")->getFloat(); + .find("fRepairMult")->mValue.getFloat(); float p = static_cast(std::max(1, basePrice)); float r = static_cast(std::max(1, static_cast(maxDurability / p))); @@ -71,7 +71,7 @@ void MerchantRepair::setPtr(const MWWorld::Ptr &actor) std::string name = iter->getClass().getName(*iter) + " - " + MyGUI::utility::toString(price) + MWBase::Environment::get().getWorld()->getStore().get() - .find("sgp")->getString(); + .find("sgp")->mValue.getString(); MyGUI::Button* button = diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index a79112b9f0..2fbce97d46 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include "../mwbase/environment.hpp" @@ -125,7 +126,7 @@ namespace MWGui { if (mInterMessageBoxe != NULL) { - std::cerr << "Warning: replacing an interactive message box that was not answered yet" << std::endl; + Log(Debug::Warning) << "Warning: replacing an interactive message box that was not answered yet"; mInterMessageBoxe->setVisible(false); delete mInterMessageBoxe; mInterMessageBoxe = NULL; diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 457bb5588c..8041c50c59 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -161,6 +161,12 @@ namespace MWGui } } assert(index != -1); + if (index < 0) + { + mSelected = nullptr; + return; + } + mSelected = &mKey[index]; // prevent reallocation of zero key from Type_HandToHand diff --git a/apps/openmw/mwgui/race.cpp b/apps/openmw/mwgui/race.cpp index 64609cbe69..96c0d7de4f 100644 --- a/apps/openmw/mwgui/race.cpp +++ b/apps/openmw/mwgui/race.cpp @@ -6,6 +6,7 @@ #include +#include #include #include "../mwworld/esmstore.hpp" @@ -340,7 +341,7 @@ namespace MWGui } catch (std::exception& e) { - std::cerr << "Error creating preview: " << e.what() << std::endl; + Log(Debug::Error) << "Error creating preview: " << e.what(); } } diff --git a/apps/openmw/mwgui/recharge.cpp b/apps/openmw/mwgui/recharge.cpp index 26a364f726..a6ed16bd40 100644 --- a/apps/openmw/mwgui/recharge.cpp +++ b/apps/openmw/mwgui/recharge.cpp @@ -169,19 +169,18 @@ void Recharge::onItemClicked(MyGUI::Widget *sender, const MWWorld::Ptr& item) MWBase::Environment::get().getWindowManager()->playSound("Enchant Success"); player.getClass().getContainerStore(player).restack(item); - - player.getClass().skillUsageSucceeded (player, ESM::Skill::Enchant, 0); } else { MWBase::Environment::get().getWindowManager()->playSound("Enchant Fail"); } + player.getClass().skillUsageSucceeded (player, ESM::Skill::Enchant, 0); gem.getContainerStore()->remove(gem, 1, player); if (gem.getRefData().getCount() == 0) { - std::string message = MWBase::Environment::get().getWorld()->getStore().get().find("sNotifyMessage51")->getString(); + std::string message = MWBase::Environment::get().getWorld()->getStore().get().find("sNotifyMessage51")->mValue.getString(); message = boost::str(boost::format(message) % gem.getClass().getName(gem)); MWBase::Environment::get().getWindowManager()->messageBox(message); diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp index 43e5111447..45790cbf5f 100644 --- a/apps/openmw/mwgui/savegamedialog.cpp +++ b/apps/openmw/mwgui/savegamedialog.cpp @@ -12,6 +12,8 @@ #include #include +#include + #include #include @@ -438,14 +440,14 @@ namespace MWGui osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("jpg"); if (!readerwriter) { - std::cerr << "Error: Can't open savegame screenshot, no jpg readerwriter found" << std::endl; + Log(Debug::Error) << "Error: Can't open savegame screenshot, no jpg readerwriter found"; return; } osgDB::ReaderWriter::ReadResult result = readerwriter->readImage(instream); if (!result.success()) { - std::cerr << "Error: Failed to read savegame screenshot: " << result.message() << " code " << result.status() << std::endl; + Log(Debug::Error) << "Error: Failed to read savegame screenshot: " << result.message() << " code " << result.status(); return; } diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 677ddefb3a..80ed9202a0 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -12,6 +12,7 @@ #include +#include #include #include @@ -31,7 +32,8 @@ namespace if (val == "linear") return "Trilinear"; if (val == "nearest") return "Bilinear"; if (val != "none") - std::cerr<< "Warning: Invalid texture mipmap option: "< - #include - +#include #include #include #include @@ -245,7 +243,7 @@ namespace MWGui const ESM::Enchantment* ench = MWBase::Environment::get().getWorld()->getStore().get().search(enchId); if (!ench) { - std::cerr << "Warning: Can't find enchantment '" << enchId << "' on item " << base.getCellRef().getRefId() << std::endl; + Log(Debug::Warning) << "Warning: Can't find enchantment '" << enchId << "' on item " << base.getCellRef().getRefId(); return false; } diff --git a/apps/openmw/mwgui/spellbuyingwindow.cpp b/apps/openmw/mwgui/spellbuyingwindow.cpp index d9c3a5f169..9876013f13 100644 --- a/apps/openmw/mwgui/spellbuyingwindow.cpp +++ b/apps/openmw/mwgui/spellbuyingwindow.cpp @@ -44,7 +44,7 @@ namespace MWGui const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - int price = static_cast(spell.mData.mCost*store.get().find("fSpellValueMult")->getFloat()); + int price = static_cast(spell.mData.mCost*store.get().find("fSpellValueMult")->mValue.getFloat()); price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr,price,true); MWWorld::Ptr player = MWMechanics::getPlayer(); diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index 4aa208d8f6..65d66f9e2e 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -32,8 +32,8 @@ namespace const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); - return gmst.find(ESM::MagicEffect::effectIdToString (id1))->getString() - < gmst.find(ESM::MagicEffect::effectIdToString (id2))->getString(); + return gmst.find(ESM::MagicEffect::effectIdToString (id1))->mValue.getString() + < gmst.find(ESM::MagicEffect::effectIdToString (id2))->mValue.getString(); } void init(ESM::ENAMstruct& effect) @@ -469,7 +469,7 @@ namespace MWGui mMagickaCost->setCaption(MyGUI::utility::toString(int(y))); float fSpellMakingValueMult = - store.get().find("fSpellMakingValueMult")->getFloat(); + store.get().find("fSpellMakingValueMult")->mValue.getFloat(); int price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, static_cast(y * fSpellMakingValueMult),true); @@ -547,7 +547,7 @@ namespace MWGui for (std::vector::const_iterator it = knownEffects.begin(); it != knownEffects.end(); ++it) { mAvailableEffectsList->addItem(MWBase::Environment::get().getWorld ()->getStore ().get().find( - ESM::MagicEffect::effectIdToString (*it))->getString()); + ESM::MagicEffect::effectIdToString (*it))->mValue.getString()); mButtonMapping[i] = *it; ++i; } @@ -557,7 +557,7 @@ namespace MWGui for (std::vector::const_iterator it = knownEffects.begin(); it != knownEffects.end(); ++it) { std::string name = MWBase::Environment::get().getWorld ()->getStore ().get().find( - ESM::MagicEffect::effectIdToString (*it))->getString(); + ESM::MagicEffect::effectIdToString (*it))->mValue.getString(); MyGUI::Widget* w = mAvailableEffectsList->getItemWidget(name); ToolTips::createMagicEffectToolTip (w, *it); diff --git a/apps/openmw/mwgui/spellicons.cpp b/apps/openmw/mwgui/spellicons.cpp index 27896381ef..c0f56e654e 100644 --- a/apps/openmw/mwgui/spellicons.cpp +++ b/apps/openmw/mwgui/spellicons.cpp @@ -75,7 +75,7 @@ namespace MWGui std::string sourcesDescription; - static const float fadeTime = MWBase::Environment::get().getWorld()->getStore().get().find("fMagicStartIconBlink")->getFloat(); + static const float fadeTime = MWBase::Environment::get().getWorld()->getStore().get().find("fMagicStartIconBlink")->mValue.getFloat(); for (std::vector::const_iterator effectIt = it->second.begin(); effectIt != it->second.end(); ++effectIt) diff --git a/apps/openmw/mwgui/spellmodel.cpp b/apps/openmw/mwgui/spellmodel.cpp index a2710b633f..0933737ca2 100644 --- a/apps/openmw/mwgui/spellmodel.cpp +++ b/apps/openmw/mwgui/spellmodel.cpp @@ -1,6 +1,6 @@ #include "spellmodel.hpp" -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -94,7 +94,7 @@ namespace MWGui const ESM::Enchantment* enchant = esmStore.get().search(enchantId); if (!enchant) { - std::cerr << "Warning: Can't find enchantment '" << enchantId << "' on item " << item.getCellRef().getRefId() << std::endl; + Log(Debug::Warning) << "Warning: Can't find enchantment '" << enchantId << "' on item " << item.getCellRef().getRefId(); continue; } diff --git a/apps/openmw/mwgui/spellmodel.hpp b/apps/openmw/mwgui/spellmodel.hpp index 8e29707aed..4aa1f9d2a7 100644 --- a/apps/openmw/mwgui/spellmodel.hpp +++ b/apps/openmw/mwgui/spellmodel.hpp @@ -26,6 +26,7 @@ namespace MWGui Spell() : mType(Type_Spell) + , mCount(0) , mSelected(false) , mActive(false) { diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index 3fe171e4e0..38de9288bf 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -86,6 +86,10 @@ namespace MWGui mUpdateTimer = 0; mSpellView->incrementalUpdate(); } + + // Update effects in-game too if the window is pinned + if (mPinned && !MWBase::Environment::get().getWindowManager()->isGuiMode()) + mSpellIcons->updateWidgets(mEffectBox, false); } void SpellWindow::updateSpells() diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index d3d487579d..db73351bfd 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -61,7 +61,7 @@ namespace MWGui const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); for (int i=0; names[i][0]; ++i) { - setText (names[i][0], store.get().find (names[i][1])->getString()); + setText (names[i][0], store.get().find (names[i][1])->mValue.getString()); } getWidget(mSkillView, "SkillView"); @@ -306,7 +306,7 @@ namespace MWGui MyGUI::Widget* levelWidget; for (int i=0; i<2; ++i) { - int max = MWBase::Environment::get().getWorld()->getStore().get().find("iLevelUpTotal")->getInt(); + int max = MWBase::Environment::get().getWorld()->getStore().get().find("iLevelUpTotal")->mValue.getInteger(); getWidget(levelWidget, i==0 ? "Level_str" : "LevelText"); levelWidget->setUserString("RangePosition_LevelProgress", MyGUI::utility::toString(PCstats.getLevelProgress())); levelWidget->setUserString("Range_LevelProgress", MyGUI::utility::toString(max)); diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index 0a9fe1b4ac..3871baa09d 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -313,7 +313,7 @@ namespace MWGui { if (MWBase::Environment::get().getMechanicsManager()->isItemStolenFrom(it->mBase.getCellRef().getRefId(), mPtr)) { - std::string msg = gmst.find("sNotifyMessage49")->getString(); + std::string msg = gmst.find("sNotifyMessage49")->mValue.getString(); if (msg.find("%s") != std::string::npos) msg.replace(msg.find("%s"), 2, it->mBase.getClass().getName(it->mBase)); MWBase::Environment::get().getWindowManager()->messageBox(msg); @@ -331,10 +331,10 @@ namespace MWGui // apply disposition change if merchant is NPC if ( mPtr.getClass().isNpc() ) { int dispositionDelta = offerAccepted - ? gmst.find("iBarterSuccessDisposition")->getInt() - : gmst.find("iBarterFailDisposition")->getInt(); + ? gmst.find("iBarterSuccessDisposition")->mValue.getInteger() + : gmst.find("iBarterFailDisposition")->mValue.getInteger(); - MWBase::Environment::get().getDialogueManager()->applyDispositionChange(dispositionDelta); + MWBase::Environment::get().getDialogueManager()->applyBarterDispositionChange(dispositionDelta); } // display message on haggle failure diff --git a/apps/openmw/mwgui/trainingwindow.cpp b/apps/openmw/mwgui/trainingwindow.cpp index 630ba8ce1d..b309da27d0 100644 --- a/apps/openmw/mwgui/trainingwindow.cpp +++ b/apps/openmw/mwgui/trainingwindow.cpp @@ -98,7 +98,7 @@ namespace MWGui for (int i=0; i<3; ++i) { int price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer - (mPtr,pcStats.getSkill (skills[i].first).getBase() * gmst.find("iTrainingMod")->getInt (),true); + (mPtr,pcStats.getSkill (skills[i].first).getBase() * gmst.find("iTrainingMod")->mValue.getInteger(),true); MyGUI::Button* button = mTrainingOptions->createWidget(price <= playerGold ? "SandTextButton" : "SandTextButtonDisabled", // can't use setEnabled since that removes tooltip MyGUI::IntCoord(5, 5+i*18, mTrainingOptions->getWidth()-10, 18), MyGUI::Align::Default); @@ -136,7 +136,7 @@ namespace MWGui const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - int price = pcStats.getSkill (skillId).getBase() * store.get().find("iTrainingMod")->getInt (); + int price = pcStats.getSkill (skillId).getBase() * store.get().find("iTrainingMod")->mValue.getInteger(); price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr,price,true); if (price > player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId)) diff --git a/apps/openmw/mwgui/travelwindow.cpp b/apps/openmw/mwgui/travelwindow.cpp index 7b65eb771c..23a5b322fd 100644 --- a/apps/openmw/mwgui/travelwindow.cpp +++ b/apps/openmw/mwgui/travelwindow.cpp @@ -46,7 +46,7 @@ namespace MWGui mSelect->getHeight()); } - void TravelWindow::addDestination(const std::string& name,ESM::Position pos,bool interior) + void TravelWindow::addDestination(const std::string& name, ESM::Position pos, bool interior) { int price; @@ -56,15 +56,15 @@ namespace MWGui MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); - if(interior) + if (!mPtr.getCell()->isExterior()) { - price = gmst.find("fMagesGuildTravel")->getInt(); + price = gmst.find("fMagesGuildTravel")->mValue.getInteger(); } else { ESM::Position PlayerPos = player.getRefData().getPosition(); float d = sqrt(pow(pos.pos[0] - PlayerPos.pos[0], 2) + pow(pos.pos[1] - PlayerPos.pos[1], 2) + pow(pos.pos[2] - PlayerPos.pos[2], 2)); - price = static_cast(d / gmst.find("fTravelMult")->getFloat()); + price = static_cast(d / gmst.find("fTravelMult")->mValue.getFloat()); } price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, price, true); @@ -154,6 +154,10 @@ namespace MWGui if (playerGoldsetPlayerTraveling(true); + if (!mPtr.getCell()->isExterior()) // Interior cell -> mages guild transport MWBase::Environment::get().getWindowManager()->playSound("mysticism cast"); @@ -168,11 +172,11 @@ namespace MWGui ESM::Position pos = *_sender->getUserData(); std::string cellname = _sender->getUserString("Destination"); bool interior = _sender->getUserString("interior") == "y"; - if (!interior) + if (mPtr.getCell()->isExterior()) { ESM::Position playerPos = player.getRefData().getPosition(); float d = (osg::Vec3f(pos.pos[0], pos.pos[1], 0) - osg::Vec3f(playerPos.pos[0], playerPos.pos[1], 0)).length(); - int hours = static_cast(d /MWBase::Environment::get().getWorld()->getStore().get().find("fTravelTimeMult")->getFloat()); + int hours = static_cast(d /MWBase::Environment::get().getWorld()->getStore().get().find("fTravelTimeMult")->mValue.getFloat()); for(int i = 0;i < hours;i++) { MWBase::Environment::get().getMechanicsManager ()->rest (true); diff --git a/apps/openmw/mwgui/videowidget.cpp b/apps/openmw/mwgui/videowidget.cpp index 5a4bb981f4..28432b811d 100644 --- a/apps/openmw/mwgui/videowidget.cpp +++ b/apps/openmw/mwgui/videowidget.cpp @@ -6,6 +6,7 @@ #include +#include #include #include @@ -21,6 +22,8 @@ VideoWidget::VideoWidget() setNeedKeyFocus(true); } +VideoWidget::~VideoWidget() = default; + void VideoWidget::setVFS(const VFS::Manager *vfs) { mVFS = vfs; @@ -37,7 +40,7 @@ void VideoWidget::playVideo(const std::string &video) } catch (std::exception& e) { - std::cerr << "Failed to open video: " << e.what() << std::endl; + Log(Debug::Error) << "Failed to open video: " << e.what(); return; } diff --git a/apps/openmw/mwgui/videowidget.hpp b/apps/openmw/mwgui/videowidget.hpp index ac240e69d5..20af579a25 100644 --- a/apps/openmw/mwgui/videowidget.hpp +++ b/apps/openmw/mwgui/videowidget.hpp @@ -25,6 +25,8 @@ namespace MWGui MYGUI_RTTI_DERIVED(VideoWidget) VideoWidget(); + + ~VideoWidget(); /// Set the VFS (virtual file system) to find the videos on. void setVFS(const VFS::Manager* vfs); diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp index 52575e25c1..c5a06a12c9 100644 --- a/apps/openmw/mwgui/waitdialog.cpp +++ b/apps/openmw/mwgui/waitdialog.cpp @@ -183,10 +183,10 @@ namespace MWGui { // figure out if player will be woken while sleeping int x = Misc::Rng::rollDice(hoursToWait); - float fSleepRandMod = world->getStore().get().find("fSleepRandMod")->getFloat(); + float fSleepRandMod = world->getStore().get().find("fSleepRandMod")->mValue.getFloat(); if (x < fSleepRandMod * hoursToWait) { - float fSleepRestMod = world->getStore().get().find("fSleepRestMod")->getFloat(); + float fSleepRestMod = world->getStore().get().find("fSleepRestMod")->mValue.getFloat(); int interruptAtHoursRemaining = int(fSleepRestMod * hoursToWait); if (interruptAtHoursRemaining != 0) { @@ -252,7 +252,7 @@ namespace MWGui // trigger levelup if possible const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); - if (mSleeping && pcstats.getLevelProgress () >= gmst.find("iLevelUpTotal")->getInt()) + if (mSleeping && pcstats.getLevelProgress () >= gmst.find("iLevelUpTotal")->mValue.getInteger()) { MWBase::Environment::get().getWindowManager()->pushGuiMode (GM_Levelup); } diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index df2d5ed6d2..45897b88cf 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -21,6 +21,8 @@ #include #include +#include + #include #include @@ -356,7 +358,7 @@ namespace MWGui mGuiModeStates[GM_Journal].mCloseSound = "book close"; mGuiModeStates[GM_Journal].mOpenSound = "book open"; - mMessageBoxManager = new MessageBoxManager(mStore->get().find("fMessageTimePerChar")->getFloat()); + mMessageBoxManager = new MessageBoxManager(mStore->get().find("fMessageTimePerChar")->mValue.getFloat()); SpellBuyingWindow* spellBuyingWindow = new SpellBuyingWindow(); mWindows.push_back(spellBuyingWindow); @@ -898,7 +900,8 @@ namespace MWGui mKeyboardNavigation->onFrame(); - mMessageBoxManager->onFrame(frameDuration); + if (mMessageBoxManager) + mMessageBoxManager->onFrame(frameDuration); mToolTips->onFrame(frameDuration); @@ -1081,7 +1084,7 @@ namespace MWGui { if (!mStore) { - std::cerr << "Error: WindowManager::onRetrieveTag: no Store set up yet, can not replace '" << tag << "'" << std::endl; + Log(Debug::Error) << "Error: WindowManager::onRetrieveTag: no Store set up yet, can not replace '" << tag << "'"; return; } const ESM::GameSetting *setting = mStore->get().find(tag); @@ -1787,7 +1790,7 @@ namespace MWGui if (found != mCurrentModals.end()) mCurrentModals.erase(found); else - std::cerr << " warning: can't find modal window " << input << std::endl; + Log(Debug::Warning) << "Warning: can't find modal window " << input; } } if (mCurrentModals.empty()) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 242058c5f7..b24c8cc149 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -10,6 +10,7 @@ #include +#include #include #include #include @@ -120,11 +121,11 @@ namespace MWInput SDL_ControllerDeviceEvent evt; evt.which = i; controllerAdded(mFakeDeviceID, evt); - std::cout << "Detected game controller: " << SDL_GameControllerNameForIndex(i) << std::endl; + Log(Debug::Info) << "Detected game controller: " << SDL_GameControllerNameForIndex(i); } else { - std::cout << "Detected unusable controller: " << SDL_JoystickNameForIndex(i) << std::endl; + Log(Debug::Info) << "Detected unusable controller: " << SDL_JoystickNameForIndex(i); } } @@ -1003,9 +1004,9 @@ namespace MWInput if (!mControlSwitch["playerfighting"] || !mControlSwitch["playercontrols"]) return; - // We want to interrupt animation only if attack is prepairing, but still is not triggered + // We want to interrupt animation only if attack is preparing, but still is not triggered // Otherwise we will get a "speedshooting" exploit, when player can skip reload animation by hitting "Toggle Weapon" key twice - if (MWBase::Environment::get().getMechanicsManager()->isAttackPrepairing(mPlayer->getPlayer())) + if (MWBase::Environment::get().getMechanicsManager()->isAttackPreparing(mPlayer->getPlayer())) mPlayer->setAttackingOrSpell(false); else if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(mPlayer->getPlayer())) return; @@ -1191,7 +1192,7 @@ namespace MWInput void InputManager::updateIdleTime(float dt) { static const float vanityDelay = MWBase::Environment::get().getWorld()->getStore().get() - .find("fVanityDelay")->getFloat(); + .find("fVanityDelay")->mValue.getFloat(); if (mTimeIdle >= 0.f) mTimeIdle += dt; if (mTimeIdle > vanityDelay) { diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index 90d29f686d..57b009689c 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -197,8 +197,14 @@ namespace MWMechanics void ActiveSpells::removeEffects(const std::string &id) { - mSpells.erase(Misc::StringUtils::lowerCase(id)); - mSpellsChanged = true; + for (TContainer::iterator spell = mSpells.begin(); spell != mSpells.end(); ++spell) + { + if (spell->first == id) + { + spell->second.mEffects.clear(); + mSpellsChanged = true; + } + } } void ActiveSpells::visitEffectSources(EffectSourceVisitor &visitor) const diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 768d9e37a8..0ee49ac45e 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -2,13 +2,12 @@ #include #include - #include #include #include #include - +#include #include #include "../mwworld/esmstore.hpp" @@ -55,22 +54,22 @@ int getBoundItemSlot (const std::string& itemId) static std::map boundItemsMap; if (boundItemsMap.empty()) { - std::string boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundBootsID")->getString(); + std::string boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundBootsID")->mValue.getString(); boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_Boots; - boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundCuirassID")->getString(); + boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundCuirassID")->mValue.getString(); boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_Cuirass; - boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundLeftGauntletID")->getString(); + boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundLeftGauntletID")->mValue.getString(); boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_LeftGauntlet; - boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundRightGauntletID")->getString(); + boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundRightGauntletID")->mValue.getString(); boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_RightGauntlet; - boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundHelmID")->getString(); + boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundHelmID")->mValue.getString(); boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_Helmet; - boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundShieldID")->getString(); + boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundShieldID")->mValue.getString(); boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_CarriedLeft; } @@ -139,7 +138,7 @@ void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float magicka = 0; if (!stunted) { - float fRestMagicMult = settings.find("fRestMagicMult")->getFloat (); + float fRestMagicMult = settings.find("fRestMagicMult")->mValue.getFloat (); magicka = fRestMagicMult * stats.getAttribute(ESM::Attribute::Intelligence).getModified(); } } @@ -180,7 +179,7 @@ namespace MWMechanics if (caster.isEmpty() || !caster.getClass().isActor()) return; - static const float fSoulgemMult = world->getStore().get().find("fSoulgemMult")->getFloat(); + static const float fSoulgemMult = world->getStore().get().find("fSoulgemMult")->mValue.getFloat(); int creatureSoulValue = mCreature.get()->mBase->mData.mSoul; if (creatureSoulValue == 0) @@ -314,9 +313,9 @@ namespace MWMechanics MWWorld::Ptr& headTrackTarget, float& sqrHeadTrackDistance) { static const float fMaxHeadTrackDistance = MWBase::Environment::get().getWorld()->getStore().get() - .find("fMaxHeadTrackDistance")->getFloat(); + .find("fMaxHeadTrackDistance")->mValue.getFloat(); static const float fInteriorHeadTrackMult = MWBase::Environment::get().getWorld()->getStore().get() - .find("fInteriorHeadTrackMult")->getFloat(); + .find("fInteriorHeadTrackMult")->mValue.getFloat(); float maxDistance = fMaxHeadTrackDistance; const ESM::Cell* currentCell = actor.getCell()->getCell(); if (!currentCell->isExterior() && !(currentCell->mData.mFlags & ESM::Cell::QuasiEx)) @@ -404,7 +403,7 @@ namespace MWMechanics std::set playerAllies; getActorsSidingWith(MWMechanics::getPlayer(), playerAllies, cachedAllies); - bool isPlayerFollowerOrEscorter = std::find(playerAllies.begin(), playerAllies.end(), actor1) != playerAllies.end(); + bool isPlayerFollowerOrEscorter = playerAllies.find(actor1) != playerAllies.end(); // If actor2 and at least one actor2 are in combat with actor1, actor1 and its allies start combat with them // Doesn't apply for player followers/escorters @@ -458,7 +457,7 @@ namespace MWMechanics // Do aggression check if actor2 is the player or a player follower or escorter if (!aggressive) { - if (againstPlayer || std::find(playerAllies.begin(), playerAllies.end(), actor2) != playerAllies.end()) + if (againstPlayer || playerAllies.find(actor2) != playerAllies.end()) { // Player followers and escorters with high fight should not initiate combat with the player or with // other player followers or escorters @@ -471,7 +470,7 @@ namespace MWMechanics if (actor1.getClass().isClass(actor1, "Guard") && !actor2.getClass().isNpc()) { // Check if the creature is too far - static const float fAlarmRadius = MWBase::Environment::get().getWorld()->getStore().get().find("fAlarmRadius")->getFloat(); + static const float fAlarmRadius = MWBase::Environment::get().getWorld()->getStore().get().find("fAlarmRadius")->mValue.getFloat(); if (sqrDist > fAlarmRadius * fAlarmRadius) return; @@ -535,9 +534,9 @@ namespace MWMechanics float base = 1.f; if (ptr == getPlayer()) - base = MWBase::Environment::get().getWorld()->getStore().get().find("fPCbaseMagickaMult")->getFloat(); + base = MWBase::Environment::get().getWorld()->getStore().get().find("fPCbaseMagickaMult")->mValue.getFloat(); else - base = MWBase::Environment::get().getWorld()->getStore().get().find("fNPCbaseMagickaMult")->getFloat(); + base = MWBase::Environment::get().getWorld()->getStore().get().find("fNPCbaseMagickaMult")->mValue.getFloat(); double magickaFactor = base + creatureStats.getMagicEffects().get (EffectKey (ESM::MagicEffect::FortifyMaximumMagicka)).getMagnitude() * 0.1; @@ -546,7 +545,7 @@ namespace MWMechanics float diff = (static_cast(magickaFactor*intelligence)) - magicka.getBase(); float currentToBaseRatio = (magicka.getCurrent() / magicka.getBase()); magicka.setModified(magicka.getModified() + diff, 0); - magicka.setCurrent(magicka.getBase() * currentToBaseRatio); + magicka.setCurrent(magicka.getBase() * currentToBaseRatio, false, true); creatureStats.setMagicka(magicka); } @@ -577,16 +576,21 @@ namespace MWMechanics float normalizedEncumbrance = ptr.getClass().getNormalizedEncumbrance(ptr); if (normalizedEncumbrance > 1) normalizedEncumbrance = 1; + + // Current fatigue can be above base value due to a fortify effect. + // In that case stop here and don't try to restore. + DynamicStat fatigue = stats.getFatigue(); + if (fatigue.getCurrent() >= fatigue.getBase()) + return; - // restore fatigue - float fFatigueReturnBase = settings.find("fFatigueReturnBase")->getFloat (); - float fFatigueReturnMult = settings.find("fFatigueReturnMult")->getFloat (); - float fEndFatigueMult = settings.find("fEndFatigueMult")->getFloat (); + // Restore fatigue + float fFatigueReturnBase = settings.find("fFatigueReturnBase")->mValue.getFloat (); + float fFatigueReturnMult = settings.find("fFatigueReturnMult")->mValue.getFloat (); + float fEndFatigueMult = settings.find("fEndFatigueMult")->mValue.getFloat (); float x = fFatigueReturnBase + fFatigueReturnMult * (1 - normalizedEncumbrance); x *= fEndFatigueMult * endurance; - DynamicStat fatigue = stats.getFatigue(); fatigue.setCurrent (fatigue.getCurrent() + 3600 * x); stats.setFatigue (fatigue); } @@ -598,16 +602,20 @@ namespace MWMechanics MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats (ptr); - int endurance = stats.getAttribute (ESM::Attribute::Endurance).getModified (); + // Current fatigue can be above base value due to a fortify effect. + // In that case stop here and don't try to restore. + DynamicStat fatigue = stats.getFatigue(); + if (fatigue.getCurrent() >= fatigue.getBase()) + return; - // restore fatigue + // Restore fatigue + int endurance = stats.getAttribute(ESM::Attribute::Endurance).getModified(); const MWWorld::Store& settings = MWBase::Environment::get().getWorld()->getStore().get(); - static const float fFatigueReturnBase = settings.find("fFatigueReturnBase")->getFloat (); - static const float fFatigueReturnMult = settings.find("fFatigueReturnMult")->getFloat (); + static const float fFatigueReturnBase = settings.find("fFatigueReturnBase")->mValue.getFloat (); + static const float fFatigueReturnMult = settings.find("fFatigueReturnMult")->mValue.getFloat (); float x = fFatigueReturnBase + fFatigueReturnMult * endurance; - DynamicStat fatigue = stats.getFatigue(); fatigue.setCurrent (fatigue.getCurrent() + duration * x); stats.setFatigue (fatigue); } @@ -688,6 +696,19 @@ namespace MWMechanics } } + // dynamic stats + for (int i = 0; i < 3; ++i) + { + DynamicStat stat = creatureStats.getDynamic(i); + stat.setCurrentModifier(effects.get(ESM::MagicEffect::FortifyHealth + i).getMagnitude() - + effects.get(ESM::MagicEffect::DrainHealth + i).getMagnitude(), + // Magicka can be decreased below zero due to a fortify effect wearing off + // Fatigue can be decreased below zero meaning the actor will be knocked out + i == 1 || i == 2); + + creatureStats.setDynamic(i, stat); + } + // attributes for(int i = 0;i < ESM::Attribute::Length;++i) { @@ -719,19 +740,6 @@ namespace MWMechanics } } - // dynamic stats - for(int i = 0;i < 3;++i) - { - DynamicStat stat = creatureStats.getDynamic(i); - stat.setModifier(effects.get(ESM::MagicEffect::FortifyHealth+i).getMagnitude() - - effects.get(ESM::MagicEffect::DrainHealth+i).getMagnitude(), - // Magicka can be decreased below zero due to a fortify effect wearing off - // Fatigue can be decreased below zero meaning the actor will be knocked out - i == 1 || i == 2); - - creatureStats.setDynamic(i, stat); - } - // AI setting modifiers int creature = !ptr.getClass().isNpc(); if (creature && ptr.get()->mBase->mData.mType == ESM::Creature::Humanoid) @@ -845,14 +853,14 @@ namespace MWMechanics std::string itemGmst = it->second; std::string item = MWBase::Environment::get().getWorld()->getStore().get().find( - itemGmst)->getString(); + itemGmst)->mValue.getString(); magnitude > 0 ? addBoundItem(item, ptr) : removeBoundItem(item, ptr); if (it->first == ESM::MagicEffect::BoundGloves) { item = MWBase::Environment::get().getWorld()->getStore().get().find( - "sMagicBoundRightGauntletID")->getString(); + "sMagicBoundRightGauntletID")->mValue.getString(); magnitude > 0 ? addBoundItem(item, ptr) : removeBoundItem(item, ptr); } } @@ -888,14 +896,14 @@ namespace MWMechanics } } - bool Actors::isAttackPrepairing(const MWWorld::Ptr& ptr) + bool Actors::isAttackPreparing(const MWWorld::Ptr& ptr) { PtrActorMap::iterator it = mActors.find(ptr); if (it == mActors.end()) return false; CharacterController* ctrl = it->second->getCharacterController(); - return ctrl->isAttackPrepairing(); + return ctrl->isAttackPreparing(); } bool Actors::isRunning(const MWWorld::Ptr& ptr) @@ -928,7 +936,7 @@ namespace MWMechanics NpcStats &stats = ptr.getClass().getNpcStats(ptr); // When npc stats are just initialized, mTimeToStartDrowning == -1 and we should get value from GMST - static const float fHoldBreathTime = MWBase::Environment::get().getWorld()->getStore().get().find("fHoldBreathTime")->getFloat(); + static const float fHoldBreathTime = MWBase::Environment::get().getWorld()->getStore().get().find("fHoldBreathTime")->mValue.getFloat(); if (stats.getTimeToStartDrowning() == -1.f) stats.setTimeToStartDrowning(fHoldBreathTime); @@ -962,7 +970,7 @@ namespace MWMechanics if(timeLeft == 0.0f && !godmode) { // If drowning, apply 3 points of damage per second - static const float fSuffocationDamage = world->getStore().get().find("fSuffocationDamage")->getFloat(); + static const float fSuffocationDamage = world->getStore().get().find("fSuffocationDamage")->mValue.getFloat(); DynamicStat health = stats.getHealth(); health.setCurrent(health.getCurrent() - fSuffocationDamage*duration); stats.setHealth(health); @@ -1088,7 +1096,7 @@ namespace MWMechanics && creatureStats.getMagicEffects().get(ESM::MagicEffect::CalmHumanoid).getMagnitude() == 0) { const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); - static const int cutoff = esmStore.get().find("iCrimeThreshold")->getInt(); + static const int cutoff = esmStore.get().find("iCrimeThreshold")->mValue.getInteger(); // 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 @@ -1096,7 +1104,7 @@ namespace MWMechanics && MWBase::Environment::get().getWorld()->getLOS(ptr, player) && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, ptr)) { - static const int iCrimeThresholdMultiplier = esmStore.get().find("iCrimeThresholdMultiplier")->getInt(); + static const int iCrimeThresholdMultiplier = esmStore.get().find("iCrimeThresholdMultiplier")->mValue.getInteger(); if (player.getClass().getNpcStats(player).getBounty() >= cutoff * iCrimeThresholdMultiplier) { MWBase::Environment::get().getMechanicsManager()->startCombat(ptr, player); @@ -1298,6 +1306,8 @@ namespace MWMechanics // AI and magic effects update for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { + bool isPlayer = iter->first == player; + float distSqr = (player.getRefData().getPosition().asVec3() - iter->first.getRefData().getPosition().asVec3()).length2(); // AI processing is only done within distance of 7168 units to the player. Note the "AI distance" slider doesn't affect this // (it only does some throttling for targets beyond the "AI distance", so doesn't give any guarantees as to whether AI will be enabled or not) @@ -1305,7 +1315,7 @@ namespace MWMechanics // using higher values will make a quest in Bloodmoon harder or impossible to complete (bug #1876) bool inProcessingRange = distSqr <= sqrAiProcessingDistance; - if (iter->first == player) + if (isPlayer) iter->second->getCharacterController()->setAttackingOrSpell(MWBase::Environment::get().getWorld()->getPlayer().getAttackingOrSpell()); // If dead or no longer in combat, no longer store any actors who attempted to hit us. Also remove for the player. @@ -1337,12 +1347,12 @@ namespace MWMechanics { if (timerUpdateAITargets == 0) { - if (iter->first != player) + if (!isPlayer) adjustCommandedActor(iter->first); for(PtrActorMap::iterator it(mActors.begin()); it != mActors.end(); ++it) { - if (it->first == iter->first || iter->first == player) // player is not AI-controlled + if (it->first == iter->first || isPlayer) // player is not AI-controlled continue; engageCombat(iter->first, it->first, cachedAllies, it->first == player); } @@ -1353,12 +1363,15 @@ namespace MWMechanics MWWorld::Ptr headTrackTarget; MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first); + bool firstPersonPlayer = isPlayer && MWBase::Environment::get().getWorld()->isFirstPerson(); - // Unconsious actor can not track target - // Also actors in combat and pursue mode do not bother to headtrack + // 1. Unconsious actor can not track target + // 2. Actors in combat and pursue mode do not bother to headtrack + // 3. Player character does not use headtracking in the 1st-person view if (!stats.getKnockedDown() && !stats.getAiSequence().isInCombat() && - !stats.getAiSequence().hasPackage(AiPackage::TypeIdPursue)) + !stats.getAiSequence().hasPackage(AiPackage::TypeIdPursue) && + !firstPersonPlayer) { for(PtrActorMap::iterator it(mActors.begin()); it != mActors.end(); ++it) { @@ -1474,9 +1487,9 @@ namespace MWMechanics static float sneakSkillTimer = 0.f; // times sneak skill progress from "avoid notice" const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); - const int radius = esmStore.get().find("fSneakUseDist")->getInt(); + const int radius = esmStore.get().find("fSneakUseDist")->mValue.getInteger(); - static float fSneakUseDelay = esmStore.get().find("fSneakUseDelay")->getFloat(); + static float fSneakUseDelay = esmStore.get().find("fSneakUseDelay")->mValue.getFloat(); if (sneakTimer >= fSneakUseDelay) sneakTimer = 0.f; @@ -1666,7 +1679,7 @@ namespace MWMechanics MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(iter->first); if (animation) - animation->updateEffects(duration); + animation->updateEffects(); } @@ -1715,7 +1728,7 @@ namespace MWMechanics } else { - std::cerr<< "Warning: Actors::playAnimationGroup: Unable to find " << ptr.getCellRef().getRefId() << std::endl; + Log(Debug::Warning) << "Warning: Actors::playAnimationGroup: Unable to find " << ptr.getCellRef().getRefId(); return false; } } @@ -1763,38 +1776,36 @@ namespace MWMechanics std::list Actors::getActorsSidingWith(const MWWorld::Ptr& actor) { std::list list; - for(PtrActorMap::iterator iter(mActors.begin());iter != mActors.end();++iter) + for(PtrActorMap::iterator iter = mActors.begin(); iter != mActors.end(); ++iter) { - const MWWorld::Class &cls = iter->first.getClass(); - const CreatureStats &stats = cls.getCreatureStats(iter->first); + const MWWorld::Ptr &iteratedActor = iter->first; + if (iteratedActor == getPlayer()) + continue; + + const bool sameActor = (iteratedActor == actor); + + const CreatureStats &stats = iteratedActor.getClass().getCreatureStats(iteratedActor); if (stats.isDead()) continue; - // An actor counts as siding with this actor if Follow or Escort is the current AI package, or there are only Combat packages before the Follow/Escort package - for (std::list::const_iterator it = stats.getAiSequence().begin(); it != stats.getAiSequence().end(); ++it) - { - if ((*it)->sideWithTarget() && (*it)->getTarget() == actor) - { - list.push_back(iter->first); - break; - } - else if ((*it)->getTypeId() != MWMechanics::AiPackage::TypeIdCombat) - break; - } + // An actor counts as siding with this actor if Follow or Escort is the current AI package, or there are only Combat and Wander packages before the Follow/Escort package // Actors that are targeted by this actor's Follow or Escort packages also side with them - if (actor != getPlayer()) + for (auto package = stats.getAiSequence().begin(); package != stats.getAiSequence().end(); ++package) { - const CreatureStats &stats2 = actor.getClass().getCreatureStats(actor); - for (std::list::const_iterator it2 = stats2.getAiSequence().begin(); it2 != stats2.getAiSequence().end(); ++it2) + if ((*package)->sideWithTarget() && !(*package)->getTarget().isEmpty()) { - if ((*it2)->sideWithTarget() && !(*it2)->getTarget().isEmpty()) + if (sameActor) { - list.push_back((*it2)->getTarget()); - break; + list.push_back((*package)->getTarget()); } - else if ((*it2)->getTypeId() != MWMechanics::AiPackage::TypeIdCombat) - break; + else if ((*package)->getTarget() == actor) + { + list.push_back(iteratedActor); + } + break; } + else if ((*package)->getTypeId() != AiPackage::TypeIdCombat && (*package)->getTypeId() != AiPackage::TypeIdWander) + break; } } return list; @@ -1805,17 +1816,21 @@ namespace MWMechanics std::list list; for(PtrActorMap::iterator iter(mActors.begin());iter != mActors.end();++iter) { - const MWWorld::Class &cls = iter->first.getClass(); - CreatureStats &stats = cls.getCreatureStats(iter->first); + const MWWorld::Ptr &iteratedActor = iter->first; + if (iteratedActor == getPlayer() || iteratedActor == actor) + continue; + + const CreatureStats &stats = iteratedActor.getClass().getCreatureStats(iteratedActor); if (stats.isDead()) continue; - // An actor counts as following if AiFollow is the current AiPackage, or there are only Combat packages before the AiFollow package - for (std::list::const_iterator it = stats.getAiSequence().begin(); it != stats.getAiSequence().end(); ++it) + // An actor counts as following if AiFollow is the current AiPackage, + // or there are only Combat and Wander packages before the AiFollow package + for (auto package = stats.getAiSequence().begin(); package != stats.getAiSequence().end(); ++package) { - if ((*it)->followTargetThroughDoors() && (*it)->getTarget() == actor) - list.push_back(iter->first); - else if ((*it)->getTypeId() != MWMechanics::AiPackage::TypeIdCombat) + if ((*package)->followTargetThroughDoors() && (*package)->getTarget() == actor) + list.push_back(iteratedActor); + else if ((*package)->getTypeId() != AiPackage::TypeIdCombat && (*package)->getTypeId() != AiPackage::TypeIdWander) break; } } @@ -1864,24 +1879,24 @@ namespace MWMechanics std::list list; for(PtrActorMap::iterator iter(mActors.begin());iter != mActors.end();++iter) { - const MWWorld::Class &cls = iter->first.getClass(); - CreatureStats &stats = cls.getCreatureStats(iter->first); + const MWWorld::Ptr &iteratedActor = iter->first; + if (iteratedActor == getPlayer() || iteratedActor == actor) + continue; + + const CreatureStats &stats = iteratedActor.getClass().getCreatureStats(iteratedActor); if (stats.isDead()) continue; - // An actor counts as following if AiFollow is the current AiPackage, or there are only Combat packages before the AiFollow package - for (std::list::const_iterator it = stats.getAiSequence().begin(); it != stats.getAiSequence().end(); ++it) + // An actor counts as following if AiFollow is the current AiPackage, + // or there are only Combat and Wander packages before the AiFollow package + for (auto package = stats.getAiSequence().begin(); package != stats.getAiSequence().end(); ++package) { - if ((*it)->getTypeId() == MWMechanics::AiPackage::TypeIdFollow) + if ((*package)->followTargetThroughDoors() && (*package)->getTarget() == actor) { - MWWorld::Ptr followTarget = (*it)->getTarget(); - if (followTarget.isEmpty()) - continue; - if (followTarget == actor) - list.push_back(static_cast(*it)->getFollowIndex()); + list.push_back(static_cast(*package)->getFollowIndex()); break; } - else if ((*it)->getTypeId() != MWMechanics::AiPackage::TypeIdCombat) + else if ((*package)->getTypeId() != AiPackage::TypeIdCombat && (*package)->getTypeId() != AiPackage::TypeIdWander) break; } } @@ -1893,14 +1908,17 @@ namespace MWMechanics std::vector neighbors; osg::Vec3f position (actor.getRefData().getPosition().asVec3()); getObjectsInRange(position, aiProcessingDistance, neighbors); - for(std::vector::const_iterator iter(neighbors.begin());iter != neighbors.end();++iter) + for(auto neighbor = neighbors.begin(); neighbor != neighbors.end(); ++neighbor) { - const MWWorld::Class &cls = iter->getClass(); - const CreatureStats &stats = cls.getCreatureStats(*iter); - if (stats.isDead() || *iter == actor) + if (*neighbor == actor) continue; + + const CreatureStats &stats = neighbor->getClass().getCreatureStats(*neighbor); + if (stats.isDead()) + continue; + if (stats.getAiSequence().isInCombat(actor)) - list.push_front(*iter); + list.push_front(*neighbor); } return list; } @@ -1912,15 +1930,18 @@ namespace MWMechanics osg::Vec3f position (actor.getRefData().getPosition().asVec3()); getObjectsInRange(position, aiProcessingDistance, neighbors); - std::list followers = getActorsFollowing(actor); - for(std::vector::const_iterator iter(neighbors.begin());iter != neighbors.end();++iter) + std::set followers; + getActorsFollowing(actor, followers); + for (auto neighbor = neighbors.begin(); neighbor != neighbors.end(); ++neighbor) { - const CreatureStats &stats = iter->getClass().getCreatureStats(*iter); - if (stats.isDead() || *iter == actor || iter->getClass().isPureWaterCreature(*iter)) + const CreatureStats &stats = neighbor->getClass().getCreatureStats(*neighbor); + if (stats.isDead() || *neighbor == actor || neighbor->getClass().isPureWaterCreature(*neighbor)) continue; - const bool isFollower = std::find(followers.begin(), followers.end(), *iter) != followers.end(); - if (stats.getAiSequence().isInCombat(actor) || (MWBase::Environment::get().getMechanicsManager()->isAggressive(*iter, actor) && !isFollower)) - list.push_back(*iter); + + const bool isFollower = followers.find(*neighbor) != followers.end(); + + if (stats.getAiSequence().isInCombat(actor) || (MWBase::Environment::get().getMechanicsManager()->isAggressive(*neighbor, actor) && !isFollower)) + list.push_back(*neighbor); } return list; } diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 492ff1e2ea..8c94ce45f2 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -118,7 +118,7 @@ namespace MWMechanics int countDeaths (const std::string& id) const; ///< Return the number of deaths for actors with the given ID. - bool isAttackPrepairing(const MWWorld::Ptr& ptr); + bool isAttackPreparing(const MWWorld::Ptr& ptr); bool isRunning(const MWWorld::Ptr& ptr); bool isSneaking(const MWWorld::Ptr& ptr); diff --git a/apps/openmw/mwmechanics/aibreathe.cpp b/apps/openmw/mwmechanics/aibreathe.cpp index 4e0076824b..36acc75d52 100644 --- a/apps/openmw/mwmechanics/aibreathe.cpp +++ b/apps/openmw/mwmechanics/aibreathe.cpp @@ -19,7 +19,7 @@ MWMechanics::AiBreathe::AiBreathe() bool MWMechanics::AiBreathe::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) { - static const float fHoldBreathTime = MWBase::Environment::get().getWorld()->getStore().get().find("fHoldBreathTime")->getFloat(); + static const float fHoldBreathTime = MWBase::Environment::get().getWorld()->getStore().get().find("fHoldBreathTime")->mValue.getFloat(); const MWWorld::Class& actorClass = actor.getClass(); if (actorClass.isNpc()) diff --git a/apps/openmw/mwmechanics/aicast.cpp b/apps/openmw/mwmechanics/aicast.cpp index 48cb17f6d7..948ffb3aac 100644 --- a/apps/openmw/mwmechanics/aicast.cpp +++ b/apps/openmw/mwmechanics/aicast.cpp @@ -42,7 +42,19 @@ bool MWMechanics::AiCast::execute(const MWWorld::Ptr& actor, MWMechanics::Charac return false; } - osg::Vec3f dir = target.getRefData().getPosition().asVec3() - actor.getRefData().getPosition().asVec3(); + osg::Vec3f targetPos = target.getRefData().getPosition().asVec3(); + if (target.getClass().isActor()) + { + osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(target); + targetPos.z() += halfExtents.z() * 2 * 0.75f; + } + + osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3(); + osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor); + actorPos.z() += halfExtents.z() * 2 * 0.75f; + + osg::Vec3f dir = targetPos - actorPos; + bool turned = smoothTurn(actor, getZAngleToDir(dir), 2, osg::DegreesToRadians(3.f)); turned &= smoothTurn(actor, getXAngleToDir(dir), 0, osg::DegreesToRadians(3.f)); diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index bc6d223263..9824347e32 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -351,7 +351,7 @@ namespace MWMechanics case AiCombatStorage::FleeState_RunToDestination: { - static const float fFleeDistance = MWBase::Environment::get().getWorld()->getStore().get().find("fFleeDistance")->getFloat(); + static const float fFleeDistance = MWBase::Environment::get().getWorld()->getStore().get().find("fFleeDistance")->mValue.getFloat(); float dist = (actor.getRefData().getPosition().asVec3() - target.getRefData().getPosition().asVec3()).length(); if ((dist > fFleeDistance && !storage.mLOS) @@ -372,21 +372,32 @@ namespace MWMechanics actorMovementSettings.mPosition[1] = storage.mMovement.mPosition[1]; actorMovementSettings.mPosition[2] = storage.mMovement.mPosition[2]; - rotateActorOnAxis(actor, 2, actorMovementSettings, storage.mMovement); - rotateActorOnAxis(actor, 0, actorMovementSettings, storage.mMovement); + rotateActorOnAxis(actor, 2, actorMovementSettings, storage); + rotateActorOnAxis(actor, 0, actorMovementSettings, storage); } void AiCombat::rotateActorOnAxis(const MWWorld::Ptr& actor, int axis, - MWMechanics::Movement& actorMovementSettings, MWMechanics::Movement& desiredMovement) + MWMechanics::Movement& actorMovementSettings, AiCombatStorage& storage) { actorMovementSettings.mRotation[axis] = 0; - float& targetAngleRadians = desiredMovement.mRotation[axis]; + float& targetAngleRadians = storage.mMovement.mRotation[axis]; if (targetAngleRadians != 0) { - if (smoothTurn(actor, targetAngleRadians, axis)) + // Some attack animations contain small amount of movement. + // Since we use cone shapes for melee, we can use a threshold to avoid jittering + std::shared_ptr& currentAction = storage.mCurrentAction; + bool isRangedCombat = false; + currentAction->getCombatRange(isRangedCombat); + // Check if the actor now facing desired direction, no need to turn any more + if (isRangedCombat) { - // actor now facing desired direction, no need to turn any more - targetAngleRadians = 0; + if (smoothTurn(actor, targetAngleRadians, axis)) + targetAngleRadians = 0; + } + else + { + if (smoothTurn(actor, targetAngleRadians, axis, osg::DegreesToRadians(3.f))) + targetAngleRadians = 0; } } } @@ -453,7 +464,7 @@ namespace MWMechanics if (distToTarget <= rangeAttackOfTarget && Misc::Rng::rollClosedProbability() < 0.25) { mMovement.mPosition[0] = Misc::Rng::rollProbability() < 0.5 ? 1.0f : -1.0f; // to the left/right - mTimerCombatMove = 0.05f + 0.15f * Misc::Rng::rollClosedProbability(); + mTimerCombatMove = 0.1f + 0.1f * Misc::Rng::rollClosedProbability(); mCombatMove = true; } } @@ -509,13 +520,13 @@ namespace MWMechanics const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - float baseDelay = store.get().find("fCombatDelayCreature")->getFloat(); + float baseDelay = store.get().find("fCombatDelayCreature")->mValue.getFloat(); if (actor.getClass().isNpc()) { - baseDelay = store.get().find("fCombatDelayNPC")->getFloat(); + baseDelay = store.get().find("fCombatDelayNPC")->mValue.getFloat(); //say a provoking combat phrase - int chance = store.get().find("iVoiceAttackOdds")->getInt(); + int chance = store.get().find("iVoiceAttackOdds")->mValue.getInteger(); if (Misc::Rng::roll0to99() < chance) { MWBase::Environment::get().getDialogueManager()->say(actor, "attack"); @@ -600,27 +611,26 @@ osg::Vec3f AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& t float duration, int weapType, float strength) { float projSpeed; + const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); // get projectile speed (depending on weapon type) if (weapType == ESM::Weapon::MarksmanThrown) { - static float fThrownWeaponMinSpeed = - MWBase::Environment::get().getWorld()->getStore().get().find("fThrownWeaponMinSpeed")->getFloat(); - static float fThrownWeaponMaxSpeed = - MWBase::Environment::get().getWorld()->getStore().get().find("fThrownWeaponMaxSpeed")->getFloat(); + static float fThrownWeaponMinSpeed = gmst.find("fThrownWeaponMinSpeed")->mValue.getFloat(); + static float fThrownWeaponMaxSpeed = gmst.find("fThrownWeaponMaxSpeed")->mValue.getFloat(); - projSpeed = - fThrownWeaponMinSpeed + (fThrownWeaponMaxSpeed - fThrownWeaponMinSpeed) * strength; + projSpeed = fThrownWeaponMinSpeed + (fThrownWeaponMaxSpeed - fThrownWeaponMinSpeed) * strength; } - else + else if (weapType != 0) { - static float fProjectileMinSpeed = - MWBase::Environment::get().getWorld()->getStore().get().find("fProjectileMinSpeed")->getFloat(); - static float fProjectileMaxSpeed = - MWBase::Environment::get().getWorld()->getStore().get().find("fProjectileMaxSpeed")->getFloat(); + static float fProjectileMinSpeed = gmst.find("fProjectileMinSpeed")->mValue.getFloat(); + static float fProjectileMaxSpeed = gmst.find("fProjectileMaxSpeed")->mValue.getFloat(); - projSpeed = - fProjectileMinSpeed + (fProjectileMaxSpeed - fProjectileMinSpeed) * strength; + projSpeed = fProjectileMinSpeed + (fProjectileMaxSpeed - fProjectileMinSpeed) * strength; + } + else // weapType is 0 ==> it's a target spell projectile + { + projSpeed = gmst.find("fTargetSpellMaxSpeed")->mValue.getFloat(); } // idea: perpendicular to dir to target speed components of target move vector and projectile vector should be the same diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 7c9891bcc2..88feba4811 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -129,7 +129,7 @@ namespace MWMechanics /// Transfer desired movement (from AiCombatStorage) to Actor void updateActorsMovement(const MWWorld::Ptr& actor, float duration, AiCombatStorage& storage); void rotateActorOnAxis(const MWWorld::Ptr& actor, int axis, - MWMechanics::Movement& actorMovementSettings, MWMechanics::Movement& desiredMovement); + MWMechanics::Movement& actorMovementSettings, AiCombatStorage& storage); }; diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index 0ccd0b6ec6..6fffff6377 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -23,8 +23,8 @@ namespace MWMechanics { float suggestCombatRange(int rangeTypes) { - static const float fCombatDistance = MWBase::Environment::get().getWorld()->getStore().get().find("fCombatDistance")->getFloat(); - static float fHandToHandReach = MWBase::Environment::get().getWorld()->getStore().get().find("fHandToHandReach")->getFloat(); + static const float fCombatDistance = MWBase::Environment::get().getWorld()->getStore().get().find("fCombatDistance")->mValue.getFloat(); + static float fHandToHandReach = MWBase::Environment::get().getWorld()->getStore().get().find("fHandToHandReach")->mValue.getFloat(); // This distance is a possible distance of melee attack static float distance = fCombatDistance * std::max(2.f, fHandToHandReach); @@ -114,13 +114,13 @@ namespace MWMechanics { isRanged = false; - static const float fCombatDistance = MWBase::Environment::get().getWorld()->getStore().get().find("fCombatDistance")->getFloat(); - static const float fProjectileMaxSpeed = MWBase::Environment::get().getWorld()->getStore().get().find("fProjectileMaxSpeed")->getFloat(); + static const float fCombatDistance = MWBase::Environment::get().getWorld()->getStore().get().find("fCombatDistance")->mValue.getFloat(); + static const float fProjectileMaxSpeed = MWBase::Environment::get().getWorld()->getStore().get().find("fProjectileMaxSpeed")->mValue.getFloat(); if (mWeapon.isEmpty()) { static float fHandToHandReach = - MWBase::Environment::get().getWorld()->getStore().get().find("fHandToHandReach")->getFloat(); + MWBase::Environment::get().getWorld()->getStore().get().find("fHandToHandReach")->mValue.getFloat(); return fHandToHandReach * fCombatDistance; } @@ -190,11 +190,6 @@ namespace MWMechanics for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { - std::vector equipmentSlots = it->getClass().getEquipmentSlots(*it).first; - if (std::find(equipmentSlots.begin(), equipmentSlots.end(), (int)MWWorld::InventoryStore::Slot_CarriedRight) - == equipmentSlots.end()) - continue; - float rating = rateWeapon(*it, actor, enemy, -1, bestArrowRating, bestBoltRating); if (rating > bestActionRating) { @@ -215,14 +210,12 @@ namespace MWMechanics for (Spells::TIterator it = spells.begin(); it != spells.end(); ++it) { - const ESM::Spell* spell = it->first; - - float rating = rateSpell(spell, actor, enemy); + float rating = rateSpell(it->first, actor, enemy); if (rating > bestActionRating) { bestActionRating = rating; - bestAction.reset(new ActionSpell(spell->mId)); - antiFleeRating = vanillaRateSpell(spell, actor, enemy); + bestAction.reset(new ActionSpell(it->first->mId)); + antiFleeRating = vanillaRateSpell(it->first, actor, enemy); } } @@ -265,11 +258,6 @@ namespace MWMechanics for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { - std::vector equipmentSlots = it->getClass().getEquipmentSlots(*it).first; - if (std::find(equipmentSlots.begin(), equipmentSlots.end(), (int)MWWorld::InventoryStore::Slot_CarriedRight) - == equipmentSlots.end()) - continue; - float rating = rateWeapon(*it, actor, enemy, -1, bestArrowRating, bestBoltRating); if (rating > bestActionRating) { @@ -280,9 +268,7 @@ namespace MWMechanics for (Spells::TIterator it = spells.begin(); it != spells.end(); ++it) { - const ESM::Spell* spell = it->first; - - float rating = rateSpell(spell, actor, enemy); + float rating = rateSpell(it->first, actor, enemy); if (rating > bestActionRating) { bestActionRating = rating; @@ -336,7 +322,7 @@ namespace MWMechanics float dist = 1.0f; if (activeWeapon.isEmpty() && !selectedSpellId.empty() && !selectedEnchItem.isEmpty()) { - static const float fHandToHandReach = gmst.find("fHandToHandReach")->getFloat(); + static const float fHandToHandReach = gmst.find("fHandToHandReach")->mValue.getFloat(); dist = fHandToHandReach; } else if (stats.getDrawState() == MWMechanics::DrawState_Spell) @@ -375,7 +361,7 @@ namespace MWMechanics } } - static const float fTargetSpellMaxSpeed = gmst.find("fTargetSpellMaxSpeed")->getFloat(); + static const float fTargetSpellMaxSpeed = gmst.find("fTargetSpellMaxSpeed")->mValue.getFloat(); dist *= std::max(1000.0f, fTargetSpellMaxSpeed); } else if (!activeWeapon.isEmpty()) @@ -383,7 +369,7 @@ namespace MWMechanics const ESM::Weapon* esmWeap = activeWeapon.get()->mBase; if (esmWeap->mData.mType >= ESM::Weapon::MarksmanBow) { - static const float fTargetSpellMaxSpeed = gmst.find("fProjectileMaxSpeed")->getFloat(); + static const float fTargetSpellMaxSpeed = gmst.find("fProjectileMaxSpeed")->mValue.getFloat(); dist = fTargetSpellMaxSpeed; if (!activeAmmo.isEmpty()) { @@ -399,8 +385,8 @@ namespace MWMechanics dist = (dist > 0.f) ? dist : 1.0f; - static const float fCombatDistance = gmst.find("fCombatDistance")->getFloat(); - static const float fCombatDistanceWerewolfMod = gmst.find("fCombatDistanceWerewolfMod")->getFloat(); + static const float fCombatDistance = gmst.find("fCombatDistance")->mValue.getFloat(); + static const float fCombatDistanceWerewolfMod = gmst.find("fCombatDistanceWerewolfMod")->mValue.getFloat(); float combatDistance = fCombatDistance; if (actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) @@ -485,19 +471,22 @@ namespace MWMechanics if (flee >= 100) return flee; - static const float fAIFleeHealthMult = gmst.find("fAIFleeHealthMult")->getFloat(); - static const float fAIFleeFleeMult = gmst.find("fAIFleeFleeMult")->getFloat(); + static const float fAIFleeHealthMult = gmst.find("fAIFleeHealthMult")->mValue.getFloat(); + static const float fAIFleeFleeMult = gmst.find("fAIFleeFleeMult")->mValue.getFloat(); float healthPercentage = (stats.getHealth().getModified() == 0.0f) ? 1.0f : stats.getHealth().getCurrent() / stats.getHealth().getModified(); float rating = (1.0f - healthPercentage) * fAIFleeHealthMult + flee * fAIFleeFleeMult; - static const int iWereWolfLevelToAttack = gmst.find("iWereWolfLevelToAttack")->getInt(); + static const int iWereWolfLevelToAttack = gmst.find("iWereWolfLevelToAttack")->mValue.getInteger(); - if (enemy.getClass().isNpc() && enemy.getClass().getNpcStats(enemy).isWerewolf() && stats.getLevel() < iWereWolfLevelToAttack) + if (actor.getClass().isNpc() && enemy.getClass().isNpc()) { - static const int iWereWolfFleeMod = gmst.find("iWereWolfFleeMod")->getInt(); - rating = iWereWolfFleeMod; + if (enemy.getClass().getNpcStats(enemy).isWerewolf() && stats.getLevel() < iWereWolfLevelToAttack) + { + static const int iWereWolfFleeMod = gmst.find("iWereWolfFleeMod")->mValue.getInteger(); + rating = iWereWolfFleeMod; + } } if (rating != 0.0f) diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 0851748200..cf572abc0f 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -1,8 +1,8 @@ #include "aisequence.hpp" #include -#include +#include #include #include "../mwbase/environment.hpp" @@ -19,6 +19,7 @@ #include "aicombataction.hpp" #include "aipursue.hpp" #include "actorutil.hpp" +#include "../mwworld/class.hpp" namespace MWMechanics { @@ -122,6 +123,20 @@ bool AiSequence::isInCombat() const return false; } +bool AiSequence::isEngagedWithActor() const +{ + for (std::list::const_iterator it = mPackages.begin(); it != mPackages.end(); ++it) + { + if ((*it)->getTypeId() == AiPackage::TypeIdCombat) + { + MWWorld::Ptr target2 = (*it)->getTarget(); + if (!target2.isEmpty() && target2.getClass().isNpc()) + return true; + } + } + return false; +} + bool AiSequence::hasPackage(int typeId) const { for (std::list::const_iterator it = mPackages.begin(); it != mPackages.end(); ++it) @@ -213,7 +228,7 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac { if ((*it)->getTypeId() != AiPackage::TypeIdCombat) break; - MWWorld::Ptr target = static_cast(*it)->getTarget(); + MWWorld::Ptr target = (*it)->getTarget(); // target disappeared (e.g. summoned creatures) if (target.isEmpty()) @@ -227,11 +242,11 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac const ESM::Position &targetPos = target.getRefData().getPosition(); - float distTo = (targetPos.asVec3() - vActorPos).length(); + float distTo = (targetPos.asVec3() - vActorPos).length2(); // Small threshold for changing target if (it == mPackages.begin()) - distTo = std::max(0.f, distTo - 50.f); + distTo = std::max(0.f, distTo - 2500.f); // if a target has higher priority than current target or has same priority but closer if (rating > bestRating || ((distTo < nearestDist) && rating == bestRating)) @@ -282,7 +297,7 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac } catch (std::exception& e) { - std::cerr << "Error during AiSequence::execute: " << e.what() << std::endl; + Log(Debug::Error) << "Error during AiSequence::execute: " << e.what(); } } } diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index 5c72bcc4ca..4d0482a985 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -88,6 +88,9 @@ namespace MWMechanics /// Is there any combat package? bool isInCombat () const; + /// Are we in combat with any other actor, who's also engaging us? + bool isEngagedWithActor () const; + /// Does this AI sequence have the given package type? bool hasPackage(int typeId) const; diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 928b09cf9a..d36c5930f8 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -1,10 +1,9 @@ #include "aiwander.hpp" #include -#include +#include #include - #include #include "../mwbase/world.hpp" @@ -24,8 +23,6 @@ #include "coordinateconverter.hpp" #include "actorutil.hpp" - - namespace MWMechanics { static const int COUNT_BEFORE_RESET = 10; @@ -498,7 +495,7 @@ namespace MWMechanics MWWorld::Ptr player = getPlayer(); static float fVoiceIdleOdds = MWBase::Environment::get().getWorld()->getStore() - .get().find("fVoiceIdleOdds")->getFloat(); + .get().find("fVoiceIdleOdds")->mValue.getFloat(); float roll = Misc::Rng::rollProbability() * 10000.0f; @@ -525,7 +522,7 @@ namespace MWMechanics int hello = actor.getClass().getCreatureStats(actor).getAiSetting(CreatureStats::AI_Hello).getModified(); float helloDistance = static_cast(hello); static int iGreetDistanceMultiplier = MWBase::Environment::get().getWorld()->getStore() - .get().find("iGreetDistanceMultiplier")->getInt(); + .get().find("iGreetDistanceMultiplier")->mValue.getInteger(); helloDistance *= iGreetDistanceMultiplier; @@ -677,7 +674,7 @@ namespace MWMechanics } else { - std::cerr<< "Error: Attempted to play out of range idle animation \""<getStore() - .get().find("fIdleChanceMultiplier")->getFloat(); + .get().find("fIdleChanceMultiplier")->mValue.getFloat(); unsigned short idleChance = static_cast(fIdleChanceMultiplier * mIdle[counter]); unsigned short randSelect = (int)(Misc::Rng::rollProbability() * int(100 / fIdleChanceMultiplier)); diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index 48705dc72b..b8f8203070 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -140,11 +140,11 @@ void MWMechanics::Alchemy::updateEffects() float x = getAlchemyFactor(); x *= mTools[ESM::Apparatus::MortarPestle].get()->mBase->mData.mQuality; - x *= MWBase::Environment::get().getWorld()->getStore().get().find ("fPotionStrengthMult")->getFloat(); + x *= MWBase::Environment::get().getWorld()->getStore().get().find ("fPotionStrengthMult")->mValue.getFloat(); // value mValue = static_cast ( - x * MWBase::Environment::get().getWorld()->getStore().get().find ("iAlchemyMod")->getFloat()); + x * MWBase::Environment::get().getWorld()->getStore().get().find ("iAlchemyMod")->mValue.getFloat()); // build quantified effect list for (std::set::const_iterator iter (effects.begin()); iter!=effects.end(); ++iter) @@ -160,13 +160,13 @@ void MWMechanics::Alchemy::updateEffects() } float fPotionT1MagMul = - MWBase::Environment::get().getWorld()->getStore().get().find ("fPotionT1MagMult")->getFloat(); + MWBase::Environment::get().getWorld()->getStore().get().find ("fPotionT1MagMult")->mValue.getFloat(); if (fPotionT1MagMul<=0) throw std::runtime_error ("invalid gmst: fPotionT1MagMul"); float fPotionT1DurMult = - MWBase::Environment::get().getWorld()->getStore().get().find ("fPotionT1DurMult")->getFloat(); + MWBase::Environment::get().getWorld()->getStore().get().find ("fPotionT1DurMult")->mValue.getFloat(); if (fPotionT1DurMult<=0) throw std::runtime_error ("invalid gmst: fPotionT1DurMult"); @@ -449,7 +449,7 @@ bool MWMechanics::Alchemy::knownEffect(unsigned int potionEffectIndex, const MWW MWMechanics::NpcStats& npcStats = npc.getClass().getNpcStats(npc); int alchemySkill = npcStats.getSkill (ESM::Skill::Alchemy).getBase(); static const float fWortChanceValue = - MWBase::Environment::get().getWorld()->getStore().get().find("fWortChanceValue")->getFloat(); + MWBase::Environment::get().getWorld()->getStore().get().find("fWortChanceValue")->mValue.getFloat(); return (potionEffectIndex <= 1 && alchemySkill >= fWortChanceValue) || (potionEffectIndex <= 3 && alchemySkill >= fWortChanceValue*2) || (potionEffectIndex <= 5 && alchemySkill >= fWortChanceValue*3) @@ -503,5 +503,5 @@ std::string MWMechanics::Alchemy::suggestPotionName() int effectId = effects.begin()->mId; return MWBase::Environment::get().getWorld()->getStore().get().find( - ESM::MagicEffect::effectIdToString(effectId))->getString(); + ESM::MagicEffect::effectIdToString(effectId))->mValue.getString(); } diff --git a/apps/openmw/mwmechanics/autocalcspell.cpp b/apps/openmw/mwmechanics/autocalcspell.cpp index f655a68b48..a52fcc059d 100644 --- a/apps/openmw/mwmechanics/autocalcspell.cpp +++ b/apps/openmw/mwmechanics/autocalcspell.cpp @@ -25,7 +25,7 @@ namespace MWMechanics std::vector autoCalcNpcSpells(const int *actorSkills, const int *actorAttributes, const ESM::Race* race) { const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); - static const float fNPCbaseMagickaMult = gmst.find("fNPCbaseMagickaMult")->getFloat(); + static const float fNPCbaseMagickaMult = gmst.find("fNPCbaseMagickaMult")->mValue.getFloat(); float baseMagicka = fNPCbaseMagickaMult * actorAttributes[ESM::Attribute::Intelligence]; static const std::string schools[] = { @@ -38,7 +38,7 @@ namespace MWMechanics for (int i=0; i<6; ++i) { const std::string& gmstName = "iAutoSpell" + schools[i] + "Max"; - iAutoSpellSchoolMax[i] = gmst.find(gmstName)->getInt(); + iAutoSpellSchoolMax[i] = gmst.find(gmstName)->mValue.getInteger(); } init = true; } @@ -70,7 +70,7 @@ namespace MWMechanics continue; if (!(spell->mData.mFlags & ESM::Spell::F_Autocalc)) continue; - static const int iAutoSpellTimesCanCast = gmst.find("iAutoSpellTimesCanCast")->getInt(); + static const int iAutoSpellTimesCanCast = gmst.find("iAutoSpellTimesCanCast")->mValue.getInteger(); if (baseMagicka < iAutoSpellTimesCanCast * spell->mData.mCost) continue; @@ -89,7 +89,7 @@ namespace MWMechanics if (cap.mReachedLimit && spell->mData.mCost <= cap.mMinCost) continue; - static const float fAutoSpellChance = gmst.find("fAutoSpellChance")->getFloat(); + static const float fAutoSpellChance = gmst.find("fAutoSpellChance")->mValue.getFloat(); if (calcAutoCastChance(spell, actorSkills, actorAttributes, school) < fAutoSpellChance) continue; @@ -146,7 +146,7 @@ namespace MWMechanics { const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); - static const float fPCbaseMagickaMult = esmStore.get().find("fPCbaseMagickaMult")->getFloat(); + static const float fPCbaseMagickaMult = esmStore.get().find("fPCbaseMagickaMult")->mValue.getFloat(); float baseMagicka = fPCbaseMagickaMult * actorAttributes[ESM::Attribute::Intelligence]; bool reachedLimit = false; @@ -173,7 +173,7 @@ namespace MWMechanics if (baseMagicka < spell->mData.mCost) continue; - static const float fAutoPCSpellChance = esmStore.get().find("fAutoPCSpellChance")->getFloat(); + static const float fAutoPCSpellChance = esmStore.get().find("fAutoPCSpellChance")->mValue.getFloat(); if (calcAutoCastChance(spell, actorSkills, actorAttributes, -1) < fAutoPCSpellChance) continue; @@ -206,7 +206,7 @@ namespace MWMechanics weakestSpell = spell; minCost = weakestSpell->mData.mCost; } - static const unsigned int iAutoPCSpellMax = esmStore.get().find("iAutoPCSpellMax")->getInt(); + static const unsigned int iAutoPCSpellMax = esmStore.get().find("iAutoPCSpellMax")->mValue.getInteger(); if (selectedSpells.size() == iAutoPCSpellMax) reachedLimit = true; } @@ -221,7 +221,7 @@ namespace MWMechanics for (std::vector::const_iterator effectIt = effects.begin(); effectIt != effects.end(); ++effectIt) { const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effectIt->mEffectID); - static const int iAutoSpellAttSkillMin = MWBase::Environment::get().getWorld()->getStore().get().find("iAutoSpellAttSkillMin")->getInt(); + static const int iAutoSpellAttSkillMin = MWBase::Environment::get().getWorld()->getStore().get().find("iAutoSpellAttSkillMin")->mValue.getInteger(); if ((magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill)) { @@ -278,7 +278,7 @@ namespace MWMechanics duration = effect.mDuration; static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore() - .get().find("fEffectCostMult")->getFloat(); + .get().find("fEffectCostMult")->mValue.getFloat(); float x = 0.5 * (std::max(1, minMagn) + std::max(1, maxMagn)); x *= 0.1 * magicEffect->mData.mBaseCost; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index f843be66ee..fddd351fa6 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -123,16 +123,16 @@ float getFallDamage(const MWWorld::Ptr& ptr, float fallHeight) MWBase::World *world = MWBase::Environment::get().getWorld(); const MWWorld::Store &store = world->getStore().get(); - const float fallDistanceMin = store.find("fFallDamageDistanceMin")->getFloat(); + const float fallDistanceMin = store.find("fFallDamageDistanceMin")->mValue.getFloat(); if (fallHeight >= fallDistanceMin) { const float acrobaticsSkill = static_cast(ptr.getClass().getSkill(ptr, ESM::Skill::Acrobatics)); const float jumpSpellBonus = ptr.getClass().getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::Jump).getMagnitude(); - const float fallAcroBase = store.find("fFallAcroBase")->getFloat(); - const float fallAcroMult = store.find("fFallAcroMult")->getFloat(); - const float fallDistanceBase = store.find("fFallDistanceBase")->getFloat(); - const float fallDistanceMult = store.find("fFallDistanceMult")->getFloat(); + const float fallAcroBase = store.find("fFallAcroBase")->mValue.getFloat(); + const float fallAcroMult = store.find("fFallAcroMult")->mValue.getFloat(); + const float fallDistanceBase = store.find("fFallDistanceBase")->mValue.getFloat(); + const float fallDistanceMult = store.find("fFallDistanceMult")->mValue.getFloat(); float x = fallHeight - fallDistanceMin; x -= (1.5f * acrobaticsSkill) + jumpSpellBonus; @@ -411,10 +411,6 @@ void CharacterController::refreshMovementAnims(const WeaponInfo* weap, Character if(force || movement != mMovementState) { mMovementState = movement; - - if (movement != CharState_None) - mIdleState = CharState_None; - std::string movementAnimName; MWRender::Animation::BlendMask movemask = MWRender::Animation::BlendMask_All; const StateInfo *movestate = std::find_if(sMovementList, sMovementListEnd, FindCharState(mMovementState)); @@ -531,7 +527,7 @@ void CharacterController::refreshMovementAnims(const WeaponInfo* weap, Character void CharacterController::refreshIdleAnims(const WeaponInfo* weap, CharacterState idle, bool force) { - if(force || idle != mIdleState || (!mAnimation->isPlaying(mCurrentIdle) && mAnimQueue.empty())) + if(force || idle != mIdleState || mIdleState == CharState_None || (!mAnimation->isPlaying(mCurrentIdle) && mAnimQueue.empty())) { mIdleState = idle; size_t numLoops = ~0ul; @@ -562,14 +558,24 @@ void CharacterController::refreshIdleAnims(const WeaponInfo* weap, CharacterStat // play until the Loop Stop key 2 to 5 times, then play until the Stop key // this replicates original engine behavior for the "Idle1h" 1st-person animation numLoops = 1 + Misc::Rng::rollDice(4); - } + } } - mAnimation->disable(mCurrentIdle); + // There is no need to restart anim if the new and old anims are the same. + // Just update a number of loops. + float startPoint = 0; + if (!mCurrentIdle.empty() && mCurrentIdle == idleGroup) + { + mAnimation->getInfo(mCurrentIdle, &startPoint); + } + + if(!mCurrentIdle.empty()) + mAnimation->disable(mCurrentIdle); + mCurrentIdle = idleGroup; if(!mCurrentIdle.empty()) mAnimation->play(mCurrentIdle, idlePriority, MWRender::Animation::BlendMask_All, false, - 1.0f, "start", "stop", 0.0f, numLoops, true); + 1.0f, "start", "stop", startPoint, numLoops, true); } } @@ -583,7 +589,7 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat refreshHitRecoilAnims(); const WeaponInfo *weap = std::find_if(sWeaponTypeList, sWeaponTypeListEnd, FindWeaponType(mWeaponType)); - if (!mPtr.getClass().isBipedal(mPtr)) + if (!mPtr.getClass().hasInventoryStore(mPtr)) weap = sWeaponTypeListEnd; refreshJumpAnims(weap, jump, force); @@ -592,7 +598,7 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat // idle handled last as it can depend on the other states // FIXME: if one of the below states is close to their last animation frame (i.e. will be disabled in the coming update), // the idle animation should be displayed - if ((mUpperBodyState != UpperCharState_Nothing + if (((mUpperBodyState != UpperCharState_Nothing && mUpperBodyState != UpperCharState_WeapEquiped) || (mMovementState != CharState_None && !isTurning()) || mHitState != CharState_None) && !mPtr.getClass().isBipedal(mPtr)) @@ -773,6 +779,20 @@ void CharacterController::playRandomDeath(float startpoint) playDeath(startpoint, mDeathState); } +std::string CharacterController::chooseRandomAttackAnimation() const +{ + std::string result; + bool isSwimming = MWBase::Environment::get().getWorld()->isSwimming(mPtr); + + if (isSwimming) + result = chooseRandomGroup("swimattack"); + + if (!isSwimming || !mAnimation->hasAnimation(result)) + result = chooseRandomGroup("attack"); + + return result; +} + CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim) : mPtr(ptr) , mWeapon(MWWorld::Ptr()) @@ -918,11 +938,14 @@ void CharacterController::handleTextKey(const std::string &groupname, const std: } } + if (soundgen == "land") // Morrowind ignores land soundgen for some reason + return; + std::string sound = mPtr.getClass().getSoundIdFromSndGen(mPtr, soundgen); if(!sound.empty()) { MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - if(evt.compare(10, evt.size()-10, "left") == 0 || evt.compare(10, evt.size()-10, "right") == 0 || evt.compare(10, evt.size()-10, "land") == 0) + if(soundgen == "left" || soundgen == "right") { // Don't make foot sounds local for the player, it makes sense to keep them // positioned on the ground. @@ -1123,16 +1146,10 @@ bool CharacterController::updateCreatureState() else mCurrentWeapon = ""; } + if (weapType != WeapType_Spell || !mAnimation->hasAnimation("spellcast")) // Not all creatures have a dedicated spellcast animation { - bool isSwimming = MWBase::Environment::get().getWorld()->isSwimming(mPtr); - int roll = Misc::Rng::rollDice(3); // [0, 2] - if (roll == 0) - mCurrentWeapon = isSwimming && mAnimation->hasAnimation("swimattack1") ? "swimattack1" : "attack1"; - else if (roll == 1) - mCurrentWeapon = isSwimming && mAnimation->hasAnimation("swimattack2") ? "swimattack2" : "attack2"; - else - mCurrentWeapon = isSwimming && mAnimation->hasAnimation("swimattack3") ? "swimattack3" : "attack3"; + mCurrentWeapon = chooseRandomAttackAnimation(); } if (!mCurrentWeapon.empty()) @@ -1212,8 +1229,11 @@ bool CharacterController::updateWeaponState() mWeapon = weapon != inv.end() ? *weapon : MWWorld::Ptr(); } + // Use blending only with 3d-person movement animations for bipedal actors + bool firstPersonPlayer = (mPtr == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->isFirstPerson()); MWRender::Animation::AnimPriority priorityWeapon(Priority_Weapon); - priorityWeapon[MWRender::Animation::BoneGroup_LowerBody] = Priority_WeaponLowerBody; + if (!firstPersonPlayer && mPtr.getClass().isBipedal(mPtr)) + priorityWeapon[MWRender::Animation::BoneGroup_LowerBody] = Priority_WeaponLowerBody; bool forcestateupdate = false; @@ -1221,11 +1241,11 @@ bool CharacterController::updateWeaponState() bool isStillWeapon = weaptype > WeapType_HandToHand && weaptype < WeapType_Spell && mWeaponType > WeapType_HandToHand && mWeaponType < WeapType_Spell; - if(weaptype != mWeaponType && !isKnockedOut() && - !isKnockedDown() && !isRecovery()) + if(!isKnockedOut() && !isKnockedDown() && !isRecovery()) { std::string weapgroup; if ((!isWerewolf || mWeaponType != WeapType_Spell) + && weaptype != mWeaponType && mUpperBodyState != UpperCharState_UnEquipingWeap && !isStillWeapon) { @@ -1239,6 +1259,10 @@ bool CharacterController::updateWeaponState() MWRender::Animation::BlendMask_All, false, 1.0f, "unequip start", "unequip stop", 0.0f, 0); mUpperBodyState = UpperCharState_UnEquipingWeap; + + // If we do not have the "unequip detach" key, hide weapon manually. + if (mAnimation->getTextKeyTime(weapgroup+": unequip detach") < 0) + mAnimation->showWeapons(false); } if(!downSoundId.empty()) @@ -1252,47 +1276,64 @@ bool CharacterController::updateWeaponState() bool animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); if (!animPlaying || complete >= 1.0f) { - forcestateupdate = true; - mAnimation->showCarriedLeft(updateCarriedLeftVisible(weaptype)); - - getWeaponGroup(weaptype, weapgroup); - mAnimation->setWeaponGroup(weapgroup); - - if (!isStillWeapon) + // Weapon is changed, no current animation (e.g. unequipping or attack). + // Start equipping animation now. + if (weaptype != mWeaponType) { - if (weaptype == WeapType_None) + forcestateupdate = true; + mAnimation->showCarriedLeft(updateCarriedLeftVisible(weaptype)); + + getWeaponGroup(weaptype, weapgroup); + mAnimation->setWeaponGroup(weapgroup); + + if (!isStillWeapon) { - // Disable current weapon animation manually mAnimation->disable(mCurrentWeapon); - } - else - { - mAnimation->showWeapons(false); - mAnimation->play(weapgroup, priorityWeapon, - MWRender::Animation::BlendMask_All, true, - 1.0f, "equip start", "equip stop", 0.0f, 0); - mUpperBodyState = UpperCharState_EquipingWeap; - } - } + if (weaptype != WeapType_None) + { + mAnimation->showWeapons(false); + mAnimation->play(weapgroup, priorityWeapon, + MWRender::Animation::BlendMask_All, true, + 1.0f, "equip start", "equip stop", 0.0f, 0); + mUpperBodyState = UpperCharState_EquipingWeap; - if(isWerewolf) - { - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - const ESM::Sound *sound = store.get().searchRandom("WolfEquip"); - if(sound) + // If we do not have the "equip attach" key, show weapon manually. + if (weaptype != WeapType_Spell) + { + if (mAnimation->getTextKeyTime(weapgroup+": equip attach") < 0) + mAnimation->showWeapons(true); + } + } + } + + if(isWerewolf) + { + const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); + const ESM::Sound *sound = store.get().searchRandom("WolfEquip"); + if(sound) + { + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + sndMgr->playSound3D(mPtr, sound->mId, 1.0f, 1.0f); + } + } + + mWeaponType = weaptype; + getWeaponGroup(mWeaponType, mCurrentWeapon); + + if(!upSoundId.empty() && !isStillWeapon) { MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - sndMgr->playSound3D(mPtr, sound->mId, 1.0f, 1.0f); + sndMgr->playSound3D(mPtr, upSoundId, 1.0f, 1.0f); } } - mWeaponType = weaptype; - getWeaponGroup(mWeaponType, mCurrentWeapon); - - if(!upSoundId.empty() && !isStillWeapon) + // Make sure that we disabled unequipping animation + if (mUpperBodyState == UpperCharState_UnEquipingWeap) { - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - sndMgr->playSound3D(mPtr, upSoundId, 1.0f, 1.0f); + mUpperBodyState = UpperCharState_Nothing; + mAnimation->disable(mCurrentWeapon); + mWeaponType = WeapType_None; + getWeaponGroup(mWeaponType, mCurrentWeapon); } } } @@ -1347,18 +1388,12 @@ bool CharacterController::updateWeaponState() { MWWorld::Ptr player = getPlayer(); - // We should reset player's idle animation in the first-person mode. - if (mPtr == player && MWBase::Environment::get().getWorld()->isFirstPerson()) - mIdleState = CharState_None; - - // In other cases we should not break swim and sneak animations - if (mIdleState != CharState_IdleSneak && mIdleState != CharState_IdleSwim) - mIdleState = CharState_None; - + bool resetIdle = ammunition; if(mUpperBodyState == UpperCharState_WeapEquiped && (mHitState == CharState_None || mHitState == CharState_Block)) { MWBase::Environment::get().getWorld()->breakInvisibility(mPtr); mAttackStrength = 0; + if(mWeaponType == WeapType_Spell) { // Unset casting flag, otherwise pressing the mouse button down would @@ -1367,15 +1402,10 @@ bool CharacterController::updateWeaponState() if (mPtr == player) { MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false); - } - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - - // For the player, set the spell we want to cast - // This has to be done at the start of the casting animation, - // *not* when selecting a spell in the GUI (otherwise you could change the spell mid-animation) - if (mPtr == player) - { + // For the player, set the spell we want to cast + // This has to be done at the start of the casting animation, + // *not* when selecting a spell in the GUI (otherwise you could change the spell mid-animation) std::string selectedSpell = MWBase::Environment::get().getWindowManager()->getSelectedSpell(); stats.getSpells().setSelectedSpell(selectedSpell); } @@ -1387,6 +1417,7 @@ bool CharacterController::updateWeaponState() MWMechanics::CastSpell cast(mPtr, NULL, false, mCastingManualSpell); cast.playSpellCastingEffects(spellid); + const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Spell *spell = store.get().find(spellid); const ESM::ENAMstruct &lastEffect = spell->mEffects.mList.back(); const ESM::MagicEffect *effect; @@ -1406,19 +1437,39 @@ bool CharacterController::updateWeaponState() const ESM::ENAMstruct &firstEffect = spell->mEffects.mList.at(0); // first effect used for casting animation - switch(firstEffect.mRange) + std::string startKey; + std::string stopKey; + if (isRandomAttackAnimation(mCurrentWeapon)) { - case 0: mAttackType = "self"; break; - case 1: mAttackType = "touch"; break; - case 2: mAttackType = "target"; break; + startKey = "start"; + stopKey = "stop"; + MWBase::Environment::get().getWorld()->castSpell(mPtr, mCastingManualSpell); // No "release" text key to use, so cast immediately + mCastingManualSpell = false; + } + else + { + switch(firstEffect.mRange) + { + case 0: mAttackType = "self"; break; + case 1: mAttackType = "touch"; break; + case 2: mAttackType = "target"; break; + } + + startKey = mAttackType+" start"; + stopKey = mAttackType+" stop"; } mAnimation->play(mCurrentWeapon, priorityWeapon, MWRender::Animation::BlendMask_All, true, - weapSpeed, mAttackType+" start", mAttackType+" stop", + 1, startKey, stopKey, 0.0f, 0); mUpperBodyState = UpperCharState_CastingSpell; } + else + { + resetIdle = false; + } + if (mPtr.getClass().hasInventoryStore(mPtr)) { MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); @@ -1458,16 +1509,26 @@ bool CharacterController::updateWeaponState() } else if (ammunition) { - if(mWeaponType == WeapType_Crossbow || mWeaponType == WeapType_BowAndArrow || - mWeaponType == WeapType_Thrown) + std::string startKey; + std::string stopKey; + if(mWeaponType == WeapType_Crossbow || mWeaponType == WeapType_BowAndArrow || mWeaponType == WeapType_Thrown) + { mAttackType = "shoot"; + startKey = mAttackType+" start"; + stopKey = mAttackType+" min attack"; + } + else if (isRandomAttackAnimation(mCurrentWeapon)) + { + startKey = "start"; + stopKey = "stop"; + } else { if(mPtr == getPlayer()) { if (isWeapon) { - if (Settings::Manager::getBool("best attack", "Game")) + if (Settings::Manager::getBool("best attack", "Game")) { MWWorld::ConstContainerStoreIterator weapon = mPtr.getClass().getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight); mAttackType = getBestAttack(weapon->get()->mBase); @@ -1476,19 +1537,32 @@ bool CharacterController::updateWeaponState() setAttackTypeBasedOnMovement(); } else - setAttackTypeRandomly(mAttackType); + { + // There is no "best attack" for Hand-to-Hand + setAttackTypeRandomly(mAttackType); + } } // else if (mPtr != getPlayer()) use mAttackType set by AiCombat + startKey = mAttackType+" start"; + stopKey = mAttackType+" min attack"; } mAnimation->play(mCurrentWeapon, priorityWeapon, MWRender::Animation::BlendMask_All, false, - weapSpeed, mAttackType+" start", mAttackType+" min attack", + weapSpeed, startKey, stopKey, 0.0f, 0); mUpperBodyState = UpperCharState_StartToMinAttack; } } + // We should reset player's idle animation in the first-person mode. + if (resetIdle && mPtr == player && MWBase::Environment::get().getWorld()->isFirstPerson()) + mIdleState = CharState_None; + + // In other cases we should not break swim and sneak animations + if (resetIdle && mIdleState != CharState_IdleSneak && mIdleState != CharState_IdleSwim) + mIdleState = CharState_None; + animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack && !isKnockedDown()) mAttackStrength = complete; @@ -1600,16 +1674,11 @@ bool CharacterController::updateWeaponState() else if(mUpperBodyState == UpperCharState_UnEquipingWeap) mUpperBodyState = UpperCharState_Nothing; } - else if(complete >= 1.0f) + else if(complete >= 1.0f && !isRandomAttackAnimation(mCurrentWeapon)) { std::string start, stop; switch(mUpperBodyState) { - case UpperCharState_StartToMinAttack: - start = mAttackType+" min attack"; - stop = mAttackType+" max attack"; - mUpperBodyState = UpperCharState_MinAttackToMaxAttack; - break; case UpperCharState_MinAttackToMaxAttack: //hack to avoid body pos desync when jumping/sneaking in 'max attack' state if(!mAnimation->isPlaying(mCurrentWeapon)) @@ -1617,6 +1686,23 @@ bool CharacterController::updateWeaponState() MWRender::Animation::BlendMask_All, false, 0, mAttackType+" min attack", mAttackType+" max attack", 0.999f, 0); break; + case UpperCharState_StartToMinAttack: + { + // If actor is already stopped preparing attack, do not play the "min attack -> max attack" part. + // Happens if the player did not hold the attack button. + // Note: if the "min attack"->"max attack" is a stub, "play" it anyway. Attack strength will be 1. + float minAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon+": "+mAttackType+" "+"min attack"); + float maxAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon+": "+mAttackType+" "+"max attack"); + if (mAttackingOrSpell || minAttackTime == maxAttackTime) + { + start = mAttackType+" min attack"; + stop = mAttackType+" max attack"; + mUpperBodyState = UpperCharState_MinAttackToMaxAttack; + break; + } + playSwishSound(0.0f); + } + // Fall-through case UpperCharState_MaxAttackToMinHit: if(mAttackType == "shoot") { @@ -1652,21 +1738,30 @@ bool CharacterController::updateWeaponState() break; } - // Note: apply reload animations only for upper body since blending with movement animations can give weird result. - // Especially noticable with crossbow reload animation. + // Note: apply crossbow reload animation only for upper body + // since blending with movement animations can give weird result. if(!start.empty()) { + int mask = MWRender::Animation::BlendMask_All; + if (mWeaponType == WeapType_Crossbow) + mask = MWRender::Animation::BlendMask_UpperBody; + mAnimation->disable(mCurrentWeapon); if (mUpperBodyState == UpperCharState_FollowStartToFollowStop) mAnimation->play(mCurrentWeapon, priorityWeapon, - MWRender::Animation::BlendMask_UpperBody, true, + mask, true, weapSpeed, start, stop, 0.0f, 0); else mAnimation->play(mCurrentWeapon, priorityWeapon, - MWRender::Animation::BlendMask_UpperBody, false, + mask, false, weapSpeed, start, stop, 0.0f, 0); } } + else if(complete >= 1.0f && isRandomAttackAnimation(mCurrentWeapon)) + { + mAnimation->disable(mCurrentWeapon); + mUpperBodyState = UpperCharState_WeapEquiped; + } if (mPtr.getClass().hasInventoryStore(mPtr)) { @@ -1729,10 +1824,11 @@ void CharacterController::update(float duration) else if(!cls.getCreatureStats(mPtr).isDead()) { bool onground = world->isOnGround(mPtr); + bool incapacitated = (cls.getCreatureStats(mPtr).isParalyzed() || cls.getCreatureStats(mPtr).getKnockedDown()); bool inwater = world->isSwimming(mPtr); - bool sneak = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Sneak); bool flying = world->isFlying(mPtr); - // Can't run while flying (see speed formula in Npc/Creature::getSpeed) + // Can't run and sneak while flying (see speed formula in Npc/Creature::getSpeed) + bool sneak = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Sneak) && !flying; bool isrunning = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run) && !flying; CreatureStats &stats = cls.getCreatureStats(mPtr); @@ -1811,14 +1907,14 @@ void CharacterController::update(float duration) // reduce fatigue const MWWorld::Store &gmst = world->getStore().get(); float fatigueLoss = 0; - static const float fFatigueRunBase = gmst.find("fFatigueRunBase")->getFloat(); - static const float fFatigueRunMult = gmst.find("fFatigueRunMult")->getFloat(); - static const float fFatigueSwimWalkBase = gmst.find("fFatigueSwimWalkBase")->getFloat(); - static const float fFatigueSwimRunBase = gmst.find("fFatigueSwimRunBase")->getFloat(); - static const float fFatigueSwimWalkMult = gmst.find("fFatigueSwimWalkMult")->getFloat(); - static const float fFatigueSwimRunMult = gmst.find("fFatigueSwimRunMult")->getFloat(); - static const float fFatigueSneakBase = gmst.find("fFatigueSneakBase")->getFloat(); - static const float fFatigueSneakMult = gmst.find("fFatigueSneakMult")->getFloat(); + static const float fFatigueRunBase = gmst.find("fFatigueRunBase")->mValue.getFloat(); + static const float fFatigueRunMult = gmst.find("fFatigueRunMult")->mValue.getFloat(); + static const float fFatigueSwimWalkBase = gmst.find("fFatigueSwimWalkBase")->mValue.getFloat(); + static const float fFatigueSwimRunBase = gmst.find("fFatigueSwimRunBase")->mValue.getFloat(); + static const float fFatigueSwimWalkMult = gmst.find("fFatigueSwimWalkMult")->mValue.getFloat(); + static const float fFatigueSwimRunMult = gmst.find("fFatigueSwimRunMult")->mValue.getFloat(); + static const float fFatigueSneakBase = gmst.find("fFatigueSneakBase")->mValue.getFloat(); + static const float fFatigueSneakMult = gmst.find("fFatigueSneakMult")->mValue.getFloat(); if (cls.getEncumbrance(mPtr) <= cls.getCapacity(mPtr)) { @@ -1847,7 +1943,7 @@ void CharacterController::update(float duration) cls.getCreatureStats(mPtr).setFatigue(fatigue); } - if(sneak || inwater || flying) + if(sneak || inwater || flying || incapacitated) vec.z() = 0.0f; bool inJump = true; @@ -1858,8 +1954,8 @@ void CharacterController::update(float duration) forcestateupdate = (mJumpState != JumpState_InAir); jumpstate = JumpState_InAir; - static const float fJumpMoveBase = gmst.find("fJumpMoveBase")->getFloat(); - static const float fJumpMoveMult = gmst.find("fJumpMoveMult")->getFloat(); + static const float fJumpMoveBase = gmst.find("fJumpMoveBase")->mValue.getFloat(); + static const float fJumpMoveMult = gmst.find("fJumpMoveMult")->mValue.getFloat(); float factor = fJumpMoveBase + fJumpMoveMult * mPtr.getClass().getSkill(mPtr, ESM::Skill::Acrobatics)/100.f; factor = std::min(1.f, factor); vec.x() *= factor; @@ -1886,8 +1982,8 @@ void CharacterController::update(float duration) cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 0); // decrease fatigue - const float fatigueJumpBase = gmst.find("fFatigueJumpBase")->getFloat(); - const float fatigueJumpMult = gmst.find("fFatigueJumpMult")->getFloat(); + const float fatigueJumpBase = gmst.find("fFatigueJumpBase")->mValue.getFloat(); + const float fatigueJumpMult = gmst.find("fFatigueJumpMult")->mValue.getFloat(); float normalizedEncumbrance = mPtr.getClass().getNormalizedEncumbrance(mPtr); if (normalizedEncumbrance > 1) normalizedEncumbrance = 1; @@ -1916,17 +2012,15 @@ void CharacterController::update(float duration) // inflict fall damages if (!godmode) { - DynamicStat health = cls.getCreatureStats(mPtr).getHealth(); float realHealthLost = static_cast(healthLost * (1.0f - 0.25f * fatigueTerm)); - health.setCurrent(health.getCurrent() - realHealthLost); - cls.getCreatureStats(mPtr).setHealth(health); cls.onHit(mPtr, realHealthLost, true, MWWorld::Ptr(), MWWorld::Ptr(), osg::Vec3f(), true); } const int acrobaticsSkill = cls.getSkill(mPtr, ESM::Skill::Acrobatics); if (healthLost > (acrobaticsSkill * fatigueTerm)) { - cls.getCreatureStats(mPtr).setKnockedDown(true); + if (!godmode) + cls.getCreatureStats(mPtr).setKnockedDown(true); } else { @@ -1935,6 +2029,12 @@ void CharacterController::update(float duration) cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 1); } } + + // Play landing sound + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + std::string sound = cls.getSoundIdFromSndGen(mPtr, "land"); + if (!sound.empty()) + sndMgr->playSound3D(mPtr, sound, 1.f, 1.f, MWSound::Type::Foot, MWSound::PlayMode::NoPlayerLocal); } else { @@ -1944,6 +2044,12 @@ void CharacterController::update(float duration) inJump = false; + // Do not play turning animation for player if rotation speed is very slow. + // Actual threshold should take framerate in account. + float rotationThreshold = 0; + if (mPtr == getPlayer()) + rotationThreshold = 0.015 * 60 * duration; + if(std::abs(vec.x()/2.0f) > std::abs(vec.y())) { if(vec.x() > 0.0f) @@ -1968,26 +2074,38 @@ void CharacterController::update(float duration) } else if(rot.z() != 0.0f && !sneak && !(mPtr == getPlayer() && MWBase::Environment::get().getWorld()->isFirstPerson())) { - if(rot.z() > 0.0f) - { + if(rot.z() > rotationThreshold) movestate = inwater ? CharState_SwimTurnRight : CharState_TurnRight; - mAnimation->disable(mCurrentJump); - } - else if(rot.z() < 0.0f) - { + else if(rot.z() < -rotationThreshold) movestate = inwater ? CharState_SwimTurnLeft : CharState_TurnLeft; - mAnimation->disable(mCurrentJump); - } } } - mTurnAnimationThreshold -= duration; - if (isTurning()) - mTurnAnimationThreshold = 0.05f; - else if (movestate == CharState_None && isTurning() - && mTurnAnimationThreshold > 0) + // Player can not use smooth turning as NPCs, so we play turning animation a bit to avoid jittering + if (mPtr == getPlayer()) { - movestate = mMovementState; + float threshold = mCurrentMovement.find("swim") == std::string::npos ? 0.4f : 0.8f; + float complete; + bool animPlaying = mAnimation->getInfo(mCurrentMovement, &complete); + if (movestate == CharState_None && isTurning()) + { + if (animPlaying && complete < threshold) + movestate = mMovementState; + } + } + else + { + mTurnAnimationThreshold -= duration; + if (movestate == CharState_TurnRight || movestate == CharState_TurnLeft || + movestate == CharState_SwimTurnRight || movestate == CharState_SwimTurnLeft) + { + mTurnAnimationThreshold = 0.05f; + } + else if (movestate == CharState_None && isTurning() + && mTurnAnimationThreshold > 0) + { + movestate = mMovementState; + } } if(movestate != CharState_None && !isTurning()) @@ -1995,7 +2113,16 @@ void CharacterController::update(float duration) if(mAnimQueue.empty() || inwater || sneak) { - idlestate = (inwater ? CharState_IdleSwim : (sneak && !inJump ? CharState_IdleSneak : CharState_Idle)); + // Note: turning animations should not interrupt idle ones. + // Also movement should not stop idle animation for spellcasting stance. + if (inwater) + idlestate = CharState_IdleSwim; + else if (sneak && !inJump) + idlestate = CharState_IdleSneak; + else if (movestate != CharState_None && !isTurning() && mWeaponType != WeapType_Spell) + idlestate = CharState_None; + else + idlestate = CharState_Idle; } else updateAnimQueue(); @@ -2017,8 +2144,10 @@ void CharacterController::update(float duration) if (isTurning()) { + // Adjust animation speed from 1.0 to 1.5 multiplier + float turnSpeed = std::min(1.5f, std::abs(rot.z()) / duration / static_cast(osg::PI)); if (duration > 0) - mAnimation->adjustSpeedMult(mCurrentMovement, std::min(1.5f, std::abs(rot.z()) / duration / static_cast(osg::PI))); + mAnimation->adjustSpeedMult(mCurrentMovement, std::max(turnSpeed, 1.0f)); } else if (mMovementState != CharState_None && mAdjustMovementAnimSpeed) { @@ -2095,9 +2224,6 @@ void CharacterController::update(float duration) moved *= (l / newLength); } - if (mSkipAnim) - mAnimation->updateEffects(duration); - if (mFloatToSurface && cls.isActor() && cls.getCreatureStats(mPtr).isDead() && cls.canSwim(mPtr)) moved.z() = 1.0; @@ -2385,7 +2511,14 @@ void CharacterController::setAttackTypeBasedOnMovement() mAttackType = "chop"; } -bool CharacterController::isAttackPrepairing() const +bool CharacterController::isRandomAttackAnimation(const std::string& group) const +{ + return (group == "attack1" || group == "swimattack1" || + group == "attack2" || group == "swimattack2" || + group == "attack3" || group == "swimattack3"); +} + +bool CharacterController::isAttackPreparing() const { return mUpperBodyState == UpperCharState_StartToMinAttack || mUpperBodyState == UpperCharState_MinAttackToMaxAttack; @@ -2486,7 +2619,7 @@ void CharacterController::setAttackTypeRandomly(std::string& attackType) bool CharacterController::readyToPrepareAttack() const { return (mHitState == CharState_None || mHitState == CharState_Block) - && mUpperBodyState <= UpperCharState_WeapEquiped; + && mUpperBodyState <= UpperCharState_WeapEquiped; } bool CharacterController::readyToStartAttack() const diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 84630a4791..43d26e52f3 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -31,8 +31,10 @@ enum Priority { Priority_WeaponLowerBody, Priority_SneakIdleLowerBody, Priority_SwimIdle, - Priority_Jump, Priority_Movement, + // Note: in vanilla movement anims have higher priority than jump ones. + // It causes issues with landing animations during movement. + Priority_Jump, Priority_Hit, Priority_Weapon, Priority_Block, @@ -222,6 +224,9 @@ class CharacterController : public MWRender::Animation::TextKeyListener bool updateCreatureState(); void updateIdleStormState(bool inwater); + std::string chooseRandomAttackAnimation() const; + bool isRandomAttackAnimation(const std::string& group) const; + bool isPersistentAnimPlaying(); void updateAnimQueue(); @@ -276,7 +281,7 @@ public: void forceStateUpdate(); - bool isAttackPrepairing() const; + bool isAttackPreparing() const; bool isCastingSpell() const; bool isReadyToBlock() const; bool isKnockedDown() const; diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 6b45a513be..be55b681f1 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -82,9 +82,9 @@ namespace MWMechanics osg::Vec3f(0,0,1))); const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); - if (angleDegrees < gmst.find("fCombatBlockLeftAngle")->getFloat()) + if (angleDegrees < gmst.find("fCombatBlockLeftAngle")->mValue.getFloat()) return false; - if (angleDegrees > gmst.find("fCombatBlockRightAngle")->getFloat()) + if (angleDegrees > gmst.find("fCombatBlockRightAngle")->mValue.getFloat()) return false; MWMechanics::CreatureStats& attackerStats = attacker.getClass().getCreatureStats(attacker); @@ -92,11 +92,11 @@ namespace MWMechanics float blockTerm = blocker.getClass().getSkill(blocker, ESM::Skill::Block) + 0.2f * blockerStats.getAttribute(ESM::Attribute::Agility).getModified() + 0.1f * blockerStats.getAttribute(ESM::Attribute::Luck).getModified(); float enemySwing = attackStrength; - float swingTerm = enemySwing * gmst.find("fSwingBlockMult")->getFloat() + gmst.find("fSwingBlockBase")->getFloat(); + float swingTerm = enemySwing * gmst.find("fSwingBlockMult")->mValue.getFloat() + gmst.find("fSwingBlockBase")->mValue.getFloat(); float blockerTerm = blockTerm * swingTerm; if (blocker.getClass().getMovementSettings(blocker).mPosition[1] <= 0) - blockerTerm *= gmst.find("fBlockStillBonus")->getFloat(); + blockerTerm *= gmst.find("fBlockStillBonus")->mValue.getFloat(); blockerTerm *= blockerStats.getFatigueTerm(); int attackerSkill = 0; @@ -109,8 +109,8 @@ namespace MWMechanics attackerTerm *= attackerStats.getFatigueTerm(); int x = int(blockerTerm - attackerTerm); - int iBlockMaxChance = gmst.find("iBlockMaxChance")->getInt(); - int iBlockMinChance = gmst.find("iBlockMinChance")->getInt(); + int iBlockMaxChance = gmst.find("iBlockMaxChance")->mValue.getInteger(); + int iBlockMinChance = gmst.find("iBlockMinChance")->mValue.getInteger(); x = std::min(iBlockMaxChance, std::max(iBlockMinChance, x)); if (Misc::Rng::roll0to99() < x) @@ -126,9 +126,9 @@ namespace MWMechanics inv.unequipItem(*shield, blocker); } // Reduce blocker fatigue - const float fFatigueBlockBase = gmst.find("fFatigueBlockBase")->getFloat(); - const float fFatigueBlockMult = gmst.find("fFatigueBlockMult")->getFloat(); - const float fWeaponFatigueBlockMult = gmst.find("fWeaponFatigueBlockMult")->getFloat(); + const float fFatigueBlockBase = gmst.find("fFatigueBlockBase")->mValue.getFloat(); + const float fFatigueBlockMult = gmst.find("fFatigueBlockMult")->mValue.getFloat(); + const float fWeaponFatigueBlockMult = gmst.find("fWeaponFatigueBlockMult")->mValue.getFloat(); MWMechanics::DynamicStat fatigue = blockerStats.getFatigue(); float normalizedEncumbrance = blocker.getClass().getNormalizedEncumbrance(blocker); normalizedEncumbrance = std::min(1.f, normalizedEncumbrance); @@ -166,7 +166,7 @@ namespace MWMechanics 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(); + damage *= MWBase::Environment::get().getWorld()->getStore().get().find("fWereWolfSilverWeaponDamageMult")->mValue.getFloat(); if (damage == 0 && attacker == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResistsWeapons}"); @@ -209,11 +209,24 @@ namespace MWMechanics adjustWeaponDamage(damage, weapon, attacker); - if(attacker == getPlayer()) + if (attacker == getPlayer()) + { attacker.getClass().skillUsageSucceeded(attacker, weaponSkill, 0); + const MWMechanics::AiSequence& sequence = victim.getClass().getCreatureStats(victim).getAiSequence(); + + bool unaware = !sequence.isInCombat() + && !MWBase::Environment::get().getMechanicsManager()->awarenessCheck(attacker, victim); + + if (unaware) + { + damage *= gmst.find("fCombatCriticalStrikeMult")->mValue.getFloat(); + MWBase::Environment::get().getWindowManager()->messageBox("#{sTargetCriticalStrike}"); + MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f); + } + } if (victim.getClass().getCreatureStats(victim).getKnockedDown()) - damage *= gmst.find("fCombatKODamageMult")->getFloat(); + damage *= gmst.find("fCombatKODamageMult")->mValue.getFloat(); } reduceWeaponCondition(damage, validVictim, weapon, attacker); @@ -228,7 +241,7 @@ namespace MWMechanics // Non-enchanted arrows shot at enemies have a chance to turn up in their inventory if (victim != getPlayer() && !appliedEnchantment) { - float fProjectileThrownStoreChance = gmst.find("fProjectileThrownStoreChance")->getFloat(); + float fProjectileThrownStoreChance = gmst.find("fProjectileThrownStoreChance")->mValue.getFloat(); if (Misc::Rng::rollProbability() < fProjectileThrownStoreChance / 100.f) victim.getClass().getContainerStore(victim).add(projectile, 1, victim); } @@ -260,10 +273,10 @@ namespace MWMechanics defenseTerm = victimStats.getEvasion(); } defenseTerm += std::min(100.f, - gmst.find("fCombatInvisoMult")->getFloat() * + gmst.find("fCombatInvisoMult")->mValue.getFloat() * victimStats.getMagicEffects().get(ESM::MagicEffect::Chameleon).getMagnitude()); defenseTerm += std::min(100.f, - gmst.find("fCombatInvisoMult")->getFloat() * + gmst.find("fCombatInvisoMult")->mValue.getFloat() * victimStats.getMagicEffects().get(ESM::MagicEffect::Invisibility).getMagnitude()); } float attackTerm = skillValue + @@ -309,7 +322,7 @@ namespace MWMechanics x = std::min(100.f, x + elementResistance); - static const float fElementalShieldMult = MWBase::Environment::get().getWorld()->getStore().get().find("fElementalShieldMult")->getFloat(); + static const float fElementalShieldMult = MWBase::Environment::get().getWorld()->getStore().get().find("fElementalShieldMult")->mValue.getFloat(); x = fElementalShieldMult * magnitude * (1.f - 0.01f * x); // Note swapped victim and attacker, since the attacker takes the damage here. @@ -339,7 +352,7 @@ namespace MWMechanics // weapon condition does not degrade when godmode is on if (!godmode) { - const float fWeaponDamageMult = MWBase::Environment::get().getWorld()->getStore().get().find("fWeaponDamageMult")->getFloat(); + const float fWeaponDamageMult = MWBase::Environment::get().getWorld()->getStore().get().find("fWeaponDamageMult")->mValue.getFloat(); float x = std::max(1.f, fWeaponDamageMult * damage); weaphealth -= std::min(int(x), weaphealth); @@ -366,21 +379,18 @@ namespace MWMechanics } static const float fDamageStrengthBase = MWBase::Environment::get().getWorld()->getStore().get() - .find("fDamageStrengthBase")->getFloat(); + .find("fDamageStrengthBase")->mValue.getFloat(); static const float fDamageStrengthMult = MWBase::Environment::get().getWorld()->getStore().get() - .find("fDamageStrengthMult")->getFloat(); + .find("fDamageStrengthMult")->mValue.getFloat(); damage *= fDamageStrengthBase + (attacker.getClass().getCreatureStats(attacker).getAttribute(ESM::Attribute::Strength).getModified() * fDamageStrengthMult * 0.1f); } void getHandToHandDamage(const MWWorld::Ptr &attacker, const MWWorld::Ptr &victim, float &damage, bool &healthdmg, float attackStrength) { - // Note: MCP contains an option to include Strength in hand-to-hand damage - // calculations. Some mods recommend using it, so we may want to include an - // option for it. const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); - float minstrike = store.get().find("fMinHandToHandMult")->getFloat(); - float maxstrike = store.get().find("fMaxHandToHandMult")->getFloat(); + float minstrike = store.get().find("fMinHandToHandMult")->mValue.getFloat(); + float maxstrike = store.get().find("fMaxHandToHandMult")->mValue.getFloat(); damage = static_cast(attacker.getClass().getSkill(attacker, ESM::Skill::HandToHand)); damage *= minstrike + ((maxstrike-minstrike)*attackStrength); @@ -388,6 +398,16 @@ namespace MWMechanics healthdmg = otherstats.isParalyzed() || otherstats.getKnockedDown(); bool isWerewolf = (attacker.getClass().isNpc() && attacker.getClass().getNpcStats(attacker).isWerewolf()); + + // Options in the launcher's combo box: unarmedFactorsStrengthComboBox + // 0 = Do not factor strength into hand-to-hand combat. + // 1 = Factor into werewolf hand-to-hand combat. + // 2 = Ignore werewolves. + int factorStrength = Settings::Manager::getInt("strength influences hand to hand", "Game"); + if (factorStrength == 1 || (factorStrength == 2 && !isWerewolf)) { + damage *= attacker.getClass().getCreatureStats(attacker).getAttribute(ESM::Attribute::Strength).getModified() / 40.0f; + } + if(isWerewolf) { healthdmg = true; @@ -395,7 +415,7 @@ namespace MWMechanics damage *= MWBase::Environment::get().getWorld()->getGlobalFloat("werewolfclawmult"); } if(healthdmg) - damage *= store.get().find("fHandtoHandHealthPer")->getFloat(); + damage *= store.get().find("fHandtoHandHealthPer")->mValue.getFloat(); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); if(isWerewolf) @@ -412,9 +432,9 @@ namespace MWMechanics { // somewhat of a guess, but using the weapon weight makes sense const MWWorld::Store& store = MWBase::Environment::get().getWorld()->getStore().get(); - const float fFatigueAttackBase = store.find("fFatigueAttackBase")->getFloat(); - const float fFatigueAttackMult = store.find("fFatigueAttackMult")->getFloat(); - const float fWeaponFatigueMult = store.find("fWeaponFatigueMult")->getFloat(); + const float fFatigueAttackBase = store.find("fFatigueAttackBase")->mValue.getFloat(); + const float fFatigueAttackMult = store.find("fFatigueAttackMult")->mValue.getFloat(); + const float fWeaponFatigueMult = store.find("fWeaponFatigueMult")->mValue.getFloat(); CreatureStats& stats = attacker.getClass().getCreatureStats(attacker); MWMechanics::DynamicStat fatigue = stats.getFatigue(); const float normalizedEncumbrance = attacker.getClass().getNormalizedEncumbrance(attacker); @@ -439,9 +459,9 @@ namespace MWMechanics float d = (pos1 - pos2).length(); static const int iFightDistanceBase = MWBase::Environment::get().getWorld()->getStore().get().find( - "iFightDistanceBase")->getInt(); + "iFightDistanceBase")->mValue.getInteger(); static const float fFightDistanceMultiplier = MWBase::Environment::get().getWorld()->getStore().get().find( - "fFightDistanceMultiplier")->getFloat(); + "fFightDistanceMultiplier")->mValue.getFloat(); return (iFightDistanceBase - fFightDistanceMultiplier * d); } diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 49823d7a5d..2994eac289 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -48,8 +48,8 @@ namespace MWMechanics const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); - static const float fFatigueBase = gmst.find("fFatigueBase")->getFloat(); - static const float fFatigueMult = gmst.find("fFatigueMult")->getFloat(); + static const float fFatigueBase = gmst.find("fFatigueBase")->mValue.getFloat(); + static const float fFatigueMult = gmst.find("fFatigueMult")->mValue.getFloat(); return fFatigueBase - fFatigueMult * (1-normalised); } @@ -195,6 +195,7 @@ namespace MWMechanics mDead = true; mDynamic[index].setModifier(0); + mDynamic[index].setCurrentModifier(0); mDynamic[index].setCurrent(0); if (MWBase::Environment::get().getWorld()->getGodModeState()) diff --git a/apps/openmw/mwmechanics/difficultyscaling.cpp b/apps/openmw/mwmechanics/difficultyscaling.cpp index 693587eda3..2376989745 100644 --- a/apps/openmw/mwmechanics/difficultyscaling.cpp +++ b/apps/openmw/mwmechanics/difficultyscaling.cpp @@ -12,10 +12,12 @@ float scaleDamage(float damage, const MWWorld::Ptr& attacker, const MWWorld::Ptr { const MWWorld::Ptr& player = MWMechanics::getPlayer(); - // [-100, 100] + // [-500, 500] int difficultySetting = Settings::Manager::getInt("difficulty", "Game"); + difficultySetting = std::min(difficultySetting, 500); + difficultySetting = std::max(difficultySetting, -500); - static const float fDifficultyMult = MWBase::Environment::get().getWorld()->getStore().get().find("fDifficultyMult")->getFloat(); + static const float fDifficultyMult = MWBase::Environment::get().getWorld()->getStore().get().find("fDifficultyMult")->mValue.getFloat(); float difficultyTerm = 0.01f * difficultySetting; diff --git a/apps/openmw/mwmechanics/disease.hpp b/apps/openmw/mwmechanics/disease.hpp index 5746af2a2f..fec3bdd376 100644 --- a/apps/openmw/mwmechanics/disease.hpp +++ b/apps/openmw/mwmechanics/disease.hpp @@ -28,7 +28,7 @@ namespace MWMechanics float fDiseaseXferChance = MWBase::Environment::get().getWorld()->getStore().get().find( - "fDiseaseXferChance")->getFloat(); + "fDiseaseXferChance")->mValue.getFloat(); MagicEffects& actorEffects = actor.getClass().getCreatureStats(actor).getMagicEffects(); @@ -59,7 +59,7 @@ namespace MWMechanics actor.getClass().getCreatureStats(actor).getSpells().add(it->first); std::string msg = "sMagicContractDisease"; - msg = MWBase::Environment::get().getWorld()->getStore().get().find(msg)->getString(); + msg = MWBase::Environment::get().getWorld()->getStore().get().find(msg)->mValue.getString(); if (msg.find("%s") != std::string::npos) msg.replace(msg.find("%s"), 2, spell->mName); MWBase::Environment::get().getWindowManager()->messageBox(msg); diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index 08f30aba1b..c0bffc4cb5 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -107,7 +107,7 @@ namespace MWMechanics } const bool powerfulSoul = getGemCharge() >= \ - MWBase::Environment::get().getWorld()->getStore().get().find ("iSoulAmountForConstantEffect")->getInt(); + MWBase::Environment::get().getWorld()->getStore().get().find ("iSoulAmountForConstantEffect")->mValue.getInteger(); if ((mObjectType == typeid(ESM::Armor).name()) || (mObjectType == typeid(ESM::Clothing).name())) { // Armor or Clothing switch(mCastStyle) @@ -184,7 +184,7 @@ namespace MWMechanics float magnitudeCost = (magMin + magMax) * baseCost * 0.05f; if (mCastStyle == ESM::Enchantment::ConstantEffect) { - magnitudeCost *= store.get().find("fEnchantmentConstantDurationMult")->getFloat(); + magnitudeCost *= store.get().find("fEnchantmentConstantDurationMult")->mValue.getFloat(); } else { @@ -193,7 +193,7 @@ namespace MWMechanics float areaCost = area * 0.05f * baseCost; - const float fEffectCostMult = store.get().find("fEffectCostMult")->getFloat(); + const float fEffectCostMult = store.get().find("fEffectCostMult")->mValue.getFloat(); cost += (magnitudeCost + areaCost) * fEffectCostMult; @@ -230,7 +230,7 @@ namespace MWMechanics if(mEnchanter.isEmpty()) return 0; - float priceMultipler = MWBase::Environment::get().getWorld()->getStore().get().find ("fEnchantmentValueMult")->getFloat(); + float priceMultipler = MWBase::Environment::get().getWorld()->getStore().get().find ("fEnchantmentValueMult")->mValue.getFloat(); int price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mEnchanter, static_cast(getEnchantPoints() * priceMultipler), true); return price; } @@ -256,7 +256,7 @@ namespace MWMechanics const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - return static_cast(mOldItemPtr.getClass().getEnchantmentPoints(mOldItemPtr) * store.get().find("fEnchantmentMult")->getFloat()); + return static_cast(mOldItemPtr.getClass().getEnchantmentPoints(mOldItemPtr) * store.get().find("fEnchantmentMult")->mValue.getFloat()); } bool Enchanting::soulEmpty() const { @@ -288,8 +288,8 @@ namespace MWMechanics const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); - float chance2 = 7.5f / (gmst.find("fEnchantmentChanceMult")->getFloat() * ((mCastStyle == ESM::Enchantment::ConstantEffect) ? - gmst.find("fEnchantmentConstantChanceMult")->getFloat() : 1.0f )) + float chance2 = 7.5f / (gmst.find("fEnchantmentChanceMult")->mValue.getFloat() * ((mCastStyle == ESM::Enchantment::ConstantEffect) ? + gmst.find("fEnchantmentConstantChanceMult")->mValue.getFloat() : 1.0f )) * getEnchantPoints(); return (chance1-chance2); diff --git a/apps/openmw/mwmechanics/levelledlist.hpp b/apps/openmw/mwmechanics/levelledlist.hpp index fb06d89357..45f7e86fec 100644 --- a/apps/openmw/mwmechanics/levelledlist.hpp +++ b/apps/openmw/mwmechanics/levelledlist.hpp @@ -1,8 +1,7 @@ #ifndef OPENMW_MECHANICS_LEVELLEDLIST_H #define OPENMW_MECHANICS_LEVELLEDLIST_H -#include - +#include #include #include "../mwworld/ptr.hpp" @@ -63,7 +62,7 @@ namespace MWMechanics // 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; + Log(Debug::Warning) << "Warning: ignoring nonexistent item '" << item << "' in levelled list '" << levItem->mId << "'"; return std::string(); } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 7814b4a91e..859b2e5229 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1,6 +1,7 @@ #include "mechanicsmanagerimp.hpp" #include +#include #include @@ -35,7 +36,7 @@ namespace float getFightDispositionBias(float disposition) { static const float fFightDispMult = MWBase::Environment::get().getWorld()->getStore().get().find( - "fFightDispMult")->getFloat(); + "fFightDispMult")->mValue.getFloat(); return ((50.f - disposition) * fFightDispMult); } @@ -44,11 +45,11 @@ namespace const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); - float persTerm = stats.getAttribute(ESM::Attribute::Personality).getModified() / gmst.find("fPersonalityMod")->getFloat(); - float luckTerm = stats.getAttribute(ESM::Attribute::Luck).getModified() / gmst.find("fLuckMod")->getFloat(); - float repTerm = stats.getReputation() * gmst.find("fReputationMod")->getFloat(); + float persTerm = stats.getAttribute(ESM::Attribute::Personality).getModified() / gmst.find("fPersonalityMod")->mValue.getFloat(); + float luckTerm = stats.getAttribute(ESM::Attribute::Luck).getModified() / gmst.find("fLuckMod")->mValue.getFloat(); + float repTerm = stats.getReputation() * gmst.find("fReputationMod")->mValue.getFloat(); float fatigueTerm = stats.getFatigueTerm(); - float levelTerm = stats.getLevel() * gmst.find("fLevelMod")->getFloat(); + float levelTerm = stats.getLevel() * gmst.find("fLevelMod")->mValue.getFloat(); rating1 = (repTerm + luckTerm + persTerm + stats.getSkill(ESM::Skill::Speechcraft).getModified()) * fatigueTerm; @@ -341,7 +342,7 @@ namespace MWMechanics if(timeToDrown != mWatchedTimeToStartDrowning) { static const float fHoldBreathTime = MWBase::Environment::get().getWorld()->getStore().get() - .find("fHoldBreathTime")->getFloat(); + .find("fHoldBreathTime")->mValue.getFloat(); mWatchedTimeToStartDrowning = timeToDrown; @@ -435,9 +436,9 @@ namespace MWMechanics return mActors.isActorDetected(actor, observer); } - bool MechanicsManager::isAttackPrepairing(const MWWorld::Ptr& ptr) + bool MechanicsManager::isAttackPreparing(const MWWorld::Ptr& ptr) { - return mActors.isAttackPrepairing(ptr); + return mActors.isAttackPreparing(ptr); } bool MechanicsManager::isRunning(const MWWorld::Ptr& ptr) @@ -542,12 +543,12 @@ namespace MWMechanics const MWMechanics::NpcStats &playerStats = playerPtr.getClass().getNpcStats(playerPtr); const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); - static const float fDispRaceMod = gmst.find("fDispRaceMod")->getFloat(); + static const float fDispRaceMod = gmst.find("fDispRaceMod")->mValue.getFloat(); if (Misc::StringUtils::ciEqual(npc->mBase->mRace, player->mBase->mRace)) x += fDispRaceMod; - static const float fDispPersonalityMult = gmst.find("fDispPersonalityMult")->getFloat(); - static const float fDispPersonalityBase = gmst.find("fDispPersonalityBase")->getFloat(); + static const float fDispPersonalityMult = gmst.find("fDispPersonalityMult")->mValue.getFloat(); + static const float fDispPersonalityBase = gmst.find("fDispPersonalityBase")->mValue.getFloat(); x += fDispPersonalityMult * (playerStats.getAttribute(ESM::Attribute::Personality).getModified() - fDispPersonalityBase); float reaction = 0; @@ -591,20 +592,20 @@ namespace MWMechanics rank = 0; } - static const float fDispFactionRankMult = gmst.find("fDispFactionRankMult")->getFloat(); - static const float fDispFactionRankBase = gmst.find("fDispFactionRankBase")->getFloat(); - static const float fDispFactionMod = gmst.find("fDispFactionMod")->getFloat(); + static const float fDispFactionRankMult = gmst.find("fDispFactionRankMult")->mValue.getFloat(); + static const float fDispFactionRankBase = gmst.find("fDispFactionRankBase")->mValue.getFloat(); + static const float fDispFactionMod = gmst.find("fDispFactionMod")->mValue.getFloat(); x += (fDispFactionRankMult * rank + fDispFactionRankBase) * fDispFactionMod * reaction; - static const float fDispCrimeMod = gmst.find("fDispCrimeMod")->getFloat(); - static const float fDispDiseaseMod = gmst.find("fDispDiseaseMod")->getFloat(); + static const float fDispCrimeMod = gmst.find("fDispCrimeMod")->mValue.getFloat(); + static const float fDispDiseaseMod = gmst.find("fDispDiseaseMod")->mValue.getFloat(); x -= fDispCrimeMod * playerStats.getBounty(); if (playerStats.hasCommonDisease() || playerStats.hasBlightDisease()) x += fDispDiseaseMod; - static const float fDispWeaponDrawn = gmst.find("fDispWeaponDrawn")->getFloat(); + static const float fDispWeaponDrawn = gmst.find("fDispWeaponDrawn")->mValue.getFloat(); if (playerStats.getDrawState() == MWMechanics::DrawState_Weapon) x += fDispWeaponDrawn; @@ -672,16 +673,16 @@ namespace MWMechanics float target2 = d * (playerRating2 - npcRating2 + 50); float bribeMod; - if (type == PT_Bribe10) bribeMod = gmst.find("fBribe10Mod")->getFloat(); - else if (type == PT_Bribe100) bribeMod = gmst.find("fBribe100Mod")->getFloat(); - else bribeMod = gmst.find("fBribe1000Mod")->getFloat(); + if (type == PT_Bribe10) bribeMod = gmst.find("fBribe10Mod")->mValue.getFloat(); + else if (type == PT_Bribe100) bribeMod = gmst.find("fBribe100Mod")->mValue.getFloat(); + else bribeMod = gmst.find("fBribe1000Mod")->mValue.getFloat(); float target3 = d * (playerRating3 - npcRating3 + 50) + bribeMod; - float iPerMinChance = floor(gmst.find("iPerMinChance")->getFloat()); - float iPerMinChange = floor(gmst.find("iPerMinChange")->getFloat()); - float fPerDieRollMult = gmst.find("fPerDieRollMult")->getFloat(); - float fPerTempMult = gmst.find("fPerTempMult")->getFloat(); + float iPerMinChance = floor(gmst.find("iPerMinChance")->mValue.getFloat()); + float iPerMinChange = floor(gmst.find("iPerMinChange")->mValue.getFloat()); + float fPerDieRollMult = gmst.find("fPerDieRollMult")->mValue.getFloat(); + float fPerTempMult = gmst.find("fPerTempMult")->mValue.getFloat(); float x = 0; float y = 0; @@ -866,7 +867,7 @@ namespace MWMechanics continue; // All sMagicBound* GMST's should be of type string - std::string currentGMSTValue = currentSetting.getString(); + std::string currentGMSTValue = currentSetting.mValue.getString(); Misc::StringUtils::lowerCaseInPlace(currentGMSTValue); boundItemIDCache.insert(currentGMSTValue); @@ -1162,7 +1163,7 @@ namespace MWMechanics osg::Vec3f from (player.getRefData().getPosition().asVec3()); const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); - float radius = esmStore.get().find("fAlarmRadius")->getFloat(); + float radius = esmStore.get().find("fAlarmRadius")->mValue.getFloat(); mActors.getObjectsInRange(from, radius, neighbors); @@ -1260,29 +1261,29 @@ namespace MWMechanics float disp = 0.f, dispVictim = 0.f; if (type == OT_Trespassing || type == OT_SleepingInOwnedBed) { - arg = store.find("iCrimeTresspass")->getInt(); - disp = dispVictim = store.find("iDispTresspass")->getFloat(); + arg = store.find("iCrimeTresspass")->mValue.getInteger(); + disp = dispVictim = store.find("iDispTresspass")->mValue.getFloat(); } else if (type == OT_Pickpocket) { - arg = store.find("iCrimePickPocket")->getInt(); - disp = dispVictim = store.find("fDispPickPocketMod")->getFloat(); + arg = store.find("iCrimePickPocket")->mValue.getInteger(); + disp = dispVictim = store.find("fDispPickPocketMod")->mValue.getFloat(); } else if (type == OT_Assault) { - arg = store.find("iCrimeAttack")->getInt(); - disp = store.find("iDispAttackMod")->getFloat(); - dispVictim = store.find("fDispAttacking")->getFloat(); + arg = store.find("iCrimeAttack")->mValue.getInteger(); + disp = store.find("iDispAttackMod")->mValue.getFloat(); + dispVictim = store.find("fDispAttacking")->mValue.getFloat(); } else if (type == OT_Murder) { - arg = store.find("iCrimeKilling")->getInt(); - disp = dispVictim = store.find("iDispKilling")->getFloat(); + arg = store.find("iCrimeKilling")->mValue.getInteger(); + disp = dispVictim = store.find("iDispKilling")->mValue.getFloat(); } else if (type == OT_Theft) { - disp = dispVictim = store.find("fDispStealing")->getFloat() * arg; - arg = static_cast(arg * store.find("fCrimeStealing")->getFloat()); + disp = dispVictim = store.find("fDispStealing")->mValue.getFloat() * arg; + arg = static_cast(arg * store.find("fCrimeStealing")->mValue.getFloat()); arg = std::max(1, arg); // Minimum bounty of 1, in case items with zero value are stolen } @@ -1292,7 +1293,7 @@ namespace MWMechanics const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); osg::Vec3f from (player.getRefData().getPosition().asVec3()); - float radius = esmStore.get().find("fAlarmRadius")->getFloat(); + float radius = esmStore.get().find("fAlarmRadius")->mValue.getFloat(); mActors.getObjectsInRange(from, radius, neighbors); @@ -1306,21 +1307,21 @@ namespace MWMechanics // Controls whether witnesses will engage combat with the criminal. int fight = 0, fightVictim = 0; if (type == OT_Trespassing || type == OT_SleepingInOwnedBed) - fight = fightVictim = esmStore.get().find("iFightTrespass")->getInt(); + fight = fightVictim = esmStore.get().find("iFightTrespass")->mValue.getInteger(); else if (type == OT_Pickpocket) { - fight = esmStore.get().find("iFightPickpocket")->getInt(); - fightVictim = esmStore.get().find("iFightPickpocket")->getInt() * 4; // *4 according to research wiki + fight = esmStore.get().find("iFightPickpocket")->mValue.getInteger(); + fightVictim = esmStore.get().find("iFightPickpocket")->mValue.getInteger() * 4; // *4 according to research wiki } else if (type == OT_Assault) { - fight = esmStore.get().find("iFightAttacking")->getInt(); - fightVictim = esmStore.get().find("iFightAttack")->getInt(); + fight = esmStore.get().find("iFightAttacking")->mValue.getInteger(); + fightVictim = esmStore.get().find("iFightAttack")->mValue.getInteger(); } else if (type == OT_Murder) - fight = fightVictim = esmStore.get().find("iFightKilling")->getInt(); + fight = fightVictim = esmStore.get().find("iFightKilling")->mValue.getInteger(); else if (type == OT_Theft) - fight = fightVictim = esmStore.get().find("fFightStealing")->getInt(); + fight = fightVictim = esmStore.get().find("fFightStealing")->mValue.getInteger(); bool reported = false; @@ -1445,11 +1446,12 @@ namespace MWMechanics if (target == getPlayer() || !attacker.getClass().isActor()) return false; - std::list followersAttacker = getActorsSidingWith(attacker); + std::set followersAttacker; + getActorsSidingWith(attacker, followersAttacker); MWMechanics::CreatureStats& statsTarget = target.getClass().getCreatureStats(target); - if (std::find(followersAttacker.begin(), followersAttacker.end(), target) != followersAttacker.end()) + if (followersAttacker.find(target) != followersAttacker.end()) { statsTarget.friendlyHit(); @@ -1460,24 +1462,11 @@ namespace MWMechanics } } - // Attacking an NPC that is already in combat with any other NPC is not a crime - AiSequence& seq = statsTarget.getAiSequence(); - bool isFightingNpc = false; - for (std::list::const_iterator it = seq.begin(); it != seq.end(); ++it) - { - if ((*it)->getTypeId() == AiPackage::TypeIdCombat) - { - MWWorld::Ptr target2 = (*it)->getTarget(); - if (!target2.isEmpty() && target2.getClass().isNpc()) - isFightingNpc = true; - } - } - - if (target.getClass().isNpc() && !attacker.isEmpty() && !seq.isInCombat(attacker) - && !isAggressive(target, attacker) && !isFightingNpc - && !target.getClass().getCreatureStats(target).getAiSequence().hasPackage(AiPackage::TypeIdPursue)) + if (canCommitCrimeAgainst(target, attacker)) commitCrime(attacker, target, MWBase::MechanicsManager::OT_Assault); + AiSequence& seq = statsTarget.getAiSequence(); + if (!attacker.isEmpty() && (attacker.getClass().getCreatureStats(attacker).getAiSequence().isInCombat(target) || attacker == getPlayer()) && !seq.isInCombat(attacker)) @@ -1504,6 +1493,14 @@ namespace MWMechanics return true; } + bool MechanicsManager::canCommitCrimeAgainst(const MWWorld::Ptr &target, const MWWorld::Ptr &attacker) + { + MWMechanics::AiSequence seq = target.getClass().getCreatureStats(target).getAiSequence(); + return target.getClass().isNpc() && !attacker.isEmpty() && !seq.isInCombat(attacker) + && !isAggressive(target, attacker) && !seq.isEngagedWithActor() + && !target.getClass().getCreatureStats(target).getAiSequence().hasPackage(AiPackage::TypeIdPursue); + } + void MechanicsManager::actorKilled(const MWWorld::Ptr &victim, const MWWorld::Ptr &attacker) { if (attacker.isEmpty() || victim.isEmpty()) @@ -1516,11 +1513,10 @@ namespace MWMechanics return; // TODO: implement animal rights const MWMechanics::NpcStats& victimStats = victim.getClass().getNpcStats(victim); - if (victimStats.getCrimeId() == -1) - return; + const MWWorld::Ptr &player = getPlayer(); + bool canCommit = attacker == player && canCommitCrimeAgainst(victim, attacker); // For now we report only about crimes of player and player's followers - const MWWorld::Ptr &player = getPlayer(); if (attacker != player) { std::set playerFollowers; @@ -1529,6 +1525,9 @@ namespace MWMechanics return; } + if (!canCommit && victimStats.getCrimeId() == -1) + return; + // Simple check for who attacked first: if the player attacked first, a crimeId should be set // Doesn't handle possible edge case where no one reported the assault, but in such a case, // for bystanders it is not possible to tell who attacked first, anyway. @@ -1553,8 +1552,8 @@ namespace MWMechanics && !MWBase::Environment::get().getWorld()->isSwimming(ptr) && MWBase::Environment::get().getWorld()->isOnGround(ptr)) { - static float fSneakSkillMult = store.find("fSneakSkillMult")->getFloat(); - static float fSneakBootMult = store.find("fSneakBootMult")->getFloat(); + static float fSneakSkillMult = store.find("fSneakSkillMult")->mValue.getFloat(); + static float fSneakBootMult = store.find("fSneakBootMult")->mValue.getFloat(); float sneak = static_cast(ptr.getClass().getSkill(ptr, ESM::Skill::Sneak)); int agility = stats.getAttribute(ESM::Attribute::Agility).getModified(); int luck = stats.getAttribute(ESM::Attribute::Luck).getModified(); @@ -1569,8 +1568,8 @@ namespace MWMechanics sneakTerm = fSneakSkillMult * sneak + 0.2f * agility + 0.1f * luck + bootWeight * fSneakBootMult; } - static float fSneakDistBase = store.find("fSneakDistanceBase")->getFloat(); - static float fSneakDistMult = store.find("fSneakDistanceMultiplier")->getFloat(); + static float fSneakDistBase = store.find("fSneakDistanceBase")->mValue.getFloat(); + static float fSneakDistMult = store.find("fSneakDistanceMultiplier")->mValue.getFloat(); osg::Vec3f pos1 (ptr.getRefData().getPosition().asVec3()); osg::Vec3f pos2 (observer.getRefData().getPosition().asVec3()); @@ -1588,8 +1587,8 @@ namespace MWMechanics float obsTerm = obsSneak + 0.2f * obsAgility + 0.1f * obsLuck - obsBlind; // is ptr behind the observer? - static float fSneakNoViewMult = store.find("fSneakNoViewMult")->getFloat(); - static float fSneakViewMult = store.find("fSneakViewMult")->getFloat(); + static float fSneakNoViewMult = store.find("fSneakNoViewMult")->mValue.getFloat(); + static float fSneakViewMult = store.find("fSneakViewMult")->mValue.getFloat(); float y = 0; osg::Vec3f vec = pos1 - pos2; if (observer.getRefData().getBaseNode()) @@ -1749,7 +1748,7 @@ namespace MWMechanics MWBase::Environment::get().getWorld()->getGlobalInt("pcknownwerewolf"))) { const ESM::GameSetting * iWerewolfFightMod = MWBase::Environment::get().getWorld()->getStore().get().find("iWerewolfFightMod"); - fight += iWerewolfFightMod->getInt(); + fight += iWerewolfFightMod->mValue.getInteger(); } } @@ -1842,7 +1841,7 @@ namespace MWMechanics // Witnesses of the player's transformation will make them a globally known werewolf std::vector closeActors; const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); - getActorsInRange(actor.getRefData().getPosition().asVec3(), gmst.find("fAlarmRadius")->getFloat(), closeActors); + getActorsInRange(actor.getRefData().getPosition().asVec3(), gmst.find("fAlarmRadius")->mValue.getFloat(), closeActors); bool detected = false, reported = false; for (std::vector::const_iterator it = closeActors.begin(); it != closeActors.end(); ++it) @@ -1867,7 +1866,7 @@ namespace MWMechanics if (reported) { npcStats.setBounty(npcStats.getBounty()+ - gmst.find("iWereWolfBounty")->getInt()); + gmst.find("iWereWolfBounty")->mValue.getInteger()); windowManager->messageBox("#{sCrimeMessage}"); } } @@ -1879,7 +1878,7 @@ namespace MWMechanics const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); MWMechanics::NpcStats &stats = actor.getClass().getNpcStats(actor); - stats.getSkill(ESM::Skill::Acrobatics).setBase(gmst.find("fWerewolfAcrobatics")->getInt()); + stats.getSkill(ESM::Skill::Acrobatics).setBase(gmst.find("fWerewolfAcrobatics")->mValue.getInteger()); } void MechanicsManager::cleanupSummonedCreature(const MWWorld::Ptr &caster, int creatureActorId) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 676a75cafb..af12d4d98f 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -132,6 +132,12 @@ namespace MWMechanics /// @note No-op for non-player attackers virtual void actorKilled (const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker); + /// Checks if commiting a crime is currently valid + /// @param victim The actor being attacked + /// @param attacker The actor commiting the crime + /// @return true if the victim is a valid target for crime + virtual bool canCommitCrimeAgainst(const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker); + /// Utility to check if taking this item is illegal and calling commitCrime if so /// @param container The container the item is in; may be empty for an item in the world virtual void itemTaken (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, const MWWorld::Ptr& container, @@ -223,7 +229,7 @@ namespace MWMechanics virtual void confiscateStolenItemToOwner(const MWWorld::Ptr &player, const MWWorld::Ptr &item, const MWWorld::Ptr& victim, int count); - virtual bool isAttackPrepairing(const MWWorld::Ptr& ptr); + virtual bool isAttackPreparing(const MWWorld::Ptr& ptr); virtual bool isRunning(const MWWorld::Ptr& ptr); virtual bool isSneaking(const MWWorld::Ptr& ptr); diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index f15152759f..cb9ef54773 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -149,12 +149,12 @@ float MWMechanics::NpcStats::getSkillProgressRequirement (int skillIndex, const const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); - float typeFactor = gmst.find ("fMiscSkillBonus")->getFloat(); + float typeFactor = gmst.find ("fMiscSkillBonus")->mValue.getFloat(); for (int i=0; i<5; ++i) if (class_.mData.mSkills[i][0]==skillIndex) { - typeFactor = gmst.find ("fMinorSkillBonus")->getFloat(); + typeFactor = gmst.find ("fMinorSkillBonus")->mValue.getFloat(); break; } @@ -162,7 +162,7 @@ float MWMechanics::NpcStats::getSkillProgressRequirement (int skillIndex, const for (int i=0; i<5; ++i) if (class_.mData.mSkills[i][1]==skillIndex) { - typeFactor = gmst.find ("fMajorSkillBonus")->getFloat(); + typeFactor = gmst.find ("fMajorSkillBonus")->mValue.getFloat(); break; } @@ -178,7 +178,7 @@ float MWMechanics::NpcStats::getSkillProgressRequirement (int skillIndex, const MWBase::Environment::get().getWorld()->getStore().get().find (skillIndex); if (skill->mData.mSpecialization==class_.mData.mSpecialization) { - specialisationFactor = gmst.find ("fSpecialSkillBonus")->getFloat(); + specialisationFactor = gmst.find ("fSpecialSkillBonus")->mValue.getFloat(); if (specialisationFactor<=0) throw std::runtime_error ("invalid skill specialisation factor"); @@ -227,21 +227,21 @@ void MWMechanics::NpcStats::increaseSkill(int skillIndex, const ESM::Class &clas MWBase::Environment::get().getWorld()->getStore().get(); // is this a minor or major skill? - int increase = gmst.find("iLevelupMiscMultAttriubte")->getInt(); // Note: GMST has a typo + int increase = gmst.find("iLevelupMiscMultAttriubte")->mValue.getInteger(); // Note: GMST has a typo for (int k=0; k<5; ++k) { if (class_.mData.mSkills[k][0] == skillIndex) { - mLevelProgress += gmst.find("iLevelUpMinorMult")->getInt(); - increase = gmst.find("iLevelUpMajorMultAttribute")->getInt(); + mLevelProgress += gmst.find("iLevelUpMinorMult")->mValue.getInteger(); + increase = gmst.find("iLevelUpMajorMultAttribute")->mValue.getInteger(); } } for (int k=0; k<5; ++k) { if (class_.mData.mSkills[k][1] == skillIndex) { - mLevelProgress += gmst.find("iLevelUpMajorMult")->getInt(); - increase = gmst.find("iLevelUpMinorMultAttribute")->getInt(); + mLevelProgress += gmst.find("iLevelUpMajorMult")->mValue.getInteger(); + increase = gmst.find("iLevelUpMinorMultAttribute")->mValue.getInteger(); } } @@ -249,7 +249,7 @@ void MWMechanics::NpcStats::increaseSkill(int skillIndex, const ESM::Class &clas MWBase::Environment::get().getWorld ()->getStore ().get().find(skillIndex); mSkillIncreases[skill->mData.mAttribute] += increase; - mSpecIncreases[skill->mData.mSpecialization] += gmst.find("iLevelupSpecialization")->getInt(); + mSpecIncreases[skill->mData.mSpecialization] += gmst.find("iLevelupSpecialization")->mValue.getInteger(); // Play sound & skill progress notification /// \todo check if character is the player, if levelling is ever implemented for NPCs @@ -266,7 +266,7 @@ void MWMechanics::NpcStats::increaseSkill(int skillIndex, const ESM::Class &clas MWBase::Environment::get().getWindowManager ()->messageBox(message.str(), MWGui::ShowInDialogueMode_Never); - if (mLevelProgress >= gmst.find("iLevelUpTotal")->getInt()) + if (mLevelProgress >= gmst.find("iLevelUpTotal")->mValue.getInteger()) { // levelup is possible now MWBase::Environment::get().getWindowManager ()->messageBox ("#{sLevelUpMsg}", MWGui::ShowInDialogueMode_Never); @@ -287,7 +287,7 @@ void MWMechanics::NpcStats::levelUp() const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); - mLevelProgress -= gmst.find("iLevelUpTotal")->getInt(); + mLevelProgress -= gmst.find("iLevelUpTotal")->mValue.getInteger(); mLevelProgress = std::max(0, mLevelProgress); // might be necessary when levelup was invoked via console for (int i=0; igetFloat()); + setHealth(getHealth().getBase() + endurance * gmst.find("fLevelUpHealthEndMult")->mValue.getFloat()); setLevel(getLevel()+1); } @@ -324,7 +324,7 @@ int MWMechanics::NpcStats::getLevelupAttributeMultiplier(int attribute) const std::stringstream gmst; gmst << "iLevelUp" << std::setfill('0') << std::setw(2) << num << "Mult"; - return MWBase::Environment::get().getWorld()->getStore().get().find(gmst.str())->getInt(); + return MWBase::Environment::get().getWorld()->getStore().get().find(gmst.str())->mValue.getInteger(); } int MWMechanics::NpcStats::getSkillIncreasesForSpecialization(int spec) const diff --git a/apps/openmw/mwmechanics/objects.cpp b/apps/openmw/mwmechanics/objects.cpp index 7d7a5f14ff..d8821276e4 100644 --- a/apps/openmw/mwmechanics/objects.cpp +++ b/apps/openmw/mwmechanics/objects.cpp @@ -1,6 +1,6 @@ #include "objects.hpp" -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -88,7 +88,7 @@ bool Objects::playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& gro } else { - std::cerr<< "Warning: Objects::playAnimationGroup: Unable to find " << ptr.getCellRef().getRefId() << std::endl; + Log(Debug::Warning) << "Warning: Objects::playAnimationGroup: Unable to find " << ptr.getCellRef().getRefId(); return false; } } diff --git a/apps/openmw/mwmechanics/pickpocket.cpp b/apps/openmw/mwmechanics/pickpocket.cpp index eca24606e6..ed64691065 100644 --- a/apps/openmw/mwmechanics/pickpocket.cpp +++ b/apps/openmw/mwmechanics/pickpocket.cpp @@ -37,9 +37,9 @@ namespace MWMechanics float pcSneak = static_cast(mThief.getClass().getSkill(mThief, ESM::Skill::Sneak)); int iPickMinChance = MWBase::Environment::get().getWorld()->getStore().get() - .find("iPickMinChance")->getInt(); + .find("iPickMinChance")->mValue.getInteger(); int iPickMaxChance = MWBase::Environment::get().getWorld()->getStore().get() - .find("iPickMaxChance")->getInt(); + .find("iPickMaxChance")->mValue.getInteger(); int roll = Misc::Rng::roll0to99(); if (t < pcSneak / iPickMinChance) @@ -57,7 +57,7 @@ namespace MWMechanics { float stackValue = static_cast(item.getClass().getValue(item) * count); float fPickPocketMod = MWBase::Environment::get().getWorld()->getStore().get() - .find("fPickPocketMod")->getFloat(); + .find("fPickPocketMod")->mValue.getFloat(); float valueTerm = 10 * fPickPocketMod * stackValue; return getDetected(valueTerm); diff --git a/apps/openmw/mwmechanics/repair.cpp b/apps/openmw/mwmechanics/repair.cpp index 5c6123e822..3ebef36bf1 100644 --- a/apps/openmw/mwmechanics/repair.cpp +++ b/apps/openmw/mwmechanics/repair.cpp @@ -42,7 +42,7 @@ void Repair::repair(const MWWorld::Ptr &itemToRepair) int armorerSkill = npcStats.getSkill(ESM::Skill::Armorer).getModified(); float fRepairAmountMult = MWBase::Environment::get().getWorld()->getStore().get() - .find("fRepairAmountMult")->getFloat(); + .find("fRepairAmountMult")->mValue.getFloat(); float toolQuality = ref->mBase->mData.mQuality; @@ -87,7 +87,7 @@ void Repair::repair(const MWWorld::Ptr &itemToRepair) store.remove(mTool, 1, player); std::string message = MWBase::Environment::get().getWorld()->getStore().get() - .find("sNotifyMessage51")->getString(); + .find("sNotifyMessage51")->mValue.getString(); MWBase::Environment::get().getWindowManager()->messageBox((boost::format(message) % mTool.getClass().getName(mTool)).str()); diff --git a/apps/openmw/mwmechanics/security.cpp b/apps/openmw/mwmechanics/security.cpp index 3e38bc5219..c8a4dd7706 100644 --- a/apps/openmw/mwmechanics/security.cpp +++ b/apps/openmw/mwmechanics/security.cpp @@ -40,7 +40,7 @@ namespace MWMechanics float pickQuality = lockpick.get()->mBase->mData.mQuality; - float fPickLockMult = MWBase::Environment::get().getWorld()->getStore().get().find("fPickLockMult")->getFloat(); + float fPickLockMult = MWBase::Environment::get().getWorld()->getStore().get().find("fPickLockMult")->mValue.getFloat(); float x = 0.2f * mAgility + 0.1f * mLuck + mSecuritySkill; x *= pickQuality * mFatigueTerm; @@ -81,7 +81,7 @@ namespace MWMechanics const ESM::Spell* trapSpell = MWBase::Environment::get().getWorld()->getStore().get().find(trap.getCellRef().getTrap()); int trapSpellPoints = trapSpell->mData.mCost; - float fTrapCostMult = MWBase::Environment::get().getWorld()->getStore().get().find("fTrapCostMult")->getFloat(); + float fTrapCostMult = MWBase::Environment::get().getWorld()->getStore().get().find("fTrapCostMult")->mValue.getFloat(); float x = 0.2f * mAgility + 0.1f * mLuck + mSecuritySkill; x += fTrapCostMult * trapSpellPoints; diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 4557a52dfc..b857c6d7e2 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -24,6 +24,7 @@ #include "../mwworld/inventorystore.hpp" #include "../mwrender/animation.hpp" +#include "../mwrender/vismask.hpp" #include "npcstats.hpp" #include "actorutil.hpp" @@ -33,13 +34,17 @@ namespace MWMechanics { ESM::Skill::SkillEnum spellSchoolToSkill(int school) { - std::map schoolSkillMap; // maps spell school to skill id - schoolSkillMap[0] = ESM::Skill::Alteration; - schoolSkillMap[1] = ESM::Skill::Conjuration; - schoolSkillMap[3] = ESM::Skill::Illusion; - schoolSkillMap[2] = ESM::Skill::Destruction; - schoolSkillMap[4] = ESM::Skill::Mysticism; - schoolSkillMap[5] = ESM::Skill::Restoration; + static std::map schoolSkillMap; // maps spell school to skill id + if (schoolSkillMap.empty()) + { + schoolSkillMap[0] = ESM::Skill::Alteration; + schoolSkillMap[1] = ESM::Skill::Conjuration; + schoolSkillMap[3] = ESM::Skill::Illusion; + schoolSkillMap[2] = ESM::Skill::Destruction; + schoolSkillMap[4] = ESM::Skill::Mysticism; + schoolSkillMap[5] = ESM::Skill::Restoration; + } + assert(schoolSkillMap.find(school) != schoolSkillMap.end()); return schoolSkillMap[school]; } @@ -47,7 +52,11 @@ namespace MWMechanics float calcEffectCost(const ESM::ENAMstruct& effect) { const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effect.mEffectID); + return calcEffectCost(effect, magicEffect); + } + float calcEffectCost(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect) + { int minMagn = 1; int maxMagn = 1; if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)) @@ -61,7 +70,7 @@ namespace MWMechanics duration = effect.mDuration; static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore() - .get().find("fEffectCostMult")->getFloat(); + .get().find("fEffectCostMult")->mValue.getFloat(); float x = 0.5 * (std::max(1, minMagn) + std::max(1, maxMagn)); x *= 0.1 * magicEffect->mData.mBaseCost; @@ -92,7 +101,7 @@ namespace MWMechanics if (it->mRange == ESM::RT_Target) x *= 1.5f; static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore().get().find( - "fEffectCostMult")->getFloat(); + "fEffectCostMult")->mValue.getFloat(); x *= fEffectCostMult; float s = 2.0f * actor.getClass().getSkill(actor, spellSchoolToSkill(magicEffect->mData.mSchool)); @@ -315,28 +324,31 @@ namespace MWMechanics return true; } - CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target, const bool fromProjectile, const bool isScripted) + CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target, const bool fromProjectile, const bool manualSpell) : mCaster(caster) , mTarget(target) , mStack(false) , mHitPosition(0,0,0) , mAlwaysSucceed(false) , mFromProjectile(fromProjectile) - , mIsScripted(isScripted) + , mManualSpell(manualSpell) { } void CastSpell::launchMagicBolt () - { - osg::Vec3f fallbackDirection (0,1,0); + { + osg::Vec3f fallbackDirection(0, 1, 0); + osg::Vec3f offset(0, 0, 0); + if (!mTarget.isEmpty() && mTarget.getClass().isActor()) + offset.z() = MWBase::Environment::get().getWorld()->getHalfExtents(mTarget).z(); // Fall back to a "caster to target" direction if we have no other means of determining it // (e.g. when cast by a non-actor) if (!mTarget.isEmpty()) fallbackDirection = - osg::Vec3f(mTarget.getRefData().getPosition().asVec3())- - osg::Vec3f(mCaster.getRefData().getPosition().asVec3()); - + (mTarget.getRefData().getPosition().asVec3() + offset) - + (mCaster.getRefData().getPosition().asVec3()); + MWBase::Environment::get().getWorld()->launchMagicBolt(mId, mCaster, fallbackDirection); } @@ -420,9 +432,9 @@ namespace MWMechanics if (!checkEffectTarget(effectIt->mEffectID, target, caster, castByPlayer)) continue; - // caster needs to be an actor for linked effects (e.g. Absorb) + // caster needs to be an actor that's not the target for linked effects (e.g. Absorb) if (magicEffect->mData.mFlags & ESM::MagicEffect::CasterLinked - && (caster.isEmpty() || !caster.getClass().isActor())) + && (caster.isEmpty() || !caster.getClass().isActor() || caster == target)) continue; // If player is healing someone, show the target's HP bar @@ -509,9 +521,18 @@ namespace MWMechanics } else // target.getClass().isActor() == true { + ActiveSpells::ActiveEffect effect; + effect.mEffectId = effectIt->mEffectID; + effect.mArg = MWMechanics::EffectKey(*effectIt).mArg; + effect.mMagnitude = magnitude; + bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration); if (hasDuration && effectIt->mDuration == 0) { + // We still should add effect to list to allow GetSpellEffects to detect this spell + effect.mDuration = 0.f; + appliedLastingEffects.push_back(effect); + // duration 0 means apply full magnitude instantly bool wasDead = target.getClass().getCreatureStats(target).isDead(); effectTick(target.getClass().getCreatureStats(target), target, EffectKey(*effectIt), magnitude); @@ -522,18 +543,15 @@ namespace MWMechanics } else { - // add to list of active effects, to apply in next frame - ActiveSpells::ActiveEffect effect; - effect.mEffectId = effectIt->mEffectID; - effect.mArg = MWMechanics::EffectKey(*effectIt).mArg; + if (!hasDuration) effect.mDuration = 1.0f; else effect.mDuration = static_cast(effectIt->mDuration); - effect.mMagnitude = magnitude; targetEffects.add(MWMechanics::EffectKey(*effectIt), MWMechanics::EffectParam(effect.mMagnitude)); + // add to list of active effects, to apply in next frame appliedLastingEffects.push_back(effect); // Unequip all items, if a spell with the ExtraSpell effect was casted @@ -554,7 +572,7 @@ namespace MWMechanics // For absorb effects, also apply the effect to the caster - but with a negative // magnitude, since we're transferring stats from the target to the caster - if (!caster.isEmpty() && caster.getClass().isActor()) + if (!caster.isEmpty() && caster != target && caster.getClass().isActor()) { for (int i=0; i<5; ++i) { @@ -864,7 +882,7 @@ namespace MWMechanics bool godmode = mCaster == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); - if (mCaster.getClass().isActor() && !mAlwaysSucceed && !mIsScripted) + if (mCaster.getClass().isActor() && !mAlwaysSucceed && !mManualSpell) { school = getSpellSchool(spell, mCaster); @@ -873,8 +891,8 @@ namespace MWMechanics if (!godmode) { // Reduce fatigue (note that in the vanilla game, both GMSTs are 0, and there's no fatigue loss) - static const float fFatigueSpellBase = store.get().find("fFatigueSpellBase")->getFloat(); - static const float fFatigueSpellMult = store.get().find("fFatigueSpellMult")->getFloat(); + static const float fFatigueSpellBase = store.get().find("fFatigueSpellBase")->mValue.getFloat(); + static const float fFatigueSpellMult = store.get().find("fFatigueSpellMult")->mValue.getFloat(); DynamicStat fatigue = stats.getFatigue(); const float normalizedEncumbrance = mCaster.getClass().getNormalizedEncumbrance(mCaster); @@ -958,7 +976,7 @@ namespace MWMechanics if (roll > x) { // "X has no effect on you" - std::string message = MWBase::Environment::get().getWorld()->getStore().get().find("sNotifyMessage50")->getString(); + std::string message = MWBase::Environment::get().getWorld()->getStore().get().find("sNotifyMessage50")->mValue.getString(); message = boost::str(boost::format(message) % ingredient->mName); MWBase::Environment::get().getWindowManager()->messageBox(message); return false; @@ -993,11 +1011,13 @@ namespace MWMechanics return true; } - void CastSpell::playSpellCastingEffects(const std::string &spellid){ - + void CastSpell::playSpellCastingEffects(const std::string &spellid) + { const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Spell *spell = store.get().find(spellid); + std::vector addedEffects; + for (std::vector::const_iterator iter = spell->mEffects.mList.begin(); iter != spell->mEffects.mList.end(); ++iter) { @@ -1006,18 +1026,56 @@ namespace MWMechanics MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(mCaster); - if (animation && mCaster.getClass().isActor()) // TODO: Non-actors should also create a spell cast vfx even if they are disabled (animation == NULL) + const ESM::Static* castStatic; + + if (!effect->mCasting.empty()) + castStatic = store.get().find (effect->mCasting); + else + castStatic = store.get().find ("VFX_DefaultCast"); + + // check if the effect was already added + if (std::find(addedEffects.begin(), addedEffects.end(), "meshes\\" + castStatic->mModel) != addedEffects.end()) + continue; + + std::string texture = effect->mParticle; + + float scale = 1.0f; + osg::Vec3f pos (mCaster.getRefData().getPosition().asVec3()); + + if (animation && mCaster.getClass().isNpc()) { - const ESM::Static* castStatic; + // For NPC we should take race height as scaling factor + const ESM::NPC *npc = mCaster.get()->mBase; + const MWWorld::ESMStore &esmStore = + MWBase::Environment::get().getWorld()->getStore(); - if (!effect->mCasting.empty()) - castStatic = store.get().find (effect->mCasting); - else - castStatic = store.get().find ("VFX_DefaultCast"); + const ESM::Race *race = + esmStore.get().find(npc->mRace); - std::string texture = effect->mParticle; + scale = npc->isMale() ? race->mData.mHeight.mMale : race->mData.mHeight.mFemale; + } + else + { + osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(mCaster); - animation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex, false, "", texture); + // TODO: take a size of particle or NPC with height and weight = 1.0 as scale = 1.0 + float scaleX = halfExtents.x() * 2 / 60.f; + float scaleY = halfExtents.y() * 2 / 60.f; + float scaleZ = halfExtents.z() * 2 / 120.f; + + scale = std::max({ scaleX, scaleY, scaleZ }); + } + + // If the caster has no animation, add the effect directly to the effectManager + if (animation) + { + animation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex, false, "", texture, scale); + } + else + { + // We should set scale for effect manager manually + float meshScale = !mCaster.getClass().isActor() ? mCaster.getCellRef().getScale() : 1.0f; + MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + castStatic->mModel, effect->mParticle, pos, scale * meshScale); } if (animation && !mCaster.getClass().isActor()) @@ -1027,6 +1085,8 @@ namespace MWMechanics "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" }; + addedEffects.push_back("meshes\\" + castStatic->mModel); + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); if(!effect->mCastSound.empty()) sndMgr->playSound3D(mCaster, effect->mCastSound, 1.0f, 1.0f); @@ -1037,7 +1097,7 @@ namespace MWMechanics bool CastSpell::spellIncreasesSkill() { - if (mIsScripted) + if (mManualSpell) return false; return MWMechanics::spellIncreasesSkill(mId); @@ -1199,7 +1259,7 @@ namespace MWMechanics float damageScale = 1.f - timeDiff / 7.f; // When cloudy, the sun damage effect is halved static float fMagicSunBlockedMult = MWBase::Environment::get().getWorld()->getStore().get().find( - "fMagicSunBlockedMult")->getFloat(); + "fMagicSunBlockedMult")->mValue.getFloat(); int weather = MWBase::Environment::get().getWorld()->getCurrentWeather(); if (weather > 1) @@ -1296,7 +1356,7 @@ namespace MWMechanics if (it == summonMap.end()) return std::string(); else - return MWBase::Environment::get().getWorld()->getStore().get().find(it->second)->getString(); + return MWBase::Environment::get().getWorld()->getStore().get().find(it->second)->mValue.getString(); } void ApplyLoopingParticlesVisitor::visit (MWMechanics::EffectKey key, diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index 5e69b4696e..2844e7f23f 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -3,6 +3,7 @@ #include #include +#include #include "../mwworld/ptr.hpp" @@ -25,6 +26,7 @@ namespace MWMechanics ESM::Skill::SkillEnum spellSchoolToSkill(int school); float calcEffectCost(const ESM::ENAMstruct& effect); + float calcEffectCost(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect); bool isSummoningEffect(int effectId); @@ -88,10 +90,10 @@ namespace MWMechanics osg::Vec3f mHitPosition; // Used for spawning area orb bool mAlwaysSucceed; // Always succeed spells casted by NPCs/creatures regardless of their chance (default: false) bool mFromProjectile; // True if spell is cast by enchantment of some projectile (arrow, bolt or thrown weapon) - bool mIsScripted; // True if spell is casted from script and ignores some checks (mana level, success chance, etc.) + bool mManualSpell; // True if spell is casted from script and ignores some checks (mana level, success chance, etc.) public: - CastSpell(const MWWorld::Ptr& caster, const MWWorld::Ptr& target, const bool fromProjectile=false, const bool isScripted=false); + CastSpell(const MWWorld::Ptr& caster, const MWWorld::Ptr& target, const bool fromProjectile=false, const bool manualSpell=false); bool cast (const ESM::Spell* spell); diff --git a/apps/openmw/mwmechanics/spellpriority.cpp b/apps/openmw/mwmechanics/spellpriority.cpp index 7bedb1e375..807b56608e 100644 --- a/apps/openmw/mwmechanics/spellpriority.cpp +++ b/apps/openmw/mwmechanics/spellpriority.cpp @@ -232,20 +232,59 @@ namespace MWMechanics case ESM::MagicEffect::CommandHumanoid: return 0.f; + case ESM::MagicEffect::Blind: + { + if (enemy.isEmpty()) + return 0.f; + + const CreatureStats& stats = enemy.getClass().getCreatureStats(enemy); + + // Enemy can't attack + if (stats.isParalyzed() || stats.getKnockedDown()) + return 0.f; + + // Enemy doesn't attack + if (stats.getDrawState() != MWMechanics::DrawState_Weapon) + return 0.f; + + break; + } + case ESM::MagicEffect::Sound: { if (enemy.isEmpty()) return 0.f; - // there is no need to cast sound if enemy is not able to cast spells - CreatureStats& stats = enemy.getClass().getCreatureStats(enemy); + const CreatureStats& stats = enemy.getClass().getCreatureStats(enemy); + // Enemy can't cast spells if (stats.getMagicEffects().get(ESM::MagicEffect::Silence).getMagnitude() > 0) return 0.f; - if (stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getMagnitude() > 0) + if (stats.isParalyzed() || stats.getKnockedDown()) return 0.f; + // Enemy doesn't cast spells + if (stats.getDrawState() != MWMechanics::DrawState_Spell) + return 0.f; + + break; + } + + case ESM::MagicEffect::Silence: + { + if (enemy.isEmpty()) + return 0.f; + + const CreatureStats& stats = enemy.getClass().getCreatureStats(enemy); + + // Enemy can't cast spells + if (stats.isParalyzed() || stats.getKnockedDown()) + return 0.f; + + // Enemy doesn't cast spells + if (stats.getDrawState() != MWMechanics::DrawState_Spell) + return 0.f; break; } @@ -353,9 +392,10 @@ namespace MWMechanics int priority = 1; if (effect.mEffectID == ESM::MagicEffect::RestoreHealth) priority = 10; - const DynamicStat& current = actor.getClass().getCreatureStats(actor). - getDynamic(effect.mEffectID - ESM::MagicEffect::RestoreHealth); - float toHeal = (effect.mMagnMin + effect.mMagnMax)/2.f * effect.mDuration; + const MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); + const DynamicStat& current = stats.getDynamic(effect.mEffectID - ESM::MagicEffect::RestoreHealth); + const float magnitude = (effect.mMagnMin + effect.mMagnMax)/2.f; + const float toHeal = magnitude * effect.mDuration; // Effect doesn't heal more than we need, *or* we are below 1/2 health if (current.getModified() - current.getCurrent() > toHeal || current.getCurrent() < current.getModified()*0.5) @@ -515,8 +555,6 @@ namespace MWMechanics return 0.f; } - const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effect.mEffectID); - // Underwater casting not possible if (effect.mRange == ESM::RT_Target) { @@ -530,6 +568,7 @@ namespace MWMechanics return 0.f; } + const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effect.mEffectID); if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful) { rating *= -1.f; @@ -565,7 +604,7 @@ namespace MWMechanics } } - rating *= calcEffectCost(effect); + rating *= calcEffectCost(effect, magicEffect); // Currently treating all "on target" or "on touch" effects to target the enemy actor. // Combat AI is egoistic, so doesn't consider applying positive effects to friendly actors. @@ -578,13 +617,19 @@ namespace MWMechanics float rateEffects(const ESM::EffectList &list, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy) { // NOTE: enemy may be empty + float rating = 0.f; + float ratingMult = 1.f; // NB: this multiplier is applied to the effect rating, not the final rating + + const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); + static const float fAIMagicSpellMult = gmst.find("fAIMagicSpellMult")->mValue.getFloat(); + static const float fAIRangeMagicSpellMult = gmst.find("fAIRangeMagicSpellMult")->mValue.getFloat(); + for (std::vector::const_iterator it = list.mList.begin(); it != list.mList.end(); ++it) { - rating += rateEffect(*it, actor, enemy); + ratingMult = (it->mRange == ESM::RT_Target) ? fAIRangeMagicSpellMult : fAIMagicSpellMult; - if (it->mRange == ESM::RT_Target) - rating *= 1.5f; + rating += rateEffect(*it, actor, enemy) * ratingMult; } return rating; } @@ -593,8 +638,8 @@ namespace MWMechanics { const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); - static const float fAIMagicSpellMult = gmst.find("fAIMagicSpellMult")->getFloat(); - static const float fAIRangeMagicSpellMult = gmst.find("fAIRangeMagicSpellMult")->getFloat(); + static const float fAIMagicSpellMult = gmst.find("fAIMagicSpellMult")->mValue.getFloat(); + static const float fAIRangeMagicSpellMult = gmst.find("fAIRangeMagicSpellMult")->mValue.getFloat(); float mult = fAIMagicSpellMult; diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index ff397f69d8..0a11ed6414 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -279,6 +279,25 @@ namespace MWMechanics } } + void Spells::removeEffects(const std::string &id) + { + if (isSpellActive(id)) + { + for (TContainer::iterator spell = mSpells.begin(); spell != mSpells.end(); ++spell) + { + if (spell->first == getSpell(id)) + { + for (long unsigned int i = 0; i != spell->first->mEffects.mList.size(); i++) + { + spell->second.mPurgedEffects.insert(i); + } + } + } + + mSpellsChanged = true; + } + } + void Spells::visitEffectSources(EffectSourceVisitor &visitor) const { if (mSpellsChanged) { diff --git a/apps/openmw/mwmechanics/spells.hpp b/apps/openmw/mwmechanics/spells.hpp index c1705b38a1..e635f4f56d 100644 --- a/apps/openmw/mwmechanics/spells.hpp +++ b/apps/openmw/mwmechanics/spells.hpp @@ -124,6 +124,8 @@ namespace MWMechanics bool hasBlightDisease() const; + void removeEffects(const std::string& id); + void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor) const; void readState (const ESM::SpellState& state); diff --git a/apps/openmw/mwmechanics/stat.cpp b/apps/openmw/mwmechanics/stat.cpp index 41c5bac5af..f53052a286 100644 --- a/apps/openmw/mwmechanics/stat.cpp +++ b/apps/openmw/mwmechanics/stat.cpp @@ -5,11 +5,11 @@ namespace MWMechanics { template - Stat::Stat() : mBase (0), mModified (0) {} + Stat::Stat() : mBase (0), mModified (0), mCurrentModified (0) {} template - Stat::Stat(T base) : mBase (base), mModified (base) {} + Stat::Stat(T base) : mBase (base), mModified (base), mCurrentModified (base) {} template - Stat::Stat(T base, T modified) : mBase (base), mModified (modified) {} + Stat::Stat(T base, T modified) : mBase (base), mModified (modified), mCurrentModified (modified) {} template const T& Stat::getBase() const @@ -22,23 +22,42 @@ namespace MWMechanics { return std::max(static_cast(0), mModified); } + + template + T Stat::getCurrentModified() const + { + return mCurrentModified; + } + template T Stat::getModifier() const { return mModified-mBase; } + + template + T Stat::getCurrentModifier() const + { + return mCurrentModified - mModified; + } + template void Stat::set (const T& value) { + T diff = value - mBase; mBase = mModified = value; + mCurrentModified += diff; } + template void Stat::setBase (const T& value) { T diff = value - mBase; mBase = value; mModified += diff; + mCurrentModified += diff; } + template void Stat::setModified (T value, const T& min, const T& max) { @@ -57,24 +76,39 @@ namespace MWMechanics mModified = value; mBase += diff; + mCurrentModified += diff; } + + template + void Stat::setCurrentModified(T value) + { + mCurrentModified = value; + } + template void Stat::setModifier (const T& modifier) { mModified = mBase + modifier; } + template + void Stat::setCurrentModifier(const T& modifier) + { + mCurrentModified = mModified + modifier; + } + template void Stat::writeState (ESM::StatState& state) const { state.mBase = mBase; - state.mMod = mModified; + state.mMod = mCurrentModified; } template void Stat::readState (const ESM::StatState& state) { mBase = state.mBase; - mModified = state.mMod; + mModified = state.mBase; + mCurrentModified = state.mMod; } @@ -98,6 +132,12 @@ namespace MWMechanics { return mStatic.getModified(); } + template + T DynamicStat::getCurrentModified() const + { + return mStatic.getCurrentModified(); + } + template const T& DynamicStat::getCurrent() const { @@ -127,14 +167,21 @@ namespace MWMechanics mCurrent = getModified(); } template - void DynamicStat::setCurrent (const T& value, bool allowDecreaseBelowZero) + void DynamicStat::setCurrentModified(T value) + { + mStatic.setCurrentModified(value); + } + template + void DynamicStat::setCurrent (const T& value, bool allowDecreaseBelowZero, bool allowIncreaseAboveModified) { if (value > mCurrent) { // increase - mCurrent = value; - - if (mCurrent > getModified()) + if (value <= getModified() || allowIncreaseAboveModified) + mCurrent = value; + else if (mCurrent > getModified()) + return; + else mCurrent = getModified(); } else if (value > 0 || allowDecreaseBelowZero) @@ -156,6 +203,16 @@ namespace MWMechanics setCurrent (getCurrent()+diff, allowCurrentDecreaseBelowZero); } + template + void DynamicStat::setCurrentModifier(const T& modifier, bool allowCurrentDecreaseBelowZero) + { + T diff = modifier - mStatic.getCurrentModifier(); + mStatic.setCurrentModifier(modifier); + + // The (modifier > 0) check here allows increase over modified only if the modifier is positive (a fortify effect is active). + setCurrent (getCurrent() + diff, allowCurrentDecreaseBelowZero, (modifier > 0)); + } + template void DynamicStat::writeState (ESM::StatState& state) const { diff --git a/apps/openmw/mwmechanics/stat.hpp b/apps/openmw/mwmechanics/stat.hpp index 5b58006344..751b80b9c2 100644 --- a/apps/openmw/mwmechanics/stat.hpp +++ b/apps/openmw/mwmechanics/stat.hpp @@ -17,6 +17,7 @@ namespace MWMechanics { T mBase; T mModified; + T mCurrentModified; public: typedef T Type; @@ -28,7 +29,9 @@ namespace MWMechanics const T& getBase() const; T getModified() const; + T getCurrentModified() const; T getModifier() const; + T getCurrentModifier() const; /// Set base and modified to \a value. void set (const T& value); @@ -36,9 +39,15 @@ namespace MWMechanics /// Set base and adjust modified accordingly. void setBase (const T& value); - /// Set modified value an adjust base accordingly. + /// Set modified value and adjust base accordingly. void setModified (T value, const T& min, const T& max = std::numeric_limits::max()); + + /// Set "current modified," used for drain and fortify. Unlike the regular modifier + /// this just adds and subtracts from the current value without changing the maximum. + void setCurrentModified(T value); + void setModifier (const T& modifier); + void setCurrentModifier (const T& modifier); void writeState (ESM::StatState& state) const; void readState (const ESM::StatState& state); @@ -73,6 +82,7 @@ namespace MWMechanics const T& getBase() const; T getModified() const; + T getCurrentModified() const; const T& getCurrent() const; /// Set base, modified and current to \a value. @@ -81,11 +91,16 @@ namespace MWMechanics /// Set base and adjust modified accordingly. void setBase (const T& value); - /// Set modified value an adjust base accordingly. + /// Set modified value and adjust base accordingly. void setModified (T value, const T& min, const T& max = std::numeric_limits::max()); - void setCurrent (const T& value, bool allowDecreaseBelowZero = false); - void setModifier (const T& modifier, bool allowCurrentDecreaseBelowZero=false); + /// Set "current modified," used for drain and fortify. Unlike the regular modifier + /// this just adds and subtracts from the current value without changing the maximum. + void setCurrentModified(T value); + + void setCurrent (const T& value, bool allowDecreaseBelowZero = false, bool allowIncreaseAboveModified = false); + void setModifier (const T& modifier, bool allowCurrentToDecreaseBelowZero=false); + void setCurrentModifier (const T& modifier, bool allowCurrentToDecreaseBelowZero = false); void writeState (ESM::StatState& state) const; void readState (const ESM::StatState& state); diff --git a/apps/openmw/mwmechanics/summoning.cpp b/apps/openmw/mwmechanics/summoning.cpp index 71c49f9df8..b3f7afc531 100644 --- a/apps/openmw/mwmechanics/summoning.cpp +++ b/apps/openmw/mwmechanics/summoning.cpp @@ -1,6 +1,6 @@ #include "summoning.hpp" -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -76,7 +76,7 @@ namespace MWMechanics } catch (std::exception& e) { - std::cerr << "Failed to spawn summoned creature: " << e.what() << std::endl; + Log(Debug::Error) << "Failed to spawn summoned creature: " << e.what(); // still insert into creatureMap so we don't try to spawn again every frame, that would spam the warning log } diff --git a/apps/openmw/mwmechanics/trading.cpp b/apps/openmw/mwmechanics/trading.cpp index eee5e64499..b824d7c450 100644 --- a/apps/openmw/mwmechanics/trading.cpp +++ b/apps/openmw/mwmechanics/trading.cpp @@ -50,11 +50,11 @@ namespace MWMechanics float e1 = 0.1f * merchantStats.getAttribute(ESM::Attribute::Luck).getModified(); float f1 = 0.2f * merchantStats.getAttribute(ESM::Attribute::Personality).getModified(); - float dispositionTerm = gmst.find("fDispositionMod")->getFloat() * (clampedDisposition - 50); + float dispositionTerm = gmst.find("fDispositionMod")->mValue.getFloat() * (clampedDisposition - 50); float pcTerm = (dispositionTerm + a1 + b1 + c1) * playerStats.getFatigueTerm(); float npcTerm = (d1 + e1 + f1) * merchantStats.getFatigueTerm(); - float x = gmst.find("fBargainOfferMulti")->getFloat() * d - + gmst.find("fBargainOfferBase")->getFloat() + float x = gmst.find("fBargainOfferMulti")->mValue.getFloat() * d + + gmst.find("fBargainOfferBase")->mValue.getFloat() + int(pcTerm - npcTerm); int roll = Misc::Rng::rollDice(100) + 1; diff --git a/apps/openmw/mwmechanics/weaponpriority.cpp b/apps/openmw/mwmechanics/weaponpriority.cpp index 5a0bd9c154..818065ae44 100644 --- a/apps/openmw/mwmechanics/weaponpriority.cpp +++ b/apps/openmw/mwmechanics/weaponpriority.cpp @@ -21,7 +21,10 @@ namespace MWMechanics float rateWeapon (const MWWorld::Ptr &item, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy, int type, float arrowRating, float boltRating) { - if (item.getTypeName() != typeid(ESM::Weapon).name()) + if (enemy.isEmpty() || item.getTypeName() != typeid(ESM::Weapon).name()) + return 0.f; + + if (item.getClass().hasItemHealth(item) && item.getClass().getItemHealth(item) == 0) return 0.f; const ESM::Weapon* weapon = item.get()->mBase; @@ -29,52 +32,46 @@ namespace MWMechanics if (type != -1 && weapon->mData.mType != type) return 0.f; + const MWBase::World* world = MWBase::Environment::get().getWorld(); + const MWWorld::Store& gmst = world->getStore().get(); + + if (type == -1 && (weapon->mData.mType == ESM::Weapon::Arrow || weapon->mData.mType == ESM::Weapon::Bolt)) + return 0.f; + float rating=0.f; - float bonus=0.f; + static const float fAIMeleeWeaponMult = gmst.find("fAIMeleeWeaponMult")->mValue.getFloat(); + float ratingMult = fAIMeleeWeaponMult; if (weapon->mData.mType >= ESM::Weapon::MarksmanBow && weapon->mData.mType <= ESM::Weapon::MarksmanThrown) { - // Range weapon is useless under water - if (MWBase::Environment::get().getWorld()->isUnderwater(MWWorld::ConstPtr(actor), 0.75f)) + // Underwater ranged combat is impossible + if (world->isUnderwater(MWWorld::ConstPtr(actor), 0.75f) + || world->isUnderwater(MWWorld::ConstPtr(enemy), 0.75f)) return 0.f; - if (enemy.isEmpty()) - return 0.f; - - if (MWBase::Environment::get().getWorld()->isUnderwater(MWWorld::ConstPtr(enemy), 0.75f)) - return 0.f; - - bonus+=1.5f; + // Use a higher rating multiplier if the actor is out of enemy's reach, use the normal mult otherwise + if (getDistanceMinusHalfExtents(actor, enemy) >= getMaxAttackDistance(enemy)) + { + static const float fAIRangeMeleeWeaponMult = gmst.find("fAIRangeMeleeWeaponMult")->mValue.getFloat(); + ratingMult = fAIRangeMeleeWeaponMult; + } } + const float chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1]) / 2.f; if (weapon->mData.mType >= ESM::Weapon::MarksmanBow) - { - rating = (weapon->mData.mChop[0] + weapon->mData.mChop[1]) / 2.f; - - if (weapon->mData.mType >= ESM::Weapon::MarksmanThrown) - MWMechanics::resistNormalWeapon(enemy, actor, item, rating); - } + rating = chop; else { - for (int i=0; i<2; ++i) - { - rating += weapon->mData.mSlash[i]; - rating += weapon->mData.mThrust[i]; - rating += weapon->mData.mChop[i]; - } - rating /= 6.f; - - MWMechanics::resistNormalWeapon(enemy, actor, item, rating); + const float slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1]) / 2.f; + const float thrust = (weapon->mData.mThrust[0] + weapon->mData.mThrust[1]) / 2.f; + rating = (slash * slash + thrust * thrust + chop * chop) / (slash + thrust + chop); } - if (item.getClass().hasItemHealth(item)) - { - if (item.getClass().getItemHealth(item) == 0) - return 0.f; - rating *= item.getClass().getItemHealth(item) / float(item.getClass().getItemMaxHealth(item)); - } + adjustWeaponDamage(rating, item, actor); - if (weapon->mData.mType == ESM::Weapon::MarksmanBow) + if (weapon->mData.mType != ESM::Weapon::MarksmanBow && weapon->mData.mType != ESM::Weapon::MarksmanCrossbow) + resistNormalWeapon(enemy, actor, item, rating); + else if (weapon->mData.mType == ESM::Weapon::MarksmanBow) { if (arrowRating <= 0.f) rating = 0.f; @@ -91,7 +88,7 @@ namespace MWMechanics if (!weapon->mEnchant.empty()) { - const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find(weapon->mEnchant); + const ESM::Enchantment* enchantment = world->getStore().get().find(weapon->mEnchant); if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes) { int castCost = getEffectiveEnchantmentCastCost(static_cast(enchantment->mData.mCost), actor); @@ -101,15 +98,31 @@ namespace MWMechanics } } - int skill = item.getClass().getEquipmentSkill(item); - if (skill != -1) - rating *= actor.getClass().getSkill(actor, skill) / 100.f; + if (enemy.getClass().isNpc()) + { + static const float fCombatArmorMinMult = gmst.find("fCombatArmorMinMult")->mValue.getFloat(); + rating *= std::max(fCombatArmorMinMult, rating / (rating + enemy.getClass().getArmorRating(enemy))); + } - // There is no need to apply bonus if weapon rating == 0 - if (rating == 0.f) - return 0.f; + int value = 50.f; + if (actor.getClass().isNpc()) + { + int skill = item.getClass().getEquipmentSkill(item); + if (skill != -1) + value = actor.getClass().getSkill(actor, skill); + } + else + { + MWWorld::LiveCellRef *ref = actor.get(); + value = ref->mBase->mData.mCombat; + } - return rating + bonus; + rating *= getHitChance(actor, enemy, value) / 100.f; + + if (weapon->mData.mType < ESM::Weapon::MarksmanBow) + rating *= weapon->mData.mSpeed; + + return rating * ratingMult; } float rateAmmo(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy, MWWorld::Ptr &bestAmmo, ESM::Weapon::Type ammoType) @@ -143,9 +156,9 @@ namespace MWMechanics { const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); - static const float fAIMeleeWeaponMult = gmst.find("fAIMeleeWeaponMult")->getFloat(); - static const float fAIMeleeArmorMult = gmst.find("fAIMeleeArmorMult")->getFloat(); - static const float fAIRangeMeleeWeaponMult = gmst.find("fAIRangeMeleeWeaponMult")->getFloat(); + static const float fAIMeleeWeaponMult = gmst.find("fAIMeleeWeaponMult")->mValue.getFloat(); + static const float fAIMeleeArmorMult = gmst.find("fAIMeleeArmorMult")->mValue.getFloat(); + static const float fAIRangeMeleeWeaponMult = gmst.find("fAIRangeMeleeWeaponMult")->mValue.getFloat(); if (weapon.isEmpty()) return 0.f; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index ae8d76a809..90668914f2 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -1,6 +1,5 @@ #include "physicssystem.hpp" -#include #include #include @@ -20,7 +19,7 @@ #include #include #include - +#include #include #include #include @@ -304,7 +303,7 @@ namespace MWPhysics position.z() += halfExtents.z(); static const float fSwimHeightScale = MWBase::Environment::get().getWorld()->getStore().get() - .find("fSwimHeightScale")->getFloat(); + .find("fSwimHeightScale")->mValue.getFloat(); float swimlevel = waterlevel + halfExtents.z() - (physicActor->getRenderingHalfExtents().z() * 2 * fSwimHeightScale); ActorTracer tracer; @@ -340,7 +339,7 @@ namespace MWPhysics osg::Vec3f stormDirection = MWBase::Environment::get().getWorld()->getStormDirection(); float angleDegrees = osg::RadiansToDegrees(std::acos(stormDirection * velocity / (stormDirection.length() * velocity.length()))); static const float fStromWalkMult = MWBase::Environment::get().getWorld()->getStore().get() - .find("fStromWalkMult")->getFloat(); + .find("fStromWalkMult")->mValue.getFloat(); velocity *= 1.f-(fStromWalkMult * (angleDegrees/180.f)); } @@ -638,7 +637,7 @@ namespace MWPhysics mPtr.getRefData().getBaseNode()->accept(visitor); if (!visitor.mFound) { - std::cerr << "Error: animateCollisionShapes can't find node " << recIndex << " for " << mPtr.getCellRef().getRefId() << std::endl; + Log(Debug::Warning) << "Warning: animateCollisionShapes can't find node " << recIndex << " for " << mPtr.getCellRef().getRefId(); // Remove nonexistent nodes from animated shapes map and early out mShapeInstance->mAnimatedShapes.erase(recIndex); @@ -651,7 +650,6 @@ namespace MWPhysics osg::NodePath& nodePath = nodePathFound->second; osg::Matrixf matrix = osg::computeLocalToWorld(nodePath); - osg::Vec3f scale = matrix.getScale(); matrix.orthoNormalize(matrix); btTransform transform; @@ -660,8 +658,8 @@ namespace MWPhysics for (int j=0; j<3; ++j) transform.getBasis()[i][j] = matrix(j,i); // NB column/row major difference - if (compound->getLocalScaling() * toBullet(scale) != compound->getChildShape(shapeIndex)->getLocalScaling()) - compound->getChildShape(shapeIndex)->setLocalScaling(compound->getLocalScaling() * toBullet(scale)); + // Note: we can not apply scaling here for now since we treat scaled shapes + // as new shapes (btScaledBvhTriangleMeshShape) with 1.0 scale for now if (!(transform == compound->getChildTransform(shapeIndex))) compound->updateChildTransform(shapeIndex, transform); } @@ -708,7 +706,7 @@ namespace MWPhysics if (physFramerate > 0) { mPhysicsDt = 1.f / physFramerate; - std::cerr << "Warning: physics framerate was overridden (a new value is " << physFramerate << ")." << std::endl; + Log(Debug::Warning) << "Warning: using custom physics framerate (" << physFramerate << " FPS)."; } } } @@ -862,8 +860,8 @@ namespace MWPhysics // Use cone shape as fallback const MWWorld::Store &store = MWBase::Environment::get().getWorld()->getStore().get(); - btConeShape shape (osg::DegreesToRadians(store.find("fCombatAngleXY")->getFloat()/2.0f), queryDistance); - shape.setLocalScaling(btVector3(1, 1, osg::DegreesToRadians(store.find("fCombatAngleZ")->getFloat()/2.0f) / + btConeShape shape (osg::DegreesToRadians(store.find("fCombatAngleXY")->mValue.getFloat()/2.0f), queryDistance); + shape.setLocalScaling(btVector3(1, 1, osg::DegreesToRadians(store.find("fCombatAngleZ")->mValue.getFloat()/2.0f) / shape.getRadius())); // The shape origin is its center, so we have to move it forward by half the length. The diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index ff31e1cfb2..1ace2f8b4b 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -8,10 +8,13 @@ #include #include #include +#include #include #include +#include + #include #include @@ -192,7 +195,7 @@ namespace for (RemoveVec::iterator it = mToRemove.begin(); it != mToRemove.end(); ++it) { if (!it->second->removeChild(it->first)) - std::cerr << "error removing " << it->first->getName() << std::endl; + Log(Debug::Error) << "Error removing " << it->first->getName(); } } @@ -202,6 +205,116 @@ namespace std::vector > mToRemove; }; + class RemoveFinishedCallbackVisitor : public RemoveVisitor + { + public: + bool mHasMagicEffects; + + RemoveFinishedCallbackVisitor() + : RemoveVisitor() + , mHasMagicEffects(false) + , mEffectId(-1) + { + } + + RemoveFinishedCallbackVisitor(int effectId) + : RemoveVisitor() + , mEffectId(effectId) + { + } + + virtual void apply(osg::Node &node) + { + traverse(node); + } + + virtual void apply(osg::Group &group) + { + traverse(group); + + osg::Callback* callback = group.getUpdateCallback(); + if (callback) + { + // We should remove empty transformation nodes and finished callbacks here + MWRender::UpdateVfxCallback* vfxCallback = dynamic_cast(callback); + if (vfxCallback) + { + bool finished = vfxCallback->mFinished; + bool toRemove = mEffectId >= 0 && vfxCallback->mParams.mEffectId == mEffectId; + if (finished || toRemove) + mToRemove.push_back(std::make_pair(group.asNode(), group.getParent(0))); + else + mHasMagicEffects = true; + } + } + } + + virtual void apply(osg::MatrixTransform &node) + { + traverse(node); + } + + virtual void apply(osg::Geometry&) + { + } + + private: + int mEffectId; + }; + + class FindVfxCallbacksVisitor : public osg::NodeVisitor + { + public: + + std::vector mCallbacks; + + FindVfxCallbacksVisitor() + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mEffectId(-1) + { + } + + FindVfxCallbacksVisitor(int effectId) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mEffectId(effectId) + { + } + + virtual void apply(osg::Node &node) + { + traverse(node); + } + + virtual void apply(osg::Group &group) + { + osg::Callback* callback = group.getUpdateCallback(); + if (callback) + { + MWRender::UpdateVfxCallback* vfxCallback = dynamic_cast(callback); + if (vfxCallback) + { + if (mEffectId < 0 || vfxCallback->mParams.mEffectId == mEffectId) + { + mCallbacks.push_back(vfxCallback); + } + } + } + traverse(group); + } + + virtual void apply(osg::MatrixTransform &node) + { + traverse(node); + } + + virtual void apply(osg::Geometry&) + { + } + + private: + int mEffectId; + }; + // Removes all drawables from a graph. class CleanObjectRootVisitor : public RemoveVisitor { @@ -285,7 +398,6 @@ namespace } } }; - } namespace MWRender @@ -430,6 +542,42 @@ namespace MWRender const std::multimap& getTextKeys() const; }; + void UpdateVfxCallback::operator()(osg::Node* node, osg::NodeVisitor* nv) + { + traverse(node, nv); + + if (mFinished) + return; + + double newTime = nv->getFrameStamp()->getSimulationTime(); + if (mStartingTime == 0) + { + mStartingTime = newTime; + return; + } + + double duration = newTime - mStartingTime; + mStartingTime = newTime; + + mParams.mAnimTime->addTime(duration); + if (mParams.mAnimTime->getTime() >= mParams.mMaxControllerLength) + { + if (mParams.mLoop) + { + // Start from the beginning again; carry over the remainder + // Not sure if this is actually needed, the controller function might already handle loops + float remainder = mParams.mAnimTime->getTime() - mParams.mMaxControllerLength; + mParams.mAnimTime->resetTime(remainder); + } + else + { + // Remove effect immediately + mParams.mObjects.reset(); + mFinished = true; + } + } + } + class ResetAccumRootCallback : public osg::NodeCallback { public: @@ -468,6 +616,7 @@ namespace MWRender , mTextKeyListener(NULL) , mHeadYawRadians(0.f) , mHeadPitchRadians(0.f) + , mHasMagicEffects(false) , mAlpha(1.f) { for(size_t i = 0;i < sNumBlendMasks;i++) @@ -612,7 +761,7 @@ namespace MWRender NodeMap::const_iterator found = nodeMap.find(bonename); if (found == nodeMap.end()) { - std::cerr << "Warning: addAnimSource: can't find bone '" + bonename << "' in " << baseModel << " (referenced by " << kfname << ")" << std::endl; + Log(Debug::Warning) << "Warning: addAnimSource: can't find bone '" + bonename << "' in " << baseModel << " (referenced by " << kfname << ")"; continue; } @@ -724,7 +873,7 @@ namespace MWRender } catch (std::exception& e) { - std::cerr << "Error handling text key " << evt << ": " << e.what() << std::endl; + Log(Debug::Error) << "Error handling text key " << evt << ": " << e.what(); } } } @@ -1179,7 +1328,7 @@ namespace MWRender ++stateiter; } - updateEffects(duration); + updateEffects(); if (mHeadController) { @@ -1434,15 +1583,22 @@ namespace MWRender useQuadratic, quadraticValue, quadraticRadiusMult, useLinear, linearRadiusMult, linearValue); } - void Animation::addEffect (const std::string& model, int effectId, bool loop, const std::string& bonename, const std::string& texture) + void Animation::addEffect (const std::string& model, int effectId, bool loop, const std::string& bonename, const std::string& texture, float scale) { if (!mObjectRoot.get()) return; // Early out if we already have this effect - for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) - if (it->mLoop && loop && it->mEffectId == effectId && it->mBoneName == bonename) + FindVfxCallbacksVisitor visitor(effectId); + mInsert->accept(visitor); + + for (std::vector::iterator it = visitor.mCallbacks.begin(); it != visitor.mCallbacks.end(); ++it) + { + UpdateVfxCallback* callback = *it; + + if (loop && !callback->mFinished && callback->mParams.mLoop && callback->mParams.mBoneName == bonename) return; + } EffectParams params; params.mModelName = model; @@ -1457,83 +1613,77 @@ namespace MWRender parentNode = found->second; } - osg::ref_ptr node = mResourceSystem->getSceneManager()->getInstance(model, parentNode); + osg::ref_ptr trans = new osg::PositionAttitudeTransform; + trans->setScale(osg::Vec3f(scale, scale, scale)); + parentNode->addChild(trans); + + osg::ref_ptr node = mResourceSystem->getSceneManager()->getInstance(model, trans); node->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); - params.mObjects = PartHolderPtr(new PartHolder(node)); - SceneUtil::FindMaxControllerLengthVisitor findMaxLengthVisitor; node->accept(findMaxLengthVisitor); // FreezeOnCull doesn't work so well with effect particles, that tend to have moving emitters SceneUtil::DisableFreezeOnCullVisitor disableFreezeOnCullVisitor; node->accept(disableFreezeOnCullVisitor); - - params.mMaxControllerLength = findMaxLengthVisitor.getMaxLength(); - node->setNodeMask(Mask_Effect); + params.mMaxControllerLength = findMaxLengthVisitor.getMaxLength(); params.mLoop = loop; params.mEffectId = effectId; params.mBoneName = bonename; - + params.mObjects = PartHolderPtr(new PartHolder(node)); params.mAnimTime = std::shared_ptr(new EffectAnimationTime); + trans->addUpdateCallback(new UpdateVfxCallback(params)); SceneUtil::AssignControllerSourcesVisitor assignVisitor(std::shared_ptr(params.mAnimTime)); node->accept(assignVisitor); + // Notify that this animation has attached magic effects + mHasMagicEffects = true; + overrideFirstRootTexture(texture, mResourceSystem, node); - - // TODO: in vanilla morrowind the effect is scaled based on the host object's bounding box. - - mEffects.push_back(params); } void Animation::removeEffect(int effectId) { - for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) - { - if (it->mEffectId == effectId) - { - mEffects.erase(it); - return; - } - } + RemoveFinishedCallbackVisitor visitor(effectId); + mInsert->accept(visitor); + visitor.remove(); + mHasMagicEffects = visitor.mHasMagicEffects; } void Animation::getLoopingEffects(std::vector &out) const { - for (std::vector::const_iterator it = mEffects.begin(); it != mEffects.end(); ++it) + if (!mHasMagicEffects) + return; + + FindVfxCallbacksVisitor visitor; + mInsert->accept(visitor); + + for (std::vector::iterator it = visitor.mCallbacks.begin(); it != visitor.mCallbacks.end(); ++it) { - if (it->mLoop) - out.push_back(it->mEffectId); + UpdateVfxCallback* callback = *it; + + if (callback->mParams.mLoop && !callback->mFinished) + out.push_back(callback->mParams.mEffectId); } } - void Animation::updateEffects(float duration) + void Animation::updateEffects() { - for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ) - { - it->mAnimTime->addTime(duration); + // We do not need to visit scene every frame. + // We can use a bool flag to check in spellcasting effect found. + if (!mHasMagicEffects) + return; - if (it->mAnimTime->getTime() >= it->mMaxControllerLength) - { - if (it->mLoop) - { - // Start from the beginning again; carry over the remainder - // Not sure if this is actually needed, the controller function might already handle loops - float remainder = it->mAnimTime->getTime() - it->mMaxControllerLength; - it->mAnimTime->resetTime(remainder); - } - else - { - it = mEffects.erase(it); - continue; - } - } - ++it; - } + // TODO: objects without animation still will have + // transformation nodes with finished callbacks + RemoveFinishedCallbackVisitor visitor; + mInsert->accept(visitor); + visitor.remove(); + mHasMagicEffects = visitor.mHasMagicEffects; } bool Animation::upperBodyReady() const @@ -1767,14 +1917,13 @@ namespace MWRender PartHolder::~PartHolder() { if (mNode.get() && !mNode->getNumParents()) - std::cerr << "Error: part has no parents " << std::endl; + Log(Debug::Verbose) << "Part has no parents" ; if (mNode.get() && mNode->getNumParents()) { if (mNode->getNumParents() > 1) - std::cerr << "Error: part has multiple parents " << mNode->getNumParents() << " " << mNode.get() << std::endl; + Log(Debug::Verbose) << "Part has multiple (" << mNode->getNumParents() << ") parents"; mNode->getParent(0)->removeChild(mNode); } } - } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 43a6268994..e10d2c995a 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -71,6 +71,17 @@ private: }; typedef std::shared_ptr PartHolderPtr; +struct EffectParams +{ + std::string mModelName; // Just here so we don't add the same effect twice + PartHolderPtr mObjects; + std::shared_ptr mAnimTime; + float mMaxControllerLength; + int mEffectId; + bool mLoop; + std::string mBoneName; +}; + class Animation : public osg::Referenced { public: @@ -247,24 +258,12 @@ protected: osg::Vec3f mAccumulate; - struct EffectParams - { - std::string mModelName; // Just here so we don't add the same effect twice - PartHolderPtr mObjects; - std::shared_ptr mAnimTime; - float mMaxControllerLength; - int mEffectId; - bool mLoop; - std::string mBoneName; - }; - - std::vector mEffects; - TextKeyListener* mTextKeyListener; osg::ref_ptr mHeadController; float mHeadYawRadians; float mHeadPitchRadians; + bool mHasMagicEffects; osg::ref_ptr mGlowLight; osg::ref_ptr mGlowUpdater; @@ -369,7 +368,7 @@ public: * @param texture override the texture specified in the model's materials - if empty, do not override * @note Will not add an effect twice. */ - void addEffect (const std::string& model, int effectId, bool loop = false, const std::string& bonename = "", const std::string& texture = ""); + void addEffect (const std::string& model, int effectId, bool loop = false, const std::string& bonename = "", const std::string& texture = "", float scale = 1.0f); void removeEffect (int effectId); void getLoopingEffects (std::vector& out) const; @@ -452,7 +451,7 @@ public: void setLoopingEnabled(const std::string &groupname, bool enabled); /// This is typically called as part of runAnimation, but may be called manually if needed. - void updateEffects(float duration); + void updateEffects(); /// Return a node with the specified name, or NULL if not existing. /// @note The matching is case-insensitive. @@ -489,5 +488,24 @@ public: ObjectAnimation(const MWWorld::Ptr& ptr, const std::string &model, Resource::ResourceSystem* resourceSystem, bool animated, bool allowLight); }; +class UpdateVfxCallback : public osg::NodeCallback +{ +public: + UpdateVfxCallback(EffectParams& params) + : mFinished(false) + , mParams(params) + , mStartingTime(0) + { + } + + bool mFinished; + EffectParams mParams; + + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv); + +private: + double mStartingTime; +}; + } #endif diff --git a/apps/openmw/mwrender/bulletdebugdraw.cpp b/apps/openmw/mwrender/bulletdebugdraw.cpp index d35c3ac5de..eb3775cb46 100644 --- a/apps/openmw/mwrender/bulletdebugdraw.cpp +++ b/apps/openmw/mwrender/bulletdebugdraw.cpp @@ -1,12 +1,12 @@ #include "bulletdebugdraw.hpp" -#include - #include #include #include +#include + #include "vismask.hpp" namespace @@ -91,7 +91,7 @@ void DebugDrawer::drawContactPoint(const btVector3 &PointOnB, const btVector3 &n void DebugDrawer::reportErrorWarning(const char *warningString) { - std::cerr << warningString << std::endl; + Log(Debug::Warning) << warningString; } void DebugDrawer::setDebugMode(int isOn) diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index faaa3799eb..2e0249e608 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -1,7 +1,6 @@ #include "characterpreview.hpp" #include -#include #include #include @@ -14,6 +13,7 @@ #include #include +#include #include #include @@ -474,7 +474,7 @@ namespace MWRender mCamera->addUpdateCallback(mUpdateCameraCallback); } else - std::cerr << "Error: Bip01 Head node not found" << std::endl; + Log(Debug::Error) << "Error: Bip01 Head node not found"; } } diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 827b576c33..6db223bd54 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -1,11 +1,9 @@ #include "creatureanimation.hpp" -#include - #include #include - +#include #include #include #include @@ -155,7 +153,7 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot) } catch (std::exception& e) { - std::cerr << "Can not add creature part: " << e.what() << std::endl; + Log(Debug::Error) << "Can not add creature part: " << e.what(); } } diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index af2bb101a8..fae524faa7 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -15,6 +15,8 @@ #include #include +#include + #include #include @@ -411,14 +413,14 @@ namespace MWRender osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("png"); if (!readerwriter) { - std::cerr << "Error: Can't write map overlay: no png readerwriter found" << std::endl; + Log(Debug::Error) << "Error: Can't write map overlay: no png readerwriter found"; return; } osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(*mOverlayImage, ostream); if (!result.success()) { - std::cerr << "Error: Can't write map overlay: " << result.message() << " code " << result.status() << std::endl; + Log(Debug::Warning) << "Error: Can't write map overlay: " << result.message() << " code " << result.status(); return; } @@ -463,14 +465,14 @@ namespace MWRender osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("png"); if (!readerwriter) { - std::cerr << "Error: Can't read map overlay: no png readerwriter found" << std::endl; + Log(Debug::Error) << "Error: Can't read map overlay: no png readerwriter found"; return; } osgDB::ReaderWriter::ReadResult result = readerwriter->readImage(istream); if (!result.success()) { - std::cerr << "Error: Can't read map overlay: " << result.message() << " code " << result.status() << std::endl; + Log(Debug::Error) << "Error: Can't read map overlay: " << result.message() << " code " << result.status(); return; } @@ -572,7 +574,7 @@ namespace MWRender CameraVector::iterator found = std::find(mActiveCameras.begin(), mActiveCameras.end(), camera); if (found == mActiveCameras.end()) { - std::cerr << "Error: GlobalMap trying to remove an inactive camera" << std::endl; + Log(Debug::Error) << "Error: GlobalMap trying to remove an inactive camera"; return; } mActiveCameras.erase(found); diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 0b65a6b13d..5e501ecf86 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -1,6 +1,5 @@ #include "localmap.hpp" -#include #include #include @@ -12,6 +11,7 @@ #include +#include #include #include #include @@ -320,7 +320,7 @@ void LocalMap::markForRemoval(osg::Camera *cam) CameraVector::iterator found = std::find(mActiveCameras.begin(), mActiveCameras.end(), cam); if (found == mActiveCameras.end()) { - std::cerr << "Error: trying to remove an inactive camera" << std::endl; + Log(Debug::Error) << "Error: trying to remove an inactive camera"; return; } mActiveCameras.erase(found); @@ -492,7 +492,7 @@ void LocalMap::requestInteriorMap(const MWWorld::CellStore* cell) // 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())) { - std::cout << "Error: fog texture count mismatch" << std::endl; + Log(Debug::Warning) << "Warning: fog texture count mismatch"; break; } @@ -684,7 +684,7 @@ void LocalMap::MapSegment::loadFogOfWar(const ESM::FogTexture &esm) osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("tga"); if (!readerwriter) { - std::cerr << "Error: Unable to load fog, can't find a tga ReaderWriter" << std::endl; + Log(Debug::Error) << "Error: Unable to load fog, can't find a tga ReaderWriter" ; return; } @@ -693,7 +693,7 @@ void LocalMap::MapSegment::loadFogOfWar(const ESM::FogTexture &esm) osgDB::ReaderWriter::ReadResult result = readerwriter->readImage(in); if (!result.success()) { - std::cerr << "Error: Failed to read fog: " << result.message() << " code " << result.status() << std::endl; + Log(Debug::Error) << "Error: Failed to read fog: " << result.message() << " code " << result.status(); return; } @@ -716,7 +716,7 @@ void LocalMap::MapSegment::saveFogOfWar(ESM::FogTexture &fog) const osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("tga"); if (!readerwriter) { - std::cerr << "Error: Unable to write fog, can't find a tga ReaderWriter" << std::endl; + Log(Debug::Error) << "Error: Unable to write fog, can't find a tga ReaderWriter"; return; } @@ -725,7 +725,7 @@ void LocalMap::MapSegment::saveFogOfWar(ESM::FogTexture &fog) const osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(*mFogOfWarImage, ostream); if (!result.success()) { - std::cerr << "Error: Unable to write fog: " << result.message() << " code " << result.status() << std::endl; + Log(Debug::Error) << "Error: Unable to write fog: " << result.message() << " code " << result.status(); return; } mFogOfWarImage->flipVertical(); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index baf6cb8229..98f8ce892d 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -7,6 +7,8 @@ #include #include +#include + #include #include @@ -435,7 +437,7 @@ void NpcAnimation::updateNpcBase() if (bp) mHeadModel = "meshes\\" + bp->mModel; else - std::cerr << "Warning: Failed to load body part '" << mNpc->mHead << "'" << std::endl; + Log(Debug::Warning) << "Warning: Failed to load body part '" << mNpc->mHead << "'"; } mHairModel = ""; @@ -445,7 +447,7 @@ void NpcAnimation::updateNpcBase() if (bp) mHairModel = "meshes\\" + bp->mModel; else - std::cerr << "Warning: Failed to load body part '" << mNpc->mHair << "'" << std::endl; + Log(Debug::Warning) << "Warning: Failed to load body part '" << mNpc->mHair << "'"; } } @@ -758,7 +760,7 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g } catch (std::exception& e) { - std::cerr << "Error adding NPC part: " << e.what() << std::endl; + Log(Debug::Error) << "Error adding NPC part: " << e.what(); return false; } @@ -845,7 +847,7 @@ void NpcAnimation::addPartGroup(int group, int priority, const std::vectormFemale << "'" << std::endl; + Log(Debug::Warning) << "Warning: Failed to find body part '" << part->mFemale << "'"; } if(!bodypart && !part->mMale.empty()) { @@ -860,7 +862,7 @@ void NpcAnimation::addPartGroup(int group, int priority, const std::vectormMale << "'" << std::endl; + Log(Debug::Warning) << "Warning: Failed to find body part '" << part->mMale << "'"; } if(bodypart) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 3dbf6475c1..ce0a41c9a0 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -22,6 +22,8 @@ #include +#include + #include #include #include @@ -47,6 +49,7 @@ #include #include "../mwworld/cellstore.hpp" +#include "../mwworld/class.hpp" #include "../mwgui/loadingscreen.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" @@ -203,6 +206,7 @@ namespace MWRender , mNightEyeFactor(0.f) , mDistantFog(false) , mDistantTerrain(false) + , mFieldOfViewOverridden(false) , mFieldOfViewOverride(0.f) , mBorders(false) { @@ -553,8 +557,8 @@ namespace MWRender mLandFogStart = mViewDistance * (1 - fogDepth); mLandFogEnd = mViewDistance; } - mUnderwaterFogStart = mViewDistance * (1 - underwaterFog); - mUnderwaterFogEnd = mViewDistance; + mUnderwaterFogStart = std::min(mViewDistance, 6666.f) * (1 - underwaterFog); + mUnderwaterFogEnd = std::min(mViewDistance, 6666.f); } mFogColor = color; } @@ -584,8 +588,6 @@ namespace MWRender mCurrentCameraPos = cameraPos; if (mWater->isUnderwater(cameraPos)) { - float viewDistance = mViewDistance; - viewDistance = std::min(viewDistance, 6666.f); setFogColor(mUnderwaterColor * mUnderwaterWeight + mFogColor * (1.f-mUnderwaterWeight)); mStateUpdater->setFogStart(mUnderwaterFogStart); mStateUpdater->setFogEnd(mUnderwaterFogEnd); @@ -691,9 +693,6 @@ namespace MWRender int screenshotW = mViewer->getCamera()->getViewport()->width(); int screenshotH = mViewer->getCamera()->getViewport()->height(); int screenshotMapping = 0; - int cubeSize = screenshotMapping == 2 ? - screenshotW: // planet mapping needs higher resolution - screenshotW / 2; std::vector settingArgs; boost::algorithm::split(settingArgs,settingStr,boost::is_any_of(" ")); @@ -713,11 +712,14 @@ namespace MWRender if (!found) { - std::cerr << "Wrong screenshot type: " << settingArgs[0] << "." << std::endl; + Log(Debug::Warning) << "Wrong screenshot type: " << settingArgs[0] << "."; return false; } } - + + // planet mapping needs higher resolution + int cubeSize = screenshotMapping == 2 ? screenshotW : screenshotW / 2; + if (settingArgs.size() > 1) screenshotW = std::min(10000,std::atoi(settingArgs[1].c_str())); @@ -729,7 +731,7 @@ namespace MWRender if (mCamera->isVanityOrPreviewModeEnabled()) { - std::cerr << "Spherical screenshots are not allowed in preview mode." << std::endl; + Log(Debug::Warning) << "Spherical screenshots are not allowed in preview mode."; return false; } @@ -1320,6 +1322,29 @@ namespace MWRender } } + osg::Vec3f RenderingManager::getHalfExtents(const MWWorld::ConstPtr& object) const + { + osg::Vec3f halfExtents(0, 0, 0); + std::string modelName = object.getClass().getModel(object); + if (modelName.empty()) + return halfExtents; + + osg::ref_ptr node = mResourceSystem->getSceneManager()->getTemplate(modelName); + osg::ComputeBoundsVisitor computeBoundsVisitor; + computeBoundsVisitor.setTraversalMask(~(MWRender::Mask_ParticleSystem|MWRender::Mask_Effect)); + const_cast(node.get())->accept(computeBoundsVisitor); + osg::BoundingBox bounds = computeBoundsVisitor.getBoundingBox(); + + if (bounds.valid()) + { + halfExtents[0] = std::abs(bounds.xMax() - bounds.xMin()) / 2.f; + halfExtents[1] = std::abs(bounds.yMax() - bounds.yMin()) / 2.f; + halfExtents[2] = std::abs(bounds.zMax() - bounds.zMin()) / 2.f; + } + + return halfExtents; + } + void RenderingManager::resetFieldOfView() { if (mFieldOfViewOverridden == true) diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index f8e7ba8bcc..b4473c3b4f 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -203,6 +203,8 @@ namespace MWRender /// reset a previous overrideFieldOfView() call, i.e. revert to field of view specified in the settings file. void resetFieldOfView(); + osg::Vec3f getHalfExtents(const MWWorld::ConstPtr& object) const; + void exportSceneGraph(const MWWorld::Ptr& ptr, const std::string& filename, const std::string& format); LandManager* getLandManager() const; diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 2e4329f691..24769019b1 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -1117,6 +1117,7 @@ SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneMana , mWindSpeed(0.f) , mEnabled(true) , mSunEnabled(true) + , mWeatherAlpha(0.f) { osg::ref_ptr skyroot (new CameraRelativeTransform); skyroot->setName("Sky Root"); diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 3c617f794e..52832ad874 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -19,6 +19,8 @@ #include #include +#include + #include #include #include @@ -29,7 +31,6 @@ #include - #include #include @@ -193,16 +194,16 @@ osg::ref_ptr readPngImage (const std::string& file) boost::filesystem::ifstream inStream; inStream.open(file, std::ios_base::in | std::ios_base::binary); if (inStream.fail()) - std::cerr << "Error: Failed to open " << file << std::endl; + Log(Debug::Error) << "Error: Failed to open " << file; osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension("png"); if (!reader) { - std::cerr << "Error: Failed to read " << file << ", no png readerwriter found" << std::endl; + Log(Debug::Error) << "Error: Failed to read " << file << ", no png readerwriter found"; return osg::ref_ptr(); } osgDB::ReaderWriter::ReadResult result = reader->readImage(inStream); if (!result.success()) - std::cerr << "Error: Failed to read " << file << ": " << result.message() << " code " << result.status() << std::endl; + Log(Debug::Error) << "Error: Failed to read " << file << ": " << result.message() << " code " << result.status(); return result.getImage(); } diff --git a/apps/openmw/mwrender/weaponanimation.cpp b/apps/openmw/mwrender/weaponanimation.cpp index 0b3d000f34..32990482a2 100644 --- a/apps/openmw/mwrender/weaponanimation.cpp +++ b/apps/openmw/mwrender/weaponanimation.cpp @@ -119,11 +119,12 @@ void WeaponAnimation::releaseArrow(MWWorld::Ptr actor, float attackStrength) return; osg::Vec3f launchPos = osg::computeLocalToWorld(nodepaths[0]).getTrans(); - float fThrownWeaponMinSpeed = gmst.find("fThrownWeaponMinSpeed")->getFloat(); - float fThrownWeaponMaxSpeed = gmst.find("fThrownWeaponMaxSpeed")->getFloat(); + float fThrownWeaponMinSpeed = gmst.find("fThrownWeaponMinSpeed")->mValue.getFloat(); + float fThrownWeaponMaxSpeed = gmst.find("fThrownWeaponMaxSpeed")->mValue.getFloat(); float speed = fThrownWeaponMinSpeed + (fThrownWeaponMaxSpeed - fThrownWeaponMinSpeed) * attackStrength; - MWBase::Environment::get().getWorld()->launchProjectile(actor, *weapon, launchPos, orient, *weapon, speed, attackStrength); + MWWorld::Ptr weaponPtr = *weapon; + MWBase::Environment::get().getWorld()->launchProjectile(actor, weaponPtr, launchPos, orient, weaponPtr, speed, attackStrength); showWeapon(false); @@ -145,13 +146,15 @@ void WeaponAnimation::releaseArrow(MWWorld::Ptr actor, float attackStrength) return; osg::Vec3f launchPos = osg::computeLocalToWorld(nodepaths[0]).getTrans(); - float fProjectileMinSpeed = gmst.find("fProjectileMinSpeed")->getFloat(); - float fProjectileMaxSpeed = gmst.find("fProjectileMaxSpeed")->getFloat(); + float fProjectileMinSpeed = gmst.find("fProjectileMinSpeed")->mValue.getFloat(); + float fProjectileMaxSpeed = gmst.find("fProjectileMaxSpeed")->mValue.getFloat(); float speed = fProjectileMinSpeed + (fProjectileMaxSpeed - fProjectileMinSpeed) * attackStrength; - MWBase::Environment::get().getWorld()->launchProjectile(actor, *ammo, launchPos, orient, *weapon, speed, attackStrength); + MWWorld::Ptr weaponPtr = *weapon; + MWWorld::Ptr ammoPtr = *ammo; + MWBase::Environment::get().getWorld()->launchProjectile(actor, ammoPtr, launchPos, orient, weaponPtr, speed, attackStrength); - inv.remove(*ammo, 1, actor); + inv.remove(ammoPtr, 1, actor); mAmmunition.reset(); } } diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index c51a55b508..0bb94a1342 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -1,7 +1,8 @@ #include "aiextensions.hpp" #include -#include + +#include #include #include @@ -50,7 +51,7 @@ namespace MWScript MWMechanics::AiActivate activatePackage(objectID); ptr.getClass().getCreatureStats (ptr).getAiSequence().stack(activatePackage, ptr); - std::cout << "AiActivate" << std::endl; + Log(Debug::Info) << "AiActivate"; } }; @@ -78,7 +79,7 @@ namespace MWScript MWMechanics::AiTravel travelPackage(x, y, z); ptr.getClass().getCreatureStats (ptr).getAiSequence().stack(travelPackage, ptr); - std::cout << "AiTravel: " << x << ", " << y << ", " << z << std::endl; + Log(Debug::Info) << "AiTravel: " << x << ", " << y << ", " << z; } }; @@ -112,8 +113,7 @@ namespace MWScript MWMechanics::AiEscort escortPackage(actorID, static_cast(duration), x, y, z); ptr.getClass().getCreatureStats (ptr).getAiSequence().stack(escortPackage, ptr); - std::cout << "AiEscort: " << x << ", " << y << ", " << z << ", " << duration - << std::endl; + Log(Debug::Info) << "AiEscort: " << x << ", " << y << ", " << z << ", " << duration; } }; @@ -155,8 +155,7 @@ namespace MWScript MWMechanics::AiEscort escortPackage(actorID, cellID, static_cast(duration), x, y, z); ptr.getClass().getCreatureStats (ptr).getAiSequence().stack(escortPackage, ptr); - std::cout << "AiEscort: " << x << ", " << y << ", " << z << ", " << duration - << std::endl; + Log(Debug::Info) << "AiEscort: " << x << ", " << y << ", " << z << ", " << duration; } }; @@ -316,8 +315,7 @@ namespace MWScript MWMechanics::AiFollow followPackage(actorID, duration, x, y ,z); ptr.getClass().getCreatureStats (ptr).getAiSequence().stack(followPackage, ptr); - std::cout << "AiFollow: " << actorID << ", " << x << ", " << y << ", " << z << ", " << duration - << std::endl; + Log(Debug::Info) << "AiFollow: " << actorID << ", " << x << ", " << y << ", " << z << ", " << duration; } }; @@ -353,8 +351,7 @@ namespace MWScript MWMechanics::AiFollow followPackage(actorID, cellID, duration, x, y ,z); ptr.getClass().getCreatureStats (ptr).getAiSequence().stack(followPackage, ptr); - std::cout << "AiFollow: " << actorID << ", " << x << ", " << y << ", " << z << ", " << duration - << std::endl; + Log(Debug::Info) << "AiFollow: " << actorID << ", " << x << ", " << y << ", " << z << ", " << duration; } }; diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp index 0feadaa59d..cf12d12c3a 100644 --- a/apps/openmw/mwscript/containerextensions.cpp +++ b/apps/openmw/mwscript/containerextensions.cpp @@ -6,6 +6,8 @@ #include +#include + #include #include @@ -190,8 +192,9 @@ namespace MWScript if (it == invStore.end()) { it = ptr.getClass().getContainerStore (ptr).add (item, 1, ptr); - std::cerr << "Implicitly adding one " << item << " to container " - "to fulfil requirements of Equip instruction" << std::endl; + Log(Debug::Warning) << "Implicitly adding one " << item << + " to the inventory store of " << ptr.getCellRef().getRefId() << + " to fulfill the requirements of Equip instruction"; } if (ptr == MWMechanics::getPlayer()) diff --git a/apps/openmw/mwscript/dialogueextensions.cpp b/apps/openmw/mwscript/dialogueextensions.cpp index 21d8d469b9..135cb787e3 100644 --- a/apps/openmw/mwscript/dialogueextensions.cpp +++ b/apps/openmw/mwscript/dialogueextensions.cpp @@ -2,7 +2,7 @@ #include #include - +#include #include #include #include @@ -134,6 +134,14 @@ namespace MWScript if (!ptr.getRefData().isEnabled()) return; + if (!ptr.getClass().isActor()) + { + const std::string error = "Warning: \"forcegreeting\" command works only for actors."; + runtime.getContext().report(error); + Log(Debug::Warning) << error; + return; + } + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Dialogue, ptr); } }; diff --git a/apps/openmw/mwscript/globalscripts.cpp b/apps/openmw/mwscript/globalscripts.cpp index 6074a12d0e..a52a4938ad 100644 --- a/apps/openmw/mwscript/globalscripts.cpp +++ b/apps/openmw/mwscript/globalscripts.cpp @@ -1,8 +1,8 @@ #include "globalscripts.hpp" #include -#include +#include #include #include #include @@ -113,9 +113,9 @@ namespace MWScript } catch (const std::exception& exception) { - std::cerr + Log(Debug::Error) << "Failed to add start script " << *iter << " because an exception has " - << "been thrown: " << exception.what() << std::endl; + << "been thrown: " << exception.what(); } } } @@ -169,10 +169,9 @@ namespace MWScript } catch (const std::exception& exception) { - std::cerr + Log(Debug::Error) << "Failed to add start script " << script.mId - << " because an exception has been thrown: " << exception.what() - << std::endl; + << " because an exception has been thrown: " << exception.what(); return true; } diff --git a/apps/openmw/mwscript/locals.cpp b/apps/openmw/mwscript/locals.cpp index 64f3058eb2..4c55731688 100644 --- a/apps/openmw/mwscript/locals.cpp +++ b/apps/openmw/mwscript/locals.cpp @@ -3,7 +3,7 @@ #include #include #include - +#include #include #include @@ -229,10 +229,10 @@ namespace MWScript } catch (std::exception& e) { - std::cerr << "Failed to read local variable state for script '" - << script << "' (legacy format): " << e.what() - << "\nNum shorts: " << numshorts << " / " << mShorts.size() - << " Num longs: " << numlongs << " / " << mLongs.size() << std::endl; + Log(Debug::Error) << "Failed to read local variable state for script '" + << script << "' (legacy format): " << e.what() + << "\nNum shorts: " << numshorts << " / " << mShorts.size() + << " Num longs: " << numlongs << " / " << mLongs.size(); } } else diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 30d0c6feee..8d592f29c6 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -1064,7 +1064,13 @@ namespace MWScript runtime.pop(); const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find (spellId); - if (spell && spell->mData.mType != ESM::Spell::ST_Spell && spell->mData.mType != ESM::Spell::ST_Power) + if (!spell) + { + runtime.getContext().report("spellcasting failed: can not find spell \""+spellId+"\""); + return; + } + + if (spell->mData.mType != ESM::Spell::ST_Spell && spell->mData.mType != ESM::Spell::ST_Power) { runtime.getContext().report("spellcasting failed: you can cast only spells and powers."); return; @@ -1082,6 +1088,7 @@ namespace MWScript MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr (targetId, false); MWMechanics::CastSpell cast(ptr, target, false, true); + cast.playSpellCastingEffects(spell->mId); cast.mHitPosition = target.getRefData().getPosition().asVec3(); cast.mAlwaysSucceed = true; cast.cast(spell); @@ -1155,8 +1162,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime &runtime) { - /// \todo implement traveling check - runtime.push (0); + runtime.push (MWBase::Environment::get().getWorld()->isPlayerTraveling()); } }; diff --git a/apps/openmw/mwscript/scriptmanagerimp.cpp b/apps/openmw/mwscript/scriptmanagerimp.cpp index 7c1f9bf4df..80496d6dbf 100644 --- a/apps/openmw/mwscript/scriptmanagerimp.cpp +++ b/apps/openmw/mwscript/scriptmanagerimp.cpp @@ -1,11 +1,12 @@ #include "scriptmanagerimp.hpp" #include -#include #include #include #include +#include + #include #include @@ -65,14 +66,14 @@ namespace MWScript } catch (const std::exception& error) { - std::cerr << "Error: An exception has been thrown: " << error.what() << std::endl; + Log(Debug::Error) << "Error: An exception has been thrown: " << error.what(); Success = false; } if (!Success) { - std::cerr - << "Warning: compiling failed: " << name << std::endl; + Log(Debug::Warning) + << "Warning: compiling failed: " << name; } if (Success) @@ -121,8 +122,8 @@ namespace MWScript } catch (const std::exception& e) { - std::cerr << "Execution of script " << name << " failed:" << std::endl; - std::cerr << e.what() << std::endl; + Log(Debug::Error) << "Execution of script " << name << " failed:"; + Log(Debug::Error) << e.what(); iter->second.first.clear(); // don't execute again. } diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index fadc99b663..29160d47e4 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -1,6 +1,5 @@ #include "statsextensions.hpp" -#include #include #include @@ -9,7 +8,7 @@ #include #include - +#include #include #include #include @@ -246,9 +245,10 @@ namespace MWScript if (R()(runtime, false, true).isEmpty()) { - std::cerr + Log(Debug::Warning) << "Warning: Compensating for broken script in Morrowind.esm by " - << "ignoring remote access to dagoth_ur_1" << std::endl; + << "ignoring remote access to dagoth_ur_1"; + return; } } @@ -261,6 +261,7 @@ namespace MWScript .getDynamic (mIndex)); stat.setModified (diff + stat.getModified(), 0); + stat.setCurrentModified (diff + stat.getCurrentModified()); stat.setCurrent (diff + current); @@ -500,6 +501,7 @@ namespace MWScript runtime.pop(); ptr.getClass().getCreatureStats (ptr).getActiveSpells().removeEffects(spellid); + ptr.getClass().getCreatureStats (ptr).getSpells().removeEffects(spellid); } }; diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 611199f723..16d7e80364 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -1,4 +1,4 @@ -#include +#include #include @@ -29,6 +29,18 @@ namespace MWScript { namespace Transformation { + void moveStandingActors(const MWWorld::Ptr &ptr, const osg::Vec3f& diff) + { + std::vector actors; + MWBase::Environment::get().getWorld()->getActorsStandingOn (ptr, actors); + for (auto& actor : actors) + { + osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3()); + actorPos += diff; + MWBase::Environment::get().getWorld()->moveObject(actor, actorPos.x(), actorPos.y(), actorPos.z()); + } + } + template class OpSetScale : public Interpreter::Opcode0 { @@ -317,7 +329,7 @@ namespace MWScript { std::string error = "Warning: PositionCell: unknown interior cell (" + cellID + "), moving to exterior instead"; runtime.getContext().report (error); - std::cerr << error << std::endl; + Log(Debug::Warning) << error; } } if(store) @@ -429,7 +441,7 @@ namespace MWScript if(!cell) { runtime.getContext().report ("unknown cell (" + cellID + ")"); - std::cerr << "unknown cell (" << cellID << ")\n"; + Log(Debug::Error) << "Error: unknown cell (" << cellID << ")"; } } if(store) @@ -666,6 +678,10 @@ namespace MWScript osg::Vec3f diff = ptr.getRefData().getBaseNode()->getAttitude() * posChange; osg::Vec3f worldPos(ptr.getRefData().getPosition().asVec3()); worldPos += diff; + + // We should move actors, standing on moving object, too. + // This approach can be used to create elevators. + moveStandingActors(ptr, diff); MWBase::Environment::get().getWorld()->moveObject(ptr, worldPos.x(), worldPos.y(), worldPos.z()); } }; @@ -688,22 +704,21 @@ namespace MWScript runtime.pop(); const float *objPos = ptr.getRefData().getPosition().pos; + osg::Vec3f diff; - MWWorld::Ptr updated; if (axis == "x") - { - updated = MWBase::Environment::get().getWorld()->moveObject(ptr, objPos[0]+movement, objPos[1], objPos[2]); - } + diff.x() += movement; else if (axis == "y") - { - updated = MWBase::Environment::get().getWorld()->moveObject(ptr, objPos[0], objPos[1]+movement, objPos[2]); - } + diff.y() += movement; else if (axis == "z") - { - updated = MWBase::Environment::get().getWorld()->moveObject(ptr, objPos[0], objPos[1], objPos[2]+movement); - } + diff.z() += movement; else throw std::runtime_error ("invalid movement axis: " + axis); + + // We should move actors, standing on moving object, too. + // This approach can be used to create elevators. + moveStandingActors(ptr, diff); + MWBase::Environment::get().getWorld()->moveObject(ptr, objPos[0]+diff.x(), objPos[1]+diff.y(), objPos[2]+diff.z()); } }; diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index f458c0a978..7dffd685a3 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -3,9 +3,9 @@ #include #include -#include #include +#include #include namespace MWSound @@ -28,7 +28,7 @@ int FFmpeg_Decoder::readPacket(void *user_data, uint8_t *buf, int buf_size) int FFmpeg_Decoder::writePacket(void *, uint8_t *, int) { - std::cerr<< "can't write to read-only stream" <codec->channels, (*mStream)->codec->channel_layout); - std::cerr<< "Unsupported channel layout: "<codec->channels == 1) { @@ -385,7 +385,7 @@ size_t FFmpeg_Decoder::read(char *buffer, size_t bytes) { if(!mStream) { - std::cerr<< "No audio stream" < &output) { if(!mStream) { - std::cerr<< "No audio stream" < #include -#include #include #include #include @@ -8,6 +7,7 @@ #include +#include #include #include @@ -44,8 +44,7 @@ ALCenum checkALCError(ALCdevice *device, const char *func, int line) { ALCenum err = alcGetError(device); if(err != ALC_NO_ERROR) - std::cerr<< ">>>>>>>>> ALC error "<>>>>>>>> AL error "<getInfo(&mSampleRate, &chans, &type); mFormat = getALFormat(chans, type); } - catch(std::exception &e) { - std::cerr<< "Failed to get stream info: "<getName()<<"\"" <getName() << "\""; mIsFinished = true; } return !mIsFinished; @@ -593,17 +591,18 @@ bool OpenAL_Output::init(const std::string &devname, const std::string &hrtfname { deinit(); - std::cout<< "Initializing OpenAL..." < OpenAL_Output::loadSound(const std::string &fname } catch(std::exception &e) { - std::cerr<< "Failed to load audio from "<getPosition(), sound->getRealVolume(), sound->getPitch(), sound->getIsLooping(), sound->getUseEnv()); + alSourcei(source, AL_BUFFER, GET_PTRID(data)); alSourcef(source, AL_SEC_OFFSET, offset); if(getALError() != AL_NO_ERROR) + { + alSourceRewind(source); + alSourcei(source, AL_BUFFER, 0); + alGetError(); return false; + } - alSourcei(source, AL_BUFFER, GET_PTRID(data)); alSourcePlay(source); if(getALError() != AL_NO_ERROR) + { + alSourceRewind(source); + alSourcei(source, AL_BUFFER, 0); + alGetError(); return false; + } mFreeSources.pop_front(); sound->mHandle = MAKE_PTRID(source); @@ -1168,7 +1177,7 @@ bool OpenAL_Output::playSound3D(Sound *sound, Sound_Handle data, float offset) if(mFreeSources.empty()) { - std::cerr<< "No free sources!" <getPosition(), sound->getMinDistance(), sound->getMaxDistance(), sound->getRealVolume(), sound->getPitch(), sound->getIsLooping(), sound->getUseEnv()); + alSourcei(source, AL_BUFFER, GET_PTRID(data)); alSourcef(source, AL_SEC_OFFSET, offset); if(getALError() != AL_NO_ERROR) + { + alSourceRewind(source); + alSourcei(source, AL_BUFFER, 0); + alGetError(); return false; + } - alSourcei(source, AL_BUFFER, GET_PTRID(data)); alSourcePlay(source); if(getALError() != AL_NO_ERROR) + { + alSourceRewind(source); + alSourcei(source, AL_BUFFER, 0); + alGetError(); return false; + } mFreeSources.pop_front(); sound->mHandle = MAKE_PTRID(source); @@ -1198,9 +1217,8 @@ void OpenAL_Output::finishSound(Sound *sound) ALuint source = GET_PTRID(sound->mHandle); sound->mHandle = 0; - // Rewind the stream instead of stopping it, this puts the source into an AL_INITIAL state, - // which works around a bug in the MacOS OpenAL implementation which would otherwise think - // the initial queue already played when it hasn't. + // Rewind the stream to put the source back into an AL_INITIAL state, for + // the next time it's used. alSourceRewind(source); alSourcei(source, AL_BUFFER, 0); getALError(); @@ -1236,13 +1254,14 @@ bool OpenAL_Output::streamSound(DecoderPtr decoder, Stream *sound) { if(mFreeSources.empty()) { - std::cerr<< "No free sources!" <getIsLooping()) - std::cout <<"Warning: cannot loop stream \""<getName()<<"\""<< std::endl; + Log(Debug::Warning) << "Warning: cannot loop stream \"" << decoder->getName() << "\""; + initCommon2D(source, sound->getPosition(), sound->getRealVolume(), sound->getPitch(), false, sound->getUseEnv()); if(getALError() != AL_NO_ERROR) @@ -1266,13 +1285,14 @@ bool OpenAL_Output::streamSound3D(DecoderPtr decoder, Stream *sound, bool getLou { if(mFreeSources.empty()) { - std::cerr<< "No free sources!" <getIsLooping()) - std::cout <<"Warning: cannot loop stream \""<getName()<<"\""<< std::endl; + Log(Debug::Warning) << "Warning: cannot loop stream \"" << decoder->getName() << "\""; + initCommon3D(source, sound->getPosition(), sound->getMinDistance(), sound->getMaxDistance(), sound->getRealVolume(), sound->getPitch(), false, sound->getUseEnv()); if(getALError() != AL_NO_ERROR) @@ -1301,9 +1321,8 @@ void OpenAL_Output::finishStream(Stream *sound) sound->mHandle = 0; mStreamThread->remove(stream); - // Rewind the stream instead of stopping it, this puts the source into an AL_INITIAL state, - // which works around a bug in the MacOS OpenAL implementation which would otherwise think - // the initial queue already played when it hasn't. + // Rewind the stream to put the source back into an AL_INITIAL state, for + // the next time it's used. alSourceRewind(source); alSourcei(source, AL_BUFFER, 0); getALError(); diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 0dd95f7732..9c12584d71 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -1,6 +1,5 @@ #include "soundmanagerimp.hpp" -#include #include #include #include @@ -8,7 +7,7 @@ #include #include - +#include #include #include "../mwbase/environment.hpp" @@ -81,7 +80,7 @@ namespace MWSound if(!useSound) { - std::cout<< "Sound disabled." <init(devname, hrtfname, hrtfmode)) { - std::cerr<< "Failed to initialize audio output, sound disabled" < names = mOutput->enumerate(); - std::cout <<"Enumerated output devices:\n"; + std::stringstream stream; + + stream << "Enumerated output devices:\n"; for(const std::string &name : names) - std::cout <<" "<enumerateHrtf(); if(!names.empty()) { - std::cout<< "Enumerated HRTF names:\n"; + stream << "Enumerated HRTF names:\n"; for(const std::string &name : names) - std::cout <<" "<getStore().get().find("fAudioDefaultMinDistance")->getFloat(); - static const float fAudioDefaultMaxDistance = world->getStore().get().find("fAudioDefaultMaxDistance")->getFloat(); - static const float fAudioMinDistanceMult = world->getStore().get().find("fAudioMinDistanceMult")->getFloat(); - static const float fAudioMaxDistanceMult = world->getStore().get().find("fAudioMaxDistanceMult")->getFloat(); + static const float fAudioDefaultMinDistance = world->getStore().get().find("fAudioDefaultMinDistance")->mValue.getFloat(); + static const float fAudioDefaultMaxDistance = world->getStore().get().find("fAudioDefaultMaxDistance")->mValue.getFloat(); + static const float fAudioMinDistanceMult = world->getStore().get().find("fAudioMinDistanceMult")->mValue.getFloat(); + static const float fAudioMaxDistanceMult = world->getStore().get().find("fAudioMaxDistanceMult")->mValue.getFloat(); float volume, min, max; volume = static_cast(pow(10.0, (sound->mData.mVolume / 255.0*3348.0 - 3348.0) / 2000.0)); - if(sound->mData.mMinRange == 0 && sound->mData.mMaxRange == 0) - { + min = sound->mData.mMinRange; + max = sound->mData.mMaxRange; + if (min == 0) min = fAudioDefaultMinDistance; + if (max == 0) max = fAudioDefaultMaxDistance; - } - else - { - min = sound->mData.mMinRange; - max = sound->mData.mMaxRange; - } min *= fAudioMinDistanceMult; max *= fAudioMaxDistanceMult; @@ -225,7 +225,7 @@ namespace MWSound do { if(mUnusedBuffers.empty()) { - std::cerr<< "No unused sound buffers to free, using "<getStore().get().find("fAudioMinDistanceMult")->getFloat(); - static const float fAudioMaxDistanceMult = world->getStore().get().find("fAudioMaxDistanceMult")->getFloat(); - static const float fAudioVoiceDefaultMinDistance = world->getStore().get().find("fAudioVoiceDefaultMinDistance")->getFloat(); - static const float fAudioVoiceDefaultMaxDistance = world->getStore().get().find("fAudioVoiceDefaultMaxDistance")->getFloat(); + static const float fAudioMinDistanceMult = world->getStore().get().find("fAudioMinDistanceMult")->mValue.getFloat(); + static const float fAudioMaxDistanceMult = world->getStore().get().find("fAudioMaxDistanceMult")->mValue.getFloat(); + static const float fAudioVoiceDefaultMinDistance = world->getStore().get().find("fAudioVoiceDefaultMinDistance")->mValue.getFloat(); + static const float fAudioVoiceDefaultMaxDistance = world->getStore().get().find("fAudioVoiceDefaultMaxDistance")->mValue.getFloat(); static float minDistance = std::max(fAudioVoiceDefaultMinDistance * fAudioMinDistanceMult, 1.0f); static float maxDistance = std::max(fAudioVoiceDefaultMaxDistance * fAudioMaxDistanceMult, minDistance); @@ -364,7 +364,7 @@ namespace MWSound { if(!mOutput->isInitialized()) return; - std::cout <<"Playing "< + #include #include #include @@ -157,7 +159,7 @@ void MWState::StateManager::newGame (bool bypass) std::stringstream error; error << "Failed to start new game: " << e.what(); - std::cerr << error.str() << std::endl; + Log(Debug::Error) << error.str(); cleanup (true); MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); @@ -283,7 +285,7 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot // 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; + Log(Debug::Warning) << "Warning: number of written savegame records does not match. Estimated: " << recordCount+1 << ", written: " << writer.getRecordCount(); writer.close(); @@ -305,7 +307,7 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot std::stringstream error; error << "Failed to save game: " << e.what(); - std::cerr << error.str() << std::endl; + Log(Debug::Error) << error.str(); std::vector buttons; buttons.push_back("#{sOk}"); @@ -483,7 +485,7 @@ void MWState::StateManager::loadGame (const Character *character, const std::str default: // ignore invalid records - std::cerr << "Warning: Ignoring unknown record: " << n.toString() << std::endl; + Log(Debug::Warning) << "Warning: Ignoring unknown record: " << n.toString(); reader.skipRecord(); } int progressPercent = static_cast(float(reader.getFileOffset())/total*100); @@ -549,7 +551,7 @@ void MWState::StateManager::loadGame (const Character *character, const std::str std::stringstream error; error << "Failed to load saved game: " << e.what(); - std::cerr << error.str() << std::endl; + Log(Debug::Error) << error.str(); cleanup (true); MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); @@ -625,7 +627,7 @@ bool MWState::StateManager::verifyProfile(const ESM::SavedGame& profile) const if (std::find(selectedContentFiles.begin(), selectedContentFiles.end(), *it) == selectedContentFiles.end()) { - std::cerr << "Warning: Savegame dependency " << *it << " is missing." << std::endl; + Log(Debug::Warning) << "Warning: Savegame dependency " << *it << " is missing."; notFound = true; } } @@ -653,7 +655,7 @@ void MWState::StateManager::writeScreenshot(std::vector &imageData) const osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("jpg"); if (!readerwriter) { - std::cerr << "Error: Unable to write screenshot, can't find a jpg ReaderWriter" << std::endl; + Log(Debug::Error) << "Error: Unable to write screenshot, can't find a jpg ReaderWriter"; return; } @@ -661,7 +663,7 @@ void MWState::StateManager::writeScreenshot(std::vector &imageData) const osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(*screenshot, ostream); if (!result.success()) { - std::cerr << "Error: Unable to write screenshot: " << result.message() << " code " << result.status() << std::endl; + Log(Debug::Error) << "Error: Unable to write screenshot: " << result.message() << " code " << result.status(); return; } diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 700b91b373..9163f1f2e3 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -1,7 +1,6 @@ #include "cellpreloader.hpp" -#include - +#include #include #include #include @@ -229,12 +228,12 @@ namespace MWWorld { if (!mWorkQueue) { - std::cerr << "Error: can't preload, no work queue set " << std::endl; + Log(Debug::Error) << "Error: can't preload, no work queue set"; return; } if (cell->getState() == CellStore::State_Unloaded) { - std::cerr << "Error: can't preload objects for unloaded cell" << std::endl; + Log(Debug::Error) << "Error: can't preload objects for unloaded cell"; return; } diff --git a/apps/openmw/mwworld/cells.cpp b/apps/openmw/mwworld/cells.cpp index 752659eb6e..5cac12b9c9 100644 --- a/apps/openmw/mwworld/cells.cpp +++ b/apps/openmw/mwworld/cells.cpp @@ -1,7 +1,6 @@ #include "cells.hpp" -#include - +#include #include #include #include @@ -350,7 +349,7 @@ bool MWWorld::Cells::readRecord (ESM::ESMReader& reader, uint32_t type, catch (...) { // silently drop cells that don't exist anymore - std::cerr << "Warning: Dropping state for cell " << state.mId.mWorldspace << " (cell no longer exists)" << std::endl; + Log(Debug::Warning) << "Warning: Dropping state for cell " << state.mId.mWorldspace << " (cell no longer exists)"; reader.skipRecord(); return true; } diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index fc3c2e245f..05ff1e3260 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -1,8 +1,9 @@ #include "cellstore.hpp" -#include #include +#include + #include #include #include @@ -138,7 +139,7 @@ namespace return; } - std::cerr << "Warning: Dropping reference to " << state.mRef.mRefID << " (invalid content file link)" << std::endl; + Log(Debug::Warning) << "Warning: Dropping reference to " << state.mRef.mRefID << " (invalid content file link)"; return; } @@ -196,9 +197,9 @@ namespace MWWorld } else { - std::cerr + Log(Debug::Warning) << "Warning: could not resolve cell reference '" << ref.mRefID << "'" - << " (dropping reference)" << std::endl; + << " (dropping reference)"; } } @@ -497,7 +498,7 @@ namespace MWWorld } catch (std::exception& e) { - std::cerr << "An error occurred listing references for cell " << getCell()->getDescription() << ": " << e.what() << std::endl; + Log(Debug::Error) << "An error occurred listing references for cell " << getCell()->getDescription() << ": " << e.what(); } } @@ -553,7 +554,7 @@ namespace MWWorld } catch (std::exception& e) { - std::cerr << "An error occurred loading references for cell " << getCell()->getDescription() << ": " << e.what() << std::endl; + Log(Debug::Error) << "An error occurred loading references for cell " << getCell()->getDescription() << ": " << e.what(); } } @@ -659,11 +660,10 @@ namespace MWWorld case ESM::REC_WEAP: mWeapons.load(ref, deleted, store); break; case ESM::REC_BODY: mBodyParts.load(ref, deleted, store); break; - case 0: std::cerr << "Cell reference '" + ref.mRefID + "' not found!\n"; return; + case 0: Log(Debug::Error) << "Cell reference '" + ref.mRefID + "' not found!"; return; default: - std::cerr - << "Error: Ignoring reference '" << ref.mRefID << "' of unhandled type\n"; + Log(Debug::Error) << "Error: Ignoring reference '" << ref.mRefID << "' of unhandled type"; return; } @@ -756,7 +756,7 @@ namespace MWWorld int type = MWBase::Environment::get().getWorld()->getStore().find(cref.mRefID); if (type == 0) { - std::cerr << "Dropping reference to '" << cref.mRefID << "' (object no longer exists)" << std::endl; + Log(Debug::Warning) << "Dropping reference to '" << cref.mRefID << "' (object no longer exists)"; reader.skipHSubUntil("OBJE"); continue; } @@ -892,7 +892,7 @@ namespace MWWorld if (!visitor.mFound) { - std::cerr << "Warning: Dropping moved ref tag for " << refnum.mIndex << " (moved object no longer exists)" << std::endl; + Log(Debug::Warning) << "Warning: Dropping moved ref tag for " << refnum.mIndex << " (moved object no longer exists)"; continue; } @@ -902,8 +902,8 @@ namespace MWWorld if (otherCell == NULL) { - std::cerr << "Warning: Dropping moved ref tag for " << movedRef->mRef.getRefId() - << " (target cell " << movedTo.mWorldspace << " no longer exists). Reference moved back to its original location." << std::endl; + Log(Debug::Warning) << "Warning: Dropping moved ref tag for " << movedRef->mRef.getRefId() + << " (target cell " << movedTo.mWorldspace << " no longer exists). Reference moved back to its original location."; // Note by dropping tag the object will automatically re-appear in its original cell, though potentially at inapproriate coordinates. // Restore original coordinates: movedRef->mData.setPosition(movedRef->mRef.getPosition()); @@ -913,7 +913,7 @@ namespace MWWorld if (otherCell == this) { // Should never happen unless someone's tampering with files. - std::cerr << "Found invalid moved ref, ignoring" << std::endl; + Log(Debug::Warning) << "Found invalid moved ref, ignoring"; continue; } @@ -944,7 +944,7 @@ namespace MWWorld void clearCorpse(const MWWorld::Ptr& ptr) { const MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); - static const float fCorpseClearDelay = MWBase::Environment::get().getWorld()->getStore().get().find("fCorpseClearDelay")->getFloat(); + static const float fCorpseClearDelay = MWBase::Environment::get().getWorld()->getStore().get().find("fCorpseClearDelay")->mValue.getFloat(); if (creatureStats.isDead() && creatureStats.isDeathAnimationFinished() && !ptr.getClass().isPersistent(ptr) && @@ -958,7 +958,7 @@ namespace MWWorld { if (mState == State_Loaded) { - static const int iMonthsToRespawn = MWBase::Environment::get().getWorld()->getStore().get().find("iMonthsToRespawn")->getInt(); + static const int iMonthsToRespawn = MWBase::Environment::get().getWorld()->getStore().get().find("iMonthsToRespawn")->mValue.getInteger(); if (MWBase::Environment::get().getWorld()->getTimeStamp() - mLastRespawn > 24*30*iMonthsToRespawn) { mLastRespawn = MWBase::Environment::get().getWorld()->getTimeStamp(); diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 6c369a1548..8fa16ff515 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include "../mwbase/environment.hpp" @@ -502,7 +503,7 @@ void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std:: } catch (const std::exception& e) { - std::cerr << "Warning: MWWorld::ContainerStore::addInitialItem: " << e.what() << std::endl; + Log(Debug::Warning) << "Warning: MWWorld::ContainerStore::addInitialItem: " << e.what(); } } @@ -826,10 +827,10 @@ void MWWorld::ContainerStore::readState (const ESM::InventoryState& inventory) case ESM::REC_WEAP: readEquipmentState (getState (weapons, state), thisIndex, inventory); break; case ESM::REC_LIGH: readEquipmentState (getState (lights, state), thisIndex, inventory); break; case 0: - std::cerr << "Dropping inventory reference to '" << state.mRef.mRefID << "' (object no longer exists)" << std::endl; + Log(Debug::Warning) << "Dropping inventory reference to '" << state.mRef.mRefID << "' (object no longer exists)"; break; default: - std::cerr << "Warning: Invalid item type in inventory state, refid " << state.mRef.mRefID << std::endl; + Log(Debug::Warning) << "Warning: Invalid item type in inventory state, refid " << state.mRef.mRefID; break; } } diff --git a/apps/openmw/mwworld/contentloader.hpp b/apps/openmw/mwworld/contentloader.hpp index 0f2d807aae..2069a78fc8 100644 --- a/apps/openmw/mwworld/contentloader.hpp +++ b/apps/openmw/mwworld/contentloader.hpp @@ -2,10 +2,10 @@ #define CONTENTLOADER_HPP #include -#include #include #include +#include #include "components/loadinglistener/loadinglistener.hpp" namespace MWWorld @@ -24,8 +24,8 @@ struct ContentLoader virtual void load(const boost::filesystem::path& filepath, int& index) { - std::cout << "Loading content file " << filepath.string() << std::endl; - mListener.setLabel(MyGUI::TextIterator::toTagsString(filepath.string())); + Log(Debug::Info) << "Loading content file " << filepath.string(); + mListener.setLabel(MyGUI::TextIterator::toTagsString(filepath.string())); } protected: diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index ac5608b8e2..01d8e4b820 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -1,12 +1,11 @@ #include "esmstore.hpp" #include -#include #include +#include #include - #include #include @@ -84,7 +83,7 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) } else { - std::cerr << "error: info record without dialog" << std::endl; + Log(Debug::Error) << "Error: info record without dialog"; esm.skipRecord(); } } else if (n.intval == ESM::REC_MGEF) { @@ -170,7 +169,7 @@ void ESMStore::validate() const ESM::Faction *fact = mFactions.search(npcFaction); if (!fact) { - std::cerr << "NPC '" << npc.mId << "' (" << npc.mName << ") has nonexistent faction '" << npc.mFaction << "', ignoring it." << std::endl; + Log(Debug::Verbose) << "NPC '" << npc.mId << "' (" << npc.mName << ") has nonexistent faction '" << npc.mFaction << "', ignoring it."; npc.mFaction = ""; npc.mNpdt.mRank = -1; changed = true; @@ -183,7 +182,7 @@ void ESMStore::validate() const ESM::Class *cls = mClasses.search(npcClass); if (!cls) { - std::cerr << "NPC '" << npc.mId << "' (" << npc.mName << ") has nonexistent class '" << npc.mClass << "', using '" << defaultCls << "' class as replacement." << std::endl; + Log(Debug::Verbose) << "NPC '" << npc.mId << "' (" << npc.mName << ") has nonexistent class '" << npc.mClass << "', using '" << defaultCls << "' class as replacement."; npc.mClass = defaultCls; changed = true; } diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index ba3ea23817..7f3018c559 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -251,8 +252,8 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor) const MWWorld::Store &store = world->getStore().get(); MWMechanics::NpcStats& stats = actor.getClass().getNpcStats(actor); - static float fUnarmoredBase1 = store.find("fUnarmoredBase1")->getFloat(); - static float fUnarmoredBase2 = store.find("fUnarmoredBase2")->getFloat(); + static float fUnarmoredBase1 = store.find("fUnarmoredBase1")->mValue.getFloat(); + static float fUnarmoredBase2 = store.find("fUnarmoredBase2")->mValue.getFloat(); int unarmoredSkill = stats.getSkill(ESM::Skill::Unarmored).getModified(); float unarmoredRating = (fUnarmoredBase1 * unarmoredSkill) * (fUnarmoredBase2 * unarmoredSkill); @@ -895,7 +896,7 @@ void MWWorld::InventoryStore::updateRechargingItems() enchantmentId); if (!enchantment) { - std::cerr << "Warning: Can't find enchantment '" << enchantmentId << "' on item " << it->getCellRef().getRefId() << std::endl; + Log(Debug::Warning) << "Warning: Can't find enchantment '" << enchantmentId << "' on item " << it->getCellRef().getRefId(); continue; } @@ -920,7 +921,7 @@ void MWWorld::InventoryStore::rechargeItems(float duration) continue; static float fMagicItemRechargePerSecond = MWBase::Environment::get().getWorld()->getStore().get().find( - "fMagicItemRechargePerSecond")->getFloat(); + "fMagicItemRechargePerSecond")->mValue.getFloat(); if (it->first->getCellRef().getEnchantmentCharge() <= it->second) { diff --git a/apps/openmw/mwworld/livecellref.cpp b/apps/openmw/mwworld/livecellref.cpp index b8178f7746..9cf8a0fe04 100644 --- a/apps/openmw/mwworld/livecellref.cpp +++ b/apps/openmw/mwworld/livecellref.cpp @@ -1,7 +1,6 @@ #include "livecellref.hpp" -#include - +#include #include #include "../mwbase/environment.hpp" @@ -38,10 +37,9 @@ void MWWorld::LiveCellRefBase::loadImp (const ESM::ObjectState& state) } catch (const std::exception& exception) { - std::cerr + Log(Debug::Error) << "Error: failed to load state for local script " << scriptId - << " because an exception has been thrown: " << exception.what() - << std::endl; + << " because an exception has been thrown: " << exception.what(); } } } @@ -51,7 +49,7 @@ void MWWorld::LiveCellRefBase::loadImp (const ESM::ObjectState& state) if (!mRef.getSoul().empty() && !MWBase::Environment::get().getWorld()->getStore().get().search(mRef.getSoul())) { - std::cerr << "Soul '" << mRef.getSoul() << "' not found, removing the soul from soul gem" << std::endl; + Log(Debug::Warning) << "Soul '" << mRef.getSoul() << "' not found, removing the soul from soul gem"; mRef.setSoul(std::string()); } } diff --git a/apps/openmw/mwworld/localscripts.cpp b/apps/openmw/mwworld/localscripts.cpp index 4ed83bf11d..ff47d3e564 100644 --- a/apps/openmw/mwworld/localscripts.cpp +++ b/apps/openmw/mwworld/localscripts.cpp @@ -1,14 +1,12 @@ #include "localscripts.hpp" -#include +#include #include "esmstore.hpp" #include "cellstore.hpp" - #include "class.hpp" #include "containerstore.hpp" - namespace { @@ -93,7 +91,7 @@ void MWWorld::LocalScripts::add (const std::string& scriptName, const Ptr& ptr) for (std::list >::iterator iter = mScripts.begin(); iter!=mScripts.end(); ++iter) if (iter->second==ptr) { - std::cerr << "Error: tried to add local script twice for " << ptr.getCellRef().getRefId() << std::endl; + Log(Debug::Warning) << "Error: tried to add local script twice for " << ptr.getCellRef().getRefId(); remove(ptr); break; } @@ -102,15 +100,15 @@ void MWWorld::LocalScripts::add (const std::string& scriptName, const Ptr& ptr) } catch (const std::exception& exception) { - std::cerr + Log(Debug::Error) << "failed to add local script " << scriptName - << " because an exception has been thrown: " << exception.what() << std::endl; + << " because an exception has been thrown: " << exception.what(); } } else - std::cerr + Log(Debug::Warning) << "failed to add local script " << scriptName - << " because the script does not exist." << std::endl; + << " because the script does not exist."; } void MWWorld::LocalScripts::addCell (CellStore *cell) diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 5439447fd0..389f599832 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -1,7 +1,8 @@ #include "player.hpp" #include -#include + +#include #include #include @@ -63,7 +64,7 @@ namespace MWWorld MWMechanics::CreatureStats& creatureStats = getPlayer().getClass().getCreatureStats(getPlayer()); MWMechanics::NpcStats& npcStats = getPlayer().getClass().getNpcStats(getPlayer()); MWMechanics::DynamicStat health = creatureStats.getDynamic(0); - creatureStats.setHealth(int(health.getBase() / gmst.find("fWereWolfHealth")->getFloat())); + creatureStats.setHealth(int(health.getBase() / gmst.find("fWereWolfHealth")->mValue.getFloat())); for (int i=0; i health = creatureStats.getDynamic(0); - creatureStats.setHealth(int(health.getBase() * gmst.find("fWereWolfHealth")->getFloat())); + creatureStats.setHealth(int(health.getBase() * gmst.find("fWereWolfHealth")->mValue.getFloat())); for(size_t i = 0;i < ESM::Attribute::Length;++i) { // Oh, Bethesda. It's "Intelligence". @@ -84,7 +85,7 @@ namespace MWWorld ESM::Attribute::sAttributeNames[i]); MWMechanics::AttributeValue value = npcStats.getAttribute(i); - value.setBase(int(gmst.find(name)->getFloat())); + value.setBase(int(gmst.find(name)->mValue.getFloat())); npcStats.setAttribute(i, value); } @@ -99,7 +100,7 @@ namespace MWWorld ESM::Skill::sSkillNames[i]); MWMechanics::SkillValue value = npcStats.getSkill(i); - value.setBase(int(gmst.find(name)->getFloat())); + value.setBase(int(gmst.find(name)->mValue.getFloat())); npcStats.setSkill(i, value); } } @@ -364,7 +365,7 @@ namespace MWWorld if (!player.mObject.mEnabled) { - std::cerr << "Warning: Savegame attempted to disable the player." << std::endl; + Log(Debug::Warning) << "Warning: Savegame attempted to disable the player."; player.mObject.mEnabled = true; } @@ -391,7 +392,7 @@ namespace MWWorld } catch (...) { - std::cerr << "Warning: Player cell '" << player.mCellId.mWorldspace << "' no longer exists" << std::endl; + Log(Debug::Warning) << "Warning: Player cell '" << player.mCellId.mWorldspace << "' no longer exists"; // Cell no longer exists. The loader will have to choose a default cell. mCellStore = NULL; } diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index a4a22ea4a3..21218fab01 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -1,11 +1,12 @@ #include "projectilemanager.hpp" #include -#include #include #include +#include + #include #include @@ -384,7 +385,7 @@ namespace MWWorld { osg::Quat orient = it->mNode->getAttitude(); static float fTargetSpellMaxSpeed = MWBase::Environment::get().getWorld()->getStore().get() - .find("fTargetSpellMaxSpeed")->getFloat(); + .find("fTargetSpellMaxSpeed")->mValue.getFloat(); float speed = fTargetSpellMaxSpeed * it->mSpeed; osg::Vec3f direction = orient * osg::Vec3f(0,1,0); direction.normalize(); @@ -647,7 +648,7 @@ namespace MWWorld } catch(...) { - std::cerr << "Warning: Failed to recreate magic projectile from saved data (id \"" << state.mSpellId << "\" no longer exists?)" << std::endl; + Log(Debug::Warning) << "Warning: Failed to recreate magic projectile from saved data (id \"" << state.mSpellId << "\" no longer exists?)"; return true; } diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 2c2d401d18..ed6dde310c 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -1,8 +1,8 @@ #include "scene.hpp" #include -#include +#include #include #include #include @@ -56,7 +56,7 @@ namespace { if (ptr.getRefData().getBaseNode() || physics.getActor(ptr)) { - std::cerr << "Warning: Tried to add " << ptr.getCellRef().getRefId() << " to the scene twice" << std::endl; + Log(Debug::Warning) << "Warning: Tried to add " << ptr.getCellRef().getRefId() << " to the scene twice"; return; } @@ -160,7 +160,7 @@ namespace catch (const std::exception& e) { std::string error ("failed to render '" + ptr.getCellRef().getRefId() + "': "); - std::cerr << error + e.what() << std::endl; + Log(Debug::Error) << error + e.what(); } } @@ -232,7 +232,7 @@ namespace MWWorld void Scene::unloadCell (CellStoreCollection::iterator iter) { - std::cout << "Unloading cell\n"; + Log(Debug::Info) << "Unloading cell " << (*iter)->getCell()->getDescription(); ListAndResetObjectsVisitor visitor; (*iter)->forEach(visitor); @@ -270,7 +270,7 @@ namespace MWWorld if(result.second) { - std::cout << "Loading cell " << cell->getCell()->getDescription() << std::endl; + Log(Debug::Info) << "Loading cell " << cell->getCell()->getDescription(); float verts = ESM::Land::LAND_SIZE; float worldsize = ESM::Land::REAL_SIZE; @@ -546,7 +546,7 @@ namespace MWWorld return; } - std::cout << "Changing to interior\n"; + Log(Debug::Info) << "Changing to interior"; // unload CellStoreCollection::iterator active = mActiveCells.begin(); @@ -624,7 +624,7 @@ namespace MWWorld } catch (std::exception& e) { - std::cerr << "failed to render '" << ptr.getCellRef().getRefId() << "': " << e.what() << std::endl; + Log(Debug::Error) << "failed to render '" << ptr.getCellRef().getRefId() << "': " << e.what(); } } diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 6f0a1b49f8..2ac78bb85d 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -1,5 +1,7 @@ #include "store.hpp" +#include + #include #include @@ -8,7 +10,6 @@ #include #include -#include namespace { @@ -692,7 +693,7 @@ namespace MWWorld if (it_lease != wipecell->mLeasedRefs.end()) wipecell->mLeasedRefs.erase(it_lease); else - std::cerr << "Error: can't find " << it->mRefNum.mIndex << " " << it->mRefNum.mContentFile << " in leasedRefs " << std::endl; + Log(Debug::Error) << "Error: can't find " << it->mRefNum.mIndex << " " << it->mRefNum.mContentFile << " in leasedRefs"; } *itold = *it; } diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index d12e633beb..2a9e8d7cc0 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -911,7 +911,7 @@ inline void WeatherManager::addWeather(const std::string& name, float dlFactor, float dlOffset, const std::string& particleEffect) { - static const float fStromWindSpeed = mStore.get().find("fStromWindSpeed")->getFloat(); + static const float fStromWindSpeed = mStore.get().find("fStromWindSpeed")->mValue.getFloat(); Weather weather(name, fallback, fStromWindSpeed, mRainSpeed, dlFactor, dlOffset, particleEffect); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index c162fd57dc..48cb1dda3c 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3,6 +3,8 @@ #include #include +#include + #include #include #include @@ -153,7 +155,8 @@ namespace MWWorld mGodMode(false), mScriptsEnabled(true), mContentFiles (contentFiles), mUserDataPath(userDataPath), mActivationDistanceOverride (activationDistanceOverride), mStartupScript(startupScript), mStartCell (startCell), mDistanceToFacedObject(-1), mTeleportEnabled(true), - mLevitationEnabled(true), mGoToJail(false), mDaysInPrison(0), mSpellPreloadTimer(0.f) + mLevitationEnabled(true), mGoToJail(false), mDaysInPrison(0), + mPlayerTraveling(false), mPlayerInJail(false), mSpellPreloadTimer(0.f) { mPhysics.reset(new MWPhysics::PhysicsSystem(resourceSystem, rootNode)); mRendering.reset(new MWRender::RenderingManager(viewer, rootNode, resourceSystem, workQueue, &mFallback, resourcePath)); @@ -187,7 +190,7 @@ namespace MWWorld mStore.setUp(true); mStore.movePlayerRecord(); - mSwimHeightScale = mStore.get().find("fSwimHeightScale")->getFloat(); + mSwimHeightScale = mStore.get().find("fSwimHeightScale")->mValue.getFloat(); mWeatherManager.reset(new MWWorld::WeatherManager(*mRendering, mFallback, mStore)); @@ -311,6 +314,8 @@ namespace MWWorld mGoToJail = false; mTeleportEnabled = true; mLevitationEnabled = true; + mPlayerTraveling = false; + mPlayerInJail = false; fillGlobalVariables(); } @@ -1021,7 +1026,7 @@ namespace MWWorld if (mActivationDistanceOverride >= 0) return static_cast(mActivationDistanceOverride); - static const int iMaxActivateDist = getStore().get().find("iMaxActivateDist")->getInt(); + static const int iMaxActivateDist = getStore().get().find("iMaxActivateDist")->mValue.getInteger(); return static_cast(iMaxActivateDist); } @@ -1180,9 +1185,9 @@ namespace MWWorld addContainerScripts (getPlayerPtr(), newCell); newPtr = getPlayerPtr(); } - else + else if (currCell) { - bool currCellActive = currCell && mWorldScene->isCellActive(*currCell); + bool currCellActive = mWorldScene->isCellActive(*currCell); bool newCellActive = newCell && mWorldScene->isCellActive(*newCell); if (!currCellActive && newCellActive) { @@ -1642,6 +1647,15 @@ namespace MWWorld if (mGoToJail && !paused) goToJail(); + // Reset "traveling" flag - there was a frame to detect traveling. + mPlayerTraveling = false; + + // The same thing for "in jail" flag: reset it if: + // 1. Player was in jail + // 2. Jailing window was closed + if (mPlayerInJail && !mGoToJail && !MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_Jail)) + mPlayerInJail = false; + updateWeather(duration, paused); if (!paused) @@ -1692,11 +1706,11 @@ namespace MWWorld // Sink the camera while sneaking bool sneaking = player.getClass().getCreatureStats(getPlayerPtr()).getStance(MWMechanics::CreatureStats::Stance_Sneak); - bool inair = !isOnGround(player); bool swimming = isSwimming(player); + bool flying = isFlying(player); - static const float i1stPersonSneakDelta = getStore().get().find("i1stPersonSneakDelta")->getFloat(); - if (sneaking && !(swimming || inair)) + static const float i1stPersonSneakDelta = getStore().get().find("i1stPersonSneakDelta")->mValue.getFloat(); + if (sneaking && !swimming && !flying) mRendering->getCamera()->setSneakOffset(i1stPersonSneakDelta); else mRendering->getCamera()->setSneakOffset(0.f); @@ -1794,7 +1808,7 @@ namespace MWWorld } catch (std::exception& e) { - std::cerr << "Error updating window manager: " << e.what() << std::endl; + Log(Debug::Error) << "Error updating window manager: " << e.what(); } } @@ -2363,6 +2377,11 @@ namespace MWWorld return !actors.empty(); } + void World::getActorsStandingOn (const MWWorld::ConstPtr& object, std::vector &actors) + { + mPhysics->getActorsStandingOn(object, actors); + } + bool World::getPlayerCollidingWith (const MWWorld::ConstPtr& object) { MWWorld::Ptr player = getPlayerPtr(); @@ -2784,7 +2803,7 @@ namespace MWWorld if (!actor.isEmpty() && actor != MWMechanics::getPlayer() && !manualSpell) stats.getAiSequence().getCombatTargets(targetActors); - const float fCombatDistance = getStore().get().find("fCombatDistance")->getFloat(); + const float fCombatDistance = getStore().get().find("fCombatDistance")->mValue.getFloat(); osg::Vec3f hitPosition = actor.getRefData().getPosition().asVec3(); @@ -2878,10 +2897,34 @@ namespace MWWorld } } - void World::launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile, - const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr bow, float speed, float attackStrength) + void World::launchProjectile (MWWorld::Ptr& actor, MWWorld::Ptr& projectile, + const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr& bow, float speed, float attackStrength) { - mProjectileManager->launchProjectile(actor, projectile, worldPos, orient, bow, speed, attackStrength); + // An initial position of projectile can be outside shooter's collision box, so any object between shooter and launch position will be ignored. + // To avoid this issue, we should check for impact immediately before launch the projectile. + // So we cast a 1-yard-length ray from shooter to launch position and check if there are collisions in this area. + // TODO: as a better solutuon we should handle projectiles during physics update, not during world update. + const osg::Vec3f sourcePos = worldPos + orient * osg::Vec3f(0,-1,0) * 64.f; + + // Early out if the launch position is underwater + bool underwater = MWBase::Environment::get().getWorld()->isUnderwater(MWMechanics::getPlayer().getCell(), worldPos); + if (underwater) + { + mRendering->emitWaterRipple(worldPos); + return; + } + + // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. + std::vector targetActors; + if (!actor.isEmpty() && actor.getClass().isActor() && actor != MWMechanics::getPlayer()) + actor.getClass().getCreatureStats(actor).getAiSequence().getCombatTargets(targetActors); + + // Check for impact, if yes, handle hit, if not, launch projectile + MWPhysics::PhysicsSystem::RayResult result = mPhysics->castRay(sourcePos, worldPos, actor, targetActors, 0xff, MWPhysics::CollisionType_Projectile); + if (result.mHit) + MWMechanics::projectileHit(actor, result.mHitObject, bow, projectile, result.mHitPos, attackStrength); + else + mProjectileManager->launchProjectile(actor, projectile, worldPos, orient, bow, speed, attackStrength); } void World::launchMagicBolt (const std::string &spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection) @@ -3075,7 +3118,7 @@ namespace MWWorld if ( closestMarker.isEmpty() ) { - std::cerr << "Failed to teleport: no closest marker found" << std::endl; + Log(Debug::Warning) << "Failed to teleport: no closest marker found"; return; } @@ -3224,8 +3267,8 @@ namespace MWWorld int bounty = player.getClass().getNpcStats(player).getBounty(); int playerGold = player.getClass().getContainerStore(player).count(ContainerStore::sGoldId); - float fCrimeGoldDiscountMult = getStore().get().find("fCrimeGoldDiscountMult")->getFloat(); - float fCrimeGoldTurnInMult = getStore().get().find("fCrimeGoldTurnInMult")->getFloat(); + float fCrimeGoldDiscountMult = getStore().get().find("fCrimeGoldDiscountMult")->mValue.getFloat(); + float fCrimeGoldTurnInMult = getStore().get().find("fCrimeGoldTurnInMult")->mValue.getFloat(); int discount = static_cast(bounty * fCrimeGoldDiscountMult); int turnIn = static_cast(bounty * fCrimeGoldTurnInMult); @@ -3250,19 +3293,19 @@ namespace MWWorld MWWorld::ConstPtr prisonMarker = getClosestMarker( ptr, "prisonmarker" ); if ( prisonMarker.isEmpty() ) { - std::cerr << "Failed to confiscate items: no closest prison marker found." << std::endl; + Log(Debug::Warning) << "Failed to confiscate items: no closest prison marker found."; return; } std::string prisonName = prisonMarker.getCellRef().getDestCell(); if ( prisonName.empty() ) { - std::cerr << "Failed to confiscate items: prison marker not linked to prison interior" << std::endl; + Log(Debug::Warning) << "Failed to confiscate items: prison marker not linked to prison interior"; return; } MWWorld::CellStore *prison = getInterior( prisonName ); if ( !prison ) { - std::cerr << "Failed to confiscate items: failed to load cell " << prisonName << std::endl; + Log(Debug::Warning) << "Failed to confiscate items: failed to load cell " << prisonName; return; } @@ -3272,7 +3315,7 @@ namespace MWWorld MWBase::Environment::get().getMechanicsManager()->confiscateStolenItems(ptr, closestChest); } else - std::cerr << "Failed to confiscate items: no stolen_goods container found" << std::endl; + Log(Debug::Warning) << "Failed to confiscate items: no stolen_goods container found"; } void World::goToJail() @@ -3281,6 +3324,7 @@ namespace MWWorld { // Reset bounty and forget the crime now, but don't change cell yet (the player should be able to read the dialog text first) mGoToJail = true; + mPlayerInJail = true; MWWorld::Ptr player = getPlayerPtr(); @@ -3289,7 +3333,7 @@ namespace MWWorld mPlayer->recordCrimeId(); confiscateStolenItems(player); - int iDaysinPrisonMod = getStore().get().find("iDaysinPrisonMod")->getInt(); + int iDaysinPrisonMod = getStore().get().find("iDaysinPrisonMod")->mValue.getInteger(); mDaysInPrison = std::max(1, bounty / iDaysinPrisonMod); return; @@ -3306,10 +3350,17 @@ namespace MWWorld bool World::isPlayerInJail() const { - if (mGoToJail) - return true; + return mPlayerInJail; + } - return MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_Jail); + void World::setPlayerTraveling(bool traveling) + { + mPlayerTraveling = traveling; + } + + bool World::isPlayerTraveling() const + { + return mPlayerTraveling; } float World::getTerrainHeightAt(const osg::Vec3f& worldPos) const @@ -3317,12 +3368,16 @@ namespace MWWorld return mRendering->getTerrainHeightAt(worldPos); } - osg::Vec3f World::getHalfExtents(const ConstPtr& actor, bool rendering) const + osg::Vec3f World::getHalfExtents(const ConstPtr& object, bool rendering) const { + if (!object.getClass().isActor()) + return mRendering->getHalfExtents(object); + + // Handle actors separately because of bodyparts if (rendering) - return mPhysics->getRenderingHalfExtents(actor); + return mPhysics->getRenderingHalfExtents(object); else - return mPhysics->getHalfExtents(actor); + return mPhysics->getHalfExtents(object); } std::string World::exportSceneGraph(const Ptr &ptr) @@ -3336,7 +3391,7 @@ namespace MWWorld { const ESM::CreatureLevList* list = getStore().get().find(creatureList); - int iNumberCreatures = getStore().get().find("iNumberCreatures")->getInt(); + int iNumberCreatures = getStore().get().find("iNumberCreatures")->mValue.getInteger(); int numCreatures = 1 + Misc::Rng::rollDice(iNumberCreatures); // [1, iNumberCreatures] for (int i=0; ispawnEffect(model, texture, worldPosition, 1.0f, false); } - void World::spawnEffect(const std::string &model, const std::string &textureOverride, const osg::Vec3f &worldPos) + void World::spawnEffect(const std::string &model, const std::string &textureOverride, const osg::Vec3f &worldPos, float scale, bool isMagicVFX) { - mRendering->spawnEffect(model, textureOverride, worldPos); + mRendering->spawnEffect(model, textureOverride, worldPos, scale, isMagicVFX); } void World::explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const Ptr& caster, const Ptr& ignore, ESM::RangeType rangeType, diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index a2616995ad..536a40468a 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -168,6 +168,8 @@ namespace MWWorld bool mLevitationEnabled; bool mGoToJail; int mDaysInPrison; + bool mPlayerTraveling; + bool mPlayerInJail; float mSpellPreloadTimer; @@ -523,6 +525,7 @@ namespace MWWorld /// @note throws an exception when invoked on a teleport door void activateDoor(const MWWorld::Ptr& door, int state) override; + void getActorsStandingOn (const MWWorld::ConstPtr& object, std::vector &actors) override; ///< get a list of actors standing on \a object bool getPlayerStandingOn (const MWWorld::ConstPtr& object) override; ///< @return true if the player is standing on \a object bool getActorStandingOn (const MWWorld::ConstPtr& object) override; ///< @return true if any actor is standing on \a object bool getPlayerCollidingWith(const MWWorld::ConstPtr& object) override; ///< @return true if the player is colliding with \a object @@ -605,8 +608,8 @@ namespace MWWorld void castSpell (const MWWorld::Ptr& actor, bool manualSpell=false) override; void launchMagicBolt (const std::string& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection) override; - void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile, - const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr bow, float speed, float attackStrength) override; + void launchProjectile (MWWorld::Ptr& actor, MWWorld::Ptr& projectile, + const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr& bow, float speed, float attackStrength) override; void applyLoopingParticles(const MWWorld::Ptr& ptr) override; @@ -644,7 +647,7 @@ namespace MWWorld /// Spawn a blood effect for \a ptr at \a worldPosition void spawnBloodEffect (const MWWorld::Ptr& ptr, const osg::Vec3f& worldPosition) override; - void spawnEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos) override; + void spawnEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos, float scale = 1.f, bool isMagicVFX = true) override; void explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const MWWorld::Ptr& ignore, ESM::RangeType rangeType, const std::string& id, const std::string& sourceName, @@ -672,6 +675,9 @@ namespace MWWorld bool isPlayerInJail() const override; + void setPlayerTraveling(bool traveling) override; + bool isPlayerTraveling() const override; + /// Return terrain height at \a worldPos position. float getTerrainHeightAt(const osg::Vec3f& worldPos) const override; diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 48c8be4d8f..bc39bc2be6 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -1,7 +1,9 @@ find_package(GTest REQUIRED) +find_package(GMock REQUIRED) -if (GTEST_FOUND) +if (GTEST_FOUND AND GMOCK_FOUND) include_directories(SYSTEM ${GTEST_INCLUDE_DIRS}) + include_directories(SYSTEM ${GMOCK_INCLUDE_DIRS}) file(GLOB UNITTEST_SRC_FILES ../openmw/mwworld/store.cpp @@ -13,17 +15,22 @@ if (GTEST_FOUND) esm/test_fixed_string.cpp misc/test_stringops.cpp + + nifloader/testbulletnifloader.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) openmw_add_executable(openmw_test_suite openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) - target_link_libraries(openmw_test_suite ${GTEST_BOTH_LIBRARIES} components) + target_link_libraries(openmw_test_suite ${GMOCK_LIBRARIES} components) # Fix for not visible pthreads functions for linker with glibc 2.15 if (UNIX AND NOT APPLE) target_link_libraries(openmw_test_suite ${CMAKE_THREAD_LIBS_INIT}) endif() + + if (BUILD_WITH_CODE_COVERAGE) + add_definitions(--coverage) + target_link_libraries(openmw_test_suite gcov) + endif() endif() - - diff --git a/apps/openmw_test_suite/misc/test_stringops.cpp b/apps/openmw_test_suite/misc/test_stringops.cpp index 53a33dbf40..081ca8da61 100644 --- a/apps/openmw_test_suite/misc/test_stringops.cpp +++ b/apps/openmw_test_suite/misc/test_stringops.cpp @@ -7,7 +7,7 @@ struct PartialBinarySearchTest : public ::testing::Test std::vector mDataVec; virtual void SetUp() { - const char* data[] = { "Head", "Chest", "Tri Head", "Tri Chest", "Bip01" }; + const char* data[] = { "Head", "Chest", "Tri Head", "Tri Chest", "Bip01", "Tri Bip01" }; mDataVec = std::vector(data, data+sizeof(data)/sizeof(data[0])); std::sort(mDataVec.begin(), mDataVec.end(), Misc::StringUtils::ciLess); } @@ -29,7 +29,15 @@ TEST_F(PartialBinarySearchTest, partial_binary_search_test) EXPECT_TRUE( matches("Tri Head 01") ); EXPECT_TRUE( matches("Tri Head") ); EXPECT_TRUE( matches("tri head") ); + EXPECT_TRUE( matches("Tri bip01") ); + EXPECT_TRUE( matches("bip01") ); + EXPECT_TRUE( matches("bip01 head") ); + EXPECT_TRUE( matches("Bip01 L Hand") ); + EXPECT_TRUE( matches("BIp01 r Clavicle") ); + EXPECT_TRUE( matches("Bip01 SpiNe1") ); + EXPECT_FALSE( matches("") ); + EXPECT_FALSE( matches("Node Bip01") ); EXPECT_FALSE( matches("Hea") ); EXPECT_FALSE( matches(" Head") ); EXPECT_FALSE( matches("Tri Head") ); diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp new file mode 100644 index 0000000000..a2311be490 --- /dev/null +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -0,0 +1,951 @@ +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace +{ + template + bool compareObjects(const T* lhs, const T* rhs) + { + return (!lhs && !rhs) || (lhs && rhs && *lhs == *rhs); + } + + std::vector getTriangles(const btBvhTriangleMeshShape& shape) + { + std::vector result; + auto callback = BulletHelpers::makeProcessTriangleCallback([&] (btVector3* triangle, int, int) { + for (std::size_t i = 0; i < 3; ++i) + result.push_back(triangle[i]); + }); + btVector3 aabbMin; + btVector3 aabbMax; + shape.getAabb(btTransform::getIdentity(), aabbMin, aabbMax); + shape.processAllTriangles(&callback, aabbMin, aabbMax); + return result; + } +} + +static std::ostream& operator <<(std::ostream& stream, const btVector3& value) +{ + return stream << "btVector3 {" + << std::setprecision(std::numeric_limits::max_exponent10) << value.getX() << ", " + << std::setprecision(std::numeric_limits::max_exponent10) << value.getY() << ", " + << std::setprecision(std::numeric_limits::max_exponent10) << value.getZ() << "}"; +} + +static std::ostream& operator <<(std::ostream& stream, const btMatrix3x3& value) +{ + stream << "btMatrix3x3 {"; + for (int i = 0; i < 3; ++i) + stream << value.getRow(i) << ", "; + return stream << "}"; +} + +static std::ostream& operator <<(std::ostream& stream, const btTransform& value) +{ + return stream << "btTransform {" << value.getBasis() << ", " << value.getOrigin() << "}"; +} + +static std::ostream& operator <<(std::ostream& stream, const btCollisionShape* value); + +static std::ostream& operator <<(std::ostream& stream, const btCompoundShape& value) +{ + stream << "btCompoundShape {" << value.getLocalScaling() << ", "; + stream << "{"; + for (int i = 0; i < value.getNumChildShapes(); ++i) + stream << value.getChildShape(i) << ", "; + stream << "},"; + stream << "{"; + for (int i = 0; i < value.getNumChildShapes(); ++i) + stream << value.getChildTransform(i) << ", "; + stream << "}"; + return stream << "}"; +} + +static std::ostream& operator <<(std::ostream& stream, const btBoxShape& value) +{ + return stream << "btBoxShape {" << value.getLocalScaling() << ", " << value.getHalfExtentsWithoutMargin() << "}"; +} + +namespace Resource +{ + +static std::ostream& operator <<(std::ostream& stream, const TriangleMeshShape& value) +{ + stream << "Resource::TriangleMeshShape {" << value.getLocalScaling() << ", " + << value.usesQuantizedAabbCompression() << ", " << value.getOwnsBvh() << ", {"; + auto callback = BulletHelpers::makeProcessTriangleCallback([&] (btVector3* triangle, int, int) { + for (std::size_t i = 0; i < 3; ++i) + stream << triangle[i] << ", "; + }); + btVector3 aabbMin; + btVector3 aabbMax; + value.getAabb(btTransform::getIdentity(), aabbMin, aabbMax); + value.processAllTriangles(&callback, aabbMin, aabbMax); + return stream << "}}"; +} + +} + +static std::ostream& operator <<(std::ostream& stream, const btCollisionShape& value) +{ + switch (value.getShapeType()) + { + case COMPOUND_SHAPE_PROXYTYPE: + return stream << static_cast(value); + case BOX_SHAPE_PROXYTYPE: + return stream << static_cast(value); + case TRIANGLE_MESH_SHAPE_PROXYTYPE: + if (const auto casted = dynamic_cast(&value)) + return stream << *casted; + break; + } + return stream << "btCollisionShape {" << value.getShapeType() << "}"; +} + +static std::ostream& operator <<(std::ostream& stream, const btCollisionShape* value) +{ + return value ? stream << "&" << *value : stream << "nullptr"; +} + +namespace osg +{ + static std::ostream& operator <<(std::ostream& stream, const Vec3f& value) + { + return stream << "osg::Vec3f {" + << value.x() << ", " + << value.y() << ", " + << value.z() << "}"; + } +} + +namespace std +{ + static std::ostream& operator <<(std::ostream& stream, const map& value) + { + stream << "std::map {"; + for (const auto& v : value) + stream << "{" << v.first << ", " << v.second << "},"; + return stream << "}"; + } +} + +namespace Resource +{ + static bool operator ==(const Resource::BulletShape& lhs, const Resource::BulletShape& rhs) + { + return compareObjects(lhs.mCollisionShape, rhs.mCollisionShape) + && lhs.mCollisionBoxHalfExtents == rhs.mCollisionBoxHalfExtents + && lhs.mCollisionBoxTranslate == rhs.mCollisionBoxTranslate + && lhs.mAnimatedShapes == rhs.mAnimatedShapes; + } + + static std::ostream& operator <<(std::ostream& stream, const Resource::BulletShape& value) + { + return stream << "Resource::BulletShape {" + << value.mCollisionShape << ", " + << value.mCollisionBoxHalfExtents << ", " + << value.mAnimatedShapes + << "}"; + } +} + +static bool operator ==(const btCollisionShape& lhs, const btCollisionShape& rhs); + +static bool operator ==(const btCompoundShape& lhs, const btCompoundShape& rhs) +{ + if (lhs.getNumChildShapes() != rhs.getNumChildShapes() || lhs.getLocalScaling() != rhs.getLocalScaling()) + return false; + for (int i = 0; i < lhs.getNumChildShapes(); ++i) + { + if (!compareObjects(lhs.getChildShape(i), rhs.getChildShape(i)) + || !(lhs.getChildTransform(i) == rhs.getChildTransform(i))) + return false; + } + return true; +} + +static bool operator ==(const btBoxShape& lhs, const btBoxShape& rhs) +{ + return lhs.getLocalScaling() == rhs.getLocalScaling() + && lhs.getHalfExtentsWithoutMargin() == rhs.getHalfExtentsWithoutMargin(); +} + +static bool operator ==(const btBvhTriangleMeshShape& lhs, const btBvhTriangleMeshShape& rhs) +{ + return lhs.getLocalScaling() == rhs.getLocalScaling() + && lhs.usesQuantizedAabbCompression() == rhs.usesQuantizedAabbCompression() + && lhs.getOwnsBvh() == rhs.getOwnsBvh() + && getTriangles(lhs) == getTriangles(rhs); +} + +static bool operator ==(const btCollisionShape& lhs, const btCollisionShape& rhs) +{ + if (lhs.getShapeType() != rhs.getShapeType()) + return false; + switch (lhs.getShapeType()) + { + case COMPOUND_SHAPE_PROXYTYPE: + return static_cast(lhs) == static_cast(rhs); + case BOX_SHAPE_PROXYTYPE: + return static_cast(lhs) == static_cast(rhs); + case TRIANGLE_MESH_SHAPE_PROXYTYPE: + if (const auto lhsCasted = dynamic_cast(&lhs)) + if (const auto rhsCasted = dynamic_cast(&rhs)) + return *lhsCasted == *rhsCasted; + return false; + } + return false; +} + +namespace +{ + using namespace testing; + using NifBullet::BulletNifLoader; + + void init(Nif::Transformation& value) + { + value = Nif::Transformation::getIdentity(); + } + + void init(Nif::Extra& value) + { + value.extra = Nif::ExtraPtr(nullptr); + } + + void init(Nif::Controlled& value) + { + init(static_cast(value)); + value.controller = Nif::ControllerPtr(nullptr); + } + + void init(Nif::Named& value) + { + init(static_cast(value)); + } + + void init(Nif::Node& value) + { + init(static_cast(value)); + value.flags = 0; + init(value.trafo); + value.hasBounds = false; + value.parent = nullptr; + value.isBone = false; + } + + void init(Nif::NiTriShape& value) + { + init(static_cast(value)); + value.recType = Nif::RC_NiTriShape; + value.data = Nif::NiTriShapeDataPtr(nullptr); + value.skin = Nif::NiSkinInstancePtr(nullptr); + } + + void init(Nif::NiSkinInstance& value) + { + value.data = Nif::NiSkinDataPtr(nullptr); + value.root = Nif::NodePtr(nullptr); + } + + void init(Nif::Controller& value) + { + value.next = Nif::ControllerPtr(nullptr); + value.flags = 0; + value.frequency = 0; + value.phase = 0; + value.timeStart = 0; + value.timeStop = 0; + value.target = Nif::ControlledPtr(nullptr); + } + + void copy(const btTransform& src, Nif::Transformation& dst) { + dst.pos = osg::Vec3f(src.getOrigin().x(), src.getOrigin().y(), src.getOrigin().z()); + for (int row = 0; row < 3; ++row) + for (int column = 0; column < 3; ++column) + dst.rotation.mValues[column][row] = src.getBasis().getRow(row)[column]; + } + + struct NifFileMock : Nif::File + { + MOCK_CONST_METHOD1(fail, void (const std::string&)); + MOCK_CONST_METHOD1(warn, void (const std::string&)); + MOCK_CONST_METHOD1(getRecord, Nif::Record* (std::size_t)); + MOCK_CONST_METHOD0(numRecords, std::size_t ()); + MOCK_CONST_METHOD1(getRoot, Nif::Record* (std::size_t)); + MOCK_CONST_METHOD0(numRoots, std::size_t ()); + MOCK_METHOD1(setUseSkinning, void (bool)); + MOCK_CONST_METHOD0(getUseSkinning, bool ()); + MOCK_CONST_METHOD0(getFilename, std::string ()); + }; + + struct RecordMock : Nif::Record + { + MOCK_METHOD1(read, void (Nif::NIFStream *nif)); + }; + + struct TestBulletNifLoader : Test + { + BulletNifLoader mLoader; + const StrictMock mNifFile; + Nif::Node mNode; + Nif::Node mNode2; + Nif::NiNode mNiNode; + Nif::NiNode mNiNode2; + Nif::NiNode mNiNode3; + Nif::NiTriShapeData mNiTriShapeData; + Nif::NiTriShape mNiTriShape; + Nif::NiTriShapeData mNiTriShapeData2; + Nif::NiTriShape mNiTriShape2; + Nif::NiSkinInstance mNiSkinInstance; + Nif::NiStringExtraData mNiStringExtraData; + Nif::NiStringExtraData mNiStringExtraData2; + Nif::Controller mController; + btTransform mTransform {btMatrix3x3(btQuaternion(btVector3(1, 0, 0), 0.5f)), btVector3(1, 2, 3)}; + btTransform mResultTransform { + btMatrix3x3( + 1, 0, 0, + 0, 0.82417738437652587890625, 0.56633174419403076171875, + 0, -0.56633174419403076171875, 0.82417738437652587890625 + ), + btVector3(1, 2, 3) + }; + btTransform mResultTransform2 { + btMatrix3x3( + 1, 0, 0, + 0, 0.7951543331146240234375, 0.606407105922698974609375, + 0, -0.606407105922698974609375, 0.7951543331146240234375 + ), + btVector3(4, 8, 12) + }; + + TestBulletNifLoader() + { + init(mNode); + init(mNode2); + init(mNiNode); + init(mNiNode2); + init(mNiNode3); + init(mNiTriShape); + init(mNiTriShape2); + init(mNiSkinInstance); + init(mNiStringExtraData); + init(mNiStringExtraData2); + init(mController); + + mNiTriShapeData.vertices = {osg::Vec3f(0, 0, 0), osg::Vec3f(1, 0, 0), osg::Vec3f(1, 1, 0)}; + mNiTriShapeData.triangles = {0, 1, 2}; + mNiTriShape.data = Nif::NiTriShapeDataPtr(&mNiTriShapeData); + + mNiTriShapeData2.vertices = {osg::Vec3f(0, 0, 1), osg::Vec3f(1, 0, 1), osg::Vec3f(1, 1, 1)}; + mNiTriShapeData2.triangles = {0, 1, 2}; + mNiTriShape2.data = Nif::NiTriShapeDataPtr(&mNiTriShapeData2); + } + }; + + TEST_F(TestBulletNifLoader, for_zero_num_roots_should_return_default) + { + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(0)); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_root_not_nif_node_should_return_default) + { + StrictMock record; + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&record)); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_default_root_nif_node_should_return_default) + { + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_default_root_collision_node_nif_node_should_return_default) + { + mNode.recType = Nif::RC_RootCollisionNode; + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_default_root_nif_node_and_filename_starting_with_x_should_return_default) + { + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("xtest.nif")); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_root_nif_node_with_bounding_box_should_return_shape_with_compound_shape_and_box_inside) + { + mNode.hasBounds = true; + mNode.flags |= Nif::NiNode::Flag_BBoxCollision; + mNode.boundXYZ = osg::Vec3f(1, 2, 3); + mNode.boundPos = osg::Vec3f(-1, -2, -3); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNode)); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3); + expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3); + std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); + std::unique_ptr shape(new btCompoundShape); + shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); + expected.mCollisionShape = shape.release(); + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_child_nif_node_with_bounding_box) + { + mNode.hasBounds = true; + mNode.flags |= Nif::NiNode::Flag_BBoxCollision; + mNode.boundXYZ = osg::Vec3f(1, 2, 3); + mNode.boundPos = osg::Vec3f(-1, -2, -3); + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNode)})); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3); + expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3); + std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); + std::unique_ptr shape(new btCompoundShape); + shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); + expected.mCollisionShape = shape.release(); + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_root_and_child_nif_node_with_bounding_box_but_root_without_flag_should_use_child_bounds) + { + mNode.hasBounds = true; + mNode.flags |= Nif::NiNode::Flag_BBoxCollision; + mNode.boundXYZ = osg::Vec3f(1, 2, 3); + mNode.boundPos = osg::Vec3f(-1, -2, -3); + + mNiNode.hasBounds = true; + mNiNode.boundXYZ = osg::Vec3f(4, 5, 6); + mNiNode.boundPos = osg::Vec3f(-4, -5, -6); + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNode)})); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3); + expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3); + std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); + std::unique_ptr shape(new btCompoundShape); + shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); + expected.mCollisionShape = shape.release(); + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_root_and_two_children_where_both_with_bounds_but_only_first_with_flag_should_use_first_bounds) + { + mNode.hasBounds = true; + mNode.flags |= Nif::NiNode::Flag_BBoxCollision; + mNode.boundXYZ = osg::Vec3f(1, 2, 3); + mNode.boundPos = osg::Vec3f(-1, -2, -3); + + mNode2.hasBounds = true; + mNode2.boundXYZ = osg::Vec3f(4, 5, 6); + mNode2.boundPos = osg::Vec3f(-4, -5, -6); + + mNiNode.hasBounds = true; + mNiNode.boundXYZ = osg::Vec3f(7, 8, 9); + mNiNode.boundPos = osg::Vec3f(-7, -8, -9); + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNode), Nif::NodePtr(&mNode2)})); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3); + expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3); + std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); + std::unique_ptr shape(new btCompoundShape); + shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); + expected.mCollisionShape = shape.release(); + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_root_and_two_children_where_both_with_bounds_but_only_second_with_flag_should_use_second_bounds) + { + mNode.hasBounds = true; + mNode.boundXYZ = osg::Vec3f(1, 2, 3); + mNode.boundPos = osg::Vec3f(-1, -2, -3); + + mNode2.hasBounds = true; + mNode2.flags |= Nif::NiNode::Flag_BBoxCollision; + mNode2.boundXYZ = osg::Vec3f(4, 5, 6); + mNode2.boundPos = osg::Vec3f(-4, -5, -6); + + mNiNode.hasBounds = true; + mNiNode.boundXYZ = osg::Vec3f(7, 8, 9); + mNiNode.boundPos = osg::Vec3f(-7, -8, -9); + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNode), Nif::NodePtr(&mNode2)})); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + expected.mCollisionBoxHalfExtents = osg::Vec3f(4, 5, 6); + expected.mCollisionBoxTranslate = osg::Vec3f(-4, -5, -6); + std::unique_ptr box(new btBoxShape(btVector3(4, 5, 6))); + std::unique_ptr shape(new btCompoundShape); + shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-4, -5, -6)), box.release()); + expected.mCollisionShape = shape.release(); + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_root_nif_node_with_bounds_but_without_flag_should_return_shape_with_bounds_but_with_null_collision_shape) + { + mNode.hasBounds = true; + mNode.boundXYZ = osg::Vec3f(1, 2, 3); + mNode.boundPos = osg::Vec3f(-1, -2, -3); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3); + expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3); + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_tri_shape_root_node_should_return_shape_with_triangle_mesh_shape) + { + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiTriShape)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + std::unique_ptr triangles(new btTriangleMesh(false)); + triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + Resource::BulletShape expected; + expected.mCollisionShape = new Resource::TriangleMeshShape(triangles.release(), true); + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_tri_shape_root_node_with_bounds_should_return_shape_with_bounds_but_with_null_collision_shape) + { + mNiTriShape.hasBounds = true; + mNiTriShape.boundXYZ = osg::Vec3f(1, 2, 3); + mNiTriShape.boundPos = osg::Vec3f(-1, -2, -3); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiTriShape)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3); + expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3); + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_tri_shape_child_node_should_return_shape_with_triangle_mesh_shape) + { + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + std::unique_ptr triangles(new btTriangleMesh(false)); + triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + Resource::BulletShape expected; + expected.mCollisionShape = new Resource::TriangleMeshShape(triangles.release(), true); + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_nested_tri_shape_child_should_return_shape_with_triangle_mesh_shape) + { + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiNode2)})); + mNiNode2.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + std::unique_ptr triangles(new btTriangleMesh(false)); + triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + Resource::BulletShape expected; + expected.mCollisionShape = new Resource::TriangleMeshShape(triangles.release(), true); + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_two_tri_shape_children_should_return_shape_with_triangle_mesh_shape_with_all_meshes) + { + mNiNode.children = Nif::NodeList(std::vector({ + Nif::NodePtr(&mNiTriShape), + Nif::NodePtr(&mNiTriShape2) + })); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + std::unique_ptr triangles(new btTriangleMesh(false)); + triangles->addTriangle(btVector3(0, 0, 1), btVector3(1, 0, 1), btVector3(1, 1, 1)); + triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + Resource::BulletShape expected; + expected.mCollisionShape = new Resource::TriangleMeshShape(triangles.release(), true); + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_tri_shape_child_node_and_filename_starting_with_x_and_not_empty_skin_should_return_shape_with_triangle_mesh_shape) + { + mNiTriShape.skin = Nif::NiSkinInstancePtr(&mNiSkinInstance); + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("xtest.nif")); + const auto result = mLoader.load(mNifFile); + + std::unique_ptr triangles(new btTriangleMesh(false)); + triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + Resource::BulletShape expected; + expected.mCollisionShape = new Resource::TriangleMeshShape(triangles.release(), true); + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_tri_shape_root_node_and_filename_starting_with_x_should_return_shape_with_compound_shape) + { + copy(mTransform, mNiTriShape.trafo); + mNiTriShape.trafo.scale = 3; + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiTriShape)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("xtest.nif")); + const auto result = mLoader.load(mNifFile); + + std::unique_ptr triangles(new btTriangleMesh(false)); + triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); + mesh->setLocalScaling(btVector3(3, 3, 3)); + std::unique_ptr shape(new btCompoundShape); + shape->addChildShape(mResultTransform, mesh.release()); + Resource::BulletShape expected; + expected.mCollisionShape = shape.release(); + expected.mAnimatedShapes = {{-1, 0}}; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_tri_shape_child_node_and_filename_starting_with_x_should_return_shape_with_compound_shape) + { + copy(mTransform, mNiTriShape.trafo); + mNiTriShape.trafo.scale = 3; + mNiTriShape.parent = &mNiNode; + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + mNiNode.trafo.scale = 4; + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("xtest.nif")); + const auto result = mLoader.load(mNifFile); + + std::unique_ptr triangles(new btTriangleMesh(false)); + triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); + mesh->setLocalScaling(btVector3(12, 12, 12)); + std::unique_ptr shape(new btCompoundShape); + shape->addChildShape(mResultTransform2, mesh.release()); + Resource::BulletShape expected; + expected.mCollisionShape = shape.release(); + expected.mAnimatedShapes = {{-1, 0}}; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_two_tri_shape_children_nodes_and_filename_starting_with_x_should_return_shape_with_compound_shape) + { + copy(mTransform, mNiTriShape.trafo); + mNiTriShape.trafo.scale = 3; + + copy(mTransform, mNiTriShape2.trafo); + mNiTriShape2.trafo.scale = 3; + + mNiNode.children = Nif::NodeList(std::vector({ + Nif::NodePtr(&mNiTriShape), + Nif::NodePtr(&mNiTriShape2), + })); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("xtest.nif")); + const auto result = mLoader.load(mNifFile); + + std::unique_ptr triangles(new btTriangleMesh(false)); + triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); + mesh->setLocalScaling(btVector3(3, 3, 3)); + + std::unique_ptr triangles2(new btTriangleMesh(false)); + triangles2->addTriangle(btVector3(0, 0, 1), btVector3(1, 0, 1), btVector3(1, 1, 1)); + std::unique_ptr mesh2(new Resource::TriangleMeshShape(triangles2.release(), true)); + mesh2->setLocalScaling(btVector3(3, 3, 3)); + + std::unique_ptr shape(new btCompoundShape); + shape->addChildShape(mResultTransform, mesh.release()); + shape->addChildShape(mResultTransform, mesh2.release()); + Resource::BulletShape expected; + expected.mCollisionShape = shape.release(); + expected.mAnimatedShapes = {{-1, 0}}; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_controller_should_return_shape_with_compound_shape) + { + mController.recType = Nif::RC_NiKeyframeController; + mController.flags |= Nif::NiNode::ControllerFlag_Active; + copy(mTransform, mNiTriShape.trafo); + mNiTriShape.trafo.scale = 3; + mNiTriShape.parent = &mNiNode; + mNiTriShape.controller = Nif::ControllerPtr(&mController); + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + mNiNode.trafo.scale = 4; + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + std::unique_ptr triangles(new btTriangleMesh(false)); + triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); + mesh->setLocalScaling(btVector3(12, 12, 12)); + std::unique_ptr shape(new btCompoundShape); + shape->addChildShape(mResultTransform2, mesh.release()); + Resource::BulletShape expected; + expected.mCollisionShape = shape.release(); + expected.mAnimatedShapes = {{-1, 0}}; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_two_tri_shape_children_nodes_where_one_with_controller_should_return_shape_with_compound_shape) + { + mController.recType = Nif::RC_NiKeyframeController; + mController.flags |= Nif::NiNode::ControllerFlag_Active; + copy(mTransform, mNiTriShape.trafo); + mNiTriShape.trafo.scale = 3; + copy(mTransform, mNiTriShape2.trafo); + mNiTriShape2.trafo.scale = 3; + mNiTriShape2.parent = &mNiNode; + mNiTriShape2.controller = Nif::ControllerPtr(&mController); + mNiNode.children = Nif::NodeList(std::vector({ + Nif::NodePtr(&mNiTriShape), + Nif::NodePtr(&mNiTriShape2), + })); + mNiNode.trafo.scale = 4; + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + std::unique_ptr triangles(new btTriangleMesh(false)); + triangles->addTriangle(btVector3(1, 2, 3), btVector3(4, 2, 3), btVector3(4, 4.632747650146484375, 1.56172335147857666015625)); + std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); + mesh->setLocalScaling(btVector3(1, 1, 1)); + + std::unique_ptr triangles2(new btTriangleMesh(false)); + triangles2->addTriangle(btVector3(0, 0, 1), btVector3(1, 0, 1), btVector3(1, 1, 1)); + std::unique_ptr mesh2(new Resource::TriangleMeshShape(triangles2.release(), true)); + mesh2->setLocalScaling(btVector3(12, 12, 12)); + + std::unique_ptr shape(new btCompoundShape); + shape->addChildShape(mResultTransform2, mesh2.release()); + shape->addChildShape(btTransform::getIdentity(), mesh.release()); + Resource::BulletShape expected; + expected.mCollisionShape = shape.release(); + expected.mAnimatedShapes = {{-1, 0}}; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_root_avoid_node_and_tri_shape_child_node_should_return_shape_with_null_collision_shape) + { + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + mNiNode.recType = Nif::RC_AvoidNode; + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_empty_data_should_return_shape_with_null_collision_shape) + { + mNiTriShape.data = Nif::NiTriShapeDataPtr(nullptr); + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_empty_data_triangles_should_return_shape_with_null_collision_shape) + { + mNiTriShape.data->triangles.clear(); + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_extra_data_string_starting_with_nc_should_return_shape_with_null_collision_shape) + { + mNiStringExtraData.string = "NC___"; + mNiStringExtraData.recType = Nif::RC_NiStringExtraData; + mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_not_first_extra_data_string_starting_with_nc_should_return_shape_with_null_collision_shape) + { + mNiStringExtraData.extra = Nif::ExtraPtr(&mNiStringExtraData2); + mNiStringExtraData2.string = "NC___"; + mNiStringExtraData2.recType = Nif::RC_NiStringExtraData; + mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_extra_data_string_mrk_should_return_shape_with_null_collision_shape) + { + mNiStringExtraData.string = "MRK"; + mNiStringExtraData.recType = Nif::RC_NiStringExtraData; + mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_extra_data_string_mrk_and_other_collision_node_should_return_shape_with_triangle_mesh_shape_with_all_meshes) + { + mNiStringExtraData.string = "MRK"; + mNiStringExtraData.recType = Nif::RC_NiStringExtraData; + mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); + mNiNode3.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + mNiNode3.recType = Nif::RC_RootCollisionNode; + mNiNode2.children = Nif::NodeList(std::vector({Nif::NodePtr(nullptr), Nif::NodePtr(&mNiNode3)})); + mNiNode2.recType = Nif::RC_NiNode; + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiNode2)})); + mNiNode.recType = Nif::RC_NiNode; + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + std::unique_ptr triangles(new btTriangleMesh(false)); + triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + Resource::BulletShape expected; + expected.mCollisionShape = new Resource::TriangleMeshShape(triangles.release(), true); + + EXPECT_EQ(*result, expected); + } +} diff --git a/appveyor.yml b/appveyor.yml index 97eaa0e262..9877d2105f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -12,6 +12,9 @@ environment: APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 - msvc: 2017 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 +matrix: + allow_failures: + - msvc: 2015 platform: # - Win32 diff --git a/cmake/FindGMock.cmake b/cmake/FindGMock.cmake new file mode 100644 index 0000000000..8d73242423 --- /dev/null +++ b/cmake/FindGMock.cmake @@ -0,0 +1,515 @@ +# Get the Google C++ Mocking Framework. +# (This file is almost an copy of the original FindGTest.cmake file, +# altered to download and compile GMock and GTest if not found +# in GMOCK_ROOT or GTEST_ROOT respectively, +# feel free to use it as it is or modify it for your own needs.) +# +# Defines the following variables: +# +# GMOCK_FOUND - Found or got the Google Mocking framework +# GTEST_FOUND - Found or got the Google Testing framework +# GMOCK_INCLUDE_DIRS - GMock include directory +# GTEST_INCLUDE_DIRS - GTest include direcotry +# +# Also defines the library variables below as normal variables +# +# GMOCK_BOTH_LIBRARIES - Both libgmock & libgmock_main +# GMOCK_LIBRARIES - libgmock +# GMOCK_MAIN_LIBRARIES - libgmock-main +# +# GTEST_BOTH_LIBRARIES - Both libgtest & libgtest_main +# GTEST_LIBRARIES - libgtest +# GTEST_MAIN_LIBRARIES - libgtest_main +# +# Accepts the following variables as input: +# +# GMOCK_ROOT - The root directory of the gmock install prefix +# GTEST_ROOT - The root directory of the gtest install prefix +# GMOCK_SRC_DIR -The directory of the gmock sources +# GMOCK_VER - The version of the gmock sources to be downloaded +# +#----------------------- +# Example Usage: +# +# set(GMOCK_ROOT "~/gmock") +# find_package(GMock REQUIRED) +# include_directories(${GMOCK_INCLUDE_DIRS}) +# +# add_executable(foo foo.cc) +# target_link_libraries(foo ${GMOCK_BOTH_LIBRARIES}) +# +#============================================================================= +# Copyright (c) 2016 Michel Estermann +# Copyright (c) 2016 Kamil Strzempowicz +# Copyright (c) 2011 Matej Svec +# +# CMake - Cross Platform Makefile Generator +# Copyright 2000-2016 Kitware, Inc. +# Copyright 2000-2011 Insight Software Consortium +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the names of Kitware, Inc., the Insight Software Consortium, +# nor the names of their contributors may be used to endorse or promote +# products derived from this software without specific prior written +# permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# ------------------------------------------------------------------------------ +# +# The above copyright and license notice applies to distributions of +# CMake in source and binary form. Some source files contain additional +# notices of original copyright by their contributors; see each source +# for details. Third-party software packages supplied with CMake under +# compatible licenses provide their own copyright notices documented in +# corresponding subdirectories. +# +# ------------------------------------------------------------------------------ +# +# CMake was initially developed by Kitware with the following sponsorship: +# +# * National Library of Medicine at the National Institutes of Health +# as part of the Insight Segmentation and Registration Toolkit (ITK). +# +# * US National Labs (Los Alamos, Livermore, Sandia) ASC Parallel +# Visualization Initiative. +# +# * National Alliance for Medical Image Computing (NAMIC) is funded by the +# National Institutes of Health through the NIH Roadmap for Medical Research, +# Grant U54 EB005149. +# +# * Kitware, Inc. +#============================================================================= +# Thanks to Daniel Blezek for the GTEST_ADD_TESTS code + +function(gtest_add_tests executable extra_args) + if(NOT ARGN) + message(FATAL_ERROR "Missing ARGN: Read the documentation for GTEST_ADD_TESTS") + endif() + if(ARGN STREQUAL "AUTO") + # obtain sources used for building that executable + get_property(ARGN TARGET ${executable} PROPERTY SOURCES) + endif() + set(gtest_case_name_regex ".*\\( *([A-Za-z_0-9]+) *, *([A-Za-z_0-9]+) *\\).*") + set(gtest_test_type_regex "(TYPED_TEST|TEST_?[FP]?)") + foreach(source ${ARGN}) + file(READ "${source}" contents) + string(REGEX MATCHALL "${gtest_test_type_regex} *\\(([A-Za-z_0-9 ,]+)\\)" found_tests ${contents}) + foreach(hit ${found_tests}) + string(REGEX MATCH "${gtest_test_type_regex}" test_type ${hit}) + + # Parameterized tests have a different signature for the filter + if("x${test_type}" STREQUAL "xTEST_P") + string(REGEX REPLACE ${gtest_case_name_regex} "*/\\1.\\2/*" test_name ${hit}) + elseif("x${test_type}" STREQUAL "xTEST_F" OR "x${test_type}" STREQUAL "xTEST") + string(REGEX REPLACE ${gtest_case_name_regex} "\\1.\\2" test_name ${hit}) + elseif("x${test_type}" STREQUAL "xTYPED_TEST") + string(REGEX REPLACE ${gtest_case_name_regex} "\\1/*.\\2" test_name ${hit}) + else() + message(WARNING "Could not parse GTest ${hit} for adding to CTest.") + continue() + endif() + add_test(NAME ${test_name} COMMAND ${executable} --gtest_filter=${test_name} ${extra_args}) + endforeach() + endforeach() +endfunction() + +function(_append_debugs _endvar _library) + if(${_library} AND ${_library}_DEBUG) + set(_output optimized ${${_library}} debug ${${_library}_DEBUG}) + else() + set(_output ${${_library}}) + endif() + set(${_endvar} ${_output} PARENT_SCOPE) +endfunction() + +function(_gmock_find_library _name) + find_library(${_name} + NAMES ${ARGN} + HINTS + ENV GMOCK_ROOT + ${GMOCK_ROOT} + PATH_SUFFIXES ${_gmock_libpath_suffixes} + ) + mark_as_advanced(${_name}) +endfunction() + +function(_gtest_find_library _name) + find_library(${_name} + NAMES ${ARGN} + HINTS + ENV GTEST_ROOT + ${GTEST_ROOT} + PATH_SUFFIXES ${_gtest_libpath_suffixes} + ) + mark_as_advanced(${_name}) +endfunction() + +if(NOT DEFINED GMOCK_MSVC_SEARCH) + set(GMOCK_MSVC_SEARCH MD) +endif() + +set(_gmock_libpath_suffixes lib) +set(_gtest_libpath_suffixes lib) +if(MSVC) + if(GMOCK_MSVC_SEARCH STREQUAL "MD") + list(APPEND _gmock_libpath_suffixes + msvc/gmock-md/Debug + msvc/gmock-md/Release) + list(APPEND _gtest_libpath_suffixes + msvc/gtest-md/Debug + msvc/gtest-md/Release) + elseif(GMOCK_MSVC_SEARCH STREQUAL "MT") + list(APPEND _gmock_libpath_suffixes + msvc/gmock/Debug + msvc/gmock/Release) + list(APPEND _gtest_libpath_suffixes + msvc/gtest/Debug + msvc/gtest/Release) + endif() +endif() + +find_path(GMOCK_INCLUDE_DIR gmock/gmock.h + HINTS + $ENV{GMOCK_ROOT}/include + ${GMOCK_ROOT}/include + ) +mark_as_advanced(GMOCK_INCLUDE_DIR) + +find_path(GTEST_INCLUDE_DIR gtest/gtest.h + HINTS + $ENV{GTEST_ROOT}/include + ${GTEST_ROOT}/include + ) +mark_as_advanced(GTEST_INCLUDE_DIR) + +if(MSVC AND GMOCK_MSVC_SEARCH STREQUAL "MD") + # The provided /MD project files for Google Mock add -md suffixes to the + # library names. + _gmock_find_library(GMOCK_LIBRARY gmock-md gmock) + _gmock_find_library(GMOCK_LIBRARY_DEBUG gmock-mdd gmockd) + _gmock_find_library(GMOCK_MAIN_LIBRARY gmock_main-md gmock_main) + _gmock_find_library(GMOCK_MAIN_LIBRARY_DEBUG gmock_main-mdd gmock_maind) + + _gtest_find_library(GTEST_LIBRARY gtest-md gtest) + _gtest_find_library(GTEST_LIBRARY_DEBUG gtest-mdd gtestd) + _gtest_find_library(GTEST_MAIN_LIBRARY gtest_main-md gtest_main) + _gtest_find_library(GTEST_MAIN_LIBRARY_DEBUG gtest_main-mdd gtest_maind) +else() + _gmock_find_library(GMOCK_LIBRARY gmock) + _gmock_find_library(GMOCK_LIBRARY_DEBUG gmockd) + _gmock_find_library(GMOCK_MAIN_LIBRARY gmock_main) + _gmock_find_library(GMOCK_MAIN_LIBRARY_DEBUG gmock_maind) + + _gtest_find_library(GTEST_LIBRARY gtest) + _gtest_find_library(GTEST_LIBRARY_DEBUG gtestd) + _gtest_find_library(GTEST_MAIN_LIBRARY gtest_main) + _gtest_find_library(GTEST_MAIN_LIBRARY_DEBUG gtest_maind) +endif() + +if(NOT TARGET GTest::GTest) + add_library(GTest::GTest UNKNOWN IMPORTED) +endif() +if(NOT TARGET GTest::Main) + add_library(GTest::Main UNKNOWN IMPORTED) +endif() + +if(NOT TARGET GMock::GMock) + add_library(GMock::GMock UNKNOWN IMPORTED) +endif() + +if(NOT TARGET GMock::Main) + add_library(GMock::Main UNKNOWN IMPORTED) +endif() + +set(GMOCK_LIBRARY_EXISTS OFF) +set(GTEST_LIBRARY_EXISTS OFF) + +if(EXISTS "${GMOCK_LIBRARY}" OR EXISTS "${GMOCK_LIBRARY_DEBUG}" AND GMOCK_INCLUDE_DIR) + set(GMOCK_LIBRARY_EXISTS ON) +endif() + +if(EXISTS "${GTEST_LIBRARY}" OR EXISTS "${GTEST_LIBRARY_DEBUG}" AND GTEST_INCLUDE_DIR) + set(GTEST_LIBRARY_EXISTS ON) +endif() + +if(NOT (${GMOCK_LIBRARY_EXISTS} AND ${GTEST_LIBRARY_EXISTS})) + + include(ExternalProject) + + if(GTEST_USE_STATIC_LIBS) + set(GTEST_CMAKE_ARGS -Dgtest_force_shared_crt:BOOL=ON -DBUILD_SHARED_LIBS=OFF) + if(BUILD_SHARED_LIBS) + list(APPEND GTEST_CMAKE_ARGS + -DCMAKE_POSITION_INDEPENDENT_CODE=ON + -Dgtest_hide_internal_symbols=ON + -DCMAKE_CXX_VISIBILITY_PRESET=hidden + -DCMAKE_VISIBILITY_INLINES_HIDDEN=ON + -DCMAKE_POLICY_DEFAULT_CMP0063=NEW + ) + endif() + set(GTEST_LIBRARY_PREFIX ${CMAKE_STATIC_LIBRARY_PREFIX}) + else() + set(GTEST_CMAKE_ARGS -DBUILD_SHARED_LIBS=ON) + set(GTEST_LIBRARY_PREFIX ${CMAKE_SHARED_LIBRARY_PREFIX}) + endif() + if(WIN32) + list(APPEND GTEST_CMAKE_ARGS -Dgtest_disable_pthreads=ON) + endif() + + if("${GMOCK_SRC_DIR}" STREQUAL "") + message(STATUS "Downloading GMock / GTest version ${GMOCK_VER} from git") + if("${GMOCK_VER}" STREQUAL "1.6.0" OR "${GMOCK_VER}" STREQUAL "1.7.0") + set(GTEST_BIN_DIR "${GMOCK_ROOT}/src/gtest-build") + set(GTEST_LIBRARY "${GTEST_BIN_DIR}/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gtest${CMAKE_STATIC_LIBRARY_SUFFIX}") + set(GTEST_MAIN_LIBRARY "${GTEST_BIN_DIR}/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gtest_main${CMAKE_STATIC_LIBRARY_SUFFIX}") + mark_as_advanced(GTEST_LIBRARY) + mark_as_advanced(GTEST_MAIN_LIBRARY) + + externalproject_add( + gtest + GIT_REPOSITORY "https://github.com/google/googletest.git" + GIT_TAG "release-${GMOCK_VER}" + PREFIX ${GMOCK_ROOT} + INSTALL_COMMAND "" + LOG_DOWNLOAD ON + LOG_CONFIGURE ON + LOG_BUILD ON + CMAKE_ARGS + ${GTEST_CMAKE_ARGS} + BINARY_DIR ${GTEST_BIN_DIR} + BUILD_BYPRODUCTS + "${GTEST_LIBRARY}" + "${GTEST_MAIN_LIBRARY}" + ) + + set(GMOCK_BIN_DIR "${GMOCK_ROOT}/src/gmock-build") + set(GMOCK_LIBRARY "${GMOCK_BIN_DIR}/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gmock${CMAKE_STATIC_LIBRARY_SUFFIX}") + set(GMOCK_MAIN_LIBRARY "${GMOCK_BIN_DIR}/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gmock_main${CMAKE_STATIC_LIBRARY_SUFFIX}") + mark_as_advanced(GMOCK_LIBRARY) + mark_as_advanced(GMOCK_MAIN_LIBRARY) + + externalproject_add( + gmock + GIT_REPOSITORY "https://github.com/google/googlemock.git" + GIT_TAG "release-${GMOCK_VER}" + PREFIX ${GMOCK_ROOT} + INSTALL_COMMAND "" + LOG_DOWNLOAD ON + LOG_CONFIGURE ON + LOG_BUILD ON + CMAKE_ARGS + ${GTEST_CMAKE_ARGS} + BINARY_DIR ${GMOCK_BIN_DIR} + BUILD_BYPRODUCTS + "${GMOCK_LIBRARY}" + "${GMOCK_MAIN_LIBRARY}" + ) + + add_dependencies(gmock gtest) + + add_dependencies(GTest::GTest gtest) + add_dependencies(GTest::Main gtest) + add_dependencies(GMock::GMock gmock) + add_dependencies(GMock::Main gmock) + + externalproject_get_property(gtest source_dir) + set(GTEST_INCLUDE_DIR "${source_dir}/include") + mark_as_advanced(GTEST_INCLUDE_DIR) + externalproject_get_property(gmock source_dir) + set(GMOCK_INCLUDE_DIR "${source_dir}/include") + mark_as_advanced(GMOCK_INCLUDE_DIR) + else() #1.8.0 + set(GMOCK_BIN_DIR "${GMOCK_ROOT}/src/gmock-build") + set(GTEST_LIBRARY "${GMOCK_BIN_DIR}/googlemock/gtest/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gtest${CMAKE_STATIC_LIBRARY_SUFFIX}") + set(GTEST_MAIN_LIBRARY "${GMOCK_BIN_DIR}/googlemock/gtest/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gtest_main${CMAKE_STATIC_LIBRARY_SUFFIX}") + set(GMOCK_LIBRARY "${GMOCK_BIN_DIR}/googlemock/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gmock${CMAKE_STATIC_LIBRARY_SUFFIX}") + set(GMOCK_MAIN_LIBRARY "${GMOCK_BIN_DIR}/googlemock/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gmock_main${CMAKE_STATIC_LIBRARY_SUFFIX}") + mark_as_advanced(GTEST_LIBRARY) + mark_as_advanced(GTEST_MAIN_LIBRARY) + mark_as_advanced(GMOCK_LIBRARY) + mark_as_advanced(GMOCK_MAIN_LIBRARY) + + externalproject_add( + gmock + GIT_REPOSITORY "https://github.com/google/googletest.git" + GIT_TAG "release-${GMOCK_VER}" + PREFIX ${GMOCK_ROOT} + INSTALL_COMMAND "" + LOG_DOWNLOAD ON + LOG_CONFIGURE ON + LOG_BUILD ON + CMAKE_ARGS + ${GTEST_CMAKE_ARGS} + BINARY_DIR "${GMOCK_BIN_DIR}" + BUILD_BYPRODUCTS + "${GTEST_LIBRARY}" + "${GTEST_MAIN_LIBRARY}" + "${GMOCK_LIBRARY}" + "${GMOCK_MAIN_LIBRARY}" + ) + + add_dependencies(GTest::GTest gmock) + add_dependencies(GTest::Main gmock) + add_dependencies(GMock::GMock gmock) + add_dependencies(GMock::Main gmock) + + externalproject_get_property(gmock source_dir) + set(GTEST_INCLUDE_DIR "${source_dir}/googletest/include") + set(GMOCK_INCLUDE_DIR "${source_dir}/googlemock/include") + mark_as_advanced(GMOCK_INCLUDE_DIR) + mark_as_advanced(GTEST_INCLUDE_DIR) + endif() + + # Prevent CMake from complaining about these directories missing when the libgtest/libgmock targets get used as dependencies + file(MAKE_DIRECTORY ${GTEST_INCLUDE_DIR} ${GMOCK_INCLUDE_DIR}) + else() + message(STATUS "Building Gmock / Gtest from dir ${GMOCK_SRC_DIR}") + + set(GMOCK_BIN_DIR "${GMOCK_ROOT}/src/gmock-build") + set(GTEST_LIBRARY "${GMOCK_BIN_DIR}/gtest/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gtest${CMAKE_STATIC_LIBRARY_SUFFIX}") + set(GTEST_MAIN_LIBRARY "${GMOCK_BIN_DIR}/gtest/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gtest_main${CMAKE_STATIC_LIBRARY_SUFFIX}") + set(GMOCK_LIBRARY "${GMOCK_BIN_DIR}/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gmock${CMAKE_STATIC_LIBRARY_SUFFIX}") + set(GMOCK_MAIN_LIBRARY "${GMOCK_BIN_DIR}/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gmock_main${CMAKE_STATIC_LIBRARY_SUFFIX}") + mark_as_advanced(GTEST_LIBRARY) + mark_as_advanced(GTEST_MAIN_LIBRARY) + mark_as_advanced(GMOCK_LIBRARY) + mark_as_advanced(GMOCK_MAIN_LIBRARY) + + if(EXISTS "${GMOCK_SRC_DIR}/gtest/include/gtest/gtest.h") + set(GTEST_INCLUDE_DIR "${GMOCK_SRC_DIR}/gtest/include") + mark_as_advanced(GTEST_INCLUDE_DIR) + endif() + if(EXISTS "${GMOCK_SRC_DIR}/include/gmock/gmock.h") + set(GMOCK_INCLUDE_DIR "${GMOCK_SRC_DIR}/include") + mark_as_advanced(GMOCK_INCLUDE_DIR) + elseif(EXISTS "${GMOCK_SRC_DIR}/../../include/gmock/gmock.h") + set(GMOCK_INCLUDE_DIR "${GMOCK_SRC_DIR}/../../include") + if(IS_ABSOLUTE "${GMOCK_INCLUDE_DIR}") + get_filename_component(GMOCK_INCLUDE_DIR "${GMOCK_INCLUDE_DIR}" ABSOLUTE) + endif() + mark_as_advanced(GMOCK_INCLUDE_DIR) + endif() + + externalproject_add( + gmock + SOURCE_DIR ${GMOCK_SRC_DIR} + PREFIX ${GMOCK_ROOT} + INSTALL_COMMAND "" + LOG_DOWNLOAD ON + LOG_CONFIGURE ON + LOG_BUILD ON + CMAKE_ARGS + ${GTEST_CMAKE_ARGS} + BINARY_DIR "${GMOCK_BIN_DIR}" + BUILD_BYPRODUCTS + "${GTEST_LIBRARY}" + "${GTEST_MAIN_LIBRARY}" + "${GMOCK_LIBRARY}" + "${GMOCK_MAIN_LIBRARY}" + ) + + add_dependencies(GTest::GTest gmock) + add_dependencies(GTest::Main gmock) + add_dependencies(GMock::GMock gmock) + add_dependencies(GMock::Main gmock) + endif() +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(GTest DEFAULT_MSG GTEST_LIBRARY GTEST_INCLUDE_DIR GTEST_MAIN_LIBRARY) +find_package_handle_standard_args(GMock DEFAULT_MSG GMOCK_LIBRARY GMOCK_INCLUDE_DIR GMOCK_MAIN_LIBRARY) + +include(CMakeFindDependencyMacro) +find_dependency(Threads) + +set_target_properties(GTest::GTest PROPERTIES + INTERFACE_LINK_LIBRARIES "Threads::Threads" + IMPORTED_LINK_INTERFACE_LANGUAGES "CXX" + IMPORTED_LOCATION "${GTEST_LIBRARY}" + ) + +if(GTEST_INCLUDE_DIR) + set_target_properties(GTest::GTest PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${GTEST_INCLUDE_DIR}" + INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${GTEST_INCLUDE_DIR}" + ) +endif() + +set_target_properties(GTest::Main PROPERTIES + INTERFACE_LINK_LIBRARIES "GTest::GTest" + IMPORTED_LINK_INTERFACE_LANGUAGES "CXX" + IMPORTED_LOCATION "${GTEST_MAIN_LIBRARY}") + +set_target_properties(GMock::GMock PROPERTIES + INTERFACE_LINK_LIBRARIES "Threads::Threads" + IMPORTED_LINK_INTERFACE_LANGUAGES "CXX" + IMPORTED_LOCATION "${GMOCK_LIBRARY}") + +if(GMOCK_INCLUDE_DIR) + set_target_properties(GMock::GMock PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${GMOCK_INCLUDE_DIR}" + INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${GMOCK_INCLUDE_DIR}" + ) + if(GMOCK_VER VERSION_LESS "1.7") + # GMock 1.6 still has GTest as an external link-time dependency, + # so just specify it on the link interface. + set_property(TARGET GMock::GMock APPEND PROPERTY + INTERFACE_LINK_LIBRARIES GTest::GTest) + elseif(GTEST_INCLUDE_DIR) + # GMock 1.7 and beyond doesn't have it as a link-time dependency anymore, + # so merge it's compile-time interface (include dirs) with ours. + set_property(TARGET GMock::GMock APPEND PROPERTY + INTERFACE_INCLUDE_DIRECTORIES "${GTEST_INCLUDE_DIR}") + set_property(TARGET GMock::GMock APPEND PROPERTY + INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${GTEST_INCLUDE_DIR}") + endif() +endif() + +set_target_properties(GMock::Main PROPERTIES + INTERFACE_LINK_LIBRARIES "GMock::GMock" + IMPORTED_LINK_INTERFACE_LANGUAGES "CXX" + IMPORTED_LOCATION "${GMOCK_MAIN_LIBRARY}") + +if(GTEST_FOUND) + set(GTEST_INCLUDE_DIRS ${GTEST_INCLUDE_DIR}) + set(GTEST_LIBRARIES GTest::GTest) + set(GTEST_MAIN_LIBRARIES GTest::Main) + set(GTEST_BOTH_LIBRARIES ${GTEST_LIBRARIES} ${GTEST_MAIN_LIBRARIES}) + if(VERBOSE) + message(STATUS "GTest includes: ${GTEST_INCLUDE_DIRS}") + message(STATUS "GTest libs: ${GTEST_BOTH_LIBRARIES}") + endif() +endif() + +if(GMOCK_FOUND) + set(GMOCK_INCLUDE_DIRS ${GMOCK_INCLUDE_DIR}) + set(GMOCK_LIBRARIES GMock::GMock) + set(GMOCK_MAIN_LIBRARIES GMock::Main) + set(GMOCK_BOTH_LIBRARIES ${GMOCK_LIBRARIES} ${GMOCK_MAIN_LIBRARIES}) + if(VERBOSE) + message(STATUS "GMock includes: ${GMOCK_INCLUDE_DIRS}") + message(STATUS "GMock libs: ${GMOCK_BOTH_LIBRARIES}") + endif() +endif() diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index b6f390b069..551767f8f0 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -88,6 +88,10 @@ add_component_dir (misc utf8stream stringops resourcehelpers rng messageformatparser ) +add_component_dir (debug + debugging debuglog + ) + IF(NOT WIN32 AND NOT APPLE) add_definitions(-DGLOBAL_DATA_PATH="${GLOBAL_DATA_PATH}") add_definitions(-DGLOBAL_CONFIG_PATH="${GLOBAL_CONFIG_PATH}") @@ -242,6 +246,11 @@ if (UNIX AND NOT APPLE) target_link_libraries(components ${CMAKE_THREAD_LIBS_INIT}) endif() +if (BUILD_WITH_CODE_COVERAGE) + add_definitions(--coverage) + target_link_libraries(components gcov) +endif() + # Make the variable accessible for other subdirectories set(COMPONENT_FILES ${COMPONENT_FILES} PARENT_SCOPE) diff --git a/components/bullethelpers/processtrianglecallback.hpp b/components/bullethelpers/processtrianglecallback.hpp new file mode 100644 index 0000000000..ee005b459e --- /dev/null +++ b/components/bullethelpers/processtrianglecallback.hpp @@ -0,0 +1,34 @@ +#ifndef OPENMW_COMPONENTS_BULLETHELPERS_PROCESSTRIANGLECALLBACK_H +#define OPENMW_COMPONENTS_BULLETHELPERS_PROCESSTRIANGLECALLBACK_H + +#include + +#include + +namespace BulletHelpers +{ + template + class ProcessTriangleCallback : public btTriangleCallback + { + public: + ProcessTriangleCallback(Impl impl) + : mImpl(std::move(impl)) + {} + + void processTriangle(btVector3* triangle, int partId, int triangleIndex) override final + { + return mImpl(triangle, partId, triangleIndex); + } + + private: + Impl mImpl; + }; + + template + ProcessTriangleCallback::type> makeProcessTriangleCallback(Impl&& impl) + { + return ProcessTriangleCallback::type>(std::forward(impl)); + } +} + +#endif diff --git a/components/compiler/lineparser.cpp b/components/compiler/lineparser.cpp index 2d551348de..602bb826fb 100644 --- a/components/compiler/lineparser.cpp +++ b/components/compiler/lineparser.cpp @@ -557,7 +557,7 @@ namespace Compiler mExplicit.clear(); } - void GetArgumentsFromMessageFormat::visitedPlaceholder(Placeholder placeholder, char /*padding*/, int /*width*/, int /*precision*/) + void GetArgumentsFromMessageFormat::visitedPlaceholder(Placeholder placeholder, char /*padding*/, int /*width*/, int /*precision*/, Notation /*notation*/) { switch (placeholder) { diff --git a/components/compiler/lineparser.hpp b/components/compiler/lineparser.hpp index d92c4895e2..8f7f64bf2c 100644 --- a/components/compiler/lineparser.hpp +++ b/components/compiler/lineparser.hpp @@ -83,7 +83,7 @@ namespace Compiler std::string mArguments; protected: - virtual void visitedPlaceholder(Placeholder placeholder, char padding, int width, int precision); + virtual void visitedPlaceholder(Placeholder placeholder, char padding, int width, int precision, Notation notation); virtual void visitedCharacter(char c) {} public: diff --git a/components/compiler/scanner.cpp b/components/compiler/scanner.cpp index bb0fb93740..b56dbb95bc 100644 --- a/components/compiler/scanner.cpp +++ b/components/compiler/scanner.cpp @@ -306,10 +306,22 @@ namespace Compiler int i = 0; std::string lowerCase = Misc::StringUtils::lowerCase(name); - + bool isKeyword = false; for (; sKeywords[i]; ++i) if (lowerCase==sKeywords[i]) + { + isKeyword = true; break; + } + + // Russian localization and some mods use a quirk - add newline character directly + // to compiled bytecode via HEX-editor to implement multiline messageboxes. + // Of course, original editor will not compile such script. + // Allow messageboxes to bybass the "incomplete string or name" error. + if (lowerCase == "messagebox") + enableIgnoreNewlines(); + else if (isKeyword) + mIgnoreNewline = false; if (sKeywords[i]) { @@ -357,9 +369,14 @@ namespace Compiler // } else if (c=='\n') { - error = true; - mErrorHandler.error ("incomplete string or name", mLoc); - break; + if (mIgnoreNewline) + mErrorHandler.warning ("string contains newline character, make sure that it is intended", mLoc); + else + { + error = true; + mErrorHandler.error ("incomplete string or name", mLoc); + break; + } } } else if (!(c=='"' && name.empty())) @@ -502,6 +519,11 @@ namespace Compiler if (get (c) && c!='=') // <== is a allowed as an alternative to <= :( putback (c); } + else if (c == '<' || c == '>') // Treat <> and << as < + { + special = S_cmpLT; + mErrorHandler.warning (std::string("invalid operator <") + c + ", treating it as <", mLoc); + } else { putback (c); @@ -525,6 +547,11 @@ namespace Compiler if (get (c) && c!='=') // >== is a allowed as an alternative to >= :( putback (c); } + else if (c == '<' || c == '>') // Treat >< and >> as > + { + special = S_cmpGT; + mErrorHandler.warning (std::string("invalid operator >") + c + ", treating it as >", mLoc); + } else { putback (c); @@ -578,7 +605,7 @@ namespace Compiler const Extensions *extensions) : mErrorHandler (errorHandler), mStream (inputStream), mExtensions (extensions), mPutback (Putback_None), mPutbackCode(0), mPutbackInteger(0), mPutbackFloat(0), - mStrictKeywords (false), mTolerantNames (false) + mStrictKeywords (false), mTolerantNames (false), mIgnoreNewline(false) { } @@ -631,6 +658,11 @@ namespace Compiler mExtensions->listKeywords (keywords); } + void Scanner::enableIgnoreNewlines() + { + mIgnoreNewline = true; + } + void Scanner::enableStrictKeywords() { mStrictKeywords = true; diff --git a/components/compiler/scanner.hpp b/components/compiler/scanner.hpp index 49fbaa96a2..a431cabb29 100644 --- a/components/compiler/scanner.hpp +++ b/components/compiler/scanner.hpp @@ -39,6 +39,7 @@ namespace Compiler TokenLoc mPutbackLoc; bool mStrictKeywords; bool mTolerantNames; + bool mIgnoreNewline; public: @@ -126,6 +127,11 @@ namespace Compiler void listKeywords (std::vector& keywords); ///< Append all known keywords to \a keywords. + /// Treat newline character as a part of script command. + /// + /// \attention This mode lasts only until the next keyword is reached. + void enableIgnoreNewlines(); + /// Do not accept keywords in quotation marks anymore. /// /// \attention This mode lasts only until the next newline is reached. diff --git a/components/crashcatcher/crashcatcher.cpp b/components/crashcatcher/crashcatcher.cpp index e5377b64f4..04b239a7fa 100644 --- a/components/crashcatcher/crashcatcher.cpp +++ b/components/crashcatcher/crashcatcher.cpp @@ -14,7 +14,7 @@ #include #include -#include +#include #include @@ -182,8 +182,10 @@ static void gdb_info(pid_t pid) /* Error creating temp file */ if(fd >= 0) { - close(fd); - remove(respfile); + if (close(fd) == 0) + remove(respfile); + else + Log(Debug::Warning) << "Warning: can not close and remove file '" << respfile << "': " << std::strerror(errno); } printf("!!! Could not create gdb command file\n"); } @@ -515,8 +517,9 @@ void crashCatcherInstall(int argc, char **argv, const std::string &crashLogPath) int s[5] = { SIGSEGV, SIGILL, SIGFPE, SIGBUS, SIGABRT }; if (crashCatcherInstallHandlers(argc, argv, 5, s, crashLogPath.c_str(), NULL) == -1) { - std::cerr << "Installing crash handler failed" << std::endl; - } else - std::cout << "Crash handler installed" << std::endl; + Log(Debug::Warning) << "Installing crash handler failed"; + } + else + Log(Debug::Info) << "Crash handler installed"; } } diff --git a/components/debug/debugging.cpp b/components/debug/debugging.cpp new file mode 100644 index 0000000000..a4c59c2216 --- /dev/null +++ b/components/debug/debugging.cpp @@ -0,0 +1,116 @@ +#include "debugging.hpp" + +#include + +namespace Debug +{ + std::streamsize DebugOutputBase::write(const char *str, std::streamsize size) + { + // Skip debug level marker + Level level = getLevelMarker(str); + if (level != NoLevel) + { + writeImpl(str+1, size-1, level); + return size; + } + + writeImpl(str, size, NoLevel); + return size; + } + + Level DebugOutputBase::getLevelMarker(const char *str) + { + if (unsigned(*str) <= unsigned(Marker)) + { + return Level(*str); + } + + return NoLevel; + } + + void DebugOutputBase::fillCurrentDebugLevel() + { + const char* env = getenv("OPENMW_DEBUG_LEVEL"); + if (env) + { + std::string value(env); + if (value == "ERROR") + CurrentDebugLevel = Error; + else if (value == "WARNING") + CurrentDebugLevel = Warning; + else if (value == "INFO") + CurrentDebugLevel = Info; + else if (value == "VERBOSE") + CurrentDebugLevel = Verbose; + + return; + } + + CurrentDebugLevel = Verbose; + } +} + +int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, char *argv[], const std::string& appName) +{ + // Some objects used to redirect cout and cerr + // Scope must be here, so this still works inside the catch block for logging exceptions + std::streambuf* cout_rdbuf = std::cout.rdbuf (); + std::streambuf* cerr_rdbuf = std::cerr.rdbuf (); + +#if !(defined(_WIN32) && defined(_DEBUG)) + boost::iostreams::stream_buffer coutsb; + boost::iostreams::stream_buffer cerrsb; +#endif + + const std::string logName = Misc::StringUtils::lowerCase(appName) + ".log"; + const std::string crashLogName = Misc::StringUtils::lowerCase(appName) + "-crash.log"; + boost::filesystem::ofstream logfile; + + int ret = 0; + try + { + Files::ConfigurationManager cfgMgr; + +#if defined(_WIN32) && defined(_DEBUG) + // Redirect cout and cerr to VS debug output when running in debug mode + boost::iostreams::stream_buffer sb; + sb.open(Debug::DebugOutput()); + std::cout.rdbuf (&sb); + std::cerr.rdbuf (&sb); +#else + // Redirect cout and cerr to the log file + logfile.open (boost::filesystem::path(cfgMgr.getLogPath() / logName)); + + std::ostream oldcout(cout_rdbuf); + std::ostream oldcerr(cerr_rdbuf); + coutsb.open (Debug::Tee(logfile, oldcout)); + cerrsb.open (Debug::Tee(logfile, oldcerr)); + + std::cout.rdbuf (&coutsb); + std::cerr.rdbuf (&cerrsb); +#endif + + // install the crash handler as soon as possible. note that the log path + // does not depend on config being read. + crashCatcherInstall(argc, argv, (cfgMgr.getLogPath() / crashLogName).string()); + + ret = innerApplication(argc, argv); + } + catch (std::exception& e) + { +#if (defined(__APPLE__) || defined(__linux) || defined(__unix) || defined(__posix)) + if (!isatty(fileno(stdin))) +#endif + SDL_ShowSimpleMessageBox(0, (appName + ": Fatal error").c_str(), e.what(), NULL); + + Log(Debug::Error) << "Error: " << e.what(); + + ret = 1; + } + + // Restore cout and cerr + std::cout.rdbuf(cout_rdbuf); + std::cerr.rdbuf(cerr_rdbuf); + + return ret; +} diff --git a/components/debug/debugging.hpp b/components/debug/debugging.hpp new file mode 100644 index 0000000000..59536d685c --- /dev/null +++ b/components/debug/debugging.hpp @@ -0,0 +1,129 @@ +#ifndef DEBUG_DEBUGGING_H +#define DEBUG_DEBUGGING_H + +#include +#include + +#include + +#include + +#include "debuglog.hpp" + +namespace Debug +{ + // ANSI colors for terminal + enum Color + { + Reset = 0, + DarkGray = 90, + Red = 91, + Yellow = 93 + }; + + class DebugOutputBase : public boost::iostreams::sink + { + public: + DebugOutputBase() + { + if (CurrentDebugLevel == NoLevel) + fillCurrentDebugLevel(); + } + + virtual std::streamsize write(const char *str, std::streamsize size); + + protected: + static Level getLevelMarker(const char *str); + + static void fillCurrentDebugLevel(); + + virtual std::streamsize writeImpl(const char *str, std::streamsize size, Level debugLevel) + { + return size; + } + + char mDebugLevel; + }; + +#if defined(_WIN32) && defined(_DEBUG) + class DebugOutput : public DebugOutputBase + { + public: + std::streamsize writeImpl(const char *str, std::streamsize size, Level debugLevel) + { + // Make a copy for null termination + std::string tmp (str, static_cast(size)); + // Write string to Visual Studio Debug output + OutputDebugString (tmp.c_str ()); + return size; + } + + virtual ~DebugOutput() {} + }; +#else + + class Tee : public DebugOutputBase + { + public: + Tee(std::ostream &stream, std::ostream &stream2) + : out(stream), out2(stream2) + { + // TODO: check which stream is stderr? + mUseColor = useColoredOutput(); + + mColors[Error] = Red; + mColors[Warning] = Yellow; + mColors[Info] = Reset; + mColors[Verbose] = DarkGray; + mColors[NoLevel] = Reset; + } + + virtual std::streamsize writeImpl(const char *str, std::streamsize size, Level debugLevel) + { + out.write (str, size); + out.flush(); + + if(mUseColor) + { + out2 << "\033[0;" << mColors[debugLevel] << "m"; + out2.write (str, size); + out2 << "\033[0;" << Reset << "m"; + } + else + { + out2.write(str, size); + } + out2.flush(); + + return size; + } + + virtual ~Tee() {} + + private: + + static bool useColoredOutput() + { + // Note: cmd.exe in Win10 should support ANSI colors, but in its own way. +#if defined(_WIN32) + return 0; +#else + char *term = getenv("TERM"); + bool useColor = term && !getenv("NO_COLOR") && isatty(fileno(stderr)); + + return useColor; +#endif + } + + std::ostream &out; + std::ostream &out2; + bool mUseColor; + + std::map mColors; + }; +#endif +} + +int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, char *argv[], const std::string& appName); + +#endif diff --git a/components/debug/debuglog.cpp b/components/debug/debuglog.cpp new file mode 100644 index 0000000000..510c638614 --- /dev/null +++ b/components/debug/debuglog.cpp @@ -0,0 +1,8 @@ +#include "debuglog.hpp" + +namespace Debug +{ + Level CurrentDebugLevel = Level::NoLevel; +} + +std::mutex Log::sLock; diff --git a/components/debug/debuglog.hpp b/components/debug/debuglog.hpp new file mode 100644 index 0000000000..f4a8e17bef --- /dev/null +++ b/components/debug/debuglog.hpp @@ -0,0 +1,57 @@ +#ifndef DEBUG_LOG_H +#define DEBUG_LOG_H + +#include +#include + +namespace Debug +{ + enum Level + { + NoLevel = 0, + Error = 1, + Warning = 2, + Info = 3, + Verbose = 4, + Marker = Verbose + }; + + extern Level CurrentDebugLevel; +} + +class Log +{ + static std::mutex sLock; + + std::unique_lock mLock; +public: + // Locks a global lock while the object is alive + Log(Debug::Level level) : + mLock(sLock), + mLevel(level) + { + if (mLevel <= Debug::CurrentDebugLevel) + std::cout << static_cast(mLevel); + } + + // Perfect forwarding wrappers to give the chain of objects to cout + template + Log& operator<<(T&& rhs) + { + if (mLevel <= Debug::CurrentDebugLevel) + std::cout << std::forward(rhs); + + return *this; + } + + ~Log() + { + if (mLevel <= Debug::CurrentDebugLevel) + std::cout << std::endl; + } + +private: + Debug::Level mLevel; +}; + +#endif diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp index 1f7c2b964d..4b9c6b0734 100644 --- a/components/esm/cellref.cpp +++ b/components/esm/cellref.cpp @@ -1,6 +1,6 @@ #include "cellref.hpp" -#include +#include #include "esmreader.hpp" #include "esmwriter.hpp" @@ -48,9 +48,7 @@ void ESM::CellRef::loadId (ESMReader& esm, bool wideRefNum) mRefID = esm.getHNOString ("NAME"); if (mRefID.empty()) { - std::ios::fmtflags f(std::cerr.flags()); - std::cerr << "Warning: got CellRef with empty RefId in " << esm.getName() << " 0x" << std::hex << esm.getFileOffset() << std::endl; - std::cerr.flags(f); + Log(Debug::Warning) << "Warning: got CellRef with empty RefId in " << esm.getName() << " 0x" << std::hex << esm.getFileOffset(); } } diff --git a/components/esm/loadcrea.cpp b/components/esm/loadcrea.cpp index ddd49f8df9..83315a5b8f 100644 --- a/components/esm/loadcrea.cpp +++ b/components/esm/loadcrea.cpp @@ -1,6 +1,6 @@ #include "loadcrea.hpp" -#include +#include #include "esmreader.hpp" #include "esmwriter.hpp" @@ -89,7 +89,7 @@ namespace ESM { // seems to occur only in .ESS files, unsure of purpose int index; esm.getHT(index); - std::cerr << "Creature::load: Unhandled INDX " << index << std::endl; + Log(Debug::Warning) << "Creature::load: Unhandled INDX " << index; break; default: esm.fail("Unknown subrecord"); diff --git a/components/esm/loaddial.cpp b/components/esm/loaddial.cpp index 9ef3d3964a..d7e0a6ee17 100644 --- a/components/esm/loaddial.cpp +++ b/components/esm/loaddial.cpp @@ -1,6 +1,6 @@ #include "loaddial.hpp" -#include +#include #include "esmreader.hpp" #include "esmwriter.hpp" @@ -128,7 +128,7 @@ namespace ESM return; } - std::cerr << "Warning: Failed to insert info " << info.mId << std::endl; + Log(Debug::Warning) << "Warning: Failed to insert info " << info.mId; } void Dialogue::clearDeletedInfos() diff --git a/components/esm/loadgmst.cpp b/components/esm/loadgmst.cpp index 1ebb002e68..da8d256e7d 100644 --- a/components/esm/loadgmst.cpp +++ b/components/esm/loadgmst.cpp @@ -22,21 +22,6 @@ namespace ESM mValue.write (esm, ESM::Variant::Format_Gmst); } - int GameSetting::getInt() const - { - return mValue.getInteger(); - } - - float GameSetting::getFloat() const - { - return mValue.getFloat(); - } - - std::string GameSetting::getString() const - { - return mValue.getString(); - } - void GameSetting::blank() { mValue.setType (ESM::VT_None); diff --git a/components/esm/loadgmst.hpp b/components/esm/loadgmst.hpp index 73a723e810..c40d348fe4 100644 --- a/components/esm/loadgmst.hpp +++ b/components/esm/loadgmst.hpp @@ -28,17 +28,6 @@ struct GameSetting void load(ESMReader &esm, bool &isDeleted); - /// \todo remove the get* functions (redundant, since mValue has equivalent functions now). - - int getInt() const; - ///< Throws an exception if GMST is not of type int or float. - - float getFloat() const; - ///< Throws an exception if GMST is not of type int or float. - - std::string getString() const; - ///< Throwns an exception if GMST is not of type string. - void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); diff --git a/components/esm/loadmgef.cpp b/components/esm/loadmgef.cpp index 9f8ad94e1e..75a94f828a 100644 --- a/components/esm/loadmgef.cpp +++ b/components/esm/loadmgef.cpp @@ -274,43 +274,46 @@ short MagicEffect::getResistanceEffect(short effect) // Source https://wiki.openmw.org/index.php?title=Research:Magic#Effect_attribute // - std::map effects; - effects[DisintegrateArmor] = Sanctuary; - effects[DisintegrateWeapon] = Sanctuary; - - for (int i=0; i<5; ++i) - effects[DrainAttribute+i] = ResistMagicka; - for (int i=0; i<5; ++i) - effects[DamageAttribute+i] = ResistMagicka; - for (int i=0; i<5; ++i) - effects[AbsorbAttribute+i] = ResistMagicka; - for (int i=0; i<10; ++i) - effects[WeaknessToFire+i] = ResistMagicka; - - effects[Burden] = ResistMagicka; - effects[Charm] = ResistMagicka; - effects[Silence] = ResistMagicka; - effects[Blind] = ResistMagicka; - effects[Sound] = ResistMagicka; - - for (int i=0; i<2; ++i) + static std::map effects; + if (effects.empty()) { - effects[CalmHumanoid+i] = ResistMagicka; - effects[FrenzyHumanoid+i] = ResistMagicka; - effects[DemoralizeHumanoid+i] = ResistMagicka; - effects[RallyHumanoid+i] = ResistMagicka; + effects[DisintegrateArmor] = Sanctuary; + effects[DisintegrateWeapon] = Sanctuary; + + for (int i=0; i<5; ++i) + effects[DrainAttribute+i] = ResistMagicka; + for (int i=0; i<5; ++i) + effects[DamageAttribute+i] = ResistMagicka; + for (int i=0; i<5; ++i) + effects[AbsorbAttribute+i] = ResistMagicka; + for (int i=0; i<10; ++i) + effects[WeaknessToFire+i] = ResistMagicka; + + effects[Burden] = ResistMagicka; + effects[Charm] = ResistMagicka; + effects[Silence] = ResistMagicka; + effects[Blind] = ResistMagicka; + effects[Sound] = ResistMagicka; + + for (int i=0; i<2; ++i) + { + effects[CalmHumanoid+i] = ResistMagicka; + effects[FrenzyHumanoid+i] = ResistMagicka; + effects[DemoralizeHumanoid+i] = ResistMagicka; + effects[RallyHumanoid+i] = ResistMagicka; + } + + effects[TurnUndead] = ResistMagicka; + + effects[FireDamage] = ResistFire; + effects[FrostDamage] = ResistFrost; + effects[ShockDamage] = ResistShock; + effects[Vampirism] = ResistCommonDisease; + effects[Corprus] = ResistCorprusDisease; + effects[Poison] = ResistPoison; + effects[Paralyze] = ResistParalysis; } - effects[TurnUndead] = ResistMagicka; - - effects[FireDamage] = ResistFire; - effects[FrostDamage] = ResistFrost; - effects[ShockDamage] = ResistShock; - effects[Vampirism] = ResistCommonDisease; - effects[Corprus] = ResistCorprusDisease; - effects[Poison] = ResistPoison; - effects[Paralyze] = ResistParalysis; - if (effects.find(effect) != effects.end()) return effects[effect]; else @@ -319,42 +322,44 @@ short MagicEffect::getResistanceEffect(short effect) short MagicEffect::getWeaknessEffect(short effect) { - std::map effects; - - for (int i=0; i<5; ++i) - effects[DrainAttribute+i] = WeaknessToMagicka; - for (int i=0; i<5; ++i) - effects[DamageAttribute+i] = WeaknessToMagicka; - for (int i=0; i<5; ++i) - effects[AbsorbAttribute+i] = WeaknessToMagicka; - for (int i=0; i<10; ++i) - effects[WeaknessToFire+i] = WeaknessToMagicka; - - effects[Burden] = WeaknessToMagicka; - effects[Charm] = WeaknessToMagicka; - effects[Silence] = WeaknessToMagicka; - effects[Blind] = WeaknessToMagicka; - effects[Sound] = WeaknessToMagicka; - - for (int i=0; i<2; ++i) + static std::map effects; + if (effects.empty()) { - effects[CalmHumanoid+i] = WeaknessToMagicka; - effects[FrenzyHumanoid+i] = WeaknessToMagicka; - effects[DemoralizeHumanoid+i] = WeaknessToMagicka; - effects[RallyHumanoid+i] = WeaknessToMagicka; + for (int i=0; i<5; ++i) + effects[DrainAttribute+i] = WeaknessToMagicka; + for (int i=0; i<5; ++i) + effects[DamageAttribute+i] = WeaknessToMagicka; + for (int i=0; i<5; ++i) + effects[AbsorbAttribute+i] = WeaknessToMagicka; + for (int i=0; i<10; ++i) + effects[WeaknessToFire+i] = WeaknessToMagicka; + + effects[Burden] = WeaknessToMagicka; + effects[Charm] = WeaknessToMagicka; + effects[Silence] = WeaknessToMagicka; + effects[Blind] = WeaknessToMagicka; + effects[Sound] = WeaknessToMagicka; + + for (int i=0; i<2; ++i) + { + effects[CalmHumanoid+i] = WeaknessToMagicka; + effects[FrenzyHumanoid+i] = WeaknessToMagicka; + effects[DemoralizeHumanoid+i] = WeaknessToMagicka; + effects[RallyHumanoid+i] = WeaknessToMagicka; + } + + effects[TurnUndead] = WeaknessToMagicka; + + effects[FireDamage] = WeaknessToFire; + effects[FrostDamage] = WeaknessToFrost; + effects[ShockDamage] = WeaknessToShock; + effects[Vampirism] = WeaknessToCommonDisease; + effects[Corprus] = WeaknessToCorprusDisease; + effects[Poison] = WeaknessToPoison; + + effects[Paralyze] = -1; } - effects[TurnUndead] = WeaknessToMagicka; - - effects[FireDamage] = WeaknessToFire; - effects[FrostDamage] = WeaknessToFrost; - effects[ShockDamage] = WeaknessToShock; - effects[Vampirism] = WeaknessToCommonDisease; - effects[Corprus] = WeaknessToCorprusDisease; - effects[Poison] = WeaknessToPoison; - - effects[Paralyze] = -1; - if (effects.find(effect) != effects.end()) return effects[effect]; else diff --git a/components/esm/loadnpc.hpp b/components/esm/loadnpc.hpp index 5f567d9990..fbe1dca1f1 100644 --- a/components/esm/loadnpc.hpp +++ b/components/esm/loadnpc.hpp @@ -90,7 +90,7 @@ struct NPC char mFactionID; unsigned short mHealth, mMana, mFatigue; - signed char mDisposition, mReputation, mRank; + unsigned char mDisposition, mReputation, mRank; char mUnknown; int mGold; }; // 52 bytes @@ -101,7 +101,7 @@ struct NPC { short mLevel; // see above - signed char mDisposition, mReputation, mRank; + unsigned char mDisposition, mReputation, mRank; char mUnknown1, mUnknown2, mUnknown3; int mGold; }; // 12 bytes diff --git a/components/esm/loadscpt.cpp b/components/esm/loadscpt.cpp index 19246c4c45..474c2b4234 100644 --- a/components/esm/loadscpt.cpp +++ b/components/esm/loadscpt.cpp @@ -1,11 +1,11 @@ #include "loadscpt.hpp" +#include + #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" -#include - namespace ESM { unsigned int Script::sRecordId = REC_SCPT; @@ -45,12 +45,12 @@ namespace ESM // an exeption, just log an error and continue. std::stringstream ss; - ss << "ESM Error: " << "String table overflow"; + ss << "String table overflow"; ss << "\n File: " << esm.getName(); ss << "\n Record: " << esm.getContext().recName.toString(); ss << "\n Subrecord: " << "SCVR"; ss << "\n Offset: 0x" << std::hex << esm.getFileOffset(); - std::cerr << ss.str() << std::endl; + Log(Debug::Verbose) << ss.str(); break; } @@ -91,10 +91,10 @@ namespace ESM if (subSize != static_cast(mData.mScriptDataSize)) { std::stringstream ss; - ss << "ESM Warning: Script data size defined in SCHD subrecord does not match size of SCDT subrecord"; + ss << "Script data size defined in SCHD subrecord does not match size of SCDT subrecord"; ss << "\n File: " << esm.getName(); ss << "\n Offset: 0x" << std::hex << esm.getFileOffset(); - std::cerr << ss.str() << std::endl; + Log(Debug::Verbose) << ss.str(); } mScriptData.resize(subSize); diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index ff3123c5b8..f77e662765 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -10,6 +10,7 @@ #include +#include #include #include @@ -37,6 +38,8 @@ namespace ESMTerrain } LandObject::LandObject(const LandObject ©, const osg::CopyOp ©op) + : mLand(nullptr) + , mLoadFlags(0) { } @@ -376,7 +379,7 @@ namespace ESMTerrain const ESM::LandTexture* ltex = getLandTexture(id.first-1, id.second); if (!ltex) { - std::cerr << "Warning: Unable to find land texture index " << id.first-1 << " in plugin " << id.second << ", using default texture instead" << std::endl; + Log(Debug::Warning) << "Warning: Unable to find land texture index " << id.first-1 << " in plugin " << id.second << ", using default texture instead"; return defaultTexture; } diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index 7c3956a293..c58130f963 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -1,7 +1,6 @@ #include "configurationmanager.hpp" -#include - +#include #include #include @@ -134,7 +133,7 @@ bool ConfigurationManager::loadConfig(const boost::filesystem::path& path, if (boost::filesystem::is_regular_file(cfgFile)) { if (!mSilent) - std::cout << "Loading config file: " << cfgFile.string() << "... "; + Log(Debug::Info) << "Loading config file: " << cfgFile.string(); boost::filesystem::ifstream configFileStreamUnfiltered(cfgFile); boost::iostreams::filtering_istream configFileStream; @@ -145,14 +144,13 @@ bool ConfigurationManager::loadConfig(const boost::filesystem::path& path, boost::program_options::store(boost::program_options::parse_config_file( configFileStream, description, true), variables); - if (!mSilent) - std::cout << "done." << std::endl; return true; } else { if (!mSilent) - std::cout << "failed." << std::endl; + Log(Debug::Error) << "Loading failed."; + return false; } } diff --git a/components/files/multidircollection.cpp b/components/files/multidircollection.cpp index 93db6834d0..98e25fcc86 100644 --- a/components/files/multidircollection.cpp +++ b/components/files/multidircollection.cpp @@ -1,9 +1,9 @@ #include "multidircollection.hpp" -#include - #include +#include + namespace Files { struct NameEqual @@ -46,7 +46,7 @@ namespace Files { if (!boost::filesystem::is_directory(*iter)) { - std::cout << "Skipping invalid directory: " << (*iter).string() << std::endl; + Log(Debug::Info) << "Skipping invalid directory: " << (*iter).string(); continue; } diff --git a/components/fontloader/fontloader.cpp b/components/fontloader/fontloader.cpp index 080739bc1d..790df7fa84 100644 --- a/components/fontloader/fontloader.cpp +++ b/components/fontloader/fontloader.cpp @@ -14,6 +14,8 @@ #include #include +#include + #include #include @@ -281,7 +283,7 @@ namespace Gui assert (image->isDataContiguous()); memcpy(image->data(), &textureData[0], textureData.size()); - std::cout << "Writing " << resourceName + ".png" << std::endl; + Log(Debug::Info) << "Writing " << resourceName + ".png"; osgDB::writeImageFile(*image, resourceName + ".png"); } @@ -461,7 +463,7 @@ namespace Gui if (exportToFile) { - std::cout << "Writing " << resourceName + ".xml" << std::endl; + Log(Debug::Info) << "Writing " << resourceName + ".xml"; xmlDocument.createDeclaration(); xmlDocument.save(resourceName + ".xml"); } diff --git a/components/interpreter/defines.cpp b/components/interpreter/defines.cpp index 515c6c7d58..3c6226d9ce 100644 --- a/components/interpreter/defines.cpp +++ b/components/interpreter/defines.cpp @@ -3,8 +3,8 @@ #include #include #include -#include +#include #include namespace Interpreter{ @@ -191,8 +191,8 @@ namespace Interpreter{ } catch (std::exception& e) { - std::cerr << "Error: Failed to replace escape character, with the following error: " << e.what() << std::endl; - std::cerr << "Full text below: " << std::endl << text << std::endl; + Log(Debug::Error) << "Error: Failed to replace escape character, with the following error: " << e.what(); + Log(Debug::Error) << "Full text below:\n" << text; } // Not found, or error diff --git a/components/interpreter/miscopcodes.hpp b/components/interpreter/miscopcodes.hpp index 20a1b52bec..03b7e186f5 100644 --- a/components/interpreter/miscopcodes.hpp +++ b/components/interpreter/miscopcodes.hpp @@ -24,7 +24,7 @@ namespace Interpreter Runtime& mRuntime; protected: - virtual void visitedPlaceholder(Placeholder placeholder, char padding, int width, int precision) + virtual void visitedPlaceholder(Placeholder placeholder, char padding, int width, int precision, Notation notation) { std::ostringstream out; out.fill(padding); @@ -58,8 +58,34 @@ namespace Interpreter float value = mRuntime[0].mFloat; mRuntime.pop(); - out << std::fixed << value; - mFormattedMessage += out.str(); + if (notation == FixedNotation) + { + out << std::fixed << value; + mFormattedMessage += out.str(); + } + else if (notation == ShortestNotation) + { + std::string scientific; + std::string fixed; + + out << std::scientific << value; + + scientific = out.str(); + + out.str(std::string()); + out.clear(); + + out << std::fixed << value; + + fixed = out.str(); + + mFormattedMessage += fixed.length() < scientific.length() ? fixed : scientific; + } + else + { + out << std::scientific << value; + mFormattedMessage += out.str(); + } } break; default: diff --git a/components/misc/messageformatparser.cpp b/components/misc/messageformatparser.cpp index 3a35c83eaf..6f0e471325 100644 --- a/components/misc/messageformatparser.cpp +++ b/components/misc/messageformatparser.cpp @@ -49,11 +49,15 @@ namespace Misc width = (widthSet) ? width : -1; if (m[i] == 'S' || m[i] == 's') - visitedPlaceholder(StringPlaceholder, pad, width, precision); - else if (m[i] == 'g' || m[i] == 'G') - visitedPlaceholder(IntegerPlaceholder, pad, width, precision); + visitedPlaceholder(StringPlaceholder, pad, width, precision, FixedNotation); + else if (m[i] == 'd' || m[i] == 'i') + visitedPlaceholder(IntegerPlaceholder, pad, width, precision, FixedNotation); else if (m[i] == 'f' || m[i] == 'F') - visitedPlaceholder(FloatPlaceholder, pad, width, precision); + visitedPlaceholder(FloatPlaceholder, pad, width, precision, FixedNotation); + else if (m[i] == 'e' || m[i] == 'E') + visitedPlaceholder(FloatPlaceholder, pad, width, precision, ScientificNotation); + else if (m[i] == 'g' || m[i] == 'G') + visitedPlaceholder(FloatPlaceholder, pad, width, precision, ShortestNotation); } } } diff --git a/components/misc/messageformatparser.hpp b/components/misc/messageformatparser.hpp index c12b9352ae..db2a8b0af4 100644 --- a/components/misc/messageformatparser.hpp +++ b/components/misc/messageformatparser.hpp @@ -15,7 +15,14 @@ namespace Misc FloatPlaceholder }; - virtual void visitedPlaceholder(Placeholder placeholder, char padding, int width, int precision) = 0; + enum Notation + { + FixedNotation, + ScientificNotation, + ShortestNotation + }; + + virtual void visitedPlaceholder(Placeholder placeholder, char padding, int width, int precision, Notation notation) = 0; virtual void visitedCharacter(char c) = 0; public: diff --git a/components/misc/stringops.hpp b/components/misc/stringops.hpp index 0fde1c96c6..79fa36d1ee 100644 --- a/components/misc/stringops.hpp +++ b/components/misc/stringops.hpp @@ -146,8 +146,15 @@ public: std::string::const_iterator yit = y.begin(); for(;xit != x.end() && yit != y.end() && len > 0;++xit,++yit,--len) { - int res = *xit - *yit; - if(res != 0 && toLower(*xit) != toLower(*yit)) + char left = *xit; + char right = *yit; + if (left == right) + continue; + + left = toLower(left); + right = toLower(right); + int res = left - right; + if(res != 0) return (res > 0) ? 1 : -1; } if(len > 0) diff --git a/components/myguiplatform/myguidatamanager.cpp b/components/myguiplatform/myguidatamanager.cpp index 485a87ba7f..ddd7ca342d 100644 --- a/components/myguiplatform/myguidatamanager.cpp +++ b/components/myguiplatform/myguidatamanager.cpp @@ -5,7 +5,7 @@ #include #include -#include +#include namespace osgMyGUI { @@ -23,7 +23,7 @@ MyGUI::IDataStream *DataManager::getData(const std::string &name) stream->open(fullpath, std::ios::binary); if (stream->fail()) { - std::cerr << "DataManager::getData: Failed to open '" << name << "'" << std::endl; + Log(Debug::Error) << "DataManager::getData: Failed to open '" << name << "'"; return NULL; } return new MyGUI::DataFileStream(stream.release()); diff --git a/components/myguiplatform/myguitexture.cpp b/components/myguiplatform/myguitexture.cpp index a3b70eac97..6c53a96998 100644 --- a/components/myguiplatform/myguitexture.cpp +++ b/components/myguiplatform/myguitexture.cpp @@ -5,6 +5,7 @@ #include +#include #include namespace osgMyGUI @@ -112,7 +113,7 @@ namespace osgMyGUI void OSGTexture::saveToFile(const std::string &fname) { - std::cerr << "Would save image to file " << fname << std::endl; + Log(Debug::Warning) << "Would save image to file " << fname; } int OSGTexture::getWidth() diff --git a/components/nif/niffile.hpp b/components/nif/niffile.hpp index 9e39a37abf..0893db72ff 100644 --- a/components/nif/niffile.hpp +++ b/components/nif/niffile.hpp @@ -5,8 +5,8 @@ #include #include -#include +#include #include #include "record.hpp" @@ -14,7 +14,30 @@ namespace Nif { -class NIFFile +struct File +{ + virtual ~File() = default; + + virtual void fail(const std::string &msg) const = 0; + + virtual void warn(const std::string &msg) const = 0; + + virtual Record *getRecord(size_t index) const = 0; + + virtual size_t numRecords() const = 0; + + virtual Record *getRoot(size_t index = 0) const = 0; + + virtual size_t numRoots() const = 0; + + virtual void setUseSkinning(bool skinning) = 0; + + virtual bool getUseSkinning() const = 0; + + virtual std::string getFilename() const = 0; +}; + +class NIFFile final : public File { enum NIFVersion { VER_MW = 0x04000002 // Morrowind NIFs @@ -48,17 +71,16 @@ class NIFFile public: /// Used if file parsing fails - void fail(const std::string &msg) const + void fail(const std::string &msg) const override { std::string err = " NIFFile Error: " + msg; err += "\nFile: " + filename; throw std::runtime_error(err); } /// Used when something goes wrong, but not catastrophically so - void warn(const std::string &msg) const + void warn(const std::string &msg) const override { - std::cerr << " NIFFile Warning: " << msg < NIFFilePtr; diff --git a/components/nif/recordptr.hpp b/components/nif/recordptr.hpp index 25beaf098b..09c3809872 100644 --- a/components/nif/recordptr.hpp +++ b/components/nif/recordptr.hpp @@ -23,6 +23,8 @@ class RecordPtrT public: RecordPtrT() : index(-2) {} + RecordPtrT(X* ptr) : ptr(ptr) {} + /// Read the index from the nif void read(NIFStream *nif) { @@ -87,6 +89,12 @@ class RecordListT std::vector list; public: + RecordListT() = default; + + RecordListT(std::vector list) + : list(std::move(list)) + {} + void read(NIFStream *nif) { int len = nif->getInt(); diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index be5a7d9d61..5239e93fcb 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -10,6 +10,8 @@ #include #include +#include + #include #include @@ -18,7 +20,6 @@ #include #include - namespace { @@ -47,8 +48,8 @@ namespace NifBullet { BulletNifLoader::BulletNifLoader() - : mCompoundShape(NULL) - , mStaticMesh(NULL) + : mCompoundShape() + , mStaticMesh() { } @@ -56,20 +57,20 @@ BulletNifLoader::~BulletNifLoader() { } -osg::ref_ptr BulletNifLoader::load(const Nif::NIFFilePtr& nif) +osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) { mShape = new Resource::BulletShape; mCompoundShape = NULL; mStaticMesh = NULL; - if (nif->numRoots() < 1) + if (nif.numRoots() < 1) { warn("Found no root nodes in NIF."); return mShape; } - Nif::Record *r = nif->getRoot(0); + Nif::Record *r = nif.getRoot(0); assert(r != NULL); Nif::Node *node = dynamic_cast(r); @@ -84,36 +85,45 @@ osg::ref_ptr BulletNifLoader::load(const Nif::NIFFilePtr& { std::unique_ptr compound (new btCompoundShape); - btBoxShape* boxShape = new btBoxShape(getbtVector(mShape->mCollisionBoxHalfExtents)); + std::unique_ptr boxShape(new btBoxShape(getbtVector(mShape->mCollisionBoxHalfExtents))); btTransform transform = btTransform::getIdentity(); transform.setOrigin(getbtVector(mShape->mCollisionBoxTranslate)); - compound->addChildShape(transform, boxShape); + compound->addChildShape(transform, boxShape.get()); + boxShape.release(); mShape->mCollisionShape = compound.release(); return mShape; } else { - bool autogenerated = hasAutoGeneratedCollision(node); - // files with the name convention xmodel.nif usually have keyframes stored in a separate file xmodel.kf (see Animation::addAnimSource). // assume all nodes in the file will be animated - const bool isAnimated = pathFileNameStartsWithX(nif->getFilename()); + const auto filename = nif.getFilename(); + const bool isAnimated = pathFileNameStartsWithX(filename); - handleNode(node, 0, autogenerated, isAnimated, autogenerated); + // If the mesh has RootCollisionNode, attached to actual root node, use it as collision mesh + const Nif::Node* rootCollisionNode = getCollisionNode(node); + if (rootCollisionNode) + handleNode(filename, rootCollisionNode, 0, false, isAnimated, false); + else + handleNode(filename, node, 0, true, isAnimated, true); if (mCompoundShape) { - mShape->mCollisionShape = mCompoundShape; if (mStaticMesh) { btTransform trans; trans.setIdentity(); - mCompoundShape->addChildShape(trans, new Resource::TriangleMeshShape(mStaticMesh,true)); + mCompoundShape->addChildShape(trans, new Resource::TriangleMeshShape(mStaticMesh.get(), true)); + mStaticMesh.release(); } + mShape->mCollisionShape = mCompoundShape.release(); } else if (mStaticMesh) - mShape->mCollisionShape = new Resource::TriangleMeshShape(mStaticMesh,true); + { + mShape->mCollisionShape = new Resource::TriangleMeshShape(mStaticMesh.get(), true); + mStaticMesh.release(); + } return mShape; } @@ -153,25 +163,34 @@ bool BulletNifLoader::findBoundingBox(const Nif::Node* node, int flags) return false; } -bool BulletNifLoader::hasAutoGeneratedCollision(const Nif::Node* rootNode) +const Nif::Node* BulletNifLoader::getCollisionNode(const Nif::Node* rootNode) { const Nif::NiNode *ninode = dynamic_cast(rootNode); if(ninode) { + // If root NiNode has only other NiNode as child, consider it as a wrapper, not as actual root node const Nif::NodeList &list = ninode->children; - for(size_t i = 0;i < list.length();i++) + if (list.length() == 1 && + rootNode->recType == Nif::RC_NiNode && + list[0].getPtr()->recType == Nif::RC_NiNode) { - if(!list[i].empty()) - { - if(list[i].getPtr()->recType == Nif::RC_RootCollisionNode) - return false; - } + return getCollisionNode(list[0].getPtr()); + } + + for(size_t i = 0; i < list.length(); i++) + { + if(list[i].empty()) + continue; + + const Nif::Node* childNode = list[i].getPtr(); + if(childNode->recType == Nif::RC_RootCollisionNode) + return childNode; } } - return true; + return nullptr; } -void BulletNifLoader::handleNode(const Nif::Node *node, int flags, +void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node *node, int flags, bool isCollisionNode, bool isAnimated, bool autogenerated) { // Accumulate the flags from all the child nodes. This works for all @@ -184,6 +203,9 @@ void BulletNifLoader::handleNode(const Nif::Node *node, int flags, isCollisionNode = isCollisionNode || (node->recType == Nif::RC_RootCollisionNode); + if (node->recType == Nif::RC_RootCollisionNode && autogenerated) + Log(Debug::Info) << "Found RootCollisionNode attached to non-root node in " << fileName << ". Treat it as a common NiTriShape."; + // Don't collide with AvoidNode shapes if(node->recType == Nif::RC_AvoidNode) flags |= 0x800; @@ -212,7 +234,6 @@ void BulletNifLoader::handleNode(const Nif::Node *node, int flags, // Marker can still have collision if the model explicitely specifies it via a RootCollisionNode. return; } - } } @@ -235,7 +256,7 @@ void BulletNifLoader::handleNode(const Nif::Node *node, int flags, for(size_t i = 0;i < list.length();i++) { if(!list[i].empty()) - handleNode(list[i].getPtr(), flags, isCollisionNode, isAnimated, autogenerated); + handleNode(fileName, list[i].getPtr(), flags, isCollisionNode, isAnimated, autogenerated); } } } @@ -262,9 +283,9 @@ void BulletNifLoader::handleNiTriShape(const Nif::NiTriShape *shape, int flags, if (isAnimated) { if (!mCompoundShape) - mCompoundShape = new btCompoundShape(); + mCompoundShape.reset(new btCompoundShape); - btTriangleMesh* childMesh = new btTriangleMesh(); + std::unique_ptr childMesh(new btTriangleMesh); const Nif::NiTriShapeData *data = shape->data.getPtr(); @@ -282,7 +303,8 @@ void BulletNifLoader::handleNiTriShape(const Nif::NiTriShape *shape, int flags, childMesh->addTriangle(getbtVector(b1), getbtVector(b2), getbtVector(b3)); } - Resource::TriangleMeshShape* childShape = new Resource::TriangleMeshShape(childMesh,true); + std::unique_ptr childShape(new Resource::TriangleMeshShape(childMesh.get(), true)); + childMesh.release(); float scale = shape->trafo.scale; const Nif::Node* parent = shape; @@ -299,12 +321,13 @@ void BulletNifLoader::handleNiTriShape(const Nif::NiTriShape *shape, int flags, mShape->mAnimatedShapes.insert(std::make_pair(shape->recIndex, mCompoundShape->getNumChildShapes())); - mCompoundShape->addChildShape(trans, childShape); + mCompoundShape->addChildShape(trans, childShape.get()); + childShape.release(); } else { if (!mStaticMesh) - mStaticMesh = new btTriangleMesh(false); + mStaticMesh.reset(new btTriangleMesh(false)); // Static shape, just transform all vertices into position const Nif::NiTriShapeData *data = shape->data.getPtr(); diff --git a/components/nifbullet/bulletnifloader.hpp b/components/nifbullet/bulletnifloader.hpp index fff51933fe..f970b7f3e5 100644 --- a/components/nifbullet/bulletnifloader.hpp +++ b/components/nifbullet/bulletnifloader.hpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include @@ -12,6 +11,7 @@ #include #include +#include #include #include @@ -41,29 +41,29 @@ public: void warn(const std::string &msg) { - std::cerr << "NIFLoader: Warn:" << msg << "\n"; + Log(Debug::Warning) << "NIFLoader: Warn:" << msg; } void fail(const std::string &msg) { - std::cerr << "NIFLoader: Fail: "<< msg << std::endl; + Log(Debug::Error) << "NIFLoader: Fail: "<< msg; abort(); } - osg::ref_ptr load(const Nif::NIFFilePtr& file); + osg::ref_ptr load(const Nif::File& file); private: bool findBoundingBox(const Nif::Node* node, int flags = 0); - void handleNode(Nif::Node const *node, int flags, bool isCollisionNode, bool isAnimated=false, bool autogenerated=false); + void handleNode(const std::string& fileName, Nif::Node const *node, int flags, bool isCollisionNode, bool isAnimated=false, bool autogenerated=false); - bool hasAutoGeneratedCollision(const Nif::Node *rootNode); + const Nif::Node* getCollisionNode(const Nif::Node* rootNode); void handleNiTriShape(const Nif::NiTriShape *shape, int flags, const osg::Matrixf& transform, bool isAnimated); - btCompoundShape* mCompoundShape; + std::unique_ptr mCompoundShape; - btTriangleMesh* mStaticMesh; + std::unique_ptr mStaticMesh; osg::ref_ptr mShape; }; diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index 262966e957..83841e0e52 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -409,7 +409,7 @@ FlipController::FlipController(const FlipController ©, const osg::CopyOp &co void FlipController::apply(osg::StateSet* stateset, osg::NodeVisitor* nv) { - if (hasInput() && mDelta != 0) + if (hasInput() && mDelta != 0 && !mTextures.empty()) { int curTexture = int(getInputValue(nv) / mDelta) % mTextures.size(); stateset->setTextureAttribute(mTexSlot, mTextures[curTexture]); diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 8353779a81..3198e995c5 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -9,6 +9,7 @@ #include // resource +#include #include #include #include @@ -242,7 +243,7 @@ namespace NifOsg callback->setFunction(std::shared_ptr(new NifOsg::ControllerFunction(key))); if (target.mKeyframeControllers.find(strdata->string) != target.mKeyframeControllers.end()) - std::cerr << "Warning: controller " << strdata->string << " present more than once in " << nif->getFilename() << ", ignoring later version" << std::endl; + Log(Debug::Verbose) << "Controller " << strdata->string << " present more than once in " << nif->getFilename() << ", ignoring later version"; else target.mKeyframeControllers[strdata->string] = callback; } @@ -356,20 +357,20 @@ namespace NifOsg { if (nifNode->recType != Nif::RC_NiTextureEffect) { - std::cerr << "Unhandled effect " << nifNode->recName << " in " << mFilename << std::endl; + Log(Debug::Info) << "Unhandled effect " << nifNode->recName << " in " << mFilename; return; } const Nif::NiTextureEffect* textureEffect = static_cast(nifNode); if (textureEffect->textureType != Nif::NiTextureEffect::Environment_Map) { - std::cerr << "Unhandled NiTextureEffect type " << textureEffect->textureType << " in " << mFilename << std::endl; + Log(Debug::Info) << "Unhandled NiTextureEffect type " << textureEffect->textureType << " in " << mFilename; return; } if (textureEffect->texture.empty()) { - std::cerr << "NiTextureEffect missing source texture in " << mFilename << std::endl; + Log(Debug::Info) << "NiTextureEffect missing source texture in " << mFilename; return; } @@ -386,7 +387,7 @@ namespace NifOsg texGen->setMode(osg::TexGen::SPHERE_MAP); break; default: - std::cerr << "Unhandled NiTextureEffect coordGenType " << textureEffect->coordGenType << " in " << mFilename << std::endl; + Log(Debug::Info) << "Unhandled NiTextureEffect coordGenType " << textureEffect->coordGenType << " in " << mFilename; return; } @@ -579,13 +580,11 @@ namespace NifOsg if (composite->getNumControllers() > 0) node->addUpdateCallback(composite); - // Note: NiTriShapes are not allowed to have KeyframeControllers (the vanilla engine just crashes when there is one). // We can take advantage of this constraint for optimizations later. - if (!nifNode->controller.empty() && node->getDataVariance() == osg::Object::DYNAMIC) + if (nifNode->recType != Nif::RC_NiTriShape && !nifNode->controller.empty() && node->getDataVariance() == osg::Object::DYNAMIC) handleNodeControllers(nifNode, static_cast(node.get()), animflags); - if (nifNode->recType == Nif::RC_NiLODNode) { const Nif::NiLODNode* niLodNode = static_cast(nifNode); @@ -639,7 +638,7 @@ namespace NifOsg else if(ctrl->recType == Nif::RC_NiGeomMorpherController) {} // handled in handleTriShape else - std::cerr << "Unhandled controller " << ctrl->recName << " on node " << nifNode->recIndex << " in " << mFilename << std::endl; + Log(Debug::Info) << "Unhandled controller " << ctrl->recName << " on node " << nifNode->recIndex << " in " << mFilename; } } @@ -665,7 +664,7 @@ namespace NifOsg handleVisController(static_cast(ctrl.getPtr()), transformNode, animflags); } else - std::cerr << "Unhandled controller " << ctrl->recName << " on node " << nifNode->recIndex << " in " << mFilename << std::endl; + Log(Debug::Info) << "Unhandled controller " << ctrl->recName << " on node " << nifNode->recIndex << " in " << mFilename; } } @@ -697,7 +696,7 @@ namespace NifOsg composite->addController(osgctrl); } else - std::cerr << "Unexpected material controller " << ctrl->recType << " in " << mFilename << std::endl; + Log(Debug::Info) << "Unexpected material controller " << ctrl->recType << " in " << mFilename; } } @@ -737,7 +736,7 @@ namespace NifOsg composite->addController(callback); } else - std::cerr << "Unexpected texture controller " << ctrl->recName << " in " << mFilename << std::endl; + Log(Debug::Info) << "Unexpected texture controller " << ctrl->recName << " in " << mFilename; } } @@ -770,7 +769,7 @@ namespace NifOsg // unused } else - std::cerr << "Unhandled particle modifier " << affectors->recName << " in " << mFilename << std::endl; + Log(Debug::Info) << "Unhandled particle modifier " << affectors->recName << " in " << mFilename; } for (; !colliders.empty(); colliders = colliders->extra) { @@ -785,7 +784,7 @@ namespace NifOsg program->addOperator(new SphericalCollider(sphericalcollider)); } else - std::cerr << "Unhandled particle collider " << colliders->recName << " in " << mFilename << std::endl; + Log(Debug::Info) << "Unhandled particle collider " << colliders->recName << " in " << mFilename; } } @@ -882,11 +881,11 @@ namespace NifOsg if(ctrl->recType == Nif::RC_NiParticleSystemController || ctrl->recType == Nif::RC_NiBSPArrayController) partctrl = static_cast(ctrl.getPtr()); else - std::cerr << "Unhandled controller " << ctrl->recName << " on node " << nifNode->recIndex << " in " << mFilename << std::endl; + Log(Debug::Info) << "Unhandled controller " << ctrl->recName << " on node " << nifNode->recIndex << " in " << mFilename; } if (!partctrl) { - std::cerr << "No particle controller found in " << mFilename << std::endl; + Log(Debug::Info) << "No particle controller found in " << mFilename; return; } @@ -926,7 +925,7 @@ namespace NifOsg rootNode->accept(find); if (!find.mFound) { - std::cerr << "can't find emitter node, wrong node order? in " << mFilename << std::endl; + Log(Debug::Info) << "can't find emitter node, wrong node order? in " << mFilename; return; } osg::Group* emitterNode = find.mFound; @@ -994,7 +993,7 @@ namespace NifOsg int uvSet = *it; if (uvSet >= (int)data->uvlist.size()) { - std::cerr << "Warning: out of bounds UV set " << uvSet << " on TriShape \"" << triShape->name << "\" in " << mFilename << std::endl; + Log(Debug::Verbose) << "Out of bounds UV set " << uvSet << " on TriShape \"" << triShape->name << "\" in " << mFilename; if (!data->uvlist.empty()) geometry->setTexCoordArray(textureStage, new osg::Vec2Array(data->uvlist[0].size(), &data->uvlist[0][0]), osg::Array::BIND_PER_VERTEX); continue; @@ -1122,7 +1121,7 @@ namespace NifOsg case 9: return osg::BlendFunc::ONE_MINUS_DST_ALPHA; case 10: return osg::BlendFunc::SRC_ALPHA_SATURATE; default: - std::cerr<< "Unexpected blend mode: "<< mode << " in " << mFilename << std::endl; + Log(Debug::Info) << "Unexpected blend mode: "<< mode << " in " << mFilename; return osg::BlendFunc::SRC_ALPHA; } } @@ -1140,7 +1139,7 @@ namespace NifOsg case 6: return osg::AlphaFunc::GEQUAL; case 7: return osg::AlphaFunc::NEVER; default: - std::cerr << "Unexpected blend mode: " << mode << " in " << mFilename << std::endl; + Log(Debug::Info) << "Unexpected blend mode: " << mode << " in " << mFilename; return osg::AlphaFunc::LEQUAL; } } @@ -1158,7 +1157,7 @@ namespace NifOsg case 6: return osg::Stencil::GEQUAL; case 7: return osg::Stencil::NEVER; // NifSkope says this is GL_ALWAYS, but in MW it's GL_NEVER default: - std::cerr << "Unexpected stencil function: " << func << " in " << mFilename << std::endl; + Log(Debug::Info) << "Unexpected stencil function: " << func << " in " << mFilename; return osg::Stencil::NEVER; } } @@ -1174,7 +1173,7 @@ namespace NifOsg case 4: return osg::Stencil::DECR; case 5: return osg::Stencil::INVERT; default: - std::cerr << "Unexpected stencil operation: " << op << " in " << mFilename << std::endl; + Log(Debug::Info) << "Unexpected stencil operation: " << op << " in " << mFilename; return osg::Stencil::KEEP; } } @@ -1193,7 +1192,7 @@ namespace NifOsg pixelformat = GL_RGBA; break; default: - std::cerr << "Unhandled internal pixel format " << pixelData->fmt << " in " << mFilename << std::endl; + Log(Debug::Info) << "Unhandled internal pixel format " << pixelData->fmt << " in " << mFilename; return NULL; } @@ -1211,7 +1210,7 @@ namespace NifOsg size_t mipSize = mip.height * mip.width * pixelData->bpp / 8; if (mipSize + mip.dataOffset > pixelData->data.size()) { - std::cerr << "Warning: Internal texture's mipmap data out of bounds, ignoring texture" << std::endl; + Log(Debug::Info) << "Internal texture's mipmap data out of bounds, ignoring texture"; return NULL; } @@ -1226,7 +1225,7 @@ namespace NifOsg if (width <= 0 || height <= 0) { - std::cerr << "Warning: Internal Texture Width and height must be non zero, ignoring texture" << std::endl; + Log(Debug::Info) << "Internal Texture Width and height must be non zero, ignoring texture"; return NULL; } @@ -1268,12 +1267,12 @@ namespace NifOsg { // Not used by the vanilla engine. MCP (Morrowind Code Patch) adds an option to use Gloss maps: // "- Gloss map fix. Morrowind removed gloss map entries from model files after loading them. This stops Morrowind from removing them." - //std::cerr << "NiTexturingProperty::GlossTexture in " << mFilename << " not currently used." << std::endl; + // Log(Debug::Info) << "NiTexturingProperty::GlossTexture in " << mFilename << " not currently used."; continue; } default: { - std::cerr << "Warning: unhandled texture stage " << i << " in " << mFilename << std::endl; + Log(Debug::Info) << "Unhandled texture stage " << i << " in " << mFilename; continue; } } @@ -1281,7 +1280,7 @@ namespace NifOsg const Nif::NiTexturingProperty::Texture& tex = texprop->textures[i]; if(tex.texture.empty() && texprop->controller.empty()) { - std::cerr << "Warning: texture layer " << i << " is in use but empty in " << mFilename << std::endl; + Log(Debug::Verbose) << "Texture layer " << i << " is in use but empty in " << mFilename; continue; } @@ -1479,7 +1478,7 @@ namespace NifOsg break; } default: - std::cerr << "Unhandled " << property->recName << " in " << mFilename << std::endl; + Log(Debug::Info) << "Unhandled " << property->recName << " in " << mFilename; break; } } diff --git a/components/nifosg/particle.cpp b/components/nifosg/particle.cpp index 62360b9d64..03e836a0fd 100644 --- a/components/nifosg/particle.cpp +++ b/components/nifosg/particle.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -284,7 +285,7 @@ void Emitter::emitParticles(double dt) if (!visitor.mFound) { - std::cerr << "Error: Can't find emitter node" << randomRecIndex << std::endl; + Log(Debug::Info) << "Can't find emitter node" << randomRecIndex; return; } diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp index a3d09311a5..622506d6b0 100644 --- a/components/resource/bulletshapemanager.cpp +++ b/components/resource/bulletshapemanager.cpp @@ -127,7 +127,7 @@ osg::ref_ptr BulletShapeManager::getShape(const std::string & if (ext == "nif") { NifBullet::BulletNifLoader loader; - shape = loader.load(mNifFileManager->get(normalized)); + shape = loader.load(*mNifFileManager->get(normalized)); } else { diff --git a/components/resource/imagemanager.cpp b/components/resource/imagemanager.cpp index 000a833cf7..c1d71ee008 100644 --- a/components/resource/imagemanager.cpp +++ b/components/resource/imagemanager.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include "objectcache.hpp" @@ -97,7 +98,7 @@ namespace Resource } catch (std::exception& e) { - std::cerr << "Failed to open image: " << e.what() << std::endl; + Log(Debug::Error) << "Failed to open image: " << e.what(); mCache->addEntryToObjectCache(normalized, mWarningImage); return mWarningImage; } @@ -109,7 +110,7 @@ namespace Resource osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension(ext); if (!reader) { - std::cerr << "Error loading " << filename << ": no readerwriter for '" << ext << "' found" << std::endl; + Log(Debug::Error) << "Error loading " << filename << ": no readerwriter for '" << ext << "' found"; mCache->addEntryToObjectCache(normalized, mWarningImage); return mWarningImage; } @@ -117,7 +118,7 @@ namespace Resource osgDB::ReaderWriter::ReadResult result = reader->readImage(*stream, mOptions); if (!result.success()) { - std::cerr << "Error loading " << filename << ": " << result.message() << " code " << result.status() << std::endl; + Log(Debug::Error) << "Error loading " << filename << ": " << result.message() << " code " << result.status(); mCache->addEntryToObjectCache(normalized, mWarningImage); return mWarningImage; } @@ -130,7 +131,7 @@ namespace Resource static bool uncompress = (getenv("OPENMW_DECOMPRESS_TEXTURES") != 0); if (!uncompress) { - std::cerr << "Error loading " << filename << ": no S3TC texture compression support installed" << std::endl; + Log(Debug::Error) << "Error loading " << filename << ": no S3TC texture compression support installed"; mCache->addEntryToObjectCache(normalized, mWarningImage); return mWarningImage; } diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 03b850fac1..8edb3d7657 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -1,6 +1,5 @@ #include "scenemanager.hpp" -#include #include #include @@ -13,6 +12,8 @@ #include #include +#include + #include #include @@ -393,9 +394,9 @@ namespace Resource static std::vector reservedNames; if (reservedNames.empty()) { - const char* reserved[] = {"Head", "Neck", "Chest", "Groin", "Right Hand", "Left Hand", "Right Wrist", "Left Wrist", "Shield Bone", "Right Forearm", "Left Forearm", "Right Upper Arm", "Left Upper Arm", "Right Foot", "Left Foot", "Right Ankle", "Left Ankle", "Right Knee", "Left Knee", "Right Upper Leg", "Left Upper Leg", "Right Clavicle", "Left Clavicle", "Weapon Bone", "Tail", - "Bip01 L Hand", "Bip01 R Hand", "Bip01 Head", "Bip01 Spine1", "Bip01 Spine2", "Bip01 L Clavicle", "Bip01 R Clavicle", "bip01", "Root Bone", "Bip01 Neck", - "BoneOffset", "AttachLight", "ArrowBone", "Camera"}; + const char* reserved[] = {"Head", "Neck", "Chest", "Groin", "Right Hand", "Left Hand", "Right Wrist", "Left Wrist", "Shield Bone", "Right Forearm", "Left Forearm", "Right Upper Arm", + "Left Upper Arm", "Right Foot", "Left Foot", "Right Ankle", "Left Ankle", "Right Knee", "Left Knee", "Right Upper Leg", "Left Upper Leg", "Right Clavicle", + "Left Clavicle", "Weapon Bone", "Tail", "Bip01", "Root Bone", "BoneOffset", "AttachLight", "ArrowBone", "Camera"}; reservedNames = std::vector(reserved, reserved + sizeof(reserved)/sizeof(reserved[0])); for (unsigned int i=0; iexists(normalized)) { - std::cerr << "Failed to load '" << name << "': " << e.what() << ", using marker_error." << sMeshTypes[i] << " instead" << std::endl; + Log(Debug::Error) << "Failed to load '" << name << "': " << e.what() << ", using marker_error." << sMeshTypes[i] << " instead"; Files::IStreamPtr file = mVFS->get(normalized); loaded = load(file, normalized, mImageManager, mNifFileManager); break; @@ -658,12 +659,12 @@ namespace Resource if(magfilter == "nearest") mag = osg::Texture::NEAREST; else if(magfilter != "linear") - std::cerr<< "Warning: Invalid texture mag filter: "< #include +#include #include #include @@ -63,7 +64,7 @@ namespace SceneUtil { osg::ref_ptr node = *it; if (node->getNumParents() > 1) - std::cerr << "Error CopyRigVisitor: node has multiple parents" << std::endl; + Log(Debug::Error) << "Error CopyRigVisitor: node has multiple parents"; while (node->getNumParents()) node->getParent(0)->removeChild(node); diff --git a/components/sceneutil/lightcontroller.cpp b/components/sceneutil/lightcontroller.cpp index e3ea93843c..199b408d74 100644 --- a/components/sceneutil/lightcontroller.cpp +++ b/components/sceneutil/lightcontroller.cpp @@ -26,7 +26,7 @@ namespace float v = 0.0f; for(int i = 0;i < 3;++i) - v += std::sin(fb*time*f[i] + o[1])*m[i]; + v += std::sin(fb*time*f[i] + o[i])*m[i]; return v * s; } @@ -73,17 +73,15 @@ namespace SceneUtil float cycle_time; float time_distortion; - const float pi = 3.14159265359; - if(mType == LT_Pulse || mType == LT_PulseSlow) { - cycle_time = 2.0f * pi; - time_distortion = mType == LT_Pulse ? 20.0f : 4.f; + cycle_time = 2.0f * osg::PI; + time_distortion = 3.0f; } else { - static const float fa = 0.785398f; - static const float phase_wavelength = 120.0f * pi / fa; + static const float fa = osg::PI / 4.0f; + static const float phase_wavelength = 120.0f * osg::PI / fa; cycle_time = 500.0f; mPhase = std::fmod(mPhase + dt, phase_wavelength); @@ -94,12 +92,14 @@ namespace SceneUtil if(mDirection > 0 && mDeltaCount > +cycle_time) { mDirection = -1.0f; - mDeltaCount = 2.0f*cycle_time - mDeltaCount; + float extra = mDeltaCount - cycle_time; + mDeltaCount -= 2*extra; } if(mDirection < 0 && mDeltaCount < -cycle_time) { mDirection = +1.0f; - mDeltaCount = -2.0f*cycle_time - mDeltaCount; + float extra = cycle_time - mDeltaCount; + mDeltaCount += 2*extra; } static const float fast = 4.0f/1.0f; diff --git a/components/sceneutil/riggeometry.cpp b/components/sceneutil/riggeometry.cpp index 7f148cf5e3..c409bcd5cc 100644 --- a/components/sceneutil/riggeometry.cpp +++ b/components/sceneutil/riggeometry.cpp @@ -1,12 +1,38 @@ #include "riggeometry.hpp" #include -#include #include +#include + #include "skeleton.hpp" #include "util.hpp" +namespace +{ + inline void accumulateMatrix(const osg::Matrixf& invBindMatrix, const osg::Matrixf& matrix, const float weight, osg::Matrixf& result) + { + osg::Matrixf m = invBindMatrix * matrix; + float* ptr = m.ptr(); + float* ptrresult = result.ptr(); + ptrresult[0] += ptr[0] * weight; + ptrresult[1] += ptr[1] * weight; + ptrresult[2] += ptr[2] * weight; + + ptrresult[4] += ptr[4] * weight; + ptrresult[5] += ptr[5] * weight; + ptrresult[6] += ptr[6] * weight; + + ptrresult[8] += ptr[8] * weight; + ptrresult[9] += ptr[9] * weight; + ptrresult[10] += ptr[10] * weight; + + ptrresult[12] += ptr[12] * weight; + ptrresult[13] += ptr[13] * weight; + ptrresult[14] += ptr[14] * weight; + } +} + namespace SceneUtil { @@ -96,13 +122,13 @@ bool RigGeometry::initFromParentSkeleton(osg::NodeVisitor* nv) if (!mSkeleton) { - std::cerr << "Error: A RigGeometry did not find its parent skeleton" << std::endl; + Log(Debug::Error) << "Error: A RigGeometry did not find its parent skeleton"; return false; } if (!mInfluenceMap) { - std::cerr << "Error: No InfluenceMap set on RigGeometry" << std::endl; + Log(Debug::Error) << "Error: No InfluenceMap set on RigGeometry"; return false; } @@ -113,7 +139,7 @@ bool RigGeometry::initFromParentSkeleton(osg::NodeVisitor* nv) Bone* bone = mSkeleton->getBone(it->first); if (!bone) { - std::cerr << "Error: RigGeometry did not find bone " << it->first << std::endl; + Log(Debug::Error) << "Error: RigGeometry did not find bone " << it->first ; continue; } @@ -140,39 +166,18 @@ bool RigGeometry::initFromParentSkeleton(osg::NodeVisitor* nv) return true; } -void accumulateMatrix(const osg::Matrixf& invBindMatrix, const osg::Matrixf& matrix, float weight, osg::Matrixf& result) -{ - osg::Matrixf m = invBindMatrix * matrix; - float* ptr = m.ptr(); - float* ptrresult = result.ptr(); - ptrresult[0] += ptr[0] * weight; - ptrresult[1] += ptr[1] * weight; - ptrresult[2] += ptr[2] * weight; - - ptrresult[4] += ptr[4] * weight; - ptrresult[5] += ptr[5] * weight; - ptrresult[6] += ptr[6] * weight; - - ptrresult[8] += ptr[8] * weight; - ptrresult[9] += ptr[9] * weight; - ptrresult[10] += ptr[10] * weight; - - ptrresult[12] += ptr[12] * weight; - ptrresult[13] += ptr[13] * weight; - ptrresult[14] += ptr[14] * weight; -} - void RigGeometry::cull(osg::NodeVisitor* nv) { if (!mSkeleton) { - std::cerr << "Error: RigGeometry rendering with no skeleton, should have been initialized by UpdateVisitor" << std::endl; + Log(Debug::Error) << "Error: RigGeometry rendering with no skeleton, should have been initialized by UpdateVisitor"; // try to recover anyway, though rendering is likely to be incorrect. if (!initFromParentSkeleton(nv)) return; } - if ((!mSkeleton->getActive() && mLastFrameNumber != 0) || mLastFrameNumber == nv->getTraversalNumber()) + unsigned int traversalNumber = nv->getTraversalNumber(); + if (mLastFrameNumber == traversalNumber || (mLastFrameNumber != 0 && !mSkeleton->getActive())) { osg::Geometry& geom = *getGeometry(mLastFrameNumber); nv->pushOntoNodePath(&geom); @@ -180,10 +185,10 @@ void RigGeometry::cull(osg::NodeVisitor* nv) nv->popFromNodePath(); return; } - mLastFrameNumber = nv->getTraversalNumber(); + mLastFrameNumber = traversalNumber; osg::Geometry& geom = *getGeometry(mLastFrameNumber); - mSkeleton->updateBoneMatrices(nv->getTraversalNumber()); + mSkeleton->updateBoneMatrices(traversalNumber); // skinning const osg::Vec3Array* positionSrc = static_cast(mSourceGeometry->getVertexArray()); @@ -194,34 +199,31 @@ void RigGeometry::cull(osg::NodeVisitor* nv) osg::Vec3Array* normalDst = static_cast(geom.getNormalArray()); osg::Vec4Array* tangentDst = static_cast(geom.getTexCoordArray(7)); - for (Bone2VertexMap::const_iterator it = mBone2VertexMap.begin(); it != mBone2VertexMap.end(); ++it) + for (auto &pair : mBone2VertexMap) { - osg::Matrixf resultMat (0, 0, 0, 0, + osg::Matrixf resultMat (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1); - for (std::vector::const_iterator weightIt = it->first.begin(); weightIt != it->first.end(); ++weightIt) + for (auto &weight : pair.first) { - Bone* bone = weightIt->first.first; - const osg::Matrix& invBindMatrix = weightIt->first.second; - float weight = weightIt->second; - const osg::Matrixf& boneMatrix = bone->mMatrixInSkeletonSpace; - accumulateMatrix(invBindMatrix, boneMatrix, weight, resultMat); + accumulateMatrix(weight.first.second, weight.first.first->mMatrixInSkeletonSpace, weight.second, resultMat); } + if (mGeomToSkelMatrix) resultMat *= (*mGeomToSkelMatrix); - for (std::vector::const_iterator vertexIt = it->second.begin(); vertexIt != it->second.end(); ++vertexIt) + for (auto &vertex : pair.second) { - unsigned short vertex = *vertexIt; (*positionDst)[vertex] = resultMat.preMult((*positionSrc)[vertex]); if (normalDst) - (*normalDst)[vertex] = osg::Matrix::transform3x3((*normalSrc)[vertex], resultMat); + (*normalDst)[vertex] = osg::Matrixf::transform3x3((*normalSrc)[vertex], resultMat); + if (tangentDst) { - osg::Vec4f srcTangent = (*tangentSrc)[vertex]; - osg::Vec3f transformedTangent = osg::Matrix::transform3x3(osg::Vec3f(srcTangent.x(), srcTangent.y(), srcTangent.z()), resultMat); + const osg::Vec4f& srcTangent = (*tangentSrc)[vertex]; + osg::Vec3f transformedTangent = osg::Matrixf::transform3x3(osg::Vec3f(srcTangent.x(), srcTangent.y(), srcTangent.z()), resultMat); (*tangentDst)[vertex] = osg::Vec4f(transformedTangent, srcTangent.w()); } } diff --git a/components/sceneutil/skeleton.cpp b/components/sceneutil/skeleton.cpp index 94ae1f234e..58ed9a27cf 100644 --- a/components/sceneutil/skeleton.cpp +++ b/components/sceneutil/skeleton.cpp @@ -3,10 +3,9 @@ #include #include +#include #include -#include - namespace SceneUtil { @@ -183,7 +182,7 @@ void Bone::update(const osg::Matrixf* parentMatrixInSkeletonSpace) { if (!mNode) { - std::cerr << "Error: Bone without node " << std::endl; + Log(Debug::Error) << "Error: Bone without node"; return; } if (parentMatrixInSkeletonSpace) diff --git a/components/sceneutil/unrefqueue.cpp b/components/sceneutil/unrefqueue.cpp index 7e5646ecb1..21ba836c0c 100644 --- a/components/sceneutil/unrefqueue.cpp +++ b/components/sceneutil/unrefqueue.cpp @@ -1,28 +1,15 @@ #include "unrefqueue.hpp" -#include - //#include -//#include -#include +//#include namespace SceneUtil { - - class UnrefWorkItem : public SceneUtil::WorkItem + void UnrefWorkItem::doWork() { - public: - std::deque > mObjects; - - virtual void doWork() - { - //osg::Timer timer; - //size_t objcount = mObjects.size(); - mObjects.clear(); - //std::cout << "cleared " << objcount << " objects in " << timer.time_m() << std::endl; - } - }; + mObjects.clear(); + } UnrefQueue::UnrefQueue() { diff --git a/components/sceneutil/unrefqueue.hpp b/components/sceneutil/unrefqueue.hpp index 4a2724927f..7155e669c6 100644 --- a/components/sceneutil/unrefqueue.hpp +++ b/components/sceneutil/unrefqueue.hpp @@ -1,13 +1,23 @@ #ifndef OPENMW_COMPONENTS_UNREFQUEUE_H #define OPENMW_COMPONENTS_UNREFQUEUE_H +#include + #include #include +#include + namespace SceneUtil { class WorkQueue; - class UnrefWorkItem; + + class UnrefWorkItem : public SceneUtil::WorkItem + { + public: + std::deque > mObjects; + virtual void doWork(); + }; /// @brief Handles unreferencing of objects through the WorkQueue. Typical use scenario /// would be the main thread pushing objects that are no longer needed, and the background thread deleting them. diff --git a/components/sceneutil/workqueue.cpp b/components/sceneutil/workqueue.cpp index 2cd1ec806d..37e8563e12 100644 --- a/components/sceneutil/workqueue.cpp +++ b/components/sceneutil/workqueue.cpp @@ -1,6 +1,6 @@ #include "workqueue.hpp" -#include +#include namespace SceneUtil { @@ -71,7 +71,7 @@ void WorkQueue::addWorkItem(osg::ref_ptr item, bool front) { if (item->isDone()) { - std::cerr << "Error: trying to add a work item that is already completed" << std::endl; + Log(Debug::Error) << "Error: trying to add a work item that is already completed"; return; } diff --git a/components/sdlutil/sdlcursormanager.cpp b/components/sdlutil/sdlcursormanager.cpp index 1747c9b941..1560b74b3e 100644 --- a/components/sdlutil/sdlcursormanager.cpp +++ b/components/sdlutil/sdlcursormanager.cpp @@ -17,6 +17,8 @@ #include #include +#include + #include "imagetosurface.hpp" #if defined(OSG_LIBRARY_STATIC) && !defined(ANDROID) @@ -63,7 +65,7 @@ namespace CursorDecompression if (!_gc) { - std::cerr << "Failed to create pbuffer, failing back to normal graphics window." << std::endl; + Log(Debug::Warning) << "Failed to create pbuffer, failing back to normal graphics window."; traits->pbuffer = false; _gc = osg::GraphicsContext::createGraphicsContext(traits.get()); @@ -283,8 +285,8 @@ namespace SDLUtil mCursorMap.insert(CursorMap::value_type(std::string(name), curs)); } catch (std::exception& e) { - std::cerr << e.what() << std::endl; - std::cerr <<"Using default cursor."< +#include #include @@ -160,9 +160,7 @@ InputWrapper::InputWrapper(SDL_Window* window, osg::ref_ptr v break; default: - std::ios::fmtflags f(std::cerr.flags()); - std::cerr << "Unhandled SDL event of type 0x" << std::hex << evt.type << std::endl; - std::cerr.flags(f); + Log(Debug::Info) << "Unhandled SDL event of type 0x" << std::hex << evt.type; break; } } diff --git a/components/sdlutil/sdlvideowrapper.cpp b/components/sdlutil/sdlvideowrapper.cpp index dd89d10724..c2963be861 100644 --- a/components/sdlutil/sdlvideowrapper.cpp +++ b/components/sdlutil/sdlvideowrapper.cpp @@ -1,6 +1,6 @@ #include "sdlvideowrapper.hpp" -#include +#include #include @@ -65,7 +65,7 @@ namespace SDLUtil red[i] = green[i] = blue[i] = static_cast(value); } if (SDL_SetWindowGammaRamp(mWindow, red, green, blue) < 0) - std::cout << "Couldn't set gamma: " << SDL_GetError() << std::endl; + Log(Debug::Warning) << "Couldn't set gamma: " << SDL_GetError(); } void VideoWrapper::setVideoMode(int width, int height, bool fullscreen, bool windowBorder) diff --git a/components/settings/settings.cpp b/components/settings/settings.cpp index 15a222d313..66e5dfc045 100644 --- a/components/settings/settings.cpp +++ b/components/settings/settings.cpp @@ -1,8 +1,8 @@ #include "settings.hpp" #include -#include +#include #include #include @@ -69,7 +69,7 @@ public: mFile = file; boost::filesystem::ifstream stream; stream.open(boost::filesystem::path(file)); - std::cout << "Loading settings file: " << file << std::endl; + Log(Debug::Info) << "Loading settings file: " << file; std::string currentCategory; mLine = 0; while (!stream.eof() && !stream.fail()) @@ -186,8 +186,8 @@ public: // Ensure that all options in the current category have been written. for (CategorySettingStatusMap::iterator mit = written.begin(); mit != written.end(); ++mit) { if (mit->second == false && mit->first.first == currentCategory) { - std::cout << "Added new setting: [" << currentCategory << "] " - << mit->first.second << " = " << settings[mit->first] << std::endl; + Log(Debug::Verbose) << "Added new setting: [" << currentCategory << "] " + << mit->first.second << " = " << settings[mit->first]; ostream << mit->first.second << " = " << settings[mit->first] << std::endl; mit->second = true; changed = true; @@ -200,7 +200,7 @@ public: // Write the (new) current category to the file. ostream << "[" << currentCategory << "]" << std::endl; - //std::cout << "Wrote category: " << currentCategory << std::endl; + // Log(Debug::Verbose) << "Wrote category: " << currentCategory; // A setting can apparently follow the category on an input line. That's rather // inconvenient, since it makes it more likely to have duplicative sections, @@ -259,8 +259,8 @@ public: finder->second = true; // Did we really change it? if (value != settings[key]) { - std::cout << "Changed setting: [" << currentCategory << "] " - << setting << " = " << settings[key] << std::endl; + Log(Debug::Verbose) << "Changed setting: [" << currentCategory << "] " + << setting << " = " << settings[key]; changed = true; } // No need to write the current line, because we just emitted a replacement. @@ -276,8 +276,8 @@ public: // the current category at the end of the file before moving on to any new categories. for (CategorySettingStatusMap::iterator mit = written.begin(); mit != written.end(); ++mit) { if (mit->second == false && mit->first.first == currentCategory) { - std::cout << "Added new setting: [" << mit->first.first << "] " - << mit->first.second << " = " << settings[mit->first] << std::endl; + Log(Debug::Verbose) << "Added new setting: [" << mit->first.first << "] " + << mit->first.second << " = " << settings[mit->first]; ostream << mit->first.second << " = " << settings[mit->first] << std::endl; mit->second = true; changed = true; @@ -305,12 +305,12 @@ public: // If the catgory has changed, write a new category header. if (mit->first.first != currentCategory) { currentCategory = mit->first.first; - std::cout << "Created new setting section: " << mit->first.first << std::endl; + Log(Debug::Verbose) << "Created new setting section: " << mit->first.first; ostream << std::endl; ostream << "[" << currentCategory << "]" << std::endl; } - std::cout << "Added new setting: [" << mit->first.first << "] " - << mit->first.second << " = " << settings[mit->first] << std::endl; + Log(Debug::Verbose) << "Added new setting: [" << mit->first.first << "] " + << mit->first.second << " = " << settings[mit->first]; // Then write the setting. No need to mark it as written because we're done. ostream << mit->first.second << " = " << settings[mit->first] << std::endl; changed = true; @@ -319,7 +319,7 @@ public: // Now install the newly written file in the requested place. if (changed) { - std::cout << "Updating settings file: " << ipath << std::endl; + Log(Debug::Info) << "Updating settings file: " << ipath; boost::filesystem::ofstream ofstream; ofstream.open(ipath); ofstream << ostream.rdbuf(); diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp index 5efd1b86ea..28f4300c29 100644 --- a/components/shader/shadermanager.cpp +++ b/components/shader/shadermanager.cpp @@ -11,6 +11,8 @@ #include #include +#include + namespace Shader { @@ -31,13 +33,13 @@ namespace Shader size_t start = source.find('"', foundPos); if (start == std::string::npos || start == source.size()-1) { - std::cerr << "Invalid #include " << std::endl; + Log(Debug::Error) << "Invalid #include"; return false; } size_t end = source.find('"', start+1); if (end == std::string::npos) { - std::cerr << "Invalid #include " << std::endl; + Log(Debug::Error) << "Invalid #include"; return false; } std::string includeFilename = source.substr(start+1, end-(start+1)); @@ -46,7 +48,7 @@ namespace Shader includeFstream.open(includePath); if (includeFstream.fail()) { - std::cerr << "Failed to open " << includePath.string() << std::endl; + Log(Debug::Error) << "Failed to open " << includePath.string(); return false; } @@ -65,7 +67,7 @@ namespace Shader if (includedFiles.insert(includePath).second == false) { - std::cerr << "Detected cyclic #includes" << std::endl; + Log(Debug::Error) << "Detected cyclic #includes"; return false; } } @@ -81,14 +83,14 @@ namespace Shader size_t endPos = source.find_first_of(" \n\r()[].;", foundPos); if (endPos == std::string::npos) { - std::cerr << "Unexpected EOF" << std::endl; + Log(Debug::Error) << "Unexpected EOF"; return false; } std::string define = source.substr(foundPos+1, endPos - (foundPos+1)); ShaderManager::DefineMap::const_iterator defineFound = defines.find(define); if (defineFound == defines.end()) { - std::cerr << "Undefined " << define << std::endl; + Log(Debug::Error) << "Undefined " << define; return false; } else @@ -112,7 +114,7 @@ namespace Shader stream.open(p); if (stream.fail()) { - std::cerr << "Failed to open " << p.string() << std::endl; + Log(Debug::Error) << "Failed to open " << p.string(); return NULL; } std::stringstream buffer; diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 9b3876d6c8..cbd950ea3b 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -10,6 +10,7 @@ #include +#include #include #include #include @@ -147,7 +148,7 @@ namespace Shader specularMap = texture; } else - std::cerr << "ShaderVisitor encountered unknown texture " << texture << std::endl; + Log(Debug::Error) << "ShaderVisitor encountered unknown texture " << texture; } } } diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index 722df95078..a0f051524b 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -12,66 +12,150 @@ #include +#include + +namespace +{ + class BlendmapTexMat + { + public: + static const osg::ref_ptr& value(const int blendmapScale) + { + static BlendmapTexMat instance; + return instance.get(blendmapScale); + } + + const osg::ref_ptr& get(const int blendmapScale) + { + const std::lock_guard lock(mMutex); + auto texMat = mTexMatMap.find(blendmapScale); + if (texMat == mTexMatMap.end()) + { + osg::Matrixf matrix; + float scale = (blendmapScale/(static_cast(blendmapScale)+1.f)); + matrix.preMultTranslate(osg::Vec3f(0.5f, 0.5f, 0.f)); + matrix.preMultScale(osg::Vec3f(scale, scale, 1.f)); + matrix.preMultTranslate(osg::Vec3f(-0.5f, -0.5f, 0.f)); + // We need to nudge the blendmap to look like vanilla. + // This causes visible seams unless the blendmap's resolution is doubled, but Vanilla also doubles the blendmap, apparently. + matrix.preMultTranslate(osg::Vec3f(1.0f/blendmapScale/4.0f, 1.0f/blendmapScale/4.0f, 0.f)); + + texMat = mTexMatMap.insert(std::make_pair(blendmapScale, new osg::TexMat(matrix))).first; + } + return texMat->second; + } + + private: + std::mutex mMutex; + std::map> mTexMatMap; + }; + + class LayerTexMat + { + public: + static const osg::ref_ptr& value(const float layerTileSize) + { + static LayerTexMat instance; + return instance.get(layerTileSize); + } + + const osg::ref_ptr& get(const float layerTileSize) + { + const std::lock_guard lock(mMutex); + auto texMat = mTexMatMap.find(layerTileSize); + if (texMat == mTexMatMap.end()) + { + texMat = mTexMatMap.insert(std::make_pair(layerTileSize, + new osg::TexMat(osg::Matrix::scale(osg::Vec3f(layerTileSize, layerTileSize, 1.f))))).first; + } + return texMat->second; + } + + private: + std::mutex mMutex; + std::map> mTexMatMap; + }; + + class EqualDepth + { + public: + static const osg::ref_ptr& value() + { + static EqualDepth instance; + return instance.mValue; + } + + private: + osg::ref_ptr mValue; + + EqualDepth() + : mValue(new osg::Depth) + { + mValue->setFunction(osg::Depth::EQUAL); + } + }; + + class LequalDepth + { + public: + static const osg::ref_ptr& value() + { + static LequalDepth instance; + return instance.mValue; + } + + private: + osg::ref_ptr mValue; + + LequalDepth() + : mValue(new osg::Depth) + { + mValue->setFunction(osg::Depth::LEQUAL); + } + }; + + class BlendFunc + { + public: + static const osg::ref_ptr& value() + { + static BlendFunc instance; + return instance.mValue; + } + + private: + osg::ref_ptr mValue; + + BlendFunc() + : mValue(new osg::BlendFunc) + { + mValue->setFunction(osg::BlendFunc::SRC_ALPHA, osg::BlendFunc::ONE); + } + }; + + class TexEnvCombine + { + public: + static const osg::ref_ptr& value() + { + static TexEnvCombine instance; + return instance.mValue; + } + + private: + osg::ref_ptr mValue; + + TexEnvCombine() + : mValue(new osg::TexEnvCombine) + { + mValue->setCombine_RGB(osg::TexEnvCombine::REPLACE); + mValue->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); + } + }; +} namespace Terrain { - - osg::ref_ptr getBlendmapTexMat(int blendmapScale) - { - static std::map > texMatMap; - osg::ref_ptr texMat = texMatMap[blendmapScale]; - if (!texMat) - { - osg::Matrixf matrix; - float scale = (blendmapScale/(static_cast(blendmapScale)+1.f)); - matrix.preMultTranslate(osg::Vec3f(0.5f, 0.5f, 0.f)); - matrix.preMultScale(osg::Vec3f(scale, scale, 1.f)); - matrix.preMultTranslate(osg::Vec3f(-0.5f, -0.5f, 0.f)); - // We need to nudge the blendmap to look like vanilla. - // This causes visible seams unless the blendmap's resolution is doubled, but Vanilla also doubles the blendmap, apparently. - matrix.preMultTranslate(osg::Vec3f(1.0f/blendmapScale/4.0f, 1.0f/blendmapScale/4.0f, 0.f)); - - texMat = new osg::TexMat(matrix); - - texMatMap[blendmapScale] = texMat; - } - return texMat; - } - - osg::ref_ptr getLayerTexMat(float layerTileSize) - { - static std::map > texMatMap; - osg::ref_ptr texMat = texMatMap[layerTileSize]; - if (!texMat) - { - texMat = new osg::TexMat(osg::Matrix::scale(osg::Vec3f(layerTileSize,layerTileSize,1.f))); - - texMatMap[layerTileSize] = texMat; - } - return texMat; - } - - osg::ref_ptr getEqualDepth() - { - static osg::ref_ptr depth; - if (!depth) - { - depth = new osg::Depth; - depth->setFunction(osg::Depth::EQUAL); - } - return depth; - } - osg::ref_ptr getLequalDepth() - { - static osg::ref_ptr depth; - if (!depth) - { - depth = new osg::Depth; - depth->setFunction(osg::Depth::LEQUAL); - } - return depth; - } - std::vector > createPasses(bool useShaders, bool forcePerPixelLighting, bool clampLighting, Shader::ShaderManager* shaderManager, const std::vector &layers, const std::vector > &blendmaps, int blendmapScale, float layerTileSize) { @@ -87,16 +171,9 @@ namespace Terrain if (!firstLayer) { - static osg::ref_ptr blendFunc; - if (!blendFunc) - { - blendFunc= new osg::BlendFunc(); - blendFunc->setFunction(osg::BlendFunc::SRC_ALPHA, osg::BlendFunc::ONE); - } stateset->setMode(GL_BLEND, osg::StateAttribute::ON); - stateset->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); - - stateset->setAttributeAndModes(getEqualDepth(), osg::StateAttribute::ON); + stateset->setAttributeAndModes(BlendFunc::value(), osg::StateAttribute::ON); + stateset->setAttributeAndModes(EqualDepth::value(), osg::StateAttribute::ON); } // disable fog if we're the first layer of several - supposed to be completely black if (firstLayer && blendmaps.size() > 0) @@ -105,7 +182,7 @@ namespace Terrain fog->setStart(10000000); fog->setEnd(10000000); stateset->setAttributeAndModes(fog, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); - stateset->setAttributeAndModes(getLequalDepth(), osg::StateAttribute::ON); + stateset->setAttributeAndModes(LequalDepth::value(), osg::StateAttribute::ON); } int texunit = 0; @@ -115,7 +192,7 @@ namespace Terrain stateset->setTextureAttributeAndModes(texunit, it->mDiffuseMap); if (layerTileSize != 1.f) - stateset->setTextureAttributeAndModes(texunit, getLayerTexMat(layerTileSize), osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(texunit, LayerTexMat::value(layerTileSize), osg::StateAttribute::ON); stateset->addUniform(new osg::Uniform("diffuseMap", texunit)); @@ -125,8 +202,7 @@ namespace Terrain osg::ref_ptr blendmap = blendmaps.at(blendmapIndex++); stateset->setTextureAttributeAndModes(texunit, blendmap.get()); - - stateset->setTextureAttributeAndModes(texunit, getBlendmapTexMat(blendmapScale)); + stateset->setTextureAttributeAndModes(texunit, BlendmapTexMat::value(blendmapScale)); stateset->addUniform(new osg::Uniform("blendMap", texunit)); } @@ -165,17 +241,8 @@ namespace Terrain stateset->setTextureAttributeAndModes(texunit, blendmap.get()); // This is to map corner vertices directly to the center of a blendmap texel. - stateset->setTextureAttributeAndModes(texunit, getBlendmapTexMat(blendmapScale)); - - static osg::ref_ptr texEnvCombine; - if (!texEnvCombine) - { - texEnvCombine = new osg::TexEnvCombine; - texEnvCombine->setCombine_RGB(osg::TexEnvCombine::REPLACE); - texEnvCombine->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); - } - - stateset->setTextureAttributeAndModes(texunit, texEnvCombine, osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(texunit, BlendmapTexMat::value(blendmapScale)); + stateset->setTextureAttributeAndModes(texunit, TexEnvCombine::value(), osg::StateAttribute::ON); ++texunit; } @@ -185,7 +252,7 @@ namespace Terrain stateset->setTextureAttributeAndModes(texunit, tex.get()); if (layerTileSize != 1.f) - stateset->setTextureAttributeAndModes(texunit, getLayerTexMat(layerTileSize), osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(texunit, LayerTexMat::value(layerTileSize), osg::StateAttribute::ON); } stateset->setRenderBinDetails(passIndex++, "RenderBin"); diff --git a/components/to_utf8/to_utf8.cpp b/components/to_utf8/to_utf8.cpp index 8af0bc5ed8..d4ab00381b 100644 --- a/components/to_utf8/to_utf8.cpp +++ b/components/to_utf8/to_utf8.cpp @@ -2,10 +2,11 @@ #include #include -#include #include #include +#include + /* This file contains the code to translate from WINDOWS-1252 (native charset used in English version of Morrowind) to UTF-8. The library is designed to be extened to support more source encodings later, @@ -319,9 +320,7 @@ void Utf8Encoder::copyFromArray2(const char*& chp, char* &out) } } - std::ios::fmtflags f(std::cout.flags()); - std::cout << "Could not find glyph " << std::hex << (int)ch << " " << (int)ch2 << " " << (int)ch3 << std::endl; - std::cout.flags(f); + Log(Debug::Info) << "Could not find glyph " << std::hex << (int)ch << " " << (int)ch2 << " " << (int)ch3; *(out++) = ch; // Could not find glyph, just put whatever } diff --git a/components/vfs/filesystemarchive.cpp b/components/vfs/filesystemarchive.cpp index 9738e2a17c..ce4ff020ef 100644 --- a/components/vfs/filesystemarchive.cpp +++ b/components/vfs/filesystemarchive.cpp @@ -1,9 +1,9 @@ #include "filesystemarchive.hpp" -#include - #include +#include + namespace VFS { @@ -41,7 +41,7 @@ namespace VFS std::transform(proper.begin() + prefix, proper.end(), std::back_inserter(searchable), normalize_function); if (!mIndex.insert (std::make_pair (searchable, file)).second) - std::cerr << "Warning: found duplicate file for '" << proper << "', please check your file system for two files with the same name in different cases." << std::endl; + Log(Debug::Warning) << "Warning: found duplicate file for '" << proper << "', please check your file system for two files with the same name in different cases."; } mBuiltIndex = true; diff --git a/components/vfs/registerarchives.cpp b/components/vfs/registerarchives.cpp index a22e785cd2..80e639f350 100644 --- a/components/vfs/registerarchives.cpp +++ b/components/vfs/registerarchives.cpp @@ -1,9 +1,10 @@ #include "registerarchives.hpp" #include -#include #include +#include + #include #include #include @@ -21,7 +22,7 @@ namespace VFS { // Last BSA has the highest priority const std::string archivePath = collections.getPath(*archive).string(); - std::cout << "Adding BSA archive " << archivePath << std::endl; + Log(Debug::Info) << "Adding BSA archive " << archivePath; vfs->addArchive(new BsaArchive(archivePath)); } @@ -40,12 +41,12 @@ namespace VFS { if (seen.insert(*iter).second) { - std::cout << "Adding data directory " << iter->string() << std::endl; + Log(Debug::Info) << "Adding data directory " << iter->string(); // Last data dir has the highest priority vfs->addArchive(new FileSystemArchive(iter->string())); } else - std::cerr << "Ignoring duplicate data directory " << iter->string() << std::endl; + Log(Debug::Info) << "Ignoring duplicate data directory " << iter->string(); } } diff --git a/components/widgets/imagebutton.cpp b/components/widgets/imagebutton.cpp index a3b0ae28ab..2ea494ebd0 100644 --- a/components/widgets/imagebutton.cpp +++ b/components/widgets/imagebutton.cpp @@ -2,6 +2,8 @@ #include +#include + namespace Gui { @@ -77,7 +79,7 @@ namespace Gui MyGUI::ITexture* texture = MyGUI::RenderManager::getInstance().getTexture(mImageNormal); if (!texture) { - std::cerr << "ImageButton: can't find " << mImageNormal << std::endl; + Log(Debug::Error) << "ImageButton: can't find image " << mImageNormal; return MyGUI::IntSize(0,0); } return MyGUI::IntSize (texture->getWidth(), texture->getHeight()); diff --git a/docs/openmw-stage1.md b/docs/openmw-stage1.md index 4707a44354..4290080b8a 100644 --- a/docs/openmw-stage1.md +++ b/docs/openmw-stage1.md @@ -1383,9 +1383,9 @@ We add several new script instructions: * PlayBackgroundMusic (t) * PlaylistBackgroundMusic (pl) * StopBackgroundMusic -* PlayEventMusic (t, loop=0, force=0) -* PlaylistEventMusic (pl, loop=0, force=0) -* StopEventMusic +* PlayEventMusic (t, priority, loop=0, force=0) +* PlaylistEventMusic (pl, priority, loop=0, force=0) +* StopEventMusic (priority) Arguments: @@ -1393,8 +1393,14 @@ Arguments: * pl (string): a playlist (specified by its ID) * loop (integer): looping or not * force (integer): Terminate any other running event music first. If this flag is 0 and there is already event music playing the new event music is ignored. Event music provided by content developers will usually not set this flag. +* priority (integer): Priority for event music. If there are multiple instances of active event music only the one with the highest priority will play. There can only ever be one instance of event music for any priority level. If event music of higher priority stops and there is event music of lower priority the event music of lower priority starts to play. -Note: An attempt to play a track or playlist that is currently playing is ignored. In this case the track or playlist does not restart. +Note: An attempt to play a track or playlist that is currently playing is ignored. In this case the track or playlist does not restart. Changes in looping behaviour are considered though. + +New games starting with a new format omwgame file can use priorities as the developer sees fit. For old games where automatic injection of scripts and music records take place we define the following priorities: + +* 10: Combat music +* 0: Basic event music ### Transition of Existing System diff --git a/docs/source/manuals/installation/common-problems.rst b/docs/source/manuals/installation/common-problems.rst index 543b8ec9b9..0967ea9eff 100644 --- a/docs/source/manuals/installation/common-problems.rst +++ b/docs/source/manuals/installation/common-problems.rst @@ -47,4 +47,33 @@ Installing game files via Steam on macOS: DISK WRITE ERROR [other entries] } - Restart the Steam client. The download should now proceed. \ No newline at end of file + Restart the Steam client. The download should now proceed. + +In-game textures show up as pink +################################ + +:Symptoms: + Some textures don't show up and are replaced by pink "filler" textures. + +:Cause: + Textures shipped with Morrowind are compressed with S3TC, a texture compression algorithm that was patented in + the United States until October 2017. Software drivers and operating system distributions released before that date + may not include support for this algorithm. + +:Fix: + Upgrade your graphics drivers and/or operating system to the latest version. + +Music plays, but only a black screen is shown +############################################# + +:Symptoms: + When launching OpenMW, audio is heard but there is only a black screen. + +:Cause: + This can occur when you did not import the Morrowind.ini file when the launcher asked for it. + +:Fix: + To fix it, you need to delete the `launcher.cfg` file from your configuration path + ([Path description on Wiki](https://wiki.openmw.org/index.php?title=Paths)), then start the launcher, select your + Morrowind installation, and when the launcher asks whether the `Morrowind.ini` file should be imported, make sure + to select "Yes". \ No newline at end of file diff --git a/docs/source/manuals/openmw-cs/index.rst b/docs/source/manuals/openmw-cs/index.rst index f124b526f2..ee82907119 100644 --- a/docs/source/manuals/openmw-cs/index.rst +++ b/docs/source/manuals/openmw-cs/index.rst @@ -9,7 +9,7 @@ few chapters to familiarise yourself with the new interface. .. warning:: OpenMW CS is still software in development. The manual does not cover any of - its shortcomings, it is written as if everything was working as inteded. + its shortcomings, it is written as if everything was working as intended. Please report any software problems as bugs in the software, not errors in the manual. diff --git a/docs/source/manuals/openmw-cs/record-filters.rst b/docs/source/manuals/openmw-cs/record-filters.rst index a579d8dd88..19eee2542c 100644 --- a/docs/source/manuals/openmw-cs/record-filters.rst +++ b/docs/source/manuals/openmw-cs/record-filters.rst @@ -121,7 +121,7 @@ existing filters into more complex ones. Scopes ====== -Every default filter has the prefix ``project``. This is a *scpoe*, a mechanism +Every default filter has the prefix ``project``. This is a *scope*, a mechanism that determines the lifetime of the filter. These are the supported scopes: ``project::`` diff --git a/docs/source/manuals/openmw-cs/tour.rst b/docs/source/manuals/openmw-cs/tour.rst index 5d7034deb3..fbd4fd0478 100644 --- a/docs/source/manuals/openmw-cs/tour.rst +++ b/docs/source/manuals/openmw-cs/tour.rst @@ -236,7 +236,7 @@ a negative number indicating that he will restock again to maintain that level. However, it's an attractive item, so he will probably wear it rather than sell it. So set his stock level too high for him to wear them all (3 works, 2 might do). -Another possibilty, again in Seyda Neen making it easy to access, would be for +Another possibility, again in Seyda Neen making it easy to access, would be for Fargoth to give it to the player in exchange for his healing ring. .. figure:: https://gitlab.com/OpenMW/openmw-docs/raw/master/docs/source/manuals/openmw-cs/_static/images/chapter-1/Ring_to_Fargoth_1.png @@ -360,7 +360,7 @@ the base game. "Modified" status will cover items from the base game which have been modified in this addon. Click on the top of the column to toggle between ascending and descending order - thus between "Added" -and "Modified" at the top. Or put your desired modified status into a filter then sort alpabetically +and "Modified" at the top. Or put your desired modified status into a filter then sort alphabetically on a different column. diff --git a/docs/source/reference/modding/settings/game.rst b/docs/source/reference/modding/settings/game.rst index 308a295469..b733cc7c49 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -130,7 +130,7 @@ enchanted weapons are magical :Range: True/False :Default: True -Makes enchanted weapons without Magical flag bypass normal weapons resistance (and weakness) certain creatures have. +Make enchanted weapons without Magical flag bypass normal weapons resistance (and weakness) certain creatures have. This is how original Morrowind behaves. This setting can only be configured by editing the settings configuration file. @@ -142,7 +142,7 @@ prevent merchant equipping :Range: True/False :Default: False -Prevents merchants from equipping items that are sold to them. +Prevent merchants from equipping items that are sold to them. This setting can only be configured by editing the settings configuration file. @@ -153,7 +153,7 @@ followers attack on sight :Range: True/False :Default: False -Makes player followers and escorters start combat with enemies who have started combat with them or the player. +Make player followers and escorters start combat with enemies who have started combat with them or the player. Otherwise they wait for the enemies or the player to do an attack first. Please note this setting has not been extensively tested and could have side effects with certain quests. @@ -171,3 +171,15 @@ For example, if the main animation mesh has name Meshes/x.nif, an engine will lo Can be useful if you want to use several animation replacers without merging them. Attention: animations from AnimKit have own format and are not supposed to be directly loaded in-game! This setting can only be configured by editing the settings configuration file. + +barter disposition change is permanent +-------------------------------------- + +:Type: boolean +:Range: True/False +:Default: False + +If this setting is true, disposition change of merchants caused by trading will be permanent and won't be discarded upon exiting dialogue with them. +This imitates the option Morrowind Code Patch offers. + +This setting can be toggled with a checkbox in Advanced tab of the launcher. diff --git a/extern/oics/tinyxmlparser.cpp b/extern/oics/tinyxmlparser.cpp index d5bda8fee7..241f85c3ae 100644 --- a/extern/oics/tinyxmlparser.cpp +++ b/extern/oics/tinyxmlparser.cpp @@ -104,25 +104,17 @@ void TiXmlBase::ConvertUTF32ToUTF8( unsigned long input, char* output, int* leng output += *length; - // Scary scary fall throughs. - switch (*length) - { - case 4: - --output; - *output = (char)((input | BYTE_MARK) & BYTE_MASK); - input >>= 6; - case 3: - --output; - *output = (char)((input | BYTE_MARK) & BYTE_MASK); - input >>= 6; - case 2: - --output; - *output = (char)((input | BYTE_MARK) & BYTE_MASK); - input >>= 6; - case 1: - --output; - *output = (char)(input | FIRST_BYTE_MARK[*length]); - } + int lengthLeft = *length; + while (lengthLeft > 1) + { + --output; + *output = (char)((input | BYTE_MARK) & BYTE_MASK); + input >>= 6; + --lengthLeft; + } + + --output; + *output = (char)(input | FIRST_BYTE_MARK[*length]); } @@ -1295,9 +1287,10 @@ const char* TiXmlUnknown::Parse( const char* p, TiXmlParsingData* data, TiXmlEnc ++p; } - if ( !p ) + if ( !p || !*p ) { - if ( document ) document->SetError( TIXML_ERROR_PARSING_UNKNOWN, 0, 0, encoding ); + if ( document ) document->SetError( TIXML_ERROR_PARSING_UNKNOWN, 0, 0, encoding ); + return 0; } if ( *p == '>' ) return p+1; diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 1390bf8c6d..c2ac2eb1ca 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -203,25 +203,33 @@ show effect duration = false # Account for the first follower in fast travel cost calculations. charge for every follower travelling = false -# Prevents merchants from equipping items that are sold to them. +# Prevent merchants from equipping items that are sold to them. prevent merchant equipping = false # Make enchanted weaponry without Magical flag bypass normal weapons resistance enchanted weapons are magical = true -# Makes player followers and escorters start combat with enemies who have started combat with them +# Make player followers and escorters start combat with enemies who have started combat with them # or the player. Otherwise they wait for the enemies or the player to do an attack first. followers attack on sight = false # Can loot non-fighting actors during death animation can loot during death animation = true -# Makes the value of filled soul gems dependent only on soul magnitude (with formula from the Morrowind Code Patch) +# Make the value of filled soul gems dependent only on soul magnitude (with formula from the Morrowind Code Patch) rebalance soul gem values = false # Allow to load per-group KF-files from Animations folder use additional anim sources = false +# Make the disposition change of merchants caused by barter dealings permanent +barter disposition change is permanent = false + +# Uses the MCP formula (damage * (strength / 40)) to factor Strength into hand-to-hand combat. +# (0 means it does not factor it in, 1 means it factors into werewolves damage calculation and +# 2 means werewolves are ignored) +strength influences hand to hand = 0 + [General] # Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16). diff --git a/files/ui/advancedpage.ui b/files/ui/advancedpage.ui index 0e43654f2c..8395d028f6 100644 --- a/files/ui/advancedpage.ui +++ b/files/ui/advancedpage.ui @@ -2,14 +2,6 @@ AdvancedPage - - - 0 - 0 - 671 - 373 - - @@ -17,14 +9,6 @@ true - - - 0 - 0 - 630 - 791 - - @@ -45,7 +29,7 @@ - <html><head/><body><p>Makes player followers and escorters start combat with enemies who have started combat with them or the player. Otherwise they wait for the enemies or the player to do an attack first.</p></body></html> + <html><head/><body><p>Make player followers and escorters start combat with enemies who have started combat with them or the player. Otherwise they wait for the enemies or the player to do an attack first.</p></body></html> Followers attack on sight @@ -65,13 +49,96 @@ - <html><head/><body><p>If this setting is true, the value of filled soul gems is dependent only on soul magnitude.</p><p>The default value is false.</p></body></html> + <html><head/><body><p>Make the value of filled soul gems dependent only on soul magnitude.</p></body></html> Rebalance soul gem values + + + + <html><head/><body><p>Account for the first follower in fast travel cost calculations.</p></body></html> + + + Charge for every follower travelling + + + + + + + <html><head/><body><p>Make enchanted weaponry without Magical flag bypass normal weapons resistance, like in Morrowind.</p></body></html> + + + Enchanted weapons are magical + + + + + + + <html><head/><body><p>Make disposition change of merchants caused by trading permanent.</p></body></html> + + + Barter disposition change is permanent + + + + + + + <html><head/><body><p>Factor strength into hand-to-hand damage calculations, as the MCP formula: damage * (strength / 40).</p><p>The default value is Off.</p></body></html> + + + + -1 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Factor strength into hand-to-hand combat: + + + + + + + 0 + + + + Off + + + + + Affect werewolves + + + + + Do not affect werewolves + + + + + + + @@ -137,7 +204,7 @@ - -1 + 6 0 @@ -312,7 +379,7 @@ - -1 + 6 0 @@ -379,7 +446,7 @@ - -1 + 6 0