diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index d0ae5c6ea4..5650ad9d2a 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -577,8 +577,8 @@ namespace MWMechanics } } - void Actors::engageCombat(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, - std::map>& cachedAllies, bool againstPlayer) const + void Actors::engageCombat( + const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, SidingCache& cachedAllies, bool againstPlayer) const { // No combat for totally static creatures if (!actor1.getClass().isMobile(actor1)) @@ -606,9 +606,8 @@ namespace MWMechanics // Get actors allied with actor1. Includes those following or escorting actor1, actors following or escorting // those actors, (recursive) and any actor currently being followed or escorted by actor1 - std::set allies1; - - getActorsSidingWith(actor1, allies1, cachedAllies); + const std::set allies1 = cachedAllies.getActorsSidingWith(actor1); + const std::set allies2 = cachedAllies.getActorsSidingWith(actor2); const auto mechanicsManager = MWBase::Environment::get().getMechanicsManager(); // If an ally of actor1 has been attacked by actor2 or has attacked actor2, start combat between actor1 and @@ -620,7 +619,7 @@ namespace MWMechanics if (creatureStats2.matchesActorId(ally.getClass().getCreatureStats(ally).getHitAttemptActorId())) { - mechanicsManager->startCombat(actor1, actor2, nullptr); + mechanicsManager->startCombat(actor1, actor2, &allies2); // Also set the same hit attempt actor. Otherwise, if fighting the player, they may stop combat // if the player gets out of reach, while the ally would continue combat with the player creatureStats1.setHitAttemptActorId(ally.getClass().getCreatureStats(ally).getHitAttemptActorId()); @@ -633,9 +632,8 @@ namespace MWMechanics aggressive = true; } - std::set playerAllies; MWWorld::Ptr player = MWMechanics::getPlayer(); - getActorsSidingWith(player, playerAllies, cachedAllies); + const std::set& playerAllies = cachedAllies.getActorsSidingWith(player); bool isPlayerFollowerOrEscorter = playerAllies.find(actor1) != playerAllies.end(); @@ -646,10 +644,6 @@ namespace MWMechanics // Check that actor2 is in combat with actor1 if (creatureStats2.getAiSequence().isInCombat(actor1)) { - std::set allies2; - - getActorsSidingWith(actor2, allies2, cachedAllies); - // Check that an ally of actor2 is also in combat with actor1 for (const MWWorld::Ptr& ally2 : allies2) { @@ -747,7 +741,7 @@ namespace MWMechanics bool LOS = world->getLOS(actor1, actor2) && mechanicsManager->awarenessCheck(actor2, actor1); if (LOS) - mechanicsManager->startCombat(actor1, actor2, nullptr); + mechanicsManager->startCombat(actor1, actor2, &allies2); } } @@ -1093,7 +1087,7 @@ namespace MWMechanics } } - void Actors::updateCrimePursuit(const MWWorld::Ptr& ptr, float duration) const + void Actors::updateCrimePursuit(const MWWorld::Ptr& ptr, float duration, SidingCache& cachedAllies) const { const MWWorld::Ptr player = getPlayer(); if (ptr == player) @@ -1133,7 +1127,7 @@ namespace MWMechanics = esmStore.get().find("iCrimeThresholdMultiplier")->mValue.getInteger(); if (playerStats.getBounty() >= cutoff * iCrimeThresholdMultiplier) { - mechanicsManager->startCombat(ptr, player, nullptr); + mechanicsManager->startCombat(ptr, player, &cachedAllies.getActorsSidingWith(player)); creatureStats.setHitAttemptActorId( playerClass.getCreatureStats(player) .getActorId()); // Stops the guard from quitting combat if player is unreachable @@ -1508,8 +1502,7 @@ namespace MWMechanics /// \todo move update logic to Actor class where appropriate - std::map> - cachedAllies; // will be filled as engageCombat iterates + SidingCache cachedAllies{ *this, true }; // will be filled as engageCombat iterates const bool aiActive = MWBase::Environment::get().getMechanicsManager()->isAIActive(); const int attackedByPlayerId = player.getClass().getCreatureStats(player).getHitAttemptActorId(); @@ -1597,7 +1590,7 @@ namespace MWMechanics updateHeadTracking(actor.getPtr(), mActors, isPlayer, ctrl); if (actor.getPtr().getClass().isNpc() && !isPlayer) - updateCrimePursuit(actor.getPtr(), duration); + updateCrimePursuit(actor.getPtr(), duration, cachedAllies); if (!isPlayer) { @@ -2156,32 +2149,6 @@ namespace MWMechanics getActorsSidingWith(follower, out, excludeInfighting); } - void Actors::getActorsSidingWith(const MWWorld::Ptr& actor, std::set& out, - std::map>& cachedAllies) const - { - // If we have already found actor's allies, use the cache - std::map>::const_iterator search = cachedAllies.find(actor); - if (search != cachedAllies.end()) - out.insert(search->second.begin(), search->second.end()); - else - { - for (const MWWorld::Ptr& follower : getActorsSidingWith(actor, true)) - if (out.insert(follower).second && follower != actor) - getActorsSidingWith(follower, out, cachedAllies); - - // Cache ptrs and their sets of allies - cachedAllies.insert(std::make_pair(actor, out)); - for (const MWWorld::Ptr& iter : out) - { - if (iter == actor) - continue; - search = cachedAllies.find(iter); - if (search == cachedAllies.end()) - cachedAllies.insert(std::make_pair(iter, out)); - } - } - } - std::vector Actors::getActorsFollowingIndices(const MWWorld::Ptr& actor) const { std::vector list; @@ -2380,4 +2347,32 @@ namespace MWMechanics seq.fastForward(ptr); } } + + const std::set& SidingCache::getActorsSidingWith(const MWWorld::Ptr& actor) + { + // If we have already found actor's allies, use the cache + auto search = mCache.find(actor); + if (search != mCache.end()) + return search->second; + std::set& out = mCache[actor]; + for (const MWWorld::Ptr& follower : mActors.getActorsSidingWith(actor, mExcludeInfighting)) + { + if (out.insert(follower).second && follower != actor) + { + const auto& allies = getActorsSidingWith(follower); + out.insert(allies.begin(), allies.end()); + } + } + + // Cache ptrs and their sets of allies + for (const MWWorld::Ptr& iter : out) + { + if (iter == actor) + continue; + search = mCache.find(iter); + if (search == mCache.end()) + mCache.emplace(iter, out); + } + return out; + } } diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 7c676ca018..b7f3eb0c41 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -36,6 +36,7 @@ namespace MWMechanics class Actor; class CharacterController; class CreatureStats; + class SidingCache; class Actors { @@ -182,7 +183,7 @@ namespace MWMechanics void calculateRestoration(const MWWorld::Ptr& ptr, float duration) const; - void updateCrimePursuit(const MWWorld::Ptr& ptr, float duration) const; + void updateCrimePursuit(const MWWorld::Ptr& ptr, float duration, SidingCache& cachedAllies) const; void killDeadActors(); @@ -194,13 +195,25 @@ namespace MWMechanics @Notes: If againstPlayer = true then actor2 should be the Player. If one of the combatants is creature it should be actor1. */ - void engageCombat(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, - std::map>& cachedAllies, bool againstPlayer) const; + void engageCombat(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, SidingCache& cachedAllies, + bool againstPlayer) const; + }; - /// Recursive version of getActorsSidingWith that takes, adds to and returns a cache of - /// actors mapped to their allies. Excludes infighting - void getActorsSidingWith(const MWWorld::Ptr& actor, std::set& out, - std::map>& cachedAllies) const; + class SidingCache + { + const Actors& mActors; + const bool mExcludeInfighting; + std::map> mCache; + + public: + SidingCache(const Actors& actors, bool excludeInfighting) + : mActors(actors) + , mExcludeInfighting(excludeInfighting) + { + } + + /// Recursive version of getActorsSidingWith that takes, returns a cached set of allies + const std::set& getActorsSidingWith(const MWWorld::Ptr& actor); }; } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 2c556185ce..7a85c31cb3 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -30,6 +30,7 @@ #include "../mwbase/world.hpp" #include "actor.hpp" +#include "actors.hpp" #include "actorutil.hpp" #include "aicombat.hpp" #include "aipursue.hpp" @@ -1508,14 +1509,14 @@ namespace MWMechanics if (!peaceful) { - startCombat(target, attacker, nullptr); + SidingCache cachedAllies{ mActors, false }; + const std::set& attackerAllies = cachedAllies.getActorsSidingWith(attacker); + startCombat(target, attacker, &attackerAllies); // Force friendly actors into combat to prevent infighting between followers - std::set followersTarget; - getActorsSidingWith(target, followersTarget); - for (const auto& follower : followersTarget) + for (const auto& follower : cachedAllies.getActorsSidingWith(target)) { if (follower != attacker && follower != player) - startCombat(follower, attacker, nullptr); + startCombat(follower, attacker, &attackerAllies); } } }