Removed JAI, added CODI AI. Fixed encounters & NPCs in Forest of Hope Central. Fixed Outcast store not opening. Added Druid Grove & associated NPCS.
2899 lines
87 KiB
Plaintext
2899 lines
87 KiB
Plaintext
#include "no_inc_ptypes"
|
|
|
|
//support functions
|
|
void NO_ActionCastSpellAtLocation( int iSpell, location lTargetLocation, int iMetaMagic=METAMAGIC_ANY, int iCheat=FALSE, int iProjectilePathType=PROJECTILE_PATH_TYPE_DEFAULT, int iInstantSpell=FALSE, int iDefensive=TRUE )
|
|
{
|
|
int iLevel = 14;
|
|
int iConc = GetCombatConcentration();
|
|
int iDefCast = FALSE;
|
|
|
|
if ( iDefensive == TRUE && iConc >= iLevel && GetHostileCount( 5.0 ) > 0 )
|
|
{
|
|
iDefCast = TRUE;
|
|
}
|
|
SetActionMode( OBJECT_SELF, ACTION_MODE_DEFENSIVE_CAST, iDefCast );
|
|
ActionCastSpellAtLocation( iSpell, lTargetLocation, iMetaMagic, iCheat, iProjectilePathType, iInstantSpell );
|
|
}
|
|
|
|
void NO_ActionCastSpellAtObject( int iSpell, object oTarget, int iMetaMagic=METAMAGIC_ANY, int iCheat=FALSE, int iDomainLevel=0, int iProjectilePathType=PROJECTILE_PATH_TYPE_DEFAULT, int iInstantSpell=FALSE, int iDefensive=TRUE )
|
|
{
|
|
int iLevel = 14;
|
|
int iConc = GetCombatConcentration();
|
|
int iDefCast = FALSE;
|
|
|
|
if ( iDefensive == TRUE && iConc >= iLevel && GetHostileCount( 5.0 ) > 0 )
|
|
{
|
|
iDefCast = TRUE;
|
|
}
|
|
SetActionMode( OBJECT_SELF, ACTION_MODE_DEFENSIVE_CAST, iDefCast );
|
|
ActionCastSpellAtObject( iSpell, oTarget, iMetaMagic, iCheat, iDomainLevel, iProjectilePathType, iInstantSpell );
|
|
}
|
|
|
|
void NO_ActionUseTalentAtLocation( talent tChosenTalent, location lTargetLocation, int iDefensive=TRUE )
|
|
{
|
|
int iLevel = 14;
|
|
int iConc = GetCombatConcentration();
|
|
int iDefCast = FALSE;
|
|
|
|
if ( iDefensive == TRUE && iConc >= iLevel && GetHostileCount( 5.0 ) > 0 )
|
|
{
|
|
iDefCast = TRUE;
|
|
}
|
|
SetActionMode( OBJECT_SELF, ACTION_MODE_DEFENSIVE_CAST, iDefCast );
|
|
ActionUseTalentAtLocation( tChosenTalent, lTargetLocation );
|
|
}
|
|
|
|
void NO_ActionUseTalentOnObject( talent tChosenTalent, object oTarget, int iDefensive=TRUE )
|
|
{
|
|
int iLevel = 14;
|
|
int iConc = GetCombatConcentration();
|
|
int iDefCast = FALSE;
|
|
|
|
if ( iDefensive == TRUE && iConc >= iLevel && GetHostileCount( 5.0 ) > 0 )
|
|
{
|
|
iDefCast = TRUE;
|
|
}
|
|
SetActionMode( OBJECT_SELF, ACTION_MODE_DEFENSIVE_CAST, iDefCast );
|
|
ActionUseTalentOnObject( tChosenTalent, oTarget );
|
|
}
|
|
|
|
void ActivateCombatMode( int iMode=0, object oE=OBJECT_SELF )
|
|
{
|
|
int iM = 0;
|
|
|
|
switch( iMode )
|
|
{
|
|
case FEAT_DIRTY_FIGHTING:
|
|
iM = ACTION_MODE_DIRTY_FIGHTING;
|
|
break;
|
|
case FEAT_EXPERTISE:
|
|
iM = ACTION_MODE_EXPERTISE;
|
|
break;
|
|
case FEAT_IMPROVED_EXPERTISE:
|
|
iM = ACTION_MODE_IMPROVED_EXPERTISE;
|
|
break;
|
|
case FEAT_FLURRY_OF_BLOWS:
|
|
iM = ACTION_MODE_FLURRY_OF_BLOWS;
|
|
break;
|
|
case FEAT_POWER_ATTACK:
|
|
iM = ACTION_MODE_POWER_ATTACK;
|
|
break;
|
|
case FEAT_IMPROVED_POWER_ATTACK:
|
|
iM = ACTION_MODE_IMPROVED_POWER_ATTACK;
|
|
break;
|
|
case FEAT_RAPID_SHOT:
|
|
iM = ACTION_MODE_RAPID_SHOT;
|
|
break;
|
|
}
|
|
if ( GetActionMode( oE, iM ) == FALSE )
|
|
{
|
|
SetActionMode( oE, iM, TRUE );
|
|
}
|
|
}
|
|
|
|
void DeactivateCombatModes( int iException=0, object oE=OBJECT_SELF )
|
|
{
|
|
if ( iException != FEAT_DIRTY_FIGHTING && GetActionMode( oE, ACTION_MODE_DIRTY_FIGHTING ) == TRUE )
|
|
{
|
|
SetActionMode( oE, ACTION_MODE_DIRTY_FIGHTING, FALSE );
|
|
}
|
|
if ( iException != FEAT_EXPERTISE && GetActionMode( oE, ACTION_MODE_EXPERTISE ) == TRUE )
|
|
{
|
|
SetActionMode( oE, ACTION_MODE_EXPERTISE, FALSE );
|
|
}
|
|
if ( iException != FEAT_IMPROVED_EXPERTISE && GetActionMode( oE, ACTION_MODE_IMPROVED_EXPERTISE ) == TRUE )
|
|
{
|
|
SetActionMode( oE, ACTION_MODE_IMPROVED_EXPERTISE, FALSE );
|
|
}
|
|
if ( iException != FEAT_FLURRY_OF_BLOWS && GetActionMode( oE, ACTION_MODE_FLURRY_OF_BLOWS ) == TRUE )
|
|
{
|
|
SetActionMode( oE, ACTION_MODE_FLURRY_OF_BLOWS, FALSE );
|
|
}
|
|
if ( iException != FEAT_POWER_ATTACK && GetActionMode( oE, ACTION_MODE_POWER_ATTACK ) == TRUE )
|
|
{
|
|
SetActionMode( oE, ACTION_MODE_POWER_ATTACK, FALSE );
|
|
}
|
|
if ( iException != FEAT_IMPROVED_POWER_ATTACK && GetActionMode( oE, ACTION_MODE_IMPROVED_POWER_ATTACK ) == TRUE )
|
|
{
|
|
SetActionMode( oE, ACTION_MODE_IMPROVED_POWER_ATTACK, FALSE );
|
|
}
|
|
if ( iException != FEAT_RAPID_SHOT && GetActionMode( oE, ACTION_MODE_RAPID_SHOT ) == TRUE )
|
|
{
|
|
SetActionMode( oE, ACTION_MODE_RAPID_SHOT, FALSE );
|
|
}
|
|
}
|
|
|
|
int DoHideInPlainSight( object oE=OBJECT_SELF )
|
|
{
|
|
if ( GetHasFeat( FEAT_HIDE_IN_PLAIN_SIGHT, oE ) )
|
|
{
|
|
SetActionMode( oE, ACTION_MODE_STEALTH, TRUE );
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
void DoFireBeholderRay( int iS, object oT )
|
|
{
|
|
if ( iS < 776 || iS > 784 || iS == 781 || iS == 782 ) //not a valid beholder ray spell definition
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( TouchAttackRanged( oT ) == FALSE )
|
|
{
|
|
vector vT;
|
|
location lT;
|
|
int iF = 785 + Random( 3 );
|
|
|
|
vT = GetPosition( oT ) + 1.0 * AngleToVector( IntToFloat( Random( 360 ) ) );
|
|
lT = Location( GetArea( OBJECT_SELF ), vT, GetFacing( OBJECT_SELF ) );
|
|
//ActionCastFakeSpellAtLocation( iS, lT );
|
|
ActionCastSpellAtLocation( iF, lT, METAMAGIC_ANY, TRUE, PROJECTILE_PATH_TYPE_DEFAULT, TRUE );
|
|
}
|
|
else
|
|
{
|
|
ActionCastSpellAtObject( iS, oT, METAMAGIC_ANY, TRUE, 0, PROJECTILE_PATH_TYPE_DEFAULT, TRUE );
|
|
}
|
|
}
|
|
|
|
void DoMoveToLocation( location lDest, int bRun=FALSE )
|
|
{
|
|
effect eVis;
|
|
float fD = 4.0;
|
|
float fMinDist = 15.0;
|
|
|
|
//activate hide in plain sight while moving
|
|
DoHideInPlainSight();
|
|
|
|
if ( GetLocalInt( OBJECT_SELF, "TELEPORTER" ) && GetDistanceBetweenLocations( GetLocation( OBJECT_SELF ), lDest ) > fMinDist )
|
|
{
|
|
DoTeleport( lDest );
|
|
}
|
|
else if ( GetLocalInt( OBJECT_SELF, "FLIER" ) && GetDistanceBetweenLocations( GetLocation( OBJECT_SELF ), lDest ) > fMinDist )
|
|
{
|
|
if ( !GetLocalInt( OBJECT_SELF, "DRAGONFLIGHTDEL" ) )
|
|
{
|
|
SetLocalInt( OBJECT_SELF, "DRAGONFLIGHTDEL", 1 );
|
|
DelayCommand( fD + 0.5, DeleteLocalInt( OBJECT_SELF, "DRAGONFLIGHTDEL" ) );
|
|
ApplyEffectToObject( DURATION_TYPE_TEMPORARY, EffectDisappearAppear( lDest ), OBJECT_SELF, fD );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ActionMoveToLocation( lDest, bRun );
|
|
}
|
|
}
|
|
|
|
void DoMoveToObject( object oDest, int bRun=FALSE, float fDist=1.0f )
|
|
{
|
|
location lDest;
|
|
vector vT;
|
|
float fD = 4.0;
|
|
float fMinDist = 15.0;
|
|
|
|
//activate hide in plain sight while moving
|
|
DoHideInPlainSight();
|
|
|
|
if ( GetIsObjectValid( oDest ) )
|
|
{
|
|
if ( GetLocalInt( OBJECT_SELF, "TELEPORTER" ) && GetDistanceBetweenLocations( GetLocation( OBJECT_SELF ), GetLocation( oDest ) ) > fMinDist )
|
|
{
|
|
vT = GetPosition( oDest ) + fDist * VectorNormalize( GetPosition( OBJECT_SELF ) - GetPosition( oDest ) );
|
|
lDest = Location( GetArea( oDest ), vT, VectorToAngle( GetPosition( oDest ) - vT ) );
|
|
DoTeleport( lDest );
|
|
}
|
|
else if ( GetLocalInt( OBJECT_SELF, "FLIER" ) && GetDistanceBetweenLocations( GetLocation( OBJECT_SELF ), GetLocation( oDest ) ) > fMinDist )
|
|
{
|
|
if ( !GetLocalInt( OBJECT_SELF, "DRAGONFLIGHTDEL" ) )
|
|
{
|
|
vT = GetPosition( oDest ) + fDist * VectorNormalize( GetPosition( OBJECT_SELF ) - GetPosition( oDest ) );
|
|
lDest = Location( GetArea( oDest ), vT, VectorToAngle( GetPosition( oDest ) - vT ) );
|
|
SetLocalInt( OBJECT_SELF, "DRAGONFLIGHTDEL", 1 );
|
|
DelayCommand( fD + 0.5, DeleteLocalInt( OBJECT_SELF, "DRAGONFLIGHTDEL" ) );
|
|
ApplyEffectToObject( DURATION_TYPE_TEMPORARY, EffectDisappearAppear( lDest ), OBJECT_SELF, fD );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ActionMoveToObject( oDest, bRun, fDist );
|
|
}
|
|
}
|
|
}
|
|
|
|
void DoTeleport( location lLoc )
|
|
{
|
|
effect eVis = EffectVisualEffect( VFX_IMP_GLOBE_USE );
|
|
|
|
ActionCastFakeSpellAtObject( SPELLABILITY_SUMMON_CELESTIAL, OBJECT_SELF );
|
|
ActionDoCommand( ApplyEffectAtLocation( DURATION_TYPE_INSTANT, eVis, GetLocation( OBJECT_SELF ) ) );
|
|
ActionDoCommand( ApplyEffectAtLocation( DURATION_TYPE_INSTANT, eVis, lLoc ) );
|
|
ActionDoCommand( ActionJumpToLocation( lLoc ) );
|
|
}
|
|
|
|
object GetTarget()
|
|
{
|
|
object oS = GetNearestCreature( CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, OBJECT_SELF, 1, CREATURE_TYPE_PERCEPTION, PERCEPTION_SEEN, CREATURE_TYPE_IS_ALIVE, TRUE );
|
|
object oH = GetNearestCreature( CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, OBJECT_SELF, 1, CREATURE_TYPE_PERCEPTION, PERCEPTION_HEARD, CREATURE_TYPE_IS_ALIVE, TRUE );
|
|
object oT = OBJECT_INVALID;
|
|
|
|
if ( !GetIsPerceived( oS, NO_PERCEPTION_SEEN ) )
|
|
{
|
|
oS = OBJECT_INVALID;
|
|
}
|
|
if ( !GetIsPerceived( oH, NO_PERCEPTION_HEARD ) )
|
|
{
|
|
oH = OBJECT_INVALID;
|
|
}
|
|
|
|
if ( GetIsObjectValid( oS ) && GetIsObjectValid( oH ) )
|
|
{
|
|
//target the closer of the two
|
|
oT = GetDistanceBetween( OBJECT_SELF, oH ) < GetDistanceBetween( OBJECT_SELF, oS ) ? oH : oS;
|
|
}
|
|
else if ( GetIsObjectValid( oS ) )
|
|
{
|
|
//oH is invalid
|
|
oT = oS;
|
|
}
|
|
else
|
|
{
|
|
//oS is invalid
|
|
oT = oH;
|
|
}
|
|
return oT;
|
|
}
|
|
|
|
float DotProduct( vector v1, vector v2 )
|
|
{
|
|
float fDP;
|
|
|
|
fDP = v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
|
|
|
|
return fDP;
|
|
}
|
|
|
|
location GetFlankLoc( object oT, location lL )
|
|
{
|
|
vector vU;
|
|
vector vT;
|
|
vector vM;
|
|
location lF;
|
|
|
|
if ( GetIsObjectValid( oT ) )
|
|
{
|
|
vT = GetPosition( oT ) - GetPosition( OBJECT_SELF );
|
|
}
|
|
else
|
|
{
|
|
vT = GetPositionFromLocation( lL ) - GetPosition( OBJECT_SELF );
|
|
}
|
|
vM = VectorNormalize( AngleToVector( VectorToAngle( vT ) - 45.0 + 90.0 * IntToFloat( Random( 2 ) ) ) );
|
|
vM = DotProduct( vT, vM ) * VectorNormalize( vM ) + GetPosition( OBJECT_SELF );
|
|
lF = Location( GetArea( oT ), vM, VectorToAngle( vT - vM ) );
|
|
|
|
return lF;
|
|
}
|
|
|
|
void DoEquipMelee( object oT )
|
|
{
|
|
object oL = GetLocalObject( OBJECT_SELF, "LHAND" );
|
|
object oR = GetLocalObject( OBJECT_SELF, "RHAND" );
|
|
|
|
if ( GetIsObjectValid( oR ) || GetIsObjectValid( oL ) )
|
|
{
|
|
if ( GetIsObjectValid( oR ) )
|
|
{
|
|
if ( !GetIsObjectValid( GetItemPossessor( oR ) ) )
|
|
{
|
|
//weapon is on the ground after being disarmed?
|
|
//try to pick it up
|
|
ActionPickUpItem( oR );
|
|
ActionEquipItem( oR, INVENTORY_SLOT_RIGHTHAND );
|
|
}
|
|
else if ( GetItemPossessor( oR ) == OBJECT_SELF && GetItemInSlot( INVENTORY_SLOT_RIGHTHAND ) != oR )
|
|
{
|
|
ActionEquipItem( oR, INVENTORY_SLOT_RIGHTHAND );
|
|
}
|
|
}
|
|
if ( GetIsObjectValid( oL ) )
|
|
{
|
|
if ( !GetIsObjectValid( GetItemPossessor( oL ) ) )
|
|
{
|
|
//weapon is on the ground after being disarmed?
|
|
//try to pick it up
|
|
ActionPickUpItem( oL );
|
|
ActionEquipItem( oL, INVENTORY_SLOT_LEFTHAND );
|
|
}
|
|
else if ( GetItemPossessor( oL ) == OBJECT_SELF && GetItemInSlot( INVENTORY_SLOT_LEFTHAND ) != oL )
|
|
{
|
|
ActionEquipItem( oL, INVENTORY_SLOT_LEFTHAND );
|
|
}
|
|
}
|
|
}
|
|
else if ( GetIsObjectValid( oT ) )
|
|
{
|
|
//nothing stored
|
|
ActionEquipMostDamagingMelee( oT );
|
|
}
|
|
else
|
|
{
|
|
ActionEquipMostDamagingMelee();
|
|
}
|
|
}
|
|
|
|
talent GetTalentSpell( int iCat, int iD )
|
|
{
|
|
int iCR = 21;
|
|
talent tT;
|
|
talent iT; //reserve for invalid talent return
|
|
|
|
while ( --iCR )
|
|
{
|
|
tT = GetCreatureTalentBest( iCat, iCR );
|
|
if ( ( GetIsTalentValid( tT ) && GetIdFromTalent( tT ) == iD ) || !GetIsTalentValid( tT ) )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if ( GetIsTalentValid( tT ) && GetIdFromTalent( tT ) == iD )
|
|
{
|
|
return tT;
|
|
}
|
|
return iT;
|
|
}
|
|
|
|
|
|
void DoFightBroadcast()
|
|
{
|
|
//broadcast fight "noise" at most once a second
|
|
//respect silence spell
|
|
int iDelay = 2;
|
|
int iTime = GetTimeSecond();
|
|
int iLast = GetLocalInt( OBJECT_SELF, "#LASTCREBC" );
|
|
int iS = iTime < iLast ? iTime + 60 : iTime;
|
|
int iVol = NO_CAN_USE_DM_CHANNEL ? TALKVOLUME_SILENT_SHOUT : TALKVOLUME_SILENT_TALK;
|
|
|
|
//anti-spamloop delay
|
|
if ( iS - iLast >= iDelay )
|
|
{
|
|
//only broadcast noise if silence is not in effect
|
|
if ( !( GetEffectsOnObject() & NO_EFFECT_SILENCE ) )
|
|
{
|
|
SpeakString( "BC_FIGHTING", iVol );
|
|
}
|
|
SetLocalInt( OBJECT_SELF, "#LASTCREBC", iTime );
|
|
DeleteLocalInt( OBJECT_SELF, "#LASTREST" );
|
|
}
|
|
}
|
|
|
|
int GetTimeSinceLastCombat()
|
|
{
|
|
int iTime = GetTimeSecond();
|
|
int iLast = GetLocalInt( OBJECT_SELF, "#LASTCREBC" );
|
|
int iS = iTime < iLast ? iTime + 60 : iTime;
|
|
int iT = iS - iLast;
|
|
|
|
return iT;
|
|
}
|
|
|
|
void DoConditionalRestart( float fT=6.0 )
|
|
{
|
|
DeleteLocalInt( OBJECT_SELF, "#RESTARTQUEUED" );
|
|
if ( GetLocalInt( OBJECT_SELF, "#ACTIVE" ) && GetTimeSinceLastCombat() > FloatToInt( fT ) )
|
|
{
|
|
ClearAllActions();
|
|
DeleteLocalInt( OBJECT_SELF, "#COMBATQUEUED" );
|
|
//SetReadyStatus();
|
|
DelayCommand( 0.1, DoQueueCombat( 8.0, 8.0 ) );
|
|
}
|
|
}
|
|
|
|
void DoQueueShutdown( float fT=0.0 )
|
|
{
|
|
if ( !GetLocalInt( OBJECT_SELF, "#QUEUESHUTDOWN" ) )
|
|
{
|
|
SetLocalInt( OBJECT_SELF, "#QUEUESHUTDOWN", 1 );
|
|
DelayCommand( fT, ExecuteScript( "no_scr_shutdown", OBJECT_SELF ) );
|
|
}
|
|
}
|
|
|
|
void DoQueueCombat( float fD=6.0, float fT=6.0 )
|
|
{
|
|
if ( !GetLocalInt( OBJECT_SELF, "#ACTIVE" ) )
|
|
{
|
|
SetLocalInt( OBJECT_SELF, "#ACTIVE", 1 );
|
|
DelayCommand( 0.1, ClearAllActions() );
|
|
}
|
|
if ( !GetLocalInt( OBJECT_SELF, "#COMBATQUEUED" ) )
|
|
{
|
|
SetLocalInt( OBJECT_SELF, "#COMBATQUEUED", 1 );
|
|
DelayCommand( 0.2, ActionDoCommand( DoCombat() ) );
|
|
}
|
|
if ( !GetLocalInt( OBJECT_SELF, "#RESTARTQUEUED" ) )
|
|
{
|
|
SetLocalInt( OBJECT_SELF, "#RESTARTQUEUED", 1 );
|
|
DelayCommand( fD, DoConditionalRestart( fT ) );
|
|
}
|
|
}
|
|
|
|
void InitCombat( object oE=OBJECT_INVALID )
|
|
{
|
|
int iT = GetLastActionTimestamp();
|
|
|
|
if ( IsActive() == FALSE || GetCurrentAction() == ACTION_INVALID || iT > 8 )
|
|
{
|
|
ClearAllActions();
|
|
SetIsActive( TRUE );
|
|
DoCombat();
|
|
}
|
|
}
|
|
|
|
void DoVoiceChat( int iV=-1 )
|
|
{
|
|
int iC = GetLocalInt( OBJECT_SELF, "#VCC_" + IntToString( iV ) );
|
|
|
|
if ( iC == -1 || !CanAct() )
|
|
{
|
|
//do not do this voice chat
|
|
return;
|
|
}
|
|
if ( iC == 0 )
|
|
{
|
|
//no specific setting for this voice chat, use default chance
|
|
iC = GetLocalInt( OBJECT_SELF, "#VCC" );
|
|
}
|
|
if ( Random( 100 ) < iC )
|
|
{
|
|
//don't bother talking to ourselves, look for a nearby friend
|
|
object oF = GetNearestCreature( CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_FRIEND, OBJECT_SELF, 1, CREATURE_TYPE_IS_ALIVE, TRUE );
|
|
if ( GetIsObjectValid( oF ) && GetDistanceBetween( OBJECT_SELF, oF ) < 50.0 )
|
|
{
|
|
//there is a friendly nearby, do the voice chat
|
|
int iD = iV;
|
|
if ( iV == NO_VC_MELEE || iV == NO_VC_RANGED || iV == NO_VC_MELEEASSIST )
|
|
{
|
|
//custom voice chat handling
|
|
iD = Random( 5 );
|
|
switch( iD )
|
|
{
|
|
case 1: iD = VOICE_CHAT_ATTACK;
|
|
case 2: iD = VOICE_CHAT_BATTLECRY1;
|
|
case 3: iD = VOICE_CHAT_BATTLECRY2;
|
|
case 4: iD = VOICE_CHAT_BATTLECRY3;
|
|
default: iD = VOICE_CHAT_TAUNT;
|
|
}
|
|
}
|
|
PlayVoiceChat( iD );
|
|
}
|
|
}
|
|
}
|
|
|
|
void DoMainCombatLoop()
|
|
{
|
|
int iCnt = 1;
|
|
string sPri = GetPriority( iCnt );
|
|
int iPri = GetPriorityChance( iCnt++ );
|
|
|
|
while ( sPri != "" )
|
|
{
|
|
if ( sPri == "+ATKMELEE" && Random( 100 ) < iPri && DoAttackMelee() ) //atkmelee
|
|
{
|
|
//PrintString( "+ATKMELEE: " + GetName( OBJECT_SELF ) );
|
|
//return;
|
|
SetLastAction( sPri );
|
|
//SetLastActionTimestamp();
|
|
break;
|
|
}
|
|
else if ( sPri == "+ATKRANGED" && Random( 100 ) < iPri && DoAttackRanged() ) //atkranged
|
|
{
|
|
//PrintString( "+ATKRANGED: " + GetName( OBJECT_SELF ) );
|
|
//return;
|
|
SetLastAction( sPri );
|
|
//SetLastActionTimestamp();
|
|
break;
|
|
}
|
|
else if ( sPri == "+COUNTERSPELL" && Random( 100 ) < iPri && DoCounterSpell() ) //counterspell
|
|
{
|
|
//PrintString( "+COUNTERSPELL: " + GetName( OBJECT_SELF ) );
|
|
//return;
|
|
SetLastAction( sPri );
|
|
//SetLastActionTimestamp();
|
|
break;
|
|
}
|
|
else if ( sPri == "+EVACAOE" && Random( 100 ) < iPri && DoEvacAOE() ) //evac AOEs
|
|
{
|
|
//PrintString( "+EVACAOE: " + GetName( OBJECT_SELF ) );
|
|
//return;
|
|
SetLastAction( sPri );
|
|
//SetLastActionTimestamp();
|
|
break;
|
|
}
|
|
else if ( sPri == "+REGROUP" && Random( 100 ) < iPri && DoRegroup() ) //regroup with allies
|
|
{
|
|
//PrintString( "+REGROUP: " + GetName( OBJECT_SELF ) );
|
|
//return;
|
|
SetLastAction( sPri );
|
|
//SetLastActionTimestamp();
|
|
break;
|
|
}
|
|
else if ( sPri == "+DEFSELF" && Random( 100 ) < iPri && DoDefendSelf() ) //apply defenses
|
|
{
|
|
//PrintString( "+DEFSELF: " + GetName( OBJECT_SELF ) );
|
|
//return;
|
|
SetLastAction( sPri );
|
|
//SetLastActionTimestamp();
|
|
break;
|
|
}
|
|
else if ( sPri == "+DEFSING" && Random( 100 ) < iPri && DoDefendSingle() ) //apply defenses to single
|
|
{
|
|
//PrintString( "+DEFSING: " + GetName( OBJECT_SELF ) );
|
|
//return;
|
|
SetLastAction( sPri );
|
|
//SetLastActionTimestamp();
|
|
break;
|
|
}
|
|
else if ( sPri == "+ENHANCESELF" && Random( 100 ) < iPri && DoEnhanceSelf() ) //enhance self
|
|
{
|
|
//PrintString( "+ENHANCESELF: " + GetName( OBJECT_SELF ) );
|
|
//return;
|
|
SetLastAction( sPri );
|
|
//SetLastActionTimestamp();
|
|
break;
|
|
}
|
|
else if ( sPri == "+ENHANCESING" && Random( 100 ) < iPri && DoEnhanceSingle() ) //enhance single
|
|
{
|
|
//PrintString( "+ENHANCESING: " + GetName( OBJECT_SELF ) );
|
|
//return;
|
|
SetLastAction( sPri );
|
|
//SetLastActionTimestamp();
|
|
break;
|
|
}
|
|
else if ( sPri == "+HELP" && Random( 100 ) < iPri && DoSpellHelp() ) //remove afflictions
|
|
{
|
|
//PrintString( "+HELP: " + GetName( OBJECT_SELF ) );
|
|
//return;
|
|
SetLastAction( sPri );
|
|
//SetLastActionTimestamp();
|
|
break;
|
|
}
|
|
else if ( sPri == "+HEAL" && Random( 100 ) < iPri && DoSpellHeal() ) //heal
|
|
{
|
|
//PrintString( "+HEAL: " + GetName( OBJECT_SELF ) );
|
|
//return;
|
|
SetLastAction( sPri );
|
|
//SetLastActionTimestamp();
|
|
break;
|
|
}
|
|
else if ( sPri == "+RAISE" && Random( 100 ) < iPri && DoSpellRaise() ) //raise
|
|
{
|
|
//PrintString( "+RAISE: " + GetName( OBJECT_SELF ) );
|
|
//return;
|
|
SetLastAction( sPri );
|
|
//SetLastActionTimestamp();
|
|
break;
|
|
}
|
|
else if ( sPri == "+BREACH" && Random( 100 ) < iPri && DoSpellBreach() ) //breach
|
|
{
|
|
//PrintString( "+BREACH: " + GetName( OBJECT_SELF ) );
|
|
//return;
|
|
SetLastAction( sPri );
|
|
//SetLastActionTimestamp();
|
|
break;
|
|
}
|
|
else if ( sPri == "+DIRECT" && Random( 100 ) < iPri && DoSpellDirect() ) //direct attack spell
|
|
{
|
|
//PrintString( "+DIRECT: " + GetName( OBJECT_SELF ) );
|
|
//return;
|
|
SetLastAction( sPri );
|
|
//SetLastActionTimestamp();
|
|
break;
|
|
}
|
|
else if ( sPri == "+TOUCH" && Random( 100 ) < iPri && DoTouch() ) //touch attack spell
|
|
{
|
|
//PrintString( "+TOUCH: " + GetName( OBJECT_SELF ) );
|
|
//return;
|
|
SetLastAction( sPri );
|
|
//SetLastActionTimestamp();
|
|
break;
|
|
}
|
|
else if ( sPri == "+AREA" && Random( 100 ) < iPri && DoSpellArea() ) //area attack spell
|
|
{
|
|
//PrintString( "+AREA: " + GetName( OBJECT_SELF ) );
|
|
//return;
|
|
SetLastAction( sPri );
|
|
//SetLastActionTimestamp();
|
|
break;
|
|
}
|
|
else if ( sPri == "+SUMMON" && Random( 100 ) < iPri && DoSpellSummon() ) //summon help
|
|
{
|
|
//PrintString( "+SUMMON: " + GetName( OBJECT_SELF ) );
|
|
//return;
|
|
SetLastAction( sPri );
|
|
//SetLastActionTimestamp();
|
|
break;
|
|
}
|
|
else if ( sPri == "+FEATENHANCE" && Random( 100 ) < iPri && DoFeatEnhance() ) //enhance via feat
|
|
{
|
|
//PrintString( "+FEATENHANCE: " + GetName( OBJECT_SELF ) );
|
|
//return;
|
|
SetLastAction( sPri );
|
|
//SetLastActionTimestamp();
|
|
break;
|
|
}
|
|
else if ( sPri == "+AVOIDMELEE" && Random( 100 ) < iPri && DoAvoidMelee() ) //avoid melee
|
|
{
|
|
//PrintString( "+AVOIDMELEE: " + GetName( OBJECT_SELF ) );
|
|
//return;
|
|
SetLastAction( sPri );
|
|
//SetLastActionTimestamp();
|
|
break;
|
|
}
|
|
else if ( sPri == "+TIMESTOP" && Random( 100 ) < iPri && DoTimeStop() ) //time stop
|
|
{
|
|
//PrintString( "+TIMESTOP: " + GetName( OBJECT_SELF ) );
|
|
//return;
|
|
SetLastAction( sPri );
|
|
//SetLastActionTimestamp();
|
|
break;
|
|
}
|
|
else if ( sPri == "+VIS" && Random( 100 ) < iPri && DoVision() ) //vision
|
|
{
|
|
//PrintString( "+VIS: " + GetName( OBJECT_SELF ) );
|
|
//return;
|
|
SetLastAction( sPri );
|
|
//SetLastActionTimestamp();
|
|
break;
|
|
}
|
|
else if ( sPri == "+BREATH" && Random( 100 ) < iPri && DoBreathWeapon() ) //breathweapon
|
|
{
|
|
//PrintString( "+BREATH: " + GetName( OBJECT_SELF ) );
|
|
//return;
|
|
SetLastAction( sPri );
|
|
//SetLastActionTimestamp();
|
|
break;
|
|
}
|
|
else if ( sPri == "+TURN" && Random( 100 ) < iPri && DoTurning() ) //turn undead
|
|
{
|
|
//PrintString( "+TURN: " + GetName( OBJECT_SELF ) );
|
|
//return;
|
|
SetLastAction( sPri );
|
|
//SetLastActionTimestamp();
|
|
break;
|
|
}
|
|
else if ( sPri == "+HEALSELF" && Random( 100 ) < iPri && DoHealSelf() ) //heal self
|
|
{
|
|
//PrintString( "+HEALSELF: " + GetName( OBJECT_SELF ) );
|
|
//return;
|
|
SetLastAction( sPri );
|
|
//SetLastActionTimestamp();
|
|
break;
|
|
}
|
|
else if ( sPri == "+GROUPENHANCE" && Random( 100 ) < iPri && DoSpellGroupEnhance() ) //group enhance
|
|
{
|
|
//PrintString( "+GROUPENHANCE: " + GetName( OBJECT_SELF ) );
|
|
//return;
|
|
SetLastAction( sPri );
|
|
//SetLastActionTimestamp();
|
|
break;
|
|
}
|
|
else if ( sPri == "+DISPELAOE" && Random( 100 ) < iPri && DoDispelPersAOE() ) //dispel hostile AOEs
|
|
{
|
|
//PrintString( "+DISPELAOE: " + GetName( OBJECT_SELF ) );
|
|
//return;
|
|
SetLastAction( sPri );
|
|
//SetLastActionTimestamp();
|
|
break;
|
|
}
|
|
else if ( sPri == "+DISPEL" && Random( 100 ) < iPri && DoDispelSingle() ) //dispel buffs on single enemy
|
|
{
|
|
//PrintString( "+DISPEL: " + GetName( OBJECT_SELF ) );
|
|
//return;
|
|
SetLastAction( sPri );
|
|
//SetLastActionTimestamp();
|
|
break;
|
|
}
|
|
else if ( sPri == "+DISMISSAL" && Random( 100 ) < iPri && DoDismissal() ) //dismiss enemy summons
|
|
{
|
|
//PrintString( "+DISMISSAL: " + GetName( OBJECT_SELF ) );
|
|
//return;
|
|
SetLastAction( sPri );
|
|
//SetLastActionTimestamp();
|
|
break;
|
|
}
|
|
else if ( sPri == "+MELEEASSIST" && Random( 100 ) < iPri && DoMeleeAssist() ) //assist friends in melee
|
|
{
|
|
//PrintString( "+MELEEASSIST: " + GetName( OBJECT_SELF ) );
|
|
//return;
|
|
SetLastAction( sPri );
|
|
//SetLastActionTimestamp();
|
|
break;
|
|
}
|
|
else if ( sPri == "+FLANK" && Random( 100 ) < iPri && DoFlank() ) //flank enemy
|
|
{
|
|
//PrintString( "+FLANK: " + GetName( OBJECT_SELF ) );
|
|
//return;
|
|
SetLastAction( sPri );
|
|
//SetLastActionTimestamp();
|
|
break;
|
|
}
|
|
else if ( sPri == "+GROUPHEAL" && Random( 100 ) < iPri && DoSpellGroupHeal() ) //use area heals
|
|
{
|
|
//PrintString( "+GROUPHEAL: " + GetName( OBJECT_SELF ) );
|
|
//return;
|
|
SetLastAction( sPri );
|
|
//SetLastActionTimestamp();
|
|
break;
|
|
}
|
|
else if ( sPri == "+MINDBLAST" && Random( 100 ) < iPri && DoMindBlast() ) //use mind blast psionics
|
|
{
|
|
//PrintString( "+MINDBLAST: " + GetName( OBJECT_SELF ) );
|
|
//return;
|
|
SetLastAction( sPri );
|
|
//SetLastActionTimestamp();
|
|
break;
|
|
}
|
|
else if ( sPri == "+BRAINEXTRACT" && Random( 100 ) < iPri && DoBrainExtraction() ) //Illithid brain extraction
|
|
{
|
|
//PrintString( "+BRAINEXTRACT: " + GetName( OBJECT_SELF ) );
|
|
//return;
|
|
SetLastAction( sPri );
|
|
//SetLastActionTimestamp();
|
|
break;
|
|
}
|
|
else if ( sPri == "+AVOIDENEMY" && Random( 100 ) < iPri && DoAvoidEnemies() ) //Move away from enemies
|
|
{
|
|
//PrintString( "+AVOIDENEMY: " + GetName( OBJECT_SELF ) );
|
|
//return;
|
|
SetLastAction( sPri );
|
|
//SetLastActionTimestamp();
|
|
break;
|
|
}
|
|
else if ( sPri == "+EYERAYS" && Random( 100 ) < iPri && DoBeholderRays() ) //Beholder eye rays
|
|
{
|
|
//PrintString( "+EYERAYS: " + GetName( OBJECT_SELF ) );
|
|
//return;
|
|
SetLastAction( sPri );
|
|
//SetLastActionTimestamp();
|
|
break;
|
|
}
|
|
else if ( sPri == "+CENTRALEYE" && Random( 100 ) < iPri && DoBeholderCentralEye() ) //Beholder's antimagic cone
|
|
{
|
|
//PrintString( "+CENTRALEYE: " + GetName( OBJECT_SELF ) );
|
|
//return;
|
|
SetLastAction( sPri );
|
|
//SetLastActionTimestamp();
|
|
break;
|
|
}
|
|
sPri = GetPriority( iCnt );
|
|
iPri = GetPriorityChance( iCnt++ );
|
|
}
|
|
}
|
|
|
|
//action functions
|
|
void DoCombat()
|
|
{
|
|
//int iTime, iLast, iDiff;
|
|
int iAct;
|
|
float fDelay = 0.0;
|
|
|
|
//delete queue marker
|
|
DeleteLocalInt( OBJECT_SELF, "#COMBATQUEUED" );
|
|
|
|
if ( GetIsDead( OBJECT_SELF ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
//int iE = GetHostileCount( 50.0 );
|
|
int iE = GetOmniscientHostileCount( 50.0 );
|
|
if ( iE == 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
//make combat noise for nearby listeners
|
|
DoFightBroadcast();
|
|
|
|
//don't bother running scripts if we can't do anything
|
|
if ( !CanAct() )
|
|
{
|
|
ClearAllActions();
|
|
//queue a delay before checking again
|
|
fDelay += 0.1;
|
|
DelayCommand( fDelay, ActionWait( 1.0 ) );
|
|
//return;
|
|
}
|
|
else
|
|
{
|
|
//can take action
|
|
//If creature is busy do not proceed
|
|
iAct = GetCurrentAction();
|
|
if ( iAct == ACTION_CASTSPELL || iAct == ACTION_HEAL || iAct == ACTION_ITEMCASTSPELL || iAct == ACTION_PICKUPITEM || iAct == ACTION_WAIT ||
|
|
GetLocalInt( OBJECT_SELF, "#EYERAYS" ) == 1 )
|
|
{
|
|
//do nothing
|
|
}
|
|
else
|
|
{
|
|
//run the combat response table
|
|
ClearAllActions();
|
|
DoMainCombatLoop();
|
|
SetLastActionTimestamp();
|
|
}
|
|
}
|
|
DoHideInPlainSight(); //testing HIPS between actions
|
|
|
|
if ( !CanAct() || ShortAction() )
|
|
{
|
|
//DoQueueCombat( 8.0, 8.0 );
|
|
fDelay += 0.1;
|
|
DelayCommand( fDelay, ActionDoCommand( DoCombat() ) );
|
|
}
|
|
}
|
|
|
|
int DoAttackMelee( object oT=OBJECT_INVALID )
|
|
{
|
|
object oTarget;
|
|
int iSpecial;
|
|
int iMode;
|
|
int iPen = 0;
|
|
int iMelee = GetAttackerCount( 5.0 );
|
|
int iCnt = 1;
|
|
int iChat;
|
|
location lLoc;
|
|
vector vT, vU;
|
|
object test;
|
|
|
|
if ( GetIsObjectValid( oT ) )
|
|
{
|
|
oTarget = oT;
|
|
}
|
|
else if ( iMelee )
|
|
{
|
|
oTarget = GetNearestCreature( CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, OBJECT_SELF, iCnt++, CREATURE_TYPE_PERCEPTION, PERCEPTION_SEEN, CREATURE_TYPE_IS_ALIVE, TRUE );
|
|
while ( GetIsObjectValid( oTarget ) && GetDistanceBetween( OBJECT_SELF, oTarget ) <= 5.0 && GetAttackTarget( oTarget ) != OBJECT_SELF )
|
|
{
|
|
oTarget = GetNearestCreature( CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, OBJECT_SELF, iCnt++, CREATURE_TYPE_PERCEPTION, PERCEPTION_SEEN, CREATURE_TYPE_IS_ALIVE, TRUE );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
oTarget = GetTarget();
|
|
}
|
|
|
|
if ( GetIsObjectValid( oTarget ) && ( GetObjectSeen( oTarget ) || GetObjectHeard( oTarget ) ) )
|
|
{
|
|
//PrintString( GetName( OBJECT_SELF ) + " melee attack " + GetName( oTarget ) + ": " + FloatToString( GetDistanceBetween( OBJECT_SELF, oTarget ) ) );
|
|
DoEquipMelee( oTarget );
|
|
DoVoiceChat( NO_VC_MELEE );
|
|
//combat modes
|
|
if ( iMode = SelectMeleeCombatModes( oTarget ) )
|
|
{
|
|
iPen = GetCombatModeModifier( iMode );
|
|
DeactivateCombatModes( iMode );
|
|
ActivateCombatMode( iMode );
|
|
//ActionUseFeat( iMode, oTarget );
|
|
|
|
}
|
|
//special melee attacks
|
|
if ( iSpecial = GetBestMeleeSpecial( oTarget, iPen ) )
|
|
{
|
|
ActionUseFeat( iSpecial, oTarget );
|
|
}
|
|
else
|
|
{
|
|
ActionAttack( oTarget );
|
|
}
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
if ( !GetIsObjectValid( oTarget = GetLastDamager() ) )
|
|
{
|
|
if ( !GetIsObjectValid( oTarget = GetLastAttacker() ) )
|
|
{
|
|
oTarget = GetNearestCreature( CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, OBJECT_SELF, 1, CREATURE_TYPE_IS_ALIVE, TRUE );
|
|
}
|
|
}
|
|
if ( GetIsObjectValid( oTarget ) && GetDistanceBetween( OBJECT_SELF, oTarget ) <= 40.0 )
|
|
{
|
|
/*
|
|
vU = GetPosition( OBJECT_SELF );
|
|
vT = GetPosition( oTarget ) - vU;
|
|
vT = VectorMagnitude( vT ) * VectorNormalize( AngleToVector( VectorToAngle( vT ) - 45.0 + IntToFloat( Random( 90 ) ) ) );
|
|
lLoc = Location( GetArea( OBJECT_SELF ), vU + vT, VectorToAngle( vT ) );
|
|
*/
|
|
lLoc = GetLocation( oTarget );
|
|
DoMoveToLocation( lLoc, TRUE );
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
int DoAttackRanged( object oT=OBJECT_INVALID )
|
|
{
|
|
object oTarget = ( GetIsObjectValid( oT ) ) ? oT : GetTarget();
|
|
object oHurt;
|
|
//talent tSpecial;
|
|
int iSpecial;
|
|
int iMode;
|
|
int iPen = 0;
|
|
int iAtk = GetAttackerCount();
|
|
int iChat;
|
|
location lLoc;
|
|
|
|
//don't proceed if enemies are close
|
|
//if creature should avoid melee that should be added as a priority
|
|
//above this behaviour
|
|
if ( GetHostileCount( 5.0 ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
//raging barbarians should not use ranged attack
|
|
if ( GetHasFeatEffect( FEAT_BARBARIAN_RAGE, OBJECT_SELF ) || GetHasFeatEffect( FEAT_MIGHTY_RAGE, OBJECT_SELF ) )
|
|
{
|
|
//raging barbarians should skip ranged
|
|
return FALSE;
|
|
}
|
|
|
|
if ( !GetLocalInt( OBJECT_SELF, "#RANGEDCAPABLE" ) )
|
|
{
|
|
if ( !GetHasRangedCapability() )
|
|
{
|
|
return FALSE;
|
|
}
|
|
SetLocalInt( OBJECT_SELF, "#RANGEDCAPABLE", 1 );
|
|
}
|
|
|
|
if ( GetIsObjectValid( oTarget ) && !iAtk )
|
|
{
|
|
/* This should be done explicitly with the avoid melee behaviour
|
|
if ( GetHostileCount( 5.0 ) )
|
|
{
|
|
if ( DoAvoidMelee() )
|
|
{
|
|
//PrintString( GetName( OBJECT_SELF ) + " avoiding melee before ranged attack" );
|
|
return TRUE;
|
|
}
|
|
}
|
|
*/
|
|
|
|
//PrintString( GetName( OBJECT_SELF ) + " ranged attack " + GetName( oTarget ) + ": " + IntToString( iAtk ) + FloatToString( GetDistanceBetween( OBJECT_SELF, oTarget ) ) );
|
|
if ( !GetIsObjectValid( oT ) ) //not specifically told to attack a target
|
|
{
|
|
if ( DoAbilityCheck( ABILITY_INTELLIGENCE, 5 ) &&
|
|
GetIsObjectValid( oHurt = GetMostDamagedAlly( 20.0, oTarget, TRUE ) ) )
|
|
{
|
|
oTarget = oHurt;
|
|
}
|
|
}
|
|
|
|
ActionEquipMostDamagingRanged( oTarget );
|
|
DoVoiceChat( NO_VC_RANGED );
|
|
//combat modes
|
|
if ( iMode = SelectRangedCombatModes( oTarget ) )
|
|
{
|
|
iPen = GetCombatModeModifier( iMode );
|
|
DeactivateCombatModes( iMode );
|
|
ActivateCombatMode( iMode );
|
|
ActionUseFeat( iMode, oTarget );
|
|
}
|
|
//special ranged attacks
|
|
if ( iSpecial = GetBestRangedSpecial( oTarget, iPen ) )
|
|
{
|
|
ActionUseFeat( iSpecial, oTarget );
|
|
}
|
|
else
|
|
{
|
|
ActionAttack( oTarget );
|
|
}
|
|
return TRUE;
|
|
}
|
|
//PrintString( GetName( OBJECT_SELF ) + " aborting ranged attack: " + IntToString( iAtk ) );
|
|
return FALSE;
|
|
}
|
|
|
|
int DoCounterSpell( object oT=OBJECT_INVALID )
|
|
{
|
|
object oC = ( GetIsObjectValid( oT ) ) ? oT : GetNearestEnemyCaster();
|
|
|
|
//do not counterspell if I have timestop running
|
|
if ( GetHasSpellEffect( SPELL_TIME_STOP ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
//do not counterspell if I have attackers
|
|
if ( GetAttackerCount( 40.0 ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
//dead/living status should be handled by GetNearestEnemyCaster()
|
|
if ( GetIsObjectValid( oC ) )
|
|
{
|
|
//PrintString( GetName( OBJECT_SELF ) + " counterspelling " + GetName( oC ) );
|
|
ActionCounterSpell( oC );
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
int DoEvacAOE()
|
|
{
|
|
vector vT;
|
|
location lLoc;
|
|
|
|
//don't bother if we have time stop
|
|
if ( GetHasSpellEffect( SPELL_TIME_STOP ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if ( GetAOECount( 7.5 ) )
|
|
{
|
|
vT = GetAOEEvacVector( GetAOEVector( 15.0 ) );
|
|
lLoc = Location( GetArea( OBJECT_SELF ), vT, GetFacing( OBJECT_SELF ) );
|
|
SetLocalLocation( OBJECT_SELF, "LASTLOC", GetLocation( OBJECT_SELF ) );
|
|
DoMoveToLocation( lLoc, TRUE );
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
int DoRegroup( object oT=OBJECT_INVALID )
|
|
{
|
|
int iCnt = 0;
|
|
int iE = 0;
|
|
object oFriend;
|
|
object oDistant = OBJECT_INVALID;
|
|
vector vU, vT, vM;
|
|
location lLoc;
|
|
|
|
//check for order to regroup to a particular object
|
|
if ( GetIsObjectValid( oT ) )
|
|
{
|
|
DoMoveToObject( oT, TRUE, 5.0 );
|
|
return TRUE;
|
|
}
|
|
|
|
//don't bother if we have time stop
|
|
if ( GetHasSpellEffect( SPELL_TIME_STOP ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
vU = GetPosition( OBJECT_SELF );
|
|
vT = Vector( 0.0, 0.0, 0.0 );
|
|
oFriend = GetNearestCreature( CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_FRIEND, OBJECT_SELF, ++iCnt, CREATURE_TYPE_PERCEPTION, PERCEPTION_SEEN, CREATURE_TYPE_IS_ALIVE, TRUE );
|
|
while ( GetIsObjectValid( oFriend ) )
|
|
{
|
|
vT = vT + GetPosition( oFriend ) - vU;
|
|
oFriend = GetNearestCreature( CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_FRIEND, OBJECT_SELF, ++iCnt, CREATURE_TYPE_PERCEPTION, PERCEPTION_SEEN, CREATURE_TYPE_IS_ALIVE, TRUE );
|
|
}
|
|
|
|
if ( VectorMagnitude( vT ) > 0.0 )
|
|
{
|
|
vT = vT / IntToFloat( iCnt - 1 );
|
|
}
|
|
|
|
if ( VectorMagnitude( vT ) == 0.0 )
|
|
{
|
|
oFriend = GetNearestCreature( CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_FRIEND, OBJECT_SELF, 1, CREATURE_TYPE_IS_ALIVE, TRUE );
|
|
if ( GetIsObjectValid( oFriend ) && GetDistanceBetween( OBJECT_SELF, oFriend ) < 60.0 /*&& GetDistanceBetween( OBJECT_SELF, oFriend ) > 30.0*/ )
|
|
{
|
|
//we do have friends nearby but can't see them
|
|
DoMoveToLocation( GetLocation( oFriend ), TRUE );
|
|
return TRUE;
|
|
}
|
|
}
|
|
if ( VectorMagnitude( vT ) > 25.0 )
|
|
{
|
|
if ( DoAbilityCheck( ABILITY_INTELLIGENCE, 5 ) )
|
|
{
|
|
//need to regroup, get movement vector
|
|
vT = vT - 10.0 * VectorNormalize( vT );
|
|
vM = vU + vT;
|
|
lLoc = Location( GetArea( OBJECT_SELF ), vM, VectorToAngle( vT ) );
|
|
DoMoveToLocation( lLoc, TRUE );
|
|
//PrintString( GetName( OBJECT_SELF ) + " REGROUPING: (" + FloatToString( vM.x ) + "," + FloatToString( vM.y ) + ")" );
|
|
DoVoiceChat( VOICE_CHAT_GROUP );
|
|
return TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//look for outliers
|
|
iCnt = 0;
|
|
oFriend = GetNearestCreature( CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_FRIEND, OBJECT_SELF, ++iCnt, CREATURE_TYPE_PERCEPTION, PERCEPTION_SEEN, CREATURE_TYPE_IS_ALIVE, TRUE );
|
|
while ( GetIsObjectValid( oFriend ) )
|
|
{
|
|
oDistant = oFriend;
|
|
oFriend = GetNearestCreature( CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_FRIEND, OBJECT_SELF, ++iCnt, CREATURE_TYPE_PERCEPTION, PERCEPTION_SEEN, CREATURE_TYPE_IS_ALIVE, TRUE );
|
|
}
|
|
if ( GetIsObjectValid( oDistant ) )
|
|
{
|
|
if ( GetDistanceBetween( OBJECT_SELF, oDistant ) > 30.0 )
|
|
{
|
|
DoMoveToObject( oDistant, TRUE, 10.0 );
|
|
//PrintString( GetName( OBJECT_SELF ) + " finding OUTLIER " + GetName( oDistant ) );
|
|
DoVoiceChat( VOICE_CHAT_GROUP );
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
int DoDefendSelf()
|
|
{
|
|
int iMagic = GetCasterCount( 40.0 );
|
|
int iPhys = GetAttackerCount( 40.0 );
|
|
int iPotAtk = GetHostileCount( 40.0 );
|
|
int iSpell = 0;
|
|
talent tSpell;
|
|
struct sSpellDefStatus strMDef = EvaluateSpellDefenses();
|
|
struct sPhysDefStatus strPDef = EvaluatePhysicalDefenses();
|
|
|
|
if ( ( iPhys || iPotAtk ) && strPDef.iTotal < 4 )
|
|
{
|
|
/*
|
|
if ( iSpell = GetBestPhysDefense() ) //again, no acid fog
|
|
{
|
|
tSpell = TalentSpell( iSpell );
|
|
}
|
|
*/
|
|
if ( iSpell = GetBestPhysDefenseSelf() )
|
|
//if ( GetIsTalentValid( tSpell ) )
|
|
{
|
|
//PrintString( "PD: " + GetName( OBJECT_SELF ) + "(" + IntToString( iSpell ) + ")" );
|
|
//ActionCastSpellAtObject( iSpell, OBJECT_SELF );
|
|
NO_ActionCastSpellAtObject( iSpell, OBJECT_SELF );
|
|
//ActionUseTalentOnObject( tSpell, OBJECT_SELF );
|
|
return TRUE;
|
|
}
|
|
}
|
|
if ( iMagic && strMDef.iTotal < 4 )
|
|
{
|
|
/*
|
|
if ( iSpell = GetBestMagicDefense() ) //can assume acid fog (int val 0) won't be returned here
|
|
{
|
|
tSpell = TalentSpell( iSpell );
|
|
}
|
|
*/
|
|
if ( iSpell = GetBestMagicDefenseSelf() )
|
|
//if ( GetIsTalentValid( tSpell ) )
|
|
{
|
|
//PrintString( "MD: " + GetName( OBJECT_SELF ) + "(" + IntToString( iSpell ) + ")" );
|
|
//ActionCastSpellAtObject( iSpell, OBJECT_SELF );
|
|
NO_ActionCastSpellAtObject( iSpell, OBJECT_SELF );
|
|
//ActionUseTalentOnObject( tSpell, OBJECT_SELF );
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
if ( iSpell = GetBestGenericProtection() )
|
|
{
|
|
tSpell = TalentSpell( iSpell );
|
|
}
|
|
if ( GetIsTalentValid( tSpell ) )
|
|
{
|
|
//ActionCastSpellAtObject( iSpell, OBJECT_SELF );
|
|
ActionUseTalentOnObject( tSpell, OBJECT_SELF );
|
|
return TRUE;
|
|
}
|
|
*/
|
|
return FALSE;
|
|
}
|
|
|
|
int DoDefendSingle( object oT=OBJECT_INVALID )
|
|
{
|
|
object oD;
|
|
int iSpell;
|
|
struct sSpellDefStatus strM;
|
|
struct sPhysDefStatus strP;
|
|
|
|
oD = ( GetIsObjectValid( oT ) ) ? oT : GetLeastDefendedAlly( 20.0 );
|
|
if ( GetIsObjectValid( oD ) )
|
|
{
|
|
strM = EvaluateSpellDefenses( oD );
|
|
strP = EvaluatePhysicalDefenses( oD );
|
|
if ( strM.iTotal < strP.iTotal )
|
|
{
|
|
if ( iSpell = GetBestMagicDefenseSingle( oD ) )
|
|
{
|
|
//PrintString( "DDSM: " + GetName( OBJECT_SELF ) + " -> " + GetName( oD ) + " (" + IntToString( iSpell ) + ")" );
|
|
//ActionCastSpellAtObject( iSpell, oD );
|
|
NO_ActionCastSpellAtObject( iSpell, oD );
|
|
return TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//strP.iTotal <= strM.iTotal
|
|
if ( iSpell = GetBestPhysDefenseSingle( oD ) )
|
|
{
|
|
//PrintString( "DDSP: " + GetName( OBJECT_SELF ) + " -> " + GetName( oD ) + " (" + IntToString( iSpell ) + ")" );
|
|
//ActionCastSpellAtObject( iSpell, oD );
|
|
NO_ActionCastSpellAtObject( iSpell, oD );
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
int DoEnhanceSelf()
|
|
{
|
|
int iMinLvl = GetHitDice( OBJECT_SELF );
|
|
iMinLvl = iMinLvl > 20 ? 20 : iMinLvl;
|
|
iMinLvl = iMinLvl / 3;
|
|
int iSpell = GetEnhanceSpellSelf( iMinLvl );
|
|
vector vT, vU;
|
|
location lM;
|
|
//talent tSpell;
|
|
|
|
if ( iSpell )
|
|
{
|
|
/*
|
|
tSpell = TalentSpell( iSpell );
|
|
if ( GetIsTalentValid( tSpell ) )
|
|
{
|
|
ActionUseTalentOnObject( tTest, OBJECT_SELF );
|
|
}
|
|
*/
|
|
//ActionCastSpellAtObject( iSpell, OBJECT_SELF );
|
|
NO_ActionCastSpellAtObject( iSpell, OBJECT_SELF );
|
|
if ( iSpell == SPELL_INVISIBILITY || iSpell == SPELL_IMPROVED_INVISIBILITY )
|
|
{
|
|
//move a bit after casting invisibility on self
|
|
if ( VectorMagnitude( vT = GetHostileVector( 10.0 ) ) > 0.0 )
|
|
{
|
|
vU = GetPosition( OBJECT_SELF );
|
|
vT = 5.0 * AngleToVector( VectorToAngle( vT ) + 90.0 + IntToFloat( Random( 180 ) ) );
|
|
lM = Location( GetArea( OBJECT_SELF ), vU + vT, VectorToAngle( vT ) );
|
|
DoMoveToLocation( lM, TRUE );
|
|
vT = 5.0 * AngleToVector( VectorToAngle( vT ) - 90.0 + IntToFloat( Random( 180 ) ) );
|
|
lM = Location( GetArea( OBJECT_SELF ), vU + vT, VectorToAngle( vT ) );
|
|
DoMoveToLocation( lM, TRUE );
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
int DoEnhanceSingle( object oT=OBJECT_INVALID )
|
|
{
|
|
int iMinLvl = GetHitDice( OBJECT_SELF );
|
|
iMinLvl = iMinLvl > 20 ? 20 : iMinLvl;
|
|
iMinLvl = iMinLvl / 3;
|
|
int iSpell = 0;
|
|
vector vT, vU;
|
|
location lM;
|
|
object oE = ( GetIsObjectValid( oT ) ) ? oT : GetLeastBuffedAlly( 20.0, TRUE );
|
|
|
|
if ( GetIsObjectValid( oE ) )
|
|
{
|
|
if ( iSpell = GetEnhanceSpellSingle( iMinLvl, oE ) )
|
|
{
|
|
//PrintString( "DES: " + GetName( OBJECT_SELF ) + " -> " + GetName( oE ) + " (" + IntToString( iSpell ) + ")" );
|
|
//ActionCastSpellAtObject( iSpell, oE );
|
|
NO_ActionCastSpellAtObject( iSpell, oE );
|
|
return TRUE;
|
|
}
|
|
if ( oE == OBJECT_SELF )
|
|
{
|
|
if ( iSpell == SPELL_INVISIBILITY || iSpell == SPELL_IMPROVED_INVISIBILITY )
|
|
{
|
|
//move a bit after casting invisibility on self
|
|
if ( VectorMagnitude( vT = GetHostileVector( 10.0 ) ) > 0.0 )
|
|
{
|
|
vU = GetPosition( OBJECT_SELF );
|
|
vT = 5.0 * AngleToVector( VectorToAngle( vT ) + 90.0 + IntToFloat( Random( 180 ) ) );
|
|
lM = Location( GetArea( OBJECT_SELF ), vU + vT, VectorToAngle( vT ) );
|
|
DoMoveToLocation( lM, TRUE );
|
|
vT = 5.0 * AngleToVector( VectorToAngle( vT ) - 90.0 + IntToFloat( Random( 180 ) ) );
|
|
lM = Location( GetArea( OBJECT_SELF ), vU + vT, VectorToAngle( vT ) );
|
|
DoMoveToLocation( lM, TRUE );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
DoSpellHelp:
|
|
|
|
Description: Find the ally with the most hampering effects on them and try to remove them.
|
|
|
|
Notes: Problem with this behaviour is that it always looks for most hampered ally. If it can't help that ally then
|
|
it will not help anyone. If there are less hampered allies that it could help it will never know because it goes
|
|
straight to the most hampered
|
|
|
|
Possible fix: First generate full list of possible help it can provide stored as bits and do bitwise AND against
|
|
effects on each ally. If there are no matches at all, do not consider them. Store the ally with the most matches
|
|
of available help vs hampering effects. Not yet tried.
|
|
====================
|
|
*/
|
|
int DoSpellHelp( object oT=OBJECT_INVALID )
|
|
{
|
|
object oHurt;
|
|
int iHelp;
|
|
|
|
if ( GetHasHelpingAbility() )
|
|
{
|
|
oHurt = ( GetIsObjectValid( oT ) ) ? oT : GetMostHamperedAlly( 30.0, OBJECT_SELF, TRUE );
|
|
if ( GetIsObjectValid( oHurt ) )
|
|
{
|
|
iHelp = GetBestHelp( oHurt );
|
|
if ( iHelp )
|
|
{
|
|
SetLocalObject( oHurt, "HELPER", OBJECT_SELF );
|
|
DelayCommand( 6.0, DeleteLocalObject( oHurt, "HELPER" ) );
|
|
//PrintString( "HELP: " + GetName( OBJECT_SELF ) + " -> " + GetName( oHurt ) + "(" + IntToString( iHelp ) + ")" );
|
|
//ActionCastSpellAtObject( iHelp, oHurt );
|
|
NO_ActionCastSpellAtObject( iHelp, oHurt );
|
|
return TRUE;
|
|
}
|
|
//PrintString( "HELP: fail " + GetName( OBJECT_SELF ) + " -> " + GetName( oHurt ) );
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
int DoSpellHeal( object oT=OBJECT_INVALID )
|
|
{
|
|
//original heal code
|
|
object oHurt;
|
|
int iHeal, iCR;
|
|
talent tHeal;
|
|
int iMin = 8;
|
|
|
|
if ( GetHasHealingAbility() )
|
|
//if ( GetIsTalentValid( GetCreatureTalentBest( TALENT_CATEGORY_BENEFICIAL_HEALING_TOUCH, 20 ) ) )
|
|
{
|
|
oHurt = ( GetIsObjectValid( oT ) ) ? oT : GetLowestHPAllyNoHealer( 30.0, iMin, OBJECT_SELF, TRUE );
|
|
if ( GetIsObjectValid( oHurt ) )
|
|
{
|
|
/*
|
|
// Need to use this hack with talents because GetHasSpell will pick up spontaneous casting ability
|
|
// but ActionCastSpell* cannot do the spontaneous casting, which leaves the creature daydreaming
|
|
// rather than acting
|
|
*/
|
|
iHeal = GetBestHeal( oHurt, iMin );
|
|
tHeal = GetTalentSpell( TALENT_CATEGORY_BENEFICIAL_HEALING_TOUCH, iHeal );
|
|
//if ( iHeal )
|
|
if ( GetIsTalentValid( tHeal ) )
|
|
{
|
|
//PrintString( "HEAL: " + GetName( OBJECT_SELF ) + " -> " + GetName( oHurt ) + " (" + IntToString( iHeal ) + ")" );
|
|
SetLocalObject( oHurt, "#HEALER", OBJECT_SELF );
|
|
DelayCommand( 6.0, DeleteLocalObject( oHurt, "#HEALER" ) );
|
|
//ActionCastSpellAtObject( iHeal, oHurt );
|
|
//ActionUseTalentOnObject( tHeal, oHurt );
|
|
NO_ActionUseTalentOnObject( tHeal, oHurt );
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
int DoSpellRaise( object oT=OBJECT_INVALID )
|
|
{
|
|
//original raise code
|
|
object oHurt;
|
|
int iRaise;
|
|
|
|
if ( GetHasRaisingAbility() )
|
|
{
|
|
oHurt = ( GetIsObjectValid( oT ) && GetIsDead( oT ) ) ? oT : GetNearestDeadAllyNoRaiser( 30.0, OBJECT_SELF, TRUE );
|
|
if ( GetIsObjectValid( oHurt ) )
|
|
{
|
|
iRaise = GetBestRaise( TRUE );
|
|
if ( iRaise )
|
|
{
|
|
SetLocalObject( oHurt, "#RAISER", OBJECT_SELF );
|
|
DelayCommand( 6.0, DeleteLocalObject( oHurt, "#RAISER" ) );
|
|
//ActionCastSpellAtObject( iRaise, oHurt );
|
|
NO_ActionCastSpellAtObject( iRaise, oHurt );
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
int DoSpellBreach( object oT=OBJECT_INVALID )
|
|
{
|
|
int iCnt = 1;
|
|
int iSpell = 0;
|
|
int iMaxLvl = 0;
|
|
int iMaxNum = 0;
|
|
struct sSpellDefStatus strMDef;
|
|
struct sPhysDefStatus strPDef;
|
|
object oSub = GetNearestCreature( CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, OBJECT_SELF, iCnt++, CREATURE_TYPE_PERCEPTION, PERCEPTION_SEEN );
|
|
object oTarget = OBJECT_INVALID;
|
|
|
|
//add breach check before doing search?
|
|
if ( GetIsObjectValid( oT ) )
|
|
{
|
|
oTarget = oT;
|
|
}
|
|
else
|
|
{
|
|
while ( !GetIsObjectValid( oTarget ) && GetIsObjectValid( oSub ) )
|
|
{
|
|
if ( !GetIsDead( oSub ) && !GetIsObjectValid( GetLocalObject( oSub, "BREACHER" ) ) )
|
|
{
|
|
strMDef = EvaluateSpellDefenses( oSub );
|
|
strPDef = EvaluatePhysicalDefenses( oSub );
|
|
|
|
if ( strMDef.iTotal + strPDef.iTotal > iMaxLvl )
|
|
{
|
|
oTarget = oSub;
|
|
iMaxLvl = strMDef.iTotal + strPDef.iTotal;
|
|
}
|
|
}
|
|
oSub = GetNearestCreature( CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, OBJECT_SELF, iCnt++, CREATURE_TYPE_PERCEPTION, PERCEPTION_SEEN );
|
|
}
|
|
}
|
|
|
|
if ( GetIsObjectValid( oTarget ) )
|
|
{
|
|
if ( iSpell = GetBestBreach( iMaxLvl ) )
|
|
{
|
|
//ActionCastSpellAtObject( iSpell, oTarget );
|
|
NO_ActionCastSpellAtObject( iSpell, oTarget );
|
|
SetLocalObject( oTarget, "BREACHER", OBJECT_SELF );
|
|
DelayCommand( 4.0, DeleteLocalObject( oTarget, "BREACHER" ) );
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
int DoSpellDirect( object oT=OBJECT_INVALID )
|
|
{
|
|
int iSpell = -1;
|
|
int iMinLvl = 0;
|
|
float fRange = 25.0;
|
|
object oHurt;
|
|
object oTarget;
|
|
float fRad;
|
|
float fRatio;
|
|
location lLoc;
|
|
talent tSpell;
|
|
|
|
//go for least defended, failing that go for most damaged
|
|
//in practice GetLeastMagicDefendedEnemy should always return someone if any
|
|
//enemies are visible to caller
|
|
if ( GetIsObjectValid( oT ) )
|
|
{
|
|
oTarget = oT;
|
|
}
|
|
else if ( !GetIsObjectValid( oTarget = GetLeastMagicDefendedEnemy( fRange ) ) )
|
|
{
|
|
oTarget = GetTarget();
|
|
if ( DoAbilityCheck( ABILITY_INTELLIGENCE, 5) )
|
|
{
|
|
oHurt = GetMostDamagedAlly( 20.0, oTarget, TRUE );
|
|
}
|
|
if ( GetIsObjectValid( oHurt ) && GetObjectSeen( oHurt, OBJECT_SELF ) )
|
|
{
|
|
oTarget = oHurt;
|
|
}
|
|
}
|
|
|
|
//open up spell level selection range
|
|
iMinLvl = GetHitDice( oTarget ) / 3;
|
|
|
|
//PrintString( GetName( OBJECT_SELF ) + " DS" + IntToString( iMinLvl ) );
|
|
if ( GetIsObjectValid( oTarget ) && GetObjectSeen( oTarget, OBJECT_SELF ) )
|
|
{
|
|
iSpell = GetDirectSpell( oTarget, FALSE, iMinLvl );
|
|
}
|
|
if ( iSpell > -1 && ( fRad = GetAreaSpellRadius( iSpell ) ) > 0.0 )
|
|
{
|
|
//direct spell has radius effect
|
|
//this code should be unnecessary now
|
|
fRatio = GetFriendFoeRatio( GetLocation( oTarget ), fRad );
|
|
if ( fRatio < GetFriendFoeTolerance() )
|
|
{
|
|
iSpell = GetDirectSpell( oTarget, TRUE, iMinLvl );
|
|
}
|
|
}
|
|
//Testing immunity checks
|
|
//result: feh, doesn't work properly, 100% immunity to damage type doesn't count as "immunity". go figure.
|
|
//SpeakString( "IT " + GetName( oTarget ) + " " + IntToString( FortitudeSave( oTarget, 1000, SAVING_THROW_TYPE_FIRE ) ) );
|
|
|
|
/* Spell/Talent Issue
|
|
if ( iSpell > -1 )
|
|
{
|
|
tSpell = TalentSpell( iSpell );
|
|
}
|
|
*/
|
|
if ( iSpell > -1 )
|
|
{
|
|
//ActionCastSpellAtObject( iSpell, oTarget );
|
|
NO_ActionCastSpellAtObject( iSpell, oTarget );
|
|
//ActionUseTalentOnObject( tSpell, oTarget );
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
int DoTouch( object oT=OBJECT_INVALID )
|
|
{
|
|
int iSpell = -1;
|
|
int iMinLvl = 0;
|
|
float fRange = 20.0;
|
|
object oHurt;
|
|
object oTarget;
|
|
|
|
//go for least defended, failing that go for most damaged
|
|
//in practice GetLeastMagicDefendedEnemy should always return someone if any
|
|
//enemies are visible to caller
|
|
if ( GetIsObjectValid( oT ) )
|
|
{
|
|
oTarget = oT;
|
|
}
|
|
else if ( !GetIsObjectValid( oTarget = GetLeastMagicDefendedEnemy( fRange ) ) )
|
|
{
|
|
oTarget = GetTarget();
|
|
if ( DoAbilityCheck( ABILITY_INTELLIGENCE, 5 ) )
|
|
{
|
|
oHurt = GetMostDamagedAlly( 10.0, oTarget, TRUE );
|
|
}
|
|
if ( GetIsObjectValid( oHurt ) && GetObjectSeen( oHurt, OBJECT_SELF ) )
|
|
{
|
|
oTarget = oHurt;
|
|
}
|
|
}
|
|
|
|
//open up spell level selection range
|
|
iMinLvl = GetHitDice( oTarget ) / 3;
|
|
|
|
//PrintString( GetName( OBJECT_SELF ) + " TS" + IntToString( iMinLvl ) );
|
|
if ( GetIsObjectValid( oTarget ) && GetObjectSeen( oTarget, OBJECT_SELF ) )
|
|
{
|
|
iSpell = GetTouchSpell( oTarget, iMinLvl );
|
|
}
|
|
|
|
/* Spell/Talent Issue
|
|
if ( iSpell > -1 )
|
|
{
|
|
tSpell = TalentSpell( iSpell );
|
|
}
|
|
*/
|
|
if ( iSpell > -1 )
|
|
{
|
|
//ActionCastSpellAtObject( iSpell, oTarget );
|
|
NO_ActionCastSpellAtObject( iSpell, oTarget );
|
|
//ActionUseTalentOnObject( tSpell, oTarget );
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
int DoSpellArea()
|
|
{
|
|
int iCnt;
|
|
int iMinLvl = 0;
|
|
int iSpell = -1;
|
|
int iHostiles = GetHostileCount( 40.0 );
|
|
float fRad;
|
|
float fRange;
|
|
float fRatio;
|
|
float fDist;
|
|
float fMinSearch = 0.0;
|
|
float fMaxSearch;
|
|
vector vU;
|
|
vector vT;
|
|
vector vS;
|
|
location lMark;
|
|
object oTarget;
|
|
object oD;
|
|
talent tSpell;
|
|
|
|
if ( !iHostiles )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
//estimate average enemy hit dice to get an idea of what spell level range we should consider
|
|
iMinLvl = GetSafeAverageEnemyLevel();
|
|
|
|
if ( iHostiles == 1 )
|
|
{
|
|
oTarget = GetTarget();
|
|
if ( !GetIsObjectValid( oTarget ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
fMaxSearch = GetDistanceBetween( OBJECT_SELF, oTarget );
|
|
iSpell = GetAreaSpell( Vector( 0.0, 0.0, 0.0 ), FALSE, iMinLvl, fMaxSearch );
|
|
if ( iSpell == -1 )
|
|
{
|
|
return FALSE;
|
|
}
|
|
fRad = GetAreaSpellRadius( iSpell );
|
|
vT = GetPosition( oTarget ) - GetPosition( OBJECT_SELF );
|
|
}
|
|
}
|
|
else if ( iHostiles > 1 )
|
|
{
|
|
//fMaxSearch = VectorMagnitude( GetHostileVector( 40.0 ) );
|
|
/*
|
|
oD = GetMostDistantEnemy( 40.0 );
|
|
if ( GetIsObjectValid( oD ) )
|
|
{
|
|
fMaxSearch = GetDistanceBetween( OBJECT_SELF, oD );
|
|
}
|
|
else
|
|
{
|
|
//apparently no enemies spotted, should be impossible at this point
|
|
fMaxSearch = 40.0;
|
|
}
|
|
*/
|
|
fMaxSearch = GetAverageDistanceToEnemy( 40.0 );
|
|
//SpeakString( "ADtE: " + FloatToString( fMaxSearch ) + " AEL: " + IntToString( iMinLvl ) );
|
|
iSpell = GetAreaSpell( Vector( 0.0, 0.0, 0.0 ), FALSE, iMinLvl, fMaxSearch );
|
|
if ( iSpell < 0 )
|
|
{
|
|
//SpeakString( "No spell" );
|
|
return FALSE;
|
|
}
|
|
//SpeakString( "Spell: " + IntToString( iSpell ) );
|
|
fRad = GetAreaSpellRadius( iSpell );
|
|
vT = GetAreaTarget( iSpell, fRad, fMinSearch, fMaxSearch );
|
|
vS = GetAverageEnemySaveInArea( vT, fRad );
|
|
//SpeakString( "AES: " + FloatToString( vS.x ) + " " + FloatToString( vS.y ) + " " + FloatToString( vS.z ) );
|
|
}
|
|
|
|
//PrintString( GetName( OBJECT_SELF ) + " AS" + IntToString( iMinLvl ) );
|
|
|
|
fRange = GetSpellRange( iSpell );
|
|
vU = GetPosition( OBJECT_SELF );
|
|
//single target case
|
|
if ( iHostiles == 1 )
|
|
{
|
|
lMark = GetLocation( OBJECT_SELF );
|
|
if ( GetIsDiscriminantSpell( iSpell ) )
|
|
{
|
|
if ( fRange == 0.0 )
|
|
{
|
|
//ActionCastSpellAtLocation( iSpell, lMark );
|
|
NO_ActionCastSpellAtLocation( iSpell, lMark );
|
|
}
|
|
else
|
|
{
|
|
//ActionCastSpellAtObject( iSpell, oTarget );
|
|
NO_ActionCastSpellAtObject( iSpell, oTarget );
|
|
}
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
if ( fRange == 0.0 )
|
|
{
|
|
fRatio = GetFriendFoeRatio( lMark, fRad );
|
|
if ( fRatio < GetFriendFoeTolerance() )
|
|
{
|
|
iSpell = GetAreaSpell( Vector( 0.0, 0.0, 0.0 ), TRUE, iMinLvl, fMaxSearch );
|
|
if ( iSpell < 0 )
|
|
{
|
|
//fRatio not acceptable, no discriminant spells
|
|
return FALSE;
|
|
}
|
|
}
|
|
//either fRatio is okay or we have a discriminant spell
|
|
fRange = GetSpellRange( iSpell );
|
|
if ( fRange == 0.0 )
|
|
{
|
|
//ActionCastSpellAtLocation( iSpell, lMark );
|
|
NO_ActionCastSpellAtLocation( iSpell, lMark );
|
|
}
|
|
else
|
|
{
|
|
//ActionCastSpellAtObject( iSpell, oTarget );
|
|
NO_ActionCastSpellAtObject( iSpell, oTarget );
|
|
}
|
|
return TRUE;
|
|
}
|
|
fRatio = GetFriendFoeRatio( GetLocation( oTarget ), fRad );
|
|
if ( fRatio < GetFriendFoeTolerance() )
|
|
{
|
|
iSpell = GetAreaSpell( Vector( 0.0, 0.0, 0.0 ), TRUE, iMinLvl, fMaxSearch );
|
|
if ( iSpell < 0 )
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
//either fRatio is okay or we have a discriminant spell
|
|
fRange = GetSpellRange( iSpell );
|
|
if ( fRange == 0.0 )
|
|
{
|
|
//ActionCastSpellAtLocation( iSpell, lMark );
|
|
NO_ActionCastSpellAtLocation( iSpell, lMark );
|
|
}
|
|
else
|
|
{
|
|
//ActionCastSpellAtObject( iSpell, oTarget );
|
|
NO_ActionCastSpellAtObject( iSpell, oTarget );
|
|
}
|
|
return TRUE;
|
|
}
|
|
}
|
|
//multiple target case
|
|
if ( iHostiles > 1 )
|
|
{
|
|
//PrintString( "AREA: " + GetName( OBJECT_SELF ) + FloatToString( fMaxSearch ) );
|
|
if ( VectorMagnitude( vT ) == 0.0 )
|
|
{
|
|
//SpeakString( "No target" );
|
|
return FALSE;
|
|
}
|
|
/*
|
|
if ( 5 - GetAbilityModifier( ABILITY_INTELLIGENCE ) > 0 )
|
|
{
|
|
lMark = Location( GetArea( OBJECT_SELF ),
|
|
vU + vT + VectorNormalize( AngleToVector( IntToFloat( Random( 360 ) ) ) ) * IntToFloat( Random( 5 - GetAbilityModifier( ABILITY_INTELLIGENCE ) ) ),
|
|
GetFacing( OBJECT_SELF ) );
|
|
}
|
|
else
|
|
{
|
|
lMark = Location( GetArea( OBJECT_SELF ), vU + vT, GetFacing( OBJECT_SELF ) );
|
|
}
|
|
*/
|
|
lMark = Location( GetArea( OBJECT_SELF ), vU + vT, VectorToAngle( vT ) );
|
|
//SpeakString( GetName( OBJECT_SELF ) + " AS(" + IntToString( iSpell ) + ")" );
|
|
if ( !GetIsDiscriminantSpell( iSpell ) )
|
|
{
|
|
fRatio = GetFriendFoeRatio( lMark, fRad );
|
|
//SpeakString( "AS FFR" + FloatToString( fRatio ) );
|
|
if ( fRatio < GetFriendFoeTolerance() )
|
|
{
|
|
iSpell = GetAreaSpell( Vector( 0.0, 0.0, 0.0 ), TRUE, iMinLvl, fMaxSearch ); //get discriminant area spell if possible
|
|
if ( iSpell < 0 )
|
|
{
|
|
//SpeakString( GetName( OBJECT_SELF ) + " !iDisc " + FloatToString( fRatio ) );
|
|
return FALSE;
|
|
}
|
|
//PrintString( GetName( OBJECT_SELF ) + " iDisc " + FloatToString( fRatio ) );
|
|
}
|
|
else
|
|
{
|
|
//SpeakString( GetName( OBJECT_SELF ) + " FFOK " + FloatToString( fRatio ) );
|
|
}
|
|
}
|
|
//either friendfoe ratio is acceptable or we have a discriminant spell
|
|
//if the spell is lightning or chain lightning target it at nearest creature
|
|
//do this sometimes with delayed blast fireball
|
|
if ( iSpell == SPELL_LIGHTNING_BOLT || iSpell == SPELL_CHAIN_LIGHTNING || iSpell == SPELL_GEDLEES_ELECTRIC_LOOP )
|
|
{
|
|
oTarget = GetFirstObjectInShape( SHAPE_SPHERE, fRad, lMark );
|
|
while ( GetIsObjectValid( oTarget ) )
|
|
{
|
|
if ( GetObjectType( oTarget ) == OBJECT_TYPE_CREATURE || GetIsPC( oTarget ) )
|
|
{
|
|
if ( GetObjectSeen( oTarget ) && !GetIsDead( oTarget ) && GetIsEnemy( oTarget ) )
|
|
{
|
|
//PrintString( "AS LCL " + GetName( oTarget ) );
|
|
break;
|
|
}
|
|
}
|
|
oTarget = GetNextObjectInShape( SHAPE_SPHERE, fRad, lMark );
|
|
}
|
|
if ( GetIsObjectValid( oTarget ) ) //should always be valid
|
|
{
|
|
//PrintString( "AS CL" + GetName( oTarget ) );
|
|
//ActionCastSpellAtObject( iSpell, oTarget );
|
|
NO_ActionCastSpellAtObject( iSpell, oTarget );
|
|
return TRUE;
|
|
}
|
|
}
|
|
//don't cast an area spell right next to myself
|
|
//maybe later add something to see if I am immune to the spell
|
|
//and cast anyway if so
|
|
//Spells with range of 0 should be okay to cast
|
|
else if ( GetIsDiscriminantSpell( iSpell ) ||
|
|
( GetSpellRange( iSpell ) > 0.0 && VectorMagnitude( vT ) > fRad ) || GetSpellRange( iSpell ) == 0.0 ||
|
|
IsCone( iSpell ) )
|
|
{
|
|
//ActionCastSpellAtLocation( iSpell, lMark );
|
|
NO_ActionCastSpellAtLocation( iSpell, lMark );
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
int DoSpellSummon()
|
|
{
|
|
int iMinLvl = GetAverageEnemyLevel();
|
|
int iSpell = GetSummonSpell( iMinLvl );
|
|
int iA;
|
|
vector vT, vA;
|
|
object oTarget = GetNearestCreature( CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, OBJECT_SELF, 1, CREATURE_TYPE_PERCEPTION, PERCEPTION_SEEN, CREATURE_TYPE_IS_ALIVE, TRUE );
|
|
|
|
if ( iSpell && !GetLocalInt( OBJECT_SELF, "#SUMMONDEL" ) && !GetIsObjectValid( GetAssociate( ASSOCIATE_TYPE_SUMMONED ) ) )
|
|
{
|
|
/*
|
|
vT = AngleToVector( GetFacing( OBJECT_SELF ) - 90.0 + IntToFloat( Random( 180 ) ) );
|
|
//testing addition to stop summoning creatures into AOEs
|
|
if ( GetAOECount( 10.0 ) )
|
|
{
|
|
vT = GetAOEVector( 10.0 );
|
|
vT *= -1.0;
|
|
}
|
|
//end test code
|
|
vT = 3.0 * VectorNormalize( vT );
|
|
vT = GetPosition( OBJECT_SELF ) + vT;
|
|
ActionCastSpellAtLocation( iSpell, Location( GetArea( OBJECT_SELF ), vT, GetFacing( OBJECT_SELF ) ) );
|
|
*/
|
|
//ActionCastSpellAtLocation( iSpell, GetLocation( OBJECT_SELF ) );
|
|
NO_ActionCastSpellAtLocation( iSpell, GetLocation( OBJECT_SELF ) );
|
|
SetLocalInt( OBJECT_SELF, "#SUMMONDEL", 1 );
|
|
DelayCommand( 6.0, DeleteLocalInt( OBJECT_SELF, "#SUMMONDEL" ) );
|
|
DelayCommand( 6.0, SpeakString( "BC_FIGHTING", TALKVOLUME_SILENT_TALK ) );
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
int DoSpellCharm()
|
|
{
|
|
//included in DoSpellDirect()
|
|
return FALSE;
|
|
}
|
|
|
|
int DoFeatEnhance()
|
|
{
|
|
//NOTE: should be okay to rely on enhance feats to be non-zero as Alertness should never be returned
|
|
int iFeat = GetEnhanceFeat();
|
|
|
|
if ( iFeat )
|
|
{
|
|
ActionUseFeat( iFeat, OBJECT_SELF );
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
int DoDragonFlight()
|
|
{
|
|
//unused due to lack of re-entry animation
|
|
/*
|
|
int iCnt = 1;
|
|
object oTarget = OBJECT_INVALID;
|
|
|
|
if ( GetLocalInt( OBJECT_SELF, "FLIER" ) && GetHostileCount() > 3 )
|
|
{
|
|
oTarget = GetNearestCreature( CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, OBJECT_SELF, iCnt++, CREATURE_TYPE_PERCEPTION, PERCEPTION_SEEN );
|
|
while ( GetIsObjectValid( oTarget ) )
|
|
{
|
|
if ( Random( 5 ) && GetDistanceBetween( OBJECT_SELF, oTarget ) > 10.0 )
|
|
{
|
|
break;
|
|
}
|
|
oTarget = GetNearestCreature( CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, OBJECT_SELF, iCnt++, CREATURE_TYPE_PERCEPTION, PERCEPTION_SEEN );
|
|
}
|
|
if ( GetIsObjectValid( oTarget ) )
|
|
{
|
|
DoMoveToObject( oTarget );
|
|
return TRUE;
|
|
}
|
|
}
|
|
*/
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
int DoAvoidMelee()
|
|
{
|
|
vector vT;
|
|
location lLoc;
|
|
|
|
//don't bother if we have time stop
|
|
if ( GetHasSpellEffect( SPELL_TIME_STOP ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
//if ( GetAttackerCount( 5.0 ) )
|
|
if ( GetHostileCount( 5.0 ) )
|
|
{
|
|
vT = GetHostileEvacVector( GetHostileVector( 15.0 ) );
|
|
if ( VectorMagnitude( vT ) > 5.0 )
|
|
{
|
|
SetLocalLocation( OBJECT_SELF, "#LASTHOTSPOT", GetLocation( OBJECT_SELF ) );
|
|
SetLocalFloat( OBJECT_SELF, "#LASTAMANGLE", VectorToAngle( vT ) );
|
|
vT = GetPosition( OBJECT_SELF ) - vT;
|
|
lLoc = Location( GetArea( OBJECT_SELF ), vT, VectorToAngle( vT ) );
|
|
DoMoveToLocation( lLoc, TRUE );
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
*/
|
|
int DoAvoidMelee()
|
|
{
|
|
vector vT;
|
|
location lD;
|
|
|
|
if ( GetHasSpellEffect( SPELL_TIME_STOP, OBJECT_SELF ) == TRUE )
|
|
{
|
|
//don't bother if we have time stop, dish out some pain instead
|
|
return FALSE;
|
|
}
|
|
|
|
if ( GetHostileCount( 5.0 ) )
|
|
{
|
|
vT = ( -1.0 * ( 10.0 + IntToFloat( Random( 20 ) ) ) ) * VectorNormalize( GetHostileVector( 20.0 ) );
|
|
lD = Location( GetArea( OBJECT_SELF ), vT + GetPosition( OBJECT_SELF ), VectorToAngle( vT ) );
|
|
if ( GetLocalInt( OBJECT_SELF, "#LASTDEST" ) )
|
|
{
|
|
if ( GetDistanceBetweenLocations( GetLocalLocation( OBJECT_SELF, "#LASTDEST" ), GetLocation( OBJECT_SELF ) ) < 5.0 )
|
|
{
|
|
//close enough to target location
|
|
DeleteLocalInt( OBJECT_SELF, "#LASTDEST" );
|
|
DeleteLocalLocation( OBJECT_SELF, "#LASTDEST" );
|
|
}
|
|
else if ( GetDistanceBetweenLocations( GetLocalLocation( OBJECT_SELF, "#LASTDEST" ), GetLocation( OBJECT_SELF ) ) > 5.0 )
|
|
{
|
|
//didn't make it close enough to our last desired location for some reason
|
|
vT = ( 10.0 + IntToFloat( Random( 20 ) ) ) * VectorNormalize( AngleToVector( VectorToAngle( vT ) - 90.0 + 180 * IntToFloat( Random( 2 ) ) ) );
|
|
lD = Location( GetArea( OBJECT_SELF ), vT + GetPosition( OBJECT_SELF ), VectorToAngle( vT ) );
|
|
}
|
|
}
|
|
SetLocalInt( OBJECT_SELF, "#LASTDEST", 1 );
|
|
SetLocalLocation( OBJECT_SELF, "#LASTDEST", lD );
|
|
DoMoveToLocation( lD, TRUE );
|
|
/*XXX this queueing now handled by main combat loop
|
|
ActionDoCommand( DelayCommand( 1.0, DoCombat() ) );
|
|
*/
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
int DoTimeStop()
|
|
{
|
|
int iEnemy = GetHostileCount( 40.0f );
|
|
|
|
if ( iEnemy )
|
|
{
|
|
if ( GetHasSpell( SPELL_TIME_STOP ) && !GetHasSpellEffect( SPELL_TIME_STOP ) )
|
|
{
|
|
//ActionCastSpellAtObject( SPELL_TIME_STOP, OBJECT_SELF );
|
|
NO_ActionCastSpellAtObject( SPELL_TIME_STOP, OBJECT_SELF );
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
int DoVision()
|
|
{
|
|
object oS;
|
|
int iCnt = 1;
|
|
int iSpell = 0;
|
|
|
|
if ( !GetHasVisionSpells() )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
oS = GetVisionDeprived( 20.0 );
|
|
if ( GetIsObjectValid( oS ) )
|
|
{
|
|
iSpell = GetVisionSpellNeeded( oS );
|
|
if ( iSpell )
|
|
{
|
|
//oS should be creature that needs vision, iSpell is the spell needed
|
|
SetLocalObject( oS, "#VISION", OBJECT_SELF );
|
|
DelayCommand( 6.0, DeleteLocalObject( oS, "#VISION" ) );
|
|
//ActionCastSpellAtObject( iSpell, oS );
|
|
NO_ActionCastSpellAtObject( iSpell, oS );
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
int DoBreathWeapon()
|
|
{
|
|
int iSpell = GetBreathWeapon();
|
|
int iHostiles;
|
|
float fRad = RADIUS_SIZE_LARGE;
|
|
float fMinSearch = 2.5;
|
|
float fMaxSearch = fRad;
|
|
vector vT;
|
|
object oT;
|
|
location lMark;
|
|
int iMeta= METAMAGIC_ANY;
|
|
int iCheat = TRUE;
|
|
|
|
if ( iSpell && GetLocalInt( OBJECT_SELF, "#BDEL" ) == 0 )
|
|
{
|
|
iHostiles = GetHostileCount( 50.0 );
|
|
if ( iHostiles == 1 )
|
|
{
|
|
//we have a breath weapon ready to go, single target
|
|
oT = GetTarget();
|
|
if ( oT != OBJECT_INVALID )
|
|
{
|
|
//ActionCastSpellAtObject( iSpell, oT, iMeta, iCheat );
|
|
NO_ActionCastSpellAtObject( iSpell, oT, iMeta, iCheat, 0, PROJECTILE_PATH_TYPE_DEFAULT, FALSE, FALSE );
|
|
SetLocalInt( OBJECT_SELF, "#BDEL", 1 );
|
|
DelayCommand( RoundsToSeconds( d4() ), DeleteLocalInt( OBJECT_SELF, "#BDEL" ) );
|
|
return TRUE;
|
|
}
|
|
}
|
|
else if ( iHostiles > 1 )
|
|
{
|
|
//we have a breath weapon ready to go, multiple targets
|
|
vT = GetAreaTarget( iSpell, fRad, fMinSearch, fMaxSearch );
|
|
if ( VectorMagnitude( vT ) > 0.0 )
|
|
{
|
|
lMark = Location( GetArea( OBJECT_SELF ), GetPosition( OBJECT_SELF ) + vT, GetFacing( OBJECT_SELF ) );
|
|
//ActionCastSpellAtLocation( iSpell, lMark );
|
|
NO_ActionCastSpellAtLocation( iSpell, lMark, iMeta, iCheat, PROJECTILE_PATH_TYPE_DEFAULT, FALSE, FALSE );
|
|
SetLocalInt( OBJECT_SELF, "#BDEL", 1 );
|
|
DelayCommand( RoundsToSeconds( d4() ), DeleteLocalInt( OBJECT_SELF, "#BDEL" ) );
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
int DoTurning()
|
|
{
|
|
int iTurn = GetHasFeat( FEAT_TURN_UNDEAD );
|
|
vector vT = Vector( 0.0, 0.0, 0.0 );
|
|
location lLoc;
|
|
|
|
if ( !iTurn )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
vT = GetTurningVector();
|
|
if ( VectorMagnitude( vT ) > 0.0 )
|
|
{
|
|
vT = GetPosition( OBJECT_SELF ) + vT;
|
|
lLoc = Location( GetArea( OBJECT_SELF ), vT, VectorToAngle( vT ) );
|
|
DoMoveToLocation( lLoc, TRUE );
|
|
ActionUseFeat( FEAT_TURN_UNDEAD, OBJECT_SELF );
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
int DoSpellCone()
|
|
{
|
|
//moving this over to DoSpellArea()
|
|
/*
|
|
object oT;
|
|
int iSpell, iMinLvl;
|
|
float fRad = 10.0;
|
|
vector vT, vU, vX;
|
|
location lLoc, lMove;
|
|
|
|
if ( GetIsObjectValid( oT = GetTarget() ) )
|
|
{
|
|
iMinLvl = GetAverageEnemyLevel() / 3;
|
|
}
|
|
|
|
if ( iSpell = GetConeSpell( iMinLvl ) )
|
|
{
|
|
vU = GetPosition( OBJECT_SELF );
|
|
vT = GetAreaTarget( iSpell, fRad );
|
|
if ( VectorMagnitude( vT ) > 0.0 )
|
|
{
|
|
//extend slightly past center of area
|
|
vT = vT + 2.5 * VectorNormalize( vT );
|
|
lLoc = Location( GetArea( OBJECT_SELF ), vU + vT, VectorToAngle( vT ) );
|
|
ActionCastSpellAtLocation( iSpell, lLoc );
|
|
return TRUE;
|
|
}
|
|
}
|
|
*/
|
|
return FALSE;
|
|
}
|
|
|
|
int DoHealSelf()
|
|
{
|
|
//Testing via talents, having to go via random seems kind of wasteful
|
|
//Look for another way to do this
|
|
int iHeal;
|
|
int iHealTalent;
|
|
int iCR;
|
|
int iFeat;
|
|
int iDam;
|
|
int iP;
|
|
int iLimit = 0;
|
|
talent tHeal, tP;
|
|
object oP, oHeal, oHealer;
|
|
event evHeal;
|
|
|
|
oHealer = GetLocalObject( OBJECT_SELF, "#HEALER" );
|
|
if ( GetIsObjectValid( oHealer ) )
|
|
{
|
|
if ( GetDistanceBetween( OBJECT_SELF, oHealer ) < 10.0 )
|
|
{
|
|
//got a healer who is close, don't double up, healing is on the way
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
iDam = GetMaxHitPoints() - GetCurrentHitPoints();
|
|
if ( iDam == 0 )
|
|
{
|
|
return FALSE;
|
|
}
|
|
//see if wholeness of body is an option
|
|
if ( GetHasFeat( FEAT_WHOLENESS_OF_BODY ) )
|
|
{
|
|
iFeat = FEAT_WHOLENESS_OF_BODY;
|
|
if ( iDam < GetLevelByClass( CLASS_TYPE_MONK ) * 2 )
|
|
{
|
|
iFeat = 0;
|
|
}
|
|
}
|
|
|
|
//iCR = 20;
|
|
iCR = 10;
|
|
iHealTalent = 0;
|
|
if ( GetRacialType( OBJECT_SELF ) != RACIAL_TYPE_UNDEAD ) //undead do not use healing potions
|
|
{
|
|
//while ( iCR && GetIsTalentValid( tP = GetCreatureTalentBest( TALENT_CATEGORY_BENEFICIAL_HEALING_POTION, iCR-- ) ) )
|
|
while ( iCR-- && GetIsTalentValid( tP = GetCreatureTalentRandom( TALENT_CATEGORY_BENEFICIAL_HEALING_POTION ) ) )
|
|
{
|
|
if ( iP = GetTalentPotionHealAmount( tP ) )
|
|
{
|
|
if ( iP > iHealTalent && iP < iDam )
|
|
{
|
|
tHeal = tP;
|
|
iHealTalent = iP;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ( iFeat )
|
|
{
|
|
ActionUseFeat( iFeat, OBJECT_SELF );
|
|
SetLocalObject( OBJECT_SELF, "#HEALER", OBJECT_SELF );
|
|
DelayCommand( 4.0, DeleteLocalObject( OBJECT_SELF, "#HEALER" ) );
|
|
return TRUE;
|
|
}
|
|
if ( iHealTalent && GetIsTalentValid( tHeal ) )
|
|
{
|
|
ActionUseTalentOnObject( tHeal, OBJECT_SELF );
|
|
SetLocalObject( OBJECT_SELF, "#HEALER", OBJECT_SELF );
|
|
DelayCommand( 4.0, DeleteLocalObject( OBJECT_SELF, "#HEALER" ) );
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
int DoSpellGroupEnhance()
|
|
{
|
|
int iSpell, iMinLvl;
|
|
float fRad = 0.0;
|
|
vector vT, vU, vX;
|
|
location lLoc, lMove;
|
|
object oT;
|
|
|
|
if ( GetIsObjectValid( oT = GetTarget() ) )
|
|
{
|
|
iMinLvl = GetAverageEnemyLevel() / 3;
|
|
}
|
|
|
|
//try feats first
|
|
if ( iSpell = GetGroupEnhanceFeat() )
|
|
{
|
|
fRad = GetGroupEnhanceFeatRadius( iSpell );
|
|
vU = GetPosition( OBJECT_SELF );
|
|
vT = GetFriendlyAreaTarget( fRad, iSpell, 1 );
|
|
if ( VectorMagnitude( vT ) > 0.0 )
|
|
{
|
|
lLoc = Location( GetArea( OBJECT_SELF ), vU + vT, VectorToAngle( vT ) );
|
|
DoMoveToLocation( lLoc, TRUE );
|
|
ActionUseFeat( iSpell, OBJECT_SELF );
|
|
return TRUE;
|
|
}
|
|
}
|
|
//then spells
|
|
if ( iSpell = GetGroupEnhanceSpell( iMinLvl ) )
|
|
{
|
|
fRad = GetGroupEnhanceSpellRadius( iSpell );
|
|
vU = GetPosition( OBJECT_SELF );
|
|
vT = GetFriendlyAreaTarget( fRad, iSpell );
|
|
if ( VectorMagnitude( vT ) > 0.0 )
|
|
{
|
|
lLoc = Location( GetArea( OBJECT_SELF ), vU + vT, VectorToAngle( vT ) );
|
|
//ActionCastSpellAtLocation( iSpell, lLoc );
|
|
NO_ActionCastSpellAtLocation( iSpell, lLoc );
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
int DoSpellGroupHeal()
|
|
{
|
|
int iSpell, iMinLvl, iHeal;
|
|
float fRad = 0.0;
|
|
vector vT, vU, vX;
|
|
location lLoc, lMove;
|
|
float fDam;
|
|
object oT;
|
|
|
|
if ( GetIsObjectValid( oT = GetTarget() ) )
|
|
{
|
|
iMinLvl = GetAverageEnemyLevel() / 3;
|
|
}
|
|
|
|
if ( iSpell = GetGroupHealSpell( iMinLvl ) )
|
|
{
|
|
iHeal = GetGroupHealSpellAmount( iSpell );
|
|
fRad = GetGroupHealSpellRadius( iSpell );
|
|
vU = GetPosition( OBJECT_SELF );
|
|
vT = GetAreaHealTarget( fRad, iHeal );
|
|
lLoc = Location( GetArea( OBJECT_SELF ), vU + vT, VectorToAngle( vT ) );
|
|
fDam = GetAllyDamageStats( lLoc, fRad );
|
|
if ( VectorMagnitude( vT ) > 0.0 && IntToFloat( iHeal ) < fDam )
|
|
{
|
|
//ActionCastSpellAtLocation( iSpell, lLoc );
|
|
NO_ActionCastSpellAtLocation( iSpell, lLoc );
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
int DoDispelPersAOE()
|
|
{
|
|
vector vT, vU;
|
|
location lT;
|
|
int iD;
|
|
|
|
if ( ( iD = GetDispelSpell() ) == -1 )
|
|
{
|
|
//no dispel capability available
|
|
return FALSE;
|
|
}
|
|
|
|
if ( GetHostileAOECount( 30.0 ) )
|
|
{
|
|
vU = GetPosition( OBJECT_SELF );
|
|
vT = GetHostileAOEVector( 30.0 );
|
|
lT = Location( GetArea( OBJECT_SELF ), vU + vT, VectorToAngle( vT ) );
|
|
//ActionCastSpellAtLocation( iD, lT );
|
|
NO_ActionCastSpellAtLocation( iD, lT );
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
int DoDispelSingle()
|
|
{
|
|
object oTarget;
|
|
int iAverage = 0;
|
|
int iSpell = 0;
|
|
int iCL = GetMaxDispelCasterLevel();
|
|
|
|
if ( GetIsObjectValid( oTarget = GetMostBuffedEnemy( 30.0 ) ) )
|
|
{
|
|
iAverage = GetAverageEffectCasterLevel( oTarget );
|
|
if ( ( iSpell = GetBestDispel( iAverage, iCL ) ) )
|
|
{
|
|
//PrintString( "DISPEL: " + GetName( OBJECT_SELF ) + " " + IntToString( iSpell ) + " " + GetName( oTarget ) );
|
|
//ActionCastSpellAtObject( iSpell, oTarget );
|
|
NO_ActionCastSpellAtObject( iSpell, oTarget );
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
int DoDismissal()
|
|
{
|
|
int iSpell;
|
|
float fRad = RADIUS_SIZE_COLOSSAL;
|
|
object oT;
|
|
vector vT;
|
|
location lT;
|
|
|
|
//banishment and dismissal is next optimal choices for enemy summons
|
|
iSpell = GetHasSpell( SPELL_BANISHMENT, OBJECT_SELF ) ? SPELL_BANISHMENT : SPELL_DISMISSAL;
|
|
if ( GetHasSpell( iSpell, OBJECT_SELF ) )
|
|
{
|
|
vT = GetEnemySummonedAssociatesVector( fRad );
|
|
if ( VectorMagnitude( vT ) > 0.0 )
|
|
{
|
|
lT = Location( GetArea( OBJECT_SELF ), GetPosition( OBJECT_SELF ) + vT, VectorToAngle( vT ) );
|
|
//ActionCastSpellAtLocation( iSpell, lT );
|
|
NO_ActionCastSpellAtLocation( iSpell, lT );
|
|
return TRUE;
|
|
}
|
|
}
|
|
//next check is to look for Outsiders and Elementals and clear them out with Word of Faith
|
|
//this will pick up against summoned and non-summoned planars
|
|
iSpell = SPELL_WORD_OF_FAITH;
|
|
if ( GetHasSpell( iSpell, OBJECT_SELF ) )
|
|
{
|
|
vT = GetEnemyPlanarVector( fRad );
|
|
if ( VectorMagnitude( vT ) > 0.0 )
|
|
{
|
|
lT = Location( GetArea( OBJECT_SELF ), GetPosition( OBJECT_SELF ) + vT, VectorToAngle( vT ) );
|
|
//ActionCastSpellAtLocation( iSpell, lT );
|
|
NO_ActionCastSpellAtLocation( iSpell, lT );
|
|
return TRUE;
|
|
}
|
|
}
|
|
//no dismissal, look for a summoned associate owner to use dispel magic on if we have it
|
|
if ( iSpell = GetDispelSpell() )
|
|
{
|
|
//we have a dispel
|
|
if ( GetIsObjectValid( oT = GetStrongestEnemySummonedAssociateOwner( 30.0 ) ) )
|
|
{
|
|
//ActionCastSpellAtObject( iSpell, oT );
|
|
NO_ActionCastSpellAtObject( iSpell, oT );
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
int DoFastBuffs()
|
|
{
|
|
int iSpell = 0;
|
|
int iCnt;
|
|
|
|
if ( !GetLocalInt( OBJECT_SELF, "#FASTBUFFER" ) || GetLocalInt( OBJECT_SELF, "#FASTBUFFED" ) )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
iCnt = GenerateFastBuffList();
|
|
|
|
while ( iCnt )
|
|
{
|
|
iSpell = GetLocalInt( OBJECT_SELF, "#SPL_FB" + IntToString( iCnt ) );
|
|
ActionCastSpellAtObject( iSpell, OBJECT_SELF, METAMAGIC_ANY, FALSE, 0, PROJECTILE_PATH_TYPE_DEFAULT, TRUE );
|
|
DeleteLocalInt( OBJECT_SELF, "#SPL_FB" + IntToString( iCnt-- ) );
|
|
}
|
|
|
|
if ( iSpell )
|
|
{
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
int DoFlee()
|
|
{
|
|
vector vT;
|
|
location lLoc;
|
|
|
|
//if we have time stop running use the time to flee
|
|
|
|
//if ( GetAttackerCount( 5.0 ) )
|
|
if ( GetHostileCount( 5.0 ) )
|
|
{
|
|
vT = GetHostileEvacVector( GetHostileVector( 15.0 ) );
|
|
if ( VectorMagnitude( vT ) > 5.0 )
|
|
{
|
|
SetLocalLocation( OBJECT_SELF, "#LASTHOTSPOT", GetLocation( OBJECT_SELF ) );
|
|
SetLocalFloat( OBJECT_SELF, "#LASTAMANGLE", VectorToAngle( vT ) );
|
|
vT = GetPosition( OBJECT_SELF ) - vT;
|
|
lLoc = Location( GetArea( OBJECT_SELF ), vT, VectorToAngle( vT ) );
|
|
DoMoveToLocation( lLoc, TRUE );
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
*/
|
|
|
|
int DoMeleeAssist()
|
|
{
|
|
object oF, oE, oT, oTarget;
|
|
int iCnt1, iCnt2, iF, iE, iChat, iSpecial;
|
|
float fAssistRange = 30.0;
|
|
float fMeleeRange = 3.0;
|
|
float fRatio, fT, fDist, fDistMin;
|
|
|
|
oT = OBJECT_INVALID;
|
|
fT = 1.0;
|
|
//look for a friend who needs assistance in melee
|
|
iCnt1 = 0;
|
|
oF = GetNearestCreature( CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_FRIEND, OBJECT_SELF, ++iCnt1, CREATURE_TYPE_IS_ALIVE, TRUE );
|
|
while ( GetIsObjectValid( oF ) && GetDistanceBetween( OBJECT_SELF, oF ) < fAssistRange )
|
|
{
|
|
if ( GetObjectSeen( oF ) || GetObjectHeard( oF ) )
|
|
{
|
|
iF = GetHitDice( oF ) + GetAllyHD( fMeleeRange, oF );
|
|
iE = GetHostileHD( fMeleeRange, oF );
|
|
fRatio = IntToFloat( iE ) / IntToFloat( iF );
|
|
if ( fRatio > fT )
|
|
{
|
|
fT = fRatio;
|
|
oT = oF;
|
|
}
|
|
}
|
|
oF = GetNearestCreature( CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_FRIEND, OBJECT_SELF, ++iCnt1, CREATURE_TYPE_IS_ALIVE, TRUE );
|
|
}
|
|
|
|
if ( GetIsObjectValid( oT ) )
|
|
{
|
|
//found a friend in trouble, stored highest enemy concentration to aid friend
|
|
//should be closest friend in a given melee
|
|
//get nearest enemy fighting friend
|
|
fDistMin = 100.0;
|
|
iCnt1 = 0;
|
|
oTarget = OBJECT_INVALID;
|
|
oE = GetNearestCreature( CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, oT, ++iCnt1, CREATURE_TYPE_IS_ALIVE, TRUE );
|
|
while ( GetIsObjectValid( oE ) )
|
|
{
|
|
if ( GetObjectSeen( oE ) || GetObjectHeard( oE ) )
|
|
{
|
|
if ( ( fDist = GetDistanceBetween( OBJECT_SELF, oE ) ) < fDistMin )
|
|
{
|
|
oTarget = oE;
|
|
fDistMin = fDist;
|
|
}
|
|
}
|
|
oE = GetNearestCreature( CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, oT, ++iCnt1, CREATURE_TYPE_IS_ALIVE, TRUE );
|
|
}
|
|
//should now have a valid enemy to target, closest to me in target group
|
|
if ( GetIsObjectValid( oTarget ) )
|
|
{
|
|
//SpeakString( "Assisting " + GetName( oT ) );
|
|
DoEquipMelee( oTarget );
|
|
DoVoiceChat( NO_VC_MELEEASSIST );
|
|
if ( iSpecial = GetBestMeleeSpecial( oTarget ) )
|
|
{
|
|
//use special attack type
|
|
ActionUseFeat( iSpecial, oTarget );
|
|
}
|
|
else
|
|
{
|
|
ActionAttack( oTarget );
|
|
}
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
//either nobody needs help or I couldn't find a target
|
|
return FALSE;
|
|
}
|
|
|
|
int DoFlank( object oE=OBJECT_INVALID )
|
|
{
|
|
object oF = OBJECT_INVALID;
|
|
location lL = GetLocation( OBJECT_SELF );
|
|
int iCnt = 0;
|
|
|
|
if ( GetIsObjectValid( oE ) )
|
|
{
|
|
oF = oE;
|
|
}
|
|
else
|
|
{
|
|
oE = GetNearestCreature( CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, OBJECT_SELF, ++iCnt, CREATURE_TYPE_IS_ALIVE, TRUE );
|
|
while ( GetIsObjectValid( oE ) && !GetIsObjectValid( oF ) )
|
|
{
|
|
if ( GetObjectSeen( oE, OBJECT_SELF ) || GetObjectHeard( oE, OBJECT_SELF ) )
|
|
{
|
|
if ( GetDistanceBetween( oE, OBJECT_SELF ) > 10.0 )
|
|
{
|
|
//enemy is seen or heard and more than 10.0 away, flank it
|
|
oF = oE;
|
|
}
|
|
else
|
|
{
|
|
//creature is within minimum range, abort flank
|
|
break;
|
|
}
|
|
}
|
|
oE = GetNearestCreature( CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, OBJECT_SELF, ++iCnt, CREATURE_TYPE_IS_ALIVE, TRUE );
|
|
}
|
|
}
|
|
if ( GetIsObjectValid( oF ) )
|
|
{
|
|
lL = GetFlankLoc( oF, lL );
|
|
DoMoveToLocation( lL, TRUE );
|
|
/*XXX
|
|
ActionDoCommand( DelayCommand( 1.0, DoCombat() ) );
|
|
*/
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
int DoMindBlast()
|
|
{
|
|
vector vT;
|
|
object oT;
|
|
int iSpell = -69;
|
|
int iEnemies = GetHostileCount( 40.0 );
|
|
int iEff;
|
|
float fRad = 15.0;
|
|
float fMinSrch = 0.0;
|
|
float fMaxSrch = 30.0;
|
|
location lMark;
|
|
|
|
if ( iEnemies == 1 )
|
|
{
|
|
oT = GetTarget();
|
|
if ( GetIsObjectValid( oT ) )
|
|
{
|
|
iEff = GetEffectsOnObject( oT );
|
|
if ( ( iEff & NO_EFFECT_STUNNED ) + ( iEff & NO_EFFECT_PARALYZE ) + ( iEff & NO_EFFECT_DAZED ) == 0 )
|
|
{
|
|
//target not stunned, paralyzed or dazed
|
|
//should switch to direct targetting once function is available
|
|
//ActionMindBlast( oT, GetLocation( oT ) );
|
|
//ActionCastSpellAtObject( 1522, oT, METAMAGIC_ANY, TRUE );
|
|
ActionCastSpellAtObject( CODI_MIND_BLAST, oT, METAMAGIC_ANY, TRUE );
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
else if ( iEnemies > 1 )
|
|
{
|
|
vT = GetAreaTarget( iSpell, fRad, fMinSrch, fMaxSrch );
|
|
if ( VectorMagnitude( vT ) > 0.0 )
|
|
{
|
|
lMark = Location( GetArea( OBJECT_SELF ), GetPosition( OBJECT_SELF ) + vT, VectorToAngle( vT ) );
|
|
//ActionMindBlast( OBJECT_INVALID, lMark );
|
|
//ActionCastSpellAtLocation( 1522, lMark, METAMAGIC_ANY, TRUE );
|
|
ActionCastSpellAtLocation( CODI_MIND_BLAST, lMark, METAMAGIC_ANY, TRUE );
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
int DoBrainExtraction()
|
|
{
|
|
float fScanRange = 30.0;
|
|
float fImmRange = 10.0;
|
|
object oT = GetNearestAddledEnemyNoExtractor( fScanRange );
|
|
int iCnt = 0;
|
|
int iC = 0;
|
|
int iH = 0;
|
|
int iEff;
|
|
int iDo = TRUE;
|
|
|
|
if ( !GetIsObjectValid( oT ) )
|
|
{
|
|
//look for non-petrified enemy close by
|
|
oT = GetNearestCreature( CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, OBJECT_SELF, ++iCnt, CREATURE_TYPE_IS_ALIVE, TRUE, CREATURE_TYPE_PERCEPTION, PERCEPTION_SEEN );
|
|
while ( GetIsObjectValid( oT ) && GetDistanceBetween( OBJECT_SELF, oT ) < fImmRange )
|
|
{
|
|
if ( IsBrainExtractable( oT ) )
|
|
{
|
|
break;
|
|
}
|
|
oT = GetNearestCreature( CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, OBJECT_SELF, ++iCnt, CREATURE_TYPE_IS_ALIVE, TRUE, CREATURE_TYPE_PERCEPTION, PERCEPTION_SEEN );
|
|
}
|
|
//double check we aren't looking at a petrified enemy
|
|
if ( GetIsObjectValid( oT ) )
|
|
{
|
|
if ( !IsBrainExtractable( oT ) )
|
|
{
|
|
oT = OBJECT_INVALID;
|
|
}
|
|
}
|
|
//see if we have decent chances of extracting
|
|
if ( GetIsObjectValid( oT ) )
|
|
{
|
|
iDo = FALSE;
|
|
int iAttNorm = GetCreatureAttackBonus( OBJECT_SELF );
|
|
int iDefAC = GetAC( oT ) - 10;
|
|
if ( iAttNorm > iDefAC )
|
|
{
|
|
int iAttGrap = GetGrappleBonus( OBJECT_SELF );
|
|
int iDefGrap = GetGrappleBonus( oT );
|
|
if ( iAttGrap > iDefGrap )
|
|
{
|
|
//we will probably hit and probably win a grapple
|
|
iDo = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ( GetIsObjectValid( oT ) && iDo )
|
|
{
|
|
//got a target, go for the brains
|
|
SetLocalInt( oT, "#EXTRACTING", 1 );
|
|
DelayCommand( 12.0, DeleteLocalInt( oT, "#EXTRACTING" ) ); //death redundancy
|
|
if ( GetDistanceBetween( OBJECT_SELF, oT ) > 10.0 )
|
|
{
|
|
DoMoveToObject( oT, TRUE, 2.1 );
|
|
}
|
|
//ActionHeadSuck( oT );
|
|
//ActionCastSpellAtObject( 1523, oT, METAMAGIC_ANY, TRUE );
|
|
ActionCastSpellAtObject( CODI_BRAIN_EXTRACT, oT, METAMAGIC_ANY, TRUE );
|
|
ActionDoCommand( DeleteLocalInt( oT, "#EXTRACTING" ) );
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
int DoAvoidEnemies()
|
|
{
|
|
object oE = GetNearestCreature( CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, OBJECT_SELF, 1, CREATURE_TYPE_IS_ALIVE, TRUE, CREATURE_TYPE_PERCEPTION, PERCEPTION_SEEN );
|
|
vector vE;
|
|
vector vT;
|
|
location lL;
|
|
|
|
if ( GetIsObjectValid( oE ) && GetDistanceBetween( OBJECT_SELF, oE ) < 15.0 )
|
|
{
|
|
vE = GetHostileVector( 20.0 );
|
|
vT = -10.0 * VectorNormalize( vE );
|
|
lL = Location( GetArea( OBJECT_SELF ), GetPosition( OBJECT_SELF ) + vT, VectorToAngle( vT ) );
|
|
DoMoveToLocation( lL, TRUE );
|
|
/*XXX
|
|
ActionDoCommand( DelayCommand( 1.0, DoCombat() ) );
|
|
*/
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
vE = GetHostileVector( 20.0 );
|
|
if ( VectorMagnitude( vE ) > 0.0 )
|
|
{
|
|
vT = -10.0 * VectorNormalize( vE );
|
|
}
|
|
else
|
|
{
|
|
vT = 5.0 * VectorNormalize( AngleToVector( IntToFloat( Random( 360 ) ) ) );
|
|
}
|
|
lL = Location( GetArea( OBJECT_SELF ), GetPosition( OBJECT_SELF ) + vT, VectorToAngle( vT ) );
|
|
DoMoveToLocation( lL, TRUE );
|
|
/*XXX
|
|
ActionDoCommand( DelayCommand( 1.0, DoCombat() ) );
|
|
*/
|
|
return TRUE;
|
|
}
|
|
//should never get down here
|
|
return FALSE;
|
|
}
|
|
|
|
int DoGrenade()
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
int DoBeholderRays()
|
|
{
|
|
float fM = GetFacing( OBJECT_SELF );
|
|
float fH;
|
|
float fS = 5.5 / BEHOLDER_MAX_RAYS;
|
|
int iRep = 95; //chance to use multiple rays on one target if enemies < rays
|
|
int iH = GetHostileCount( 40.0 );
|
|
int iC = 0;
|
|
int iQ1 = 0;
|
|
int iQ2 = 0;
|
|
int iQ3 = 0;
|
|
int iQ4 = 0;
|
|
int iR = 0;
|
|
int iS;
|
|
int iM;
|
|
object oT;
|
|
|
|
InitializeBeholderRaySelection();
|
|
|
|
if ( GetHasSpellEffect( SPELL_HASTE, OBJECT_SELF ) || GetHasSpellEffect( SPELL_MASS_HASTE, OBJECT_SELF ) && GetHasFeatEffect( FEAT_EPIC_BLINDING_SPEED, OBJECT_SELF ) )
|
|
{
|
|
fS *= 0.5;
|
|
}
|
|
|
|
oT = GetNearestCreature( CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, OBJECT_SELF, ++iC, CREATURE_TYPE_IS_ALIVE, TRUE );
|
|
while ( iC <= iH && iR < BEHOLDER_MAX_RAYS )
|
|
{
|
|
iM = TRUE; //default is that we will not target multiple rays at individual targets
|
|
if ( GetIsObjectValid( oT ) == FALSE )
|
|
{
|
|
//run out of targets unexpectedly
|
|
break;
|
|
}
|
|
else if ( GetIsPerceived( oT, NO_PERCEPTION_SEEN ) == FALSE && GetIsPerceived( oT, NO_PERCEPTION_HEARD ) == FALSE )
|
|
{
|
|
//can't see or hear this creature, ignore it
|
|
iC++;
|
|
}
|
|
else
|
|
{
|
|
SetLocalInt( OBJECT_SELF, "#EYERAYS", 1 ); //flag the beholder as being busy shooting eye rays
|
|
//oT is valid, we can see or hear it
|
|
fH = VectorToAngle( GetPosition( oT ) - GetPosition( OBJECT_SELF ) );
|
|
fH = fH < fM ? fH + 360.0 : fH;
|
|
fH -= fM;
|
|
if ( fH > 315.0 || fH < 45.0 )
|
|
{
|
|
if ( iQ1 < 3 && GetIsCentralEyeOpen() == FALSE )
|
|
{
|
|
//first quadrant target, still have rays available in this quadrant
|
|
iS = MatchRayToTarget( oT );
|
|
if ( iS != 0 )
|
|
{
|
|
iQ1++;
|
|
iR++;
|
|
RemoveBeholderRayFromSelection( iS );
|
|
DelayCommand( fS * iR, DoFireBeholderRay( iS, oT ) );
|
|
if ( iH - iC < BEHOLDER_MAX_RAYS - iR ) //we have more rays left to fire than enemies remaining to fire them at
|
|
{
|
|
if ( iQ1 < 3 && Random( 100 ) < iRep ) //we have rays left in this quadrant, random chance to shoot another at this target
|
|
{
|
|
iM = FALSE; //we will shoot more rays at this target, not moving on
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if ( fH < 135.0 )
|
|
{
|
|
if ( iQ2 < 3 )
|
|
{
|
|
//second quadrant target, still have rays available in this quadrant
|
|
iS = MatchRayToTarget( oT );
|
|
if ( iS != 0 )
|
|
{
|
|
iQ2++;
|
|
iR++;
|
|
RemoveBeholderRayFromSelection( iS );
|
|
DelayCommand( fS * iR, DoFireBeholderRay( iS, oT ) );
|
|
if ( iH - iC < BEHOLDER_MAX_RAYS - iR ) //we have more rays left to fire than enemies remaining to fire them at
|
|
{
|
|
if ( iQ2 < 3 && Random( 100 ) < iRep ) //we have rays left in this quadrant, random chance to shoot another at this target
|
|
{
|
|
iM = FALSE; //we will shoot more rays at this target, not moving on
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if ( fH < 225.0 )
|
|
{
|
|
if ( iQ3 < 3 )
|
|
{
|
|
//third quadrant target, still have rays available in this quadrant
|
|
iS = MatchRayToTarget( oT );
|
|
if ( iS != 0 )
|
|
{
|
|
iQ3++;
|
|
iR++;
|
|
RemoveBeholderRayFromSelection( iS );
|
|
DelayCommand( fS * iR, DoFireBeholderRay( iS, oT ) );
|
|
if ( iH - iC < BEHOLDER_MAX_RAYS - iR ) //we have more rays left to fire than enemies remaining to fire them at
|
|
{
|
|
if ( iQ3 < 3 && Random( 100 ) < iRep ) //we have rays left in this quadrant, random chance to shoot another at this target
|
|
{
|
|
iM = FALSE; //we will shoot more rays at this target, not moving on
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if ( iQ4 < 3 )
|
|
{
|
|
//fourth quadrant target, still have rays available in this quadrant
|
|
iS = MatchRayToTarget( oT );
|
|
if ( iS != 0 )
|
|
{
|
|
iQ4++;
|
|
iR++;
|
|
RemoveBeholderRayFromSelection( iS );
|
|
DelayCommand( fS * iR, DoFireBeholderRay( iS, oT ) );
|
|
if ( iH - iC < BEHOLDER_MAX_RAYS - iR ) //we have more rays left to fire than enemies remaining to fire them at
|
|
{
|
|
if ( iQ4 < 3 && Random( 100 ) < iRep ) //we have rays left in this quadrant, random chance to shoot another at this target
|
|
{
|
|
iM = FALSE; //we will shoot more rays at this target, not moving on
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//we're either getting the same target again or moving onto the next target
|
|
iC += iM; //if iM is true we are moving onto next target, iC is incremented, otherwise it stays where it is
|
|
oT = GetNearestCreature( CREATURE_TYPE_REPUTATION, REPUTATION_TYPE_ENEMY, OBJECT_SELF, iC, CREATURE_TYPE_IS_ALIVE, TRUE );
|
|
}
|
|
DelayCommand( fS * ( iR + 1 ), DeleteLocalInt( OBJECT_SELF, "#EYERAYS" ) );
|
|
|
|
ClearBeholderRaySelection();
|
|
|
|
if ( iR > 0 )
|
|
{
|
|
//we have fired some rays
|
|
return TRUE;
|
|
}
|
|
//we have not fired any rays
|
|
return FALSE;
|
|
}
|
|
|
|
int DoBeholderCentralEye()
|
|
{
|
|
int iSpell = SPELL_BEH_ANTIMAGIC_CONE;
|
|
int iHostiles;
|
|
float fRad = RADIUS_SIZE_MEDIUM;
|
|
float fMinSearch = 2.5;
|
|
float fMaxSearch = fRad;
|
|
vector vT;
|
|
object oT;
|
|
location lMark;
|
|
int iMeta= METAMAGIC_ANY;
|
|
int iCheat = TRUE;
|
|
|
|
if ( iSpell && GetLastAction() != "+CENTRALEYE" )
|
|
{
|
|
iHostiles = GetHostileCount( 50.0 );
|
|
if ( iHostiles == 1 )
|
|
{
|
|
//we have a breath weapon ready to go, single target
|
|
oT = GetTarget();
|
|
if ( oT != OBJECT_INVALID )
|
|
{
|
|
//ActionCastSpellAtObject( iSpell, oT, iMeta, iCheat );
|
|
NO_ActionCastSpellAtObject( iSpell, oT, iMeta, iCheat, 0, PROJECTILE_PATH_TYPE_DEFAULT, TRUE, FALSE );
|
|
SetIsCentralEyeOpen( TRUE );
|
|
DoBeholderRays();
|
|
SetIsCentralEyeOpen( FALSE );
|
|
return TRUE;
|
|
}
|
|
}
|
|
else if ( iHostiles > 1 )
|
|
{
|
|
//we have a breath weapon ready to go, multiple targets
|
|
vT = GetAreaTarget( iSpell, fRad, fMinSearch, fMaxSearch );
|
|
if ( VectorMagnitude( vT ) > 0.0 )
|
|
{
|
|
lMark = Location( GetArea( OBJECT_SELF ), GetPosition( OBJECT_SELF ) + vT, GetFacing( OBJECT_SELF ) );
|
|
//ActionCastSpellAtLocation( iSpell, lMark );
|
|
NO_ActionCastSpellAtLocation( iSpell, lMark, iMeta, iCheat, PROJECTILE_PATH_TYPE_DEFAULT, TRUE, FALSE );
|
|
SetIsCentralEyeOpen( TRUE );
|
|
DoBeholderRays();
|
|
SetIsCentralEyeOpen( FALSE );
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
return FALSE;
|
|
} |