diff --git a/CHANGELOG.md b/CHANGELOG.md index b75a179b1d..c9f3d18161 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ Bug #5415: Environment maps in ebony cuirass and HiRez Armors Indoril cuirass don't work Bug #5416: Junk non-node records before the root node are not handled gracefully Bug #5422: The player loses all spells when resurrected + Bug #5423: Guar follows actors too closely Bug #5424: Creatures do not headtrack player Bug #5425: Poison effect only appears for one frame Bug #5427: GetDistance unknown ID error is misleading diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index b6ce4d0619..967504552d 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -199,6 +199,7 @@ namespace MWBase virtual std::list getActorsSidingWith(const MWWorld::Ptr& actor) = 0; virtual std::list getActorsFollowing(const MWWorld::Ptr& actor) = 0; virtual std::list getActorsFollowingIndices(const MWWorld::Ptr& actor) = 0; + virtual std::map getActorsFollowingByIndex(const MWWorld::Ptr& actor) = 0; ///Returns a list of actors who are fighting the given actor within the fAlarmDistance /** ie AiCombat is active and the target is the actor **/ diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 047741baca..88c402b14c 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -142,6 +142,29 @@ void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float magicka = fRestMagicMult * stats.getAttribute(ESM::Attribute::Intelligence).getModified(); } +template +void forEachFollowingPackage(MWMechanics::Actors::PtrActorMap& actors, const MWWorld::Ptr& actor, const MWWorld::Ptr& player, T&& func) +{ + for(auto& iter : actors) + { + const MWWorld::Ptr &iteratedActor = iter.first; + if (iteratedActor == player || iteratedActor == actor) + continue; + + const MWMechanics::CreatureStats &stats = iteratedActor.getClass().getCreatureStats(iteratedActor); + if (stats.isDead()) + continue; + + // An actor counts as following if AiFollow is the current AiPackage, + // or there are only Combat and Wander packages before the AiFollow package + for (const auto& package : stats.getAiSequence()) + { + if(!func(iter, package)) + break; + } + } +} + } namespace MWMechanics @@ -2512,26 +2535,14 @@ namespace MWMechanics std::list Actors::getActorsFollowing(const MWWorld::Ptr& actor) { std::list list; - for(PtrActorMap::iterator iter(mActors.begin());iter != mActors.end();++iter) + forEachFollowingPackage(mActors, actor, getPlayer(), [&] (auto& iter, const std::unique_ptr& package) { - const MWWorld::Ptr &iteratedActor = iter->first; - if (iteratedActor == getPlayer() || iteratedActor == actor) - continue; - - const CreatureStats &stats = iteratedActor.getClass().getCreatureStats(iteratedActor); - if (stats.isDead()) - continue; - - // An actor counts as following if AiFollow is the current AiPackage, - // or there are only Combat and Wander packages before the AiFollow package - for (const auto& package : stats.getAiSequence()) - { - if (package->followTargetThroughDoors() && package->getTarget() == actor) - list.push_back(iteratedActor); - else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander) - break; - } - } + if (package->followTargetThroughDoors() && package->getTarget() == actor) + list.push_back(iter.first); + else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander) + return false; + return true; + }); return list; } @@ -2575,32 +2586,38 @@ namespace MWMechanics std::list Actors::getActorsFollowingIndices(const MWWorld::Ptr &actor) { std::list list; - for(PtrActorMap::iterator iter(mActors.begin());iter != mActors.end();++iter) + forEachFollowingPackage(mActors, actor, getPlayer(), [&] (auto& iter, const std::unique_ptr& package) { - const MWWorld::Ptr &iteratedActor = iter->first; - if (iteratedActor == getPlayer() || iteratedActor == actor) - continue; - - const CreatureStats &stats = iteratedActor.getClass().getCreatureStats(iteratedActor); - if (stats.isDead()) - continue; - - // An actor counts as following if AiFollow is the current AiPackage, - // or there are only Combat and Wander packages before the AiFollow package - for (const auto& package : stats.getAiSequence()) + if (package->followTargetThroughDoors() && package->getTarget() == actor) { - if (package->followTargetThroughDoors() && package->getTarget() == actor) - { - list.push_back(static_cast(package.get())->getFollowIndex()); - break; - } - else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander) - break; + list.push_back(static_cast(package.get())->getFollowIndex()); + return false; } - } + else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander) + return false; + return true; + }); return list; } + std::map Actors::getActorsFollowingByIndex(const MWWorld::Ptr &actor) + { + std::map map; + forEachFollowingPackage(mActors, actor, getPlayer(), [&] (auto& iter, const std::unique_ptr& package) + { + if (package->followTargetThroughDoors() && package->getTarget() == actor) + { + int index = static_cast(package.get())->getFollowIndex(); + map[index] = iter.first; + return false; + } + else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander) + return false; + return true; + }); + return map; + } + std::list Actors::getActorsFighting(const MWWorld::Ptr& actor) { std::list list; std::vector neighbors; diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 4535400016..59814bcc22 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -181,6 +181,7 @@ namespace MWMechanics /// Get the list of AiFollow::mFollowIndex for all actors following this target std::list getActorsFollowingIndices(const MWWorld::Ptr& actor); + std::map getActorsFollowingByIndex(const MWWorld::Ptr& actor); ///Returns the list of actors which are fighting the given actor /**ie AiCombat is active and the target is the actor **/ diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index b3c308d75f..1e4fb206e3 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -113,24 +113,23 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte if (!mActive) return false; - // The distances below are approximations based on observations of the original engine. - // If only one actor is following the target, it uses 186. - // If there are multiple actors following the same target, they form a group with each group member at 313 + (130 * i) distance to the target. - - short followDistance = 186; - std::list followers = MWBase::Environment::get().getMechanicsManager()->getActorsFollowingIndices(target); - if (followers.size() >= 2) + // In the original engine the first follower stays closer to the player than any subsequent followers. + // Followers beyond the first usually attempt to stand inside each other. + osg::Vec3f::value_type floatingDistance = 0; + auto followers = MWBase::Environment::get().getMechanicsManager()->getActorsFollowingByIndex(target); + if (followers.size() >= 2 && followers.cbegin()->first != mFollowIndex) { - followDistance = 313; - short i = 0; - followers.sort(); - for (int followIndex : followers) + osg::Vec3f::value_type maxSize = 0; + for(auto& follower : followers) { - if (followIndex == mFollowIndex) - followDistance += 130 * i; - ++i; + auto halfExtent = MWBase::Environment::get().getWorld()->getHalfExtents(follower.second).y(); + if(halfExtent > floatingDistance) + floatingDistance = halfExtent; } } + floatingDistance += MWBase::Environment::get().getWorld()->getHalfExtents(target).y(); + floatingDistance += MWBase::Environment::get().getWorld()->getHalfExtents(actor).y() * 2; + short followDistance = static_cast(floatingDistance); if (!mAlwaysFollow) //Update if you only follow for a bit { diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index b1db2562b2..fb67218532 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1654,6 +1654,11 @@ namespace MWMechanics return mActors.getActorsFollowingIndices(actor); } + std::map MechanicsManager::getActorsFollowingByIndex(const MWWorld::Ptr& actor) + { + return mActors.getActorsFollowingByIndex(actor); + } + std::list MechanicsManager::getActorsFighting(const MWWorld::Ptr& actor) { return mActors.getActorsFighting(actor); } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 28f62b7774..3f2c3f5e98 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -150,6 +150,7 @@ namespace MWMechanics std::list getActorsSidingWith(const MWWorld::Ptr& actor) override; std::list getActorsFollowing(const MWWorld::Ptr& actor) override; std::list getActorsFollowingIndices(const MWWorld::Ptr& actor) override; + std::map getActorsFollowingByIndex(const MWWorld::Ptr& actor) override; std::list getActorsFighting(const MWWorld::Ptr& actor) override; std::list getEnemiesNearby(const MWWorld::Ptr& actor) override;