mirror of
				https://github.com/OpenMW/openmw.git
				synced 2025-10-30 02:56:44 +00:00 
			
		
		
		
	Merge branch 'refactor/aisequence-2' into 'master'
#6091: Optimize isInCombat See merge request OpenMW/openmw!1636
This commit is contained in:
		
						commit
						d8127fdad2
					
				
					 6 changed files with 219 additions and 161 deletions
				
			
		|  | @ -113,15 +113,12 @@ namespace MWLua | ||||||
|         { |         { | ||||||
|             const MWWorld::Ptr& ptr = self.ptr(); |             const MWWorld::Ptr& ptr = self.ptr(); | ||||||
|             MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); |             MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); | ||||||
|             std::list<std::shared_ptr<AiPackage>>& list = ai.getUnderlyingList(); | 
 | ||||||
|             for (auto it = list.begin(); it != list.end();) |             ai.erasePackagesIf([&](auto& entry) | ||||||
|             { |             { | ||||||
|                 bool keep = LuaUtil::call(callback, *it).get<bool>(); |                 bool keep = LuaUtil::call(callback, entry).template get<bool>(); | ||||||
|                 if (keep) |                 return !keep; | ||||||
|                     ++it; |             }); | ||||||
|                 else |  | ||||||
|                     it = list.erase(it); |  | ||||||
|             } |  | ||||||
|         }; |         }; | ||||||
|         selfAPI["_startAiCombat"] = [](SelfObject& self, const LObject& target) |         selfAPI["_startAiCombat"] = [](SelfObject& self, const LObject& target) | ||||||
|         { |         { | ||||||
|  |  | ||||||
|  | @ -73,23 +73,20 @@ bool isCommanded(const MWWorld::Ptr& actor) | ||||||
| // Check for command effects having ended and remove package if necessary
 | // Check for command effects having ended and remove package if necessary
 | ||||||
| void adjustCommandedActor (const MWWorld::Ptr& actor) | void adjustCommandedActor (const MWWorld::Ptr& actor) | ||||||
| { | { | ||||||
|  |     if (!isCommanded(actor)) | ||||||
|  |         return; | ||||||
|  | 
 | ||||||
|     MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); |     MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); | ||||||
| 
 | 
 | ||||||
|     bool hasCommandPackage = false; |     stats.getAiSequence().erasePackageIf([](auto& entry) | ||||||
| 
 |  | ||||||
|     auto it = stats.getAiSequence().begin(); |  | ||||||
|     for (; it != stats.getAiSequence().end(); ++it) |  | ||||||
|     { |     { | ||||||
|         if ((*it)->getTypeId() == MWMechanics::AiPackageTypeId::Follow && |         if (entry->getTypeId() == MWMechanics::AiPackageTypeId::Follow && | ||||||
|                 static_cast<const MWMechanics::AiFollow*>(it->get())->isCommanded()) |             static_cast<const MWMechanics::AiFollow*>(entry.get())->isCommanded()) | ||||||
|         { |         { | ||||||
|             hasCommandPackage = true; |             return true; | ||||||
|             break; |  | ||||||
|         } |         } | ||||||
|     } |         return false; | ||||||
| 
 |     }); | ||||||
|     if (!isCommanded(actor) && hasCommandPackage) |  | ||||||
|         stats.getAiSequence().erase(it); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float& magicka) | void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float& magicka) | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| #include "aisequence.hpp" | #include "aisequence.hpp" | ||||||
| 
 | 
 | ||||||
| #include <limits> | #include <limits> | ||||||
|  | #include <algorithm> | ||||||
| 
 | 
 | ||||||
| #include <components/debug/debuglog.hpp> | #include <components/debug/debuglog.hpp> | ||||||
| #include <components/esm3/aisequence.hpp> | #include <components/esm3/aisequence.hpp> | ||||||
|  | @ -29,6 +30,9 @@ void AiSequence::copy (const AiSequence& sequence) | ||||||
|     // We need to keep an AiWander storage, if present - it has a state machine.
 |     // We need to keep an AiWander storage, if present - it has a state machine.
 | ||||||
|     // Not sure about another temporary storages
 |     // Not sure about another temporary storages
 | ||||||
|     sequence.mAiState.copy<AiWanderStorage>(mAiState); |     sequence.mAiState.copy<AiWanderStorage>(mAiState); | ||||||
|  | 
 | ||||||
