
| Deepflame | AI Tutorial #2 |
Welcome to Part 2 of my AI-Tutorial. Today we are going to make our monster kill =)
#define MELEE_ATTACK1 (6) // This is for the monsters animation event, set in the models .qc fileThen go to the class definition, and add
BOOL CheckMeleeAttack1 ( float flDot, float flDist ); // Checks if the monster CAN attack void HandleAnimEvent( MonsterEvent_t *pEvent ); // Handles the animation event, set in the previous define void StartTask ( Task_t *pTask ); // Starts the task Schedule_t *GetSchedule( void ); // Handles some schedules void EXPORT DoDamage ( CBaseEntity *pOther ); // Does the damageAnd in the Precache function add :
PRECACHE_SOUND("bullchicken/bc_bite2.wav"); // Sounds for the attack
PRECACHE_SOUND("bullchicken/bc_bite3.wav");
PRECACHE_SOUND("bullchicken/bc_attackgrowl.wav");
PRECACHE_SOUND("bullchicken/bc_attackgrowl2.wav");
PRECACHE_SOUND("bullchicken/bc_attackgrowl3.wav");
Ok, now go completely down the file, and add :
// This BOOL checks if the monster can or cannot melee attack
// If he is closer as 85 units it results TRUE and it'll attack
// Else it'll result FALSE and it'll move closer
BOOL CMonster :: CheckMeleeAttack1 ( float flDot, float flDist )
{
if ( flDist <= 85 && flDot >= 0.7 ) // The player & bullsquid can be as much as their bboxes
{ // apart (48 * sqrt(3)) and he can still attack (85 is a little more than 48*sqrt(3))
return TRUE;
}
return FALSE;
}
Then add :
// This handles the Animation Event, which is set by defines.
// if he does animation 6, or MELEE_ATTACK1 (as we've set) he'll do this
void CMonster :: HandleAnimEvent( MonsterEvent_t *pEvent )
{
switch( pEvent->event )
{
case MELEE_ATTACK1:
{
// This is only to hurt players. He'll do more dmg to players then other monsters
CBaseEntity *pHurt = CheckTraceHullAttack( 70, 0, 0 );
UTIL_MakeVectors( pev->angles );
pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_forward * 300 + gpGlobals->v_up * 300;
}
break;
default:
CBaseMonster::HandleAnimEvent( pEvent );
}
}
Next :
// This is the part that does the damage to the monsters
void CMonster :: StartTask ( Task_t *pTask )
{
m_iTaskStatus = TASKSTATUS_RUNNING;
switch ( pTask->iTask )
{
case TASK_MELEE_ATTACK1:
{ // Sounds...
switch ( RANDOM_LONG ( 0, 2 ) )
{
case 0:
EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_attackgrowl.wav", 1, ATTN_NORM );
SetTouch ( DoDamage ); // Makes the monster kill things it touches
break;
case 1:
EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_attackgrowl2.wav", 1, ATTN_NORM );
SetTouch ( DoDamage );
break;
case 2:
EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_attackgrowl3.wav", 1, ATTN_NORM );
SetTouch ( DoDamage );
break;
}
CBaseMonster :: StartTask ( pTask );
break;
}
case TASK_GET_PATH_TO_ENEMY: // And this chases the enemy
{
if ( BuildRoute ( m_hEnemy->pev->origin, bits_MF_TO_ENEMY, m_hEnemy ) )
{
// Results something positive and he'll chase his enemy
m_iTaskStatus = TASKSTATUS_COMPLETE;
}
else
{// This gives a message in the console if it has an error. It can only follow info_node's
ALERT ( at_console, "GetPathToEnemy failed!!\n" );
TaskFail();
}
break;
}
default:
{
CBaseMonster :: StartTask ( pTask );
break;
}
}
}
Next :
// This handles his current state. He has 4 states : IDLE, ALERT, COMBAT and DEAD
Schedule_t *CMonster :: GetSchedule( void )
{
switch ( m_MonsterState )
{
case MONSTERSTATE_COMBAT:
{
// dead enemy
if ( HasConditions( bits_COND_ENEMY_DEAD ) )
{
// call base class, all code to handle dead enemies is centralized there.
return CBaseMonster :: GetSchedule();
}
if ( HasConditions( bits_COND_CAN_MELEE_ATTACK1 ) )
{
return GetScheduleOfType ( SCHED_MELEE_ATTACK1 );
}
return GetScheduleOfType ( SCHED_CHASE_ENEMY );
break;
}
}
return CBaseMonster :: GetSchedule();
}
And last but not least :
// This actually does the damage.
// The way I currently have the monster do damage is rather simple.
// This is by no means the best way, but it IS the easiest way. Also a good way to learn.
void CMonster :: DoDamage ( CBaseEntity *pOther )
{
if ( !pOther->pev->takedamage ) // Did he do damage?
{
return; // Oops, I missed
}
if ( pOther->Classify() == Classify() ) // Checks if the monsters classify is the same as his
{
return; // He's a friend, don't wanna hurt friends
}
pOther->TakeDamage( pev, pev, 25, DMG_SLASH ); // Does the damage
switch ( RANDOM_LONG ( 0, 1 ) ) // Makes cool sounds when doing damage
{
case 0:
EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_bite2.wav", 1, ATTN_NORM ); // Sounds...
break;
case 1:
EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_bite3.wav", 1, ATTN_NORM ); // Sounds...
break;
}
SetTouch( NULL );
}
And that's it.
Compile and play your monster in your test map.
I assume you already know how to add monsters in your map.
Just a reminder, his name is monster_monster
He's an ALLY, so add a headcrab for him to kill.
Drop in a couple of info_nodes and play it.
In my next tutorial we'll make him get angry at you if you shoot him, and say cool things when IDLE, ALERT or COMBAT