Portable Half-Life SDK. GoldSource and Xash3D. Crossplatform.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2133 lines
65 KiB

// botman's Half-Life bot example
//
// http://planethalflife.com/botman/
//
// bot.cpp
//
#include "extdll.h"
#include "util.h"
#include "client.h"
#include "cbase.h"
#include "player.h"
#include "items.h"
#include "effects.h"
#include "weapons.h"
#include "soundent.h"
#include "gamerules.h"
#include "animation.h"
#include "bot.h"
#include <sys/types.h>
#include <sys/stat.h>
extern cvar_t pausebots;
extern DLL_GLOBAL ULONG g_ulModelIndexPlayer;
// Set in combat.cpp. Used to pass the damage inflictor for death messages.
extern entvars_t *g_pevLastInflictor;
extern DLL_GLOBAL BOOL g_fGameOver;
extern int gmsgHealth;
extern int gmsgCurWeapon;
extern int gmsgSetFOV;
extern int gmsgSayText;
#define MODE_CHAT 0
#define PLAYER_SEARCH_RADIUS (float)40
int f_Observer = 0; // flag to indicate if player is in observer mode
int f_botskill = 3; // default bot skill level
int f_botdontshoot = 0;
// array of bot respawn information
respawn_t bot_respawn[32] = {
{FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL},
{FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL},
{FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL},
{FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL},
{FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL},
{FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL},
{FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL},
{FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL},
{FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL},
{FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL},
{FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL},
{FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL},
{FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL},
{FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL},
{FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL}};
#define MAX_SKINS 10
// indicate which models are currently used for random model allocation
BOOL skin_used[MAX_SKINS] = {
FALSE, FALSE, FALSE, FALSE, FALSE,
FALSE, FALSE, FALSE, FALSE, FALSE};
// store the names of the models...
6 years ago
const char *bot_skins[MAX_SKINS] = {
"barney", "jamison", "gman", "gordon", "eva01",
"hgrunt", "haruhi", "robo", "scientist", "vader"};
// store the player names for each of the models...
6 years ago
const char *bot_names[MAX_SKINS] = {
"Barney", "Jamison_Trumpets", "G-Man", "Gordon", "Evangelion01",
"H-Grunt", "Haruhi", "Robo", "Scientist", "DarthVader"};
// how often (out of 1000 times) the bot will pause, based on bot skill
float pause_frequency[5] = {4, 7, 10, 15, 20};
// how long the bot will delay when paused, based on bot skill
float pause_time[5][2] = {
{0.2, 0.5}, {0.5, 1.0}, {0.7, 1.3}, {1.0, 1.7}, {1.2, 2.0}};
extern ammo_check_t ammo_check[];
// sounds for TakeDamage speaking effects...
6 years ago
const char *hgrunt_sounds[] = { HG_SND1, HG_SND2, HG_SND3, HG_SND4, HG_SND5 };
const char *barney_sounds[] = { BA_SND1, BA_SND2, BA_SND3, BA_SND4, BA_SND5 };
const char *scientist_sounds[] = { SC_SND1, SC_SND2, SC_SND3, SC_SND4, SC_SND5 };
LINK_ENTITY_TO_CLASS( bot, CBot );
inline edict_t *CREATE_FAKE_CLIENT( const char *netname )
{
return (*g_engfuncs.pfnCreateFakeClient)( netname );
}
inline char *GET_INFOBUFFER( edict_t *e )
{
return (*g_engfuncs.pfnGetInfoKeyBuffer)( e );
}
6 years ago
inline char *GET_INFO_KEY_VALUE( const char *infobuffer, const char *key )
{
return (g_engfuncs.pfnInfoKeyValue( infobuffer, key ));
}
6 years ago
inline void SET_CLIENT_KEY_VALUE( int clientIndex, const char *infobuffer,
const char *key, const char *value )
{
(*g_engfuncs.pfnSetClientKeyValue)( clientIndex, infobuffer, key, value );
}
void BotDebug( char *buffer )
{
// print out debug messages to the HUD of all players
// this allows you to print messages from bots to your display
// as you are playing the game. Use STRING(pev->netname) in
// buffer to see the name of the bot.
UTIL_ClientPrintAll( HUD_PRINTNOTIFY, buffer );
}
void BotCreate(const char *skin, const char *name, const char *skill)
{
edict_t *BotEnt;
CBot *BotClass;
int skill_level;
char c_skill[2];
char c_skin[BOT_SKIN_LEN+1];
char c_name[BOT_NAME_LEN+1];
char c_index[3];
int i, j, length;
int index; // index into array of predefined names
BOOL found = FALSE;
if ((skin == NULL) || (*skin == 0))
{
// pick a random skin
index = RANDOM_LONG(0, MAX_SKINS-1); // there are 15 possible skins
// check if this skin has already been used...
while (skin_used[index] == TRUE)
{
index++;
if (index == MAX_SKINS)
index = 0;
}
skin_used[index] = TRUE;
// check if all skins are used...
for (i = 0; i < MAX_SKINS; i++)
{
if (skin_used[i] == FALSE)
break;
}
// if all skins are used, reset used to FALSE for next selection
if (i == MAX_SKINS)
{
for (i = 0; i < MAX_SKINS; i++)
skin_used[i] = FALSE;
}
strcpy( c_skin, bot_skins[index] );
}
else
{
strncpy( c_skin, skin, BOT_SKIN_LEN);
c_skin[BOT_SKIN_LEN] = 0; // make sure c_skin is null terminated
}
for (i = 0; c_skin[i] != 0; i++)
c_skin[i] = tolower( c_skin[i] ); // convert to all lowercase
index = 0;
while ((!found) && (index < MAX_SKINS))
{
if (strcmp(c_skin, bot_skins[index]) == 0)
found = TRUE;
else
index++;
}
if (found == TRUE)
{
if ((name != NULL) && (*name != 0))
{
strncpy( c_name, name, 31 );
c_name[31] = 0; // make sure c_name is null terminated
}
else
{
strcpy( c_name, bot_names[index] );
}
}
else
{
char dir_name[32];
char filename[128];
struct stat stat_str;
GET_GAME_DIR(dir_name);
sprintf(filename, "%s\\models\\player\\%s", dir_name, c_skin);
if (stat(filename, &stat_str) != 0)
{
sprintf(filename, "valve\\models\\player\\%s", c_skin);
if (stat(filename, &stat_str) != 0)
{
char err_msg[80];
sprintf( err_msg, "model \"%s\" is unknown.\n", c_skin );
UTIL_ClientPrintAll( HUD_PRINTNOTIFY, err_msg );
if (IS_DEDICATED_SERVER())
printf( "%s", err_msg );
UTIL_ClientPrintAll( HUD_PRINTNOTIFY,
"use barney, gina, gman, gordon, helmet, hgrunt,\n");
if (IS_DEDICATED_SERVER())
printf("use barney, gina, gman, gordon, helmet, hgrunt,\n");
UTIL_ClientPrintAll( HUD_PRINTNOTIFY,
" recon, robo, scientist, or zombie\n");
if (IS_DEDICATED_SERVER())
printf(" recon, robo, scientist, or zombie\n");
return;
}
}
// copy the name of the model to the bot's name...
strncpy( c_name, skin, BOT_SKIN_LEN);
c_name[BOT_SKIN_LEN] = 0; // make sure c_skin is null terminated
}
length = strlen(c_name);
// remove any illegal characters from name...
for (i = 0; i < length; i++)
{
if ((c_name[i] <= ' ') || (c_name[i] > '~') ||
(c_name[i] == '"'))
{
for (j = i; j < length; j++) // shuffle chars left (and null)
c_name[j] = c_name[j+1];
length--;
}
}
skill_level = 0;
if ((skill != NULL) && (*skill != 0))
sscanf( skill, "%d", &skill_level );
else
skill_level = f_botskill;
if ((skill_level < 1) || (skill_level > 5))
skill_level = f_botskill;
sprintf( c_skill, "%d", skill_level );
BotEnt = CREATE_FAKE_CLIENT( c_name );
if (FNullEnt( BotEnt ))
{
UTIL_ClientPrintAll( HUD_PRINTNOTIFY, "Max. Players reached. Can't create bot!\n");
if (IS_DEDICATED_SERVER())
printf("Max. Players reached. Can't create bot!\n");
}
else
{
char ptr[128]; // allocate space for message from ClientConnect
char *infobuffer;
int clientIndex;
index = 0;
while ((bot_respawn[index].is_used) && (index < 32))
index++;
if (index >= 32)
{
UTIL_ClientPrintAll( HUD_PRINTNOTIFY, "Can't create bot!\n");
return;
}
sprintf(c_index, "%d", index);
bot_respawn[index].is_used = TRUE; // this slot is used
// don't store the name here, it might change if same as another
strcpy(bot_respawn[index].skin, c_skin);
strcpy(bot_respawn[index].skill, c_skill);
sprintf(ptr, "Creating bot \"%s\" using model %s with skill=%d\n", c_name, c_skin, skill_level);
UTIL_ClientPrintAll( HUD_PRINTNOTIFY, ptr);
if (IS_DEDICATED_SERVER())
printf("%s", ptr);
BotClass = GetClassPtr( (CBot *) VARS(BotEnt) );
infobuffer = GET_INFOBUFFER( BotClass->edict( ) );
clientIndex = BotClass->entindex( );
SET_CLIENT_KEY_VALUE( clientIndex, infobuffer, "model", c_skin );
SET_CLIENT_KEY_VALUE( clientIndex, infobuffer, "skill", c_skill );
SET_CLIENT_KEY_VALUE( clientIndex, infobuffer, "index", c_index );
ClientConnect( BotClass->edict( ), c_name, "127.0.0.1", ptr );
DispatchSpawn( BotClass->edict( ) );
}
}
void CBot::Spawn( )
{
char c_skill[2];
char c_index[3];
CBasePlayer::Spawn();
pev->flags = FL_CLIENT | FL_FAKECLIENT;
// set the respawn index value based on key from BotCreate
strcpy(c_index, GET_INFO_KEY_VALUE(GET_INFOBUFFER(edict( )), "index") );
sscanf(c_index, "%d", &respawn_index);
bot_respawn[respawn_index].pBot = (CBasePlayer *)this;
// get the bot's name and save it in respawn array...
strcpy(bot_respawn[respawn_index].name, STRING(pev->netname));
bot_respawn[respawn_index].state = BOT_IDLE;
pev->ideal_yaw = pev->v_angle.y;
pev->yaw_speed = BOT_YAW_SPEED;
// bot starts out in "paused" state since it hasn't moved yet...
bot_was_paused = TRUE;
v_prev_origin = pev->origin;
f_shoot_time = 0;
// get bot's skill level (0=good, 4=bad)
strcpy(c_skill, GET_INFO_KEY_VALUE(GET_INFOBUFFER(edict( )), "skill") );
sscanf(c_skill, "%d", &bot_skill);
bot_skill--; // make 0 based for array index (now 0-4)
f_max_speed = CVAR_GET_FLOAT("sv_maxspeed");
f_speed_check_time = gpGlobals->time + 2.0f;
ladder_dir = 0;
// pick a wander direction (50% of the time to the left, 50% to the right)
if (RANDOM_LONG(1, 100) <= 50)
wander_dir = WANDER_LEFT;
else
wander_dir = WANDER_RIGHT;
f_pause_time = 0;
f_find_item = 0;
if (g_pGameRules->IsTeamplay()) // is team play enabled?
{
strcpy(model_name, g_pGameRules->GetTeamID(this));
}
else
{
strcpy(model_name, GET_INFO_KEY_VALUE(GET_INFOBUFFER(edict( )), "model") );
}
bot_model = 0;
if ((strcmp( model_name, "hgrunt" ) == 0) ||
(strcmp( model_name, "recon" ) == 0))
{
bot_model = MODEL_HGRUNT;
}
else if (strcmp( model_name, "barney") == 0)
{
bot_model = MODEL_BARNEY;
}
else if (strcmp( model_name, "scientist") == 0)
{
bot_model = MODEL_SCIENTIST;
}
else if (strcmp( model_name, "vader") == 0)
{
bot_model = MODEL_VADER;
}
f_pain_time = gpGlobals->time + 5.0f;
b_use_health_station = FALSE;
b_use_HEV_station = FALSE;
b_use_button = FALSE;
f_use_button_time = 0;
b_lift_moving = FALSE;
f_use_ladder_time = 0;
f_fire_gauss = -1; // -1 means not charging gauss gun
b_see_tripmine = FALSE;
b_shoot_tripmine = FALSE;
f_weapon_inventory_time = 0;
f_dont_avoid_wall_time = 0;
f_bot_use_time = 0;
f_wall_on_right = 0;
f_wall_on_left = 0;
pBotEnemy = NULL;
pBotUser = NULL;
pBotPickupItem = NULL;
}
int CBot::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker,
float flDamage, int bitsDamageType )
{
CBaseEntity *pAttacker = CBaseEntity::Instance(pevAttacker);
char sound[40];
int ret_damage;
// do the damage first...
ret_damage = CBasePlayer::TakeDamage( pevInflictor, pevAttacker, flDamage,
bitsDamageType );
// if the bot doesn't have an enemy and someone is shooting at it then
// turn in the attacker's direction...
if (pBotEnemy == NULL)
{
// face the attacker...
Vector v_enemy = pAttacker->pev->origin - pev->origin;
Vector bot_angles = UTIL_VecToAngles( v_enemy );
pev->ideal_yaw = bot_angles.y;
// check for wrap around of angle...
if (pev->ideal_yaw > 180)
pev->ideal_yaw -= 360;
if (pev->ideal_yaw < -180)
pev->ideal_yaw += 360;
// stop using health or HEV stations...
b_use_health_station = FALSE;
b_use_HEV_station = FALSE;
}
// check if bot model is known, attacker is not a bot,
// time for pain sound, and bot has some of health left...
if ((bot_model != 0) && (pAttacker->IsNetClient()) &&
(f_pain_time <= gpGlobals->time) && (pev->health > 0) &&
( !IS_DEDICATED_SERVER() ))
{
float distance = (pAttacker->pev->origin - pev->origin).Length( );
// check if the distance to attacker is close enough (otherwise
// the attacker is too far away to hear the pain sounds)
if (distance <= 400)
{
// speak pain sounds about 50% of the time
if (RANDOM_LONG(1, 100) <= 50)
{
f_pain_time = gpGlobals->time + 5.0f;
if (bot_model == MODEL_HGRUNT)
strcpy( sound, hgrunt_sounds[RANDOM_LONG(0,4)] );
else if (bot_model == MODEL_BARNEY)
strcpy( sound, barney_sounds[RANDOM_LONG(0,4)] );
else if (bot_model == MODEL_SCIENTIST)
strcpy( sound, scientist_sounds[RANDOM_LONG(0,4)] );
EMIT_SOUND(ENT(pevAttacker), CHAN_VOICE, sound,
RANDOM_FLOAT(0.9, 1.0), ATTN_NORM);
}
}
}
return ret_damage;
}
void CBot::Use( CBaseEntity *pActivator, CBaseEntity *pCaller,
USE_TYPE useType, float value )
{
if (g_pGameRules->IsTeamplay()) // is team play enabled?
{
// check the bot and player are on the same team...
if (UTIL_TeamsMatch(g_pGameRules->GetTeamID(this),
g_pGameRules->GetTeamID(pActivator)))
{
if (pBotEnemy == NULL) // is bot NOT currently engaged in combat?
{
if (pBotUser == NULL) // does bot NOT have a "user"
{
// tell teammate that bot will cover them...
EMIT_SOUND(ENT(pActivator->pev), CHAN_VOICE, USE_TEAMPLAY_SND,
1.0, ATTN_NORM);
pBotUser = pActivator;
f_bot_use_time = gpGlobals->time;
}
else
{
// tell teammate that you'll see them later..
EMIT_SOUND(ENT(pActivator->pev), CHAN_VOICE, USE_TEAMPLAY_LATER_SND,
1.0, ATTN_NORM);
pBotUser = NULL;
f_bot_use_time = 0;
}
}
else
{
EMIT_SOUND(ENT(pActivator->pev), CHAN_VOICE, USE_TEAMPLAY_ENEMY_SND, 1.0,
ATTN_NORM);
}
}
}
}
int CBot::BotInFieldOfView(Vector dest)
{
// find angles from source to destination...
Vector entity_angles = UTIL_VecToAngles( dest );
// make yaw angle 0 to 360 degrees if negative...
if (entity_angles.y < 0)
entity_angles.y += 360;
// get bot's current view angle...
float view_angle = pev->v_angle.y;
// make view angle 0 to 360 degrees if negative...
if (view_angle < 0)
view_angle += 360;
// return the absolute value of angle to destination entity
// zero degrees means straight ahead, 45 degrees to the left or
// 45 degrees to the right is the limit of the normal view angle
return abs((int)view_angle - (int)entity_angles.y);
}
BOOL CBot::BotEntityIsVisible( Vector dest )
{
TraceResult tr;
// trace a line from bot's eyes to destination...
UTIL_TraceLine( pev->origin + pev->view_ofs, dest, ignore_monsters,
ENT(pev), &tr );
// check if line of sight to object is not blocked (i.e. visible)
if (tr.flFraction >= 1.0f)
return TRUE;
else
return FALSE;
}
float CBot::BotChangeYaw( float speed )
{
float ideal;
float current;
float current_180; // current +/- 180 degrees
float diff;
// turn from the current v_angle yaw to the ideal_yaw by selecting
// the quickest way to turn to face that direction
current = pev->v_angle.y;
ideal = pev->ideal_yaw;
// find the difference in the current and ideal angle
diff = fabs(current - ideal);
// check if the bot is already facing the ideal_yaw direction...
if (diff <= 1)
return diff; // return number of degrees turned
// check if difference is less than the max degrees per turn
if (diff < speed)
speed = diff; // just need to turn a little bit (less than max)
// here we have four cases, both angle positive, one positive and
// the other negative, one negative and the other positive, or
// both negative. handle each case separately...
if ((current >= 0) && (ideal >= 0)) // both positive
{
if (current > ideal)
current -= speed;
else
current += speed;
}
else if ((current >= 0) && (ideal < 0))
{
current_180 = current - 180;
if (current_180 > ideal)
current += speed;
else
current -= speed;
}
else if ((current < 0) && (ideal >= 0))
{
current_180 = current + 180;
if (current_180 > ideal)
current += speed;
else
current -= speed;
}
else // (current < 0) && (ideal < 0) both negative
{
if (current > ideal)
current -= speed;
else
current += speed;
}
// check for wrap around of angle...
if (current > 180)
current -= 360;
if (current < -180)
current += 360;
pev->v_angle.y = current;
pev->angles.x = 0;
pev->angles.y = pev->v_angle.y;
pev->angles.z = 0;
return speed; // return number of degrees turned
}
void CBot::BotOnLadder( float moved_distance )
{
// moves the bot up or down a ladder. if the bot can't move
// (i.e. get's stuck with someone else on ladder), the bot will
// change directions and go the other way on the ladder.
if (ladder_dir == LADDER_UP) // is the bot currently going up?
{
pev->v_angle.x = -60; // look upwards
// check if the bot hasn't moved much since the last location...
if (moved_distance <= 1)
{
// the bot must be stuck, change directions...
pev->v_angle.x = 60; // look downwards
ladder_dir = LADDER_DOWN;
}
}
else if (ladder_dir == LADDER_DOWN) // is the bot currently going down?
{
pev->v_angle.x = 60; // look downwards
// check if the bot hasn't moved much since the last location...
if (moved_distance <= 1)
{
// the bot must be stuck, change directions...
pev->v_angle.x = -60; // look upwards
ladder_dir = LADDER_UP;
}
}
else // the bot hasn't picked a direction yet, try going up...
{
pev->v_angle.x = -60; // look upwards
ladder_dir = LADDER_UP;
}
// move forward (i.e. in the direction the bot is looking, up or down)
pev->button |= IN_FORWARD;
}
void CBot::BotUnderWater( void )
{
// handle movements under water. right now, just try to keep from
// drowning by swimming up towards the surface and look to see if
// there is a surface the bot can jump up onto to get out of the
// water. bots DON'T like water!
Vector v_src, v_forward;
TraceResult tr;
int contents;
// swim up towards the surface
pev->v_angle.x = -60; // look upwards
// move forward (i.e. in the direction the bot is looking, up or down)
pev->button |= IN_FORWARD;
// set gpGlobals angles based on current view angle (for TraceLine)
UTIL_MakeVectors( pev->v_angle );
// look from eye position straight forward (remember: the bot is looking
// upwards at a 60 degree angle so TraceLine will go out and up...
v_src = pev->origin + pev->view_ofs; // EyePosition()
v_forward = v_src + gpGlobals->v_forward * 90;
// trace from the bot's eyes straight forward...
UTIL_TraceLine( v_src, v_forward, dont_ignore_monsters, ENT(pev), &tr);
// check if the trace didn't hit anything (i.e. nothing in the way)...
if (tr.flFraction >= 1.0f)
{
// find out what the contents is of the end of the trace...
contents = UTIL_PointContents( tr.vecEndPos );
// check if the trace endpoint is in open space...
if (contents == CONTENTS_EMPTY)
{
// ok so far, we are at the surface of the water, continue...
v_src = tr.vecEndPos;
v_forward = v_src;
v_forward.z -= 90;
// trace from the previous end point straight down...
UTIL_TraceLine( v_src, v_forward, dont_ignore_monsters,
ENT(pev), &tr);
// check if the trace hit something...
if (tr.flFraction < 1.0f)
{
contents = UTIL_PointContents( tr.vecEndPos );
// if contents isn't water then assume it's land, jump!
if (contents != CONTENTS_WATER)
{
pev->button |= IN_JUMP;
}
}
}
}
}
void CBot::BotFindItem( void )
{
CBaseEntity *pEntity = NULL;
CBaseEntity *pPickupEntity = NULL;
Vector pickup_origin;
Vector entity_origin;
float radius = 500;
BOOL can_pickup;
float min_distance;
char item_name[40];
TraceResult tr;
Vector vecStart;
Vector vecEnd;
int angle_to_entity;
pBotPickupItem = NULL;
min_distance = radius + 1.0f;
while ((pEntity = UTIL_FindEntityInSphere( pEntity, pev->origin, radius )) != NULL)
{
can_pickup = FALSE; // assume can't use it until known otherwise
strcpy(item_name, STRING(pEntity->pev->classname));
// see if this is a "func_" type of entity (func_button, etc.)...
if (strncmp("func_", item_name, 5) == 0)
{
// BModels have 0,0,0 for origin so must use VecBModelOrigin...
entity_origin = VecBModelOrigin(pEntity->pev);
vecStart = pev->origin + pev->view_ofs;
vecEnd = entity_origin;
angle_to_entity = BotInFieldOfView( vecEnd - vecStart );
// check if entity is outside field of view (+/- 45 degrees)
if (angle_to_entity > 45)
continue; // skip this item if bot can't "see" it
// check if entity is a ladder (ladders are a special case)...
if (strcmp("func_ladder", item_name) == 0)
{
// force ladder origin to same z coordinate as bot since
// the VecBModelOrigin is the center of the ladder. For
// LONG ladders, the center MAY be hundreds of units above
// the bot. Fake an origin at the same level as the bot...
entity_origin.z = pev->origin.z;
vecEnd = entity_origin;
// trace a line from bot's eyes to func_ladder entity...
UTIL_TraceLine( vecStart, vecEnd, dont_ignore_monsters, ENT(pev), &tr);
// check if traced all the way up to the entity (didn't hit wall)
if (tr.flFraction >= 1.0f)
{
// find distance to item for later use...
float distance = (vecEnd - vecStart).Length( );
// use the ladder about 100% of the time, if haven't
// used a ladder in at least 5 seconds...
if ((RANDOM_LONG(1, 100) <= 100) &&
((f_use_ladder_time + 5) < gpGlobals->time))
{
// if close to ladder...
if (distance < 100)
{
// don't avoid walls for a while
f_dont_avoid_wall_time = gpGlobals->time + 5.0f;
}
can_pickup = TRUE;
}
}
}
else
{
// trace a line from bot's eyes to func_ entity...
UTIL_TraceLine( vecStart, vecEnd, dont_ignore_monsters, ENT(pev), &tr);
// check if traced all the way up to the entity (didn't hit wall)
if (strcmp(item_name, STRING(tr.pHit->v.classname)) == 0)
{
// find distance to item for later use...
float distance = (vecEnd - vecStart).Length( );
// check if entity is wall mounted health charger...
if (strcmp("func_healthcharger", item_name) == 0)
{
// check if the bot can use this item and
// check if the recharger is ready to use (has power left)...
if ((pev->health < 100) && (pEntity->pev->frame == 0))
{
// check if flag not set...
if (!b_use_health_station)
{
// check if close enough and facing it directly...
if ((distance < PLAYER_SEARCH_RADIUS) &&
(angle_to_entity <= 10))
{
b_use_health_station = TRUE;
f_use_health_time = gpGlobals->time;
}
// if close to health station...
if (distance < 100)
{
// don't avoid walls for a while
f_dont_avoid_wall_time = gpGlobals->time + 5.0f;
}
can_pickup = TRUE;
}
}
else
{
// don't need or can't use this item...
b_use_health_station = FALSE;
}
}
// check if entity is wall mounted HEV charger...
else if (strcmp("func_recharge", item_name) == 0)
{
// check if the bot can use this item and
// check if the recharger is ready to use (has power left)...
if ((pev->armorvalue < MAX_NORMAL_BATTERY) &&
(pev->weapons & (1<<WEAPON_SUIT)) &&
(pEntity->pev->frame == 0))
{
// check if flag not set and facing it...
if (!b_use_HEV_station)
{
// check if close enough and facing it directly...
if ((distance < PLAYER_SEARCH_RADIUS) &&
(angle_to_entity <= 10))
{
b_use_HEV_station = TRUE;
f_use_HEV_time = gpGlobals->time;
}
// if close to HEV recharger...
if (distance < 100)
{
// don't avoid walls for a while
f_dont_avoid_wall_time = gpGlobals->time + 5.0f;
}
can_pickup = TRUE;
}
}
else
{
// don't need or can't use this item...
b_use_HEV_station = FALSE;
}
}
// check if entity is a button...
else if (strcmp("func_button", item_name) == 0)
{
// use the button about 100% of the time, if haven't
// used a button in at least 5 seconds...
if ((RANDOM_LONG(1, 100) <= 100) &&
((f_use_button_time + 5) < gpGlobals->time))
{
// check if flag not set and facing it...
if (!b_use_button)
{
// check if close enough and facing it directly...
if ((distance < PLAYER_SEARCH_RADIUS) &&
(angle_to_entity <= 10))
{
b_use_button = TRUE;
b_lift_moving = FALSE;
f_use_button_time = gpGlobals->time;
}
// if close to button...
if (distance < 100)
{
// don't avoid walls for a while
f_dont_avoid_wall_time = gpGlobals->time + 5.0f;
}
can_pickup = TRUE;
}
}
else
{
// don't need or can't use this item...
b_use_button = FALSE;
}
}
}
}
}
// check if entity is an armed tripmine beam
else if (strcmp("beam", item_name) == 0)
{
CBeam *pBeam = (CBeam *)pEntity;
// Vector v_beam_start = pBeam->GetStartPos();
// Vector v_beam_end = pBeam->GetEndPos();
//
// if (FInViewCone( &v_beam_start ) && FVisible( v_beam_start ))
// {
// BotDebug("I see a beam start!\n");
// }
//
// if (FInViewCone( &v_beam_end ) && FVisible( v_beam_end ))
// {
// BotDebug("I see a beam end!\n");
// }
}
else // everything else...
{
entity_origin = pEntity->pev->origin;
vecStart = pev->origin + pev->view_ofs;
vecEnd = entity_origin;
// find angles from bot origin to entity...
angle_to_entity = BotInFieldOfView( vecEnd - vecStart );
// check if entity is outside field of view (+/- 45 degrees)
if (angle_to_entity > 45)
continue; // skip this item if bot can't "see" it
// check if line of sight to object is not blocked (i.e. visible)
if (BotEntityIsVisible( vecEnd ))
{
// check if entity is a weapon...
if (strncmp("weapon_", item_name, 7) == 0)
{
CBasePlayerWeapon *pWeapon = (CBasePlayerWeapon *)pEntity;
if ((pWeapon->m_pPlayer) || (pWeapon->pev->effects & EF_NODRAW))
{
// someone owns this weapon or it hasn't respawned yet
continue;
}
if (g_pGameRules->CanHavePlayerItem( this, pWeapon ))
{
can_pickup = TRUE;
}
}
// check if entity is ammo...
else if (strncmp("ammo_", item_name, 5) == 0)
{
CBasePlayerAmmo *pAmmo = (CBasePlayerAmmo *)pEntity;
BOOL ammo_found = FALSE;
int i;
// check if the item is not visible (i.e. has not respawned)
if (pAmmo->pev->effects & EF_NODRAW)
continue;
i = 0;
while (ammo_check[i].ammo_name[0])
{
if (strcmp(ammo_check[i].ammo_name, item_name) == 0)
{
ammo_found = TRUE;
// see if the bot can pick up this item...
if (g_pGameRules->CanHaveAmmo( this,
ammo_check[i].weapon_name, ammo_check[i].max_carry))
{
can_pickup = TRUE;
break;
}
}
i++;
}
if (ammo_found == FALSE)
{
sprintf(message, "unknown ammo: %s\n", item_name);
BotDebug(message);
}
}
// check if entity is a battery...
else if (strcmp("item_battery", item_name) == 0)
{
CItem *pBattery = (CItem *)pEntity;
// check if the item is not visible (i.e. has not respawned)
if (pBattery->pev->effects & EF_NODRAW)
continue;
// check if the bot can use this item...
if ((pev->armorvalue < MAX_NORMAL_BATTERY) &&
(pev->weapons & (1<<WEAPON_SUIT)))
{
can_pickup = TRUE;
}
}
// check if entity is a healthkit...
else if (strcmp("item_healthkit", item_name) == 0)
{
CItem *pHealthKit = (CItem *)pEntity;
// check if the item is not visible (i.e. has not respawned)
if (pHealthKit->pev->effects & EF_NODRAW)
continue;
// check if the bot can use this item...
if (pev->health < 100)
{
can_pickup = TRUE;
}
}
// check if entity is a packed up weapons box...
else if (strcmp("weaponbox", item_name) == 0)
{
can_pickup = TRUE;
}
// check if entity is the spot from RPG laser
else if (strcmp("laser_spot", item_name) == 0)
{
}
// check if entity is an armed tripmine
else if (strcmp("monster_tripmine", item_name) == 0)
{
float distance = (pEntity->pev->origin - pev->origin).Length( );
if (b_see_tripmine)
{
// see if this tripmine is closer to bot...
if (distance < (v_tripmine_origin - pev->origin).Length())
{
v_tripmine_origin = pEntity->pev->origin;
b_shoot_tripmine = FALSE;
// see if bot is far enough to shoot the tripmine...
if (distance >= 375)
{
b_shoot_tripmine = TRUE;
}
}
}
else
{
b_see_tripmine = TRUE;
v_tripmine_origin = pEntity->pev->origin;
b_shoot_tripmine = FALSE;
// see if bot is far enough to shoot the tripmine...
if (distance >= 375) // 375 is damage radius
{
b_shoot_tripmine = TRUE;
}
}
}
// check if entity is an armed satchel charge
else if (strcmp("monster_satchel", item_name) == 0)
{
}
// check if entity is a snark (squeak grenade)
else if (strcmp("monster_snark", item_name) == 0)
{
}
} // end if object is visible
} // end else not "func_" entity
if (can_pickup) // if the bot found something it can pickup...
{
float distance = (entity_origin - pev->origin).Length( );
// see if it's the closest item so far...
if (distance < min_distance)
{
min_distance = distance; // update the minimum distance
pPickupEntity = pEntity; // remember this entity
pickup_origin = entity_origin; // remember location of entity
}
}
} // end while loop
if (pPickupEntity != NULL)
{
// let's head off toward that item...
Vector v_item = pickup_origin - pev->origin;
Vector bot_angles = UTIL_VecToAngles( v_item );
pev->ideal_yaw = bot_angles.y;
// check for wrap around of angle...
if (pev->ideal_yaw > 180)
pev->ideal_yaw -= 360;
if (pev->ideal_yaw < -180)
pev->ideal_yaw += 360;
pBotPickupItem = pPickupEntity; // save the item bot is trying to get
}
}
void CBot::BotUseLift( float moved_distance )
{
// just need to press the button once, when the flag gets set...
if (f_use_button_time == gpGlobals->time)
{
pev->button = IN_USE;
// face opposite from the button
pev->ideal_yaw = -pev->ideal_yaw; // rotate 180 degrees
// check for wrap around of angle...
if (pev->ideal_yaw > 180)
pev->ideal_yaw -= 360;
if (pev->ideal_yaw < -180)
pev->ideal_yaw += 360;
}
// check if the bot has waited too long for the lift to move...
if (((f_use_button_time + 2.0f) < gpGlobals->time) &&
(!b_lift_moving))
{
// clear use button flag
b_use_button = FALSE;
// bot doesn't have to set f_find_item since the bot
// should already be facing away from the button
f_move_speed = f_max_speed;
}
// check if lift has started moving...
if ((moved_distance > 1) && (!b_lift_moving))
{
b_lift_moving = TRUE;
}
// check if lift has stopped moving...
if ((moved_distance <= 1) && (b_lift_moving))
{
TraceResult tr1, tr2;
Vector v_src, v_forward, v_right, v_left;
Vector v_down, v_forward_down, v_right_down, v_left_down;
b_use_button = FALSE;
// TraceLines in 4 directions to find which way to go...
UTIL_MakeVectors( pev->v_angle );
v_src = pev->origin + pev->view_ofs;
v_forward = v_src + gpGlobals->v_forward * 90;
v_right = v_src + gpGlobals->v_right * 90;
v_left = v_src + gpGlobals->v_right * -90;
v_down = pev->v_angle;
v_down.x = v_down.x + 45; // look down at 45 degree angle
UTIL_MakeVectors( v_down );
v_forward_down = v_src + gpGlobals->v_forward * 100;
v_right_down = v_src + gpGlobals->v_right * 100;
v_left_down = v_src + gpGlobals->v_right * -100;
// try tracing forward first...
UTIL_TraceLine( v_src, v_forward, dont_ignore_monsters, ENT(pev), &tr1);
UTIL_TraceLine( v_src, v_forward_down, dont_ignore_monsters, ENT(pev), &tr2);
// check if we hit a wall or didn't find a floor...
if ((tr1.flFraction < 1.0f) || (tr2.flFraction >= 1.0f))
{
// try tracing to the RIGHT side next...
UTIL_TraceLine( v_src, v_right, dont_ignore_monsters, ENT(pev), &tr1);
UTIL_TraceLine( v_src, v_right_down, dont_ignore_monsters, ENT(pev), &tr2);
// check if we hit a wall or didn't find a floor...
if ((tr1.flFraction < 1.0f) || (tr2.flFraction >= 1.0f))
{
// try tracing to the LEFT side next...
UTIL_TraceLine( v_src, v_left, dont_ignore_monsters, ENT(pev), &tr1);
UTIL_TraceLine( v_src, v_left_down, dont_ignore_monsters, ENT(pev), &tr2);
// check if we hit a wall or didn't find a floor...
if ((tr1.flFraction < 1.0f) || (tr2.flFraction >= 1.0f))
{
// only thing to do is turn around...
pev->ideal_yaw += 180; // turn all the way around
}
else
{
pev->ideal_yaw += 90; // turn to the LEFT
}
}
else
{
pev->ideal_yaw -= 90; // turn to the RIGHT
}
// check for wrap around of angle...
if (pev->ideal_yaw > 180)
pev->ideal_yaw -= 360;
if (pev->ideal_yaw < -180)
pev->ideal_yaw += 360;
}
BotChangeYaw( pev->yaw_speed );
f_move_speed = f_max_speed;
}
}
void CBot::BotTurnAtWall( TraceResult *tr )
{
Vector Normal;
float Y, Y1, Y2, D1, D2, Z;
// Find the normal vector from the trace result. The normal vector will
// be a vector that is perpendicular to the surface from the TraceResult.
Normal = UTIL_VecToAngles(tr->vecPlaneNormal);
// Since the bot keeps it's view angle in -180 < x < 180 degrees format,
// and since TraceResults are 0 < x < 360, we convert the bot's view
// angle (yaw) to the same format at TraceResult.
Y = pev->v_angle.y;
Y = Y + 180;
if (Y > 359) Y -= 360;
// Turn the normal vector around 180 degrees (i.e. make it point towards
// the wall not away from it. That makes finding the angles that the
// bot needs to turn a little easier.
Normal.y = Normal.y - 180;
if (Normal.y < 0)
Normal.y += 360;
// Here we compare the bots view angle (Y) to the Normal - 90 degrees (Y1)
// and the Normal + 90 degrees (Y2). These two angles (Y1 & Y2) represent
// angles that are parallel to the wall surface, but heading in opposite
// directions. We want the bot to choose the one that will require the
// least amount of turning (saves time) and have the bot head off in that
// direction.
Y1 = Normal.y - 90;
if (RANDOM_LONG(1, 100) <= 50)
{
Y1 = Y1 - RANDOM_FLOAT(5.0, 20.0);
}
if (Y1 < 0) Y1 += 360;
Y2 = Normal.y + 90;
if (RANDOM_LONG(1, 100) <= 50)
{
Y2 = Y2 + RANDOM_FLOAT(5.0, 20.0);
}
if (Y2 > 359) Y2 -= 360;
// D1 and D2 are the difference (in degrees) between the bot's current
// angle and Y1 or Y2 (respectively).
D1 = fabs(Y - Y1);
if (D1 > 179) D1 = fabs(D1 - 360);
D2 = fabs(Y - Y2);
if (D2 > 179) D2 = fabs(D2 - 360);
// If difference 1 (D1) is more than difference 2 (D2) then the bot will
// have to turn LESS if it heads in direction Y1 otherwise, head in
// direction Y2. I know this seems backwards, but try some sample angles
// out on some graph paper and go through these equations using a
// calculator, you'll see what I mean.
if (D1 > D2)
Z = Y1;
else
Z = Y2;
// convert from TraceResult 0 to 360 degree format back to bot's
// -180 to 180 degree format.
if (Z > 180)
Z -= 360;
// set the direction to head off into...
pev->ideal_yaw = Z;
// check for wrap around of angle...
if (pev->ideal_yaw > 180)
pev->ideal_yaw -= 360;
if (pev->ideal_yaw < -180)
pev->ideal_yaw += 360;
f_move_speed = 0; // don't move while turning
}
BOOL CBot::BotCantMoveForward( TraceResult *tr )
{
// use some TraceLines to determine if anything is blocking the current
// path of the bot.
Vector v_src, v_forward;
UTIL_MakeVectors( pev->v_angle );
// first do a trace from the bot's eyes forward...
v_src = pev->origin + pev->view_ofs; // EyePosition()
v_forward = v_src + gpGlobals->v_forward * 40;
// trace from the bot's eyes straight forward...
UTIL_TraceLine( v_src, v_forward, dont_ignore_monsters, ENT(pev), tr);
// check if the trace hit something...
if (tr->flFraction < 1.0f)
{
return TRUE; // bot's head will hit something
}
// bot's head is clear, check at waist level...
v_src = pev->origin;
v_forward = v_src + gpGlobals->v_forward * 40;
// trace from the bot's waist straight forward...
UTIL_TraceLine( v_src, v_forward, dont_ignore_monsters, ENT(pev), tr);
// check if the trace hit something...
if (tr->flFraction < 1.0f)
{
return TRUE; // bot's body will hit something
}
return FALSE; // bot can move forward, return false
}
BOOL CBot::BotCanJumpUp( void )
{
// What I do here is trace 3 lines straight out, one unit higher than
// the highest normal jumping distance. I trace once at the center of
// the body, once at the right side, and once at the left side. If all
// three of these TraceLines don't hit an obstruction then I know the
// area to jump to is clear. I then need to trace from head level,
// above where the bot will jump to, downward to see if there is anything
// blocking the jump. There could be a narrow opening that the body
// will not fit into. These horizontal and vertical TraceLines seem
// to catch most of the problems with falsely trying to jump on something
// that the bot can not get onto.
TraceResult tr;
Vector v_jump, v_source, v_dest;
// convert current view angle to vectors for TraceLine math...
v_jump = pev->v_angle;
v_jump.x = 0; // reset pitch to 0 (level horizontally)
v_jump.z = 0; // reset roll to 0 (straight up and down)
UTIL_MakeVectors( v_jump );
// use center of the body first...
// maximum jump height is 45, so check one unit above that (46)
v_source = pev->origin + Vector(0, 0, -36 + 46);
v_dest = v_source + gpGlobals->v_forward * 24;
// trace a line forward at maximum jump height...
UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr);
// if trace hit something, return FALSE
if (tr.flFraction < 1.0f)
return FALSE;
// now check same height to one side of the bot...
v_source = pev->origin + gpGlobals->v_right * 16 + Vector(0, 0, -36 + 46);
v_dest = v_source + gpGlobals->v_forward * 24;
// trace a line forward at maximum jump height...
UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr);
// if trace hit something, return FALSE
if (tr.flFraction < 1.0f)
return FALSE;
// now check same height on the other side of the bot...
v_source = pev->origin + gpGlobals->v_right * -16 + Vector(0, 0, -36 + 46);
v_dest = v_source + gpGlobals->v_forward * 24;
// trace a line forward at maximum jump height...
UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr);
// if trace hit something, return FALSE
if (tr.flFraction < 1.0f)
return FALSE;
// now trace from head level downward to check for obstructions...
// start of trace is 24 units in front of bot, 72 units above head...
v_source = pev->origin + gpGlobals->v_forward * 24;
// offset 72 units from top of head (72 + 36)
v_source.z = v_source.z + 108;
// end point of trace is 99 units straight down from start...
// (99 is 108 minus the jump limit height which is 45 - 36 = 9)
v_dest = v_source + Vector(0, 0, -99);
// trace a line straight down toward the ground...
UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr);
// if trace hit something, return FALSE
if (tr.flFraction < 1.0f)
return FALSE;
// now check same height to one side of the bot...
v_source = pev->origin + gpGlobals->v_right * 16 + gpGlobals->v_forward * 24;
v_source.z = v_source.z + 108;
v_dest = v_source + Vector(0, 0, -99);
// trace a line straight down toward the ground...
UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr);
// if trace hit something, return FALSE
if (tr.flFraction < 1.0f)
return FALSE;
// now check same height on the other side of the bot...
v_source = pev->origin + gpGlobals->v_right * -16 + gpGlobals->v_forward * 24;
v_source.z = v_source.z + 108;
v_dest = v_source + Vector(0, 0, -99);
// trace a line straight down toward the ground...
UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr);
// if trace hit something, return FALSE
if (tr.flFraction < 1.0f)
return FALSE;
return TRUE;
}
BOOL CBot::BotCanDuckUnder( void )
{
// What I do here is trace 3 lines straight out, one unit higher than
// the ducking height. I trace once at the center of the body, once
// at the right side, and once at the left side. If all three of these
// TraceLines don't hit an obstruction then I know the area to duck to
// is clear. I then need to trace from the ground up, 72 units, to make
// sure that there is something blocking the TraceLine. Then we know
// we can duck under it.
TraceResult tr;
Vector v_duck, v_source, v_dest;
// convert current view angle to vectors for TraceLine math...
v_duck = pev->v_angle;
v_duck.x = 0; // reset pitch to 0 (level horizontally)
v_duck.z = 0; // reset roll to 0 (straight up and down)
UTIL_MakeVectors( v_duck );
// use center of the body first...
// duck height is 36, so check one unit above that (37)
v_source = pev->origin + Vector(0, 0, -36 + 37);
v_dest = v_source + gpGlobals->v_forward * 24;
// trace a line forward at duck height...
UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr);
// if trace hit something, return FALSE
if (tr.flFraction < 1.0f)
return FALSE;
// now check same height to one side of the bot...
v_source = pev->origin + gpGlobals->v_right * 16 + Vector(0, 0, -36 + 37);
v_dest = v_source + gpGlobals->v_forward * 24;
// trace a line forward at duck height...
UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr);
// if trace hit something, return FALSE
if (tr.flFraction < 1.0f)
return FALSE;
// now check same height on the other side of the bot...
v_source = pev->origin + gpGlobals->v_right * -16 + Vector(0, 0, -36 + 37);
v_dest = v_source + gpGlobals->v_forward * 24;
// trace a line forward at duck height...
UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr);
// if trace hit something, return FALSE
if (tr.flFraction < 1.0f)
return FALSE;
// now trace from the ground up to check for object to duck under...
// start of trace is 24 units in front of bot near ground...
v_source = pev->origin + gpGlobals->v_forward * 24;
v_source.z = v_source.z - 35; // offset to feet + 1 unit up
// end point of trace is 72 units straight up from start...
v_dest = v_source + Vector(0, 0, 72);
// trace a line straight up in the air...
UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr);
// if trace didn't hit something, return FALSE
if (tr.flFraction >= 1.0f)
return FALSE;
// now check same height to one side of the bot...
v_source = pev->origin + gpGlobals->v_right * 16 + gpGlobals->v_forward * 24;
v_source.z = v_source.z - 35; // offset to feet + 1 unit up
v_dest = v_source + Vector(0, 0, 72);
// trace a line straight up in the air...
UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr);
// if trace didn't hit something, return FALSE
if (tr.flFraction >= 1.0f)
return FALSE;
// now check same height on the other side of the bot...
v_source = pev->origin + gpGlobals->v_right * -16 + gpGlobals->v_forward * 24;
v_source.z = v_source.z - 35; // offset to feet + 1 unit up
v_dest = v_source + Vector(0, 0, 72);
// trace a line straight up in the air...
UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr);
// if trace didn't hit something, return FALSE
if (tr.flFraction >= 1.0f)
return FALSE;
return TRUE;
}
BOOL CBot::BotShootTripmine( void )
{
if (b_shoot_tripmine != TRUE)
return FALSE;
// aim at the tripmine and fire the glock...
Vector v_enemy = v_tripmine_origin - GetGunPosition( );
pev->v_angle = UTIL_VecToAngles( v_enemy );
pev->angles.x = 0;
pev->angles.y = pev->v_angle.y;
pev->angles.z = 0;
pev->ideal_yaw = pev->v_angle.y;
// check for wrap around of angle...
if (pev->ideal_yaw > 180)
pev->ideal_yaw -= 360;
if (pev->ideal_yaw < -180)
pev->ideal_yaw += 360;
pev->v_angle.x = -pev->v_angle.x; //adjust pitch to point gun
return (BotFireWeapon( v_tripmine_origin, WEAPON_GLOCK, TRUE ));
}
BOOL CBot::BotFollowUser( void )
{
BOOL user_visible;
float f_distance;
Vector vecEnd = pBotUser->pev->origin + pBotUser->pev->view_ofs;
pev->v_angle.x = 0; // reset pitch to 0 (level horizontally)
pev->v_angle.z = 0; // reset roll to 0 (straight up and down)
pev->angles.x = 0;
pev->angles.y = pev->v_angle.y;
pev->angles.z = 0;
if (!pBotUser->IsAlive( ))
{
// the bot's user is dead!
pBotUser = NULL;
return FALSE;
}
user_visible = FInViewCone( &vecEnd ) && FVisible( vecEnd );
// check if the "user" is still visible or if the user has been visible
// in the last 5 seconds (or the player just starting "using" the bot)
if (user_visible || (f_bot_use_time + 5 > gpGlobals->time))
{
if (user_visible)
f_bot_use_time = gpGlobals->time; // reset "last visible time"
// face the user
Vector v_user = pBotUser->pev->origin - pev->origin;
Vector bot_angles = UTIL_VecToAngles( v_user );
pev->ideal_yaw = bot_angles.y;
// check for wrap around of angle...
if (pev->ideal_yaw > 180)
pev->ideal_yaw -= 360;
if (pev->ideal_yaw < -180)
pev->ideal_yaw += 360;
f_distance = v_user.Length( ); // how far away is the "user"?
if (f_distance > 200) // run if distance to enemy is far
f_move_speed = f_max_speed;
else if (f_distance > 50) // walk if distance is closer
f_move_speed = f_max_speed / 2;
else // don't move if close enough
f_move_speed = 0.0;
return TRUE;
}
else
{
// person to follow has gone out of sight...
pBotUser = NULL;
return FALSE;
}
}
BOOL CBot::BotCheckWallOnLeft( void )
{
Vector v_src, v_left;
TraceResult tr;
UTIL_MakeVectors( pev->v_angle );
// do a trace to the left...
v_src = pev->origin;
v_left = v_src + gpGlobals->v_right * -40; // 40 units to the left
UTIL_TraceLine( v_src, v_left, dont_ignore_monsters, ENT(pev), &tr);
// check if the trace hit something...
if (tr.flFraction < 1.0f)
{
if (f_wall_on_left == 0)
f_wall_on_left = gpGlobals->time;
return TRUE;
}
return FALSE;
}
BOOL CBot::BotCheckWallOnRight( void )
{
Vector v_src, v_right;
TraceResult tr;
UTIL_MakeVectors( pev->v_angle );
// do a trace to the right...
v_src = pev->origin;
v_right = v_src + gpGlobals->v_right * 40; // 40 units to the right
UTIL_TraceLine( v_src, v_right, dont_ignore_monsters, ENT(pev), &tr);
// check if the trace hit something...
if (tr.flFraction < 1.0f)
{
if (f_wall_on_right == 0)
f_wall_on_right = gpGlobals->time;
return TRUE;
}
return FALSE;
}
void CBot::BotThink( void )
{
Vector v_diff; // vector from previous to current location
float moved_distance; // length of v_diff vector (distance bot moved)
float degrees_turned;
// check if someone kicked the bot off of the server (DON'T RESPAWN!)...
if ((pev->takedamage == DAMAGE_NO) && (respawn_index >= 0))
{
pev->health = 0;
pev->deadflag = DEAD_DEAD; // make the kicked bot be dead
bot_respawn[respawn_index].is_used = FALSE; // this slot is now free
bot_respawn[respawn_index].state = BOT_IDLE;
respawn_index = -1; // indicate no slot used
// fall through to next if statement (respawn_index will be -1)
}
// is the round over (time/frag limit) or has the bot been removed?
if ((g_fGameOver) || (respawn_index == -1))
{
CSound *pSound;
// keep resetting the sound entity until the bot is respawned...
pSound = CSoundEnt::SoundPointerForIndex( CSoundEnt::ClientSoundIndex( edict() ) );
if ( pSound )
{
pSound->Reset();
}
return;
}
pev->button = 0; // make sure no buttons are pressed
// if the bot is dead, randomly press fire to respawn...
if ((pev->health < 1) || (pev->deadflag != DEAD_NO))
{
if (RANDOM_LONG(1, 100) > 50)
pev->button = IN_ATTACK;
g_engfuncs.pfnRunPlayerMove( edict( ), pev->v_angle, f_move_speed,
0, 0, pev->button, 0,
gpGlobals->frametime * 1000 );
return;
}
// see if it's time to check for sv_maxspeed change...
if (f_speed_check_time <= gpGlobals->time)
{
// store current sv_maxspeed setting for quick access
f_max_speed = CVAR_GET_FLOAT("sv_maxspeed");
// set next check time to 2 seconds from now
f_speed_check_time = gpGlobals->time + 2.0f;
}
// see how far bot has moved since the previous position...
v_diff = v_prev_origin - pev->origin;
moved_distance = v_diff.Length();
v_prev_origin = pev->origin; // save current position as previous
f_move_speed = f_max_speed; // set to max speed unless known otherwise
// turn towards ideal_yaw by yaw_speed degrees
degrees_turned = BotChangeYaw( pev->yaw_speed );
if (degrees_turned >= pev->yaw_speed)
{
// if bot is still turning, turn in place by setting speed to 0
f_move_speed = 0;
}
else if (IsOnLadder( )) // check if the bot is on a ladder...
{
f_use_ladder_time = gpGlobals->time;
BotOnLadder( moved_distance ); // go handle the ladder movement
}
else // else handle movement related actions...
{
// bot is not on a ladder so clear ladder direction flag...
ladder_dir = 0;
if (f_botdontshoot == 0) // is bot targeting turned on?
pBotEnemy = BotFindEnemy( );
else
pBotEnemy = NULL; // clear enemy pointer (no ememy for you!)
if (pBotEnemy != NULL) // does an enemy exist?
{
BotShootAtEnemy( ); // shoot at the enemy
}
else if (pBotEnemy == NULL && g_pGameRules->IsHeavyRain())
{
SelectItem("weapon_jason"); // select the crowbar
pev->button |= IN_ATTACK; // use primary attack (whack! whack!)
// set next time to "shoot"
f_shoot_time = gpGlobals->time + 3 + 1;
}
else if (g_pGameRules->IsTest() && pausebots.value)
{
SelectItem("weapon_jason");
f_pause_time = gpGlobals->time + 99999; //Pause the bot dead.
}
else if (f_pause_time > gpGlobals->time) // is bot "paused"?
{
// you could make the bot look left then right, or look up
// and down, to make it appear that the bot is hunting for
// something (don't do anything right now)
f_move_speed = 0;
}
// is bot being "used" and can still follow "user"?
else if ((pBotUser != NULL) && BotFollowUser( ))
{
// do nothing here!
;
}
else
{
// no enemy, let's just wander around...
pev->v_angle.x = 0; // reset pitch to 0 (level horizontally)
pev->v_angle.z = 0; // reset roll to 0 (straight up and down)
pev->angles.x = 0;
pev->angles.y = pev->v_angle.y;
pev->angles.z = 0;
// check if bot should look for items now or not...
if (f_find_item < gpGlobals->time)
{
BotFindItem( ); // see if there are any visible items
}
// check if bot sees a tripmine...
if (b_see_tripmine)
{
// check if bot can shoot the tripmine...
if ((b_shoot_tripmine) && BotShootTripmine( ))
{
// shot at tripmine, may or may not have hit it, clear
// flags anyway, next BotFindItem will see it again if
// it is still there...
b_shoot_tripmine = FALSE;
b_see_tripmine = FALSE;
// pause for a while to allow tripmine to explode...
f_pause_time = gpGlobals->time + 0.5f;
}
else // run away!!!
{
Vector tripmine_angles;
tripmine_angles = UTIL_VecToAngles( v_tripmine_origin - pev->origin );
// face away from the tripmine
pev->ideal_yaw = -tripmine_angles.y;
// check for wrap around of angle...
if (pev->ideal_yaw > 180)
pev->ideal_yaw -= 360;
if (pev->ideal_yaw < -180)
pev->ideal_yaw += 360;
b_see_tripmine = FALSE;
f_move_speed = 0; // don't run while turning
}
}
// check if should use wall mounted health station...
else if (b_use_health_station)
{
if ((f_use_health_time + 10.0f) > gpGlobals->time)
{
f_move_speed = 0; // don't move while using health station
pev->button = IN_USE;
}
else
{
// bot is stuck trying to "use" a health station...
b_use_health_station = FALSE;
// don't look for items for a while since the bot
// could be stuck trying to get to an item
f_find_item = gpGlobals->time + 0.5f;
}
}
// check if should use wall mounted HEV station...
else if (b_use_HEV_station)
{
if ((f_use_HEV_time + 10.0f) > gpGlobals->time)
{
f_move_speed = 0; // don't move while using HEV station
pev->button = IN_USE;
}
else
{
// bot is stuck trying to "use" a HEV station...
b_use_HEV_station = FALSE;
// don't look for items for a while since the bot
// could be stuck trying to get to an item
f_find_item = gpGlobals->time + 0.5f;
}
}
else if (b_use_button)
{
f_move_speed = 0; // don't move while using elevator
BotUseLift( moved_distance );
}
else
{
TraceResult tr;
if (pev->waterlevel == 3) // check if the bot is underwater...
{
BotUnderWater( );
}
// check if there is a wall on the left...
if (!BotCheckWallOnLeft())
{
// if there was a wall on the left over 1/2 a second ago then
// 20% of the time randomly turn between 45 and 60 degrees
if ((f_wall_on_left != 0) &&
(f_wall_on_left <= gpGlobals->time - 0.5f) &&
(RANDOM_LONG(1, 100) <= 20))
{
pev->ideal_yaw += RANDOM_LONG(45, 60);
// check for wrap around of angle...
if (pev->ideal_yaw > 180)
pev->ideal_yaw -= 360;
if (pev->ideal_yaw < -180)
pev->ideal_yaw += 360;
f_move_speed = 0; // don't move while turning
f_dont_avoid_wall_time = gpGlobals->time + 1.0f;
}
f_wall_on_left = 0; // reset wall detect time
}
// check if there is a wall on the right...
if (!BotCheckWallOnRight())
{
// if there was a wall on the right over 1/2 a second ago then
// 20% of the time randomly turn between 45 and 60 degrees
if ((f_wall_on_right != 0) &&
(f_wall_on_right <= gpGlobals->time - 0.5f) &&
(RANDOM_LONG(1, 100) <= 20))
{
pev->ideal_yaw -= RANDOM_LONG(45, 60);
// check for wrap around of angle...
if (pev->ideal_yaw > 180)
pev->ideal_yaw -= 360;
if (pev->ideal_yaw < -180)
pev->ideal_yaw += 360;
f_move_speed = 0; // don't move while turning
f_dont_avoid_wall_time = gpGlobals->time + 1.0f;
}
f_wall_on_right = 0; // reset wall detect time
}
// check if bot is about to hit a wall. TraceResult gets returned
if ((f_dont_avoid_wall_time <= gpGlobals->time) &&
BotCantMoveForward( &tr ))
{
// ADD LATER
// need to check if bot can jump up or duck under here...
// ADD LATER
BotTurnAtWall( &tr );
}
// check if the bot hasn't moved much since the last location
else if ((moved_distance <= 1) && (!bot_was_paused))
{
// the bot must be stuck!
if (BotCanJumpUp( )) // can the bot jump onto something?
{
pev->button |= IN_JUMP; // jump up and move forward
}
else if (BotCanDuckUnder( )) // can the bot duck under something?
{
pev->button |= IN_DUCK; // duck down and move forward
}
else
{
f_move_speed = 0; // don't move while turning
// turn randomly between 30 and 60 degress
if (wander_dir == WANDER_LEFT)
pev->ideal_yaw += RANDOM_LONG(30, 60);
else
pev->ideal_yaw -= RANDOM_LONG(30, 60);
// check for wrap around of angle...
if (pev->ideal_yaw > 180)
pev->ideal_yaw -= 360;
if (pev->ideal_yaw < -180)
pev->ideal_yaw += 360;
// is the bot trying to get to an item?...
if (pBotPickupItem != NULL)
{
// don't look for items for a while since the bot
// could be stuck trying to get to an item
f_find_item = gpGlobals->time + 0.5f;
}
}
}
// should the bot pause for a while here?
if ((RANDOM_LONG(1, 1000) <= pause_frequency[bot_skill]) &&
(pBotUser == NULL)) // don't pause if being "used"
{
// set the time that the bot will stop "pausing"
f_pause_time = gpGlobals->time +
RANDOM_FLOAT(pause_time[bot_skill][0],
pause_time[bot_skill][1]);
f_move_speed = 0; // don't move while turning
}
}
}
}
if (f_move_speed < 1)
bot_was_paused = TRUE;
else
bot_was_paused = FALSE;
// TheFatal START - from www.telefragged.com/thefatal/jumblow.shtml
g_engfuncs.pfnRunPlayerMove( edict( ), pev->v_angle, f_move_speed,
0, 0, pev->button, 0,
gpGlobals->frametime * 1000 );
// TheFatal - END
}