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.
737 lines
21 KiB
737 lines
21 KiB
8 years ago
|
// botman's Half-Life bot example (http://jump.to/botman/)
|
||
|
|
||
|
#include "extdll.h"
|
||
|
#include "util.h"
|
||
|
#include "client.h"
|
||
|
#include "cbase.h"
|
||
|
#include "player.h"
|
||
|
#include "items.h"
|
||
|
#include "weapons.h"
|
||
|
#include "soundent.h"
|
||
|
#include "gamerules.h"
|
||
|
|
||
|
#include "bot.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 int gmsgHealth;
|
||
|
extern int gmsgCurWeapon;
|
||
|
extern int gmsgSetFOV;
|
||
|
|
||
|
int f_Observer = 0; // flag to indicate if we are in observer mode
|
||
|
|
||
|
ammo_check_t ammo_check[] = {
|
||
|
/* {"ammo_glockclip", "9mm", _9MM_MAX_CARRY},
|
||
|
{"ammo_9mmclip", "9mm", _9MM_MAX_CARRY},
|
||
|
{"ammo_9mmAR", "9mm", _9MM_MAX_CARRY},
|
||
|
{"ammo_mp5clip", "9mm", _9MM_MAX_CARRY},
|
||
|
{"ammo_9mmbox", "9mm", _9MM_MAX_CARRY},
|
||
|
{"ammo_chainboxclip", "9mm", _9MM_MAX_CARRY},
|
||
|
{"ammo_mp5grenades", "ARgrenades", M203_GRENADE_MAX_CARRY},
|
||
|
{"ammo_ARgrenades", "ARgrenades", M203_GRENADE_MAX_CARRY},
|
||
|
{"ammo_buckshot", "buckshot", BUCKSHOT_MAX_CARRY},
|
||
|
{"ammo_crossbow", "bolts", BOLTGUN_MAX_CARRY},
|
||
|
{"ammo_357", "357", _357_MAX_CARRY},
|
||
|
{"ammo_rpgclip", "rockets", ROCKET_MAX_CARRY},
|
||
|
{"ammo_egonclip", "uranium", URANIUM_MAX_CARRY},
|
||
|
{"ammo_gaussclip", "uranium", URANIUM_MAX_CARRY},
|
||
|
*/
|
||
|
{"", 0, 0}};
|
||
|
|
||
|
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 void SET_CLIENT_KEY_VALUE( int clientIndex, char *infobuffer,
|
||
|
char *key, char *value )
|
||
|
{
|
||
|
(*g_engfuncs.pfnSetClientKeyValue)( clientIndex, infobuffer, key, value );
|
||
|
}
|
||
|
|
||
|
|
||
|
void BotCreate( void )
|
||
|
{
|
||
|
edict_t *BotEnt;
|
||
|
|
||
|
BotEnt = CREATE_FAKE_CLIENT( "Bot" );
|
||
|
|
||
|
if (FNullEnt( BotEnt ))
|
||
|
ALERT ( at_console, "NULL Ent in CREATE_FAKE_CLIENT!\n" );
|
||
|
else
|
||
|
{
|
||
|
char ptr[128]; // allocate space for message from ClientConnect
|
||
|
CBot *BotClass;
|
||
|
char *infobuffer;
|
||
|
int clientIndex;
|
||
|
|
||
|
BotClass = GetClassPtr( (CBot *) VARS(BotEnt) );
|
||
|
infobuffer = GET_INFOBUFFER( BotClass->edict( ) );
|
||
|
clientIndex = BotClass->entindex( );
|
||
|
|
||
|
SET_CLIENT_KEY_VALUE( clientIndex, infobuffer, "model", "gina" );
|
||
|
|
||
|
ClientConnect( BotClass->edict( ), "Bot", "127.0.0.1", ptr );
|
||
|
DispatchSpawn( BotClass->edict( ) );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CBot::Spawn( )
|
||
|
{
|
||
|
pev->classname = MAKE_STRING( "player" );
|
||
|
pev->health = 100;
|
||
|
pev->armorvalue = 0;
|
||
|
pev->takedamage = DAMAGE_AIM;
|
||
|
pev->solid = SOLID_SLIDEBOX;
|
||
|
pev->movetype = MOVETYPE_WALK;
|
||
|
pev->max_health = pev->health;
|
||
|
pev->flags = FL_CLIENT | FL_FAKECLIENT;
|
||
|
pev->air_finished = gpGlobals->time + 12;
|
||
|
pev->dmg = 2; // initial water damage
|
||
|
pev->effects = 0;
|
||
|
pev->deadflag = DEAD_NO;
|
||
|
pev->dmg_take = 0;
|
||
|
pev->dmg_save = 0;
|
||
|
|
||
|
m_bitsHUDDamage = -1;
|
||
|
m_bitsDamageType = 0;
|
||
|
m_afPhysicsFlags = 0;
|
||
|
m_fLongJump = FALSE; // no longjump module.
|
||
|
m_iFOV = 0; // init field of view.
|
||
|
m_iClientFOV = -1; // make sure fov reset is sent
|
||
|
|
||
|
m_flNextDecalTime = 0; // let this player decal as soon as he spawns.
|
||
|
|
||
|
// wait a few seconds until user-defined message registrations are recieved by all clients
|
||
|
m_flgeigerDelay = gpGlobals->time + 2.0;
|
||
|
|
||
|
m_flTimeStepSound = 0;
|
||
|
m_iStepLeft = 0;
|
||
|
|
||
|
// some monsters use this to determine whether or not the player is looking at them.
|
||
|
m_flFieldOfView = 0.5;
|
||
|
|
||
|
m_bloodColor = BLOOD_COLOR_RED;
|
||
|
m_flNextAttack = gpGlobals->time;
|
||
|
StartSneaking( );
|
||
|
|
||
|
m_iFlashBattery = 99;
|
||
|
m_flFlashLightTime = 1; // force first message
|
||
|
|
||
|
// dont let uninitialized value here hurt the player
|
||
|
m_flFallVelocity = 0;
|
||
|
|
||
|
g_pGameRules->SetDefaultPlayerTeam( this );
|
||
|
g_pGameRules->GetPlayerSpawnSpot( this );
|
||
|
|
||
|
SET_MODEL( ENT( pev ), "models/player.mdl" );
|
||
|
|
||
|
g_ulModelIndexPlayer = pev->modelindex;
|
||
|
|
||
|
pev->sequence = LookupActivity( ACT_IDLE );
|
||
|
|
||
|
if (FBitSet( pev->flags, FL_DUCKING ))
|
||
|
UTIL_SetSize( pev, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX );
|
||
|
else
|
||
|
UTIL_SetSize( pev, VEC_HULL_MIN, VEC_HULL_MAX );
|
||
|
|
||
|
pev->view_ofs = VEC_VIEW;
|
||
|
|
||
|
Precache( );
|
||
|
|
||
|
m_HackedGunPos = Vector( 0, 32, 0 );
|
||
|
|
||
|
if (m_iPlayerSound == SOUNDLIST_EMPTY)
|
||
|
{
|
||
|
ALERT ( at_console, "Couldn't alloc player sound slot!\n" );
|
||
|
}
|
||
|
|
||
|
m_fNoPlayerSound = FALSE; // normal sound behavior.
|
||
|
|
||
|
m_pLastItem = NULL;
|
||
|
m_fInitHUD = TRUE;
|
||
|
m_iClientHideHUD = -1; // force this to be recalculated
|
||
|
m_fWeapon = FALSE;
|
||
|
m_pClientActiveItem = NULL;
|
||
|
m_iClientBattery = -1;
|
||
|
|
||
|
// reset all ammo values to 0
|
||
|
for ( int i = 0; i < MAX_AMMO_SLOTS; i++ )
|
||
|
{
|
||
|
// client ammo values also have to be reset
|
||
|
// (the death hud clear messages does on the client side)
|
||
|
m_rgAmmo[i] = 0;
|
||
|
m_rgAmmoLast[i] = 0;
|
||
|
}
|
||
|
|
||
|
m_lastx = m_lasty = 0;
|
||
|
|
||
|
g_pGameRules->PlayerSpawn( this );
|
||
|
|
||
|
SetThink( BotThink );
|
||
|
pev->nextthink = gpGlobals->time + .1;
|
||
|
|
||
|
v_prev_origin = pev->origin; // save previous position
|
||
|
f_shoot_time = 0;
|
||
|
|
||
|
f_max_speed = CVAR_GET_FLOAT("sv_maxspeed") * .60;
|
||
|
f_speed_check_time = gpGlobals->time + 1.0;
|
||
|
ladder_dir = 0;
|
||
|
|
||
|
if (RANDOM_LONG(1, 100) & 1)
|
||
|
wander_dir = WANDER_LEFT;
|
||
|
else
|
||
|
wander_dir = WANDER_RIGHT;
|
||
|
|
||
|
f_pause_time = 0;
|
||
|
f_find_item = 0;
|
||
|
|
||
|
pBotEnemy = NULL;
|
||
|
}
|
||
|
|
||
|
|
||
|
void CBot::Killed( entvars_t *pevAttacker, int iGib )
|
||
|
{
|
||
|
CSound *pSound;
|
||
|
|
||
|
g_pGameRules->PlayerKilled( this, pevAttacker, g_pevLastInflictor );
|
||
|
|
||
|
if (m_pTank != NULL)
|
||
|
{
|
||
|
m_pTank->Use( this, this, USE_OFF, 0 );
|
||
|
m_pTank = NULL;
|
||
|
}
|
||
|
|
||
|
// this client isn't going to be thinking for a while, so reset the sound
|
||
|
// until they respawn
|
||
|
pSound = CSoundEnt::SoundPointerForIndex( CSoundEnt::ClientSoundIndex(edict( )) );
|
||
|
{
|
||
|
if (pSound)
|
||
|
{
|
||
|
pSound->Reset( );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
SetAnimation( PLAYER_DIE );
|
||
|
|
||
|
pev->modelindex = g_ulModelIndexPlayer; // don't use eyes
|
||
|
|
||
|
#if !defined(DUCKFIX)
|
||
|
pev->view_ofs = Vector( 0, 0, -8 );
|
||
|
#endif
|
||
|
pev->deadflag = DEAD_DYING;
|
||
|
pev->solid = SOLID_NOT;
|
||
|
pev->movetype = MOVETYPE_TOSS;
|
||
|
// pev->movetype = MOVETYPE_NONE; // should we use this instead???
|
||
|
|
||
|
ClearBits( pev->flags, FL_ONGROUND );
|
||
|
|
||
|
if (pev->velocity.z < 10)
|
||
|
pev->velocity.z += RANDOM_FLOAT( 0, 300 );
|
||
|
|
||
|
// clear out the suit message cache so we don't keep chattering
|
||
|
SetSuitUpdate( NULL, FALSE, 0 );
|
||
|
|
||
|
// send "health" update message to zero
|
||
|
m_iClientHealth = 0;
|
||
|
|
||
|
MESSAGE_BEGIN( MSG_ONE, gmsgHealth, NULL, pev );
|
||
|
WRITE_BYTE( m_iClientHealth );
|
||
|
MESSAGE_END( );
|
||
|
|
||
|
// Tell Ammo Hud that the player is dead
|
||
|
MESSAGE_BEGIN( MSG_ONE, gmsgCurWeapon, NULL, pev );
|
||
|
WRITE_BYTE( 0 );
|
||
|
WRITE_BYTE( 0xFF );
|
||
|
WRITE_BYTE( 0xFF );
|
||
|
MESSAGE_END( );
|
||
|
|
||
|
// reset FOV
|
||
|
m_iFOV = m_iClientFOV = 0;
|
||
|
|
||
|
MESSAGE_BEGIN( MSG_ONE, gmsgSetFOV, NULL, pev );
|
||
|
WRITE_BYTE( 0 );
|
||
|
MESSAGE_END( );
|
||
|
|
||
|
// UNDONE: Put this in, but add FFADE_PERMANENT and make fade time 8.8 instead of 4.12
|
||
|
// UTIL_ScreenFade( edict( ), Vector( 128, 0, 0 ), 6, 15, 255, FFADE_OUT | FFADE_MODULATE );
|
||
|
|
||
|
if (( pev->health < -40 && iGib != GIB_NEVER ) || iGib == GIB_ALWAYS)
|
||
|
{
|
||
|
GibMonster( ); // This clears pev->model
|
||
|
|
||
|
// TAKE THIS OUT!!! THIS HAPPENS SOMETIMES WHEN HEALTH IS < -40, THEN THE
|
||
|
// THINK FUNCTION DOESN'T EVER GET SET TO PlayerDeathThink
|
||
|
// (should we REALLY be doing this???)
|
||
|
// pev->effects |= EF_NODRAW;
|
||
|
// return;
|
||
|
}
|
||
|
|
||
|
DeathSound( );
|
||
|
|
||
|
pev->angles.x = 0; // don't let the player tilt
|
||
|
pev->angles.z = 0;
|
||
|
|
||
|
SetThink( PlayerDeathThink );
|
||
|
pev->nextthink = gpGlobals->time + 0.1;
|
||
|
}
|
||
|
|
||
|
|
||
|
void CBot::PlayerDeathThink( void )
|
||
|
{
|
||
|
float flForward;
|
||
|
|
||
|
pev->nextthink = gpGlobals->time + 0.1;
|
||
|
|
||
|
if (FBitSet( pev->flags, FL_ONGROUND ))
|
||
|
{
|
||
|
flForward = pev->velocity.Length( ) - 20;
|
||
|
if (flForward <= 0)
|
||
|
pev->velocity = g_vecZero;
|
||
|
else
|
||
|
pev->velocity = flForward * pev->velocity.Normalize( );
|
||
|
}
|
||
|
|
||
|
if (HasWeapons( ))
|
||
|
{
|
||
|
// we drop the guns here because weapons that have an area effect and can kill their user
|
||
|
// will sometimes crash coming back from CBasePlayer::Killed() if they kill their owner because the
|
||
|
// player class sometimes is freed. It's safer to manipulate the weapons once we know
|
||
|
// we aren't calling into any of their code anymore through the player pointer.
|
||
|
|
||
|
PackDeadPlayerItems( );
|
||
|
}
|
||
|
|
||
|
if (pev->modelindex && (!m_fSequenceFinished) && (pev->deadflag == DEAD_DYING))
|
||
|
{
|
||
|
StudioFrameAdvance( );
|
||
|
|
||
|
m_iRespawnFrames++;
|
||
|
if (m_iRespawnFrames < 60) // animations should be no longer than this
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (pev->deadflag == DEAD_DYING)
|
||
|
{
|
||
|
pev->deadflag = DEAD_DEAD;
|
||
|
DROP_TO_FLOOR ( ENT(pev) ); // put the body on the ground
|
||
|
}
|
||
|
|
||
|
StopAnimation( );
|
||
|
|
||
|
pev->effects |= EF_NOINTERP;
|
||
|
pev->framerate = 0.0;
|
||
|
|
||
|
if (pev->deadflag == DEAD_DEAD)
|
||
|
{
|
||
|
if (g_pGameRules->FPlayerCanRespawn( this ))
|
||
|
{
|
||
|
m_fDeadTime = gpGlobals->time;
|
||
|
pev->deadflag = DEAD_RESPAWNABLE;
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// check if time to respawn...
|
||
|
if (gpGlobals->time > (m_fDeadTime + 5.0))
|
||
|
{
|
||
|
pev->button = 0;
|
||
|
m_iRespawnFrames = 0;
|
||
|
|
||
|
//ALERT( at_console, "Respawn\n" );
|
||
|
|
||
|
respawn( pev, !(m_afPhysicsFlags & PFLAG_OBSERVER) );
|
||
|
pev->nextthink = -1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CBot::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.
|
||
|
|
||
|
UTIL_ClientPrintAll( HUD_PRINTNOTIFY, buffer );
|
||
|
}
|
||
|
|
||
|
|
||
|
void CBot::BotOnLadder( void )
|
||
|
{
|
||
|
// 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) // are we currently going up?
|
||
|
{
|
||
|
pev->v_angle.x = -60; // look upwards
|
||
|
|
||
|
Vector v_diff = v_prev_origin - pev->origin;
|
||
|
|
||
|
// check if we haven't moved much since the last location...
|
||
|
if (v_diff.Length() <= 1)
|
||
|
{
|
||
|
// we must be stuck, change directions...
|
||
|
|
||
|
pev->v_angle.x = 60; // look downwards
|
||
|
ladder_dir = LADDER_DOWN;
|
||
|
}
|
||
|
}
|
||
|
else if (ladder_dir == LADDER_DOWN) // are we currently going down?
|
||
|
{
|
||
|
pev->v_angle.x = 60; // look downwards
|
||
|
|
||
|
Vector v_diff = v_prev_origin - pev->origin;
|
||
|
|
||
|
// check if we haven't moved much since the last location...
|
||
|
if (v_diff.Length() <= 1)
|
||
|
{
|
||
|
// we must be stuck, change directions...
|
||
|
|
||
|
pev->v_angle.x = -60; // look upwards
|
||
|
ladder_dir = LADDER_UP;
|
||
|
}
|
||
|
}
|
||
|
else // we haven'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 we are looking, up or down)
|
||
|
pev->button |= IN_FORWARD;
|
||
|
|
||
|
f_move_speed = f_max_speed;
|
||
|
}
|
||
|
|
||
|
|
||
|
CBaseEntity * CBot::BotFindEnemy( void )
|
||
|
{
|
||
|
if (pBotEnemy != NULL) // do we already have an enemy?
|
||
|
{
|
||
|
if (!pBotEnemy->IsAlive( ))
|
||
|
{
|
||
|
// the enemy is dead, jump for joy every once in a while...
|
||
|
if (RANDOM_LONG(1, 10) == 1)
|
||
|
pev->button |= IN_JUMP;
|
||
|
}
|
||
|
else if (FVisible( pBotEnemy ) && FInViewCone( pBotEnemy ))
|
||
|
{
|
||
|
// if enemy is still visible and in field of view, keep it
|
||
|
return (pBotEnemy);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int i;
|
||
|
float nearestdistance = 1000;
|
||
|
CBaseEntity *pNewEnemy = NULL;
|
||
|
|
||
|
// search the world for players...
|
||
|
for (i = 1; i <= gpGlobals->maxClients; i++)
|
||
|
{
|
||
|
CBaseEntity *pPlayer = UTIL_PlayerByIndex( i );
|
||
|
|
||
|
// skip invalid players and skip self (i.e. this bot)
|
||
|
if ((!pPlayer) || (pPlayer == this))
|
||
|
continue;
|
||
|
|
||
|
// skip this player if not alive (i.e. dead or dying)
|
||
|
if (pPlayer->pev->deadflag != DEAD_NO)
|
||
|
continue;
|
||
|
|
||
|
// skip players that are netclients (i.e. not bot, observer mode)
|
||
|
if (pPlayer->IsNetClient() && f_Observer)
|
||
|
continue;
|
||
|
|
||
|
// see if you can see the player (within your field of view)...
|
||
|
if (FVisible( pPlayer ) && FInViewCone( pPlayer ))
|
||
|
{
|
||
|
float distance = (pPlayer->pev->origin - pev->origin).Length( );
|
||
|
if (distance < nearestdistance)
|
||
|
{
|
||
|
nearestdistance = distance;
|
||
|
pNewEnemy = pPlayer;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return (pNewEnemy);
|
||
|
}
|
||
|
|
||
|
|
||
|
void CBot::BotShootAtEnemy( void )
|
||
|
{
|
||
|
float f_length;
|
||
|
|
||
|
// aim for the head and/or body
|
||
|
Vector v_src; // dummy vector for Player::BodyTarget()
|
||
|
Vector v_enemy = pBotEnemy->BodyTarget( v_src ) - GetGunPosition( );
|
||
|
|
||
|
// face the enemy
|
||
|
pev->v_angle = UTIL_VecToAngles( v_enemy );
|
||
|
pev->v_angle.x = -pev->v_angle.x; //adjust pitch to point gun
|
||
|
|
||
|
v_enemy.z = 0; // ignore z component (up & down)
|
||
|
|
||
|
f_length = v_enemy.Length(); // how far away is the enemy scum?
|
||
|
|
||
|
if (f_length > 200) // run if distance to enemy is far
|
||
|
f_move_speed = f_max_speed;
|
||
|
else if (f_length > 10) // walk if distance is closer
|
||
|
f_move_speed = f_max_speed / 2;
|
||
|
else // don't move if close enough
|
||
|
f_move_speed = 0.0;
|
||
|
|
||
|
// is it time to shoot yet?
|
||
|
if (f_shoot_time <= gpGlobals->time)
|
||
|
{
|
||
|
pev->button |= IN_ATTACK; // use primary attack (bang! bang!)
|
||
|
|
||
|
// set next time to shoot
|
||
|
f_shoot_time = gpGlobals->time + 0.3 + RANDOM_FLOAT(0, 0.2);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CBot::BotFindItem( void )
|
||
|
{
|
||
|
CBaseEntity *pEntity = NULL;
|
||
|
CBasePlayerItem *pItem = NULL;
|
||
|
float radius = 100;
|
||
|
|
||
|
while ((pEntity = UTIL_FindEntityInSphere( pEntity, pev->origin, radius )) != NULL)
|
||
|
{
|
||
|
// check to make sure that the entity is visible and in field of view...
|
||
|
if (FVisible( pEntity ) && FInViewCone( pEntity ))
|
||
|
{
|
||
|
// check if entity is a weapon...
|
||
|
if (strncmp("weapon_", STRING(pEntity->pev->classname), 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 ))
|
||
|
{
|
||
|
// let's head off toward that item...
|
||
|
Vector v_item = pEntity->pev->origin - pev->origin;
|
||
|
pev->v_angle = UTIL_VecToAngles( v_item );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// check if entity is ammo...
|
||
|
if (strncmp("ammo_", STRING(pEntity->pev->classname), 5) == 0)
|
||
|
{
|
||
|
CBasePlayerAmmo *pAmmo = (CBasePlayerAmmo *)pEntity;
|
||
|
char ammo_name[40];
|
||
|
int i;
|
||
|
BOOL can_pickup = FALSE;
|
||
|
|
||
|
// check if the item is not visible (i.e. has not respawned)
|
||
|
if (pAmmo->pev->effects & EF_NODRAW)
|
||
|
continue;
|
||
|
|
||
|
strcpy(ammo_name, STRING(pEntity->pev->classname));
|
||
|
|
||
|
i = 0;
|
||
|
while (ammo_check[i].ammo_name[0])
|
||
|
{
|
||
|
if (strcmp(ammo_check[i].ammo_name, ammo_name) == 0)
|
||
|
{
|
||
|
// see if we 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 (can_pickup)
|
||
|
{
|
||
|
// let's head off toward that item...
|
||
|
Vector v_item = pEntity->pev->origin - pev->origin;
|
||
|
pev->v_angle = UTIL_VecToAngles( v_item );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// check if entity is a battery...
|
||
|
if (strcmp("item_battery", STRING(pEntity->pev->classname)) == 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)))
|
||
|
{
|
||
|
// let's head off toward that item...
|
||
|
Vector v_item = pEntity->pev->origin - pev->origin;
|
||
|
pev->v_angle = UTIL_VecToAngles( v_item );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// check if entity is a healthkit...
|
||
|
if (strcmp("item_healthkit", STRING(pEntity->pev->classname)) == 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 <= 90)
|
||
|
{
|
||
|
// let's head off toward that item...
|
||
|
Vector v_item = pEntity->pev->origin - pev->origin;
|
||
|
pev->v_angle = UTIL_VecToAngles( v_item );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// check if entity is a packed up weapons box...
|
||
|
if (strcmp("weaponbox", STRING(pEntity->pev->classname)) == 0)
|
||
|
{
|
||
|
// let's head off toward that item...
|
||
|
Vector v_item = pEntity->pev->origin - pev->origin;
|
||
|
pev->v_angle = UTIL_VecToAngles( v_item );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CBot::BotThink( void )
|
||
|
{
|
||
|
if (!IsInWorld( ))
|
||
|
{
|
||
|
SetTouch( NULL );
|
||
|
UTIL_Remove( this );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (!IsAlive( ))
|
||
|
{
|
||
|
// if bot is dead, don't take up any space in world (set size to 0)
|
||
|
UTIL_SetSize( pev, Vector(0, 0, 0), Vector(0, 0, 0));
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
pev->button = 0; // make sure no buttons are pressed
|
||
|
|
||
|
|
||
|
if (IsOnLadder( )) // check if the bot is on a ladder...
|
||
|
{
|
||
|
BotOnLadder( ); // go handle the ladder movement
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// bot is not on a ladder so clear ladder direction flag...
|
||
|
ladder_dir = 0;
|
||
|
|
||
|
pBotEnemy = BotFindEnemy( ); // try to find an enemy
|
||
|
|
||
|
if (pBotEnemy != NULL) // does an enemy exist?
|
||
|
{
|
||
|
BotShootAtEnemy( ); // go handle shooting at the enemy
|
||
|
}
|
||
|
else if (f_pause_time > gpGlobals->time) // are we "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;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// no enemy, let's just wander around...
|
||
|
|
||
|
// check if we should look for items now or not...
|
||
|
if (f_find_item < gpGlobals->time)
|
||
|
{
|
||
|
BotFindItem( ); // see if there are any visible items
|
||
|
}
|
||
|
|
||
|
pev->v_angle.x = 0; // reset pitch to 0 (level horizontally)
|
||
|
pev->v_angle.z = 0; // reset roll to 0 (straight up and down)
|
||
|
|
||
|
Vector v_diff = v_prev_origin - pev->origin;
|
||
|
|
||
|
// check if we haven't moved much since the last location...
|
||
|
if (v_diff.Length() <= 1)
|
||
|
{
|
||
|
// we must be stuck!
|
||
|
|
||
|
// turn randomly between 10 and 30 degress from current direction
|
||
|
if (wander_dir == WANDER_LEFT)
|
||
|
pev->v_angle.y += RANDOM_LONG(10, 30);
|
||
|
else
|
||
|
pev->v_angle.y -= RANDOM_LONG(10, 30);
|
||
|
|
||
|
// check for wrap around of angle...
|
||
|
if (pev->v_angle.y > 180)
|
||
|
pev->v_angle.y -= 360;
|
||
|
if (pev->v_angle.y < -180)
|
||
|
pev->v_angle.y += 360;
|
||
|
|
||
|
// also don't look for items for 5 seconds since we could be
|
||
|
// stuck trying to get to an item
|
||
|
f_find_item = gpGlobals->time + 5.0;
|
||
|
}
|
||
|
|
||
|
f_move_speed = f_max_speed;
|
||
|
|
||
|
if (RANDOM_LONG(1, 1000) <= 4) // should we pause for a while here?
|
||
|
{
|
||
|
// set the time that the bot will stop "pausing"
|
||
|
f_pause_time = gpGlobals->time + RANDOM_FLOAT(0.5, 1.0);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
v_prev_origin = pev->origin; // save current position as previous
|
||
|
|
||
|
// g_engfuncs.pfnRunPlayerMove(<bot edict>, <bot's view angles>,
|
||
|
// <forward move>, <side move>, <up move> (does nothing?),
|
||
|
// <buttons> (IN_ATTACK, etc.), <impulse number>, <msec value>);
|
||
|
|
||
|
g_engfuncs.pfnRunPlayerMove( edict( ), pev->v_angle, f_move_speed,
|
||
|
0, 0, pev->button, 0, 50 );
|
||
|
}
|
||
|
|