|  |     mNumCombatPackages = sequence.mNumCombatPackages; | ||||||
|  |     mNumPursuitPackages = sequence.mNumPursuitPackages; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| AiSequence::AiSequence() : mDone (false), mLastAiPackage(AiPackageTypeId::None) {} | AiSequence::AiSequence() : mDone (false), mLastAiPackage(AiPackageTypeId::None) {} | ||||||
|  | @ -58,6 +62,28 @@ AiSequence::~AiSequence() | ||||||
|     clear(); |     clear(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void AiSequence::onPackageAdded(const AiPackage& package) | ||||||
|  | { | ||||||
|  |     if (package.getTypeId() == AiPackageTypeId::Combat) | ||||||
|  |         mNumCombatPackages++; | ||||||
|  |     else if (package.getTypeId() == AiPackageTypeId::Pursue) | ||||||
|  |         mNumPursuitPackages++; | ||||||
|  | 
 | ||||||
|  |     assert(mNumCombatPackages >= 0); | ||||||
|  |     assert(mNumPursuitPackages >= 0); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void AiSequence::onPackageRemoved(const AiPackage& package) | ||||||
|  | { | ||||||
|  |     if (package.getTypeId() == AiPackageTypeId::Combat) | ||||||
|  |         mNumCombatPackages--; | ||||||
|  |     else if (package.getTypeId() == AiPackageTypeId::Pursue) | ||||||
|  |         mNumPursuitPackages--; | ||||||
|  | 
 | ||||||
|  |     assert(mNumCombatPackages >= 0); | ||||||
|  |     assert(mNumPursuitPackages >= 0); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| AiPackageTypeId AiSequence::getTypeId() const | AiPackageTypeId AiSequence::getTypeId() const | ||||||
| { | { | ||||||
|     if (mPackages.empty()) |     if (mPackages.empty()) | ||||||
|  | @ -87,32 +113,30 @@ bool AiSequence::getCombatTargets(std::vector<MWWorld::Ptr> &targetActors) const | ||||||
|     return !targetActors.empty(); |     return !targetActors.empty(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void AiSequence::erase(std::list<std::shared_ptr<AiPackage>>::const_iterator package) | AiPackages::iterator AiSequence::erase(AiPackages::iterator package) | ||||||
| { | { | ||||||
|     // Not sure if manually terminated packages should trigger mDone, probably not?
 |     // Not sure if manually terminated packages should trigger mDone, probably not?
 | ||||||
|     for(auto it = mPackages.begin(); it != mPackages.end(); ++it) |     auto& ptr = *package; | ||||||
|     { |     onPackageRemoved(*ptr); | ||||||
|         if (package == it) | 
 | ||||||
|         { |     return mPackages.erase(package); | ||||||
|             mPackages.erase(it); |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     throw std::runtime_error("can't find package to erase"); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool AiSequence::isInCombat() const | bool AiSequence::isInCombat() const | ||||||
| { | { | ||||||
|     for (auto it = mPackages.begin(); it != mPackages.end(); ++it) |     return mNumCombatPackages > 0; | ||||||
|     { | } | ||||||
|         if ((*it)->getTypeId() == AiPackageTypeId::Combat) | 
 | ||||||
|             return true; | bool AiSequence::isInPursuit() const | ||||||
|     } | { | ||||||
|     return false; |     return mNumPursuitPackages > 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool AiSequence::isEngagedWithActor() const | bool AiSequence::isEngagedWithActor() const | ||||||
| { | { | ||||||
|  |     if (!isInCombat()) | ||||||
|  |         return false; | ||||||
|  | 
 | ||||||
|     for (auto it = mPackages.begin(); it != mPackages.end(); ++it) |     for (auto it = mPackages.begin(); it != mPackages.end(); ++it) | ||||||
|     { |     { | ||||||
|         if ((*it)->getTypeId() == AiPackageTypeId::Combat) |         if ((*it)->getTypeId() == AiPackageTypeId::Combat) | ||||||
|  | @ -127,16 +151,18 @@ bool AiSequence::isEngagedWithActor() const | ||||||
| 
 | 
 | ||||||
| bool AiSequence::hasPackage(AiPackageTypeId typeId) const | bool AiSequence::hasPackage(AiPackageTypeId typeId) const | ||||||
| { | { | ||||||
|     for (auto it = mPackages.begin(); it != mPackages.end(); ++it) |     auto it = std::find_if(mPackages.begin(), mPackages.end(), [typeId](const auto& package) | ||||||
|     { |     { | ||||||
|         if ((*it)->getTypeId() == typeId) |         return package->getTypeId() == typeId; | ||||||
|             return true; |     }); | ||||||
|     } |     return it != mPackages.end(); | ||||||
|     return false; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool AiSequence::isInCombat(const MWWorld::Ptr &actor) const | bool AiSequence::isInCombat(const MWWorld::Ptr &actor) const | ||||||
| { | { | ||||||
|  |     if (!isInCombat()) | ||||||
|  |         return false; | ||||||
|  | 
 | ||||||
|     for (auto it = mPackages.begin(); it != mPackages.end(); ++it) |     for (auto it = mPackages.begin(); it != mPackages.end(); ++it) | ||||||
|     { |     { | ||||||
|         if ((*it)->getTypeId() == AiPackageTypeId::Combat) |         if ((*it)->getTypeId() == AiPackageTypeId::Combat) | ||||||
|  | @ -148,27 +174,31 @@ bool AiSequence::isInCombat(const MWWorld::Ptr &actor) const | ||||||
|     return false; |     return false; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // TODO: use std::list::remove_if for all these methods when we switch to C++20
 | void AiSequence::removePackagesById(AiPackageTypeId id) | ||||||
| void AiSequence::stopCombat() |  | ||||||
| { | { | ||||||
|     for(auto it = mPackages.begin(); it != mPackages.end(); ) |     for (auto it = mPackages.begin(); it != mPackages.end(); ) | ||||||
|     { |     { | ||||||
|         if ((*it)->getTypeId() == AiPackageTypeId::Combat) |         if ((*it)->getTypeId() == id) | ||||||
|         { |         { | ||||||
|             it = mPackages.erase(it); |             it = erase(it); | ||||||
|         } |         } | ||||||
|         else |         else | ||||||
|             ++it; |             ++it; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void AiSequence::stopCombat() | ||||||
|  | { | ||||||
|  |     removePackagesById(AiPackageTypeId::Combat); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void AiSequence::stopCombat(const std::vector<MWWorld::Ptr>& targets) | void AiSequence::stopCombat(const std::vector<MWWorld::Ptr>& targets) | ||||||
| { | { | ||||||
|     for(auto it = mPackages.begin(); it != mPackages.end(); ) |     for(auto it = mPackages.begin(); it != mPackages.end(); ) | ||||||
|     { |     { | ||||||
|         if ((*it)->getTypeId() == AiPackageTypeId::Combat && std::find(targets.begin(), targets.end(), (*it)->getTarget()) != targets.end()) |         if ((*it)->getTypeId() == AiPackageTypeId::Combat && std::find(targets.begin(), targets.end(), (*it)->getTarget()) != targets.end()) | ||||||
|         { |         { | ||||||
|             it = mPackages.erase(it); |             it = erase(it); | ||||||
|         } |         } | ||||||
|         else |         else | ||||||
|             ++it; |             ++it; | ||||||
|  | @ -177,15 +207,7 @@ void AiSequence::stopCombat(const std::vector<MWWorld::Ptr>& targets) | ||||||
| 
 | 
 | ||||||
| void AiSequence::stopPursuit() | void AiSequence::stopPursuit() | ||||||
| { | { | ||||||
|     for(auto it = mPackages.begin(); it != mPackages.end(); ) |     removePackagesById(AiPackageTypeId::Pursue); | ||||||
|     { |  | ||||||
|         if ((*it)->getTypeId() == AiPackageTypeId::Pursue) |  | ||||||
|         { |  | ||||||
|             it = mPackages.erase(it); |  | ||||||
|         } |  | ||||||
|         else |  | ||||||
|             ++it; |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool AiSequence::isPackageDone() const | bool AiSequence::isPackageDone() const | ||||||
|  | @ -204,112 +226,117 @@ namespace | ||||||
| 
 | 
 | ||||||
| void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& characterController, float duration, bool outOfRange) | void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& characterController, float duration, bool outOfRange) | ||||||
| { | { | ||||||
|     if(actor != getPlayer()) |     if (actor == getPlayer()) | ||||||
|     { |     { | ||||||
|         if (mPackages.empty()) |         // Players don't use this.
 | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (mPackages.empty()) | ||||||
|  |     { | ||||||
|  |         mLastAiPackage = AiPackageTypeId::None; | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     auto packageIt = mPackages.begin(); | ||||||
|  |     MWMechanics::AiPackage* package = packageIt->get(); | ||||||
|  |     if (!package->alwaysActive() && outOfRange) | ||||||
|  |         return; | ||||||
|  | 
 | ||||||
|  |     auto packageTypeId = package->getTypeId(); | ||||||
|  |     // workaround ai packages not being handled as in the vanilla engine
 | ||||||
|  |     if (isActualAiPackage(packageTypeId)) | ||||||
|  |         mLastAiPackage = packageTypeId; | ||||||
|  |     // if active package is combat one, choose nearest target
 | ||||||
|  |     if (packageTypeId == AiPackageTypeId::Combat) | ||||||
|  |     { | ||||||
|  |         auto itActualCombat = mPackages.end(); | ||||||
|  | 
 | ||||||
|  |         float nearestDist = std::numeric_limits<float>::max(); | ||||||
|  |         osg::Vec3f vActorPos = actor.getRefData().getPosition().asVec3(); | ||||||
|  | 
 | ||||||
|  |         float bestRating = 0.f; | ||||||
|  | 
 | ||||||
|  |         for (auto it = mPackages.begin(); it != mPackages.end();) | ||||||
|         { |         { | ||||||
|             mLastAiPackage = AiPackageTypeId::None; |             if ((*it)->getTypeId() != AiPackageTypeId::Combat) break; | ||||||
|             return; |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         auto packageIt = mPackages.begin(); |             MWWorld::Ptr target = (*it)->getTarget(); | ||||||
|         MWMechanics::AiPackage* package = packageIt->get(); |  | ||||||
|         if (!package->alwaysActive() && outOfRange) |  | ||||||
|             return; |  | ||||||
| 
 | 
 | ||||||
|         auto packageTypeId = package->getTypeId(); |             // target disappeared (e.g. summoned creatures)
 | ||||||
|         // workaround ai packages not being handled as in the vanilla engine
 |             if (target.isEmpty()) | ||||||
|         if (isActualAiPackage(packageTypeId)) |  | ||||||
|             mLastAiPackage = packageTypeId; |  | ||||||
|         // if active package is combat one, choose nearest target
 |  | ||||||
|         if (packageTypeId == AiPackageTypeId::Combat) |  | ||||||
|         { |  | ||||||
|             auto itActualCombat = mPackages.end(); |  | ||||||
| 
 |  | ||||||
|             float nearestDist = std::numeric_limits<float>::max(); |  | ||||||
|             osg::Vec3f vActorPos = actor.getRefData().getPosition().asVec3(); |  | ||||||
| 
 |  | ||||||
|             float bestRating = 0.f; |  | ||||||
| 
 |  | ||||||
|             for (auto it = mPackages.begin(); it != mPackages.end();) |  | ||||||
|             { |             { | ||||||
|                 if ((*it)->getTypeId() != AiPackageTypeId::Combat) break; |                 it = erase(it); | ||||||
| 
 |  | ||||||
|                 MWWorld::Ptr target = (*it)->getTarget(); |  | ||||||
| 
 |  | ||||||
|                 // target disappeared (e.g. summoned creatures)
 |  | ||||||
|                 if (target.isEmpty()) |  | ||||||
|                 { |  | ||||||
|                     it = mPackages.erase(it); |  | ||||||
|                 } |  | ||||||
|                 else |  | ||||||
|                 { |  | ||||||
|                     float rating = MWMechanics::getBestActionRating(actor, target); |  | ||||||
| 
 |  | ||||||
|                     const ESM::Position &targetPos = target.getRefData().getPosition(); |  | ||||||
| 
 |  | ||||||
|                     float distTo = (targetPos.asVec3() - vActorPos).length2(); |  | ||||||
| 
 |  | ||||||
|                     // Small threshold for changing target
 |  | ||||||
|                     if (it == mPackages.begin()) |  | ||||||
|                         distTo = std::max(0.f, distTo - 2500.f); |  | ||||||
| 
 |  | ||||||
|                     // if a target has higher priority than current target or has same priority but closer
 |  | ||||||
|                     if (rating > bestRating || ((distTo < nearestDist) && rating == bestRating)) |  | ||||||
|                     { |  | ||||||
|                         nearestDist = distTo; |  | ||||||
|                         itActualCombat = it; |  | ||||||
|                         bestRating = rating; |  | ||||||
|                     } |  | ||||||
|                     ++it; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             assert(!mPackages.empty()); |  | ||||||
| 
 |  | ||||||
|             if (nearestDist < std::numeric_limits<float>::max() && mPackages.begin() != itActualCombat) |  | ||||||
|             { |  | ||||||
|                 assert(itActualCombat != mPackages.end()); |  | ||||||
|                 // move combat package with nearest target to the front
 |  | ||||||
|                 mPackages.splice(mPackages.begin(), mPackages, itActualCombat); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             packageIt = mPackages.begin(); |  | ||||||
|             package = packageIt->get(); |  | ||||||
|             packageTypeId = package->getTypeId(); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         try |  | ||||||
|         { |  | ||||||
|             if (package->execute(actor, characterController, mAiState, duration)) |  | ||||||
|             { |  | ||||||
|                 // Put repeating noncombat AI packages on the end of the stack so they can be used again
 |  | ||||||
|                 if (isActualAiPackage(packageTypeId) && package->getRepeat()) |  | ||||||
|                 { |  | ||||||
|                     package->reset(); |  | ||||||
|                     mPackages.push_back(package->clone()); |  | ||||||
|                 } |  | ||||||
|                 // To account for the rare case where AiPackage::execute() queued another AI package
 |  | ||||||
|                 // (e.g. AiPursue executing a dialogue script that uses startCombat)
 |  | ||||||
|                 mPackages.erase(packageIt); |  | ||||||
|                 if (isActualAiPackage(packageTypeId)) |  | ||||||
|                     mDone = true; |  | ||||||
|             } |             } | ||||||
|             else |             else | ||||||
|             { |             { | ||||||
|                 mDone = false; |                 float rating = MWMechanics::getBestActionRating(actor, target); | ||||||
|  | 
 | ||||||
|  |                 const ESM::Position &targetPos = target.getRefData().getPosition(); | ||||||
|  | 
 | ||||||
|  |                 float distTo = (targetPos.asVec3() - vActorPos).length2(); | ||||||
|  | 
 | ||||||
|  |                 // Small threshold for changing target
 | ||||||
|  |                 if (it == mPackages.begin()) | ||||||
|  |                     distTo = std::max(0.f, distTo - 2500.f); | ||||||
|  | 
 | ||||||
|  |                 // if a target has higher priority than current target or has same priority but closer
 | ||||||
|  |                 if (rating > bestRating || ((distTo < nearestDist) && rating == bestRating)) | ||||||
|  |                 { | ||||||
|  |                     nearestDist = distTo; | ||||||
|  |                     itActualCombat = it; | ||||||
|  |                     bestRating = rating; | ||||||
|  |                 } | ||||||
|  |                 ++it; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         catch (std::exception& e) | 
 | ||||||
|  |         assert(!mPackages.empty()); | ||||||
|  | 
 | ||||||
|  |         if (nearestDist < std::numeric_limits<float>::max() && mPackages.begin() != itActualCombat) | ||||||
|         { |         { | ||||||
|             Log(Debug::Error) << "Error during AiSequence::execute: " << e.what(); |             assert(itActualCombat != mPackages.end()); | ||||||
|  |             // move combat package with nearest target to the front
 | ||||||
|  |             std::rotate(mPackages.begin(), itActualCombat, std::next(itActualCombat)); | ||||||
|         } |         } | ||||||
|  | 
 | ||||||
|  |         packageIt = mPackages.begin(); | ||||||
|  |         package = packageIt->get(); | ||||||
|  |         packageTypeId = package->getTypeId(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     try | ||||||
|  |     { | ||||||
|  |         if (package->execute(actor, characterController, mAiState, duration)) | ||||||
|  |         { | ||||||
|  |             // Put repeating noncombat AI packages on the end of the stack so they can be used again
 | ||||||
|  |             if (isActualAiPackage(packageTypeId) && package->getRepeat()) | ||||||
|  |             { | ||||||
|  |                 package->reset(); | ||||||
|  |                 mPackages.push_back(package->clone()); | ||||||
|  |             } | ||||||
|  |             // To account for the rare case where AiPackage::execute() queued another AI package
 | ||||||
|  |             // (e.g. AiPursue executing a dialogue script that uses startCombat)
 | ||||||
|  |             erase(packageIt); | ||||||
|  |             if (isActualAiPackage(packageTypeId)) | ||||||
|  |                 mDone = true; | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             mDone = false; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     catch (std::exception& e) | ||||||
|  |     { | ||||||
|  |         Log(Debug::Error) << "Error during AiSequence::execute: " << e.what(); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void AiSequence::clear() | void AiSequence::clear() | ||||||
| { | { | ||||||
|     mPackages.clear(); |     mPackages.clear(); | ||||||
|  |     mNumCombatPackages = 0; | ||||||
|  |     mNumPursuitPackages = 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor, bool cancelOther) | void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor, bool cancelOther) | ||||||
|  | @ -353,7 +380,7 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor, boo | ||||||
|         { |         { | ||||||
|             if((*it)->canCancel()) |             if((*it)->canCancel()) | ||||||
|             { |             { | ||||||
|                 it = mPackages.erase(it); |                 it = erase(it); | ||||||
|             } |             } | ||||||
|             else |             else | ||||||
|                 ++it; |                 ++it; | ||||||
|  | @ -373,11 +400,13 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor, boo | ||||||
| 
 | 
 | ||||||
|         if((*it)->getPriority() <= package.getPriority()) |         if((*it)->getPriority() <= package.getPriority()) | ||||||
|         { |         { | ||||||
|  |             onPackageAdded(package); | ||||||
|             mPackages.insert(it, package.clone()); |             mPackages.insert(it, package.clone()); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     onPackageAdded(package); | ||||||
|     mPackages.push_back(package.clone()); |     mPackages.push_back(package.clone()); | ||||||
| 
 | 
 | ||||||
|     // Make sure that temporary storage is empty
 |     // Make sure that temporary storage is empty
 | ||||||
|  | @ -435,6 +464,8 @@ void AiSequence::fill(const ESM::AIPackageList &list) | ||||||
|             ESM::AITarget data = esmPackage.mTarget; |             ESM::AITarget data = esmPackage.mTarget; | ||||||
|             package = std::make_unique<MWMechanics::AiFollow>(data.mId.toStringView(), data.mDuration, data.mX, data.mY, data.mZ, data.mShouldRepeat != 0); |             package = std::make_unique<MWMechanics::AiFollow>(data.mId.toStringView(), data.mDuration, data.mX, data.mY, data.mZ, data.mShouldRepeat != 0); | ||||||
|         } |         } | ||||||
|  | 
 | ||||||
|  |         onPackageAdded(*package); | ||||||
|         mPackages.push_back(std::move(package)); |         mPackages.push_back(std::move(package)); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -504,6 +535,7 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence) | ||||||
|         if (!package.get()) |         if (!package.get()) | ||||||
|             continue; |             continue; | ||||||
| 
 | 
 | ||||||
|  |         onPackageAdded(*package); | ||||||
|         mPackages.push_back(std::move(package)); |         mPackages.push_back(std::move(package)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,8 +1,9 @@ | ||||||
| #ifndef GAME_MWMECHANICS_AISEQUENCE_H | #ifndef GAME_MWMECHANICS_AISEQUENCE_H | ||||||
| #define GAME_MWMECHANICS_AISEQUENCE_H | #define GAME_MWMECHANICS_AISEQUENCE_H | ||||||
| 
 | 
 | ||||||
| #include <list> |  | ||||||
| #include <memory> | #include <memory> | ||||||
|  | #include <vector> | ||||||
|  | #include <algorithm> | ||||||
| 
 | 
 | ||||||
| #include "aistate.hpp" | #include "aistate.hpp" | ||||||
| #include "aipackagetypeid.hpp" | #include "aipackagetypeid.hpp" | ||||||
|  | @ -22,8 +23,6 @@ namespace ESM | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| namespace MWMechanics | namespace MWMechanics | ||||||
| { | { | ||||||
|     class AiPackage; |     class AiPackage; | ||||||
|  | @ -33,15 +32,20 @@ namespace MWMechanics | ||||||
|     struct AiTemporaryBase; |     struct AiTemporaryBase; | ||||||
|     typedef DerivedClassStorage<AiTemporaryBase> AiState; |     typedef DerivedClassStorage<AiTemporaryBase> AiState; | ||||||
| 
 | 
 | ||||||
|  |     using AiPackages = std::vector<std::shared_ptr<AiPackage>>; | ||||||
|  | 
 | ||||||
|     /// \brief Sequence of AI-packages for a single actor
 |     /// \brief Sequence of AI-packages for a single actor
 | ||||||
|     /** The top-most AI package is run each frame. When completed, it is removed from the stack. **/ |     /** The top-most AI package is run each frame. When completed, it is removed from the stack. **/ | ||||||
|     class AiSequence |     class AiSequence | ||||||
|     { |     { | ||||||
|             ///AiPackages to run though
 |             ///AiPackages to run though
 | ||||||
|             std::list<std::shared_ptr<AiPackage>> mPackages; |             AiPackages mPackages; | ||||||
| 
 | 
 | ||||||
|             ///Finished with top AIPackage, set for one frame
 |             ///Finished with top AIPackage, set for one frame
 | ||||||
|             bool mDone; |             bool mDone{}; | ||||||
|  | 
 | ||||||
|  |             int mNumCombatPackages{}; | ||||||
|  |             int mNumPursuitPackages{}; | ||||||
| 
 | 
 | ||||||
|             ///Copy AiSequence
 |             ///Copy AiSequence
 | ||||||
|             void copy (const AiSequence& sequence); |             void copy (const AiSequence& sequence); | ||||||
|  | @ -50,6 +54,11 @@ namespace MWMechanics | ||||||
|             AiPackageTypeId mLastAiPackage; |             AiPackageTypeId mLastAiPackage; | ||||||
|             AiState mAiState; |             AiState mAiState; | ||||||
| 
 | 
 | ||||||
|  |             void onPackageAdded(const AiPackage& package); | ||||||
|  |             void onPackageRemoved(const AiPackage& package); | ||||||
|  | 
 | ||||||
|  |             AiPackages::iterator erase(AiPackages::iterator package); | ||||||
|  | 
 | ||||||
|         public: |         public: | ||||||
|             ///Default constructor
 |             ///Default constructor
 | ||||||
|             AiSequence(); |             AiSequence(); | ||||||
|  | @ -63,12 +72,31 @@ namespace MWMechanics | ||||||
|             virtual ~AiSequence(); |             virtual ~AiSequence(); | ||||||
| 
 | 
 | ||||||
|             /// Iterator may be invalidated by any function calls other than begin() or end().
 |             /// Iterator may be invalidated by any function calls other than begin() or end().
 | ||||||
|             std::list<std::shared_ptr<AiPackage>>::const_iterator begin() const { return mPackages.begin(); } |             AiPackages::const_iterator begin() const { return mPackages.begin(); } | ||||||
|             std::list<std::shared_ptr<AiPackage>>::const_iterator end() const { return mPackages.end(); } |             AiPackages::const_iterator end() const { return mPackages.end(); } | ||||||
| 
 | 
 | ||||||
|             void erase(std::list<std::shared_ptr<AiPackage>>::const_iterator package); |             /// Removes all packages controlled by the predicate.
 | ||||||
|  |             template<typename F> | ||||||
|  |             void erasePackagesIf(const F&& pred) | ||||||
|  |             { | ||||||
|  |                 mPackages.erase(std::remove_if(mPackages.begin(), mPackages.end(), [&](auto& entry) | ||||||
|  |                 { | ||||||
|  |                     const bool doRemove = pred(entry); | ||||||
|  |                     if (doRemove) | ||||||
|  |                         onPackageRemoved(*entry); | ||||||
|  |                     return doRemove; | ||||||
|  |                 }), mPackages.end()); | ||||||
|  |             } | ||||||
| 
 | 
 | ||||||
|             std::list<std::shared_ptr<AiPackage>>& getUnderlyingList() { return mPackages; } |             /// Removes a single package controlled by the predicate.
 | ||||||
|  |             template<typename F> | ||||||
|  |             void erasePackageIf(const F&& pred) | ||||||
|  |             { | ||||||
|  |                 auto it = std::find_if(mPackages.begin(), mPackages.end(), pred); | ||||||
|  |                 if (it == mPackages.end()) | ||||||
|  |                     return; | ||||||
|  |                 erase(it); | ||||||
|  |             } | ||||||
| 
 | 
 | ||||||
|             /// Returns currently executing AiPackage type
 |             /// Returns currently executing AiPackage type
 | ||||||
|             /** \see enum class AiPackageTypeId **/ |             /** \see enum class AiPackageTypeId **/ | ||||||
|  | @ -89,6 +117,12 @@ namespace MWMechanics | ||||||
|             /// Is there any combat package?
 |             /// Is there any combat package?
 | ||||||
|             bool isInCombat () const; |             bool isInCombat () const; | ||||||
| 
 | 
 | ||||||
|  |             /// Is there any pursuit package.
 | ||||||
|  |             bool isInPursuit() const; | ||||||
|  | 
 | ||||||
|  |             /// Removes all packages using the specified id.
 | ||||||
|  |             void removePackagesById(AiPackageTypeId id); | ||||||
|  | 
 | ||||||
|             /// Are we in combat with any other actor, who's also engaging us?
 |             /// Are we in combat with any other actor, who's also engaging us?
 | ||||||
|             bool isEngagedWithActor () const; |             bool isEngagedWithActor () const; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1318,7 +1318,7 @@ namespace MWMechanics | ||||||
|                 // once the bounty has been paid.
 |                 // once the bounty has been paid.
 | ||||||
|                 actor.getClass().getNpcStats(actor).setCrimeId(id); |                 actor.getClass().getNpcStats(actor).setCrimeId(id); | ||||||
| 
 | 
 | ||||||
|                 if (!actor.getClass().getCreatureStats(actor).getAiSequence().hasPackage(AiPackageTypeId::Pursue)) |                 if (!actor.getClass().getCreatureStats(actor).getAiSequence().isInPursuit()) | ||||||
|                 { |                 { | ||||||
|                     actor.getClass().getCreatureStats(actor).getAiSequence().stack(AiPursue(player), actor); |                     actor.getClass().getCreatureStats(actor).getAiSequence().stack(AiPursue(player), actor); | ||||||
|                 } |                 } | ||||||
|  | @ -1396,7 +1396,7 @@ namespace MWMechanics | ||||||
|             { |             { | ||||||
|                 // Attacker is in combat with us, but we are not in combat with the attacker yet. Time to fight back.
 |                 // Attacker is in combat with us, but we are not in combat with the attacker yet. Time to fight back.
 | ||||||
|                 // Note: accidental or collateral damage attacks are ignored.
 |                 // Note: accidental or collateral damage attacks are ignored.
 | ||||||
|                 if (!victim.getClass().getCreatureStats(victim).getAiSequence().hasPackage(AiPackageTypeId::Pursue)) |                 if (!victim.getClass().getCreatureStats(victim).getAiSequence().isInPursuit()) | ||||||
|                     startCombat(victim, player); |                     startCombat(victim, player); | ||||||
| 
 | 
 | ||||||
|                 // Set the crime ID, which we will use to calm down participants
 |                 // Set the crime ID, which we will use to calm down participants
 | ||||||
|  | @ -1442,7 +1442,7 @@ namespace MWMechanics | ||||||
|         { |         { | ||||||
|             // Attacker is in combat with us, but we are not in combat with the attacker yet. Time to fight back.
 |             // Attacker is in combat with us, but we are not in combat with the attacker yet. Time to fight back.
 | ||||||
|             // Note: accidental or collateral damage attacks are ignored.
 |             // Note: accidental or collateral damage attacks are ignored.
 | ||||||
|             if (!target.getClass().getCreatureStats(target).getAiSequence().hasPackage(AiPackageTypeId::Pursue)) |             if (!target.getClass().getCreatureStats(target).getAiSequence().isInPursuit()) | ||||||
|             { |             { | ||||||
|                 // If an actor has OnPCHitMe declared in his script, his Fight = 0 and the attacker is player,
 |                 // If an actor has OnPCHitMe declared in his script, his Fight = 0 and the attacker is player,
 | ||||||
|                 // he will attack the player only if we will force him (e.g. via StartCombat console command)
 |                 // he will attack the player only if we will force him (e.g. via StartCombat console command)
 | ||||||
|  | @ -1467,7 +1467,7 @@ namespace MWMechanics | ||||||
|         const MWMechanics::AiSequence& seq = target.getClass().getCreatureStats(target).getAiSequence(); |         const MWMechanics::AiSequence& seq = target.getClass().getCreatureStats(target).getAiSequence(); | ||||||
|         return target.getClass().isNpc() && !attacker.isEmpty() && !seq.isInCombat(attacker) |         return target.getClass().isNpc() && !attacker.isEmpty() && !seq.isInCombat(attacker) | ||||||
|                 && !isAggressive(target, attacker) && !seq.isEngagedWithActor() |                 && !isAggressive(target, attacker) && !seq.isEngagedWithActor() | ||||||
|                 && !target.getClass().getCreatureStats(target).getAiSequence().hasPackage(AiPackageTypeId::Pursue); |                 && !target.getClass().getCreatureStats(target).getAiSequence().isInPursuit(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     void MechanicsManager::actorKilled(const MWWorld::Ptr &victim, const MWWorld::Ptr &attacker) |     void MechanicsManager::actorKilled(const MWWorld::Ptr &victim, const MWWorld::Ptr &attacker) | ||||||
|  |  | ||||||
|  | @ -979,12 +979,10 @@ void removeMagicEffect(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellPara | ||||||
|             if(magnitudes.get(effect.mEffectId).getMagnitude() <= 0.f) |             if(magnitudes.get(effect.mEffectId).getMagnitude() <= 0.f) | ||||||
|             { |             { | ||||||
|                 auto& seq = target.getClass().getCreatureStats(target).getAiSequence(); |                 auto& seq = target.getClass().getCreatureStats(target).getAiSequence(); | ||||||
|                 auto it = std::find_if(seq.begin(), seq.end(), [&](const auto& package) |                 seq.erasePackageIf([&](const auto& package) | ||||||
|                 { |                 { | ||||||
|                     return package->getTypeId() == MWMechanics::AiPackageTypeId::Follow && static_cast<const MWMechanics::AiFollow*>(package.get())->isCommanded(); |                     return package->getTypeId() == MWMechanics::AiPackageTypeId::Follow && static_cast<const MWMechanics::AiFollow*>(package.get())->isCommanded(); | ||||||
|                 }); |                 }); | ||||||
|                 if(it != seq.end()) |  | ||||||
|                     seq.erase(it); |  | ||||||
|             } |             } | ||||||
|             break; |             break; | ||||||
|         case ESM::MagicEffect::ExtraSpell: |         case ESM::MagicEffect::ExtraSpell: | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue