@ -9,6 +9,8 @@
# include <components/sceneutil/positionattitudetransform.hpp>
# include <components/settings/settings.hpp>
# include "../mwworld/esmstore.hpp"
# include "../mwworld/class.hpp"
# include "../mwworld/inventorystore.hpp"
@ -280,7 +282,7 @@ namespace MWMechanics
void Actors : : engageCombat ( const MWWorld : : Ptr & actor1 , const MWWorld : : Ptr & actor2 , bool againstPlayer )
{
const CreatureStats & creatureStats1 = actor1 . getClass ( ) . getCreatureStats ( actor1 ) ;
CreatureStats & creatureStats1 = actor1 . getClass ( ) . getCreatureStats ( actor1 ) ;
if ( creatureStats1 . getAiSequence ( ) . isInCombat ( actor2 ) )
return ;
@ -298,97 +300,125 @@ namespace MWMechanics
if ( ! actor1 . getClass ( ) . isMobile ( actor1 ) )
return ;
// Start combat if target actor is in combat with one of our followers or escorters
const std : : list < MWWorld : : Ptr > & followersAndEscorters = getActorsSidingWith ( actor1 ) ;
for ( std : : list < MWWorld : : Ptr > : : const_iterator it = followersAndEscorters . begin ( ) ; it ! = followersAndEscorters . end ( ) ; + + it )
// If this is set to true, actor1 will start combat with actor2 if the awareness check at the end of the method returns true
bool aggressive = false ;
// 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 ) ;
// If an ally of actor1 has been attacked by actor2 or has attacked actor2, start combat between actor1 and actor2
for ( std : : set < MWWorld : : Ptr > : : const_iterator it = allies1 . begin ( ) ; it ! = allies1 . end ( ) ; + + it )
{
// Need to check both ways since player doesn't use AI packages
if ( ( creatureStats2 . getAiSequence ( ) . isInCombat ( * it )
| | it - > getClass ( ) . getCreatureStats ( * it ) . getAiSequence ( ) . isInCombat ( actor2 ) )
& & ! creatureStats1 . getAiSequence ( ) . isInCombat ( * it ) )
if ( creatureStats1 . getAiSequence ( ) . isInCombat ( * it ) )
continue ;
if ( ! it - > getClass ( ) . getCreatureStats ( * it ) . getHitAttemptActor ( ) . isEmpty ( )
& & it - > getClass ( ) . getCreatureStats ( * it ) . getHitAttemptActor ( ) = = actor2 )
{
MWBase : : Environment : : get ( ) . getMechanicsManager ( ) - > startCombat ( actor1 , 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 . setHitAttemptActor ( actor2 ) ;
return ;
}
}
// Start combat if target actor is in combat with someone we are following through a follow package
for ( std : : list < MWMechanics : : AiPackage * > : : const_iterator it = creatureStats1 . getAiSequence ( ) . begin ( ) ; it ! = creatureStats1 . getAiSequence ( ) . end ( ) ; + + it )
{
if ( ! ( * it ) - > sideWithTarget ( ) )
continue ;
// If there's been no attack attempt yet but an ally of actor1 is in combat with actor2, become aggressive to actor2
if ( it - > getClass ( ) . getCreatureStats ( * it ) . getAiSequence ( ) . isInCombat ( actor2 ) )
aggressive = true ;
}
MWWorld : : Ptr followTarget = ( * it ) - > getTarget ( ) ;
std : : set < MWWorld : : Ptr > playerFollowersAndEscorters ;
getActorsSidingWith ( MWMechanics : : getPlayer ( ) , playerFollowersAndEscorters ) ;
if ( followTarget . isEmpty ( ) )
continue ;
bool isPlayerFollowerOrEscorter = std : : find ( playerFollowersAndEscorters . begin ( ) , playerFollowersAndEscorters . end ( ) , actor1 ) ! = playerFollowersAndEscorters . end ( ) ;
if ( creatureStats1 . getAiSequence ( ) . isInCombat ( followTarget ) )
continue ;
// Need to check both ways since player doesn't use AI packages
if ( creatureStats2 . getAiSequence ( ) . isInCombat ( followTarget )
| | followTarget . getClass ( ) . getCreatureStats ( followTarget ) . getAiSequence ( ) . isInCombat ( actor2 ) )
// If actor2 and at least one actor2 are in combat with actor1, actor1 and its allies start combat with them
// Doesn't apply for player followers/escorters
if ( ! aggressive & & ! isPlayerFollowerOrEscorter )
{
// Check that actor2 is in combat with actor1
if ( actor2 . getClass ( ) . getCreatureStats ( actor2 ) . getAiSequence ( ) . isInCombat ( actor1 ) )
{
std : : set < MWWorld : : Ptr > allies2 ;
getActorsSidingWith ( actor2 , allies2 ) ;
// Check that an ally of actor2 is also in combat with actor1
for ( std : : set < MWWorld : : Ptr > : : const_iterator it = allies2 . begin ( ) ; it ! = allies2 . end ( ) ; + + it )
{
if ( ( it ) - > getClass ( ) . getCreatureStats ( * it ) . getAiSequence ( ) . isInCombat ( actor1 ) )
{
MWBase : : Environment : : get ( ) . getMechanicsManager ( ) - > startCombat ( actor1 , actor2 ) ;
// Also have actor1's allies start combat
for ( std : : set < MWWorld : : Ptr > : : const_iterator it2 = allies1 . begin ( ) ; it2 ! = allies1 . end ( ) ; + + it2 )
MWBase : : Environment : : get ( ) . getMechanicsManager ( ) - > startCombat ( * it2 , actor2 ) ;
return ;
}
}
}
}
// Start combat with the player if we are already in combat with a player follower or escorter
const std : : list < MWWorld : : Ptr > & playerFollowersAndEscorters = getActorsSidingWith ( getPlayer ( ) ) ;
if ( againstPlayer )
// If set in the settings file, player followers and escorters will become aggressive toward enemies in combat with them or the player
if ( ! aggressive & & isPlayerFollowerOrEscorter & & Settings : : Manager : : getBool ( " followers attack on sight " , " Game " ) )
{
if ( actor2 . getClass ( ) . getCreatureStats ( actor2 ) . getAiSequence ( ) . isInCombat ( actor1 ) )
aggressive = true ;
else
{
for ( std : : list < MWWorld : : Ptr > : : const_iterator it = playerFollowersAndEscorters . begin ( ) ; it ! = playerFollowersAndEscorters . end ( ) ; + + it )
for ( std : : se t< MWWorld : : Ptr > : : const_iterator it = allies1. begin ( ) ; it ! = allies1 . end ( ) ; + + it )
{
if ( creatureStats1 . getAiSequence ( ) . isInCombat ( * it ) )
if ( actor2 . getClass ( ) . getCreatureStats ( actor2 ) . getAiSequence ( ) . isInCombat ( * it ) )
{
MWBase : : Environment : : get ( ) . getMechanicsManager ( ) - > startCombat ( actor1 , actor2 ) ;
return ;
aggressive = true ;
break ;
}
}
}
}
// Otherwise, don't initiate combat with an unreachable target
// Stop here if target is unreachable
if ( ! MWMechanics : : canFight ( actor1 , actor2 ) )
return ;
bool aggressive = false ;
// Do aggression check if actor2 is the player or a player follower or escorter
if ( ! aggressive )
{
if ( againstPlayer | | std : : find ( playerFollowersAndEscorters . begin ( ) , playerFollowersAndEscorters . end ( ) , actor2 ) ! = playerFollowersAndEscorters . end ( ) )
{
// Player followers and escorters with high fight should not initiate combat here with the player or with
// Player followers and escorters with high fight should not initiate combat with the player or with
// other player followers or escorters
if ( std : : find ( playerFollowersAndEscorters . begin ( ) , playerFollowersAndEscorters . end ( ) , actor1 ) ! = playerFollowersAndEscorters . end ( ) )
return ;
if ( std : : find ( playerFollowersAndEscorters . begin ( ) , playerFollowersAndEscorters . end ( ) , actor1 ) = = playerFollowersAndEscorters . end ( ) )
aggressive = MWBase : : Environment : : get ( ) . getMechanicsManager ( ) - > isAggressive ( actor1 , actor2 ) ;
}
else
}
// Make guards go aggressive with creatures that are in combat, unless the creature is a follower or escorter
if ( actor1 . getClass ( ) . isClass ( actor1 , " Guard " ) & & ! actor2 . getClass ( ) . isNpc ( ) )
{
// Make guards fight aggressive creatures
if ( ! actor1 . getClass ( ) . isNpc ( ) & & actor2 . getClass ( ) . isClass ( actor2 , " Guard " ) )
bool followerOrEscorter = false ;
for ( std : : list < MWMechanics : : AiPackage * > : : const_iterator it = creatureStats2 . getAiSequence ( ) . begin ( ) ; it ! = creatureStats2 . getAiSequence ( ) . end ( ) ; + + it )
{
if ( creatureStats1 . getAiSequence ( ) . isInCombat ( ) & & MWBase : : Environment : : get ( ) . getMechanicsManager ( ) - > isAggressive ( actor1 , actor2 ) )
aggressive = true ;
// The follow package must be first or have nothing but combat before it
if ( ( * it ) - > sideWithTarget ( ) )
{
followerOrEscorter = true ;
break ;
}
else if ( ( * it ) - > getTypeId ( ) ! = MWMechanics : : AiPackage : : TypeIdCombat )
break ;
}
if ( ! followerOrEscorter & & creatureStats2 . getAiSequence ( ) . isInCombat ( ) )
aggressive = true ;
}
// If any of the above conditions turned actor1 aggressive towards actor2, do an awareness check. If it passes, start combat with actor2.
if ( aggressive )
{
bool LOS = MWBase : : Environment : : get ( ) . getWorld ( ) - > getLOS ( actor1 , actor2 ) ;
if ( againstPlayer | | std : : find ( playerFollowersAndEscorters . begin ( ) , playerFollowersAndEscorters . end ( ) , actor2 ) ! = playerFollowersAndEscorters . end ( ) )
LOS & = MWBase : : Environment : : get ( ) . getMechanicsManager ( ) - > awarenessCheck ( actor2 , actor1 ) ;
if ( LOS )
{
MWBase : : Environment : : get ( ) . getMechanicsManager ( ) - > startCombat ( actor1 , actor2 ) ;
if ( ! againstPlayer ) // start combat between each other
{
MWBase : : Environment : : get ( ) . getMechanicsManager ( ) - > startCombat ( actor2 , actor1 ) ;
}
}
}
}
@ -931,7 +961,10 @@ namespace MWMechanics
{
static const int iCrimeThresholdMultiplier = esmStore . get < ESM : : GameSetting > ( ) . find ( " iCrimeThresholdMultiplier " ) - > getInt ( ) ;
if ( player . getClass ( ) . getNpcStats ( player ) . getBounty ( ) > = cutoff * iCrimeThresholdMultiplier )
{
MWBase : : Environment : : get ( ) . getMechanicsManager ( ) - > startCombat ( ptr , player ) ;
creatureStats . setHitAttemptActor ( player ) ; // Stops the guard from quitting combat if player is unreachable
}
else
creatureStats . getAiSequence ( ) . stack ( AiPursue ( player ) , ptr ) ;
creatureStats . setAlarmed ( true ) ;
@ -1054,10 +1087,25 @@ namespace MWMechanics
if ( iter - > first = = player )
iter - > second - > getCharacterController ( ) - > setAttackingOrSpell ( MWBase : : Environment : : get ( ) . getWorld ( ) - > getPlayer ( ) . getAttackingOrSpell ( ) ) ;
// If dead or no longer in combat, no longer store any actors who attempted to hit us. Also remove for the player.
if ( iter - > first ! = player & & ( iter - > first . getClass ( ) . getCreatureStats ( iter - > first ) . isDead ( )
| | ! iter - > first . getClass ( ) . getCreatureStats ( iter - > first ) . getAiSequence ( ) . isInCombat ( )
| | ! inProcessingRange ) )
{
iter - > first . getClass ( ) . getCreatureStats ( iter - > first ) . setHitAttemptActor ( NULL ) ;
if ( player . getClass ( ) . getCreatureStats ( player ) . getHitAttemptActor ( ) = = iter - > first )
player . getClass ( ) . getCreatureStats ( player ) . setHitAttemptActor ( NULL ) ;
}
const MWWorld : : Ptr playerHitAttemptActor = MWBase : : Environment : : get ( ) . getWorld ( ) - > searchPtrViaActorId ( player . getClass ( ) . getCreatureStats ( player ) . getHitAttemptActorId ( ) ) ;
if ( ! playerHitAttemptActor . isInCell ( ) )
player . getClass ( ) . getCreatureStats ( player ) . setHitAttemptActorId ( - 1 ) ;
if ( ! iter - > first . getClass ( ) . getCreatureStats ( iter - > first ) . isDead ( ) )
{
MWWorld : : Ptr actor = iter - > first ; // make a copy of the map key to avoid it being invalidated when the player teleports
bool cellChanged = MWBase : : Environment : : get ( ) . getWorld ( ) - > hasCellChanged ( ) ;
MWWorld : : Ptr actor = iter - > first ; // make a copy of the map key to avoid it being invalidated when the player teleports
updateActor ( actor , duration ) ;
if ( ! cellChanged & & MWBase : : Environment : : get ( ) . getWorld ( ) - > hasCellChanged ( ) )
{
@ -1470,18 +1518,36 @@ namespace MWMechanics
for ( PtrActorMap : : iterator iter ( mActors . begin ( ) ) ; iter ! = mActors . end ( ) ; + + iter )
{
const MWWorld : : Class & cls = iter - > first . getClass ( ) ;
CreatureStats & stats = cls . getCreatureStats ( iter - > first ) ;
const CreatureStats & stats = cls . getCreatureStats ( iter - > first ) ;
if ( stats . isDead ( ) )
continue ;
// An actor counts as following if AiFollow or AiEscort is the current AiP ackage, or there are only Combat packages before the Ai Follow/Ai Escort package
// An actor counts as siding with this actor if Follow or Escort is the current AI p ackage, or there are only Combat packages before the Follow/Escort package
for ( std : : list < MWMechanics : : AiPackage * > : : const_iterator it = stats . getAiSequence ( ) . begin ( ) ; it ! = stats . getAiSequence ( ) . end ( ) ; + + it )
{
if ( ( * it ) - > sideWithTarget ( ) & & ( * it ) - > getTarget ( ) = = actor )
{
list . push_back ( iter - > first ) ;
break ;
}
else if ( ( * it ) - > getTypeId ( ) ! = MWMechanics : : AiPackage : : TypeIdCombat )
break ;
}
// Actors that are targeted by this actor's Follow or Escort packages also side with them
if ( actor ! = getPlayer ( ) )
{
const CreatureStats & stats2 = actor . getClass ( ) . getCreatureStats ( actor ) ;
for ( std : : list < MWMechanics : : AiPackage * > : : const_iterator it2 = stats2 . getAiSequence ( ) . begin ( ) ; it2 ! = stats2 . getAiSequence ( ) . end ( ) ; + + it2 )
{
if ( ( * it2 ) - > sideWithTarget ( ) & & ! ( * it2 ) - > getTarget ( ) . isEmpty ( ) )
{
list . push_back ( ( * it2 ) - > getTarget ( ) ) ;
break ;
}
else if ( ( * it2 ) - > getTypeId ( ) ! = MWMechanics : : AiPackage : : TypeIdCombat )
break ;
}
}
}
return list ;
}
@ -1551,8 +1617,6 @@ namespace MWMechanics
return list ;
}
std : : list < MWWorld : : Ptr > Actors : : getActorsFighting ( const MWWorld : : Ptr & actor ) {
std : : list < MWWorld : : Ptr > list ;
std : : vector < MWWorld : : Ptr > neighbors ;