/*
================
rvMonsterConvoyGround::State_Torso_BlasterAttack
================
*/
stateResult_t rvMonsterConvoyGround::State_Torso_BlasterAttack ( const stateParms_t& parms ) {
	enum { 
		STAGE_INIT,
		STAGE_ATTACK,
		STAGE_WAIT
	};
	switch ( parms.stage ) {
		case STAGE_INIT:
			shots = (gameLocal.random.RandomInt ( maxShots - minShots ) + minShots) * combat.aggressiveScale;
			return SRESULT_STAGE ( STAGE_ATTACK );

		case STAGE_ATTACK:			
			PlayAnim ( ANIMCHANNEL_TORSO, "range_attack", parms.blendFrames );
			shots--;
			return SRESULT_STAGE ( STAGE_WAIT );
			
		case STAGE_WAIT:
			if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) {
				if ( --shots <= 0 || (IsEnemyVisible() && !enemy.fl.inFov)  ) {
					return SRESULT_DONE;
				}
				return SRESULT_STAGE ( STAGE_ATTACK );
			}
			return SRESULT_WAIT;
	}
	return SRESULT_ERROR; 
}
bool CCSBot::__MAKE_VHOOK(IsEnemyPartVisible)(VisiblePartType part) const
{
	if (!IsEnemyVisible())
		return false;

	return (m_visibleEnemyParts & part) != 0;
}
END_CLASS_STATES

