diff --git a/CHANGELOG.md b/CHANGELOG.md index 09f1279bad..2f5b83f158 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ------ Bug #1751: Birthsign abilities increase modified attribute values instead of base ones + Bug #1930: Followers are still fighting if a target stops combat with a leader Bug #3246: ESSImporter: Most NPCs are dead on save load Bug #3488: AI combat aiming is too slow Bug #3514: Editing a reference's position after loading an esp file makes the reference disappear diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 6bedbb5b4d..484940e3e6 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -112,6 +112,9 @@ namespace MWBase /// Makes \a ptr fight \a target. Also shouts a combat taunt. virtual void startCombat (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target) = 0; + /// Removes an actor and its allies from combat with the actor's targets. + virtual void stopCombat(const MWWorld::Ptr& ptr) = 0; + enum OffenseType { OT_Theft, // Taking items owned by an NPC or a faction you are not a member of diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index b3ddefec3f..ce2465ec97 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -443,6 +443,22 @@ namespace MWMechanics } } + void Actors::stopCombat(const MWWorld::Ptr& ptr) + { + auto& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); + std::vector targets; + if(ai.getCombatTargets(targets)) + { + std::set allySet; + getActorsSidingWith(ptr, allySet); + std::vector allies(allySet.begin(), allySet.end()); + for(const auto& ally : allies) + ally.getClass().getCreatureStats(ally).getAiSequence().stopCombat(targets); + for(const auto& target : targets) + target.getClass().getCreatureStats(target).getAiSequence().stopCombat(allies); + } + } + void Actors::engageCombat (const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, std::map >& cachedAllies, bool againstPlayer) { // No combat for totally static creatures @@ -979,7 +995,7 @@ namespace MWMechanics // Calm witness down if (ptr.getClass().isClass(ptr, "Guard")) creatureStats.getAiSequence().stopPursuit(); - creatureStats.getAiSequence().stopCombat(); + stopCombat(ptr); // Reset factors to attack creatureStats.setAttacked(false); @@ -1967,7 +1983,7 @@ namespace MWMechanics if (stats.isDead()) continue; - // An actor counts as siding with this actor if Follow or Escort is the current AI package, or there are only Combat and Wander packages before the Follow/Escort package + // An actor counts as siding with this actor if Follow or Escort is the current AI package, or there are only Wander packages before the Follow/Escort package // Actors that are targeted by this actor's Follow or Escort packages also side with them for (const auto& package : stats.getAiSequence()) { @@ -1983,7 +1999,7 @@ namespace MWMechanics } break; } - else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander) + else if (package->getTypeId() > AiPackageTypeId::Wander && package->getTypeId() <= AiPackageTypeId::Activate) // Don't count "fake" package types break; } } diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 7d44fd06cd..f922be6556 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -111,6 +111,8 @@ namespace MWMechanics ///< This function is normally called automatically during the update process, but it can /// also be called explicitly at any time to force an update. + /// Removes an actor from combat and makes all of their allies stop fighting the actor's targets + void stopCombat(const MWWorld::Ptr& ptr); /** Start combat between two actors @Notes: If againstPlayer = true then actor2 should be the Player. If one of the combatants is creature it should be actor1. diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index b1827dd8ef..545ec7f140 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -158,6 +158,7 @@ bool AiSequence::isInCombat(const MWWorld::Ptr &actor) const return false; } +// TODO: use std::list::remove_if for all these methods when we switch to C++20 void AiSequence::stopCombat() { for(auto it = mPackages.begin(); it != mPackages.end(); ) @@ -171,6 +172,19 @@ void AiSequence::stopCombat() } } +void AiSequence::stopCombat(const std::vector& targets) +{ + for(auto it = mPackages.begin(); it != mPackages.end(); ) + { + if ((*it)->getTypeId() == AiPackageTypeId::Combat && std::find(targets.begin(), targets.end(), (*it)->getTarget()) != targets.end()) + { + it = mPackages.erase(it); + } + else + ++it; + } +} + void AiSequence::stopPursuit() { for(auto it = mPackages.begin(); it != mPackages.end(); ) diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index 90bd999c91..77f6b2f7c0 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -102,6 +102,9 @@ namespace MWMechanics /// Removes all combat packages until first non-combat or stack empty. void stopCombat(); + /// Removes all combat packages with the given targets + void stopCombat(const std::vector& targets); + /// Has a package been completed during the last update? bool isPackageDone() const; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index e16bfd4794..f1101d2566 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1608,6 +1608,11 @@ namespace MWMechanics MWBase::Environment::get().getDialogueManager()->say(ptr, "attack"); } + void MechanicsManager::stopCombat(const MWWorld::Ptr& actor) + { + mActors.stopCombat(actor); + } + void MechanicsManager::getObjectsInRange(const osg::Vec3f &position, float radius, std::vector &objects) { mActors.getObjectsInRange(position, radius, objects); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 06da2fde51..0f4c2e606a 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -102,6 +102,8 @@ namespace MWMechanics /// Makes \a ptr fight \a target. Also shouts a combat taunt. void startCombat (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target) override; + void stopCombat(const MWWorld::Ptr& ptr) override; + /** * @note victim may be empty * @param arg Depends on \a type, e.g. for Theft, the value of the item that was stolen. diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index 5ebc0bc529..b7b6de9463 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -515,8 +515,7 @@ namespace MWScript MWWorld::Ptr actor = R()(runtime); if (!actor.getClass().isActor()) return; - MWMechanics::CreatureStats& creatureStats = actor.getClass().getCreatureStats(actor); - creatureStats.getAiSequence().stopCombat(); + MWBase::Environment::get().getMechanicsManager()->stopCombat(actor); } };