Browse Source

Merge original botman's bot 10 source code.

pull/1/head
Night Owl 7 years ago
parent
commit
e308b0c7d1
  1. 6
      dlls/Android.mk
  2. 5
      dlls/CMakeLists.txt
  3. 2109
      dlls/bot/bot.cpp
  4. 168
      dlls/bot/bot.h
  5. 909
      dlls/bot/bot_combat.cpp
  6. 142
      dlls/bot/botcam.cpp
  7. 26
      dlls/bot/botcam.h
  8. 596
      dlls/client.cpp
  9. 8
      dlls/game.cpp
  10. 38
      dlls/multiplay_gamerules.cpp
  11. 3
      dlls/player.cpp
  12. 8
      dlls/player.h

6
dlls/Android.mk

@ -26,7 +26,8 @@ LOCAL_C_INCLUDES := $(SDL_PATH)/include \
$(LOCAL_PATH)/../engine \ $(LOCAL_PATH)/../engine \
$(LOCAL_PATH)/../public \ $(LOCAL_PATH)/../public \
$(LOCAL_PATH)/../pm_shared \ $(LOCAL_PATH)/../pm_shared \
$(LOCAL_PATH)/../game_shared $(LOCAL_PATH)/../game_shared \
$(LOCAL_PATH)/bot
LOCAL_SRC_FILES := agrunt.cpp airtank.cpp \ LOCAL_SRC_FILES := agrunt.cpp airtank.cpp \
aflock.cpp \ aflock.cpp \
@ -126,6 +127,9 @@ LOCAL_SRC_FILES := agrunt.cpp airtank.cpp \
world.cpp \ world.cpp \
xen.cpp \ xen.cpp \
zombie.cpp \ zombie.cpp \
bot/bot_combat.cpp \
bot/bot.cpp \
bot/botcam.cpp \
../pm_shared/pm_debug.c \ ../pm_shared/pm_debug.c \
../pm_shared/pm_math.c \ ../pm_shared/pm_math.c \
../pm_shared/pm_shared.c ../pm_shared/pm_shared.c

5
dlls/CMakeLists.txt

@ -128,12 +128,15 @@ set (SVDLL_SOURCES
world.cpp world.cpp
xen.cpp xen.cpp
zombie.cpp zombie.cpp
bot/bot_combat.cpp
bot/bot.cpp
bot/botcam.cpp
../pm_shared/pm_debug.c ../pm_shared/pm_debug.c
../pm_shared/pm_math.c ../pm_shared/pm_math.c
../pm_shared/pm_shared.c ../pm_shared/pm_shared.c
) )
include_directories (. wpn_shared ../common ../engine ../pm_shared ../game_shared ../public) include_directories (. wpn_shared ../common ../engine ../pm_shared ../game_shared ../public bot)
if(USE_VOICEMGR) if(USE_VOICEMGR)
set(SVDLL_SOURCES set(SVDLL_SOURCES

2109
dlls/bot/bot.cpp

File diff suppressed because it is too large Load Diff

168
dlls/bot/bot.h

@ -0,0 +1,168 @@
// botman's Half-Life bot example
//
// http://planethalflife.com/botman/
//
// bot.h
//
#ifndef BOT_H
#define BOT_H
#define LADDER_UP 1
#define LADDER_DOWN 2
#define WANDER_LEFT 1
#define WANDER_RIGHT 2
#define MODEL_HGRUNT 1
#define MODEL_BARNEY 2
#define MODEL_SCIENTIST 3
#define BOT_YAW_SPEED 20 // degrees per 10th of second turning speed
#define BOT_SKIN_LEN 16
#define BOT_NAME_LEN 31
typedef struct // used in checking if bot can pick up ammo
{
char *ammo_name;
char *weapon_name;
int max_carry;
} ammo_check_t;
#define BOT_IDLE 0
#define BOT_NEED_TO_KICK 1
#define BOT_NEED_TO_RESPAWN 2
#define BOT_IS_RESPAWNING 3
typedef struct // used to respawn bot at end of round (time/frag limit)
{
BOOL is_used; // is this slot in use?
int state; // current state of the bot
char skin[BOT_SKIN_LEN+1];
char name[BOT_NAME_LEN+1];
char skill[2];
CBasePlayer *pBot;
} respawn_t;
#define HG_SND1 "hgrunt/gr_pain1.wav"
#define HG_SND2 "hgrunt/gr_pain2.wav"
#define HG_SND3 "hgrunt/gr_pain3.wav"
#define HG_SND4 "hgrunt/gr_pain4.wav"
#define HG_SND5 "hgrunt/gr_pain5.wav"
#define BA_SND1 "barney/ba_bring.wav"
#define BA_SND2 "barney/ba_pain1.wav"
#define BA_SND3 "barney/ba_pain2.wav"
#define BA_SND4 "barney/ba_pain3.wav"
#define BA_SND5 "barney/ba_die1.wav"
#define SC_SND1 "scientist/sci_fear8.wav"
#define SC_SND2 "scientist/sci_fear13.wav"
#define SC_SND3 "scientist/sci_fear14.wav"
#define SC_SND4 "scientist/sci_fear15.wav"
#define SC_SND5 "scientist/sci_pain3.wav"
#define BA_TNT1 "barney/ba_another.wav"
#define BA_TNT2 "barney/ba_endline.wav"
#define BA_TNT3 "barney/ba_gotone.wav"
#define BA_TNT4 "barney/ba_seethat.wav"
#define BA_TNT5 "barney/ba_tomb.wav"
#define SC_TNT1 "scientist/odorfromyou.wav"
#define SC_TNT2 "scientist/smellburn.wav"
#define SC_TNT3 "scientist/somethingfoul.wav"
#define SC_TNT4 "scientist/youlookbad.wav"
#define SC_TNT5 "scientist/youneedmedic.wav"
#define USE_TEAMPLAY_SND "barney/teamup2.wav"
#define USE_TEAMPLAY_LATER_SND "barney/seeya.wav"
#define USE_TEAMPLAY_ENEMY_SND "barney/ba_raincheck.wav"
void BotDebug( char *buffer ); // print out message to HUD for debugging
class CBot : public CBasePlayer //Derive a bot class from CBasePlayer
{
public:
Vector v_prev_origin; // previous origin (i.e. location)
float f_shoot_time; // next time to shoot weapon at
float f_max_speed; // last sv_maxspeed setting
float f_speed_check_time; // check sv_maxspeed every so often
float f_move_speed; // speed at which the bot will move
int ladder_dir; // direction traveling on ladder (UP or DOWN)
int wander_dir; // randomly wander left or right
float f_pause_time; // timeout for periods when the bot pauses
float f_find_item; // timeout for not looking for items
char model_name[20];
int bot_model;
int bot_skill; // bot skill level (0=very good, 4=very bad)
float f_pain_time; // time when pain sound can be spoken
BOOL b_use_health_station; // set if bot should "use" health station
float f_use_health_time; // time when b_use_health_station is set
BOOL b_use_HEV_station; // set if bot should "use" HEV station
float f_use_HEV_time; // time when b_use_HEV_station is set
BOOL b_use_button; // set if bot should "use" button
float f_use_button_time; // time when b_use_button is set
BOOL b_lift_moving; // flag set when lift (elevator) is moving
float f_use_ladder_time; // time when bot sees a ladder
BOOL b_see_tripmine; // set if bot "sees" a tripmine
BOOL b_shoot_tripmine; // set if bot should shoot a tripmine
Vector v_tripmine_origin; // origin of tripmine
float f_fire_gauss; // time to release secondary fire on gauss gun
BOOL bot_was_paused; // TRUE if bot was previously "paused"
float f_weapon_inventory_time; // time to check weapon inventory
int respawn_index; // index in respawn structure for this bot
float f_dont_avoid_wall_time; // time when avoiding walls is OK
float f_bot_use_time; // time the bot was "used" by player
float f_wall_on_left; // time since bot has had a wall on the left
float f_wall_on_right; // time since bot has had a wall on the right
CBaseEntity *pBotEnemy; // pointer to bot's enemy
CBaseEntity *pBotUser; // pointer to player using bot
CBaseEntity *pBotPickupItem; // pointer to item we are trying to get
CBasePlayerItem *weapon_ptr[MAX_WEAPONS]; // pointer array to weapons
int primary_ammo[MAX_WEAPONS]; // amount of primary ammo available
int secondary_ammo[MAX_WEAPONS]; // amount of secondary ammo available
char message[256]; // buffer for debug messages
void Spawn( void );
void BotThink( void ); // think function for the bot
// Bots should return FALSE for this, they can't receive NET messages
virtual BOOL IsNetClient( void ) { return FALSE; }
int BloodColor() { return BLOOD_COLOR_RED; }
int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker,
float flDamage, int bitsDamageType );
int ObjectCaps() { return FCAP_IMPULSE_USE; };
void Use( CBaseEntity *pActivator, CBaseEntity *pCaller,
USE_TYPE useType, float value );
int BotInFieldOfView( Vector dest );
BOOL BotEntityIsVisible( Vector dest );
float BotChangeYaw( float speed );
void BotOnLadder( float moved_distance );
void BotUnderWater( void );
CBaseEntity * BotFindEnemy( void );
Vector BotBodyTarget( CBaseEntity *pBotEnemy );
void BotWeaponInventory( void );
BOOL BotFireWeapon( Vector enemy, int weapon_choice = 0, BOOL primary = TRUE );
void BotShootAtEnemy( void );
void BotFindItem( void );
void BotUseLift( float moved_distance );
void BotTurnAtWall( TraceResult *tr );
BOOL BotCantMoveForward( TraceResult *tr );
BOOL BotCanJumpUp( void );
BOOL BotCanDuckUnder( void );
BOOL BotShootTripmine( void );
BOOL BotFollowUser( void ); // returns FALSE if can find "user"
BOOL BotCheckWallOnLeft( void );
BOOL BotCheckWallOnRight( void );
};
#endif // BOT_H

909
dlls/bot/bot_combat.cpp

@ -0,0 +1,909 @@
// botman's Half-Life bot example
//
// http://planethalflife.com/botman/
//
// bot_combat.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"
extern int f_Observer; // flag to indicate if player is in observer mode
// weapon firing delay based on skill (min and max delay for each weapon)
float primary_fire_delay[WEAPON_SNARK+1][5][2] = {
// WEAPON_NONE - NOT USED
{{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}},
// WEAPON_CROWBAR
{{0.0, 0.1}, {0.2, 0.3}, {0.3, 0.5}, {0.4, 0.6}, {0.6, 1.0}},
// WEAPON_GLOCK (9mm)
{{0.0, 0.1}, {0.1, 0.2}, {0.2, 0.3}, {0.3, 0.4}, {0.4, 0.5}},
// WEAPON_PYTHON (357)
{{0.0, 0.25}, {0.2, 0.5}, {0.4, 0.8}, {1.0, 1.3}, {1.5, 2.0}},
// WEAPON_MP5 (9mmAR)
{{0.0, 0.1}, {0.1, 0.3}, {0.3, 0.5}, {0.4, 0.6}, {0.5, 0.8}},
// WEAPON_CHAINGUN - NOT USED
{{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}},
// WEAPON_CROSSBOW
{{0.0, 0.25}, {0.2, 0.4}, {0.5, 0.7}, {0.8, 1.0}, {1.0, 1.3}},
// WEAPON_SHOTGUN
{{0.0, 0.25}, {0.2, 0.5}, {0.4, 0.8}, {0.6, 1.2}, {0.8, 2.0}},
// WEAPON_RPG
{{1.0, 3.0}, {2.0, 4.0}, {3.0, 5.0}, {4.0, 6.0}, {5.0, 7.0}},
// WEAPON_GAUSS
{{0.0, 0.1}, {0.2, 0.3}, {0.3, 0.5}, {0.5, 0.8}, {1.0, 1.2}},
// WEAPON_EGON
{{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}},
// WEAPON_HORNETGUN
{{0.0, 0.1}, {0.25, 0.4}, {0.4, 0.7}, {0.6, 1.0}, {1.0, 1.5}},
// WEAPON_HANDGRENADE
{{1.0, 1.4}, {1.4, 2.0}, {1.8, 2.6}, {2.0, 3.0}, {2.5, 3.8}},
// WEAPON_TRIPMINE
{{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}},
// WEAPON_SATCHEL
{{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}},
// WEAPON_SNARK
{{0.0, 0.1}, {0.1, 0.2}, {0.2, 0.5}, {0.5, 0.7}, {0.6, 1.0}},
};
float secondary_fire_delay[WEAPON_SNARK+1][5][2] = {
// WEAPON_NONE - NOT USED
{{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}},
// WEAPON_CROWBAR - Not applicable
{{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}},
// WEAPON_GLOCK (9mm)
{{0.0, 0.1}, {0.0, 0.1}, {0.1, 0.2}, {0.1, 0.2}, {0.2, 0.4}},
// WEAPON_PYTHON (357)
{{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}},
// WEAPON_MP5 (9mmAR)
{{0.0, 0.3}, {0.5, 0.8}, {0.7, 1.0}, {1.0, 1.6}, {1.4, 2.0}},
// WEAPON_CHAINGUN - NOT USED
{{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}},
// WEAPON_CROSSBOW
{{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}},
// WEAPON_SHOTGUN
{{0.0, 0.25}, {0.2, 0.5}, {0.4, 0.8}, {0.6, 1.2}, {0.8, 2.0}},
// WEAPON_RPG - Not applicable
{{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}},
// WEAPON_GAUSS
{{0.2, 0.5}, {0.3, 0.7}, {0.5, 1.0}, {0.8, 1.5}, {1.0, 2.0}},
// WEAPON_EGON - Not applicable
{{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}},
// WEAPON_HORNETGUN
{{0.0, 0.1}, {0.2, 0.3}, {0.3, 0.5}, {0.5, 0.8}, {0.7, 1.2}},
// WEAPON_HANDGRENADE - Not applicable
{{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}},
// WEAPON_TRIPMINE - Not applicable
{{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}},
// WEAPON_SATCHEL
{{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}},
// WEAPON_SNARK - Not applicable
{{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}}
};
ammo_check_t ammo_check[] = {
{"ammo_glockclip", "9mm", _9MM_MAX_CARRY},
{"ammo_9mmclip", "9mm", _9MM_MAX_CARRY},
{"ammo_9mmAR", "9mm", _9MM_MAX_CARRY},
{"ammo_9mmbox", "9mm", _9MM_MAX_CARRY},
{"ammo_mp5clip", "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", BOLT_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}};
// sounds for Bot taunting after a kill...
char barney_taunt[][30] = { BA_TNT1, BA_TNT2, BA_TNT3, BA_TNT4, BA_TNT5 };
char scientist_taunt[][30] = { SC_TNT1, SC_TNT2, SC_TNT3, SC_TNT4, SC_TNT5 };
CBaseEntity * CBot::BotFindEnemy( void )
{
Vector vecEnd;
static BOOL flag=TRUE;
char sound[40]; // for taunting sounds
if (pBotEnemy != NULL) // does the bot already have an enemy?
{
vecEnd = pBotEnemy->EyePosition();
// if the enemy is dead or has switched to botcam mode...
if (!pBotEnemy->IsAlive() || (pBotEnemy->pev->effects & EF_NODRAW))
{
if (!pBotEnemy->IsAlive()) // is the enemy dead?, assume bot killed it
{
// the enemy is dead, jump for joy about 10% of the time
if (RANDOM_LONG(1, 100) <= 10)
pev->button |= IN_JUMP;
// check if this player is not a bot (i.e. not fake client)...
if (pBotEnemy->IsNetClient() && !IS_DEDICATED_SERVER())
{
// speak taunt sounds about 10% of the time
if (RANDOM_LONG(1, 100) <= 10)
{
if (bot_model == MODEL_BARNEY)
strcpy( sound, barney_taunt[RANDOM_LONG(0,4)] );
else if (bot_model == MODEL_SCIENTIST)
strcpy( sound, scientist_taunt[RANDOM_LONG(0,4)] );
EMIT_SOUND(ENT(pBotEnemy->pev), CHAN_VOICE, sound,
RANDOM_FLOAT(0.9, 1.0), ATTN_NORM);
}
}
}
// don't have an enemy anymore so null out the pointer...
pBotEnemy = NULL;
}
else if (FInViewCone( &vecEnd ) && FVisible( vecEnd ))
{
// if enemy is still visible and in field of view, keep it
// face the enemy
Vector v_enemy = pBotEnemy->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;
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 not bots in observer mode...
if (pPlayer->IsNetClient() && f_Observer)
continue;
// skip players that are in botcam mode...
if (pPlayer->pev->effects & EF_NODRAW)
continue;
// BigGuy - START
// is team play enabled?
if (g_pGameRules->IsTeamplay())
{
// don't target your teammates if team names match...
if (UTIL_TeamsMatch(g_pGameRules->GetTeamID(this),
g_pGameRules->GetTeamID(pPlayer)))
continue;
}
// BigGuy - END
vecEnd = pPlayer->EyePosition();
// see if bot can see the player...
if (FInViewCone( &vecEnd ) && FVisible( vecEnd ))
{
float distance = (pPlayer->pev->origin - pev->origin).Length();
if (distance < nearestdistance)
{
nearestdistance = distance;
pNewEnemy = pPlayer;
pBotUser = NULL; // don't follow user when enemy found
}
}
}
if (pNewEnemy)
{
// face the enemy
Vector v_enemy = pNewEnemy->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;
}
return (pNewEnemy);
}
Vector CBot::BotBodyTarget( CBaseEntity *pBotEnemy )
{
Vector target;
float f_distance;
float f_scale;
int d_x, d_y, d_z;
f_distance = (pBotEnemy->pev->origin - pev->origin).Length();
if (f_distance > 1000)
f_scale = 1.0;
else if (f_distance > 100)
f_scale = f_distance / 1000.0;
else
f_scale = 0.1;
switch (bot_skill)
{
case 0:
// VERY GOOD, same as from CBasePlayer::BodyTarget (in player.h)
target = pBotEnemy->Center() + pBotEnemy->pev->view_ofs * RANDOM_FLOAT( 0.5, 1.1 );
d_x = 0; // no offset
d_y = 0;
d_z = 0;
break;
case 1:
// GOOD, offset a little for x, y, and z
target = pBotEnemy->Center() + pBotEnemy->pev->view_ofs;
d_x = RANDOM_FLOAT(-5, 5) * f_scale;
d_y = RANDOM_FLOAT(-5, 5) * f_scale;
d_z = RANDOM_FLOAT(-9, 9) * f_scale;
break;
case 2:
// FAIR, offset somewhat for x, y, and z
target = pBotEnemy->Center() + pBotEnemy->pev->view_ofs;
d_x = RANDOM_FLOAT(-9, 9) * f_scale;
d_y = RANDOM_FLOAT(-9, 9) * f_scale;
d_z = RANDOM_FLOAT(-15, 15) * f_scale;
break;
case 3:
// POOR, offset for x, y, and z
target = pBotEnemy->Center() + pBotEnemy->pev->view_ofs;
d_x = RANDOM_FLOAT(-16, 16) * f_scale;
d_y = RANDOM_FLOAT(-16, 16) * f_scale;
d_z = RANDOM_FLOAT(-20, 20) * f_scale;
break;
case 4:
// BAD, offset lots for x, y, and z
target = pBotEnemy->Center() + pBotEnemy->pev->view_ofs;
d_x = RANDOM_FLOAT(-20, 20) * f_scale;
d_y = RANDOM_FLOAT(-20, 20) * f_scale;
d_z = RANDOM_FLOAT(-27, 27) * f_scale;
break;
}
target = target + Vector(d_x, d_y, d_z);
return target;
}
void CBot::BotWeaponInventory( void )
{
int i;
// initialize the elements of the weapons arrays...
for (i = 0; i < MAX_WEAPONS; i++)
{
weapon_ptr[i] = NULL;
primary_ammo[i] = 0;
secondary_ammo[i] = 0;
}
// find out which weapons the bot is carrying...
for (i = 0; i < MAX_ITEM_TYPES; i++)
{
CBasePlayerItem *pItem = NULL;
if (m_rgpPlayerItems[i])
{
pItem = m_rgpPlayerItems[i];
while (pItem)
{
weapon_ptr[pItem->m_iId] = pItem; // store pointer to item
pItem = pItem->m_pNext;
}
}
}
// find out how much ammo of each type the bot is carrying...
for (i = 0; i < MAX_AMMO_SLOTS; i++)
{
if (!CBasePlayerItem::AmmoInfoArray[i].pszName)
continue;
if (strcmp("9mm", CBasePlayerItem::AmmoInfoArray[i].pszName) == 0)
{
primary_ammo[WEAPON_GLOCK] = m_rgAmmo[i];
primary_ammo[WEAPON_MP5] = m_rgAmmo[i];
}
else if (strcmp("357", CBasePlayerItem::AmmoInfoArray[i].pszName) == 0)
{
primary_ammo[WEAPON_PYTHON] = m_rgAmmo[i];
}
else if (strcmp("ARgrenades", CBasePlayerItem::AmmoInfoArray[i].pszName) == 0)
secondary_ammo[WEAPON_MP5] = m_rgAmmo[i];
else if (strcmp("bolts", CBasePlayerItem::AmmoInfoArray[i].pszName) == 0)
{
primary_ammo[WEAPON_CROSSBOW] = m_rgAmmo[i];
}
else if (stricmp("buckshot", CBasePlayerItem::AmmoInfoArray[i].pszName) == 0)
{
primary_ammo[WEAPON_SHOTGUN] = m_rgAmmo[i];
}
else if (stricmp("rockets", CBasePlayerItem::AmmoInfoArray[i].pszName) == 0)
primary_ammo[WEAPON_RPG] = m_rgAmmo[i];
else if (strcmp("uranium", CBasePlayerItem::AmmoInfoArray[i].pszName) == 0)
{
primary_ammo[WEAPON_GAUSS] = m_rgAmmo[i];
primary_ammo[WEAPON_EGON] = m_rgAmmo[i];
}
else if (stricmp("Hornets", CBasePlayerItem::AmmoInfoArray[i].pszName) == 0)
primary_ammo[WEAPON_HORNETGUN] = m_rgAmmo[i];
else if (stricmp("Hand Grenade", CBasePlayerItem::AmmoInfoArray[i].pszName) == 0)
primary_ammo[WEAPON_HANDGRENADE] = m_rgAmmo[i];
else if (stricmp("Trip Mine", CBasePlayerItem::AmmoInfoArray[i].pszName) == 0)
primary_ammo[WEAPON_TRIPMINE] = m_rgAmmo[i];
else if (stricmp("Satchel Charge", CBasePlayerItem::AmmoInfoArray[i].pszName) == 0)
primary_ammo[WEAPON_SATCHEL] = m_rgAmmo[i];
else if (stricmp("Snarks", CBasePlayerItem::AmmoInfoArray[i].pszName) == 0)
primary_ammo[WEAPON_SNARK] = m_rgAmmo[i];
}
}
// specifing a weapon_choice allows you to choose the weapon the bot will
// use (assuming enough ammo exists for that weapon)
// BotFireWeapon will return TRUE if weapon was fired, FALSE otherwise
// primary is used to indicate whether you want primary or secondary fire
// if you have specified a weapon using weapon_choice
BOOL CBot::BotFireWeapon( Vector v_enemy_origin, int weapon_choice, BOOL primary )
{
CBasePlayerItem *new_weapon;
BOOL enemy_below;
// is it time to check weapons inventory yet?
if (f_weapon_inventory_time <= gpGlobals->time)
{
// check weapon and ammo inventory then update check time...
BotWeaponInventory();
f_weapon_inventory_time = gpGlobals->time + 1.0;
}
Vector v_enemy = v_enemy_origin - GetGunPosition( );
float distance = v_enemy.Length(); // how far away is the enemy?
// is enemy at least 45 units below bot? (for handgrenades and snarks)
if (v_enemy_origin.z < (pev->origin.z - 45))
enemy_below = TRUE;
else
enemy_below = FALSE;
// if bot is carrying the crowbar...
if (pev->weapons & (1<<WEAPON_CROWBAR))
{
// if close to enemy, and skill level is 1, 2 or 3, use the crowbar
if (((distance <= 40) && (bot_skill <= 2) && (weapon_choice == 0)) ||
(weapon_choice == WEAPON_CROWBAR))
{
new_weapon = weapon_ptr[WEAPON_CROWBAR];
// check if the bot isn't already using this item...
if (m_pActiveItem != new_weapon)
SelectItem("weapon_crowbar"); // select the crowbar
pev->button |= IN_ATTACK; // use primary attack (whack! whack!)
// set next time to "shoot"
f_shoot_time = gpGlobals->time + 0.3 +
RANDOM_FLOAT(primary_fire_delay[WEAPON_CROWBAR][bot_skill][0],
primary_fire_delay[WEAPON_CROWBAR][bot_skill][1]);
return TRUE;
}
}
// if bot is carrying any hand grenades and enemy is below bot...
if ((pev->weapons & (1<<WEAPON_HANDGRENADE)) && (enemy_below))
{
long use_grenade = RANDOM_LONG(1,100);
// use hand grenades about 30% of the time...
if (((distance > 250) && (distance < 750) &&
(weapon_choice == 0) && (use_grenade <= 30)) ||
(weapon_choice == WEAPON_HANDGRENADE))
{
// BigGuy - START
new_weapon = weapon_ptr[WEAPON_HANDGRENADE];
// check if the bot isn't already using this item...
if (m_pActiveItem != new_weapon)
SelectItem("weapon_handgrenade"); // select the hand grenades
pev->button |= IN_ATTACK; // use primary attack (boom!)
// set next time to "shoot"
f_shoot_time = gpGlobals->time + 0.1 +
RANDOM_FLOAT(primary_fire_delay[WEAPON_HANDGRENADE][bot_skill][0],
primary_fire_delay[WEAPON_HANDGRENADE][bot_skill][1]);
return TRUE;
// BigGuy - END
}
}
// if bot is carrying any snarks (can't use underwater) and enemy is below bot...
if ((pev->weapons & (1<<WEAPON_SNARK)) && (pev->waterlevel != 3) &&
(enemy_below))
{
long use_snark = RANDOM_LONG(1,100);
// use snarks about 50% of the time...
if (((distance > 150) && (distance < 500) &&
(weapon_choice == 0) && (use_snark <= 50)) ||
(weapon_choice == WEAPON_SNARK))
{
// BigGuy - START
new_weapon = weapon_ptr[WEAPON_SNARK];
// check if the bot isn't already using this item...
if (m_pActiveItem != new_weapon)
SelectItem("weapon_snark"); // select the "squeak grenades"
pev->button |= IN_ATTACK; // use primary attack (eek! eek!)
// set next time to "shoot"
f_shoot_time = gpGlobals->time + 0.1 +
RANDOM_FLOAT(primary_fire_delay[WEAPON_SNARK][bot_skill][0],
primary_fire_delay[WEAPON_SNARK][bot_skill][1]);
return TRUE;
// BigGuy - END
}
}
// if the bot is carrying the egon gun (can't use underwater)...
if ((pev->weapons & (1<<WEAPON_EGON)) && (pev->waterlevel != 3))
{
if ((weapon_choice == 0) || (weapon_choice == WEAPON_EGON))
{
new_weapon = weapon_ptr[WEAPON_EGON];
// check if the bot has any ammo left for this weapon...
if (primary_ammo[WEAPON_EGON] > 0)
{
// check if the bot isn't already using this item...
if (m_pActiveItem != new_weapon)
SelectItem("weapon_egon"); // select the egon gun
pev->button |= IN_ATTACK; // use primary attack (bang! bang!)
// set next time to shoot
f_shoot_time = gpGlobals->time;
return TRUE;
}
}
}
// if the bot is carrying the gauss gun (can't use underwater)...
if ((pev->weapons & (1<<WEAPON_GAUSS)) && (pev->waterlevel != 3))
{
if ((weapon_choice == 0) || (weapon_choice == WEAPON_GAUSS))
{
new_weapon = weapon_ptr[WEAPON_GAUSS];
// check if the bot has any ammo left for this weapon...
if (primary_ammo[WEAPON_GAUSS] > 1)
{
// check if the bot isn't already using this item...
if (m_pActiveItem != new_weapon)
SelectItem("weapon_gauss"); // select the gauss gun
long use_secondary = RANDOM_LONG(1,100);
// are we charging the gauss gun?
if (f_fire_gauss > 0)
{
// is it time to fire the charged gauss gun?
if (f_fire_gauss >= gpGlobals->time)
{
// we DON'T set pev->button here to release the secondary
// fire button which will fire the charged gauss gun
f_fire_gauss = -1; // -1 means not charging gauss gun
// set next time to shoot
f_shoot_time = gpGlobals->time + 1.0 +
RANDOM_FLOAT(secondary_fire_delay[WEAPON_GAUSS][bot_skill][0],
secondary_fire_delay[WEAPON_GAUSS][bot_skill][1]);
}
else
{
pev->button |= IN_ATTACK2; // charge the gauss gun
f_shoot_time = gpGlobals->time; // keep charging
}
}
else if ((use_secondary <= 20) &&
(primary_ammo[WEAPON_GAUSS] >= 10))
{
// release secondary fire in 0.5 seconds...
f_fire_gauss = gpGlobals->time + 0.5;
pev->button |= IN_ATTACK2; // charge the gauss gun
f_shoot_time = gpGlobals->time; // keep charging
}
else
{
pev->button |= IN_ATTACK; // use primary attack (bang! bang!)
// set next time to shoot
f_shoot_time = gpGlobals->time + 0.2 +
RANDOM_FLOAT(primary_fire_delay[WEAPON_GAUSS][bot_skill][0],
primary_fire_delay[WEAPON_GAUSS][bot_skill][1]);
}
return TRUE;
}
}
}
// if the bot is carrying the shotgun (can't use underwater)...
if ((pev->weapons & (1<<WEAPON_SHOTGUN)) && (pev->waterlevel != 3))
{
// if close enough for good shotgun blasts...
if (((distance > 30) && (distance < 150) && (weapon_choice == 0)) ||
(weapon_choice == WEAPON_SHOTGUN))
{
new_weapon = weapon_ptr[WEAPON_SHOTGUN];
// check if the bot has any ammo left for this weapon...
if (primary_ammo[WEAPON_SHOTGUN] > 0)
{
// check if the bot isn't already using this item...
if (m_pActiveItem != new_weapon)
SelectItem("weapon_shotgun"); // select the shotgun
long use_secondary = RANDOM_LONG(1,100);
// use secondary attack about 30% of the time
if ((use_secondary <= 30) && (primary_ammo[WEAPON_SHOTGUN] >= 2))
{
// BigGuy - START
pev->button |= IN_ATTACK2; // use secondary attack (bang! bang!)
// set next time to shoot
f_shoot_time = gpGlobals->time + 1.5 +
RANDOM_FLOAT(secondary_fire_delay[WEAPON_SHOTGUN][bot_skill][0],
secondary_fire_delay[WEAPON_SHOTGUN][bot_skill][1]);
}
// BigGuy - END
else
{
pev->button |= IN_ATTACK; // use primary attack (bang! bang!)
// set next time to shoot
f_shoot_time = gpGlobals->time + 0.75 +
RANDOM_FLOAT(primary_fire_delay[WEAPON_SHOTGUN][bot_skill][0],
primary_fire_delay[WEAPON_SHOTGUN][bot_skill][1]);
}
return TRUE;
}
}
}
// if the bot is carrying the 357/PYTHON, (can't use underwater)...
if ((pev->weapons & (1<<WEAPON_PYTHON)) && (pev->waterlevel != 3))
{
// if close enough for 357 shot...
if (((distance > 30) && (distance < 700) && (weapon_choice == 0)) ||
(weapon_choice == WEAPON_PYTHON))
{
new_weapon = weapon_ptr[WEAPON_PYTHON];
// check if the bot has any ammo left for this weapon...
if (primary_ammo[WEAPON_PYTHON] > 0)
{
// check if the bot isn't already using this item...
if (m_pActiveItem != new_weapon)
SelectItem("weapon_357"); // select the 357 python
pev->button |= IN_ATTACK; // use primary attack (bang! bang!)
// set next time to shoot
f_shoot_time = gpGlobals->time + 0.75 +
RANDOM_FLOAT(primary_fire_delay[WEAPON_PYTHON][bot_skill][0],
primary_fire_delay[WEAPON_PYTHON][bot_skill][1]);
return TRUE;
}
}
}
// if the bot is carrying the hornet gun...
if (pev->weapons & (1<<WEAPON_HORNETGUN))
{
// if close enough for hornet gun...
if (((distance > 30) && (distance < 1000) && (weapon_choice == 0)) ||
(weapon_choice == WEAPON_HORNETGUN))
{
new_weapon = weapon_ptr[WEAPON_HORNETGUN];
// check if the bot has any ammo left for this weapon...
if (primary_ammo[WEAPON_HORNETGUN] > 0)
{
// check if the bot isn't already using this item...
if (m_pActiveItem != new_weapon)
SelectItem("weapon_hornetgun"); // select the hornet gun
long use_secondary = RANDOM_LONG(1,100);
// use secondary attack about 50% of the time (if fully reloaded)
if ((use_secondary <= 50) &&
(primary_ammo[WEAPON_HORNETGUN] >= HORNET_MAX_CARRY))
{
// BigGuy - START
pev->button |= IN_ATTACK2; // use secondary attack (buzz! buzz!)
// set next time to shoot
f_shoot_time = gpGlobals->time + 0.1 +
RANDOM_FLOAT(secondary_fire_delay[WEAPON_HORNETGUN][bot_skill][0],
secondary_fire_delay[WEAPON_HORNETGUN][bot_skill][1]);
// BigGuy - END
}
else
{
pev->button |= IN_ATTACK; // use primary attack (buzz! buzz!)
// set next time to shoot
f_shoot_time = gpGlobals->time + 0.25 +
RANDOM_FLOAT(primary_fire_delay[WEAPON_HORNETGUN][bot_skill][0],
primary_fire_delay[WEAPON_HORNETGUN][bot_skill][1]);
}
return TRUE;
}
}
}
// if the bot is carrying the MP5 (can't use underwater)...
if ((pev->weapons & (1<<WEAPON_MP5)) && (pev->waterlevel != 3))
{
long use_secondary = RANDOM_LONG(1,100);
// use secondary attack about 10% of the time...
if (((distance > 300) && (distance < 600) &&
(weapon_choice == 0) && (use_secondary <= 10)) ||
((weapon_choice == WEAPON_MP5) && (primary == FALSE)))
{
// at some point we need to fire upwards in the air slightly
// for long distance kills. for right now, just fire the
// grenade at the poor sucker.
// BigGuy - START
new_weapon = weapon_ptr[WEAPON_MP5];
// check if the bot has any ammo left for this weapon...
if (secondary_ammo[WEAPON_MP5] > 0)
{
// check if the bot isn't already using this item...
if (m_pActiveItem != new_weapon)
SelectItem("weapon_9mmAR"); // select the 9mmAR (MP5)
pev->button |= IN_ATTACK2; // use secodnary attack (boom!)
// set next time to shoot
f_shoot_time = gpGlobals->time + 1.0 +
RANDOM_FLOAT(secondary_fire_delay[WEAPON_MP5][bot_skill][0],
secondary_fire_delay[WEAPON_MP5][bot_skill][1]);
return TRUE;
}
// BigGuy - END
}
// if close enough for good MP5 shot...
if (((distance < 250) && (weapon_choice == 0)) ||
(weapon_choice == WEAPON_MP5))
{
new_weapon = weapon_ptr[WEAPON_MP5];
// check if the bot has any ammo left for this weapon...
if (primary_ammo[WEAPON_MP5] > 0)
{
// check if the bot isn't already using this item...
if (m_pActiveItem != new_weapon)
SelectItem("weapon_9mmAR"); // select the 9mmAR (MP5)
pev->button |= IN_ATTACK; // use primary attack (bang! bang!)
// set next time to shoot
f_shoot_time = gpGlobals->time + 0.1 +
RANDOM_FLOAT(primary_fire_delay[WEAPON_MP5][bot_skill][0],
primary_fire_delay[WEAPON_MP5][bot_skill][1]);
return TRUE;
}
}
}
// if the bot is carrying the crossbow...
if (pev->weapons & (1<<WEAPON_CROSSBOW))
{
// if bot is not too close for crossbow and not too far...
if (((distance > 100) && (distance < 1000) && (weapon_choice == 0)) ||
(weapon_choice == WEAPON_CROSSBOW))
{
new_weapon = weapon_ptr[WEAPON_CROSSBOW];
// check if the bot has any ammo left for this weapon...
if (primary_ammo[WEAPON_CROSSBOW] > 0)
{
// check if the bot isn't already using this item...
if (m_pActiveItem != new_weapon)
SelectItem("weapon_crossbow"); // select the crossbow
pev->button |= IN_ATTACK; // use primary attack (bang! bang!)
// set next time to shoot
f_shoot_time = gpGlobals->time + 0.75 +
RANDOM_FLOAT(primary_fire_delay[WEAPON_CROSSBOW][bot_skill][0],
primary_fire_delay[WEAPON_CROSSBOW][bot_skill][1]);
return TRUE;
}
}
}
// if the bot is carrying the RPG...
if (pev->weapons & (1<<WEAPON_RPG))
{
// don't use the RPG unless the enemy is pretty far away...
if (((distance > 300) && (weapon_choice == 0)) ||
(weapon_choice == WEAPON_RPG))
{
new_weapon = weapon_ptr[WEAPON_RPG];
// check if the bot has any ammo left for this weapon...
if (primary_ammo[WEAPON_RPG] > 0)
{
// check if the bot isn't already using this item...
if (m_pActiveItem != new_weapon)
SelectItem("weapon_rpg"); // select the RPG rocket launcher
pev->button |= IN_ATTACK; // use primary attack (bang! bang!)
// set next time to shoot
f_shoot_time = gpGlobals->time + 1.5 +
RANDOM_FLOAT(primary_fire_delay[WEAPON_RPG][bot_skill][0],
primary_fire_delay[WEAPON_RPG][bot_skill][1]);
return TRUE;
}
}
}
// if the bot is carrying the 9mm glock...
if (pev->weapons & (1<<WEAPON_GLOCK))
{
// if nothing else was selected, try the good ol' 9mm glock...
if (((distance < 1200) && (weapon_choice == 0)) ||
(weapon_choice == WEAPON_GLOCK))
{
new_weapon = weapon_ptr[WEAPON_GLOCK];
// check if the bot has any ammo left for this weapon...
if (primary_ammo[WEAPON_GLOCK] > 0)
{
// check if the bot isn't already using this item...
if (m_pActiveItem != new_weapon)
SelectItem("weapon_9mmhandgun"); // select the trusty 9mm glock
long use_secondary = RANDOM_LONG(1,100);
// use secondary attack about 30% of the time
if (use_secondary <= 30)
{
// BigGuy - START
pev->button |= IN_ATTACK2; // use secondary attack (bang! bang!)
// set next time to shoot
f_shoot_time = gpGlobals->time + 0.2 +
RANDOM_FLOAT(secondary_fire_delay[WEAPON_GLOCK][bot_skill][0],
secondary_fire_delay[WEAPON_GLOCK][bot_skill][1]);
// BigGuy - END
}
else
{
pev->button |= IN_ATTACK; // use primary attack (bang! bang!)
// set next time to shoot
f_shoot_time = gpGlobals->time + 0.3 +
RANDOM_FLOAT(primary_fire_delay[WEAPON_GLOCK][bot_skill][0],
primary_fire_delay[WEAPON_GLOCK][bot_skill][1]);
}
return TRUE;
}
}
}
// didn't have any available weapons or ammo, return FALSE
return FALSE;
}
void CBot::BotShootAtEnemy( void )
{
float f_distance;
// aim for the head and/or body
Vector v_enemy = BotBodyTarget( pBotEnemy ) - 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
// is it time to shoot yet?
if (f_shoot_time <= gpGlobals->time)
{
// select the best weapon to use at this distance and fire...
BotFireWeapon( pBotEnemy->pev->origin );
}
v_enemy.z = 0; // ignore z component (up & down)
f_distance = v_enemy.Length(); // how far away is the enemy scum?
if (f_distance > 200) // run if distance to enemy is far
f_move_speed = f_max_speed;
else if (f_distance > 20) // walk if distance is closer
f_move_speed = f_max_speed / 2;
else // don't move if close enough
f_move_speed = 0.0;
}

142
dlls/bot/botcam.cpp

@ -0,0 +1,142 @@
// botman's Half-Life bot example
//
// http://planethalflife.com/botman/
//
// botcam.cpp
//
#include "extdll.h"
#include "util.h"
#include "client.h"
#include "cbase.h"
#include "player.h"
#include "botcam.h"
LINK_ENTITY_TO_CLASS(entity_botcam, CBotCam);
CBotCam *CBotCam::Create( CBasePlayer *pPlayer, CBasePlayer *pBot )
{
CBotCam *pBotCam = GetClassPtr( (CBotCam *)NULL );
PRECACHE_MODEL( "models/mechgibs.mdl" );
pBotCam->m_pPlayer = pPlayer;
pBotCam->m_pBot = pBot;
pPlayer->pev->effects |= EF_NODRAW;
pPlayer->pev->solid = SOLID_NOT;
pPlayer->pev->takedamage = DAMAGE_NO;
pPlayer->m_iHideHUD |= HIDEHUD_ALL;
pBotCam->Spawn();
return pBotCam;
}
void CBotCam::Spawn( void )
{
pev->classname = MAKE_STRING("entity_botcam");
UTIL_MakeVectors(m_pBot->pev->v_angle);
TraceResult tr;
UTIL_TraceLine(m_pBot->pev->origin + m_pBot->pev->view_ofs,
m_pBot->pev->origin + m_pBot->pev->view_ofs + gpGlobals->v_forward * -16 + gpGlobals->v_up * 10,
dont_ignore_monsters, m_pBot->edict(), &tr );
UTIL_SetOrigin(pev, tr.vecEndPos);
pev->angles = m_pBot->pev->v_angle;
pev->fixangle = TRUE;
SET_VIEW (m_pPlayer->edict(), edict());
// mechgibs seems to be an "invisible" model. Other players won't see
// anything when this model is used as the botcam...
SET_MODEL(ENT(pev), "models/mechgibs.mdl");
pev->movetype = MOVETYPE_FLY;
pev->solid = SOLID_NOT;
pev->takedamage = DAMAGE_NO;
pev->renderamt = 0;
m_pPlayer->EnableControl(FALSE);
SetTouch( NULL );
SetThink( IdleThink );
pev->nextthink = gpGlobals->time + 0.1;
}
void CBotCam::IdleThink( void )
{
// make sure bot is still in the game...
if (m_pBot->pev->takedamage != DAMAGE_NO) // not "kicked"
{
UTIL_MakeVectors(m_pBot->pev->v_angle);
TraceResult tr;
UTIL_TraceLine(m_pBot->pev->origin + m_pBot->pev->view_ofs,
m_pBot->pev->origin + m_pBot->pev->view_ofs + gpGlobals->v_forward * -16 + gpGlobals->v_up * 10,
dont_ignore_monsters, m_pBot->edict(), &tr );
UTIL_SetOrigin(pev, tr.vecEndPos);
pev->angles = m_pBot->pev->v_angle;
pev->fixangle = TRUE;
SET_VIEW (m_pPlayer->edict(), edict());
pev->nextthink = gpGlobals->time;
}
else
{
SET_VIEW (m_pPlayer->edict(), m_pPlayer->edict());
m_pPlayer->pev->effects &= ~EF_NODRAW;
m_pPlayer->pev->solid = SOLID_SLIDEBOX;
m_pPlayer->pev->takedamage = DAMAGE_AIM;
m_pPlayer->m_iHideHUD &= ~HIDEHUD_ALL;
m_pPlayer->EnableControl(TRUE);
m_pPlayer->pBotCam = NULL; // player's botcam is no longer valid
m_pBot = NULL;
m_pPlayer = NULL;
REMOVE_ENTITY( ENT(pev) );
}
}
void CBotCam::Disconnect( void )
{
SET_VIEW (m_pPlayer->edict(), m_pPlayer->edict());
m_pPlayer->pev->effects &= ~EF_NODRAW;
m_pPlayer->pev->solid = SOLID_SLIDEBOX;
m_pPlayer->pev->takedamage = DAMAGE_AIM;
m_pPlayer->m_iHideHUD &= ~HIDEHUD_ALL;
m_pPlayer->EnableControl(TRUE);
m_pPlayer->pBotCam = NULL; // player's botcam is no longer valid
m_pBot = NULL;
m_pPlayer = NULL;
REMOVE_ENTITY( ENT(pev) );
}

26
dlls/bot/botcam.h

@ -0,0 +1,26 @@
// botman's Half-Life bot example
//
// http://planethalflife.com/botman/
//
// botcam.h
//
#ifndef BOTCAM_H
#define BOTCAM_H
class CBotCam : public CBaseEntity
{
public:
CBasePlayer *m_pPlayer;
CBasePlayer *m_pBot;
static CBotCam *Create( CBasePlayer *pPlayer, CBasePlayer *pBot );
void Spawn( void );
void Disconnect( void );
void EXPORT IdleThink( void );
};
#endif

596
dlls/client.cpp

@ -40,6 +40,20 @@
#include "netadr.h" #include "netadr.h"
#include "pm_shared.h" #include "pm_shared.h"
// START BOT
#include "bot.h"
#include "botcam.h"
void BotCreate(const char *skin, const char *name, const char *skill);
extern int f_Observer; // flag for observer mode
extern int f_botskill; // default bot skill level
extern int f_botdontshoot; // flag to disable targeting other bots
extern respawn_t bot_respawn[32];
float bot_check_time = 10.0;
int min_bots = 0;
int max_bots = 0;
// END BOT
extern DLL_GLOBAL ULONG g_ulModelIndexPlayer; extern DLL_GLOBAL ULONG g_ulModelIndexPlayer;
extern DLL_GLOBAL BOOL g_fGameOver; extern DLL_GLOBAL BOOL g_fGameOver;
extern DLL_GLOBAL int g_iSkillLevel; extern DLL_GLOBAL int g_iSkillLevel;
@ -85,6 +99,41 @@ called when a player connects to a server
*/ */
BOOL ClientConnect( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[128] ) BOOL ClientConnect( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[128] )
{ {
int i;
int count = 0;
// check if this is NOT a bot joining the server...
if( strcmp( pszAddress, "127.0.0.1" ) != 0 )
{
// don't try to add bots for 30 seconds, give client time to get added
bot_check_time = gpGlobals->time + 30.0;
for( i = 0; i < 32; i++ )
{
if( bot_respawn[i].is_used ) // count the number of bots in use
count++;
}
// if there are currently more than the minimum number of bots running
// then kick one of the bots off the server...
if( ( min_bots != 0 ) && ( count > min_bots ) )
{
for( i = 0; i < 32; i++ )
{
if( bot_respawn[i].is_used ) // is this slot used?
{
char cmd[40];
sprintf( cmd, "kick \"%s\"\n", bot_respawn[i].name );
bot_respawn[i].state = BOT_IDLE;
SERVER_COMMAND( cmd ); // kick the bot using (kick "name")
break;
}
}
}
}
return g_pGameRules->ClientConnected( pEntity, pszName, pszAddress, szRejectReason ); return g_pGameRules->ClientConnected( pEntity, pszName, pszAddress, szRejectReason );
// a client connecting during an intermission can cause problems // a client connecting during an intermission can cause problems
@ -599,6 +648,132 @@ void ClientCommand( edict_t *pEntity )
if( pPlayer->IsObserver() ) if( pPlayer->IsObserver() )
pPlayer->Observer_FindNextPlayer( atoi( CMD_ARGV( 1 ) ) ? true : false ); pPlayer->Observer_FindNextPlayer( atoi( CMD_ARGV( 1 ) ) ? true : false );
} }
// START BOT
else if( FStrEq( pcmd, "addbot" ) )
{
if( !IS_DEDICATED_SERVER() )
{
// If user types "addbot" in console, add a bot with skin and name
BotCreate( CMD_ARGV( 1 ), CMD_ARGV( 2 ), CMD_ARGV( 3 ) );
}
else
CLIENT_PRINTF( pEntity, print_console, "addbot not allowed from client!\n" );
}
else if( FStrEq( pcmd, "observer" ) )
{
if( !IS_DEDICATED_SERVER() )
{
if( CMD_ARGC() > 1 ) // is there an argument to the command?
{
f_Observer = atoi( CMD_ARGV( 1 ) ); // set observer flag
CLIENT_PRINTF( pEntity, print_console, UTIL_VarArgs( "\"observer\" set to %d\n", (int)f_Observer ) );
}
else
{
CLIENT_PRINTF( pEntity, print_console, UTIL_VarArgs( "\"observer\" is %d\n", (int)f_Observer ) );
}
}
else
CLIENT_PRINTF( pEntity, print_console, "observer not allowed from client!\n" );
}
else if( FStrEq( pcmd, "botskill" ) )
{
if( !IS_DEDICATED_SERVER() )
{
if( CMD_ARGC() > 1 )
{
f_botskill = atoi( CMD_ARGV( 1 ) ); // set default bot skill level
CLIENT_PRINTF( pEntity, print_console, UTIL_VarArgs( "\"botskill\" set to %d\n", (int)f_botskill ) );
}
else
{
CLIENT_PRINTF( pEntity, print_console, UTIL_VarArgs( "\"botskill\" is %d\n", (int)f_botskill ) );
}
}
else
CLIENT_PRINTF( pEntity, print_console, "botskill not allowed from client!\n" );
}
else if( FStrEq( pcmd, "botdontshoot" ) )
{
if( !IS_DEDICATED_SERVER() )
{
if( CMD_ARGC() > 1) // is there an argument to the command?
{
f_botdontshoot = atoi( CMD_ARGV( 1 ) ); // set bot shoot flag
CLIENT_PRINTF( pEntity, print_console, UTIL_VarArgs( "\"botdontshoot\" set to %d\n", (int)f_botdontshoot ) );
}
else
{
CLIENT_PRINTF( pEntity, print_console, UTIL_VarArgs( "\"botdontshoot\" is %d\n", (int)f_botdontshoot ) );
}
}
else
CLIENT_PRINTF( pEntity, print_console, "botdontshoot not allowed from client!\n" );
}
else if( FStrEq( pcmd, "botcam" ) )
{
CBasePlayer *pPlayer = GetClassPtr( (CBasePlayer *)pev );
CBasePlayer *pBot = NULL;
char botname[BOT_NAME_LEN + 1];
int index;
botname[0] = 0;
if( CMD_ARGC() > 1 ) // is there an argument to the command?
{
if( strstr( CMD_ARGV( 1 ), "\"" ) == NULL )
strcpy( botname, CMD_ARGV( 1 ) );
else
sscanf( CMD_ARGV( 1 ), "\"%s\"", &botname[0] );
index = 0;
while( index < 32 )
{
if( ( bot_respawn[index].is_used ) &&
( stricmp( bot_respawn[index].name, botname ) == 0 ) )
break;
else
index++;
}
if( index < 32 )
pBot = bot_respawn[index].pBot;
}
else
{
index = 0;
while( ( bot_respawn[index].is_used == FALSE ) && ( index < 32 ) )
index++;
if( index < 32 )
pBot = bot_respawn[index].pBot;
}
if( pBot == NULL )
{
if( botname[0] )
CLIENT_PRINTF( pEntity, print_console, UTIL_VarArgs( "there is no bot named \"%s\"!\n", botname ) );
else
CLIENT_PRINTF( pEntity, print_console, UTIL_VarArgs( "there are no bots!\n" ) );
}
else
{
if( pPlayer->pBotCam ) // if botcam in use, disconnect first...
pPlayer->pBotCam->Disconnect();
pPlayer->pBotCam = CBotCam::Create( pPlayer, pBot );
}
}
else if( FStrEq( pcmd, "nobotcam" ) )
{
CBasePlayer *pPlayer = GetClassPtr( (CBasePlayer *)pev );
if( pPlayer->pBotCam )
pPlayer->pBotCam->Disconnect();
}
//END BOT
else if( g_pGameRules->ClientCommand( GetClassPtr( (CBasePlayer *)pev ), pcmd ) ) else if( g_pGameRules->ClientCommand( GetClassPtr( (CBasePlayer *)pev ), pcmd ) )
{ {
// MenuSelect returns true only if the command is properly handled, so don't print a warning // MenuSelect returns true only if the command is properly handled, so don't print a warning
@ -798,10 +973,357 @@ void ParmsChangeLevel( void )
// //
void StartFrame( void ) void StartFrame( void )
{ {
// START BOT
static BOOL file_opened = FALSE;
static int length;
static char *pFileList, *aFileList;
static char cmd_line[80];
static char server_cmd[80];
static int index, i;
static float pause_time;
static float check_server_cmd = 0;
char *cmd, *arg1, *arg2, *arg3;
static float respawn_time = 0;
static float previous_time = 0.0;
char msg[120];
// END BOT
// START BOT - thanks Jehannum!
// loop through all the players...
for( i = 1; i <= gpGlobals->maxClients; i++ )
{
CBaseEntity *pPlayer = UTIL_PlayerByIndex( i );
if( !pPlayer ) // if invalid then continue with next index...
continue;
// check if this is a FAKECLIENT (i.e. is it a bot?)
if( FBitSet( pPlayer->pev->flags, FL_FAKECLIENT ) )
{
CBot *pBot = (CBot*)pPlayer;
// call the think function for the bot...
pBot->BotThink();
}
}
// END BOT
// START BOT
if( ( g_fGameOver ) && ( respawn_time < 1.0 ) )
{
// if the game is over (time/frag limit) set the respawn time...
respawn_time = 5.0;
// check if any players are using the botcam...
for( i = 1; i <= gpGlobals->maxClients; i++ )
{
CBasePlayer *pPlayer = (CBasePlayer *)UTIL_PlayerByIndex( i );
if( !pPlayer )
continue; // if invalid then continue with next index...
if( pPlayer->pBotCam )
pPlayer->pBotCam->Disconnect();
}
}
// check if a map was changed via "map" without kicking bots...
if( previous_time > gpGlobals->time )
{
bot_check_time = gpGlobals->time + 10.0;
for( index = 0; index < 32; index++ )
{
if( ( bot_respawn[index].is_used) && // is this slot used?
( bot_respawn[index].state != BOT_NEED_TO_RESPAWN ) )
{
// bot has already been "kicked" by server so just set flag
bot_respawn[index].state = BOT_NEED_TO_RESPAWN;
// if the map was changed set the respawn time...
respawn_time = 5.0;
}
}
}
// is new game started and time to respawn bots yet?
if( ( !g_fGameOver ) && ( respawn_time > 1.0 ) &&
( gpGlobals->time >= respawn_time ) )
{
int index = 0;
bot_check_time = gpGlobals->time + 5.0;
// find bot needing to be respawned...
while( ( index < 32 ) &&
( bot_respawn[index].state != BOT_NEED_TO_RESPAWN ) )
index++;
if( index < 32 )
{
bot_respawn[index].state = BOT_IS_RESPAWNING;
bot_respawn[index].is_used = FALSE; // free up this slot
// respawn 1 bot then wait a while (otherwise engine crashes)
BotCreate( bot_respawn[index].skin,
bot_respawn[index].name,
bot_respawn[index].skill );
respawn_time = gpGlobals->time + 1.0; // set next respawn time
}
else
{
respawn_time = 0.0;
}
}
// END BOT
//ALERT( at_console, "SV_Physics( %g, frametime %g )\n", gpGlobals->time, gpGlobals->frametime ); //ALERT( at_console, "SV_Physics( %g, frametime %g )\n", gpGlobals->time, gpGlobals->frametime );
if( g_pGameRules ) if( g_pGameRules )
{
g_pGameRules->Think(); g_pGameRules->Think();
// START BOT
if( !file_opened ) // have we open bot.cfg file yet?
{
ALERT( at_console, "Executing bot.cfg\n" );
pFileList = (char *)LOAD_FILE_FOR_ME( "bot.cfg", &length );
file_opened = TRUE;
if( pFileList == NULL )
ALERT( at_console, "bot.cfg file not found\n" );
pause_time = gpGlobals->time;
index = 0;
cmd_line[index] = 0; // null out command line
}
// if the bot.cfg file is still open and time to execute command...
while( ( pFileList && *pFileList ) && ( pause_time <= gpGlobals->time ) )
{
while( *pFileList == ' ' ) // skip any leading blanks
pFileList++;
while( ( *pFileList != '\r' ) && ( *pFileList != '\n' ) &&
( *pFileList != 0 ) )
{
if( *pFileList == '\t' ) // convert tabs to spaces
*pFileList = ' ';
cmd_line[index] = *pFileList;
pFileList++;
while( ( cmd_line[index] == ' ' ) && ( *pFileList == ' ' ) )
pFileList++; // skip multiple spaces
index++;
}
if( *pFileList == '\r' )
{
pFileList++; // skip the carriage return
pFileList++; // skip the linefeed
}
else if( *pFileList == '\n' )
{
pFileList++; // skip the newline
}
cmd_line[index] = 0; // terminate the command line
// copy the command line to a server command buffer...
strcpy( server_cmd, cmd_line );
strcat( server_cmd, "\n" );
index = 0;
cmd = cmd_line;
arg1 = arg2 = arg3 = NULL;
// skip to blank or end of string...
while( ( cmd_line[index] != ' ' ) && ( cmd_line[index] != 0 ) )
index++;
if( cmd_line[index] == ' ' )
{
cmd_line[index++] = 0;
arg1 = &cmd_line[index];
// skip to blank or end of string...
while( ( cmd_line[index] != ' ' ) && ( cmd_line[index] != 0 ) )
index++;
if( cmd_line[index] == ' ' )
{
cmd_line[index++] = 0;
arg2 = &cmd_line[index];
// skip to blank or end of string...
while( ( cmd_line[index] != ' ' ) && ( cmd_line[index] != 0 ) )
index++;
if( cmd_line[index] == ' ' )
{
cmd_line[index++] = 0;
arg3 = &cmd_line[index];
}
}
}
index = 0; // reset for next input line
if( ( cmd_line[0] == '#' ) || ( cmd_line[0] == 0 ) )
{
continue; // ignore comments or blank lines
}
else if( strcmp( cmd, "addbot" ) == 0 )
{
BotCreate( arg1, arg2, arg3 );
// have to delay here or engine gives "Tried to write to
// uninitialized sizebuf_t" error and crashes...
pause_time = gpGlobals->time + 1;
break;
}
else if( strcmp( cmd, "botskill" ) == 0 )
{
f_botskill = atoi( arg1 ); // set default bot skill level
}
else if( strcmp( cmd, "observer" ) == 0 )
{
f_Observer = atoi( arg1 ); // set observer flag
}
else if( strcmp( cmd, "botdontshoot" ) == 0 )
{
f_botdontshoot = atoi( arg1 ); // set bot shoot flag
}
else if( strcmp( cmd, "min_bots" ) == 0 )
{
min_bots = atoi( arg1 );
if( min_bots < 0 )
min_bots = 0;
if( IS_DEDICATED_SERVER() )
{
sprintf( msg, "min_bots set to %d\n", min_bots );
printf( msg );
}
}
else if( strcmp( cmd, "max_bots" ) == 0 )
{
max_bots = atoi( arg1 );
if( max_bots >= gpGlobals->maxClients )
max_bots = gpGlobals->maxClients - 1;
if( IS_DEDICATED_SERVER() )
{
sprintf( msg, "max_bots set to %d\n", max_bots );
printf( msg );
}
}
else if( strcmp( cmd, "pause" ) == 0 )
{
pause_time = gpGlobals->time + atoi( arg1 );
break;
}
else
{
sprintf( msg, "executing server command: %s\n", server_cmd );
ALERT( at_console, msg );
if( IS_DEDICATED_SERVER() )
printf( msg );
SERVER_COMMAND( server_cmd );
}
}
// if bot.cfg file is open and reached end of file, then close and free it
if( pFileList && ( *pFileList == 0 ) )
{
FREE_FILE( aFileList );
pFileList = NULL;
}
// if time to check for server commands then do so...
if( check_server_cmd <= gpGlobals->time )
{
check_server_cmd = gpGlobals->time + 1.0;
char *cvar_bot = (char *)CVAR_GET_STRING( "bot" );
if( cvar_bot && cvar_bot[0] )
{
strcpy( cmd_line, cvar_bot );
index = 0;
cmd = cmd_line;
arg1 = arg2 = arg3 = NULL;
// skip to blank or end of string...
while( ( cmd_line[index] != ' ' ) && ( cmd_line[index] != 0 ) )
index++;
if( cmd_line[index] == ' ' )
{
cmd_line[index++] = 0;
arg1 = &cmd_line[index];
// skip to blank or end of string...
while( ( cmd_line[index] != ' ' ) && ( cmd_line[index] != 0 ) )
index++;
if( cmd_line[index] == ' ' )
{
cmd_line[index++] = 0;
arg2 = &cmd_line[index];
// skip to blank or end of string...
while( ( cmd_line[index] != ' ' ) && ( cmd_line[index] != 0 ) )
index++;
if( cmd_line[index] == ' ' )
{
cmd_line[index++] = 0;
arg3 = &cmd_line[index];
}
}
}
if( strcmp( cmd, "addbot" ) == 0 )
{
printf( "adding new bot...\n" );
BotCreate( arg1, arg2, arg3 );
}
else if( strcmp( cmd, "botskill" ) == 0 )
{
if( arg1 != NULL )
{
printf( "setting botskill to %d\n", atoi( arg1 ) );
f_botskill = atoi( arg1 ); // set default bot skill level
}
else
printf( "botskill is %d\n", f_botskill );
}
else if( strcmp( cmd, "botdontshoot" ) == 0 )
{
if( arg1 != NULL )
{
printf( "setting botdontshoot to %d\n", atoi( arg1 ) );
f_botdontshoot = atoi( arg1 ); // set bot shoot flag
}
else
printf( "botdontshoot is %d\n", f_botdontshoot );
}
CVAR_SET_STRING( "bot", "" );
}
}
// END BOT
}
if( g_fGameOver ) if( g_fGameOver )
return; return;
@ -809,6 +1331,38 @@ void StartFrame( void )
gpGlobals->teamplay = teamplay.value; gpGlobals->teamplay = teamplay.value;
g_ulFrameCount++; g_ulFrameCount++;
// START BOT
// check if time to see if a bot needs to be created...
if( bot_check_time < gpGlobals->time )
{
int count = 0;
bot_check_time = gpGlobals->time + 5.0;
for( i = 1; i <= gpGlobals->maxClients; i++ )
{
CBaseEntity *pPlayer = UTIL_PlayerByIndex( i );
if( !pPlayer )
continue; // if invalid then continue with next index...
if( pPlayer->pev->takedamage == DAMAGE_NO )
continue; // if bot was kicked, don't count as a player...
count++; // count the number of bots and players
}
// if there are currently less than the maximum number of "players"
// then add another bot using the default skill level...
if( count < max_bots )
{
BotCreate( NULL, NULL, NULL );
}
}
previous_time = gpGlobals->time; // keep track of last time in StartFrame()
// END BOT
int oldBhopcap = g_bhopcap; int oldBhopcap = g_bhopcap;
g_bhopcap = ( g_pGameRules->IsMultiplayer() && bhopcap.value != 0.0f ) ? 1 : 0; g_bhopcap = ( g_pGameRules->IsMultiplayer() && bhopcap.value != 0.0f ) ? 1 : 0;
if( g_bhopcap != oldBhopcap ) if( g_bhopcap != oldBhopcap )
@ -929,6 +1483,48 @@ void ClientPrecache( void )
if( giPrecacheGrunt ) if( giPrecacheGrunt )
UTIL_PrecacheOther( "monster_human_grunt" ); UTIL_PrecacheOther( "monster_human_grunt" );
// START BOT
if( !IS_DEDICATED_SERVER() )
{
PRECACHE_SOUND( HG_SND1 );
PRECACHE_SOUND( HG_SND2 );
PRECACHE_SOUND( HG_SND3 );
PRECACHE_SOUND( HG_SND4 );
PRECACHE_SOUND( HG_SND5 );
PRECACHE_SOUND( BA_SND1 );
PRECACHE_SOUND( BA_SND2 );
PRECACHE_SOUND( BA_SND3 );
PRECACHE_SOUND( BA_SND4 );
PRECACHE_SOUND( BA_SND5 );
PRECACHE_SOUND( SC_SND1 );
PRECACHE_SOUND( SC_SND2 );
PRECACHE_SOUND( SC_SND3 );
PRECACHE_SOUND( SC_SND4 );
PRECACHE_SOUND( SC_SND5 );
PRECACHE_SOUND( BA_TNT1 );
PRECACHE_SOUND( BA_TNT2 );
PRECACHE_SOUND( BA_TNT3 );
PRECACHE_SOUND( BA_TNT4 );
PRECACHE_SOUND( BA_TNT5 );
PRECACHE_SOUND( SC_TNT1 );
PRECACHE_SOUND( SC_TNT2 );
PRECACHE_SOUND( SC_TNT3 );
PRECACHE_SOUND( SC_TNT4 );
PRECACHE_SOUND( SC_TNT5 );
PRECACHE_SOUND( USE_TEAMPLAY_SND );
PRECACHE_SOUND( USE_TEAMPLAY_LATER_SND );
PRECACHE_SOUND( USE_TEAMPLAY_ENEMY_SND );
}
UTIL_PrecacheOther( "entity_botcam" );
PRECACHE_MODEL( "models/mechgibs.mdl" );
// END BOT
} }
/* /*

8
dlls/game.cpp

@ -446,6 +446,10 @@ cvar_t sk_player_leg3 = { "sk_player_leg3","1" };
// END Cvars for Skill Level settings // END Cvars for Skill Level settings
//START BOT
cvar_t cvar_bot = { "bot", "" };
//END BOT
// Register your console variables here // Register your console variables here
// This gets called one time when the game is initialied // This gets called one time when the game is initialied
void GameDLLInit( void ) void GameDLLInit( void )
@ -858,6 +862,10 @@ void GameDLLInit( void )
CVAR_REGISTER( &sk_player_leg3 ); CVAR_REGISTER( &sk_player_leg3 );
// END REGISTER CVARS FOR SKILL LEVEL STUFF // END REGISTER CVARS FOR SKILL LEVEL STUFF
//START BOT
CVAR_REGISTER( &cvar_bot );
//END BOT
SERVER_COMMAND( "exec skill.cfg\n" ); SERVER_COMMAND( "exec skill.cfg\n" );
} }

38
dlls/multiplay_gamerules.cpp

@ -31,6 +31,12 @@
#endif #endif
#include "hltv.h" #include "hltv.h"
// START BOT
#include "bot.h"
#include "botcam.h"
extern respawn_t bot_respawn[32];
// END BOT
extern DLL_GLOBAL CGameRules *g_pGameRules; extern DLL_GLOBAL CGameRules *g_pGameRules;
extern DLL_GLOBAL BOOL g_fGameOver; extern DLL_GLOBAL BOOL g_fGameOver;
extern int gmsgDeathMsg; // client dll messages extern int gmsgDeathMsg; // client dll messages
@ -1626,6 +1632,38 @@ void CHalfLifeMultiplay::ChangeLevel( void )
g_fGameOver = TRUE; g_fGameOver = TRUE;
// START BOT
// loop through all the players...
for( int i = 1; i <= gpGlobals->maxClients; i++ )
{
CBaseEntity *pEntity = UTIL_PlayerByIndex( i );
if( !pEntity ) // if invalid then continue with next index...
continue;
CBasePlayer *pPlayer = (CBasePlayer *)pEntity;
// if botcam is in use, disconnect so buttons will work...
if( pPlayer->pBotCam )
pPlayer->pBotCam->Disconnect();
}
// kick any bot off of the server after time/frag limit...
for( int index = 0; index < 32; index++ )
{
if( bot_respawn[index].is_used ) // is this slot used?
{
char cmd[40];
sprintf( cmd, "kick \"%s\"\n", bot_respawn[index].name );
bot_respawn[index].state = BOT_NEED_TO_RESPAWN;
SERVER_COMMAND( cmd ); // kick the bot using (kick "name")
}
}
// END BOT
ALERT( at_console, "CHANGE LEVEL: %s\n", szNextMap ); ALERT( at_console, "CHANGE LEVEL: %s\n", szNextMap );
if( minplayers || maxplayers ) if( minplayers || maxplayers )
{ {

3
dlls/player.cpp

@ -2870,6 +2870,9 @@ void CBasePlayer::Spawn( void )
m_flNextChatTime = gpGlobals->time; m_flNextChatTime = gpGlobals->time;
// START BOT
pBotCam = NULL;
// END BOT
g_pGameRules->PlayerSpawn( this ); g_pGameRules->PlayerSpawn( this );
} }

8
dlls/player.h

@ -17,6 +17,10 @@
#include "pm_materials.h" #include "pm_materials.h"
//START BOT
class CBotCam;
//END BOT
#define PLAYER_FATAL_FALL_SPEED 1024// approx 60 feet #define PLAYER_FATAL_FALL_SPEED 1024// approx 60 feet
#define PLAYER_MAX_SAFE_FALL_SPEED 580// approx 20 feet #define PLAYER_MAX_SAFE_FALL_SPEED 580// approx 20 feet
#define DAMAGE_FOR_FALL_SPEED (float) 100 / ( PLAYER_FATAL_FALL_SPEED - PLAYER_MAX_SAFE_FALL_SPEED )// damage per unit per second. #define DAMAGE_FOR_FALL_SPEED (float) 100 / ( PLAYER_FATAL_FALL_SPEED - PLAYER_MAX_SAFE_FALL_SPEED )// damage per unit per second.
@ -196,6 +200,10 @@ public:
char m_szTeamName[TEAM_NAME_LENGTH]; char m_szTeamName[TEAM_NAME_LENGTH];
//START BOT
CBotCam *pBotCam;
//END BOT
virtual void Spawn( void ); virtual void Spawn( void );
void Pain( void ); void Pain( void );

Loading…
Cancel
Save