//------------------------------------------------------------
// rvMonsterBossBuddy::State_Torso_RocketAttack
//------------------------------------------------------------
stateResult_t rvMonsterBossBuddy::State_Torso_RocketAttack( const stateParms_t& parms ) 
{
	enum 
	{ 
		STAGE_INIT,
		STAGE_WAITSTART,
		STAGE_LOOP,
		STAGE_WAITLOOP,
		STAGE_WAITEND
	};
	switch ( parms.stage ) 
	{
		case STAGE_INIT:
			DisableAnimState( ANIMCHANNEL_LEGS );
			PlayAnim( ANIMCHANNEL_TORSO, "attack_rocket2start", parms.blendFrames );
			mShots = (gameLocal.random.RandomInt( 3 ) + 2) * combat.aggressiveScale;
			return SRESULT_STAGE( STAGE_WAITSTART );
			
		case STAGE_WAITSTART:
			if ( AnimDone( ANIMCHANNEL_TORSO, 0 )) 
			{
				return SRESULT_STAGE( STAGE_LOOP );
			}
			return SRESULT_WAIT;
		
		case STAGE_LOOP:
			PlayAnim( ANIMCHANNEL_TORSO, "attack_rocket2loop2", 0 );
			return SRESULT_STAGE( STAGE_WAITLOOP );
		
		case STAGE_WAITLOOP:
			if ( AnimDone( ANIMCHANNEL_TORSO, 0 )) 
			{
				if ( --mShots <= 0 ||										// exhausted mShots? .. or
						(!IsEnemyVisible() && rvRandom::irand(0,10)>=8 ) ||	// ... player is no longer visible .. or
						( enemy.ent && DistanceTo(enemy.ent)<256 ) ) 		// ... player is so close, we prolly want to do a melee attack
				{
					PlayAnim( ANIMCHANNEL_TORSO, "attack_rocket2end", 0 );
					return SRESULT_STAGE( STAGE_WAITEND );
				}
				return SRESULT_STAGE( STAGE_LOOP );
			}
			return SRESULT_WAIT;
		
		case STAGE_WAITEND:
			if ( AnimDone( ANIMCHANNEL_TORSO, 4 )) 
			{
				return SRESULT_DONE;
			}
			return SRESULT_WAIT;				
	}
	return SRESULT_ERROR; 
}
/*
================
rvMonsterStreamProtector::State_Torso_LightningAttack
================
*/
stateResult_t rvMonsterStreamProtector::State_Torso_LightningAttack ( const stateParms_t& parms ) {
	enum { 
		STAGE_START,
		STAGE_WAITSTART,
		STAGE_LOOP,
		STAGE_WAITLOOP,
		STAGE_WAITEND
	};
	switch ( parms.stage ) {
		case STAGE_START:
			DisableAnimState ( ANIMCHANNEL_LEGS );
			attackEndTime = gameLocal.time + 5000;
			PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_lightning_start", parms.blendFrames );
			return SRESULT_STAGE ( STAGE_WAITSTART );
			
		case STAGE_WAITSTART:
			if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) {
				return SRESULT_STAGE ( STAGE_LOOP );
			}
			return SRESULT_WAIT;
		
		case STAGE_LOOP:
			PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_lightning_fire", 0 );
			return SRESULT_STAGE ( STAGE_WAITLOOP );
		
		case STAGE_WAITLOOP:
			if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) {
				if ( gameLocal.time > attackEndTime || (IsEnemyVisible() && !enemy.fl.inFov)  ) {
					PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_lightning_end", 0 );
					return SRESULT_STAGE ( STAGE_WAITEND );
				}
				PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_lightning_fire", 0 );
				return SRESULT_STAGE ( STAGE_LOOP );
			}
			return SRESULT_WAIT;
		
		case STAGE_WAITEND:
			if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) {
				return SRESULT_DONE;
			}
			return SRESULT_WAIT;				
	}
	return SRESULT_ERROR; 
}
/*
================
rvMonsterStreamProtector::State_Torso_BlasterAttack
================
*/
stateResult_t rvMonsterStreamProtector::State_Torso_BlasterAttack ( const stateParms_t& parms ) {
	enum { 
		STAGE_INIT,
		STAGE_WAITSTART,
		STAGE_LOOP,
		STAGE_WAITLOOP,
		STAGE_WAITEND
	};
	switch ( parms.stage ) {
		case STAGE_INIT:
			DisableAnimState ( ANIMCHANNEL_LEGS );
			PlayAnim ( ANIMCHANNEL_TORSO, "range_blaster_start", parms.blendFrames );
			shots = (gameLocal.random.RandomInt ( 8 ) + 4) * combat.aggressiveScale;
			return SRESULT_STAGE ( STAGE_WAITSTART );
			
		case STAGE_WAITSTART:
			if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) {
				return SRESULT_STAGE ( STAGE_LOOP );
			}
			return SRESULT_WAIT;
		
		case STAGE_LOOP:
			PlayAnim ( ANIMCHANNEL_TORSO, "range_blaster_fire", 0 );
			return SRESULT_STAGE ( STAGE_WAITLOOP );
		
		case STAGE_WAITLOOP:
			if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) {
				if ( --shots <= 0 || (IsEnemyVisible() && !enemy.fl.inFov)  ) {
					PlayAnim ( ANIMCHANNEL_TORSO, "range_blaster_end", 0 );
					return SRESULT_STAGE ( STAGE_WAITEND );
				}
				return SRESULT_STAGE ( STAGE_LOOP );
			}
			return SRESULT_WAIT;
		
		case STAGE_WAITEND:
			if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) {
				return SRESULT_DONE;
			}
			return SRESULT_WAIT;				
	}
	return SRESULT_ERROR; 
}
/*
================
rvMonsterStreamProtector::State_Torso_PlasmaAttack
================
*/
stateResult_t rvMonsterStreamProtector::State_Torso_PlasmaAttack ( const stateParms_t& parms ) {	
	enum {
		STAGE_START,
		STAGE_START_WAIT,
		STAGE_FIRE,
		STAGE_INITIALFIRE_WAIT,
		STAGE_FIRE_WAIT,
		STAGE_END,
		STAGE_END_WAIT,
	};	
	switch ( parms.stage ) {
		case STAGE_START:
			DisableAnimState ( ANIMCHANNEL_LEGS );

			// Loop the flame animation
			PlayAnim( ANIMCHANNEL_TORSO, "range_plasma_start", parms.blendFrames );

			// Make sure we clean up some things when this state is finished (effects for one)			
			PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishPlasmaAttack", 0, 0, SFLAG_ONCLEAR );
			
			return SRESULT_STAGE ( STAGE_START_WAIT );
		
		case STAGE_START_WAIT:
			if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) {
				return SRESULT_STAGE ( STAGE_FIRE );
			}
			return SRESULT_WAIT;

		case STAGE_FIRE:
			attackEndTime = gameLocal.time + 500;
			attackNextTime = gameLocal.time;
			
			// Flame effect
			PlayEffect ( "fx_plasma_muzzle", jointPlasmaMuzzle, true );
			
			PlayCycle ( ANIMCHANNEL_TORSO, "range_plasma_fire", 0 );
			
			return SRESULT_STAGE ( STAGE_INITIALFIRE_WAIT );

		case STAGE_INITIALFIRE_WAIT:
			if ( gameLocal.time > attackEndTime ) {
				attackEndTime = gameLocal.time + SEC2MS ( 1.0f + gameLocal.random.RandomFloat ( ) * 4.0f );
				return SRESULT_STAGE ( STAGE_FIRE_WAIT );
			}
			// Launch another attack?
			if ( gameLocal.time >= attackNextTime ) {
				Attack ( "plasma", jointPlasmaMuzzle, enemy.ent );
				attackNextTime = gameLocal.time + plasmaAttackRate;
			}
			return SRESULT_WAIT;
			
		case STAGE_FIRE_WAIT:
			// If we have been using plasma too long or havent seen our enemy for at least half a second then
			// stop now.
			if ( gameLocal.time > attackEndTime || gameLocal.time - enemy.lastVisibleTime > 500 || (IsEnemyVisible() && !enemy.fl.inFov) ) {
				StopEffect ( "fx_plasma_muzzle" );
				return SRESULT_STAGE ( STAGE_END );
			}
			// Launch another attack?
			if ( gameLocal.time >= attackNextTime  ) {
				Attack ( "plasma", jointPlasmaMuzzle, enemy.ent );
				attackNextTime = gameLocal.time + plasmaAttackRate;
			}
			return SRESULT_WAIT;
				
		case STAGE_END:
			// End animations
			PlayAnim( ANIMCHANNEL_TORSO, "range_plasma_end", parms.blendFrames );			
			return SRESULT_STAGE ( STAGE_END_WAIT );
		
		case STAGE_END_WAIT:
			if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) {
				return SRESULT_DONE;
			}
			return SRESULT_WAIT;						
	}

	return SRESULT_ERROR;
}
// Update the "looking around" behavior.
void CCSBot::UpdateLookAround(bool updateNow)
{
	// check if looking around has been inhibited
	// Moved inhibit to allow high priority enemy lookats to still occur
	if (gpGlobals->time < m_inhibitLookAroundTimestamp)
		return;

	const float recentThreatTime = 0.25f; // 1.0f;

	// Unless we can hear them moving, in which case look towards the noise
	if (!IsEnemyVisible())
	{
		const float noiseStartleRange = 1000.0f;
		if (CanHearNearbyEnemyGunfire(noiseStartleRange))
		{
			Vector spot = m_noisePosition;
			spot.z += HalfHumanHeight;

			SetLookAt("Check dangerous noise", &spot, PRIORITY_HIGH, recentThreatTime);
			InhibitLookAround(RANDOM_FLOAT(2.0f, 4.0f));

			return;
		}
	}

	// If we recently saw an enemy, look towards where we last saw them
	if (!IsLookingAtSpot(PRIORITY_MEDIUM) && gpGlobals->time - m_lastSawEnemyTimestamp < recentThreatTime)
	{
		ClearLookAt();

		Vector spot = m_lastEnemyPosition;

		// find enemy position on the ground
		if (GetSimpleGroundHeight(&m_lastEnemyPosition, &spot.z))
		{
			spot.z += HalfHumanHeight;
			SetLookAt("Last Enemy Position", &spot, PRIORITY_MEDIUM, RANDOM_FLOAT(2.0f, 3.0f), true);
			return;
		}
	}

	// Look at nearby enemy noises
	if (UpdateLookAtNoise())
		return;

	if (IsNotMoving())
	{
		// if we're sniping, zoom in to watch our approach points
		if (IsUsingSniperRifle())
		{
			// low skill bots don't pre-zoom
			if (GetProfile()->GetSkill() > 0.4f)
			{
				if (!IsViewMoving())
				{
					float range = ComputeWeaponSightRange();
					AdjustZoom(range);
				}
				else
				{
					// zoom out
					if (GetZoomLevel() != NO_ZOOM)
						SecondaryAttack();
				}
			}
		}

		if (m_lastKnownArea == NULL)
			return;

		if (gpGlobals->time < m_lookAroundStateTimestamp)
			return;

		// if we're sniping, switch look-at spots less often
		if (IsUsingSniperRifle())
			m_lookAroundStateTimestamp = gpGlobals->time + RANDOM_FLOAT(5.0f, 10.0f);
		else
			m_lookAroundStateTimestamp = gpGlobals->time + RANDOM_FLOAT(1.0f, 2.0f);

		if (m_approachPointCount == 0)
		{
			ClearLookAt();
			return;
		}

		int which = RANDOM_LONG(0, m_approachPointCount - 1);
		Vector spot = m_approachPoint[ which ];

		// don't look at the floor, look roughly at chest level
		// TODO: If this approach point is very near, this will cause us to aim up in the air if were crouching
		spot.z += HalfHumanHeight;

		SetLookAt("Approach Point (Hiding)", &spot, PRIORITY_LOW);

		return;
	}

	// Glance at "encouter spots" as we move past them
	if (m_spotEncounter)
	{
		// Check encounter spots
		if (!IsSafe() && !IsLookingAtSpot(PRIORITY_LOW))
		{
			// allow a short time to look where we're going
			if (gpGlobals->time < m_spotCheckTimestamp)
				return;

			// TODO: Use skill parameter instead of accuracy

			// lower skills have exponentially longer delays
			float_precision asleep = (1.0f - GetProfile()->GetSkill());
			asleep *= asleep;
			asleep *= asleep;

			m_spotCheckTimestamp = gpGlobals->time + asleep * RANDOM_FLOAT(10.0f, 30.0f);

			// figure out how far along the path segment we are
			Vector delta = m_spotEncounter->path.to - m_spotEncounter->path.from;
			float_precision length = delta.Length();
			float adx = float(Q_abs(int64(delta.x)));
			float ady = float(Q_abs(int64(delta.y)));
			float_precision t;

			if (adx > ady)
				t = (pev->origin.x - m_spotEncounter->path.from.x) / delta.x;
			else
				t = (pev->origin.y - m_spotEncounter->path.from.y) / delta.y;

			// advance parameter a bit so we "lead" our checks
			const float leadCheckRange = 50.0f;
			t += leadCheckRange / length;

			if (t < 0.0f)
				t = 0.0f;
			else if (t > 1.0f)
				t = 1.0f;

			// collect the unchecked spots so far
			const int MAX_DANGER_SPOTS = 8;
			HidingSpot *dangerSpot[MAX_DANGER_SPOTS];
			int dangerSpotCount = 0;
			int dangerIndex = 0;

			const float checkTime = 10.0f;
			const SpotOrder *spotOrder;

			for (SpotOrderList::iterator iter = m_spotEncounter->spotList.begin(); iter != m_spotEncounter->spotList.end(); ++iter)
			{
				spotOrder = &(*iter);

				// if we have seen this spot recently, we don't need to look at it
				if (gpGlobals->time - GetHidingSpotCheckTimestamp(spotOrder->spot) <= checkTime)
					continue;

				if (spotOrder->t > t)
					break;

				dangerSpot[ dangerIndex++ ] = spotOrder->spot;
				if (dangerIndex >= MAX_DANGER_SPOTS)
					dangerIndex = 0;
				if (dangerSpotCount < MAX_DANGER_SPOTS)
					++dangerSpotCount;
			}

			if (dangerSpotCount)
			{
				// pick one of the spots at random
				int which = RANDOM_LONG(0, dangerSpotCount - 1);

				const Vector *checkSpot = dangerSpot[ which ]->GetPosition();

				Vector pos = *checkSpot;
				pos.z += HalfHumanHeight;

				// glance at the spot for minimum time
				SetLookAt("Encounter Spot", &pos, PRIORITY_LOW, 0, true, 10.0f);

				// immediately mark it as "checked", so we don't check it again
				// if we get distracted before we check it - that's the way it goes
				SetHidingSpotCheckTimestamp(dangerSpot[which]);
			}
		}
	}
}
Beispiel #8
0
// Invoked when injured by something
// NOTE: We dont want to directly call Attack() here, or the bots will have super-human reaction times when injured
BOOL CCSBot::TakeDamage(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType)
{
	CBaseEntity *pAttacker = GetClassPtr<CCSEntity>((CBaseEntity *)pevInflictor);

	// if we were attacked by a teammate, rebuke
	if (pAttacker->IsPlayer())
	{
		CBasePlayer *pPlayer = static_cast<CBasePlayer *>(pAttacker);
		if (BotRelationship(pPlayer) == BOT_TEAMMATE && !pPlayer->IsBot())
		{
			GetChatter()->FriendlyFire();
		}

		if (IsEnemy(pPlayer))
		{
			// Track previous attacker so we don't try to panic multiple times for a shotgun blast
			CBasePlayer *lastAttacker = m_attacker;
			float lastAttackedTimestamp = m_attackedTimestamp;

			// keep track of our last attacker
			m_attacker = pPlayer;
			m_attackedTimestamp = gpGlobals->time;

			// no longer safe
			AdjustSafeTime();

			if (!IsSurprised() && (m_attacker != lastAttacker || m_attackedTimestamp != lastAttackedTimestamp))
			{
				// being hurt by an enemy we can't see causes panic
				if (!IsVisible(pPlayer, CHECK_FOV))
				{
					bool bPanic = false;

					// if not attacking anything, look around to try to find attacker
					if (!IsAttacking())
					{
						bPanic = true;
					}
					else
					{
						// we are attacking
						if (!IsEnemyVisible())
						{
							// can't see our current enemy, panic to acquire new attacker
							bPanic = true;
						}
					}

					if (!bPanic)
					{
						float invSkill = 1.0f - GetProfile()->GetSkill();
						float panicChance = invSkill * invSkill * 50.0f;

						if (panicChance > RANDOM_FLOAT(0, 100))
						{
							bPanic = true;
						}
					}

					if (bPanic)
					{
						// can't see our current enemy, panic to acquire new attacker
						Panic(m_attacker);
					}
				}
			}
		}
	}

	// extend
	return CBasePlayer::TakeDamage(pevInflictor, pevAttacker, flDamage, bitsDamageType);
}