2012-03-30 14:18:58 +00:00
# include "actors.hpp"
2015-07-07 17:16:32 +00:00
# include <components/esm/esmreader.hpp>
# include <components/esm/esmwriter.hpp>
2016-06-17 14:07:16 +00:00
2015-11-20 20:57:04 +00:00
# include <components/sceneutil/positionattitudetransform.hpp>
2018-08-14 19:05:43 +00:00
# include <components/debug/debuglog.hpp>
2018-12-14 15:29:56 +00:00
# include <components/misc/rng.hpp>
2017-02-01 17:15:10 +00:00
# include <components/settings/settings.hpp>
2012-10-01 15:17:04 +00:00
# include "../mwworld/esmstore.hpp"
2012-03-30 14:18:58 +00:00
# include "../mwworld/class.hpp"
2012-03-31 15:26:15 +00:00
# include "../mwworld/inventorystore.hpp"
2013-11-21 02:39:55 +00:00
# include "../mwworld/actionequip.hpp"
2014-01-15 20:56:55 +00:00
# include "../mwworld/player.hpp"
2012-03-30 14:18:58 +00:00
2012-09-21 15:53:16 +00:00
# include "../mwbase/world.hpp"
# include "../mwbase/environment.hpp"
# include "../mwbase/windowmanager.hpp"
2015-08-19 13:51:04 +00:00
# include "../mwbase/dialoguemanager.hpp"
2013-08-28 00:08:23 +00:00
# include "../mwbase/soundmanager.hpp"
2015-05-31 16:04:14 +00:00
# include "../mwbase/mechanicsmanager.hpp"
2016-06-11 22:04:50 +00:00
# include "../mwbase/statemanager.hpp"
2012-09-21 15:53:16 +00:00
2017-07-25 05:51:55 +00:00
# include "../mwmechanics/aibreathe.hpp"
2020-04-20 16:47:14 +00:00
# include "../mwrender/vismask.hpp"
2016-06-17 14:07:16 +00:00
# include "spellcasting.hpp"
2019-10-09 16:57:24 +00:00
# include "steering.hpp"
2013-08-07 13:34:11 +00:00
# include "npcstats.hpp"
2012-05-17 11:15:31 +00:00
# include "creaturestats.hpp"
2013-03-31 07:13:56 +00:00
# include "movement.hpp"
2014-10-08 08:58:52 +00:00
# include "character.hpp"
2013-11-18 11:33:09 +00:00
# include "aicombat.hpp"
2016-12-25 14:31:44 +00:00
# include "aicombataction.hpp"
2014-01-12 13:02:15 +00:00
# include "aifollow.hpp"
2014-05-03 10:23:22 +00:00
# include "aipursue.hpp"
2014-12-21 15:45:30 +00:00
# include "actor.hpp"
2015-01-05 17:52:37 +00:00
# include "summoning.hpp"
2015-01-11 01:25:46 +00:00
# include "combat.hpp"
2015-08-21 09:12:39 +00:00
# include "actorutil.hpp"
2020-04-26 17:46:51 +00:00
# include "tickableeffects.hpp"
2014-12-21 15:45:30 +00:00
2013-11-21 02:39:55 +00:00
namespace
{
2015-01-13 17:09:10 +00:00
bool isConscious ( const MWWorld : : Ptr & ptr )
{
const MWMechanics : : CreatureStats & stats = ptr . getClass ( ) . getCreatureStats ( ptr ) ;
return ! stats . isDead ( ) & & ! stats . getKnockedDown ( ) ;
}
2017-11-23 15:57:36 +00:00
int getBoundItemSlot ( const std : : string & itemId )
2013-11-21 02:39:55 +00:00
{
2017-11-23 15:57:36 +00:00
static std : : map < std : : string , int > boundItemsMap ;
if ( boundItemsMap . empty ( ) )
2013-11-21 02:39:55 +00:00
{
2018-08-29 15:38:12 +00:00
std : : string boundId = MWBase : : Environment : : get ( ) . getWorld ( ) - > getStore ( ) . get < ESM : : GameSetting > ( ) . find ( " sMagicBoundBootsID " ) - > mValue . getString ( ) ;
2017-11-23 15:57:36 +00:00
boundItemsMap [ boundId ] = MWWorld : : InventoryStore : : Slot_Boots ;
2018-08-29 15:38:12 +00:00
boundId = MWBase : : Environment : : get ( ) . getWorld ( ) - > getStore ( ) . get < ESM : : GameSetting > ( ) . find ( " sMagicBoundCuirassID " ) - > mValue . getString ( ) ;
2017-11-23 15:57:36 +00:00
boundItemsMap [ boundId ] = MWWorld : : InventoryStore : : Slot_Cuirass ;
2018-08-29 15:38:12 +00:00
boundId = MWBase : : Environment : : get ( ) . getWorld ( ) - > getStore ( ) . get < ESM : : GameSetting > ( ) . find ( " sMagicBoundLeftGauntletID " ) - > mValue . getString ( ) ;
2017-11-23 15:57:36 +00:00
boundItemsMap [ boundId ] = MWWorld : : InventoryStore : : Slot_LeftGauntlet ;
2018-08-29 15:38:12 +00:00
boundId = MWBase : : Environment : : get ( ) . getWorld ( ) - > getStore ( ) . get < ESM : : GameSetting > ( ) . find ( " sMagicBoundRightGauntletID " ) - > mValue . getString ( ) ;
2017-11-23 15:57:36 +00:00
boundItemsMap [ boundId ] = MWWorld : : InventoryStore : : Slot_RightGauntlet ;
2018-08-29 15:38:12 +00:00
boundId = MWBase : : Environment : : get ( ) . getWorld ( ) - > getStore ( ) . get < ESM : : GameSetting > ( ) . find ( " sMagicBoundHelmID " ) - > mValue . getString ( ) ;
2017-11-23 15:57:36 +00:00
boundItemsMap [ boundId ] = MWWorld : : InventoryStore : : Slot_Helmet ;
2018-08-29 15:38:12 +00:00
boundId = MWBase : : Environment : : get ( ) . getWorld ( ) - > getStore ( ) . get < ESM : : GameSetting > ( ) . find ( " sMagicBoundShieldID " ) - > mValue . getString ( ) ;
2017-11-23 15:57:36 +00:00
boundItemsMap [ boundId ] = MWWorld : : InventoryStore : : Slot_CarriedLeft ;
2013-11-21 02:39:55 +00:00
}
2017-11-23 15:57:36 +00:00
int slot = MWWorld : : InventoryStore : : Slot_CarriedRight ;
std : : map < std : : string , int > : : iterator it = boundItemsMap . find ( itemId ) ;
if ( it ! = boundItemsMap . end ( ) )
slot = it - > second ;
return slot ;
2013-11-21 02:39:55 +00:00
}
2014-08-06 19:16:14 +00:00
class CheckActorCommanded : public MWMechanics : : EffectSourceVisitor
{
MWWorld : : Ptr mActor ;
public :
bool mCommanded ;
2017-04-20 11:36:14 +00:00
CheckActorCommanded ( const MWWorld : : Ptr & actor )
2014-08-06 19:16:14 +00:00
: mActor ( actor )
, mCommanded ( false ) { }
virtual void visit ( MWMechanics : : EffectKey key ,
2015-01-05 17:52:37 +00:00
const std : : string & sourceName , const std : : string & sourceId , int casterActorId ,
2014-12-20 19:56:44 +00:00
float magnitude , float remainingTime = - 1 , float totalTime = - 1 )
2014-08-06 19:16:14 +00:00
{
2017-01-11 18:11:45 +00:00
if ( ( ( key . mId = = ESM : : MagicEffect : : CommandHumanoid & & mActor . getClass ( ) . isNpc ( ) )
| | ( key . mId = = ESM : : MagicEffect : : CommandCreature & & mActor . getTypeName ( ) = = typeid ( ESM : : Creature ) . name ( ) ) )
2014-08-06 19:16:14 +00:00
& & magnitude > = mActor . getClass ( ) . getCreatureStats ( mActor ) . getLevel ( ) )
mCommanded = true ;
}
} ;
2017-01-11 17:23:03 +00:00
// Check for command effects having ended and remove package if necessary
2014-08-06 19:16:14 +00:00
void adjustCommandedActor ( const MWWorld : : Ptr & actor )
{
CheckActorCommanded check ( actor ) ;
MWMechanics : : CreatureStats & stats = actor . getClass ( ) . getCreatureStats ( actor ) ;
stats . getActiveSpells ( ) . visitEffectSources ( check ) ;
bool hasCommandPackage = false ;
std : : list < MWMechanics : : AiPackage * > : : const_iterator it ;
for ( it = stats . getAiSequence ( ) . begin ( ) ; it ! = stats . getAiSequence ( ) . end ( ) ; + + it )
{
if ( ( * it ) - > getTypeId ( ) = = MWMechanics : : AiPackage : : TypeIdFollow & &
2015-01-12 18:57:54 +00:00
static_cast < MWMechanics : : AiFollow * > ( * it ) - > isCommanded ( ) )
2014-08-06 19:16:14 +00:00
{
hasCommandPackage = true ;
break ;
}
}
2017-01-11 17:23:03 +00:00
if ( ! check . mCommanded & & hasCommandPackage )
2014-08-06 19:16:14 +00:00
stats . getAiSequence ( ) . erase ( it ) ;
}
2014-01-14 01:52:34 +00:00
void getRestorationPerHourOfSleep ( const MWWorld : : Ptr & ptr , float & health , float & magicka )
{
MWMechanics : : CreatureStats & stats = ptr . getClass ( ) . getCreatureStats ( ptr ) ;
const MWWorld : : Store < ESM : : GameSetting > & settings = MWBase : : Environment : : get ( ) . getWorld ( ) - > getStore ( ) . get < ESM : : GameSetting > ( ) ;
int endurance = stats . getAttribute ( ESM : : Attribute : : Endurance ) . getModified ( ) ;
2015-03-08 04:42:07 +00:00
health = 0.1f * endurance ;
2014-01-14 01:52:34 +00:00
2019-02-06 18:40:50 +00:00
float fRestMagicMult = settings . find ( " fRestMagicMult " ) - > mValue . getFloat ( ) ;
magicka = fRestMagicMult * stats . getAttribute ( ESM : : Attribute : : Intelligence ) . getModified ( ) ;
2014-01-14 01:52:34 +00:00
}
2014-01-02 00:03:44 +00:00
2013-11-21 02:39:55 +00:00
}
2012-03-30 14:18:58 +00:00
namespace MWMechanics
{
2020-01-11 17:47:08 +00:00
static const int GREETING_SHOULD_START = 4 ; // how many updates should pass before NPC can greet player
static const int GREETING_SHOULD_END = 20 ; // how many updates should pass before NPC stops turning to player
static const int GREETING_COOLDOWN = 40 ; // how many updates should pass before NPC can continue movement
2019-10-31 05:44:40 +00:00
static const float DECELERATE_DISTANCE = 512.f ;
2019-10-09 16:57:24 +00:00
2019-02-06 18:40:50 +00:00
class GetStuntedMagickaDuration : public MWMechanics : : EffectSourceVisitor
{
public :
float mRemainingTime ;
GetStuntedMagickaDuration ( const MWWorld : : Ptr & actor )
: mRemainingTime ( 0.f ) { }
virtual void visit ( MWMechanics : : EffectKey key ,
const std : : string & sourceName , const std : : string & sourceId , int casterActorId ,
float magnitude , float remainingTime = - 1 , float totalTime = - 1 )
{
if ( mRemainingTime = = - 1 ) return ;
if ( key . mId = = ESM : : MagicEffect : : StuntedMagicka )
{
if ( totalTime = = - 1 )
{
mRemainingTime = - 1 ;
return ;
}
if ( remainingTime > mRemainingTime )
mRemainingTime = remainingTime ;
}
}
} ;
2014-01-02 20:21:28 +00:00
class SoulTrap : public MWMechanics : : EffectSourceVisitor
{
MWWorld : : Ptr mCreature ;
MWWorld : : Ptr mActor ;
2015-12-26 17:47:03 +00:00
bool mTrapped ;
2014-01-02 20:21:28 +00:00
public :
2017-04-20 11:36:14 +00:00
SoulTrap ( const MWWorld : : Ptr & trappedCreature )
2015-12-26 17:47:03 +00:00
: mCreature ( trappedCreature )
, mTrapped ( false )
{
}
2014-01-02 20:21:28 +00:00
virtual void visit ( MWMechanics : : EffectKey key ,
2015-01-05 17:52:37 +00:00
const std : : string & sourceName , const std : : string & sourceId , int casterActorId ,
2014-12-20 19:56:44 +00:00
float magnitude , float remainingTime = - 1 , float totalTime = - 1 )
2014-01-02 20:21:28 +00:00
{
2015-12-26 17:47:03 +00:00
if ( mTrapped )
return ;
2014-01-02 20:21:28 +00:00
if ( key . mId ! = ESM : : MagicEffect : : Soultrap )
return ;
if ( magnitude < = 0 )
return ;
MWBase : : World * world = MWBase : : Environment : : get ( ) . getWorld ( ) ;
2014-05-14 05:14:08 +00:00
MWWorld : : Ptr caster = world - > searchPtrViaActorId ( casterActorId ) ;
2014-01-02 20:21:28 +00:00
if ( caster . isEmpty ( ) | | ! caster . getClass ( ) . isActor ( ) )
return ;
2018-08-29 15:38:12 +00:00
static const float fSoulgemMult = world - > getStore ( ) . get < ESM : : GameSetting > ( ) . find ( " fSoulgemMult " ) - > mValue . getFloat ( ) ;
2014-01-02 20:21:28 +00:00
2015-03-08 04:42:07 +00:00
int creatureSoulValue = mCreature . get < ESM : : Creature > ( ) - > mBase - > mData . mSoul ;
2014-01-17 17:43:45 +00:00
if ( creatureSoulValue = = 0 )
return ;
2014-01-02 20:21:28 +00:00
// Use the smallest soulgem that is large enough to hold the soul
MWWorld : : ContainerStore & container = caster . getClass ( ) . getContainerStore ( caster ) ;
MWWorld : : ContainerStoreIterator gem = container . end ( ) ;
2014-10-12 21:26:03 +00:00
float gemCapacity = std : : numeric_limits < float > : : max ( ) ;
2014-01-02 20:21:28 +00:00
std : : string soulgemFilter = " misc_soulgem " ; // no other way to check for soulgems? :/
for ( MWWorld : : ContainerStoreIterator it = container . begin ( MWWorld : : ContainerStore : : Type_Miscellaneous ) ;
it ! = container . end ( ) ; + + it )
{
2014-05-25 12:13:07 +00:00
const std : : string & id = it - > getCellRef ( ) . getRefId ( ) ;
2014-01-02 20:21:28 +00:00
if ( id . size ( ) > = soulgemFilter . size ( )
& & id . substr ( 0 , soulgemFilter . size ( ) ) = = soulgemFilter )
{
float thisGemCapacity = it - > get < ESM : : Miscellaneous > ( ) - > mBase - > mData . mValue * fSoulgemMult ;
if ( thisGemCapacity > = creatureSoulValue & & thisGemCapacity < gemCapacity
2014-05-25 12:13:07 +00:00
& & it - > getCellRef ( ) . getSoul ( ) . empty ( ) )
2014-01-02 20:21:28 +00:00
{
gem = it ;
gemCapacity = thisGemCapacity ;
}
}
}
if ( gem = = container . end ( ) )
return ;
// Set the soul on just one of the gems, not the whole stack
gem - > getContainerStore ( ) - > unstack ( * gem , caster ) ;
2014-05-25 12:13:07 +00:00
gem - > getCellRef ( ) . setSoul ( mCreature . getCellRef ( ) . getRefId ( ) ) ;
2014-01-02 20:21:28 +00:00
2017-09-20 06:44:24 +00:00
// Restack the gem with other gems with the same soul
gem - > getContainerStore ( ) - > restack ( * gem ) ;
2015-12-26 17:47:03 +00:00
mTrapped = true ;
2015-08-21 09:12:39 +00:00
if ( caster = = getPlayer ( ) )
2014-01-02 20:21:28 +00:00
MWBase : : Environment : : get ( ) . getWindowManager ( ) - > messageBox ( " #{sSoultrapSuccess} " ) ;
2014-08-28 00:55:22 +00:00
const ESM : : Static * fx = MWBase : : Environment : : get ( ) . getWorld ( ) - > getStore ( ) . get < ESM : : Static > ( )
. search ( " VFX_Soul_Trap " ) ;
if ( fx )
MWBase : : Environment : : get ( ) . getWorld ( ) - > spawnEffect ( " meshes \\ " + fx - > mModel ,
2015-04-24 23:20:07 +00:00
" " , mCreature . getRefData ( ) . getPosition ( ) . asVec3 ( ) ) ;
2014-08-28 14:30:42 +00:00
2015-11-25 07:24:42 +00:00
MWBase : : Environment : : get ( ) . getSoundManager ( ) - > playSound3D (
mCreature . getRefData ( ) . getPosition ( ) . asVec3 ( ) , " conjuration hit " , 1.f , 1.f
) ;
2014-01-02 20:21:28 +00:00
}
} ;
2017-12-24 13:18:16 +00:00
void Actors : : addBoundItem ( const std : : string & itemId , const MWWorld : : Ptr & actor )
2017-11-23 15:57:36 +00:00
{
MWWorld : : InventoryStore & store = actor . getClass ( ) . getInventoryStore ( actor ) ;
2017-12-24 12:26:43 +00:00
int slot = getBoundItemSlot ( itemId ) ;
2017-11-23 15:57:36 +00:00
2017-12-24 13:18:16 +00:00
if ( actor . getClass ( ) . getContainerStore ( actor ) . count ( itemId ) ! = 0 )
return ;
2017-11-23 15:57:36 +00:00
2017-12-24 13:18:16 +00:00
MWWorld : : ContainerStoreIterator prevItem = store . getSlot ( slot ) ;
2017-11-23 15:57:36 +00:00
2017-12-24 13:18:16 +00:00
MWWorld : : Ptr boundPtr = * store . MWWorld : : ContainerStore : : add ( itemId , 1 , actor ) ;
MWWorld : : ActionEquip action ( boundPtr ) ;
action . execute ( actor ) ;
2017-11-23 15:57:36 +00:00
2017-12-24 13:18:16 +00:00
if ( actor ! = MWMechanics : : getPlayer ( ) )
return ;
2017-11-23 15:57:36 +00:00
2017-12-24 13:18:16 +00:00
MWWorld : : Ptr newItem = * store . getSlot ( slot ) ;
2017-11-23 15:57:36 +00:00
2017-12-24 13:18:16 +00:00
if ( newItem . isEmpty ( ) | | boundPtr ! = newItem )
return ;
2017-11-23 15:57:36 +00:00
2018-06-12 18:05:00 +00:00
MWWorld : : Player & player = MWBase : : Environment : : get ( ) . getWorld ( ) - > getPlayer ( ) ;
2017-12-24 13:18:16 +00:00
// change draw state only if the item is in player's right hand
if ( slot = = MWWorld : : InventoryStore : : Slot_CarriedRight )
2018-06-12 18:05:00 +00:00
player . setDrawState ( MWMechanics : : DrawState_Weapon ) ;
2017-11-23 15:57:36 +00:00
2017-12-24 13:18:16 +00:00
if ( prevItem ! = store . end ( ) )
2018-06-12 18:05:00 +00:00
player . setPreviousItem ( itemId , prevItem - > getCellRef ( ) . getRefId ( ) ) ;
2017-12-24 13:18:16 +00:00
}
2017-12-24 12:26:43 +00:00
2017-12-24 13:18:16 +00:00
void Actors : : removeBoundItem ( const std : : string & itemId , const MWWorld : : Ptr & actor )
{
MWWorld : : InventoryStore & store = actor . getClass ( ) . getInventoryStore ( actor ) ;
int slot = getBoundItemSlot ( itemId ) ;
2017-12-24 12:26:43 +00:00
2017-12-24 13:18:16 +00:00
MWWorld : : ContainerStoreIterator currentItem = store . getSlot ( slot ) ;
2017-11-23 15:57:36 +00:00
2018-03-09 04:56:04 +00:00
bool wasEquipped = currentItem ! = store . end ( ) & & Misc : : StringUtils : : ciEqual ( currentItem - > getCellRef ( ) . getRefId ( ) , itemId ) ;
2017-11-23 15:57:36 +00:00
2017-12-24 13:18:16 +00:00
if ( actor ! = MWMechanics : : getPlayer ( ) )
2019-10-12 10:00:36 +00:00
{
2019-12-14 17:30:46 +00:00
store . remove ( itemId , 1 , actor ) ;
2019-10-12 10:00:36 +00:00
// Equip a replacement
if ( ! wasEquipped )
return ;
std : : string type = currentItem - > getTypeName ( ) ;
if ( type ! = typeid ( ESM : : Weapon ) . name ( ) & & type ! = typeid ( ESM : : Armor ) . name ( ) & & type ! = typeid ( ESM : : Clothing ) . name ( ) )
return ;
if ( actor . getClass ( ) . getCreatureStats ( actor ) . isDead ( ) )
return ;
2019-10-27 22:58:23 +00:00
if ( ! actor . getClass ( ) . hasInventoryStore ( actor ) )
2019-10-12 10:00:36 +00:00
return ;
if ( actor . getClass ( ) . isNpc ( ) & & actor . getClass ( ) . getNpcStats ( actor ) . isWerewolf ( ) )
return ;
actor . getClass ( ) . getInventoryStore ( actor ) . autoEquip ( actor ) ;
2017-12-24 13:18:16 +00:00
return ;
2019-10-12 10:00:36 +00:00
}
2017-11-23 15:57:36 +00:00
2018-06-12 18:05:00 +00:00
MWWorld : : Player & player = MWBase : : Environment : : get ( ) . getWorld ( ) - > getPlayer ( ) ;
std : : string prevItemId = player . getPreviousItem ( itemId ) ;
player . erasePreviousItem ( itemId ) ;
2017-11-27 09:00:14 +00:00
2019-12-14 17:30:46 +00:00
if ( ! prevItemId . empty ( ) )
{
// Find previous item (or its replacement) by id.
// we should equip previous item only if expired bound item was equipped.
MWWorld : : Ptr item = store . findReplacement ( prevItemId ) ;
if ( ! item . isEmpty ( ) & & wasEquipped )
{
MWWorld : : ActionEquip action ( item ) ;
action . execute ( actor ) ;
}
}
2017-12-24 13:18:16 +00:00
2019-12-14 17:30:46 +00:00
store . remove ( itemId , 1 , actor ) ;
2017-11-23 15:57:36 +00:00
}
2012-03-30 15:01:55 +00:00
void Actors : : updateActor ( const MWWorld : : Ptr & ptr , float duration )
{
2012-05-17 11:21:49 +00:00
// magic effects
adjustMagicEffects ( ptr ) ;
2013-12-28 16:19:35 +00:00
if ( ptr . getClass ( ) . getCreatureStats ( ptr ) . needToRecalcDynamicStats ( ) )
calculateDynamicStats ( ptr ) ;
2014-04-25 02:47:45 +00:00
2013-11-16 02:16:21 +00:00
calculateCreatureStatModifiers ( ptr , duration ) ;
2014-05-15 20:03:48 +00:00
// fatigue restoration
2014-07-23 22:25:02 +00:00
calculateRestoration ( ptr , duration ) ;
2014-05-15 20:03:48 +00:00
}
2014-12-16 19:47:45 +00:00
void Actors : : updateHeadTracking ( const MWWorld : : Ptr & actor , const MWWorld : : Ptr & targetActor ,
MWWorld : : Ptr & headTrackTarget , float & sqrHeadTrackDistance )
{
2018-11-05 22:35:20 +00:00
if ( ! actor . getRefData ( ) . getBaseNode ( ) )
return ;
if ( targetActor . getClass ( ) . getCreatureStats ( targetActor ) . isDead ( ) )
return ;
2014-12-16 19:47:45 +00:00
static const float fMaxHeadTrackDistance = MWBase : : Environment : : get ( ) . getWorld ( ) - > getStore ( ) . get < ESM : : GameSetting > ( )
2018-08-29 15:38:12 +00:00
. find ( " fMaxHeadTrackDistance " ) - > mValue . getFloat ( ) ;
2014-12-16 19:47:45 +00:00
static const float fInteriorHeadTrackMult = MWBase : : Environment : : get ( ) . getWorld ( ) - > getStore ( ) . get < ESM : : GameSetting > ( )
2018-08-29 15:38:12 +00:00
. find ( " fInteriorHeadTrackMult " ) - > mValue . getFloat ( ) ;
2014-12-16 19:47:45 +00:00
float maxDistance = fMaxHeadTrackDistance ;
const ESM : : Cell * currentCell = actor . getCell ( ) - > getCell ( ) ;
if ( ! currentCell - > isExterior ( ) & & ! ( currentCell - > mData . mFlags & ESM : : Cell : : QuasiEx ) )
maxDistance * = fInteriorHeadTrackMult ;
2018-11-05 22:35:20 +00:00
const osg : : Vec3f actor1Pos ( actor . getRefData ( ) . getPosition ( ) . asVec3 ( ) ) ;
const osg : : Vec3f actor2Pos ( targetActor . getRefData ( ) . getPosition ( ) . asVec3 ( ) ) ;
float sqrDist = ( actor1Pos - actor2Pos ) . length2 ( ) ;
2014-12-16 19:47:45 +00:00
if ( sqrDist > maxDistance * maxDistance )
return ;
2014-12-18 16:36:38 +00:00
2014-12-16 19:47:45 +00:00
// stop tracking when target is behind the actor
2015-05-31 16:04:14 +00:00
osg : : Vec3f actorDirection = actor . getRefData ( ) . getBaseNode ( ) - > getAttitude ( ) * osg : : Vec3f ( 0 , 1 , 0 ) ;
2018-11-05 22:35:20 +00:00
osg : : Vec3f targetDirection ( actor2Pos - actor1Pos ) ;
2015-05-31 16:04:14 +00:00
actorDirection . z ( ) = 0 ;
targetDirection . z ( ) = 0 ;
actorDirection . normalize ( ) ;
targetDirection . normalize ( ) ;
if ( std : : acos ( actorDirection * targetDirection ) < osg : : DegreesToRadians ( 90.f )
2014-12-16 19:47:45 +00:00
& & sqrDist < = sqrHeadTrackDistance
& & MWBase : : Environment : : get ( ) . getWorld ( ) - > getLOS ( actor , targetActor ) // check LOS and awareness last as it's the most expensive function
& & MWBase : : Environment : : get ( ) . getMechanicsManager ( ) - > awarenessCheck ( targetActor , actor ) )
{
sqrHeadTrackDistance = sqrDist ;
headTrackTarget = targetActor ;
}
}
2018-12-14 15:29:56 +00:00
void Actors : : playIdleDialogue ( const MWWorld : : Ptr & actor )
{
2019-09-06 05:19:41 +00:00
if ( ! actor . getClass ( ) . isActor ( ) | | actor = = getPlayer ( ) | | MWBase : : Environment : : get ( ) . getSoundManager ( ) - > sayActive ( actor ) )
2018-12-14 15:29:56 +00:00
return ;
const CreatureStats & stats = actor . getClass ( ) . getCreatureStats ( actor ) ;
if ( stats . getAiSetting ( CreatureStats : : AI_Hello ) . getModified ( ) = = 0 )
return ;
const MWMechanics : : AiSequence & seq = stats . getAiSequence ( ) ;
if ( seq . isInCombat ( ) | | seq . hasPackage ( AiPackage : : TypeIdFollow ) | | seq . hasPackage ( AiPackage : : TypeIdEscort ) )
return ;
const osg : : Vec3f playerPos ( getPlayer ( ) . getRefData ( ) . getPosition ( ) . asVec3 ( ) ) ;
const osg : : Vec3f actorPos ( actor . getRefData ( ) . getPosition ( ) . asVec3 ( ) ) ;
MWBase : : World * world = MWBase : : Environment : : get ( ) . getWorld ( ) ;
if ( world - > isSwimming ( actor ) | | ( playerPos - actorPos ) . length2 ( ) > = 3000 * 3000 )
return ;
// Our implementation is not FPS-dependent unlike Morrowind's so it needs to be recalibrated.
// We chose to use the chance MW would have when run at 60 FPS with the default value of the GMST.
const float delta = MWBase : : Environment : : get ( ) . getFrameDuration ( ) * 6.f ;
static const float fVoiceIdleOdds = world - > getStore ( ) . get < ESM : : GameSetting > ( ) . find ( " fVoiceIdleOdds " ) - > mValue . getFloat ( ) ;
if ( Misc : : Rng : : rollProbability ( ) * 10000.f < fVoiceIdleOdds * delta & & world - > getLOS ( getPlayer ( ) , actor ) )
MWBase : : Environment : : get ( ) . getDialogueManager ( ) - > say ( actor , " idle " ) ;
}
2019-10-31 05:44:40 +00:00
void Actors : : updateMovementSpeed ( const MWWorld : : Ptr & actor )
{
float previousSpeedFactor = actor . getClass ( ) . getMovementSettings ( actor ) . mSpeedFactor ;
float newSpeedFactor = 1.f ;
CreatureStats & stats = actor . getClass ( ) . getCreatureStats ( actor ) ;
MWMechanics : : AiSequence & seq = stats . getAiSequence ( ) ;
if ( ! seq . isEmpty ( ) & & seq . getActivePackage ( ) - > useVariableSpeed ( ) )
{
osg : : Vec3f targetPos = seq . getActivePackage ( ) - > getDestination ( ) ;
osg : : Vec3f actorPos = actor . getRefData ( ) . getPosition ( ) . asVec3 ( ) ;
float distance = ( targetPos - actorPos ) . length ( ) ;
if ( distance < DECELERATE_DISTANCE )
newSpeedFactor = std : : max ( 0.7f , 0.1f * previousSpeedFactor * ( distance / 64.f + 2.f ) ) ;
}
actor . getClass ( ) . getMovementSettings ( actor ) . mSpeedFactor = newSpeedFactor ;
}
2020-05-18 20:04:48 +00:00
void Actors : : updateGreetingState ( const MWWorld : : Ptr & actor , Actor & actorState , bool turnOnly )
2019-10-09 16:57:24 +00:00
{
if ( ! actor . getClass ( ) . isActor ( ) | | actor = = getPlayer ( ) )
return ;
CreatureStats & stats = actor . getClass ( ) . getCreatureStats ( actor ) ;
const MWMechanics : : AiSequence & seq = stats . getAiSequence ( ) ;
int packageId = seq . getTypeId ( ) ;
2020-01-29 12:31:09 +00:00
if ( seq . isInCombat ( ) | |
MWBase : : Environment : : get ( ) . getWorld ( ) - > isSwimming ( actor ) | |
( packageId ! = AiPackage : : TypeIdWander & & packageId ! = AiPackage : : TypeIdTravel & & packageId ! = - 1 ) )
2019-10-09 16:57:24 +00:00
{
2020-05-18 20:04:48 +00:00
actorState . setTurningToPlayer ( false ) ;
actorState . setGreetingTimer ( 0 ) ;
actorState . setGreetingState ( Greet_None ) ;
2019-10-09 16:57:24 +00:00
return ;
}
2020-01-29 12:31:09 +00:00
MWWorld : : Ptr player = getPlayer ( ) ;
osg : : Vec3f playerPos ( player . getRefData ( ) . getPosition ( ) . asVec3 ( ) ) ;
osg : : Vec3f actorPos ( actor . getRefData ( ) . getPosition ( ) . asVec3 ( ) ) ;
osg : : Vec3f dir = playerPos - actorPos ;
2020-05-18 20:04:48 +00:00
if ( actorState . isTurningToPlayer ( ) )
2019-10-09 16:57:24 +00:00
{
// Reduce the turning animation glitch by using a *HUGE* value of
// epsilon... TODO: a proper fix might be in either the physics or the
// animation subsystem
2020-05-18 20:04:48 +00:00
if ( zTurn ( actor , actorState . getAngleToPlayer ( ) , osg : : DegreesToRadians ( 5.f ) ) )
2019-10-09 16:57:24 +00:00
{
2020-05-18 20:04:48 +00:00
actorState . setTurningToPlayer ( false ) ;
2019-10-09 16:57:24 +00:00
// An original engine launches an endless idle2 when an actor greets player.
playAnimationGroup ( actor , " idle2 " , 0 , std : : numeric_limits < int > : : max ( ) , false ) ;
}
}
if ( turnOnly )
return ;
// Play a random voice greeting if the player gets too close
static int iGreetDistanceMultiplier = MWBase : : Environment : : get ( ) . getWorld ( ) - > getStore ( )
. get < ESM : : GameSetting > ( ) . find ( " iGreetDistanceMultiplier " ) - > mValue . getInteger ( ) ;
2020-01-29 12:31:09 +00:00
float helloDistance = static_cast < float > ( stats . getAiSetting ( CreatureStats : : AI_Hello ) . getModified ( ) * iGreetDistanceMultiplier ) ;
2019-10-09 16:57:24 +00:00
2020-05-18 20:04:48 +00:00
int greetingTimer = actorState . getGreetingTimer ( ) ;
GreetingState greetingState = actorState . getGreetingState ( ) ;
2019-10-09 16:57:24 +00:00
if ( greetingState = = Greet_None )
{
if ( ( playerPos - actorPos ) . length2 ( ) < = helloDistance * helloDistance & &
! player . getClass ( ) . getCreatureStats ( player ) . isDead ( ) & & ! actor . getClass ( ) . getCreatureStats ( actor ) . isParalyzed ( )
& & MWBase : : Environment : : get ( ) . getWorld ( ) - > getLOS ( player , actor )
& & MWBase : : Environment : : get ( ) . getMechanicsManager ( ) - > awarenessCheck ( player , actor ) )
greetingTimer + + ;
if ( greetingTimer > = GREETING_SHOULD_START )
{
greetingState = Greet_InProgress ;
MWBase : : Environment : : get ( ) . getDialogueManager ( ) - > say ( actor , " hello " ) ;
greetingTimer = 0 ;
}
}
if ( greetingState = = Greet_InProgress )
{
greetingTimer + + ;
2020-01-11 17:47:08 +00:00
if ( greetingTimer < = GREETING_SHOULD_END | | MWBase : : Environment : : get ( ) . getSoundManager ( ) - > sayActive ( actor ) )
2020-05-17 01:06:39 +00:00
turnActorToFacePlayer ( actor , actorState , dir ) ;
2019-10-09 16:57:24 +00:00
2020-01-11 17:47:08 +00:00
if ( greetingTimer > = GREETING_COOLDOWN )
2019-10-09 16:57:24 +00:00
{
greetingState = Greet_Done ;
greetingTimer = 0 ;
}
}
if ( greetingState = = Greet_Done )
{
float resetDist = 2 * helloDistance ;
if ( ( playerPos - actorPos ) . length2 ( ) > = resetDist * resetDist )
greetingState = Greet_None ;
}
2020-05-18 20:04:48 +00:00
actorState . setGreetingTimer ( greetingTimer ) ;
actorState . setGreetingState ( greetingState ) ;
2019-10-09 16:57:24 +00:00
}
2020-05-18 20:04:48 +00:00
void Actors : : turnActorToFacePlayer ( const MWWorld : : Ptr & actor , Actor & actorState , const osg : : Vec3f & dir )
2019-10-09 16:57:24 +00:00
{
actor . getClass ( ) . getMovementSettings ( actor ) . mPosition [ 1 ] = 0 ;
actor . getClass ( ) . getMovementSettings ( actor ) . mPosition [ 0 ] = 0 ;
2020-05-18 20:04:48 +00:00
if ( ! actorState . isTurningToPlayer ( ) )
2019-10-09 16:57:24 +00:00
{
2020-05-18 20:04:48 +00:00
actorState . setAngleToPlayer ( std : : atan2 ( dir . x ( ) , dir . y ( ) ) ) ;
actorState . setTurningToPlayer ( true ) ;
2019-10-09 16:57:24 +00:00
}
}
2017-04-12 16:01:50 +00:00
void Actors : : engageCombat ( const MWWorld : : Ptr & actor1 , const MWWorld : : Ptr & actor2 , std : : map < const MWWorld : : Ptr , const std : : set < MWWorld : : Ptr > > & cachedAllies , bool againstPlayer )
2014-05-15 20:03:48 +00:00
{
2018-11-05 22:35:20 +00:00
// No combat for totally static creatures
if ( ! actor1 . getClass ( ) . isMobile ( actor1 ) )
return ;
2017-02-01 17:15:10 +00:00
CreatureStats & creatureStats1 = actor1 . getClass ( ) . getCreatureStats ( actor1 ) ;
2018-11-05 22:35:20 +00:00
if ( creatureStats1 . isDead ( ) | | creatureStats1 . getAiSequence ( ) . isInCombat ( actor2 ) )
2016-12-25 17:03:17 +00:00
return ;
2014-09-14 20:29:06 +00:00
2016-12-25 17:03:17 +00:00
const CreatureStats & creatureStats2 = actor2 . getClass ( ) . getCreatureStats ( actor2 ) ;
2018-11-05 22:35:20 +00:00
if ( creatureStats2 . isDead ( ) )
2014-07-27 18:30:52 +00:00
return ;
2018-11-05 22:35:20 +00:00
const osg : : Vec3f actor1Pos ( actor1 . getRefData ( ) . getPosition ( ) . asVec3 ( ) ) ;
const osg : : Vec3f actor2Pos ( actor2 . getRefData ( ) . getPosition ( ) . asVec3 ( ) ) ;
float sqrDist = ( actor1Pos - actor2Pos ) . length2 ( ) ;
2018-09-21 12:34:23 +00:00
if ( sqrDist > mActorsProcessingRange * mActorsProcessingRange )
2014-07-27 18:30:52 +00:00
return ;
2012-09-21 15:53:16 +00:00
2017-02-01 17:15:10 +00:00
// 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 ;
2017-04-12 16:01:50 +00:00
getActorsSidingWith ( actor1 , allies1 , cachedAllies ) ;
2017-02-01 17:15:10 +00:00
// If an ally of actor1 has been attacked by actor2 or has attacked actor2, start combat between actor1 and actor2
2018-11-05 22:35:20 +00:00
for ( const MWWorld : : Ptr & ally : allies1 )
2014-07-27 18:30:52 +00:00
{
2018-11-05 22:35:20 +00:00
if ( creatureStats1 . getAiSequence ( ) . isInCombat ( ally ) )
2017-02-01 17:15:10 +00:00
continue ;
2018-11-05 22:35:20 +00:00
if ( creatureStats2 . matchesActorId ( ally . getClass ( ) . getCreatureStats ( ally ) . getHitAttemptActorId ( ) ) )
2016-12-25 20:11:06 +00:00
{
MWBase : : Environment : : get ( ) . getMechanicsManager ( ) - > startCombat ( actor1 , actor2 ) ;
2017-02-01 17:15:10 +00:00
// 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
2018-11-05 22:35:20 +00:00
creatureStats1 . setHitAttemptActorId ( ally . getClass ( ) . getCreatureStats ( ally ) . getHitAttemptActorId ( ) ) ;
return ;
2016-12-25 20:11:06 +00:00
}
2014-08-13 23:08:09 +00:00
2017-02-01 17:15:10 +00:00
// If there's been no attack attempt yet but an ally of actor1 is in combat with actor2, become aggressive to actor2
2018-11-05 22:35:20 +00:00
if ( ally . getClass ( ) . getCreatureStats ( ally ) . getAiSequence ( ) . isInCombat ( actor2 ) )
2017-02-01 17:15:10 +00:00
aggressive = true ;
}
2014-09-26 20:08:07 +00:00
2017-04-12 16:01:50 +00:00
std : : set < MWWorld : : Ptr > playerAllies ;
getActorsSidingWith ( MWMechanics : : getPlayer ( ) , playerAllies , cachedAllies ) ;
2015-12-06 22:32:49 +00:00
2018-08-14 15:14:43 +00:00
bool isPlayerFollowerOrEscorter = playerAllies . find ( actor1 ) ! = playerAllies . end ( ) ;
2015-12-06 22:32:49 +00:00
2017-02-01 17:15:10 +00:00
// 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 ) )
2016-12-25 20:11:06 +00:00
{
2017-02-01 17:15:10 +00:00
std : : set < MWWorld : : Ptr > allies2 ;
2017-04-12 16:01:50 +00:00
getActorsSidingWith ( actor2 , allies2 , cachedAllies ) ;
2017-02-01 17:15:10 +00:00
// Check that an ally of actor2 is also in combat with actor1
2018-11-05 22:35:20 +00:00
for ( const MWWorld : : Ptr & ally2 : allies2 )
2017-02-01 17:15:10 +00:00
{
2018-11-05 22:35:20 +00:00
if ( ally2 . getClass ( ) . getCreatureStats ( ally2 ) . getAiSequence ( ) . isInCombat ( actor1 ) )
2017-02-01 17:15:10 +00:00
{
MWBase : : Environment : : get ( ) . getMechanicsManager ( ) - > startCombat ( actor1 , actor2 ) ;
// Also have actor1's allies start combat
2019-05-11 05:44:30 +00:00
for ( const MWWorld : : Ptr & ally1 : allies1 )
2018-11-05 22:35:20 +00:00
MWBase : : Environment : : get ( ) . getMechanicsManager ( ) - > startCombat ( ally1 , actor2 ) ;
2017-02-01 17:15:10 +00:00
return ;
}
}
2016-12-25 20:11:06 +00:00
}
2014-07-27 18:30:52 +00:00
}
2017-02-01 17:15:10 +00:00
2018-11-05 22:35:20 +00:00
// Stop here if target is unreachable
if ( ! canFight ( actor1 , actor2 ) )
return ;
2017-02-01 17:15:10 +00:00
// If set in the settings file, player followers and escorters will become aggressive toward enemies in combat with them or the player
2017-02-26 16:34:24 +00:00
static const bool followersAttackOnSight = Settings : : Manager : : getBool ( " followers attack on sight " , " Game " ) ;
if ( ! aggressive & & isPlayerFollowerOrEscorter & & followersAttackOnSight )
2016-12-25 19:50:22 +00:00
{
2017-02-01 17:15:10 +00:00
if ( actor2 . getClass ( ) . getCreatureStats ( actor2 ) . getAiSequence ( ) . isInCombat ( actor1 ) )
aggressive = true ;
else
2016-12-25 19:50:22 +00:00
{
2018-11-05 22:35:20 +00:00
for ( const MWWorld : : Ptr & ally : allies1 )
2016-12-25 19:50:22 +00:00
{
2018-11-05 22:35:20 +00:00
if ( actor2 . getClass ( ) . getCreatureStats ( actor2 ) . getAiSequence ( ) . isInCombat ( ally ) )
2017-02-01 17:15:10 +00:00
{
aggressive = true ;
break ;
}
2016-12-25 19:50:22 +00:00
}
}
}
2017-02-01 17:15:10 +00:00
// Do aggression check if actor2 is the player or a player follower or escorter
if ( ! aggressive )
2016-12-25 14:31:44 +00:00
{
2018-08-14 15:14:43 +00:00
if ( againstPlayer | | playerAllies . find ( actor2 ) ! = playerAllies . end ( ) )
2017-02-01 17:15:10 +00:00
{
// Player followers and escorters with high fight should not initiate combat with the player or with
// other player followers or escorters
2018-03-23 14:49:31 +00:00
if ( ! isPlayerFollowerOrEscorter )
2017-02-01 17:15:10 +00:00
aggressive = MWBase : : Environment : : get ( ) . getMechanicsManager ( ) - > isAggressive ( actor1 , actor2 ) ;
}
2016-12-25 14:31:44 +00:00
}
2017-09-10 11:26:48 +00:00
2017-02-01 17:15:10 +00:00
// Make guards go aggressive with creatures that are in combat, unless the creature is a follower or escorter
2018-11-05 22:35:20 +00:00
if ( ! aggressive & & actor1 . getClass ( ) . isClass ( actor1 , " Guard " ) & & ! actor2 . getClass ( ) . isNpc ( ) & & creatureStats2 . getAiSequence ( ) . isInCombat ( ) )
2016-12-25 14:31:44 +00:00
{
2017-09-10 11:26:48 +00:00
// Check if the creature is too far
2018-08-29 15:38:12 +00:00
static const float fAlarmRadius = MWBase : : Environment : : get ( ) . getWorld ( ) - > getStore ( ) . get < ESM : : GameSetting > ( ) . find ( " fAlarmRadius " ) - > mValue . getFloat ( ) ;
2017-09-10 11:26:48 +00:00
if ( sqrDist > fAlarmRadius * fAlarmRadius )
return ;
2017-02-01 17:15:10 +00:00
bool followerOrEscorter = false ;
2018-11-05 22:35:20 +00:00
for ( const AiPackage * package : creatureStats2 . getAiSequence ( ) )
2016-12-25 14:31:44 +00:00
{
2017-02-01 17:15:10 +00:00
// The follow package must be first or have nothing but combat before it
2018-11-05 22:35:20 +00:00
if ( package - > sideWithTarget ( ) )
2017-02-01 17:15:10 +00:00
{
followerOrEscorter = true ;
break ;
}
2018-11-05 22:35:20 +00:00
else if ( package - > getTypeId ( ) ! = MWMechanics : : AiPackage : : TypeIdCombat )
2017-02-01 17:15:10 +00:00
break ;
2016-12-25 14:31:44 +00:00
}
2018-11-05 22:35:20 +00:00
if ( ! followerOrEscorter )
2017-02-01 17:15:10 +00:00
aggressive = true ;
2014-07-27 18:30:52 +00:00
}
2017-02-01 17:15:10 +00:00
// If any of the above conditions turned actor1 aggressive towards actor2, do an awareness check. If it passes, start combat with actor2.
2016-12-25 17:03:17 +00:00
if ( aggressive )
2014-05-15 20:03:48 +00:00
{
2018-11-07 16:41:28 +00:00
bool LOS = MWBase : : Environment : : get ( ) . getWorld ( ) - > getLOS ( actor1 , actor2 )
& & MWBase : : Environment : : get ( ) . getMechanicsManager ( ) - > awarenessCheck ( actor2 , actor1 ) ;
2014-05-15 20:03:48 +00:00
2014-07-27 18:30:52 +00:00
if ( LOS )
MWBase : : Environment : : get ( ) . getMechanicsManager ( ) - > startCombat ( actor1 , actor2 ) ;
2013-05-27 02:54:59 +00:00
}
2012-03-30 15:01:55 +00:00
}
2012-05-17 11:21:49 +00:00
void Actors : : adjustMagicEffects ( const MWWorld : : Ptr & creature )
{
2014-05-22 18:37:22 +00:00
CreatureStats & creatureStats = creature . getClass ( ) . getCreatureStats ( creature ) ;
2019-06-13 21:42:25 +00:00
if ( creatureStats . isDeathAnimationFinished ( ) )
2014-08-10 21:52:32 +00:00
return ;
2014-08-17 01:57:26 +00:00
2012-07-22 14:29:54 +00:00
MagicEffects now = creatureStats . getSpells ( ) . getMagicEffects ( ) ;
2012-05-17 11:21:49 +00:00
2019-10-11 16:29:12 +00:00
if ( creature . getClass ( ) . hasInventoryStore ( creature ) )
2012-05-18 13:48:55 +00:00
{
2014-05-22 18:37:22 +00:00
MWWorld : : InventoryStore & store = creature . getClass ( ) . getInventoryStore ( creature ) ;
2012-05-18 13:48:55 +00:00
now + = store . getMagicEffects ( ) ;
}
2012-07-22 14:29:54 +00:00
now + = creatureStats . getActiveSpells ( ) . getMagicEffects ( ) ;
2012-05-17 11:21:49 +00:00
2014-08-17 01:57:26 +00:00
creatureStats . modifyMagicEffects ( now ) ;
2012-05-17 11:21:49 +00:00
}
2012-07-17 10:18:43 +00:00
void Actors : : calculateDynamicStats ( const MWWorld : : Ptr & ptr )
{
2014-05-22 18:37:22 +00:00
CreatureStats & creatureStats = ptr . getClass ( ) . getCreatureStats ( ptr ) ;
2012-07-17 10:18:43 +00:00
2014-08-06 00:19:32 +00:00
int intelligence = creatureStats . getAttribute ( ESM : : Attribute : : Intelligence ) . getModified ( ) ;
2012-07-17 10:18:43 +00:00
2014-09-18 12:56:43 +00:00
float base = 1.f ;
2015-08-21 09:12:39 +00:00
if ( ptr = = getPlayer ( ) )
2018-08-29 15:38:12 +00:00
base = MWBase : : Environment : : get ( ) . getWorld ( ) - > getStore ( ) . get < ESM : : GameSetting > ( ) . find ( " fPCbaseMagickaMult " ) - > mValue . getFloat ( ) ;
2014-09-18 12:56:43 +00:00
else
2018-08-29 15:38:12 +00:00
base = MWBase : : Environment : : get ( ) . getWorld ( ) - > getStore ( ) . get < ESM : : GameSetting > ( ) . find ( " fNPCbaseMagickaMult " ) - > mValue . getFloat ( ) ;
2014-09-18 12:56:43 +00:00
double magickaFactor = base +
creatureStats . getMagicEffects ( ) . get ( EffectKey ( ESM : : MagicEffect : : FortifyMaximumMagicka ) ) . getMagnitude ( ) * 0.1 ;
2012-07-17 10:18:43 +00:00
2012-10-19 11:10:06 +00:00
DynamicStat < float > magicka = creatureStats . getMagicka ( ) ;
2014-08-11 17:07:14 +00:00
float diff = ( static_cast < int > ( magickaFactor * intelligence ) ) - magicka . getBase ( ) ;
2016-06-24 15:38:59 +00:00
float currentToBaseRatio = ( magicka . getCurrent ( ) / magicka . getBase ( ) ) ;
magicka . setModified ( magicka . getModified ( ) + diff , 0 ) ;
2016-11-23 15:58:30 +00:00
magicka . setCurrent ( magicka . getBase ( ) * currentToBaseRatio , false , true ) ;
2013-08-28 05:44:52 +00:00
creatureStats . setMagicka ( magicka ) ;
2012-07-17 10:18:43 +00:00
}
2019-01-25 16:04:35 +00:00
void Actors : : restoreDynamicStats ( const MWWorld : : Ptr & ptr , double hours , bool sleep )
2012-09-21 15:53:16 +00:00
{
2018-09-23 18:03:43 +00:00
MWMechanics : : CreatureStats & stats = ptr . getClass ( ) . getCreatureStats ( ptr ) ;
if ( stats . isDead ( ) )
2013-11-29 19:21:57 +00:00
return ;
2013-10-12 22:32:28 +00:00
2014-01-14 01:52:34 +00:00
const MWWorld : : Store < ESM : : GameSetting > & settings = MWBase : : Environment : : get ( ) . getWorld ( ) - > getStore ( ) . get < ESM : : GameSetting > ( ) ;
2012-09-21 15:53:16 +00:00
2014-01-14 01:20:13 +00:00
if ( sleep )
2012-09-21 15:53:16 +00:00
{
2014-01-14 01:52:34 +00:00
float health , magicka ;
getRestorationPerHourOfSleep ( ptr , health , magicka ) ;
2012-09-21 15:53:16 +00:00
2014-01-14 01:52:34 +00:00
DynamicStat < float > stat = stats . getHealth ( ) ;
2019-01-25 16:04:35 +00:00
stat . setCurrent ( stat . getCurrent ( ) + health * hours ) ;
2014-01-14 01:52:34 +00:00
stats . setHealth ( stat ) ;
2013-03-18 09:54:47 +00:00
2019-02-06 18:40:50 +00:00
double restoreHours = hours ;
bool stunted = stats . getMagicEffects ( ) . get ( ESM : : MagicEffect : : StuntedMagicka ) . getMagnitude ( ) > 0 ;
if ( stunted )
{
// Stunted Magicka effect should be taken into account.
GetStuntedMagickaDuration visitor ( ptr ) ;
stats . getActiveSpells ( ) . visitEffectSources ( visitor ) ;
stats . getSpells ( ) . visitEffectSources ( visitor ) ;
if ( ptr . getClass ( ) . hasInventoryStore ( ptr ) )
ptr . getClass ( ) . getInventoryStore ( ptr ) . visitEffectSources ( visitor ) ;
// Take a maximum remaining duration of Stunted Magicka effects (-1 is a constant one) in game hours.
if ( visitor . mRemainingTime > 0 )
{
double timeScale = MWBase : : Environment : : get ( ) . getWorld ( ) - > getTimeScaleFactor ( ) ;
2019-09-18 06:21:25 +00:00
if ( timeScale = = 0.0 )
timeScale = 1 ;
2019-02-06 18:40:50 +00:00
restoreHours = std : : max ( 0.0 , hours - visitor . mRemainingTime * timeScale / 3600.f ) ;
}
else if ( visitor . mRemainingTime = = - 1 )
restoreHours = 0 ;
}
if ( restoreHours > 0 )
{
stat = stats . getMagicka ( ) ;
stat . setCurrent ( stat . getCurrent ( ) + magicka * restoreHours ) ;
stats . setMagicka ( stat ) ;
}
2012-09-21 15:53:16 +00:00
}
2016-11-23 15:58:30 +00:00
// Current fatigue can be above base value due to a fortify effect.
// In that case stop here and don't try to restore.
DynamicStat < float > fatigue = stats . getFatigue ( ) ;
if ( fatigue . getCurrent ( ) > = fatigue . getBase ( ) )
return ;
2013-10-12 22:32:28 +00:00
2016-11-23 15:58:30 +00:00
// Restore fatigue
2018-08-29 15:38:12 +00:00
float fFatigueReturnBase = settings . find ( " fFatigueReturnBase " ) - > mValue . getFloat ( ) ;
float fFatigueReturnMult = settings . find ( " fFatigueReturnMult " ) - > mValue . getFloat ( ) ;
float fEndFatigueMult = settings . find ( " fEndFatigueMult " ) - > mValue . getFloat ( ) ;
2013-10-12 22:32:28 +00:00
2018-09-23 18:03:43 +00:00
int endurance = stats . getAttribute ( ESM : : Attribute : : Endurance ) . getModified ( ) ;
float normalizedEncumbrance = ptr . getClass ( ) . getNormalizedEncumbrance ( ptr ) ;
if ( normalizedEncumbrance > 1 )
normalizedEncumbrance = 1 ;
2013-10-12 22:32:28 +00:00
float x = fFatigueReturnBase + fFatigueReturnMult * ( 1 - normalizedEncumbrance ) ;
x * = fEndFatigueMult * endurance ;
2019-01-25 16:04:35 +00:00
fatigue . setCurrent ( fatigue . getCurrent ( ) + 3600 * x * hours ) ;
2013-10-12 22:32:28 +00:00
stats . setFatigue ( fatigue ) ;
2014-07-23 22:25:02 +00:00
}
void Actors : : calculateRestoration ( const MWWorld : : Ptr & ptr , float duration )
{
if ( ptr . getClass ( ) . getCreatureStats ( ptr ) . isDead ( ) )
return ;
2014-01-14 01:52:34 +00:00
2014-07-23 22:25:02 +00:00
MWMechanics : : CreatureStats & stats = ptr . getClass ( ) . getCreatureStats ( ptr ) ;
2016-11-23 15:58:30 +00:00
// Current fatigue can be above base value due to a fortify effect.
// In that case stop here and don't try to restore.
DynamicStat < float > fatigue = stats . getFatigue ( ) ;
if ( fatigue . getCurrent ( ) > = fatigue . getBase ( ) )
return ;
2014-07-23 22:25:02 +00:00
2016-11-23 15:58:30 +00:00
// Restore fatigue
int endurance = stats . getAttribute ( ESM : : Attribute : : Endurance ) . getModified ( ) ;
2014-09-27 13:19:15 +00:00
const MWWorld : : Store < ESM : : GameSetting > & settings = MWBase : : Environment : : get ( ) . getWorld ( ) - > getStore ( ) . get < ESM : : GameSetting > ( ) ;
2018-08-29 15:38:12 +00:00
static const float fFatigueReturnBase = settings . find ( " fFatigueReturnBase " ) - > mValue . getFloat ( ) ;
static const float fFatigueReturnMult = settings . find ( " fFatigueReturnMult " ) - > mValue . getFloat ( ) ;
2014-07-23 22:25:02 +00:00
float x = fFatigueReturnBase + fFatigueReturnMult * endurance ;
fatigue . setCurrent ( fatigue . getCurrent ( ) + duration * x ) ;
stats . setFatigue ( fatigue ) ;
2012-09-21 15:53:16 +00:00
}
2016-07-05 22:20:23 +00:00
class ExpiryVisitor : public EffectSourceVisitor
{
private :
MWWorld : : Ptr mActor ;
float mDuration ;
public :
ExpiryVisitor ( const MWWorld : : Ptr & actor , float duration )
: mActor ( actor ) , mDuration ( duration )
{
}
virtual void visit ( MWMechanics : : EffectKey key ,
const std : : string & /*sourceName*/ , const std : : string & /*sourceId*/ , int /*casterActorId*/ ,
float magnitude , float remainingTime = - 1 , float /*totalTime*/ = - 1 )
{
if ( magnitude > 0 & & remainingTime > 0 & & remainingTime < mDuration )
{
CreatureStats & creatureStats = mActor . getClass ( ) . getCreatureStats ( mActor ) ;
2016-12-05 11:12:13 +00:00
if ( effectTick ( creatureStats , mActor , key , magnitude * remainingTime ) )
creatureStats . getMagicEffects ( ) . add ( key , - magnitude ) ;
2016-07-05 22:20:23 +00:00
}
}
} ;
2013-11-16 02:16:21 +00:00
void Actors : : calculateCreatureStatModifiers ( const MWWorld : : Ptr & ptr , float duration )
2012-07-17 13:49:37 +00:00
{
2014-05-22 18:37:22 +00:00
CreatureStats & creatureStats = ptr . getClass ( ) . getCreatureStats ( ptr ) ;
2013-08-28 04:40:31 +00:00
const MagicEffects & effects = creatureStats . getMagicEffects ( ) ;
2012-07-17 13:49:37 +00:00
2014-06-17 01:54:41 +00:00
bool wasDead = creatureStats . isDead ( ) ;
2015-12-15 19:41:00 +00:00
if ( duration > 0 )
2015-07-18 18:39:45 +00:00
{
2016-12-05 11:12:13 +00:00
// Apply correct magnitude for tickable effects that have just expired,
// in case duration > remaining time of effect.
// One case where this will happen is when the player uses the rest/wait command
// while there is a tickable effect active that should expire before the end of the rest/wait.
2016-07-05 22:20:23 +00:00
ExpiryVisitor visitor ( ptr , duration ) ;
creatureStats . getActiveSpells ( ) . visitEffectSources ( visitor ) ;
2015-12-15 19:41:00 +00:00
for ( MagicEffects : : Collection : : const_iterator it = effects . begin ( ) ; it ! = effects . end ( ) ; + + it )
{
// tickable effects (i.e. effects having a lasting impact after expiry)
effectTick ( creatureStats , ptr , it - > first , it - > second . getMagnitude ( ) * duration ) ;
2015-12-14 02:27:49 +00:00
2015-12-15 19:41:00 +00:00
// instant effects are already applied on spell impact in spellcasting.cpp, but may also come from permanent abilities
2015-12-15 19:46:05 +00:00
if ( it - > second . getMagnitude ( ) > 0 )
{
CastSpell cast ( ptr , ptr ) ;
2015-12-16 18:33:39 +00:00
if ( cast . applyInstantEffect ( ptr , ptr , it - > first , it - > second . getMagnitude ( ) ) )
{
2016-07-01 16:50:28 +00:00
creatureStats . getSpells ( ) . purgeEffect ( it - > first . mId ) ;
2015-12-16 18:33:39 +00:00
creatureStats . getActiveSpells ( ) . purgeEffect ( it - > first . mId ) ;
if ( ptr . getClass ( ) . hasInventoryStore ( ptr ) )
ptr . getClass ( ) . getInventoryStore ( ptr ) . purgeEffect ( it - > first . mId ) ;
}
2015-12-15 19:46:05 +00:00
}
2015-12-15 19:41:00 +00:00
}
2015-07-18 18:39:45 +00:00
}
2015-01-01 01:46:18 +00:00
2017-04-19 14:38:25 +00:00
// purge levitate effect if levitation is disabled
// check only modifier, because base value can be setted from SetFlying console command.
if ( MWBase : : Environment : : get ( ) . getWorld ( ) - > isLevitationEnabled ( ) = = false & & effects . get ( ESM : : MagicEffect : : Levitate ) . getModifier ( ) > 0 )
{
creatureStats . getSpells ( ) . purgeEffect ( ESM : : MagicEffect : : Levitate ) ;
creatureStats . getActiveSpells ( ) . purgeEffect ( ESM : : MagicEffect : : Levitate ) ;
if ( ptr . getClass ( ) . hasInventoryStore ( ptr ) )
ptr . getClass ( ) . getInventoryStore ( ptr ) . purgeEffect ( ESM : : MagicEffect : : Levitate ) ;
if ( ptr = = getPlayer ( ) )
{
MWBase : : Environment : : get ( ) . getWindowManager ( ) - > messageBox ( " #{sLevitateDisabled} " ) ;
}
}
2017-12-03 12:34:17 +00:00
// dynamic stats
for ( int i = 0 ; i < 3 ; + + i )
{
DynamicStat < float > stat = creatureStats . getDynamic ( i ) ;
stat . setCurrentModifier ( effects . get ( ESM : : MagicEffect : : FortifyHealth + i ) . getMagnitude ( ) -
effects . get ( ESM : : MagicEffect : : DrainHealth + i ) . getMagnitude ( ) ,
// Magicka can be decreased below zero due to a fortify effect wearing off
// Fatigue can be decreased below zero meaning the actor will be knocked out
i = = 1 | | i = = 2 ) ;
creatureStats . setDynamic ( i , stat ) ;
}
2012-07-17 13:49:37 +00:00
// attributes
2013-08-28 04:40:31 +00:00
for ( int i = 0 ; i < ESM : : Attribute : : Length ; + + i )
2012-07-17 13:49:37 +00:00
{
2014-01-03 00:59:15 +00:00
AttributeValue stat = creatureStats . getAttribute ( i ) ;
2015-03-14 19:49:03 +00:00
stat . setModifier ( static_cast < int > ( effects . get ( EffectKey ( ESM : : MagicEffect : : FortifyAttribute , i ) ) . getMagnitude ( ) -
2015-03-07 15:23:02 +00:00
effects . get ( EffectKey ( ESM : : MagicEffect : : DrainAttribute , i ) ) . getMagnitude ( ) -
2015-03-14 19:49:03 +00:00
effects . get ( EffectKey ( ESM : : MagicEffect : : AbsorbAttribute , i ) ) . getMagnitude ( ) ) ) ;
2012-07-17 13:49:37 +00:00
2013-08-28 04:40:31 +00:00
creatureStats . setAttribute ( i , stat ) ;
2012-07-17 13:49:37 +00:00
}
2016-06-29 17:20:27 +00:00
if ( creatureStats . needToRecalcDynamicStats ( ) )
calculateDynamicStats ( ptr ) ;
2014-08-18 13:33:12 +00:00
{
Spells & spells = creatureStats . getSpells ( ) ;
for ( Spells : : TIterator it = spells . begin ( ) ; it ! = spells . end ( ) ; + + it )
{
2014-08-20 10:40:38 +00:00
if ( spells . getCorprusSpells ( ) . find ( it - > first ) ! = spells . getCorprusSpells ( ) . end ( ) )
2014-08-18 13:33:12 +00:00
{
2014-08-20 10:40:38 +00:00
if ( MWBase : : Environment : : get ( ) . getWorld ( ) - > getTimeStamp ( ) > = spells . getCorprusSpells ( ) . at ( it - > first ) . mNextWorsening )
2014-08-18 13:33:12 +00:00
{
spells . worsenCorprus ( it - > first ) ;
2015-08-21 09:12:39 +00:00
if ( ptr = = getPlayer ( ) )
2014-08-18 13:33:12 +00:00
MWBase : : Environment : : get ( ) . getWindowManager ( ) - > messageBox ( " #{sMagicCorprusWorsens} " ) ;
}
}
}
}
2014-01-05 00:34:35 +00:00
// AI setting modifiers
int creature = ! ptr . getClass ( ) . isNpc ( ) ;
if ( creature & & ptr . get < ESM : : Creature > ( ) - > mBase - > mData . mType = = ESM : : Creature : : Humanoid )
creature = false ;
// Note: the Creature variants only work on normal creatures, not on daedra or undead creatures.
if ( ! creature | | ptr . get < ESM : : Creature > ( ) - > mBase - > mData . mType = = ESM : : Creature : : Creatures )
{
Stat < int > stat = creatureStats . getAiSetting ( CreatureStats : : AI_Fight ) ;
2015-08-20 06:17:02 +00:00
stat . setModifier ( static_cast < int > ( effects . get ( ESM : : MagicEffect : : FrenzyHumanoid + creature ) . getMagnitude ( )
- effects . get ( ESM : : MagicEffect : : CalmHumanoid + creature ) . getMagnitude ( ) ) ) ;
2014-01-05 00:34:35 +00:00
creatureStats . setAiSetting ( CreatureStats : : AI_Fight , stat ) ;
stat = creatureStats . getAiSetting ( CreatureStats : : AI_Flee ) ;
2015-08-20 06:17:02 +00:00
stat . setModifier ( static_cast < int > ( effects . get ( ESM : : MagicEffect : : DemoralizeHumanoid + creature ) . getMagnitude ( )
- effects . get ( ESM : : MagicEffect : : RallyHumanoid + creature ) . getMagnitude ( ) ) ) ;
2014-01-05 00:34:35 +00:00
creatureStats . setAiSetting ( CreatureStats : : AI_Flee , stat ) ;
}
if ( creature & & ptr . get < ESM : : Creature > ( ) - > mBase - > mData . mType = = ESM : : Creature : : Undead )
{
Stat < int > stat = creatureStats . getAiSetting ( CreatureStats : : AI_Flee ) ;
2015-08-20 06:17:02 +00:00
stat . setModifier ( static_cast < int > ( effects . get ( ESM : : MagicEffect : : TurnUndead ) . getMagnitude ( ) ) ) ;
2014-01-05 00:34:35 +00:00
creatureStats . setAiSetting ( CreatureStats : : AI_Flee , stat ) ;
}
2014-06-17 01:54:41 +00:00
if ( ! wasDead & & creatureStats . isDead ( ) )
{
// The actor was killed by a magic effect. Figure out if the player was responsible for it.
const ActiveSpells & spells = creatureStats . getActiveSpells ( ) ;
2015-08-21 09:12:39 +00:00
MWWorld : : Ptr player = getPlayer ( ) ;
2018-06-15 10:03:43 +00:00
std : : set < MWWorld : : Ptr > playerFollowers ;
getActorsSidingWith ( player , playerFollowers ) ;
2014-06-17 01:54:41 +00:00
for ( ActiveSpells : : TIterator it = spells . begin ( ) ; it ! = spells . end ( ) ; + + it )
{
2018-07-24 19:52:05 +00:00
bool actorKilled = false ;
2014-06-17 01:54:41 +00:00
const ActiveSpells : : ActiveSpellParams & spell = it - > second ;
2018-06-15 10:03:43 +00:00
MWWorld : : Ptr caster = MWBase : : Environment : : get ( ) . getWorld ( ) - > searchPtrViaActorId ( spell . mCasterActorId ) ;
2014-06-17 01:54:41 +00:00
for ( std : : vector < ActiveSpells : : ActiveEffect > : : const_iterator effectIt = spell . mEffects . begin ( ) ;
effectIt ! = spell . mEffects . end ( ) ; + + effectIt )
{
int effectId = effectIt - > mEffectId ;
bool isDamageEffect = false ;
2015-07-18 18:39:45 +00:00
int damageEffects [ ] = {
ESM : : MagicEffect : : FireDamage , ESM : : MagicEffect : : ShockDamage , ESM : : MagicEffect : : FrostDamage , ESM : : MagicEffect : : Poison ,
ESM : : MagicEffect : : SunDamage , ESM : : MagicEffect : : DamageHealth , ESM : : MagicEffect : : AbsorbHealth
} ;
2014-06-17 01:54:41 +00:00
for ( unsigned int i = 0 ; i < sizeof ( damageEffects ) / sizeof ( int ) ; + + i )
{
if ( damageEffects [ i ] = = effectId )
isDamageEffect = true ;
}
2018-06-15 10:03:43 +00:00
if ( isDamageEffect )
{
if ( caster = = player | | playerFollowers . find ( caster ) ! = playerFollowers . end ( ) )
{
2019-11-17 05:17:03 +00:00
if ( caster . getClass ( ) . isNpc ( ) & & caster . getClass ( ) . getNpcStats ( caster ) . isWerewolf ( ) )
2018-06-15 10:03:43 +00:00
caster . getClass ( ) . getNpcStats ( caster ) . addWerewolfKill ( ) ;
MWBase : : Environment : : get ( ) . getMechanicsManager ( ) - > actorKilled ( ptr , player ) ;
2018-07-24 19:52:05 +00:00
actorKilled = true ;
2018-06-15 10:03:43 +00:00
break ;
}
}
2014-06-17 01:54:41 +00:00
}
2018-07-24 19:52:05 +00:00
if ( actorKilled )
break ;
2014-06-17 01:54:41 +00:00
}
}
2013-11-21 02:39:55 +00:00
// TODO: dirty flag for magic effects to avoid some unnecessary work below?
2014-12-14 18:35:34 +00:00
// any value of calm > 0 will stop the actor from fighting
2015-08-20 06:17:02 +00:00
if ( ( effects . get ( ESM : : MagicEffect : : CalmHumanoid ) . getMagnitude ( ) > 0 & & ptr . getClass ( ) . isNpc ( ) )
2017-04-20 10:09:38 +00:00
| | ( effects . get ( ESM : : MagicEffect : : CalmCreature ) . getMagnitude ( ) > 0 & & ! ptr . getClass ( ) . isNpc ( ) ) )
creatureStats . getAiSequence ( ) . stopCombat ( ) ;
2014-12-14 18:35:34 +00:00
2013-11-21 02:39:55 +00:00
// Update bound effects
2015-01-01 01:46:18 +00:00
// Note: in vanilla MW multiple bound items of the same type can be created by different spells.
// As these extra copies are kinda useless this may or may not be important.
2013-11-21 02:39:55 +00:00
static std : : map < int , std : : string > boundItemsMap ;
if ( boundItemsMap . empty ( ) )
{
2014-01-03 19:54:14 +00:00
boundItemsMap [ ESM : : MagicEffect : : BoundBattleAxe ] = " sMagicBoundBattleAxeID " ;
boundItemsMap [ ESM : : MagicEffect : : BoundBoots ] = " sMagicBoundBootsID " ;
boundItemsMap [ ESM : : MagicEffect : : BoundCuirass ] = " sMagicBoundCuirassID " ;
boundItemsMap [ ESM : : MagicEffect : : BoundDagger ] = " sMagicBoundDaggerID " ;
boundItemsMap [ ESM : : MagicEffect : : BoundGloves ] = " sMagicBoundLeftGauntletID " ; // Note: needs RightGauntlet variant too (see below)
boundItemsMap [ ESM : : MagicEffect : : BoundHelm ] = " sMagicBoundHelmID " ;
boundItemsMap [ ESM : : MagicEffect : : BoundLongbow ] = " sMagicBoundLongbowID " ;
boundItemsMap [ ESM : : MagicEffect : : BoundLongsword ] = " sMagicBoundLongswordID " ;
boundItemsMap [ ESM : : MagicEffect : : BoundMace ] = " sMagicBoundMaceID " ;
boundItemsMap [ ESM : : MagicEffect : : BoundShield ] = " sMagicBoundShieldID " ;
boundItemsMap [ ESM : : MagicEffect : : BoundSpear ] = " sMagicBoundSpearID " ;
2013-11-21 02:39:55 +00:00
}
for ( std : : map < int , std : : string > : : iterator it = boundItemsMap . begin ( ) ; it ! = boundItemsMap . end ( ) ; + + it )
{
bool found = creatureStats . mBoundItems . find ( it - > first ) ! = creatureStats . mBoundItems . end ( ) ;
2015-08-20 06:17:02 +00:00
float magnitude = effects . get ( it - > first ) . getMagnitude ( ) ;
2013-11-21 02:39:55 +00:00
if ( found ! = ( magnitude > 0 ) )
{
2017-12-24 12:26:43 +00:00
if ( magnitude > 0 )
creatureStats . mBoundItems . insert ( it - > first ) ;
else
creatureStats . mBoundItems . erase ( it - > first ) ;
2014-01-03 19:54:14 +00:00
std : : string itemGmst = it - > second ;
std : : string item = MWBase : : Environment : : get ( ) . getWorld ( ) - > getStore ( ) . get < ESM : : GameSetting > ( ) . find (
2018-08-29 15:38:12 +00:00
itemGmst ) - > mValue . getString ( ) ;
2017-12-24 12:26:43 +00:00
2017-12-24 13:18:16 +00:00
magnitude > 0 ? addBoundItem ( item , ptr ) : removeBoundItem ( item , ptr ) ;
2013-11-21 02:39:55 +00:00
if ( it - > first = = ESM : : MagicEffect : : BoundGloves )
{
2014-06-19 14:00:27 +00:00
item = MWBase : : Environment : : get ( ) . getWorld ( ) - > getStore ( ) . get < ESM : : GameSetting > ( ) . find (
2018-08-29 15:38:12 +00:00
" sMagicBoundRightGauntletID " ) - > mValue . getString ( ) ;
2017-12-24 13:18:16 +00:00
magnitude > 0 ? addBoundItem ( item , ptr ) : removeBoundItem ( item , ptr ) ;
2013-11-21 02:39:55 +00:00
}
}
}
2017-02-04 16:54:59 +00:00
bool hasSummonEffect = false ;
for ( MagicEffects : : Collection : : const_iterator it = effects . begin ( ) ; it ! = effects . end ( ) ; + + it )
2018-12-29 22:27:16 +00:00
{
2017-02-14 06:57:48 +00:00
if ( isSummoningEffect ( it - > first . mId ) )
2018-12-29 22:27:16 +00:00
{
2017-02-04 16:54:59 +00:00
hasSummonEffect = true ;
2018-12-29 22:27:16 +00:00
break ;
}
}
2017-02-04 16:54:59 +00:00
if ( ! creatureStats . getSummonedCreatureMap ( ) . empty ( ) | | ! creatureStats . getSummonedCreatureGraveyard ( ) . empty ( ) | | hasSummonEffect )
{
UpdateSummonedCreatures updateSummonedCreatures ( ptr ) ;
creatureStats . getActiveSpells ( ) . visitEffectSources ( updateSummonedCreatures ) ;
if ( ptr . getClass ( ) . hasInventoryStore ( ptr ) )
ptr . getClass ( ) . getInventoryStore ( ptr ) . visitEffectSources ( updateSummonedCreatures ) ;
2017-08-18 07:58:28 +00:00
updateSummonedCreatures . process ( mTimerDisposeSummonsCorpses = = 0.f ) ;
2017-02-04 16:54:59 +00:00
}
2012-07-17 13:49:37 +00:00
}
2014-11-02 17:01:12 +00:00
void Actors : : calculateNpcStatModifiers ( const MWWorld : : Ptr & ptr , float duration )
2013-11-09 09:49:00 +00:00
{
2014-05-22 18:37:22 +00:00
NpcStats & npcStats = ptr . getClass ( ) . getNpcStats ( ptr ) ;
2013-11-09 09:49:00 +00:00
const MagicEffects & effects = npcStats . getMagicEffects ( ) ;
// skills
for ( int i = 0 ; i < ESM : : Skill : : Length ; + + i )
{
2014-01-03 02:46:30 +00:00
SkillValue & skill = npcStats . getSkill ( i ) ;
2015-03-14 19:49:03 +00:00
skill . setModifier ( static_cast < int > ( effects . get ( EffectKey ( ESM : : MagicEffect : : FortifySkill , i ) ) . getMagnitude ( ) -
2015-03-07 15:23:02 +00:00
effects . get ( EffectKey ( ESM : : MagicEffect : : DrainSkill , i ) ) . getMagnitude ( ) -
2015-03-14 19:49:03 +00:00
effects . get ( EffectKey ( ESM : : MagicEffect : : AbsorbSkill , i ) ) . getMagnitude ( ) ) ) ;
2013-11-09 09:49:00 +00:00
}
}
2018-08-16 13:47:06 +00:00
bool Actors : : isAttackPreparing ( const MWWorld : : Ptr & ptr )
2017-09-01 05:34:15 +00:00
{
PtrActorMap : : iterator it = mActors . find ( ptr ) ;
if ( it = = mActors . end ( ) )
return false ;
CharacterController * ctrl = it - > second - > getCharacterController ( ) ;
2018-08-16 13:47:06 +00:00
return ctrl - > isAttackPreparing ( ) ;
2017-09-01 05:34:15 +00:00
}
2017-08-16 16:30:47 +00:00
bool Actors : : isRunning ( const MWWorld : : Ptr & ptr )
{
PtrActorMap : : iterator it = mActors . find ( ptr ) ;
if ( it = = mActors . end ( ) )
return false ;
CharacterController * ctrl = it - > second - > getCharacterController ( ) ;
return ctrl - > isRunning ( ) ;
}
bool Actors : : isSneaking ( const MWWorld : : Ptr & ptr )
{
PtrActorMap : : iterator it = mActors . find ( ptr ) ;
if ( it = = mActors . end ( ) )
return false ;
CharacterController * ctrl = it - > second - > getCharacterController ( ) ;
return ctrl - > isSneaking ( ) ;
}
2018-09-22 11:48:36 +00:00
void Actors : : updateDrowning ( const MWWorld : : Ptr & ptr , float duration , bool isKnockedOut , bool isPlayer )
2013-08-07 13:34:11 +00:00
{
2013-08-28 00:08:23 +00:00
NpcStats & stats = ptr . getClass ( ) . getNpcStats ( ptr ) ;
2017-04-16 18:15:25 +00:00
// When npc stats are just initialized, mTimeToStartDrowning == -1 and we should get value from GMST
2018-08-29 15:38:12 +00:00
static const float fHoldBreathTime = MWBase : : Environment : : get ( ) . getWorld ( ) - > getStore ( ) . get < ESM : : GameSetting > ( ) . find ( " fHoldBreathTime " ) - > mValue . getFloat ( ) ;
2017-04-16 18:15:25 +00:00
if ( stats . getTimeToStartDrowning ( ) = = - 1.f )
stats . setTimeToStartDrowning ( fHoldBreathTime ) ;
2018-11-05 22:35:20 +00:00
if ( ! isPlayer & & stats . getTimeToStartDrowning ( ) < fHoldBreathTime / 2 )
2017-07-25 05:51:55 +00:00
{
2018-11-05 22:35:20 +00:00
AiSequence & seq = ptr . getClass ( ) . getCreatureStats ( ptr ) . getAiSequence ( ) ;
if ( seq . getTypeId ( ) ! = AiPackage : : TypeIdBreathe ) //Only add it once
seq . stack ( AiBreathe ( ) , ptr ) ;
2017-07-25 05:51:55 +00:00
}
2014-12-12 16:39:00 +00:00
MWBase : : World * world = MWBase : : Environment : : get ( ) . getWorld ( ) ;
2018-09-22 11:48:36 +00:00
bool knockedOutUnderwater = ( isKnockedOut & & world - > isUnderwater ( ptr . getCell ( ) , osg : : Vec3f ( ptr . getRefData ( ) . getPosition ( ) . asVec3 ( ) ) ) ) ;
2014-12-12 16:39:00 +00:00
if ( ( world - > isSubmerged ( ptr ) | | knockedOutUnderwater )
& & stats . getMagicEffects ( ) . get ( ESM : : MagicEffect : : WaterBreathing ) . getMagnitude ( ) = = 0 )
2013-08-07 13:34:11 +00:00
{
2013-08-28 00:08:23 +00:00
float timeLeft = 0.0f ;
2014-12-12 16:39:00 +00:00
if ( knockedOutUnderwater )
2013-08-07 13:34:11 +00:00
stats . setTimeToStartDrowning ( 0 ) ;
2013-08-28 00:08:23 +00:00
else
{
timeLeft = stats . getTimeToStartDrowning ( ) - duration ;
if ( timeLeft < 0.0f )
timeLeft = 0.0f ;
stats . setTimeToStartDrowning ( timeLeft ) ;
}
2017-03-25 18:40:11 +00:00
2018-09-22 11:48:36 +00:00
bool godmode = isPlayer & & MWBase : : Environment : : get ( ) . getWorld ( ) - > getGodModeState ( ) ;
2017-03-25 18:40:11 +00:00
if ( timeLeft = = 0.0f & & ! godmode )
2013-08-28 00:08:23 +00:00
{
// If drowning, apply 3 points of damage per second
2018-08-29 15:38:12 +00:00
static const float fSuffocationDamage = world - > getStore ( ) . get < ESM : : GameSetting > ( ) . find ( " fSuffocationDamage " ) - > mValue . getFloat ( ) ;
2015-07-24 18:23:27 +00:00
DynamicStat < float > health = stats . getHealth ( ) ;
health . setCurrent ( health . getCurrent ( ) - fSuffocationDamage * duration ) ;
stats . setHealth ( health ) ;
2013-08-28 00:08:23 +00:00
2014-10-11 20:21:48 +00:00
// Play a drowning sound
MWBase : : SoundManager * sndmgr = MWBase : : Environment : : get ( ) . getSoundManager ( ) ;
if ( ! sndmgr - > getSoundPlaying ( ptr , " drown " ) )
sndmgr - > playSound3D ( ptr , " drown " , 1.0f , 1.0f ) ;
2018-09-22 11:48:36 +00:00
if ( isPlayer )
2014-10-11 20:21:48 +00:00
MWBase : : Environment : : get ( ) . getWindowManager ( ) - > activateHitOverlay ( false ) ;
2013-08-28 00:08:23 +00:00
}
2013-08-07 13:34:11 +00:00
}
else
2014-01-10 21:27:31 +00:00
stats . setTimeToStartDrowning ( fHoldBreathTime ) ;
2013-08-07 13:34:11 +00:00
}
2018-03-03 13:18:40 +00:00
void Actors : : updateEquippedLight ( const MWWorld : : Ptr & ptr , float duration , bool mayEquip )
2013-10-15 19:23:42 +00:00
{
2015-08-21 09:12:39 +00:00
bool isPlayer = ( ptr = = getPlayer ( ) ) ;
2013-12-20 21:38:23 +00:00
2014-05-22 18:37:22 +00:00
MWWorld : : InventoryStore & inventoryStore = ptr . getClass ( ) . getInventoryStore ( ptr ) ;
2013-10-15 19:23:42 +00:00
MWWorld : : ContainerStoreIterator heldIter =
inventoryStore . getSlot ( MWWorld : : InventoryStore : : Slot_CarriedLeft ) ;
2013-12-20 21:38:23 +00:00
/**
* Automatically equip NPCs torches at night and unequip them at day
*/
2013-12-30 16:53:02 +00:00
if ( ! isPlayer )
2013-12-20 21:38:23 +00:00
{
2013-12-30 16:53:02 +00:00
MWWorld : : ContainerStoreIterator torch = inventoryStore . end ( ) ;
for ( MWWorld : : ContainerStoreIterator it = inventoryStore . begin ( ) ; it ! = inventoryStore . end ( ) ; + + it )
2013-12-20 21:38:23 +00:00
{
2018-06-16 13:34:49 +00:00
if ( it - > getTypeName ( ) = = typeid ( ESM : : Light ) . name ( ) & &
it - > getClass ( ) . canBeEquipped ( * it , ptr ) . first )
2013-12-30 16:53:02 +00:00
{
torch = it ;
break ;
}
2013-12-20 21:38:23 +00:00
}
2013-12-30 16:53:02 +00:00
2018-03-03 13:18:40 +00:00
if ( mayEquip )
2013-12-20 21:38:23 +00:00
{
2013-12-30 16:53:02 +00:00
if ( torch ! = inventoryStore . end ( ) )
{
2014-07-28 14:41:12 +00:00
if ( ! ptr . getClass ( ) . getCreatureStats ( ptr ) . getAiSequence ( ) . isInCombat ( ) )
2013-12-30 16:53:02 +00:00
{
// For non-hostile NPCs, unequip whatever is in the left slot in favor of a light.
if ( heldIter ! = inventoryStore . end ( ) & & heldIter - > getTypeName ( ) ! = typeid ( ESM : : Light ) . name ( ) )
inventoryStore . unequipItem ( * heldIter , ptr ) ;
}
heldIter = inventoryStore . getSlot ( MWWorld : : InventoryStore : : Slot_CarriedLeft ) ;
2018-03-03 10:16:02 +00:00
// If we have a torch and can equip it, then equip it now.
2019-10-27 22:58:23 +00:00
if ( heldIter = = inventoryStore . end ( ) )
2013-12-30 16:53:02 +00:00
{
inventoryStore . equip ( MWWorld : : InventoryStore : : Slot_CarriedLeft , torch , ptr ) ;
}
}
2013-12-20 21:38:23 +00:00
}
2013-12-30 16:53:02 +00:00
else
2013-12-20 21:38:23 +00:00
{
2013-12-30 16:53:02 +00:00
if ( heldIter ! = inventoryStore . end ( ) & & heldIter - > getTypeName ( ) = = typeid ( ESM : : Light ) . name ( ) )
{
// At day, unequip lights and auto equip shields or other suitable items
// (Note: autoEquip will ignore lights)
inventoryStore . autoEquip ( ptr ) ;
}
2013-12-20 21:38:23 +00:00
}
}
2013-10-15 19:23:42 +00:00
2013-12-30 16:53:02 +00:00
heldIter = inventoryStore . getSlot ( MWWorld : : InventoryStore : : Slot_CarriedLeft ) ;
2013-12-20 21:38:23 +00:00
//If holding a light...
2013-10-15 19:23:42 +00:00
if ( heldIter . getType ( ) = = MWWorld : : ContainerStore : : Type_Light )
{
2013-10-16 00:56:42 +00:00
// Use time from the player's light
if ( isPlayer )
{
2020-04-04 16:20:52 +00:00
// But avoid using it up if the light source is hidden
MWRender : : Animation * anim = MWBase : : Environment : : get ( ) . getWorld ( ) - > getAnimation ( ptr ) ;
if ( anim & & anim - > getCarriedLeftShown ( ) )
2013-10-16 00:56:42 +00:00
{
2020-04-04 16:20:52 +00:00
float timeRemaining = heldIter - > getClass ( ) . getRemainingUsageTime ( * heldIter ) ;
2013-10-16 00:56:42 +00:00
2020-04-04 16:20:52 +00:00
// -1 is infinite light source. Other negative values are treated as 0.
if ( timeRemaining ! = - 1.0f )
2013-10-16 00:56:42 +00:00
{
2020-04-04 16:20:52 +00:00
timeRemaining - = duration ;
if ( timeRemaining < = 0.f )
{
inventoryStore . remove ( * heldIter , 1 , ptr ) ; // remove it
return ;
}
heldIter - > getClass ( ) . setRemainingUsageTime ( * heldIter , timeRemaining ) ;
2013-10-16 00:56:42 +00:00
}
}
}
2013-10-15 19:23:42 +00:00
2013-10-16 00:56:42 +00:00
// Both NPC and player lights extinguish in water.
if ( MWBase : : Environment : : get ( ) . getWorld ( ) - > isSwimming ( ptr ) )
2013-10-15 19:23:42 +00:00
{
2013-08-12 23:19:33 +00:00
inventoryStore . remove ( * heldIter , 1 , ptr ) ; // remove it
2013-10-15 19:23:42 +00:00
2013-10-16 00:56:42 +00:00
// ...But, only the player makes a sound.
if ( isPlayer )
MWBase : : Environment : : get ( ) . getSoundManager ( ) - > playSound ( " torch out " ,
2017-09-15 08:03:41 +00:00
1.0 , 1.0 , MWSound : : Type : : Sfx , MWSound : : PlayMode : : NoEnv ) ;
2013-10-15 19:23:42 +00:00
}
}
}
2018-03-23 12:10:43 +00:00
void Actors : : updateCrimePursuit ( const MWWorld : : Ptr & ptr , float duration )
2014-04-03 04:50:09 +00:00
{
2015-08-21 09:12:39 +00:00
MWWorld : : Ptr player = getPlayer ( ) ;
2014-04-06 02:45:40 +00:00
if ( ptr ! = player & & ptr . getClass ( ) . isNpc ( ) )
2014-04-03 04:50:09 +00:00
{
2014-04-05 14:26:14 +00:00
// get stats of witness
2014-05-22 18:37:22 +00:00
CreatureStats & creatureStats = ptr . getClass ( ) . getCreatureStats ( ptr ) ;
NpcStats & npcStats = ptr . getClass ( ) . getNpcStats ( ptr ) ;
2014-04-05 14:26:14 +00:00
2014-09-09 02:15:54 +00:00
if ( player . getClass ( ) . getNpcStats ( player ) . isWerewolf ( ) )
return ;
2017-12-05 14:41:08 +00:00
if ( ptr . getClass ( ) . isClass ( ptr , " Guard " ) & & creatureStats . getAiSequence ( ) . getTypeId ( ) ! = AiPackage : : TypeIdPursue & & ! creatureStats . getAiSequence ( ) . isInCombat ( )
& & creatureStats . getMagicEffects ( ) . get ( ESM : : MagicEffect : : CalmHumanoid ) . getMagnitude ( ) = = 0 )
2014-04-03 18:53:31 +00:00
{
2014-04-05 14:26:14 +00:00
const MWWorld : : ESMStore & esmStore = MWBase : : Environment : : get ( ) . getWorld ( ) - > getStore ( ) ;
2018-08-29 15:38:12 +00:00
static const int cutoff = esmStore . get < ESM : : GameSetting > ( ) . find ( " iCrimeThreshold " ) - > mValue . getInteger ( ) ;
2014-05-03 10:09:34 +00:00
// Force dialogue on sight if bounty is greater than the cutoff
// In vanilla morrowind, the greeting dialogue is scripted to either arrest the player (< 5000 bounty) or attack (>= 5000 bounty)
2014-04-06 02:45:40 +00:00
if ( player . getClass ( ) . getNpcStats ( player ) . getBounty ( ) > = cutoff
2014-05-03 10:09:34 +00:00
// TODO: do not run these two every frame. keep an Aware state for each actor and update it every 0.2 s or so?
2014-04-06 02:45:40 +00:00
& & MWBase : : Environment : : get ( ) . getWorld ( ) - > getLOS ( ptr , player )
& & MWBase : : Environment : : get ( ) . getMechanicsManager ( ) - > awarenessCheck ( player , ptr ) )
2014-04-03 18:53:31 +00:00
{
2018-08-29 15:38:12 +00:00
static const int iCrimeThresholdMultiplier = esmStore . get < ESM : : GameSetting > ( ) . find ( " iCrimeThresholdMultiplier " ) - > mValue . getInteger ( ) ;
2014-07-23 22:59:58 +00:00
if ( player . getClass ( ) . getNpcStats ( player ) . getBounty ( ) > = cutoff * iCrimeThresholdMultiplier )
2017-02-01 17:15:10 +00:00
{
2014-07-23 22:59:58 +00:00
MWBase : : Environment : : get ( ) . getMechanicsManager ( ) - > startCombat ( ptr , player ) ;
2017-02-06 12:32:36 +00:00
creatureStats . setHitAttemptActorId ( player . getClass ( ) . getCreatureStats ( player ) . getActorId ( ) ) ; // Stops the guard from quitting combat if player is unreachable
2017-02-01 17:15:10 +00:00
}
2014-07-23 22:59:58 +00:00
else
creatureStats . getAiSequence ( ) . stack ( AiPursue ( player ) , ptr ) ;
2014-05-03 10:09:34 +00:00
creatureStats . setAlarmed ( true ) ;
npcStats . setCrimeId ( MWBase : : Environment : : get ( ) . getWorld ( ) - > getPlayer ( ) . getNewCrimeId ( ) ) ;
2014-04-03 18:53:31 +00:00
}
}
// if I was a witness to a crime
2014-04-05 14:26:14 +00:00
if ( npcStats . getCrimeId ( ) ! = - 1 )
2014-04-03 04:50:09 +00:00
{
2014-05-03 10:23:22 +00:00
// if you've paid for your crimes and I havent noticed
2014-04-06 02:45:40 +00:00
if ( npcStats . getCrimeId ( ) < = MWBase : : Environment : : get ( ) . getWorld ( ) - > getPlayer ( ) . getCrimeId ( ) )
2014-04-03 04:50:09 +00:00
{
2014-04-05 14:26:14 +00:00
// Calm witness down
2014-04-03 04:50:09 +00:00
if ( ptr . getClass ( ) . isClass ( ptr , " Guard " ) )
2014-05-03 10:23:22 +00:00
creatureStats . getAiSequence ( ) . stopPursuit ( ) ;
2014-04-03 04:50:09 +00:00
creatureStats . getAiSequence ( ) . stopCombat ( ) ;
2014-04-05 14:26:14 +00:00
// Reset factors to attack
creatureStats . setAttacked ( false ) ;
2014-04-18 11:44:09 +00:00
creatureStats . setAlarmed ( false ) ;
2014-12-14 18:35:34 +00:00
creatureStats . setAiSetting ( CreatureStats : : AI_Fight , ptr . getClass ( ) . getBaseFightRating ( ptr ) ) ;
2014-04-25 02:47:45 +00:00
2014-04-05 14:26:14 +00:00
// Update witness crime id
npcStats . setCrimeId ( - 1 ) ;
2014-04-03 04:50:09 +00:00
}
}
}
}
2018-09-21 12:34:23 +00:00
Actors : : Actors ( )
{
2017-08-18 07:58:28 +00:00
mTimerDisposeSummonsCorpses = 0.2f ; // We should add a delay between summoned creature death and its corpse despawning
2018-09-21 12:34:23 +00:00
updateProcessingRange ( ) ;
2017-08-18 07:58:28 +00:00
}
2012-03-30 14:18:58 +00:00
2013-12-30 20:47:06 +00:00
Actors : : ~ Actors ( )
{
2014-07-27 21:10:58 +00:00
clear ( ) ;
2013-12-30 20:47:06 +00:00
}
2018-09-21 12:34:23 +00:00
float Actors : : getProcessingRange ( ) const
{
return mActorsProcessingRange ;
}
void Actors : : updateProcessingRange ( )
{
// We have to cap it since using high values (larger than 7168) will make some quests harder or impossible to complete (bug #1876)
static const float maxProcessingRange = 7168.f ;
static const float minProcessingRange = maxProcessingRange / 2.f ;
float actorsProcessingRange = Settings : : Manager : : getFloat ( " actors processing range " , " Game " ) ;
actorsProcessingRange = std : : min ( actorsProcessingRange , maxProcessingRange ) ;
actorsProcessingRange = std : : max ( actorsProcessingRange , minProcessingRange ) ;
mActorsProcessingRange = actorsProcessingRange ;
}
2014-05-13 17:01:02 +00:00
void Actors : : addActor ( const MWWorld : : Ptr & ptr , bool updateImmediately )
2012-03-30 14:18:58 +00:00
{
2013-07-18 04:39:21 +00:00
removeActor ( ptr ) ;
2013-01-16 18:16:37 +00:00
MWRender : : Animation * anim = MWBase : : Environment : : get ( ) . getWorld ( ) - > getAnimation ( ptr ) ;
2015-04-25 13:19:17 +00:00
if ( ! anim )
return ;
2014-12-21 15:45:30 +00:00
mActors . insert ( std : : make_pair ( ptr , new Actor ( ptr , anim ) ) ) ;
2019-02-07 15:11:12 +00:00
CharacterController * ctrl = mActors [ ptr ] - > getCharacterController ( ) ;
2014-05-13 17:01:02 +00:00
if ( updateImmediately )
2019-02-07 15:11:12 +00:00
ctrl - > update ( 0 ) ;
// We should initially hide actors outside of processing range.
// Note: since we update player after other actors, distance will be incorrect during teleportation.
// Do not update visibility if player was teleported, so actors will be visible during teleportation frame.
if ( MWBase : : Environment : : get ( ) . getWorld ( ) - > getPlayer ( ) . wasTeleported ( ) )
return ;
updateVisibility ( ptr , ctrl ) ;
}
void Actors : : updateVisibility ( const MWWorld : : Ptr & ptr , CharacterController * ctrl )
{
MWWorld : : Ptr player = MWMechanics : : getPlayer ( ) ;
if ( ptr = = player )
return ;
const float dist = ( player . getRefData ( ) . getPosition ( ) . asVec3 ( ) - ptr . getRefData ( ) . getPosition ( ) . asVec3 ( ) ) . length ( ) ;
if ( dist > mActorsProcessingRange )
{
2020-04-20 16:47:14 +00:00
ptr . getRefData ( ) . getBaseNode ( ) - > setNodeMask ( 0 ) ;
2019-02-07 15:11:12 +00:00
return ;
}
else
2020-04-20 16:47:14 +00:00
ptr . getRefData ( ) . getBaseNode ( ) - > setNodeMask ( MWRender : : Mask_Actor ) ;
2019-02-07 15:11:12 +00:00
// Fade away actors on large distance (>90% of actor's processing distance)
float visibilityRatio = 1.0 ;
float fadeStartDistance = mActorsProcessingRange * 0.9f ;
float fadeEndDistance = mActorsProcessingRange ;
float fadeRatio = ( dist - fadeStartDistance ) / ( fadeEndDistance - fadeStartDistance ) ;
if ( fadeRatio > 0 )
visibilityRatio - = std : : max ( 0.f , fadeRatio ) ;
visibilityRatio = std : : min ( 1.f , visibilityRatio ) ;
ctrl - > setVisibility ( visibilityRatio ) ;
2012-03-30 14:18:58 +00:00
}
void Actors : : removeActor ( const MWWorld : : Ptr & ptr )
{
2014-12-21 15:45:30 +00:00
PtrActorMap : : iterator iter = mActors . find ( ptr ) ;
2013-01-12 15:12:12 +00:00
if ( iter ! = mActors . end ( ) )
2013-07-08 21:05:53 +00:00
{
delete iter - > second ;
2013-01-12 15:12:12 +00:00
mActors . erase ( iter ) ;
2013-07-08 21:05:53 +00:00
}
2012-03-30 14:18:58 +00:00
}
2018-06-28 12:58:51 +00:00
void Actors : : castSpell ( const MWWorld : : Ptr & ptr , const std : : string spellId , bool manualSpell )
{
PtrActorMap : : iterator iter = mActors . find ( ptr ) ;
if ( iter ! = mActors . end ( ) )
iter - > second - > getCharacterController ( ) - > castSpell ( spellId , manualSpell ) ;
}
2017-09-18 17:46:57 +00:00
bool Actors : : isActorDetected ( const MWWorld : : Ptr & actor , const MWWorld : : Ptr & observer )
{
if ( ! actor . getClass ( ) . isActor ( ) )
return false ;
// If an observer is NPC, check if he detected an actor
if ( ! observer . isEmpty ( ) & & observer . getClass ( ) . isNpc ( ) )
{
return
MWBase : : Environment : : get ( ) . getWorld ( ) - > getLOS ( observer , actor ) & &
MWBase : : Environment : : get ( ) . getMechanicsManager ( ) - > awarenessCheck ( actor , observer ) ;
}
// Otherwise check if any actor in AI processing range sees the target actor
2018-11-05 22:35:20 +00:00
std : : vector < MWWorld : : Ptr > neighbors ;
2017-09-18 17:46:57 +00:00
osg : : Vec3f position ( actor . getRefData ( ) . getPosition ( ) . asVec3 ( ) ) ;
2018-11-05 22:35:20 +00:00
getObjectsInRange ( position , mActorsProcessingRange , neighbors ) ;
for ( const MWWorld : : Ptr & neighbor : neighbors )
2017-09-18 17:46:57 +00:00
{
2018-11-05 22:35:20 +00:00
if ( neighbor = = actor )
2017-09-18 17:46:57 +00:00
continue ;
2018-11-07 16:41:28 +00:00
bool result = MWBase : : Environment : : get ( ) . getWorld ( ) - > getLOS ( neighbor , actor )
& & MWBase : : Environment : : get ( ) . getMechanicsManager ( ) - > awarenessCheck ( actor , neighbor ) ;
2017-09-18 17:46:57 +00:00
if ( result )
return true ;
}
return false ;
}
2013-02-25 17:57:34 +00:00
void Actors : : updateActor ( const MWWorld : : Ptr & old , const MWWorld : : Ptr & ptr )
2013-01-29 08:19:24 +00:00
{
2014-12-21 15:45:30 +00:00
PtrActorMap : : iterator iter = mActors . find ( old ) ;
2013-01-29 08:19:24 +00:00
if ( iter ! = mActors . end ( ) )
{
2014-12-21 15:45:30 +00:00
Actor * actor = iter - > second ;
2013-01-29 08:19:24 +00:00
mActors . erase ( iter ) ;
2013-02-25 17:57:34 +00:00
2014-12-21 15:45:30 +00:00
actor - > updatePtr ( ptr ) ;
mActors . insert ( std : : make_pair ( ptr , actor ) ) ;
2013-01-29 08:19:24 +00:00
}
}
2013-12-07 11:27:06 +00:00
void Actors : : dropActors ( const MWWorld : : CellStore * cellStore , const MWWorld : : Ptr & ignore )
2012-03-30 14:18:58 +00:00
{
2014-12-21 15:45:30 +00:00
PtrActorMap : : iterator iter = mActors . begin ( ) ;
2013-01-12 15:12:12 +00:00
while ( iter ! = mActors . end ( ) )
{
2015-11-27 20:38:57 +00:00
if ( ( iter - > first . isInCell ( ) & & iter - > first . getCell ( ) = = cellStore ) & & iter - > first ! = ignore )
2013-07-08 21:05:53 +00:00
{
delete iter - > second ;
2013-01-12 15:12:12 +00:00
mActors . erase ( iter + + ) ;
2013-07-08 21:05:53 +00:00
}
2012-03-30 14:18:58 +00:00
else
+ + iter ;
2013-01-12 15:12:12 +00:00
}
2012-03-30 14:18:58 +00:00
}
2017-11-28 16:49:48 +00:00
void Actors : : updateCombatMusic ( )
{
MWWorld : : Ptr player = getPlayer ( ) ;
2018-09-21 18:34:18 +00:00
const osg : : Vec3f playerPos = player . getRefData ( ) . getPosition ( ) . asVec3 ( ) ;
bool hasHostiles = false ; // need to know this to play Battle music
bool aiActive = MWBase : : Environment : : get ( ) . getMechanicsManager ( ) - > isAIActive ( ) ;
2017-11-28 16:49:48 +00:00
2018-09-21 18:34:18 +00:00
if ( aiActive )
2017-11-28 16:49:48 +00:00
{
2018-09-21 18:34:18 +00:00
for ( PtrActorMap : : iterator iter ( mActors . begin ( ) ) ; iter ! = mActors . end ( ) ; + + iter )
2017-11-28 16:49:48 +00:00
{
2018-09-21 18:34:18 +00:00
if ( iter - > first = = player ) continue ;
2017-11-28 16:49:48 +00:00
2018-09-21 12:34:23 +00:00
bool inProcessingRange = ( playerPos - iter - > first . getRefData ( ) . getPosition ( ) . asVec3 ( ) ) . length2 ( ) < = mActorsProcessingRange * mActorsProcessingRange ;
2018-09-21 18:34:18 +00:00
if ( inProcessingRange )
2017-11-28 16:49:48 +00:00
{
2018-09-21 18:34:18 +00:00
MWMechanics : : CreatureStats & stats = iter - > first . getClass ( ) . getCreatureStats ( iter - > first ) ;
if ( ! stats . isDead ( ) & & stats . getAiSequence ( ) . isInCombat ( ) )
2017-11-28 16:49:48 +00:00
{
2018-09-21 18:34:18 +00:00
hasHostiles = true ;
break ;
2017-11-28 16:49:48 +00:00
}
}
}
}
// check if we still have any player enemies to switch music
static int currentMusic = 0 ;
2018-09-21 18:34:18 +00:00
if ( currentMusic ! = 1 & & ! hasHostiles & & ! ( player . getClass ( ) . getCreatureStats ( player ) . isDead ( ) & &
2017-11-28 16:49:48 +00:00
MWBase : : Environment : : get ( ) . getSoundManager ( ) - > isMusicPlaying ( ) ) )
{
MWBase : : Environment : : get ( ) . getSoundManager ( ) - > playPlaylist ( std : : string ( " Explore " ) ) ;
currentMusic = 1 ;
}
2018-09-21 18:34:18 +00:00
else if ( currentMusic ! = 2 & & hasHostiles )
2017-11-28 16:49:48 +00:00
{
MWBase : : Environment : : get ( ) . getSoundManager ( ) - > playPlaylist ( std : : string ( " Battle " ) ) ;
currentMusic = 2 ;
}
}
2013-01-16 16:22:38 +00:00
void Actors : : update ( float duration , bool paused )
2012-03-30 14:18:58 +00:00
{
2014-01-25 14:54:24 +00:00
if ( ! paused )
2014-09-14 20:29:06 +00:00
{
2014-05-15 20:03:48 +00:00
static float timerUpdateAITargets = 0 ;
2014-12-16 19:47:45 +00:00
static float timerUpdateHeadTrack = 0 ;
2017-02-04 16:20:47 +00:00
static float timerUpdateEquippedLight = 0 ;
2019-10-09 16:57:24 +00:00
static float timerUpdateHello = 0 ;
2017-02-04 16:20:47 +00:00
const float updateEquippedLightInterval = 1.0f ;
2014-05-15 20:03:48 +00:00
// target lists get updated once every 1.0 sec
if ( timerUpdateAITargets > = 1.0f ) timerUpdateAITargets = 0 ;
2014-12-16 19:47:45 +00:00
if ( timerUpdateHeadTrack > = 0.3f ) timerUpdateHeadTrack = 0 ;
2019-10-09 16:57:24 +00:00
if ( timerUpdateHello > = 0.25f ) timerUpdateHello = 0 ;
2017-08-18 07:58:28 +00:00
if ( mTimerDisposeSummonsCorpses > = 0.2f ) mTimerDisposeSummonsCorpses = 0 ;
2017-02-04 16:20:47 +00:00
if ( timerUpdateEquippedLight > = updateEquippedLightInterval ) timerUpdateEquippedLight = 0 ;
2014-05-15 20:03:48 +00:00
2018-03-03 13:18:40 +00:00
// show torches only when there are darkness and no precipitations
2018-09-21 12:34:23 +00:00
MWBase : : World * world = MWBase : : Environment : : get ( ) . getWorld ( ) ;
bool showTorches = world - > useTorches ( ) ;
2018-03-03 13:18:40 +00:00
2015-08-21 09:12:39 +00:00
MWWorld : : Ptr player = getPlayer ( ) ;
2018-09-21 18:39:47 +00:00
const osg : : Vec3f playerPos = player . getRefData ( ) . getPosition ( ) . asVec3 ( ) ;
2014-05-15 20:03:48 +00:00
2014-12-21 15:45:30 +00:00
/// \todo move update logic to Actor class where appropriate
2017-04-12 16:01:50 +00:00
std : : map < const MWWorld : : Ptr , const std : : set < MWWorld : : Ptr > > cachedAllies ; // will be filled as engageCombat iterates
2018-09-21 15:02:28 +00:00
bool aiActive = MWBase : : Environment : : get ( ) . getMechanicsManager ( ) - > isAIActive ( ) ;
int attackedByPlayerId = player . getClass ( ) . getCreatureStats ( player ) . getHitAttemptActorId ( ) ;
if ( attackedByPlayerId ! = - 1 )
{
2018-09-21 12:34:23 +00:00
const MWWorld : : Ptr playerHitAttemptActor = world - > searchPtrViaActorId ( attackedByPlayerId ) ;
2018-09-21 15:02:28 +00:00
if ( ! playerHitAttemptActor . isInCell ( ) )
player . getClass ( ) . getCreatureStats ( player ) . setHitAttemptActorId ( - 1 ) ;
}
2014-05-15 20:03:48 +00:00
// AI and magic effects update
2016-02-02 14:55:19 +00:00
for ( PtrActorMap : : iterator iter ( mActors . begin ( ) ) ; iter ! = mActors . end ( ) ; + + iter )
2014-02-05 08:50:21 +00:00
{
2018-08-09 07:16:19 +00:00
bool isPlayer = iter - > first = = player ;
2018-09-22 11:57:58 +00:00
CharacterController * ctrl = iter - > second - > getCharacterController ( ) ;
2018-08-09 07:16:19 +00:00
2018-09-21 18:39:47 +00:00
float distSqr = ( playerPos - iter - > first . getRefData ( ) . getPosition ( ) . asVec3 ( ) ) . length2 ( ) ;
2018-09-21 12:34:23 +00:00
// AI processing is only done within given distance to the player.
bool inProcessingRange = distSqr < = mActorsProcessingRange * mActorsProcessingRange ;
2015-04-29 21:48:08 +00:00
2018-08-09 07:16:19 +00:00
if ( isPlayer )
2018-09-21 12:34:23 +00:00
ctrl - > setAttackingOrSpell ( world - > getPlayer ( ) . getAttackingOrSpell ( ) ) ;
2015-07-02 17:14:28 +00:00
2017-02-01 17:15:10 +00:00
// 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 ) )
{
2017-02-06 12:32:36 +00:00
iter - > first . getClass ( ) . getCreatureStats ( iter - > first ) . setHitAttemptActorId ( - 1 ) ;
if ( player . getClass ( ) . getCreatureStats ( player ) . getHitAttemptActorId ( ) = = iter - > first . getClass ( ) . getCreatureStats ( iter - > first ) . getActorId ( ) )
player . getClass ( ) . getCreatureStats ( player ) . setHitAttemptActorId ( - 1 ) ;
2017-02-01 17:15:10 +00:00
}
2019-09-18 05:43:32 +00:00
iter - > first . getClass ( ) . getCreatureStats ( iter - > first ) . getActiveSpells ( ) . update ( duration ) ;
2019-02-05 07:02:19 +00:00
// For dead actors we need to remove looping spell particles
if ( iter - > first . getClass ( ) . getCreatureStats ( iter - > first ) . isDead ( ) )
ctrl - > updateContinuousVfx ( ) ;
else
2014-02-05 08:50:21 +00:00
{
2018-09-21 12:34:23 +00:00
bool cellChanged = world - > hasCellChanged ( ) ;
2017-02-01 17:15:10 +00:00
MWWorld : : Ptr actor = iter - > first ; // make a copy of the map key to avoid it being invalidated when the player teleports
2016-06-13 00:06:44 +00:00
updateActor ( actor , duration ) ;
2019-01-29 18:52:40 +00:00
// Looping magic VFX update
// Note: we need to do this before any of the animations are updated.
// Reaching the text keys may trigger Hit / Spellcast (and as such, particles),
// so updating VFX immediately after that would just remove the particle effects instantly.
// There needs to be a magic effect update in between.
ctrl - > updateContinuousVfx ( ) ;
2018-09-21 12:34:23 +00:00
if ( ! cellChanged & & world - > hasCellChanged ( ) )
2016-02-01 23:50:21 +00:00
{
return ; // for now abort update of the old cell when cell changes by teleportation magic effect
// a better solution might be to apply cell changes at the end of the frame
}
2018-09-21 15:02:28 +00:00
if ( aiActive & & inProcessingRange )
2014-05-15 20:03:48 +00:00
{
2014-07-27 18:30:52 +00:00
if ( timerUpdateAITargets = = 0 )
2014-08-06 19:16:14 +00:00
{
2018-08-09 07:16:19 +00:00
if ( ! isPlayer )
2014-08-06 19:16:14 +00:00
adjustCommandedActor ( iter - > first ) ;
2016-02-02 14:55:19 +00:00
for ( PtrActorMap : : iterator it ( mActors . begin ( ) ) ; it ! = mActors . end ( ) ; + + it )
2014-05-15 20:03:48 +00:00
{
2018-08-09 07:16:19 +00:00
if ( it - > first = = iter - > first | | isPlayer ) // player is not AI-controlled
2014-07-27 18:30:52 +00:00
continue ;
2018-08-10 05:29:01 +00:00
engageCombat ( iter - > first , it - > first , cachedAllies , it - > first = = player ) ;
2014-05-15 20:03:48 +00:00
}
}
2014-12-16 19:47:45 +00:00
if ( timerUpdateHeadTrack = = 0 )
{
float sqrHeadTrackDistance = std : : numeric_limits < float > : : max ( ) ;
MWWorld : : Ptr headTrackTarget ;
2017-11-11 15:46:59 +00:00
MWMechanics : : CreatureStats & stats = iter - > first . getClass ( ) . getCreatureStats ( iter - > first ) ;
2018-09-21 12:34:23 +00:00
bool firstPersonPlayer = isPlayer & & world - > isFirstPerson ( ) ;
2017-11-11 15:46:59 +00:00
2018-08-08 19:21:20 +00:00
// 1. Unconsious actor can not track target
// 2. Actors in combat and pursue mode do not bother to headtrack
// 3. Player character does not use headtracking in the 1st-person view
2017-11-11 15:46:59 +00:00
if ( ! stats . getKnockedDown ( ) & &
! stats . getAiSequence ( ) . isInCombat ( ) & &
2018-08-08 19:21:20 +00:00
! stats . getAiSequence ( ) . hasPackage ( AiPackage : : TypeIdPursue ) & &
! firstPersonPlayer )
2014-12-16 19:47:45 +00:00
{
2017-09-30 16:55:42 +00:00
for ( PtrActorMap : : iterator it ( mActors . begin ( ) ) ; it ! = mActors . end ( ) ; + + it )
{
if ( it - > first = = iter - > first )
continue ;
updateHeadTracking ( iter - > first , it - > first , headTrackTarget , sqrHeadTrackDistance ) ;
}
2014-12-16 19:47:45 +00:00
}
2017-11-11 15:46:59 +00:00
2018-09-22 11:57:58 +00:00
ctrl - > setHeadTrackTarget ( headTrackTarget ) ;
2014-12-16 19:47:45 +00:00
}
2014-05-15 20:03:48 +00:00
if ( iter - > first . getClass ( ) . isNpc ( ) & & iter - > first ! = player )
2018-03-23 12:10:43 +00:00
updateCrimePursuit ( iter - > first , duration ) ;
2014-05-15 20:03:48 +00:00
if ( iter - > first ! = player )
2014-09-20 11:55:57 +00:00
{
2015-01-13 17:09:10 +00:00
CreatureStats & stats = iter - > first . getClass ( ) . getCreatureStats ( iter - > first ) ;
if ( isConscious ( iter - > first ) )
2018-12-14 15:29:56 +00:00
{
2018-09-22 11:57:58 +00:00
stats . getAiSequence ( ) . execute ( iter - > first , * ctrl , duration ) ;
2020-05-18 20:04:48 +00:00
updateGreetingState ( iter - > first , * iter - > second , timerUpdateHello > 0 ) ;
2018-12-14 15:29:56 +00:00
playIdleDialogue ( iter - > first ) ;
2019-10-31 05:44:40 +00:00
updateMovementSpeed ( iter - > first ) ;
2018-12-14 15:29:56 +00:00
}
2014-09-20 11:55:57 +00:00
}
2014-05-15 20:03:48 +00:00
}
2020-01-06 12:09:32 +00:00
else if ( aiActive & & iter - > first ! = player & & isConscious ( iter - > first ) )
{
CreatureStats & stats = iter - > first . getClass ( ) . getCreatureStats ( iter - > first ) ;
stats . getAiSequence ( ) . execute ( iter - > first , * ctrl , duration , /*outOfRange*/ true ) ;
}
2014-05-15 20:03:48 +00:00
2019-02-18 22:10:55 +00:00
if ( iter - > first . getClass ( ) . isNpc ( ) )
2017-02-04 16:20:47 +00:00
{
2019-04-14 14:09:11 +00:00
// We can not update drowning state for actors outside of AI distance - they can not resurface to breathe
if ( inProcessingRange )
updateDrowning ( iter - > first , duration , ctrl - > isKnockedOut ( ) , isPlayer ) ;
2018-09-22 11:48:36 +00:00
calculateNpcStatModifiers ( iter - > first , duration ) ;
2017-02-04 16:20:47 +00:00
if ( timerUpdateEquippedLight = = 0 )
2018-03-03 13:18:40 +00:00
updateEquippedLight ( iter - > first , updateEquippedLightInterval , showTorches ) ;
2017-02-04 16:20:47 +00:00
}
2014-02-05 08:50:21 +00:00
}
}
2014-05-15 20:03:48 +00:00
timerUpdateAITargets + = duration ;
2014-12-16 19:47:45 +00:00
timerUpdateHeadTrack + = duration ;
2017-02-06 04:10:40 +00:00
timerUpdateEquippedLight + = duration ;
2019-10-09 16:57:24 +00:00
timerUpdateHello + = duration ;
2017-08-18 07:58:28 +00:00
mTimerDisposeSummonsCorpses + = duration ;
2014-05-15 20:03:48 +00:00
2014-02-05 08:50:21 +00:00
// Animation/movement update
2018-10-09 06:21:12 +00:00
CharacterController * playerCharacter = nullptr ;
2016-02-02 14:55:19 +00:00
for ( PtrActorMap : : iterator iter ( mActors . begin ( ) ) ; iter ! = mActors . end ( ) ; + + iter )
2014-01-25 14:54:24 +00:00
{
2018-09-21 12:34:23 +00:00
const float dist = ( playerPos - iter - > first . getRefData ( ) . getPosition ( ) . asVec3 ( ) ) . length ( ) ;
2018-01-11 01:49:35 +00:00
bool isPlayer = iter - > first = = player ;
2020-01-06 12:09:32 +00:00
CreatureStats & stats = iter - > first . getClass ( ) . getCreatureStats ( iter - > first ) ;
2020-01-09 11:11:53 +00:00
// Actors with active AI should be able to move.
bool alwaysActive = false ;
if ( ! isPlayer & & isConscious ( iter - > first ) & & ! stats . isParalyzed ( ) )
{
MWMechanics : : AiSequence & seq = stats . getAiSequence ( ) ;
alwaysActive = ! seq . isEmpty ( ) & & seq . getActivePackage ( ) - > alwaysActive ( ) ;
}
bool inRange = isPlayer | | dist < = mActorsProcessingRange | | alwaysActive ;
2018-01-11 01:49:35 +00:00
int activeFlag = 1 ; // Can be changed back to '2' to keep updating bounding boxes off screen (more accurate, but slower)
if ( isPlayer )
activeFlag = 2 ;
2018-09-21 12:34:23 +00:00
int active = inRange ? activeFlag : 0 ;
2018-09-22 11:57:58 +00:00
CharacterController * ctrl = iter - > second - > getCharacterController ( ) ;
ctrl - > setActive ( active ) ;
2018-01-11 01:49:35 +00:00
2018-09-21 12:34:23 +00:00
if ( ! inRange )
{
2020-04-20 16:47:14 +00:00
iter - > first . getRefData ( ) . getBaseNode ( ) - > setNodeMask ( 0 ) ;
2018-12-31 15:39:20 +00:00
world - > setActorCollisionMode ( iter - > first , false , false ) ;
2014-10-01 19:54:59 +00:00
continue ;
2018-09-21 12:34:23 +00:00
}
else if ( ! isPlayer )
2020-04-20 16:47:14 +00:00
iter - > first . getRefData ( ) . getBaseNode ( ) - > setNodeMask ( MWRender : : Mask_Actor ) ;
2014-10-01 19:54:59 +00:00
2019-07-12 09:31:10 +00:00
const bool isDead = iter - > first . getClass ( ) . getCreatureStats ( iter - > first ) . isDead ( ) ;
if ( ! isDead & & iter - > first . getClass ( ) . getCreatureStats ( iter - > first ) . isParalyzed ( ) )
2018-09-22 11:57:58 +00:00
ctrl - > skipAnim ( ) ;
2014-09-25 16:00:32 +00:00
2016-02-02 14:55:19 +00:00
// Handle player last, in case a cell transition occurs by casting a teleportation spell
// (would invalidate the iterator)
if ( iter - > first = = getPlayer ( ) )
{
2018-09-22 11:57:58 +00:00
playerCharacter = ctrl ;
2016-02-02 14:55:19 +00:00
continue ;
}
2018-09-21 12:34:23 +00:00
2018-12-31 15:39:20 +00:00
world - > setActorCollisionMode ( iter - > first , true , ! iter - > first . getClass ( ) . getCreatureStats ( iter - > first ) . isDeathAnimationFinished ( ) ) ;
2018-09-22 11:57:58 +00:00
ctrl - > update ( duration ) ;
2018-09-21 12:34:23 +00:00
2019-02-07 15:11:12 +00:00
updateVisibility ( iter - > first , ctrl ) ;
2014-01-25 14:54:24 +00:00
}
2014-01-30 23:15:59 +00:00
2016-02-02 14:55:19 +00:00
if ( playerCharacter )
2018-09-21 12:34:23 +00:00
{
2016-02-02 14:55:19 +00:00
playerCharacter - > update ( duration ) ;
2018-09-21 12:34:23 +00:00
playerCharacter - > setVisibility ( 1.f ) ;
}
2016-02-02 14:55:19 +00:00
for ( PtrActorMap : : iterator iter ( mActors . begin ( ) ) ; iter ! = mActors . end ( ) ; + + iter )
2012-03-30 15:01:55 +00:00
{
2014-05-22 18:37:22 +00:00
const MWWorld : : Class & cls = iter - > first . getClass ( ) ;
2013-07-26 15:08:52 +00:00
CreatureStats & stats = cls . getCreatureStats ( iter - > first ) ;
2014-04-28 00:54:22 +00:00
//KnockedOutOneFrameLogic
//Used for "OnKnockedOut" command
//Put here to ensure that it's run for PRECISELY one frame.
2014-08-03 05:42:40 +00:00
if ( stats . getKnockedDown ( ) & & ! stats . getKnockedDownOneFrame ( ) & & ! stats . getKnockedDownOverOneFrame ( ) )
{ //Start it for one frame if nessesary
2014-04-28 00:54:22 +00:00
stats . setKnockedDownOneFrame ( true ) ;
}
2014-08-03 05:42:40 +00:00
else if ( stats . getKnockedDownOneFrame ( ) & & ! stats . getKnockedDownOverOneFrame ( ) )
{ //Turn off KnockedOutOneframe
2014-04-28 00:54:22 +00:00
stats . setKnockedDownOneFrame ( false ) ;
stats . setKnockedDownOverOneFrame ( true ) ;
}
2014-07-21 18:37:14 +00:00
}
2014-04-28 00:54:22 +00:00
2014-07-21 18:37:14 +00:00
killDeadActors ( ) ;
2019-01-13 02:11:51 +00:00
updateSneaking ( playerCharacter , duration ) ;
2013-01-17 02:03:39 +00:00
}
2017-11-28 16:49:48 +00:00
updateCombatMusic ( ) ;
2012-03-30 14:18:58 +00:00
}
2014-07-21 18:37:14 +00:00
2019-08-27 18:42:41 +00:00
void Actors : : notifyDied ( const MWWorld : : Ptr & actor )
{
actor . getClass ( ) . getCreatureStats ( actor ) . notifyDied ( ) ;
+ + mDeathCount [ Misc : : StringUtils : : lowerCase ( actor . getCellRef ( ) . getRefId ( ) ) ] ;
}
2019-09-21 16:22:45 +00:00
void Actors : : resurrect ( const MWWorld : : Ptr & ptr )
{
PtrActorMap : : iterator iter = mActors . find ( ptr ) ;
if ( iter ! = mActors . end ( ) )
{
if ( iter - > second - > getCharacterController ( ) - > isDead ( ) )
{
// Actor has been resurrected. Notify the CharacterController and re-enable collision.
MWBase : : Environment : : get ( ) . getWorld ( ) - > enableActorCollision ( iter - > first , true ) ;
iter - > second - > getCharacterController ( ) - > resurrect ( ) ;
}
}
}
2014-07-21 18:37:14 +00:00
void Actors : : killDeadActors ( )
{
2016-02-02 14:55:19 +00:00
for ( PtrActorMap : : iterator iter ( mActors . begin ( ) ) ; iter ! = mActors . end ( ) ; + + iter )
2014-07-21 18:37:14 +00:00
{
const MWWorld : : Class & cls = iter - > first . getClass ( ) ;
CreatureStats & stats = cls . getCreatureStats ( iter - > first ) ;
if ( ! stats . isDead ( ) )
2019-09-21 16:22:45 +00:00
continue ;
2014-07-21 18:37:14 +00:00
2018-11-03 06:42:14 +00:00
MWBase : : Environment : : get ( ) . getWorld ( ) - > removeActorPath ( iter - > first ) ;
2016-06-11 22:04:50 +00:00
CharacterController : : KillResult killResult = iter - > second - > getCharacterController ( ) - > kill ( ) ;
if ( killResult = = CharacterController : : Result_DeathAnimStarted )
2014-07-21 18:37:14 +00:00
{
2015-08-19 13:51:04 +00:00
// Play dying words
2016-06-11 22:04:50 +00:00
// Note: It's not known whether the soundgen tags scream, roar, and moan are reliable
// for NPCs since some of the npc death animation files are missing them.
2015-08-19 13:51:04 +00:00
MWBase : : Environment : : get ( ) . getDialogueManager ( ) - > say ( iter - > first , " hit " ) ;
2014-07-21 18:37:14 +00:00
// Apply soultrap
if ( iter - > first . getTypeName ( ) = = typeid ( ESM : : Creature ) . name ( ) )
{
SoulTrap soulTrap ( iter - > first ) ;
stats . getActiveSpells ( ) . visitEffectSources ( soulTrap ) ;
}
2020-03-26 12:22:31 +00:00
// Magic effects will be reset later, and the magic effect that could kill the actor
// needs to be determined now
2014-07-21 18:37:14 +00:00
calculateCreatureStatModifiers ( iter - > first , 0 ) ;
if ( cls . isEssential ( iter - > first ) )
MWBase : : Environment : : get ( ) . getWindowManager ( ) - > messageBox ( " #{sKilledEssential} " ) ;
}
2016-06-11 22:04:50 +00:00
else if ( killResult = = CharacterController : : Result_DeathAnimJustFinished )
{
2019-08-27 18:42:41 +00:00
notifyDied ( iter - > first ) ;
2016-06-11 22:04:50 +00:00
2019-06-13 21:42:25 +00:00
// Reset magic effects and recalculate derived effects
// One case where we need this is to make sure bound items are removed upon death
stats . modifyMagicEffects ( MWMechanics : : MagicEffects ( ) ) ;
stats . getActiveSpells ( ) . clear ( ) ;
2019-10-12 16:06:10 +00:00
stats . getSpells ( ) . clear ( ) ;
2016-06-11 22:04:50 +00:00
// Make sure spell effects are removed
2016-06-11 22:41:13 +00:00
purgeSpellEffects ( stats . getActorId ( ) ) ;
2016-06-11 22:04:50 +00:00
2020-03-26 12:22:31 +00:00
// Reset dynamic stats, attributes and skills
2019-10-12 10:00:36 +00:00
calculateCreatureStatModifiers ( iter - > first , 0 ) ;
2020-03-28 16:13:02 +00:00
if ( iter - > first . getClass ( ) . isNpc ( ) )
calculateNpcStatModifiers ( iter - > first , 0 ) ;
2019-10-12 10:00:36 +00:00
2016-06-11 22:04:50 +00:00
if ( iter - > first = = getPlayer ( ) )
{
//player's death animation is over
MWBase : : Environment : : get ( ) . getStateManager ( ) - > askLoadRecent ( ) ;
}
2016-12-15 18:29:05 +00:00
else
{
// NPC death animation is over, disable actor collision
MWBase : : Environment : : get ( ) . getWorld ( ) - > enableActorCollision ( iter - > first , false ) ;
}
2016-06-11 22:04:50 +00:00
// Play Death Music if it was the player dying
if ( iter - > first = = getPlayer ( ) )
MWBase : : Environment : : get ( ) . getSoundManager ( ) - > streamMusic ( " Special/MW_Death.mp3 " ) ;
}
2014-07-21 18:37:14 +00:00
}
}
2016-06-12 00:43:33 +00:00
void Actors : : cleanupSummonedCreature ( MWMechanics : : CreatureStats & casterStats , int creatureActorId )
{
MWWorld : : Ptr ptr = MWBase : : Environment : : get ( ) . getWorld ( ) - > searchPtrViaActorId ( creatureActorId ) ;
if ( ! ptr . isEmpty ( ) )
{
MWBase : : Environment : : get ( ) . getWorld ( ) - > deleteObject ( ptr ) ;
const ESM : : Static * fx = MWBase : : Environment : : get ( ) . getWorld ( ) - > getStore ( ) . get < ESM : : Static > ( )
. search ( " VFX_Summon_End " ) ;
if ( fx )
MWBase : : Environment : : get ( ) . getWorld ( ) - > spawnEffect ( " meshes \\ " + fx - > mModel ,
" " , ptr . getRefData ( ) . getPosition ( ) . asVec3 ( ) ) ;
2019-11-12 14:50:06 +00:00
// Remove the summoned creature's summoned creatures as well
MWMechanics : : CreatureStats & stats = ptr . getClass ( ) . getCreatureStats ( ptr ) ;
std : : map < CreatureStats : : SummonKey , int > & creatureMap = stats . getSummonedCreatureMap ( ) ;
for ( const auto & creature : creatureMap )
cleanupSummonedCreature ( stats , creature . second ) ;
creatureMap . clear ( ) ;
2016-06-12 00:43:33 +00:00
}
else if ( creatureActorId ! = - 1 )
{
// We didn't find the creature. It's probably in an inactive cell.
// Add to graveyard so we can delete it when the cell becomes active.
std : : vector < int > & graveyard = casterStats . getSummonedCreatureGraveyard ( ) ;
graveyard . push_back ( creatureActorId ) ;
}
purgeSpellEffects ( creatureActorId ) ;
}
2016-06-11 22:41:13 +00:00
void Actors : : purgeSpellEffects ( int casterActorId )
{
for ( PtrActorMap : : iterator iter ( mActors . begin ( ) ) ; iter ! = mActors . end ( ) ; + + iter )
{
MWMechanics : : ActiveSpells & spells = iter - > first . getClass ( ) . getCreatureStats ( iter - > first ) . getActiveSpells ( ) ;
spells . purge ( casterActorId ) ;
}
}
2019-01-25 16:04:35 +00:00
void Actors : : rest ( double hours , bool sleep )
2012-09-21 15:53:16 +00:00
{
2019-09-18 06:21:25 +00:00
float duration = hours * 3600.f ;
float timeScale = MWBase : : Environment : : get ( ) . getWorld ( ) - > getTimeScaleFactor ( ) ;
if ( timeScale ! = 0.f )
duration / = timeScale ;
2018-09-21 18:39:47 +00:00
const MWWorld : : Ptr player = MWBase : : Environment : : get ( ) . getWorld ( ) - > getPlayerPtr ( ) ;
const osg : : Vec3f playerPos = player . getRefData ( ) . getPosition ( ) . asVec3 ( ) ;
2016-07-05 22:20:23 +00:00
for ( PtrActorMap : : iterator iter ( mActors . begin ( ) ) ; iter ! = mActors . end ( ) ; + + iter )
{
if ( iter - > first . getClass ( ) . getCreatureStats ( iter - > first ) . isDead ( ) )
continue ;
2018-09-23 18:03:43 +00:00
if ( ! sleep | | iter - > first = = player )
2019-01-25 16:04:35 +00:00
restoreDynamicStats ( iter - > first , hours , sleep ) ;
2016-07-05 22:20:23 +00:00
if ( ( ! iter - > first . getRefData ( ) . getBaseNode ( ) ) | |
2018-09-21 12:34:23 +00:00
( playerPos - iter - > first . getRefData ( ) . getPosition ( ) . asVec3 ( ) ) . length2 ( ) > mActorsProcessingRange * mActorsProcessingRange )
2016-07-05 22:20:23 +00:00
continue ;
adjustMagicEffects ( iter - > first ) ;
if ( iter - > first . getClass ( ) . getCreatureStats ( iter - > first ) . needToRecalcDynamicStats ( ) )
calculateDynamicStats ( iter - > first ) ;
calculateCreatureStatModifiers ( iter - > first , duration ) ;
if ( iter - > first . getClass ( ) . isNpc ( ) )
calculateNpcStatModifiers ( iter - > first , duration ) ;
2017-07-28 12:50:52 +00:00
MWRender : : Animation * animation = MWBase : : Environment : : get ( ) . getWorld ( ) - > getAnimation ( iter - > first ) ;
if ( animation )
2018-10-01 17:57:13 +00:00
{
animation - > removeEffects ( ) ;
MWBase : : Environment : : get ( ) . getWorld ( ) - > applyLoopingParticles ( iter - > first ) ;
}
2017-07-28 12:50:52 +00:00
2016-07-05 22:20:23 +00:00
}
fastForwardAi ( ) ;
2012-09-21 15:53:16 +00:00
}
2013-03-18 09:54:47 +00:00
2019-01-13 02:11:51 +00:00
void Actors : : updateSneaking ( CharacterController * ctrl , float duration )
{
static float sneakTimer = 0.f ; // Times update of sneak icon
if ( ! ctrl )
{
MWBase : : Environment : : get ( ) . getWindowManager ( ) - > setSneakVisibility ( false ) ;
return ;
}
MWWorld : : Ptr player = getPlayer ( ) ;
2019-07-30 17:58:19 +00:00
if ( ! MWBase : : Environment : : get ( ) . getMechanicsManager ( ) - > isSneaking ( player ) )
2019-01-13 02:11:51 +00:00
{
MWBase : : Environment : : get ( ) . getWindowManager ( ) - > setSneakVisibility ( false ) ;
return ;
}
static float sneakSkillTimer = 0.f ; // Times sneak skill progress from "avoid notice"
2019-07-30 17:58:19 +00:00
MWBase : : World * world = MWBase : : Environment : : get ( ) . getWorld ( ) ;
2019-01-13 02:11:51 +00:00
const MWWorld : : Store < ESM : : GameSetting > & gmst = world - > getStore ( ) . get < ESM : : GameSetting > ( ) ;
static const float fSneakUseDist = gmst . find ( " fSneakUseDist " ) - > mValue . getFloat ( ) ;
static const float fSneakUseDelay = gmst . find ( " fSneakUseDelay " ) - > mValue . getFloat ( ) ;
if ( sneakTimer > = fSneakUseDelay )
sneakTimer = 0.f ;
if ( sneakTimer = = 0.f )
{
// Set when an NPC is within line of sight and distance, but is still unaware. Used for skill progress.
bool avoidedNotice = false ;
bool detected = false ;
std : : vector < MWWorld : : Ptr > observers ;
osg : : Vec3f position ( player . getRefData ( ) . getPosition ( ) . asVec3 ( ) ) ;
float radius = std : : min ( fSneakUseDist , mActorsProcessingRange ) ;
getObjectsInRange ( position , radius , observers ) ;
for ( const MWWorld : : Ptr & observer : observers )
{
if ( observer = = player | | observer . getClass ( ) . getCreatureStats ( observer ) . isDead ( ) )
continue ;
if ( world - > getLOS ( player , observer ) )
{
if ( MWBase : : Environment : : get ( ) . getMechanicsManager ( ) - > awarenessCheck ( player , observer ) )
{
detected = true ;
avoidedNotice = false ;
MWBase : : Environment : : get ( ) . getWindowManager ( ) - > setSneakVisibility ( false ) ;
break ;
}
else
{
avoidedNotice = true ;
}
}
}
if ( sneakSkillTimer > = fSneakUseDelay )
sneakSkillTimer = 0.f ;
if ( avoidedNotice & & sneakSkillTimer = = 0.f )
player . getClass ( ) . skillUsageSucceeded ( player , ESM : : Skill : : Sneak , 0 ) ;
if ( ! detected )
MWBase : : Environment : : get ( ) . getWindowManager ( ) - > setSneakVisibility ( true ) ;
}
sneakTimer + = duration ;
sneakSkillTimer + = duration ;
}
2014-01-14 01:52:34 +00:00
int Actors : : getHoursToRest ( const MWWorld : : Ptr & ptr ) const
{
float healthPerHour , magickaPerHour ;
getRestorationPerHourOfSleep ( ptr , healthPerHour , magickaPerHour ) ;
CreatureStats & stats = ptr . getClass ( ) . getCreatureStats ( ptr ) ;
2019-02-06 18:40:50 +00:00
bool stunted = stats . getMagicEffects ( ) . get ( ESM : : MagicEffect : : StuntedMagicka ) . getMagnitude ( ) > 0 ;
2014-01-14 01:52:34 +00:00
2014-06-06 13:13:25 +00:00
float healthHours = healthPerHour > 0
2014-01-14 01:52:34 +00:00
? ( stats . getHealth ( ) . getModified ( ) - stats . getHealth ( ) . getCurrent ( ) ) / healthPerHour
: 1.0f ;
2019-02-06 18:40:50 +00:00
float magickaHours = magickaPerHour > 0 & & ! stunted
2014-01-14 01:52:34 +00:00
? ( stats . getMagicka ( ) . getModified ( ) - stats . getMagicka ( ) . getCurrent ( ) ) / magickaPerHour
: 1.0f ;
2015-03-08 04:42:07 +00:00
int autoHours = static_cast < int > ( std : : ceil ( std : : max ( 1.f , std : : max ( healthHours , magickaHours ) ) ) ) ;
2014-01-14 01:52:34 +00:00
return autoHours ;
2012-09-21 15:53:16 +00:00
}
2013-03-18 09:54:47 +00:00
2012-10-27 09:33:18 +00:00
int Actors : : countDeaths ( const std : : string & id ) const
{
2013-01-12 15:12:12 +00:00
std : : map < std : : string , int > : : const_iterator iter = mDeathCount . find ( id ) ;
if ( iter ! = mDeathCount . end ( ) )
2012-10-27 09:33:18 +00:00
return iter - > second ;
return 0 ;
}
2013-01-17 01:53:18 +00:00
2013-04-25 14:08:11 +00:00
void Actors : : forceStateUpdate ( const MWWorld : : Ptr & ptr )
{
2014-12-21 15:45:30 +00:00
PtrActorMap : : iterator iter = mActors . find ( ptr ) ;
2013-04-25 14:08:11 +00:00
if ( iter ! = mActors . end ( ) )
2014-12-21 15:45:30 +00:00
iter - > second - > getCharacterController ( ) - > forceStateUpdate ( ) ;
2013-04-25 14:08:11 +00:00
}
2016-07-30 17:24:03 +00:00
bool Actors : : playAnimationGroup ( const MWWorld : : Ptr & ptr , const std : : string & groupName , int mode , int number , bool persist )
2013-01-17 01:53:18 +00:00
{
2014-12-21 15:45:30 +00:00
PtrActorMap : : iterator iter = mActors . find ( ptr ) ;
2013-01-17 01:53:18 +00:00
if ( iter ! = mActors . end ( ) )
2015-07-29 18:15:06 +00:00
{
2016-07-30 17:24:03 +00:00
return iter - > second - > getCharacterController ( ) - > playGroup ( groupName , mode , number , persist ) ;
2015-07-29 18:15:06 +00:00
}
else
{
2018-08-14 19:05:43 +00:00
Log ( Debug : : Warning ) < < " Warning: Actors::playAnimationGroup: Unable to find " < < ptr . getCellRef ( ) . getRefId ( ) ;
2015-07-29 18:15:06 +00:00
return false ;
}
2013-01-17 01:53:18 +00:00
}
void Actors : : skipAnimation ( const MWWorld : : Ptr & ptr )
{
2014-12-21 15:45:30 +00:00
PtrActorMap : : iterator iter = mActors . find ( ptr ) ;
2013-01-17 01:53:18 +00:00
if ( iter ! = mActors . end ( ) )
2014-12-21 15:45:30 +00:00
iter - > second - > getCharacterController ( ) - > skipAnim ( ) ;
2013-01-17 01:53:18 +00:00
}
2013-05-25 03:10:07 +00:00
bool Actors : : checkAnimationPlaying ( const MWWorld : : Ptr & ptr , const std : : string & groupName )
{
2014-12-21 15:45:30 +00:00
PtrActorMap : : iterator iter = mActors . find ( ptr ) ;
2013-05-25 03:10:07 +00:00
if ( iter ! = mActors . end ( ) )
2014-12-21 15:45:30 +00:00
return iter - > second - > getCharacterController ( ) - > isAnimPlaying ( groupName ) ;
2013-05-25 03:10:07 +00:00
return false ;
}
2014-01-20 12:00:43 +00:00
2016-07-30 17:24:03 +00:00
void Actors : : persistAnimationStates ( )
{
for ( PtrActorMap : : iterator iter = mActors . begin ( ) ; iter ! = mActors . end ( ) ; + + iter )
iter - > second - > getCharacterController ( ) - > persistAnimationState ( ) ;
}
2015-06-01 19:41:13 +00:00
void Actors : : getObjectsInRange ( const osg : : Vec3f & position , float radius , std : : vector < MWWorld : : Ptr > & out )
2014-01-20 12:00:43 +00:00
{
2014-12-21 15:45:30 +00:00
for ( PtrActorMap : : iterator iter = mActors . begin ( ) ; iter ! = mActors . end ( ) ; + + iter )
2014-01-20 12:00:43 +00:00
{
2015-06-01 19:41:13 +00:00
if ( ( iter - > first . getRefData ( ) . getPosition ( ) . asVec3 ( ) - position ) . length2 ( ) < = radius * radius )
2014-01-20 12:00:43 +00:00
out . push_back ( iter - > first ) ;
}
}
2014-01-28 11:33:31 +00:00
2017-11-11 08:31:18 +00:00
bool Actors : : isAnyObjectInRange ( const osg : : Vec3f & position , float radius )
{
for ( PtrActorMap : : iterator iter = mActors . begin ( ) ; iter ! = mActors . end ( ) ; + + iter )
{
if ( ( iter - > first . getRefData ( ) . getPosition ( ) . asVec3 ( ) - position ) . length2 ( ) < = radius * radius )
return true ;
}
return false ;
}
2015-12-06 22:32:49 +00:00
std : : list < MWWorld : : Ptr > Actors : : getActorsSidingWith ( const MWWorld : : Ptr & actor )
2014-01-12 13:02:15 +00:00
{
std : : list < MWWorld : : Ptr > list ;
2018-08-14 12:36:52 +00:00
for ( PtrActorMap : : iterator iter = mActors . begin ( ) ; iter ! = mActors . end ( ) ; + + iter )
2014-01-12 13:02:15 +00:00
{
2018-08-14 12:36:52 +00:00
const MWWorld : : Ptr & iteratedActor = iter - > first ;
if ( iteratedActor = = getPlayer ( ) )
continue ;
2018-08-21 14:02:56 +00:00
const bool sameActor = ( iteratedActor = = actor ) ;
2018-08-14 12:36:52 +00:00
const CreatureStats & stats = iteratedActor . getClass ( ) . getCreatureStats ( iteratedActor ) ;
2014-08-13 23:08:09 +00:00
if ( stats . isDead ( ) )
continue ;
2018-08-14 12:36:52 +00:00
// An actor counts as siding with this actor if Follow or Escort is the current AI package, or there are only Combat and Wander packages before the Follow/Escort package
2017-02-01 17:15:10 +00:00
// Actors that are targeted by this actor's Follow or Escort packages also side with them
2018-11-05 22:35:20 +00:00
for ( const AiPackage * package : stats . getAiSequence ( ) )
2017-02-01 17:15:10 +00:00
{
2018-11-05 22:35:20 +00:00
if ( package - > sideWithTarget ( ) & & ! package - > getTarget ( ) . isEmpty ( ) )
2017-02-01 17:15:10 +00:00
{
2018-08-21 14:02:56 +00:00
if ( sameActor )
2017-02-01 17:15:10 +00:00
{
2018-11-05 22:35:20 +00:00
list . push_back ( package - > getTarget ( ) ) ;
2017-02-01 17:15:10 +00:00
}
2018-11-05 22:35:20 +00:00
else if ( package - > getTarget ( ) = = actor )
2018-08-14 12:36:52 +00:00
{
list . push_back ( iteratedActor ) ;
}
break ;
2017-02-01 17:15:10 +00:00
}
2018-11-05 22:35:20 +00:00
else if ( package - > getTypeId ( ) ! = AiPackage : : TypeIdCombat & & package - > getTypeId ( ) ! = AiPackage : : TypeIdWander )
2018-08-14 12:36:52 +00:00
break ;
2017-02-01 17:15:10 +00:00
}
2014-01-12 13:02:15 +00:00
}
return list ;
}
2014-04-25 02:47:45 +00:00
2015-12-19 14:11:07 +00:00
std : : list < MWWorld : : Ptr > Actors : : getActorsFollowing ( const MWWorld : : Ptr & actor )
{
std : : list < MWWorld : : Ptr > list ;
for ( PtrActorMap : : iterator iter ( mActors . begin ( ) ) ; iter ! = mActors . end ( ) ; + + iter )
{
2018-08-14 12:36:52 +00:00
const MWWorld : : Ptr & iteratedActor = iter - > first ;
2018-08-21 14:02:56 +00:00
if ( iteratedActor = = getPlayer ( ) | | iteratedActor = = actor )
2018-08-14 12:36:52 +00:00
continue ;
const CreatureStats & stats = iteratedActor . getClass ( ) . getCreatureStats ( iteratedActor ) ;
2015-12-19 14:11:07 +00:00
if ( stats . isDead ( ) )
continue ;
2018-08-14 12:36:52 +00:00
// An actor counts as following if AiFollow is the current AiPackage,
// or there are only Combat and Wander packages before the AiFollow package
2018-11-05 22:35:20 +00:00
for ( const AiPackage * package : stats . getAiSequence ( ) )
2015-12-19 14:11:07 +00:00
{
2018-11-05 22:35:20 +00:00
if ( package - > followTargetThroughDoors ( ) & & package - > getTarget ( ) = = actor )
2018-08-14 12:36:52 +00:00
list . push_back ( iteratedActor ) ;
2018-11-05 22:35:20 +00:00
else if ( package - > getTypeId ( ) ! = AiPackage : : TypeIdCombat & & package - > getTypeId ( ) ! = AiPackage : : TypeIdWander )
2015-12-19 14:11:07 +00:00
break ;
}
}
return list ;
}
2016-12-20 11:38:51 +00:00
void Actors : : getActorsFollowing ( const MWWorld : : Ptr & actor , std : : set < MWWorld : : Ptr > & out ) {
std : : list < MWWorld : : Ptr > followers = getActorsFollowing ( actor ) ;
2018-11-05 22:35:20 +00:00
for ( const MWWorld : : Ptr & follower : followers )
if ( out . insert ( follower ) . second )
getActorsFollowing ( follower , out ) ;
2016-12-20 11:38:51 +00:00
}
void Actors : : getActorsSidingWith ( const MWWorld : : Ptr & actor , std : : set < MWWorld : : Ptr > & out ) {
std : : list < MWWorld : : Ptr > followers = getActorsSidingWith ( actor ) ;
2018-11-05 22:35:20 +00:00
for ( const MWWorld : : Ptr & follower : followers )
if ( out . insert ( follower ) . second )
getActorsSidingWith ( follower , out ) ;
2016-12-20 11:38:51 +00:00
}
2017-04-12 16:01:50 +00:00
void Actors : : getActorsSidingWith ( const MWWorld : : Ptr & actor , std : : set < MWWorld : : Ptr > & out , std : : map < const MWWorld : : Ptr , const std : : set < MWWorld : : Ptr > > & cachedAllies ) {
// 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 ( ) )
2017-04-12 18:57:59 +00:00
out . insert ( search - > second . begin ( ) , search - > second . end ( ) ) ;
2017-04-12 16:01:50 +00:00
else
{
2017-04-12 18:57:59 +00:00
std : : list < MWWorld : : Ptr > followers = getActorsSidingWith ( actor ) ;
2018-11-05 22:35:20 +00:00
for ( const MWWorld : : Ptr & follower : followers )
if ( out . insert ( follower ) . second )
getActorsSidingWith ( follower , out , cachedAllies ) ;
2017-04-12 16:01:50 +00:00
// Cache ptrs and their sets of allies
cachedAllies . insert ( std : : make_pair ( actor , out ) ) ;
2018-11-05 22:35:20 +00:00
for ( const MWWorld : : Ptr & iter : out )
2017-04-12 16:01:50 +00:00
{
2018-11-05 22:35:20 +00:00
search = cachedAllies . find ( iter ) ;
2017-04-12 16:01:50 +00:00
if ( search = = cachedAllies . end ( ) )
2018-11-05 22:35:20 +00:00
cachedAllies . insert ( std : : make_pair ( iter , out ) ) ;
2017-04-12 16:01:50 +00:00
}
}
}
2014-12-09 15:02:07 +00:00
std : : list < int > Actors : : getActorsFollowingIndices ( const MWWorld : : Ptr & actor )
{
std : : list < int > list ;
2014-12-21 15:45:30 +00:00
for ( PtrActorMap : : iterator iter ( mActors . begin ( ) ) ; iter ! = mActors . end ( ) ; + + iter )
2014-12-09 15:02:07 +00:00
{
2018-08-14 12:36:52 +00:00
const MWWorld : : Ptr & iteratedActor = iter - > first ;
2018-08-21 14:02:56 +00:00
if ( iteratedActor = = getPlayer ( ) | | iteratedActor = = actor )
2018-08-14 12:36:52 +00:00
continue ;
const CreatureStats & stats = iteratedActor . getClass ( ) . getCreatureStats ( iteratedActor ) ;
2014-12-09 15:02:07 +00:00
if ( stats . isDead ( ) )
continue ;
2018-08-14 12:36:52 +00:00
// An actor counts as following if AiFollow is the current AiPackage,
// or there are only Combat and Wander packages before the AiFollow package
2018-11-05 22:35:20 +00:00
for ( AiPackage * package : stats . getAiSequence ( ) )
2014-12-09 15:02:07 +00:00
{
2018-11-05 22:35:20 +00:00
if ( package - > followTargetThroughDoors ( ) & & package - > getTarget ( ) = = actor )
2014-12-09 15:02:07 +00:00
{
2018-11-05 22:35:20 +00:00
list . push_back ( static_cast < AiFollow * > ( package ) - > getFollowIndex ( ) ) ;
2015-12-13 16:42:11 +00:00
break ;
2014-12-09 15:02:07 +00:00
}
2018-11-05 22:35:20 +00:00
else if ( package - > getTypeId ( ) ! = AiPackage : : TypeIdCombat & & package - > getTypeId ( ) ! = AiPackage : : TypeIdWander )
2014-12-09 15:02:07 +00:00
break ;
}
}
return list ;
}
2014-04-25 02:47:45 +00:00
std : : list < MWWorld : : Ptr > Actors : : getActorsFighting ( const MWWorld : : Ptr & actor ) {
std : : list < MWWorld : : Ptr > list ;
std : : vector < MWWorld : : Ptr > neighbors ;
2015-06-01 19:41:13 +00:00
osg : : Vec3f position ( actor . getRefData ( ) . getPosition ( ) . asVec3 ( ) ) ;
2018-09-21 12:34:23 +00:00
getObjectsInRange ( position , mActorsProcessingRange , neighbors ) ;
2019-05-11 05:44:30 +00:00
for ( const MWWorld : : Ptr & neighbor : neighbors )
2014-04-25 02:47:45 +00:00
{
2018-11-05 22:35:20 +00:00
if ( neighbor = = actor )
2018-08-21 14:02:56 +00:00
continue ;
2018-11-05 22:35:20 +00:00
const CreatureStats & stats = neighbor . getClass ( ) . getCreatureStats ( neighbor ) ;
2018-08-21 14:02:56 +00:00
if ( stats . isDead ( ) )
2016-06-08 20:12:52 +00:00
continue ;
2018-08-14 12:36:52 +00:00
2016-06-08 20:12:52 +00:00
if ( stats . getAiSequence ( ) . isInCombat ( actor ) )
2018-11-05 22:35:20 +00:00
list . push_front ( neighbor ) ;
2014-04-25 02:47:45 +00:00
}
return list ;
}
2014-06-12 23:24:58 +00:00
2016-06-06 23:53:16 +00:00
std : : list < MWWorld : : Ptr > Actors : : getEnemiesNearby ( const MWWorld : : Ptr & actor )
{
std : : list < MWWorld : : Ptr > list ;
std : : vector < MWWorld : : Ptr > neighbors ;
osg : : Vec3f position ( actor . getRefData ( ) . getPosition ( ) . asVec3 ( ) ) ;
2018-09-21 12:34:23 +00:00
getObjectsInRange ( position , mActorsProcessingRange , neighbors ) ;
2016-06-11 15:45:56 +00:00
2018-08-14 13:14:48 +00:00
std : : set < MWWorld : : Ptr > followers ;
getActorsFollowing ( actor , followers ) ;
2018-08-14 15:14:43 +00:00
for ( auto neighbor = neighbors . begin ( ) ; neighbor ! = neighbors . end ( ) ; + + neighbor )
2016-06-06 23:53:16 +00:00
{
2018-08-14 12:36:52 +00:00
const CreatureStats & stats = neighbor - > getClass ( ) . getCreatureStats ( * neighbor ) ;
if ( stats . isDead ( ) | | * neighbor = = actor | | neighbor - > getClass ( ) . isPureWaterCreature ( * neighbor ) )
2016-06-06 23:53:16 +00:00
continue ;
2018-08-14 12:36:52 +00:00
2018-08-14 15:14:43 +00:00
const bool isFollower = followers . find ( * neighbor ) ! = followers . end ( ) ;
2018-08-14 12:36:52 +00:00
if ( stats . getAiSequence ( ) . isInCombat ( actor ) | | ( MWBase : : Environment : : get ( ) . getMechanicsManager ( ) - > isAggressive ( * neighbor , actor ) & & ! isFollower ) )
list . push_back ( * neighbor ) ;
2016-06-06 23:53:16 +00:00
}
return list ;
}
2014-06-12 23:24:58 +00:00
void Actors : : write ( ESM : : ESMWriter & writer , Loading : : Listener & listener ) const
{
writer . startRecord ( ESM : : REC_DCOU ) ;
for ( std : : map < std : : string , int > : : const_iterator it = mDeathCount . begin ( ) ; it ! = mDeathCount . end ( ) ; + + it )
{
writer . writeHNString ( " ID__ " , it - > first ) ;
writer . writeHNT ( " COUN " , it - > second ) ;
}
writer . endRecord ( ESM : : REC_DCOU ) ;
}
2015-01-22 18:04:59 +00:00
void Actors : : readRecord ( ESM : : ESMReader & reader , uint32_t type )
2014-06-12 23:24:58 +00:00
{
if ( type = = ESM : : REC_DCOU )
{
while ( reader . isNextSub ( " ID__ " ) )
{
std : : string id = reader . getHString ( ) ;
2017-09-17 13:16:17 +00:00
int count ;
reader . getHNT ( count , " COUN " ) ;
2017-09-17 13:09:29 +00:00
if ( MWBase : : Environment : : get ( ) . getWorld ( ) - > getStore ( ) . find ( id ) )
mDeathCount [ id ] = count ;
2014-06-12 23:24:58 +00:00
}
}
}
void Actors : : clear ( )
{
2014-12-21 15:45:30 +00:00
PtrActorMap : : iterator it ( mActors . begin ( ) ) ;
2014-07-27 21:10:58 +00:00
for ( ; it ! = mActors . end ( ) ; + + it )
{
delete it - > second ;
2018-10-09 06:21:12 +00:00
it - > second = nullptr ;
2014-07-27 21:10:58 +00:00
}
mActors . clear ( ) ;
2014-06-12 23:24:58 +00:00
mDeathCount . clear ( ) ;
}
2014-08-14 23:13:38 +00:00
void Actors : : updateMagicEffects ( const MWWorld : : Ptr & ptr )
{
adjustMagicEffects ( ptr ) ;
calculateCreatureStatModifiers ( ptr , 0.f ) ;
2014-08-17 03:42:52 +00:00
if ( ptr . getClass ( ) . isNpc ( ) )
2014-11-02 17:01:12 +00:00
calculateNpcStatModifiers ( ptr , 0.f ) ;
2014-08-14 23:13:38 +00:00
}
2014-12-12 15:49:22 +00:00
bool Actors : : isReadyToBlock ( const MWWorld : : Ptr & ptr ) const
{
2014-12-21 15:45:30 +00:00
PtrActorMap : : const_iterator it = mActors . find ( ptr ) ;
2014-12-12 15:49:22 +00:00
if ( it = = mActors . end ( ) )
return false ;
2014-12-21 15:45:30 +00:00
return it - > second - > getCharacterController ( ) - > isReadyToBlock ( ) ;
2014-12-12 15:49:22 +00:00
}
2014-12-31 17:41:57 +00:00
2018-06-28 12:58:51 +00:00
bool Actors : : isCastingSpell ( const MWWorld : : Ptr & ptr ) const
{
PtrActorMap : : const_iterator it = mActors . find ( ptr ) ;
if ( it = = mActors . end ( ) )
return false ;
return it - > second - > getCharacterController ( ) - > isCastingSpell ( ) ;
}
2017-08-18 15:24:34 +00:00
bool Actors : : isAttackingOrSpell ( const MWWorld : : Ptr & ptr ) const
{
PtrActorMap : : const_iterator it = mActors . find ( ptr ) ;
if ( it = = mActors . end ( ) )
return false ;
CharacterController * ctrl = it - > second - > getCharacterController ( ) ;
return ctrl - > isAttackingOrSpell ( ) ;
}
2020-05-17 01:06:39 +00:00
int Actors : : getGreetingTimer ( const MWWorld : : Ptr & ptr ) const
{
PtrActorMap : : const_iterator it = mActors . find ( ptr ) ;
if ( it = = mActors . end ( ) )
return 0 ;
return it - > second - > getGreetingTimer ( ) ;
}
float Actors : : getAngleToPlayer ( const MWWorld : : Ptr & ptr ) const
{
PtrActorMap : : const_iterator it = mActors . find ( ptr ) ;
if ( it = = mActors . end ( ) )
return 0.f ;
return it - > second - > getAngleToPlayer ( ) ;
}
GreetingState Actors : : getGreetingState ( const MWWorld : : Ptr & ptr ) const
{
PtrActorMap : : const_iterator it = mActors . find ( ptr ) ;
if ( it = = mActors . end ( ) )
return Greet_None ;
return it - > second - > getGreetingState ( ) ;
}
bool Actors : : isTurningToPlayer ( const MWWorld : : Ptr & ptr ) const
{
PtrActorMap : : const_iterator it = mActors . find ( ptr ) ;
if ( it = = mActors . end ( ) )
return false ;
return it - > second - > isTurningToPlayer ( ) ;
}
2014-12-31 17:41:57 +00:00
void Actors : : fastForwardAi ( )
{
if ( ! MWBase : : Environment : : get ( ) . getMechanicsManager ( ) - > isAIActive ( ) )
return ;
2015-01-01 17:11:37 +00:00
// making a copy since fast-forward could move actor to a different cell and invalidate the mActors iterator
PtrActorMap map = mActors ;
for ( PtrActorMap : : iterator it = map . begin ( ) ; it ! = map . end ( ) ; + + it )
2014-12-31 17:41:57 +00:00
{
MWWorld : : Ptr ptr = it - > first ;
2015-08-21 09:12:39 +00:00
if ( ptr = = getPlayer ( )
2015-01-13 17:09:10 +00:00
| | ! isConscious ( ptr )
2015-08-20 06:12:37 +00:00
| | ptr . getClass ( ) . getCreatureStats ( ptr ) . isParalyzed ( ) )
2014-12-31 17:41:57 +00:00
continue ;
MWMechanics : : AiSequence & seq = ptr . getClass ( ) . getCreatureStats ( ptr ) . getAiSequence ( ) ;
2018-06-27 08:48:34 +00:00
seq . fastForward ( ptr ) ;
2014-12-31 17:41:57 +00:00
}
}
2012-03-30 14:18:58 +00:00
}