mirror of
https://github.com/OpenMW/openmw.git
synced 2025-07-02 01:11:37 +00:00
Merge branch 'sworc' into 'master'
Make StartCombat a no-op for dead targets and don't always play attack lines Closes #7769 and #5413 See merge request OpenMW/openmw!3803
This commit is contained in:
commit
838785d5a3
14 changed files with 161 additions and 87 deletions
|
@ -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
|
||||
|
|
|
@ -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<MWWorld::Ptr>* targetAllies)
|
||||
= 0;
|
||||
|
||||
/// Removes an actor and its allies from combat with the actor's targets.
|
||||
virtual void stopCombat(const MWWorld::Ptr& ptr) = 0;
|
||||
|
|
|
@ -577,8 +577,8 @@ namespace MWMechanics
|
|||
}
|
||||
}
|
||||
|
||||
void Actors::engageCombat(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2,
|
||||
std::map<const MWWorld::Ptr, const std::set<MWWorld::Ptr>>& 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<MWWorld::Ptr> allies1;
|
||||
|
||||
getActorsSidingWith(actor1, allies1, cachedAllies);
|
||||
const std::set<MWWorld::Ptr>& 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<MWWorld::Ptr> playerAllies;
|
||||
MWWorld::Ptr player = MWMechanics::getPlayer();
|
||||
getActorsSidingWith(player, playerAllies, cachedAllies);
|
||||
const std::set<MWWorld::Ptr>& 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<MWWorld::Ptr> allies2;
|
||||
|
||||
getActorsSidingWith(actor2, allies2, cachedAllies);
|
||||
|
||||
const std::set<MWWorld::Ptr>& 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<ESM::GameSetting>().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<const MWWorld::Ptr, const std::set<MWWorld::Ptr>>
|
||||
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<MWWorld::Ptr> list;
|
||||
forEachFollowingPackage(
|
||||
mActors, actorPtr, getPlayer(), [&](const Actor& actor, const std::shared_ptr<AiPackage>& 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<MWWorld::Ptr>& out,
|
||||
std::map<const MWWorld::Ptr, const std::set<MWWorld::Ptr>>& cachedAllies) const
|
||||
{
|
||||
// If we have already found actor's allies, use the cache
|
||||
std::map<const MWWorld::Ptr, const std::set<MWWorld::Ptr>>::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<int> Actors::getActorsFollowingIndices(const MWWorld::Ptr& actor) const
|
||||
{
|
||||
std::vector<int> list;
|
||||
forEachFollowingPackage(
|
||||
mActors, actor, getPlayer(), [&](const Actor&, const std::shared_ptr<AiPackage>& package) {
|
||||
if (package->followTargetThroughDoors() && package->getTarget() == actor)
|
||||
if (package->followTargetThroughDoors() && package->targetIs(actor))
|
||||
{
|
||||
list.push_back(static_cast<const AiFollow*>(package.get())->getFollowIndex());
|
||||
return false;
|
||||
|
@ -2230,7 +2197,7 @@ namespace MWMechanics
|
|||
std::map<int, MWWorld::Ptr> map;
|
||||
forEachFollowingPackage(
|
||||
mActors, actor, getPlayer(), [&](const Actor& otherActor, const std::shared_ptr<AiPackage>& package) {
|
||||
if (package->followTargetThroughDoors() && package->getTarget() == actor)
|
||||
if (package->followTargetThroughDoors() && package->targetIs(actor))
|
||||
{
|
||||
const int index = static_cast<const AiFollow*>(package.get())->getFollowIndex();
|
||||
map[index] = otherActor.getPtr();
|
||||
|
@ -2405,4 +2372,32 @@ namespace MWMechanics
|
|||
seq.fastForward(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
const std::set<MWWorld::Ptr>& 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<MWWorld::Ptr>& 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<const MWWorld::Ptr, const std::set<MWWorld::Ptr>>& 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<MWWorld::Ptr>& out,
|
||||
std::map<const MWWorld::Ptr, const std::set<MWWorld::Ptr>>& cachedAllies) const;
|
||||
class SidingCache
|
||||
{
|
||||
const Actors& mActors;
|
||||
const bool mExcludeInfighting;
|
||||
std::map<MWWorld::Ptr, std::set<MWWorld::Ptr>> 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<MWWorld::Ptr>& getActorsSidingWith(const MWWorld::Ptr& actor);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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<ESM::Spell>().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<ESM::GameSetting>().find("fCombatDelayNPC")->mValue.getFloat();
|
||||
}
|
||||
|
||||
// Say a provoking combat phrase
|
||||
const int iVoiceAttackOdds
|
||||
= store.get<ESM::GameSetting>().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<ESM::GameSetting>().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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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); }
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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<MWWorld::Ptr>& attackerAllies = cachedAllies.getActorsSidingWith(attacker);
|
||||
startCombat(target, attacker, &attackerAllies);
|
||||
// Force friendly actors into combat to prevent infighting between followers
|
||||
std::set<MWWorld::Ptr> 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<MWWorld::Ptr>* 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<MWWorld::Ptr> 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)
|
||||
|
|
|
@ -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<MWWorld::Ptr>* targetAllies) override;
|
||||
|
||||
void stopCombat(const MWWorld::Ptr& ptr) override;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue