/* ================ 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]); } } } }
// 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); }