2018-03-29 17:01:57 -04:00
# include "StdAfx.h"
# include "MonsterAI.h"
# include "WeenieObject.h"
# include "Monster.h"
# include "World.h"
# include "SpellcastingManager.h"
# include "EmoteManager.h"
# define DEFAULT_AWARENESS_RANGE 40.0
2018-05-23 13:45:22 +01:00
MonsterAIManager : : MonsterAIManager ( std : : shared_ptr < CMonsterWeenie > pWeenie , const Position & HomePos )
2018-03-29 17:01:57 -04:00
{
m_pWeenie = pWeenie ;
_toleranceType = pWeenie - > InqIntQuality ( TOLERANCE_INT , 0 , TRUE ) ;
_aiOptions = pWeenie - > InqIntQuality ( AI_OPTIONS_INT , 0 , TRUE ) ;
2018-05-29 23:47:16 +01:00
_cachedVisualAwarenessRange = pWeenie - > InqFloatQuality ( VISUAL_AWARENESS_RANGE_FLOAT , DEFAULT_AWARENESS_RANGE ) ;
2018-03-29 17:01:57 -04:00
2018-05-29 23:47:16 +01:00
if ( ! pWeenie - > GetWieldedCombat ( COMBAT_USE_TWO_HANDED ) )
_meleeWeapon = pWeenie - > GetWieldedCombat ( COMBAT_USE_MELEE ) ;
2018-04-02 16:34:35 -05:00
else {
2018-05-29 23:47:16 +01:00
_meleeWeapon = pWeenie - > GetWieldedCombat ( COMBAT_USE_TWO_HANDED ) ;
2018-04-02 16:34:35 -05:00
}
2018-05-29 23:47:16 +01:00
_missileWeapon = pWeenie - > GetWieldedCombat ( COMBAT_USE_MISSILE ) ;
_shield = pWeenie - > GetWieldedCombat ( COMBAT_USE_SHIELD ) ;
2018-03-29 17:01:57 -04:00
SKILL_ADVANCEMENT_CLASS unarmedSkill ;
2018-05-29 23:47:16 +01:00
pWeenie - > m_Qualities . InqSkillAdvancementClass ( LIGHT_WEAPONS_SKILL , unarmedSkill ) ;
2018-03-29 17:01:57 -04:00
_hasUnarmedSkill = ( unarmedSkill > UNTRAINED_SKILL_ADVANCEMENT_CLASS ) ;
2018-05-29 23:47:16 +01:00
// TODO these may need to be shared not weak
std : : shared_ptr < CWeenieObject > pMeleeWeapon = _meleeWeapon . lock ( ) ;
std : : shared_ptr < CWeenieObject > pMissileWeapon = _meleeWeapon . lock ( ) ;
std : : shared_ptr < CWeenieObject > pShield = _shield . lock ( ) ;
if ( pMeleeWeapon & & pMissileWeapon ) //if we have both melee and missile weapons, favor missile
2018-03-29 17:01:57 -04:00
{
2018-05-29 23:47:16 +01:00
pWeenie - > FinishMoveItemToContainer ( pMeleeWeapon , pWeenie , 0 , true ) ;
if ( pShield & & pMissileWeapon - > InqIntQuality ( DEFAULT_COMBAT_STYLE_INT , 0 ) ! = ThrownWeapon_CombatStyle )
pWeenie - > FinishMoveItemToContainer ( pShield , pWeenie , 0 , true ) ;
2018-03-29 17:01:57 -04:00
else
_currentShield = _shield ;
_currentWeapon = _missileWeapon ;
}
2018-05-29 23:47:16 +01:00
else if ( pMissileWeapon )
2018-03-29 17:01:57 -04:00
{
_currentWeapon = _missileWeapon ;
2018-05-29 23:47:16 +01:00
if ( pShield & & pMissileWeapon - > InqIntQuality ( DEFAULT_COMBAT_STYLE_INT , 0 ) ! = ThrownWeapon_CombatStyle )
pWeenie - > FinishMoveItemToContainer ( pShield , pWeenie , 0 , true ) ;
2018-03-29 17:01:57 -04:00
else
_currentShield = _shield ;
}
2018-05-29 23:47:16 +01:00
else if ( pMeleeWeapon )
2018-03-29 17:01:57 -04:00
{
_currentWeapon = _meleeWeapon ;
_currentShield = _shield ;
}
if ( pWeenie - > m_Qualities . _emote_table & & pWeenie - > m_Qualities . _emote_table - > _emote_table . lookup ( Taunt_EmoteCategory ) )
_nextTaunt = Timer : : cur_time + Random : : GenUInt ( 10 , 30 ) ; //We have taunts so schedule them.
// List of qualities that could be used to affect the AI behavior:
// AI_CP_THRESHOLD_INT
// AI_PP_THRESHOLD_INT
// AI_ADVANCEMENT_STRATEGY_INT
// AI_ALLOWED_COMBAT_STYLE_INT
// COMBAT_TACTIC_INT
// TARGETING_TACTIC_INT
// HOMESICK_TARGETING_TACTIC_INT
// AI_OPTIONS_INT
// FRIEND_TYPE_INT
// FOE_TYPE_INT
// ATTACKER_AI_BOOL
// AI_USES_MANA_BOOL
// AI_USE_HUMAN_MAGIC_ANIMATIONS_BOOL
// AI_IMMOBILE_BOOL
// AI_ALLOW_TRADE_BOOL
// AI_ACCEPT_EVERYTHING_BOOL
// AI_USE_MAGIC_DELAY_FLOAT
// AI_ACQUIRE_HEALTH_FLOAT
// AI_ACQUIRE_STAMINA_FLOAT
// AI_ACQUIRE_MANA_FLOAT
// AI_COUNTERACT_ENCHANTMENT_FLOAT
// AI_DISPEL_ENCHANTMENT_FLOAT
// AI_TARGETED_DETECTION_RADIUS_FLOAT
// SetHomePosition(HomePos); don't use preset home position
}
MonsterAIManager : : ~ MonsterAIManager ( )
{
}
void MonsterAIManager : : SetHomePosition ( const Position & pos )
{
m_HomePosition = pos ;
}
void MonsterAIManager : : Update ( )
{
2018-05-29 23:47:16 +01:00
std : : shared_ptr < CWeenieObject > pWeenie = m_pWeenie . lock ( ) ;
if ( ! pWeenie )
{
return ;
}
2018-03-29 17:01:57 -04:00
if ( ! m_HomePosition . objcell_id )
{
// make sure we set a home position
2018-05-29 23:47:16 +01:00
if ( ! ( pWeenie - > transient_state & ON_WALKABLE_TS ) )
2018-03-29 17:01:57 -04:00
return ;
2018-05-29 23:47:16 +01:00
SetHomePosition ( pWeenie - > m_Position ) ;
2018-03-29 17:01:57 -04:00
}
switch ( m_State )
{
case MonsterAIState : : Idle :
UpdateIdle ( ) ;
break ;
case MonsterAIState : : MeleeModeAttack :
UpdateMeleeModeAttack ( ) ;
break ;
case MonsterAIState : : MissileModeAttack :
UpdateMissileModeAttack ( ) ;
break ;
case MonsterAIState : : ReturningToSpawn :
UpdateReturningToSpawn ( ) ;
break ;
case MonsterAIState : : SeekNewTarget :
UpdateSeekNewTarget ( ) ;
break ;
}
}
void MonsterAIManager : : SwitchState ( int state )
{
if ( state = = m_State )
return ;
switch ( state )
{
case MonsterAIState : : Idle :
EndIdle ( ) ;
break ;
case MonsterAIState : : MeleeModeAttack :
EndMeleeModeAttack ( ) ;
break ;
case MonsterAIState : : MissileModeAttack :
EndMissileModeAttack ( ) ;
break ;
case MonsterAIState : : ReturningToSpawn :
EndReturningToSpawn ( ) ;
break ;
case MonsterAIState : : SeekNewTarget :
EndSeekNewTarget ( ) ;
break ;
}
EnterState ( state ) ;
}
void MonsterAIManager : : EnterState ( int state )
{
m_State = state ;
switch ( state )
{
case MonsterAIState : : Idle :
BeginIdle ( ) ;
break ;
case MonsterAIState : : MeleeModeAttack :
BeginMeleeModeAttack ( ) ;
break ;
case MonsterAIState : : MissileModeAttack :
BeginMissileModeAttack ( ) ;
break ;
case MonsterAIState : : ReturningToSpawn :
BeginReturningToSpawn ( ) ;
break ;
case MonsterAIState : : SeekNewTarget :
BeginSeekNewTarget ( ) ;
break ;
}
}
void MonsterAIManager : : BeginIdle ( )
{
2018-05-29 23:47:16 +01:00
std : : shared_ptr < CWeenieObject > pWeenie = m_pWeenie . lock ( ) ;
if ( ! pWeenie )
{
return ;
}
2018-03-29 17:01:57 -04:00
m_fNextPVSCheck = Timer : : cur_time ;
2018-05-29 23:47:16 +01:00
pWeenie - > ChangeCombatMode ( COMBAT_MODE : : NONCOMBAT_COMBAT_MODE , false ) ;
2018-03-29 17:01:57 -04:00
}
void MonsterAIManager : : EndIdle ( )
{
}
void MonsterAIManager : : UpdateIdle ( )
{
if ( _toleranceType = = TolerateNothing )
{
SeekTarget ( ) ;
}
}
bool MonsterAIManager : : SeekTarget ( )
{
2018-05-29 23:47:16 +01:00
std : : shared_ptr < CWeenieObject > pWeenie = m_pWeenie . lock ( ) ;
if ( ! pWeenie )
{
2018-05-31 18:00:27 +01:00
return false ;
2018-05-29 23:47:16 +01:00
}
2018-03-29 17:01:57 -04:00
if ( m_fNextPVSCheck < = Timer : : cur_time )
{
m_fNextPVSCheck = Timer : : cur_time + 2.0f ;
2018-05-23 13:45:22 +01:00
std : : list < std : : shared_ptr < CWeenieObject > > results ;
2018-05-29 23:47:16 +01:00
g_pWorld - > EnumNearbyPlayers ( pWeenie , _cachedVisualAwarenessRange , & results ) ; // m_HomePosition
2018-03-29 17:01:57 -04:00
2018-05-23 13:45:22 +01:00
std : : list < std : : shared_ptr < CWeenieObject > > validTargets ;
2018-03-29 17:01:57 -04:00
2018-05-23 13:45:22 +01:00
std : : shared_ptr < CWeenieObject > pClosestWeenie = NULL ;
2018-03-29 17:01:57 -04:00
double fClosestWeenieDist = FLT_MAX ;
for ( auto weenie : results )
{
2018-05-29 23:47:16 +01:00
if ( weenie = = pWeenie )
2018-03-29 17:01:57 -04:00
continue ;
if ( ! weenie - > _IsPlayer ( ) ) // only attack players
continue ;
if ( ! weenie - > IsAttackable ( ) )
continue ;
2018-05-29 23:47:16 +01:00
if ( weenie - > ImmuneToDamage ( pWeenie ) ) // only attackable players (not dead, not in portal space, etc.
2018-03-29 17:01:57 -04:00
continue ;
validTargets . push_back ( weenie ) ;
/*
2018-05-29 23:47:16 +01:00
double fWeenieDist = pWeenie - > DistanceTo ( weenie ) ;
2018-03-29 17:01:57 -04:00
if ( pClosestWeenie & & fWeenieDist > = fClosestWeenieDist )
continue ;
pClosestWeenie = weenie ;
fClosestWeenieDist = fWeenieDist ;
*/
}
/*
if ( pClosestWeenie )
SetNewTarget ( pClosestWeenie ) ;
*/
if ( ! validTargets . empty ( ) )
{
// Random target
2018-05-23 13:45:22 +01:00
std : : list < std : : shared_ptr < CWeenieObject > > : : iterator i = validTargets . begin ( ) ;
2018-03-29 17:01:57 -04:00
std : : advance ( i , Random : : GenInt ( 0 , ( unsigned int ) ( validTargets . size ( ) - 1 ) ) ) ;
SetNewTarget ( * i ) ;
return true ;
}
}
return false ;
}
2018-05-23 13:45:22 +01:00
void MonsterAIManager : : SetNewTarget ( std : : shared_ptr < CWeenieObject > pTarget )
2018-03-29 17:01:57 -04:00
{
2018-05-29 23:47:16 +01:00
std : : shared_ptr < CWeenieObject > pWeenie = m_pWeenie . lock ( ) ;
if ( ! pWeenie | | ! pTarget )
{
return ;
}
2018-03-29 17:01:57 -04:00
m_TargetID = pTarget - > GetID ( ) ;
2018-05-29 23:47:16 +01:00
if ( _missileWeapon . lock ( ) )
2018-03-29 17:01:57 -04:00
{
2018-05-29 23:47:16 +01:00
double fTargetDist = pWeenie - > DistanceTo ( pTarget , true ) ;
if ( fTargetDist > 5 | | ( ! _meleeWeapon . lock ( ) & & ! _hasUnarmedSkill ) )
2018-03-29 17:01:57 -04:00
SwitchState ( MissileModeAttack ) ;
else
SwitchState ( MeleeModeAttack ) ;
}
else
SwitchState ( MeleeModeAttack ) ;
2018-05-29 23:47:16 +01:00
pWeenie - > ChanceExecuteEmoteSet ( pTarget - > GetID ( ) , NewEnemy_EmoteCategory ) ;
2018-03-29 17:01:57 -04:00
}
2018-05-23 13:45:22 +01:00
std : : shared_ptr < CWeenieObject > MonsterAIManager : : GetTargetWeenie ( )
2018-03-29 17:01:57 -04:00
{
return g_pWorld - > FindObject ( m_TargetID ) ;
}
float MonsterAIManager : : DistanceToHome ( )
{
2018-05-29 23:47:16 +01:00
std : : shared_ptr < CWeenieObject > pWeenie = m_pWeenie . lock ( ) ;
if ( ! pWeenie )
{
return FLT_MAX ;
}
2018-03-29 17:01:57 -04:00
if ( ! m_HomePosition . objcell_id )
return FLT_MAX ;
2018-05-29 23:47:16 +01:00
return m_HomePosition . distance ( pWeenie - > m_Position ) ;
2018-03-29 17:01:57 -04:00
}
bool MonsterAIManager : : ShouldSeekNewTarget ( )
{
if ( DistanceToHome ( ) > = m_fMaxHomeRange )
return false ;
return true ;
}
bool MonsterAIManager : : RollDiceCastSpell ( )
{
2018-05-29 23:47:16 +01:00
std : : shared_ptr < CWeenieObject > pWeenie = m_pWeenie . lock ( ) ;
if ( ! pWeenie )
{
return false ;
}
2018-03-29 17:01:57 -04:00
if ( m_fNextCastTime > Timer : : cur_time )
{
return false ;
}
2018-05-29 23:47:16 +01:00
if ( pWeenie - > m_Qualities . _spell_book )
2018-03-29 17:01:57 -04:00
{
/* not correct, these must be independent events (look at wisps)
float dice = Random : : RollDice ( 0.0f , 1.0f ) ;
2018-05-29 23:47:16 +01:00
auto spellIterator = pWeenie - > m_Qualities . _spell_book - > _spellbook . begin ( ) ;
2018-03-29 17:01:57 -04:00
2018-05-29 23:47:16 +01:00
while ( spellIterator ! = pWeenie - > m_Qualities . _spell_book - > _spellbook . end ( ) )
2018-03-29 17:01:57 -04:00
{
float likelihood = spellIterator - > second . _casting_likelihood ;
if ( dice < = likelihood )
{
return DoCastSpell ( spellIterator - > first ) ;
}
dice - = likelihood ;
spellIterator + + ;
}
*/
2018-05-29 23:47:16 +01:00
auto spellIterator = pWeenie - > m_Qualities . _spell_book - > _spellbook . begin ( ) ;
2018-03-29 17:01:57 -04:00
2018-05-29 23:47:16 +01:00
while ( spellIterator ! = pWeenie - > m_Qualities . _spell_book - > _spellbook . end ( ) )
2018-03-29 17:01:57 -04:00
{
float dice = Random : : RollDice ( 0.0f , 1.0f ) ;
float likelihood = spellIterator - > second . _casting_likelihood ;
if ( dice < = likelihood )
{
2018-05-29 23:47:16 +01:00
m_fNextCastTime = Timer : : cur_time + pWeenie - > m_Qualities . GetFloat ( AI_USE_MAGIC_DELAY_FLOAT , 0.0 ) ;
2018-03-29 17:01:57 -04:00
return DoCastSpell ( spellIterator - > first ) ;
}
spellIterator + + ;
}
}
return false ;
}
bool MonsterAIManager : : DoCastSpell ( DWORD spell_id )
2018-05-29 23:47:16 +01:00
{
std : : shared_ptr < CWeenieObject > pWeenie = m_pWeenie . lock ( ) ;
if ( ! pWeenie )
{
return false ;
}
2018-05-23 13:45:22 +01:00
std : : shared_ptr < CWeenieObject > pTarget = GetTargetWeenie ( ) ;
2018-05-29 23:47:16 +01:00
pWeenie - > MakeSpellcastingManager ( ) - > CreatureBeginCast ( pTarget ? pTarget - > GetID ( ) : 0 , spell_id ) ;
2018-03-29 17:01:57 -04:00
return true ;
}
bool MonsterAIManager : : DoMeleeAttack ( )
{
2018-05-29 23:47:16 +01:00
std : : shared_ptr < CWeenieObject > pWeenie = m_pWeenie . lock ( ) ;
if ( ! pWeenie )
{
2018-05-31 18:00:27 +01:00
return false ;
2018-05-29 23:47:16 +01:00
}
2018-03-29 17:01:57 -04:00
DWORD motion = 0 ;
ATTACK_HEIGHT height = ATTACK_HEIGHT : : UNDEF_ATTACK_HEIGHT ;
float power = 0.0f ;
GenerateRandomAttack ( & motion , & height , & power ) ;
if ( ! motion )
{
return false ;
}
2018-05-23 13:45:22 +01:00
std : : shared_ptr < CWeenieObject > pTarget = GetTargetWeenie ( ) ;
2018-03-29 17:01:57 -04:00
if ( ! pTarget )
{
return false ;
}
2018-05-29 23:47:16 +01:00
pWeenie - > TryMeleeAttack ( pTarget - > GetID ( ) , height , power , motion ) ;
2018-03-29 17:01:57 -04:00
m_fNextAttackTime = Timer : : cur_time + 2.0f ;
m_fNextChaseTime = Timer : : cur_time ; // chase again anytime
m_fMinCombatStateTime = Timer : : cur_time + m_fMinCombatStateDuration ;
return true ;
}
2018-05-23 13:45:22 +01:00
void MonsterAIManager : : GenerateRandomAttack ( DWORD * motion , ATTACK_HEIGHT * height , float * power , std : : shared_ptr < CWeenieObject > weapon )
2018-03-29 17:01:57 -04:00
{
2018-05-29 23:47:16 +01:00
std : : shared_ptr < CWeenieObject > pWeenie = m_pWeenie . lock ( ) ;
if ( ! pWeenie )
{
return ;
}
2018-03-29 17:01:57 -04:00
* motion = 0 ;
* height = ATTACK_HEIGHT : : UNDEF_ATTACK_HEIGHT ;
* power = Random : : GenFloat ( 0 , 1 ) ;
2018-05-29 23:47:16 +01:00
if ( pWeenie - > _combatTable )
2018-03-29 17:01:57 -04:00
{
if ( weapon = = NULL )
2018-05-29 23:47:16 +01:00
if ( ! pWeenie - > GetWieldedCombat ( COMBAT_USE_TWO_HANDED ) )
weapon = pWeenie - > GetWieldedCombat ( COMBAT_USE_MELEE ) ;
2018-04-02 16:34:35 -05:00
else {
2018-05-29 23:47:16 +01:00
weapon = pWeenie - > GetWieldedCombat ( COMBAT_USE_TWO_HANDED ) ;
2018-04-02 16:34:35 -05:00
}
2018-03-29 17:01:57 -04:00
if ( weapon )
{
AttackType attackType = ( AttackType ) weapon - > InqIntQuality ( ATTACK_TYPE_INT , 0 ) ;
if ( attackType = = ( Thrust_AttackType | Slash_AttackType ) )
{
if ( * power > = 0.75f )
attackType = Slash_AttackType ;
else
attackType = Thrust_AttackType ;
}
CombatManeuver * combatManeuver ;
// some monster have undef'd attack heights (hollow?) which is index 0
2018-05-29 23:47:16 +01:00
combatManeuver = pWeenie - > _combatTable - > TryGetCombatManuever ( pWeenie - > get_minterp ( ) - > InqStyle ( ) , attackType , ( ATTACK_HEIGHT ) Random : : GenUInt ( 0 , 3 ) ) ;
2018-03-29 17:01:57 -04:00
if ( ! combatManeuver )
{
// and some don't
2018-05-29 23:47:16 +01:00
combatManeuver = pWeenie - > _combatTable - > TryGetCombatManuever ( pWeenie - > get_minterp ( ) - > InqStyle ( ) , attackType , ( ATTACK_HEIGHT ) Random : : GenUInt ( 1 , 3 ) ) ;
2018-03-29 17:01:57 -04:00
if ( ! combatManeuver )
{
2018-05-29 23:47:16 +01:00
combatManeuver = pWeenie - > _combatTable - > TryGetCombatManuever ( pWeenie - > get_minterp ( ) - > InqStyle ( ) , attackType , ATTACK_HEIGHT : : HIGH_ATTACK_HEIGHT ) ;
2018-03-29 17:01:57 -04:00
if ( ! combatManeuver )
{
2018-05-29 23:47:16 +01:00
combatManeuver = pWeenie - > _combatTable - > TryGetCombatManuever ( pWeenie - > get_minterp ( ) - > InqStyle ( ) , attackType , ATTACK_HEIGHT : : MEDIUM_ATTACK_HEIGHT ) ;
2018-03-29 17:01:57 -04:00
if ( ! combatManeuver )
{
2018-05-29 23:47:16 +01:00
combatManeuver = pWeenie - > _combatTable - > TryGetCombatManuever ( pWeenie - > get_minterp ( ) - > InqStyle ( ) , attackType , ATTACK_HEIGHT : : LOW_ATTACK_HEIGHT ) ;
2018-03-29 17:01:57 -04:00
}
}
}
}
if ( combatManeuver )
{
* motion = combatManeuver - > motion ;
* height = combatManeuver - > attack_height ;
}
}
else
{
AttackType attackType ;
if ( * power > = 0.75f )
{
attackType = Kick_AttackType ;
}
else
{
attackType = Punch_AttackType ;
}
CombatManeuver * combatManeuver ;
// some monster have undef'd attack heights (hollow?) which is index 0
2018-05-29 23:47:16 +01:00
combatManeuver = pWeenie - > _combatTable - > TryGetCombatManuever ( pWeenie - > get_minterp ( ) - > InqStyle ( ) , attackType , ( ATTACK_HEIGHT ) Random : : GenUInt ( 0 , 3 ) ) ;
2018-03-29 17:01:57 -04:00
if ( ! combatManeuver )
{
// and some don't
2018-05-29 23:47:16 +01:00
combatManeuver = pWeenie - > _combatTable - > TryGetCombatManuever ( pWeenie - > get_minterp ( ) - > InqStyle ( ) , attackType , ( ATTACK_HEIGHT ) Random : : GenUInt ( 1 , 3 ) ) ;
2018-03-29 17:01:57 -04:00
if ( ! combatManeuver )
{
2018-05-29 23:47:16 +01:00
combatManeuver = pWeenie - > _combatTable - > TryGetCombatManuever ( pWeenie - > get_minterp ( ) - > InqStyle ( ) , attackType , ATTACK_HEIGHT : : HIGH_ATTACK_HEIGHT ) ;
2018-03-29 17:01:57 -04:00
if ( ! combatManeuver )
{
2018-05-29 23:47:16 +01:00
combatManeuver = pWeenie - > _combatTable - > TryGetCombatManuever ( pWeenie - > get_minterp ( ) - > InqStyle ( ) , attackType , ATTACK_HEIGHT : : MEDIUM_ATTACK_HEIGHT ) ;
2018-03-29 17:01:57 -04:00
if ( ! combatManeuver )
{
2018-05-29 23:47:16 +01:00
combatManeuver = pWeenie - > _combatTable - > TryGetCombatManuever ( pWeenie - > get_minterp ( ) - > InqStyle ( ) , attackType , ATTACK_HEIGHT : : LOW_ATTACK_HEIGHT ) ;
2018-03-29 17:01:57 -04:00
}
}
}
}
if ( combatManeuver )
{
* motion = combatManeuver - > motion ;
* height = combatManeuver - > attack_height ;
}
}
}
if ( ! * motion )
{
* motion = Motion_AttackHigh1 ;
}
}
void MonsterAIManager : : BeginReturningToSpawn ( )
{
2018-05-29 23:47:16 +01:00
std : : shared_ptr < CWeenieObject > pWeenie = m_pWeenie . lock ( ) ;
if ( ! pWeenie )
{
return ;
}
// pWeenie->DoForcedStopCompletely();
2018-03-29 17:01:57 -04:00
MovementParameters params ;
params . can_walk = 0 ;
MovementStruct mvs ;
mvs . type = MovementTypes : : MoveToPosition ;
mvs . pos = m_HomePosition ;
mvs . params = & params ;
2018-05-29 23:47:16 +01:00
pWeenie - > movement_manager - > PerformMovement ( mvs ) ;
2018-03-29 17:01:57 -04:00
m_fReturnTimeoutTime = Timer : : cur_time + m_fReturnTimeout ;
2018-05-29 23:47:16 +01:00
pWeenie - > ChanceExecuteEmoteSet ( m_TargetID , Homesick_EmoteCategory ) ;
2018-03-29 17:01:57 -04:00
}
void MonsterAIManager : : EndReturningToSpawn ( )
{
}
void MonsterAIManager : : UpdateReturningToSpawn ( )
{
2018-05-29 23:47:16 +01:00
std : : shared_ptr < CWeenieObject > pWeenie = m_pWeenie . lock ( ) ;
if ( ! pWeenie )
{
return ;
}
float fDistToHome = m_HomePosition . distance ( pWeenie - > m_Position ) ;
2018-03-29 17:01:57 -04:00
if ( fDistToHome < 5.0f )
{
SwitchState ( Idle ) ;
return ;
}
if ( m_fReturnTimeoutTime < = Timer : : cur_time )
{
// teleport back to spawn
2018-05-29 23:47:16 +01:00
pWeenie - > Movement_Teleport ( m_HomePosition ) ;
2018-03-29 17:01:57 -04:00
SwitchState ( Idle ) ;
return ;
}
}
void MonsterAIManager : : OnDeath ( )
{
}
2018-05-23 13:45:22 +01:00
bool MonsterAIManager : : IsValidTarget ( std : : shared_ptr < CWeenieObject > pWeenie )
2018-03-29 17:01:57 -04:00
{
if ( ! pWeenie )
return false ;
2018-05-29 23:47:16 +01:00
if ( pWeenie = = pWeenie )
2018-03-29 17:01:57 -04:00
return false ;
if ( ! pWeenie - > _IsPlayer ( ) ) // only attack players
return false ;
2018-05-29 23:47:16 +01:00
if ( pWeenie - > ImmuneToDamage ( pWeenie ) ) // only attackable players (not dead, not in portal space, etc.
2018-03-29 17:01:57 -04:00
return false ;
return true ;
}
2018-05-23 13:45:22 +01:00
void MonsterAIManager : : AlertIdleFriendsToAggro ( std : : shared_ptr < CWeenieObject > pAttacker )
2018-03-29 17:01:57 -04:00
{
2018-05-29 23:47:16 +01:00
std : : shared_ptr < CWeenieObject > pWeenie = m_pWeenie . lock ( ) ;
if ( ! pWeenie )
{
return ;
}
2018-05-23 13:45:22 +01:00
std : : list < std : : shared_ptr < CWeenieObject > > results ;
2018-05-29 23:47:16 +01:00
g_pWorld - > EnumNearby ( pWeenie , 20.0f , & results ) ;
2018-03-29 17:01:57 -04:00
2018-05-29 23:47:16 +01:00
int ourType = pWeenie - > InqIntQuality ( CREATURE_TYPE_INT , 0 ) ;
int ourFriendType = pWeenie - > InqIntQuality ( FRIEND_TYPE_INT , 0 ) ;
2018-03-29 17:01:57 -04:00
for ( auto weenie : results )
{
2018-05-29 23:47:16 +01:00
if ( weenie = = pWeenie )
2018-03-29 17:01:57 -04:00
continue ;
if ( ! weenie - > IsCreature ( ) )
continue ;
2018-05-23 13:45:22 +01:00
// TODO is this safe to assume? mwnciau
std : : shared_ptr < CMonsterWeenie > creature = weenie - > AsMonster ( ) ;
2018-03-29 17:01:57 -04:00
if ( ! creature - > m_MonsterAI )
continue ;
switch ( creature - > m_MonsterAI - > m_State )
{
case Idle :
case ReturningToSpawn :
case SeekNewTarget :
break ;
default :
continue ;
}
if ( ! creature - > m_MonsterAI - > IsValidTarget ( pAttacker ) )
continue ;
if ( creature - > m_MonsterAI - > _toleranceType = = TolerateEverything )
continue ;
if ( creature - > m_MonsterAI - > _toleranceType = = TolerateUnlessAttacked & & _aiOptions ! = 1 ) // _aiOptions = 1 creatures do not tolerate their own kind/friends being attacked.
continue ;
int theirType = creature - > InqIntQuality ( CREATURE_TYPE_INT , 0 ) ;
int theirFriendType = creature - > InqIntQuality ( FRIEND_TYPE_INT , 0 ) ;
if ( ourType > 0 & & theirType > 0 )
{
if ( ourType ! = theirType & & ourType ! = theirFriendType & & theirType ! = ourFriendType ) //we only help our own kind or friends
continue ;
}
creature - > m_MonsterAI - > m_fAggroTime = Timer : : cur_time + 10.0 ;
creature - > m_MonsterAI - > SetNewTarget ( pAttacker ) ;
}
}
2018-05-23 13:45:22 +01:00
void MonsterAIManager : : OnResistSpell ( std : : shared_ptr < CWeenieObject > attacker )
2018-03-29 17:01:57 -04:00
{
2018-05-29 23:47:16 +01:00
std : : shared_ptr < CWeenieObject > pWeenie = m_pWeenie . lock ( ) ;
if ( ! pWeenie )
{
return ;
}
2018-03-29 17:01:57 -04:00
if ( attacker )
2018-05-29 23:47:16 +01:00
pWeenie - > ChanceExecuteEmoteSet ( attacker - > GetID ( ) , ResistSpell_EmoteCategory ) ;
2018-03-29 17:01:57 -04:00
}
2018-05-23 13:45:22 +01:00
void MonsterAIManager : : OnEvadeAttack ( std : : shared_ptr < CWeenieObject > attacker )
2018-03-29 17:01:57 -04:00
{
}
void MonsterAIManager : : OnDealtDamage ( DamageEventData & damageData )
{
2018-05-29 23:47:16 +01:00
std : : shared_ptr < CWeenieObject > pWeenie = m_pWeenie . lock ( ) ;
if ( ! pWeenie )
{
return ;
}
2018-03-29 17:01:57 -04:00
if ( _nextTaunt > 0 & & _nextTaunt < = Timer : : cur_time )
{
2018-05-29 23:47:16 +01:00
std : : shared_ptr < CWeenieObject > pTarget = damageData . target . lock ( ) ;
if ( pTarget )
pWeenie - > ChanceExecuteEmoteSet ( pTarget - > GetID ( ) , Taunt_EmoteCategory ) ;
2018-03-29 17:01:57 -04:00
_nextTaunt = Timer : : cur_time + Random : : GenUInt ( 10 , 30 ) ;
}
}
void MonsterAIManager : : OnTookDamage ( DamageEventData & damageData )
{
2018-05-29 23:47:16 +01:00
std : : shared_ptr < CWeenieObject > pWeenie = m_pWeenie . lock ( ) ;
if ( ! pWeenie )
{
return ;
}
2018-03-29 17:01:57 -04:00
2018-05-29 23:47:16 +01:00
std : : shared_ptr < CWeenieObject > source = damageData . source . lock ( ) ;
2018-03-29 17:01:57 -04:00
if ( ! source )
2018-05-29 23:47:16 +01:00
{
2018-03-29 17:01:57 -04:00
return ;
2018-05-29 23:47:16 +01:00
}
unsigned int damage = damageData . outputDamageFinal ;
2018-03-29 17:01:57 -04:00
HandleAggro ( source ) ;
if ( damageData . wasCrit )
2018-05-29 23:47:16 +01:00
pWeenie - > ChanceExecuteEmoteSet ( source - > GetID ( ) , ReceiveCritical_EmoteCategory ) ;
2018-03-29 17:01:57 -04:00
2018-05-29 23:47:16 +01:00
if ( pWeenie - > m_Qualities . _emote_table & & ! pWeenie - > IsExecutingEmote ( ) )
2018-03-29 17:01:57 -04:00
{
2018-05-29 23:47:16 +01:00
PackableList < EmoteSet > * emoteSetList = pWeenie - > m_Qualities . _emote_table - > _emote_table . lookup ( WoundedTaunt_EmoteCategory ) ;
2018-03-29 17:01:57 -04:00
if ( emoteSetList )
{
2018-05-29 23:47:16 +01:00
double healthPercent = pWeenie - > GetHealthPercent ( ) ;
2018-03-29 17:01:57 -04:00
if ( m_fLastWoundedTauntHP > healthPercent )
{
double dice = Random : : GenFloat ( 0.0 , 1.0 ) ;
for ( auto & emoteSet : * emoteSetList )
{
// ignore probability?
if ( healthPercent > = emoteSet . minhealth & & healthPercent < emoteSet . maxhealth )
{
m_fLastWoundedTauntHP = healthPercent ;
2018-05-29 23:47:16 +01:00
pWeenie - > MakeEmoteManager ( ) - > ExecuteEmoteSet ( emoteSet , source - > GetID ( ) ) ;
2018-03-29 17:01:57 -04:00
}
}
}
}
}
}
2018-05-23 13:45:22 +01:00
void MonsterAIManager : : OnIdentifyAttempted ( std : : shared_ptr < CWeenieObject > other )
2018-03-29 17:01:57 -04:00
{
2018-05-29 23:47:16 +01:00
std : : shared_ptr < CWeenieObject > pWeenie = m_pWeenie . lock ( ) ;
if ( ! pWeenie )
{
return ;
}
2018-03-29 17:01:57 -04:00
if ( _toleranceType ! = TolerateUnlessBothered )
{
return ;
}
2018-05-29 23:47:16 +01:00
if ( pWeenie - > DistanceTo ( other , true ) > = 60.0 )
2018-03-29 17:01:57 -04:00
{
return ;
}
HandleAggro ( other ) ;
}
2018-05-23 13:45:22 +01:00
void MonsterAIManager : : HandleAggro ( std : : shared_ptr < CWeenieObject > pAttacker )
2018-03-29 17:01:57 -04:00
{
2018-05-29 23:47:16 +01:00
std : : shared_ptr < CWeenieObject > pWeenie = m_pWeenie . lock ( ) ;
if ( ! pWeenie )
{
return ;
}
2018-03-29 17:01:57 -04:00
if ( _toleranceType = = TolerateEverything )
{
return ;
}
2018-05-29 23:47:16 +01:00
if ( ! pWeenie - > IsDead ( ) )
2018-03-29 17:01:57 -04:00
{
switch ( m_State )
{
case Idle :
case ReturningToSpawn :
case SeekNewTarget :
{
if ( IsValidTarget ( pAttacker ) )
{
2018-05-29 23:47:16 +01:00
//if (pWeenie->DistanceTo(pAttacker, true) <= pWeenie->InqFloatQuality(VISUAL_AWARENESS_RANGE_FLOAT, DEFAULT_AWARENESS_RANGE))
2018-03-29 17:01:57 -04:00
//{
SetNewTarget ( pAttacker ) ;
//}
2018-05-29 23:47:16 +01:00
pWeenie - > ChanceExecuteEmoteSet ( pAttacker - > GetID ( ) , Scream_EmoteCategory ) ;
2018-03-29 17:01:57 -04:00
m_fAggroTime = Timer : : cur_time + 10.0 ;
}
break ;
}
}
}
AlertIdleFriendsToAggro ( pAttacker ) ;
}
void MonsterAIManager : : BeginSeekNewTarget ( )
{
if ( ! ShouldSeekNewTarget ( ) | | ! SeekTarget ( ) )
{
SwitchState ( ReturningToSpawn ) ;
}
}
void MonsterAIManager : : UpdateSeekNewTarget ( )
{
SwitchState ( ReturningToSpawn ) ;
}
void MonsterAIManager : : EndSeekNewTarget ( )
{
}
void MonsterAIManager : : BeginMeleeModeAttack ( )
{
2018-05-29 23:47:16 +01:00
std : : shared_ptr < CMonsterWeenie > pWeenie = m_pWeenie . lock ( ) ;
if ( ! pWeenie )
2018-03-29 17:01:57 -04:00
{
2018-05-29 23:47:16 +01:00
return ;
}
std : : shared_ptr < CWeenieObject > pShield = _shield . lock ( ) ;
std : : shared_ptr < CWeenieObject > pCurrentShield = _currentShield . lock ( ) ;
if ( pShield & & ! pCurrentShield )
{
if ( pWeenie - > FinishMoveItemToWield ( pShield , SHIELD_LOC ) ) //make sure our shield is equipped
2018-03-29 17:01:57 -04:00
_currentShield = _shield ;
}
2018-05-29 23:47:16 +01:00
std : : shared_ptr < CWeenieObject > pMeleeWeapon = _meleeWeapon . lock ( ) ;
std : : shared_ptr < CWeenieObject > pCurrentWeapon = _currentWeapon . lock ( ) ;
if ( pCurrentWeapon ! = pMeleeWeapon )
2018-03-29 17:01:57 -04:00
{
2018-05-29 23:47:16 +01:00
if ( pCurrentWeapon )
{
pWeenie - > FinishMoveItemToContainer ( pCurrentWeapon , pWeenie , 0 , true ) ;
}
if ( pMeleeWeapon )
2018-03-29 17:01:57 -04:00
{
2018-05-29 23:47:16 +01:00
if ( pWeenie - > FinishMoveItemToWield ( pMeleeWeapon , MELEE_WEAPON_LOC ) )
{
_currentWeapon = pMeleeWeapon ;
}
2018-03-29 17:01:57 -04:00
else
2018-05-29 23:47:16 +01:00
{
2018-06-06 18:08:32 +01:00
_currentWeapon = std : : weak_ptr < CWeenieObject > ( ) ;
2018-05-29 23:47:16 +01:00
}
2018-03-29 17:01:57 -04:00
}
}
2018-05-29 23:47:16 +01:00
pWeenie - > ChangeCombatMode ( COMBAT_MODE : : MELEE_COMBAT_MODE , false ) ;
2018-03-29 17:01:57 -04:00
m_fChaseTimeoutTime = Timer : : cur_time + m_fChaseTimeoutDuration ;
m_fNextAttackTime = Timer : : cur_time ;
m_fNextChaseTime = Timer : : cur_time ;
m_fMinCombatStateTime = Timer : : cur_time + m_fMinCombatStateDuration ;
m_fMinReturnStateTime = Timer : : cur_time + m_fMinReturnStateDuration ;
}
void MonsterAIManager : : EndMeleeModeAttack ( )
{
2018-05-29 23:47:16 +01:00
std : : shared_ptr < CWeenieObject > pWeenie = m_pWeenie . lock ( ) ;
if ( ! pWeenie )
{
return ;
}
pWeenie - > unstick_from_object ( ) ;
2018-03-29 17:01:57 -04:00
}
void MonsterAIManager : : UpdateMeleeModeAttack ( )
{
2018-05-29 23:47:16 +01:00
std : : shared_ptr < CWeenieObject > pWeenie = m_pWeenie . lock ( ) ;
if ( ! pWeenie | | pWeenie - > IsBusyOrInAction ( ) )
2018-03-29 17:01:57 -04:00
{
// still animating or busy (attacking, etc.)
return ;
}
// rules:
// dont switch targets to one that is farther than visual awareness range, unless attacked
// dont chase a target that is outside the chase range, unless attacked
// dont chase any new target, even if attacked, outside home range
2018-05-23 13:45:22 +01:00
std : : shared_ptr < CWeenieObject > pTarget = GetTargetWeenie ( ) ;
2018-05-29 23:47:16 +01:00
if ( ! pTarget | | pTarget - > IsDead ( ) | | ! pTarget - > IsAttackable ( ) | | pTarget - > ImmuneToDamage ( pWeenie ) | | pWeenie - > DistanceTo ( pTarget ) > = m_fChaseRange )
2018-03-29 17:01:57 -04:00
{
if ( ShouldSeekNewTarget ( ) )
{
SwitchState ( SeekNewTarget ) ;
}
else
{
SwitchState ( ReturningToSpawn ) ;
}
return ;
}
if ( DistanceToHome ( ) > = m_fMaxHomeRange )
{
SwitchState ( ReturningToSpawn ) ;
return ;
}
2018-05-29 23:47:16 +01:00
double fTargetDist = pWeenie - > DistanceTo ( pTarget , true ) ;
if ( fTargetDist > = pWeenie - > InqFloatQuality ( VISUAL_AWARENESS_RANGE_FLOAT , DEFAULT_AWARENESS_RANGE ) & & m_fAggroTime < = Timer : : cur_time )
2018-03-29 17:01:57 -04:00
{
SwitchState ( ReturningToSpawn ) ;
return ;
}
if ( m_fNextAttackTime > Timer : : cur_time )
{
return ;
}
2018-05-29 23:47:16 +01:00
if ( ! RollDiceCastSpell ( ) & & pWeenie - > DistanceTo ( pTarget ) < m_fChaseRange )
2018-03-29 17:01:57 -04:00
{
// do physics attack
DWORD motion = 0 ;
ATTACK_HEIGHT height = ATTACK_HEIGHT : : UNDEF_ATTACK_HEIGHT ;
float power = 0.0f ;
GenerateRandomAttack ( & motion , & height , & power ) ;
2018-05-29 23:47:16 +01:00
pWeenie - > TryMeleeAttack ( pTarget - > GetID ( ) , height , power , motion ) ;
2018-03-29 17:01:57 -04:00
m_fNextAttackTime = Timer : : cur_time + 2.0f ;
m_fNextChaseTime = Timer : : cur_time ; // chase again anytime
m_fMinCombatStateTime = Timer : : cur_time + m_fMinCombatStateDuration ;
}
}
void MonsterAIManager : : BeginMissileModeAttack ( )
{
2018-05-29 23:47:16 +01:00
std : : shared_ptr < CMonsterWeenie > pWeenie = m_pWeenie . lock ( ) ;
if ( ! pWeenie )
{
return ;
}
std : : shared_ptr < CWeenieObject > pMissileWeapon = _missileWeapon . lock ( ) ;
if ( ! pMissileWeapon )
2018-03-29 17:01:57 -04:00
{
SwitchState ( MeleeModeAttack ) ;
return ;
}
else
{
2018-05-29 23:47:16 +01:00
std : : shared_ptr < CWeenieObject > pShield = _shield . lock ( ) ;
std : : shared_ptr < CWeenieObject > pCurrentShield = _currentShield . lock ( ) ;
if ( pMissileWeapon - > InqIntQuality ( DEFAULT_COMBAT_STYLE_INT , 0 ) ! = ThrownWeapon_CombatStyle )
2018-03-29 17:01:57 -04:00
{
2018-05-29 23:47:16 +01:00
std : : shared_ptr < CWeenieObject > equippedAmmo = pWeenie - > GetWieldedCombat ( COMBAT_USE : : COMBAT_USE_AMMO ) ;
2018-03-29 17:01:57 -04:00
if ( ! equippedAmmo )
{
//we don't have ammo, disable missile mode and switch to melee.
2018-06-06 18:08:32 +01:00
_missileWeapon = std : : weak_ptr < CWeenieObject > ( ) ;
2018-03-29 17:01:57 -04:00
SwitchState ( MeleeModeAttack ) ;
return ;
}
2018-05-29 23:47:16 +01:00
if ( pCurrentShield )
2018-03-29 17:01:57 -04:00
{
2018-05-29 23:47:16 +01:00
pWeenie - > FinishMoveItemToContainer ( pCurrentShield , pWeenie , 0 , true ) ; //get rid of the shield.
2018-06-06 18:08:32 +01:00
_currentShield = std : : weak_ptr < CWeenieObject > ( ) ;
2018-03-29 17:01:57 -04:00
}
}
2018-05-29 23:47:16 +01:00
else if ( pShield & & ! pCurrentShield )
2018-03-29 17:01:57 -04:00
{
2018-05-29 23:47:16 +01:00
if ( pWeenie - > FinishMoveItemToWield ( pShield , SHIELD_LOC ) ) //shields can be wielded with thrown weapons.
{
_currentShield = pShield ;
}
2018-03-29 17:01:57 -04:00
}
}
2018-05-29 23:47:16 +01:00
std : : shared_ptr < CWeenieObject > pCurrentWeapon = _currentWeapon . lock ( ) ;
2018-03-29 17:01:57 -04:00
2018-05-29 23:47:16 +01:00
if ( pCurrentWeapon & & pCurrentWeapon ! = pMissileWeapon )
2018-03-29 17:01:57 -04:00
{
2018-05-29 23:47:16 +01:00
pWeenie - > FinishMoveItemToContainer ( pCurrentWeapon , pWeenie , 0 , true ) ;
if ( pWeenie - > FinishMoveItemToWield ( pMissileWeapon , MISSILE_WEAPON_LOC ) )
_currentWeapon = _missileWeapon ;
else
2018-03-29 17:01:57 -04:00
{
2018-06-06 18:08:32 +01:00
_currentWeapon = std : : weak_ptr < CWeenieObject > ( ) ;
2018-05-29 23:47:16 +01:00
SwitchState ( MeleeModeAttack ) ;
2018-03-29 17:01:57 -04:00
}
}
2018-05-29 23:47:16 +01:00
pWeenie - > ChangeCombatMode ( COMBAT_MODE : : MISSILE_COMBAT_MODE , false ) ;
2018-03-29 17:01:57 -04:00
m_fChaseTimeoutTime = Timer : : cur_time + m_fChaseTimeoutDuration ;
m_fNextAttackTime = Timer : : cur_time ;
m_fNextChaseTime = Timer : : cur_time ;
m_fMinCombatStateTime = Timer : : cur_time + m_fMinCombatStateDuration ;
m_fMinReturnStateTime = Timer : : cur_time + m_fMinReturnStateDuration ;
}
void MonsterAIManager : : EndMissileModeAttack ( )
{
}
void MonsterAIManager : : UpdateMissileModeAttack ( )
{
2018-05-29 23:47:16 +01:00
std : : shared_ptr < CMonsterWeenie > pWeenie = m_pWeenie . lock ( ) ;
if ( ! pWeenie )
{
return ;
}
if ( pWeenie - > IsBusyOrInAction ( ) | | pWeenie - > motions_pending ( ) )
2018-03-29 17:01:57 -04:00
{
// still animating or busy (attacking, etc.)
return ;
}
// rules:
// dont switch targets to one that is farther than visual awareness range, unless attacked
// dont chase a target that is outside the chase range, unless attacked
// dont chase any new target, even if attacked, outside home range
2018-05-23 13:45:22 +01:00
std : : shared_ptr < CWeenieObject > pTarget = GetTargetWeenie ( ) ;
2018-05-29 23:47:16 +01:00
if ( ! pTarget | | pTarget - > IsDead ( ) | | ! pTarget - > IsAttackable ( ) | | pTarget - > ImmuneToDamage ( pWeenie ) | | pWeenie - > DistanceTo ( pTarget ) > = m_fChaseRange )
2018-03-29 17:01:57 -04:00
{
if ( ShouldSeekNewTarget ( ) )
{
SwitchState ( SeekNewTarget ) ;
}
else
{
SwitchState ( ReturningToSpawn ) ;
}
return ;
}
if ( DistanceToHome ( ) > = m_fMaxHomeRange )
{
SwitchState ( ReturningToSpawn ) ;
return ;
}
float weaponMinRange = 1 ;
float weaponMaxRange = 60 ; //todo: get the value from the weapon? Players currently have 60 as a fixed max value.
2018-05-29 23:47:16 +01:00
double fTargetDist = pWeenie - > DistanceTo ( pTarget , true ) ;
if ( fTargetDist > = max ( pWeenie - > InqFloatQuality ( VISUAL_AWARENESS_RANGE_FLOAT , DEFAULT_AWARENESS_RANGE ) , weaponMaxRange ) & & m_fAggroTime < = Timer : : cur_time )
2018-03-29 17:01:57 -04:00
{
SwitchState ( ReturningToSpawn ) ;
return ;
}
if ( m_fNextAttackTime > Timer : : cur_time )
{
return ;
}
2018-05-29 23:47:16 +01:00
std : : shared_ptr < CWeenieObject > pMeleeWeapon = _meleeWeapon . lock ( ) ;
if ( pMeleeWeapon | | _hasUnarmedSkill ) //we also have a melee weapon(or know how to fight without one)
2018-03-29 17:01:57 -04:00
{
double roll = Random : : GenFloat ( 0.0 , 1.0 ) ;
if ( fTargetDist < weaponMinRange & & roll < 0.3 ) //the target is too close, let's go melee
{
SwitchState ( MeleeModeAttack ) ;
return ;
}
if ( roll < 0.02 ) //we're tired of doing missile attacks, let's switch it up
{
SwitchState ( MeleeModeAttack ) ;
return ;
}
}
if ( ! RollDiceCastSpell ( ) )
{
2018-05-29 23:47:16 +01:00
if ( pWeenie - > DistanceTo ( pTarget ) < weaponMaxRange )
2018-03-29 17:01:57 -04:00
{
// do physics attack
DWORD motion = 0 ;
ATTACK_HEIGHT height = ( ATTACK_HEIGHT ) Random : : GenUInt ( 1 , 3 ) ;
float power = Random : : GenFloat ( 0 , 1 ) ;
2018-05-29 23:47:16 +01:00
pWeenie - > TryMissileAttack ( pTarget - > GetID ( ) , height , power ) ;
2018-03-29 17:01:57 -04:00
m_fNextAttackTime = Timer : : cur_time + 2.0f ;
m_fNextChaseTime = Timer : : cur_time ; // chase again anytime
m_fMinCombatStateTime = Timer : : cur_time + m_fMinCombatStateDuration ;
}
}
}