Deepflame AI Tutorial #3

 

Welcome to Part 3 of my ai tut for HL.
This tutorial will cover :
1. Making the monster get angry at you if you shoot him.
2. Random chatting according to it's state.
Source
Open up monster.cpp and move to the class definition. Then add the following :

	int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType );
	int	Save( CSave &save ); 
	int Restore( CRestore &restore );
	BOOL m_fPlayerAttacked; // This is to check if he's been attacked by the player
	static TYPEDESCRIPTION m_SaveData[];
This is to check the damage it got and change things with it, and special values to save. Right underneath the LINK_ENTITY add :

TYPEDESCRIPTION	CMonster::m_SaveData[] = 
{
	DEFINE_FIELD( CBullsquid, m_fPlayerAttacked, FIELD_BOOLEAN ),
};
IMPLEMENT_SAVERESTORE( CMonster, CBaseMonster );
That saves the boolean attacked by the player to THAT monster alone. (If you don't know what a boolean is, it's the value that stores TRUE, FALSE or NULL) Then go all the way down the code and add :

int CMonster :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType )
{
	if ( pevAttacker->flags & FL_CLIENT )
	{
		m_fPlayerAttacked = TRUE; // Yup, I am attacked by him, so now I'm pissed
	}
	else
	{
		if (m_fPlayerAttacked != TRUE)
			m_fPlayerAttacked = FALSE; // He's still my friend! (This is just to make sure it'll go right.
	}

	return CBaseMonster :: TakeDamage ( pevInflictor, pevAttacker, flDamage, bitsDamageType );
}
Now go up to void CMonster::Classify. Remove return CLASS_PLAYER_ALLY; and add :

	if (m_fPlayerAttacked == FALSE || m_fPlayerAttacked == NULL)
		return	CLASS_PLAYER_ALLY; // This is a friendly monster
	else
		return  CLASS_ALIEN_MONSTER; // Now he's a bad guy =)
There you go, if you'll shoot him, he'll fight ya. Else he won't. But did you notice you have to actually touch him to be damaged? That is good for killing headcrabs, but you won't hurt any other creatures with it. I said, in Part 2, that this wasn't the most efficient way of doing it, and it shows here. And ofcourse you want to fix that, and I don't blame you. First go to the class definition, and remove :

	void EXPORT DoDamage ( CBaseEntity *pOther ); // Does the damage
Then go down and remove the function DoDamage. Then go back to the class definition, and add :

	CBaseEntity	*Bite( void );
And go all the way down, and add :

CBaseEntity *CMonster :: Bite( void )
{
	TraceResult tr;

	UTIL_MakeVectors( pev->angles );
	Vector vecStart = pev->origin;
	vecStart.z += pev->size.z * 0.5;
	Vector vecEnd = vecStart + (gpGlobals->v_forward * 70);

	UTIL_TraceHull( vecStart, vecEnd, dont_ignore_monsters, head_hull, ENT(pev), &tr );
	
	if ( tr.pHit )
	{
		CBaseEntity *pEntity = CBaseEntity::Instance( tr.pHit );
		return pEntity;
	}

	return NULL;
}
This does the damage. Then go to HandleAnimEvent, to case MELEE_ATTACK1 and remove everything between the first and the last bracket. Then add :

	CBaseEntity *pHurt = Bite();
	if ( pHurt )
	{
		// SOUND HERE!
		UTIL_MakeVectors( pev->angles );
		pHurt->pev->punchangle.x = 15;
	pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_forward * 100 + gpGlobals->v_up * 50;
		pHurt->TakeDamage( pev, pev, 25, DMG_CLUB );
	}
Incase you missed what I meant with remove everything between the brackets, it should look like this :

	case MELEE_ATTACK1:
		{
			CBaseEntity *pHurt = Bite();
			if ( pHurt )
			{
				// SOUND HERE!
				UTIL_MakeVectors( pev->angles );
				pHurt->pev->punchangle.x = 15;
			pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_forward * 100 + gpGlobals->v_up * 50;
				pHurt->TakeDamage( pev, pev, 25, DMG_CLUB );
			}
		}
	break;
