Deepflame AI Tutorial #2

 

Welcome to Part 2 of my AI-Tutorial. Today we are going to make our monster kill =)

Ok, open up monster.cpp and add just below the #include's

#define MELEE_ATTACK1 (6) // This is for the monsters animation event, set in the models .qc file
Then 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 damage
And 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

For any questions/bugs mail me.
- Deepflame - tox.rwld@rendo.dekooi.nl