diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a510122c..52436a11a 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 @@ -74,9 +75,11 @@ 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 Feature #2606: Editor: Implemented (optional) case sensitive global search Feature #3083: Play animation when NPC is casting spell via script + 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 diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index 3e0314607..ff229e02d 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/openmw/mwbase/dialoguemanager.hpp b/apps/openmw/mwbase/dialoguemanager.hpp index 7bace3790..2ecab1c4c 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 fb492ff3b..df214ce86 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 f267f7542..29a90082c 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 e8ac33f6d..b367c6f49 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/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index 0a9fe1b4a..e11147c74 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/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/character.cpp b/apps/openmw/mwmechanics/character.cpp index f843be66e..f5d6f8584 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)); + // 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); } } } diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 0741bedeb..09dadcaba 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 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/docs/source/reference/modding/settings/game.rst b/docs/source/reference/modding/settings/game.rst index 308a29546..b733cc7c4 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -130,7 +130,7 @@ enchanted weapons are magical :Range: True/False :Default: True -Makes enchanted weapons without Magical flag bypass normal weapons resistance (and weakness) certain creatures have. +Make enchanted weapons without Magical flag bypass normal weapons resistance (and weakness) certain creatures have. This is how original Morrowind behaves. This setting can only be configured by editing the settings configuration file. @@ -142,7 +142,7 @@ prevent merchant equipping :Range: True/False :Default: False -Prevents merchants from equipping items that are sold to them. +Prevent merchants from equipping items that are sold to them. This setting can only be configured by editing the settings configuration file. @@ -153,7 +153,7 @@ followers attack on sight :Range: True/False :Default: False -Makes player followers and escorters start combat with enemies who have started combat with them or the player. +Make player followers and escorters start combat with enemies who have started combat with them or the player. Otherwise they wait for the enemies or the player to do an attack first. Please note this setting has not been extensively tested and could have side effects with certain quests. @@ -171,3 +171,15 @@ For example, if the main animation mesh has name Meshes/x.nif, an engine will lo Can be useful if you want to use several animation replacers without merging them. Attention: animations from AnimKit have own format and are not supposed to be directly loaded in-game! This setting can only be configured by editing the settings configuration file. + +barter disposition change is permanent +-------------------------------------- + +:Type: boolean +:Range: True/False +:Default: False + +If this setting is true, disposition change of merchants caused by trading will be permanent and won't be discarded upon exiting dialogue with them. +This imitates the option Morrowind Code Patch offers. + +This setting can be toggled with a checkbox in Advanced tab of the launcher. diff --git a/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/ui/advancedpage.ui b/files/ui/advancedpage.ui index 486d410df..553c244d3 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