diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a87cf7a80..9fd37ac9f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ Bug #5129: Stuttering animation on Centurion Archer Bug #5280: Unskinned shapes in skinned equipment are rendered in the wrong place Bug #5371: Keyframe animation tracks are used for any file that begins with an X + Bug #5413: Enemies do a battlecry everytime the player summons a creature Bug #5714: Touch spells cast using ExplodeSpell don't always explode Bug #5849: Paralysis breaks landing Bug #5870: Disposing of actors who were selected in the console doesn't deselect them like vanilla @@ -132,6 +133,7 @@ Bug #7758: Water walking is not taken into account to compute path cost on the water Bug #7761: Rain and ambient loop sounds are mutually exclusive Bug #7765: OpenMW-CS: Touch Record option is broken + Bug #7769: Sword of the Perithia: Broken NPCs Bug #7770: Sword of the Perithia: Script execution failure Bug #7780: Non-ASCII texture paths in NIF files don't work Bug #7785: OpenMW-CS initialising Skill and Attribute fields to 0 instead of -1 on non-FortifyStat spells diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index d7e377a0eb..c8e353acc9 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -110,7 +110,9 @@ namespace MWBase virtual bool awarenessCheck(const MWWorld::Ptr& ptr, const MWWorld::Ptr& observer) = 0; /// Makes \a ptr fight \a target. Also shouts a combat taunt. - virtual void startCombat(const MWWorld::Ptr& ptr, const MWWorld::Ptr& target) = 0; + virtual void startCombat( + const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, const std::set* targetAllies) + = 0; /// Removes an actor and its allies from combat with the actor's targets. virtual void stopCombat(const MWWorld::Ptr& ptr) = 0; diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index a4e7168b0f..d463fa729b 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,7 @@ 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 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 +618,7 @@ namespace MWMechanics if (creatureStats2.matchesActorId(ally.getClass().getCreatureStats(ally).getHitAttemptActorId())) { - mechanicsManager->startCombat(actor1, actor2); + mechanicsManager->startCombat(actor1, actor2, &cachedAllies.getActorsSidingWith(actor2)); // 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 +631,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,20 +643,17 @@ namespace MWMechanics // Check that actor2 is in combat with actor1 if (creatureStats2.getAiSequence().isInCombat(actor1)) { - std::set allies2; - - getActorsSidingWith(actor2, allies2, cachedAllies); - + const std::set& allies2 = cachedAllies.getActorsSidingWith(actor2); // Check that an ally of actor2 is also in combat with actor1 for (const MWWorld::Ptr& ally2 : allies2) { if (ally2 != actor2 && ally2.getClass().getCreatureStats(ally2).getAiSequence().isInCombat(actor1)) { - mechanicsManager->startCombat(actor1, actor2); + mechanicsManager->startCombat(actor1, actor2, &allies2); // Also have actor1's allies start combat for (const MWWorld::Ptr& ally1 : allies1) if (ally1 != player) - mechanicsManager->startCombat(ally1, actor2); + mechanicsManager->startCombat(ally1, actor2, &allies2); return; } } @@ -747,7 +741,7 @@ namespace MWMechanics bool LOS = world->getLOS(actor1, actor2) && mechanicsManager->awarenessCheck(actor2, actor1); if (LOS) - mechanicsManager->startCombat(actor1, actor2); + mechanicsManager->startCombat(actor1, actor2, &cachedAllies.getActorsSidingWith(actor2)); } } @@ -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); + 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) { @@ -2115,7 +2108,7 @@ namespace MWMechanics for (const auto& package : stats.getAiSequence()) { if (excludeInfighting && !sameActor && package->getTypeId() == AiPackageTypeId::Combat - && package->getTarget() == actorPtr) + && package->targetIs(actorPtr)) break; if (package->sideWithTarget() && !package->getTarget().isEmpty()) { @@ -2135,7 +2128,7 @@ namespace MWMechanics } list.push_back(package->getTarget()); } - else if (package->getTarget() == actorPtr) + else if (package->targetIs(actorPtr)) { list.push_back(iteratedActor); } @@ -2154,7 +2147,7 @@ namespace MWMechanics std::vector list; forEachFollowingPackage( mActors, actorPtr, getPlayer(), [&](const Actor& actor, const std::shared_ptr& package) { - if (package->followTargetThroughDoors() && package->getTarget() == actorPtr) + if (package->followTargetThroughDoors() && package->targetIs(actorPtr)) list.push_back(actor.getPtr()); else if (package->getTypeId() != AiPackageTypeId::Combat && package->getTypeId() != AiPackageTypeId::Wander) @@ -2181,38 +2174,12 @@ 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; forEachFollowingPackage( mActors, actor, getPlayer(), [&](const Actor&, const std::shared_ptr& package) { - if (package->followTargetThroughDoors() && package->getTarget() == actor) + if (package->followTargetThroughDoors() && package->targetIs(actor)) { list.push_back(static_cast(package.get())->getFollowIndex()); return false; @@ -2230,7 +2197,7 @@ namespace MWMechanics std::map map; forEachFollowingPackage( mActors, actor, getPlayer(), [&](const Actor& otherActor, const std::shared_ptr& package) { - if (package->followTargetThroughDoors() && package->getTarget() == actor) + if (package->followTargetThroughDoors() && package->targetIs(actor)) { const int index = static_cast(package.get())->getFollowIndex(); map[index] = otherActor.getPtr(); @@ -2405,4 +2372,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 fe9f37511a..14c55c4e45 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 { @@ -186,7 +187,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(); @@ -198,13 +199,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/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 12072df2ac..0b3c2b8bd2 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -270,7 +270,15 @@ namespace MWMechanics { storage.startCombatMove(isRangedCombat, distToTarget, rangeAttack, actor, target); // start new attack - storage.startAttackIfReady(actor, characterController, weapon, isRangedCombat); + bool canShout = true; + ESM::RefId spellId = storage.mCurrentAction->getSpell(); + if (!spellId.empty()) + { + const ESM::Spell* spell = MWBase::Environment::get().getESMStore()->get().find(spellId); + if (spell->mEffects.mList.empty() || spell->mEffects.mList[0].mRange != ESM::RT_Target) + canShout = false; + } + storage.startAttackIfReady(actor, characterController, weapon, isRangedCombat, canShout); } // If actor uses custom destination it has to try to rebuild path because environment can change @@ -635,7 +643,7 @@ namespace MWMechanics } void AiCombatStorage::startAttackIfReady(const MWWorld::Ptr& actor, CharacterController& characterController, - const ESM::Weapon* weapon, bool distantCombat) + const ESM::Weapon* weapon, bool distantCombat, bool canShout) { if (mReadyToAttack && characterController.readyToStartAttack()) { @@ -658,12 +666,15 @@ namespace MWMechanics baseDelay = store.get().find("fCombatDelayNPC")->mValue.getFloat(); } - // Say a provoking combat phrase - const int iVoiceAttackOdds - = store.get().find("iVoiceAttackOdds")->mValue.getInteger(); - if (Misc::Rng::roll0to99(prng) < iVoiceAttackOdds) + if (canShout) { - MWBase::Environment::get().getDialogueManager()->say(actor, ESM::RefId::stringRefId("attack")); + // Say a provoking combat phrase + const int iVoiceAttackOdds + = store.get().find("iVoiceAttackOdds")->mValue.getInteger(); + if (Misc::Rng::roll0to99(prng) < iVoiceAttackOdds) + { + MWBase::Environment::get().getDialogueManager()->say(actor, ESM::RefId::stringRefId("attack")); + } } mAttackCooldown = std::min(baseDelay + 0.01 * Misc::Rng::roll0to99(prng), baseDelay + 0.9); } diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 494f038d6e..d5a9c3464c 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -64,7 +64,7 @@ namespace MWMechanics void updateCombatMove(float duration); void stopCombatMove(); void startAttackIfReady(const MWWorld::Ptr& actor, CharacterController& characterController, - const ESM::Weapon* weapon, bool distantCombat); + const ESM::Weapon* weapon, bool distantCombat, bool canShout); void updateAttack(const MWWorld::Ptr& actor, CharacterController& characterController); void stopAttack(); diff --git a/apps/openmw/mwmechanics/aicombataction.hpp b/apps/openmw/mwmechanics/aicombataction.hpp index 05d2a18e25..9eb7df211a 100644 --- a/apps/openmw/mwmechanics/aicombataction.hpp +++ b/apps/openmw/mwmechanics/aicombataction.hpp @@ -16,6 +16,7 @@ namespace MWMechanics virtual float getCombatRange(bool& isRanged) const = 0; virtual float getActionCooldown() const { return 0.f; } virtual const ESM::Weapon* getWeapon() const { return nullptr; } + virtual ESM::RefId getSpell() const { return {}; } virtual bool isAttackingOrSpell() const { return true; } virtual bool isFleeing() const { return false; } }; @@ -43,6 +44,7 @@ namespace MWMechanics void prepare(const MWWorld::Ptr& actor) override; float getCombatRange(bool& isRanged) const override; + ESM::RefId getSpell() const override { return mSpellId; } }; class ActionEnchantedItem : public Action diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index fe83ce11ab..4bcfc7dedd 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -98,6 +98,26 @@ MWWorld::Ptr MWMechanics::AiPackage::getTarget() const return mCachedTarget; } +bool MWMechanics::AiPackage::targetIs(const MWWorld::Ptr& ptr) const +{ + if (mTargetActorId == -2) + return ptr.isEmpty(); + else if (mTargetActorId == -1) + { + if (mTargetActorRefId.empty()) + { + mTargetActorId = -2; + return ptr.isEmpty(); + } + if (!ptr.isEmpty() && ptr.getCellRef().getRefId() == mTargetActorRefId) + return getTarget() == ptr; + return false; + } + if (ptr.isEmpty() || !ptr.getClass().isActor()) + return false; + return ptr.getClass().getCreatureStats(ptr).getActorId() == mTargetActorId; +} + void MWMechanics::AiPackage::reset() { // reset all members diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 9e13ee9cd5..29a9f9c9ad 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -87,6 +87,8 @@ namespace MWMechanics /// Get the target actor the AI is targeted at (not applicable to all AI packages, default return empty Ptr) virtual MWWorld::Ptr getTarget() const; + /// Optimized version of getTarget() == ptr + virtual bool targetIs(const MWWorld::Ptr& ptr) const; /// Get the destination point of the AI package (not applicable to all AI packages, default return (0, 0, 0)) virtual osg::Vec3f getDestination(const MWWorld::Ptr& actor) const { return osg::Vec3f(0, 0, 0); } diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 5d6f25ecb8..82c4b1c0bd 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -176,11 +176,11 @@ namespace MWMechanics if (!isInCombat()) return false; - for (auto it = mPackages.begin(); it != mPackages.end(); ++it) + for (const auto& package : mPackages) { - if ((*it)->getTypeId() == AiPackageTypeId::Combat) + if (package->getTypeId() == AiPackageTypeId::Combat) { - if ((*it)->getTarget() == actor) + if (package->targetIs(actor)) return true; } } diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index 92c1724ea6..6b02da6633 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -132,9 +132,6 @@ namespace MWMechanics /// Are we in combat with this particular actor? bool isInCombat(const MWWorld::Ptr& actor) const; - bool canAddTarget(const ESM::Position& actorPos, float distToTarget) const; - ///< Function assumes that actor can have only 1 target apart player - /// Removes all combat packages until first non-combat or stack empty. void stopCombat(); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 9fd1b3ff8d..d1855d00bb 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" @@ -1192,9 +1193,9 @@ namespace MWMechanics && !victim.getClass().getCreatureStats(victim).getAiSequence().hasPackage(AiPackageTypeId::Pursue)) reported = reportCrime(player, victim, type, ESM::RefId(), arg); + // TODO: combat should be started with an "unaware" flag, which makes the victim flee? if (!reported) - startCombat(victim, - player); // TODO: combat should be started with an "unaware" flag, which makes the victim flee? + startCombat(victim, player, &playerFollowers); } return crimeSeen; } @@ -1435,7 +1436,7 @@ namespace MWMechanics observerStats.modCrimeDispositionModifier(dispositionModifier); } - startCombat(actor, player); + startCombat(actor, player, &playerFollowers); // Apply aggression value to the base Fight rating, so that the actor can continue fighting // after a Calm spell wears off @@ -1486,7 +1487,7 @@ namespace MWMechanics // 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. if (!victim.getClass().getCreatureStats(victim).getAiSequence().isInPursuit()) - startCombat(victim, player); + startCombat(victim, player, &playerFollowers); // Set the crime ID, which we will use to calm down participants // once the bounty has been paid. @@ -1531,14 +1532,14 @@ namespace MWMechanics if (!peaceful) { - startCombat(target, attacker); + 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); + startCombat(follower, attacker, &attackerAllies); } } } @@ -1687,7 +1688,8 @@ namespace MWMechanics } } - void MechanicsManager::startCombat(const MWWorld::Ptr& ptr, const MWWorld::Ptr& target) + void MechanicsManager::startCombat( + const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, const std::set* targetAllies) { CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); @@ -1704,6 +1706,31 @@ namespace MWMechanics return; } + const bool inCombat = stats.getAiSequence().isInCombat(); + bool shout = !inCombat; + if (inCombat) + { + const auto isInCombatWithOneOf = [&](const auto& allies) { + for (const MWWorld::Ptr& ally : allies) + { + if (stats.getAiSequence().isInCombat(ally)) + return true; + } + return false; + }; + if (targetAllies) + shout = !isInCombatWithOneOf(*targetAllies); + else + { + shout = stats.getAiSequence().isInCombat(target); + if (!shout) + { + std::set sidingActors; + getActorsSidingWith(target, sidingActors); + shout = !isInCombatWithOneOf(sidingActors); + } + } + } stats.getAiSequence().stack(MWMechanics::AiCombat(target), ptr); if (target == getPlayer()) { @@ -1738,7 +1765,8 @@ namespace MWMechanics } // Must be done after the target is set up, so that CreatureTargetted dialogue filter works properly - MWBase::Environment::get().getDialogueManager()->say(ptr, ESM::RefId::stringRefId("attack")); + if (shout) + MWBase::Environment::get().getDialogueManager()->say(ptr, ESM::RefId::stringRefId("attack")); } void MechanicsManager::stopCombat(const MWWorld::Ptr& actor) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 029d974ac2..516b778f1e 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -107,7 +107,8 @@ namespace MWMechanics bool awarenessCheck(const MWWorld::Ptr& ptr, const MWWorld::Ptr& observer) override; /// Makes \a ptr fight \a target. Also shouts a combat taunt. - void startCombat(const MWWorld::Ptr& ptr, const MWWorld::Ptr& target) override; + void startCombat( + const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, const std::set* targetAllies) override; void stopCombat(const MWWorld::Ptr& ptr) override; diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index b6acbd246b..a91a585367 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -507,8 +507,9 @@ namespace MWScript runtime.pop(); MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(targetID, true, false); - if (!target.isEmpty()) - MWBase::Environment::get().getMechanicsManager()->startCombat(actor, target); + if (!target.isEmpty() && !target.getBase()->isDeleted() + && !target.getClass().getCreatureStats(target).isDead()) + MWBase::Environment::get().getMechanicsManager()->startCombat(actor, target, nullptr); } };