mirror of
https://github.com/OpenMW/openmw.git
synced 2025-12-12 22:43:07 +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 #5129: Stuttering animation on Centurion Archer
|
||||||
Bug #5280: Unskinned shapes in skinned equipment are rendered in the wrong place
|
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 #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 #5714: Touch spells cast using ExplodeSpell don't always explode
|
||||||
Bug #5849: Paralysis breaks landing
|
Bug #5849: Paralysis breaks landing
|
||||||
Bug #5870: Disposing of actors who were selected in the console doesn't deselect them like vanilla
|
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 #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 #7761: Rain and ambient loop sounds are mutually exclusive
|
||||||
Bug #7765: OpenMW-CS: Touch Record option is broken
|
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 #7770: Sword of the Perithia: Script execution failure
|
||||||
Bug #7780: Non-ASCII texture paths in NIF files don't work
|
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
|
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;
|
virtual bool awarenessCheck(const MWWorld::Ptr& ptr, const MWWorld::Ptr& observer) = 0;
|
||||||
|
|
||||||
/// Makes \a ptr fight \a target. Also shouts a combat taunt.
|
/// 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.
|
/// Removes an actor and its allies from combat with the actor's targets.
|
||||||
virtual void stopCombat(const MWWorld::Ptr& ptr) = 0;
|
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,
|
void Actors::engageCombat(
|
||||||
std::map<const MWWorld::Ptr, const std::set<MWWorld::Ptr>>& cachedAllies, bool againstPlayer) const
|
const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, SidingCache& cachedAllies, bool againstPlayer) const
|
||||||
{
|
{
|
||||||
// No combat for totally static creatures
|
// No combat for totally static creatures
|
||||||
if (!actor1.getClass().isMobile(actor1))
|
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
|
// 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
|
// those actors, (recursive) and any actor currently being followed or escorted by actor1
|
||||||
std::set<MWWorld::Ptr> allies1;
|
const std::set<MWWorld::Ptr>& allies1 = cachedAllies.getActorsSidingWith(actor1);
|
||||||
|
|
||||||
getActorsSidingWith(actor1, allies1, cachedAllies);
|
|
||||||
|
|
||||||
const auto mechanicsManager = MWBase::Environment::get().getMechanicsManager();
|
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
|
// 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()))
|
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
|
// 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
|
// if the player gets out of reach, while the ally would continue combat with the player
|
||||||
creatureStats1.setHitAttemptActorId(ally.getClass().getCreatureStats(ally).getHitAttemptActorId());
|
creatureStats1.setHitAttemptActorId(ally.getClass().getCreatureStats(ally).getHitAttemptActorId());
|
||||||
|
|
@ -633,9 +631,8 @@ namespace MWMechanics
|
||||||
aggressive = true;
|
aggressive = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::set<MWWorld::Ptr> playerAllies;
|
|
||||||
MWWorld::Ptr player = MWMechanics::getPlayer();
|
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();
|
bool isPlayerFollowerOrEscorter = playerAllies.find(actor1) != playerAllies.end();
|
||||||
|
|
||||||
|
|
@ -646,20 +643,17 @@ namespace MWMechanics
|
||||||
// Check that actor2 is in combat with actor1
|
// Check that actor2 is in combat with actor1
|
||||||
if (creatureStats2.getAiSequence().isInCombat(actor1))
|
if (creatureStats2.getAiSequence().isInCombat(actor1))
|
||||||
{
|
{
|
||||||
std::set<MWWorld::Ptr> allies2;
|
const std::set<MWWorld::Ptr>& allies2 = cachedAllies.getActorsSidingWith(actor2);
|
||||||
|
|
||||||
getActorsSidingWith(actor2, allies2, cachedAllies);
|
|
||||||
|
|
||||||
// Check that an ally of actor2 is also in combat with actor1
|
// Check that an ally of actor2 is also in combat with actor1
|
||||||
for (const MWWorld::Ptr& ally2 : allies2)
|
for (const MWWorld::Ptr& ally2 : allies2)
|
||||||
{
|
{
|
||||||
if (ally2 != actor2 && ally2.getClass().getCreatureStats(ally2).getAiSequence().isInCombat(actor1))
|
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
|
// Also have actor1's allies start combat
|
||||||
for (const MWWorld::Ptr& ally1 : allies1)
|
for (const MWWorld::Ptr& ally1 : allies1)
|
||||||
if (ally1 != player)
|
if (ally1 != player)
|
||||||
mechanicsManager->startCombat(ally1, actor2);
|
mechanicsManager->startCombat(ally1, actor2, &allies2);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -747,7 +741,7 @@ namespace MWMechanics
|
||||||
bool LOS = world->getLOS(actor1, actor2) && mechanicsManager->awarenessCheck(actor2, actor1);
|
bool LOS = world->getLOS(actor1, actor2) && mechanicsManager->awarenessCheck(actor2, actor1);
|
||||||
|
|
||||||
if (LOS)
|
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();
|
const MWWorld::Ptr player = getPlayer();
|
||||||
if (ptr == player)
|
if (ptr == player)
|
||||||
|
|
@ -1133,7 +1127,7 @@ namespace MWMechanics
|
||||||
= esmStore.get<ESM::GameSetting>().find("iCrimeThresholdMultiplier")->mValue.getInteger();
|
= esmStore.get<ESM::GameSetting>().find("iCrimeThresholdMultiplier")->mValue.getInteger();
|
||||||
if (playerStats.getBounty() >= cutoff * iCrimeThresholdMultiplier)
|
if (playerStats.getBounty() >= cutoff * iCrimeThresholdMultiplier)
|
||||||
{
|
{
|
||||||
mechanicsManager->startCombat(ptr, player);
|
mechanicsManager->startCombat(ptr, player, &cachedAllies.getActorsSidingWith(player));
|
||||||
creatureStats.setHitAttemptActorId(
|
creatureStats.setHitAttemptActorId(
|
||||||
playerClass.getCreatureStats(player)
|
playerClass.getCreatureStats(player)
|
||||||
.getActorId()); // Stops the guard from quitting combat if player is unreachable
|
.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
|
/// \todo move update logic to Actor class where appropriate
|
||||||
|
|
||||||
std::map<const MWWorld::Ptr, const std::set<MWWorld::Ptr>>
|
SidingCache cachedAllies{ *this, true }; // will be filled as engageCombat iterates
|
||||||
cachedAllies; // will be filled as engageCombat iterates
|
|
||||||
|
|
||||||
const bool aiActive = MWBase::Environment::get().getMechanicsManager()->isAIActive();
|
const bool aiActive = MWBase::Environment::get().getMechanicsManager()->isAIActive();
|
||||||
const int attackedByPlayerId = player.getClass().getCreatureStats(player).getHitAttemptActorId();
|
const int attackedByPlayerId = player.getClass().getCreatureStats(player).getHitAttemptActorId();
|
||||||
|
|
@ -1597,7 +1590,7 @@ namespace MWMechanics
|
||||||
updateHeadTracking(actor.getPtr(), mActors, isPlayer, ctrl);
|
updateHeadTracking(actor.getPtr(), mActors, isPlayer, ctrl);
|
||||||
|
|
||||||
if (actor.getPtr().getClass().isNpc() && !isPlayer)
|
if (actor.getPtr().getClass().isNpc() && !isPlayer)
|
||||||
updateCrimePursuit(actor.getPtr(), duration);
|
updateCrimePursuit(actor.getPtr(), duration, cachedAllies);
|
||||||
|
|
||||||
if (!isPlayer)
|
if (!isPlayer)
|
||||||
{
|
{
|
||||||
|
|
@ -2115,7 +2108,7 @@ namespace MWMechanics
|
||||||
for (const auto& package : stats.getAiSequence())
|
for (const auto& package : stats.getAiSequence())
|
||||||
{
|
{
|
||||||
if (excludeInfighting && !sameActor && package->getTypeId() == AiPackageTypeId::Combat
|
if (excludeInfighting && !sameActor && package->getTypeId() == AiPackageTypeId::Combat
|
||||||
&& package->getTarget() == actorPtr)
|
&& package->targetIs(actorPtr))
|
||||||
break;
|
break;
|
||||||
if (package->sideWithTarget() && !package->getTarget().isEmpty())
|
if (package->sideWithTarget() && !package->getTarget().isEmpty())
|
||||||
{
|
{
|
||||||
|
|
@ -2135,7 +2128,7 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
list.push_back(package->getTarget());
|
list.push_back(package->getTarget());
|
||||||
}
|
}
|
||||||
else if (package->getTarget() == actorPtr)
|
else if (package->targetIs(actorPtr))
|
||||||
{
|
{
|
||||||
list.push_back(iteratedActor);
|
list.push_back(iteratedActor);
|
||||||
}
|
}
|
||||||
|
|
@ -2154,7 +2147,7 @@ namespace MWMechanics
|
||||||
std::vector<MWWorld::Ptr> list;
|
std::vector<MWWorld::Ptr> list;
|
||||||
forEachFollowingPackage(
|
forEachFollowingPackage(
|
||||||
mActors, actorPtr, getPlayer(), [&](const Actor& actor, const std::shared_ptr<AiPackage>& package) {
|
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());
|
list.push_back(actor.getPtr());
|
||||||
else if (package->getTypeId() != AiPackageTypeId::Combat
|
else if (package->getTypeId() != AiPackageTypeId::Combat
|
||||||
&& package->getTypeId() != AiPackageTypeId::Wander)
|
&& package->getTypeId() != AiPackageTypeId::Wander)
|
||||||
|
|
@ -2181,38 +2174,12 @@ namespace MWMechanics
|
||||||
getActorsSidingWith(follower, out, excludeInfighting);
|
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> Actors::getActorsFollowingIndices(const MWWorld::Ptr& actor) const
|
||||||
{
|
{
|
||||||
std::vector<int> list;
|
std::vector<int> list;
|
||||||
forEachFollowingPackage(
|
forEachFollowingPackage(
|
||||||
mActors, actor, getPlayer(), [&](const Actor&, const std::shared_ptr<AiPackage>& package) {
|
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());
|
list.push_back(static_cast<const AiFollow*>(package.get())->getFollowIndex());
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -2230,7 +2197,7 @@ namespace MWMechanics
|
||||||
std::map<int, MWWorld::Ptr> map;
|
std::map<int, MWWorld::Ptr> map;
|
||||||
forEachFollowingPackage(
|
forEachFollowingPackage(
|
||||||
mActors, actor, getPlayer(), [&](const Actor& otherActor, const std::shared_ptr<AiPackage>& package) {
|
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();
|
const int index = static_cast<const AiFollow*>(package.get())->getFollowIndex();
|
||||||
map[index] = otherActor.getPtr();
|
map[index] = otherActor.getPtr();
|
||||||
|
|
@ -2405,4 +2372,32 @@ namespace MWMechanics
|
||||||
seq.fastForward(ptr);
|
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 Actor;
|
||||||
class CharacterController;
|
class CharacterController;
|
||||||
class CreatureStats;
|
class CreatureStats;
|
||||||
|
class SidingCache;
|
||||||
|
|
||||||
class Actors
|
class Actors
|
||||||
{
|
{
|
||||||
|
|
@ -186,7 +187,7 @@ namespace MWMechanics
|
||||||
|
|
||||||
void calculateRestoration(const MWWorld::Ptr& ptr, float duration) const;
|
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();
|
void killDeadActors();
|
||||||
|
|
||||||
|
|
@ -198,13 +199,25 @@ namespace MWMechanics
|
||||||
@Notes: If againstPlayer = true then actor2 should be the Player.
|
@Notes: If againstPlayer = true then actor2 should be the Player.
|
||||||
If one of the combatants is creature it should be actor1.
|
If one of the combatants is creature it should be actor1.
|
||||||
*/
|
*/
|
||||||
void engageCombat(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2,
|
void engageCombat(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, SidingCache& cachedAllies,
|
||||||
std::map<const MWWorld::Ptr, const std::set<MWWorld::Ptr>>& cachedAllies, bool againstPlayer) const;
|
bool againstPlayer) const;
|
||||||
|
};
|
||||||
|
|
||||||
/// Recursive version of getActorsSidingWith that takes, adds to and returns a cache of
|
class SidingCache
|
||||||
/// actors mapped to their allies. Excludes infighting
|
{
|
||||||
void getActorsSidingWith(const MWWorld::Ptr& actor, std::set<MWWorld::Ptr>& out,
|
const Actors& mActors;
|
||||||
std::map<const MWWorld::Ptr, const std::set<MWWorld::Ptr>>& cachedAllies) const;
|
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);
|
storage.startCombatMove(isRangedCombat, distToTarget, rangeAttack, actor, target);
|
||||||
// start new attack
|
// 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
|
// 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,
|
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())
|
if (mReadyToAttack && characterController.readyToStartAttack())
|
||||||
{
|
{
|
||||||
|
|
@ -658,12 +666,15 @@ namespace MWMechanics
|
||||||
baseDelay = store.get<ESM::GameSetting>().find("fCombatDelayNPC")->mValue.getFloat();
|
baseDelay = store.get<ESM::GameSetting>().find("fCombatDelayNPC")->mValue.getFloat();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Say a provoking combat phrase
|
if (canShout)
|
||||||
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"));
|
// 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);
|
mAttackCooldown = std::min(baseDelay + 0.01 * Misc::Rng::roll0to99(prng), baseDelay + 0.9);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ namespace MWMechanics
|
||||||
void updateCombatMove(float duration);
|
void updateCombatMove(float duration);
|
||||||
void stopCombatMove();
|
void stopCombatMove();
|
||||||
void startAttackIfReady(const MWWorld::Ptr& actor, CharacterController& characterController,
|
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 updateAttack(const MWWorld::Ptr& actor, CharacterController& characterController);
|
||||||
void stopAttack();
|
void stopAttack();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ namespace MWMechanics
|
||||||
virtual float getCombatRange(bool& isRanged) const = 0;
|
virtual float getCombatRange(bool& isRanged) const = 0;
|
||||||
virtual float getActionCooldown() const { return 0.f; }
|
virtual float getActionCooldown() const { return 0.f; }
|
||||||
virtual const ESM::Weapon* getWeapon() const { return nullptr; }
|
virtual const ESM::Weapon* getWeapon() const { return nullptr; }
|
||||||
|
virtual ESM::RefId getSpell() const { return {}; }
|
||||||
virtual bool isAttackingOrSpell() const { return true; }
|
virtual bool isAttackingOrSpell() const { return true; }
|
||||||
virtual bool isFleeing() const { return false; }
|
virtual bool isFleeing() const { return false; }
|
||||||
};
|
};
|
||||||
|
|
@ -43,6 +44,7 @@ namespace MWMechanics
|
||||||
void prepare(const MWWorld::Ptr& actor) override;
|
void prepare(const MWWorld::Ptr& actor) override;
|
||||||
|
|
||||||
float getCombatRange(bool& isRanged) const override;
|
float getCombatRange(bool& isRanged) const override;
|
||||||
|
ESM::RefId getSpell() const override { return mSpellId; }
|
||||||
};
|
};
|
||||||
|
|
||||||
class ActionEnchantedItem : public Action
|
class ActionEnchantedItem : public Action
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,26 @@ MWWorld::Ptr MWMechanics::AiPackage::getTarget() const
|
||||||
return mCachedTarget;
|
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()
|
void MWMechanics::AiPackage::reset()
|
||||||
{
|
{
|
||||||
// reset all members
|
// 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)
|
/// Get the target actor the AI is targeted at (not applicable to all AI packages, default return empty Ptr)
|
||||||
virtual MWWorld::Ptr getTarget() const;
|
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))
|
/// 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); }
|
virtual osg::Vec3f getDestination(const MWWorld::Ptr& actor) const { return osg::Vec3f(0, 0, 0); }
|
||||||
|
|
|
||||||
|
|
@ -176,11 +176,11 @@ namespace MWMechanics
|
||||||
if (!isInCombat())
|
if (!isInCombat())
|
||||||
return false;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -132,9 +132,6 @@ namespace MWMechanics
|
||||||
/// Are we in combat with this particular actor?
|
/// Are we in combat with this particular actor?
|
||||||
bool isInCombat(const MWWorld::Ptr& actor) const;
|
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.
|
/// Removes all combat packages until first non-combat or stack empty.
|
||||||
void stopCombat();
|
void stopCombat();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@
|
||||||
#include "../mwbase/world.hpp"
|
#include "../mwbase/world.hpp"
|
||||||
|
|
||||||
#include "actor.hpp"
|
#include "actor.hpp"
|
||||||
|
#include "actors.hpp"
|
||||||
#include "actorutil.hpp"
|
#include "actorutil.hpp"
|
||||||
#include "aicombat.hpp"
|
#include "aicombat.hpp"
|
||||||
#include "aipursue.hpp"
|
#include "aipursue.hpp"
|
||||||
|
|
@ -1192,9 +1193,9 @@ namespace MWMechanics
|
||||||
&& !victim.getClass().getCreatureStats(victim).getAiSequence().hasPackage(AiPackageTypeId::Pursue))
|
&& !victim.getClass().getCreatureStats(victim).getAiSequence().hasPackage(AiPackageTypeId::Pursue))
|
||||||
reported = reportCrime(player, victim, type, ESM::RefId(), arg);
|
reported = reportCrime(player, victim, type, ESM::RefId(), arg);
|
||||||
|
|
||||||
|
// TODO: combat should be started with an "unaware" flag, which makes the victim flee?
|
||||||
if (!reported)
|
if (!reported)
|
||||||
startCombat(victim,
|
startCombat(victim, player, &playerFollowers);
|
||||||
player); // TODO: combat should be started with an "unaware" flag, which makes the victim flee?
|
|
||||||
}
|
}
|
||||||
return crimeSeen;
|
return crimeSeen;
|
||||||
}
|
}
|
||||||
|
|
@ -1435,7 +1436,7 @@ namespace MWMechanics
|
||||||
observerStats.modCrimeDispositionModifier(dispositionModifier);
|
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
|
// Apply aggression value to the base Fight rating, so that the actor can continue fighting
|
||||||
// after a Calm spell wears off
|
// 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.
|
// 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().isInPursuit())
|
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
|
// Set the crime ID, which we will use to calm down participants
|
||||||
// once the bounty has been paid.
|
// once the bounty has been paid.
|
||||||
|
|
@ -1531,14 +1532,14 @@ namespace MWMechanics
|
||||||
|
|
||||||
if (!peaceful)
|
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
|
// Force friendly actors into combat to prevent infighting between followers
|
||||||
std::set<MWWorld::Ptr> followersTarget;
|
for (const auto& follower : cachedAllies.getActorsSidingWith(target))
|
||||||
getActorsSidingWith(target, followersTarget);
|
|
||||||
for (const auto& follower : followersTarget)
|
|
||||||
{
|
{
|
||||||
if (follower != attacker && follower != player)
|
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);
|
CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
|
||||||
|
|
||||||
|
|
@ -1704,6 +1706,31 @@ namespace MWMechanics
|
||||||
return;
|
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);
|
stats.getAiSequence().stack(MWMechanics::AiCombat(target), ptr);
|
||||||
if (target == getPlayer())
|
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
|
// 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)
|
void MechanicsManager::stopCombat(const MWWorld::Ptr& actor)
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,8 @@ namespace MWMechanics
|
||||||
bool awarenessCheck(const MWWorld::Ptr& ptr, const MWWorld::Ptr& observer) override;
|
bool awarenessCheck(const MWWorld::Ptr& ptr, const MWWorld::Ptr& observer) override;
|
||||||
|
|
||||||
/// Makes \a ptr fight \a target. Also shouts a combat taunt.
|
/// 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;
|
void stopCombat(const MWWorld::Ptr& ptr) override;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -507,8 +507,9 @@ namespace MWScript
|
||||||
runtime.pop();
|
runtime.pop();
|
||||||
|
|
||||||
MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(targetID, true, false);
|
MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(targetID, true, false);
|
||||||
if (!target.isEmpty())
|
if (!target.isEmpty() && !target.getBase()->isDeleted()
|
||||||
MWBase::Environment::get().getMechanicsManager()->startCombat(actor, target);
|
&& !target.getClass().getCreatureStats(target).isDead())
|
||||||
|
MWBase::Environment::get().getMechanicsManager()->startCombat(actor, target, nullptr);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue