diff --git a/dlls/Android.mk b/dlls/Android.mk index 1dc79616..74f8caa3 100644 --- a/dlls/Android.mk +++ b/dlls/Android.mk @@ -26,7 +26,8 @@ LOCAL_C_INCLUDES := $(SDL_PATH)/include \ $(LOCAL_PATH)/../engine \ $(LOCAL_PATH)/../public \ $(LOCAL_PATH)/../pm_shared \ - $(LOCAL_PATH)/../game_shared + $(LOCAL_PATH)/../game_shared \ + $(LOCAL_PATH)/bot LOCAL_SRC_FILES := agrunt.cpp airtank.cpp \ aflock.cpp \ @@ -126,6 +127,9 @@ LOCAL_SRC_FILES := agrunt.cpp airtank.cpp \ world.cpp \ xen.cpp \ zombie.cpp \ + bot/bot_combat.cpp \ + bot/bot.cpp \ + bot/botcam.cpp \ ../pm_shared/pm_debug.c \ ../pm_shared/pm_math.c \ ../pm_shared/pm_shared.c diff --git a/dlls/CMakeLists.txt b/dlls/CMakeLists.txt index 4e56ab44..d1434955 100644 --- a/dlls/CMakeLists.txt +++ b/dlls/CMakeLists.txt @@ -128,12 +128,15 @@ set (SVDLL_SOURCES world.cpp xen.cpp zombie.cpp + bot/bot_combat.cpp + bot/bot.cpp + bot/botcam.cpp ../pm_shared/pm_debug.c ../pm_shared/pm_math.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) set(SVDLL_SOURCES diff --git a/dlls/bot/bot.cpp b/dlls/bot/bot.cpp new file mode 100644 index 00000000..06eabc05 --- /dev/null +++ b/dlls/bot/bot.cpp @@ -0,0 +1,2109 @@ +// 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 +#include + +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 10 + +// indicate which models are currently used for random model allocation +BOOL skin_used[MAX_SKINS] = { + FALSE, FALSE, FALSE, FALSE, FALSE, + FALSE, FALSE, FALSE, FALSE, FALSE}; + +// store the names of the models... +char *bot_skins[MAX_SKINS] = { + "barney", "gina", "gman", "gordon", "helmet", + "hgrunt", "recon", "robo", "scientist", "zombie"}; + +// store the player names for each of the models... +char *bot_names[MAX_SKINS] = { + "Barney", "Gina", "G-Man", "Gordon", "Helmet", + "H-Grunt", "Recon", "Robo", "Scientist", "Zombie"}; + +// 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( char *infobuffer, char *key ) +{ + return (g_engfuncs.pfnInfoKeyValue( infobuffer, key )); +} + +inline void SET_CLIENT_KEY_VALUE( int clientIndex, char *infobuffer, + char *key, 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, 9); // 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(err_msg); + + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, + "use barney, gina, gman, gordon, helmet, hgrunt,\n"); + if (IS_DEDICATED_SERVER()) + printf("use barney, gina, gman, gordon, helmet, hgrunt,\n"); + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, + " recon, robo, scientist, or zombie\n"); + if (IS_DEDICATED_SERVER()) + printf(" recon, robo, scientist, or zombie\n"); + return; + } + } + + // copy the name of the model to the bot's name... + strncpy( c_name, skin, BOT_SKIN_LEN); + c_name[BOT_SKIN_LEN] = 0; // make sure c_skin is null terminated + } + + length = strlen(c_name); + + // remove any illegal characters from name... + for (i = 0; i < length; i++) + { + if ((c_name[i] <= ' ') || (c_name[i] > '~') || + (c_name[i] == '"')) + { + for (j = i; j < length; j++) // shuffle chars left (and null) + c_name[j] = c_name[j+1]; + length--; + } + } + + skill_level = 0; + + if ((skill != NULL) && (*skill != 0)) + sscanf( skill, "%d", &skill_level ); + else + skill_level = f_botskill; + + if ((skill_level < 1) || (skill_level > 5)) + skill_level = f_botskill; + + sprintf( c_skill, "%d", skill_level ); + + BotEnt = CREATE_FAKE_CLIENT( c_name ); + + if (FNullEnt( BotEnt )) + { + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, "Max. Players reached. Can't create bot!\n"); + + if (IS_DEDICATED_SERVER()) + printf("Max. Players reached. Can't create bot!\n"); + } + else + { + char ptr[128]; // allocate space for message from ClientConnect + char *infobuffer; + int clientIndex; + + index = 0; + while ((bot_respawn[index].is_used) && (index < 32)) + index++; + + if (index >= 32) + { + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, "Can't create bot!\n"); + return; + } + + sprintf(c_index, "%d", index); + + bot_respawn[index].is_used = TRUE; // this slot is used + + // don't store the name here, it might change if same as another + strcpy(bot_respawn[index].skin, c_skin); + strcpy(bot_respawn[index].skill, c_skill); + + sprintf(ptr, "Creating bot \"%s\" using model %s with skill=%d\n", c_name, c_skin, skill_level); + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, ptr); + + if (IS_DEDICATED_SERVER()) + printf("%s", ptr); + + BotClass = GetClassPtr( (CBot *) VARS(BotEnt) ); + infobuffer = GET_INFOBUFFER( BotClass->edict( ) ); + clientIndex = BotClass->entindex( ); + + SET_CLIENT_KEY_VALUE( clientIndex, infobuffer, "model", c_skin ); + SET_CLIENT_KEY_VALUE( clientIndex, infobuffer, "skill", c_skill ); + SET_CLIENT_KEY_VALUE( clientIndex, infobuffer, "index", c_index ); + + ClientConnect( BotClass->edict( ), c_name, "127.0.0.1", ptr ); + DispatchSpawn( BotClass->edict( ) ); + } +} + + +void CBot::Spawn( ) +{ + char c_skill[2]; + char c_index[3]; + + CBasePlayer::Spawn(); + + pev->flags = FL_CLIENT | FL_FAKECLIENT; + + // set the respawn index value based on key from BotCreate + strcpy(c_index, GET_INFO_KEY_VALUE(GET_INFOBUFFER(edict( )), "index") ); + sscanf(c_index, "%d", &respawn_index); + + bot_respawn[respawn_index].pBot = (CBasePlayer *)this; + + // get the bot's name and save it in respawn array... + strcpy(bot_respawn[respawn_index].name, STRING(pev->netname)); + + bot_respawn[respawn_index].state = BOT_IDLE; + + pev->ideal_yaw = pev->v_angle.y; + pev->yaw_speed = BOT_YAW_SPEED; + + // bot starts out in "paused" state since it hasn't moved yet... + bot_was_paused = TRUE; + v_prev_origin = pev->origin; + + f_shoot_time = 0; + + // get bot's skill level (0=good, 4=bad) + strcpy(c_skill, GET_INFO_KEY_VALUE(GET_INFOBUFFER(edict( )), "skill") ); + sscanf(c_skill, "%d", &bot_skill); + bot_skill--; // make 0 based for array index (now 0-4) + + f_max_speed = CVAR_GET_FLOAT("sv_maxspeed"); + + f_speed_check_time = gpGlobals->time + 2.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 = abs(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<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<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 = abs(Y - Y1); + if (D1 > 179) D1 = abs(D1 - 360); + D2 = abs(Y - Y2); + if (D2 > 179) D2 = abs(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 + bot_respawn[respawn_index].state = BOT_IDLE; + respawn_index = -1; // indicate no slot used + + // fall through to next if statement (respawn_index will be -1) + } + + // is the round over (time/frag limit) or has the bot been removed? + if ((g_fGameOver) || (respawn_index == -1)) + { + CSound *pSound; + + // keep resetting the sound entity until the bot is respawned... + pSound = CSoundEnt::SoundPointerForIndex( CSoundEnt::ClientSoundIndex( edict() ) ); + if ( pSound ) + { + pSound->Reset(); + } + + return; + } + + pev->button = 0; // make sure no buttons are pressed + + // if the bot is dead, randomly press fire to respawn... + if ((pev->health < 1) || (pev->deadflag != DEAD_NO)) + { + if (RANDOM_LONG(1, 100) > 50) + pev->button = IN_ATTACK; + + g_engfuncs.pfnRunPlayerMove( edict( ), pev->v_angle, f_move_speed, + 0, 0, pev->button, 0, + gpGlobals->frametime * 1000 ); + return; + } + + // see if it's time to check for sv_maxspeed change... + if (f_speed_check_time <= gpGlobals->time) + { + // store current sv_maxspeed setting for quick access + f_max_speed = CVAR_GET_FLOAT("sv_maxspeed"); + + // set next check time to 2 seconds from now + f_speed_check_time = gpGlobals->time + 2.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 +} + diff --git a/dlls/bot/bot.h b/dlls/bot/bot.h new file mode 100644 index 00000000..a459d97b --- /dev/null +++ b/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 + diff --git a/dlls/bot/bot_combat.cpp b/dlls/bot/bot_combat.cpp new file mode 100644 index 00000000..7b5a780f --- /dev/null +++ b/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<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< 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<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<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<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<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<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< 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<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< 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< 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< 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; +} + + + diff --git a/dlls/bot/botcam.cpp b/dlls/bot/botcam.cpp new file mode 100644 index 00000000..825906d3 --- /dev/null +++ b/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) ); +} + diff --git a/dlls/bot/botcam.h b/dlls/bot/botcam.h new file mode 100644 index 00000000..f112f9f3 --- /dev/null +++ b/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 + diff --git a/dlls/client.cpp b/dlls/client.cpp index 578534e3..0a3e7e79 100644 --- a/dlls/client.cpp +++ b/dlls/client.cpp @@ -40,6 +40,20 @@ #include "netadr.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 BOOL g_fGameOver; 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] ) { + 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 ); // a client connecting during an intermission can cause problems @@ -599,6 +648,132 @@ void ClientCommand( edict_t *pEntity ) if( pPlayer->IsObserver() ) 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 ) ) { // 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 ) { + // 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 ); if( g_pGameRules ) + { 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 ) return; @@ -809,6 +1331,38 @@ void StartFrame( void ) gpGlobals->teamplay = teamplay.value; 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; g_bhopcap = ( g_pGameRules->IsMultiplayer() && bhopcap.value != 0.0f ) ? 1 : 0; if( g_bhopcap != oldBhopcap ) @@ -929,6 +1483,48 @@ void ClientPrecache( void ) if( giPrecacheGrunt ) 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 } /* diff --git a/dlls/game.cpp b/dlls/game.cpp index 4354a63d..c0c57a00 100644 --- a/dlls/game.cpp +++ b/dlls/game.cpp @@ -446,6 +446,10 @@ cvar_t sk_player_leg3 = { "sk_player_leg3","1" }; // END Cvars for Skill Level settings +//START BOT +cvar_t cvar_bot = { "bot", "" }; +//END BOT + // Register your console variables here // This gets called one time when the game is initialied void GameDLLInit( void ) @@ -858,6 +862,10 @@ void GameDLLInit( void ) CVAR_REGISTER( &sk_player_leg3 ); // END REGISTER CVARS FOR SKILL LEVEL STUFF +//START BOT + CVAR_REGISTER( &cvar_bot ); +//END BOT + SERVER_COMMAND( "exec skill.cfg\n" ); } diff --git a/dlls/multiplay_gamerules.cpp b/dlls/multiplay_gamerules.cpp index 7922d413..5380a326 100644 --- a/dlls/multiplay_gamerules.cpp +++ b/dlls/multiplay_gamerules.cpp @@ -31,6 +31,12 @@ #endif #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 BOOL g_fGameOver; extern int gmsgDeathMsg; // client dll messages @@ -1626,6 +1632,38 @@ void CHalfLifeMultiplay::ChangeLevel( void ) 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 ); if( minplayers || maxplayers ) { diff --git a/dlls/player.cpp b/dlls/player.cpp index d0a1f763..93a7c72f 100644 --- a/dlls/player.cpp +++ b/dlls/player.cpp @@ -2870,6 +2870,9 @@ void CBasePlayer::Spawn( void ) m_flNextChatTime = gpGlobals->time; + // START BOT + pBotCam = NULL; + // END BOT g_pGameRules->PlayerSpawn( this ); } diff --git a/dlls/player.h b/dlls/player.h index fecaf3c9..067fbab1 100644 --- a/dlls/player.h +++ b/dlls/player.h @@ -17,6 +17,10 @@ #include "pm_materials.h" +//START BOT +class CBotCam; +//END BOT + #define PLAYER_FATAL_FALL_SPEED 1024// approx 60 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. @@ -196,6 +200,10 @@ public: char m_szTeamName[TEAM_NAME_LENGTH]; + //START BOT + CBotCam *pBotCam; + //END BOT + virtual void Spawn( void ); void Pain( void );