Now, that's step 1. Time for the last part of the damaging, go to StartTask. Change case TASK_MELEE_ATTACK1 so that it looks like this :

	case TASK_MELEE_ATTACK1:
	{
		CBaseEntity *pHurt = Bite();
		switch ( RANDOM_LONG ( 0, 2 ) )
		{
		case 0:	
			EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_attackgrowl.wav", 1, ATTN_NORM );		
								
			if ( pHurt )
			{
				// SOUND HERE!
				UTIL_MakeVectors( pev->angles );
				pHurt->pev->punchangle.x = 15;
			pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_forward * 100 + gpGlobals->v_up * 50;
				pHurt->TakeDamage( pev, pev, 25, DMG_CLUB );
			}
			break;
		case 1:	
			EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_attackgrowl2.wav", 1, ATTN_NORM );	
								
			if ( pHurt )
			{
				// SOUND HERE!
				UTIL_MakeVectors( pev->angles );
				pHurt->pev->punchangle.x = 15;
			pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_forward * 100 + gpGlobals->v_up * 50;
				pHurt->TakeDamage( pev, pev, 25, DMG_CLUB );
			}
			break;
		case 2:	
			EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_attackgrowl3.wav", 1, ATTN_NORM );	
								
			if ( pHurt )
			{
				// SOUND HERE!
				UTIL_MakeVectors( pev->angles );
				pHurt->pev->punchangle.x = 15;
			pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_forward * 100 + gpGlobals->v_up * 50;
				pHurt->TakeDamage( pev, pev, 25, DMG_CLUB );
			}
			break;
		}
		CBaseMonster :: StartTask ( pTask );
		break;
	}
If you've done this right, it should compile and work PERFECTLY! I'll give my monster.cpp up to this point with it, so if you can't get it right, you can use mine. But do try it yourself first, as that gives a much better feeling =) (I always get happy when my code works)

------------------------------------------------------------------
Part 2 : Random Chattering according to the monsters current state
------------------------------------------------------------------
Ok, let's asume we have world's smartest monster, and he knows how to speak English. You can either record a couple of sounds yourself, or you can just use Barney's sounds. For this tutorial I'll just use Barney's sounds =)
In the class definition :

	void TalkIdle();
	void TalkAlert();
in Schedule_t *CMonster :: GetSchedule( void ) under switch ( m_MonsterState )
	
	case MONSTERSTATE_IDLE:
		{
			TalkIdle();
			break;
		}
	case MONSTERSTATE_ALERT:
		{
			TalkAlert();
			break;
		}
And completely under the file :

void CMonster :: TalkAlert ( void )
{
	switch ( RANDOM_LONG ( 0, 2 ) )
	{
	case 0:	
		EMIT_SOUND( ENT(pev), CHAN_VOICE, "barney/badarea.wav", 1, ATTN_NORM );
		break;
	case 1:
		EMIT_SOUND( ENT(pev), CHAN_VOICE, "barney/badfeeling.wav", 1, ATTN_NORM );
		break;
	case 2:
		EMIT_SOUND( ENT(pev), CHAN_VOICE, "barney/hearsomething.wav", 1, ATTN_NORM );
		break;
	}

	return;
}

void CMonster :: TalkIdle ( void )
{
	switch ( RANDOM_LONG ( 0, 2 ) )
	{
	case 0:	
		EMIT_SOUND( ENT(pev), CHAN_VOICE, "barney/beertopside.wav", 1, ATTN_NORM );
		break;
	case 1:
		EMIT_SOUND( ENT(pev), CHAN_VOICE, "barney/aintscared.wav", 1, ATTN_NORM );
		break;
	case 2:
		EMIT_SOUND( ENT(pev), CHAN_VOICE, "barney/howdy.wav", 1, ATTN_NORM );
		break;
	}

	return;
}
And now he'll talk, although a bit shocky. To fix that you'll have to set a timer. But I won't go into detail for that now. For tutorial Part 4 I'll probably fix this timer stuff and give your monster shooting ability. And if you make a cool monster with special abilities, or any other cool stuff, be sure to tell me. =)

- Deepflame - tox.rwld@rendo.dekooi.nl