diff --git a/CHANGELOG.md b/CHANGELOG.md index b1f00cccd..775f0545d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,12 @@ 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 + Bug #2626: Resurrecting the player does not resume the game Bug #2772: Non-existing class or faction freezes the game 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 @@ -20,6 +22,7 @@ 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 #3950: FLATTEN_STATIC_TRANSFORMS optimization breaks animated collision shapes Bug #3993: Terrain texture blending map is not upscaled Bug #3997: Almalexia doesn't pace Bug #4036: Weird behaviour of AI packages if package target has non-unique ID @@ -28,15 +31,18 @@ Bug #4125: OpenMW logo cropped on bugtracker 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 #4251: Stationary NPCs do not return to their position after combat 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 #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 @@ -62,17 +68,32 @@ Bug #4480: Segfault in QuickKeysMenu when item no longer in inventory Bug #4489: Goodbye doesn't block dialogue hyperlinks Bug #4490: PositionCell on player gives "Error: tried to add local script twice" - Bug #4491: Training cap based off Base Skill instead of Modified Skill + Bug #4494: Training cap based off Base Skill instead of Modified Skill Bug #4495: Crossbow animations blending is buggy Bug #4496: SpellTurnLeft and SpellTurnRight animation groups are unused Bug #4497: File names starting with x or X are not classified as animation 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 #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 #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 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 @@ -81,10 +102,13 @@ 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 0.44.0 ------ diff --git a/apps/essimporter/importinventory.cpp b/apps/essimporter/importinventory.cpp index 2c87a9a19..8c7d07f63 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 b0a35f0a5..ff229e02d 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -43,7 +43,6 @@ void Launcher::AdvancedPage::on_runScriptAfterStartupBrowseButton_clicked() QDir::currentPath(), QString(tr("Text file (*.txt)"))); - if (scriptFile.isEmpty()) return; @@ -53,7 +52,7 @@ void Launcher::AdvancedPage::on_runScriptAfterStartupBrowseButton_clicked() return; const QString path(QDir::toNativeSeparators(info.absoluteFilePath())); - + runScriptAfterStartupField->setText(path); } bool Launcher::AdvancedPage::loadSettings() @@ -74,6 +73,9 @@ 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"); // Input Settings loadSettingBool(allowThirdPersonZoomCheckBox, "allow third person zoom", "Input"); @@ -126,6 +128,9 @@ 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"); // Input Settings saveSettingBool(allowThirdPersonZoomCheckBox, "allow third person zoom", "Input"); @@ -168,4 +173,4 @@ void Launcher::AdvancedPage::saveSettingBool(QCheckBox *checkbox, const std::str void Launcher::AdvancedPage::slotLoadedCellsChanged(QStringList cellNames) { loadCellsForAutocomplete(cellNames); -} \ No newline at end of file +} diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 98b574208..c999fb950 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -90,19 +90,19 @@ void Launcher::MainDialog::createIcons() dataFilesButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); QListWidgetItem *graphicsButton = new QListWidgetItem(iconWidget); - graphicsButton->setIcon(QIcon::fromTheme("video-display")); + graphicsButton->setIcon(QIcon(":/images/preferences-video.png")); graphicsButton->setText(tr("Graphics")); graphicsButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom | Qt::AlignAbsolute); graphicsButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); QListWidgetItem *settingsButton = new QListWidgetItem(iconWidget); - settingsButton->setIcon(QIcon::fromTheme("preferences-system")); + settingsButton->setIcon(QIcon(":/images/preferences.png")); settingsButton->setText(tr("Settings")); settingsButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom); settingsButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); QListWidgetItem *advancedButton = new QListWidgetItem(iconWidget); - advancedButton->setIcon(QIcon::fromTheme("emblem-system")); + advancedButton->setIcon(QIcon(":/images/preferences-advanced.png")); advancedButton->setText(tr("Advanced")); advancedButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom); advancedButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp index d599f1f96..ebc686c23 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 68aa1c102..8a54ea73c 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -289,6 +289,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 ae49cfadb..d8f9d8cf1 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -5,12 +5,10 @@ #include #include #include +#include -#include #include "engine.hpp" -#include - /* Start of tes3mp addition @@ -331,43 +329,25 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat return true; } -#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 +int runApplication(int argc, char *argv[]) { -public: - Tee(std::ostream &stream, std::ostream &stream2) - : out(stream), out2(stream2) - { - } +#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)); - std::streamsize write(const char *str, std::streamsize size) + if (parseOptions(argc, argv, *engine, cfgMgr)) { - out.write (str, size); - out.flush(); - out2.write (str, size); - out2.flush(); - return size; + engine->go(); } -private: - std::ostream &out; - std::ostream &out2; -}; -#endif + return 0; +} #ifdef ANDROID extern "C" int SDL_main(int argc, char**argv) @@ -375,99 +355,16 @@ extern "C" int SDL_main(int argc, char**argv) 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 - /* - Start of tes3mp change (major) - - Instead of logging information in openmw.log, use a more descriptive filename - that includes a timestamp - */ - // Redirect cout and cerr to tes3mp client log - logfile.open (boost::filesystem::path(cfgMgr.getLogPath() / "/tes3mp-client-" += Log::getFilenameTimestamp() += ".log")); - /* - End of tes3mp change (major) - */ - - coutsb.open (Tee(logfile, oldcout)); - cerrsb.open (Tee(logfile, oldcerr)); - - std::cout.rdbuf (&coutsb); - std::cerr.rdbuf (&cerrsb); -#endif - - /* - Start of tes3mp addition - - Initialize the logger added for multiplayer - */ - LOG_INIT(Log::LOG_INFO); - /* - End of tes3mp addition - */ - - 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); + /* + Start of tes3mp change (major) - return ret; + Instead of logging information in openmw.log, use a more descriptive filename + that includes a timestamp + */ + return wrapApplication(&runApplication, argc, argv, "/tes3mp-client-" + Log::getFilenameTimestamp() + ".log"); + /* + End of tes3mp change (major) + */ } // 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 0636468c5..869fe94df 100644 --- a/apps/openmw/mwbase/dialoguemanager.hpp +++ b/apps/openmw/mwbase/dialoguemanager.hpp @@ -86,8 +86,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/statemanager.hpp b/apps/openmw/mwbase/statemanager.hpp index 48a95f029..643695c37 100644 --- a/apps/openmw/mwbase/statemanager.hpp +++ b/apps/openmw/mwbase/statemanager.hpp @@ -55,6 +55,8 @@ namespace MWBase virtual void endGame() = 0; + virtual void resumeGame() = 0; + virtual void deleteGame (const MWState::Character *character, const MWState::Slot *slot) = 0; virtual void saveGame (const std::string& description, const MWState::Slot *slot = 0) = 0; diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index dc8c716d9..43b1d1af6 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -445,7 +445,7 @@ namespace MWBase End of tes3mp addition */ - virtual bool castRay (float x1, float y1, float z1, float x2, float y2, float z2) = 0; + virtual bool castRay (float x1, float y1, float z1, float x2, float y2, float z2, bool ignoreDoors=false) = 0; ///< cast a Ray and return true if there is an object in the ray path. virtual bool toggleCollisionMode() = 0; diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index dc630178a..02401d890 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -747,7 +747,7 @@ namespace MWClass float Creature::getCapacity (const MWWorld::Ptr& ptr) const { const MWMechanics::CreatureStats& stats = getCreatureStats (ptr); - return static_cast(stats.getAttribute(0).getModified() * 5); + return static_cast(stats.getAttribute(ESM::Attribute::Strength).getModified() * 5); } int Creature::getServices(const MWWorld::ConstPtr &actor) const diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 433ccc8ed..4024f1d06 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1250,7 +1250,7 @@ namespace MWClass { const MWMechanics::CreatureStats& stats = getCreatureStats (ptr); static const float fEncumbranceStrMult = MWBase::Environment::get().getWorld()->getStore().get().find("fEncumbranceStrMult")->getFloat(); - return stats.getAttribute(0).getModified()*fEncumbranceStrMult; + return stats.getAttribute(ESM::Attribute::Strength).getModified()*fEncumbranceStrMult; } float Npc::getEncumbrance (const MWWorld::Ptr& ptr) const diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 4949dca4f..cb66cea04 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -21,6 +21,7 @@ #include #include +#include /* Start of tes3mp addition @@ -559,9 +560,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 a859b020c..03c5f0f46 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp @@ -105,8 +105,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 aba87c0ca..46c1d50e4 100644 --- a/apps/openmw/mwgui/console.cpp +++ b/apps/openmw/mwgui/console.cpp @@ -316,6 +316,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; @@ -352,6 +360,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 1f1fa092a..8cbc00076 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -67,7 +67,7 @@ namespace MWGui void ContainerWindow::onItemSelected(int index) { - if (mDragAndDrop->mIsOnDragAndDrop && mModel) + if (mDragAndDrop->mIsOnDragAndDrop) { dropItem(); return; @@ -103,6 +103,9 @@ namespace MWGui void ContainerWindow::dragItem(MyGUI::Widget* sender, int count) { + if (!mModel) + return; + if (!onTakeItem(mModel->getItem(mSelectedItem), count)) return; @@ -148,6 +151,9 @@ namespace MWGui void ContainerWindow::dropItem() { + if (!mModel) + return; + bool success = mModel->onDropItem(mDragAndDrop->mItem.mBase, mDragAndDrop->mDraggedCount); /* @@ -206,7 +212,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 f4fe54917..7b177fdb0 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; - } - - if (id >= separatorPos) + const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); + + 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/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 0e56087bc..d0f17b211 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -81,7 +81,12 @@ namespace MWGui , mLastYSize(0) , mPreview(new MWRender::InventoryPreview(parent, resourceSystem, MWMechanics::getPlayer())) , mTrading(false) + , mScaleFactor(1.0f) { + float uiScale = Settings::Manager::getFloat("scaling factor", "GUI"); + if (uiScale > 1.0) + mScaleFactor = uiScale; + mPreviewTexture.reset(new osgMyGUI::OSGTexture(mPreview->getTexture())); mPreview->rebuild(); @@ -445,10 +450,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) @@ -615,6 +620,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) diff --git a/apps/openmw/mwgui/inventorywindow.hpp b/apps/openmw/mwgui/inventorywindow.hpp index 124fe7b0e..d9cf6870c 100644 --- a/apps/openmw/mwgui/inventorywindow.hpp +++ b/apps/openmw/mwgui/inventorywindow.hpp @@ -101,6 +101,7 @@ namespace MWGui std::unique_ptr mPreview; bool mTrading; + float mScaleFactor; void onItemSelected(int index); void onItemSelectedFromSourceModel(int index); diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 99fe777aa..28f4b8890 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) { @@ -169,12 +170,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 +190,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/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 298fa5836..d5fc1b70d 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -201,6 +201,12 @@ namespace MWGui } } assert(index != -1); + if (index < 0) + { + mSelected = nullptr; + return; + } + mSelected = &mKey[index]; // prevent reallocation of zero key from Type_HandToHand @@ -398,15 +404,19 @@ namespace MWGui bool isReturnNeeded = playerStats.isParalyzed() || playerStats.isDead(); - if (isReturnNeeded) + if (isReturnNeeded && key->type != Type_Item) + { return; - - else if (isDelayNeeded) + } + else if (isDelayNeeded && key->type != Type_Item) + { mActivated = key; - + return; + } else + { mActivated = nullptr; - + } if (key->type == Type_Item || key->type == Type_MagicItem) { @@ -444,6 +454,11 @@ namespace MWGui // delay weapon switching if player is busy if (isDelayNeeded && (isWeapon || isTool)) + { + mActivated = key; + return; + } + else if (isReturnNeeded && (isWeapon || isTool)) { return; } diff --git a/apps/openmw/mwgui/spellmodel.hpp b/apps/openmw/mwgui/spellmodel.hpp index 8e29707ae..4aa1f9d2a 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 104aacc8d..fd1b9f0d7 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -345,7 +345,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/trainingwindow.cpp b/apps/openmw/mwgui/trainingwindow.cpp index 6dafcac7c..f0d2eaf34 100644 --- a/apps/openmw/mwgui/trainingwindow.cpp +++ b/apps/openmw/mwgui/trainingwindow.cpp @@ -154,7 +154,7 @@ namespace MWGui return; MWMechanics::NpcStats& npcStats = mPtr.getClass().getNpcStats (mPtr); - if (npcStats.getSkill (skillId).getBase () <= pcStats.getSkill (skillId).getBase ()) + if (npcStats.getSkill (skillId).getModified () <= pcStats.getSkill (skillId).getBase ()) { MWBase::Environment::get().getWindowManager()->messageBox ("#{sServiceTrainingWords}"); return; diff --git a/apps/openmw/mwgui/travelwindow.cpp b/apps/openmw/mwgui/travelwindow.cpp index 81c977bd0..5e8647b00 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,7 +56,7 @@ 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(); } @@ -168,7 +168,7 @@ 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(); diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 9a8234fa3..fe816d1ea 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -941,7 +941,8 @@ namespace MWGui mKeyboardNavigation->onFrame(); - mMessageBoxManager->onFrame(frameDuration); + if (mMessageBoxManager) + mMessageBoxManager->onFrame(frameDuration); mToolTips->onFrame(frameDuration); diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 3ff39ded6..f96406807 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -805,6 +805,8 @@ namespace MWMechanics for (ActiveSpells::TIterator it = spells.begin(); it != spells.end(); ++it) { + bool actorKilled = false; + const ActiveSpells::ActiveSpellParams& spell = it->second; MWWorld::Ptr caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(spell.mCasterActorId); for (std::vector::const_iterator effectIt = spell.mEffects.begin(); @@ -832,10 +834,14 @@ namespace MWMechanics caster.getClass().getNpcStats(caster).addWerewolfKill(); MWBase::Environment::get().getMechanicsManager()->actorKilled(ptr, player); + actorKilled = true; break; } } } + + if (actorKilled) + break; } } @@ -1377,6 +1383,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) @@ -1390,7 +1398,7 @@ namespace MWMechanics Instead of merely updating the player character's mAttackingOrSpell here, prepare an Attack packet for the LocalPlayer */ - if (iter->first == player) + if (isPlayer) { bool state = MWBase::Environment::get().getWorld()->getPlayer().getAttackingOrSpell(); DrawState_ dstate = player.getClass().getNpcStats(player).getDrawState(); @@ -1478,12 +1486,12 @@ namespace MWMechanics { if (timerUpdateAITargets == 0 && (isLocalActor || isAIActive)) { - 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); } @@ -1494,12 +1502,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) { diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index 0ccd0b6ec..2685e0e0c 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/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index 72e6ced19..8b52b15a4 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -53,7 +53,23 @@ namespace MWMechanics if (!isWithinMaxRange(osg::Vec3f(mX, mY, mZ), pos.asVec3())) return false; - if (pathTo(actor, ESM::Pathgrid::Point(static_cast(mX), static_cast(mY), static_cast(mZ)), duration)) + // Unfortunately, with vanilla assets destination is sometimes blocked by other actor. + // If we got close to target, check for actors nearby. If they are, finish AI package. + int destinationTolerance = 64; + ESM::Pathgrid::Point dest(static_cast(mX), static_cast(mY), static_cast(mZ)); + if (distance(pos.pos, dest) <= destinationTolerance) + { + std::vector targetActors; + std::pair result = MWBase::Environment::get().getWorld()->getHitContact(actor, destinationTolerance, targetActors); + + if (!result.first.isEmpty()) + { + actor.getClass().getMovementSettings(actor).mPosition[1] = 0; + return true; + } + } + + if (pathTo(actor, dest, duration)) { actor.getClass().getMovementSettings(actor).mPosition[1] = 0; return true; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index f0fbbadf6..fb5e9cb1e 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1272,8 +1272,10 @@ bool CharacterController::updateWeaponState() mWeapon = weapon != inv.end() ? *weapon : MWWorld::Ptr(); } + // Apply 1st-person weapon animations only for upper body MWRender::Animation::AnimPriority priorityWeapon(Priority_Weapon); - priorityWeapon[MWRender::Animation::BoneGroup_LowerBody] = Priority_WeaponLowerBody; + if (mPtr != MWMechanics::getPlayer() || !MWBase::Environment::get().getWorld()->isFirstPerson()) + priorityWeapon[MWRender::Animation::BoneGroup_LowerBody] = Priority_WeaponLowerBody; bool forcestateupdate = false; @@ -1281,11 +1283,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) { @@ -1310,49 +1312,60 @@ bool CharacterController::updateWeaponState() float complete; bool animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); + if (!animPlaying || complete >= 1.0f) { - forcestateupdate = true; - mAnimation->showCarriedLeft(updateCarriedLeftVisible(weaptype)); + // Weapon is changed, no current animation (e.g. unequipping or attack). + // Start equipping animation now. + if (weaptype != mWeaponType) + { + forcestateupdate = true; + mAnimation->showCarriedLeft(updateCarriedLeftVisible(weaptype)); - getWeaponGroup(weaptype, weapgroup); - mAnimation->setWeaponGroup(weapgroup); + getWeaponGroup(weaptype, weapgroup); + mAnimation->setWeaponGroup(weapgroup); - if (!isStillWeapon) - { - if (weaptype == WeapType_None) + 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 + + if(isWerewolf) { - mAnimation->showWeapons(false); - mAnimation->play(weapgroup, priorityWeapon, - MWRender::Animation::BlendMask_All, true, - 1.0f, "equip start", "equip stop", 0.0f, 0); - mUpperBodyState = UpperCharState_EquipingWeap; + 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); + } } - } - if(isWerewolf) - { - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - const ESM::Sound *sound = store.get().searchRandom("WolfEquip"); - if(sound) + 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); } } } @@ -1407,14 +1420,7 @@ 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); @@ -1498,6 +1504,11 @@ bool CharacterController::updateWeaponState() 0.0f, 0); mUpperBodyState = UpperCharState_CastingSpell; } + else + { + resetIdle = false; + } + if (mPtr.getClass().hasInventoryStore(mPtr)) { MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); @@ -1578,6 +1589,14 @@ bool CharacterController::updateWeaponState() } } + // 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; @@ -1741,18 +1760,22 @@ 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); } } @@ -2091,25 +2114,37 @@ void CharacterController::update(float duration) else if(rot.z() != 0.0f && !sneak && !(mPtr == getPlayer() && MWBase::Environment::get().getWorld()->isFirstPerson())) { if(rot.z() > 0.0f) - { movestate = inwater ? CharState_SwimTurnRight : CharState_TurnRight; - mAnimation->disable(mCurrentJump); - } else if(rot.z() < 0.0f) - { 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()) + { + 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 { - movestate = mMovementState; + 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()) @@ -2140,8 +2175,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) { diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 84630a479..754f551f9 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, diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 2b3684310..2256808b3 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -282,8 +282,21 @@ 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")->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(); diff --git a/apps/openmw/mwmechanics/difficultyscaling.cpp b/apps/openmw/mwmechanics/difficultyscaling.cpp index d5007d68c..bd78000ce 100644 --- a/apps/openmw/mwmechanics/difficultyscaling.cpp +++ b/apps/openmw/mwmechanics/difficultyscaling.cpp @@ -23,8 +23,10 @@ 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); /* Start of tes3mp change (major) diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 0591667b7..b1b04f304 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -231,6 +231,28 @@ namespace MWMechanics { mPath = pathgridGraph.aStarSearch(startNode, endNode.first); + // If nearest path node is in opposite direction from second, remove it from path. + // Especially useful for wandering actors, if the nearest node is blocked for some reason. + if (mPath.size() > 1) + { + ESM::Pathgrid::Point secondNode = *(++mPath.begin()); + osg::Vec3f firstNodeVec3f = MakeOsgVec3(mPathgrid->mPoints[startNode]); + osg::Vec3f secondNodeVec3f = MakeOsgVec3(secondNode); + osg::Vec3f toSecondNodeVec3f = secondNodeVec3f - firstNodeVec3f; + osg::Vec3f toStartPointVec3f = startPointInLocalCoords - firstNodeVec3f; + if (toSecondNodeVec3f * toStartPointVec3f > 0) + { + ESM::Pathgrid::Point temp(secondNode); + converter.toWorld(temp); + // Add Z offset since path node can overlap with other objects. + // Also ignore doors in raytesting. + bool isPathClear = !MWBase::Environment::get().getWorld()->castRay( + startPoint.mX, startPoint.mY, startPoint.mZ+16, temp.mX, temp.mY, temp.mZ+16, true); + if (isPathClear) + mPath.pop_front(); + } + } + // convert supplied path to world coordinates for (std::list::iterator iter(mPath.begin()); iter != mPath.end(); ++iter) { diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index efa3cf58b..060db70f9 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -332,14 +332,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) { } @@ -437,9 +437,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 @@ -595,7 +595,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) { @@ -983,7 +983,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); @@ -1173,7 +1173,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 5e69b4696..72f6a1f4a 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 5a0bd9c15..26c784c4d 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; - - // There is no need to apply bonus if weapon rating == 0 - if (rating == 0.f) - return 0.f; + { + int value = actor.getClass().getSkill(actor, skill); + rating *= MWMechanics::getHitChance(actor, enemy, value) / 100.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/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 6dafea3ad..a0ddbfad9 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -626,7 +626,6 @@ namespace MWPhysics assert (mShapeInstance->getCollisionShape()->isCompound()); btCompoundShape* compound = static_cast(mShapeInstance->getCollisionShape()); - for (std::map::const_iterator it = mShapeInstance->mAnimatedShapes.begin(); it != mShapeInstance->mAnimatedShapes.end(); ++it) { int recIndex = it->first; @@ -640,6 +639,9 @@ namespace MWPhysics if (!visitor.mFound) { std::cerr << "Error: animateCollisionShapes can't find node " << recIndex << " for " << mPtr.getCellRef().getRefId() << std::endl; + + // Remove nonexistent nodes from animated shapes map and early out + mShapeInstance->mAnimatedShapes.erase(recIndex); return; } osg::NodePath nodePath = visitor.mFoundPath; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 3dbf6475c..03863000f 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -203,6 +203,7 @@ namespace MWRender , mNightEyeFactor(0.f) , mDistantFog(false) , mDistantTerrain(false) + , mFieldOfViewOverridden(false) , mFieldOfViewOverride(0.f) , mBorders(false) { @@ -553,8 +554,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 +585,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 +690,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(" ")); @@ -717,7 +713,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 2e4329f69..24769019b 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/mwscript/dialogueextensions.cpp b/apps/openmw/mwscript/dialogueextensions.cpp index 1e488467b..24da3136b 100644 --- a/apps/openmw/mwscript/dialogueextensions.cpp +++ b/apps/openmw/mwscript/dialogueextensions.cpp @@ -1,3 +1,5 @@ +#include + #include "dialogueextensions.hpp" /* @@ -181,6 +183,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; + } + /* Start of tes3mp change (major) diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index c3618aa9e..32b25ba69 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -28,6 +28,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/dialoguemanager.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/statemanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" @@ -1260,7 +1261,11 @@ namespace MWScript MWWorld::Ptr ptr = R()(runtime); if (ptr == MWMechanics::getPlayer()) + { ptr.getClass().getCreatureStats(ptr).resurrect(); + if (MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_Ended) + MWBase::Environment::get().getStateManager()->resumeGame(); + } else if (ptr.getClass().getCreatureStats(ptr).isDead()) { bool wasEnabled = ptr.getRefData().isEnabled(); diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 0dd95f773..8c71ab61b 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/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index c1bb589e8..234925839 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -173,6 +173,11 @@ void MWState::StateManager::endGame() mState = State_Ended; } +void MWState::StateManager::resumeGame() +{ + mState = State_Running; +} + void MWState::StateManager::saveGame (const std::string& description, const Slot *slot) { MWState::Character* character = getCurrentCharacter(); diff --git a/apps/openmw/mwstate/statemanagerimp.hpp b/apps/openmw/mwstate/statemanagerimp.hpp index 5defcdea3..d71fae850 100644 --- a/apps/openmw/mwstate/statemanagerimp.hpp +++ b/apps/openmw/mwstate/statemanagerimp.hpp @@ -48,6 +48,8 @@ namespace MWState virtual void endGame(); + virtual void resumeGame(); + virtual void deleteGame (const MWState::Character *character, const MWState::Slot *slot); ///< Delete a saved game slot from this character. If all save slots are deleted, the character will be deleted too. diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 2a6e5dda2..2b7830a4e 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1373,9 +1373,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) { @@ -1708,12 +1708,16 @@ namespace MWWorld moveObjectImp(player->first, player->second.x(), player->second.y(), player->second.z(), false); } - bool World::castRay (float x1, float y1, float z1, float x2, float y2, float z2) + bool World::castRay (float x1, float y1, float z1, float x2, float y2, float z2, bool ignoreDoors) { osg::Vec3f a(x1,y1,z1); osg::Vec3f b(x2,y2,z2); - MWPhysics::PhysicsSystem::RayResult result = mPhysics->castRay(a, b, MWWorld::Ptr(), std::vector(), MWPhysics::CollisionType_World|MWPhysics::CollisionType_Door); + int mask = MWPhysics::CollisionType_World; + if (!ignoreDoors) + mask |= MWPhysics::CollisionType_Door; + + MWPhysics::PhysicsSystem::RayResult result = mPhysics->castRay(a, b, MWWorld::Ptr(), std::vector(), mask); return result.mHit; } @@ -3164,7 +3168,7 @@ namespace MWWorld // 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 != MWMechanics::getPlayer() && !manualSpell) - actor.getClass().getCreatureStats(actor).getAiSequence().getCombatTargets(targetActors); + stats.getAiSequence().getCombatTargets(targetActors); const float fCombatDistance = getStore().get().find("fCombatDistance")->getFloat(); @@ -3187,7 +3191,6 @@ namespace MWWorld // Actors that are targeted by this actor's Follow or Escort packages also side with them if (actor != MWMechanics::getPlayer()) { - const MWMechanics::CreatureStats &stats = actor.getClass().getCreatureStats(actor); for (std::list::const_iterator it = stats.getAiSequence().begin(); it != stats.getAiSequence().end(); ++it) { if ((*it)->getTypeId() == MWMechanics::AiPackage::TypeIdCast) diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 6a455d049..f98b3473b 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -535,7 +535,7 @@ namespace MWWorld End of tes3mp addition */ - bool castRay (float x1, float y1, float z1, float x2, float y2, float z2) override; + bool castRay (float x1, float y1, float z1, float x2, float y2, float z2, bool ignoreDoors=false) override; ///< cast a Ray and return true if there is an object in the ray path. bool toggleCollisionMode() override; diff --git a/apps/openmw_test_suite/esm/test_fixed_string.cpp b/apps/openmw_test_suite/esm/test_fixed_string.cpp index 598e8daad..89a390ff6 100644 --- a/apps/openmw_test_suite/esm/test_fixed_string.cpp +++ b/apps/openmw_test_suite/esm/test_fixed_string.cpp @@ -64,7 +64,7 @@ TEST(EsmFixedString, struct_size) ASSERT_EQ(256, sizeof(ESM::NAME256)); } -TEST(EsmFixedString, DISABLED_is_pod) +TEST(EsmFixedString, is_pod) { ASSERT_TRUE(std::is_pod::value); ASSERT_TRUE(std::is_pod::value); diff --git a/apps/openmw_test_suite/misc/test_stringops.cpp b/apps/openmw_test_suite/misc/test_stringops.cpp index 53a33dbf4..081ca8da6 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 97eaa0e26..9877d2105 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 c022098dd..9c916ca7d 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -87,7 +87,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 e5377b64f..f7b8717a6 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 ff3123c5b..0f5b17502 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 000000000..f9c070b74 --- /dev/null +++ b/components/misc/debugging.hpp @@ -0,0 +1,128 @@ +#ifndef MISC_DEBUGGING_H +#define MISC_DEBUGGING_H + +#include +#include + +/* + Start of tes3mp addition + + Include additional headers for multiplayer purposes +*/ +#include +/* + End of tes3mp addition +*/ + +#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 + logfile.open (boost::filesystem::path(cfgMgr.getLogPath() / logName)); + + coutsb.open (Misc::Tee(logfile, oldcout)); + cerrsb.open (Misc::Tee(logfile, oldcerr)); + + std::cout.rdbuf (&coutsb); + std::cerr.rdbuf (&cerrsb); + + /* + Start of tes3mp addition + + Initialize the logger added for multiplayer + */ + LOG_INIT(Log::LOG_INFO); + /* + End of tes3mp addition + */ +#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 0fde1c96c..79fa36d1e 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/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index be5a7d9d6..6f8c8f2c0 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -94,13 +94,16 @@ osg::ref_ptr BulletNifLoader::load(const Nif::NIFFilePtr& } 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()); - 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(nif->getFilename(), rootCollisionNode, 0, false, isAnimated, false); + else + handleNode(nif->getFilename(), node, 0, true, isAnimated, true); if (mCompoundShape) { @@ -153,25 +156,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 +196,9 @@ void BulletNifLoader::handleNode(const Nif::Node *node, int flags, isCollisionNode = isCollisionNode || (node->recType == Nif::RC_RootCollisionNode); + if (node->recType == Nif::RC_RootCollisionNode && autogenerated) + std::cerr << "Found RootCollisionNode attached to non-root node in " << fileName << ". Treat it as a common NiTriShape." << std::endl; + // Don't collide with AvoidNode shapes if(node->recType == Nif::RC_AvoidNode) flags |= 0x800; @@ -212,7 +227,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 +249,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); } } } diff --git a/components/nifbullet/bulletnifloader.hpp b/components/nifbullet/bulletnifloader.hpp index fff51933f..8fd9cc2a1 100644 --- a/components/nifbullet/bulletnifloader.hpp +++ b/components/nifbullet/bulletnifloader.hpp @@ -55,9 +55,9 @@ public: 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); diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index aada21fce..4e7f6d511 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -547,6 +547,11 @@ namespace NifOsg node->setDataVariance(osg::Object::DYNAMIC); } + if (nifNode->recType == Nif::RC_NiTriShape && isAnimated) // the same thing for animated NiTriShapes + { + node->setDataVariance(osg::Object::DYNAMIC); + } + osg::ref_ptr composite = new SceneUtil::CompositeStateSetUpdater; applyNodeProperties(nifNode, node, composite, imageManager, boundTextures, animflags); @@ -574,13 +579,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); diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 03b850fac..db8d99ab4 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; i>= 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/launcher/images/clear.png b/files/launcher/images/clear.png index 0e72a65ea..74684bfb5 100644 Binary files a/files/launcher/images/clear.png and b/files/launcher/images/clear.png differ diff --git a/files/launcher/images/down.png b/files/launcher/images/down.png index f529cda7d..9219cc6ed 100644 Binary files a/files/launcher/images/down.png and b/files/launcher/images/down.png differ diff --git a/files/launcher/images/openmw-header.png b/files/launcher/images/openmw-header.png index 82223e7fa..3a82e471f 100644 Binary files a/files/launcher/images/openmw-header.png and b/files/launcher/images/openmw-header.png differ diff --git a/files/launcher/images/openmw-plugin.png b/files/launcher/images/openmw-plugin.png index c34cba554..75ad4be0b 100644 Binary files a/files/launcher/images/openmw-plugin.png and b/files/launcher/images/openmw-plugin.png differ diff --git a/files/launcher/images/openmw.png b/files/launcher/images/openmw.png index 7a5393821..83ace6708 100644 Binary files a/files/launcher/images/openmw.png and b/files/launcher/images/openmw.png differ diff --git a/files/launcher/images/playpage-background.png b/files/launcher/images/playpage-background.png index ccd36d029..b733ecf80 100644 Binary files a/files/launcher/images/playpage-background.png and b/files/launcher/images/playpage-background.png differ diff --git a/files/launcher/images/preferences-advanced.png b/files/launcher/images/preferences-advanced.png new file mode 100644 index 000000000..8da557d97 Binary files /dev/null and b/files/launcher/images/preferences-advanced.png differ diff --git a/files/launcher/images/preferences-video.png b/files/launcher/images/preferences-video.png new file mode 100644 index 000000000..fdcf9c077 Binary files /dev/null and b/files/launcher/images/preferences-video.png differ diff --git a/files/launcher/images/preferences.png b/files/launcher/images/preferences.png new file mode 100644 index 000000000..e6ed15e2c Binary files /dev/null and b/files/launcher/images/preferences.png differ diff --git a/files/launcher/launcher.qrc b/files/launcher/launcher.qrc index ddcc26e59..17c38a838 100644 --- a/files/launcher/launcher.qrc +++ b/files/launcher/launcher.qrc @@ -5,6 +5,9 @@ images/openmw.png images/openmw-plugin.png images/openmw-header.png + images/preferences.png + images/preferences-advanced.png + images/preferences-video.png images/playpage-background.png diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 1390bf8c6..b0ddd5223 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/shaders/water_fragment.glsl b/files/shaders/water_fragment.glsl index 75113755e..ee91df75f 100644 --- a/files/shaders/water_fragment.glsl +++ b/files/shaders/water_fragment.glsl @@ -194,9 +194,9 @@ void main(void) float bump = mix(BUMP,BUMP_RAIN,rainIntensity); vec3 normal = (normal0 * bigWaves.x + normal1 * bigWaves.y + - normal2 * midWaves.x + normal3 * midWaves.y + - normal4 * smallWaves.x + normal5 * smallWaves.y + - rippleAdd); + normal2 * midWaves.x + normal3 * midWaves.y + + normal4 * smallWaves.x + normal5 * smallWaves.y + + rippleAdd); normal = normalize(vec3(normal.x * bump, normal.y * bump, normal.z)); @@ -204,9 +204,9 @@ void main(void) // normal for sunlight scattering vec3 lNormal = (normal0 * bigWaves.x * 0.5 + normal1 * bigWaves.y * 0.5 + - normal2 * midWaves.x * 0.2 + normal3 * midWaves.y * 0.2 + - normal4 * smallWaves.x * 0.1 + normal5 * smallWaves.y * 0.1 + - rippleAdd).xyz; + normal2 * midWaves.x * 0.2 + normal3 * midWaves.y * 0.2 + + normal4 * smallWaves.x * 0.1 + normal5 * smallWaves.y * 0.1 + + rippleAdd).xyz; lNormal = normalize(vec3(lNormal.x * bump, lNormal.y * bump, lNormal.z)); lNormal = vec3(-lNormal.x, -lNormal.y, lNormal.z); diff --git a/files/ui/advancedpage.ui b/files/ui/advancedpage.ui index 0e43654f2..553c244d3 100644 --- a/files/ui/advancedpage.ui +++ b/files/ui/advancedpage.ui @@ -21,8 +21,8 @@ 0 0 - 630 - 791 + 641 + 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,13 +65,43 @@ - <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 + + + @@ -137,7 +167,7 @@ - -1 + 6 0 @@ -312,7 +342,7 @@ - -1 + 6 0 @@ -379,7 +409,7 @@ - -1 + 6 0