From 579f35511afebba2ea3b36c140f793b7efebf6d3 Mon Sep 17 00:00:00 2001 From: James Carty Date: Sun, 12 Aug 2018 22:45:03 +0100 Subject: [PATCH 01/24] Add support for scietific notation flag for MessageBox --- components/compiler/lineparser.cpp | 2 +- components/compiler/lineparser.hpp | 2 +- components/interpreter/miscopcodes.hpp | 8 ++++++-- components/misc/messageformatparser.cpp | 10 ++++++---- components/misc/messageformatparser.hpp | 8 +++++++- 5 files changed, 21 insertions(+), 9 deletions(-) diff --git a/components/compiler/lineparser.cpp b/components/compiler/lineparser.cpp index 2d551348d..602bb826f 100644 --- a/components/compiler/lineparser.cpp +++ b/components/compiler/lineparser.cpp @@ -557,7 +557,7 @@ namespace Compiler mExplicit.clear(); } - void GetArgumentsFromMessageFormat::visitedPlaceholder(Placeholder placeholder, char /*padding*/, int /*width*/, int /*precision*/) + void GetArgumentsFromMessageFormat::visitedPlaceholder(Placeholder placeholder, char /*padding*/, int /*width*/, int /*precision*/, Notation /*notation*/) { switch (placeholder) { diff --git a/components/compiler/lineparser.hpp b/components/compiler/lineparser.hpp index d92c4895e..8f7f64bf2 100644 --- a/components/compiler/lineparser.hpp +++ b/components/compiler/lineparser.hpp @@ -83,7 +83,7 @@ namespace Compiler std::string mArguments; protected: - virtual void visitedPlaceholder(Placeholder placeholder, char padding, int width, int precision); + virtual void visitedPlaceholder(Placeholder placeholder, char padding, int width, int precision, Notation notation); virtual void visitedCharacter(char c) {} public: diff --git a/components/interpreter/miscopcodes.hpp b/components/interpreter/miscopcodes.hpp index 20a1b52be..8bca0aec1 100644 --- a/components/interpreter/miscopcodes.hpp +++ b/components/interpreter/miscopcodes.hpp @@ -24,7 +24,7 @@ namespace Interpreter Runtime& mRuntime; protected: - virtual void visitedPlaceholder(Placeholder placeholder, char padding, int width, int precision) + virtual void visitedPlaceholder(Placeholder placeholder, char padding, int width, int precision, Notation notation) { std::ostringstream out; out.fill(padding); @@ -58,7 +58,11 @@ namespace Interpreter float value = mRuntime[0].mFloat; mRuntime.pop(); - out << std::fixed << value; + if (notation == FixedNotation) + out << std::fixed << value; + else + out << std::scientific << value; + mFormattedMessage += out.str(); } break; diff --git a/components/misc/messageformatparser.cpp b/components/misc/messageformatparser.cpp index 3a35c83ea..708d9565d 100644 --- a/components/misc/messageformatparser.cpp +++ b/components/misc/messageformatparser.cpp @@ -49,11 +49,13 @@ namespace Misc width = (widthSet) ? width : -1; if (m[i] == 'S' || m[i] == 's') - visitedPlaceholder(StringPlaceholder, pad, width, precision); - else if (m[i] == 'g' || m[i] == 'G') - visitedPlaceholder(IntegerPlaceholder, pad, width, precision); + visitedPlaceholder(StringPlaceholder, pad, width, precision, FixedNotation); + else if (m[i] == 'd' || m[i] == 'i') + visitedPlaceholder(IntegerPlaceholder, pad, width, precision, FixedNotation); else if (m[i] == 'f' || m[i] == 'F') - visitedPlaceholder(FloatPlaceholder, pad, width, precision); + visitedPlaceholder(FloatPlaceholder, pad, width, precision, FixedNotation); + else if (m[i] == 'e' || m[i] == 'E') + visitedPlaceholder(FloatPlaceholder, pad, width, precision, ScientificNotation); } } } diff --git a/components/misc/messageformatparser.hpp b/components/misc/messageformatparser.hpp index c12b9352a..10da624d4 100644 --- a/components/misc/messageformatparser.hpp +++ b/components/misc/messageformatparser.hpp @@ -15,7 +15,13 @@ namespace Misc FloatPlaceholder }; - virtual void visitedPlaceholder(Placeholder placeholder, char padding, int width, int precision) = 0; + enum Notation + { + FixedNotation, + ScientificNotation + }; + + virtual void visitedPlaceholder(Placeholder placeholder, char padding, int width, int precision, Notation notation) = 0; virtual void visitedCharacter(char c) = 0; public: From 1c4969805350c0c7f4f7657e2584ec3f38107970 Mon Sep 17 00:00:00 2001 From: James Carty Date: Mon, 13 Aug 2018 20:31:11 +0100 Subject: [PATCH 02/24] Implement 'g' flag --- components/interpreter/miscopcodes.hpp | 26 +++++++++++++++++++++++-- components/misc/messageformatparser.cpp | 2 ++ components/misc/messageformatparser.hpp | 3 ++- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/components/interpreter/miscopcodes.hpp b/components/interpreter/miscopcodes.hpp index 8bca0aec1..03b7e186f 100644 --- a/components/interpreter/miscopcodes.hpp +++ b/components/interpreter/miscopcodes.hpp @@ -59,11 +59,33 @@ namespace Interpreter mRuntime.pop(); if (notation == FixedNotation) + { out << std::fixed << value; - else + mFormattedMessage += out.str(); + } + else if (notation == ShortestNotation) + { + std::string scientific; + std::string fixed; + out << std::scientific << value; - mFormattedMessage += out.str(); + scientific = out.str(); + + out.str(std::string()); + out.clear(); + + out << std::fixed << value; + + fixed = out.str(); + + mFormattedMessage += fixed.length() < scientific.length() ? fixed : scientific; + } + else + { + out << std::scientific << value; + mFormattedMessage += out.str(); + } } break; default: diff --git a/components/misc/messageformatparser.cpp b/components/misc/messageformatparser.cpp index 708d9565d..6f0e47132 100644 --- a/components/misc/messageformatparser.cpp +++ b/components/misc/messageformatparser.cpp @@ -56,6 +56,8 @@ namespace Misc visitedPlaceholder(FloatPlaceholder, pad, width, precision, FixedNotation); else if (m[i] == 'e' || m[i] == 'E') visitedPlaceholder(FloatPlaceholder, pad, width, precision, ScientificNotation); + else if (m[i] == 'g' || m[i] == 'G') + visitedPlaceholder(FloatPlaceholder, pad, width, precision, ShortestNotation); } } } diff --git a/components/misc/messageformatparser.hpp b/components/misc/messageformatparser.hpp index 10da624d4..db2a8b0af 100644 --- a/components/misc/messageformatparser.hpp +++ b/components/misc/messageformatparser.hpp @@ -18,7 +18,8 @@ namespace Misc enum Notation { FixedNotation, - ScientificNotation + ScientificNotation, + ShortestNotation }; virtual void visitedPlaceholder(Placeholder placeholder, char padding, int width, int precision, Notation notation) = 0; From 5d392d3ef5c02fabfaa079deed8de484b4d20a04 Mon Sep 17 00:00:00 2001 From: Diego Crespo Date: Tue, 14 Aug 2018 17:20:19 -0400 Subject: [PATCH 03/24] fixed some spelling mistakes in .rst files --- docs/source/manuals/openmw-cs/index.rst | 2 +- docs/source/manuals/openmw-cs/record-filters.rst | 2 +- docs/source/manuals/openmw-cs/tour.rst | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/source/manuals/openmw-cs/index.rst b/docs/source/manuals/openmw-cs/index.rst index f124b526f..ee8290711 100644 --- a/docs/source/manuals/openmw-cs/index.rst +++ b/docs/source/manuals/openmw-cs/index.rst @@ -9,7 +9,7 @@ few chapters to familiarise yourself with the new interface. .. warning:: OpenMW CS is still software in development. The manual does not cover any of - its shortcomings, it is written as if everything was working as inteded. + its shortcomings, it is written as if everything was working as intended. Please report any software problems as bugs in the software, not errors in the manual. diff --git a/docs/source/manuals/openmw-cs/record-filters.rst b/docs/source/manuals/openmw-cs/record-filters.rst index a579d8dd8..19eee2542 100644 --- a/docs/source/manuals/openmw-cs/record-filters.rst +++ b/docs/source/manuals/openmw-cs/record-filters.rst @@ -121,7 +121,7 @@ existing filters into more complex ones. Scopes ====== -Every default filter has the prefix ``project``. This is a *scpoe*, a mechanism +Every default filter has the prefix ``project``. This is a *scope*, a mechanism that determines the lifetime of the filter. These are the supported scopes: ``project::`` diff --git a/docs/source/manuals/openmw-cs/tour.rst b/docs/source/manuals/openmw-cs/tour.rst index 5d7034deb..fbd4fd047 100644 --- a/docs/source/manuals/openmw-cs/tour.rst +++ b/docs/source/manuals/openmw-cs/tour.rst @@ -236,7 +236,7 @@ a negative number indicating that he will restock again to maintain that level. However, it's an attractive item, so he will probably wear it rather than sell it. So set his stock level too high for him to wear them all (3 works, 2 might do). -Another possibilty, again in Seyda Neen making it easy to access, would be for +Another possibility, again in Seyda Neen making it easy to access, would be for Fargoth to give it to the player in exchange for his healing ring. .. figure:: https://gitlab.com/OpenMW/openmw-docs/raw/master/docs/source/manuals/openmw-cs/_static/images/chapter-1/Ring_to_Fargoth_1.png @@ -360,7 +360,7 @@ the base game. "Modified" status will cover items from the base game which have been modified in this addon. Click on the top of the column to toggle between ascending and descending order - thus between "Added" -and "Modified" at the top. Or put your desired modified status into a filter then sort alpabetically +and "Modified" at the top. Or put your desired modified status into a filter then sort alphabetically on a different column. From 68894320301d1027e55ae3d87a5817197f3024c3 Mon Sep 17 00:00:00 2001 From: James Carty Date: Thu, 16 Aug 2018 00:26:02 +0100 Subject: [PATCH 04/24] Move code to seperate functions for reusability --- apps/openmw/mwmechanics/aisequence.cpp | 15 +++++++++++ apps/openmw/mwmechanics/aisequence.hpp | 3 +++ .../mwmechanics/mechanicsmanagerimp.cpp | 27 ++++++++----------- .../mwmechanics/mechanicsmanagerimp.hpp | 2 ++ 4 files changed, 31 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 085174820..d55ba13dd 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -19,6 +19,7 @@ #include "aicombataction.hpp" #include "aipursue.hpp" #include "actorutil.hpp" +#include "../mwworld/class.hpp" namespace MWMechanics { @@ -122,6 +123,20 @@ bool AiSequence::isInCombat() const return false; } +bool AiSequence::isEngagedWithActor() const +{ + bool isFightingNpc = false; + for (std::list::const_iterator it = mPackages.begin(); it != mPackages.end(); ++it) + { + if ((*it)->getTypeId() == AiPackage::TypeIdCombat) + { + MWWorld::Ptr target2 = (*it)->getTarget(); + if (!target2.isEmpty() && target2.getClass().isNpc()) + isFightingNpc = true; + } + } +} + bool AiSequence::hasPackage(int typeId) const { for (std::list::const_iterator it = mPackages.begin(); it != mPackages.end(); ++it) diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index 5c72bcc4c..4d0482a98 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -88,6 +88,9 @@ namespace MWMechanics /// Is there any combat package? bool isInCombat () const; + /// Are we in combat with any other actor, who's also engaging us? + bool isEngagedWithActor () const; + /// Does this AI sequence have the given package type? bool hasPackage(int typeId) const; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 7814b4a91..6ef358790 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1460,24 +1460,11 @@ namespace MWMechanics } } - // Attacking an NPC that is already in combat with any other NPC is not a crime - AiSequence& seq = statsTarget.getAiSequence(); - bool isFightingNpc = false; - for (std::list::const_iterator it = seq.begin(); it != seq.end(); ++it) - { - if ((*it)->getTypeId() == AiPackage::TypeIdCombat) - { - MWWorld::Ptr target2 = (*it)->getTarget(); - if (!target2.isEmpty() && target2.getClass().isNpc()) - isFightingNpc = true; - } - } - - if (target.getClass().isNpc() && !attacker.isEmpty() && !seq.isInCombat(attacker) - && !isAggressive(target, attacker) && !isFightingNpc - && !target.getClass().getCreatureStats(target).getAiSequence().hasPackage(AiPackage::TypeIdPursue)) + if (canCommitCrimeAgainst(target, attacker)) commitCrime(attacker, target, MWBase::MechanicsManager::OT_Assault); + AiSequence& seq = statsTarget.getAiSequence(); + if (!attacker.isEmpty() && (attacker.getClass().getCreatureStats(attacker).getAiSequence().isInCombat(target) || attacker == getPlayer()) && !seq.isInCombat(attacker)) @@ -1504,6 +1491,14 @@ namespace MWMechanics return true; } + bool MechanicsManager::canCommitCrimeAgainst(const MWWorld::Ptr &target, const MWWorld::Ptr &attacker) + { + MWMechanics::AiSequence seq = target.getClass().getCreatureStats(target).getAiSequence(); + return target.getClass().isNpc() && !attacker.isEmpty() && !seq.isInCombat(attacker) + && !isAggressive(target, attacker) && !seq.isEngagedWithActor() + && !target.getClass().getCreatureStats(target).getAiSequence().hasPackage(AiPackage::TypeIdPursue); + } + void MechanicsManager::actorKilled(const MWWorld::Ptr &victim, const MWWorld::Ptr &attacker) { if (attacker.isEmpty() || victim.isEmpty()) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 676a75caf..85d2e65cf 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -132,6 +132,8 @@ namespace MWMechanics /// @note No-op for non-player attackers virtual void actorKilled (const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker); + virtual bool canCommitCrimeAgainst(const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker); + /// Utility to check if taking this item is illegal and calling commitCrime if so /// @param container The container the item is in; may be empty for an item in the world virtual void itemTaken (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, const MWWorld::Ptr& container, From 4b0a6074e7b7024aa54a6bd026a88203093af46a Mon Sep 17 00:00:00 2001 From: James Carty Date: Thu, 16 Aug 2018 00:29:14 +0100 Subject: [PATCH 05/24] Add comment --- apps/openmw/mwmechanics/mechanicsmanagerimp.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 85d2e65cf..8c8a9e68f 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -132,6 +132,10 @@ namespace MWMechanics /// @note No-op for non-player attackers virtual void actorKilled (const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker); + /// Checks if commiting a crime is currently valid + /// @param victim The actor being attacked + /// @param attacker The actor commiting the crime + /// @return true if the victim is a valid target for crime virtual bool canCommitCrimeAgainst(const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker); /// Utility to check if taking this item is illegal and calling commitCrime if so From 8a48258b1bd407bcb381e0853163f7c87594fe88 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 15 Aug 2018 14:16:19 +0400 Subject: [PATCH 06/24] Fix attack after shoot --- apps/openmw/mwmechanics/character.cpp | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 6f9cb941d..594a8c9d0 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1385,14 +1385,6 @@ bool CharacterController::updateWeaponState() MWBase::Environment::get().getWorld()->breakInvisibility(mPtr); mAttackStrength = 0; - // Randomize attacks for non-bipedal creatures with Weapon flag - if (mPtr.getClass().getTypeName() == typeid(ESM::Creature).name() && - !mPtr.getClass().isBipedal(mPtr) && - (!mAnimation->hasAnimation(mCurrentWeapon) || isRandomAttackAnimation(mCurrentWeapon))) - { - mCurrentWeapon = chooseRandomAttackAnimation(); - } - if(mWeaponType == WeapType_Spell) { // Unset casting flag, otherwise pressing the mouse button down would @@ -1401,15 +1393,10 @@ bool CharacterController::updateWeaponState() if (mPtr == player) { MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false); - } - - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - // For the player, set the spell we want to cast - // This has to be done at the start of the casting animation, - // *not* when selecting a spell in the GUI (otherwise you could change the spell mid-animation) - if (mPtr == player) - { + // For the player, set the spell we want to cast + // This has to be done at the start of the casting animation, + // *not* when selecting a spell in the GUI (otherwise you could change the spell mid-animation) std::string selectedSpell = MWBase::Environment::get().getWindowManager()->getSelectedSpell(); stats.getSpells().setSelectedSpell(selectedSpell); } @@ -1421,6 +1408,7 @@ bool CharacterController::updateWeaponState() MWMechanics::CastSpell cast(mPtr, NULL, false, mCastingManualSpell); cast.playSpellCastingEffects(spellid); + const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Spell *spell = store.get().find(spellid); const ESM::ENAMstruct &lastEffect = spell->mEffects.mList.back(); const ESM::MagicEffect *effect; @@ -1520,13 +1508,12 @@ bool CharacterController::updateWeaponState() startKey = mAttackType+" start"; stopKey = mAttackType+" min attack"; } - - if (isRandomAttackAnimation(mCurrentWeapon)) + else if (isRandomAttackAnimation(mCurrentWeapon)) { startKey = "start"; stopKey = "stop"; } - else if (mAttackType != "shoot") + else { if(mPtr == getPlayer()) { From e444b9581c57884e604a2e882bb79b3e07921f06 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 15 Aug 2018 17:33:27 +0400 Subject: [PATCH 07/24] Do not play min attack -> max attack animation when attack strength is 0 (bug #4591) --- apps/openmw/mwmechanics/character.cpp | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 594a8c9d0..1748e3d45 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1670,11 +1670,6 @@ bool CharacterController::updateWeaponState() std::string start, stop; switch(mUpperBodyState) { - case UpperCharState_StartToMinAttack: - start = mAttackType+" min attack"; - stop = mAttackType+" max attack"; - mUpperBodyState = UpperCharState_MinAttackToMaxAttack; - break; case UpperCharState_MinAttackToMaxAttack: //hack to avoid body pos desync when jumping/sneaking in 'max attack' state if(!mAnimation->isPlaying(mCurrentWeapon)) @@ -1682,6 +1677,23 @@ bool CharacterController::updateWeaponState() MWRender::Animation::BlendMask_All, false, 0, mAttackType+" min attack", mAttackType+" max attack", 0.999f, 0); break; + case UpperCharState_StartToMinAttack: + { + // If actor is already stopped prepairing attack, do not play the "min attack -> max attack" part. + // Happens if the player did not hold the attack button. + // Note: if the "min attack"->"max attack" is a stub, "play" it anyway. Attack strength will be 1. + float minAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon+": "+mAttackType+" "+"min attack"); + float maxAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon+": "+mAttackType+" "+"max attack"); + if (mAttackingOrSpell || minAttackTime == maxAttackTime) + { + start = mAttackType+" min attack"; + stop = mAttackType+" max attack"; + mUpperBodyState = UpperCharState_MinAttackToMaxAttack; + break; + } + playSwishSound(0.0f); + } + // Fall-through case UpperCharState_MaxAttackToMinHit: if(mAttackType == "shoot") { From 15fa47827b5e2c053c1b4830cc31a58fafb5818f Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 15 Aug 2018 19:05:57 +0400 Subject: [PATCH 08/24] AiCombat: Avoid jittering when aiming in melee --- apps/openmw/mwmechanics/aicombat.cpp | 27 +++++++++++++++++++-------- apps/openmw/mwmechanics/aicombat.hpp | 2 +- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index cd9709885..438f4f7c6 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -372,21 +372,32 @@ namespace MWMechanics actorMovementSettings.mPosition[1] = storage.mMovement.mPosition[1]; actorMovementSettings.mPosition[2] = storage.mMovement.mPosition[2]; - rotateActorOnAxis(actor, 2, actorMovementSettings, storage.mMovement); - rotateActorOnAxis(actor, 0, actorMovementSettings, storage.mMovement); + rotateActorOnAxis(actor, 2, actorMovementSettings, storage); + rotateActorOnAxis(actor, 0, actorMovementSettings, storage); } void AiCombat::rotateActorOnAxis(const MWWorld::Ptr& actor, int axis, - MWMechanics::Movement& actorMovementSettings, MWMechanics::Movement& desiredMovement) + MWMechanics::Movement& actorMovementSettings, AiCombatStorage& storage) { actorMovementSettings.mRotation[axis] = 0; - float& targetAngleRadians = desiredMovement.mRotation[axis]; + float& targetAngleRadians = storage.mMovement.mRotation[axis]; if (targetAngleRadians != 0) { - if (smoothTurn(actor, targetAngleRadians, axis)) + // Some attack animations contain small amount of movement. + // Since we use cone shapes for melee, we can use a threshold to avoid jittering + std::shared_ptr& currentAction = storage.mCurrentAction; + bool isRangedCombat = false; + currentAction->getCombatRange(isRangedCombat); + // Check if the actor now facing desired direction, no need to turn any more + if (isRangedCombat) + { + if (smoothTurn(actor, targetAngleRadians, axis)) + targetAngleRadians = 0; + } + else { - // actor now facing desired direction, no need to turn any more - targetAngleRadians = 0; + if (smoothTurn(actor, targetAngleRadians, axis, osg::DegreesToRadians(3.f))) + targetAngleRadians = 0; } } } @@ -453,7 +464,7 @@ namespace MWMechanics if (distToTarget <= rangeAttackOfTarget && Misc::Rng::rollClosedProbability() < 0.25) { mMovement.mPosition[0] = Misc::Rng::rollProbability() < 0.5 ? 1.0f : -1.0f; // to the left/right - mTimerCombatMove = 0.05f + 0.15f * Misc::Rng::rollClosedProbability(); + mTimerCombatMove = 0.1f + 0.1f * Misc::Rng::rollClosedProbability(); mCombatMove = true; } } diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 7c9891bcc..88feba481 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -129,7 +129,7 @@ namespace MWMechanics /// Transfer desired movement (from AiCombatStorage) to Actor void updateActorsMovement(const MWWorld::Ptr& actor, float duration, AiCombatStorage& storage); void rotateActorOnAxis(const MWWorld::Ptr& actor, int axis, - MWMechanics::Movement& actorMovementSettings, MWMechanics::Movement& desiredMovement); + MWMechanics::Movement& actorMovementSettings, AiCombatStorage& storage); }; From 3a6c480d414ed51bfa3cc80acfe3aa1cdd57545e Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 15 Aug 2018 19:10:15 +0400 Subject: [PATCH 09/24] Do not reset idle animations when turning --- apps/openmw/mwmechanics/character.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 1748e3d45..dd7c15ff6 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -411,8 +411,8 @@ void CharacterController::refreshMovementAnims(const WeaponInfo* weap, Character if(force || movement != mMovementState) { mMovementState = movement; - - if (movement != CharState_None) + // Turning animations should not interrupt idle ones + if (movement != CharState_None && !isTurning()) mIdleState = CharState_None; std::string movementAnimName; From 66a8402cdf3a3eb443a4609d13ec5e147d1cbb3c Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 15 Aug 2018 23:12:38 +0400 Subject: [PATCH 10/24] Add missing changelog entries --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8eef0d45..54a1a8ea7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ Bug #4230: AiTravel package issues break some Tribunal quests Bug #4231: Infected rats from the "Crimson Plague" quest rendered unconscious by change in Drain Fatigue functionality Bug #4251: Stationary NPCs do not return to their position after combat + Bug #4271: Scamp flickers when attacking Bug #4274: Pre-0.43 death animations are not forward-compatible with 0.43+ Bug #4286: Scripted animations can be interrupted Bug #4291: Non-persistent actors that started the game as dead do not play death animations @@ -92,6 +93,7 @@ Bug #4574: Player turning animations are twitchy Bug #4575: Weird result of attack animation blending with movement animations Bug #4576: Reset of idle animations when attack can not be started + Bug #4591: Attack strength should be 0 if player did not hold the attack button 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 From b7a448cf42833c573612d532547380cede021625 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Thu, 16 Aug 2018 11:21:48 +0400 Subject: [PATCH 11/24] Update idle animations after reset of mIdleState (bug #4531) --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/character.cpp | 22 ++++++++++++++-------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54a1a8ea7..8d9e9f97a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -81,6 +81,7 @@ Bug #4503: Cast and ExplodeSpell commands increase alteration skill Bug #4510: Division by zero in MWMechanics::CreatureStats::setAttribute Bug #4519: Knockdown does not discard movement in the 1st-person mode + Bug #4531: Movement does not reset idle animations Bug #4539: Paper Doll is affected by GUI scaling Bug #4545: Creatures flee from werewolves Bug #4551: Replace 0 sound range with default range separately diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index dd7c15ff6..855285b07 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -411,10 +411,6 @@ void CharacterController::refreshMovementAnims(const WeaponInfo* weap, Character if(force || movement != mMovementState) { mMovementState = movement; - // Turning animations should not interrupt idle ones - if (movement != CharState_None && !isTurning()) - mIdleState = CharState_None; - std::string movementAnimName; MWRender::Animation::BlendMask movemask = MWRender::Animation::BlendMask_All; const StateInfo *movestate = std::find_if(sMovementList, sMovementListEnd, FindCharState(mMovementState)); @@ -531,7 +527,7 @@ void CharacterController::refreshMovementAnims(const WeaponInfo* weap, Character void CharacterController::refreshIdleAnims(const WeaponInfo* weap, CharacterState idle, bool force) { - if(force || idle != mIdleState || (!mAnimation->isPlaying(mCurrentIdle) && mAnimQueue.empty())) + if(force || idle != mIdleState || mIdleState == CharState_None || (!mAnimation->isPlaying(mCurrentIdle) && mAnimQueue.empty())) { mIdleState = idle; size_t numLoops = ~0ul; @@ -562,10 +558,12 @@ void CharacterController::refreshIdleAnims(const WeaponInfo* weap, CharacterStat // play until the Loop Stop key 2 to 5 times, then play until the Stop key // this replicates original engine behavior for the "Idle1h" 1st-person animation numLoops = 1 + Misc::Rng::rollDice(4); - } + } } - mAnimation->disable(mCurrentIdle); + if(!mCurrentIdle.empty()) + mAnimation->disable(mCurrentIdle); + mCurrentIdle = idleGroup; if(!mCurrentIdle.empty()) mAnimation->play(mCurrentIdle, idlePriority, MWRender::Animation::BlendMask_All, false, @@ -2093,7 +2091,15 @@ void CharacterController::update(float duration) if(mAnimQueue.empty() || inwater || sneak) { - idlestate = (inwater ? CharState_IdleSwim : (sneak && !inJump ? CharState_IdleSneak : CharState_Idle)); + // Note: turning animations should not interrupt idle ones + if (inwater) + idlestate = CharState_IdleSwim; + else if (sneak && !inJump) + idlestate = CharState_IdleSneak; + else if (movestate != CharState_None && !isTurning()) + idlestate = CharState_None; + else + idlestate = CharState_Idle; } else updateAnimQueue(); From a73d42e711e68b759cc81d51543cbb791209ff73 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Thu, 16 Aug 2018 14:37:18 +0400 Subject: [PATCH 12/24] Do not stop idle animation in spellcasting stance --- apps/openmw/mwmechanics/character.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 855285b07..bdbf204c0 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -561,13 +561,21 @@ void CharacterController::refreshIdleAnims(const WeaponInfo* weap, CharacterStat } } + // There is no need to restart anim if the new and old anims are the same. + // Just update a number of loops. + float startPoint = 0; + if (!mCurrentIdle.empty() && mCurrentIdle == idleGroup) + { + mAnimation->getInfo(mCurrentIdle, &startPoint); + } + if(!mCurrentIdle.empty()) mAnimation->disable(mCurrentIdle); mCurrentIdle = idleGroup; if(!mCurrentIdle.empty()) mAnimation->play(mCurrentIdle, idlePriority, MWRender::Animation::BlendMask_All, false, - 1.0f, "start", "stop", 0.0f, numLoops, true); + 1.0f, "start", "stop", startPoint, numLoops, true); } } @@ -2091,12 +2099,13 @@ void CharacterController::update(float duration) if(mAnimQueue.empty() || inwater || sneak) { - // Note: turning animations should not interrupt idle ones + // Note: turning animations should not interrupt idle ones. + // Also movement should not stop idle animation for spellcasting stance. if (inwater) idlestate = CharState_IdleSwim; else if (sneak && !inJump) idlestate = CharState_IdleSneak; - else if (movestate != CharState_None && !isTurning()) + else if (movestate != CharState_None && !isTurning() && mWeaponType != WeapType_Spell) idlestate = CharState_None; else idlestate = CharState_Idle; From 16edac8c479660ab511bf78e3653a883c2c2ef97 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Thu, 16 Aug 2018 17:47:06 +0400 Subject: [PATCH 13/24] Fix incorrect 'preparing' word spelling --- apps/openmw/mwbase/mechanicsmanager.hpp | 2 +- apps/openmw/mwinput/inputmanagerimp.cpp | 4 ++-- apps/openmw/mwmechanics/actors.cpp | 4 ++-- apps/openmw/mwmechanics/actors.hpp | 2 +- apps/openmw/mwmechanics/character.cpp | 4 ++-- apps/openmw/mwmechanics/character.hpp | 2 +- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 4 ++-- apps/openmw/mwmechanics/mechanicsmanagerimp.hpp | 2 +- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 995c8d736..fe3fc5721 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -257,7 +257,7 @@ namespace MWBase virtual void cleanupSummonedCreature(const MWWorld::Ptr& caster, int creatureActorId) = 0; virtual void confiscateStolenItemToOwner(const MWWorld::Ptr &player, const MWWorld::Ptr &item, const MWWorld::Ptr& victim, int count) = 0; - virtual bool isAttackPrepairing(const MWWorld::Ptr& ptr) = 0; + virtual bool isAttackPreparing(const MWWorld::Ptr& ptr) = 0; virtual bool isRunning(const MWWorld::Ptr& ptr) = 0; virtual bool isSneaking(const MWWorld::Ptr& ptr) = 0; }; diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 709c61196..65fc0c098 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -1004,9 +1004,9 @@ namespace MWInput if (!mControlSwitch["playerfighting"] || !mControlSwitch["playercontrols"]) return; - // We want to interrupt animation only if attack is prepairing, but still is not triggered + // We want to interrupt animation only if attack is preparing, but still is not triggered // Otherwise we will get a "speedshooting" exploit, when player can skip reload animation by hitting "Toggle Weapon" key twice - if (MWBase::Environment::get().getMechanicsManager()->isAttackPrepairing(mPlayer->getPlayer())) + if (MWBase::Environment::get().getMechanicsManager()->isAttackPreparing(mPlayer->getPlayer())) mPlayer->setAttackingOrSpell(false); else if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(mPlayer->getPlayer())) return; diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 6a605c056..e794e0aaa 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -897,14 +897,14 @@ namespace MWMechanics } } - bool Actors::isAttackPrepairing(const MWWorld::Ptr& ptr) + bool Actors::isAttackPreparing(const MWWorld::Ptr& ptr) { PtrActorMap::iterator it = mActors.find(ptr); if (it == mActors.end()) return false; CharacterController* ctrl = it->second->getCharacterController(); - return ctrl->isAttackPrepairing(); + return ctrl->isAttackPreparing(); } bool Actors::isRunning(const MWWorld::Ptr& ptr) diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 492ff1e2e..8c94ce45f 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -118,7 +118,7 @@ namespace MWMechanics int countDeaths (const std::string& id) const; ///< Return the number of deaths for actors with the given ID. - bool isAttackPrepairing(const MWWorld::Ptr& ptr); + bool isAttackPreparing(const MWWorld::Ptr& ptr); bool isRunning(const MWWorld::Ptr& ptr); bool isSneaking(const MWWorld::Ptr& ptr); diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index bdbf204c0..c7c6b57d0 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1685,7 +1685,7 @@ bool CharacterController::updateWeaponState() break; case UpperCharState_StartToMinAttack: { - // If actor is already stopped prepairing attack, do not play the "min attack -> max attack" part. + // If actor is already stopped preparing attack, do not play the "min attack -> max attack" part. // Happens if the player did not hold the attack button. // Note: if the "min attack"->"max attack" is a stub, "play" it anyway. Attack strength will be 1. float minAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon+": "+mAttackType+" "+"min attack"); @@ -2507,7 +2507,7 @@ bool CharacterController::isRandomAttackAnimation(const std::string& group) cons group == "attack3" || group == "swimattack3"); } -bool CharacterController::isAttackPrepairing() const +bool CharacterController::isAttackPreparing() const { return mUpperBodyState == UpperCharState_StartToMinAttack || mUpperBodyState == UpperCharState_MinAttackToMaxAttack; diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 631f78208..43d26e52f 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -281,7 +281,7 @@ public: void forceStateUpdate(); - bool isAttackPrepairing() const; + bool isAttackPreparing() const; bool isCastingSpell() const; bool isReadyToBlock() const; bool isKnockedDown() const; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 7814b4a91..dbc2cd93c 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -435,9 +435,9 @@ namespace MWMechanics return mActors.isActorDetected(actor, observer); } - bool MechanicsManager::isAttackPrepairing(const MWWorld::Ptr& ptr) + bool MechanicsManager::isAttackPreparing(const MWWorld::Ptr& ptr) { - return mActors.isAttackPrepairing(ptr); + return mActors.isAttackPreparing(ptr); } bool MechanicsManager::isRunning(const MWWorld::Ptr& ptr) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 676a75caf..e9ec14685 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -223,7 +223,7 @@ namespace MWMechanics virtual void confiscateStolenItemToOwner(const MWWorld::Ptr &player, const MWWorld::Ptr &item, const MWWorld::Ptr& victim, int count); - virtual bool isAttackPrepairing(const MWWorld::Ptr& ptr); + virtual bool isAttackPreparing(const MWWorld::Ptr& ptr); virtual bool isRunning(const MWWorld::Ptr& ptr); virtual bool isSneaking(const MWWorld::Ptr& ptr); From 43f1c9163cc254db8fd390604cbaa77a7edb9dfe Mon Sep 17 00:00:00 2001 From: James Carty Date: Thu, 16 Aug 2018 18:58:51 +0100 Subject: [PATCH 14/24] Fix issue in which murder wouldn't be reported after paying fine --- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 6ef358790..a46204e66 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1511,11 +1511,10 @@ namespace MWMechanics return; // TODO: implement animal rights const MWMechanics::NpcStats& victimStats = victim.getClass().getNpcStats(victim); - if (victimStats.getCrimeId() == -1) - return; + const MWWorld::Ptr &player = getPlayer(); + bool canCommit = attacker == player && canCommitCrimeAgainst(attacker, victim); // For now we report only about crimes of player and player's followers - const MWWorld::Ptr &player = getPlayer(); if (attacker != player) { std::set playerFollowers; @@ -1524,6 +1523,9 @@ namespace MWMechanics return; } + if (!canCommit && victimStats.getCrimeId() == -1) + return; + // Simple check for who attacked first: if the player attacked first, a crimeId should be set // Doesn't handle possible edge case where no one reported the assault, but in such a case, // for bystanders it is not possible to tell who attacked first, anyway. From a492978fe677e0a14e8ff806ee12f96ef704be58 Mon Sep 17 00:00:00 2001 From: David Walley Date: Fri, 17 Aug 2018 08:23:47 +0000 Subject: [PATCH 15/24] Update AUTHORS.md - add Loriel (Documentation) --- AUTHORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.md b/AUTHORS.md index a722ff723..7252e3930 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -181,6 +181,7 @@ Documentation Alejandro Sanchez (HiPhish) Bodillium Bret Curtis (psi29a) + David Walley (Loriel) Cramal Ryan Tucker (Ravenwing) sir_herrbatka From 8516aee6e0a69d59569218dff74808b83b5e3418 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 16 Jun 2018 10:18:04 +0400 Subject: [PATCH 16/24] Implement getHalfExtents() for non-actor objects --- apps/openmw/mwrender/renderingmanager.cpp | 24 +++++++++++++++++++++++ apps/openmw/mwrender/renderingmanager.hpp | 2 ++ apps/openmw/mwworld/worldimp.cpp | 10 +++++++--- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index e17fe3191..ce0a41c9a 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -49,6 +49,7 @@ #include #include "../mwworld/cellstore.hpp" +#include "../mwworld/class.hpp" #include "../mwgui/loadingscreen.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" @@ -1321,6 +1322,29 @@ namespace MWRender } } + osg::Vec3f RenderingManager::getHalfExtents(const MWWorld::ConstPtr& object) const + { + osg::Vec3f halfExtents(0, 0, 0); + std::string modelName = object.getClass().getModel(object); + if (modelName.empty()) + return halfExtents; + + osg::ref_ptr node = mResourceSystem->getSceneManager()->getTemplate(modelName); + osg::ComputeBoundsVisitor computeBoundsVisitor; + computeBoundsVisitor.setTraversalMask(~(MWRender::Mask_ParticleSystem|MWRender::Mask_Effect)); + const_cast(node.get())->accept(computeBoundsVisitor); + osg::BoundingBox bounds = computeBoundsVisitor.getBoundingBox(); + + if (bounds.valid()) + { + halfExtents[0] = std::abs(bounds.xMax() - bounds.xMin()) / 2.f; + halfExtents[1] = std::abs(bounds.yMax() - bounds.yMin()) / 2.f; + halfExtents[2] = std::abs(bounds.zMax() - bounds.zMin()) / 2.f; + } + + return halfExtents; + } + void RenderingManager::resetFieldOfView() { if (mFieldOfViewOverridden == true) diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index f8e7ba8bc..b4473c3b4 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -203,6 +203,8 @@ namespace MWRender /// reset a previous overrideFieldOfView() call, i.e. revert to field of view specified in the settings file. void resetFieldOfView(); + osg::Vec3f getHalfExtents(const MWWorld::ConstPtr& object) const; + void exportSceneGraph(const MWWorld::Ptr& ptr, const std::string& filename, const std::string& format); LandManager* getLandManager() const; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index bf829225a..f9237761d 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3339,12 +3339,16 @@ namespace MWWorld return mRendering->getTerrainHeightAt(worldPos); } - osg::Vec3f World::getHalfExtents(const ConstPtr& actor, bool rendering) const + osg::Vec3f World::getHalfExtents(const ConstPtr& object, bool rendering) const { + if (!object.getClass().isActor()) + return mRendering->getHalfExtents(object); + + // Handle actors separately because of bodyparts if (rendering) - return mPhysics->getRenderingHalfExtents(actor); + return mPhysics->getRenderingHalfExtents(object); else - return mPhysics->getHalfExtents(actor); + return mPhysics->getHalfExtents(object); } std::string World::exportSceneGraph(const Ptr &ptr) From 31f8bea1dd6e0a93b130b472fa83f1076f41d8a5 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 15 Jul 2018 12:44:25 +0400 Subject: [PATCH 17/24] Rework spell effects management --- CHANGELOG.md | 1 + apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwmechanics/spellcasting.cpp | 76 ++++++-- apps/openmw/mwrender/animation.cpp | 223 ++++++++++++++++++----- apps/openmw/mwrender/animation.hpp | 45 +++-- apps/openmw/mwscript/miscextensions.cpp | 1 + apps/openmw/mwworld/worldimp.cpp | 4 +- apps/openmw/mwworld/worldimp.hpp | 2 +- 8 files changed, 273 insertions(+), 81 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c016ca82f..9efdf0260 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -95,6 +95,7 @@ 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 #1645: Casting effects from objects Feature #2606: Editor: Implemented (optional) case sensitive global search Feature #3083: Play animation when NPC is casting spell via script Feature #3103: Provide option for disposition to get increased by successful trade diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index ee1227e0c..3c46298b0 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -536,7 +536,7 @@ namespace MWBase /// Spawn a blood effect for \a ptr at \a worldPosition virtual void spawnBloodEffect (const MWWorld::Ptr& ptr, const osg::Vec3f& worldPosition) = 0; - virtual void spawnEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos) = 0; + virtual void spawnEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos, float scale = 1.f, bool isMagicVFX = true) = 0; virtual void explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const MWWorld::Ptr& ignore, ESM::RangeType rangeType, const std::string& id, diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index b337fa6b7..76140013d 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -24,6 +24,7 @@ #include "../mwworld/inventorystore.hpp" #include "../mwrender/animation.hpp" +#include "../mwrender/vismask.hpp" #include "npcstats.hpp" #include "actorutil.hpp" @@ -327,16 +328,19 @@ namespace MWMechanics } void CastSpell::launchMagicBolt () - { - osg::Vec3f fallbackDirection (0,1,0); + { + osg::Vec3f fallbackDirection(0, 1, 0); + osg::Vec3f offset(0, 0, 0); + if (!mTarget.isEmpty() && mTarget.getClass().isActor()) + offset.z() = MWBase::Environment::get().getWorld()->getHalfExtents(mTarget).z(); // Fall back to a "caster to target" direction if we have no other means of determining it // (e.g. when cast by a non-actor) if (!mTarget.isEmpty()) fallbackDirection = - osg::Vec3f(mTarget.getRefData().getPosition().asVec3())- - osg::Vec3f(mCaster.getRefData().getPosition().asVec3()); - + (mTarget.getRefData().getPosition().asVec3() + offset) - + (mCaster.getRefData().getPosition().asVec3()); + MWBase::Environment::get().getWorld()->launchMagicBolt(mId, mCaster, fallbackDirection); } @@ -999,11 +1003,13 @@ namespace MWMechanics return true; } - void CastSpell::playSpellCastingEffects(const std::string &spellid){ - + void CastSpell::playSpellCastingEffects(const std::string &spellid) + { const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Spell *spell = store.get().find(spellid); + std::vector addedEffects; + for (std::vector::const_iterator iter = spell->mEffects.mList.begin(); iter != spell->mEffects.mList.end(); ++iter) { @@ -1012,18 +1018,56 @@ namespace MWMechanics MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(mCaster); - if (animation && mCaster.getClass().isActor()) // TODO: Non-actors should also create a spell cast vfx even if they are disabled (animation == NULL) + const ESM::Static* castStatic; + + if (!effect->mCasting.empty()) + castStatic = store.get().find (effect->mCasting); + else + castStatic = store.get().find ("VFX_DefaultCast"); + + // check if the effect was already added + if (std::find(addedEffects.begin(), addedEffects.end(), "meshes\\" + castStatic->mModel) != addedEffects.end()) + continue; + + std::string texture = effect->mParticle; + + float scale = 1.0f; + osg::Vec3f pos (mCaster.getRefData().getPosition().asVec3()); + + if (animation && mCaster.getClass().isNpc()) { - const ESM::Static* castStatic; + // For NPC we should take race height as scaling factor + const ESM::NPC *npc = mCaster.get()->mBase; + const MWWorld::ESMStore &esmStore = + MWBase::Environment::get().getWorld()->getStore(); - if (!effect->mCasting.empty()) - castStatic = store.get().find (effect->mCasting); - else - castStatic = store.get().find ("VFX_DefaultCast"); + const ESM::Race *race = + esmStore.get().find(npc->mRace); - std::string texture = effect->mParticle; + scale = npc->isMale() ? race->mData.mHeight.mMale : race->mData.mHeight.mFemale; + } + else + { + osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(mCaster); - animation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex, false, "", texture); + // TODO: take a size of particle or NPC with height and weight = 1.0 as scale = 1.0 + float scaleX = halfExtents.x() * 2 / 60.f; + float scaleY = halfExtents.y() * 2 / 60.f; + float scaleZ = halfExtents.z() * 2 / 120.f; + + scale = std::max({ scaleX, scaleY, scaleZ }); + } + + // If the caster has no animation, add the effect directly to the effectManager + if (animation) + { + animation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex, false, "", texture, scale); + } + else + { + // We should set scale for effect manager manually + float meshScale = !mCaster.getClass().isActor() ? mCaster.getCellRef().getScale() : 1.0f; + MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + castStatic->mModel, effect->mParticle, pos, scale * meshScale); } if (animation && !mCaster.getClass().isActor()) @@ -1033,6 +1077,8 @@ namespace MWMechanics "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" }; + addedEffects.push_back("meshes\\" + castStatic->mModel); + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); if(!effect->mCastSound.empty()) sndMgr->playSound3D(mCaster, effect->mCastSound, 1.0f, 1.0f); diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 8e1105b9f..4da3ec4a8 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -204,6 +205,110 @@ namespace std::vector > mToRemove; }; + class RemoveFinishedCallbackVisitor : public RemoveVisitor + { + public: + RemoveFinishedCallbackVisitor() + : RemoveVisitor() + , mEffectId(-1) + { + } + + RemoveFinishedCallbackVisitor(int effectId) + : RemoveVisitor() + , mEffectId(effectId) + { + } + + virtual void apply(osg::Node &node) + { + traverse(node); + } + + virtual void apply(osg::Group &group) + { + traverse(group); + + osg::Callback* callback = group.getUpdateCallback(); + if (callback) + { + // We should remove empty transformation nodes and finished callbacks here + MWRender::UpdateVfxCallback* vfxCallback = dynamic_cast(callback); + bool finished = vfxCallback && vfxCallback->mFinished; + bool toRemove = vfxCallback && mEffectId >= 0 && vfxCallback->mParams.mEffectId == mEffectId; + if (finished || toRemove) + { + mToRemove.push_back(std::make_pair(group.asNode(), group.getParent(0))); + } + } + } + + virtual void apply(osg::MatrixTransform &node) + { + traverse(node); + } + + virtual void apply(osg::Geometry&) + { + } + + private: + int mEffectId; + }; + + class FindVfxCallbacksVisitor : public osg::NodeVisitor + { + public: + + std::vector mCallbacks; + + FindVfxCallbacksVisitor() + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mEffectId(-1) + { + } + + FindVfxCallbacksVisitor(int effectId) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mEffectId(effectId) + { + } + + virtual void apply(osg::Node &node) + { + traverse(node); + } + + virtual void apply(osg::Group &group) + { + osg::Callback* callback = group.getUpdateCallback(); + if (callback) + { + MWRender::UpdateVfxCallback* vfxCallback = dynamic_cast(callback); + if (vfxCallback) + { + if (mEffectId < 0 || vfxCallback->mParams.mEffectId == mEffectId) + { + mCallbacks.push_back(vfxCallback); + } + } + } + traverse(group); + } + + virtual void apply(osg::MatrixTransform &node) + { + traverse(node); + } + + virtual void apply(osg::Geometry&) + { + } + + private: + int mEffectId; + }; + // Removes all drawables from a graph. class CleanObjectRootVisitor : public RemoveVisitor { @@ -287,7 +392,6 @@ namespace } } }; - } namespace MWRender @@ -432,6 +536,42 @@ namespace MWRender const std::multimap& getTextKeys() const; }; + void UpdateVfxCallback::operator()(osg::Node* node, osg::NodeVisitor* nv) + { + traverse(node, nv); + + if (mFinished) + return; + + double newTime = nv->getFrameStamp()->getSimulationTime(); + if (mStartingTime == 0) + { + mStartingTime = newTime; + return; + } + + double duration = newTime - mStartingTime; + mStartingTime = newTime; + + mParams.mAnimTime->addTime(duration); + if (mParams.mAnimTime->getTime() >= mParams.mMaxControllerLength) + { + if (mParams.mLoop) + { + // Start from the beginning again; carry over the remainder + // Not sure if this is actually needed, the controller function might already handle loops + float remainder = mParams.mAnimTime->getTime() - mParams.mMaxControllerLength; + mParams.mAnimTime->resetTime(remainder); + } + else + { + // Remove effect immediately + mParams.mObjects.reset(); + mFinished = true; + } + } + } + class ResetAccumRootCallback : public osg::NodeCallback { public: @@ -1436,15 +1576,22 @@ namespace MWRender useQuadratic, quadraticValue, quadraticRadiusMult, useLinear, linearRadiusMult, linearValue); } - void Animation::addEffect (const std::string& model, int effectId, bool loop, const std::string& bonename, const std::string& texture) + void Animation::addEffect (const std::string& model, int effectId, bool loop, const std::string& bonename, const std::string& texture, float scale) { if (!mObjectRoot.get()) return; // Early out if we already have this effect - for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) - if (it->mLoop && loop && it->mEffectId == effectId && it->mBoneName == bonename) + FindVfxCallbacksVisitor visitor(effectId); + mInsert->accept(visitor); + + for (std::vector::iterator it = visitor.mCallbacks.begin(); it != visitor.mCallbacks.end(); ++it) + { + UpdateVfxCallback* callback = *it; + + if (loop && !callback->mFinished && callback->mParams.mLoop && callback->mParams.mBoneName == bonename) return; + } EffectParams params; params.mModelName = model; @@ -1459,11 +1606,13 @@ namespace MWRender parentNode = found->second; } - osg::ref_ptr node = mResourceSystem->getSceneManager()->getInstance(model, parentNode); - node->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + osg::ref_ptr trans = new osg::PositionAttitudeTransform; + trans->setScale(osg::Vec3f(scale, scale, scale)); + parentNode->addChild(trans); - params.mObjects = PartHolderPtr(new PartHolder(node)); + osg::ref_ptr node = mResourceSystem->getSceneManager()->getInstance(model, trans); + node->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); SceneUtil::FindMaxControllerLengthVisitor findMaxLengthVisitor; node->accept(findMaxLengthVisitor); @@ -1471,71 +1620,50 @@ namespace MWRender // FreezeOnCull doesn't work so well with effect particles, that tend to have moving emitters SceneUtil::DisableFreezeOnCullVisitor disableFreezeOnCullVisitor; node->accept(disableFreezeOnCullVisitor); - - params.mMaxControllerLength = findMaxLengthVisitor.getMaxLength(); - node->setNodeMask(Mask_Effect); + params.mMaxControllerLength = findMaxLengthVisitor.getMaxLength(); params.mLoop = loop; params.mEffectId = effectId; params.mBoneName = bonename; - + params.mObjects = PartHolderPtr(new PartHolder(node)); params.mAnimTime = std::shared_ptr(new EffectAnimationTime); + trans->addUpdateCallback(new UpdateVfxCallback(params)); SceneUtil::AssignControllerSourcesVisitor assignVisitor(std::shared_ptr(params.mAnimTime)); node->accept(assignVisitor); overrideFirstRootTexture(texture, mResourceSystem, node); - - // TODO: in vanilla morrowind the effect is scaled based on the host object's bounding box. - - mEffects.push_back(params); } void Animation::removeEffect(int effectId) { - for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) - { - if (it->mEffectId == effectId) - { - mEffects.erase(it); - return; - } - } + RemoveFinishedCallbackVisitor visitor(effectId); + mInsert->accept(visitor); + visitor.remove(); } void Animation::getLoopingEffects(std::vector &out) const { - for (std::vector::const_iterator it = mEffects.begin(); it != mEffects.end(); ++it) + FindVfxCallbacksVisitor visitor; + mInsert->accept(visitor); + + for (std::vector::iterator it = visitor.mCallbacks.begin(); it != visitor.mCallbacks.end(); ++it) { - if (it->mLoop) - out.push_back(it->mEffectId); + UpdateVfxCallback* callback = *it; + + if (callback->mParams.mLoop && !callback->mFinished) + out.push_back(callback->mParams.mEffectId); } } void Animation::updateEffects(float duration) { - for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ) - { - it->mAnimTime->addTime(duration); - - if (it->mAnimTime->getTime() >= it->mMaxControllerLength) - { - if (it->mLoop) - { - // Start from the beginning again; carry over the remainder - // Not sure if this is actually needed, the controller function might already handle loops - float remainder = it->mAnimTime->getTime() - it->mMaxControllerLength; - it->mAnimTime->resetTime(remainder); - } - else - { - it = mEffects.erase(it); - continue; - } - } - ++it; - } + // TODO: objects without animation still will have + // transformation nodes with finished callbacks + RemoveFinishedCallbackVisitor visitor; + mInsert->accept(visitor); + visitor.remove(); } bool Animation::upperBodyReady() const @@ -1778,5 +1906,4 @@ namespace MWRender mNode->getParent(0)->removeChild(mNode); } } - } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 43a626899..1da1fe4ef 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -71,6 +71,17 @@ private: }; typedef std::shared_ptr PartHolderPtr; +struct EffectParams +{ + std::string mModelName; // Just here so we don't add the same effect twice + PartHolderPtr mObjects; + std::shared_ptr mAnimTime; + float mMaxControllerLength; + int mEffectId; + bool mLoop; + std::string mBoneName; +}; + class Animation : public osg::Referenced { public: @@ -247,19 +258,6 @@ protected: osg::Vec3f mAccumulate; - struct EffectParams - { - std::string mModelName; // Just here so we don't add the same effect twice - PartHolderPtr mObjects; - std::shared_ptr mAnimTime; - float mMaxControllerLength; - int mEffectId; - bool mLoop; - std::string mBoneName; - }; - - std::vector mEffects; - TextKeyListener* mTextKeyListener; osg::ref_ptr mHeadController; @@ -369,7 +367,7 @@ public: * @param texture override the texture specified in the model's materials - if empty, do not override * @note Will not add an effect twice. */ - void addEffect (const std::string& model, int effectId, bool loop = false, const std::string& bonename = "", const std::string& texture = ""); + void addEffect (const std::string& model, int effectId, bool loop = false, const std::string& bonename = "", const std::string& texture = "", float scale = 1.0f); void removeEffect (int effectId); void getLoopingEffects (std::vector& out) const; @@ -489,5 +487,24 @@ public: ObjectAnimation(const MWWorld::Ptr& ptr, const std::string &model, Resource::ResourceSystem* resourceSystem, bool animated, bool allowLight); }; +class UpdateVfxCallback : public osg::NodeCallback +{ +public: + UpdateVfxCallback(EffectParams& params) + : mFinished(false) + , mParams(params) + , mStartingTime(0) + { + } + + bool mFinished; + EffectParams mParams; + + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv); + +private: + double mStartingTime; +}; + } #endif diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 7da1a4833..3d1978d62 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -1082,6 +1082,7 @@ namespace MWScript MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr (targetId, false); MWMechanics::CastSpell cast(ptr, target, false, true); + cast.playSpellCastingEffects(spell->mId); cast.mHitPosition = target.getRefData().getPosition().asVec3(); cast.mAlwaysSucceed = true; cast.cast(spell); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index f9237761d..c2ebac8fc 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3407,9 +3407,9 @@ namespace MWWorld mRendering->spawnEffect(model, texture, worldPosition, 1.0f, false); } - void World::spawnEffect(const std::string &model, const std::string &textureOverride, const osg::Vec3f &worldPos) + void World::spawnEffect(const std::string &model, const std::string &textureOverride, const osg::Vec3f &worldPos, float scale, bool isMagicVFX) { - mRendering->spawnEffect(model, textureOverride, worldPos); + mRendering->spawnEffect(model, textureOverride, worldPos, scale, isMagicVFX); } void World::explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const Ptr& caster, const Ptr& ignore, ESM::RangeType rangeType, diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 2352fd31c..432991059 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -646,7 +646,7 @@ namespace MWWorld /// Spawn a blood effect for \a ptr at \a worldPosition void spawnBloodEffect (const MWWorld::Ptr& ptr, const osg::Vec3f& worldPosition) override; - void spawnEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos) override; + void spawnEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos, float scale = 1.f, bool isMagicVFX = true) override; void explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const MWWorld::Ptr& ignore, ESM::RangeType rangeType, const std::string& id, const std::string& sourceName, From d7ca087f592eb90e9dfbeeb779ad04bc00b9bd3e Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 16 Jul 2018 11:23:39 +0400 Subject: [PATCH 18/24] AiCast: fix aiming --- apps/openmw/mwmechanics/aicast.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/aicast.cpp b/apps/openmw/mwmechanics/aicast.cpp index 48cb17f6d..948ffb3aa 100644 --- a/apps/openmw/mwmechanics/aicast.cpp +++ b/apps/openmw/mwmechanics/aicast.cpp @@ -42,7 +42,19 @@ bool MWMechanics::AiCast::execute(const MWWorld::Ptr& actor, MWMechanics::Charac return false; } - osg::Vec3f dir = target.getRefData().getPosition().asVec3() - actor.getRefData().getPosition().asVec3(); + osg::Vec3f targetPos = target.getRefData().getPosition().asVec3(); + if (target.getClass().isActor()) + { + osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(target); + targetPos.z() += halfExtents.z() * 2 * 0.75f; + } + + osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3(); + osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor); + actorPos.z() += halfExtents.z() * 2 * 0.75f; + + osg::Vec3f dir = targetPos - actorPos; + bool turned = smoothTurn(actor, getZAngleToDir(dir), 2, osg::DegreesToRadians(3.f)); turned &= smoothTurn(actor, getXAngleToDir(dir), 0, osg::DegreesToRadians(3.f)); From 76932ebfc5447f8f0dca05dffe3a90844b1c42e0 Mon Sep 17 00:00:00 2001 From: Diego Crespo Date: Fri, 17 Aug 2018 08:39:50 -0400 Subject: [PATCH 19/24] Updated AUTHORS.md --- AUTHORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.md b/AUTHORS.md index d2fd05180..9fc092c8e 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -183,6 +183,7 @@ Documentation Cramal Ryan Tucker (Ravenwing) sir_herrbatka + Diego Crespo Packagers --------- From 331016c782a3ac8582919165f79a6898df09ff49 Mon Sep 17 00:00:00 2001 From: James Carty Date: Fri, 17 Aug 2018 13:48:02 +0100 Subject: [PATCH 20/24] Update Authors --- AUTHORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.md b/AUTHORS.md index d2fd05180..bc22b230f 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -172,6 +172,7 @@ Programmers Vincent Heuken vocollapse zelurker + James Carty (MrTopCat) Documentation ------------- From 99b465eede0a76058e5fdd498b9c079d6a89a4a2 Mon Sep 17 00:00:00 2001 From: James Carty Date: Fri, 17 Aug 2018 13:54:58 +0100 Subject: [PATCH 21/24] Update authors --- AUTHORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.md b/AUTHORS.md index d2fd05180..bc22b230f 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -172,6 +172,7 @@ Programmers Vincent Heuken vocollapse zelurker + James Carty (MrTopCat) Documentation ------------- From 513e99148e135fbf60309db718b3853d12898918 Mon Sep 17 00:00:00 2001 From: James Carty Date: Fri, 17 Aug 2018 20:17:26 +0100 Subject: [PATCH 22/24] Fix function with no return value --- apps/openmw/mwmechanics/aisequence.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index d55ba13dd..9a42168d6 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -125,16 +125,16 @@ bool AiSequence::isInCombat() const bool AiSequence::isEngagedWithActor() const { - bool isFightingNpc = false; for (std::list::const_iterator it = mPackages.begin(); it != mPackages.end(); ++it) { if ((*it)->getTypeId() == AiPackage::TypeIdCombat) { MWWorld::Ptr target2 = (*it)->getTarget(); if (!target2.isEmpty() && target2.getClass().isNpc()) - isFightingNpc = true; + return true; } } + return false; } bool AiSequence::hasPackage(int typeId) const From 4489838ea386f16975646c1366ef3025b69469e3 Mon Sep 17 00:00:00 2001 From: James Carty Date: Fri, 17 Aug 2018 22:35:04 +0100 Subject: [PATCH 23/24] Fix incorrect function call --- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index a46204e66..42e46016a 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1512,7 +1512,7 @@ namespace MWMechanics const MWMechanics::NpcStats& victimStats = victim.getClass().getNpcStats(victim); const MWWorld::Ptr &player = getPlayer(); - bool canCommit = attacker == player && canCommitCrimeAgainst(attacker, victim); + bool canCommit = attacker == player && canCommitCrimeAgainst(victim, attacker); // For now we report only about crimes of player and player's followers if (attacker != player) From 74229490e44e74f9b59cd49f02895091acfce825 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Mon, 20 Aug 2018 21:38:57 +0300 Subject: [PATCH 24/24] Use the correct skill for creature AI weapon hit chance rating --- apps/openmw/mwmechanics/weaponpriority.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwmechanics/weaponpriority.cpp b/apps/openmw/mwmechanics/weaponpriority.cpp index 26c784c4d..9030d6254 100644 --- a/apps/openmw/mwmechanics/weaponpriority.cpp +++ b/apps/openmw/mwmechanics/weaponpriority.cpp @@ -108,11 +108,19 @@ namespace MWMechanics } } - int skill = item.getClass().getEquipmentSkill(item); - if (skill != -1) + if (actor.getClass().isNpc()) { - int value = actor.getClass().getSkill(actor, skill); - rating *= MWMechanics::getHitChance(actor, enemy, value) / 100.f; + int skill = item.getClass().getEquipmentSkill(item); + if (skill != -1) + { + int value = actor.getClass().getSkill(actor, skill); + rating *= MWMechanics::getHitChance(actor, enemy, value) / 100.f; + } + } + else + { + MWWorld::LiveCellRef *ref = actor.get(); + rating *= MWMechanics::getHitChance(actor, enemy, ref->mBase->mData.mCombat) / 100.f; } return rating * rangedMult;