// #################################### // # # // # Ping of Death - Bot # // # by # // # Markus Klinge aka Count Floyd # // # # // #################################### // // Started from the HPB-Bot Alpha Source // by Botman so Credits for a lot of the basic // HL Server/Client Stuff goes to him // // bot_combat.cpp // // Does Enemy Sensing (spotting), combat movement and firing weapons #include "bot_globals.h" int NumTeammatesNearPos (bot_t *pBot, Vector vecPosition, int iRadius) { int iCount = 0; float fDistance; edict_t *pEdict = pBot->pEdict; int i; for (i = 0; i < gpGlobals->maxClients; i++) { if (!clients[i].IsUsed || !clients[i].IsAlive || (clients[i].iTeam != pBot->bot_team) || (clients[i].pEdict == pEdict)) continue; fDistance = (clients[i].vOrigin - vecPosition).Length (); if (fDistance < iRadius) iCount++; } return (iCount); } int NumEnemiesNearPos (bot_t *pBot, Vector vecPosition, int iRadius) { int iCount = 0; float fDistance; edict_t *pEdict = pBot->pEdict; int i; for (i = 0; i < gpGlobals->maxClients; i++) { if (!clients[i].IsUsed || !clients[i].IsAlive || (clients[i].iTeam == pBot->bot_team)) continue; fDistance = (clients[i].vOrigin - vecPosition).Length (); if (fDistance < iRadius) iCount++; } return (iCount); } bool BotFindEnemy (bot_t *pBot) { // Returns if an Enemy can be seen // FIXME: Bot should lock onto the best shoot position for // a target instead of going through all of them everytime Vector vecEnd; static bool flag = TRUE; float distance; float nearestdistance = pBot->f_view_distance; edict_t *pNewEnemy = NULL; edict_t *pPlayer; edict_t *pEdict = pBot->pEdict; Vector vecVisible; unsigned char cHit; int i, j; pBot->ucVisibility = 0; pBot->vecEnemy = Vector (0, 0, 0); // We're blind and can't see anything !! if (pBot->f_blind_time > gpGlobals->time) return (FALSE); // Setup Potentially Visible Set for this Bot Vector vecOrg = pEdict->v.origin + pEdict->v.view_ofs; if (pEdict->v.flags & FL_DUCKING) vecOrg = vecOrg + (VEC_HULL_MIN - VEC_DUCK_HULL_MIN); unsigned char *pvs = ENGINE_SET_PVS ((float *) &vecOrg); // Clear suspected Flag pBot->iStates &= ~STATE_SUSPECTENEMY; if (!FNullEnt (pBot->pBotEnemy) && (pBot->fEnemyUpdateTime > gpGlobals->time)) { pPlayer = pBot->pBotEnemy; if (IsAlive (pPlayer)) { vecEnd = pPlayer->v.origin + pPlayer->v.view_ofs; if (FInViewCone (&vecEnd, pEdict) && FBoxVisible (pEdict, pPlayer, &vecVisible, &cHit)) { pNewEnemy = pPlayer; pBot->vecEnemy = vecVisible; pBot->ucVisibility = cHit; } } } if (FNullEnt (pNewEnemy)) { pBot->fEnemyUpdateTime = gpGlobals->time + 0.5; // search the world for players... for (i = 0; i < gpGlobals->maxClients; i++) { if (!clients[i].IsUsed || !clients[i].IsAlive || (clients[i].iTeam == pBot->bot_team) || (clients[i].pEdict == pEdict)) continue; pPlayer = clients[i].pEdict; // Let the Engine check if this Player is potentially visible if (!ENGINE_CHECK_VISIBILITY (pPlayer, pvs)) continue; vecEnd = pPlayer->v.origin + pPlayer->v.view_ofs; // see if bot can see the player... if (FInViewCone (&vecEnd, pEdict) && FBoxVisible (pEdict, pPlayer, &vecVisible, &cHit)) { distance = (pPlayer->v.origin - pEdict->v.origin).Length (); if (distance < nearestdistance) { nearestdistance = distance; pNewEnemy = pPlayer; pBot->vecEnemy = vecVisible; pBot->ucVisibility = cHit; // On Assault Maps target VIP first ! if ((g_iMapType & MAP_AS) && (strcmp ("vip", INFOKEY_VALUE (GET_INFOKEYBUFFER (pEdict), "model")) == 0)) // Is VIP ? break; } } } } if (pNewEnemy) { g_bBotsCanPause = TRUE; pBot->iAimFlags |= AIM_ENEMY; if (pNewEnemy == pBot->pBotEnemy) { // if enemy is still visible and in field of view, keep it pBot->f_bot_see_enemy_time = gpGlobals->time; // keep track of when we last saw an enemy // Zero out reaction time pBot->f_actual_reaction_time = 0.0; pBot->pLastEnemy = pNewEnemy; pBot->vecLastEnemyOrigin = pBot->vecEnemy; return (TRUE); } else { if ((pBot->f_bot_see_enemy_time + 3.0 < gpGlobals->time) && ((pEdict->v.weapons & (1 << CS_WEAPON_C4)) || BotHasHostage (pBot) || !FNullEnt (pBot->pBotUser))) BotPlayRadioMessage (pBot, RADIO_ENEMYSPOTTED); pBot->f_enemy_surprise_time = gpGlobals->time + pBot->f_actual_reaction_time; // Zero out reaction time pBot->f_actual_reaction_time = 0.0; pBot->pBotEnemy = pNewEnemy; pBot->pLastEnemy = pNewEnemy; pBot->vecLastEnemyOrigin = pBot->vecEnemy; pBot->vecEnemyVelocity = Vector (0, 0, 0); pBot->fEnemyReachableTimer = 0.0; // keep track of when we last saw an enemy pBot->f_bot_see_enemy_time = gpGlobals->time; // Now alarm all Teammates who see this Bot & // don't have an actual Enemy of the Bots Enemy // Should simulate human players seeing a Teammate firing bot_t *pFriendlyBot; for (j = 0; j < gpGlobals->maxClients; j++) { if (!clients[j].IsUsed || !clients[j].IsAlive || (clients[j].iTeam != pBot->bot_team) || (clients[j].pEdict == pEdict)) continue; pFriendlyBot = UTIL_GetBotPointer (clients[j].pEdict); if (pFriendlyBot != NULL) { if ((pFriendlyBot->f_bot_see_enemy_time + 3.0 < gpGlobals->time) || FNullEnt (pFriendlyBot->pLastEnemy)) { if (FVisible (pEdict->v.origin, pFriendlyBot->pEdict)) { pFriendlyBot->pLastEnemy = pNewEnemy; pFriendlyBot->vecLastEnemyOrigin = pBot->vecLastEnemyOrigin; pFriendlyBot->f_bot_see_enemy_time = gpGlobals->time; } } } } return (TRUE); } } else if (!FNullEnt (pBot->pBotEnemy)) { pNewEnemy = pBot->pBotEnemy; pBot->pLastEnemy = pNewEnemy; if (!IsAlive (pNewEnemy)) { pBot->pBotEnemy = NULL; return (FALSE); } // If no Enemy visible check if last one shootable thru Wall int iShootThruFreq = BotAimTab[pBot->bot_skill / 20].iSeenShootThruProb; if (g_bShootThruWalls && (RANDOM_LONG (1, 100) < iShootThruFreq) && WeaponShootsThru (pBot->current_weapon.iId)) { if (IsShootableThruObstacle (pEdict, pNewEnemy->v.origin)) { pBot->f_bot_see_enemy_time = gpGlobals->time; pBot->iStates |= STATE_SUSPECTENEMY; pBot->iAimFlags |= AIM_LASTENEMY; pBot->pLastEnemy = pNewEnemy; pBot->vecLastEnemyOrigin = pNewEnemy->v.origin; return (TRUE); } } return (FALSE); } return (FALSE); } Vector BotBodyTarget (edict_t *pBotEnemy, bot_t *pBot) { // Returns the aiming Vector for an Enemy // FIXME: Doesn't take the spotted part of the enemy Vector target; unsigned char ucVis = pBot->ucVisibility; edict_t *pEdict = pBot->pEdict; Vector vecVel = pBot->vecEnemyVelocity; float fOffset; // No Up/Down Compensation vecVel.z = 0.0; float fSkillMult = 0.1 - ((float) pBot->bot_skill / 1000.0); vecVel = vecVel * fSkillMult; float fDistance = (pBotEnemy->v.origin - pEdict->v.origin).Length (); // More Precision if using sniper weapon if (BotUsesSniper (pBot)) vecVel = vecVel / 2; // Waist Visible ? else if (ucVis & WAIST_VISIBLE) { // Use Waist as Target for big distances if (fDistance > 1500) ucVis &= ~HEAD_VISIBLE; } // If we only suspect an Enemy behind a Wall take the worst Skill if (pBot->iStates & STATE_SUSPECTENEMY) { target = pBotEnemy->v.origin; target.x = target.x + RANDOM_FLOAT (-40.0, 40.0); target.y = target.y + RANDOM_FLOAT (-40.0, 40.0); target.z = target.z + RANDOM_FLOAT (-50.0, 50.0); } else { if ((ucVis & HEAD_VISIBLE) && (ucVis & WAIST_VISIBLE)) { if (RANDOM_LONG (1, 100) < BotAimTab[pBot->bot_skill / 20].iHeadShot_Frequency) target = pBotEnemy->v.origin + pBotEnemy->v.view_ofs; // aim for the head else target = pBotEnemy->v.origin + (pBotEnemy->v.view_ofs / 4); // aim for body } else if (ucVis & HEAD_VISIBLE) { target = pBotEnemy->v.origin + pBotEnemy->v.view_ofs; // aim for the head target.z -= 8.0; } else if (ucVis & WAIST_VISIBLE) target = pBotEnemy->v.origin + (pBotEnemy->v.view_ofs / 4); // aim for body else if (ucVis & CUSTOM_VISIBLE) target = pBot->vecEnemy; // aim for custom part // Something went wrong - use last enemy origin else { assert (pBot == NULL); target = pBot->vecLastEnemyOrigin; } } if ((pEdict->v.fov == 10) || (pEdict->v.fov == 15)) fOffset = fDistance / 4000; else if ((pEdict->v.fov == 40) || (pEdict->v.fov == 55)) fOffset = fDistance / 2000; else fOffset = fDistance / 1000; target.x += RANDOM_FLOAT (-(fOffset * BotAimTab[pBot->bot_skill / 20].fAim_X), (fOffset * BotAimTab[pBot->bot_skill / 20].fAim_X)); target.y += RANDOM_FLOAT (-(fOffset * BotAimTab[pBot->bot_skill / 20].fAim_Y), (fOffset * BotAimTab[pBot->bot_skill / 20].fAim_Y)); target.z += RANDOM_FLOAT (-(fOffset * BotAimTab[pBot->bot_skill / 20].fAim_Z), (fOffset * BotAimTab[pBot->bot_skill / 20].fAim_Z)); pBot->vecEnemyVelocity = -pBotEnemy->v.velocity; pBot->vecEnemy = target; return (target); } bool WeaponShootsThru (int iId) { // Returns if Weapon can pierce thru a wall int i = 0; while (cs_weapon_select[i].iId) { if (cs_weapon_select[i].iId == iId) { if (cs_weapon_select[i].bShootsThru) return (TRUE); return (FALSE); } i++; } return (FALSE); } bool WeaponIsSniper (int iId) { if ((iId == CS_WEAPON_AWP) || (iId == CS_WEAPON_G3SG1) || (iId == CS_WEAPON_SCOUT || iId == CS_WEAPON_SG550)) return (TRUE); return (FALSE); } bool FireHurtsFriend (bot_t *pBot, float fDistance) { edict_t *pEdict = pBot->pEdict; edict_t *pPlayer; int i; // search the world for players... for (i = 0; i < gpGlobals->maxClients; i++) { if (!clients[i].IsUsed || !clients[i].IsAlive || (clients[i].iTeam != pBot->bot_team) || (clients[i].pEdict == pEdict)) continue; pPlayer = clients[i].pEdict; if (GetShootingConeDeviation (pEdict, &pPlayer->v.origin) > 0.90) { if ((pPlayer->v.origin - pEdict->v.origin).Length () <= fDistance) return (TRUE); } } return (FALSE); } bool IsShootableThruObstacle (edict_t *pEdict, Vector vecDest) { // Returns if enemy can be shoot through some obstacle // TODO: After seeing the disassembled CS Routine it could be speedup and simplified a lot Vector vecSrc = pEdict->v.origin + pEdict->v.view_ofs; Vector vecDir = (vecDest - vecSrc).Normalize (); // 1 unit long Vector vecPoint; int iThickness = 0; int iHits = 0; edict_t *pentIgnore = pEdict; TraceResult tr; UTIL_TraceLine (vecSrc, vecDest, ignore_monsters, ignore_glass, pentIgnore, &tr); while ((tr.flFraction != 1.0) && (iHits < 3)) { iHits++; iThickness++; vecPoint = tr.vecEndPos + vecDir; while ((POINT_CONTENTS (vecPoint) == CONTENTS_SOLID) && (iThickness < 64)) { vecPoint = vecPoint + vecDir; iThickness++; } UTIL_TraceLine (vecPoint, vecDest, ignore_monsters, ignore_glass, pentIgnore, &tr); } if ((iHits < 3) && (iThickness < 64)) { float f_distance = (vecDest - vecPoint).Length (); if (f_distance < 112) return (TRUE); } return (FALSE); } bool BotDoFirePause (bot_t *pBot, float fDistance, bot_fire_delay_t* pDelay) { // Returns true if Bot needs to pause between firing to compensate for // punchangle & weapon spread if (fDistance < MIN_BURST_DISTANCE) return (FALSE); else if (pBot->fTimeFirePause > gpGlobals->time) { pBot->f_shoot_time = gpGlobals->time; return (TRUE); } else if (pDelay->iMaxFireBullets + RANDOM_LONG (0, 1) <= pBot->iBurstShotsFired) { pBot->fTimeFirePause = gpGlobals->time + (fDistance / pDelay->fMinBurstPauseFactor); pBot->f_shoot_time = gpGlobals->time; pBot->iBurstShotsFired = 0; return (TRUE); } return (FALSE); } bool BotFireWeapon (Vector v_enemy, bot_t *pBot) { // BotFireWeapon will return (TRUE) if weapon was fired, FALSE otherwise bot_weapon_select_t *pSelect = NULL; bot_fire_delay_t *pDelay = NULL; int iId; int select_index = 0; int iChosenWeaponIndex = 0; edict_t *pEdict = pBot->pEdict; edict_t *pEnemy = pBot->pBotEnemy; float distance = v_enemy.Length (); // how far away is the enemy? // Currently switching Weapon ? if ((pBot->current_weapon.iId == CS_WEAPON_INSWITCH) || pBot->bUsingGrenade) return (FALSE); // Don't shoot through TeamMates ! if (FireHurtsFriend (pBot,distance)) return (FALSE); iId = pBot->current_weapon.iId; // TODO: Ammo Check doesn't always work in CS !! It seems to be because // AmmoX and CurrentWeapon Messages don't work very well together if ((pBot->current_weapon.iClip == 0) && (weapon_defs[iId].iAmmo1 != -1) && (pBot->current_weapon.iAmmo1 > 0)) { if (pBot->bIsReloading) return (FALSE); pEdict->v.button |= IN_RELOAD; // reload pBot->bIsReloading = TRUE; return (FALSE); } pBot->bIsReloading = FALSE; pSelect = &cs_weapon_select[0]; pDelay = &cs_fire_delay[0]; if (!FNullEnt (pBot->pBotEnemy)) { bool bUseKnife = FALSE; // Use Knife if near and good skill (l33t dude!) if (g_bJasonMode) bUseKnife = TRUE; if (pBot->bot_skill > 80) { if ((distance < 100) && (pBot->pEdict->v.health > 80) /*&& (pBot->pEdict->v.health > pEnemy->v.health)*/ && !IsGroupOfEnemies (pBot, pEdict->v.origin)) bUseKnife = TRUE; } if (bUseKnife) goto WeaponSelectEnd; } // loop through all the weapons until terminator is found... while (pSelect[select_index].iId) { // is the bot NOT carrying this weapon? if (!(pEdict->v.weapons & (1 << pSelect[select_index].iId))) { select_index++; // skip to next weapon continue; } iId = pSelect[select_index].iId; // Check if it's the currently used weapon because // otherwise Ammo in Clip will be ignored if ((iId == pBot->current_weapon.iId) && (pBot->current_weapon.iClip > 0 || weapon_defs[iId].iAmmo1 == -1)) iChosenWeaponIndex = select_index; // is primary percent less than weapon primary percent AND // no ammo required for this weapon OR // enough ammo available to fire AND // the bot is far enough away to use primary fire AND // the bot is close enough to the enemy to use primary fire else if (((weapon_defs[iId].iAmmo1 == -1) || (pBot->m_rgAmmo[weapon_defs[iId].iAmmo1] >= pSelect[select_index].min_primary_ammo)) && (distance >= pSelect[select_index].primary_min_distance) && (distance <= pSelect[select_index].primary_max_distance)) iChosenWeaponIndex = select_index; select_index++; } select_index = iChosenWeaponIndex; WeaponSelectEnd: iId = pSelect[select_index].iId; // select this weapon if it isn't already selected if (pBot->current_weapon.iId != iId) { if (pBot->current_weapon.iId != CS_WEAPON_INSWITCH) { SelectWeaponByName(pBot, pSelect[select_index].weapon_name); // Reset Burst Fire Variables pBot->fTimeLastFired = 0.0; pBot->fTimeFirePause = 0.0; pBot->iBurstShotsFired = 0; } return (FALSE); } if (BotHasShieldDrawn (pBot)) pEdict->v.button |= IN_ATTACK2; if (BotUsesSniper (pBot) && (pBot->f_zoomchecktime < gpGlobals->time)) { // Check Distance for correct Sniper Zooming int iZoomMagnification; if (distance < 1500) iZoomMagnification = 1; else iZoomMagnification = 2; bool bZoomChange = FALSE; switch(iZoomMagnification) { case 1: if ((pEdict->v.fov != 40) && (pEdict->v.fov != 55)) bZoomChange = TRUE; break; case 2: if ((pEdict->v.fov != 10) && (pEdict->v.fov != 15)) bZoomChange = TRUE; } if (bZoomChange) pEdict->v.button |= IN_ATTACK2; pBot->f_zoomchecktime = gpGlobals->time + 2.0; } if (pDelay[select_index].iId != iId) return (FALSE); // Need to care for burst fire ? if (distance < MIN_BURST_DISTANCE || pBot->f_blind_time > gpGlobals->time) { if (iId == CS_WEAPON_KNIFE) { if (distance < 64) { if (RANDOM_LONG (1, 100) > 60) pEdict->v.button |= IN_ATTACK; // use primary attack else pEdict->v.button |= IN_ATTACK2; // use secondary attack } } else { // If Automatic Weapon, just press attack if (pSelect[select_index].primary_fire_hold) pEdict->v.button |= IN_ATTACK; // if not, toggle buttons else { if ((pEdict->v.oldbuttons & IN_ATTACK) == 0) pEdict->v.button |= IN_ATTACK; } } pBot->f_shoot_time = gpGlobals->time; } else { if (BotDoFirePause (pBot, distance, &pDelay[select_index])) return (FALSE); // Don't attack with knife over long distance if (iId == CS_WEAPON_KNIFE) { pBot->f_shoot_time = gpGlobals->time; return (FALSE); } if (pSelect[select_index].primary_fire_hold) { pBot->f_shoot_time = gpGlobals->time; pEdict->v.button |= IN_ATTACK; // use primary attack } else { pEdict->v.button |= IN_ATTACK; // use primary attack int skill = abs ((pBot->bot_skill / 20) - 5); float base_delay, min_delay, max_delay; base_delay = pDelay[select_index].primary_base_delay; min_delay = pDelay[select_index].primary_min_delay[skill]; max_delay = pDelay[select_index].primary_max_delay[skill]; pBot->f_shoot_time = gpGlobals->time + base_delay + RANDOM_FLOAT (min_delay, max_delay); } } return (TRUE); } void BotFocusEnemy (bot_t *pBot) { if (FNullEnt (pBot->pBotEnemy)) return; edict_t *pEdict = pBot->pEdict; // aim for the head and/or body Vector vecEnemy = BotBodyTarget (pBot->pBotEnemy, pBot); pBot->vecLookAt = vecEnemy; if (pBot->f_enemy_surprise_time > gpGlobals->time) return; vecEnemy = vecEnemy - GetGunPosition(pEdict); // vecEnemy.z = 0; // ignore z component (up & down) float f_distance = vecEnemy.Length (); // how far away is the enemy scum? if (f_distance < 128) { if (pBot->current_weapon.iId == CS_WEAPON_KNIFE) { if (f_distance < 80) pBot->bWantsToFire = TRUE; } else pBot->bWantsToFire = TRUE; } else { float flDot = GetShootingConeDeviation (pEdict, &pBot->vecEnemy); if (flDot < 0.90) pBot->bWantsToFire = FALSE; else { float flEnemyDot = GetShootingConeDeviation (pBot->pBotEnemy, &pEdict->v.origin); // Enemy faces Bot ? if (flEnemyDot >= 0.90) pBot->bWantsToFire = TRUE; else { if (flDot > 0.99) pBot->bWantsToFire = TRUE; else pBot->bWantsToFire = FALSE; } } } } // Does the (unwaypointed) attack movement void BotDoAttackMovement (bot_t *pBot) { // No enemy ? No need to do strafing if (FNullEnt (pBot->pBotEnemy)) return; //pBot->dest_origin = pBot->pBotEnemy->v.origin; float f_distance; TraceResult tr; edict_t *pEdict = pBot->pEdict; Vector vecEnemy = pBot->vecLookAt; vecEnemy = vecEnemy - GetGunPosition(pEdict); vecEnemy.z = 0; // ignore z component (up & down) f_distance = vecEnemy.Length (); // how far away is the enemy scum? if (pBot->fTimeWaypointMove + pBot->fTimeFrameInterval + 0.5 < gpGlobals->time) { int iId = pBot->current_weapon.iId; int iApproach; bool bUsesSniper = BotUsesSniper(pBot); // If suspecting Enemy stand still if (pBot->iStates & STATE_SUSPECTENEMY) iApproach = 49; // If reloading or VIP back off else if (pBot->bIsReloading || pBot->bIsVIP) iApproach = 29; else if (iId == CS_WEAPON_KNIFE) // Knife ? iApproach = 100; else { iApproach = pBot->pEdict->v.health * pBot->fAgressionLevel; if (bUsesSniper && (iApproach > 49)) iApproach = 49; } if (iApproach < 30) { pBot->f_move_speed = -pEdict->v.maxspeed; BotGetSafeTask(pBot)->iTask = TASK_SEEKCOVER; BotGetSafeTask(pBot)->bCanContinue = TRUE; BotGetSafeTask(pBot)->fDesire = TASKPRI_ATTACK + 1; } else if (iApproach < 50) pBot->f_move_speed = 0.0; else pBot->f_move_speed = pEdict->v.maxspeed; if ((f_distance < 96) && (iId != CS_WEAPON_KNIFE)) pBot->f_move_speed = -pEdict->v.maxspeed; bool bUsesRifle = BotUsesRifle (pBot); if (bUsesRifle) { if (pBot->f_lastfightstylecheck + 3.0 < gpGlobals->time) { int iRand = RANDOM_LONG (1, 100); if (f_distance < 500) pBot->byFightStyle = 0; else if (f_distance < 1024) { if (iRand < 50) pBot->byFightStyle = 0; else pBot->byFightStyle = 1; } else { if (iRand < 90) pBot->byFightStyle = 1; else pBot->byFightStyle = 0; } pBot->f_lastfightstylecheck = gpGlobals->time; } } else pBot->byFightStyle = 0; if ((pBot->bot_skill > 60) && (pBot->byFightStyle == 0)) { if (pBot->f_StrafeSetTime < gpGlobals->time) { Vector2D vec2DirToPoint; Vector2D vec2RightSide; // to start strafing, we have to first figure out if the target is on the left side or right side MAKE_VECTORS (pBot->pBotEnemy->v.v_angle); vec2DirToPoint = (pEdict->v.origin - pBot->pBotEnemy->v.origin).Make2D ().Normalize (); vec2RightSide = gpGlobals->v_right.Make2D ().Normalize (); if (DotProduct (vec2DirToPoint, vec2RightSide) < 0) pBot->byCombatStrafeDir = 1; else pBot->byCombatStrafeDir = 0; if (RANDOM_LONG (1, 100) < 30) pBot->byCombatStrafeDir ^= 1; pBot->f_StrafeSetTime = gpGlobals->time + RANDOM_FLOAT (0.5, 3.0); } if (pBot->byCombatStrafeDir == 0) { if (!BotCheckWallOnLeft (pBot)) pBot->f_sidemove_speed = -pEdict->v.maxspeed; else { pBot->byCombatStrafeDir ^= 1; pBot->f_StrafeSetTime = gpGlobals->time + 1.0; } } else { if (!BotCheckWallOnRight (pBot)) pBot->f_sidemove_speed = pEdict->v.maxspeed; else { pBot->byCombatStrafeDir ^= 1; pBot->f_StrafeSetTime = gpGlobals->time + 1.0; } } if (pBot->bot_skill > 80) { if ((pBot->f_jumptime + 2.0 < gpGlobals->time) && (pEdict->v.flags & FL_ONGROUND)) { if ((RANDOM_LONG (1, 100) < 5) && (pEdict->v.velocity.Length2D () > 150)) pEdict->v.button |= IN_JUMP; } } } else if (pBot->byFightStyle == 1) { pEdict->v.button |= IN_DUCK; if (pBot->byCombatStrafeDir == 0) { if (!BotCheckWallOnLeft (pBot)) pBot->f_sidemove_speed = -pEdict->v.maxspeed; else pBot->byCombatStrafeDir ^= 1; } else { if (!BotCheckWallOnRight (pBot)) pBot->f_sidemove_speed = pEdict->v.maxspeed; else pBot->byCombatStrafeDir ^= 1; } // Don't run towards enemy if (pBot->f_move_speed > 0) pBot->f_move_speed = 0; } } else if ((pBot->bot_skill > 80) && (pEdict->v.flags & FL_ONGROUND)) { if (f_distance < 500) { if ((RANDOM_LONG (1, 100) < 5) && (pEdict->v.velocity.Length2D () > 150)) pEdict->v.button |= IN_JUMP; } else pEdict->v.button |= IN_DUCK; } if (pBot->bIsReloading) pBot->f_move_speed = -pEdict->v.maxspeed; if (!pBot->bInWater && !pBot->bOnLadder && ((pBot->f_move_speed != 0) || (pBot->f_sidemove_speed != 0))) { float fTimeRange = pBot->fTimeFrameInterval; Vector vecForward = (gpGlobals->v_forward * pBot->f_move_speed) * 0.2; Vector vecSide = (gpGlobals->v_right * pBot->f_sidemove_speed) * 0.2; Vector vecTargetPos = pEdict->v.origin + vecForward+vecSide + (pEdict->v.velocity * fTimeRange); if (IsDeadlyDrop (pBot, vecTargetPos)) { pBot->f_sidemove_speed = -pBot->f_sidemove_speed; pBot->f_move_speed = -pBot->f_move_speed; pEdict->v.button &= ~IN_JUMP; } } return; } bool BotHasPrimaryWeapon (bot_t *pBot) { bot_weapon_select_t *pSelect = &cs_weapon_select[7]; int iWeapons = pBot->pEdict->v.weapons; // loop through all the weapons until terminator is found... while (pSelect->iId) { // is the bot carrying this weapon? if (iWeapons & (1 << pSelect->iId)) return (TRUE); pSelect++; } return (FALSE); } bool BotHasShield (bot_t *pBot) { // code courtesy of Wei Mingzhi. He stuffed all the bot stuff into a CBaseBot class, but // I'm a lazy bugger so I won't do the same. return (strncmp (STRING (pBot->pEdict->v.viewmodel), "models/shield/v_shield_", 23) == 0); } bool BotHasShieldDrawn (bot_t *pBot) { // code courtesy of Wei Mingzhi. BTW check out his YaPB ! if (!BotHasShield (pBot)) return (FALSE); return ((pBot->pEdict->v.weaponanim == 6) || (pBot->pEdict->v.weaponanim == 7)); } bool BotUsesSniper (bot_t *pBot) { int iId = pBot->current_weapon.iId; if ((iId == CS_WEAPON_AWP) || (iId == CS_WEAPON_G3SG1) || (iId == CS_WEAPON_SCOUT) || (iId == CS_WEAPON_SG550)) return (TRUE); return (FALSE); } bool BotUsesRifle (bot_t *pBot) { bot_weapon_select_t *pSelect = &cs_weapon_select[0]; int iId = pBot->current_weapon.iId; int iCount = 0; while (pSelect->iId) { if (iId == pSelect->iId) break; pSelect++; iCount++; } if (pSelect->iId && (iCount > 13)) return (TRUE); return (FALSE); } int BotCheckGrenades (bot_t *pBot) { int weapons = pBot->pEdict->v.weapons; if (weapons & (1 << CS_WEAPON_HEGRENADE)) return (CS_WEAPON_HEGRENADE); else if (weapons & (1 << CS_WEAPON_FLASHBANG)) return (CS_WEAPON_FLASHBANG); else if (weapons & (1 << CS_WEAPON_SMOKEGRENADE)) return (CS_WEAPON_SMOKEGRENADE); return (-1); } void BotSelectBestWeapon(bot_t *pBot) { bot_weapon_select_t *pSelect = &cs_weapon_select[0]; int select_index = 0; int iChosenWeaponIndex = 0; int iId; int iWeapons = pBot->pEdict->v.weapons; // loop through all the weapons until terminator is found... while (pSelect[select_index].iId) { // is the bot NOT carrying this weapon? if (!(iWeapons & (1 << pSelect[select_index].iId))) { select_index++; // skip to next weapon continue; } iId = pSelect[select_index].iId; // is primary percent less than weapon primary percent AND // no ammo required for this weapon OR // enough ammo available to fire AND // the bot is far enough away to use primary fire AND // the bot is close enough to the enemy to use primary fire if (((weapon_defs[iId].iAmmo1 == -1) || (pBot->m_rgAmmo[weapon_defs[iId].iAmmo1] >= pSelect[select_index].min_primary_ammo))) iChosenWeaponIndex = select_index; select_index++; } iChosenWeaponIndex %= NUM_WEAPONS + 1; if (g_bJasonMode) select_index = 0; else select_index = iChosenWeaponIndex; iId = pSelect[select_index].iId; // select this weapon if it isn't already selected if (pBot->current_weapon.iId != iId) SelectWeaponByName (pBot, pSelect[select_index].weapon_name); } int HighestWeaponOfEdict (edict_t *pEdict) { bot_weapon_select_t *pSelect = &cs_weapon_select[0]; int iWeapons = pEdict->v.weapons; int iNum = 0; int i = 0; // loop through all the weapons until terminator is found... while (pSelect->iId) { // is the bot carrying this weapon? if ((iWeapons & (1 << pSelect->iId))) iNum = i; i++; pSelect++; } return (iNum); } void SelectWeaponByName (bot_t *pBot, char *pszName) { pBot->current_weapon.iId = CS_WEAPON_INSWITCH; pBot->fTimeWeaponSwitch = gpGlobals->time; FakeClientCommand (pBot->pEdict, pszName); } void SelectWeaponbyNumber (bot_t *pBot, int iNum) { pBot->current_weapon.iId = CS_WEAPON_INSWITCH; pBot->fTimeWeaponSwitch = gpGlobals->time; FakeClientCommand (pBot->pEdict, (char *) &cs_weapon_select[iNum].weapon_name); } void BotCommandTeam (bot_t *pBot) { // Prevent spamming if (pBot->fTimeTeamOrder + 5.0 < gpGlobals->time) { bool bMemberNear = FALSE; edict_t *pTeamEdict; edict_t *pEdict = pBot->pEdict; int ind; // Search Teammates seen by this Bot for (ind = 0; ind < gpGlobals->maxClients; ind++) { if (!clients[ind].IsUsed || !clients[ind].IsAlive || (clients[ind].iTeam != pBot->bot_team) || (clients[ind].pEdict == pEdict)) continue; pTeamEdict = clients[ind].pEdict; if (BotEntityIsVisible (pBot, pTeamEdict->v.origin)) { bMemberNear = TRUE; break; } } // Has Teammates ? if (bMemberNear) { if ((pBot->bot_personality == 1) && !pBot->bIsVIP) BotPlayRadioMessage (pBot, RADIO_STORMTHEFRONT); else BotPlayRadioMessage (pBot, RADIO_FALLBACK); } else BotPlayRadioMessage (pBot, RADIO_TAKINGFIRE); pBot->fTimeTeamOrder = gpGlobals->time; } } bool IsGroupOfEnemies (bot_t *pBot, Vector vLocation) { edict_t *pPlayer; edict_t *pEdict = pBot->pEdict; int iNumPlayers = 0; float distance; int i; // search the world for enemy players... for (i = 0; i < gpGlobals->maxClients; i++) { if (!clients[i].IsUsed || !clients[i].IsAlive || (clients[i].pEdict == pEdict)) continue; pPlayer = clients[i].pEdict; distance = (pPlayer->v.origin - vLocation).Length (); if (distance < 256) { // don't target our teammates... if (clients[i].iTeam == pBot->bot_team) return (FALSE); iNumPlayers++; } } if (iNumPlayers > 1) return (TRUE); return (FALSE); } // // VecCheckToss - returns the velocity at which an object should be lobbed from vecspot1 to land near vecspot2. // returns g_vecZero if toss is not feasible. // Vector VecCheckToss (edict_t *pEdict, const Vector &vecSpot1, Vector vecSpot2) { float flGravityAdj = 0.5; TraceResult tr; Vector vecMidPoint; // halfway point between Spot1 and Spot2 Vector vecApex; // highest point Vector vecScale; Vector vecGrenadeVel; Vector vecTemp; float flGravity = CVAR_GET_FLOAT ("sv_gravity") * flGravityAdj; vecSpot2 = vecSpot2 - pEdict->v.velocity; vecSpot2.z -= 15.0; if (vecSpot2.z - vecSpot1.z > 500) return g_vecZero; // to high, fail // calculate the midpoint and apex of the 'triangle' // UNDONE: normalize any Z position differences between spot1 and spot2 so that triangle is always RIGHT // How much time does it take to get there? // get a rough idea of how high it can be thrown vecMidPoint = vecSpot1 + (vecSpot2 - vecSpot1) * 0.5; TRACE_LINE (vecMidPoint, vecMidPoint + Vector (0, 0, 500), ignore_monsters, pEdict, &tr); if (tr.flFraction != 1.0) { vecMidPoint = tr.vecEndPos; vecMidPoint.z = tr.pHit->v.absmin.z; vecMidPoint.z--; } if (vecMidPoint.z < vecSpot2.z) return (g_vecZero); // to not enough space, fail // How high should the grenade travel to reach the apex float distance1 = (vecMidPoint.z - vecSpot1.z); float distance2 = (vecMidPoint.z - vecSpot2.z); // How long will it take for the grenade to travel this distance float time1 = sqrt (distance1 / (0.5 * flGravity)); float time2 = sqrt (distance2 / (0.5 * flGravity)); if (time1 < 0.1) return (g_vecZero); // too close // how hard to throw sideways to get there in time. vecGrenadeVel = (vecSpot2 - vecSpot1) / (time1 + time2); // how hard upwards to reach the apex at the right time. vecGrenadeVel.z = flGravity * time1; // find the apex vecApex = vecSpot1 + vecGrenadeVel * time1; vecApex.z = vecMidPoint.z; TRACE_HULL (vecSpot1, vecApex, dont_ignore_monsters, head_hull, pEdict, &tr); if (tr.flFraction != 1.0 || tr.fAllSolid) return (g_vecZero); // fail! TRACE_HULL (vecSpot2, vecApex, ignore_monsters, head_hull, pEdict, &tr); if (tr.flFraction != 1.0) { Vector vecDir = (vecApex - vecSpot2).Normalize (); float n = -DotProduct (tr.vecPlaneNormal, vecDir); if ((n > 0.7) || (tr.flFraction < 0.8)) // 60 degrees return (g_vecZero); } return (vecGrenadeVel * 0.6667); } // // VecCheckThrow - returns the velocity vector at which an object should be thrown from vecspot1 to hit vecspot2. // returns g_vecZero if throw is not feasible. // Vector VecCheckThrow (edict_t *pEdict, const Vector &vecSpot1, Vector vecSpot2, float flSpeed) { float flGravityAdj = 1.0; float flGravity = CVAR_GET_FLOAT ("sv_gravity") * flGravityAdj; vecSpot2 = vecSpot2 - pEdict->v.velocity; Vector vecGrenadeVel = (vecSpot2 - vecSpot1); // throw at a constant time float time = 1.0; vecGrenadeVel = vecGrenadeVel * (1.0 / time); // adjust upward toss to compensate for gravity loss vecGrenadeVel.z += flGravity * time * 0.5; Vector vecApex = vecSpot1 + (vecSpot2 - vecSpot1) * 0.5; vecApex.z += 0.5 * flGravity * (time * 0.5) * (time * 0.5); TraceResult tr; TRACE_HULL (vecSpot1, vecApex, dont_ignore_monsters, head_hull, pEdict, &tr); if (tr.flFraction != 1.0 || tr.fAllSolid) return (g_vecZero); // fail! TRACE_HULL (vecSpot2, vecApex, ignore_monsters, head_hull, pEdict, &tr); if (tr.flFraction != 1.0) { Vector vecDir = (vecApex - vecSpot2).Normalize (); float n = -DotProduct(tr.vecPlaneNormal, vecDir); if (n > 0.7 || tr.flFraction < 0.8) // 60 degrees return g_vecZero; } return (vecGrenadeVel * 0.6667); }