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.
2111 lines
64 KiB
2111 lines
64 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 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; |
|
|
|
#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 11 |
|
|
|
// 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, |
|
FALSE}; |
|
|
|
// store the names of the models... |
|
const char *bot_skins[MAX_SKINS] = { |
|
"barney", "gina", "gman", "gordon", "helmet", |
|
"hgrunt", "recon", "robo", "scientist", "zombie", |
|
"huylo"}; |
|
|
|
// store the player names for each of the models... |
|
const char *bot_names[MAX_SKINS] = { |
|
"Barney", "Gina", "G-Man", "Gordon", "Helmet", |
|
"H-Grunt", "Recon", "Robo", "Scientist", "Zombie", |
|
"Huylo"}; |
|
|
|
// 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... |
|
char hgrunt_sounds[][30] = { HG_SND1, HG_SND2, HG_SND3, HG_SND4, HG_SND5 }; |
|
char barney_sounds[][30] = { BA_SND1, BA_SND2, BA_SND3, BA_SND4, BA_SND5 }; |
|
char scientist_sounds[][30] = { 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 ); |
|
} |
|
|
|
inline char *GET_INFO_KEY_VALUE( const char *infobuffer, const char *key ) |
|
{ |
|
return (g_engfuncs.pfnInfoKeyValue( infobuffer, key )); |
|
} |
|
|
|
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, 10); // there are ten 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\n", 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, zombie or huylo\n"); |
|
if (IS_DEDICATED_SERVER()) |
|
printf(" recon, robo, scientist, zombie or huylo\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.0; |
|
|
|
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; |
|
} |
|
|
|
f_pain_time = gpGlobals->time + 5.0; |
|
|
|
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.0; |
|
|
|
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.0) |
|
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.0) |
|
{ |
|
// 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.0) |
|
{ |
|
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.0; |
|
|
|
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.0) |
|
{ |
|
// 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.0; |
|
} |
|
|
|
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.0; |
|
} |
|
|
|
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.0; |
|
} |
|
|
|
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.0; |
|
} |
|
|
|
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.0) < 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.0) || (tr2.flFraction >= 1.0)) |
|
{ |
|
// 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.0) || (tr2.flFraction >= 1.0)) |
|
{ |
|
// 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.0) || (tr2.flFraction >= 1.0)) |
|
{ |
|
// 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.0) |
|
{ |
|
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.0) |
|
{ |
|
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.0) |
|
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.0) |
|
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.0) |
|
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.0) |
|
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.0) |
|
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.0) |
|
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.0) |
|
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.0) |
|
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.0) |
|
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.0) |
|
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.0) |
|
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.0) |
|
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.0) |
|
{ |
|
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.0) |
|
{ |
|
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 |
|
|
|
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.0; |
|
} |
|
|
|
// 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 (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.5; |
|
} |
|
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.0) > 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.5; |
|
} |
|
} |
|
|
|
// check if should use wall mounted HEV station... |
|
else if (b_use_HEV_station) |
|
{ |
|
if ((f_use_HEV_time + 10.0) > 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.5; |
|
} |
|
} |
|
|
|
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.5) && |
|
(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.0; |
|
} |
|
|
|
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.5) && |
|
(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.0; |
|
} |
|
|
|
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.5; |
|
} |
|
} |
|
} |
|
|
|
// 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 |
|
}
|
|
|