diff --git a/CHANGELOG.md b/CHANGELOG.md index 1923e52377..a286613727 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ 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 #3249: Fixed revert function not updating views properly Bug #3374: Touch spells not hitting kwama foragers @@ -64,6 +65,7 @@ Bug #4475: Scripted animations should not cause movement Bug #4479: "Game" category on Advanced page is getting too long Bug #4480: Segfault in QuickKeysMenu when item no longer in inventory + Bug #4483: Shapes without NiTexturingProperty are rendered Bug #4489: Goodbye doesn't block dialogue hyperlinks Bug #4490: PositionCell on player gives "Error: tried to add local script twice" Bug #4494: Training cap based off Base Skill instead of Modified Skill @@ -74,11 +76,18 @@ Bug #4510: Division by zero in MWMechanics::CreatureStats::setAttribute Bug #4519: Knockdown does not discard movement in the 1st-person mode 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 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 @@ -87,9 +96,11 @@ 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 Task #2490: Don't open command prompt window on Release-mode builds automatically Task #4545: Enable is_pod string test 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/advancedpage.cpp b/apps/launcher/advancedpage.cpp index 3e0314607e..ff229e02d9 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -75,6 +75,7 @@ bool Launcher::AdvancedPage::loadSettings() 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"); // Input Settings loadSettingBool(allowThirdPersonZoomCheckBox, "allow third person zoom", "Input"); @@ -129,6 +130,7 @@ void Launcher::AdvancedPage::saveSettings() 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"); // Input Settings saveSettingBool(allowThirdPersonZoomCheckBox, "allow third person zoom", "Input"); diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp index d599f1f961..ebc686c233 100644 --- a/apps/opencs/main.cpp +++ b/apps/opencs/main.cpp @@ -8,8 +8,9 @@ #include #include -#include "model/doc/messages.hpp" +#include +#include "model/doc/messages.hpp" #include "model/world/universalid.hpp" #ifdef Q_OS_MAC @@ -41,45 +42,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.log"); } diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 8b6f9d0b3b..bd40dc1abe 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -198,6 +198,7 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) : mWindow(NULL) , mEncoding(ToUTF8::WINDOWS_1252) , mEncoder(NULL) + , mScreenCaptureOperation(nullptr) , mSkipMenu (false) , mUseSound (true) , mCompileAll (false) diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index d13a731279..d9fff69528 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -5,12 +5,10 @@ #include #include #include +#include -#include #include "engine.hpp" -#include - #if defined(_WIN32) // For OutputDebugString #ifndef WIN32_LEAN_AND_MEAN @@ -241,124 +239,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.log"); } // 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/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index fb492ff3b2..df214ce863 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -22,6 +22,8 @@ #include #include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/journal.hpp" @@ -508,9 +510,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) 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/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..7b177fdb03 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -362,52 +362,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")->getString(); + const std::string sCompanionShare = gmst.find("sCompanionShare")->getString(); + const std::string sBarter = gmst.find("sBarter")->getString(); + const std::string sSpells = gmst.find("sSpells")->getString(); + const std::string sTravel = gmst.find("sTravel")->getString(); + const std::string sSpellMakingMenuTitle = gmst.find("sSpellMakingMenuTitle")->getString(); + const std::string sEnchanting = gmst.find("sEnchanting")->getString(); + const std::string sServiceTrainingTitle = gmst.find("sServiceTrainingTitle")->getString(); + const std::string sRepair = gmst.find("sRepair")->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()) + { + std::cerr << "Warning: can not talk with non-actor object." << std::endl; + return; + } + bool sameActor = (mPtr == actor); if (!sameActor) { diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 99fe777aa7..3df482d684 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -37,6 +37,7 @@ namespace MWGui , mLastRenderTime(0.0) , mLoadingOnTime(0.0) , mImportantLabel(false) + , mVisible(false) , mProgress(0) , mShowWallpaper(true) { 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/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/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index 0a9fe1b4ac..e11147c742 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -334,7 +334,7 @@ namespace MWGui ? gmst.find("iBarterSuccessDisposition")->getInt() : gmst.find("iBarterFailDisposition")->getInt(); - MWBase::Environment::get().getDialogueManager()->applyDispositionChange(dispositionDelta); + MWBase::Environment::get().getDialogueManager()->applyBarterDispositionChange(dispositionDelta); } // display message on haggle failure diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index df2d5ed6d2..c97efed34d 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -898,7 +898,8 @@ namespace MWGui mKeyboardNavigation->onFrame(); - mMessageBoxManager->onFrame(frameDuration); + if (mMessageBoxManager) + mMessageBoxManager->onFrame(frameDuration); mToolTips->onFrame(frameDuration); diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index 0ccd0b6ec6..2685e0e0c2 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -494,10 +494,13 @@ namespace MWMechanics static const int iWereWolfLevelToAttack = gmst.find("iWereWolfLevelToAttack")->getInt(); - 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")->getInt(); + rating = iWereWolfFleeMod; + } } if (rating != 0.0f) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index f843be66ee..f5d6f8584b 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1221,11 +1221,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) { @@ -1250,49 +1250,60 @@ bool CharacterController::updateWeaponState() float complete; 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); + 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; + } } - 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(isWerewolf) - { - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - const ESM::Sound *sound = store.get().searchRandom("WolfEquip"); - if(sound) + 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); } } } diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 0741bedeb4..09dadcabab 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -315,14 +315,14 @@ 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) { } @@ -864,7 +864,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); @@ -1037,7 +1037,7 @@ namespace MWMechanics bool CastSpell::spellIncreasesSkill() { - if (mIsScripted) + if (mManualSpell) return false; return MWMechanics::spellIncreasesSkill(mId); diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index 5e69b4696e..72f6a1f4a0 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -88,10 +88,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/weaponpriority.cpp b/apps/openmw/mwmechanics/weaponpriority.cpp index 5a0bd9c154..26c784c4d3 100644 --- a/apps/openmw/mwmechanics/weaponpriority.cpp +++ b/apps/openmw/mwmechanics/weaponpriority.cpp @@ -30,7 +30,7 @@ namespace MWMechanics return 0.f; float rating=0.f; - float bonus=0.f; + float rangedMult=1.f; if (weapon->mData.mType >= ESM::Weapon::MarksmanBow && weapon->mData.mType <= ESM::Weapon::MarksmanThrown) { @@ -44,25 +44,33 @@ namespace MWMechanics if (MWBase::Environment::get().getWorld()->isUnderwater(MWWorld::ConstPtr(enemy), 0.75f)) return 0.f; - bonus+=1.5f; + if (getDistanceMinusHalfExtents(actor, enemy) >= getMaxAttackDistance(enemy)) + rangedMult = 1.5f; } if (weapon->mData.mType >= ESM::Weapon::MarksmanBow) { - rating = (weapon->mData.mChop[0] + weapon->mData.mChop[1]) / 2.f; + float rangedDamage = weapon->mData.mChop[0] + weapon->mData.mChop[1]; + MWMechanics::adjustWeaponDamage(rangedDamage, item, actor); + + rating = rangedDamage / 2.f; if (weapon->mData.mType >= ESM::Weapon::MarksmanThrown) MWMechanics::resistNormalWeapon(enemy, actor, item, rating); } else { + float meleeDamage = 0.f; + for (int i=0; i<2; ++i) { - rating += weapon->mData.mSlash[i]; - rating += weapon->mData.mThrust[i]; - rating += weapon->mData.mChop[i]; + meleeDamage += weapon->mData.mSlash[i]; + meleeDamage += weapon->mData.mThrust[i]; + meleeDamage += weapon->mData.mChop[i]; } - rating /= 6.f; + + MWMechanics::adjustWeaponDamage(meleeDamage, item, actor); + rating = meleeDamage / 6.f; MWMechanics::resistNormalWeapon(enemy, actor, item, rating); } @@ -71,7 +79,6 @@ namespace MWMechanics { if (item.getClass().getItemHealth(item) == 0) return 0.f; - rating *= item.getClass().getItemHealth(item) / float(item.getClass().getItemMaxHealth(item)); } if (weapon->mData.mType == ESM::Weapon::MarksmanBow) @@ -103,13 +110,12 @@ namespace MWMechanics int skill = item.getClass().getEquipmentSkill(item); if (skill != -1) - rating *= actor.getClass().getSkill(actor, skill) / 100.f; + { + int value = actor.getClass().getSkill(actor, skill); + rating *= MWMechanics::getHitChance(actor, enemy, value) / 100.f; + } - // There is no need to apply bonus if weapon rating == 0 - if (rating == 0.f) - return 0.f; - - return rating + bonus; + return rating * rangedMult; } float rateAmmo(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy, MWWorld::Ptr &bestAmmo, ESM::Weapon::Type ammoType) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 852ab0b523..410157d800 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -204,6 +204,7 @@ namespace MWRender , mNightEyeFactor(0.f) , mDistantFog(false) , mDistantTerrain(false) + , mFieldOfViewOverridden(false) , mFieldOfViewOverride(0.f) , mBorders(false) { @@ -717,9 +718,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(" ")); @@ -743,7 +741,10 @@ namespace MWRender 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())); diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 37fc399450..dab3bb7d9b 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -1118,6 +1118,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/mwscript/dialogueextensions.cpp b/apps/openmw/mwscript/dialogueextensions.cpp index 21d8d469b9..4b6ddcf9fb 100644 --- a/apps/openmw/mwscript/dialogueextensions.cpp +++ b/apps/openmw/mwscript/dialogueextensions.cpp @@ -1,3 +1,5 @@ +#include + #include "dialogueextensions.hpp" #include @@ -134,6 +136,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); + std::cerr << error << std::endl; + return; + } + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Dialogue, ptr); } }; diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 0dd95f7732..8c71ab61b1 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -142,16 +142,12 @@ namespace MWSound 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; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index c162fd57dc..f4c2a75f3d 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1180,9 +1180,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) { 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/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/components/CMakeLists.txt b/components/CMakeLists.txt index 3553433b70..2c92c4256d 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -85,7 +85,7 @@ add_component_dir (esmterrain ) add_component_dir (misc - utf8stream stringops resourcehelpers rng messageformatparser + utf8stream stringops resourcehelpers rng debugging messageformatparser ) IF(NOT WIN32 AND NOT APPLE) diff --git a/components/crashcatcher/crashcatcher.cpp b/components/crashcatcher/crashcatcher.cpp index e5377b64f4..f7b8717a6b 100644 --- a/components/crashcatcher/crashcatcher.cpp +++ b/components/crashcatcher/crashcatcher.cpp @@ -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 + std::cerr << "Warning: can not close and remove file '" << respfile << "': " << std::strerror(errno) << std::endl; } printf("!!! Could not create gdb command file\n"); } diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index ff3123c5b8..0f5b175025 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -37,6 +37,8 @@ namespace ESMTerrain } LandObject::LandObject(const LandObject ©, const osg::CopyOp ©op) + : mLand(nullptr) + , mLoadFlags(0) { } diff --git a/components/misc/debugging.hpp b/components/misc/debugging.hpp new file mode 100644 index 0000000000..a983a88dfa --- /dev/null +++ b/components/misc/debugging.hpp @@ -0,0 +1,112 @@ +#ifndef MISC_DEBUGGING_H +#define MISC_DEBUGGING_H + +#include +#include + +#include + +namespace Misc +{ +#if defined(_WIN32) && defined(_DEBUG) + + class DebugOutput : public boost::iostreams::sink + { + 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; + }; +#endif +} + +int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, char *argv[], const std::string& logName) +{ + // Some objects used to redirect cout and cerr + // Scope must be here, so this still works inside the catch block for logging exceptions + std::streambuf* cout_rdbuf = std::cout.rdbuf (); + std::streambuf* cerr_rdbuf = std::cerr.rdbuf (); + +#if !(defined(_WIN32) && defined(_DEBUG)) + boost::iostreams::stream_buffer coutsb; + boost::iostreams::stream_buffer cerrsb; +#endif + + std::ostream oldcout(cout_rdbuf); + std::ostream oldcerr(cerr_rdbuf); + + boost::filesystem::ofstream logfile; + + int ret = 0; + 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(Misc::DebugOutput()); + std::cout.rdbuf (&sb); + std::cerr.rdbuf (&sb); +#else + // Redirect cout and cerr to the log file + boost::filesystem::ofstream logfile; + logfile.open (boost::filesystem::path(cfgMgr.getLogPath() / logName)); + + std::ostream oldcout(cout_rdbuf); + std::ostream oldcerr(cerr_rdbuf); + + coutsb.open (Misc::Tee(logfile, oldcout)); + cerrsb.open (Misc::Tee(logfile, oldcerr)); + + std::cout.rdbuf (&coutsb); + std::cerr.rdbuf (&cerrsb); +#endif + 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, "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; +} + +#endif 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/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 4e7f6d511e..6d2a01f5d1 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -573,6 +573,10 @@ namespace NifOsg } } + // Make sure we don't render untextured shapes + if (nifNode->recType == Nif::RC_NiTriShape && boundTextures.empty()) + node->setNodeMask(0x0); + if(nifNode->recType == Nif::RC_NiAutoNormalParticles || nifNode->recType == Nif::RC_NiRotatingParticles) handleParticleSystem(nifNode, node, composite, animflags, rootNode); diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 03b850fac1..db8d99ab4e 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -393,9 +393,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; iSetError( 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 277b5dd083..41063929e5 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -203,25 +203,28 @@ 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 + [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 486d410df9..553c244d38 100644 --- a/files/ui/advancedpage.ui +++ b/files/ui/advancedpage.ui @@ -22,7 +22,7 @@ 0 0 641 - 968 + 998 @@ -45,7 +45,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,7 +65,7 @@ - <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 @@ -89,6 +89,16 @@ 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