Add zbots (undone, only spawn and jump)

This commit is contained in:
mittorn 2017-01-08 15:28:41 +00:00
parent 6ef6d0d27a
commit 8041c4fe81
74 changed files with 34698 additions and 77 deletions

View File

@ -27,7 +27,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 \
@ -129,10 +130,40 @@ LOCAL_SRC_FILES := agrunt.cpp airtank.cpp \
gravgun.cpp \
ar2.cpp \
big_cock.cpp \
bot/cs_bot_event.cpp \
bot/cs_bot_listen.cpp \
bot/cs_bot.cpp \
bot/cs_bot_chatter.cpp \
bot/cs_bot_vision.cpp \
bot/manager/bot_profile.cpp \
bot/manager/nav_node.cpp \
bot/manager/nav_file.cpp \
bot/manager/nav_path.cpp \
bot/manager/bot_util.cpp \
bot/manager/bot_manager.cpp \
bot/manager/nav_area.cpp \
bot/manager/bot.cpp \
bot/cs_bot_learn.cpp \
bot/cs_bot_weapon.cpp \
bot/cs_bot_pathfind.cpp \
bot/cs_bot_nav.cpp \
bot/cs_bot_init.cpp \
bot/cs_bot_manager.cpp \
bot/cs_bot_update.cpp \
bot/cs_bot_statemachine.cpp \
bot/shared_util.cpp \
../pm_shared/pm_debug.c \
../pm_shared/pm_math.c \
../pm_shared/pm_shared.c
# ../game_shared/voice_gamemgr.cpp
../pm_shared/pm_shared.c \
bot/states/cs_bot_investigate_noise.cpp \
bot/states/cs_bot_use_entity.cpp \
bot/states/cs_bot_hunt.cpp \
bot/states/cs_bot_move_to.cpp \
bot/states/cs_bot_idle.cpp \
bot/states/cs_bot_hide.cpp \
bot/states/cs_bot_attack.cpp \
bot/states/cs_bot_follow.cpp \
bot/cs_gamestate.cpp
LOCAL_LDLIBS := -llog

145
dlls/bot/GameEvent.h Normal file
View File

@ -0,0 +1,145 @@
/*
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* In addition, as a special exception, the author gives permission to
* link the code of this program with the Half-Life Game Engine ("HL
* Engine") and Modified Game Libraries ("MODs") developed by Valve,
* L.L.C ("Valve"). You must obey the GNU General Public License in all
* respects for all of the code used other than the HL Engine and MODs
* from Valve. If you modify this file, you may extend this exception
* to your version of the file, but you are not obligated to do so. If
* you do not wish to do so, delete this exception statement from your
* version.
*
*/
#ifndef GAME_EVENT_H
#define GAME_EVENT_H
#ifdef _WIN32
#pragma once
#endif
enum GameEventType
{
EVENT_INVALID = 0,
EVENT_WEAPON_FIRED, // tell bots the player is attack (argumens: 1 = attacker, 2 = NULL)
EVENT_WEAPON_FIRED_ON_EMPTY, // tell bots the player is attack without clip ammo (argumens: 1 = attacker, 2 = NULL)
EVENT_WEAPON_RELOADED, // tell bots the player is reloading his weapon (argumens: 1 = reloader, 2 = NULL)
EVENT_HE_GRENADE_EXPLODED, // tell bots the HE grenade is exploded (argumens: 1 = grenade thrower, 2 = NULL)
EVENT_FLASHBANG_GRENADE_EXPLODED, // tell bots the flashbang grenade is exploded (argumens: 1 = grenade thrower, 2 = explosion origin)
EVENT_SMOKE_GRENADE_EXPLODED, // tell bots the smoke grenade is exploded (argumens: 1 = grenade thrower, 2 = NULL)
EVENT_GRENADE_BOUNCED,
EVENT_BEING_SHOT_AT,
EVENT_PLAYER_BLINDED_BY_FLASHBANG, // tell bots the player is flashed (argumens: 1 = flashed player, 2 = NULL)
EVENT_PLAYER_FOOTSTEP, // tell bots the player is running (argumens: 1 = runner, 2 = NULL)
EVENT_PLAYER_JUMPED, // tell bots the player is jumped (argumens: 1 = jumper, 2 = NULL)
EVENT_PLAYER_DIED, // tell bots the player is killed (argumens: 1 = victim, 2 = killer)
EVENT_PLAYER_LANDED_FROM_HEIGHT, // tell bots the player is fell with some damage (argumens: 1 = felled player, 2 = NULL)
EVENT_PLAYER_TOOK_DAMAGE, // tell bots the player is take damage (argumens: 1 = victim, 2 = attacker)
EVENT_HOSTAGE_DAMAGED, // tell bots the player has injured a hostage (argumens: 1 = hostage, 2 = injurer)
EVENT_HOSTAGE_KILLED, // tell bots the player has killed a hostage (argumens: 1 = hostage, 2 = killer)
EVENT_DOOR, // tell bots the door is moving (argumens: 1 = door, 2 = NULL)
EVENT_BREAK_GLASS, // tell bots the glass has break (argumens: 1 = glass, 2 = NULL)
EVENT_BREAK_WOOD, // tell bots the wood has break (argumens: 1 = wood, 2 = NULL)
EVENT_BREAK_METAL, // tell bots the metal/computer has break (argumens: 1 = metal/computer, 2 = NULL)
EVENT_BREAK_FLESH, // tell bots the flesh has break (argumens: 1 = flesh, 2 = NULL)
EVENT_BREAK_CONCRETE, // tell bots the concrete has break (argumens: 1 = concrete, 2 = NULL)
EVENT_BOMB_PLANTED, // tell bots the bomb has been planted (argumens: 1 = planter, 2 = NULL)
EVENT_BOMB_DROPPED, // tell bots the bomb has been dropped (argumens: 1 = NULL, 2 = NULL)
EVENT_BOMB_PICKED_UP, // let the bots hear the bomb pickup (argumens: 1 = player that pickup c4, 2 = NULL)
EVENT_BOMB_BEEP, // let the bots hear the bomb beeping (argumens: 1 = c4, 2 = NULL)
EVENT_BOMB_DEFUSING, // tell the bots someone has started defusing (argumens: 1 = defuser, 2 = NULL)
EVENT_BOMB_DEFUSE_ABORTED, // tell the bots someone has aborted defusing (argumens: 1 = NULL, 2 = NULL)
EVENT_BOMB_DEFUSED, // tell the bots the bomb is defused (argumens: 1 = defuser, 2 = NULL)
EVENT_BOMB_EXPLODED, // let the bots hear the bomb exploding (argumens: 1 = NULL, 2 = NULL)
EVENT_HOSTAGE_USED, // tell bots the hostage is used (argumens: 1 = user, 2 = NULL)
EVENT_HOSTAGE_RESCUED, // tell bots the hostage is rescued (argumens: 1 = rescuer (CBasePlayer *), 2 = hostage (CHostage *))
EVENT_ALL_HOSTAGES_RESCUED, // tell bots the all hostages are rescued (argumens: 1 = NULL, 2 = NULL)
EVENT_VIP_ESCAPED, // tell bots the VIP is escaped (argumens: 1 = NULL, 2 = NULL)
EVENT_VIP_ASSASSINATED, // tell bots the VIP is assassinated (argumens: 1 = NULL, 2 = NULL)
EVENT_TERRORISTS_WIN, // tell bots the terrorists won the round (argumens: 1 = NULL, 2 = NULL)
EVENT_CTS_WIN, // tell bots the CTs won the round (argumens: 1 = NULL, 2 = NULL)
EVENT_ROUND_DRAW, // tell bots the round was a draw (argumens: 1 = NULL, 2 = NULL)
EVENT_ROUND_WIN, // tell carreer the round was a win (argumens: 1 = NULL, 2 = NULL)
EVENT_ROUND_LOSS, // tell carreer the round was a loss (argumens: 1 = NULL, 2 = NULL)
EVENT_ROUND_START, // tell bots the round was started (when freeze period is expired) (argumens: 1 = NULL, 2 = NULL)
EVENT_PLAYER_SPAWNED, // tell bots the player is spawned (argumens: 1 = spawned player, 2 = NULL)
EVENT_CLIENT_CORPSE_SPAWNED,
EVENT_BUY_TIME_START,
EVENT_PLAYER_LEFT_BUY_ZONE,
EVENT_DEATH_CAMERA_START,
EVENT_KILL_ALL,
EVENT_ROUND_TIME,
EVENT_DIE,
EVENT_KILL,
EVENT_HEADSHOT,
EVENT_KILL_FLASHBANGED,
EVENT_TUTOR_BUY_MENU_OPENNED,
EVENT_TUTOR_AUTOBUY,
EVENT_PLAYER_BOUGHT_SOMETHING,
EVENT_TUTOR_NOT_BUYING_ANYTHING,
EVENT_TUTOR_NEED_TO_BUY_PRIMARY_WEAPON,
EVENT_TUTOR_NEED_TO_BUY_PRIMARY_AMMO,
EVENT_TUTOR_NEED_TO_BUY_SECONDARY_AMMO,
EVENT_TUTOR_NEED_TO_BUY_ARMOR,
EVENT_TUTOR_NEED_TO_BUY_DEFUSE_KIT,
EVENT_TUTOR_NEED_TO_BUY_GRENADE,
EVENT_CAREER_TASK_DONE,
EVENT_START_RADIO_1,
EVENT_RADIO_COVER_ME,
EVENT_RADIO_YOU_TAKE_THE_POINT,
EVENT_RADIO_HOLD_THIS_POSITION,
EVENT_RADIO_REGROUP_TEAM,
EVENT_RADIO_FOLLOW_ME,
EVENT_RADIO_TAKING_FIRE,
EVENT_START_RADIO_2,
EVENT_RADIO_GO_GO_GO,
EVENT_RADIO_TEAM_FALL_BACK,
EVENT_RADIO_STICK_TOGETHER_TEAM,
EVENT_RADIO_GET_IN_POSITION_AND_WAIT,
EVENT_RADIO_STORM_THE_FRONT,
EVENT_RADIO_REPORT_IN_TEAM,
EVENT_START_RADIO_3,
EVENT_RADIO_AFFIRMATIVE,
EVENT_RADIO_ENEMY_SPOTTED,
EVENT_RADIO_NEED_BACKUP,
EVENT_RADIO_SECTOR_CLEAR,
EVENT_RADIO_IN_POSITION,
EVENT_RADIO_REPORTING_IN,
EVENT_RADIO_GET_OUT_OF_THERE,
EVENT_RADIO_NEGATIVE,
EVENT_RADIO_ENEMY_DOWN,
EVENT_END_RADIO,
EVENT_NEW_MATCH, // tell bots the game is new (argumens: 1 = NULL, 2 = NULL)
EVENT_PLAYER_CHANGED_TEAM, // tell bots the player is switch his team (also called from ClientPutInServer()) (argumens: 1 = switcher, 2 = NULL)
EVENT_BULLET_IMPACT, // tell bots the player is shoot at wall (argumens: 1 = shooter, 2 = shoot trace end position)
EVENT_GAME_COMMENCE, // tell bots the game is commencing (argumens: 1 = NULL, 2 = NULL)
EVENT_WEAPON_ZOOMED, // tell bots the player is switch weapon zoom (argumens: 1 = zoom switcher, 2 = NULL)
EVENT_HOSTAGE_CALLED_FOR_HELP, // tell bots the hostage is talking (argumens: 1 = listener, 2 = NULL)
NUM_GAME_EVENTS,
};
extern const char *GameEventName[ NUM_GAME_EVENTS + 1 ];
#endif // GAME_EVENT_H

168
dlls/bot/bot_common.h Normal file
View File

@ -0,0 +1,168 @@
// halflife common
#include "extdll.h"
#include "util.h"
#include "cbase.h"
#include "weapons.h"
#include "player.h"
#include "gamerules.h"
#include "client.h"
// regamedll
#define NOXREF
#include "bot/GameEvent.h"
#include "bot/botsengcallback.h"
typedef long long int64;
typedef int int32;
typedef unsigned int uint32;
#define Q_stricmp strcasecmp
#define Q_max( a, b ) (((a) > (b)) ? (a) : (b))
#define Q_min( a, b ) (((a) < (b)) ? (a) : (b))
#define Q_atoi atoi
#define Q_snprintf snprintf
#define Q_strncpy strncpy
#define CloneString strdup
#define Q_atof atof
#define Q_strchr strchr
#define Q_strcmp strcmp
#define Q_strcpy strcpy
#define Q_sprintf sprintf
#define Q_strlen strlen
#define Q_write write
#define Q_close close
#define Q_strcat strcat
#define Q_stricmp strcasecmp
#define Q_strnicmp strncasecmp
#define Q_vsnprintf vsnprintf
template <typename T>
T clamp(T a, T min, T max) { return (a > max) ? max : (a < min) ? min : a; }
#include "bot/pm_math.h"
static char mp_com_token[64];
static inline char *MP_COM_GetToken()
{
return mp_com_token;
}
static inline char *MP_COM_Parse(char *data)
{
int c;
int len;
len = 0;
mp_com_token[0] = '\0';
if (!data)
{
return NULL;
}
skipwhite:
// skip whitespace
while (*data <= ' ')
{
if (!data[0])
return NULL;
++data;
}
c = *data;
// skip // comments till the next line
if (c == '/' && data[1] == '/')
{
while (*data && *data != '\n')
++data;
goto skipwhite; // start over new line
}
// handle quoted strings specially: copy till the end or another quote
if (c == '\"')
{
++data; // skip starting quote
while (true)
{
// get char and advance
c = *data++;
if (c == '\"' || !c)
{
mp_com_token[ len ] = '\0';
return data;
}
mp_com_token[ len++ ] = c;
}
}
// parse single characters
if (c == '{' || c == '}'|| c == ')'|| c == '(' || c == '\'' || c == ',')
{
mp_com_token[ len++ ] = c;
mp_com_token[ len ] = '\0';
return data + 1;
}
// parse a regular word
do
{
mp_com_token[ len++ ] = c;
++data;
c = *data;
if (c == '{' || c == '}'|| c == ')'|| c == '(' || c == '\'' || c == ',')
break;
}
while (c > 32);
mp_com_token[ len ] = '\0';
return data;
}
static inline int MP_COM_TokenWaiting(char *buffer)
{
char *p;
p = buffer;
while (*p && *p != '\n')
{
if (!isspace(*p) || isalnum(*p))
return 1;
++p;
}
return 0;
}
#include "bot/shared_util.h"
#include "bot/utllinkedlist.h"
#include "bot/utlvector.h"
#define MAX_CLIENTS 32
// manager
class CNavNode;
#include "bot/steam_util.h"
#include "bot/manager/nav.h"
#include "bot/manager/nav_area.h"
#include "bot/manager/nav_node.h"
#include "bot/manager/nav_file.h"
#include "bot/manager/improv.h"
#include "bot/manager/bot_util.h"
#include "bot/manager/nav_path.h"
#include "bot/manager/bot.h"
#include "bot/manager/bot_manager.h"
#include "bot/manager/bot_constants.h"
#include "bot/manager/bot_profile.h"
// zbot
#include "bot/cs_bot.h"

View File

@ -0,0 +1,45 @@
#include "enginecallback.h"
#define GET_FILE_SIZE (*g_engfuncs.pfnGetFileSize)
#define SERVER_PRINT (*g_engfuncs.pfnServerPrint)
#define CREATE_FAKE_CLIENT (*g_engfuncs.pfnCreateFakeClient)
#define PLAYER_RUN_MOVE (*g_engfuncs.pfnRunPlayerMove)
#define NUMBER_OF_ENTITIES (*g_engfuncs.pfnNumberOfEntities)
#define GET_INFO_BUFFER (*g_engfuncs.pfnGetInfoKeyBuffer)
#define GET_KEY_VALUE (*g_engfuncs.pfnInfoKeyValue)
#define SET_KEY_VALUE (*g_engfuncs.pfnSetKeyValue)
#define SET_CLIENT_KEY_VALUE (*g_engfuncs.pfnSetClientKeyValue)
#define IS_MAP_VALID (*g_engfuncs.pfnIsMapValid)
#define STATIC_DECAL (*g_engfuncs.pfnStaticDecal)
#define IS_DEDICATED_SERVER (*g_engfuncs.pfnIsDedicatedServer)
#define PRECACHE_EVENT (*g_engfuncs.pfnPrecacheEvent)
#define PLAYBACK_EVENT_FULL (*g_engfuncs.pfnPlaybackEvent)
#define ENGINE_SET_PVS (*g_engfuncs.pfnSetFatPVS)
#define ENGINE_SET_PAS (*g_engfuncs.pfnSetFatPAS)
#define ENGINE_CHECK_VISIBILITY (*g_engfuncs.pfnCheckVisibility)
#define DELTA_SET (*g_engfuncs.pfnDeltaSetField)
#define DELTA_UNSET (*g_engfuncs.pfnDeltaUnsetField)
#define DELTA_ADDENCODER (*g_engfuncs.pfnDeltaAddEncoder)
#define ENGINE_CURRENT_PLAYER (*g_engfuncs.pfnGetCurrentPlayer)
#define ENGINE_CANSKIP (*g_engfuncs.pfnCanSkipPlayer)
#define DELTA_FINDFIELD (*g_engfuncs.pfnDeltaFindField)
#define DELTA_SETBYINDEX (*g_engfuncs.pfnDeltaSetFieldByIndex)
#define DELTA_UNSETBYINDEX (*g_engfuncs.pfnDeltaUnsetFieldByIndex)
#define REMOVE_KEY_VALUE (*g_engfuncs.pfnInfo_RemoveKey)
#define SET_PHYSICS_KEY_VALUE (*g_engfuncs.pfnSetPhysicsKeyValue)
#define ENGINE_GETPHYSINFO (*g_engfuncs.pfnGetPhysicsInfoString)
#define ENGINE_SETGROUPMASK (*g_engfuncs.pfnSetGroupMask)
#define ENGINE_INSTANCE_BASELINE (*g_engfuncs.pfnCreateInstancedBaseline)
#define ENGINE_FORCE_UNMODIFIED (*g_engfuncs.pfnForceUnmodified)
#define PLAYER_CNX_STATS (*g_engfuncs.pfnGetPlayerStats)
#define ADD_SERVER_COMMAND (*g_engfuncs.pfnAddServerCommand)
#define SET_CLIENT_LISTENING (*g_engfuncs.pfnVoice_SetClientListening)
#define GETPLAYERAUTHID (*g_engfuncs.pfnGetPlayerAuthId)
#define GET_FILE_SIZE (*g_engfuncs.pfnGetFileSize)
#define GET_APPROX_WAVE_PLAY_LEN (*g_engfuncs.pfnGetApproxWavePlayLen)
#define IS_CAREER_MATCH (*g_engfuncs.pfnIsCareerMatch)
#define GET_LOCALIZED_STRING_LENGTH (*g_engfuncs.pfnGetLocalizedStringLength)
#define REGISTER_TUTOR_MESSAGE_SHOWN (*g_engfuncs.pfnRegisterTutorMessageShown)
#define GET_TIMES_TUTOR_MESSAGE_SHOWN (*g_engfuncs.pfnGetTimesTutorMessageShown)
#define ENG_CHECK_PARM (*g_engfuncs.pfnEngCheckParm)
void CONSOLE_ECHO(char *pszMsg, ...);

View File

@ -0,0 +1,31 @@
#define CREATE_FAKE_CLIENT (*g_engfuncs.pfnCreateFakeClient)
#define PLAYER_RUN_MOVE (*g_engfuncs.pfnRunPlayerMove)
#define NUMBER_OF_ENTITIES (*g_engfuncs.pfnNumberOfEntities)
#define GET_INFO_BUFFER (*g_engfuncs.pfnGetInfoKeyBuffer)
#define GET_KEY_VALUE (*g_engfuncs.pfnInfoKeyValue)
#define SET_KEY_VALUE (*g_engfuncs.pfnSetKeyValue)
#define SET_CLIENT_KEY_VALUE (*g_engfuncs.pfnSetClientKeyValue)
#define IS_MAP_VALID (*g_engfuncs.pfnIsMapValid)
#define STATIC_DECAL (*g_engfuncs.pfnStaticDecal)
#define IS_DEDICATED_SERVER (*g_engfuncs.pfnIsDedicatedServer)
#define PRECACHE_EVENT (*g_engfuncs.pfnPrecacheEvent)
#define PLAYBACK_EVENT_FULL (*g_engfuncs.pfnPlaybackEvent)
#define ENGINE_SET_PVS (*g_engfuncs.pfnSetFatPVS)
#define ENGINE_SET_PAS (*g_engfuncs.pfnSetFatPAS)
#define ENGINE_CHECK_VISIBILITY (*g_engfuncs.pfnCheckVisibility)
#define DELTA_SET (*g_engfuncs.pfnDeltaSetField)
#define DELTA_UNSET (*g_engfuncs.pfnDeltaUnsetField)
#define DELTA_ADDENCODER (*g_engfuncs.pfnDeltaAddEncoder)
#define ENGINE_CURRENT_PLAYER (*g_engfuncs.pfnGetCurrentPlayer)
#define ENGINE_CANSKIP (*g_engfuncs.pfnCanSkipPlayer)
#define DELTA_FINDFIELD (*g_engfuncs.pfnDeltaFindField)
#define DELTA_SETBYINDEX (*g_engfuncs.pfnDeltaSetFieldByIndex)
#define DELTA_UNSETBYINDEX (*g_engfuncs.pfnDeltaUnsetFieldByIndex)
#define REMOVE_KEY_VALUE (*g_engfuncs.pfnInfo_RemoveKey)
#define SET_PHYSICS_KEY_VALUE (*g_engfuncs.pfnSetPhysicsKeyValue)
#define ENGINE_GETPHYSINFO (*g_engfuncs.pfnGetPhysicsInfoString)
#define ENGINE_SETGROUPMASK (*g_engfuncs.pfnSetGroupMask)
#define ENGINE_INSTANCE_BASELINE (*g_engfuncs.pfnCreateInstancedBaseline)
#define ENGINE_FORCE_UNMODIFIED (*g_engfuncs.pfnForceUnmodified)
#define PLAYER_CNX_STATS (*g_engfuncs.pfnGetPlayerStats)

867
dlls/bot/cs_bot.cpp Normal file
View File

@ -0,0 +1,867 @@
#include "bot_common.h"
// Return the number of bots following the given player
int GetBotFollowCount(CBasePlayer *leader)
{
int count = 0;
for (int i = 1; i <= gpGlobals->maxClients; ++i)
{
CBaseEntity *entity = UTIL_PlayerByIndex(i);
if (entity == NULL)
continue;
if (FNullEnt(entity->pev))
continue;
if (FStrEq(STRING(entity->pev->netname), ""))
continue;
CBasePlayer *player = static_cast<CBasePlayer *>(entity);
if (!player->IsBot())
continue;
if (!player->IsAlive())
continue;
CCSBot *bot = dynamic_cast<CCSBot *>(player);
if (bot != NULL && bot->GetFollowLeader() == leader)
++count;
}
return count;
}
// Change movement speed to walking
void CCSBot::Walk()
{
if (m_mustRunTimer.IsElapsed())
{
CBot::Walk();
return;
}
// must run
Run();
}
// Return true if jump was started.
// This is extended from the base jump to disallow jumping when in a crouch area.
bool CCSBot::Jump(bool mustJump)
{
// prevent jumping if we're crouched, unless we're in a crouchjump area - jump wins
bool inCrouchJumpArea = (m_lastKnownArea &&
(m_lastKnownArea->GetAttributes() & NAV_CROUCH) &&
!(m_lastKnownArea->GetAttributes() & NAV_JUMP));
if (inCrouchJumpArea)
{
return false;
}
return CBot::Jump(mustJump);
}
// Invoked when injured by something
// NOTE: We dont want to directly call Attack() here, or the bots will have super-human reaction times when injured
int CCSBot::TakeDamage(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType)
{
CBaseEntity *attacker = GetClassPtr((CBaseEntity *)pevInflictor);
// if we were attacked by a teammate, rebuke
if (attacker->IsPlayer())
{
CBasePlayer *player = static_cast<CBasePlayer *>(attacker);
/*if (player->m_iTeam == m_iTeam && !player->IsBot())
{
GetChatter()->FriendlyFire();
}*/
}
if (attacker->IsPlayer() && IsEnemy(attacker))
{
// Track previous attacker so we don't try to panic multiple times for a shotgun blast
CBasePlayer *lastAttacker = m_attacker;
float lastAttackedTimestamp = m_attackedTimestamp;
// keep track of our last attacker
m_attacker = static_cast<CBasePlayer *>(attacker);
m_attackedTimestamp = gpGlobals->time;
// no longer safe
AdjustSafeTime();
if (!IsSurprised() && (m_attacker != lastAttacker || m_attackedTimestamp != lastAttackedTimestamp))
{
CBasePlayer *enemy = static_cast<CBasePlayer *>(attacker);
// being hurt by an enemy we can't see causes panic
if (!IsVisible(enemy, CHECK_FOV))
{
bool panic = false;
// if not attacking anything, look around to try to find attacker
if (!IsAttacking())
{
panic = true;
}
else
{
// we are attacking
if (!IsEnemyVisible())
{
// can't see our current enemy, panic to acquire new attacker
panic = true;
}
}
if (!panic)
{
float invSkill = 1.0f - GetProfile()->GetSkill();
float panicChance = invSkill * invSkill * 50.0f;
if (panicChance > RANDOM_FLOAT(0, 100))
{
panic = true;
}
}
if (panic != false)
{
// can't see our current enemy, panic to acquire new attacker
Panic(m_attacker);
}
}
}
}
// extend
return CBasePlayer::TakeDamage(pevInflictor, pevAttacker, flDamage, bitsDamageType);
}
// Invoked when killed
void CCSBot::Killed(entvars_t *pevAttacker, int iGib)
{
PrintIfWatched("Killed( attacker = %s )\n", STRING(pevAttacker->netname));
GetChatter()->OnDeath();
// increase the danger where we died
const float deathDanger = 1.0f;
const float deathDangerRadius = 500.0f;
IncreaseDangerNearby(m_iTeam - 1, deathDanger, m_lastKnownArea, &pev->origin, deathDangerRadius);
// end voice feedback
// EndVoiceFeedback();
// extend
CBasePlayer::Killed(pevAttacker, iGib);
}
// Return true if line segment intersects rectagular volume
bool IsIntersectingBox(const Vector *start, const Vector *end, const Vector *boxMin, const Vector *boxMax)
{
unsigned char startFlags = 0;
unsigned char endFlags = 0;
// classify start point
if (start->x < boxMin->x)
startFlags |= LO_X;
if (start->x > boxMax->x)
startFlags |= HI_X;
if (start->y < boxMin->y)
startFlags |= LO_Y;
if (start->y > boxMax->y)
startFlags |= HI_Y;
if (start->z < boxMin->z)
startFlags |= LO_Z;
if (start->z > boxMax->z)
startFlags |= HI_Z;
// classify end point
if (end->x < boxMin->x)
endFlags |= LO_X;
if (end->x > boxMax->x)
endFlags |= HI_X;
if (end->y < boxMin->y)
endFlags |= LO_Y;
if (end->y > boxMax->y)
endFlags |= HI_Y;
if (end->z < boxMin->z)
endFlags |= LO_Z;
if (end->z > boxMax->z)
endFlags |= HI_Z;
// trivial reject
if (startFlags & endFlags)
return false;
// TODO: Do exact line/box intersection check
return true;
}
// When bot is touched by another entity.
void CCSBot::BotTouch(CBaseEntity *other)
{
// if we have touched a higher-priority player, make way
// TODO: Need to account for reaction time, etc.
if (other->IsPlayer())
{
// if we are defusing a bomb, don't move
if (IsDefusingBomb())
return;
CBasePlayer *player = static_cast<CBasePlayer *>(other);
// get priority of other player
unsigned int otherPri = TheCSBots()->GetPlayerPriority(player);
// get our priority
unsigned int myPri = TheCSBots()->GetPlayerPriority(this);
// if our priority is better, don't budge
if (myPri < otherPri)
return;
// they are higher priority - make way, unless we're already making way for someone more important
if (m_avoid != NULL)
{
unsigned int avoidPri = TheCSBots()->GetPlayerPriority(static_cast<CBasePlayer *>(static_cast<CBaseEntity *>(m_avoid)));
if (avoidPri < otherPri)
{
// ignore 'other' because we're already avoiding someone better
return;
}
}
m_avoid = other;
m_avoidTimestamp = gpGlobals->time;
return;
}
// If we won't be able to break it, don't try
if (other->pev->takedamage != DAMAGE_YES)
return;
if (IsAttacking())
return;
// See if it's breakable
if (FClassnameIs(other->pev, "func_breakable"))
{
Vector center = (other->pev->absmax + other->pev->absmin) / 2.0f;
bool breakIt = true;
if (m_pathLength)
{
Vector goal = m_goalPosition + Vector(0, 0, HalfHumanHeight);
breakIt = IsIntersectingBox(&pev->origin, &goal, &other->pev->absmin, &other->pev->absmax);
}
if (breakIt)
{
// it's breakable - try to shoot it.
SetLookAt("Breakable", &center, PRIORITY_HIGH, 0.2, 0, 5.0);
if (IsUsingGrenade())
{
EquipBestWeapon(0);
return;
}
PrimaryAttack();
}
}
}
bool CCSBot::IsBusy() const
{
if (IsAttacking() ||
IsBuying() ||
IsDefusingBomb() ||
GetTask() == PLANT_BOMB ||
GetTask() == RESCUE_HOSTAGES ||
IsSniping())
{
return true;
}
return false;
}
void CCSBot::BotDeathThink()
{
;
}
CBasePlayer *CCSBot::FindNearbyPlayer()
{
CBaseEntity *pEntity = NULL;
Vector vecSrc = pev->origin;
const float flRadius = 800.0f;
while ((pEntity = UTIL_FindEntityInSphere(pEntity, vecSrc, flRadius)) != NULL)
{
if (!pEntity->IsPlayer())
continue;
if (!(pEntity->pev->flags & FL_FAKECLIENT))
continue;
return static_cast<CBasePlayer *>(pEntity);
}
return NULL;
}
// Assign given player as our current enemy to attack
void CCSBot::SetEnemy(CBasePlayer *enemy)
{
if (m_enemy != enemy)
{
m_enemy = enemy;
m_currentEnemyAcquireTimestamp = gpGlobals->time;
}
}
// If we are not on the navigation mesh (m_currentArea == NULL),
// move towards last known area.
// Return false if off mesh.
bool CCSBot::StayOnNavMesh()
{
if (m_currentArea != NULL)
return true;
// move back onto the area map
// if we have no lastKnownArea, we probably started off
// of the nav mesh - find the closest nav area and use it
CNavArea *goalArea;
if (!m_currentArea && !m_lastKnownArea)
{
goalArea = TheNavAreaGrid.GetNearestNavArea(&pev->origin);
PrintIfWatched("Started off the nav mesh - moving to closest nav area...\n");
}
else
{
goalArea = m_lastKnownArea;
PrintIfWatched("Getting out of NULL area...\n");
}
if (goalArea != NULL)
{
Vector pos;
goalArea->GetClosestPointOnArea(&pev->origin, &pos);
// move point into area
Vector to = pos - pev->origin;
to.NormalizeInPlace();
// how far to "step into" an area - must be less than min area size
const float stepInDist = 5.0f;
pos = pos + (stepInDist * to);
MoveTowardsPosition(&pos);
}
// if we're stuck, try to get un-stuck
// do stuck movements last, so they override normal movement
if (m_isStuck)
{
Wiggle();
}
return false;
}
void CCSBot::Panic(CBasePlayer *enemy)
{
if (IsSurprised())
return;
Vector2D dir(BotCOS(pev->v_angle.y), BotSIN(pev->v_angle.y));
Vector2D perp(-dir.y, dir.x);
Vector spot;
if (GetProfile()->GetSkill() >= 0.5f)
{
Vector2D toEnemy = (enemy->pev->origin - pev->origin).Make2D();
toEnemy.NormalizeInPlace();
float along = DotProduct(toEnemy, dir);
float c45 = 0.7071f;
float size = 100.0f;
float shift = RANDOM_FLOAT(-75.0, 75.0);
if (along > c45)
{
spot.x = pev->origin.x + dir.x * size + perp.x * shift;
spot.y = pev->origin.y + dir.y * size + perp.y * shift;
}
else if (along < -c45)
{
spot.x = pev->origin.x - dir.x * size + perp.x * shift;
spot.y = pev->origin.y - dir.y * size + perp.y * shift;
}
else if (DotProduct(toEnemy, perp) > 0.0)
{
spot.x = pev->origin.x + perp.x * size + dir.x * shift;
spot.y = pev->origin.y + perp.y * size + dir.y * shift;
}
else
{
spot.x = pev->origin.x - perp.x * size + dir.x * shift;
spot.y = pev->origin.y - perp.y * size + dir.y * shift;
}
}
else
{
const float offset = 200.0f;
float side = RANDOM_FLOAT(-offset, offset) * 2.0f;
spot.x = pev->origin.x - dir.x * offset + perp.x * side;
spot.y = pev->origin.y - dir.y * offset + perp.y * side;
}
spot.z = pev->origin.z + RANDOM_FLOAT(-50.0, 50.0);
// we are stunned for a moment
m_surpriseDelay = RANDOM_FLOAT(0.1, 0.2);
m_surpriseTimestamp = gpGlobals->time;
SetLookAt("Panic", &spot, PRIORITY_HIGH, 0, 0, 5.0);
PrintIfWatched("Aaaah!\n");
}
bool CCSBot::IsDoingScenario() const
{
if (cv_bot_defer_to_human.value <= 0.0f)
return true;
return !UTIL_HumansOnTeam(m_iTeam, true);
}
// Return true if we noticed the bomb on the ground or on the radar (for T's only)
bool CCSBot::NoticeLooseBomb() const
{
CCSBotManager *ctrl = TheCSBots();
if (ctrl->GetScenario() != CCSBotManager::SCENARIO_DEFUSE_BOMB)
return false;
CBaseEntity *bomb = ctrl->GetLooseBomb();
if (bomb != NULL)
{
// T's can always see bomb on their radar
return true;
}
return false;
}
// Return true if can see the bomb lying on the ground
bool CCSBot::CanSeeLooseBomb() const
{
CCSBotManager *ctrl = TheCSBots();
if (ctrl->GetScenario() != CCSBotManager::SCENARIO_DEFUSE_BOMB)
return false;
CBaseEntity *bomb = ctrl->GetLooseBomb();
if (bomb != NULL)
{
if (IsVisible(&bomb->pev->origin, CHECK_FOV))
return true;
}
return false;
}
// Return true if can see the planted bomb
bool CCSBot::CanSeePlantedBomb() const
{
CCSBotManager *ctrl = TheCSBots();
if (ctrl->GetScenario() != CCSBotManager::SCENARIO_DEFUSE_BOMB)
return false;
if (!GetGameState()->IsBombPlanted())
return false;
const Vector *bombPos = GetGameState()->GetBombPosition();
if (bombPos != NULL && IsVisible((Vector*)bombPos, CHECK_FOV))
return true;
return false;
}
// Return last enemy that hurt us
CBasePlayer *CCSBot::GetAttacker() const
{
if (m_attacker != NULL && m_attacker->IsAlive())
return m_attacker;
return NULL;
}
// Immediately jump off of our ladder, if we're on one
void CCSBot::GetOffLadder()
{
if (IsUsingLadder())
{
Jump(MUST_JUMP);
DestroyPath();
}
}
// Return time when given spot was last checked
float CCSBot::GetHidingSpotCheckTimestamp(HidingSpot *spot) const
{
for (int i = 0; i < m_checkedHidingSpotCount; ++i)
{
if (m_checkedHidingSpot[i].spot->GetID() == spot->GetID())
return m_checkedHidingSpot[i].timestamp;
}
return -999999.9f;
}
// Set the timestamp of the given spot to now.
// If the spot is not in the set, overwrite the least recently checked spot.
void CCSBot::SetHidingSpotCheckTimestamp(HidingSpot *spot)
{
int leastRecent = 0;
float leastRecentTime = gpGlobals->time + 1.0f;
for (int i = 0; i < m_checkedHidingSpotCount; ++i)
{
// if spot is in the set, just update its timestamp
if (m_checkedHidingSpot[i].spot->GetID() == spot->GetID())
{
m_checkedHidingSpot[i].timestamp = gpGlobals->time;
return;
}
// keep track of least recent spot
if (m_checkedHidingSpot[i].timestamp < leastRecentTime)
{
leastRecentTime = m_checkedHidingSpot[i].timestamp;
leastRecent = i;
}
}
// if there is room for more spots, append this one
if (m_checkedHidingSpotCount < MAX_CHECKED_SPOTS)
{
m_checkedHidingSpot[ m_checkedHidingSpotCount ].spot = spot;
m_checkedHidingSpot[ m_checkedHidingSpotCount ].timestamp = gpGlobals->time;
++m_checkedHidingSpotCount;
}
else
{
// replace the least recent spot
m_checkedHidingSpot[ leastRecent ].spot = spot;
m_checkedHidingSpot[ leastRecent ].timestamp = gpGlobals->time;
}
}
// Periodic check of hostage count in case we lost some
void CCSBot::UpdateHostageEscortCount()
{
}
// Return true if we are outnumbered by enemies
bool CCSBot::IsOutnumbered() const
{
return (GetNearbyFriendCount() < GetNearbyEnemyCount() - 1) ? true : false;
}
// Return number of enemies we are outnumbered by
int CCSBot::OutnumberedCount() const
{
if (IsOutnumbered())
{
return (GetNearbyEnemyCount() - 1) - GetNearbyFriendCount();
}
return 0;
}
// Return the closest "important" enemy for the given scenario (bomb carrier, VIP, hostage escorter)
CBasePlayer *CCSBot::GetImportantEnemy(bool checkVisibility) const
{
CCSBotManager *ctrl = TheCSBots();
CBasePlayer *nearEnemy = NULL;
float nearDist = 999999999.9f;
for (int i = 1; i <= gpGlobals->maxClients; ++i)
{
CBaseEntity *entity = UTIL_PlayerByIndex(i);
if (entity == NULL)
continue;
if (FNullEnt(entity->pev))
continue;
if (FStrEq(STRING(entity->pev->netname), ""))
continue;
// is it a player?
if (!entity->IsPlayer())
continue;
CBasePlayer *player = static_cast<CBasePlayer *>(entity);
// is it alive?
if (!player->IsAlive())
continue;
// skip friends
// is it closest?
Vector d = pev->origin - player->pev->origin;
float distSq = d.x * d.x + d.y * d.y + d.z * d.z;
if (distSq < nearDist)
{
if (checkVisibility && !IsVisible(player, CHECK_FOV))
continue;
nearEnemy = player;
nearDist = distSq;
}
}
return nearEnemy;
}
// Sets our current disposition
void CCSBot::SetDisposition(DispositionType disposition)
{
m_disposition = disposition;
if (m_disposition != IGNORE_ENEMIES)
{
m_ignoreEnemiesTimer.Invalidate();
}
}
// Return our current disposition
CCSBot::DispositionType CCSBot::GetDisposition() const
{
if (!m_ignoreEnemiesTimer.IsElapsed())
return IGNORE_ENEMIES;
return m_disposition;
}
// Ignore enemies for a short durationy
void CCSBot::IgnoreEnemies(float duration)
{
m_ignoreEnemiesTimer.Start(duration);
}
// Increase morale one step
void CCSBot::IncreaseMorale()
{
if (m_morale < EXCELLENT)
{
m_morale = static_cast<MoraleType>(m_morale + 1);
}
}
// Decrease morale one step
void CCSBot::DecreaseMorale()
{
if (m_morale > TERRIBLE)
{
m_morale = static_cast<MoraleType>(m_morale - 1);
}
}
// Return true if we are acting like a rogue (not listening to teammates, not doing scenario goals)
// TODO: Account for morale
bool CCSBot::IsRogue() const
{
CCSBotManager *ctrl = TheCSBots();
if (!ctrl->AllowRogues())
return false;
// periodically re-evaluate our rogue status
if (m_rogueTimer.IsElapsed())
{
m_rogueTimer.Start(RANDOM_FLOAT(10, 30));
// our chance of going rogue is inversely proportional to our teamwork attribute
const float rogueChance = 100.0f * (1.0f - GetProfile()->GetTeamwork());
m_isRogue = (RANDOM_FLOAT(0, 100) < rogueChance);
}
return m_isRogue;
}
// Return true if we are in a hurry
bool CCSBot::IsHurrying() const
{
if (!m_hurryTimer.IsElapsed())
return true;
CCSBotManager *ctrl = TheCSBots();
return false;
}
// Return true if it is the early, "safe", part of the round
bool CCSBot::IsSafe() const
{
CCSBotManager *ctrl = TheCSBots();
if (ctrl->GetElapsedRoundTime() < m_safeTime)
return true;
return false;
}
// Return true if it is well past the early, "safe", part of the round
bool CCSBot::IsWellPastSafe() const
{
CCSBotManager *ctrl = TheCSBots();
if (ctrl->GetElapsedRoundTime() > 1.25f * m_safeTime)
return true;
return false;
}
// Return true if we were in the safe time last update, but not now
bool CCSBot::IsEndOfSafeTime() const
{
return m_wasSafe && !IsSafe();
}
// Return the amount of "safe time" we have left
float CCSBot::GetSafeTimeRemaining() const
{
CCSBotManager *ctrl = TheCSBots();
return m_safeTime - ctrl->GetElapsedRoundTime();
}
// Called when enemy seen to adjust safe time for this round
void CCSBot::AdjustSafeTime()
{
CCSBotManager *ctrl = TheCSBots();
// if we spotted an enemy sooner than we thought possible, adjust our notion of "safe" time
if (m_safeTime > ctrl->GetElapsedRoundTime())
{
// since right now is not safe, adjust safe time to be a few seconds ago
m_safeTime = ctrl->GetElapsedRoundTime() - 2.0f;
}
}
// Return true if we haven't seen an enemy for "a long time"
bool CCSBot::HasNotSeenEnemyForLongTime() const
{
const float longTime = 30.0f;
return (GetTimeSinceLastSawEnemy() > longTime);
}
// Pick a random zone and hide near it
bool CCSBot::GuardRandomZone(float range)
{
CCSBotManager *ctrl = TheCSBots();
const CCSBotManager::Zone *zone = ctrl->GetRandomZone();
if (zone != NULL)
{
CNavArea *rescueArea = ctrl->GetRandomAreaInZone(zone);
if (rescueArea != NULL)
{
Hide(rescueArea, -1.0f, range);
return true;
}
}
return false;
}
// Do a breadth-first search to find a good retreat spot.
// Don't pick a spot that a Player is currently occupying.
const Vector *FindNearbyRetreatSpot(CCSBot *me, float maxRange)
{
CNavArea *area = me->GetLastKnownArea();
if (area == NULL)
return NULL;
// collect spots that enemies cannot see
CollectRetreatSpotsFunctor collector(me, maxRange);
SearchSurroundingAreas(area, &me->pev->origin, collector, maxRange);
if (collector.m_count == 0)
return NULL;
// select a hiding spot at random
int which = RANDOM_LONG(0, collector.m_count - 1);
return collector.m_spot[ which ];
}
// Return euclidean distance to farthest escorted hostage.
// Return -1 if no hostage is following us.
float CCSBot::GetRangeToFarthestEscortedHostage() const
{
return 0;
}

1777
dlls/bot/cs_bot.h Normal file

File diff suppressed because it is too large Load Diff

2246
dlls/bot/cs_bot_chatter.cpp Normal file

File diff suppressed because it is too large Load Diff

607
dlls/bot/cs_bot_chatter.h Normal file
View File

@ -0,0 +1,607 @@
/*
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* In addition, as a special exception, the author gives permission to
* link the code of this program with the Half-Life Game Engine ("HL
* Engine") and Modified Game Libraries ("MODs") developed by Valve,
* L.L.C ("Valve"). You must obey the GNU General Public License in all
* respects for all of the code used other than the HL Engine and MODs
* from Valve. If you modify this file, you may extend this exception
* to your version of the file, but you are not obligated to do so. If
* you do not wish to do so, delete this exception statement from your
* version.
*
*/
#ifndef CS_BOT_CHATTER_H
#define CS_BOT_CHATTER_H
#ifdef _WIN32
#pragma once
#endif
#define UNDEFINED_COUNT 0xFFFF
#define MAX_PLACES_PER_MAP 64
#define UNDEFINED_SUBJECT (-1)
#define COUNT_MANY 4 // equal to or greater than this is "many"
class CCSBot;
class BotChatterInterface;
typedef unsigned int PlaceCriteria;
typedef unsigned int CountCriteria;
// A meme is a unit information that bots use to
// transmit information to each other via the radio
class BotMeme
{
public:
virtual ~BotMeme(){}
void Transmit(CCSBot *sender) const; // transmit meme to other bots
virtual void Interpret(CCSBot *sender, CCSBot *receiver) const = 0; // cause the given bot to act on this meme
};
class BotAllHostagesGoneMeme: public BotMeme
{
public:
virtual void Interpret(CCSBot *sender, CCSBot *receiver) const; // cause the given bot to act on this meme
};
class BotHostageBeingTakenMeme: public BotMeme
{
public:
virtual void Interpret(CCSBot *sender, CCSBot *receiver) const; // cause the given bot to act on this meme
};
class BotHelpMeme: public BotMeme
{
public:
BotHelpMeme(Place place = UNDEFINED_PLACE)
{
m_place = place;
}
virtual void Interpret(CCSBot *sender, CCSBot *receiver) const; // cause the given bot to act on this meme
private:
Place m_place;
};
class BotBombsiteStatusMeme: public BotMeme
{
public:
enum StatusType { CLEAR, PLANTED };
BotBombsiteStatusMeme(int zoneIndex, StatusType status)
{
m_zoneIndex = zoneIndex;
m_status = status;
}
virtual void Interpret(CCSBot *sender, CCSBot *receiver) const; // cause the given bot to act on this meme
private:
int m_zoneIndex; // the bombsite
StatusType m_status; // whether it is cleared or the bomb is there (planted)
};
class BotBombStatusMeme: public BotMeme
{
public:
BotBombStatusMeme(CSGameState::BombState state, const Vector &pos)
{
m_state = state;
m_pos = pos;
}
public:
virtual void Interpret(CCSBot *sender, CCSBot *receiver) const; // cause the given bot to act on this meme
private:
CSGameState::BombState m_state;
Vector m_pos;
};
class BotFollowMeme: public BotMeme
{
public:
virtual void Interpret(CCSBot *sender, CCSBot *receiver) const; // cause the given bot to act on this meme
};
class BotDefendHereMeme: public BotMeme
{
public:
BotDefendHereMeme(const Vector &pos)
{
m_pos = pos;
}
virtual void Interpret(CCSBot *sender, CCSBot *receiver) const; // cause the given bot to act on this meme
private:
Vector m_pos;
};
class BotWhereBombMeme: public BotMeme
{
public:
virtual void Interpret(CCSBot *sender, CCSBot *receiver) const; // cause the given bot to act on this meme
};
class BotRequestReportMeme: public BotMeme
{
public:
virtual void Interpret(CCSBot *sender, CCSBot *receiver) const; // cause the given bot to act on this meme
};
enum BotStatementType
{
REPORT_VISIBLE_ENEMIES,
REPORT_ENEMY_ACTION,
REPORT_MY_CURRENT_TASK,
REPORT_MY_INTENTION,
REPORT_CRITICAL_EVENT,
REPORT_REQUEST_HELP,
REPORT_REQUEST_INFORMATION,
REPORT_ROUND_END,
REPORT_MY_PLAN,
REPORT_INFORMATION,
REPORT_EMOTE,
REPORT_ACKNOWLEDGE, // affirmative or negative
REPORT_ENEMIES_REMAINING,
REPORT_FRIENDLY_FIRE,
REPORT_KILLED_FRIEND,
//REPORT_ENEMY_LOST
NUM_BOT_STATEMENT_TYPES,
};
// BotSpeakables are the smallest unit of bot chatter.
// They represent a specific wav file of a phrase, and the criteria for which it is useful
class BotSpeakable
{
public:
BotSpeakable();
~BotSpeakable();
char *m_phrase;
float m_duration;
PlaceCriteria m_place;
CountCriteria m_count;
};
typedef CUtlVector<BotSpeakable *> BotSpeakableVector;
typedef CUtlVector<BotSpeakableVector *> BotVoiceBankVector;
// The BotPhrase class is a collection of Speakables associated with a name, ID, and criteria
class BotPhrase
{
public:
char *GetSpeakable(int bankIndex, float *duration = NULL) const; // return a random speakable and its duration in seconds that meets the current criteria
// NOTE: Criteria must be set just before the GetSpeakable() call, since they are shared among all bots
void ClearCriteria() const;
void SetPlaceCriteria(PlaceCriteria place) const; // all returned phrases must have this place criteria
void SetCountCriteria(CountCriteria count) const; // all returned phrases must have this count criteria
const char *GetName() const { return m_name; }
Place GetID() const { return m_id; }
GameEventType GetRadioEquivalent() const { return m_radioEvent; }
bool IsImportant() const { return m_isImportant; } // return true if this phrase is part of an important statement
bool IsPlace() const { return m_isPlace; }
void Randomize(); // randomly shuffle the speakable order
private:
friend class BotPhraseManager;
BotPhrase(unsigned int id, bool isPlace);
~BotPhrase();
char *m_name;
Place m_id;
bool m_isPlace; // true if this is a Place phrase
GameEventType m_radioEvent;
bool m_isImportant; // mission-critical statement
mutable BotVoiceBankVector m_voiceBank; // array of voice banks (arrays of speakables)
CUtlVector<int> m_count; // number of speakables
mutable CUtlVector< int > m_index; // index of next speakable to return
int m_numVoiceBanks; // number of voice banks that have been initialized
void InitVoiceBank(int bankIndex); // sets up the vector of voice banks for the first bankIndex voice banks
mutable PlaceCriteria m_placeCriteria;
mutable CountCriteria m_countCriteria;
};
typedef CUtlLinkedList<BotPhrase *, int> BotPhraseList;
inline void BotPhrase::ClearCriteria() const
{
m_placeCriteria = ANY_PLACE;
m_countCriteria = UNDEFINED_COUNT;
}
inline void BotPhrase::SetPlaceCriteria(PlaceCriteria place) const
{
m_placeCriteria = place;
}
inline void BotPhrase::SetCountCriteria(CountCriteria count) const
{
m_countCriteria = count;
}
// The BotPhraseManager is a singleton that provides an interface to all BotPhrase collections
class BotPhraseManager
{
public:
BotPhraseManager();
~BotPhraseManager();
// initialize phrase system from database file for a specific voice bank (0 is the default voice bank)
bool Initialize(const char *filename, int bankIndex);
// invoked when round resets
void OnRoundRestart();
// invoked when map changes
void OnMapChange();
Place NameToID(const char *name) const;
const char *IDToName(Place id) const;
// given a name, return the associated phrase collection
const BotPhrase *GetPhrase(const char *name) const;
// given a name, return the associated Place phrase collection
const BotPhrase *GetPlace(const char *name) const;
// given an id, return the associated Place phrase collection
const BotPhrase *GetPlace(PlaceCriteria place) const;
const BotPhraseList *GetPlaceList() const { return &m_placeList; }
// return time last statement of given type was emitted by a teammate for the given place
float GetPlaceStatementInterval(Place place) const;
// set time of last statement of given type was emitted by a teammate for the given place
void ResetPlaceStatementInterval(Place place) const;
private:
int FindPlaceIndex(Place where) const;
// master list of all phrase collections
BotPhraseList m_list;
// master list of all Place phrases
BotPhraseList m_placeList;
struct PlaceTimeInfo
{
Place placeID;
IntervalTimer timer;
};
mutable PlaceTimeInfo m_placeStatementHistory[ MAX_PLACES_PER_MAP ];
mutable int m_placeCount;
};
inline int BotPhraseManager::FindPlaceIndex(Place where) const
{
for (int i = 0; i < m_placeCount; ++i)
{
if (m_placeStatementHistory[i].placeID == where)
return i;
}
if (m_placeCount < MAX_PLACES_PER_MAP)
{
m_placeStatementHistory[++m_placeCount].placeID = where;
m_placeStatementHistory[++m_placeCount].timer.Invalidate();
return m_placeCount - 1;
}
return -1;
}
inline float BotPhraseManager::GetPlaceStatementInterval(Place place) const
{
int index = FindPlaceIndex(place);
if (index < 0)
return 999999.9f;
if (index >= m_placeCount)
return 999999.9f;
return m_placeStatementHistory[ index ].timer.GetElapsedTime();
}
inline void BotPhraseManager::ResetPlaceStatementInterval(Place place) const
{
int index = FindPlaceIndex(place);
if (index < 0)
return;
if (index >= m_placeCount)
return;
m_placeStatementHistory[index].timer.Reset();
}
// Statements are meaningful collections of phrases
class BotStatement
{
public:
BotStatement(BotChatterInterface *chatter, BotStatementType type, float expireDuration);
~BotStatement();
public:
BotChatterInterface *GetChatter() const { return m_chatter; }
CCSBot *GetOwner() const;
BotStatementType GetType() const { return m_type; } // return the type of statement this is
bool IsImportant() const; // return true if this statement is "important" and not personality chatter
bool HasSubject() const { return (m_subject != UNDEFINED_SUBJECT); }
void SetSubject(int playerID) { m_subject = playerID; } // who this statement is about
int GetSubject() const { return m_subject; } // who this statement is about
bool HasPlace() const { return (GetPlace()) ? true : false; }
Place GetPlace() const; // if this statement refers to a specific place, return that place
void SetPlace(Place where) { m_place = where; } // explicitly set place
bool HasCount() const; // return true if this statement has an associated count
bool IsRedundant(const BotStatement *say) const; // return true if this statement is the same as the given one
bool IsObsolete() const; // return true if this statement is no longer appropriate to say
void Convert(const BotStatement *say); // possibly change what were going to say base on what teammate is saying
void AppendPhrase(const BotPhrase *phrase);
void SetStartTime(float timestamp) { m_startTime = timestamp; } // define the earliest time this statement can be spoken
float GetStartTime() const { return m_startTime; }
enum ConditionType
{
IS_IN_COMBAT,
RADIO_SILENCE,
ENEMIES_REMAINING,
NUM_CONDITIONS,
};
void AddCondition(ConditionType condition); // conditions must be true for the statement to be spoken
bool IsValid() const; // verify all attached conditions
enum ContextType
{
CURRENT_ENEMY_COUNT,
REMAINING_ENEMY_COUNT,
SHORT_DELAY,
LONG_DELAY,
ACCUMULATE_ENEMIES_DELAY,
};
void AppendPhrase(ContextType contextPhrase); // special phrases that depend on the context
bool Update(); // emit statement over time, return false if statement is done
bool IsSpeaking() const { return m_isSpeaking; } // return true if this statement is currently being spoken
float GetTimestamp() const { return m_timestamp; } // get time statement was created (but not necessarily started talking)
void AttachMeme(BotMeme *meme); // attach a meme to this statement, to be transmitted to other friendly bots when spoken
public:
friend class BotChatterInterface;
BotChatterInterface *m_chatter; // the chatter system this statement is part of
BotStatement *m_next, *m_prev; // linked list hooks
BotStatementType m_type; // what kind of statement this is
int m_subject; // who this subject is about
Place m_place; // explicit place - note some phrases have implicit places as well
BotMeme *m_meme; // a statement can only have a single meme for now
float m_timestamp; // time when message was created
float m_startTime; // the earliest time this statement can be spoken
float m_expireTime; // time when this statement is no longer valid
float m_speakTimestamp; // time when message began being spoken
bool m_isSpeaking; // true if this statement is current being spoken
float m_nextTime; // time for next phrase to begin
enum { MAX_BOT_PHRASES = 4 };
struct
{
bool isPhrase;
union
{
const BotPhrase *phrase;
ContextType context;
};
}
m_statement[ MAX_BOT_PHRASES ];
enum { MAX_BOT_CONDITIONS = 4 };
ConditionType m_condition[ MAX_BOT_CONDITIONS ]; // conditions that must be true for the statement to be said
int m_conditionCount;
int m_index; // m_index refers to the phrase currently being spoken, or -1 if we havent started yet
int m_count;
};
// This class defines the interface to the bot radio chatter system
class BotChatterInterface
{
public:
BotChatterInterface() {};
BotChatterInterface(CCSBot *me);
~BotChatterInterface();
void Reset(); // reset to initial state
void Update(); // process ongoing chatter
void OnEvent(GameEventType event, CBaseEntity *entity, CBaseEntity *other); // invoked when event occurs in the game (some events have NULL entities)
void OnDeath(); // invoked when we die
enum VerbosityType
{
NORMAL, // full chatter
MINIMAL, // only scenario-critical events
RADIO, // use the standard radio instead
OFF // no chatter at all
};
VerbosityType GetVerbosity() const; // return our current level of verbosity
CCSBot *GetOwner() const { return m_me; }
bool IsTalking() const; // return true if we are currently talking
float GetRadioSilenceDuration(); // return time since any teammate said anything
void ResetRadioSilenceDuration();
enum { MUST_ADD = 1 };
void AddStatement(BotStatement *statement, bool mustAdd = false); // register a statement for speaking
void RemoveStatement(BotStatement *statement); // remove a statement
BotStatement *GetActiveStatement(); // returns the statement that is being spoken, or is next to be spoken if no-one is speaking now
BotStatement *GetStatement() const; // returns our current statement, or NULL if we aren't speaking
int GetPitch() const { return m_pitch; }
// things the bots can say
void Say(const char *phraseName, float lifetime = 3.0f, float delay = 0.0f);
void AnnouncePlan(const char *phraseName, Place place);
void Affirmative();
void Negative();
void EnemySpotted(); // report enemy sightings
void KilledMyEnemy(int victimID);
void EnemiesRemaining();
NOXREF void Clear(Place place);
void ReportIn(); // ask for current situation
void ReportingIn(); // report current situation
bool NeedBackup();
void PinnedDown();
void Scared();
void HeardNoise(const Vector *pos);
void TheyPickedUpTheBomb();
void GoingToPlantTheBomb(Place place);
void BombsiteClear(int zoneIndex);
void FoundPlantedBomb(int zoneIndex);
void PlantingTheBomb(Place place);
void SpottedBomber(CBasePlayer *bomber);
void SpottedLooseBomb(CBaseEntity *bomb);
NOXREF void GuardingLooseBomb(CBaseEntity *bomb);
void RequestBombLocation();
#define IS_PLAN true
void GuardingHostages(Place place, bool isPlan = false);
void GuardingHostageEscapeZone(bool isPlan = false);
void HostagesBeingTaken();
void HostagesTaken();
void TalkingToHostages();
void EscortingHostages();
NOXREF void HostageDown();
void CelebrateWin();
void Encourage(const char *phraseName, float repeatInterval = 10.0f, float lifetime = 3.0f); // "encourage" the player to do the scenario
void KilledFriend();
void FriendlyFire();
bool SeesAtLeastOneEnemy() const { return m_seeAtLeastOneEnemy; }
private:
BotStatement *m_statementList; // list of all active/pending messages for this bot
void ReportEnemies(); // track nearby enemy count and generate enemy activity statements
bool ShouldSpeak() const; // return true if we speaking makes sense now
CCSBot *m_me; // the bot this chatter is for
bool m_seeAtLeastOneEnemy;
float m_timeWhenSawFirstEnemy;
bool m_reportedEnemies;
bool m_requestedBombLocation; // true if we already asked where the bomb has been planted
int m_pitch;
static IntervalTimer m_radioSilenceInterval[2]; // one timer for each team
IntervalTimer m_needBackupInterval;
IntervalTimer m_spottedBomberInterval;
IntervalTimer m_scaredInterval;
IntervalTimer m_planInterval;
CountdownTimer m_spottedLooseBombTimer;
CountdownTimer m_heardNoiseTimer;
CountdownTimer m_escortingHostageTimer;
static CountdownTimer m_encourageTimer; // timer to know when we can "encourage" the human player again - shared by all bots
};
inline BotChatterInterface::VerbosityType BotChatterInterface::GetVerbosity() const
{
const char *string = cv_bot_chatter.string;
if (string == NULL)
return NORMAL;
if (string[0] == 'm' || string[0] == 'M')
return MINIMAL;
if (string[0] == 'r' || string[0] == 'R')
return RADIO;
if (string[0] == 'o' || string[0] == 'O')
return OFF;
return NORMAL;
}
inline bool BotChatterInterface::IsTalking() const
{
if (m_statementList != NULL)
{
return m_statementList->IsSpeaking();
}
return false;
}
inline BotStatement *BotChatterInterface::GetStatement() const
{
return m_statementList;
}
extern BotPhraseManager *TheBotPhrases;
inline void BotChatterInterface::Say(const char *phraseName, float lifetime, float delay)
{
BotStatement *say = new BotStatement(this, REPORT_MY_INTENTION, lifetime);
say->AppendPhrase(TheBotPhrases->GetPhrase(phraseName));
if (delay > 0.0f)
say->SetStartTime(gpGlobals->time + delay);
AddStatement(say);
}
const Vector *GetRandomSpotAtPlace(Place place);
#endif // CS_BOT_CHATTER_H

388
dlls/bot/cs_bot_event.cpp Normal file
View File

@ -0,0 +1,388 @@
#include "bot_common.h"
void CCSBot::OnEvent(GameEventType event, CBaseEntity *entity, CBaseEntity *other)
{
#if 0
GetGameState()->OnEvent(event, entity, other);
GetChatter()->OnEvent(event, entity, other);
// Morale adjustments happen even for dead players
/* switch (event)
{
case EVENT_TERRORISTS_WIN:
if (m_iTeam == CT)
{
DecreaseMorale();
}
else
{
IncreaseMorale();
}
break;
case EVENT_CTS_WIN:
if (m_iTeam == CT)
{
IncreaseMorale();
}
else
{
DecreaseMorale();
}
break;
}*/
if (!IsAlive())
return;
CBasePlayer *player = static_cast<CBasePlayer *>(entity);
// If we just saw a nearby friend die, and we haven't yet acquired an enemy
// automatically acquire our dead friend's killer
if (!IsAttacking() && (GetDisposition() == ENGAGE_AND_INVESTIGATE || GetDisposition() == OPPORTUNITY_FIRE))
{
if (event == EVENT_PLAYER_DIED)
{
if (player->m_iTeam == m_iTeam)
{
CBasePlayer *killer = static_cast<CBasePlayer *>(other);
// check that attacker is an enemy (for friendly fire, etc)
if (killer && killer->IsPlayer())
{
// check if we saw our friend die - dont check FOV - assume we're aware of our surroundings in combat
// snipers stay put
if (!IsSniper() && IsVisible(&player->pev->origin))
{
// people are dying - we should hurry
Hurry(RANDOM_FLOAT(10.0f, 15.0f));
// if we're hiding with only our knife, be a little more cautious
const float knifeAmbushChance = 50.0f;
if (!IsHiding() || !IsUsingKnife() || RANDOM_FLOAT(0, 100) < knifeAmbushChance)
{
PrintIfWatched("Attacking our friend's killer!\n");
Attack(killer);
return;
}
}
}
}
}
}
switch (event)
{
case EVENT_PLAYER_DIED:
{
CBasePlayer *victim = player;
CBasePlayer *killer = (other && other->IsPlayer()) ? static_cast<CBasePlayer *>(other) : NULL;
// if the human player died in the single player game, tell the team
if (g_pGameRules->IsCareer() && !victim->IsBot() && victim->m_iTeam == m_iTeam)
{
GetChatter()->Say("CommanderDown", 20.0f);
}
// keep track of the last player we killed
if (killer == this)
{
m_lastVictimID = victim->entindex();
}
// react to teammate death
if (victim->m_iTeam == m_iTeam)
{
// chastise friendly fire from humans
if (killer != NULL && !killer->IsBot() && killer->m_iTeam == m_iTeam && killer != this)
{
GetChatter()->KilledFriend();
}
if (IsHunting())
{
PrintIfWatched("Rethinking hunt due to teammate death\n");
Idle();
return;
}
if (IsAttacking())
{
if (GetTimeSinceLastSawEnemy() > 0.4f)
{
PrintIfWatched("Rethinking my attack due to teammate death\n");
// allow us to sneak past windows, doors, etc
IgnoreEnemies(1.0f);
// move to last known position of enemy - this could cause us to flank if
// the danger has changed due to our teammate's recent death
SetTask(MOVE_TO_LAST_KNOWN_ENEMY_POSITION, GetEnemy());
MoveTo(&GetLastKnownEnemyPosition());
return;
}
}
}
// an enemy was killed
else
{
if (killer != NULL && killer->m_iTeam == m_iTeam)
{
// only chatter about enemy kills if we see them occur, and they were the last one we see
if (GetNearbyEnemyCount() <= 1)
{
// report if number of enemies left is few and we killed the last one we saw locally
GetChatter()->EnemiesRemaining();
if (IsVisible(&victim->pev->origin, CHECK_FOV))
{
// congratulate teammates on their kills
if (killer != NULL && killer != this)
{
float delay = RANDOM_FLOAT(2.0f, 3.0f);
if (killer->IsBot())
{
if (RANDOM_FLOAT(0.0f, 100.0f) < 40.0f)
GetChatter()->Say("NiceShot", 3.0f, delay);
}
else
{
// humans get the honorific
if (g_pGameRules->IsCareer())
GetChatter()->Say("NiceShotCommander", 3.0f, delay);
else
GetChatter()->Say("NiceShotSir", 3.0f, delay);
}
}
}
}
}
}
return;
}
case EVENT_TERRORISTS_WIN:
if (m_iTeam == TERRORIST)
GetChatter()->CelebrateWin();
return;
case EVENT_CTS_WIN:
if (m_iTeam == CT)
GetChatter()->CelebrateWin();
return;
case EVENT_BOMB_DEFUSED:
if (m_iTeam == CT && TheCSBots()->GetBombTimeLeft() < 2.0)
GetChatter()->Say("BarelyDefused");
return;
case EVENT_BOMB_PICKED_UP:
{
if (m_iTeam == CT && player != NULL)
{
// check if we're close enough to hear it
const float bombPickupHearRangeSq = 1000.0f * 1000.0f;
if ((pev->origin - player->pev->origin).LengthSquared() < bombPickupHearRangeSq)
{
GetChatter()->TheyPickedUpTheBomb();
}
}
return;
}
case EVENT_BOMB_BEEP:
{
// if we don't know where the bomb is, but heard it beep, we've discovered it
if (GetGameState()->IsPlantedBombLocationKnown() == false)
{
// check if we're close enough to hear it
const float bombBeepHearRangeSq = 1000.0f * 1000.0f;
if ((pev->origin - entity->pev->origin).LengthSquared() < bombBeepHearRangeSq)
{
// radio the news to our team
if (m_iTeam == CT && GetGameState()->GetPlantedBombsite() == CSGameState::UNKNOWN)
{
const CCSBotManager::Zone *zone = TheCSBots()->GetZone(&entity->pev->origin);
if (zone != NULL)
GetChatter()->FoundPlantedBomb(zone->m_index);
}
// remember where the bomb is
GetGameState()->UpdatePlantedBomb(&entity->pev->origin);
}
}
return;
}
case EVENT_BOMB_PLANTED:
{
// if we're a CT, forget what we're doing and go after the bomb
if (m_iTeam == CT)
{
Idle();
}
// if we are following someone, stop following
if (IsFollowing())
{
StopFollowing();
Idle();
}
OnEvent(EVENT_BOMB_BEEP, other);
return;
}
case EVENT_BOMB_DEFUSE_ABORTED:
PrintIfWatched("BOMB DEFUSE ABORTED\n");
return;
case EVENT_WEAPON_FIRED:
case EVENT_WEAPON_FIRED_ON_EMPTY:
case EVENT_WEAPON_RELOADED:
{
if (m_enemy == entity && IsUsingKnife())
ForceRun(5.0f);
break;
}
default:
break;
}
// Process radio events from our team
if (player != NULL && player->m_iTeam == m_iTeam && event > EVENT_START_RADIO_1 && event < EVENT_END_RADIO)
{
// TODO: Distinguish between radio commands and responses
if (event != EVENT_RADIO_AFFIRMATIVE && event != EVENT_RADIO_NEGATIVE && event != EVENT_RADIO_REPORTING_IN)
{
m_lastRadioCommand = event;
m_lastRadioRecievedTimestamp = gpGlobals->time;
m_radioSubject = player;
m_radioPosition = player->pev->origin;
}
}
// player_follows needs a player
if (player == NULL)
return;
if (!IsRogue() && event == EVENT_HOSTAGE_CALLED_FOR_HELP && m_iTeam == CT && IsHunting())
{
if ((entity->pev->origin - pev->origin).IsLengthGreaterThan(1000.0f))
return;
Vector v = entity->Center();
if (IsVisible(&v))
{
m_task = COLLECT_HOSTAGES;
m_taskEntity = NULL;
Run();
m_goalEntity = entity;
MoveTo(&entity->pev->origin, (RouteType)(m_hostageEscortCount == 0));
PrintIfWatched("I'm fetching a hostage that called out to me\n");
return;
}
}
// don't pay attention to noise that friends make
if (!IsEnemy(player))
return;
float range;
PriorityType priority;
bool isHostile;
if (IsGameEventAudible(event, entity, other, &range, &priority, &isHostile) == false)
return;
if (event == EVENT_HOSTAGE_USED)
{
if (m_iTeam == CT)
return;
if ((entity->pev->origin - pev->origin).IsLengthGreaterThan(range))
return;
GetChatter()->HostagesBeingTaken();
if (!GetGameState()->GetNearestVisibleFreeHostage() && m_task != GUARD_HOSTAGE_RESCUE_ZONE && GuardRandomZone())
{
m_task = GUARD_HOSTAGE_RESCUE_ZONE;
m_taskEntity = NULL;
SetDisposition(OPPORTUNITY_FIRE);
PrintIfWatched("Trying to beat them to an escape zone!\n");
}
}
// check if noise is close enough for us to hear
const Vector *newNoisePosition = &player->pev->origin;
float newNoiseDist = (pev->origin - *newNoisePosition).Length();
if (newNoiseDist < range)
{
// we heard the sound
if ((IsLocalPlayerWatchingMe() && cv_bot_debug.value == 3.0f) || cv_bot_debug.value == 4.0f)
{
PrintIfWatched("Heard noise (%s from %s, pri %s, time %3.1f)\n",
(event == EVENT_WEAPON_FIRED) ? "Weapon fire " : "",
STRING(player->pev->netname),
(priority == PRIORITY_HIGH) ? "HIGH" : ((priority == PRIORITY_MEDIUM) ? "MEDIUM" : "LOW"),
gpGlobals->time);
}
if (event == EVENT_PLAYER_FOOTSTEP && IsUsingSniperRifle() && newNoiseDist < 300.0)
EquipPistol();
// should we pay attention to it
// if noise timestamp is zero, there is no prior noise
if (m_noiseTimestamp > 0.0f)
{
// only overwrite recent sound if we are louder (closer), or more important - if old noise was long ago, its faded
const float shortTermMemoryTime = 3.0f;
if (gpGlobals->time - m_noiseTimestamp < shortTermMemoryTime)
{
// prior noise is more important - ignore new one
if (priority < m_noisePriority)
return;
float oldNoiseDist = (pev->origin - m_noisePosition).Length();
if (newNoiseDist >= oldNoiseDist)
return;
}
}
// find the area in which the noise occured
// TODO: Better handle when noise occurs off the nav mesh
// TODO: Make sure noise area is not through a wall or ceiling from source of noise
// TODO: Change GetNavTravelTime to better deal with NULL destination areas
CNavArea *noiseArea = TheNavAreaGrid.GetNavArea(newNoisePosition);
if (noiseArea == NULL)
noiseArea = TheNavAreaGrid.GetNearestNavArea(newNoisePosition);
if (noiseArea == NULL)
{
PrintIfWatched(" *** Noise occurred off the nav mesh - ignoring!\n");
return;
}
m_noiseArea = noiseArea;
// remember noise priority
m_noisePriority = priority;
// randomize noise position in the area a bit - hearing isn't very accurate
// the closer the noise is, the more accurate our placement
// TODO: Make sure not to pick a position on the opposite side of ourselves.
const float maxErrorRadius = 400.0f;
const float maxHearingRange = 2000.0f;
float errorRadius = maxErrorRadius * newNoiseDist / maxHearingRange;
m_noisePosition.x = newNoisePosition->x + RANDOM_FLOAT(-errorRadius, errorRadius);
m_noisePosition.y = newNoisePosition->y + RANDOM_FLOAT(-errorRadius, errorRadius);
// make sure noise position remains in the same area
m_noiseArea->GetClosestPointOnArea(&m_noisePosition, &m_noisePosition);
m_isNoiseTravelRangeChecked = false;
// note when we heard the noise
m_noiseTimestamp = gpGlobals->time;
}
#endif
}

318
dlls/bot/cs_bot_init.cpp Normal file
View File

@ -0,0 +1,318 @@
#include "bot_common.h"
/*
* Globals initialization
*/
cvar_t cv_bot_traceview = { "bot_traceview", "0", FCVAR_SERVER, 0.0f, NULL };
cvar_t cv_bot_stop = { "bot_stop", "0", FCVAR_SERVER, 0.0f, NULL };
cvar_t cv_bot_show_nav = { "bot_show_nav", "0", FCVAR_SERVER, 0.0f, NULL };
cvar_t cv_bot_show_danger = { "bot_show_danger", "0", FCVAR_SERVER, 0.0f, NULL };
cvar_t cv_bot_nav_edit = { "bot_nav_edit", "0", FCVAR_SERVER, 0.0f, NULL };
cvar_t cv_bot_nav_zdraw = { "bot_nav_zdraw", "4", FCVAR_SERVER, 0.0f, NULL };
cvar_t cv_bot_walk = { "bot_walk", "0", FCVAR_SERVER, 0.0f, NULL };
cvar_t cv_bot_difficulty = { "bot_difficulty", "0", FCVAR_SERVER, 0.0f, NULL };
cvar_t cv_bot_debug = { "bot_debug", "0", FCVAR_SERVER, 0.0f, NULL };
cvar_t cv_bot_quicksave = { "bot_quicksave", "0", FCVAR_SERVER, 0.0f, NULL };
cvar_t cv_bot_quota = { "bot_quota", "0", FCVAR_SERVER, 0.0f, NULL };
cvar_t cv_bot_quota_match = { "bot_quota_match", "0", FCVAR_SERVER, 0.0f, NULL };
cvar_t cv_bot_prefix = { "bot_prefix", "", FCVAR_SERVER, 0.0f, NULL };
cvar_t cv_bot_allow_rogues = { "bot_allow_rogues", "1", FCVAR_SERVER, 0.0f, NULL };
cvar_t cv_bot_allow_pistols = { "bot_allow_pistols", "1", FCVAR_SERVER, 0.0f, NULL };
cvar_t cv_bot_allow_shotguns = { "bot_allow_shotguns", "1", FCVAR_SERVER, 0.0f, NULL };
cvar_t cv_bot_allow_sub_machine_guns = { "bot_allow_sub_machine_guns", "1", FCVAR_SERVER, 0.0f, NULL };
cvar_t cv_bot_allow_rifles = { "bot_allow_rifles", "1", FCVAR_SERVER, 0.0f, NULL };
cvar_t cv_bot_allow_machine_guns = { "bot_allow_machine_guns", "1", FCVAR_SERVER, 0.0f, NULL };
cvar_t cv_bot_allow_grenades = { "bot_allow_grenades", "1", FCVAR_SERVER, 0.0f, NULL };
cvar_t cv_bot_allow_snipers = { "bot_allow_snipers", "1", FCVAR_SERVER, 0.0f, NULL };
cvar_t cv_bot_allow_shield = { "bot_allow_shield", "1", FCVAR_SERVER, 0.0f, NULL };
cvar_t cv_bot_join_team = { "bot_join_team", "any", FCVAR_SERVER, 0.0f, NULL };
cvar_t cv_bot_join_after_player = { "bot_join_after_player", "1", FCVAR_SERVER, 0.0f, NULL };
cvar_t cv_bot_auto_vacate = { "bot_auto_vacate", "1", FCVAR_SERVER, 0.0f, NULL };
cvar_t cv_bot_zombie = { "bot_zombie", "0", FCVAR_SERVER, 0.0f, NULL };
cvar_t cv_bot_defer_to_human = { "bot_defer_to_human", "0", FCVAR_SERVER, 0.0f, NULL };
cvar_t cv_bot_chatter = { "bot_chatter", "normal", FCVAR_SERVER, 0.0f, NULL };
cvar_t cv_bot_profile_db = { "bot_profile_db", "BotProfile.db", FCVAR_SERVER, 0.0f, NULL };
void InstallBotControl()
{
if (TheBots != NULL)
{
delete TheBots;
TheBots = NULL;
}
TheBots = new CCSBotManager;
}
// Engine callback for custom server commands
void Bot_ServerCommand()
{
if (TheBots != NULL)
{
const char *pcmd = CMD_ARGV(0);
TheBots->ServerCommand(pcmd);
}
}
void Bot_RegisterCvars()
{
if (true)
{
CVAR_REGISTER (&cv_bot_traceview);
CVAR_REGISTER (&cv_bot_stop);
CVAR_REGISTER (&cv_bot_show_nav);
CVAR_REGISTER (&cv_bot_show_danger);
CVAR_REGISTER (&cv_bot_nav_edit);
CVAR_REGISTER (&cv_bot_nav_zdraw);
CVAR_REGISTER (&cv_bot_walk);
CVAR_REGISTER (&cv_bot_difficulty);
CVAR_REGISTER (&cv_bot_debug);
CVAR_REGISTER (&cv_bot_quicksave);
CVAR_REGISTER (&cv_bot_quota);
CVAR_REGISTER (&cv_bot_quota_match);
CVAR_REGISTER (&cv_bot_prefix);
CVAR_REGISTER (&cv_bot_allow_rogues);
CVAR_REGISTER (&cv_bot_allow_pistols);
CVAR_REGISTER (&cv_bot_allow_shotguns);
CVAR_REGISTER (&cv_bot_allow_sub_machine_guns);
CVAR_REGISTER (&cv_bot_allow_rifles);
CVAR_REGISTER (&cv_bot_allow_machine_guns);
CVAR_REGISTER (&cv_bot_allow_grenades);
CVAR_REGISTER (&cv_bot_allow_snipers);
CVAR_REGISTER (&cv_bot_allow_shield);
CVAR_REGISTER (&cv_bot_join_team);
CVAR_REGISTER (&cv_bot_join_after_player);
CVAR_REGISTER (&cv_bot_auto_vacate);
CVAR_REGISTER (&cv_bot_zombie);
CVAR_REGISTER (&cv_bot_defer_to_human);
CVAR_REGISTER (&cv_bot_chatter);
CVAR_REGISTER (&cv_bot_profile_db);
}
}
// Constructor
CCSBot::CCSBot() : m_chatter(this), m_gameState(this)
{
;
}
// Prepare bot for action
bool CCSBot::Initialize(const BotProfile *profile)
{
// extend
CBot::Initialize(profile);
// CS bot initialization
m_diedLastRound = false;
m_morale = POSITIVE; // starting a new round makes everyone a little happy
m_combatRange = RANDOM_FLOAT(325, 425);
m_navNodeList = NULL;
m_currentNode = NULL;
// set initial safe time guess for this map
m_safeTime = 15.0f + 5.0f * GetProfile()->GetAggression();
m_name[0] = '\0';
ResetValues();
StartNormalProcess();
return true;
}
// Reset internal data to initial state
void CCSBot::ResetValues()
{
m_chatter.Reset();
m_gameState.Reset();
m_avoid = NULL;
m_avoidTimestamp = 0.0f;
m_hurryTimer.Invalidate();
m_isStuck = false;
m_stuckTimestamp = 0.0f;
m_wiggleTimestamp = 0.0f;
m_stuckJumpTimestamp = 0.0f;
m_pathLength = 0;
m_pathIndex = 0;
m_areaEnteredTimestamp = 0.0f;
m_currentArea = NULL;
m_lastKnownArea = NULL;
m_avoidFriendTimer.Invalidate();
m_isFriendInTheWay = false;
m_isWaitingBehindFriend = false;
m_disposition = ENGAGE_AND_INVESTIGATE;
m_enemy = NULL;
m_isWaitingToTossGrenade = false;
m_wasSafe = true;
m_nearbyEnemyCount = 0;
m_enemyPlace = 0;
m_nearbyFriendCount = 0;
m_closestVisibleFriend = NULL;
m_closestVisibleHumanFriend = NULL;
for (int w = 0; w < ARRAYSIZE(m_watchInfo); ++w)
{
m_watchInfo[w].timestamp = 0.0f;
m_watchInfo[w].isEnemy = false;
}
m_isEnemyVisible = false;
m_visibleEnemyParts = NONE;
m_lastSawEnemyTimestamp = 0.0f;
m_firstSawEnemyTimestamp = 0.0f;
m_currentEnemyAcquireTimestamp = 0.0f;
m_isLastEnemyDead = true;
m_attacker = NULL;
m_attackedTimestamp = 0.0f;
m_enemyDeathTimestamp = 0.0f;
m_lastVictimID = 0;
m_isAimingAtEnemy = false;
m_fireWeaponTimestamp = 0.0f;
m_equipTimer.Invalidate();
m_isFollowing = false;
m_leader = NULL;
m_followTimestamp = 0.0f;
m_allowAutoFollowTime = 0.0f;
m_enemyQueueIndex = 0;
m_enemyQueueCount = 0;
m_enemyQueueAttendIndex = 0;
m_bomber = NULL;
m_lookAroundStateTimestamp = 0.0f;
m_inhibitLookAroundTimestamp = 0.0f;
m_lookPitch = 0.0f;
m_lookPitchVel = 0.0f;
m_lookYaw = 0.0f;
m_lookYawVel = 0.0f;
m_aimOffsetTimestamp = 0.0f;
m_aimSpreadTimestamp = 0.0f;
m_lookAtSpotState = NOT_LOOKING_AT_SPOT;
m_spotEncounter = NULL;
m_spotCheckTimestamp = 0.0f;
m_peripheralTimestamp = 0.0f;
m_avgVelIndex = 0;
m_avgVelCount = 0;
m_lastOrigin = (pev != NULL) ? pev->origin : Vector(0, 0, 0);
m_lastRadioCommand = EVENT_INVALID;
m_lastRadioRecievedTimestamp = 0.0f;
m_lastRadioSentTimestamp = 0.0f;
m_radioSubject = NULL;
m_voiceFeedbackEndTimestamp = 0.0f;
m_hostageEscortCount = 0;
m_hostageEscortCountTimestamp = 0.0f;
m_noisePosition = Vector(0, 0, 0);
m_noiseTimestamp = 0.0f;
m_noiseCheckTimestamp = 0.0f;
m_isNoiseTravelRangeChecked = false;
m_stateTimestamp = 0.0f;
m_task = SEEK_AND_DESTROY;
m_taskEntity = NULL;
m_approachPointCount = 0;
m_approachPointViewPosition = Vector(0, 0, 0);
m_checkedHidingSpotCount = 0;
m_isJumpCrouching = false;
StandUp();
Run();
m_mustRunTimer.Invalidate();
m_repathTimer.Invalidate();
m_pathLadder = NULL;
m_huntState.ClearHuntArea();
// adjust morale - if we died, our morale decreased,
// but if we live, no adjustement (round win/loss also adjusts morale
if (m_diedLastRound)
DecreaseMorale();
m_diedLastRound = false;
// IsRogue() randomly changes this
m_isRogue = false;
m_surpriseDelay = 0.0f;
m_surpriseTimestamp = 0.0f;
// even though these are EHANDLEs, they need to be NULL-ed
m_goalEntity = NULL;
m_avoid = NULL;
m_enemy = NULL;
for (int i = 0; i < MAX_ENEMY_QUEUE; ++i)
{
m_enemyQueue[i].player = NULL;
m_enemyQueue[i].isReloading = false;
m_enemyQueue[i].isProtectedByShield = false;
}
// start in idle state
StopAttacking();
Idle();
}
// Called when bot is placed in map, and when bots are reset after a round ends.
// NOTE: For some reason, this can be called twice when a bot is added.
void CCSBot::SpawnBot()
{
CCSBotManager *ctrl = TheCSBots();
ctrl->ValidateMapData();
ResetValues();
Q_strcpy(m_name, STRING(pev->netname));
SetState(&m_huntState);
SetTouch(&CCSBot::BotTouch);
if (!TheNavAreaList.Count () && !ctrl->IsLearningMap())
{
ctrl->SetLearningMapFlag();
StartLearnProcess();
}
}
void CCSBot::RoundRespawn()
{
// do the normal player spawn process
//CBasePlayer::RoundRespawn();
// EndVoiceFeedback();
}
void CCSBot::Disconnect()
{
// EndVoiceFeedback();
if (m_processMode != PROCESS_NORMAL)
{
hideProgressMeter();
}
}

480
dlls/bot/cs_bot_learn.cpp Normal file
View File

@ -0,0 +1,480 @@
#include "bot_common.h"
const float updateTimesliceDuration = 0.5f;
int _navAreaCount = 0;
int _currentIndex = 0;
inline CNavNode *LadderEndSearch(CBaseEntity *entity, const Vector *pos, NavDirType mountDir)
{
Vector center = *pos;
AddDirectionVector(&center, mountDir, HalfHumanWidth);
// Test the ladder dismount point first, then each cardinal direction one and two steps away
for (int d = (-1); d < 2 * NUM_DIRECTIONS; ++d)
{
Vector tryPos = center;
if (d >= NUM_DIRECTIONS)
AddDirectionVector(&tryPos, (NavDirType)(d - NUM_DIRECTIONS), GenerationStepSize * 2.0f);
else if (d >= 0)
AddDirectionVector(&tryPos, (NavDirType)d, GenerationStepSize);
// step up a rung, to ensure adjacent floors are below us
tryPos.z += GenerationStepSize;
SnapToGrid(&tryPos);
// adjust height to account for sloping areas
Vector tryNormal;
if (GetGroundHeight(&tryPos, &tryPos.z, &tryNormal) == false)
continue;
// make sure this point is not on the other side of a wall
const float fudge = 2.0f;
TraceResult result;
UTIL_TraceLine(center + Vector(0, 0, fudge), tryPos + Vector(0, 0, fudge), ignore_monsters, dont_ignore_glass, ENT(entity->pev), &result);
if (result.flFraction != 1.0f || result.fStartSolid)
continue;
// if no node exists here, create one and continue the search
if (CNavNode::GetNode(&tryPos) == NULL)
{
return new CNavNode(&tryPos, &tryNormal, NULL);
}
}
return NULL;
}
CNavNode *CCSBot::AddNode(const Vector *destPos, const Vector *normal, NavDirType dir, CNavNode *source)
{
// check if a node exists at this location
CNavNode *node = const_cast<CNavNode *>(CNavNode::GetNode(destPos));
// if no node exists, create one
bool useNew = false;
if (node == NULL)
{
node = new CNavNode(destPos, normal, source);
useNew = true;
}
// connect source node to new node
source->ConnectTo(node, dir);
// optimization: if deltaZ changes very little, assume connection is commutative
const float zTolerance = 10.0f; // 50.0f;
if (fabs(source->GetPosition()->z - destPos->z) < zTolerance)
{
node->ConnectTo(source, OppositeDirection(dir));
node->MarkAsVisited(OppositeDirection(dir));
}
if (useNew)
{
// new node becomes current node
m_currentNode = node;
}
Vector ceiling;
Vector floor;
TraceResult result;
bool crouch = false;
const float epsilon = 0.1f;
for (float y = -16.0f; y <= 16.0f + epsilon; y += 16.0f)
{
for (float x = -16.0f; x <= 16.0f + epsilon; x += 16.0f)
{
floor = *destPos + Vector(x, y, 5.0f);
ceiling = *destPos + Vector(x, y, 72.0f - epsilon);
UTIL_TraceLine(floor, ceiling, ignore_monsters, dont_ignore_glass, ENT(pev), &result);
if (result.flFraction != 1.0f)
{
crouch = true;
break;
}
}
if (crouch)
{
node->SetAttributes(NAV_CROUCH);
break;
}
}
return node;
}
void drawProgressMeter(float progress, char *title)
{
#if 0
MESSAGE_BEGIN(MSG_ALL, gmsgBotProgress);
WRITE_BYTE(FLAG_PROGRESS_DRAW);
WRITE_BYTE((int)progress);
WRITE_STRING(title);
MESSAGE_END();
#endif
}
void startProgressMeter(const char *title)
{
#if 0
MESSAGE_BEGIN(MSG_ALL, gmsgBotProgress);
WRITE_BYTE(FLAG_PROGRESS_START);
WRITE_STRING(title);
MESSAGE_END();
#endif
}
void hideProgressMeter()
{
#if 0
MESSAGE_BEGIN(MSG_ALL, gmsgBotProgress);
WRITE_BYTE(FLAG_PROGRESS_HIDE);
MESSAGE_END();
#endif
}
void CCSBot::StartLearnProcess()
{
startProgressMeter("Analyzing map geometry...");
drawProgressMeter(0, "Analyzing map geometry...");
BuildLadders();
Vector normal;
Vector pos = pev->origin;
SnapToGrid(&pos.x);
SnapToGrid(&pos.y);
if (!GetGroundHeight(&pos, &pos.z, &normal))
{
CONSOLE_ECHO("ERROR: Start position invalid\n\n");
m_processMode = PROCESS_NORMAL;
return;
}
m_currentNode = new CNavNode(&pos, &normal);
m_goalPosition = pev->origin;
m_processMode = PROCESS_LEARN;
}
// Search the world and build a map of possible movements.
// The algorithm begins at the bot's current location, and does a recursive search
// outwards, tracking all valid steps and generating a directed graph of CNavNodes.
// Sample the map one "step" in a cardinal direction to learn the map.
// Returns true if sampling needs to continue, or false if done.
bool CCSBot::LearnStep()
{
// take a step
while (true)
{
if (m_currentNode == NULL)
{
// search is exhausted - continue search from ends of ladders
FOR_EACH_LL (TheNavLadderList, it)
{
CNavLadder *ladder = TheNavLadderList[it];
// check ladder bottom
if ((m_currentNode = LadderEndSearch(ladder->m_entity, &ladder->m_bottom, ladder->m_dir)) != 0)
break;
// check ladder top
if ((m_currentNode = LadderEndSearch(ladder->m_entity, &ladder->m_top, ladder->m_dir)) != 0)
break;
}
if (m_currentNode == NULL)
{
// all seeds exhausted, sampling complete
GenerateNavigationAreaMesh();
return false;
}
}
// Take a step from this node
for (int dir = NORTH; dir < NUM_DIRECTIONS; dir++)
{
if (!m_currentNode->HasVisited((NavDirType)dir))
{
float feetOffset = pev->origin.z - GetFeetZ();
// start at current node position
Vector pos = *m_currentNode->GetPosition();
// snap to grid
int cx = SnapToGrid(pos.x);
int cy = SnapToGrid(pos.y);
// attempt to move to adjacent node
switch (dir)
{
case NORTH: cy -= GenerationStepSize; break;
case SOUTH: cy += GenerationStepSize; break;
case EAST: cx += GenerationStepSize; break;
case WEST: cx -= GenerationStepSize; break;
}
pos.x = cx;
pos.y = cy;
m_generationDir = (NavDirType)dir;
// mark direction as visited
m_currentNode->MarkAsVisited(m_generationDir);
// test if we can move to new position
TraceResult result;
Vector from, to;
// modify position to account for change in ground level during step
to.x = pos.x;
to.y = pos.y;
Vector toNormal;
if (GetGroundHeight(&pos, &to.z, &toNormal) == false)
{
return true;
}
from = *m_currentNode->GetPosition();
Vector fromOrigin = from + Vector(0, 0, feetOffset);
Vector toOrigin = to + Vector(0, 0, feetOffset);
UTIL_SetOrigin(pev, toOrigin);
UTIL_TraceLine(fromOrigin, toOrigin, ignore_monsters, dont_ignore_glass, ENT(pev), &result);
bool walkable;
if (result.flFraction == 1.0f && !result.fStartSolid)
{
// the trace didnt hit anything - clear
float toGround = to.z;
float fromGround = from.z;
float epsilon = 0.1f;
// check if ledge is too high to reach or will cause us to fall to our death
if (toGround - fromGround > JumpCrouchHeight + epsilon || fromGround - toGround > DeathDrop)
{
walkable = false;
}
else
{
// check surface normals along this step to see if we would cross any impassable slopes
Vector delta = to - from;
const float inc = 2.0f;
float along = inc;
bool done = false;
float ground;
Vector normal;
walkable = true;
while (!done)
{
Vector p;
// need to guarantee that we test the exact edges
if (along >= GenerationStepSize)
{
p = to;
done = true;
}
else
{
p = from + delta * (along / GenerationStepSize);
}
if (GetGroundHeight(&p, &ground, &normal) == false)
{
walkable = false;
break;
}
// check for maximum allowed slope
if (normal.z < 0.7f)
{
walkable = false;
break;
}
along += inc;
}
}
}
// TraceLine hit something...
else
{
if (IsEntityWalkable(VARS(result.pHit), WALK_THRU_EVERYTHING))
{
walkable = true;
}
else
{
walkable = false;
}
}
// if we're incrementally generating, don't overlap existing nav areas
CNavArea *overlap = TheNavAreaGrid.GetNavArea(&to, HumanHeight);
if (overlap != NULL)
{
walkable = false;
}
if (walkable)
{
// we can move here
// create a new navigation node, and update current node pointer
CNavNode *newNode = AddNode(&to, &toNormal, m_generationDir, m_currentNode);
}
return true;
}
}
// all directions have been searched from this node - pop back to its parent and continue
m_currentNode = m_currentNode->GetParent();
}
}
void CCSBot::UpdateLearnProcess()
{
float startTime = g_engfuncs.pfnTime();
while (g_engfuncs.pfnTime() - startTime < updateTimesliceDuration)
{
if (LearnStep() == false)
{
StartAnalyzeAlphaProcess();
return;
}
}
}
void CCSBot::StartAnalyzeAlphaProcess()
{
m_processMode = PROCESS_ANALYZE_ALPHA;
m_analyzeIter = TheNavAreaList.Head ();
_navAreaCount = TheNavAreaList.Count();
_currentIndex = 0;
DestroyHidingSpots();
startProgressMeter("Analyzing hiding spots...");
drawProgressMeter(0, "Analyzing hiding spots...");
}
bool CCSBot::AnalyzeAlphaStep()
{
++_currentIndex;
if (m_analyzeIter == TheNavAreaList.InvalidIndex ())
return false;
CNavArea *area = TheNavAreaList.Element (m_analyzeIter);
area->ComputeHidingSpots();
area->ComputeApproachAreas();
m_analyzeIter = TheNavAreaList.Next (m_analyzeIter);
return true;
}
void CCSBot::UpdateAnalyzeAlphaProcess()
{
float startTime = g_engfuncs.pfnTime();
while (g_engfuncs.pfnTime() - startTime < updateTimesliceDuration)
{
if (AnalyzeAlphaStep() == false)
{
drawProgressMeter(50, "Analyzing hiding spots...");
StartAnalyzeBetaProcess();
return;
}
}
float progress = (double (_currentIndex) / double (_navAreaCount)) * 0.5f;
drawProgressMeter(progress, "Analyzing hiding spots...");
}
void CCSBot::StartAnalyzeBetaProcess()
{
m_processMode = PROCESS_ANALYZE_BETA;
m_analyzeIter = TheNavAreaList.Head ();
_navAreaCount = TheNavAreaList.Count ();
_currentIndex = 0;
}
bool CCSBot::AnalyzeBetaStep()
{
++_currentIndex;
if (m_analyzeIter == TheNavAreaList.InvalidIndex ())
return false;
CNavArea *area = TheNavAreaList.Element (m_analyzeIter);
area->ComputeSpotEncounters();
area->ComputeSniperSpots();
m_analyzeIter = TheNavAreaList.Next (m_analyzeIter);
return true;
}
void CCSBot::UpdateAnalyzeBetaProcess()
{
float startTime = g_engfuncs.pfnTime();
while (g_engfuncs.pfnTime() - startTime < updateTimesliceDuration)
{
if (AnalyzeBetaStep() == false)
{
drawProgressMeter(100, "Analyzing approach points...");
StartSaveProcess();
return;
}
}
float progress = (double (_currentIndex) / double (_navAreaCount) + 1.0f) * 0.5f;
drawProgressMeter(progress, "Analyzing approach points...");
}
void CCSBot::StartSaveProcess()
{
m_processMode = PROCESS_SAVE;
}
void CCSBot::UpdateSaveProcess()
{
char filename[256];
char msg[256];
char cmd[128];
GET_GAME_DIR(filename);
Q_strcat(filename, "\\");
Q_strcat(filename, TheBots->GetNavMapFilename());
HintMessageToAllPlayers("Saving...");
SaveNavigationMap(filename);
Q_sprintf(msg, "Navigation file '%s' saved.", filename);
HintMessageToAllPlayers(msg);
hideProgressMeter();
StartNormalProcess();
Q_sprintf (cmd, "changelevel %s\n", STRING (gpGlobals->mapname));
SERVER_COMMAND(cmd);
}
void CCSBot::StartNormalProcess()
{
m_processMode = PROCESS_NORMAL;
}

210
dlls/bot/cs_bot_listen.cpp Normal file
View File

@ -0,0 +1,210 @@
#include "bot_common.h"
// Listen for enemy noises, and determine if we should react to them.
// Returns true if heard a noise and should move to investigate.
bool CCSBot::ShouldInvestigateNoise(float *retNoiseDist)
{
if (m_isNoiseTravelRangeChecked)
return false;
// don't investigate noises during safe time
if (!IsWellPastSafe())
return false;
// if our disposition is not to investigate, dont investigate
if (GetDisposition() != ENGAGE_AND_INVESTIGATE)
return false;
// listen for enemy noises
if (IsNoiseHeard() && gpGlobals->time - m_noiseCheckTimestamp >= 0.25f)
{
m_noiseCheckTimestamp = gpGlobals->time;
Vector toNoise = m_noisePosition - pev->origin;
float noiseDist = toNoise.Length();
float const oneStoreyHeight = 120.0f;
if (abs(int64(toNoise.z)) > oneStoreyHeight)
{
PathCost pc(this);
float travelDistToNoise = NavAreaTravelDistance(m_lastKnownArea, m_noiseArea, pc);
m_isNoiseTravelRangeChecked = true;
const float tooFar = 1500.0f;
if (travelDistToNoise < 0.0f || travelDistToNoise > tooFar)
return false;
if (noiseDist <= travelDistToNoise)
noiseDist = travelDistToNoise;
}
// if we are hiding, only react to noises very nearby, depending on how aggressive we are
if (IsAtHidingSpot() && noiseDist > 100.0f + 400.0f * GetProfile()->GetAggression())
return false;
// chance of investigating is inversely proportional to distance
const float maxNoiseDist = 2000.0f;
float chance = (1.0f - (noiseDist / maxNoiseDist));
// modify chance by number of friends remaining
// if we have lots of friends, presumably one of them is closer and will check it out
if (GetFriendsRemaining() >= 3)
{
float friendFactor = 0.05f * GetFriendsRemaining();
if (friendFactor > 0.5f)
friendFactor = 0.5f;
chance -= friendFactor;
}
if (RANDOM_FLOAT(0.0f, 1.0f) <= chance)
{
if (retNoiseDist)
*retNoiseDist = noiseDist;
return true;
}
}
return false;
}
// Return true if we hear nearby threatening enemy gunfire within given range
// -1 == infinite range
bool CCSBot::CanHearNearbyEnemyGunfire(float range) const
{
// only attend to noise if it just happened
if (gpGlobals->time - m_noiseTimestamp > 0.5f)
return false;
// gunfire is high priority
if (m_noisePriority < PRIORITY_HIGH)
return false;
// check noise range
if (range > 0.0f && (pev->origin - m_noisePosition).IsLengthGreaterThan(range))
return false;
// if we dont have line of sight, it's not threatening (cant get shot)
if (!CanSeeNoisePosition())
return false;
if (IsAttacking() && m_enemy != NULL)
{
// gunfire is only threatening if it is closer than our current enemy
float gunfireDistSq = (m_noisePosition - pev->origin).LengthSquared();
float enemyDistSq = (m_enemy->pev->origin - pev->origin).LengthSquared();
const float muchCloserSq = 100.0f * 100.0f;
if (gunfireDistSq > enemyDistSq - muchCloserSq)
return false;
}
return true;
}
// Return true if we directly see where we think the noise came from
// NOTE: Dont check FOV, since this is used to determine if we should turn our head to look at the noise
// NOTE: Dont use IsVisible(), because smoke shouldnt cause us to not look toward noises
bool CCSBot::CanSeeNoisePosition() const
{
TraceResult result;
UTIL_TraceLine(GetEyePosition(), m_noisePosition + Vector(0, 0, HalfHumanHeight), ignore_monsters, ignore_glass, ENT(pev), &result);
if (result.flFraction == 1.0f)
{
// we can see the source of the noise
return true;
}
return false;
}
// Return true if we decided to look towards the most recent noise source
// Assumes m_noisePosition is valid.
bool CCSBot::UpdateLookAtNoise()
{
// make sure a noise exists
if (!IsNoiseHeard() || gpGlobals->time - m_noiseTimestamp > 0.5f)
return false;
bool nearbyThreat = false;
float const recentThreatTime = 5.0f;
if (GetTimeSinceLastSawEnemy() < recentThreatTime)
{
const float closeThreatRange = 750.0f;
if ((pev->origin - m_lastEnemyPosition).IsLengthLessThan(closeThreatRange))
{
nearbyThreat = true;
}
}
Vector spot;
// if we have clear line of sight to noise position, look directly at it
if ((!IsAtHidingSpot() && nearbyThreat) || CanSeeNoisePosition())
{
// TODO: adjust noise Z to keep consistent with current height while fighting
spot = m_noisePosition + Vector(0, 0, HalfHumanHeight);
}
else
{
// line of sight is blocked, bend it
if (m_approachPointCount == 0)
return false;
int nearIdx = -1;
float nearRangeSq = 9.9999998e10f;
for (int i = 0; i < m_approachPointCount; ++i)
{
float distanceSq = (m_approachPoint[i] - m_noisePosition).LengthSquared();
if (distanceSq < nearRangeSq)
{
nearRangeSq = distanceSq;
nearIdx = i;
}
}
if (nearIdx != -1)
{
// line of sight is blocked, bend it
if (BendLineOfSight(&pev->origin, &m_approachPoint[nearIdx], &spot) == false)
return false;
spot.z += HalfHumanHeight;
}
else
{
// prior bend failed
return false;
}
}
// it's always important to look at enemy noises, because they come from ... enemies!
PriorityType pri = (GetNoisePriority() == PRIORITY_HIGH) ? PRIORITY_HIGH : PRIORITY_MEDIUM;
// look longer if we're hiding
if (IsAtHidingSpot())
{
// if there is only one enemy left, look for a long time
if (GetEnemiesRemaining() == 1)
{
SetLookAt("Noise", &spot, pri, RANDOM_FLOAT(5.0f, 15.0f), true);
}
else
{
SetLookAt("Noise", &spot, pri, RANDOM_FLOAT(2.0f, 5.0f), true);
}
}
else
{
SetLookAt("Noise", &spot, pri, RANDOM_FLOAT(1.0f, 2.0f), true);
}
return true;
}

1506
dlls/bot/cs_bot_manager.cpp Normal file

File diff suppressed because it is too large Load Diff

257
dlls/bot/cs_bot_manager.h Normal file
View File

@ -0,0 +1,257 @@
/*
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* In addition, as a special exception, the author gives permission to
* link the code of this program with the Half-Life Game Engine ("HL
* Engine") and Modified Game Libraries ("MODs") developed by Valve,
* L.L.C ("Valve"). You must obey the GNU General Public License in all
* respects for all of the code used other than the HL Engine and MODs
* from Valve. If you modify this file, you may extend this exception
* to your version of the file, but you are not obligated to do so. If
* you do not wish to do so, delete this exception statement from your
* version.
*
*/
#ifndef CS_BOT_MANAGER_H
#define CS_BOT_MANAGER_H
#ifdef _WIN32
#pragma once
#endif
extern CBotManager *TheBots;
// The manager for Counter-Strike specific bots
class CCSBotManager: public CBotManager
{
public:
CCSBotManager();
virtual void ClientDisconnect(CBasePlayer *pPlayer);
virtual BOOL ClientCommand(CBasePlayer *pPlayer, const char *pcmd);
virtual void ServerActivate();
virtual void ServerDeactivate();
virtual void ServerCommand(const char *pcmd);
virtual void AddServerCommand(const char *cmd);
virtual void AddServerCommands();
virtual void RestartRound(); // (EXTEND) invoked when a new round begins
virtual void StartFrame(); // (EXTEND) called each frame
virtual void OnEvent(GameEventType event, CBaseEntity *entity = NULL, CBaseEntity *other = NULL);
virtual unsigned int GetPlayerPriority(CBasePlayer *player) const; // return priority of player (0 = max pri)
virtual bool IsImportantPlayer(CBasePlayer *player) const; // return true if player is important to scenario (VIP, bomb carrier, etc)
public:
void ValidateMapData();
bool IsLearningMap() const { return m_isLearningMap; }
void SetLearningMapFlag() { m_isLearningMap = true;}
bool IsAnalysisRequested() const { return m_isAnalysisRequested; }
void RequestAnalysis() { m_isAnalysisRequested = true; }
void AckAnalysisRequest() { m_isAnalysisRequested = false; }
// difficulty levels
static BotDifficultyType GetDifficultyLevel()
{
if (cv_bot_difficulty.value < 0.9f)
return BOT_EASY;
if (cv_bot_difficulty.value < 1.9f)
return BOT_NORMAL;
if (cv_bot_difficulty.value < 2.9f)
return BOT_HARD;
return BOT_EXPERT;
}
// the supported game scenarios
enum GameScenarioType
{
SCENARIO_DEATHMATCH,
SCENARIO_DEFUSE_BOMB,
SCENARIO_RESCUE_HOSTAGES,
SCENARIO_ESCORT_VIP
};
GameScenarioType GetScenario() const { return m_gameScenario; }
// "zones"
// depending on the game mode, these are bomb zones, rescue zones, etc.
enum { MAX_ZONES = 4 }; // max # of zones in a map
enum { MAX_ZONE_NAV_AREAS = 16 }; // max # of nav areas in a zone
struct Zone
{
CBaseEntity *m_entity; // the map entity
CNavArea *m_area[MAX_ZONE_NAV_AREAS]; // nav areas that overlap this zone
int m_areaCount;
Vector m_center;
bool m_isLegacy; // if true, use pev->origin and 256 unit radius as zone
int m_index;
Extent m_extent;
};
const Zone *GetZone(int i) const { return &m_zone[i]; }
const Zone *GetZone(const Vector *pos) const; // return the zone that contains the given position
const Zone *GetClosestZone(const Vector *pos) const; // return the closest zone to the given position
const Zone *GetClosestZone(const CBaseEntity *entity) const { return GetClosestZone(&entity->pev->origin); } // return the closest zone to the given entity
int GetZoneCount() const { return m_zoneCount; }
const Vector *GetRandomPositionInZone(const Zone *zone) const;
CNavArea *GetRandomAreaInZone(const Zone *zone) const;
// Return the zone closest to the given position, using the given cost heuristic
template<typename CostFunctor>
const Zone *GetClosestZone(CNavArea *startArea, CostFunctor costFunc, float *travelDistance = NULL) const
{
const Zone *closeZone = NULL;
float closeDist = 99999999.9f;
if (startArea == NULL)
return NULL;
for (int i = 0; i < m_zoneCount; ++i)
{
if (m_zone[i].m_areaCount == 0)
continue;
// just use the first overlapping nav area as a reasonable approximation
float dist = NavAreaTravelDistance(startArea, m_zone[i].m_area[0], costFunc);
if (/*dist >= 0.0f && */dist < closeDist)
{
closeZone = &m_zone[i];
closeDist = dist;
}
}
if (travelDistance != NULL)
*travelDistance = closeDist;
return closeZone;
}
// pick a zone at random and return it
const Zone *GetRandomZone() const
{
if (!m_zoneCount)
return NULL;
return &m_zone[ RANDOM_LONG(0, m_zoneCount - 1) ];
}
bool IsBombPlanted() const { return m_isBombPlanted; } // returns true if bomb has been planted
float GetBombPlantTimestamp() const { return m_bombPlantTimestamp; } // return time bomb was planted
bool IsTimeToPlantBomb() const { return (gpGlobals->time >= m_earliestBombPlantTimestamp); } // return true if it's ok to try to plant bomb
CBasePlayer *GetBombDefuser() const { return m_bombDefuser; } // return the player currently defusing the bomb, or NULL
float GetBombTimeLeft() const; // get the time remaining before the planted bomb explodes
CBaseEntity *GetLooseBomb() { return m_looseBomb; } // return the bomb if it is loose on the ground
CNavArea *GetLooseBombArea() const { return m_looseBombArea; } // return area that bomb is in/near
void SetLooseBomb(CBaseEntity *bomb);
float GetRadioMessageTimestamp(GameEventType event, int teamID) const; // return the last time the given radio message was sent for given team
float GetRadioMessageInterval(GameEventType event, int teamID) const; // return the interval since the last time this message was sent
void SetRadioMessageTimestamp(GameEventType event, int teamID);
void ResetRadioMessageTimestamps();
float GetLastSeenEnemyTimestamp() const { return m_lastSeenEnemyTimestamp; } // return the last time anyone has seen an enemy
void SetLastSeenEnemyTimestamp() { m_lastSeenEnemyTimestamp = gpGlobals->time; }
float GetRoundStartTime() const { return m_roundStartTimestamp; }
float GetElapsedRoundTime() const { return gpGlobals->time - m_roundStartTimestamp; } // return the elapsed time since the current round began
bool AllowRogues() const { return cv_bot_allow_rogues.value != 0.0f; }
bool AllowPistols() const { return cv_bot_allow_pistols.value != 0.0f; }
bool AllowShotguns() const { return cv_bot_allow_shotguns.value != 0.0f; }
bool AllowSubMachineGuns() const { return cv_bot_allow_sub_machine_guns.value != 0.0f; }
bool AllowRifles() const { return cv_bot_allow_rifles.value != 0.0f; }
bool AllowMachineGuns() const { return cv_bot_allow_machine_guns.value != 0.0f; }
bool AllowGrenades() const { return cv_bot_allow_grenades.value != 0.0f; }
bool AllowSnipers() const { return cv_bot_allow_snipers.value != 0.0f; }
bool AllowTacticalShield() const { return cv_bot_allow_shield.value != 0.0f; }
bool AllowFriendlyFireDamage() const { return true; }//friendlyfire.value != 0.0f; }
bool IsWeaponUseable(CBasePlayerItem *item) const; // return true if the bot can use this weapon
bool IsDefenseRushing() const { return m_isDefenseRushing; } // returns true if defense team has "decided" to rush this round
bool IsOnDefense(CBasePlayer *player) const; // return true if this player is on "defense"
bool IsOnOffense(CBasePlayer *player) const; // return true if this player is on "offense"
bool IsRoundOver() const { return m_isRoundOver; } // return true if the round has ended
unsigned int GetNavPlace() const { return m_navPlace; }
void SetNavPlace(unsigned int place) { m_navPlace = place; }
enum SkillType { LOW, AVERAGE, HIGH, RANDOM };
NOXREF const char *GetRandomBotName(SkillType skill);
static void MonitorBotCVars();
static void MaintainBotQuota();
static bool AddBot(const BotProfile *profile, BotProfileTeamType team);
#define FROM_CONSOLE true
static bool BotAddCommand(BotProfileTeamType team, bool isFromConsole = false); // process the "bot_add" console command
private:
static float m_flNextCVarCheck;
static bool m_isMapDataLoaded; // true if we've attempted to load map data
static bool m_isLearningMap;
static bool m_isAnalysisRequested;
GameScenarioType m_gameScenario; // what kind of game are we playing
Zone m_zone[ MAX_ZONES ];
int m_zoneCount;
bool m_isBombPlanted; // true if bomb has been planted
float m_bombPlantTimestamp; // time bomb was planted
float m_earliestBombPlantTimestamp; // don't allow planting until after this time has elapsed
CBasePlayer *m_bombDefuser; // the player currently defusing a bomb
EHANDLE m_looseBomb; // will be non-NULL if bomb is loose on the ground
CNavArea *m_looseBombArea; // area that bomb is is/near
bool m_isRoundOver; // true if the round has ended
float m_radioMsgTimestamp[24][2];
float m_lastSeenEnemyTimestamp;
float m_roundStartTimestamp; // the time when the current round began
bool m_isDefenseRushing; // whether defensive team is rushing this round or not
static NavEditCmdType m_editCmd;
unsigned int m_navPlace;
CountdownTimer m_respawnTimer;
bool m_isRespawnStarted;
bool m_canRespawn;
bool m_bServerActive;
};
NOXREF inline int OtherTeam(int team)
{
return 0;
}
inline CCSBotManager *TheCSBots()
{
return reinterpret_cast<CCSBotManager *>(TheBots);
}
void PrintAllEntities();
void UTIL_DrawBox(Extent *extent, int lifetime, int red, int green, int blue);
#endif // CS_BOT_MANAGER_H

456
dlls/bot/cs_bot_nav.cpp Normal file
View File

@ -0,0 +1,456 @@
#include "bot_common.h"
// Reset the stuck-checker.
void CCSBot::ResetStuckMonitor()
{
if (m_isStuck)
{
if (IsLocalPlayerWatchingMe() && cv_bot_debug.value > 0.0f)
{
EMIT_SOUND(edict(), CHAN_ITEM, "buttons/bell1.wav", VOL_NORM, ATTN_NORM);
}
}
m_isStuck = false;
m_stuckTimestamp = 0.0f;
m_stuckJumpTimestamp = 0.0f;
m_avgVelIndex = 0;
m_avgVelCount = 0;
m_areaEnteredTimestamp = gpGlobals->time;
}
// Test if we have become stuck
void CCSBot::StuckCheck()
{
if (m_isStuck)
{
// we are stuck - see if we have moved far enough to be considered unstuck
Vector delta = pev->origin - m_stuckSpot;
const float unstuckRange = 75.0f;
if (delta.IsLengthGreaterThan(unstuckRange))
{
// we are no longer stuck
ResetStuckMonitor();
PrintIfWatched("UN-STUCK\n");
}
}
else
{
// check if we are stuck
// compute average velocity over a short period (for stuck check)
Vector vel = pev->origin - m_lastOrigin;
// if we are jumping, ignore Z
if (IsJumping())
vel.z = 0.0f;
// cannot be Length2D, or will break ladder movement (they are only Z)
float moveDist = vel.Length();
float deltaT = g_flBotFullThinkInterval;
m_avgVel[ m_avgVelIndex++ ] = moveDist / deltaT;
if (m_avgVelIndex == MAX_VEL_SAMPLES)
m_avgVelIndex = 0;
if (m_avgVelCount < MAX_VEL_SAMPLES)
{
m_avgVelCount++;
}
else
{
// we have enough samples to know if we're stuck
float avgVel = 0.0f;
for (int t = 0; t < m_avgVelCount; ++t)
avgVel += m_avgVel[t];
avgVel /= m_avgVelCount;
// cannot make this velocity too high, or bots will get "stuck" when going down ladders
float stuckVel = (IsUsingLadder()) ? 10.0f : 20.0f;
if (avgVel < stuckVel)
{
// we are stuck - note when and where we initially become stuck
m_stuckTimestamp = gpGlobals->time;
m_stuckSpot = pev->origin;
m_stuckJumpTimestamp = gpGlobals->time + RANDOM_FLOAT(0.0f, 0.5f);
PrintIfWatched("STUCK\n");
if (IsLocalPlayerWatchingMe() && cv_bot_debug.value > 0.0f)
{
EMIT_SOUND(ENT(pev), CHAN_ITEM, "buttons/button11.wav", VOL_NORM, ATTN_NORM);
}
m_isStuck = true;
}
}
}
// always need to track this
m_lastOrigin = pev->origin;
}
// Check if we need to jump due to height change
bool CCSBot::DiscontinuityJump(float ground, bool onlyJumpDown, bool mustJump)
{
// don't try to jump again.
if (m_isJumpCrouching)
return false;
float dz = ground - GetFeetZ();
if (dz > StepHeight && !onlyJumpDown)
{
// dont restrict jump time when going up
if (Jump(MUST_JUMP))
{
m_isJumpCrouching = true;
m_isJumpCrouched = false;
StandUp();
m_jumpCrouchTimestamp = gpGlobals->time;
return true;
}
}
else if (!IsUsingLadder() && dz < -JumpHeight)
{
if (Jump(mustJump))
{
m_isJumpCrouching = true;
m_isJumpCrouched = false;
StandUp();
m_jumpCrouchTimestamp = gpGlobals->time;
return true;
}
}
return false;
}
// Find "simple" ground height, treating current nav area as part of the floor
bool CCSBot::GetSimpleGroundHeightWithFloor(const Vector *pos, float *height, Vector *normal)
{
if (GetSimpleGroundHeight(pos, height, normal))
{
// our current nav area also serves as a ground polygon
if (m_lastKnownArea != NULL && m_lastKnownArea->IsOverlapping(pos))
{
*height = Q_max((*height), m_lastKnownArea->GetZ(pos));
}
return true;
}
return false;
}
Place CCSBot::GetPlace() const
{
if (m_lastKnownArea != NULL)
return m_lastKnownArea->GetPlace();
return UNDEFINED_PLACE;
}
void CCSBot::MoveTowardsPosition(const Vector *pos)
{
// Jump up on ledges
// Because we may not be able to get to our goal position and enter the next
// area because our extent collides with a nearby vertical ledge, make sure
// we look far enough ahead to avoid this situation.
// Can't look too far ahead, or bots will try to jump up slopes.
// NOTE: We need to do this frequently to catch edges at the right time
// TODO: Look ahead *along path* instead of straight line
if ((m_lastKnownArea == NULL || !(m_lastKnownArea->GetAttributes() & NAV_NO_JUMP)) &&
!IsOnLadder() && !m_isJumpCrouching)
{
float ground;
Vector aheadRay(pos->x - pev->origin.x, pos->y - pev->origin.y, 0);
aheadRay.NormalizeInPlace();
// look far ahead to allow us to smoothly jump over gaps, ledges, etc
// only jump if ground is flat at lookahead spot to avoid jumping up slopes
bool jumped = false;
if (IsRunning())
{
const float farLookAheadRange = 80.0f;
Vector normal;
Vector stepAhead = pev->origin + farLookAheadRange * aheadRay;
stepAhead.z += HalfHumanHeight;
if (GetSimpleGroundHeightWithFloor(&stepAhead, &ground, &normal))
{
if (normal.z > 0.9f)
jumped = DiscontinuityJump(ground, ONLY_JUMP_DOWN);
}
}
if (!jumped)
{
// close up jumping
// cant be less or will miss jumps over low walls
const float lookAheadRange = 30.0f;
Vector stepAhead = pev->origin + lookAheadRange * aheadRay;
stepAhead.z += HalfHumanHeight;
if (GetSimpleGroundHeightWithFloor(&stepAhead, &ground))
{
jumped = DiscontinuityJump(ground);
}
}
if (!jumped)
{
// about to fall gap-jumping
const float lookAheadRange = 10.0f;
Vector stepAhead = pev->origin + lookAheadRange * aheadRay;
stepAhead.z += HalfHumanHeight;
if (GetSimpleGroundHeightWithFloor(&stepAhead, &ground))
{
jumped = DiscontinuityJump(ground, ONLY_JUMP_DOWN, MUST_JUMP);
}
}
}
// compute our current forward and lateral vectors
float angle = pev->v_angle.y;
Vector2D dir(BotCOS(angle), BotSIN(angle));
Vector2D lat(-dir.y, dir.x);
// compute unit vector to goal position
Vector2D to(pos->x - pev->origin.x, pos->y - pev->origin.y);
to.NormalizeInPlace();
// move towards the position independant of our view direction
float toProj = to.x * dir.x + to.y * dir.y;
float latProj = to.x * lat.x + to.y * lat.y;
const float c = 0.25f;
if (toProj > c)
MoveForward();
else if (toProj < -c)
MoveBackward();
// if we are avoiding someone via strafing, don't override
if (m_avoid != NULL)
return;
if (latProj >= c)
StrafeLeft();
else if (latProj <= -c)
StrafeRight();
}
// Move away from position, independant of view angle
NOXREF void CCSBot::MoveAwayFromPosition(const Vector *pos)
{
// compute our current forward and lateral vectors
float angle = pev->v_angle[ YAW ];
Vector2D dir(BotCOS(angle), BotSIN(angle));
Vector2D lat(-dir.y, dir.x);
// compute unit vector to goal position
Vector2D to(pos->x - pev->origin.x, pos->y - pev->origin.y);
to.NormalizeInPlace();
// move away from the position independant of our view direction
float toProj = to.x * dir.x + to.y * dir.y;
float latProj = to.x * lat.x + to.y * lat.y;
const float c = 0.5f;
if (toProj > c)
MoveBackward();
else if (toProj < -c)
MoveForward();
if (latProj >= c)
StrafeRight();
else if (latProj <= -c)
StrafeLeft();
}
// Strafe (sidestep) away from position, independant of view angle
void CCSBot::StrafeAwayFromPosition(const Vector *pos)
{
// compute our current forward and lateral vectors
float angle = pev->v_angle[ YAW ];
Vector2D dir(BotCOS(angle), BotSIN(angle));
Vector2D lat(-dir.y, dir.x);
// compute unit vector to goal position
Vector2D to(pos->x - pev->origin.x, pos->y - pev->origin.y);
to.NormalizeInPlace();
float latProj = to.x * lat.x + to.y * lat.y;
if (latProj >= 0.0f)
StrafeRight();
else
StrafeLeft();
}
// For getting un-stuck
void CCSBot::Wiggle()
{
if (IsCrouching())
{
ResetStuckMonitor();
return;
}
// for wiggling
if (gpGlobals->time >= m_wiggleTimestamp)
{
m_wiggleDirection = (NavRelativeDirType)RANDOM_LONG(0, 3);
m_wiggleTimestamp = RANDOM_FLOAT(0.5, 1.5) + gpGlobals->time;
}
// TODO: implement checking of the movement to fall down
switch (m_wiggleDirection)
{
case LEFT:
StrafeLeft();
break;
case RIGHT:
StrafeRight();
break;
case FORWARD:
MoveForward();
break;
case BACKWARD:
MoveBackward();
break;
}
if (gpGlobals->time >= m_stuckJumpTimestamp)
{
if (Jump())
{
m_stuckJumpTimestamp = RANDOM_FLOAT(1.0, 2.0) + gpGlobals->time;
}
}
}
// Determine approach points from eye position and approach areas of current area
void CCSBot::ComputeApproachPoints()
{
m_approachPointCount = 0;
if (m_lastKnownArea == NULL)
{
return;
}
// assume we're crouching for now
Vector eye = pev->origin;
Vector ap;
float halfWidth;
for (int i = 0; i < m_lastKnownArea->GetApproachInfoCount() && m_approachPointCount < MAX_APPROACH_POINTS; ++i)
{
const CNavArea::ApproachInfo *info = m_lastKnownArea->GetApproachInfo(i);
if (info->here.area == NULL || info->prev.area == NULL)
{
continue;
}
// compute approach point (approach area is "info->here")
if (info->prevToHereHow <= GO_WEST)
{
info->prev.area->ComputePortal(info->here.area, (NavDirType)info->prevToHereHow, &ap, &halfWidth);
ap.z = info->here.area->GetZ(&ap);
}
else
{
// use the area's center as an approach point
ap = *info->here.area->GetCenter();
}
// "bend" our line of sight around corners until we can see the approach point
Vector bendPoint;
if (BendLineOfSight(&eye, &ap, &bendPoint))
{
m_approachPoint[ m_approachPointCount++ ] = bendPoint;
}
}
}
void CCSBot::DrawApproachPoints()
{
for (int i = 0; i < m_approachPointCount; ++i)
{
UTIL_DrawBeamPoints(m_approachPoint[i], m_approachPoint[i] + Vector(0, 0, 50), 3, 0, 255, 255);
}
}
// Find the approach point that is nearest to our current path, ahead of us
NOXREF bool CCSBot::FindApproachPointNearestPath(Vector *pos)
{
if (!HasPath())
return false;
// make sure approach points are accurate
ComputeApproachPoints();
if (m_approachPointCount == 0)
return false;
Vector target = Vector(0, 0, 0), close;
float targetRangeSq = 0.0f;
bool found = false;
int start = m_pathIndex;
int end = m_pathLength;
// We dont want the strictly closest point, but the farthest approach point
// from us that is near our path
const float nearPathSq = 10000.0f;
for (int i = 0; i < m_approachPointCount; ++i)
{
if (FindClosestPointOnPath(&m_approachPoint[i], start, end, &close) == false)
continue;
float rangeSq = (m_approachPoint[i] - close).LengthSquared();
if (rangeSq > nearPathSq)
continue;
if (rangeSq > targetRangeSq)
{
target = close;
targetRangeSq = rangeSq;
found = true;
}
}
if (found)
{
*pos = target + Vector(0, 0, HalfHumanHeight);
return true;
}
return false;
}

1756
dlls/bot/cs_bot_pathfind.cpp Normal file

File diff suppressed because it is too large Load Diff

329
dlls/bot/cs_bot_radio.cpp Normal file
View File

@ -0,0 +1,329 @@
#include "bot_common.h"
// Returns true if the radio message is an order to do something
// NOTE: "Report in" is not considered a "command" because it doesnt ask the bot to go somewhere, or change its mind
bool CCSBot::IsRadioCommand(GameEventType event) const
{
if (event == EVENT_RADIO_AFFIRMATIVE
|| event == EVENT_RADIO_NEGATIVE
|| event == EVENT_RADIO_ENEMY_SPOTTED
|| event == EVENT_RADIO_SECTOR_CLEAR
|| event == EVENT_RADIO_REPORTING_IN
|| event == EVENT_RADIO_REPORT_IN_TEAM
|| event == EVENT_RADIO_ENEMY_DOWN)
return false;
return true;
}
// Respond to radio commands from HUMAN players
void CCSBot::RespondToRadioCommands()
{
// bots use the chatter system to respond to each other
if (m_radioSubject != NULL && m_radioSubject->IsPlayer())
{
CBasePlayer *player = m_radioSubject;
if (player->IsBot())
{
m_lastRadioCommand = EVENT_INVALID;
return;
}
}
if (m_lastRadioCommand == EVENT_INVALID)
return;
// a human player has issued a radio command
GetChatter()->ResetRadioSilenceDuration();
// if we are doing something important, ignore the radio
// unless it is a "report in" request - we can do that while we continue to do other things
// TODO: Create "uninterruptable" flag
if (m_lastRadioCommand != EVENT_RADIO_REPORT_IN_TEAM)
{
if (IsBusy())
{
// consume command
m_lastRadioCommand = EVENT_INVALID;
return;
}
}
// wait for reaction time before responding
// delay needs to be long enough for the radio message we're responding to to finish
float respondTime = 1.0f + 2.0f * GetProfile()->GetReactionTime();
if (IsRogue())
respondTime += 2.0f;
if (gpGlobals->time - m_lastRadioRecievedTimestamp < respondTime)
return;
// rogues won't follow commands, unless already following the player
if (!IsFollowing() && IsRogue())
{
if (IsRadioCommand(m_lastRadioCommand))
{
GetChatter()->Negative();
}
// consume command
m_lastRadioCommand = EVENT_INVALID;
return;
}
CCSBotManager *ctrl = TheCSBots();
CBasePlayer *player = m_radioSubject;
if (player == NULL)
return;
// respond to command
bool canDo = false;
const float inhibitAutoFollowDuration = 60.0f;
switch (m_lastRadioCommand)
{
case EVENT_RADIO_REPORT_IN_TEAM:
{
GetChatter()->ReportingIn();
break;
}
case EVENT_RADIO_FOLLOW_ME:
case EVENT_RADIO_COVER_ME:
case EVENT_RADIO_STICK_TOGETHER_TEAM:
case EVENT_RADIO_REGROUP_TEAM:
{
if (!IsFollowing())
{
Follow(player);
player->AllowAutoFollow();
canDo = true;
}
break;
}
case EVENT_RADIO_ENEMY_SPOTTED:
case EVENT_RADIO_NEED_BACKUP:
case EVENT_RADIO_TAKING_FIRE:
{
if (!IsFollowing())
{
Follow(player);
GetChatter()->Say("OnMyWay");
player->AllowAutoFollow();
canDo = false;
}
break;
}
case EVENT_RADIO_TEAM_FALL_BACK:
{
if (TryToRetreat())
canDo = true;
break;
}
case EVENT_RADIO_HOLD_THIS_POSITION:
{
// find the leader's area
SetTask(HOLD_POSITION);
StopFollowing();
player->InhibitAutoFollow(inhibitAutoFollowDuration);
Hide(TheNavAreaGrid.GetNearestNavArea(&m_radioPosition));
canDo = true;
break;
}
case EVENT_RADIO_GO_GO_GO:
case EVENT_RADIO_STORM_THE_FRONT:
{
StopFollowing();
Hunt();
canDo = true;
player->InhibitAutoFollow(inhibitAutoFollowDuration);
break;
}
case EVENT_RADIO_GET_OUT_OF_THERE:
{
if (ctrl->IsBombPlanted())
{
EscapeFromBomb();
player->InhibitAutoFollow(inhibitAutoFollowDuration);
canDo = true;
}
break;
}
case EVENT_RADIO_SECTOR_CLEAR:
{
// if this is a defusal scenario, and the bomb is planted,
// and a human player cleared a bombsite, check it off our list too
if (ctrl->GetScenario() == CCSBotManager::SCENARIO_DEFUSE_BOMB)
{
if (m_iTeam == CT && ctrl->IsBombPlanted())
{
const CCSBotManager::Zone *zone = ctrl->GetClosestZone(player);
if (zone != NULL)
{
GetGameState()->ClearBombsite(zone->m_index);
// if we are huting for the planted bomb, re-select bombsite
if (GetTask() == FIND_TICKING_BOMB)
Idle();
canDo = true;
}
}
}
break;
}
default:
// ignore all other radio commands for now
return;
}
if (canDo)
{
// affirmative
GetChatter()->Affirmative();
// if we agreed to follow a new command, put away our grenade
if (IsRadioCommand(m_lastRadioCommand) && IsUsingGrenade())
{
EquipBestWeapon();
}
}
// consume command
m_lastRadioCommand = EVENT_INVALID;
}
// Send voice chatter. Also sends the entindex.
void CCSBot::StartVoiceFeedback(float duration)
{
m_voiceFeedbackStartTimestamp = gpGlobals->time;
m_voiceFeedbackEndTimestamp = duration + gpGlobals->time;
CBasePlayer *pPlayer = NULL;
while ((pPlayer = GetNextRadioRecipient(pPlayer)) != NULL)
{
MESSAGE_BEGIN(MSG_ONE, gmsgBotVoice, NULL, pPlayer->pev);
WRITE_BYTE(1); // active is talking
WRITE_BYTE(entindex()); // client index speaking
MESSAGE_END();
}
}
void CCSBot::EndVoiceFeedback(bool force)
{
if (!force && !m_voiceFeedbackEndTimestamp)
return;
m_voiceFeedbackEndTimestamp = 0;
MESSAGE_BEGIN(MSG_ALL, gmsgBotVoice);
WRITE_BYTE(0);
WRITE_BYTE(ENTINDEX(edict()));
MESSAGE_END();
}
// Decide if we should move to help the player, return true if we will
bool CCSBot::RespondToHelpRequest(CBasePlayer *them, Place place, float maxRange)
{
if (IsRogue())
return false;
// if we're busy, ignore
if (IsBusy())
return false;
// if we are too far away, ignore
if (maxRange > 0.0f)
{
// compute actual travel distance
PathCost pc(this);
float travelDistance = NavAreaTravelDistance(m_lastKnownArea, TheNavAreaGrid.GetNearestNavArea(&them->pev->origin), pc);
if (travelDistance < 0.0f)
return false;
if (travelDistance > maxRange)
return false;
}
if (place == UNDEFINED_PLACE)
{
// if we have no "place" identifier, go directly to them
// if we are already there, ignore
float rangeSq = (them->pev->origin - pev->origin).LengthSquared();
const float close = 750.0f * 750.0f;
if (rangeSq < close)
return true;
MoveTo(&them->pev->origin, FASTEST_ROUTE);
}
else
{
// if we are already there, ignore
if (GetPlace() == place)
return true;
// go to where help is needed
const Vector *pos = GetRandomSpotAtPlace(place);
if (pos != NULL)
{
MoveTo(pos, FASTEST_ROUTE);
}
else
{
MoveTo(&them->pev->origin, FASTEST_ROUTE);
}
}
// acknowledge
GetChatter()->Say("OnMyWay");
return true;
}
// Send a radio message
void CCSBot::SendRadioMessage(GameEventType event)
{
// make sure this is a radio event
if (event <= EVENT_START_RADIO_1 || event >= EVENT_END_RADIO)
{
return;
}
CCSBotManager *ctrl = TheCSBots();
PrintIfWatched("%3.1f: SendRadioMessage( %s )\n", gpGlobals->time, GameEventName[ event ]);
// note the time the message was sent
ctrl->SetRadioMessageTimestamp(event, m_iTeam);
m_lastRadioSentTimestamp = gpGlobals->time;
char slot[2];
slot[1] = '\0';
if (event > EVENT_START_RADIO_1 && event < EVENT_START_RADIO_2)
{
slot[0] = event - EVENT_START_RADIO_1;
ClientCommand("radio1");
//Radio1(this, event - EVENT_START_RADIO_3);
}
else if (event > EVENT_START_RADIO_2 && event < EVENT_START_RADIO_3)
{
slot[0] = event - EVENT_START_RADIO_2;
ClientCommand("radio2");
//Radio2(this, event - EVENT_START_RADIO_3);
}
else
{
slot[0] = event - EVENT_START_RADIO_3;
ClientCommand("radio3");
//Radio3(this, event - EVENT_START_RADIO_3);
}
ClientCommand("menuselect", slot);
ClientCommand("menuselect", "10");
}

View File

@ -0,0 +1,439 @@
#include "bot_common.h"
// This method is the ONLY legal way to change a bot's current state
void CCSBot::SetState(BotState *state)
{
PrintIfWatched("SetState: %s -> %s\n", (m_state != NULL) ? m_state->GetName() : "NULL", state->GetName());
// if we changed state from within the special Attack state, we are no longer attacking
if (m_isAttacking)
StopAttacking();
if (m_state != NULL)
m_state->OnExit(this);
state->OnEnter(this);
m_state = state;
m_stateTimestamp = gpGlobals->time;
}
void CCSBot::Idle()
{
SetTask(SEEK_AND_DESTROY);
SetState(&m_idleState);
}
void CCSBot::EscapeFromBomb()
{
// SetTask(ESCAPE_FROM_BOMB);
// SetState(&m_escapeFromBombState);
}
void CCSBot::Follow(CBasePlayer *player)
{
if (player == NULL)
return;
// note when we began following
if (!m_isFollowing || m_leader != player)
m_followTimestamp = gpGlobals->time;
m_isFollowing = true;
m_leader = player;
SetTask(FOLLOW);
m_followState.SetLeader(player);
SetState(&m_followState);
}
// Continue following our leader after finishing what we were doing
void CCSBot::ContinueFollowing()
{
SetTask(FOLLOW);
m_followState.SetLeader(m_leader);
SetState(&m_followState);
}
// Stop following
void CCSBot::StopFollowing()
{
m_isFollowing = false;
m_leader = NULL;
m_allowAutoFollowTime = gpGlobals->time + 10.0f;
}
// Begin process of rescuing hostages
void CCSBot::RescueHostages()
{
SetTask(RESCUE_HOSTAGES);
}
// Use the entity
void CCSBot::UseEntity(CBaseEntity *entity)
{
m_useEntityState.SetEntity(entity);
SetState(&m_useEntityState);
}
// DEPRECATED: Use TryToHide() instead.
// Move to a hiding place.
// If 'searchFromArea' is non-NULL, hiding spots are looked for from that area first.
void CCSBot::Hide(CNavArea *searchFromArea, float duration, float hideRange, bool holdPosition)
{
DestroyPath();
CNavArea *source;
Vector sourcePos;
if (searchFromArea)
{
source = searchFromArea;
sourcePos = *searchFromArea->GetCenter();
}
else
{
source = m_lastKnownArea;
sourcePos = pev->origin;
}
if (source == NULL)
{
PrintIfWatched("Hide from area is NULL.\n");
Idle();
return;
}
m_hideState.SetSearchArea(source);
m_hideState.SetSearchRange(hideRange);
m_hideState.SetDuration(duration);
m_hideState.SetHoldPosition(holdPosition);
// search around source area for a good hiding spot
Vector useSpot;
const Vector *pos = FindNearbyHidingSpot(this, &sourcePos, source, hideRange, IsSniper());
if (pos == NULL)
{
PrintIfWatched("No available hiding spots.\n");
// hide at our current position
useSpot = pev->origin;
}
else
{
useSpot = *pos;
}
m_hideState.SetHidingSpot(useSpot);
// build a path to our new hiding spot
if (ComputePath(TheNavAreaGrid.GetNavArea(&useSpot), &useSpot, FASTEST_ROUTE) == false)
{
PrintIfWatched("Can't pathfind to hiding spot\n");
Idle();
return;
}
SetState(&m_hideState);
}
// Move to the given hiding place
void CCSBot::Hide(const Vector *hidingSpot, float duration, bool holdPosition)
{
CNavArea *hideArea = TheNavAreaGrid.GetNearestNavArea(hidingSpot);
if (hideArea == NULL)
{
PrintIfWatched("Hiding spot off nav mesh\n");
Idle();
return;
}
DestroyPath();
m_hideState.SetSearchArea(hideArea);
m_hideState.SetSearchRange(750.0f);
m_hideState.SetDuration(duration);
m_hideState.SetHoldPosition(holdPosition);
m_hideState.SetHidingSpot(*hidingSpot);
// build a path to our new hiding spot
if (ComputePath(hideArea, hidingSpot, FASTEST_ROUTE) == false)
{
PrintIfWatched("Can't pathfind to hiding spot\n");
Idle();
return;
}
SetState(&m_hideState);
}
// Try to hide nearby. Return true if hiding, false if can't hide here.
// If 'searchFromArea' is non-NULL, hiding spots are looked for from that area first.
bool CCSBot::TryToHide(CNavArea *searchFromArea, float duration, float hideRange, bool holdPosition, bool useNearest)
{
CNavArea *source;
Vector sourcePos;
if (searchFromArea)
{
source = searchFromArea;
sourcePos = *searchFromArea->GetCenter();
}
else
{
source = m_lastKnownArea;
sourcePos = pev->origin;
}
if (source == NULL)
{
PrintIfWatched("Hide from area is NULL.\n");
return false;
}
m_hideState.SetSearchArea(source);
m_hideState.SetSearchRange(hideRange);
m_hideState.SetDuration(duration);
m_hideState.SetHoldPosition(holdPosition);
// search around source area for a good hiding spot
const Vector *pos = FindNearbyHidingSpot(this, &sourcePos, source, hideRange, IsSniper(), useNearest);
if (pos == NULL)
{
PrintIfWatched("No available hiding spots.\n");
return false;
}
m_hideState.SetHidingSpot(*pos);
// build a path to our new hiding spot
if (ComputePath(TheNavAreaGrid.GetNavArea(pos), pos, FASTEST_ROUTE) == false)
{
PrintIfWatched("Can't pathfind to hiding spot\n");
return false;
}
SetState(&m_hideState);
return true;
}
// Retreat to a nearby hiding spot, away from enemies
bool CCSBot::TryToRetreat()
{
const float maxRange = 1000.0f;
const Vector *spot = FindNearbyRetreatSpot(this, maxRange);
if (spot != NULL)
{
// ignore enemies for a second to give us time to hide
// reaching our hiding spot clears our disposition
IgnoreEnemies(10.0f);
float holdTime = RANDOM_FLOAT(3.0f, 15.0f);
StandUp();
Run();
Hide(spot, holdTime);
PrintIfWatched("Retreating to a safe spot!\n");
return true;
}
return false;
}
void CCSBot::Hunt()
{
SetState(&m_huntState);
}
// Attack our the given victim
// NOTE: Attacking does not change our task.
void CCSBot::Attack(CBasePlayer *victim)
{
if (victim == NULL)
return;
// zombies never attack
if (cv_bot_zombie.value != 0.0f)
return;
// cannot attack if we are reloading
if (IsActiveWeaponReloading())
return;
// change enemy
SetEnemy(victim);
// Do not "re-enter" the attack state if we are already attacking
if (IsAttacking())
return;
if (IsAtHidingSpot())
m_attackState.SetCrouchAndHold((RANDOM_FLOAT(0, 100) < 60.0f) != 0);
else
m_attackState.SetCrouchAndHold(false);
PrintIfWatched("ATTACK BEGIN (reaction time = %g (+ update time), surprise time = %g, attack delay = %g)\n");
m_isAttacking = true;
m_attackState.OnEnter(this);
// cheat a bit and give the bot the initial location of its victim
m_lastEnemyPosition = victim->pev->origin;
m_lastSawEnemyTimestamp = gpGlobals->time;
m_aimSpreadTimestamp = gpGlobals->time;
// compute the angle difference between where are looking, and where we need to look
Vector toEnemy = victim->pev->origin - pev->origin;
Vector idealAngle = UTIL_VecToAngles(toEnemy);
float deltaYaw = abs((int)(m_lookYaw - idealAngle.y));
while (deltaYaw > 180.0f)
deltaYaw -= 360.0f;
if (deltaYaw < 0.0f)
deltaYaw = -deltaYaw;
// immediately aim at enemy - accuracy penalty depending on how far we must turn to aim
// accuracy is halved if we have to turn 180 degrees
float turn = deltaYaw / 180.0f;
float accuracy = GetProfile()->GetSkill() / (1.0f + turn);
SetAimOffset(accuracy);
// define time when aim offset will automatically be updated
// longer time the more we had to turn (surprise)
m_aimOffsetTimestamp = gpGlobals->time + RANDOM_FLOAT(0.25f + turn, 1.5f);
}
// Exit the Attack state
void CCSBot::StopAttacking()
{
PrintIfWatched("ATTACK END\n");
m_attackState.OnExit(this);
m_isAttacking = false;
// if we are following someone, go to the Idle state after the attack to decide whether we still want to follow
if (IsFollowing())
{
Idle();
}
}
bool CCSBot::IsAttacking() const
{
return m_isAttacking;
}
// Return true if we are escaping from the bomb
bool CCSBot::IsEscapingFromBomb() const
{
// if (m_state == static_cast<const BotState *>(&m_escapeFromBombState))
// return true;
return false;
}
// Return true if we are defusing the bomb
bool CCSBot::IsDefusingBomb() const
{
// if (m_state == static_cast<const BotState *>(&m_defuseBombState))
// return true;
return false;
}
// Return true if we are hiding
bool CCSBot::IsHiding() const
{
if (m_state == static_cast<const BotState *>(&m_hideState))
return true;
return false;
}
// Return true if we are hiding and at our hiding spot
bool CCSBot::IsAtHidingSpot() const
{
if (!IsHiding())
return false;
return m_hideState.IsAtSpot();
}
// Return true if we are huting
bool CCSBot::IsHunting() const
{
if (m_state == static_cast<const BotState *>(&m_huntState))
return true;
return false;
}
// Return true if we are in the MoveTo state
bool CCSBot::IsMovingTo() const
{
if (m_state == static_cast<const BotState *>(&m_moveToState))
return true;
return false;
}
// Return true if we are buying
bool CCSBot::IsBuying() const
{
// if (m_state == static_cast<const BotState *>(&m_buyState))
// return true;
return false;
}
// Move to potentially distant position
void CCSBot::MoveTo(const Vector *pos, RouteType route)
{
m_moveToState.SetGoalPosition(*pos);
m_moveToState.SetRouteType(route);
SetState(&m_moveToState);
}
void CCSBot::PlantBomb()
{
// SetState(&m_plantBombState);
}
// Bomb has been dropped - go get it
void CCSBot::FetchBomb()
{
// SetState(&m_fetchBombState);
}
void CCSBot::DefuseBomb()
{
// SetState(&m_defuseBombState);
}
// Investigate recent enemy noise
void CCSBot::InvestigateNoise()
{
SetState(&m_investigateNoiseState);
}

752
dlls/bot/cs_bot_update.cpp Normal file
View File

@ -0,0 +1,752 @@
#include "bot_common.h"
// Lightweight maintenance, invoked frequently
void CCSBot::Upkeep()
{
CCSBotManager *ctrl = TheCSBots();
if (ctrl->IsLearningMap() || !IsAlive())
return;
if (m_isRapidFiring)
TogglePrimaryAttack();
// aiming must be smooth - update often
if (IsAimingAtEnemy())
{
UpdateAimOffset();
// aim at enemy, if he's still alive
if (m_enemy != NULL)
{
float feetOffset = pev->origin.z - GetFeetZ();
if (IsEnemyVisible())
{
if (GetProfile()->GetSkill() > 0.5f)
{
const float k = 3.0f;
m_aimSpot = (m_enemy->pev->velocity - pev->velocity) * g_flBotCommandInterval * k + m_enemy->pev->origin;
}
else
m_aimSpot = m_enemy->pev->origin;
bool aimBlocked = false;
const float sharpshooter = 0.8f;
if (IsUsingAWP() || IsUsingShotgun() || IsUsingMachinegun() || GetProfile()->GetSkill() < sharpshooter
|| (IsActiveWeaponRecoilHigh() && !IsUsingPistol() && !IsUsingSniperRifle()))
{
if (IsEnemyPartVisible(CHEST))
{
// No headshots, go for the chest.
aimBlocked = true;
}
}
if (aimBlocked)
m_aimSpot.z -= feetOffset * 0.25f;
else if (!IsEnemyPartVisible(HEAD))
{
if (IsEnemyPartVisible(CHEST))
{
m_aimSpot.z -= feetOffset * 0.5f;
}
else if (IsEnemyPartVisible(LEFT_SIDE))
{
Vector2D to = (m_enemy->pev->origin - pev->origin).Make2D();
to.NormalizeInPlace();
m_aimSpot.x -= to.y * 16.0f;
m_aimSpot.y += to.x * 16.0f;
m_aimSpot.z -= feetOffset * 0.5f;
}
else if (IsEnemyPartVisible(RIGHT_SIDE))
{
Vector2D to = (m_enemy->pev->origin - pev->origin).Make2D();
to.NormalizeInPlace();
m_aimSpot.x += to.y * 16.0f;
m_aimSpot.y -= to.x * 16.0f;
m_aimSpot.z -= feetOffset * 0.5f;
}
else // FEET
m_aimSpot.z -= (feetOffset + feetOffset);
}
}
else
m_aimSpot = m_lastEnemyPosition;
// add in aim error
m_aimSpot.x += m_aimOffset.x;
m_aimSpot.y += m_aimOffset.y;
m_aimSpot.z += m_aimOffset.z;
Vector toEnemy = m_aimSpot - pev->origin;
Vector idealAngle = UTIL_VecToAngles(toEnemy);
idealAngle.x = 360.0 - idealAngle.x;
SetLookAngles(idealAngle.y, idealAngle.x);
}
}
else
{
if (m_lookAtSpotClearIfClose)
{
// dont look at spots just in front of our face - it causes erratic view rotation
const float tooCloseRange = 100.0f;
if ((m_lookAtSpot - pev->origin).IsLengthLessThan(tooCloseRange))
m_lookAtSpotState = NOT_LOOKING_AT_SPOT;
}
switch (m_lookAtSpotState)
{
case NOT_LOOKING_AT_SPOT:
{
// look ahead
SetLookAngles(m_lookAheadAngle, 0);
break;
}
case LOOK_TOWARDS_SPOT:
{
UpdateLookAt();
/*if (IsLookingAtPosition(&m_lookAtSpot, m_lookAtSpotAngleTolerance))
{
m_lookAtSpotState = LOOK_AT_SPOT;
m_lookAtSpotTimestamp = gpGlobals->time;
}*/
break;
}
case LOOK_AT_SPOT:
{
UpdateLookAt();
if (m_lookAtSpotDuration >= 0.0f && gpGlobals->time - m_lookAtSpotTimestamp > m_lookAtSpotDuration)
{
m_lookAtSpotState = NOT_LOOKING_AT_SPOT;
m_lookAtSpotDuration = 0.0f;
}
break;
}
}
float driftAmplitude = 2.0f;
// have view "drift" very slowly, so view looks "alive"
if (IsUsingSniperRifle() && IsUsingScope())
{
driftAmplitude = 0.5f;
}
m_lookYaw += driftAmplitude * BotCOS(33.0f * gpGlobals->time);
m_lookPitch += driftAmplitude * BotSIN(13.0f * gpGlobals->time);
}
// view angles can change quickly
UpdateLookAngles();
}
// Heavyweight processing, invoked less often
void CCSBot::Update()
{
CCSBotManager *ctrl = TheCSBots();
if (ctrl->IsAnalysisRequested() && m_processMode == PROCESS_NORMAL)
{
ctrl->AckAnalysisRequest();
StartAnalyzeAlphaProcess();
}
switch (m_processMode)
{
case PROCESS_LEARN: UpdateLearnProcess(); return;
case PROCESS_ANALYZE_ALPHA: UpdateAnalyzeAlphaProcess(); return;
case PROCESS_ANALYZE_BETA: UpdateAnalyzeBetaProcess(); return;
case PROCESS_SAVE: UpdateSaveProcess(); return;
}
// update our radio chatter
// need to allow bots to finish their chatter even if they are dead
GetChatter()->Update();
if (m_voiceFeedbackEndTimestamp != 0.0f
&& (m_voiceFeedbackEndTimestamp <= gpGlobals->time || gpGlobals->time < m_voiceFeedbackStartTimestamp))
{
// EndVoiceFeedback(NO_FORCE);
}
// check if we are dead
if (!IsAlive())
{
// remember that we died
m_diedLastRound = true;
BotDeathThink();
return;
}
// show line of fire
if ((cv_bot_traceview.value == 100.0f && IsLocalPlayerWatchingMe()) || cv_bot_traceview.value == 101.0f)
{
UTIL_MakeVectors(pev->punchangle + pev->v_angle);
if (!IsFriendInLineOfFire())
{
Vector vecAiming = gpGlobals->v_forward;
Vector vecSrc = GetGunPosition();
//if (m_iTeam == TERRORIST)
UTIL_DrawBeamPoints(vecSrc, vecSrc + 2000.0f * vecAiming, 1, 255, 224, 0);
//else
// UTIL_DrawBeamPoints(vecSrc, vecSrc + 2000.0f * vecAiming, 1, 0, 50, 255);
}
}
// Debug beam rendering
if( ( cv_bot_traceview.value == 2.0f && IsLocalPlayerWatchingMe() ) || cv_bot_traceview.value == 3.0f )
DrawApproachPoints();
if( ( cv_bot_traceview.value == 4.0f && IsLocalPlayerWatchingMe() ) || cv_bot_traceview.value == 5.0f )
{
// ...
}
if (cv_bot_traceview.value == 1.0f && IsLocalPlayerWatchingMe())
{
// ...
}
if (cv_bot_stop.value != 0.0f)
return;
// check if we are stuck
StuckCheck();
// if our current 'noise' was heard a long time ago, forget it
const float rememberNoiseDuration = 20.0f;
if (m_noiseTimestamp > 0.0f && gpGlobals->time - m_noiseTimestamp > rememberNoiseDuration)
{
ForgetNoise();
}
// where are we
if (!m_currentArea || !m_currentArea->Contains(&pev->origin))
{
m_currentArea = TheNavAreaGrid.GetNavArea(&pev->origin);
}
// track the last known area we were in
if (m_currentArea != NULL && m_currentArea != m_lastKnownArea)
{
m_lastKnownArea = m_currentArea;
// assume that we "clear" an area of enemies when we enter it
m_currentArea->SetClearedTimestamp(m_iTeam - 1);
}
// update approach points
const float recomputeApproachPointTolerance = 50.0f;
if ((m_approachPointViewPosition - pev->origin).IsLengthGreaterThan(recomputeApproachPointTolerance))
{
ComputeApproachPoints();
m_approachPointViewPosition = pev->origin;
}
if (cv_bot_show_nav.value > 0.0f && m_lastKnownArea != NULL)
{
m_lastKnownArea->DrawConnectedAreas();
}
#if 0
// if we're blind, retreat!
if (IsBlind())
{
if (!IsAtHidingSpot())
{
switch (m_blindMoveDir)
{
case FORWARD: MoveForward(); break;
case RIGHT: StrafeRight(); break;
case BACKWARD: MoveBackward(); break;
case LEFT: StrafeLeft(); break;
default: Crouch(); break;
}
}
if (m_blindFire)
{
PrimaryAttack();
}
return;
}
#endif
// Enemy acquisition and attack initiation
// take a snapshot and update our reaction time queue
UpdateReactionQueue();
// "threat" may be the same as our current enemy
CBasePlayer *threat = GetRecognizedEnemy();
if (threat != NULL)
{
// adjust our personal "safe" time
AdjustSafeTime();
// Decide if we should attack
bool doAttack = false;
if (!IsUsingGrenade())
{
switch (GetDisposition())
{
case IGNORE_ENEMIES:
{
// never attack
doAttack = false;
break;
}
case SELF_DEFENSE:
{
// attack if fired on
doAttack = IsPlayerLookingAtMe(threat);
// attack if enemy very close
if (!doAttack)
{
const float selfDefenseRange = 750.0f;
doAttack = (pev->origin - threat->pev->origin).IsLengthLessThan(selfDefenseRange);
}
break;
}
case ENGAGE_AND_INVESTIGATE:
case OPPORTUNITY_FIRE:
{
// normal combat range
doAttack = true;
break;
}
}
}
else
ThrowGrenade(&threat->pev->origin);
// if we aren't attacking but we are being attacked, retaliate
if (!doAttack && !IsAttacking() && GetDisposition() != IGNORE_ENEMIES)
{
const float recentAttackDuration = 1.0f;
if (GetTimeSinceAttacked() < recentAttackDuration)
{
// we may not be attacking our attacker, but at least we're not just taking it
// (since m_attacker isn't reaction-time delayed, we can't directly use it)
doAttack = true;
PrintIfWatched("Ouch! Retaliating!\n");
}
}
if (doAttack)
{
if (GetEnemy() == NULL || !IsAttacking() || threat != GetEnemy())
{
if (IsUsingKnife() && IsHiding())
{
// if hiding with a knife, wait until threat is close
const float knifeAttackRange = 250.0f;
if ((pev->origin - threat->pev->origin).IsLengthLessThan(knifeAttackRange))
{
Attack(threat);
}
}
else
{
Attack(threat);
}
}
}
else
{
// dont attack, but keep track of nearby enemies
SetEnemy(threat);
m_isEnemyVisible = true;
}
ctrl->SetLastSeenEnemyTimestamp();
}
// Validate existing enemy, if any
if (m_enemy != NULL)
{
if (IsAwareOfEnemyDeath())
{
// we have noticed that our enemy has died
m_enemy = NULL;
m_isEnemyVisible = false;
}
else
{
// check LOS to current enemy (chest & head), in case he's dead (GetNearestEnemy() only returns live players)
// note we're not checking FOV - once we've acquired an enemy (which does check FOV), assume we know roughly where he is
if (IsVisible(m_enemy, false, &m_visibleEnemyParts))
{
m_isEnemyVisible = true;
m_lastSawEnemyTimestamp = gpGlobals->time;
m_lastEnemyPosition = m_enemy->pev->origin;
}
else
{
m_isEnemyVisible = false;
}
// check if enemy died
if (m_enemy->IsAlive())
{
m_enemyDeathTimestamp = 0.0f;
m_isLastEnemyDead = false;
}
else if (m_enemyDeathTimestamp == 0.0f)
{
// note time of death (to allow bots to overshoot for a time)
m_enemyDeathTimestamp = gpGlobals->time;
m_isLastEnemyDead = true;
}
}
}
else
{
m_isEnemyVisible = false;
}
// if we have seen an enemy recently, keep an eye on him if we can
const float seenRecentTime = 3.0f;
if (m_enemy != NULL && GetTimeSinceLastSawEnemy() < seenRecentTime)
{
AimAtEnemy();
}
else
{
StopAiming();
}
// Hack to fire while retreating
// TODO: Encapsulate aiming and firing on enemies separately from current task
if (GetDisposition() == IGNORE_ENEMIES)
{
FireWeaponAtEnemy();
}
if (IsEndOfSafeTime() && IsUsingGrenade() && (IsWellPastSafe() || !IsUsingHEGrenade()) && !m_isWaitingToTossGrenade)
{
Vector target;
if (FindGrenadeTossPathTarget(&target))
{
ThrowGrenade(&target);
}
}
if (IsUsingGrenade())
{
bool doToss = (m_isWaitingToTossGrenade && (m_tossGrenadeTimer.IsElapsed() || m_lookAtSpotState == LOOK_AT_SPOT));
if (doToss)
{
ClearPrimaryAttack();
m_isWaitingToTossGrenade = false;
}
else
{
PrimaryAttack();
}
}
else
{
m_isWaitingToTossGrenade = false;
}
if (IsHunting() && IsWellPastSafe() && IsUsingGrenade())
{
EquipBestWeapon(MUST_EQUIP);
}
// check if our weapon is totally out of ammo
// or if we no longer feel "safe", equip our weapon
if (!IsSafe() && !IsUsingGrenade() && IsActiveWeaponOutOfAmmo())
{
EquipBestWeapon();
}
// TODO: This doesn't work if we are restricted to just knives and sniper rifles because we cant use the rifle at close range
if (!IsSafe() && !IsUsingGrenade() && IsUsingKnife() && !IsEscapingFromBomb())
{
EquipBestWeapon();
}
// if we haven't seen an enemy in awhile, and we switched to our pistol during combat,
// switch back to our primary weapon (if it still has ammo left)
const float safeRearmTime = 5.0f;
if (!IsActiveWeaponReloading() && IsUsingPistol() && !IsPrimaryWeaponEmpty() && GetTimeSinceLastSawEnemy() > safeRearmTime)
{
EquipBestWeapon();
}
// reload our weapon if we must
ReloadCheck();
// equip silencer
SilencerCheck();
// listen to the radio
// RespondToRadioCommands();
// make way
const float avoidTime = 0.33f;
if (gpGlobals->time - m_avoidTimestamp < avoidTime && m_avoid != NULL)
{
StrafeAwayFromPosition(&m_avoid->pev->origin);
}
else
{
m_avoid = NULL;
}
if (m_isJumpCrouching)
{
const float duration = 0.75f;
const float crouchDelayTime = 0.05f;
const float standUpTime = 0.6f;
float elapsed = gpGlobals->time - m_jumpCrouchTimestamp;
if (elapsed > crouchDelayTime && elapsed < standUpTime)
Crouch();
if (elapsed >= standUpTime)
StandUp();
if (elapsed > duration)
m_isJumpCrouching = false;
}
// if we're using a sniper rifle and are no longer attacking, stop looking thru scope
if (!IsAtHidingSpot() && !IsAttacking() && IsUsingSniperRifle() && IsUsingScope())
{
SecondaryAttack();
}
// check encounter spots
UpdatePeripheralVision();
// Update gamestate
if (m_bomber != NULL)
GetChatter()->SpottedBomber(GetBomber());
if (CanSeeLooseBomb())
GetChatter()->SpottedLooseBomb(ctrl->GetLooseBomb());
// Scenario interrupts
switch (ctrl->GetScenario())
{
case CCSBotManager::SCENARIO_DEFUSE_BOMB:
{
// flee if the bomb is ready to blow and we aren't defusing it or attacking and we know where the bomb is
// (aggressive players wait until its almost too late)
float gonnaBlowTime = 8.0f - (2.0f * GetProfile()->GetAggression());
// if we have a defuse kit, can wait longer
// if (m_bHasDefuser)
// gonnaBlowTime *= 0.66f;
if (!IsEscapingFromBomb() // we aren't already escaping the bomb
&& ctrl->IsBombPlanted() // is the bomb planted
&& GetGameState()->IsPlantedBombLocationKnown() // we know where the bomb is
&& ctrl->GetBombTimeLeft() < gonnaBlowTime // is the bomb about to explode
&& !IsDefusingBomb() // we aren't defusing the bomb
&& !IsAttacking()) // we aren't in the midst of a firefight
{
EscapeFromBomb();
break;
}
break;
}
case CCSBotManager::SCENARIO_RESCUE_HOSTAGES:
{
#if 0
if (m_iTeam == CT)
{
UpdateHostageEscortCount();
}
else
{
// Terrorists have imperfect information on status of hostages
CSGameState::ValidateStatusType status = GetGameState()->ValidateHostagePositions();
if (status & CSGameState::HOSTAGES_ALL_GONE)
{
GetChatter()->HostagesTaken();
Idle();
}
else if (status & CSGameState::HOSTAGE_GONE)
{
GetGameState()->HostageWasTaken();
Idle();
}
}
break;
#endif
}
}
// Follow nearby humans if our co-op is high and we have nothing else to do
// If we were just following someone, don't auto-follow again for a short while to
// give us a chance to do something else.
const float earliestAutoFollowTime = 5.0f;
const float minAutoFollowTeamwork = 0.4f;
if (ctrl->GetElapsedRoundTime() > earliestAutoFollowTime
&& GetProfile()->GetTeamwork() > minAutoFollowTeamwork
&& CanAutoFollow()
&& !IsBusy()
&& !IsFollowing()
&& !GetGameState()->IsAtPlantedBombsite())
{
// chance of following is proportional to teamwork attribute
if (GetProfile()->GetTeamwork() > RANDOM_FLOAT(0.0f, 1.0f))
{
CBasePlayer *leader = GetClosestVisibleHumanFriend();
#if 0
if (leader != NULL && leader->IsAutoFollowAllowed())
{
// count how many bots are already following this player
const float maxFollowCount = 2;
if (GetBotFollowCount(leader) < maxFollowCount)
{
const float autoFollowRange = 300.0f;
if ((leader->pev->origin - pev->origin).IsLengthLessThan(autoFollowRange))
{
CNavArea *leaderArea = TheNavAreaGrid.GetNavArea(&leader->pev->origin);
if (leaderArea != NULL)
{
PathCost cost(this, FASTEST_ROUTE);
float travelRange = NavAreaTravelDistance(GetLastKnownArea(), leaderArea, cost);
if (/*travelRange >= 0.0f &&*/ travelRange < autoFollowRange)
{
// follow this human
Follow(leader);
PrintIfWatched("Auto-Following %s\n", STRING(leader->pev->netname));
if (g_pGameRules->IsCareer())
{
GetChatter()->Say("FollowingCommander", 10.0f);
}
else
{
GetChatter()->Say("FollowingSir", 10.0f);
}
}
}
}
}
}
#endif
}
else
{
// we decided not to follow, don't re-check for a duration
m_allowAutoFollowTime = gpGlobals->time + 15.0f + (1.0f - GetProfile()->GetTeamwork()) * 30.0f;
}
}
if (IsFollowing())
{
// if we are following someone, make sure they are still alive
CBaseEntity *leader = m_leader;
if (leader == NULL || !leader->IsAlive())
{
StopFollowing();
}
// decide whether to continue following them
const float highTeamwork = 0.85f;
if (GetProfile()->GetTeamwork() < highTeamwork)
{
float minFollowDuration = 15.0f;
if (GetFollowDuration() > minFollowDuration + 40.0f * GetProfile()->GetTeamwork())
{
// we are bored of following our leader
StopFollowing();
PrintIfWatched("Stopping following - bored\n");
}
}
}
else
{
if (GetMorale() < NEUTRAL && IsSafe() && GetSafeTimeRemaining() < 2.0f && IsHunting())
{
if (GetMorale() * -40.0 > RANDOM_FLOAT(0.0f, 100.0f))
{
if (ctrl->IsOnOffense(this) || !ctrl->IsDefenseRushing())
{
SetDisposition(OPPORTUNITY_FIRE);
Hide(m_lastKnownArea, RANDOM_FLOAT(3.0f, 15.0f));
GetChatter()->Say("WaitingHere");
}
}
}
}
// Execute state machine
if (m_isAttacking)
{
m_attackState.OnUpdate(this);
}
else
{
m_state->OnUpdate(this);
}
if (m_isWaitingToTossGrenade)
{
ResetStuckMonitor();
ClearMovement();
}
#if 0
// don't move while reloading unless we see an enemy
if (IsReloading() && !m_isEnemyVisible)
{
ResetStuckMonitor();
ClearMovement();
}
#endif
// if we get too far ahead of the hostages we are escorting, wait for them
if (!IsAttacking() && m_inhibitWaitingForHostageTimer.IsElapsed())
{
const float waitForHostageRange = 500.0f;
if (GetTask() == RESCUE_HOSTAGES && GetRangeToFarthestEscortedHostage() > waitForHostageRange)
{
if (!m_isWaitingForHostage)
{
// just started waiting
m_isWaitingForHostage = true;
m_waitForHostageTimer.Start(10.0f);
}
else
{
// we've been waiting
if (m_waitForHostageTimer.IsElapsed())
{
// give up waiting for awhile
m_isWaitingForHostage = false;
m_inhibitWaitingForHostageTimer.Start(3.0f);
}
else
{
// keep waiting
ResetStuckMonitor();
ClearMovement();
}
}
}
}
// remember our prior safe time status
m_wasSafe = IsSafe();
}

1010
dlls/bot/cs_bot_vision.cpp Normal file

File diff suppressed because it is too large Load Diff

901
dlls/bot/cs_bot_weapon.cpp Normal file
View File

@ -0,0 +1,901 @@
#include "bot_common.h"
// Fire our active weapon towards our current enemy
// NOTE: Aiming our weapon is handled in RunBotUpkeep()
void CCSBot::FireWeaponAtEnemy()
{
CBasePlayer *enemy = GetEnemy();
if (enemy == NULL)
{
StopRapidFire();
return;
}
if (IsUsingSniperRifle())
{
// if we're using a sniper rifle, don't fire until we are standing still, are zoomed in, and not rapidly moving our view
if (!IsNotMoving())
{
return;
}
}
if (gpGlobals->time > m_fireWeaponTimestamp && GetTimeSinceAcquiredCurrentEnemy() >= GetProfile()->GetAttackDelay() && GetTimeSinceAcquiredCurrentEnemy() >= GetSurpriseDelay())
{
ClearSurpriseDelay();
if (!(IsRecognizedEnemyProtectedByShield() && IsPlayerFacingMe(enemy)) // dont shoot at enemies behind shields
&& !IsActiveWeaponReloading()
&& !IsActiveWeaponClipEmpty()
&& IsEnemyVisible())
{
// we have a clear shot - pull trigger if we are aiming at enemy
Vector2D toAimSpot = (m_aimSpot - pev->origin).Make2D();
float rangeToEnemy = toAimSpot.NormalizeInPlace();
const float halfPI = (M_PI / 180.0f);
float yaw = pev->v_angle[ YAW ] * halfPI;
Vector2D dir(cos(yaw), sin(yaw));
float onTarget = DotProduct(toAimSpot, dir);
// aim more precisely with a sniper rifle
// because rifles' bullets spray, dont have to be very precise
const float halfSize = (IsUsingSniperRifle()) ? HalfHumanWidth : 2.0f * HalfHumanWidth;
// aiming tolerance depends on how close the target is - closer targets subtend larger angles
float aimTolerance = cos(atan(halfSize / rangeToEnemy));
if (onTarget > aimTolerance)
{
bool doAttack;
// if friendly fire is on, don't fire if a teammate is blocking our line of fire
if (TheCSBots()->AllowFriendlyFireDamage())
{
if (IsFriendInLineOfFire())
doAttack = false;
else
doAttack = true;
}
else
{
// fire freely
doAttack = true;
}
if (doAttack)
{
// if we are using a knife, only swing it if we're close
if (IsUsingKnife())
{
const float knifeRange = 75.0f; // 50.0f
if (rangeToEnemy < knifeRange)
{
// since we've given ourselves away - run!
ForceRun(5.0f);
// if our prey is facing away, backstab him!
if (!IsPlayerFacingMe(enemy))
{
SecondaryAttack();
}
else
{
// randomly choose primary and secondary attacks with knife
const float knifeStabChance = 33.3f;
if (RANDOM_FLOAT(0, 100) < knifeStabChance)
SecondaryAttack();
else
PrimaryAttack();
}
}
}
else
{
PrimaryAttack();
}
}
if (IsUsingPistol())
{
// high-skill bots fire their pistols quickly at close range
const float closePistolRange = 999999.9f;
if (GetProfile()->GetSkill() > 0.75f && rangeToEnemy < closePistolRange)
{
StartRapidFire();
// fire as fast as possible
m_fireWeaponTimestamp = 0.0f;
}
else
{
// fire somewhat quickly
m_fireWeaponTimestamp = RANDOM_FLOAT(0.15f, 0.4f);
}
}
// not using a pistol
else
{
const float sprayRange = 400.0f;
if (GetProfile()->GetSkill() < 0.5f || rangeToEnemy < sprayRange || IsUsingMachinegun())
{
// spray 'n pray if enemy is close, or we're not that good, or we're using the big machinegun
m_fireWeaponTimestamp = 0.0f;
}
else
{
const float distantTargetRange = 800.0f;
if (!IsUsingSniperRifle() && rangeToEnemy > distantTargetRange)
{
// if very far away, fire slowly for better accuracy
m_fireWeaponTimestamp = RANDOM_FLOAT(0.3f, 0.7f);
}
else
{
// fire short bursts for accuracy
m_fireWeaponTimestamp = RANDOM_FLOAT(0.15f, 0.5f); // 0.15f, 0.25f
}
}
}
// subtract system latency
m_fireWeaponTimestamp -= g_flBotFullThinkInterval;
m_fireWeaponTimestamp += gpGlobals->time;
}
}
}
}
// Set the current aim offset using given accuracy (1.0 = perfect aim, 0.0f = terrible aim)
void CCSBot::SetAimOffset(float accuracy)
{
// if our accuracy is less than perfect, it will improve as we "focus in" while not rotating our view
if (accuracy < 1.0f)
{
// if we moved our view, reset our "focus" mechanism
if (IsViewMoving(100.0f))
{
m_aimSpreadTimestamp = gpGlobals->time;
}
// focusTime is the time it takes for a bot to "focus in" for very good aim, from 2 to 5 seconds
const float focusTime = Q_max(5.0f * (1.0f - accuracy), 2.0f);
float focusInterval = gpGlobals->time - m_aimSpreadTimestamp;
float focusAccuracy = focusInterval / focusTime;
// limit how much "focus" will help
const float maxFocusAccuracy = 0.75f;
if (focusAccuracy > maxFocusAccuracy)
focusAccuracy = maxFocusAccuracy;
accuracy = Q_max(accuracy, focusAccuracy);
}
PrintIfWatched("Accuracy = %4.3f\n", accuracy);
float range = (m_lastEnemyPosition - pev->origin).Length();
const float maxOffset = range * 0.1;
float error = maxOffset * (1 - accuracy);
m_aimOffsetGoal[0] = RANDOM_FLOAT(-error, error);
m_aimOffsetGoal[1] = RANDOM_FLOAT(-error, error);
m_aimOffsetGoal[2] = RANDOM_FLOAT(-error, error);
// define time when aim offset will automatically be updated
m_aimOffsetTimestamp = gpGlobals->time + RANDOM_FLOAT(0.25, 1);
}
// Wiggle aim error based on GetProfile()->GetSkill()
void CCSBot::UpdateAimOffset()
{
if (gpGlobals->time >= m_aimOffsetTimestamp)
{
SetAimOffset(GetProfile()->GetSkill());
}
// move current offset towards goal offset
Vector d = m_aimOffsetGoal - m_aimOffset;
const float stiffness = 0.1f;
m_aimOffset.x += stiffness * d.x;
m_aimOffset.y += stiffness * d.y;
m_aimOffset.z += stiffness * d.z;
}
// Change our zoom level to be appropriate for the given range.
// Return true if the zoom level changed.
bool CCSBot::AdjustZoom(float range)
{
bool adjustZoom = false;
if (IsUsingSniperRifle())
{
// NOTE: This must be less than sniperMinRange in AttackState
const float sniperZoomRange = 300.0f; //150.0f
const float sniperFarZoomRange = 1500.0f;
// if range is too close, don't zoom
if (range <= sniperZoomRange)
{
// zoom out
if (GetZoomLevel() != NO_ZOOM)
{
adjustZoom = true;
}
}
else if (range < sniperFarZoomRange)
{
// maintain low zoom
if (GetZoomLevel() != LOW_ZOOM)
{
adjustZoom = true;
}
}
else
{
// maintain high zoom
if (GetZoomLevel() != HIGH_ZOOM)
{
adjustZoom = true;
}
}
}
else
{
// zoom out
if (GetZoomLevel() != NO_ZOOM)
{
adjustZoom = true;
}
}
if (adjustZoom)
{
SecondaryAttack();
}
return adjustZoom;
}
// Return true if the given weapon is a sniper rifle
bool isSniperRifle(CBasePlayerItem *item)
{
switch (item->m_iId)
{
case WEAPON_CROSSBOW:
return true;
default:
return false;
}
}
bool CCSBot::IsUsingAWP() const
{
CBasePlayerWeapon *weapon = GetActiveWeapon();
if (weapon != NULL && weapon->m_iId == WEAPON_CROSSBOW)
return true;
return false;
}
// Returns true if we are using a weapon with a removable silencer
bool CCSBot::DoesActiveWeaponHaveSilencer() const
{
CBasePlayerWeapon *weapon = GetActiveWeapon();
if (weapon == NULL)
return false;
//if (weapon->m_iId == WEAPON_M4A1 || weapon->m_iId == WEAPON_USP)
// return true;
return false;
}
// Return true if we are using a sniper rifle
bool CCSBot::IsUsingSniperRifle() const
{
CBasePlayerWeapon *weapon = GetActiveWeapon();
if (weapon != NULL && isSniperRifle(weapon))
return true;
return false;
}
// Return true if we have a sniper rifle in our inventory
bool CCSBot::IsSniper() const
{
for (int i = 0; i < MAX_ITEM_TYPES; ++i)
{
CBasePlayerItem *item = m_rgpPlayerItems[i];
while (item != NULL)
{
if (isSniperRifle(item))
return true;
item = item->m_pNext;
}
}
return false;
}
// Return true if we are actively sniping (moving to sniper spot or settled in)
bool CCSBot::IsSniping() const
{
if (GetTask() == MOVE_TO_SNIPER_SPOT || GetTask() == SNIPING)
return true;
return false;
}
// Return true if we are using a shotgun
bool CCSBot::IsUsingShotgun() const
{
CBasePlayerWeapon *weapon = GetActiveWeapon();
if (weapon == NULL)
return false;
if (weapon->m_iId == WEAPON_SHOTGUN)
return true;
return false;
}
// Returns true if using the big 'ol machinegun
bool CCSBot::IsUsingMachinegun() const
{
CBasePlayerWeapon *weapon = GetActiveWeapon();
if (weapon != NULL && weapon->m_iId == WEAPON_MP5)
return true;
return false;
}
// Return true if primary weapon doesn't exist or is totally out of ammo
bool CCSBot::IsPrimaryWeaponEmpty() const
{
CBasePlayerWeapon *weapon = static_cast<CBasePlayerWeapon *>(m_rgpPlayerItems[ 2 ]);
if (weapon == NULL)
return true;
// check if gun has any ammo left
if (HasAnyAmmo(weapon))
return false;
return true;
}
// Return true if pistol doesn't exist or is totally out of ammo
bool CCSBot::IsPistolEmpty() const
{
CBasePlayerWeapon *weapon = static_cast<CBasePlayerWeapon *>(m_rgpPlayerItems[ 1 ]);
if (weapon == NULL)
return true;
// check if gun has any ammo left
if (HasAnyAmmo(weapon))
{
return false;
}
return true;
}
// Equip the given item
bool CCSBot::DoEquip(CBasePlayerWeapon *gun)
{
if (gun == NULL)
return false;
// check if weapon has any ammo left
if (!HasAnyAmmo(gun))
return false;
// equip it
SelectItem(STRING(gun->pev->classname));
m_equipTimer.Start();
return true;
}
// throttle how often equipping is allowed
const float minEquipInterval = 5.0f;
// Equip the best weapon we are carrying that has ammo
void CCSBot::EquipBestWeapon(bool mustEquip)
{
// throttle how often equipping is allowed
if (!mustEquip && m_equipTimer.GetElapsedTime() < minEquipInterval)
return;
CCSBotManager *ctrl = TheCSBots();
CBasePlayerWeapon *primary = static_cast<CBasePlayerWeapon *>(m_rgpPlayerItems[ 2 ]);
#if 0
if (primary != NULL)
{
WeaponClassType weaponClass = WeaponIDToWeaponClass(primary->m_iId);
if ((ctrl->AllowShotguns() && weaponClass == WEAPONCLASS_SHOTGUN)
|| (ctrl->AllowMachineGuns() && weaponClass == WEAPONCLASS_MACHINEGUN)
|| (ctrl->AllowRifles() && weaponClass == WEAPONCLASS_RIFLE)
|| (ctrl->AllowSnipers() && weaponClass == WEAPONCLASS_SNIPERRIFLE)
|| (ctrl->AllowSubMachineGuns() && weaponClass == WEAPONCLASS_SUBMACHINEGUN)
|| (ctrl->AllowTacticalShield() && primary->m_iId == WEAPON_SHIELDGUN))
{
if (DoEquip(primary))
return;
}
}
if (ctrl->AllowPistols())
{
if (DoEquip(static_cast<CBasePlayerWeapon *>(m_rgpPlayerItems[ 1 ])))
return;
}
#endif
// always have a knife
EquipKnife();
}
// Equip our pistol
void CCSBot::EquipPistol()
{
// throttle how often equipping is allowed
if (m_equipTimer.GetElapsedTime() < minEquipInterval)
return;
if (TheCSBots()->AllowPistols() && !IsUsingPistol())
{
CBasePlayerWeapon *pistol = static_cast<CBasePlayerWeapon *>(m_rgpPlayerItems[ 1 ]);
DoEquip(pistol);
}
}
// Equip the knife
void CCSBot::EquipKnife()
{
if (!IsUsingKnife())
{
CBasePlayerWeapon *knife = static_cast<CBasePlayerWeapon *>(m_rgpPlayerItems[ 0 ]);
if (knife != NULL)
{
SelectItem(STRING(knife->pev->classname));
}
}
}
// Return true if we have a grenade in our inventory
bool CCSBot::HasGrenade() const
{
CBasePlayerWeapon *grenade = static_cast<CBasePlayerWeapon *>(m_rgpPlayerItems[ 4 ]);
return grenade != NULL;
}
// Equip a grenade, return false if we cant
bool CCSBot::EquipGrenade(bool noSmoke)
{
// snipers don't use grenades
if (IsSniper())
return false;
if (IsUsingGrenade())
return true;
if (HasGrenade())
{
CBasePlayerWeapon *grenade = static_cast<CBasePlayerWeapon *>(m_rgpPlayerItems[ 4 ]);
if (grenade != NULL)
{
//if (noSmoke && grenade->m_iId == WEAPON_SMOKEGRENADE)
//return false;
SelectItem(STRING(grenade->pev->classname));
return true;
}
}
return false;
}
// Returns true if we have knife equipped
bool CCSBot::IsUsingKnife() const
{
CBasePlayerWeapon *weapon = GetActiveWeapon();
if (weapon != NULL && weapon->m_iId == WEAPON_CROWBAR)
return true;
return false;
}
// Returns true if we have pistol equipped
bool CCSBot::IsUsingPistol() const
{
CBasePlayerWeapon *weapon = GetActiveWeapon();
//if (weapon != NULL && weapon->IsPistol())
// return true;
return false;
}
// Returns true if we have a grenade equipped
bool CCSBot::IsUsingGrenade() const
{
CBasePlayerWeapon *weapon = GetActiveWeapon();
if (weapon == NULL)
return false;
if (weapon->m_iId == WEAPON_HANDGRENADE )
return true;
return false;
}
bool CCSBot::IsUsingHEGrenade() const
{
CBasePlayerWeapon *weapon = GetActiveWeapon();
if (weapon != NULL && weapon->m_iId == WEAPON_HANDGRENADE)
return true;
return false;
}
// Begin the process of throwing the grenade
void CCSBot::ThrowGrenade(const Vector *target)
{
if (IsUsingGrenade() && !m_isWaitingToTossGrenade)
{
const float angleTolerance = 1.0f;
SetLookAt("GrenadeThrow", target, PRIORITY_UNINTERRUPTABLE, 3.0f, false, angleTolerance);
m_isWaitingToTossGrenade = true;
m_tossGrenadeTimer.Start(3.0f);
}
}
// Find spot to throw grenade ahead of us and "around the corner" along our path
bool CCSBot::FindGrenadeTossPathTarget(Vector *pos)
{
if (!HasPath())
return false;
// find farthest point we can see on the path
int i;
for (i = m_pathIndex; i < m_pathLength; ++i)
{
if (!FVisible(m_path[i].pos + Vector(0, 0, HalfHumanHeight)))
break;
}
if (i == m_pathIndex)
return false;
// find exact spot where we lose sight
Vector dir = m_path[i].pos - m_path[i - 1].pos;
float length = dir.NormalizeInPlace();
const float inc = 25.0f;
Vector p;
Vector visibleSpot = m_path[i - 1].pos;
for (float t = 0.0f; t < length; t += inc)
{
p = m_path[i - 1].pos + t * dir;
p.z += HalfHumanHeight;
if (!FVisible(p))
break;
visibleSpot = p;
}
// massage the location a bit
visibleSpot.z += 10.0f;
const float bufferRange = 50.0f;
TraceResult result;
Vector check;
// check +X
check = visibleSpot + Vector(999.9f, 0, 0);
UTIL_TraceLine(visibleSpot, check, dont_ignore_monsters, ignore_glass, ENT(pev), &result);
if (result.flFraction < 1.0f)
{
float range = result.vecEndPos.x - visibleSpot.x;
if (range < bufferRange)
{
visibleSpot.x = result.vecEndPos.x - bufferRange;
}
}
// check -X
check = visibleSpot + Vector(-999.9f, 0, 0);
UTIL_TraceLine(visibleSpot, check, dont_ignore_monsters, ignore_glass, ENT(pev), &result);
if (result.flFraction < 1.0f)
{
float range = visibleSpot.x - result.vecEndPos.x;
if (range < bufferRange)
{
visibleSpot.x = result.vecEndPos.x + bufferRange;
}
}
// check +Y
check = visibleSpot + Vector(0, 999.9f, 0);
UTIL_TraceLine(visibleSpot, check, dont_ignore_monsters, ignore_glass, ENT(pev), &result);
if (result.flFraction < 1.0f)
{
float range = result.vecEndPos.y - visibleSpot.y;
if (range < bufferRange)
{
visibleSpot.y = result.vecEndPos.y - bufferRange;
}
}
// check -Y
check = visibleSpot + Vector(0, -999.9f, 0);
UTIL_TraceLine(visibleSpot, check, dont_ignore_monsters, ignore_glass, ENT(pev), &result);
if (result.flFraction < 1.0f)
{
float range = visibleSpot.y - result.vecEndPos.y;
if (range < bufferRange)
{
visibleSpot.y = result.vecEndPos.y + bufferRange;
}
}
*pos = visibleSpot;
return true;
}
// Reload our weapon if we must
void CCSBot::ReloadCheck()
{
const float safeReloadWaitTime = 3.0f;
const float reloadAmmoRatio = 0.6f;
// don't bother to reload if there are no enemies left
if (GetEnemiesRemaining() == 0)
return;
if (IsDefusingBomb() || IsActiveWeaponReloading())
return;
if (IsActiveWeaponClipEmpty())
{
#if 0
// high-skill players switch to pistol instead of reloading during combat
if (GetProfile()->GetSkill() > 0.5f && IsAttacking())
{
if (!GetActiveWeapon()->IsPistol() && !IsPistolEmpty())
{
// switch to pistol instead of reloading
EquipPistol();
return;
}
}
#endif
}
else if (GetTimeSinceLastSawEnemy() > safeReloadWaitTime && GetActiveWeaponAmmoRatio() <= reloadAmmoRatio)
{
// high-skill players use all their ammo and switch to pistol instead of reloading during combat
if (GetProfile()->GetSkill() > 0.5f && IsAttacking())
return;
}
else
{
// do not need to reload
return;
}
// don't reload the AWP until it is totally out of ammo
if (IsUsingAWP() && !IsActiveWeaponClipEmpty())
return;
Reload();
// move to cover to reload if there are enemies nearby
if (GetNearbyEnemyCount())
{
// avoid enemies while reloading (above 0.75 skill always hide to reload)
const float hideChance = 25.0f + 100.0f * GetProfile()->GetSkill();
if (!IsHiding() && RANDOM_FLOAT(0.0f, 100.0f) < hideChance)
{
const float safeTime = 5.0f;
if (GetTimeSinceLastSawEnemy() < safeTime)
{
PrintIfWatched("Retreating to a safe spot to reload!\n");
const Vector *spot = FindNearbyRetreatSpot(this, 1000.0f);
if (spot != NULL)
{
// ignore enemies for a second to give us time to hide
// reaching our hiding spot clears our disposition
IgnoreEnemies(10.0f);
Run();
StandUp();
Hide(spot, 0.0f);
}
}
}
}
}
// Silence/unsilence our weapon if we must
void CCSBot::SilencerCheck()
{
// longer than reload check because reloading should take precedence
const float safeSilencerWaitTime = 3.5f;
if (IsDefusingBomb() || IsActiveWeaponReloading() || IsAttacking())
return;
// M4A1 and USP are the only weapons with removable silencers
if (!DoesActiveWeaponHaveSilencer())
return;
if (GetTimeSinceLastSawEnemy() < safeSilencerWaitTime)
return;
// don't touch the silencer if there are enemies nearby
if (GetNearbyEnemyCount() == 0)
{
CBasePlayerWeapon *myGun = GetActiveWeapon();
if (myGun == NULL)
return;
//bool isSilencerOn = (myGun->m_iWeaponState & (WPNSTATE_M4A1_SILENCED | WPNSTATE_USP_SILENCED)) != 0;
if (myGun->m_flNextSecondaryAttack >= gpGlobals->time)
return;
// equip silencer if we want to and we don't have a shield.
/*if (isSilencerOn != (GetProfile()->PrefersSilencer() || GetProfile()->GetSkill() > 0.7f) && !HasShield())
{
PrintIfWatched("%s silencer!\n", (isSilencerOn) ? "Unequipping" : "Equipping");
myGun->SecondaryAttack();
}*/
}
}
// Invoked when in contact with a CWeaponBox
void CCSBot::OnTouchingWeapon(CWeaponBox *box)
{
CBasePlayerItem *droppedGun = dynamic_cast<CBasePlayerItem *>(box->m_rgpPlayerItems[ 2 ]);
// right now we only care about primary weapons on the ground
if (droppedGun != NULL)
{
CBasePlayerWeapon *myGun = dynamic_cast<CBasePlayerWeapon *>(m_rgpPlayerItems[ 2 ]);
// if the gun on the ground is the same one we have, dont bother
if (myGun != NULL && droppedGun->m_iId != myGun->m_iId)
{
// if we don't have a weapon preference, give up
if (GetProfile()->HasPrimaryPreference())
{
// don't change weapons if we've seen enemies recently
const float safeTime = 2.5f;
if (GetTimeSinceLastSawEnemy() >= safeTime)
{
// we have a primary weapon - drop it if the one on the ground is better
for (int i = 0; i < GetProfile()->GetWeaponPreferenceCount(); ++i)
{
int prefID = GetProfile()->GetWeaponPreference(i);
//if (!IsPrimaryWeapon(prefID))
// continue;
// if the gun we are using is more desirable, give up
if (prefID == myGun->m_iId)
break;
if (prefID == droppedGun->m_iId)
{
// the gun on the ground is better than the one we have - drop our gun
//DropPrimary(this);
break;
}
}
}
}
}
}
}
// Return true if a friend is in our weapon's way
// TODO: Check more rays for safety.
bool CCSBot::IsFriendInLineOfFire()
{
UTIL_MakeVectors(pev->punchangle + pev->v_angle);
// compute the unit vector along our view
Vector aimDir = gpGlobals->v_forward;
Vector target = GetGunPosition();
// trace the bullet's path
TraceResult result;
UTIL_TraceLine(GetGunPosition(), target + 10000.0f * aimDir, dont_ignore_monsters, ignore_glass, ENT(pev), &result);
if (result.pHit != NULL)
{
CBaseEntity *victim = CBaseEntity::Instance(result.pHit);
if (victim != NULL && victim->IsPlayer() && victim->IsAlive())
{
CBasePlayer *player = static_cast<CBasePlayer *>(victim);
// if (player->m_iTeam == m_iTeam)
// return true;
}
}
return false;
}
// Return line-of-sight distance to obstacle along weapon fire ray
// TODO: Re-use this computation with IsFriendInLineOfFire()
float CCSBot::ComputeWeaponSightRange()
{
UTIL_MakeVectors(pev->punchangle + pev->v_angle);
// compute the unit vector along our view
Vector aimDir = gpGlobals->v_forward;
Vector target = GetGunPosition();
// trace the bullet's path
TraceResult result;
UTIL_TraceLine(GetGunPosition(), target + 10000.0f * aimDir, dont_ignore_monsters, ignore_glass, ENT(pev), &result);
return (GetGunPosition() - result.vecEndPos).Length();
}

695
dlls/bot/cs_gamestate.cpp Normal file
View File

@ -0,0 +1,695 @@
#include "bot_common.h"
CSGameState::CSGameState(CCSBot *owner)
{
m_owner = owner;
m_isRoundOver = false;
// m_bombState = MOVING;
m_lastSawBomber.Invalidate();
m_lastSawLooseBomb.Invalidate();
m_validateInterval.Invalidate();
// m_isPlantedBombPosKnown = false;
// m_plantedBombsite = UNKNOWN;
// m_bombsiteCount = 0;
// m_bombsiteSearchIndex = 0;
#if 0
for (int i = 0; i < MAX_HOSTAGES; ++i)
{
HostageInfo *info = &m_hostage[i];
info->hostage = NULL;
info->knownPos = Vector(0, 0, 0);
info->isValid = false;
info->isAlive = false;
info->isFree = true;
}
#endif
}
// Reset at round start
void CSGameState::Reset()
{
int i;
CCSBotManager *ctrl = TheCSBots();
m_isRoundOver = false;
// bomb
m_bombState = MOVING;
m_lastSawBomber.Invalidate();
m_lastSawLooseBomb.Invalidate();
#if 0
m_bombsiteCount = ctrl->GetZoneCount();
m_isPlantedBombPosKnown = false;
m_plantedBombsite = UNKNOWN;
for (i = 0; i < m_bombsiteCount; ++i)
{
m_isBombsiteClear[i] = false;
m_bombsiteSearchOrder[i] = i;
}
// shuffle the bombsite search order
// allows T's to plant at random site, and CT's to search in a random order
// NOTE: VS6 std::random_shuffle() doesn't work well with an array of two elements (most maps)
for (i = 0; i < m_bombsiteCount; ++i)
{
int swap = m_bombsiteSearchOrder[i];
int rnd = RANDOM_LONG(i, m_bombsiteCount - 1);
m_bombsiteSearchOrder[i] = m_bombsiteSearchOrder[rnd];
m_bombsiteSearchOrder[rnd] = swap;
}
m_bombsiteSearchIndex = 0;
InitializeHostageInfo();
#endif
}
// Update game state based on events we have received
void CSGameState::OnEvent(GameEventType event, CBaseEntity *entity, CBaseEntity *other)
{
#if 0
switch (event)
{
case EVENT_BOMB_PLANTED:
SetBombState(PLANTED);
if (m_owner->m_iTeam == TERRORIST && other != NULL)
{
UpdatePlantedBomb(&other->pev->origin);
}
break;
case EVENT_BOMB_DEFUSED:
SetBombState(DEFUSED);
break;
case EVENT_BOMB_EXPLODED:
SetBombState(EXPLODED);
break;
case EVENT_ALL_HOSTAGES_RESCUED:
m_allHostagesRescued = true;
break;
case EVENT_TERRORISTS_WIN:
case EVENT_CTS_WIN:
case EVENT_ROUND_DRAW:
m_isRoundOver = true;
break;
default:
break;
}
#endif
}
// True if round has been won or lost (but not yet reset)
bool CSGameState::IsRoundOver() const
{
return m_isRoundOver;
}
void CSGameState::SetBombState(BombState state)
{
// if state changed, reset "last seen" timestamps
if (m_bombState != state)
{
m_bombState = state;
}
}
void CSGameState::UpdateLooseBomb(const Vector *pos)
{
m_looseBombPos = *pos;
m_lastSawLooseBomb.Reset();
// we saw the loose bomb, update our state
SetBombState(LOOSE);
}
float CSGameState::TimeSinceLastSawLooseBomb() const
{
return m_lastSawLooseBomb.GetElapsedTime();
}
bool CSGameState::IsLooseBombLocationKnown() const
{
if (m_bombState != LOOSE)
return false;
return (m_lastSawLooseBomb.HasStarted()) ? true : false;
}
void CSGameState::UpdateBomber(const Vector *pos)
{
m_bomberPos = *pos;
m_lastSawBomber.Reset();
// we saw the bomber, update our state
SetBombState(MOVING);
}
float CSGameState::TimeSinceLastSawBomber() const
{
return m_lastSawBomber.GetElapsedTime();
}
bool CSGameState::IsPlantedBombLocationKnown() const
{
// if (m_bombState != PLANTED)
// return false;
return false;//m_isPlantedBombPosKnown;
}
// Return the zone index of the planted bombsite, or UNKNOWN
int CSGameState::GetPlantedBombsite() const
{
//if (m_bombState != PLANTED)
return UNKNOWN;
//return m_plantedBombsite;
}
// Return true if we are currently in the bombsite where the bomb is planted
bool CSGameState::IsAtPlantedBombsite() const
{
#if 0
if (m_bombState != PLANTED)
return false;
CCSBotManager *ctrl = TheCSBots();
const CCSBotManager::Zone *zone = ctrl->GetClosestZone(&m_owner->pev->origin);
if (zone != NULL)
{
return (m_plantedBombsite == zone->m_index);
}
#endif
return false;
}
// Return the zone index of the next bombsite to search
int CSGameState::GetNextBombsiteToSearch()
{
// if (m_bombsiteCount <= 0)
return 0;
#if 0
int i;
// return next non-cleared bombsite index
for (i = m_bombsiteSearchIndex; i < m_bombsiteCount; ++i)
{
int z = m_bombsiteSearchOrder[i];
if (!m_isBombsiteClear[z])
{
m_bombsiteSearchIndex = i;
return z;
}
}
// all the bombsites are clear, someone must have been mistaken - start search over
for (i = 0; i < m_bombsiteCount; ++i)
{
m_isBombsiteClear[i] = false;
}
m_bombsiteSearchIndex = 0;
return GetNextBombsiteToSearch();
#endif
}
// Returns position of bomb in its various states (moving, loose, planted),
// or NULL if we don't know where the bomb is
const Vector *CSGameState::GetBombPosition() const
{
#if 0
switch (m_bombState)
{
case MOVING:
{
if (!m_lastSawBomber.HasStarted())
return NULL;
return &m_bomberPos;
}
case LOOSE:
{
if (IsLooseBombLocationKnown())
return &m_looseBombPos;
return NULL;
}
case PLANTED:
{
if (IsPlantedBombLocationKnown())
return &m_plantedBombPos;
return NULL;
}
}
#endif
return NULL;
}
// We see the planted bomb at 'pos'
void CSGameState::UpdatePlantedBomb(const Vector *pos)
{
#if 0
CCSBotManager *ctrl = TheCSBots();
const CCSBotManager::Zone *zone = ctrl->GetClosestZone(pos);
if (zone == NULL)
{
CONSOLE_ECHO("ERROR: Bomb planted outside of a zone!\n");
m_plantedBombsite = UNKNOWN;
}
else
{
m_plantedBombsite = zone->m_index;
}
m_plantedBombPos = *pos;
m_isPlantedBombPosKnown = true;
SetBombState(PLANTED);
#endif
}
// Someone told us where the bomb is planted
void CSGameState::MarkBombsiteAsPlanted(int zoneIndex)
{
#if 0
m_plantedBombsite = zoneIndex;
SetBombState(PLANTED);
#endif
}
// Someone told us a bombsite is clear
void CSGameState::ClearBombsite(int zoneIndex)
{
#if 0
if (zoneIndex >= 0 && zoneIndex < m_bombsiteCount)
m_isBombsiteClear[zoneIndex] = true;
#endif
}
bool CSGameState::IsBombsiteClear(int zoneIndex) const
{
#if 0
if (zoneIndex >= 0 && zoneIndex < m_bombsiteCount)
return m_isBombsiteClear[zoneIndex];
#endif
return false;
}
#if 0
void CSGameState::InitializeHostageInfo()
{
m_hostageCount = 0;
m_allHostagesRescued = 0;
m_haveSomeHostagesBeenTaken = 0;
CBaseEntity *hostage = NULL;
while ((hostage = UTIL_FindEntityByClassname(hostage, "hostage_entity")) != NULL)
{
if (m_hostageCount >= MAX_HOSTAGES)
break;
if (hostage->pev->takedamage != DAMAGE_YES)
continue;
m_hostage[m_hostageCount].hostage = static_cast<CHostage *>(hostage);
m_hostage[m_hostageCount].knownPos = hostage->pev->origin;
m_hostage[m_hostageCount].isValid = true;
m_hostage[m_hostageCount].isAlive = true;
m_hostage[m_hostageCount].isFree = true;
++m_hostageCount;
}
}
#endif
// Return the closest free and live hostage
// If we are a CT this information is perfect.
// Otherwise, this is based on our individual memory of the game state.
// If NULL is returned, we don't think there are any hostages left, or we dont know where they are.
// NOTE: a T can remember a hostage who has died. knowPos will be filled in, but NULL will be
// returned, since CHostages get deleted when they die.
#if 0
CHostage *CSGameState::GetNearestFreeHostage(Vector *knowPos) const
{
if (m_owner == NULL)
return NULL;
CNavArea *startArea = m_owner->GetLastKnownArea();
if (startArea == NULL)
return NULL;
CHostage *close = NULL;
const Vector *closePos = NULL;
float closeDistance = 9999999999.9f;
for (int i = 0; i < m_hostageCount; ++i)
{
CHostage *hostage = m_hostage[i].hostage;
const Vector *hostagePos = NULL;
if (m_owner->m_iTeam == CT)
{
// we know exactly where the hostages are, and if they are alive
if (!m_hostage[i].hostage || !m_hostage[i].hostage->IsValid())
continue;
if (m_hostage[i].hostage->IsFollowingSomeone())
continue;
hostagePos = &hostage->pev->origin;
}
else
{
// use our memory of where we think the hostages are
if (m_hostage[i].isValid == false)
continue;
hostagePos = &m_hostage[i].knownPos;
}
CNavArea *hostageArea = TheNavAreaGrid.GetNearestNavArea(hostagePos);
if (hostageArea != NULL)
{
ShortestPathCost pc;
float travelDistance = NavAreaTravelDistance(startArea, hostageArea, pc);
if (travelDistance >= 0.0f && travelDistance < closeDistance)
{
closePos = hostagePos;
closeDistance = travelDistance;
close = hostage;
}
}
}
// return where we think the hostage is
if (knowPos != NULL && closePos != NULL)
{
knowPos = const_cast<Vector *>(closePos);
}
return close;
}
#endif
// Return the location of a "free" hostage, or NULL if we dont know of any
#if 0
const Vector *CSGameState::GetRandomFreeHostagePosition()
{
// TODO: use static?
const Vector *freePos[MAX_HOSTAGES];
int freeCount = 0;
if (m_owner == NULL)
return NULL;
for (int i = 0; i < m_hostageCount; ++i)
{
const HostageInfo *info = &m_hostage[i];
const Vector *hostagePos = NULL;
if (m_owner->m_iTeam == CT)
{
// we know exactly where the hostages are, and if they are alive
if (!info->hostage || !info->hostage->IsAlive())
continue;
// escorted hostages are not "free"
if (info->hostage->IsFollowingSomeone())
continue;
freePos[ freeCount++ ] = &info->hostage->pev->origin;
}
else
{
// use our memory of where we think the hostages are
if (info->isValid == false)
continue;
freePos[ freeCount++ ] = &info->knownPos;
}
}
if (freeCount)
{
return freePos[RANDOM_LONG(0, freeCount - 1)];
}
return NULL;
}
#endif
// If we can see any of the positions where we think a hostage is, validate it
// Return status of any changes (a hostage died or was moved)
#if 0
CSGameState::ValidateStatusType CSGameState::ValidateHostagePositions()
{
// limit how often we validate
if (!m_validateInterval.IsElapsed())
return NO_CHANGE;
const float validateInterval = 0.5f;
m_validateInterval.Start(validateInterval);
// check the status of hostages
int status = NO_CHANGE;
int i;
int startValidCount = 0;
for (i = 0; i < m_hostageCount; ++i)
{
if (m_hostage[i].isValid)
++startValidCount;
}
for (i = 0; i < m_hostageCount; ++i)
{
HostageInfo *info = &m_hostage[i];
if (!info->hostage)
continue;
// if we can see a hostage, update our knowledge of it
if (m_owner->IsVisible(&info->hostage->pev->origin, CHECK_FOV))
{
if (info->hostage->pev->takedamage == DAMAGE_YES)
{
// live hostage
// if hostage is being escorted by a CT, we don't "see" it, we see the CT
if (info->hostage->IsFollowingSomeone())
{
info->isValid = false;
}
else
{
info->knownPos = info->hostage->pev->origin;
info->isValid = true;
}
}
else
{
// dead hostage
// if we thought it was alive, this is news to us
if (info->isAlive)
status |= HOSTAGE_DIED;
info->isAlive = false;
info->isValid = false;
}
continue;
}
// if we dont know where this hostage is, nothing to validate
if (!info->isValid)
continue;
// can't directly see this hostage
// check line of sight to where we think this hostage is, to see if we noticed that is has moved
if (m_owner->IsVisible(&info->knownPos, CHECK_FOV))
{
// we can see where we thought the hostage was - verify it is still there and alive
if (info->hostage->pev->takedamage != DAMAGE_YES)
{
// since we have line of sight to an invalid hostage, it must be dead
// discovered that hostage has been killed
status |= HOSTAGE_DIED;
info->isAlive = false;
info->isValid = false;
continue;
}
if (info->hostage->IsFollowingSomeone())
{
// discovered the hostage has been taken
status |= HOSTAGE_GONE;
info->isValid = false;
continue;
}
const float tolerance = 50.0f;
if ((info->hostage->pev->origin - info->knownPos).IsLengthGreaterThan(tolerance))
{
// discovered that hostage has been moved
status |= HOSTAGE_GONE;
info->isValid = false;
continue;
}
}
}
int endValidCount = 0;
for (i = 0; i < m_hostageCount; ++i)
{
if (m_hostage[i].isValid)
++endValidCount;
}
if (endValidCount == 0 && startValidCount > 0)
{
// we discovered all the hostages are gone
status &= ~HOSTAGE_GONE;
status |= HOSTAGES_ALL_GONE;
}
return (ValidateStatusType) status;
}
// Return the nearest visible free hostage
// Since we can actually see any hostage we return, we know its actual position
CHostage *CSGameState::GetNearestVisibleFreeHostage() const
{
CHostage *close = NULL;
float closeRangeSq = 999999999.9f;
float rangeSq;
Vector pos;
for (int i = 0; i < m_hostageCount; ++i)
{
const HostageInfo *info = &m_hostage[i];
if (!info->hostage)
continue;
// if the hostage is dead or rescued, its not free
if (info->hostage->pev->takedamage != DAMAGE_YES)
continue;
// if this hostage is following someone, its not free
if (info->hostage->IsFollowingSomeone())
continue;
// TODO: Use travel distance here
pos = info->hostage->pev->origin + Vector(0, 0, HumanHeight * 0.75f);
rangeSq = (pos - m_owner->pev->origin).LengthSquared();
if (rangeSq < closeRangeSq)
{
if (!m_owner->IsVisible(&pos))
continue;
close = info->hostage;
closeRangeSq = rangeSq;
}
}
return close;
}
// Return true if there are no free hostages
bool CSGameState::AreAllHostagesBeingRescued() const
{
// if the hostages have all been rescued, they are not being rescued any longer
if (m_allHostagesRescued)
return false;
bool isAllDead = true;
for (int i = 0; i < m_hostageCount; ++i)
{
const HostageInfo *info = &m_hostage[i];
if (m_owner->m_iTeam == CT)
{
// CT's have perfect knowledge via their radar
if (info->hostage != NULL && info->hostage->IsValid())
{
if (!info->hostage->IsFollowingSomeone())
return false;
isAllDead = false;
}
}
else
{
if (info->isValid && info->isAlive)
return false;
if (info->isAlive)
isAllDead = false;
}
}
// if all of the remaining hostages are dead, they arent being rescued
if (isAllDead)
return false;
return true;
}
// All hostages have been rescued or are dead
bool CSGameState::AreAllHostagesGone() const
{
if (m_allHostagesRescued)
return true;
// do we know that all the hostages are dead
for (int i = 0; i < m_hostageCount; ++i)
{
const HostageInfo *info = &m_hostage[i];
if (m_owner->m_iTeam == CT)
{
// CT's have perfect knowledge via their radar
if (info->hostage->IsAlive())// == DAMAGE_YES)
return false;
}
else
{
if (info->isValid && info->isAlive)
return false;
}
}
return true;
}
// Someone told us all the hostages are gone
void CSGameState::AllHostagesGone()
{
for (int i = 0; i < m_hostageCount; ++i)
m_hostage[i].isValid = false;
}
#endif

107
dlls/bot/cs_gamestate.h Normal file
View File

@ -0,0 +1,107 @@
/*
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* In addition, as a special exception, the author gives permission to
* link the code of this program with the Half-Life Game Engine ("HL
* Engine") and Modified Game Libraries ("MODs") developed by Valve,
* L.L.C ("Valve"). You must obey the GNU General Public License in all
* respects for all of the code used other than the HL Engine and MODs
* from Valve. If you modify this file, you may extend this exception
* to your version of the file, but you are not obligated to do so. If
* you do not wish to do so, delete this exception statement from your
* version.
*
*/
#ifndef CS_GAMESTATE_H
#define CS_GAMESTATE_H
#ifdef _WIN32
#pragma once
#endif
class CCSBot;
// This class represents the game state as known by a particular bot
class CSGameState
{
public:
CSGameState() {};
CSGameState(CCSBot *owner);
void Reset();
void OnEvent(GameEventType event, CBaseEntity *entity, CBaseEntity *other); // Event handling
bool IsRoundOver() const; // true if round has been won or lost (but not yet reset)
// bomb defuse scenario
enum BombState
{
MOVING, // being carried by a Terrorist
LOOSE, // loose on the ground somewhere
PLANTED, // planted and ticking
DEFUSED, // the bomb has been defused
EXPLODED, // the bomb has exploded
};
bool IsBombMoving() const { return (m_bombState == MOVING); }
bool IsBombLoose() const { return (m_bombState == LOOSE); }
bool IsBombPlanted() const { return (m_bombState == PLANTED); }
bool IsBombDefused() const { return (m_bombState == DEFUSED); }
bool IsBombExploded() const { return (m_bombState == EXPLODED); }
void UpdateLooseBomb(const Vector *pos); // we see the loose bomb
float TimeSinceLastSawLooseBomb() const; // how long has is been since we saw the loose bomb
bool IsLooseBombLocationKnown() const; // do we know where the loose bomb is
void UpdateBomber(const Vector *pos); // we see the bomber
float TimeSinceLastSawBomber() const; // how long has is been since we saw the bomber
void UpdatePlantedBomb(const Vector *pos); // we see the planted bomb
bool IsPlantedBombLocationKnown() const; // do we know where the bomb was planted
void MarkBombsiteAsPlanted(int zoneIndex); // mark bombsite as the location of the planted bomb
enum { UNKNOWN = -1 };
int GetPlantedBombsite() const; // return the zone index of the planted bombsite, or UNKNOWN
bool IsAtPlantedBombsite() const; // return true if we are currently in the bombsite where the bomb is planted
int GetNextBombsiteToSearch(); // return the zone index of the next bombsite to search
bool IsBombsiteClear(int zoneIndex) const; // return true if given bombsite has been cleared
void ClearBombsite(int zoneIndex); // mark bombsite as clear
const Vector *GetBombPosition() const; // return where we think the bomb is, or NULL if we don't know
// hostage rescue scenario
//enum ValidateStatusType:unsigned char //C++11 feature
private:
CCSBot *m_owner; // who owns this gamestate
bool m_isRoundOver; // true if round is over, but no yet reset
// bomb defuse scenario
void SetBombState(BombState state);
BombState GetBombState() { return m_bombState; }
BombState m_bombState; // what we think the bomb is doing
IntervalTimer m_lastSawBomber;
Vector m_bomberPos;
IntervalTimer m_lastSawLooseBomb;
Vector m_looseBombPos;
CountdownTimer m_validateInterval;
};
#endif // CS_GAMESTATE_H

535
dlls/bot/manager/bot.cpp Normal file
View File

@ -0,0 +1,535 @@
#include "bot/bot_common.h"
/*
* Globals initialization
*/
// 30 times per second, just like human clients
float g_flBotCommandInterval = 1.0 / 30.0;
// full AI only 10 times per second
float g_flBotFullThinkInterval = 1.0 / 10.0;
// Nasty Hack. See client.cpp/ClientCommand()
const char *BotArgs[4] = { NULL };
bool UseBotArgs = false;
CBot::CBot()
{
// the profile will be attached after this instance is constructed
m_profile = NULL;
// assign this bot a unique ID
static unsigned int nextID = 1;
// wraparound (highly unlikely)
if (nextID == 0)
++nextID;
m_id = nextID++;
m_postureStackIndex = 0;
}
// Prepare bot for action
bool CBot::Initialize(const BotProfile *profile)
{
m_profile = profile;
return true;
}
void CBot::Spawn()
{
// Let CBasePlayer set some things up
CBasePlayer::Spawn();
// Make sure everyone knows we are a bot
pev->flags |= (FL_CLIENT | FL_FAKECLIENT);
// Bots use their own thinking mechanism
SetThink(NULL);
pev->nextthink = -1;
m_flNextBotThink = gpGlobals->time + g_flBotCommandInterval;
m_flNextFullBotThink = gpGlobals->time + g_flBotFullThinkInterval;
m_flPreviousCommandTime = gpGlobals->time;
m_isRunning = true;
m_isCrouching = false;
m_postureStackIndex = 0;
m_jumpTimestamp = 0.0f;
// Command interface variable initialization
ResetCommand();
// Allow derived classes to setup at spawn time
SpawnBot();
}
Vector CBot::GetAutoaimVector(float flDelta)
{
UTIL_MakeVectors(pev->v_angle + pev->punchangle);
return gpGlobals->v_forward;
}
void CBot::BotThink()
{
if (gpGlobals->time >= m_flNextBotThink)
{
m_flNextBotThink = gpGlobals->time + g_flBotCommandInterval;
Upkeep();
if (gpGlobals->time >= m_flNextFullBotThink)
{
m_flNextFullBotThink = gpGlobals->time + g_flBotFullThinkInterval;
ResetCommand();
Update();
}
ExecuteCommand();
}
}
void CBot::MoveForward()
{
m_forwardSpeed = GetMoveSpeed();
m_buttonFlags |= IN_FORWARD;
// make mutually exclusive
m_buttonFlags &= ~IN_BACK;
}
void CBot::MoveBackward()
{
m_forwardSpeed = -GetMoveSpeed();
m_buttonFlags |= IN_BACK;
// make mutually exclusive
m_buttonFlags &= ~IN_FORWARD;
}
void CBot::StrafeLeft()
{
m_strafeSpeed = -GetMoveSpeed();
m_buttonFlags |= IN_MOVELEFT;
// make mutually exclusive
m_buttonFlags &= ~IN_MOVERIGHT;
}
void CBot::StrafeRight()
{
m_strafeSpeed = GetMoveSpeed();
m_buttonFlags |= IN_MOVERIGHT;
// make mutually exclusive
m_buttonFlags &= ~IN_MOVELEFT;
}
bool CBot::Jump(bool mustJump)
{
if (IsJumping() || IsCrouching())
return false;
if (!mustJump)
{
const float minJumpInterval = 0.9f; // 1.5f;
if (gpGlobals->time - m_jumpTimestamp < minJumpInterval)
return false;
}
// still need sanity check for jumping frequency
const float sanityInterval = 0.3f;
if (gpGlobals->time - m_jumpTimestamp < sanityInterval)
return false;
// jump
m_buttonFlags |= IN_JUMP;
m_jumpTimestamp = gpGlobals->time;
return true;
}
// Zero any MoveForward(), Jump(), etc
void CBot::ClearMovement()
{
ResetCommand();
}
// Returns true if we are in the midst of a jump
bool CBot::IsJumping()
{
// if long time after last jump, we can't be jumping
if (gpGlobals->time - m_jumpTimestamp > 3.0f)
return false;
// if we just jumped, we're still jumping
if (gpGlobals->time - m_jumpTimestamp < 1.0f)
return true;
// a little after our jump, we're jumping until we hit the ground
if (pev->flags & FL_ONGROUND)
return false;
return true;
}
void CBot::Crouch()
{
m_isCrouching = true;
}
void CBot::StandUp()
{
m_isCrouching = false;
}
void CBot::UseEnvironment()
{
m_buttonFlags |= IN_USE;
}
void CBot::PrimaryAttack()
{
m_buttonFlags |= IN_ATTACK;
}
void CBot::ClearPrimaryAttack()
{
m_buttonFlags &= ~IN_ATTACK;
}
void CBot::TogglePrimaryAttack()
{
if (m_buttonFlags & IN_ATTACK)
m_buttonFlags &= ~IN_ATTACK;
else
m_buttonFlags |= IN_ATTACK;
}
void CBot::SecondaryAttack()
{
m_buttonFlags |= IN_ATTACK2;
}
void CBot::Reload()
{
m_buttonFlags |= IN_RELOAD;
}
// Returns ratio of ammo left to max ammo (1 = full clip, 0 = empty)
float CBot::GetActiveWeaponAmmoRatio() const
{
CBasePlayerWeapon *weapon = GetActiveWeapon();
if (!weapon)
return 0.0f;
// weapons with no ammo are always full
if (weapon->m_iClip < 0)
return 1.0f;
return (float)weapon->m_iClip / (float)weapon->iMaxClip();
}
// Return true if active weapon has an empty clip
bool CBot::IsActiveWeaponClipEmpty() const
{
CBasePlayerWeapon *weapon = GetActiveWeapon();
if (weapon != NULL && weapon->m_iClip == 0)
return true;
return false;
}
// Return true if active weapon has no ammo at all
bool CBot::IsActiveWeaponOutOfAmmo() const
{
CBasePlayerWeapon *gun = GetActiveWeapon();
if (gun == NULL)
return true;
if (gun->m_iClip < 0)
return false;
if (gun->m_iClip == 0 && m_rgAmmo[ gun->m_iPrimaryAmmoType ] <= 0)
return true;
return false;
}
// Return true if looking thru weapon's scope
bool CBot::IsUsingScope() const
{
// if our field of view is less than 90, we're looking thru a scope (maybe only true for CS...)
if (m_iFOV < 90.0f)
return true;
return false;
}
void CBot::ExecuteCommand()
{
byte adjustedMSec;
// Adjust msec to command time interval
adjustedMSec = ThrottledMsec();
// player model is "munged"
pev->angles = pev->v_angle;
pev->angles.x /= -3.0f;
// save the command time
m_flPreviousCommandTime = gpGlobals->time;
if (IsCrouching())
{
m_buttonFlags |= IN_DUCK;
}
// Run the command
PLAYER_RUN_MOVE(edict(), pev->v_angle, m_forwardSpeed, m_strafeSpeed, m_verticalSpeed, m_buttonFlags, 0, adjustedMSec);
}
void CBot::ResetCommand()
{
m_forwardSpeed = 0.0f;
m_strafeSpeed = 0.0f;
m_verticalSpeed = 0.0f;
m_buttonFlags = 0;
}
byte CBot::ThrottledMsec() const
{
int iNewMsec;
// Estimate Msec to use for this command based on time passed from the previous command
iNewMsec = (int)((gpGlobals->time - m_flPreviousCommandTime) * 1000);
// Doh, bots are going to be slower than they should if this happens.
// Upgrade that CPU or use less bots!
if (iNewMsec > 255)
iNewMsec = 255;
return (byte)iNewMsec;
}
// Do a "client command" - useful for invoking menu choices, etc.
void CBot::ClientCommand(const char *cmd, const char *arg1, const char *arg2, const char *arg3)
{
#if 0
BotArgs[0] = cmd;
BotArgs[1] = arg1;
BotArgs[2] = arg2;
BotArgs[3] = arg3;
UseBotArgs = true;
::ClientCommand(ENT(pev));
UseBotArgs = false;
#endif
}
// Returns TRUE if given entity is our enemy
bool CBot::IsEnemy(CBaseEntity *ent) const
{
// only Players (real and AI) can be enemies
if (!ent->IsPlayer())
return false;
// corpses are no threat
if (!ent->IsAlive())
return false;
CBasePlayer *player = static_cast<CBasePlayer *>(ent);
// if they are on our team, they are our friends
// if (player->m_iTeam == m_iTeam)
// return false;
// yep, we hate 'em
return true;
}
// Return number of enemies left alive
int CBot::GetEnemiesRemaining() const
{
int count = 0;
for (int i = 1; i <= gpGlobals->maxClients; ++i)
{
CBaseEntity *player = UTIL_PlayerByIndex(i);
if (player == NULL)
continue;
if (FNullEnt(player->pev))
continue;
if (FStrEq(STRING(player->pev->netname), ""))
continue;
if (!IsEnemy(player))
continue;
if (!player->IsAlive())
continue;
count++;
}
return count;
}
// Return number of friends left alive
int CBot::GetFriendsRemaining() const
{
int count = 0;
for (int i = 1; i <= gpGlobals->maxClients; ++i)
{
CBaseEntity *player = UTIL_PlayerByIndex(i);
if (player == NULL)
continue;
if (FNullEnt(player->pev))
continue;
if (FStrEq(STRING(player->pev->netname), ""))
continue;
if (IsEnemy(player))
continue;
if (!player->IsAlive())
continue;
if (player == static_cast<CBaseEntity *>(const_cast<CBot *>(this)))
continue;
count++;
}
return count;
}
bool CBot::IsLocalPlayerWatchingMe() const
{
// avoid crash during spawn
if (pev == NULL)
return false;
int myIndex = const_cast<CBot *>(this)->entindex();
CBasePlayer *player = UTIL_GetLocalPlayer();
if (player == NULL)
return false;
#if 0
if ((player->pev->flags & FL_SPECTATOR || player->m_iTeam == SPECTATOR) && player->pev->iuser2 == myIndex)
{
switch (player->pev->iuser1)
{
case OBS_CHASE_LOCKED:
case OBS_CHASE_FREE:
case OBS_IN_EYE:
return true;
}
}
#endif
return false;
}
NOXREF void CBot::Print(char *format, ...) const
{
va_list varg;
char buffer[1024];
// prefix the message with the bot's name
Q_sprintf(buffer, "%s: ", STRING(pev->netname));
SERVER_PRINT(buffer);
va_start(varg, format);
vsprintf(buffer, format, varg);
va_end(varg);
SERVER_PRINT(buffer);
}
void CBot::PrintIfWatched(char *format, ...) const
{
if (!cv_bot_debug.value)
return;
if ((IsLocalPlayerWatchingMe() && (cv_bot_debug.value == 1 || cv_bot_debug.value == 3))
|| (cv_bot_debug.value == 2 || cv_bot_debug.value == 4))
{
va_list varg;
char buffer[1024];
// prefix the message with the bot's name (this can be NULL if bot was just added)
const char *name;
if (pev == NULL)
name = "(NULL pev)";
else
name = STRING(pev->netname);
Q_sprintf(buffer, "%s: ", (name != NULL) ? name : "(NULL netname)");
SERVER_PRINT(buffer);
va_start(varg, format);
vsprintf(buffer, format, varg);
va_end(varg);
SERVER_PRINT(buffer);
}
}
ActiveGrenade::ActiveGrenade(int weaponID, CGrenade *grenadeEntity)
{
m_id = weaponID;
m_entity = grenadeEntity;
m_detonationPosition = grenadeEntity->pev->origin;
m_dieTimestamp = 0;
}
void ActiveGrenade::OnEntityGone()
{
#if 0
if (m_id == WEAPON_SMOKEGRENADE)
{
// smoke lingers after grenade is gone
const float smokeLingerTime = 4.0f;
m_dieTimestamp = gpGlobals->time + smokeLingerTime;
}
#endif
m_entity = NULL;
}
bool ActiveGrenade::IsValid() const
{
if (!m_entity)
{
if (gpGlobals->time > m_dieTimestamp)
return false;
}
return true;
}
const Vector *ActiveGrenade::GetPosition() const
{
return &m_entity->pev->origin;
}

388
dlls/bot/manager/bot.h Normal file
View File

@ -0,0 +1,388 @@
/*
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* In addition, as a special exception, the author gives permission to
* link the code of this program with the Half-Life Game Engine ("HL
* Engine") and Modified Game Libraries ("MODs") developed by Valve,
* L.L.C ("Valve"). You must obey the GNU General Public License in all
* respects for all of the code used other than the HL Engine and MODs
* from Valve. If you modify this file, you may extend this exception
* to your version of the file, but you are not obligated to do so. If
* you do not wish to do so, delete this exception statement from your
* version.
*
*/
#ifndef BOT_H
#define BOT_H
#ifdef _WIN32
#pragma once
#endif
class BotProfile;
template <class T>
T *CreateBot(const BotProfile *profile)
{
edict_t *pentBot;
if (UTIL_ClientsInGame() >= gpGlobals->maxClients)
{
CONSOLE_ECHO("Unable to create bot: Server is full (%d/%d clients).\n", UTIL_ClientsInGame(), gpGlobals->maxClients);
return NULL;
}
char netname[64];
UTIL_ConstructBotNetName(netname, sizeof(netname), profile);
pentBot = CREATE_FAKE_CLIENT(netname);
if (FNullEnt(pentBot))
{
CONSOLE_ECHO("Unable to create bot: pfnCreateFakeClient() returned null.\n");
return NULL;
}
else
{
T *pBot = NULL;
FREE_PRIVATE(pentBot);
pBot = GetClassPtr((T *)VARS(pentBot));
pBot->Initialize(profile);
return pBot;
}
}
// The base bot class from which bots for specific games are derived
class CBot: public CBasePlayer
{
public:
// constructor initializes all values to zero
CBot();
virtual void Spawn();
// invoked when injured by something
virtual int TakeDamage(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType)
{
return CBasePlayer::TakeDamage(pevInflictor, pevAttacker, flDamage, bitsDamageType);
}
// invoked when killed
virtual void Killed(entvars_t *pevAttacker, int iGib)
{
CBasePlayer::Killed(pevAttacker, iGib);
}
virtual void Think() {};
virtual BOOL IsBot() { return true; }
virtual Vector GetAutoaimVector(float flDelta);
// invoked when in contact with a CWeaponBox
virtual void OnTouchingWeapon(CWeaponBox *box) {}
virtual bool Initialize(const BotProfile *profile);
virtual void SpawnBot() = 0;
// lightweight maintenance, invoked frequently
virtual void Upkeep() = 0;
// heavyweight algorithms, invoked less often
virtual void Update() = 0;
virtual void Run();
virtual void Walk();
virtual void Crouch();
virtual void StandUp();
virtual void MoveForward();
virtual void MoveBackward();
virtual void StrafeLeft();
virtual void StrafeRight();
// returns true if jump was started
#define MUST_JUMP true
virtual bool Jump(bool mustJump = false);
// zero any MoveForward(), Jump(), etc
virtual void ClearMovement();
// Weapon interface
virtual void UseEnvironment();
virtual void PrimaryAttack();
virtual void ClearPrimaryAttack();
virtual void TogglePrimaryAttack();
virtual void SecondaryAttack();
virtual void Reload();
// invoked when event occurs in the game (some events have NULL entities)
virtual void OnEvent(GameEventType event, CBaseEntity *entity = NULL, CBaseEntity *other = NULL) {};
// return true if we can see the point
virtual bool IsVisible(Vector *pos, bool testFOV = false) const = 0;
// return true if we can see any part of the player
virtual bool IsVisible(CBasePlayer *player, bool testFOV = false, unsigned char *visParts = NULL) const = 0;
//enum VisiblePartType:unsigned char // C++11 feature
enum VisiblePartType
{
NONE = 0x00,
CHEST = 0x01,
HEAD = 0x02,
LEFT_SIDE = 0x04, // the left side of the object from our point of view (not their left side)
RIGHT_SIDE = 0x08, // the right side of the object from our point of view (not their right side)
FEET = 0x10
};
// if enemy is visible, return the part we see
virtual bool IsEnemyPartVisible(VisiblePartType part) const = 0;
// return true if player is facing towards us
virtual bool IsPlayerFacingMe(CBasePlayer *other) const;
// returns true if other player is pointing right at us
virtual bool IsPlayerLookingAtMe(CBasePlayer *other) const;
virtual void ExecuteCommand();
virtual void SetModel(const char *modelName);
public:
unsigned int GetID() const { return m_id; }
bool IsRunning() const { return m_isRunning; }
bool IsCrouching() const { return m_isCrouching; }
// push the current posture context onto the top of the stack
void PushPostureContext();
// restore the posture context to the next context on the stack
void PopPostureContext();
bool IsJumping();
// return time last jump began
float GetJumpTimestamp() const { return m_jumpTimestamp; }
// returns ratio of ammo left to max ammo (1 = full clip, 0 = empty)
float GetActiveWeaponAmmoRatio() const;
// return true if active weapon has any empty clip
bool IsActiveWeaponClipEmpty() const;
// return true if active weapon has no ammo at all
bool IsActiveWeaponOutOfAmmo() const;
// is the weapon in the middle of a reload
bool IsActiveWeaponReloading() const;
// return true if active weapon's bullet spray has become large and inaccurate
bool IsActiveWeaponRecoilHigh() const;
// return the weapon the bot is currently using
CBasePlayerWeapon *GetActiveWeapon() const;
// return true if looking thru weapon's scope
bool IsUsingScope() const;
// returns TRUE if given entity is our enemy
bool IsEnemy(CBaseEntity *ent) const;
// return number of enemies left alive
int GetEnemiesRemaining() const;
// return number of friends left alive
int GetFriendsRemaining() const;
// return true if local player is observing this bot
bool IsLocalPlayerWatchingMe() const;
// output message to console
NOXREF void Print(char *format,...) const;
// output message to console if we are being watched by the local player
void PrintIfWatched(char *format,...) const;
void BotThink();
bool IsNetClient() const { return false; }
int Save(CSave &save) const;
int Restore(CRestore &restor) const;
// return our personality profile
const BotProfile *GetProfile() const { return m_profile; }
protected:
// Do a "client command" - useful for invoking menu choices, etc.
void ClientCommand(const char *cmd, const char *arg1 = NULL, const char *arg2 = NULL, const char *arg3 = NULL);
// the "personality" profile of this bot
const BotProfile *m_profile;
private:
void ResetCommand();
byte ThrottledMsec() const;
// returns current movement speed (for walk/run)
float GetMoveSpeed();
// unique bot ID
unsigned int m_id;
// Think mechanism variables
float m_flNextBotThink;
float m_flNextFullBotThink;
// Command interface variables
float m_flPreviousCommandTime;
// run/walk mode
bool m_isRunning;
// true if crouching (ducking)
bool m_isCrouching;
float m_forwardSpeed;
float m_strafeSpeed;
float m_verticalSpeed;
// bitfield of movement buttons
unsigned short m_buttonFlags;
// time when we last began a jump
float m_jumpTimestamp;
// the PostureContext represents the current settings of walking and crouching
struct PostureContext
{
bool isRunning;
bool isCrouching;
};
enum { MAX_POSTURE_STACK = 8 };
PostureContext m_postureStack[MAX_POSTURE_STACK];
// index of top of stack
int m_postureStackIndex;
};
inline void CBot::SetModel(const char *modelName)
{
SET_CLIENT_KEY_VALUE(entindex(), GET_INFO_BUFFER(edict()), "model", (char *)modelName);
}
inline float CBot::GetMoveSpeed()
{
if (m_isRunning || m_isCrouching)
return pev->maxspeed;
return 0.4f * pev->maxspeed;
}
inline void CBot::Run()
{
m_isRunning = true;
}
inline void CBot::Walk()
{
m_isRunning = false;
}
inline CBasePlayerWeapon *CBot::GetActiveWeapon() const
{
return static_cast<CBasePlayerWeapon *>(m_pActiveItem);
}
inline bool CBot::IsActiveWeaponReloading() const
{
CBasePlayerWeapon *weapon = GetActiveWeapon();
if (weapon == NULL)
return false;
return (weapon->m_fInReload || weapon->m_fInSpecialReload) != 0;
}
inline bool CBot::IsActiveWeaponRecoilHigh() const
{
//CBasePlayerWeapon *gun = GetActiveWeapon();
/*if (gun != NULL)
{
const float highRecoil = 0.4f;
return (gun->m_flAccuracy > highRecoil) != 0;
}*/
return false;
}
inline void CBot::PushPostureContext()
{
if (m_postureStackIndex == MAX_POSTURE_STACK)
{
if (pev)
PrintIfWatched("PushPostureContext() overflow error!\n");
return;
}
m_postureStack[m_postureStackIndex].isRunning = m_isRunning;
m_postureStack[m_postureStackIndex].isCrouching = m_isCrouching;
++m_postureStackIndex;
}
inline void CBot::PopPostureContext()
{
if (m_postureStackIndex == 0)
{
if (pev)
PrintIfWatched("PopPostureContext() underflow error!\n");
m_isRunning = true;
m_isCrouching = false;
return;
}
--m_postureStackIndex;
m_isRunning = m_postureStack[m_postureStackIndex].isRunning;
m_isCrouching = m_postureStack[m_postureStackIndex].isCrouching;
}
inline bool CBot::IsPlayerFacingMe(CBasePlayer *other) const
{
Vector toOther = other->pev->origin - pev->origin;
UTIL_MakeVectors(other->pev->v_angle + other->pev->punchangle);
Vector otherDir = gpGlobals->v_forward;
if (otherDir.x * toOther.x + otherDir.y * toOther.y < 0.0f)
return true;
return false;
}
inline bool CBot::IsPlayerLookingAtMe(CBasePlayer *other) const
{
Vector toOther = other->pev->origin - pev->origin;
toOther.NormalizeInPlace();
UTIL_MakeVectors(other->pev->v_angle + other->pev->punchangle);
Vector otherDir = gpGlobals->v_forward;
const float lookAtCos = 0.9f;
if (otherDir.x * toOther.x + otherDir.y * toOther.y < -lookAtCos)
{
Vector vec(other->EyePosition());
if (IsVisible(&vec))
return true;
}
return false;
}
extern float g_flBotCommandInterval;
extern float g_flBotFullThinkInterval;
extern const char *BotArgs[4];
extern bool UseBotArgs;
class BotProfile;
#endif // BOT_H

View File

@ -0,0 +1,54 @@
/*
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* In addition, as a special exception, the author gives permission to
* link the code of this program with the Half-Life Game Engine ("HL
* Engine") and Modified Game Libraries ("MODs") developed by Valve,
* L.L.C ("Valve"). You must obey the GNU General Public License in all
* respects for all of the code used other than the HL Engine and MODs
* from Valve. If you modify this file, you may extend this exception
* to your version of the file, but you are not obligated to do so. If
* you do not wish to do so, delete this exception statement from your
* version.
*
*/
#ifndef BOT_CONSTANTS_H
#define BOT_CONSTANTS_H
#ifdef _WIN32
#pragma once
#endif
// We'll define our own version of this, because everyone else does.
// This needs to stay in sync with MAX_CLIENTS, but there's no header with the #define.
#define BOT_MAX_CLIENTS 32
// version number is MAJOR.MINOR
#define BOT_VERSION_MAJOR 1
#define BOT_VERSION_MINOR 50
// Difficulty levels
enum BotDifficultyType
{
BOT_EASY = 0,
BOT_NORMAL,
BOT_HARD,
BOT_EXPERT,
NUM_DIFFICULTY_LEVELS
};
#endif // BOT_CONSTANTS_H

View File

@ -0,0 +1,463 @@
#include "bot/bot_common.h"
/*
* Globals initialization
*/
const char *GameEventName[NUM_GAME_EVENTS + 1] =
{
"EVENT_INVALID",
"EVENT_WEAPON_FIRED",
"EVENT_WEAPON_FIRED_ON_EMPTY",
"EVENT_WEAPON_RELOADED",
"EVENT_HE_GRENADE_EXPLODED",
"EVENT_FLASHBANG_GRENADE_EXPLODED",
"EVENT_SMOKE_GRENADE_EXPLODED",
"EVENT_GRENADE_BOUNCED",
"EVENT_BEING_SHOT_AT",
"EVENT_PLAYER_BLINDED_BY_FLASHBANG",
"EVENT_PLAYER_FOOTSTEP",
"EVENT_PLAYER_JUMPED",
"EVENT_PLAYER_DIED",
"EVENT_PLAYER_LANDED_FROM_HEIGHT",
"EVENT_PLAYER_TOOK_DAMAGE",
"EVENT_HOSTAGE_DAMAGED",
"EVENT_HOSTAGE_KILLED",
"EVENT_DOOR",
"EVENT_BREAK_GLASS",
"EVENT_BREAK_WOOD",
"EVENT_BREAK_METAL",
"EVENT_BREAK_FLESH",
"EVENT_BREAK_CONCRETE",
"EVENT_BOMB_PLANTED",
"EVENT_BOMB_DROPPED",
"EVENT_BOMB_PICKED_UP",
"EVENT_BOMB_BEEP",
"EVENT_BOMB_DEFUSING",
"EVENT_BOMB_DEFUSE_ABORTED",
"EVENT_BOMB_DEFUSED",
"EVENT_BOMB_EXPLODED",
"EVENT_HOSTAGE_USED",
"EVENT_HOSTAGE_RESCUED",
"EVENT_ALL_HOSTAGES_RESCUED",
"EVENT_VIP_ESCAPED",
"EVENT_VIP_ASSASSINATED",
"EVENT_TERRORISTS_WIN",
"EVENT_CTS_WIN",
"EVENT_ROUND_DRAW",
"EVENT_ROUND_WIN",
"EVENT_ROUND_LOSS",
"EVENT_ROUND_START",
"EVENT_PLAYER_SPAWNED",
"EVENT_CLIENT_CORPSE_SPAWNED",
"EVENT_BUY_TIME_START",
"EVENT_PLAYER_LEFT_BUY_ZONE",
"EVENT_DEATH_CAMERA_START",
"EVENT_KILL_ALL",
"EVENT_ROUND_TIME",
"EVENT_DIE",
"EVENT_KILL",
"EVENT_HEADSHOT",
"EVENT_KILL_FLASHBANGED",
"EVENT_TUTOR_BUY_MENU_OPENNED",
"EVENT_TUTOR_AUTOBUY",
"EVENT_PLAYER_BOUGHT_SOMETHING",
"EVENT_TUTOR_NOT_BUYING_ANYTHING",
"EVENT_TUTOR_NEED_TO_BUY_PRIMARY_WEAPON",
"EVENT_TUTOR_NEED_TO_BUY_PRIMARY_AMMO",
"EVENT_TUTOR_NEED_TO_BUY_SECONDARY_AMMO",
"EVENT_TUTOR_NEED_TO_BUY_ARMOR",
"EVENT_TUTOR_NEED_TO_BUY_DEFUSE_KIT",
"EVENT_TUTOR_NEED_TO_BUY_GRENADE",
"EVENT_CAREER_TASK_DONE",
"EVENT_START_RADIO_1",
"EVENT_RADIO_COVER_ME",
"EVENT_RADIO_YOU_TAKE_THE_POINT",
"EVENT_RADIO_HOLD_THIS_POSITION",
"EVENT_RADIO_REGROUP_TEAM",
"EVENT_RADIO_FOLLOW_ME",
"EVENT_RADIO_TAKING_FIRE",
"EVENT_START_RADIO_2",
"EVENT_RADIO_GO_GO_GO",
"EVENT_RADIO_TEAM_FALL_BACK",
"EVENT_RADIO_STICK_TOGETHER_TEAM",
"EVENT_RADIO_GET_IN_POSITION_AND_WAIT",
"EVENT_RADIO_STORM_THE_FRONT",
"EVENT_RADIO_REPORT_IN_TEAM",
"EVENT_START_RADIO_3",
"EVENT_RADIO_AFFIRMATIVE",
"EVENT_RADIO_ENEMY_SPOTTED",
"EVENT_RADIO_NEED_BACKUP",
"EVENT_RADIO_SECTOR_CLEAR",
"EVENT_RADIO_IN_POSITION",
"EVENT_RADIO_REPORTING_IN",
"EVENT_RADIO_GET_OUT_OF_THERE",
"EVENT_RADIO_NEGATIVE",
"EVENT_RADIO_ENEMY_DOWN",
"EVENT_END_RADIO",
"EVENT_NEW_MATCH",
"EVENT_PLAYER_CHANGED_TEAM",
"EVENT_BULLET_IMPACT",
"EVENT_GAME_COMMENCE",
"EVENT_WEAPON_ZOOMED",
"EVENT_HOSTAGE_CALLED_FOR_HELP",
NULL,
};
// STL uses exceptions, but we are not compiling with them - ignore warning
#pragma warning(disable : 4530)
const float smokeRadius = 115.0f; // for smoke grenades
// Convert name to GameEventType
// TODO: Find more appropriate place for this function
GameEventType NameToGameEvent(const char *name)
{
for (int i = 0; GameEventName[i] != NULL; ++i)
{
if (!Q_stricmp(GameEventName[i], name))
return static_cast<GameEventType>(i);
}
return EVENT_INVALID;
}
CBotManager::CBotManager()
{
InitBotTrig();
}
// Invoked when the round is restarting
void CBotManager::RestartRound()
{
DestroyAllGrenades();
}
// Invoked at the start of each frame
void CBotManager::StartFrame()
{
// debug smoke grenade visualization
if (cv_bot_debug.value == 5)
{
Vector edge, lastEdge;
int it = m_activeGrenadeList.Head ();
while (it != m_activeGrenadeList.InvalidIndex ())
{
ActiveGrenade *ag = m_activeGrenadeList[it];
int current = it;
it = m_activeGrenadeList.Next (it);
// lazy validation
if (!ag->IsValid ())
{
m_activeGrenadeList.Remove (current);
delete ag;
continue;
}
const Vector *pos = ag->GetDetonationPosition();
UTIL_DrawBeamPoints(*pos, *pos + Vector(0, 0, 50), 1, 255, 100, 0);
lastEdge = Vector(smokeRadius + pos->x, pos->y, pos->z);
float angle;
for (angle = 0.0f; angle <= 180.0f; angle += 22.5f)
{
edge.x = smokeRadius * BotCOS(angle) + pos->x;
edge.y = pos->y;
edge.z = smokeRadius * BotSIN(angle) + pos->z;
UTIL_DrawBeamPoints(edge, lastEdge, 1, 255, 50, 0);
lastEdge = edge;
}
lastEdge = Vector(pos->x, smokeRadius + pos->y, pos->z);
for (angle = 0.0f; angle <= 180.0f; angle += 22.5f)
{
edge.x = pos->x;
edge.y = smokeRadius * BotCOS(angle) + pos->y;
edge.z = smokeRadius * BotSIN(angle) + pos->z;
UTIL_DrawBeamPoints(edge, lastEdge, 1, 255, 50, 0);
lastEdge = edge;
}
}
}
// Process each active bot
for (int i = 1; i <= gpGlobals->maxClients; ++i)
{
CBasePlayer *pPlayer = static_cast<CBasePlayer *>(UTIL_PlayerByIndex(i));
if (!pPlayer)
continue;
if (pPlayer->IsBot() && IsEntityValid(pPlayer))
{
CBot *pBot = static_cast<CBot *>(pPlayer);
pBot->BotThink();
}
}
}
// Return the filename for this map's "nav map" file
const char *CBotManager::GetNavMapFilename() const
{
static char filename[256];
Q_sprintf(filename, "maps\\%s.nav", STRING(gpGlobals->mapname));
return filename;
}
// Invoked when given player does given event (some events have NULL player).
// Events are propogated to all bots.
// TODO: This has become the game-wide event dispatcher. We should restructure this.
void CBotManager::OnEvent(GameEventType event, CBaseEntity *entity, CBaseEntity *other)
{
// propogate event to all bots
for (int i = 1; i <= gpGlobals->maxClients; ++i)
{
CBasePlayer *player = static_cast<CBasePlayer *>(UTIL_PlayerByIndex(i));
if (player == NULL)
continue;
if (FNullEnt(player->pev))
continue;
if (FStrEq(STRING(player->pev->netname), ""))
continue;
if (!player->IsBot())
continue;
// do not send self-generated event
if (entity == player)
continue;
CBot *bot = static_cast<CBot *>(player);
bot->OnEvent(event, entity, other);
}
}
// Add an active grenade to the bot's awareness
void CBotManager::AddGrenade(int type, CGrenade *grenade)
{
m_activeGrenadeList.AddToTail(new ActiveGrenade (type, grenade));
}
// The grenade entity in the world is going away
void CBotManager::RemoveGrenade(CGrenade *grenade)
{
FOR_EACH_LL (m_activeGrenadeList, it)
{
ActiveGrenade *ag = m_activeGrenadeList[it];
if (ag->IsEntity (grenade))
{
ag->OnEntityGone ();
return;
}
}
}
// Destroy any invalid active grenades
NOXREF void CBotManager::ValidateActiveGrenades()
{
int it = m_activeGrenadeList.Head ();
while (it != m_activeGrenadeList.InvalidIndex ())
{
ActiveGrenade *ag = m_activeGrenadeList[it];
int current = it;
it = m_activeGrenadeList.Next (it);
// lazy validation
if (!ag->IsValid ())
{
m_activeGrenadeList.Remove (current);
delete ag;
continue;
}
}
}
void CBotManager::DestroyAllGrenades()
{
m_activeGrenadeList.PurgeAndDeleteElements ();
}
// Return true if position is inside a smoke cloud
bool CBotManager::IsInsideSmokeCloud(const Vector *pos)
{
#if 0
int it = m_activeGrenadeList.Head ();
while (it != m_activeGrenadeList.InvalidIndex ())
{
ActiveGrenade *ag = m_activeGrenadeList[it];
int current = it;
it = m_activeGrenadeList.Next (it);
// lazy validation
if (!ag->IsValid())
{
m_activeGrenadeList.Remove (current);
delete ag;
continue;
}
if (ag->GetID() == WEAPON_SMOKEGRENADE)
{
const Vector *smokeOrigin = ag->GetDetonationPosition();
if ((*smokeOrigin - *pos).IsLengthLessThan(smokeRadius))
return true;
}
}
#endif
return false;
}
// Return true if line intersects smoke volume
// Determine the length of the line of sight covered by each smoke cloud,
// and sum them (overlap is additive for obstruction).
// If the overlap exceeds the threshold, the bot can't see through.
bool CBotManager::IsLineBlockedBySmoke(const Vector *from, const Vector *to)
{
#if 0
const float smokeRadiusSq = smokeRadius * smokeRadius;
// distance along line of sight covered by smoke
float totalSmokedLength = 0.0f;
// compute unit vector and length of line of sight segment
Vector sightDir = *to - *from;
float sightLength = sightDir.NormalizeInPlace();
int it = m_activeGrenadeList.Head ();
while (it != m_activeGrenadeList.InvalidIndex ())
{
ActiveGrenade *ag = m_activeGrenadeList[it];
int current = it;
it = m_activeGrenadeList.Next (it);
// lazy validation
if (!ag->IsValid ())
{
m_activeGrenadeList.Remove (current);
delete ag;
continue;
}
if (ag->GetID() == WEAPON_SMOKEGRENADE)
{
const Vector *smokeOrigin = ag->GetDetonationPosition();
Vector toGrenade = *smokeOrigin - *from;
float alongDist = DotProduct(toGrenade, sightDir);
// compute closest point to grenade along line of sight ray
Vector close;
// constrain closest point to line segment
if (alongDist < 0.0f)
close = *from;
else if (alongDist >= sightLength)
close = *to;
else
close = *from + sightDir * alongDist;
// if closest point is within smoke radius, the line overlaps the smoke cloud
Vector toClose = close - *smokeOrigin;
float lengthSq = toClose.LengthSquared();
if (lengthSq < smokeRadiusSq)
{
// some portion of the ray intersects the cloud
float fromSq = toGrenade.LengthSquared();
float toSq = (*smokeOrigin - *to).LengthSquared();
if (fromSq < smokeRadiusSq)
{
if (toSq < smokeRadiusSq)
{
// both 'from' and 'to' lie within the cloud
// entire length is smoked
totalSmokedLength += (*to - *from).Length();
}
else
{
// 'from' is inside the cloud, 'to' is outside
// compute half of total smoked length as if ray crosses entire cloud chord
float halfSmokedLength = sqrt(smokeRadiusSq - lengthSq);
if (alongDist > 0.0f)
{
// ray goes thru 'close'
totalSmokedLength += halfSmokedLength + (close - *from).Length();
}
else
{
// ray starts after 'close'
totalSmokedLength += halfSmokedLength - (close - *from).Length();
}
}
}
else if (toSq < smokeRadiusSq)
{
// 'from' is outside the cloud, 'to' is inside
// compute half of total smoked length as if ray crosses entire cloud chord
float halfSmokedLength = sqrt(smokeRadiusSq - lengthSq);
Vector v = *to - *smokeOrigin;
if (DotProduct(v, sightDir) > 0.0f)
{
// ray goes thru 'close'
totalSmokedLength += halfSmokedLength + (close - *to).Length();
}
else
{
// ray ends before 'close'
totalSmokedLength += halfSmokedLength - (close - *to).Length();
}
}
else
{
// 'from' and 'to' lie outside of the cloud - the line of sight completely crosses it
// determine the length of the chord that crosses the cloud
float smokedLength = 2.0f * sqrt(smokeRadiusSq - lengthSq);
totalSmokedLength += smokedLength;
}
}
}
}
// define how much smoke a bot can see thru
const float maxSmokedLength = 0.7f * smokeRadius;
// return true if the total length of smoke-covered line-of-sight is too much
return (totalSmokedLength > maxSmokedLength);
#endif
return false;
}

View File

@ -0,0 +1,102 @@
/*
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* In addition, as a special exception, the author gives permission to
* link the code of this program with the Half-Life Game Engine ("HL
* Engine") and Modified Game Libraries ("MODs") developed by Valve,
* L.L.C ("Valve"). You must obey the GNU General Public License in all
* respects for all of the code used other than the HL Engine and MODs
* from Valve. If you modify this file, you may extend this exception
* to your version of the file, but you are not obligated to do so. If
* you do not wish to do so, delete this exception statement from your
* version.
*
*/
#ifndef BOT_MANAGER_H
#define BOT_MANAGER_H
#ifdef _WIN32
#pragma once
#endif
class CNavArea;
class CGrenade;
class ActiveGrenade
{
public:
ActiveGrenade(int weaponID, CGrenade *grenadeEntity);
void OnEntityGone();
bool IsValid() const;
bool IsEntity(CGrenade *grenade) const { return (grenade == m_entity) ? true : false; }
int GetID() const { return m_id; }
const Vector *GetDetonationPosition() const { return &m_detonationPosition; }
const Vector *GetPosition() const;
private:
int m_id;
CGrenade *m_entity;
Vector m_detonationPosition;
float m_dieTimestamp;
};
typedef CUtlLinkedList<ActiveGrenade *, int> ActiveGrenadeList;
class CBotManager
{
public:
CBotManager();
virtual ~CBotManager(){}
virtual void ClientDisconnect(CBasePlayer *pPlayer) = 0;
virtual BOOL ClientCommand(CBasePlayer *pPlayer, const char *pcmd) = 0;
virtual void ServerActivate() = 0;
virtual void ServerDeactivate() = 0;
virtual void ServerCommand(const char *pcmd) = 0;
virtual void AddServerCommand(const char *cmd) = 0;
virtual void AddServerCommands() = 0;
virtual void RestartRound();
virtual void StartFrame();
// Events are propogated to all bots.
virtual void OnEvent(GameEventType event, CBaseEntity *entity = NULL, CBaseEntity *other = NULL); // Invoked when event occurs in the game (some events have NULL entity).
virtual unsigned int GetPlayerPriority(CBasePlayer *player) const = 0; // return priority of player (0 = max pri)
public:
const char *GetNavMapFilename() const; // return the filename for this map's "nav" file
void AddGrenade(int type, CGrenade *grenade); // add an active grenade to the bot's awareness
void RemoveGrenade(CGrenade *grenade); // the grenade entity in the world is going away
NOXREF void ValidateActiveGrenades(); // destroy any invalid active grenades
void DestroyAllGrenades();
bool IsLineBlockedBySmoke(const Vector *from, const Vector *to); // return true if line intersects smoke volume
bool IsInsideSmokeCloud(const Vector *pos); // return true if position is inside a smoke cloud
private:
// the list of active grenades the bots are aware of
ActiveGrenadeList m_activeGrenadeList;
};
GameEventType NameToGameEvent(const char *name);
#endif // BOT_MANAGER_H

View File

@ -0,0 +1,633 @@
#include "bot/bot_common.h"
#include "bot/simple_checksum.h"
/*
* Globals initialization
*/
BotProfileManager *TheBotProfiles = NULL;
char *BotDifficultyName[] = { "EASY", "NORMAL", "HARD", "EXPERT", NULL };
// Generates a filename-decorated skin name
const char *GetDecoratedSkinName(const char *name, const char *filename)
{
const int BufLen = MAX_PATH + 64;
static char buf[BufLen];
Q_snprintf(buf, BufLen, "%s/%s", filename, name);
return buf;
}
const char *BotProfile::GetWeaponPreferenceAsString(int i) const
{
if (i < 0 || i >= m_weaponPreferenceCount)
return NULL;
return "";//WeaponIDToAlias(m_weaponPreference[i]);
}
// Return true if this profile has a primary weapon preference
bool BotProfile::HasPrimaryPreference() const
{
return true;
#if 0
for (int i = 0; i < m_weaponPreferenceCount; ++i)
{
int weaponClass = AliasToWeaponClass(WeaponIDToAlias(m_weaponPreference[i]));
if (weaponClass == WEAPONCLASS_SUBMACHINEGUN ||
weaponClass == WEAPONCLASS_SHOTGUN ||
weaponClass == WEAPONCLASS_MACHINEGUN ||
weaponClass == WEAPONCLASS_RIFLE ||
weaponClass == WEAPONCLASS_SNIPERRIFLE)
return true;
}
#endif
return false;
}
// Return true if this profile has a pistol weapon preference
bool BotProfile::HasPistolPreference() const
{
return false;
#if 0
for (int i = 0; i < m_weaponPreferenceCount; ++i)
{
if (AliasToWeaponClass(WeaponIDToAlias(m_weaponPreference[i])) == WEAPONCLASS_PISTOL)
return true;
}
#endif
return false;
}
// Return true if this profile is valid for the specified team
bool BotProfile::IsValidForTeam(BotProfileTeamType team) const
{
return (team == BOT_TEAM_ANY || m_teams == BOT_TEAM_ANY || team == m_teams);
}
BotProfileManager::BotProfileManager()
{
m_nextSkin = 0;
for (int i = 0; i < NumCustomSkins; ++i)
{
m_skins[i] = NULL;
m_skinFilenames[i] = NULL;
m_skinModelnames[i] = NULL;
}
}
// Load the bot profile database
void BotProfileManager::Init(const char *filename, unsigned int *checksum)
{
int dataLength;
char *dataPointer = (char *)LOAD_FILE_FOR_ME(const_cast<char *>(filename), &dataLength);
const char *dataFile = dataPointer;
if (dataFile == NULL)
{
if (true)
{
CONSOLE_ECHO("WARNING: Cannot access bot profile database '%s'\n", filename);
}
return;
}
// compute simple checksum
if (checksum)
{
*checksum = ComputeSimpleChecksum((const unsigned char *)dataPointer, dataLength);
}
// keep list of templates used for inheritance
BotProfileList templateList;
BotProfile defaultProfile;
// Parse the BotProfile.db into BotProfile instances
while (true)
{
dataFile = SharedParse(dataFile);
if (!dataFile)
break;
char *token = SharedGetToken();
bool isDefault = (!Q_stricmp(token, "Default"));
bool isTemplate = (!Q_stricmp(token, "Template"));
bool isCustomSkin = (!Q_stricmp(token, "Skin"));
if (isCustomSkin)
{
const int BufLen = 64;
char skinName[BufLen];
// get skin name
dataFile = SharedParse(dataFile);
if (!dataFile)
{
CONSOLE_ECHO("Error parsing %s - expected skin name\n", filename);
FREE_FILE(dataPointer);
return;
}
token = SharedGetToken();
Q_snprintf(skinName, BufLen, "%s", token);
// get attribute name
dataFile = SharedParse(dataFile);
if (!dataFile)
{
CONSOLE_ECHO("Error parsing %s - expected 'Model'\n", filename);
FREE_FILE(dataPointer);
return;
}
token = SharedGetToken();
if (Q_stricmp("Model", token))
{
CONSOLE_ECHO("Error parsing %s - expected 'Model'\n", filename);
FREE_FILE(dataPointer);
return;
}
// eat '='
dataFile = SharedParse(dataFile);
if (!dataFile)
{
CONSOLE_ECHO("Error parsing %s - expected '='\n", filename);
FREE_FILE(dataPointer);
return;
}
token = SharedGetToken();
if (Q_strcmp("=", token))
{
CONSOLE_ECHO("Error parsing %s - expected '='\n", filename);
FREE_FILE(dataPointer);
return;
}
// get attribute value
dataFile = SharedParse(dataFile);
if (!dataFile)
{
CONSOLE_ECHO("Error parsing %s - expected attribute value\n", filename);
FREE_FILE(dataPointer);
return;
}
token = SharedGetToken();
const char *decoratedName = GetDecoratedSkinName(skinName, filename);
bool skinExists = GetCustomSkinIndex(decoratedName) > 0;
if (m_nextSkin < NumCustomSkins && !skinExists)
{
// decorate the name
m_skins[ m_nextSkin ] = CloneString(decoratedName);
// construct the model filename
m_skinModelnames[ m_nextSkin ] = CloneString(token);
m_skinFilenames[ m_nextSkin ] = new char[ Q_strlen(token) * 2 + Q_strlen("models/player//.mdl") + 1 ];
Q_sprintf(m_skinFilenames[ m_nextSkin ], "models/player/%s/%s.mdl", token, token);
++m_nextSkin;
}
// eat 'End'
dataFile = SharedParse(dataFile);
if (!dataFile)
{
CONSOLE_ECHO("Error parsing %s - expected 'End'\n", filename);
FREE_FILE(dataPointer);
return;
}
token = SharedGetToken();
if (Q_strcmp("End", token))
{
CONSOLE_ECHO("Error parsing %s - expected 'End'\n", filename);
FREE_FILE(dataPointer);
return;
}
// it's just a custom skin - no need to do inheritance on a bot profile, etc.
continue;
}
// encountered a new profile
BotProfile *profile;
if (isDefault)
{
profile = &defaultProfile;
}
else
{
profile = new BotProfile;
// always inherit from Default
*profile = defaultProfile;
}
// do inheritance in order of appearance
if (!isTemplate && !isDefault)
{
const BotProfile *inherit = NULL;
// template names are separated by "+"
while (true)
{
char *c = Q_strchr(token, '+');
if (c)
*c = '\0';
// find the given template name
FOR_EACH_LL (templateList, it)
{
BotProfile *profile = templateList[it];
if (!Q_stricmp(profile->GetName(), token))
{
inherit = profile;
break;
}
}
if (inherit == NULL)
{
CONSOLE_ECHO("Error parsing '%s' - invalid template reference '%s'\n", filename, token);
FREE_FILE(dataPointer);
return;
}
// inherit the data
profile->Inherit(inherit, &defaultProfile);
if (c == NULL)
break;
token = c + 1;
}
}
// get name of this profile
if (!isDefault)
{
dataFile = SharedParse(dataFile);
if (!dataFile)
{
CONSOLE_ECHO("Error parsing '%s' - expected name\n", filename);
FREE_FILE(dataPointer);
return;
}
profile->m_name = CloneString(SharedGetToken());
// HACK HACK
// Until we have a generalized means of storing bot preferences, we're going to hardcode the bot's
// preference towards silencers based on his name.
if (profile->m_name[0] % 2)
{
profile->m_prefersSilencer = true;
}
}
// read attributes for this profile
bool isFirstWeaponPref = true;
while (true)
{
// get next token
dataFile = SharedParse(dataFile);
if (!dataFile)
{
CONSOLE_ECHO("Error parsing %s - expected 'End'\n", filename);
FREE_FILE(dataPointer);
return;
}
token = SharedGetToken();
// check for End delimiter
if (!Q_stricmp(token, "End"))
break;
// found attribute name - keep it
char attributeName[64];
Q_strcpy(attributeName, token);
// eat '='
dataFile = SharedParse(dataFile);
if (!dataFile)
{
CONSOLE_ECHO("Error parsing %s - expected '='\n", filename);
FREE_FILE(dataPointer);
return;
}
token = SharedGetToken();
if (Q_strcmp("=", token))
{
CONSOLE_ECHO("Error parsing %s - expected '='\n", filename);
FREE_FILE(dataPointer);
return;
}
// get attribute value
dataFile = SharedParse(dataFile);
if (!dataFile)
{
CONSOLE_ECHO("Error parsing %s - expected attribute value\n", filename);
FREE_FILE(dataPointer);
return;
}
token = SharedGetToken();
// store value in appropriate attribute
if (!Q_stricmp("Aggression", attributeName))
{
profile->m_aggression = Q_atof(token) / 100.0f;
}
else if (!Q_stricmp("Skill", attributeName))
{
profile->m_skill = Q_atof(token) / 100.0f;
}
else if (!Q_stricmp("Skin", attributeName))
{
profile->m_skin = Q_atoi(token);
if (profile->m_skin == 0)
{
// Q_atoi() failed - try to look up a custom skin by name
profile->m_skin = GetCustomSkinIndex(token, filename);
}
}
else if (!Q_stricmp("Teamwork", attributeName))
{
profile->m_teamwork = Q_atof(token) / 100.0f;
}
else if (!Q_stricmp("Cost", attributeName))
{
profile->m_cost = Q_atoi(token);
}
else if (!Q_stricmp("VoicePitch", attributeName))
{
profile->m_voicePitch = Q_atoi(token);
}
else if (!Q_stricmp("VoiceBank", attributeName))
{
profile->m_voiceBank = FindVoiceBankIndex(token);
}
else if (!Q_stricmp("WeaponPreference", attributeName))
{
// weapon preferences override parent prefs
if (isFirstWeaponPref)
{
isFirstWeaponPref = false;
profile->m_weaponPreferenceCount = 0;
}
if (!Q_stricmp(token, "none"))
{
profile->m_weaponPreferenceCount = 0;
}
else
{
#if 0
if (profile->m_weaponPreferenceCount < BotProfile::MAX_WEAPON_PREFS)
{
profile->m_weaponPreference[ profile->m_weaponPreferenceCount++ ] = AliasToWeaponID(token);
}
#endif
}
}
else if (!Q_stricmp("ReactionTime", attributeName))
{
profile->m_reactionTime = Q_atof(token);
#ifndef GAMEUI_EXPORTS
// subtract off latency due to "think" update rate.
// In GameUI, we don't really care.
profile->m_reactionTime -= g_flBotFullThinkInterval;
#endif // GAMEUI_EXPORTS
}
else if (!Q_stricmp("AttackDelay", attributeName))
{
profile->m_attackDelay = Q_atof(token);
}
else if (!Q_stricmp("Difficulty", attributeName))
{
// override inheritance
profile->m_difficultyFlags = 0;
// parse bit flags
while (true)
{
char *c = Q_strchr(token, '+');
if (c)
*c = '\0';
for (int i = 0; i < NUM_DIFFICULTY_LEVELS; ++i)
{
if (!Q_stricmp(BotDifficultyName[i], token))
profile->m_difficultyFlags |= (1 << i);
}
if (c == NULL)
break;
token = c + 1;
}
}
else if (!Q_stricmp("Team", attributeName))
{
if (!Q_stricmp(token, "T"))
{
profile->m_teams = BOT_TEAM_T;
}
else if (!Q_stricmp(token, "CT"))
{
profile->m_teams = BOT_TEAM_CT;
}
else
{
profile->m_teams = BOT_TEAM_ANY;
}
}
else
{
CONSOLE_ECHO("Error parsing %s - unknown attribute '%s'\n", filename, attributeName);
}
}
if (!isDefault)
{
if (isTemplate)
{
// add to template list
templateList.AddToTail(profile);
}
else
{
// add profile to the master list
m_profileList.AddToTail (profile);
}
}
}
FREE_FILE(dataPointer);
// free the templates
templateList.PurgeAndDeleteElements ();
}
BotProfileManager::~BotProfileManager()
{
Reset();
m_voiceBanks.PurgeAndDeleteElements ();
}
// Free all bot profiles
void BotProfileManager::Reset()
{
m_profileList.PurgeAndDeleteElements ();
for (int i = 0; i < NumCustomSkins; ++i)
{
if (m_skins[i])
{
delete[] m_skins[i];
m_skins[i] = NULL;
}
if (m_skinFilenames[i])
{
delete[] m_skinFilenames[i];
m_skinFilenames[i] = NULL;
}
if (m_skinModelnames[i])
{
delete[] m_skinModelnames[i];
m_skinModelnames[i] = NULL;
}
}
}
// Returns custom skin name at a particular index
const char *BotProfileManager::GetCustomSkin(int index)
{
if (index < FirstCustomSkin || index > LastCustomSkin)
{
return NULL;
}
return m_skins[ index - FirstCustomSkin ];
}
// Returns custom skin filename at a particular index
const char *BotProfileManager::GetCustomSkinFname(int index)
{
if (index < FirstCustomSkin || index > LastCustomSkin)
{
return NULL;
}
return m_skinFilenames[ index - FirstCustomSkin ];
}
// Returns custom skin modelname at a particular index
const char *BotProfileManager::GetCustomSkinModelname(int index)
{
if (index < FirstCustomSkin || index > LastCustomSkin)
{
return NULL;
}
return m_skinModelnames[ index - FirstCustomSkin ];
}
// Looks up a custom skin index by filename-decorated name (will decorate the name if filename is given)
int BotProfileManager::GetCustomSkinIndex(const char *name, const char *filename)
{
const char *skinName = name;
if (filename)
{
skinName = GetDecoratedSkinName(name, filename);
}
for (int i = 0; i < NumCustomSkins; ++i)
{
if (m_skins[i])
{
if (!Q_stricmp(skinName, m_skins[i]))
{
return FirstCustomSkin + i;
}
}
}
return 0;
}
// return index of the (custom) bot phrase db, inserting it if needed
int BotProfileManager::FindVoiceBankIndex(const char *filename)
{
int index = 0;
for (int i = 0; i<m_voiceBanks.Count (); ++i)
{
if (!Q_stricmp (filename, m_voiceBanks[i]))
{
return index;
}
}
m_voiceBanks.AddToTail (CloneString (filename));
return index;
}
// Return random unused profile that matches the given difficulty level
const BotProfile *BotProfileManager::GetRandomProfile(BotDifficultyType difficulty, BotProfileTeamType team) const
{
#ifdef RANDOM_LONG
// count up valid profiles
CUtlVector< const BotProfile * > profiles;
FOR_EACH_LL( m_profileList, it )
{
const BotProfile *profile = m_profileList[ it ];
// Match difficulty
if ( !profile->IsDifficulty( difficulty ) )
continue;
// Prevent duplicate names
if ( UTIL_IsNameTaken( profile->GetName() ) )
continue;
// Match team choice
if ( !profile->IsValidForTeam( team ) )
continue;
profiles.AddToTail( profile );
}
if ( !profiles.Count() )
return NULL;
// select one at random
int which = RANDOM_LONG( 0, profiles.Count()-1 );
return profiles[which];
#else
// we don't need random profiles when we're not in the game dll
return NULL;
#endif // RANDOM_LONG
}

View File

@ -0,0 +1,223 @@
/*
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* In addition, as a special exception, the author gives permission to
* link the code of this program with the Half-Life Game Engine ("HL
* Engine") and Modified Game Libraries ("MODs") developed by Valve,
* L.L.C ("Valve"). You must obey the GNU General Public License in all
* respects for all of the code used other than the HL Engine and MODs
* from Valve. If you modify this file, you may extend this exception
* to your version of the file, but you are not obligated to do so. If
* you do not wish to do so, delete this exception statement from your
* version.
*
*/
#ifndef BOT_PROFILE
#define BOT_PROFILE
#ifdef _WIN32
#pragma once
#endif
// long STL names get truncated in browse info.
#pragma warning(disable : 4786)
#ifndef _WIN32
#include <strings.h>
#include <stdio.h>
#endif // _WIN32
#undef min
#undef max
#include "bot_constants.h"
enum
{
FirstCustomSkin = 100,
NumCustomSkins = 100,
LastCustomSkin = FirstCustomSkin + NumCustomSkins - 1,
};
enum BotProfileTeamType
{
BOT_TEAM_T,
BOT_TEAM_CT,
BOT_TEAM_ANY
};
class BotProfile
{
public:
BotProfile()
{
m_name = NULL;
m_aggression = 0.0f;
m_skill = 0.0f;
m_teamwork = 0.0f;
m_weaponPreferenceCount = 0;
m_cost = 0;
m_skin = 0;
m_difficultyFlags = 0;
m_voicePitch = 100;
m_reactionTime = 0.3f;
m_attackDelay = 0.0f;
m_teams = BOT_TEAM_ANY;
m_voiceBank = 0;
m_prefersSilencer = false;
}
const char *GetName() const { return m_name; }
float GetAggression() const { return m_aggression; }
float GetSkill() const { return m_skill; }
float GetTeamwork() const { return m_teamwork; }
int GetWeaponPreference(int i) const { return m_weaponPreference[i]; }
const char *GetWeaponPreferenceAsString(int i) const;
int GetWeaponPreferenceCount() const { return m_weaponPreferenceCount; }
bool HasPrimaryPreference() const;
bool HasPistolPreference() const;
int GetCost() const { return m_cost; }
int GetSkin() const { return m_skin; }
bool IsDifficulty(BotDifficultyType diff) const;
int GetVoicePitch() const { return m_voicePitch; }
float GetReactionTime() const { return m_reactionTime; }
float GetAttackDelay() const { return m_attackDelay; }
int GetVoiceBank() const { return m_voiceBank; }
bool IsValidForTeam(BotProfileTeamType team) const;
bool PrefersSilencer() const { return m_prefersSilencer; }
private:
void Inherit(const BotProfile *parent, const BotProfile *baseline);
friend class BotProfileManager;
char *m_name;
float m_aggression;
float m_skill;
float m_teamwork;
enum { MAX_WEAPON_PREFS = 16 };
int m_weaponPreference[MAX_WEAPON_PREFS];
int m_weaponPreferenceCount;
int m_cost;
int m_skin;
unsigned char m_difficultyFlags;
int m_voicePitch;
float m_reactionTime;
float m_attackDelay;
enum BotProfileTeamType m_teams;
bool m_prefersSilencer;
int m_voiceBank;
};
inline bool BotProfile::IsDifficulty(BotDifficultyType diff) const
{
return (m_difficultyFlags & (1 << diff)) ? true : false;
}
inline void BotProfile::Inherit(const BotProfile *parent, const BotProfile *baseline)
{
if (parent->m_aggression != baseline->m_aggression)
m_aggression = parent->m_aggression;
if (parent->m_skill != baseline->m_skill)
m_skill = parent->m_skill;
if (parent->m_teamwork != baseline->m_teamwork)
m_teamwork = parent->m_teamwork;
if (parent->m_weaponPreferenceCount != baseline->m_weaponPreferenceCount)
{
m_weaponPreferenceCount = parent->m_weaponPreferenceCount;
for (int i = 0; i < parent->m_weaponPreferenceCount; ++i)
m_weaponPreference[i] = parent->m_weaponPreference[i];
}
if (parent->m_cost != baseline->m_cost)
m_cost = parent->m_cost;
if (parent->m_skin != baseline->m_skin)
m_skin = parent->m_skin;
if (parent->m_difficultyFlags != baseline->m_difficultyFlags)
m_difficultyFlags = parent->m_difficultyFlags;
if (parent->m_voicePitch != baseline->m_voicePitch)
m_voicePitch = parent->m_voicePitch;
if (parent->m_reactionTime != baseline->m_reactionTime)
m_reactionTime = parent->m_reactionTime;
if (parent->m_attackDelay != baseline->m_attackDelay)
m_attackDelay = parent->m_attackDelay;
if (parent->m_teams != baseline->m_teams)
m_teams = parent->m_teams;
if (parent->m_voiceBank != baseline->m_voiceBank)
m_voiceBank = parent->m_voiceBank;
}
typedef CUtlLinkedList <BotProfile *, int> BotProfileList;
class BotProfileManager
{
public:
BotProfileManager();
~BotProfileManager();
void Init(const char *filename, unsigned int *checksum = NULL);
void Reset();
const BotProfile *GetProfile (const char *name, BotProfileTeamType team) const
{
FOR_EACH_LL (m_profileList, it)
{
BotProfile *profile = m_profileList[it];
if (!Q_stricmp (name, profile->GetName ()) && profile->IsValidForTeam (team))
return profile;
}
return NULL;
}
const BotProfileList *GetProfileList() const { return &m_profileList; }
const BotProfile *GetRandomProfile(BotDifficultyType difficulty, BotProfileTeamType team) const;
const char *GetCustomSkin(int index);
const char *GetCustomSkinModelname(int index);
const char *GetCustomSkinFname(int index);
int GetCustomSkinIndex(const char *name, const char *filename = NULL);
typedef CUtlVector<char *> VoiceBankList;
const VoiceBankList *GetVoiceBanks() const { return &m_voiceBanks; }
int FindVoiceBankIndex(const char *filename);
protected:
BotProfileList m_profileList;
VoiceBankList m_voiceBanks;
char *m_skins[NumCustomSkins];
char *m_skinModelnames[NumCustomSkins];
char *m_skinFilenames[NumCustomSkins];
int m_nextSkin;
};
extern BotProfileManager *TheBotProfiles;
#endif // BOT_PROFILE

View File

@ -0,0 +1,693 @@
#include "bot/bot_common.h"
/*
* Globals initialization
*/
short s_iBeamSprite = 0;
float cosTable[ COS_TABLE_SIZE ];
bool UTIL_IsNameTaken (const char *name, bool ignoreHumans)
{
for (int i = 1; i <= gpGlobals->maxClients; ++i)
{
CBaseEntity *player = UTIL_PlayerByIndex (i);
if (player == NULL)
continue;
if (FNullEnt (player->pev))
continue;
if (FStrEq (STRING (player->pev->netname), ""))
continue;
if (player->IsPlayer () && (((CBasePlayer *)player)->IsBot () == TRUE))
{
// bots can have prefixes so we need to check the name
// against the profile name instead.
CBot *bot = static_cast<CBot *>(player);
if (FStrEq (name, bot->GetProfile ()->GetName ()))
{
return true;
}
}
else
{
if (!ignoreHumans)
{
if (FStrEq (name, STRING (player->pev->netname)))
return true;
}
}
}
return false;
}
int UTIL_ClientsInGame ()
{
int iCount = 0;
for (int iIndex = 1; iIndex <= gpGlobals->maxClients; ++iIndex)
{
CBaseEntity *pPlayer = UTIL_PlayerByIndex (iIndex);
if (pPlayer == NULL)
continue;
if (FNullEnt (pPlayer->pev))
continue;
if (FStrEq (STRING (pPlayer->pev->netname), ""))
continue;
++iCount;
}
return iCount;
}
int UTIL_ActivePlayersInGame()
{
int iCount = 0;
for (int iIndex = 1; iIndex <= gpGlobals->maxClients; ++iIndex)
{
CBaseEntity *entity = UTIL_PlayerByIndex(iIndex);
if (entity == NULL)
continue;
if (FNullEnt(entity->pev))
continue;
if (FStrEq(STRING(entity->pev->netname), ""))
continue;
CBasePlayer *player = static_cast<CBasePlayer *>(entity);
++iCount;
}
return iCount;
}
int UTIL_HumansInGame(bool ignoreSpectators)
{
int iCount = 0;
for (int iIndex = 1; iIndex <= gpGlobals->maxClients; ++iIndex)
{
CBaseEntity *entity = UTIL_PlayerByIndex(iIndex);
if (entity == NULL)
continue;
if (FNullEnt(entity->pev))
continue;
if (FStrEq(STRING(entity->pev->netname), ""))
continue;
CBasePlayer *player = static_cast<CBasePlayer *>(entity);
if (player->IsBot())
continue;
//if (ignoreSpectators && player->m_iTeam != TERRORIST && player->m_iTeam != CT)
// continue;
//if (ignoreSpectators && player->m_iJoiningState != JOINED)
// continue;
++iCount;
}
return iCount;
}
int UTIL_HumansOnTeam(int teamID, bool isAlive)
{
int iCount = 0;
for (int iIndex = 1; iIndex <= gpGlobals->maxClients; ++iIndex)
{
CBaseEntity *entity = UTIL_PlayerByIndex(iIndex);
if (entity == NULL)
continue;
if (FNullEnt(entity->pev))
continue;
if (FStrEq(STRING(entity->pev->netname), ""))
continue;
CBasePlayer *player = static_cast<CBasePlayer *>(entity);
if (player->IsBot())
continue;
//if (player->m_iTeam != teamID)
// continue;
if (isAlive && !player->IsAlive())
continue;
++iCount;
}
return iCount;
}
int UTIL_BotsInGame()
{
int iCount = 0;
for (int iIndex = 1; iIndex <= gpGlobals->maxClients; ++iIndex)
{
CBasePlayer *pPlayer = static_cast<CBasePlayer *>(UTIL_PlayerByIndex(iIndex));
if (pPlayer == NULL)
continue;
if (FNullEnt(pPlayer->pev))
continue;
if (FStrEq(STRING(pPlayer->pev->netname), ""))
continue;
if (!pPlayer->IsBot())
continue;
++iCount;
}
return iCount;
}
/*
bool UTIL_KickBotFromTeam(TeamName kickTeam)
{
int i;
// try to kick a dead bot first
for (i = 1; i <= gpGlobals->maxClients; ++i)
{
CBasePlayer *player = static_cast<CBasePlayer *>(UTIL_PlayerByIndex(i));
if (player == NULL)
continue;
if (FNullEnt(player->pev))
continue;
const char *name = STRING(player->pev->netname);
if (FStrEq(name, ""))
continue;
if (!player->IsBot())
continue;
if (!player->IsAlive() && player->m_iTeam == kickTeam)
{
// its a bot on the right team - kick it
SERVER_COMMAND(UTIL_VarArgs("kick \"%s\"\n", STRING(player->pev->netname)));
return true;
}
}
// no dead bots, kick any bot on the given team
for (i = 1; i <= gpGlobals->maxClients; ++i)
{
CBasePlayer *player = static_cast<CBasePlayer *>(UTIL_PlayerByIndex(i));
if (player == NULL)
continue;
if (FNullEnt(player->pev))
continue;
const char *name = STRING(player->pev->netname);
if (FStrEq(name, ""))
continue;
if (!player->IsBot())
continue;
if (player->m_iTeam == kickTeam)
{
// its a bot on the right team - kick it
SERVER_COMMAND(UTIL_VarArgs("kick \"%s\"\n", STRING(player->pev->netname)));
return true;
}
}
return false;
}
*/
bool UTIL_IsTeamAllBots(int team)
{
int botCount = 0;
for (int i = 1; i <= gpGlobals->maxClients; ++i)
{
CBasePlayer *player = static_cast<CBasePlayer *>(UTIL_PlayerByIndex(i));
if (player == NULL)
continue;
//if (player->m_iTeam != team)
// continue;
if (FNullEnt(player->pev))
continue;
if (FStrEq(STRING(player->pev->netname), ""))
continue;
if (!(player->pev->flags & FL_FAKECLIENT))
return false;
++botCount;
}
return (botCount) ? true : false;
}
// Return the closest active player to the given position.
// If 'distance' is non-NULL, the distance to the closest player is returned in it.
/*extern*/ CBasePlayer *UTIL_GetClosestPlayer(const Vector *pos, float *distance)
{
CBasePlayer *closePlayer = NULL;
float closeDistSq = 1.0e12f; // 999999999999.9f
for (int i = 1; i <= gpGlobals->maxClients; ++i)
{
CBasePlayer *player = static_cast<CBasePlayer *>(UTIL_PlayerByIndex(i));
if (!IsEntityValid(player))
continue;
if (!player->IsAlive())
continue;
float distSq = (player->pev->origin - *pos).LengthSquared();
if (distSq < closeDistSq)
{
closeDistSq = distSq;
closePlayer = player;
}
}
if (distance)
*distance = sqrt(closeDistSq);
return closePlayer;
}
// Return the closest active player on the given team to the given position.
// If 'distance' is non-NULL, the distance to the closest player is returned in it.
/*extern*/ CBasePlayer *UTIL_GetClosestPlayer(const Vector *pos, int team, float *distance)
{
CBasePlayer *closePlayer = NULL;
float closeDistSq = 1.0e12f; // 999999999999.9f
for (int i = 1; i <= gpGlobals->maxClients; ++i)
{
CBasePlayer *player = static_cast<CBasePlayer *>(UTIL_PlayerByIndex(i));
if (!IsEntityValid(player))
continue;
if (!player->IsAlive())
continue;
// if (player->m_iTeam != team)
// continue;
float distSq = (player->pev->origin - *pos).LengthSquared();
if (distSq < closeDistSq)
{
closeDistSq = distSq;
closePlayer = player;
}
}
if (distance)
*distance = sqrt(closeDistSq);
return closePlayer;
}
const char *UTIL_GetBotPrefix()
{
return cv_bot_prefix.string;
}
void UTIL_ConstructBotNetName(char *name, int nameLength, const BotProfile *profile)
{
if (profile == NULL)
{
name[0] = '\0';
return;
}
// if there is no bot prefix just use the profile name.
if ((UTIL_GetBotPrefix() == NULL) || (Q_strlen(UTIL_GetBotPrefix()) == 0))
{
Q_strncpy(name, profile->GetName(), nameLength);
return;
}
Q_snprintf(name, nameLength, "%s %s", UTIL_GetBotPrefix(), profile->GetName());
}
bool UTIL_IsVisibleToTeam(const Vector &spot, int team, float maxRange)
{
for (int i = 1; i <= gpGlobals->maxClients; ++i)
{
CBasePlayer *player = static_cast<CBasePlayer *>(UTIL_PlayerByIndex(i));
if (player == NULL)
continue;
if (FNullEnt(player->pev))
continue;
if (FStrEq(STRING(player->pev->netname), ""))
continue;
if (!player->IsAlive())
continue;
// if (player->m_iTeam != team)
// continue;
if (maxRange > 0.0f && (spot - player->Center()).IsLengthGreaterThan(maxRange))
continue;
TraceResult result;
UTIL_TraceLine(player->EyePosition(), spot, ignore_monsters, ignore_glass, ENT(player->pev), &result);
if (result.flFraction == 1.0f)
return true;
}
return false;
}
CBasePlayer *UTIL_GetLocalPlayer()
{
if (!IS_DEDICATED_SERVER())
return static_cast<CBasePlayer *>(UTIL_PlayerByIndex(1));
return NULL;
}
NOXREF Vector UTIL_ComputeOrigin(entvars_t *pevVars)
{
if (pevVars->origin.x == 0.0f && pevVars->origin.y == 0.0f && pevVars->origin.z == 0.0f)
return (pevVars->absmax + pevVars->absmin) * 0.5f;
else
return pevVars->origin;
}
NOXREF Vector UTIL_ComputeOrigin(CBaseEntity *pEntity)
{
return UTIL_ComputeOrigin(pEntity->pev);
}
NOXREF Vector UTIL_ComputeOrigin(edict_t *pentEdict)
{
return UTIL_ComputeOrigin(VARS(pentEdict));
}
NOXREF void UTIL_DrawBeamFromEnt(int iIndex, Vector vecEnd, int iLifetime, byte bRed, byte bGreen, byte bBlue)
{
MESSAGE_BEGIN(MSG_PVS, SVC_TEMPENTITY, vecEnd);
WRITE_BYTE(TE_BEAMENTPOINT);
WRITE_SHORT(iIndex);
WRITE_COORD(vecEnd.x);
WRITE_COORD(vecEnd.y);
WRITE_COORD(vecEnd.z);
WRITE_SHORT(s_iBeamSprite);
WRITE_BYTE(0);
WRITE_BYTE(0);
WRITE_BYTE(iLifetime);
WRITE_BYTE(10);
WRITE_BYTE(0);
WRITE_BYTE(bRed);
WRITE_BYTE(bGreen);
WRITE_BYTE(bBlue);
WRITE_BYTE(255);
WRITE_BYTE(0);
MESSAGE_END();
}
void UTIL_DrawBeamPoints(Vector vecStart, Vector vecEnd, int iLifetime, byte bRed, byte bGreen, byte bBlue)
{
MESSAGE_BEGIN(MSG_PVS, SVC_TEMPENTITY, vecStart);
WRITE_BYTE(TE_BEAMPOINTS);
WRITE_COORD(vecStart.x);
WRITE_COORD(vecStart.y);
WRITE_COORD(vecStart.z);
WRITE_COORD(vecEnd.x);
WRITE_COORD(vecEnd.y);
WRITE_COORD(vecEnd.z);
WRITE_SHORT(s_iBeamSprite);
WRITE_BYTE(0);
WRITE_BYTE(0);
WRITE_BYTE(iLifetime);
WRITE_BYTE(10);
WRITE_BYTE(0);
WRITE_BYTE(bRed);
WRITE_BYTE(bGreen);
WRITE_BYTE(bBlue);
WRITE_BYTE(255);
WRITE_BYTE(0);
MESSAGE_END();
}
void CONSOLE_ECHO(char *pszMsg, ...)
{
va_list argptr;
static char szStr[1024];
va_start(argptr, pszMsg);
vsprintf(szStr, pszMsg, argptr);
va_end(argptr);
SERVER_PRINT(szStr);
}
void CONSOLE_ECHO_LOGGED(char *pszMsg, ...)
{
va_list argptr;
static char szStr[1024];
va_start(argptr, pszMsg);
vsprintf(szStr, pszMsg, argptr);
va_end(argptr);
SERVER_PRINT(szStr);
UTIL_LogPrintf(szStr);
}
void BotPrecache()
{
s_iBeamSprite = PRECACHE_MODEL("sprites/smoke.spr");
PRECACHE_SOUND("buttons/bell1.wav");
PRECACHE_SOUND("buttons/blip1.wav");
PRECACHE_SOUND("buttons/blip2.wav");
PRECACHE_SOUND("buttons/button11.wav");
PRECACHE_SOUND("buttons/latchunlocked2.wav");
PRECACHE_SOUND("buttons/lightswitch2.wav");
PRECACHE_SOUND("ambience/quail1.wav");
PRECACHE_SOUND("events/tutor_msg.wav");
PRECACHE_SOUND("events/enemy_died.wav");
PRECACHE_SOUND("events/friend_died.wav");
PRECACHE_SOUND("events/task_complete.wav");
}
void InitBotTrig()
{
for (int i = 0; i < COS_TABLE_SIZE; ++i)
{
float angle = 2.0f * M_PI * (float)i / (float)(COS_TABLE_SIZE - 1);
cosTable[i] = cos(angle);
}
}
float BotCOS(float angle)
{
angle = NormalizeAnglePositive(angle);
int i = angle * ((COS_TABLE_SIZE - 1) / 360.0f);
return cosTable[ i ];
}
float BotSIN(float angle)
{
angle = NormalizeAnglePositive(angle - 90);
int i = angle * ((COS_TABLE_SIZE - 1) / 360.0f);
return cosTable[ i ];
}
// Determine if this event is audible, and if so, return its audible range and priority
bool IsGameEventAudible(GameEventType event, CBaseEntity *entity, CBaseEntity *other, float *range, PriorityType *priority, bool *isHostile)
{
#if 0
CBasePlayer *player = static_cast<CBasePlayer *>(entity);
if (entity == NULL || !player->IsPlayer())
player = NULL;
const float ShortRange = 1000.0f;
const float NormalRange = 2000.0f;
switch (event)
{
// TODO: Check weapon type (knives are pretty quiet)
// TODO: Use actual volume, account for silencers, etc.
case EVENT_WEAPON_FIRED:
{
if (player->m_pActiveItem == NULL)
return false;
switch (player->m_pActiveItem->m_iId)
{
// silent "firing"
case WEAPON_HEGRENADE:
case WEAPON_SMOKEGRENADE:
case WEAPON_FLASHBANG:
case WEAPON_SHIELDGUN:
case WEAPON_C4:
return false;
// quiet
case WEAPON_KNIFE:
case WEAPON_TMP:
*range = ShortRange;
break;
// M4A1 - check for silencer
case WEAPON_M4A1:
{
CBasePlayerWeapon *pWeapon = static_cast<CBasePlayerWeapon *>(player->m_pActiveItem);
if (pWeapon->m_iWeaponState & WPNSTATE_M4A1_SILENCED)
*range = ShortRange;
else
*range = NormalRange;
break;
}
// USP - check for silencer
case WEAPON_USP:
{
CBasePlayerWeapon *pWeapon = static_cast<CBasePlayerWeapon *>(player->m_pActiveItem);
if (pWeapon->m_iWeaponState & WPNSTATE_USP_SILENCED)
*range = ShortRange;
else
*range = NormalRange;
break;
}
// loud
case WEAPON_AWP:
*range = 99999.0f;
break;
// normal
default:
*range = NormalRange;
break;
}
*priority = PRIORITY_HIGH;
*isHostile = true;
return true;
}
case EVENT_HE_GRENADE_EXPLODED:
*range = 99999.0f;
*priority = PRIORITY_HIGH;
*isHostile = true;
return true;
case EVENT_FLASHBANG_GRENADE_EXPLODED:
*range = 1000.0f;
*priority = PRIORITY_LOW;
*isHostile = true;
return true;
case EVENT_SMOKE_GRENADE_EXPLODED:
*range = 1000.0f;
*priority = PRIORITY_LOW;
*isHostile = true;
return true;
case EVENT_GRENADE_BOUNCED:
*range = 500.0f;
*priority = PRIORITY_LOW;
*isHostile = true;
return true;
case EVENT_BREAK_GLASS:
case EVENT_BREAK_WOOD:
case EVENT_BREAK_METAL:
case EVENT_BREAK_FLESH:
case EVENT_BREAK_CONCRETE:
*range = 1100.0f;
*priority = PRIORITY_MEDIUM;
*isHostile = true;
return true;
case EVENT_DOOR:
*range = 1100.0f;
*priority = PRIORITY_MEDIUM;
*isHostile = false;
return true;
case EVENT_WEAPON_FIRED_ON_EMPTY:
case EVENT_PLAYER_FOOTSTEP:
case EVENT_WEAPON_RELOADED:
case EVENT_WEAPON_ZOOMED:
case EVENT_PLAYER_LANDED_FROM_HEIGHT:
*range = 1100.0f;
*priority = PRIORITY_LOW;
*isHostile = false;
return true;
case EVENT_HOSTAGE_USED:
case EVENT_HOSTAGE_CALLED_FOR_HELP:
*range = 1200.0f;
*priority = PRIORITY_MEDIUM;
*isHostile = false;
return true;
}
return false;
#else
return true;
#endif
}
void HintMessageToAllPlayers(const char *message)
{
hudtextparms_t textParms;
textParms.x = -1.0f;
textParms.y = -1.0f;
textParms.effect = 0;
textParms.r1 = 100;
textParms.g1 = 255;
textParms.b1 = 100;
textParms.r2 = 255;
textParms.g2 = 255;
textParms.b2 = 255;
textParms.fadeinTime = 1.0f;
textParms.fadeoutTime = 5.0f;
textParms.holdTime = 5.0f;
textParms.fxTime = 0.0f;
textParms.channel = 0;
UTIL_HudMessageAll(textParms, message);
}

242
dlls/bot/manager/bot_util.h Normal file
View File

@ -0,0 +1,242 @@
/*
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* In addition, as a special exception, the author gives permission to
* link the code of this program with the Half-Life Game Engine ("HL
* Engine") and Modified Game Libraries ("MODs") developed by Valve,
* L.L.C ("Valve"). You must obey the GNU General Public License in all
* respects for all of the code used other than the HL Engine and MODs
* from Valve. If you modify this file, you may extend this exception
* to your version of the file, but you are not obligated to do so. If
* you do not wish to do so, delete this exception statement from your
* version.
*
*/
#ifndef BOT_UTIL_H
#define BOT_UTIL_H
#ifdef _WIN32
#pragma once
#endif
#define COS_TABLE_SIZE 256
#define RAD_TO_DEG(deg) ((deg) * 180.0 / M_PI)
#define DEG_TO_RAD(rad) ((rad) * M_PI / 180.0)
#define SIGN(num) (((num) < 0) ? -1 : 1)
#define ABS(num) (SIGN(num) * (num))
class CBasePlayer;
class BotProfile;
enum PriorityType
{
PRIORITY_LOW, PRIORITY_MEDIUM, PRIORITY_HIGH, PRIORITY_UNINTERRUPTABLE
};
// Simple class for tracking intervals of game time
class IntervalTimer
{
public:
IntervalTimer() { m_timestamp = -1.0f; }
void Reset() { m_timestamp = gpGlobals->time; }
void Start() { m_timestamp = gpGlobals->time; }
void Invalidate() { m_timestamp = -1.0f; }
bool HasStarted() const { return (m_timestamp > 0.0f); }
// if not started, elapsed time is very large
float GetElapsedTime() const { return (HasStarted()) ? (gpGlobals->time - m_timestamp) : 99999.9f; }
bool IsLessThen(float duration) const { return (gpGlobals->time - m_timestamp < duration) ? true : false; }
bool IsGreaterThen(float duration) const { return (gpGlobals->time - m_timestamp > duration) ? true : false; }
private:
float m_timestamp;
};
// Simple class for counting down a short interval of time
class CountdownTimer
{
public:
CountdownTimer() { m_timestamp = -1.0f; m_duration = 0.0f; }
void Reset() { m_timestamp = gpGlobals->time + m_duration; }
void Start(float duration) { m_timestamp = gpGlobals->time + duration; m_duration = duration; }
bool HasStarted() const { return (m_timestamp > 0.0f); }
void Invalidate() { m_timestamp = -1.0f; }
bool IsElapsed() const { return (gpGlobals->time > m_timestamp); }
private:
float m_duration;
float m_timestamp;
};
// Return true if the given entity is valid
inline bool IsEntityValid(CBaseEntity *entity)
{
if (entity == NULL)
return false;
if (FNullEnt(entity->pev))
return false;
if (FStrEq(STRING(entity->pev->netname), ""))
return false;
if (entity->pev->flags & FL_DORMANT)
return false;
return true;
}
// Given two line segments: startA to endA, and startB to endB, return true if they intesect
// and put the intersection point in "result".
// Note that this computes the intersection of the 2D (x,y) projection of the line segments.
inline bool IsIntersecting2D(const Vector &startA, const Vector &endA, const Vector &startB, const Vector &endB, Vector *result = NULL)
{
float denom = (endA.x - startA.x) * (endB.y - startB.y) - (endA.y - startA.y) * (endB.x - startB.x);
if (denom == 0.0f)
{
// parallel
return false;
}
float numS = (startA.y - startB.y) * (endB.x - startB.x) - (startA.x - startB.x) * (endB.y - startB.y);
if (numS == 0.0f)
{
// coincident
return true;
}
float numT = (startA.y - startB.y) * (endA.x - startA.x) - (startA.x - startB.x) * (endA.y - startA.y);
float s = numS / denom;
if (s < 0.0f || s > 1.0f)
{
// intersection is not within line segment of startA to endA
return false;
}
float t = numT / denom;
if (t < 0.0f || t > 1.0f)
{
// intersection is not within line segment of startB to endB
return false;
}
// compute intesection point
if (result)
{
*result = startA + s * (endA - startA);
}
return true;
}
// Iterate over all active players in the game, invoking functor on each.
// If functor returns false, stop iteration and return false.
template <typename Functor>
bool ForEachPlayer(Functor &func)
{
for (int i = 1; i <= gpGlobals->maxClients; ++i)
{
CBasePlayer *player = static_cast<CBasePlayer *>(UTIL_PlayerByIndex(i));
if (!IsEntityValid((CBaseEntity *)player))
continue;
if (!player->IsPlayer())
continue;
if (func(player) == false)
return false;
}
return true;
}
extern cvar_t cv_bot_traceview;
extern cvar_t cv_bot_stop;
extern cvar_t cv_bot_show_nav;
extern cvar_t cv_bot_show_danger;
extern cvar_t cv_bot_nav_edit;
extern cvar_t cv_bot_nav_zdraw;
extern cvar_t cv_bot_walk;
extern cvar_t cv_bot_difficulty;
extern cvar_t cv_bot_debug;
extern cvar_t cv_bot_quicksave;
extern cvar_t cv_bot_quota;
extern cvar_t cv_bot_quota_match;
extern cvar_t cv_bot_prefix;
extern cvar_t cv_bot_allow_rogues;
extern cvar_t cv_bot_allow_pistols;
extern cvar_t cv_bot_allow_shotguns;
extern cvar_t cv_bot_allow_sub_machine_guns;
extern cvar_t cv_bot_allow_rifles;
extern cvar_t cv_bot_allow_machine_guns;
extern cvar_t cv_bot_allow_grenades;
extern cvar_t cv_bot_allow_snipers;
extern cvar_t cv_bot_allow_shield;
extern cvar_t cv_bot_join_team;
extern cvar_t cv_bot_join_after_player;
extern cvar_t cv_bot_auto_vacate;
extern cvar_t cv_bot_zombie;
extern cvar_t cv_bot_defer_to_human;
extern cvar_t cv_bot_chatter;
extern cvar_t cv_bot_profile_db;
#define IS_ALIVE true
int UTIL_HumansOnTeam(int teamID, bool isAlive = false);
#define IGNORE_SPECTATORS true
int UTIL_HumansInGame(bool ignoreSpectators = false);
bool UTIL_IsNameTaken(const char *name, bool ignoreHumans = false);
int UTIL_ClientsInGame();
int UTIL_ActivePlayersInGame();
int UTIL_BotsInGame();
//bool UTIL_KickBotFromTeam(TeamName kickTeam);
bool UTIL_IsTeamAllBots(int team);
CBasePlayer *UTIL_GetClosestPlayer(const Vector *pos, float *distance = NULL);
CBasePlayer *UTIL_GetClosestPlayer(const Vector *pos, int team, float *distance = NULL);
const char *UTIL_GetBotPrefix();
void UTIL_ConstructBotNetName(char *name, int nameLength, const BotProfile *profile);
bool UTIL_IsVisibleToTeam(const Vector &spot, int team, float maxRange = -1.0f);
CBasePlayer *UTIL_GetLocalPlayer();
NOXREF Vector UTIL_ComputeOrigin(entvars_t *pevVars);
NOXREF Vector UTIL_ComputeOrigin(CBaseEntity *pEntity);
NOXREF Vector UTIL_ComputeOrigin(edict_t *pentEdict);
NOXREF void UTIL_DrawBeamFromEnt(int iIndex, Vector vecEnd, int iLifetime, byte bRed, byte bGreen, byte bBlue);
void UTIL_DrawBeamPoints(Vector vecStart, Vector vecEnd, int iLifetime, byte bRed, byte bGreen, byte bBlue);
// Echos text to the console, and prints it on the client's screen. This is NOT tied to the developer cvar.
// If you are adding debugging output in cstrike, use UTIL_DPrintf() (debug.h) instead.
void CONSOLE_ECHO(char *pszMsg, ...);
void CONSOLE_ECHO_LOGGED(char *pszMsg, ...);
void BotPrecache();
void InitBotTrig();
float BotCOS(float angle);
float BotSIN(float angle);
bool IsGameEventAudible(enum GameEventType event, CBaseEntity *entity, CBaseEntity *other, float *range, PriorityType *priority, bool *isHostile);
void HintMessageToAllPlayers(const char *message);
#endif // BOT_UTIL_H

131
dlls/bot/manager/improv.h Normal file
View File

@ -0,0 +1,131 @@
/*
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* In addition, as a special exception, the author gives permission to
* link the code of this program with the Half-Life Game Engine ("HL
* Engine") and Modified Game Libraries ("MODs") developed by Valve,
* L.L.C ("Valve"). You must obey the GNU General Public License in all
* respects for all of the code used other than the HL Engine and MODs
* from Valve. If you modify this file, you may extend this exception
* to your version of the file, but you are not obligated to do so. If
* you do not wish to do so, delete this exception statement from your
* version.
*
*/
#ifndef IMPROV_H
#define IMPROV_H
#ifdef _WIN32
#pragma once
#endif
class CBaseEntity;
class CNavLadder;
// Improv-specific events
class IImprovEvent
{
public:
virtual void OnMoveToSuccess(const Vector &goal) {}; // invoked when an improv reaches its MoveTo goal
enum MoveToFailureType
{
FAIL_INVALID_PATH = 0,
FAIL_STUCK,
FAIL_FELL_OFF,
};
virtual void OnMoveToFailure(const Vector &goal, MoveToFailureType reason) {}; // invoked when an improv fails to reach a MoveTo goal
virtual void OnInjury(float amount) {}; // invoked when the improv is injured
};
// The Improv interface definition
// An "Improv" is an improvisational actor that simulates the
// behavor of a human in an unscripted, "make it up as you go" manner.
class CImprov: public IImprovEvent
{
public:
virtual ~CImprov() {};
virtual bool IsAlive() const = 0; // return true if this improv is alive
virtual void MoveTo(const Vector &goal) = 0; // move improv towards far-away goal (pathfind)
virtual void LookAt(const Vector &target) = 0; // define desired view target
virtual void ClearLookAt() = 0; // remove view goal
virtual void FaceTo(const Vector &goal) = 0; // orient body towards goal
virtual void ClearFaceTo() = 0; // remove body orientation goal
virtual bool IsAtMoveGoal(float error = 20.0f) const = 0; // return true if improv is standing on its movement goal
virtual bool HasLookAt() const = 0; // return true if improv has a look at goal
virtual bool HasFaceTo() const = 0; // return true if improv has a face to goal
virtual bool IsAtFaceGoal() const = 0; // return true if improv is facing towards its face goal
virtual bool IsFriendInTheWay(const Vector &goalPos) const = 0; // return true if a friend is blocking our line to the given goal position
virtual bool IsFriendInTheWay(CBaseEntity *myFriend, const Vector &goalPos) const = 0; // return true if the given friend is blocking our line to the given goal position
virtual void MoveForward() = 0;
virtual void MoveBackward() = 0;
virtual void StrafeLeft() = 0;
virtual void StrafeRight() = 0;
virtual bool Jump() = 0;
virtual void Crouch() = 0;
virtual void StandUp() = 0; // "un-crouch"
virtual void TrackPath(const Vector &pathGoal, float deltaT) = 0; // move along path by following "pathGoal"
virtual void StartLadder(const CNavLadder *ladder, NavTraverseType how, const Vector *approachPos, const Vector *departPos) = 0; // invoked when a ladder is encountered while following a path
virtual bool TraverseLadder(const CNavLadder *ladder, NavTraverseType how, const Vector *approachPos, const Vector *departPos, float deltaT) = 0; // traverse given ladder
virtual bool GetSimpleGroundHeightWithFloor(const Vector *pos, float *height, Vector *normal = NULL) = 0; // find "simple" ground height, treating current nav area as part of the floor
virtual void Run() = 0;
virtual void Walk() = 0;
virtual void Stop() = 0;
virtual float GetMoveAngle() const = 0; // return direction of movement
virtual float GetFaceAngle() const = 0; // return direction of view
virtual const Vector &GetFeet() const = 0; // return position of "feet" - point below centroid of improv at feet level
virtual const Vector &GetCentroid() const = 0;
virtual const Vector &GetEyes() const = 0;
virtual bool IsRunning() const = 0;
virtual bool IsWalking() const = 0;
virtual bool IsStopped() const = 0;
virtual bool IsCrouching() const = 0;
virtual bool IsJumping() const = 0;
virtual bool IsUsingLadder() const = 0;
virtual bool IsOnGround() const = 0;
virtual bool IsMoving() const = 0; // if true, improv is walking, crawling, running somewhere
virtual bool CanRun() const = 0;
virtual bool CanCrouch() const = 0;
virtual bool CanJump() const = 0;
virtual bool IsVisible(const Vector &pos, bool testFOV = false) const = 0; // return true if improv can see position
virtual bool IsPlayerLookingAtMe(CBasePlayer *other, float cosTolerance = 0.95f) const = 0; // return true if 'other' is looking right at me
virtual CBasePlayer *IsAnyPlayerLookingAtMe(int team = 0, float cosTolerance = 0.95f) const = 0; // return player on given team that is looking right at me (team == 0 means any team), NULL otherwise
virtual CBasePlayer *GetClosestPlayerByTravelDistance(int team = 0, float *range = NULL) const = 0; // return actual travel distance to closest player on given team (team == 0 means any team)
virtual CNavArea *GetLastKnownArea() const = 0;
virtual void OnUpdate(float deltaT) = 0; // a less frequent, full update 'tick'
virtual void OnUpkeep(float deltaT) = 0; // a frequent, lightweight update 'tick'
virtual void OnReset() = 0; // reset improv to initial state
virtual void OnGameEvent(GameEventType event, CBaseEntity *entity, CBaseEntity *other) = 0; // invoked when an event occurs in the game
virtual void OnTouch(CBaseEntity *other) = 0; // "other" has touched us
};
#endif // IMPROV_H

408
dlls/bot/manager/nav.h Normal file
View File

@ -0,0 +1,408 @@
/*
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* In addition, as a special exception, the author gives permission to
* link the code of this program with the Half-Life Game Engine ("HL
* Engine") and Modified Game Libraries ("MODs") developed by Valve,
* L.L.C ("Valve"). You must obey the GNU General Public License in all
* respects for all of the code used other than the HL Engine and MODs
* from Valve. If you modify this file, you may extend this exception
* to your version of the file, but you are not obligated to do so. If
* you do not wish to do so, delete this exception statement from your
* version.
*
*/
#ifndef NAV_H
#define NAV_H
#ifdef _WIN32
#pragma once
#endif
// STL uses exceptions, but we are not compiling with them - ignore warning
#pragma warning(disable : 4530)
// to help identify nav files
#define NAV_MAGIC_NUMBER 0xFEEDFACE
// A place is a named group of navigation areas
typedef unsigned int Place;
// ie: "no place"
#define UNDEFINED_PLACE 0
#define ANY_PLACE 0xFFFF
#define WALK_THRU_DOORS 0x01
#define WALK_THRU_BREAKABLES 0x02
#define WALK_THRU_EVERYTHING (WALK_THRU_DOORS | WALK_THRU_BREAKABLES)
enum NavErrorType
{
NAV_OK,
NAV_CANT_ACCESS_FILE,
NAV_INVALID_FILE,
NAV_BAD_FILE_VERSION,
NAV_CORRUPT_DATA,
};
enum NavAttributeType
{
NAV_CROUCH = 0x01, // must crouch to use this node/area
NAV_JUMP = 0x02, // must jump to traverse this area
NAV_PRECISE = 0x04, // do not adjust for obstacles, just move along area
NAV_NO_JUMP = 0x08, // inhibit discontinuity jumping
};
enum NavDirType
{
NORTH = 0,
EAST,
SOUTH,
WEST,
NUM_DIRECTIONS
};
// Defines possible ways to move from one area to another
enum NavTraverseType
{
// NOTE: First 4 directions MUST match NavDirType
GO_NORTH = 0,
GO_EAST,
GO_SOUTH,
GO_WEST,
GO_LADDER_UP,
GO_LADDER_DOWN,
GO_JUMP,
NUM_TRAVERSE_TYPES
};
enum NavCornerType
{
NORTH_WEST = 0,
NORTH_EAST,
SOUTH_EAST,
SOUTH_WEST,
NUM_CORNERS
};
enum NavRelativeDirType
{
FORWARD = 0,
RIGHT,
BACKWARD,
LEFT,
UP,
DOWN,
NUM_RELATIVE_DIRECTIONS
};
const float GenerationStepSize = 25.0f; // (30) was 20, but bots can't fit always fit
const float StepHeight = 18.0f; // if delta Z is greater than this, we have to jump to get up
const float JumpHeight = 41.8f; // if delta Z is less than this, we can jump up on it
const float JumpCrouchHeight = 58.0f; // (48) if delta Z is less than or equal to this, we can jumpcrouch up on it
// Strictly speaking, you CAN get up a slope of 1.643 (about 59 degrees), but you move very, very slowly
// This slope will represent the slope you can navigate without much slowdown
// rise/run - if greater than this, we can't move up it (de_survivor canyon ramps)
const float MaxSlope = 1.4f;
// instead of MaxSlope, we are using the following max Z component of a unit normal
const float MaxUnitZSlope = 0.7f;
const float BotRadius = 10.0f; // circular extent that contains bot
const float DeathDrop = 200.0f; // (300) distance at which we will die if we fall - should be about 600, and pay attention to fall damage during pathfind
const float HalfHumanWidth = 16.0f;
const float HalfHumanHeight = 36.0f;
const float HumanHeight = 72.0f;
struct Extent
{
Vector lo;
Vector hi;
float SizeX() const { return hi.x - lo.x; }
float SizeY() const { return hi.y - lo.y;}
float SizeZ() const { return hi.z - lo.z; }
float Area() const { return SizeX() * SizeY(); }
// return true if 'pos' is inside of this extent
bool Contains(const Vector *pos) const
{
return (pos->x >= lo.x && pos->x <= hi.x &&
pos->y >= lo.y && pos->y <= hi.y &&
pos->z >= lo.z && pos->z <= hi.z);
}
};
struct Ray
{
Vector from;
Vector to;
};
inline NavDirType OppositeDirection(NavDirType dir)
{
switch (dir)
{
case NORTH:
return SOUTH;
case EAST:
return WEST;
case SOUTH:
return NORTH;
case WEST:
return EAST;
}
return NORTH;
}
inline NavDirType DirectionLeft(NavDirType dir)
{
switch (dir)
{
case NORTH:
return WEST;
case SOUTH:
return EAST;
case EAST:
return NORTH;
case WEST:
return SOUTH;
}
return NORTH;
}
inline NavDirType DirectionRight(NavDirType dir)
{
switch (dir)
{
case NORTH:
return EAST;
case SOUTH:
return WEST;
case EAST:
return SOUTH;
case WEST:
return NORTH;
}
return NORTH;
}
inline void AddDirectionVector(Vector *v, NavDirType dir, float amount)
{
switch (dir)
{
case NORTH:
v->y -= amount;
return;
case SOUTH:
v->y += amount;
return;
case EAST:
v->x += amount;
return;
case WEST:
v->x -= amount;
return;
}
}
inline float DirectionToAngle(NavDirType dir)
{
switch (dir)
{
case NORTH:
return 270.0f;
case EAST:
return 0.0f;
case SOUTH:
return 90.0f;
case WEST:
return 180.0f;
}
return 0.0f;
}
inline NavDirType AngleToDirection(float angle)
{
while (angle < 0.0f)
angle += 360.0f;
while (angle > 360.0f)
angle -= 360.0f;
if (angle < 45 || angle > 315)
return EAST;
if (angle >= 45 && angle < 135)
return SOUTH;
if (angle >= 135 && angle < 225)
return WEST;
return NORTH;
}
inline void DirectionToVector2D(NavDirType dir, Vector2D *v)
{
switch (dir)
{
case NORTH:
v->x = 0.0f;
v->y = -1.0f;
break;
case SOUTH:
v->x = 0.0f;
v->y = 1.0f;
break;
case EAST:
v->x = 1.0f;
v->y = 0.0f;
break;
case WEST:
v->x = -1.0f;
v->y = 0.0f;
break;
}
}
inline void SnapToGrid(Vector *pos)
{
int cx = pos->x / GenerationStepSize;
int cy = pos->y / GenerationStepSize;
pos->x = cx * GenerationStepSize;
pos->y = cy * GenerationStepSize;
}
inline void SnapToGrid(float *value)
{
int c = *value / GenerationStepSize;
*value = c * GenerationStepSize;
}
// custom
inline float SnapToGrid(float value)
{
int c = value / GenerationStepSize;
return c * GenerationStepSize;
}
inline float NormalizeAngle(float angle)
{
while (angle < -180.0f)
angle += 360.0f;
while (angle > 180.0f)
angle -= 360.0f;
return angle;
}
inline float NormalizeAnglePositive(float angle)
{
while (angle < 0.0f)
angle += 360.0f;
while (angle >= 360.0f)
angle -= 360.0f;
return angle;
}
inline float AngleDifference(float a, float b)
{
float angleDiff = a - b;
while (angleDiff > 180.0f)
angleDiff -= 360.0f;
while (angleDiff < -180.0f)
angleDiff += 360.0f;
return angleDiff;
}
inline bool AnglesAreEqual(float a, float b, float tolerance = 5.0f)
{
if (abs(int64(AngleDifference(a, b))) < tolerance)
return true;
return false;
}
inline bool VectorsAreEqual(const Vector *a, const Vector *b, float tolerance = 0.1f)
{
if (abs(a->x - b->x) < tolerance &&
abs(a->y - b->y) < tolerance &&
abs(a->z - b->z) < tolerance)
return true;
return false;
}
inline bool IsEntityWalkable(entvars_t *entity, unsigned int flags)
{
// if we hit a door, assume its walkable because it will open when we touch it
if (FClassnameIs(entity, "func_door") || FClassnameIs(entity, "func_door_rotating"))
return (flags & WALK_THRU_DOORS) ? true : false;
// if we hit a breakable object, assume its walkable because we will shoot it when we touch it
if (FClassnameIs(entity, "func_breakable") && entity->takedamage == DAMAGE_YES)
return (flags & WALK_THRU_BREAKABLES) ? true : false;
return false;
}
// Check LOS, ignoring any entities that we can walk through
inline bool IsWalkableTraceLineClear(Vector &from, Vector &to, unsigned int flags = 0)
{
TraceResult result;
edict_t *ignore = NULL;
Vector useFrom = from;
while (true)
{
UTIL_TraceLine(useFrom, to, ignore_monsters, ignore, &result);
if (result.flFraction != 1.0f && IsEntityWalkable(VARS(result.pHit), flags))
{
ignore = result.pHit;
Vector dir = to - from;
dir.NormalizeInPlace();
useFrom = result.vecEndPos + 5.0f * dir;
}
else
break;
}
if (result.flFraction == 1.0f)
return true;
return false;
}
#endif // NAV_H

File diff suppressed because it is too large Load Diff

1176
dlls/bot/manager/nav_area.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,868 @@
#include "bot/bot_common.h"
#include <fcntl.h>
#include <sys/stat.h>
#include <assert.h>
#ifndef _WIN32
#include <unistd.h>
#endif // _WIN32
PlaceDirectory placeDirectory;
void PlaceDirectory::Reset()
{
m_directory.RemoveAll();
}
bool PlaceDirectory::IsKnown(Place place) const
{
return m_directory.Find (place) >= 0;
}
PlaceDirectory::EntryType PlaceDirectory::GetEntry(Place place) const
{
if (place == UNDEFINED_PLACE)
return 0;
int i = m_directory.Find (place);
if (i < 0)
{
Assert (false && "PlaceDirectory::GetIndex failure");
return 0;
}
return (EntryType)(i + 1);
}
void PlaceDirectory::AddPlace(Place place)
{
if (place == UNDEFINED_PLACE)
return;
assert(place < 1000);
if (IsKnown(place))
return;
m_directory.AddToTail (place);
}
Place PlaceDirectory::EntryToPlace(EntryType entry) const
{
if (entry == 0)
return UNDEFINED_PLACE;
unsigned int i = entry - 1;
if ((int) i > m_directory.Count ())
{
assert(false && "PlaceDirectory::EntryToPlace: Invalid entry");
return UNDEFINED_PLACE;
}
return m_directory[i];
}
void PlaceDirectory::Save(int fd)
{
// store number of entries in directory
EntryType count = static_cast <EntryType> (m_directory.Count ());
Q_write(fd, &count, sizeof(EntryType));
// store entries
for (int i = 0; i < m_directory.Count (); ++i)
{
const char *placeName = TheBotPhrases->IDToName(m_directory[i]);
// store string length followed by string itself
unsigned short len = (unsigned short) (Q_strlen(placeName) + 1);
Q_write(fd, &len, sizeof(unsigned short));
Q_write(fd, placeName, len);
}
}
void PlaceDirectory::Load(SteamFile *file)
{
// read number of entries
EntryType count;
file->Read(&count, sizeof(EntryType));
m_directory.RemoveAll ();
// read each entry
char placeName[256];
unsigned short len;
for (int i = 0; i < count; ++i)
{
file->Read(&len, sizeof(unsigned short));
file->Read(placeName, len);
AddPlace(TheBotPhrases->NameToID(placeName));
}
}
char *GetBspFilename(const char *navFilename)
{
static char bspFilename[256];
Q_sprintf(bspFilename, "maps\\%s.bsp", STRING(gpGlobals->mapname));
int len = Q_strlen(bspFilename);
if (len < 3)
return NULL;
bspFilename[len - 3] = 'b';
bspFilename[len - 2] = 's';
bspFilename[len - 1] = 'p';
return bspFilename;
}
void CNavArea::Save(FILE *fp) const
{
fprintf(fp, "v %f %f %f\n", m_extent.lo.x, m_extent.lo.y, m_extent.lo.z);
fprintf(fp, "v %f %f %f\n", m_extent.hi.x, m_extent.lo.y, m_neZ);
fprintf(fp, "v %f %f %f\n", m_extent.hi.x, m_extent.hi.y, m_extent.hi.z);
fprintf(fp, "v %f %f %f\n", m_extent.lo.x, m_extent.hi.y, m_swZ);
static int base = 1;
fprintf(fp, "\n\ng %04dArea%s%s%s%s\n", m_id,
(GetAttributes() & NAV_CROUCH) ? "CROUCH" : "", (GetAttributes() & NAV_JUMP) ? "JUMP" : "",
(GetAttributes() & NAV_PRECISE) ? "PRECISE" : "", (GetAttributes() & NAV_NO_JUMP) ? "NO_JUMP" : "");
fprintf(fp, "f %d %d %d %d\n\n", base, base + 1, base + 2, base + 3);
base += 4;
}
void CNavArea::Save(int fd, unsigned int version)
{
// save ID
Q_write(fd, &m_id, sizeof(unsigned int));
// save attribute flags
Q_write(fd, &m_attributeFlags, sizeof(unsigned char));
// save extent of area
Q_write(fd, &m_extent, 6 * sizeof(float));
// save heights of implicit corners
Q_write(fd, &m_neZ, sizeof(float));
Q_write(fd, &m_swZ, sizeof(float));
// save connections to adjacent areas
// in the enum order NORTH, EAST, SOUTH, WEST
for (int d = 0; d < NUM_DIRECTIONS; ++d)
{
// save number of connections for this direction
unsigned int count = m_connect[d].Count();
Q_write(fd, &count, sizeof(unsigned int));
FOR_EACH_LL (m_connect[d], it)
{
NavConnect connect = m_connect[d][it];
Q_write(fd, &connect.area->m_id, sizeof(unsigned int));
}
}
// Store hiding spots for this area
unsigned char count;
if (m_hidingSpotList.Count () > 255)
{
count = 255;
CONSOLE_ECHO("Warning: NavArea #%d: Truncated hiding spot list to 255\n", m_id);
}
else
{
count = (unsigned char) m_hidingSpotList.Count();
}
Q_write(fd, &count, sizeof(unsigned char));
// store HidingSpot objects
unsigned int saveCount = 0;
FOR_EACH_LL (m_hidingSpotList, it)
{
HidingSpot *spot = m_hidingSpotList[it];
spot->Save(fd, version);
// overflow check
if (++saveCount == count)
break;
}
// Save the approach areas for this area
// save number of approach areas
Q_write(fd, &m_approachCount, sizeof(unsigned char));
if (cv_bot_debug.value > 0.0f)
{
CONSOLE_ECHO(" m_approachCount = %d\n", m_approachCount);
}
// save approach area info
unsigned char type;
unsigned int zero = 0;
for (int a = 0; a < m_approachCount; ++a)
{
if (m_approach[a].here.area)
Q_write(fd, &m_approach[a].here.area->m_id, sizeof(unsigned int));
else
Q_write(fd, &zero, sizeof(unsigned int));
if (m_approach[a].prev.area)
Q_write(fd, &m_approach[a].prev.area->m_id, sizeof(unsigned int));
else
Q_write(fd, &zero, sizeof(unsigned int));
type = (unsigned char)m_approach[a].prevToHereHow;
Q_write(fd, &type, sizeof(unsigned char));
if (m_approach[a].next.area)
Q_write(fd, &m_approach[a].next.area->m_id, sizeof(unsigned int));
else
Q_write(fd, &zero, sizeof(unsigned int));
type = (unsigned char)m_approach[a].hereToNextHow;
Q_write(fd, &type, sizeof(unsigned char));
}
// Save encounter spots for this area
{
// save number of encounter paths for this area
unsigned int count = m_spotEncounterList.Count();
Q_write(fd, &count, sizeof(unsigned int));
if (cv_bot_debug.value > 0.0f)
CONSOLE_ECHO(" m_spotEncounterList.size() = %d\n", count);
SpotEncounter *e;
FOR_EACH_LL (m_spotEncounterList, it)
{
e = m_spotEncounterList[it];
if (e->from.area)
Q_write(fd, &e->from.area->m_id, sizeof(unsigned int));
else
Q_write(fd, &zero, sizeof(unsigned int));
unsigned char dir = e->fromDir;
Q_write(fd, &dir, sizeof(unsigned char));
if (e->to.area)
Q_write(fd, &e->to.area->m_id, sizeof(unsigned int));
else
Q_write(fd, &zero, sizeof(unsigned int));
dir = e->toDir;
Q_write(fd, &dir, sizeof(unsigned char));
// write list of spots along this path
unsigned char spotCount;
if (e->spotList.Count() > 255)
{
spotCount = 255;
CONSOLE_ECHO("Warning: NavArea #%d: Truncated encounter spot list to 255\n", m_id);
}
else
{
spotCount = (unsigned char) e->spotList.Count();
}
Q_write(fd, &spotCount, sizeof(unsigned char));
saveCount = 0;
FOR_EACH_LL (e->spotList, it)
{
SpotOrder *order = &e->spotList[it];
// order->spot may be NULL if we've loaded a nav mesh that has been edited but not re-analyzed
unsigned int id = (order->spot) ? order->spot->GetID() : 0;
Q_write(fd, &id, sizeof(unsigned int));
unsigned char t = 255 * order->t;
Q_write(fd, &t, sizeof(unsigned char));
// overflow check
if (++saveCount == spotCount)
break;
}
}
}
// store place dictionary entry
PlaceDirectory::EntryType entry = placeDirectory.GetEntry(GetPlace());
Q_write(fd, &entry, sizeof(entry));
}
void CNavArea::Load(SteamFile *file, unsigned int version)
{
// load ID
file->Read(&m_id, sizeof(unsigned int));
// update nextID to avoid collisions
if (m_id >= m_nextID)
m_nextID = m_id + 1;
// load attribute flags
file->Read(&m_attributeFlags, sizeof(unsigned char));
// load extent of area
file->Read(&m_extent, 6 * sizeof(float));
m_center.x = (m_extent.lo.x + m_extent.hi.x) / 2.0f;
m_center.y = (m_extent.lo.y + m_extent.hi.y) / 2.0f;
m_center.z = (m_extent.lo.z + m_extent.hi.z) / 2.0f;
// load heights of implicit corners
file->Read(&m_neZ, sizeof(float));
file->Read(&m_swZ, sizeof(float));
// load connections (IDs) to adjacent areas
// in the enum order NORTH, EAST, SOUTH, WEST
for (int d = 0; d < NUM_DIRECTIONS; ++d)
{
// load number of connections for this direction
unsigned int count;
file->Read(&count, sizeof(unsigned int));
for (unsigned int i = 0; i < count; ++i)
{
NavConnect connect;
file->Read(&connect.id, sizeof(unsigned int));
m_connect[d].AddToTail(connect);
}
}
// Load hiding spots
// load number of hiding spots
unsigned char hidingSpotCount;
file->Read(&hidingSpotCount, sizeof(unsigned char));
if (version == 1)
{
// load simple vector array
Vector pos;
for (int h = 0; h < hidingSpotCount; ++h)
{
file->Read(&pos, 3 * sizeof(float));
// create new hiding spot and put on master list
HidingSpot *spot = new HidingSpot(&pos, HidingSpot::IN_COVER);
m_hidingSpotList.AddToTail(spot);
}
}
else
{
// load HidingSpot objects for this area
for (int h = 0; h < hidingSpotCount; ++h)
{
// create new hiding spot and put on master list
HidingSpot *spot = new HidingSpot;
spot->Load(file, version);
m_hidingSpotList.FindAndRemove(spot);
}
}
// Load number of approach areas
file->Read(&m_approachCount, sizeof(unsigned char));
// load approach area info (IDs)
unsigned char type;
for (int a = 0; a < m_approachCount; ++a)
{
file->Read(&m_approach[a].here.id, sizeof(unsigned int));
file->Read(&m_approach[a].prev.id, sizeof(unsigned int));
file->Read(&type, sizeof(unsigned char) );
m_approach[a].prevToHereHow = (NavTraverseType)type;
file->Read(&m_approach[a].next.id, sizeof(unsigned int));
file->Read(&type, sizeof(unsigned char));
m_approach[a].hereToNextHow = (NavTraverseType)type;
}
// Load encounter paths for this area
unsigned int count;
file->Read(&count, sizeof(unsigned int));
if (version < 3)
{
// old data, read and discard
for (unsigned int e = 0; e < count; ++e)
{
SpotEncounter encounter;
file->Read(&encounter.from.id, sizeof(unsigned int));
file->Read(&encounter.to.id, sizeof(unsigned int));
file->Read(&encounter.path.from.x, 3 * sizeof(float));
file->Read(&encounter.path.to.x, 3 * sizeof(float));
// read list of spots along this path
unsigned char spotCount;
file->Read(&spotCount, sizeof(unsigned char));
for (int s = 0; s < spotCount; ++s)
{
Vector pos;
file->Read(&pos, 3 * sizeof(float));
file->Read(&pos, sizeof(float));
}
}
return;
}
for (unsigned int e = 0; e < count; ++e)
{
SpotEncounter *encounter = new SpotEncounter;
file->Read(&encounter->from.id, sizeof(unsigned int));
unsigned char dir;
file->Read(&dir, sizeof(unsigned char));
encounter->fromDir = static_cast<NavDirType>(dir);
file->Read(&encounter->to.id, sizeof(unsigned int));
file->Read(&dir, sizeof(unsigned char));
encounter->toDir = static_cast<NavDirType>(dir);
// read list of spots along this path
unsigned char spotCount;
file->Read(&spotCount, sizeof(unsigned char));
SpotOrder order;
for (int s = 0; s < spotCount; ++s)
{
file->Read(&order.id, sizeof(unsigned int));
unsigned char t;
file->Read(&t, sizeof(unsigned char));
order.t = (float)t / 255.0f;
encounter->spotList.AddToTail(order);
}
m_spotEncounterList.AddToTail(encounter);
}
if (version < 5)
return;
// Load Place data
PlaceDirectory::EntryType entry;
file->Read(&entry, sizeof(entry));
// convert entry to actual Place
SetPlace(placeDirectory.EntryToPlace(entry));
}
NavErrorType CNavArea::PostLoad()
{
NavErrorType error = NAV_OK;
// connect areas together
for (int d = 0; d < NUM_DIRECTIONS; ++d)
{
FOR_EACH_LL (m_connect[d], it)
{
NavConnect *connect = &(m_connect[d][it]);
unsigned int id = connect->id;
connect->area = TheNavAreaGrid.GetNavAreaByID(id);
if (id && connect->area == NULL)
{
CONSOLE_ECHO("ERROR: Corrupt navigation data. Cannot connect Navigation Areas.\n");
error = NAV_CORRUPT_DATA;
}
}
}
// resolve approach area IDs
for (int a = 0; a < m_approachCount; ++a)
{
m_approach[a].here.area = TheNavAreaGrid.GetNavAreaByID(m_approach[a].here.id);
if (m_approach[a].here.id && m_approach[a].here.area == NULL)
{
CONSOLE_ECHO("ERROR: Corrupt navigation data. Missing Approach Area (here).\n");
error = NAV_CORRUPT_DATA;
}
m_approach[a].prev.area = TheNavAreaGrid.GetNavAreaByID(m_approach[a].prev.id);
if (m_approach[a].prev.id && m_approach[a].prev.area == NULL)
{
CONSOLE_ECHO("ERROR: Corrupt navigation data. Missing Approach Area (prev).\n");
error = NAV_CORRUPT_DATA;
}
m_approach[a].next.area = TheNavAreaGrid.GetNavAreaByID(m_approach[a].next.id);
if (m_approach[a].next.id && m_approach[a].next.area == NULL)
{
CONSOLE_ECHO("ERROR: Corrupt navigation data. Missing Approach Area (next).\n");
error = NAV_CORRUPT_DATA;
}
}
// resolve spot encounter IDs
SpotEncounter *e;
FOR_EACH_LL (m_spotEncounterList, it)
{
e = m_spotEncounterList[it];
e->from.area = TheNavAreaGrid.GetNavAreaByID(e->from.id);
if (e->from.area == NULL)
{
CONSOLE_ECHO("ERROR: Corrupt navigation data. Missing \"from\" Navigation Area for Encounter Spot.\n");
error = NAV_CORRUPT_DATA;
}
e->to.area = TheNavAreaGrid.GetNavAreaByID(e->to.id);
if (e->to.area == NULL)
{
CONSOLE_ECHO("ERROR: Corrupt navigation data. Missing \"to\" Navigation Area for Encounter Spot.\n");
error = NAV_CORRUPT_DATA;
}
if (e->from.area && e->to.area)
{
// compute path
float halfWidth;
ComputePortal(e->to.area, e->toDir, &e->path.to, &halfWidth);
ComputePortal(e->from.area, e->fromDir, &e->path.from, &halfWidth);
const float eyeHeight = HalfHumanHeight;
e->path.from.z = e->from.area->GetZ(&e->path.from) + eyeHeight;
e->path.to.z = e->to.area->GetZ(&e->path.to) + eyeHeight;
}
// resolve HidingSpot IDs
FOR_EACH_LL (e->spotList, it2)
{
SpotOrder *order = &e->spotList[it2];
order->spot = GetHidingSpotByID(order->id);
if (order->spot == NULL)
{
CONSOLE_ECHO("ERROR: Corrupt navigation data. Missing Hiding Spot\n");
error = NAV_CORRUPT_DATA;
}
}
}
// build overlap list
// TODO: Optimize this
FOR_EACH_LL (TheNavAreaList, it3)
{
CNavArea *area = TheNavAreaList[it3];
if (area == this)
continue;
if (IsOverlapping(area))
m_overlapList.AddToTail(area);
}
return error;
}
// Changes all '/' characters into '\' characters, in place.
inline void COM_FixSlashes(char *pname)
{
#ifdef _WIN32
while (*pname)
{
if (*pname == '/')
*pname = '\\';
pname++;
}
#else
while (*pname)
{
if (*pname == '\\')
*pname = '/';
pname++;
}
#endif // _WIN32
}
// Store AI navigation data to a file
bool SaveNavigationMap(const char *filename)
{
if (filename == NULL)
return false;
// Store the NAV file
COM_FixSlashes(const_cast<char *>(filename));
#ifdef WIN32
int fd = _open(filename, _O_BINARY | _O_CREAT | _O_WRONLY, _S_IREAD | _S_IWRITE);
#else
int fd = creat(filename, S_IRUSR | S_IWUSR | S_IRGRP);
#endif // WIN32
if (fd < 0)
return false;
// store "magic number" to help identify this kind of file
unsigned int magic = NAV_MAGIC_NUMBER;
Q_write(fd, &magic, sizeof(unsigned int));
// store version number of file
// 1 = hiding spots as plain vector array
// 2 = hiding spots as HidingSpot objects
// 3 = Encounter spots use HidingSpot ID's instead of storing vector again
// 4 = Includes size of source bsp file to verify nav data correlation
// ---- Beta Release at V4 -----
// 5 = Added Place info
unsigned int version = 5;
Q_write(fd, &version, sizeof(unsigned int));
// get size of source bsp file and store it in the nav file
// so we can test if the bsp changed since the nav file was made
char *bspFilename = GetBspFilename(filename);
if (bspFilename == NULL)
return false;
unsigned int bspSize = (unsigned int)GET_FILE_SIZE(bspFilename);
CONSOLE_ECHO("Size of bsp file '%s' is %u bytes.\n", bspFilename, bspSize);
Q_write(fd, &bspSize, sizeof(unsigned int));
// Build a directory of the Places in this map
placeDirectory.Reset();
FOR_EACH_LL (TheNavAreaList, it)
{
CNavArea *area = TheNavAreaList[it];
Place place = area->GetPlace();
if (place)
{
placeDirectory.AddPlace(place);
}
}
placeDirectory.Save(fd);
// Store navigation areas
// store number of areas
unsigned int count = TheNavAreaList.Count();
Q_write(fd, &count, sizeof(unsigned int));
// store each area
FOR_EACH_LL (TheNavAreaList, it)
{
CNavArea *area = TheNavAreaList[it];
area->Save(fd, version);
}
Q_close(fd);
return true;
}
// Performs a lightweight sanity-check of the specified map's nav mesh
void SanityCheckNavigationMap(const char *mapName)
{
if (!mapName)
{
CONSOLE_ECHO("ERROR: navigation file not specified.\n");
return;
}
// nav filename is derived from map filename
const int BufLen = 4096;
char bspFilename[BufLen];
char navFilename[BufLen];
Q_snprintf(bspFilename, BufLen, "maps\\%s.bsp", mapName);
Q_snprintf(navFilename, BufLen, "maps\\%s.nav", mapName);
SteamFile navFile(navFilename);
if (!navFile.IsValid())
{
CONSOLE_ECHO("ERROR: navigation file %s does not exist.\n", navFilename);
return;
}
// check magic number
bool result;
unsigned int magic;
result = navFile.Read(&magic, sizeof(unsigned int));
if (!result || magic != NAV_MAGIC_NUMBER)
{
CONSOLE_ECHO("ERROR: Invalid navigation file '%s'.\n", navFilename);
return;
}
// read file version number
unsigned int version;
result = navFile.Read(&version, sizeof(unsigned int));
if (!result || version > 5)
{
CONSOLE_ECHO("ERROR: Unknown version in navigation file %s.\n", navFilename);
return;
}
if (version >= 4)
{
// get size of source bsp file and verify that the bsp hasn't changed
unsigned int saveBspSize;
navFile.Read(&saveBspSize, sizeof(unsigned int));
// verify size
if (bspFilename == NULL)
{
CONSOLE_ECHO("ERROR: No map corresponds to navigation file %s.\n", navFilename);
return;
}
unsigned int bspSize = (unsigned int)GET_FILE_SIZE(bspFilename);
/*if (bspSize != saveBspSize)
{
// this nav file is out of date for this bsp file
CONSOLE_ECHO("ERROR: Out-of-date navigation data in navigation file %s.\n", navFilename);
return;
}*/
}
CONSOLE_ECHO("navigation file %s passes the sanity check.\n", navFilename);
}
NavErrorType LoadNavigationMap()
{
// since the navigation map is destroyed on map change,
// if it exists it has already been loaded for this map
if (TheNavAreaList.Count())
return NAV_OK;
// nav filename is derived from map filename
char filename[256];
Q_sprintf(filename, "maps\\%s.nav", STRING(gpGlobals->mapname));
// free previous navigation map data
DestroyNavigationMap();
placeDirectory.Reset();
CNavArea::m_nextID = 1;
SteamFile navFile(filename);
if (!navFile.IsValid())
return NAV_CANT_ACCESS_FILE;
// check magic number
bool result;
unsigned int magic;
result = navFile.Read(&magic, sizeof(unsigned int));
if (!result || magic != NAV_MAGIC_NUMBER)
{
CONSOLE_ECHO("ERROR: Invalid navigation file '%s'.\n", filename);
return NAV_INVALID_FILE;
}
// read file version number
unsigned int version;
result = navFile.Read(&version, sizeof(unsigned int));
if (!result || version > 5)
{
CONSOLE_ECHO("ERROR: Unknown navigation file version.\n");
return NAV_BAD_FILE_VERSION;
}
if (version >= 4)
{
// get size of source bsp file and verify that the bsp hasn't changed
unsigned int saveBspSize;
navFile.Read(&saveBspSize, sizeof(unsigned int));
// verify size
char *bspFilename = GetBspFilename(filename);
if (bspFilename == NULL)
return NAV_INVALID_FILE;
unsigned int bspSize = (unsigned int)GET_FILE_SIZE(bspFilename);
if (bspSize != saveBspSize)
{
// this nav file is out of date for this bsp file
char *msg = "*** WARNING ***\nThe AI navigation data is from a different version of this map.\nThe CPU players will likely not perform well.\n";
HintMessageToAllPlayers(msg);
CONSOLE_ECHO("\n-----------------\n");
CONSOLE_ECHO(msg);
CONSOLE_ECHO("-----------------\n\n");
}
}
// load Place directory
if (version >= 5)
{
placeDirectory.Load(&navFile);
}
// get number of areas
unsigned int count;
result = navFile.Read(&count, sizeof(unsigned int));
Extent extent;
extent.lo.x = 9999999999.9f;
extent.lo.y = 9999999999.9f;
extent.hi.x = -9999999999.9f;
extent.hi.y = -9999999999.9f;
// load the areas and compute total extent
for (unsigned int i = 0; i < count; ++i)
{
CNavArea *area = new CNavArea;
area->Load(&navFile, version);
TheNavAreaList.AddToTail(area);
const Extent *areaExtent = area->GetExtent();
// check validity of nav area
if (areaExtent->lo.x >= areaExtent->hi.x || areaExtent->lo.y >= areaExtent->hi.y)
CONSOLE_ECHO("WARNING: Degenerate Navigation Area #%d at ( %g, %g, %g )\n",
area->GetID(), area->m_center.x, area->m_center.y, area->m_center.z);
if (areaExtent->lo.x < extent.lo.x)
extent.lo.x = areaExtent->lo.x;
if (areaExtent->lo.y < extent.lo.y)
extent.lo.y = areaExtent->lo.y;
if (areaExtent->hi.x > extent.hi.x)
extent.hi.x = areaExtent->hi.x;
if (areaExtent->hi.y > extent.hi.y)
extent.hi.y = areaExtent->hi.y;
}
// add the areas to the grid
TheNavAreaGrid.Initialize(extent.lo.x, extent.hi.x, extent.lo.y, extent.hi.y);
FOR_EACH_LL (TheNavAreaList, it)
{
TheNavAreaGrid.AddNavArea (TheNavAreaList[it]);
}
// allow areas to connect to each other, etc
FOR_EACH_LL (TheNavAreaList, it)
{
CNavArea *area = TheNavAreaList[it];
area->PostLoad();
}
// Set up all the ladders
BuildLadders();
return NAV_OK;
}

View File

@ -0,0 +1,67 @@
/*
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* In addition, as a special exception, the author gives permission to
* link the code of this program with the Half-Life Game Engine ("HL
* Engine") and Modified Game Libraries ("MODs") developed by Valve,
* L.L.C ("Valve"). You must obey the GNU General Public License in all
* respects for all of the code used other than the HL Engine and MODs
* from Valve. If you modify this file, you may extend this exception
* to your version of the file, but you are not obligated to do so. If
* you do not wish to do so, delete this exception statement from your
* version.
*
*/
#ifndef NAV_FILE_H
#define NAV_FILE_H
#ifdef _WIN32
#pragma once
#endif
// The 'place directory' is used to save and load places from
// nav files in a size-efficient manner that also allows for the
// order of the place ID's to change without invalidating the
// nav files.
//
// The place directory is stored in the nav file as a list of
// place name strings. Each nav area then contains an index
// into that directory, or zero if no place has been assigned to
// that area.
class PlaceDirectory
{
public:
typedef unsigned short EntryType;
void Reset();
bool IsKnown(Place place) const; // return true if this place is already in the directory
EntryType GetEntry(Place place) const; // return the directory entry corresponding to this Place (0 = no entry)
void AddPlace(Place place); // add the place to the directory if not already known
Place EntryToPlace(EntryType entry) const; // given an entry, return the Place
void Save(int fd); // store the directory
void Load(SteamFile *file); // load the directory
private:
CUtlVector<Place> m_directory;
};
char *GetBspFilename(const char *navFilename);
bool SaveNavigationMap(const char *filename);
void SanityCheckNavigationMap(const char *mapName); // Performs a lightweight sanity-check of the specified map's nav mesh
NavErrorType LoadNavigationMap();
#endif // NAV_FILE_H

View File

@ -0,0 +1,84 @@
#include "bot/bot_common.h"
/*
* Globals initialization
*/
NavDirType Opposite[ NUM_DIRECTIONS ] = { SOUTH, WEST, NORTH, EAST };
CNavNode *CNavNode::m_list = NULL;
unsigned int CNavNode::m_listLength = 0;
CNavNode::CNavNode(const Vector *pos, const Vector *normal, CNavNode *parent)
{
m_pos = *pos;
m_normal = *normal;
static unsigned int nextID = 1;
m_id = nextID++;
for (int i = 0; i < NUM_DIRECTIONS; ++i)
m_to[i] = NULL;
m_visited = 0;
m_parent = parent;
m_next = m_list;
m_list = this;
m_listLength++;
m_isCovered = FALSE;
m_area = NULL;
m_attributeFlags = 0;
}
// Create a connection FROM this node TO the given node, in the given direction
void CNavNode::ConnectTo(CNavNode *node, NavDirType dir)
{
m_to[ dir ] = node;
}
// Return node at given position
// TODO: Need a hash table to make this lookup fast
const CNavNode *CNavNode::GetNode(const Vector *pos)
{
const float tolerance = 0.45f * GenerationStepSize;
for (const CNavNode *node = m_list; node != NULL; node = node->m_next)
{
float dx = ABS(node->m_pos.x - pos->x);
float dy = ABS(node->m_pos.y - pos->y);
float dz = ABS(node->m_pos.z - pos->z);
if (dx < tolerance && dy < tolerance && dz < tolerance)
return node;
}
return NULL;
}
// Return true if this node is bidirectionally linked to
// another node in the given direction
BOOL CNavNode::IsBiLinked(NavDirType dir) const
{
if (m_to[ dir ] && m_to[ dir ]->m_to[ Opposite[dir] ] == this)
return true;
return false;
}
// Return true if this node is the NW corner of a quad of nodes
// that are all bidirectionally linked
BOOL CNavNode::IsClosedCell() const
{
if (IsBiLinked( SOUTH ) && IsBiLinked( EAST ) && m_to[ EAST ]->IsBiLinked( SOUTH ) && m_to[ SOUTH ]->IsBiLinked( EAST )
&& m_to[ EAST ]->m_to[ SOUTH ] == m_to[ SOUTH ]->m_to[ EAST ])
return true;
return false;
}

132
dlls/bot/manager/nav_node.h Normal file
View File

@ -0,0 +1,132 @@
/*
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* In addition, as a special exception, the author gives permission to
* link the code of this program with the Half-Life Game Engine ("HL
* Engine") and Modified Game Libraries ("MODs") developed by Valve,
* L.L.C ("Valve"). You must obey the GNU General Public License in all
* respects for all of the code used other than the HL Engine and MODs
* from Valve. If you modify this file, you may extend this exception
* to your version of the file, but you are not obligated to do so. If
* you do not wish to do so, delete this exception statement from your
* version.
*
*/
#ifndef NAV_NODE_H
#define NAV_NODE_H
#ifdef _WIN32
#pragma once
#endif
class CNavNode
{
public:
CNavNode(const Vector *pos, const Vector *normal, CNavNode *parent = NULL);
// return navigation node at the position, or NULL if none exists
static const CNavNode *GetNode(const Vector *pos);
// get navigation node connected in given direction, or NULL if cant go that way
CNavNode *GetConnectedNode(NavDirType dir) const;
const Vector *GetPosition() const;
const Vector *GetNormal() const { return &m_normal; }
unsigned int GetID() const { return m_id; }
static CNavNode *GetFirst() { return m_list; }
static unsigned int GetListLength() { return m_listLength; }
CNavNode *GetNext() { return m_next; }
// create a connection FROM this node TO the given node, in the given direction
void ConnectTo(CNavNode *node, NavDirType dir);
CNavNode *GetParent() const;
void MarkAsVisited(NavDirType dir); // mark the given direction as having been visited
BOOL HasVisited(NavDirType dir); // return TRUE if the given direction has already been searched
BOOL IsBiLinked(NavDirType dir) const; // node is bidirectionally linked to another node in the given direction
BOOL IsClosedCell() const; // node is the NW corner of a bi-linked quad of nodes
void Cover() { m_isCovered = true; } // TODO: Should pass in area that is covering
BOOL IsCovered() const { return m_isCovered; } // return true if this node has been covered by an area
void AssignArea(CNavArea *area); // assign the given area to this node
CNavArea *GetArea() const; // return associated area
void SetAttributes(unsigned char bits) { m_attributeFlags = bits; }
unsigned char GetAttributes() const { return m_attributeFlags; }
private:
friend void DestroyNavigationMap();
Vector m_pos; // position of this node in the world
Vector m_normal; // surface normal at this location
CNavNode *m_to[ NUM_DIRECTIONS ]; // links to north, south, east, and west. NULL if no link
unsigned int m_id; // unique ID of this node
unsigned char m_attributeFlags; // set of attribute bit flags (see NavAttributeType)
static CNavNode *m_list; // the master list of all nodes for this map
static unsigned int m_listLength;
CNavNode *m_next; // next link in master list
// below are only needed when generating
// flags for automatic node generation. If direction bit is clear, that direction hasn't been explored yet.
unsigned char m_visited;
CNavNode *m_parent; // the node prior to this in the search, which we pop back to when this node's search is done (a stack)
BOOL m_isCovered; // true when this node is "covered" by a CNavArea
CNavArea *m_area; // the area this node is contained within
};
inline CNavNode *CNavNode::GetConnectedNode(NavDirType dir) const
{
return m_to[ dir ];
}
inline const Vector *CNavNode::GetPosition() const
{
return &m_pos;
}
inline CNavNode *CNavNode::GetParent() const
{
return m_parent;
}
inline void CNavNode::MarkAsVisited(NavDirType dir)
{
m_visited |= (1 << dir);
}
inline BOOL CNavNode::HasVisited(NavDirType dir)
{
if (m_visited & (1 << dir))
return true;
return false;
}
inline void CNavNode::AssignArea(CNavArea *area)
{
m_area = area;
}
inline CNavArea *CNavNode::GetArea() const
{
return m_area;
}
#endif // NAV_NODE_H

File diff suppressed because it is too large Load Diff

221
dlls/bot/manager/nav_path.h Normal file
View File

@ -0,0 +1,221 @@
/*
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* In addition, as a special exception, the author gives permission to
* link the code of this program with the Half-Life Game Engine ("HL
* Engine") and Modified Game Libraries ("MODs") developed by Valve,
* L.L.C ("Valve"). You must obey the GNU General Public License in all
* respects for all of the code used other than the HL Engine and MODs
* from Valve. If you modify this file, you may extend this exception
* to your version of the file, but you are not obligated to do so. If
* you do not wish to do so, delete this exception statement from your
* version.
*
*/
#ifndef NAV_PATH_H
#define NAV_PATH_H
#ifdef _WIN32
#pragma once
#endif
// STL uses exceptions, but we are not compiling with them - ignore warning
#pragma warning(disable : 4530)
class CNavPath
{
public:
CNavPath() { m_segmentCount = 0; }
struct PathSegment
{
CNavArea *area; // the area along the path
NavTraverseType how; // how to enter this area from the previous one
Vector pos; // our movement goal position at this point in the path
const CNavLadder *ladder; // if "how" refers to a ladder, this is it
};
const PathSegment *operator[](int i) { return (i >= 0 && i < m_segmentCount) ? &m_path[i] : NULL; }
int GetSegmentCount() const { return m_segmentCount; }
const Vector &GetEndpoint() const { return m_path[ m_segmentCount - 1 ].pos; }
bool IsAtEnd(const Vector &pos) const; // return true if position is at the end of the path
float GetLength() const; // return length of path from start to finish
NOXREF bool GetPointAlongPath(float distAlong, Vector *pointOnPath) const; // return point a given distance along the path - if distance is out of path bounds, point is clamped to start/end
// return the node index closest to the given distance along the path without going over - returns (-1) if error
int GetSegmentIndexAlongPath(float distAlong) const;
bool IsValid() const { return (m_segmentCount > 0); }
void Invalidate() { m_segmentCount = 0; }
// draw the path for debugging
void Draw();
// compute closest point on path to given point
NOXREF bool FindClosestPointOnPath(const Vector *worldPos, int startIndex, int endIndex, Vector *close) const;
void Optimize();
// Compute shortest path from 'start' to 'goal' via A* algorithm
template<typename CostFunctor>
bool Compute(const Vector *start, const Vector *goal, CostFunctor &costFunc)
{
Invalidate();
if (start == NULL || goal == NULL)
return false;
CNavArea *startArea = TheNavAreaGrid.GetNearestNavArea(start);
if (startArea == NULL)
return false;
CNavArea *goalArea = TheNavAreaGrid.GetNavArea(goal);
// if we are already in the goal area, build trivial path
if (startArea == goalArea)
{
BuildTrivialPath(start, goal);
return true;
}
// make sure path end position is on the ground
Vector pathEndPosition = *goal;
if (goalArea)
pathEndPosition.z = goalArea->GetZ(&pathEndPosition);
else
GetGroundHeight(&pathEndPosition, &pathEndPosition.z);
// Compute shortest path to goal
CNavArea *closestArea;
bool pathToGoalExists = NavAreaBuildPath(startArea, goalArea, goal, costFunc, &closestArea);
CNavArea *effectiveGoalArea = (pathToGoalExists) ? goalArea : closestArea;
// Build path by following parent links
int count = 0;
CNavArea *area;
for (area = effectiveGoalArea; area; area = area->GetParent())
++count;
// save room for endpoint
if (count > MAX_PATH_SEGMENTS - 1)
count = MAX_PATH_SEGMENTS - 1;
if (count == 0)
return false;
if (count == 1)
{
BuildTrivialPath(start, goal);
return true;
}
m_segmentCount = count;
for (area = effectiveGoalArea; count && area; area = area->GetParent())
{
--count;
m_path[ count ].area = area;
m_path[ count ].how = area->GetParentHow();
}
// compute path positions
if (ComputePathPositions() == false)
{
Invalidate();
return false;
}
// append path end position
m_path[ m_segmentCount ].area = effectiveGoalArea;
m_path[ m_segmentCount ].pos = pathEndPosition;
m_path[ m_segmentCount ].ladder = NULL;
m_path[ m_segmentCount ].how = NUM_TRAVERSE_TYPES;
++m_segmentCount;
return true;
}
private:
enum { MAX_PATH_SEGMENTS = 256 };
PathSegment m_path[ MAX_PATH_SEGMENTS ];
int m_segmentCount;
bool ComputePathPositions(); // determine actual path positions
bool BuildTrivialPath(const Vector *start, const Vector *goal); // utility function for when start and goal are in the same area
int FindNextOccludedNode(int anchor_); // used by Optimize()
};
// Monitor improv movement and determine if it becomes stuck
class CStuckMonitor
{
public:
CStuckMonitor();
void Reset();
void Update(CImprov *improv);
bool IsStuck() const { return m_isStuck; }
float GetDuration() const { return m_isStuck ? m_stuckTimer.GetElapsedTime() : 0.0f; }
private:
bool m_isStuck; // if true, we are stuck
Vector m_stuckSpot; // the location where we became stuck
IntervalTimer m_stuckTimer; // how long we have been stuck
enum { MAX_VEL_SAMPLES = 5 };
float m_avgVel[ MAX_VEL_SAMPLES ];
int m_avgVelIndex;
int m_avgVelCount;
Vector m_lastCentroid;
float m_lastTime;
};
// The CNavPathFollower class implements path following behavior
class CNavPathFollower
{
public:
CNavPathFollower();
void SetImprov(CImprov *improv) { m_improv = improv; }
void SetPath(CNavPath *path) { m_path = path; }
void Reset();
void Update(float deltaT, bool avoidObstacles = true); // move improv along path
void Debug(bool status) { m_isDebug = status; } // turn debugging on/off
bool IsStuck() const { return m_stuckMonitor.IsStuck(); } // return true if improv is stuck
void ResetStuck() { m_stuckMonitor.Reset(); }
float GetStuckDuration() const { return m_stuckMonitor.GetDuration(); } // return how long we've been stuck
void FeelerReflexAdjustment(Vector *goalPosition, float height = -1.0f); // adjust goal position if "feelers" are touched
private:
int FindOurPositionOnPath(Vector *close, bool local) const; // return the closest point to our current position on current path
int FindPathPoint(float aheadRange, Vector *point, int *prevIndex); // compute a point a fixed distance ahead along our path.
CImprov *m_improv; // who is doing the path following
CNavPath *m_path; // the path being followed
int m_segmentIndex; // the point on the path the improv is moving towards
int m_behindIndex; // index of the node on the path just behind us
Vector m_goal; // last computed follow goal
bool m_isLadderStarted;
bool m_isDebug;
CStuckMonitor m_stuckMonitor;
};
#endif // NAV_PATH_H

View File

@ -0,0 +1,112 @@
/*
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* In addition, as a special exception, the author gives permission to
* link the code of this program with the Half-Life Game Engine ("HL
* Engine") and Modified Game Libraries ("MODs") developed by Valve,
* L.L.C ("Valve"). You must obey the GNU General Public License in all
* respects for all of the code used other than the HL Engine and MODs
* from Valve. If you modify this file, you may extend this exception
* to your version of the file, but you are not obligated to do so. If
* you do not wish to do so, delete this exception statement from your
* version.
*
*/
#ifndef SIMPLE_STATE_MACHINE_H
#define SIMPLE_STATE_MACHINE_H
#ifdef _WIN32
#pragma once
#endif
// Encapsulation of a finite-state-machine state
template<typename T>
class SimpleState
{
public:
SimpleState() { m_parent = NULL; }
virtual ~SimpleState() {};
virtual void OnEnter(T userData) {}; // when state is entered
virtual void OnUpdate(T userData) {}; // state behavior
virtual void OnExit(T userData) {}; // when state exited
virtual const char *GetName() const = 0; // return state name
void SetParent(SimpleState<T> *parent)
{
m_parent = parent;
}
SimpleState<T> *GetParent() const
{
return m_parent;
}
private:
// the parent state that contains this state
SimpleState<T> *m_parent;
};
// Encapsulation of a finite state machine
template<typename T, typename S>
class SimpleStateMachine
{
public:
SimpleStateMachine()
{
m_state = NULL;
}
void Reset(T userData)
{
m_userData = userData;
m_state = NULL;
}
// change behavior state - WARNING: not re-entrant. Do not SetState() from within OnEnter() or OnExit()
void SetState(S *newState)
{
if (m_state)
m_state->OnExit(m_userData);
newState->OnEnter(m_userData);
m_state = newState;
m_stateTimer.Start();
}
// how long have we been in the current state
float GetStateDuration() const
{
return m_stateTimer.GetElapsedTime();
}
// return true if given state is current state of machine
bool IsState(const S *state) const
{
return (state == m_state);
}
// execute current state of machine
void Update()
{
if (m_state)
m_state->OnUpdate(m_userData);
}
/*protected:*/
S *m_state; // current behavior state
IntervalTimer m_stateTimer; // how long have we been in the current state
T m_userData;
};
#endif // SIMPLE_STATE_MACHINE_H

238
dlls/bot/osconfig.h Normal file
View File

@ -0,0 +1,238 @@
/*
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* In addition, as a special exception, the author gives permission to
* link the code of this program with the Half-Life Game Engine ("HL
* Engine") and Modified Game Libraries ("MODs") developed by Valve,
* L.L.C ("Valve"). You must obey the GNU General Public License in all
* respects for all of the code used other than the HL Engine and MODs
* from Valve. If you modify this file, you may extend this exception
* to your version of the file, but you are not obligated to do so. If
* you do not wish to do so, delete this exception statement from your
* version.
*
*/
#ifndef OSCONFIG_H
#define OSCONFIG_H
#ifdef _WIN32
#pragma once
#endif
#ifdef _WIN32 // WINDOWS
#pragma warning(disable : 4005)
#endif // _WIN32
// disable must return a value
#pragma warning(disable : 4716)
#ifndef _WIN32
// disable missing return statement at end of non-void function
#pragma warning(disable : 1011)
// disable offsetof applied to non-POD (Plain Old Data) types is nonstandard
#pragma warning(disable : 1875)
#endif // _WIN32
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stddef.h>
#include <string.h>
#include <time.h>
#include <math.h>
#include <setjmp.h>
#include <assert.h>
#ifdef _WIN32 // WINDOWS
#include <windows.h>
#include <winsock.h>
#include <wsipx.h> // for support IPX
#define PSAPI_VERSION 1
#include <psapi.h>
#include <nmmintrin.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <io.h>
#else // _WIN32
#include <arpa/inet.h>
#include <ctype.h>
//#include <dirent.h>
#include <dlfcn.h>
//#include <elf.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
//#include <link.h>
#include <netdb.h>
#include <netinet/in.h>
#include <pthread.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__APPLE__)
#include <sys/sysctl.h>
#else
#include <sys/sysinfo.h>
#endif
#include <unistd.h>
// Deail with stupid macro in kernel.h
#undef __FUNCTION__
#endif // _WIN32
#include <stdint.h>
#ifdef _WIN32 // WINDOWS
typedef unsigned char uint8;
#ifndef _STDINT
typedef unsigned __int64 uint64_t;
typedef unsigned __int32 uint32_t;
typedef unsigned __int16 uint16_t;
typedef unsigned __int8 uint8_t;
typedef __int64 int64_t;
typedef __int32 int32_t;
typedef __int16 int16_t;
typedef __int8 int8_t;
typedef wchar_t uchar16;
typedef unsigned int uchar32;
#endif
#else // _WIN32
typedef unsigned long long uint64_t;
typedef unsigned int uint32_t;
typedef unsigned short uint16_t;
typedef unsigned char uint8_t;
#ifndef __int8_t_defined
typedef long long int64_t;
typedef int int32_t;
typedef short int16_t;
//typedef char int8_t;
#endif
typedef long long __int64_t;
typedef unsigned char byte;
typedef unsigned char BYTE;
typedef unsigned long ULONG;
typedef unsigned short uchar16;
typedef wchar_t uchar32;
#endif // _WIN32
#ifdef _WIN32 // WINDOWS
#define _CRT_SECURE_NO_WARNINGS
#define WIN32_LEAN_AND_MEAN
#ifndef CDECL
#define CDECL __cdecl
#endif
#define STDCALL __stdcall
#define HIDDEN
#define NOINLINE __declspec(noinline)
#define ALIGN16 __declspec(align(16))
#define FORCE_STACK_ALIGN
// Attributes to specify an "exported" function, visible from outside the
// DLL.
#undef DLLEXPORT
#define DLLEXPORT __declspec(dllexport)
// WINAPI should be provided in the windows compiler headers.
// It's usually defined to something like "__stdcall".
typedef int socklen_t;
#define SOCKET_FIONBIO(s, m) ioctlsocket(s, FIONBIO, (u_long*)&m)
#define SOCKET_MSGLEN(s, r) ioctlsocket(s, FIONREAD, (u_long*)&r)
#define SIN_GET_ADDR(saddr, r) r = (saddr)->S_un.S_addr
#define SIN_SET_ADDR(saddr, r) (saddr)->S_un.S_addr = (r)
#define SOCKET_CLOSE(s) closesocket(s)
#define SOCKET_AGAIN() (WSAGetLastError() == WSAEWOULDBLOCK)
#else // _WIN32
#ifndef PAGESIZE
#define PAGESIZE 4096
#endif
#define ALIGN(addr) (size_t)((size_t)addr & ~(PAGESIZE-1))
#define ARRAYSIZE(p) (sizeof(p)/sizeof(p[0]))
#define _MAX_FNAME NAME_MAX
#define MAX_PATH 260
typedef void *HWND;
typedef unsigned long DWORD;
typedef unsigned short WORD;
typedef unsigned int UNINT32;
typedef unsigned char uint8;
#define CDECL __attribute__ ((cdecl))
#define STDCALL __attribute__ ((stdcall))
#define HIDDEN __attribute__((visibility("hidden")))
#define NOINLINE __attribute__((noinline))
#define ALIGN16 __attribute__((aligned(16)))
#define FORCE_STACK_ALIGN __attribute__((force_align_arg_pointer))
#undef DLLEXPORT
#define DLLEXPORT __attribute__((visibility("default")))
#define WINAPI /* */
typedef int SOCKET;
#define INVALID_SOCKET (SOCKET)(~0)
#define SOCKET_FIONBIO(s, m) ioctl(s, FIONBIO, (char*)&m)
#define SOCKET_MSGLEN(s, r) ioctl(s, FIONREAD, (char*)&r)
#define SIN_GET_ADDR(saddr, r) r = (saddr)->s_addr
#define SIN_SET_ADDR(saddr, r) (saddr)->s_addr = (r)
#define SOCKET_CLOSE(s) close(s)
#define SOCKET_AGAIN() (errno == EAGAIN)
#define SOCKET_ERROR -1
#define WSAENOPROTOOPT ENOPROTOOPT
#ifndef FALSE
#define FALSE 0
#endif
#ifndef TRUE
#define TRUE 1
#endif
#endif // _WIN32
// Simplified macro for declaring/defining exported DLL functions. They
// need to be 'extern "C"' so that the C++ compiler enforces parameter
// type-matching, rather than considering routines with mis-matched
// arguments/types to be overloaded functions...
//
// AFAIK, this is os-independent, but it's included here in osdep.h where
// DLLEXPORT is defined, for convenience.
#define C_DLLEXPORT extern "C" DLLEXPORT
#ifdef _WIN32
static const bool __isWindows = true;
static const bool __isLinux = false;
#else
static const bool __isWindows = false;
static const bool __isLinux = true;
#endif
#define EXT_FUNC /*FORCE_STACK_ALIGN*/
#endif // OSCONFIG_H

72
dlls/bot/pm_math.h Normal file
View File

@ -0,0 +1,72 @@
/*
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* In addition, as a special exception, the author gives permission to
* link the code of this program with the Half-Life Game Engine ("HL
* Engine") and Modified Game Libraries ("MODs") developed by Valve,
* L.L.C ("Valve"). You must obey the GNU General Public License in all
* respects for all of the code used other than the HL Engine and MODs
* from Valve. If you modify this file, you may extend this exception
* to your version of the file, but you are not obligated to do so. If
* you do not wish to do so, delete this exception statement from your
* version.
*
*/
#ifndef PM_MATH_H
#define PM_MATH_H
#ifdef _WIN32
#pragma once
#endif
#define PITCH 0 // up/down
#define YAW 1 // left/right
#define ROLL 2 // fall over
extern vec3_t vec3_origin;
extern int nanmask;
#define IS_NAN(x) ((*reinterpret_cast<int *>(&(x)) & nanmask) == nanmask)
float anglemod(float a);
void AngleVectors(const vec_t *angles, vec_t *forward, vec_t *right, vec_t *up);
void AngleVectorsTranspose(const vec_t *angles, vec_t *forward, vec_t *right, vec_t *up);
void AngleMatrix(const vec_t *angles, float (*matrix)[4]);
void AngleIMatrix(const vec_t *angles, float (*matrix)[4]);
void NormalizeAngles(float *angles);
void InterpolateAngles(float *start, float *end, float *output, float frac);
float AngleBetweenVectors(const vec_t *v1, const vec_t *v2);
void VectorTransform(const vec_t *in1, float *in2, vec_t *out);
int VectorCompare(const vec_t *v1, const vec_t *v2);
void VectorMA(const vec_t *veca, float scale, const vec_t *vecb, vec_t *vecc);
float _DotProduct(const vec_t *v1, const vec_t *v2);
void _VectorSubtract(vec_t *veca, vec_t *vecb, vec_t *out);
void _VectorAdd(vec_t *veca, vec_t *vecb, vec_t *out);
void _VectorCopy(vec_t *in, vec_t *out);
void _CrossProduct(const vec_t *v1, const vec_t *v2, vec_t *cross);
float Length(const vec_t *v);
float Distance(const vec_t *v1, const vec_t *v2);
float VectorNormalize(vec_t *v);
void VectorInverse(vec_t *v);
void VectorScale(const vec_t *in, vec_t scale, vec_t *out);
int Q_log2(int val);
void VectorMatrix(vec_t *forward, vec_t *right, vec_t *up);
void VectorAngles(const vec_t *forward, vec_t *angles);
#endif // PM_MATH_H

178
dlls/bot/shared_util.cpp Normal file
View File

@ -0,0 +1,178 @@
#include "bot/bot_common.h"
/*
* Globals initialization
*/
char s_shared_token[ 256 ];
char s_shared_quote = '\"';
/* <2d4b0a> ../game_shared/shared_util.cpp:68 */
char *SharedVarArgs(char *format, ...)
{
va_list argptr;
const int BufLen = 1024;
const int NumBuffers = 4;
static char string[ NumBuffers ][ BufLen ];
static int curstring = 0;
curstring = (curstring + 1) % NumBuffers;
va_start(argptr, format);
Q_vsnprintf(string[ curstring ], BufLen, format, argptr);
va_end(argptr);
return string[ curstring ];
}
/* <2d4ba1> ../game_shared/shared_util.cpp:90 */
char *BufPrintf(char *buf, int &len, const char *fmt, ...)
{
va_list argptr;
if (len > 0)
{
va_start(argptr, fmt);
Q_vsnprintf(buf, len, fmt, argptr);
va_end(argptr);
len -= Q_strlen(buf);
return buf + Q_strlen(buf);
}
return NULL;
}
/* <2d4d11> ../game_shared/shared_util.cpp:137 */
const char *NumAsString(int val)
{
const int BufLen = 16;
const int NumBuffers = 4;
static char string[ NumBuffers ][ BufLen ];
static int curstring = 0;
int len = 16;
curstring = (curstring + 1) % 4;
BufPrintf(string[curstring], len, "%d", val);
return string[curstring];
}
// Returns the token parsed by SharedParse()
/* <2d4da4> ../game_shared/shared_util.cpp:155 */
char *SharedGetToken()
{
return s_shared_token;
}
// Returns the token parsed by SharedParse()
/* <2d4dbf> ../game_shared/shared_util.cpp:164 */
NOXREF void SharedSetQuoteChar(char c)
{
s_shared_quote = c;
}
// Parse a token out of a string
/* <2d4de7> ../game_shared/shared_util.cpp:173 */
const char *SharedParse(const char *data)
{
int c;
int len;
len = 0;
s_shared_token[0] = '\0';
if (!data)
return NULL;
// skip whitespace
skipwhite:
while ((c = *data) <= ' ')
{
if (c == 0)
{
// end of file;
return NULL;
}
data++;
}
// skip // comments
if (c == '/' && data[1] == '/')
{
while (*data && *data != '\n')
data++;
goto skipwhite;
}
// handle quoted strings specially
if (c == s_shared_quote)
{
data++;
while (true)
{
c = *data++;
if (c == s_shared_quote || !c)
{
s_shared_token[len] = '\0';
return data;
}
s_shared_token[len] = c;
len++;
}
}
// parse single characters
if (c == '{' || c == '}'|| c == ')'|| c == '(' || c == '\'' || c == ',')
{
s_shared_token[len] = c;
len++;
s_shared_token[len] = '\0';
return data + 1;
}
// parse a regular word
do
{
s_shared_token[len] = c;
data++;
len++;
c = *data;
if (c == '{' || c == '}'|| c == ')'|| c == '(' || c == '\'' || c == ',')
break;
} while (c > 32);
s_shared_token[len] = '\0';
return data;
}
// Returns true if additional data is waiting to be processed on this line
/* <2d4e40> ../game_shared/shared_util.cpp:247 */
NOXREF bool SharedTokenWaiting(const char *buffer)
{
const char *p;
p = buffer;
while (*p && *p!='\n')
{
if (!isspace(*p) || isalnum(*p))
return true;
p++;
}
return false;
}

84
dlls/bot/shared_util.h Normal file
View File

@ -0,0 +1,84 @@
/*
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* In addition, as a special exception, the author gives permission to
* link the code of this program with the Half-Life Game Engine ("HL
* Engine") and Modified Game Libraries ("MODs") developed by Valve,
* L.L.C ("Valve"). You must obey the GNU General Public License in all
* respects for all of the code used other than the HL Engine and MODs
* from Valve. If you modify this file, you may extend this exception
* to your version of the file, but you are not obligated to do so. If
* you do not wish to do so, delete this exception statement from your
* version.
*
*/
#ifndef SHARED_UTIL
#define SHARED_UTIL
#ifdef _WIN32
#pragma once
#endif
#ifndef _WIN32
#include <string.h>
#include <wchar.h>
#endif // _WIN32
NOXREF wchar_t *SharedWVarArgs(wchar_t *format, ...);
char *SharedVarArgs(char *format, ...);
char *BufPrintf(char *buf, int &len, const char *fmt, ...);
NOXREF wchar_t *BufWPrintf(wchar_t *buf, int &len, const wchar_t *fmt, ...);
NOXREF const wchar_t *NumAsWString(int val);
const char *NumAsString(int val);
char *SharedGetToken();
NOXREF void SharedSetQuoteChar(char c);
const char *SharedParse(const char *data);
NOXREF bool SharedTokenWaiting(const char *buffer);
// Simple utility function to allocate memory and duplicate a string
/* <db469> ../game_shared/shared_util.h:46 */
inline char *CloneString(const char *str)
{
if (!str)
{
char *cloneStr = new char[1];
cloneStr[0] = '\0';
return cloneStr;
}
char *cloneStr = new char [Q_strlen(str) + 1];
Q_strcpy(cloneStr, str);
return cloneStr;
}
// Simple utility function to allocate memory and duplicate a wide string
inline wchar_t *CloneWString(const wchar_t *str)
{
if (!str)
{
wchar_t *cloneStr = new wchar_t[1];
cloneStr[0] = L'\0';
return cloneStr;
}
wchar_t *cloneStr = new wchar_t [wcslen(str) + 1];
wcscpy(cloneStr, str);
return cloneStr;
}
#endif // SHARED_UTIL

View File

@ -0,0 +1,50 @@
/*
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* In addition, as a special exception, the author gives permission to
* link the code of this program with the Half-Life Game Engine ("HL
* Engine") and Modified Game Libraries ("MODs") developed by Valve,
* L.L.C ("Valve"). You must obey the GNU General Public License in all
* respects for all of the code used other than the HL Engine and MODs
* from Valve. If you modify this file, you may extend this exception
* to your version of the file, but you are not obligated to do so. If
* you do not wish to do so, delete this exception statement from your
* version.
*
*/
#ifndef SIMPLE_CHECKSUM_H
#define SIMPLE_CHECKSUM_H
#ifdef _WIN32
#pragma once
#endif
// Compute a simple checksum for the given data.
// Each byte in the data is multiplied by its position to track re-ordering changes
/* <4a799e> ../game_shared/simple_checksum.h:12 */
inline unsigned int ComputeSimpleChecksum(const unsigned char *dataPointer, int dataLength)
{
unsigned int checksum = 0;
for (int i = 1; i <= dataLength; i++)
{
checksum += (*dataPointer) * i;
++dataPointer;
}
return checksum;
}
#endif // SIMPLE_CHECKSUM_H

View File

@ -0,0 +1,563 @@
#include "bot_common.h"
// Begin attacking
void AttackState::OnEnter(CCSBot *me)
{
CBasePlayer *enemy = me->GetEnemy();
// store our posture when the attack began
me->PushPostureContext();
me->DestroyPath();
// if we are using a knife, try to sneak up on the enemy
if (enemy != NULL && me->IsUsingKnife() && !me->IsPlayerFacingMe(enemy))
me->Walk();
else
me->Run();
me->GetOffLadder();
me->ResetStuckMonitor();
m_repathTimer.Invalidate();
m_haveSeenEnemy = me->IsEnemyVisible();
m_nextDodgeStateTimestamp = 0.0f;
m_firstDodge = true;
m_isEnemyHidden = false;
m_reacquireTimestamp = 0.0f;
m_pinnedDownTimestamp = gpGlobals->time + RANDOM_FLOAT(7.0f, 10.0f);
m_shieldToggleTimestamp = gpGlobals->time + RANDOM_FLOAT(2.0f, 10.0f);
m_shieldForceOpen = false;
// if we encountered someone while escaping, grab our weapon and fight!
if (me->IsEscapingFromBomb())
me->EquipBestWeapon();
if (me->IsUsingKnife())
{
// can't crouch and hold with a knife
m_crouchAndHold = false;
me->StandUp();
}
else
{
// decide whether to crouch where we are, or run and gun (if we havent already - see CCSBot::Attack())
if (!m_crouchAndHold)
{
if (enemy != NULL)
{
const float crouchFarRange = 750.0f;
float crouchChance;
// more likely to crouch if using sniper rifle or if enemy is far away
if (me->IsUsingSniperRifle())
crouchChance = 50.0f;
else if ((me->pev->origin - enemy->pev->origin).IsLengthGreaterThan(crouchFarRange))
crouchChance = 50.0f;
else
crouchChance = 20.0f * (1.0f - me->GetProfile()->GetAggression());
if (RANDOM_FLOAT(0.0f, 100.0f) < crouchChance)
{
// make sure we can still see if we crouch
TraceResult result;
Vector origin = me->pev->origin;
if (!me->IsCrouching())
{
// we are standing, adjust for lower crouch origin
origin.z -= 20.0f;
}
UTIL_TraceLine(origin, enemy->EyePosition(), ignore_monsters, ignore_glass, ENT(me->pev), &result);
if (result.flFraction == 1.0f)
{
m_crouchAndHold = true;
}
}
}
}
if (m_crouchAndHold)
{
me->Crouch();
me->PrintIfWatched("Crouch and hold attack!\n");
}
}
m_scopeTimestamp = 0;
m_didAmbushCheck = false;
float skill = me->GetProfile()->GetSkill();
// tendency to dodge is proportional to skill
float dodgeChance = 80.0f * skill;
if (me->IsUsingKnife())
{
dodgeChance *= 2.0f;
}
// high skill bots always dodge if outnumbered, or they see a sniper
if (skill > 0.5f && me->IsOutnumbered())
{
dodgeChance = 100.0f;
}
m_dodge = (RANDOM_FLOAT(0.0f, 100.0f) < dodgeChance) != 0;
// decide whether we might bail out of this fight
m_isCoward = (RANDOM_FLOAT(0.0f, 100.0f) > 100.0f * me->GetProfile()->GetAggression());
}
void AttackState::StopAttacking(CCSBot *me)
{
if (me->m_task == CCSBot::SNIPING)
{
// stay in our hiding spot
me->Hide(me->GetLastKnownArea(), -1.0f, 50.0f);
}
else
{
me->StopAttacking();
}
}
// Perform attack behavior
void AttackState::OnUpdate(CCSBot *me)
{
// can't be stuck while attacking
me->ResetStuckMonitor();
me->StopRapidFire();
CBasePlayerWeapon *weapon = me->GetActiveWeapon();
#if 0
if (weapon != NULL)
{
if (weapon->m_iId == WEAPON_C4 ||
weapon->m_iId == WEAPON_HEGRENADE ||
weapon->m_iId == WEAPON_FLASHBANG ||
weapon->m_iId == WEAPON_SMOKEGRENADE)
{
me->EquipBestWeapon();
}
}
#endif
CBasePlayer *enemy = me->GetEnemy();
if (enemy == NULL)
{
StopAttacking(me);
return;
}
// keep track of whether we have seen our enemy at least once yet
if (!m_haveSeenEnemy)
m_haveSeenEnemy = me->IsEnemyVisible();
// Retreat check
// Do not retreat if the enemy is too close
if (m_retreatTimer.IsElapsed())
{
// If we've been fighting this battle for awhile, we're "pinned down" and
// need to do something else.
// If we are outnumbered, retreat.
bool isPinnedDown = (gpGlobals->time > m_pinnedDownTimestamp);
if (isPinnedDown ||
(me->IsOutnumbered() && m_isCoward) ||
(me->OutnumberedCount() >= 2 && me->GetProfile()->GetAggression() < 1.0f))
{
// tell our teammates our plight
if (isPinnedDown)
me->GetChatter()->PinnedDown();
else
me->GetChatter()->Scared();
m_retreatTimer.Start(RANDOM_FLOAT(3.0f, 15.0f));
// try to retreat
if (me->TryToRetreat())
{
// if we are a sniper, equip our pistol so we can fire while retreating
if (me->IsUsingSniperRifle())
{
me->EquipPistol();
}
}
else
{
me->PrintIfWatched("I want to retreat, but no safe spots nearby!\n");
}
}
}
// Knife fighting
// We need to pathfind right to the enemy to cut him
if (me->IsUsingKnife())
{
// can't crouch and hold with a knife
m_crouchAndHold = false;
me->StandUp();
// if we are using a knife and our prey is looking towards us, run at him
if (me->IsPlayerFacingMe(enemy))
{
me->ForceRun(5.0f);
me->Hurry(10.0f);
}
else
{
me->Walk();
}
// slash our victim
me->FireWeaponAtEnemy();
// if our victim has moved, repath
bool repath = false;
if (me->HasPath())
{
const float repathRange = 100.0f;
if ((me->GetPathEndpoint() - enemy->pev->origin).IsLengthGreaterThan(repathRange))
{
repath = true;
}
}
else
{
repath = true;
}
if (repath && m_repathTimer.IsElapsed())
{
me->ComputePath(TheNavAreaGrid.GetNearestNavArea(&enemy->pev->origin), &enemy->pev->origin, FASTEST_ROUTE);
const float repathInterval = 0.5f;
m_repathTimer.Start(repathInterval);
}
// move towards victim
if (me->UpdatePathMovement(NO_SPEED_CHANGE) != CCSBot::PROGRESSING)
{
me->DestroyPath();
}
return;
}
#if 0
// Simple shield usage
if (me->HasShield())
{
if (me->IsEnemyVisible() && !m_shieldForceOpen)
{
if (!me->IsRecognizedEnemyReloading() && !me->IsReloading() && me->IsPlayerLookingAtMe(enemy))
{
// close up - enemy is pointing his gun at us
if (!me->IsProtectedByShield())
me->SecondaryAttack();
}
else
{
// enemy looking away or reloading his weapon - open up and shoot him
if (me->IsProtectedByShield())
me->SecondaryAttack();
}
}
else
{
// can't see enemy, open up
if (me->IsProtectedByShield())
me->SecondaryAttack();
}
if (gpGlobals->time > m_shieldToggleTimestamp)
{
m_shieldToggleTimestamp = gpGlobals->time + RANDOM_FLOAT(0.5, 2.0f);
// toggle shield force open
m_shieldForceOpen = !m_shieldForceOpen;
}
}
#endif
// check if our weapon range is bad and we should switch to pistol
if (me->IsUsingSniperRifle())
{
// if we have a sniper rifle and our enemy is too close, switch to pistol
// NOTE: Must be larger than NO_ZOOM range in AdjustZoom()
const float sniperMinRange = 310.0f;
if ((enemy->pev->origin - me->pev->origin).IsLengthLessThan(sniperMinRange))
me->EquipPistol();
}
else if (me->IsUsingShotgun())
{
// if we have a shotgun equipped and enemy is too far away, switch to pistol
const float shotgunMaxRange = 1000.0f;
if ((enemy->pev->origin - me->pev->origin).IsLengthGreaterThan(shotgunMaxRange))
me->EquipPistol();
}
// if we're sniping, look through the scope - need to do this here in case a reload resets our scope
if (me->IsUsingSniperRifle())
{
// for Scouts and AWPs, we need to wait for zoom to resume
// if (me->m_bResumeZoom)
{
m_scopeTimestamp = gpGlobals->time;
return;
}
Vector toAimSpot3D = me->m_aimSpot - me->pev->origin;
float targetRange = toAimSpot3D.Length();
// dont adjust zoom level if we're already zoomed in - just fire
if (me->GetZoomLevel() == CCSBot::NO_ZOOM && me->AdjustZoom(targetRange))
m_scopeTimestamp = gpGlobals->time;
const float waitScopeTime = 0.2f + me->GetProfile()->GetReactionTime();
if (gpGlobals->time - m_scopeTimestamp < waitScopeTime)
{
// force us to wait until zoomed in before firing
return;
}
}
// see if we "notice" that our prey is dead
if (me->IsAwareOfEnemyDeath())
{
// let team know if we killed the last enemy
if (me->GetLastVictimID() == enemy->entindex() && me->GetNearbyEnemyCount() <= 1)
{
me->GetChatter()->KilledMyEnemy(enemy->entindex());
}
StopAttacking(me);
return;
}
float notSeenEnemyTime = gpGlobals->time - me->GetLastSawEnemyTimestamp();
// if we haven't seen our enemy for a moment, continue on if we dont want to fight, or decide to ambush if we do
if (!me->IsEnemyVisible())
{
// attend to nearby enemy gunfire
if (notSeenEnemyTime > 0.5f && me->CanHearNearbyEnemyGunfire())
{
// give up the attack, since we didn't want it in the first place
StopAttacking(me);
me->SetLookAt("Nearby enemy gunfire", me->GetNoisePosition(), PRIORITY_HIGH, 0.0f);
me->PrintIfWatched("Checking nearby threatening enemy gunfire!\n");
return;
}
// check if we have lost track of our enemy during combat
if (notSeenEnemyTime > 0.25f)
{
m_isEnemyHidden = true;
}
if (notSeenEnemyTime > 0.1f)
{
if (me->GetDisposition() == CCSBot::ENGAGE_AND_INVESTIGATE)
{
// decide whether we should hide and "ambush" our enemy
if (m_haveSeenEnemy && !m_didAmbushCheck)
{
const float hideChance = 33.3f;
if (RANDOM_FLOAT(0.0, 100.0f) < hideChance)
{
float ambushTime = RANDOM_FLOAT(3.0f, 15.0f);
// hide in ambush nearby
// TODO: look towards where we know enemy is
const Vector *spot = FindNearbyRetreatSpot(me, 200.0f);
if (spot != NULL)
{
me->IgnoreEnemies(1.0f);
me->Run();
me->StandUp();
me->Hide(spot, ambushTime, true);
return;
}
}
// don't check again
m_didAmbushCheck = true;
}
}
else
{
// give up the attack, since we didn't want it in the first place
StopAttacking(me);
return;
}
}
}
else
{
// we can see the enemy again - reset our ambush check
m_didAmbushCheck = false;
// if the enemy is coming out of hiding, we need time to react
if (m_isEnemyHidden)
{
m_reacquireTimestamp = gpGlobals->time + me->GetProfile()->GetReactionTime();
m_isEnemyHidden = false;
}
}
// if we haven't seen our enemy for a long time, chase after them
float chaseTime = 2.0f + 2.0f * (1.0f - me->GetProfile()->GetAggression());
// if we are sniping, be very patient
if (me->IsUsingSniperRifle())
chaseTime += 3.0f;
// if we are crouching, be a little patient
else if (me->IsCrouching())
chaseTime += 1.0f;
// if we can't see the enemy, and have either seen him but currently lost sight of him,
// or haven't yet seen him, chase after him (unless we are a sniper)
if (!me->IsEnemyVisible() && (notSeenEnemyTime > chaseTime || !m_haveSeenEnemy))
{
// snipers don't chase their prey - they wait for their prey to come to them
if (me->GetTask() == CCSBot::SNIPING)
{
StopAttacking(me);
return;
}
else
{
// move to last known position of enemy
me->SetTask(CCSBot::MOVE_TO_LAST_KNOWN_ENEMY_POSITION, enemy);
me->MoveTo(&me->GetLastKnownEnemyPosition());
return;
}
}
// if we can't see our enemy at the moment, and were shot by
// a different visible enemy, engage them instead
const float hurtRecentlyTime = 3.0f;
if (!me->IsEnemyVisible() &&
me->GetTimeSinceAttacked() < hurtRecentlyTime &&
me->GetAttacker() != NULL &&
me->GetAttacker() != me->GetEnemy())
{
// if we can see them, attack, otherwise panic
if (me->IsVisible(me->GetAttacker(), CHECK_FOV))
{
me->Attack(me->GetAttacker());
me->PrintIfWatched("Switching targets to retaliate against new attacker!\n");
}
return;
}
if (gpGlobals->time > m_reacquireTimestamp)
me->FireWeaponAtEnemy();
// do dodge behavior
// If sniping or crouching, stand still.
if (m_dodge && !me->IsUsingSniperRifle() && !m_crouchAndHold)
{
Vector toEnemy = enemy->pev->origin - me->pev->origin;
float range = toEnemy.Length2D();
const float hysterisRange = 125.0f; // (+/-) m_combatRange
float minRange = me->GetCombatRange() - hysterisRange;
float maxRange = me->GetCombatRange() + hysterisRange;
// move towards (or away from) enemy if we are using a knife, behind a corner, or we aren't very skilled
if (me->GetProfile()->GetSkill() < 0.66f || !me->IsEnemyVisible())
{
if (range > maxRange)
me->MoveForward();
else if (range < minRange)
me->MoveBackward();
}
// don't dodge if enemy is facing away
const float dodgeRange = 2000.0f;
if (range > dodgeRange || !me->IsPlayerFacingMe(enemy))
{
m_dodgeState = STEADY_ON;
m_nextDodgeStateTimestamp = 0.0f;
}
else if (gpGlobals->time >= m_nextDodgeStateTimestamp)
{
int next;
// select next dodge state that is different that our current one
do
{
// high-skill bots may jump when first engaging the enemy (if they are moving)
const float jumpChance = 33.3f;
if (m_firstDodge && me->GetProfile()->GetSkill() > 0.5f && RANDOM_FLOAT(0, 100) < jumpChance && !me->IsNotMoving())
next = RANDOM_LONG(0, NUM_ATTACK_STATES - 1);
else
next = RANDOM_LONG(0, NUM_ATTACK_STATES - 2);
}
while (!m_firstDodge && next == m_dodgeState);
m_dodgeState = (DodgeStateType)next;
m_nextDodgeStateTimestamp = gpGlobals->time + RANDOM_FLOAT(0.3f, 1.0f);
m_firstDodge = false;
}
switch (m_dodgeState)
{
case STEADY_ON:
{
break;
}
case SLIDE_LEFT:
{
me->StrafeLeft();
break;
}
case SLIDE_RIGHT:
{
me->StrafeRight();
break;
}
case JUMP:
{
if (me->m_isEnemyVisible)
{
me->Jump();
}
break;
}
}
}
}
// Finish attack
void AttackState::OnExit(CCSBot *me)
{
me->PrintIfWatched("AttackState:OnExit()\n");
m_crouchAndHold = false;
// clear any noises we heard during battle
me->ForgetNoise();
me->ResetStuckMonitor();
// resume our original posture
me->PopPostureContext();
// put shield away
// if (me->IsProtectedByShield())
// me->SecondaryAttack();
me->StopRapidFire();
me->ClearSurpriseDelay();
}

View File

@ -0,0 +1,510 @@
#include "bot_common.h"
bool HasDefaultPistol(CCSBot *me)
{
CBasePlayerWeapon *pistol = static_cast<CBasePlayerWeapon *>(me->m_rgpPlayerItems[ PISTOL_SLOT ]);
if (pistol == NULL)
return false;
if (me->m_iTeam == TERRORIST && pistol->m_iId == WEAPON_GLOCK18)
return true;
if (me->m_iTeam == CT && pistol->m_iId == WEAPON_USP)
return true;
return false;
}
// Buy weapons, armor, etc.
void BuyState::OnEnter(CCSBot *me)
{
CCSBotManager *ctrl = TheCSBots();
m_retries = 0;
m_prefRetries = 0;
m_prefIndex = 0;
m_doneBuying = false;
m_isInitialDelay = true;
// this will force us to stop holding live grenade
me->EquipBestWeapon();
m_buyDefuseKit = false;
m_buyShield = false;
if (me->m_iTeam == CT)
{
if (ctrl->GetScenario() == CCSBotManager::SCENARIO_DEFUSE_BOMB)
{
// CT's sometimes buy defuse kits in the bomb scenario (except in career mode, where the player should defuse)
if (g_pGameRules->IsCareer() == false)
{
const float buyDefuseKitChance = 50.0f; // 100.0f * (me->GetProfile()->GetSkill() + 0.2f);
if (RANDOM_FLOAT(0.0f, 100.0f) < buyDefuseKitChance)
{
m_buyDefuseKit = true;
}
}
}
// determine if we want a tactical shield
if (!me->m_bHasPrimary && ctrl->AllowTacticalShield())
{
if (me->m_iAccount > 2500)
{
if (me->m_iAccount < 4000)
m_buyShield = (RANDOM_FLOAT(0, 100.0f) < 33.3f) ? true : false;
else
m_buyShield = (RANDOM_FLOAT(0, 100.0f) < 10.0f) ? true : false;
}
}
}
if (ctrl->AllowGrenades())
{
m_buyGrenade = (RANDOM_FLOAT(0.0f, 100.0f) < 33.3f) ? true : false;
}
else
{
m_buyGrenade = false;
}
m_buyPistol = false;
if (ctrl->AllowPistols())
{
CBasePlayerWeapon *pistol = static_cast<CBasePlayerWeapon *>(me->m_rgpPlayerItems[ PISTOL_SLOT ]);
// check if we have a pistol
if (pistol != NULL)
{
// if we have our default pistol, think about buying a different one
if (HasDefaultPistol(me))
{
// if everything other than pistols is disallowed, buy a pistol
if (ctrl->AllowShotguns() == false &&
ctrl->AllowSubMachineGuns() == false &&
ctrl->AllowRifles() == false &&
ctrl->AllowMachineGuns() == false &&
ctrl->AllowTacticalShield() == false &&
ctrl->AllowSnipers() == false)
{
m_buyPistol = (RANDOM_FLOAT(0, 100) < 75.0f);
}
else if (me->m_iAccount < 1000)
{
// if we're low on cash, buy a pistol
m_buyPistol = (RANDOM_FLOAT(0, 100) < 75.0f);
}
else
{
m_buyPistol = (RANDOM_FLOAT(0, 100) < 33.3f);
}
}
}
else
{
// we dont have a pistol - buy one
m_buyPistol = true;
}
}
}
enum WeaponType
{
PISTOL,
SHOTGUN,
SUB_MACHINE_GUN,
RIFLE,
MACHINE_GUN,
SNIPER_RIFLE,
GRENADE,
NUM_WEAPON_TYPES,
};
struct BuyInfo
{
WeaponType type;
bool preferred; // more challenging bots prefer these weapons
char *buyAlias; // the buy alias for this equipment
};
// These tables MUST be kept in sync with the CT and T buy aliases
static BuyInfo primaryWeaponBuyInfoCT[ PRIMARY_WEAPON_BUY_COUNT ] =
{
{ SHOTGUN, false, "m3" }, // WEAPON_M3
{ SHOTGUN, false, "xm1014" }, // WEAPON_XM1014
{ SUB_MACHINE_GUN, false, "tmp" }, // WEAPON_TMP
{ SUB_MACHINE_GUN, false, "mp5" }, // WEAPON_MP5N
{ SUB_MACHINE_GUN, false, "ump45" }, // WEAPON_UMP45
{ SUB_MACHINE_GUN, false, "p90" }, // WEAPON_P90
{ RIFLE, true, "famas" }, // WEAPON_FAMAS
{ SNIPER_RIFLE, false, "scout" }, // WEAPON_SCOUT
{ RIFLE, true, "m4a1" }, // WEAPON_M4A1
{ RIFLE, false, "aug" }, // WEAPON_AUG
{ SNIPER_RIFLE, true, "sg550" }, // WEAPON_SG550
{ SNIPER_RIFLE, true, "awp" }, // WEAPON_AWP
{ MACHINE_GUN, false, "m249" } // WEAPON_M249
};
static BuyInfo secondaryWeaponBuyInfoCT[ SECONDARY_WEAPON_BUY_COUNT ] =
{
// { PISTOL, false, "glock" },
// { PISTOL, false, "usp" },
{ PISTOL, true, "p228" },
{ PISTOL, true, "deagle" },
{ PISTOL, true, "fn57" }
};
static BuyInfo primaryWeaponBuyInfoT[ PRIMARY_WEAPON_BUY_COUNT ] =
{
{ SHOTGUN, false, "m3" }, // WEAPON_M3
{ SHOTGUN, false, "xm1014" }, // WEAPON_XM1014
{ SUB_MACHINE_GUN, false, "mac10" }, // WEAPON_MAC10
{ SUB_MACHINE_GUN, false, "mp5" }, // WEAPON_MP5N
{ SUB_MACHINE_GUN, false, "ump45" }, // WEAPON_UMP45
{ SUB_MACHINE_GUN, false, "p90" }, // WEAPON_P90
{ RIFLE, true, "galil" }, // WEAPON_GALIL
{ RIFLE, true, "ak47" }, // WEAPON_AK47
{ SNIPER_RIFLE, false, "scout" }, // WEAPON_SCOUT
{ RIFLE, true, "sg552" }, // WEAPON_SG552
{ SNIPER_RIFLE, true, "awp" }, // WEAPON_AWP
{ SNIPER_RIFLE, true, "g3sg1" }, // WEAPON_G3SG1
{ MACHINE_GUN, false, "m249" } // WEAPON_M249
};
static BuyInfo secondaryWeaponBuyInfoT[ SECONDARY_WEAPON_BUY_COUNT ] =
{
// { PISTOL, false, "glock" },
// { PISTOL, false, "usp" },
{ PISTOL, true, "p228" },
{ PISTOL, true, "deagle" },
{ PISTOL, true, "elites" }
};
// Given a weapon alias, return the kind of weapon it is
inline WeaponType GetWeaponType(const char *alias)
{
int i;
for (i = 0; i < PRIMARY_WEAPON_BUY_COUNT; ++i)
{
if (!Q_stricmp(alias, primaryWeaponBuyInfoCT[i].buyAlias))
return primaryWeaponBuyInfoCT[i].type;
if (!Q_stricmp(alias, primaryWeaponBuyInfoT[i].buyAlias))
return primaryWeaponBuyInfoT[i].type;
}
for (i = 0; i < SECONDARY_WEAPON_BUY_COUNT; ++i)
{
if (!Q_stricmp(alias, secondaryWeaponBuyInfoCT[i].buyAlias))
return secondaryWeaponBuyInfoCT[i].type;
if (!Q_stricmp(alias, secondaryWeaponBuyInfoT[i].buyAlias))
return secondaryWeaponBuyInfoT[i].type;
}
return NUM_WEAPON_TYPES;
}
void BuyState::OnUpdate(CCSBot *me)
{
// wait for a Navigation Mesh
if (!TheNavAreaList.Count())
return;
// apparently we cant buy things in the first few seconds, so wait a bit
if (m_isInitialDelay)
{
const float waitToBuyTime = 2.0f; // 0.25f;
if (gpGlobals->time - me->GetStateTimestamp() < waitToBuyTime)
return;
m_isInitialDelay = false;
}
// if we're done buying and still in the freeze period, wait
if (m_doneBuying)
{
if (g_pGameRules->IsMultiplayer () && g_pGameRules->IsFreezePeriod ())
{
// make sure we're locked and loaded
me->EquipBestWeapon (MUST_EQUIP);
me->Reload ();
me->ResetStuckMonitor ();
return;
}
me->Idle();
return;
}
// is the bot spawned outside of a buy zone?
if (!(me->m_signals.GetState() & SIGNAL_BUY))
{
m_doneBuying = true;
UTIL_DPrintf("%s bot spawned outside of a buy zone (%d, %d, %d)\n", (me->m_iTeam == CT) ? "CT" : "Terrorist", me->pev->origin.x, me->pev->origin.y, me->pev->origin.z);
return;
}
CCSBotManager *ctrl = TheCSBots();
// try to buy some weapons
const float buyInterval = 0.2f; // 0.02f
if (gpGlobals->time - me->GetStateTimestamp() > buyInterval)
{
me->m_stateTimestamp = gpGlobals->time;
bool isPreferredAllDisallowed = true;
// try to buy our preferred weapons first
if (m_prefIndex < me->GetProfile()->GetWeaponPreferenceCount())
{
// need to retry because sometimes first buy fails??
const int maxPrefRetries = 2;
if (m_prefRetries >= maxPrefRetries)
{
// try to buy next preferred weapon
++m_prefIndex;
m_prefRetries = 0;
return;
}
int weaponPreference = me->GetProfile()->GetWeaponPreference(m_prefIndex);
// don't buy it again if we still have one from last round
CBasePlayerWeapon *weapon = me->GetActiveWeapon();
if (weapon != NULL && weapon->m_iId == weaponPreference)
{
// done with buying preferred weapon
m_prefIndex = 9999;
return;
}
if (me->HasShield() && weaponPreference == WEAPON_SHIELDGUN)
{
// done with buying preferred weapon
m_prefIndex = 9999;
return;
}
const char *buyAlias = NULL;
if (weaponPreference == WEAPON_SHIELDGUN)
{
if (ctrl->AllowTacticalShield())
buyAlias = "shield";
}
else
{
buyAlias = WeaponIDToAlias(weaponPreference);
WeaponType type = GetWeaponType(buyAlias);
switch (type)
{
case PISTOL:
if (!ctrl->AllowPistols())
buyAlias = NULL;
break;
case SHOTGUN:
if (!ctrl->AllowShotguns())
buyAlias = NULL;
break;
case SUB_MACHINE_GUN:
if (!ctrl->AllowSubMachineGuns())
buyAlias = NULL;
break;
case RIFLE:
if (!ctrl->AllowRifles())
buyAlias = NULL;
break;
case MACHINE_GUN:
if (!ctrl->AllowMachineGuns())
buyAlias = NULL;
break;
case SNIPER_RIFLE:
if (!ctrl->AllowSnipers())
buyAlias = NULL;
break;
}
}
if (buyAlias)
{
me->ClientCommand(buyAlias);
me->PrintIfWatched("Tried to buy preferred weapon %s.\n", buyAlias);
isPreferredAllDisallowed = false;
}
++m_prefRetries;
// bail out so we dont waste money on other equipment
// unless everything we prefer has been disallowed, then buy at random
if (isPreferredAllDisallowed == false)
return;
}
// if we have no preferred primary weapon (or everything we want is disallowed), buy at random
if (!me->m_bHasPrimary && (isPreferredAllDisallowed || !me->GetProfile()->HasPrimaryPreference()))
{
if (m_buyShield)
{
// buy a shield
me->ClientCommand("shield");
me->PrintIfWatched("Tried to buy a shield.\n");
}
else
{
// build list of allowable weapons to buy
BuyInfo *masterPrimary = (me->m_iTeam == TERRORIST) ? primaryWeaponBuyInfoT : primaryWeaponBuyInfoCT;
BuyInfo *stockPrimary[ PRIMARY_WEAPON_BUY_COUNT ];
int stockPrimaryCount = 0;
// dont choose sniper rifles as often
const float sniperRifleChance = 50.0f;
bool wantSniper = (RANDOM_FLOAT(0, 100) < sniperRifleChance) ? true : false;
for (int i = 0; i < PRIMARY_WEAPON_BUY_COUNT; ++i)
{
if ((masterPrimary[i].type == SHOTGUN && ctrl->AllowShotguns()) ||
(masterPrimary[i].type == SUB_MACHINE_GUN && ctrl->AllowSubMachineGuns()) ||
(masterPrimary[i].type == RIFLE && ctrl->AllowRifles()) ||
(masterPrimary[i].type == SNIPER_RIFLE && ctrl->AllowSnipers() && wantSniper) ||
(masterPrimary[i].type == MACHINE_GUN && ctrl->AllowMachineGuns()))
{
stockPrimary[ stockPrimaryCount++ ] = &masterPrimary[i];
}
}
if (stockPrimaryCount)
{
// buy primary weapon if we don't have one
int which;
// on hard difficulty levels, bots try to buy preferred weapons on the first pass
if (m_retries == 0 && ctrl->GetDifficultyLevel() >= BOT_HARD)
{
// count up available preferred weapons
int prefCount = 0;
for (which = 0; which < stockPrimaryCount; ++which)
{
if (stockPrimary[which]->preferred)
++prefCount;
}
if (prefCount)
{
int whichPref = RANDOM_LONG(0, prefCount - 1);
for (which = 0; which < stockPrimaryCount; ++which)
{
if (stockPrimary[which]->preferred && whichPref-- == 0)
break;
}
}
else
{
// no preferred weapons available, just pick randomly
which = RANDOM_LONG(0, stockPrimaryCount - 1);
}
}
else
{
which = RANDOM_LONG(0, stockPrimaryCount - 1);
}
me->ClientCommand(stockPrimary[ which ]->buyAlias);
me->PrintIfWatched("Tried to buy %s.\n", stockPrimary[ which ]->buyAlias);
}
}
}
// If we now have a weapon, or have tried for too long, we're done
if (me->m_bHasPrimary || m_retries++ > 5)
{
// primary ammo
if (me->m_bHasPrimary)
{
me->ClientCommand("primammo");
}
// buy armor last, to make sure we bought a weapon first
me->ClientCommand("vesthelm");
me->ClientCommand("vest");
// pistols - if we have no preferred pistol, buy at random
if (ctrl->AllowPistols() && !me->GetProfile()->HasPistolPreference())
{
if (m_buyPistol)
{
int which = RANDOM_LONG(0, SECONDARY_WEAPON_BUY_COUNT - 1);
if (me->m_iTeam == TERRORIST)
me->ClientCommand(secondaryWeaponBuyInfoT[ which ].buyAlias);
else
me->ClientCommand(secondaryWeaponBuyInfoCT[ which ].buyAlias);
// only buy one pistol
m_buyPistol = false;
}
me->ClientCommand("secammo");
}
// buy a grenade if we wish, and we don't already have one
if (m_buyGrenade && !me->HasGrenade())
{
if (UTIL_IsTeamAllBots(me->m_iTeam))
{
// only allow Flashbangs if everyone on the team is a bot (dont want to blind our friendly humans)
float rnd = RANDOM_FLOAT(0, 100);
if (rnd < 10.0f)
{
// smoke grenade
me->ClientCommand("sgren");
}
else if (rnd < 35.0f)
{
// flashbang
me->ClientCommand("flash");
}
else
{
// he grenade
me->ClientCommand("hegren");
}
}
else
{
if (RANDOM_FLOAT(0, 100) < 10.0f)
{
// smoke grenade
me->ClientCommand("sgren");
}
else
{
// he grenade
me->ClientCommand("hegren");
}
}
}
if (m_buyDefuseKit)
{
me->ClientCommand("defuser");
}
m_doneBuying = true;
}
}
}
void BuyState::OnExit(CCSBot *me)
{
me->ResetStuckMonitor();
me->EquipBestWeapon();
}

View File

@ -0,0 +1,65 @@
#include "bot_common.h"
// Begin defusing the bomb
void DefuseBombState::OnEnter(CCSBot *me)
{
me->Crouch();
me->SetDisposition(CCSBot::SELF_DEFENSE);
me->GetChatter()->Say("DefusingBomb");
}
// Defuse the bomb
void DefuseBombState::OnUpdate(CCSBot *me)
{
const Vector *bombPos = me->GetGameState()->GetBombPosition();
CCSBotManager *ctrl = TheCSBots();
if (bombPos == NULL)
{
me->PrintIfWatched("In Defuse state, but don't know where the bomb is!\n");
me->Idle();
return;
}
// look at the bomb
me->SetLookAt("Defuse bomb", bombPos, PRIORITY_HIGH);
// defuse...
me->UseEnvironment();
if (gpGlobals->time - me->GetStateTimestamp() > 1.0f)
{
// if we missed starting the defuse, give up
if (ctrl->GetBombDefuser() == NULL)
{
me->PrintIfWatched("Failed to start defuse, giving up\n");
me->Idle();
return;
}
else if (ctrl->GetBombDefuser() != me)
{
// if someone else got the defuse, give up
me->PrintIfWatched("Someone else started defusing, giving up\n");
me->Idle();
return;
}
}
// if bomb has been defused, give up
if (!ctrl->IsBombPlanted())
{
me->Idle();
return;
}
}
void DefuseBombState::OnExit(CCSBot *me)
{
me->StandUp();
me->ResetStuckMonitor();
me->SetTask(CCSBot::SEEK_AND_DESTROY);
me->SetDisposition(CCSBot::ENGAGE_AND_INVESTIGATE);
me->ClearLookAt();
}

View File

@ -0,0 +1,44 @@
#include "bot_common.h"
void EscapeFromBombState::OnEnter(CCSBot *me)
{
me->StandUp();
me->Run();
me->DestroyPath();
me->EquipKnife();
}
// Escape from the bomb
void EscapeFromBombState::OnUpdate(CCSBot *me)
{
const Vector *bombPos = me->GetGameState()->GetBombPosition();
// if we don't know where the bomb is, we shouldn't be in this state
if (bombPos == NULL)
{
me->Idle();
return;
}
// grab our knife to move quickly
me->EquipKnife();
// look around
me->UpdateLookAround();
if (me->UpdatePathMovement() != CCSBot::PROGRESSING)
{
// we have no path, or reached the end of one - create a new path far away from the bomb
FarAwayFromPositionFunctor func(bombPos);
CNavArea *goalArea = FindMinimumCostArea(me->GetLastKnownArea(), func);
// if this fails, we'll try again next time
me->ComputePath(goalArea, NULL, FASTEST_ROUTE);
}
}
void EscapeFromBombState::OnExit(CCSBot *me)
{
me->EquipBestWeapon();
}

View File

@ -0,0 +1,50 @@
#include "bot_common.h"
// Move to the bomb on the floor and pick it up
void FetchBombState::OnEnter(CCSBot *me)
{
me->DestroyPath();
}
// Move to the bomb on the floor and pick it up
void FetchBombState::OnUpdate(CCSBot *me)
{
if (me->IsCarryingBomb())
{
me->PrintIfWatched( "I picked up the bomb\n" );
me->Idle();
return;
}
CBaseEntity *bomb = TheCSBots()->GetLooseBomb();
if (bomb != NULL)
{
if (!me->HasPath())
{
// build a path to the bomb
if (me->ComputePath(TheNavAreaGrid.GetNavArea(&bomb->pev->origin), &bomb->pev->origin, SAFEST_ROUTE) == false)
{
me->PrintIfWatched("Fetch bomb pathfind failed\n");
// go Hunt instead of Idle to prevent continuous re-pathing to inaccessible bomb
me->Hunt();
//return;
}
}
}
else
{
// someone picked up the bomb
me->PrintIfWatched("Bomb not loose\n");
me->Idle();
return;
}
// look around
me->UpdateLookAround();
if (me->UpdatePathMovement() != CCSBot::PROGRESSING)
me->Idle();
}

View File

@ -0,0 +1,263 @@
#include "bot_common.h"
// Follow our leader
void FollowState::OnEnter(CCSBot *me)
{
me->StandUp();
me->Run();
me->DestroyPath();
m_isStopped = false;
m_stoppedTimestamp = 0.0f;
// to force immediate repath
m_lastLeaderPos.x = -99999999.9f;
m_lastLeaderPos.y = -99999999.9f;
m_lastLeaderPos.z = -99999999.9f;
m_lastSawLeaderTime = 0;
// set re-pathing frequency
m_repathInterval.Invalidate();
m_isSneaking = false;
m_walkTime.Invalidate();
m_isAtWalkSpeed = false;
m_leaderMotionState = INVALID;
m_idleTimer.Start(RANDOM_FLOAT(2.0f, 5.0f));
}
// Determine the leader's motion state by tracking his speed
void FollowState::ComputeLeaderMotionState(float leaderSpeed)
{
// walk = 130, run = 250
const float runWalkThreshold = 140.0f;
const float walkStopThreshold = 10.0f;
LeaderMotionStateType prevState = m_leaderMotionState;
if (leaderSpeed > runWalkThreshold)
{
m_leaderMotionState = RUNNING;
m_isAtWalkSpeed = false;
}
else if (leaderSpeed > walkStopThreshold)
{
// track when began to walk
if (!m_isAtWalkSpeed)
{
m_walkTime.Start();
m_isAtWalkSpeed = true;
}
const float minWalkTime = 0.25f;
if (m_walkTime.GetElapsedTime() > minWalkTime)
{
m_leaderMotionState = WALKING;
}
}
else
{
m_leaderMotionState = STOPPED;
m_isAtWalkSpeed = false;
}
// track time spent in this motion state
if (prevState != m_leaderMotionState)
{
m_leaderMotionStateTime.Start();
m_waitTime = RANDOM_FLOAT(1.0f, 3.0f);
}
}
// Follow our leader
// TODO: Clean up this nasty mess
void FollowState::OnUpdate(CCSBot *me)
{
// if we lost our leader, give up
if (m_leader == NULL || !m_leader->IsAlive())
{
me->Idle();
return;
}
// if we are carrying the bomb and at a bombsite, plant
if (me->IsCarryingBomb() && me->IsAtBombsite())
{
// plant it
me->SetTask(CCSBot::PLANT_BOMB);
me->PlantBomb();
// radio to the team
me->GetChatter()->PlantingTheBomb(me->GetPlace());
return;
}
// look around
me->UpdateLookAround();
// if we are moving, we are not idle
if (me->IsNotMoving() == false)
m_idleTimer.Start(RANDOM_FLOAT(2.0f, 5.0f));
// compute the leader's speed
float leaderSpeed = Vector2D(m_leader->pev->velocity.x, m_leader->pev->velocity.y).Length();
// determine our leader's movement state
ComputeLeaderMotionState(leaderSpeed);
// track whether we can see the leader
bool isLeaderVisible;
if (me->IsVisible(&m_leader->pev->origin))
{
m_lastSawLeaderTime = gpGlobals->time;
isLeaderVisible = true;
}
else
{
isLeaderVisible = false;
}
// determine whether we should sneak or not
const float farAwayRange = 750.0f;
if ((m_leader->pev->origin - me->pev->origin).IsLengthGreaterThan(farAwayRange))
{
// far away from leader - run to catch up
m_isSneaking = false;
}
else if (isLeaderVisible)
{
// if we see leader walking and we are nearby, walk
if (m_leaderMotionState == WALKING)
m_isSneaking = true;
// if we are sneaking and our leader starts running, stop sneaking
if (m_isSneaking && m_leaderMotionState == RUNNING)
m_isSneaking = false;
}
// if we haven't seen the leader for a long time, run
const float longTime = 20.0f;
if (gpGlobals->time - m_lastSawLeaderTime > longTime)
m_isSneaking = false;
if (m_isSneaking)
me->Walk();
else
me->Run();
bool repath = false;
// if the leader has stopped, hide nearby
const float nearLeaderRange = 250.0f;
if (!me->HasPath() && m_leaderMotionState == STOPPED && m_leaderMotionStateTime.GetElapsedTime() > m_waitTime)
{
// throttle how often this check occurs
m_waitTime += RANDOM_FLOAT(1.0f, 3.0f);
// the leader has stopped - if we are close to him, take up a hiding spot
if ((m_leader->pev->origin - me->pev->origin).IsLengthLessThan(nearLeaderRange))
{
const float hideRange = 250.0f;
if (me->TryToHide(NULL, -1.0f, hideRange, false, USE_NEAREST))
{
me->ResetStuckMonitor();
return;
}
}
}
// if we have been idle for awhile, move
if (m_idleTimer.IsElapsed())
{
repath = true;
// always walk when we move such a short distance
m_isSneaking = true;
}
// if our leader has moved, repath (don't repath if leading is stopping)
if (leaderSpeed > 100.0f && m_leaderMotionState != STOPPED)
{
repath = true;
}
// move along our path
if (me->UpdatePathMovement(NO_SPEED_CHANGE) != CCSBot::PROGRESSING)
{
me->DestroyPath();
}
// recompute our path if necessary
if (repath && m_repathInterval.IsElapsed())
{
// recompute our path to keep us near our leader
m_lastLeaderPos = m_leader->pev->origin;
me->ResetStuckMonitor();
const float runSpeed = 200.0f;
const float collectRange = (leaderSpeed > runSpeed) ? 600.0f : 400.0f;
FollowTargetCollector collector(m_leader);
SearchSurroundingAreas(TheNavAreaGrid.GetNearestNavArea(&m_lastLeaderPos), &m_lastLeaderPos, collector, collectRange);
if (cv_bot_debug.value > 0.0f)
{
for (int i = 0; i < collector.m_targetAreaCount; ++i)
collector.m_targetArea[i]->Draw(255, 0, 0, 2);
}
// move to one of the collected areas
if (collector.m_targetAreaCount)
{
CNavArea *target = NULL;
Vector targetPos;
// if we are idle, pick a random area
if (m_idleTimer.IsElapsed())
{
target = collector.m_targetArea[ RANDOM_LONG(0, collector.m_targetAreaCount - 1) ];
targetPos = *target->GetCenter();
me->PrintIfWatched("%4.1f: Bored. Repathing to a new nearby area\n", gpGlobals->time);
}
else
{
me->PrintIfWatched("%4.1f: Repathing to stay with leader.\n", gpGlobals->time);
// find closest area to where we are
CNavArea *area;
float closeRangeSq = 9999999999.9f;
Vector close;
for (int a = 0; a < collector.m_targetAreaCount; ++a)
{
area = collector.m_targetArea[a];
area->GetClosestPointOnArea(&me->pev->origin, &close);
float rangeSq = (me->pev->origin - close).LengthSquared();
if (rangeSq < closeRangeSq)
{
target = area;
targetPos = close;
closeRangeSq = rangeSq;
}
}
}
if (me->ComputePath(target, NULL, FASTEST_ROUTE) == NULL)
me->PrintIfWatched("Pathfind to leader failed.\n");
// throttle how often we repath
m_repathInterval.Start(0.5f);
m_idleTimer.Reset();
}
}
}
void FollowState::OnExit(CCSBot *me)
{
;
}

View File

@ -0,0 +1,437 @@
#include "bot_common.h"
// Begin moving to a nearby hidey-hole.
// NOTE: Do not forget this state may include a very long "move-to" time to get to our hidey spot!
void HideState::OnEnter(CCSBot *me)
{
m_isAtSpot = false;
// if duration is "infinite", set it to a reasonably long time to prevent infinite camping
if (m_duration < 0.0f)
{
m_duration = RANDOM_FLOAT(30.0f, 60.0f);
}
// decide whether to "ambush" or not - never set to false so as not to override external setting
if (RANDOM_FLOAT(0.0f, 100.0f) < 50.0f)
{
m_isHoldingPosition = true;
}
// if we are holding position, decide for how long
if (m_isHoldingPosition)
{
m_holdPositionTime = RANDOM_FLOAT(3.0f, 10.0f);
}
else
{
m_holdPositionTime = 0.0f;
}
m_heardEnemy = false;
m_firstHeardEnemyTime = 0.0f;
m_retry = 0;
if (me->IsFollowing())
{
m_leaderAnchorPos = me->GetFollowLeader()->pev->origin;
}
}
// Move to a nearby hidey-hole.
// NOTE: Do not forget this state may include a very long "move-to" time to get to our hidey spot!
void HideState::OnUpdate(CCSBot *me)
{
CCSBotManager *ctrl = TheCSBots();
// wait until finished reloading to leave hide state
if (!me->IsActiveWeaponReloading())
{
if (gpGlobals->time - me->GetStateTimestamp() > m_duration)
{
if (me->GetTask() == CCSBot::GUARD_LOOSE_BOMB)
{
// if we're guarding the loose bomb, continue to guard it but pick a new spot
me->Hide(ctrl->GetLooseBombArea());
return;
}
else if (me->GetTask() == CCSBot::GUARD_BOMB_ZONE)
{
// if we're guarding a bombsite, continue to guard it but pick a new spot
const CCSBotManager::Zone *zone = ctrl->GetClosestZone(&me->pev->origin);
if (zone != NULL)
{
CNavArea *area = ctrl->GetRandomAreaInZone(zone);
if (area != NULL)
{
me->Hide(area);
return;
}
}
}
else if (me->GetTask() == CCSBot::GUARD_HOSTAGE_RESCUE_ZONE)
{
// if we're guarding a rescue zone, continue to guard this or another rescue zone
if (me->GuardRandomZone())
{
me->SetTask(CCSBot::GUARD_HOSTAGE_RESCUE_ZONE);
me->PrintIfWatched("Continuing to guard hostage rescue zones\n");
me->SetDisposition(CCSBot::OPPORTUNITY_FIRE);
me->GetChatter()->GuardingHostageEscapeZone(IS_PLAN);
return;
}
}
me->Idle();
return;
}
// if we are momentarily hiding while following someone, check to see if he has moved on
if (me->IsFollowing())
{
CBasePlayer *leader = me->GetFollowLeader();
// BOTPORT: Determine walk/run velocity thresholds
float runThreshold = 200.0f;
if (leader->pev->velocity.IsLengthGreaterThan(runThreshold))
{
// leader is running, stay with him
me->Follow(leader);
return;
}
// if leader has moved, stay with him
const float followRange = 250.0f;
if ((m_leaderAnchorPos - leader->pev->origin).IsLengthGreaterThan(followRange))
{
me->Follow(leader);
return;
}
}
// if we see a nearby buddy in combat, join him
// TODO: Perhaps tie in to TakeDamage(), so it works for human players, too
#if 0
// Scenario logic
switch (ctrl->GetScenario())
{
case CCSBotManager::SCENARIO_DEFUSE_BOMB:
{
if (me->m_iTeam == CT)
{
// if we are just holding position (due to a radio order) and the bomb has just planted, go defuse it
if (me->GetTask() == CCSBot::HOLD_POSITION &&
ctrl->IsBombPlanted() &&
ctrl->GetBombPlantTimestamp() > me->GetStateTimestamp())
{
me->Idle();
return;
}
// if we are guarding the defuser and he dies/gives up, stop hiding (to choose another defuser)
if (me->GetTask() == CCSBot::GUARD_BOMB_DEFUSER && ctrl->GetBombDefuser() == NULL)
{
me->Idle();
return;
}
// if we are guarding the loose bomb and it is picked up, stop hiding
if (me->GetTask() == CCSBot::GUARD_LOOSE_BOMB && ctrl->GetLooseBomb() == NULL)
{
me->GetChatter()->TheyPickedUpTheBomb();
me->Idle();
return;
}
// if we are guarding a bombsite and the bomb is dropped and we hear about it, stop guarding
if (me->GetTask() == CCSBot::GUARD_BOMB_ZONE && me->GetGameState()->IsLooseBombLocationKnown())
{
me->Idle();
return;
}
// if we are guarding (bombsite, initial encounter, etc) and the bomb is planted, go defuse it
if (me->IsDoingScenario() && me->GetTask() == CCSBot::GUARD_BOMB_ZONE && ctrl->IsBombPlanted())
{
me->Idle();
return;
}
}
// TERRORIST
else
{
// if we are near the ticking bomb and someone starts defusing it, attack!
if (ctrl->GetBombDefuser())
{
Vector toDefuser = ctrl->GetBombDefuser()->pev->origin;
const float hearDefuseRange = 2000.0f;
if ((toDefuser - me->pev->origin).IsLengthLessThan(hearDefuseRange))
{
// if we are nearby, attack, otherwise move to the bomb (which will cause us to attack when we see defuser)
if (me->CanSeePlantedBomb())
{
me->Attack(ctrl->GetBombDefuser());
}
else
{
me->MoveTo(&toDefuser, FASTEST_ROUTE);
me->InhibitLookAround(10.0f);
}
return;
}
}
}
break;
}
case CCSBotManager::SCENARIO_RESCUE_HOSTAGES:
{
// if we're guarding the hostages and they all die or are taken, do something else
if (me->GetTask() == CCSBot::GUARD_HOSTAGES)
{
if (me->GetGameState()->AreAllHostagesBeingRescued() || me->GetGameState()->AreAllHostagesGone())
{
me->Idle();
return;
}
}
else if (me->GetTask() == CCSBot::GUARD_HOSTAGE_RESCUE_ZONE)
{
// if we stumble across a hostage, guard it
CHostage *hostage = me->GetGameState()->GetNearestVisibleFreeHostage();
if (hostage != NULL)
{
// we see a free hostage, guard it
CNavArea *area = TheNavAreaGrid.GetNearestNavArea(&hostage->pev->origin);
if (area != NULL)
{
me->SetTask(CCSBot::GUARD_HOSTAGES);
me->Hide(area);
me->PrintIfWatched("I'm guarding hostages I found\n");
// don't chatter here - he'll tell us when he's in his hiding spot
return;
}
}
}
}
}
#endif
bool isSettledInSniper = (me->IsSniper() && m_isAtSpot) ? true : false;
// only investigate noises if we are initiating attacks, and we aren't a "settled in" sniper
// dont investigate noises if we are reloading
if (!me->IsActiveWeaponReloading() &&
!isSettledInSniper &&
me->GetDisposition() == CCSBot::ENGAGE_AND_INVESTIGATE)
{
// if we are holding position, and have heard the enemy nearby, investigate after our hold time is up
if (m_isHoldingPosition && m_heardEnemy && (gpGlobals->time - m_firstHeardEnemyTime > m_holdPositionTime))
{
// TODO: We might need to remember specific location of last enemy noise here
me->InvestigateNoise();
return;
}
// investigate nearby enemy noises
if (me->ShouldInvestigateNoise())
{
// if we are holding position, check if enough time has elapsed since we first heard the enemy
if (m_isAtSpot && m_isHoldingPosition)
{
if (!m_heardEnemy)
{
// first time we heard the enemy
m_heardEnemy = true;
m_firstHeardEnemyTime = gpGlobals->time;
me->PrintIfWatched("Heard enemy, holding position for %f2.1 seconds...\n", m_holdPositionTime);
}
}
else
{
// not holding position - investigate enemy noise
me->InvestigateNoise();
return;
}
}
}
}
// look around
me->UpdateLookAround();
// if we are at our hiding spot, crouch and wait
if (m_isAtSpot)
{
me->Crouch();
// if we have a shield, hide behind it
// if (me->HasShield() && !me->IsProtectedByShield())
// me->SecondaryAttack();
// while sitting at our hiding spot, if we are being attacked but can't see our attacker, move somewhere else
const float hurtRecentlyTime = 1.0f;
if (!me->IsEnemyVisible() && me->GetTimeSinceAttacked() < hurtRecentlyTime)
{
me->Idle();
return;
}
#if 0
// encourage the human player
if (!me->IsDoingScenario())
{
if (me->m_iTeam == CT)
{
if (me->GetTask() == CCSBot::GUARD_BOMB_ZONE && me->IsAtHidingSpot() && ctrl->IsBombPlanted())
{
if (me->GetNearbyEnemyCount() == 0)
{
const float someTime = 30.0f;
const float littleTime = 11.0;
if (ctrl->GetBombTimeLeft() > someTime)
me->GetChatter()->Encourage("BombsiteSecure", RANDOM_FLOAT(10.0f, 15.0f));
else if (ctrl->GetBombTimeLeft() > littleTime)
me->GetChatter()->Encourage("WaitingForHumanToDefuseBomb", RANDOM_FLOAT(5.0f, 8.0f));
else
me->GetChatter()->Encourage("WaitingForHumanToDefuseBombPanic", RANDOM_FLOAT(3.0f, 4.0f));
}
}
if (me->GetTask() == CCSBot::GUARD_HOSTAGES && me->IsAtHidingSpot())
{
if (me->GetNearbyEnemyCount() == 0)
{
CHostage *hostage = me->GetGameState()->GetNearestVisibleFreeHostage();
if (hostage != NULL)
{
me->GetChatter()->Encourage("WaitingForHumanToRescueHostages", RANDOM_FLOAT(10.0f, 15.0f));
}
}
}
}
}
#endif
}
else
{
// if a Player is using this hiding spot, give up
float range;
CBasePlayer *camper = UTIL_GetClosestPlayer(&m_hidingSpot, &range);
const float closeRange = 75.0f;
if (camper != NULL && camper != me && range < closeRange && me->IsVisible(camper, CHECK_FOV))
{
// player is in our hiding spot
me->PrintIfWatched("Someone's in my hiding spot - picking another...\n");
const int maxRetries = 3;
if (m_retry++ >= maxRetries)
{
me->PrintIfWatched("Can't find a free hiding spot, giving up.\n");
me->Idle();
return;
}
// pick another hiding spot near where we were planning on hiding
me->Hide(TheNavAreaGrid.GetNavArea(&m_hidingSpot));
return;
}
Vector toSpot;
toSpot.x = m_hidingSpot.x - me->pev->origin.x;
toSpot.y = m_hidingSpot.y - me->pev->origin.y;
toSpot.z = m_hidingSpot.z - me->GetFeetZ(); // use feet location
float dist = toSpot.Length();
const float crouchDist = 200.0f;
if (dist < crouchDist)
me->Crouch();
const float atDist = 20.0f;
if (dist < atDist)
{
m_isAtSpot = true;
// make sure our approach points are valid, since we'll be watching them
me->ComputeApproachPoints();
// ready our weapon and prepare to attack
me->EquipBestWeapon(MUST_EQUIP);
me->SetDisposition(CCSBot::OPPORTUNITY_FIRE);
// if we are a sniper, update our task
if (me->GetTask() == CCSBot::MOVE_TO_SNIPER_SPOT)
{
me->SetTask(CCSBot::SNIPING);
}
// determine which way to look
TraceResult result;
float outAngle = 0.0f;
float outAngleRange = 0.0f;
for (float angle = 0.0f; angle < 360.0f; angle += 45.0f)
{
UTIL_TraceLine(me->GetEyePosition(), me->GetEyePosition() + 1000.0f * Vector(BotCOS(angle), BotSIN(angle), 0.0f), ignore_monsters, ignore_glass, ENT(me->pev), &result);
if (result.flFraction > outAngleRange)
{
outAngle = angle;
outAngleRange = result.flFraction;
}
}
me->SetLookAheadAngle(outAngle);
}
// move to hiding spot
if (me->UpdatePathMovement() != CCSBot::PROGRESSING && !m_isAtSpot)
{
// we couldn't get to our hiding spot - pick another
me->PrintIfWatched("Can't get to my hiding spot - finding another...\n");
// search from hiding spot, since we know it was valid
const Vector *pos = FindNearbyHidingSpot(me, &m_hidingSpot, m_searchFromArea, m_range, me->IsSniper());
if (pos == NULL)
{
// no available hiding spots
me->PrintIfWatched("No available hiding spots - hiding where I'm at.\n");
// hide where we are
m_hidingSpot.x = me->pev->origin.x;
m_hidingSpot.y = me->pev->origin.y;
m_hidingSpot.z = me->GetFeetZ();
}
else
{
m_hidingSpot = *pos;
}
// build a path to our new hiding spot
if (me->ComputePath(TheNavAreaGrid.GetNavArea(&m_hidingSpot), &m_hidingSpot, FASTEST_ROUTE) == false)
{
me->PrintIfWatched("Can't pathfind to hiding spot\n");
me->Idle();
return;
}
}
}
}
void HideState::OnExit(CCSBot *me)
{
m_isHoldingPosition = false;
me->StandUp();
me->ResetStuckMonitor();
me->ClearLookAt();
me->ClearApproachPoints();
// if we have a shield, put it away
// if (me->HasShield() && me->IsProtectedByShield())
// me->SecondaryAttack();
}

View File

@ -0,0 +1,198 @@
#include "bot_common.h"
// Begin the hunt
void HuntState::OnEnter(CCSBot *me)
{
// lurking death
if (me->IsUsingKnife() && me->IsWellPastSafe() && !me->IsHurrying())
me->Walk();
else
me->Run();
me->StandUp();
me->SetDisposition(CCSBot::ENGAGE_AND_INVESTIGATE);
me->SetTask(CCSBot::SEEK_AND_DESTROY);
me->DestroyPath();
}
// Hunt down our enemies
void HuntState::OnUpdate(CCSBot *me)
{
// if we've been hunting for a long time, drop into Idle for a moment to
// select something else to do
const float huntingTooLongTime = 30.0f;
if (gpGlobals->time - me->GetStateTimestamp() > huntingTooLongTime)
{
// stop being a rogue and do the scenario, since there must not be many enemies left to hunt
me->PrintIfWatched("Giving up hunting, and being a rogue\n");
me->SetRogue(false);
me->Idle();
return;
}
CCSBotManager *ctrl = TheCSBots();
#if 0
// scenario logic
if (ctrl->GetScenario() == CCSBotManager::SCENARIO_DEFUSE_BOMB)
{
if (me->m_iTeam == TERRORIST)
{
// if we have the bomb and it's time to plant, or we happen to be in a bombsite and it seems safe, do it
if (me->IsCarryingBomb())
{
const float safeTime = 3.0f;
if (ctrl->IsTimeToPlantBomb() || (me->IsAtBombsite() && gpGlobals->time - me->GetLastSawEnemyTimestamp() > safeTime))
{
me->Idle();
return;
}
}
// if we notice the bomb lying on the ground, go get it
if (me->NoticeLooseBomb())
{
me->FetchBomb();
return;
}
// if bomb has been planted, and we hear it, move to a hiding spot near the bomb and watch it
const Vector *bombPos = me->GetGameState()->GetBombPosition();
if (!me->IsRogue() && me->GetGameState()->IsBombPlanted() && bombPos != NULL)
{
me->SetTask(CCSBot::GUARD_TICKING_BOMB);
me->Hide(TheNavAreaGrid.GetNavArea(bombPos));
return;
}
}
// CT
else
{
if (!me->IsRogue() && me->CanSeeLooseBomb())
{
// if we are near the loose bomb and can see it, hide nearby and guard it
me->SetTask(CCSBot::GUARD_LOOSE_BOMB);
me->Hide(ctrl->GetLooseBombArea());
me->GetChatter()->AnnouncePlan("GoingToGuardLooseBomb", ctrl->GetLooseBombArea()->GetPlace());
return;
}
else if (ctrl->IsBombPlanted())
{
// rogues will defuse a bomb, but not guard the defuser
if (!me->IsRogue() || !ctrl->GetBombDefuser())
{
// search for the planted bomb to defuse
me->Idle();
return;
}
}
}
}
else if (ctrl->GetScenario() == CCSBotManager::SCENARIO_RESCUE_HOSTAGES)
{
if (me->m_iTeam == TERRORIST)
{
if (me->GetGameState()->AreAllHostagesBeingRescued())
{
// all hostages are being rescued, head them off at the escape zones
if (me->GuardRandomZone())
{
me->SetTask(CCSBot::GUARD_HOSTAGE_RESCUE_ZONE);
me->PrintIfWatched("Trying to beat them to an escape zone!\n");
me->SetDisposition(CCSBot::OPPORTUNITY_FIRE);
me->GetChatter()->GuardingHostageEscapeZone(IS_PLAN);
return;
}
}
// if safe time is up, and we stumble across a hostage, guard it
if (!me->IsRogue() && !me->IsSafe())
{
CHostage *hostage = me->GetGameState()->GetNearestVisibleFreeHostage();
if (hostage != NULL)
{
CNavArea *area = TheNavAreaGrid.GetNearestNavArea(&hostage->pev->origin);
if (area != NULL)
{
// we see a free hostage, guard it
me->SetTask(CCSBot::GUARD_HOSTAGES);
me->Hide(area);
me->PrintIfWatched("I'm guarding hostages\n");
me->GetChatter()->GuardingHostages(area->GetPlace(), IS_PLAN);
return;
}
}
}
}
}
#endif
// listen for enemy noises
if (me->ShouldInvestigateNoise())
{
me->InvestigateNoise();
return;
}
// look around
me->UpdateLookAround();
// if we have reached our destination area, pick a new one
// if our path fails, pick a new one
if (me->GetLastKnownArea() == m_huntArea || me->UpdatePathMovement() != CCSBot::PROGRESSING)
{
m_huntArea = NULL;
float oldest = 0.0f;
int areaCount = 0;
const float minSize = 150.0f;
FOR_EACH_LL (TheNavAreaList, it)
{
CNavArea *area = TheNavAreaList[it];
++areaCount;
// skip the small areas
const Extent *extent = area->GetExtent();
if (extent->hi.x - extent->lo.x < minSize || extent->hi.y - extent->lo.y < minSize)
continue;
// keep track of the least recently cleared area
float age = gpGlobals->time - area->GetClearedTimestamp(me->m_iTeam - 1);
if (age > oldest)
{
oldest = age;
m_huntArea = area;
}
}
// if all the areas were too small, pick one at random
int which = RANDOM_LONG(0, areaCount - 1);
areaCount = 0;
FOR_EACH_LL (TheNavAreaList, it2)
{
m_huntArea = TheNavAreaList[it2];
if (which == areaCount)
break;
--which;
}
if (m_huntArea != NULL)
{
// create a new path to a far away area of the map
me->ComputePath(m_huntArea, NULL, SAFEST_ROUTE);
}
}
}
// Done hunting
void HuntState::OnExit(CCSBot *me)
{
;
}

View File

@ -0,0 +1,755 @@
#include "bot_common.h"
// range for snipers to select a hiding spot
const float sniperHideRange = 2000.0f;
// The Idle state.
// We never stay in the Idle state - it is a "home base" for the state machine that
// does various checks to determine what we should do next.
void IdleState::OnEnter(CCSBot *me)
{
me->DestroyPath();
me->SetEnemy(NULL);
// lurking death
if (me->IsUsingKnife() && me->IsWellPastSafe() && !me->IsHurrying())
me->Walk();
// Since Idle assigns tasks, we assume that coming back to Idle means our task is complete
me->SetTask(CCSBot::SEEK_AND_DESTROY);
me->SetDisposition(CCSBot::ENGAGE_AND_INVESTIGATE);
}
void IdleState::OnUpdate(CCSBot *me)
{
// all other states assume GetLastKnownArea() is valid, ensure that it is
if (me->GetLastKnownArea() == NULL && me->StayOnNavMesh() == false)
return;
// zombies never leave the Idle state
if (cv_bot_zombie.value > 0.0f)
{
me->ResetStuckMonitor();
return;
}
// if we are in the early "safe" time, grab a knife or grenade
if (me->IsSafe())
{
// if we have a grenade, use it
if (!me->EquipGrenade())
{
// high-skill bots run with the knife
if (me->GetProfile()->GetSkill() > 0.33f)
{
me->EquipKnife();
}
}
}
CCSBotManager *ctrl = TheCSBots();
// if round is over, hunt
if (me->GetGameState()->IsRoundOver())
{
// if we are escorting hostages, try to get to the rescue zone
if (me->GetHostageEscortCount())
{
const CCSBotManager::Zone *zone = ctrl->GetClosestZone(me->GetLastKnownArea(), PathCost(me, FASTEST_ROUTE));
me->SetTask(CCSBot::RESCUE_HOSTAGES);
me->Run();
me->SetDisposition(CCSBot::SELF_DEFENSE);
me->MoveTo(ctrl->GetRandomPositionInZone(zone), FASTEST_ROUTE);
me->PrintIfWatched("Trying to rescue hostages at the end of the round\n");
return;
}
me->Hunt();
return;
}
const float defenseSniperCampChance = 75.0f;
const float offenseSniperCampChance = 10.0f;
// if we were following someone, continue following them
if (me->IsFollowing())
{
me->ContinueFollowing();
return;
}
#if 0
// Scenario logic
switch (ctrl->GetScenario())
{
case CCSBotManager::SCENARIO_DEFUSE_BOMB:
{
static int inumpo = 0;
inumpo++;
// if this is a bomb game and we have the bomb, go plant it
if (me->m_iTeam == TERRORIST)
{
if (me->GetGameState()->IsBombPlanted())
{
if (me->GetGameState()->GetPlantedBombsite() != CSGameState::UNKNOWN)
{
// T's always know where the bomb is - go defend it
const CCSBotManager::Zone *zone = ctrl->GetZone(me->GetGameState()->GetPlantedBombsite());
me->SetTask(CCSBot::GUARD_TICKING_BOMB);
Place place = TheNavAreaGrid.GetPlace(&zone->m_center);
if (place != UNDEFINED_PLACE)
{
// pick a random hiding spot in this place
const Vector *spot = FindRandomHidingSpot(me, place, me->IsSniper());
if (spot != NULL)
{
me->Hide(spot);
return;
}
}
// hide nearby
me->Hide(TheNavAreaGrid.GetNearestNavArea(&zone->m_center));
return;
}
else
{
// ask our teammates where the bomb is
me->GetChatter()->RequestBombLocation();
// we dont know where the bomb is - we must search the bombsites
int zoneIndex = me->GetGameState()->GetNextBombsiteToSearch();
// move to bombsite - if we reach it, we'll update its cleared status, causing us to select another
const Vector *pos = ctrl->GetRandomPositionInZone(ctrl->GetZone(zoneIndex));
if (pos != NULL)
{
me->SetTask(CCSBot::FIND_TICKING_BOMB);
me->MoveTo(pos);
return;
}
}
}
else if (me->IsCarryingBomb())
{
// if we're at a bomb site, plant the bomb
if (me->IsAtBombsite())
{
// plant it
me->SetTask(CCSBot::PLANT_BOMB);
me->PlantBomb();
// radio to the team
me->GetChatter()->PlantingTheBomb(me->GetPlace());
return;
}
else if (ctrl->IsTimeToPlantBomb())
{
// move to the closest bomb site
const CCSBotManager::Zone *zone = ctrl->GetClosestZone(me->GetLastKnownArea(), PathCost(me));
if (zone != NULL)
{
// pick a random spot within the bomb zone
const Vector *pos = ctrl->GetRandomPositionInZone(zone);
if (pos != NULL)
{
// move to bombsite
me->SetTask(CCSBot::PLANT_BOMB);
me->Run();
me->MoveTo(pos);
return;
}
}
}
}
else
{
// small chance of sniper camping on offense, if we aren't carrying the bomb
if (me->GetFriendsRemaining() && me->IsSniper() && RANDOM_FLOAT(0, 100.0f) < offenseSniperCampChance)
{
me->SetTask(CCSBot::MOVE_TO_SNIPER_SPOT);
me->Hide(me->GetLastKnownArea(), RANDOM_FLOAT(10.0f, 30.0f), sniperHideRange);
me->SetDisposition(CCSBot::OPPORTUNITY_FIRE);
me->PrintIfWatched("Sniping!\n");
return;
}
// if the bomb is loose (on the ground), go get it
if (me->NoticeLooseBomb())
{
me->FetchBomb();
return;
}
// if bomb has been planted, and we hear it, move to a hiding spot near the bomb and guard it
if (!me->IsRogue() && me->GetGameState()->IsBombPlanted() && me->GetGameState()->GetBombPosition() != NULL)
{
const Vector *bombPos = me->GetGameState()->GetBombPosition();
if (bombPos != NULL)
{
me->SetTask(CCSBot::GUARD_TICKING_BOMB);
me->Hide(TheNavAreaGrid.GetNavArea(bombPos));
return;
}
}
}
}
// CT
else
{
if (me->GetGameState()->IsBombPlanted())
{
// if the bomb has been planted, attempt to defuse it
const Vector *bombPos = me->GetGameState()->GetBombPosition();
if (bombPos != NULL)
{
// if someone is defusing the bomb, guard them
if (ctrl->GetBombDefuser())
{
if (!me->IsRogue())
{
me->SetTask(CCSBot::GUARD_BOMB_DEFUSER);
me->Hide(TheNavAreaGrid.GetNavArea(bombPos));
return;
}
}
else if (me->IsDoingScenario())
{
// move to the bomb and defuse it
me->SetTask(CCSBot::DEFUSE_BOMB);
me->SetDisposition(CCSBot::OPPORTUNITY_FIRE);
me->MoveTo(bombPos);
return;
}
else
{
// we're not allowed to defuse, guard the bomb zone
me->SetTask(CCSBot::GUARD_BOMB_ZONE);
me->Hide(TheNavAreaGrid.GetNavArea(bombPos));
me->SetDisposition(CCSBot::OPPORTUNITY_FIRE);
return;
}
}
else if (me->GetGameState()->GetPlantedBombsite() != CSGameState::UNKNOWN)
{
// we know which bombsite, but not exactly where the bomb is, go there
const CCSBotManager::Zone *zone = ctrl->GetZone(me->GetGameState()->GetPlantedBombsite());
if (zone != NULL)
{
if (me->IsDoingScenario())
{
me->SetTask(CCSBot::DEFUSE_BOMB);
me->MoveTo(&zone->m_center);
me->SetDisposition(CCSBot::OPPORTUNITY_FIRE);
return;
}
else
{
// we're not allowed to defuse, guard the bomb zone
me->SetTask(CCSBot::GUARD_BOMB_ZONE);
me->Hide(TheNavAreaGrid.GetNavArea(&zone->m_center));
me->SetDisposition(CCSBot::OPPORTUNITY_FIRE);
return;
}
}
}
else
{
// we dont know where the bomb is - we must search the bombsites
// find closest un-cleared bombsite
const CCSBotManager::Zone *zone = NULL;
float travelDistance = 9999999.9f;
for (int z = 0; z < ctrl->GetZoneCount(); ++z)
{
if (ctrl->GetZone(z)->m_areaCount == 0)
continue;
// don't check bombsites that have been cleared
if (me->GetGameState()->IsBombsiteClear(z))
continue;
// just use the first overlapping nav area as a reasonable approximation
ShortestPathCost pathCost = ShortestPathCost();
float dist = NavAreaTravelDistance(me->GetLastKnownArea(), TheNavAreaGrid.GetNearestNavArea(&ctrl->GetZone(z)->m_center), pathCost);
if (/*dist >= 0.0f && */dist < travelDistance)
{
zone = ctrl->GetZone(z);
travelDistance = dist;
}
}
if (zone != NULL)
{
const float farAwayRange = 2000.0f;
if (travelDistance > farAwayRange)
{
zone = NULL;
}
}
// if closest bombsite is "far away", pick one at random
if (zone == NULL)
{
int zoneIndex = me->GetGameState()->GetNextBombsiteToSearch();
zone = ctrl->GetZone(zoneIndex);
}
// move to bombsite - if we reach it, we'll update its cleared status, causing us to select another
if (zone != NULL)
{
const Vector *pos = ctrl->GetRandomPositionInZone(zone);
if (pos != NULL)
{
me->SetTask(CCSBot::FIND_TICKING_BOMB);
me->MoveTo(pos);
return;
}
}
}
assert((0, "A CT bot doesn't know what to do while the bomb is planted!\n"));
}
// if we have a sniper rifle, we like to camp, whether rogue or not
if (me->IsSniper())
{
if (RANDOM_FLOAT(0, 100) <= defenseSniperCampChance)
{
CNavArea *snipingArea = NULL;
// if the bomb is loose, snipe near it
if (me->GetGameState()->IsLooseBombLocationKnown())
{
snipingArea = TheNavAreaGrid.GetNearestNavArea(me->GetGameState()->GetBombPosition());
me->PrintIfWatched("Sniping near loose bomb\n");
}
else
{
// snipe bomb zone(s)
const CCSBotManager::Zone *zone = ctrl->GetRandomZone();
if (zone != NULL)
{
snipingArea = ctrl->GetRandomAreaInZone(zone);
me->PrintIfWatched("Sniping near bombsite\n");
}
}
if (snipingArea != NULL)
{
me->SetTask(CCSBot::MOVE_TO_SNIPER_SPOT);
me->Hide(snipingArea, -1.0f, sniperHideRange);
me->SetDisposition(CCSBot::OPPORTUNITY_FIRE);
return;
}
}
}
// rogues just hunt, unless they want to snipe
// if the whole team has decided to rush, hunt
// if we know the bomb is dropped, hunt for enemies and the loose bomb
if (me->IsRogue() || ctrl->IsDefenseRushing() || me->GetGameState()->IsLooseBombLocationKnown())
{
me->Hunt();
return;
}
// the lower our morale gets, the more we want to camp the bomb zone(s)
// only decide to camp at the start of the round, or if we haven't seen anything for a long time
if (me->IsSafe() || me->HasNotSeenEnemyForLongTime())
{
float guardBombsiteChance = -34.0f * me->GetMorale();
if (RANDOM_FLOAT(0.0f, 100.0f) < guardBombsiteChance)
{
float guardRange = 500.0f + 100.0f * (me->GetMorale() + 3);
// guard bomb zone(s)
const CCSBotManager::Zone *zone = ctrl->GetRandomZone();
if (zone != NULL)
{
CNavArea *area = ctrl->GetRandomAreaInZone(zone);
if (area != NULL)
{
me->PrintIfWatched("I'm guarding a bombsite\n");
me->GetChatter()->AnnouncePlan("GoingToDefendBombsite", area->GetPlace());
me->SetTask(CCSBot::GUARD_BOMB_ZONE);
me->Hide(area, -1.0, guardRange);
me->SetDisposition(CCSBot::OPPORTUNITY_FIRE);
return;
}
}
}
}
}
break;
}
case CCSBotManager::SCENARIO_ESCORT_VIP:
{
if (me->m_iTeam == TERRORIST)
{
// if we have a sniper rifle, we like to camp, whether rogue or not
if (me->IsSniper())
{
if (RANDOM_FLOAT(0, 100) <= defenseSniperCampChance)
{
// snipe escape zone(s)
const CCSBotManager::Zone *zone = ctrl->GetRandomZone();
if (zone != NULL)
{
CNavArea *area = ctrl->GetRandomAreaInZone(zone);
if (area != NULL)
{
me->SetTask(CCSBot::MOVE_TO_SNIPER_SPOT);
me->Hide(area, -1.0, sniperHideRange);
me->SetDisposition(CCSBot::OPPORTUNITY_FIRE);
me->PrintIfWatched("Sniping near escape zone\n");
return;
}
}
}
}
// rogues just hunt, unless they want to snipe
// if the whole team has decided to rush, hunt
if (me->IsRogue() || ctrl->IsDefenseRushing())
break;
// the lower our morale gets, the more we want to camp the escape zone(s)
float guardEscapeZoneChance = -34.0f * me->GetMorale();
if (RANDOM_FLOAT(0.0f, 100.0f) < guardEscapeZoneChance)
{
// guard escape zone(s)
const CCSBotManager::Zone *zone = ctrl->GetRandomZone();
if (zone != NULL)
{
CNavArea *area = ctrl->GetRandomAreaInZone(zone);
if (area != NULL)
{
// guard the escape zone - stay closer if our morale is low
me->SetTask(CCSBot::GUARD_VIP_ESCAPE_ZONE);
me->PrintIfWatched("I'm guarding an escape zone\n");
float escapeGuardRange = 750.0f + 250.0f * (me->GetMorale() + 3);
me->Hide(area, -1.0, escapeGuardRange);
me->SetDisposition(CCSBot::OPPORTUNITY_FIRE);
return;
}
}
}
}
// CT
else
{
if (me->m_bIsVIP)
{
// if early in round, pick a random zone, otherwise pick closest zone
const float earlyTime = 20.0f;
const CCSBotManager::Zone *zone = NULL;
if (ctrl->GetElapsedRoundTime() < earlyTime)
{
// pick random zone
zone = ctrl->GetRandomZone();
}
else
{
// pick closest zone
zone = ctrl->GetClosestZone(me->GetLastKnownArea(), PathCost(me));
}
if (zone != NULL)
{
// pick a random spot within the escape zone
const Vector *pos = ctrl->GetRandomPositionInZone(zone);
if (pos != NULL)
{
// move to escape zone
me->SetTask(CCSBot::VIP_ESCAPE);
me->Run();
me->MoveTo(pos);
// tell team to follow
const float repeatTime = 30.0f;
if (me->GetFriendsRemaining() && ctrl->GetRadioMessageInterval(EVENT_RADIO_FOLLOW_ME, me->m_iTeam) > repeatTime)
me->SendRadioMessage(EVENT_RADIO_FOLLOW_ME);
return;
}
}
}
else
{
// small chance of sniper camping on offense, if we aren't VIP
if (me->GetFriendsRemaining() && me->IsSniper() && RANDOM_FLOAT(0, 100.0f) < offenseSniperCampChance)
{
me->SetTask(CCSBot::MOVE_TO_SNIPER_SPOT);
me->Hide(me->GetLastKnownArea(), RANDOM_FLOAT(10.0f, 30.0f), sniperHideRange);
me->SetDisposition(CCSBot::OPPORTUNITY_FIRE);
me->PrintIfWatched("Sniping!\n");
return;
}
}
}
break;
}
case CCSBotManager::SCENARIO_RESCUE_HOSTAGES:
{
if (me->m_iTeam == TERRORIST)
{
bool campHostages;
// if we are in early game, camp the hostages
if (me->IsSafe())
{
campHostages = true;
}
else if (me->GetGameState()->HaveSomeHostagesBeenTaken() || me->GetGameState()->AreAllHostagesBeingRescued())
{
campHostages = false;
}
else
{
// later in the game, camp either hostages or escape zone
const float campZoneChance = 100.0f * (ctrl->GetElapsedRoundTime() - me->GetSafeTime()) / 120.0f;
campHostages = (RANDOM_FLOAT(0, 100) > campZoneChance) ? true : false;
}
// if we have a sniper rifle, we like to camp, whether rogue or not
if (me->IsSniper())
{
if (RANDOM_FLOAT(0, 100) <= defenseSniperCampChance)
{
const Vector *hostagePos = me->GetGameState()->GetRandomFreeHostagePosition();
if (hostagePos != NULL && campHostages)
{
me->SetTask(CCSBot::MOVE_TO_SNIPER_SPOT);
me->PrintIfWatched("Sniping near hostages\n");
me->Hide(TheNavAreaGrid.GetNearestNavArea(hostagePos), -1.0, sniperHideRange);
me->SetDisposition(CCSBot::OPPORTUNITY_FIRE);
return;
}
else
{
// camp the escape zone(s)
if (me->GuardRandomZone(sniperHideRange))
{
me->SetTask(CCSBot::MOVE_TO_SNIPER_SPOT);
me->PrintIfWatched("Sniping near a rescue zone\n");
me->SetDisposition(CCSBot::OPPORTUNITY_FIRE);
return;
}
}
}
}
// if safe time is up, and we stumble across a hostage, guard it
if (!me->IsSafe() && !me->IsRogue())
{
CBaseEntity *hostage = me->GetGameState()->GetNearestVisibleFreeHostage();
if (hostage != NULL)
{
// we see a free hostage, guard it
CNavArea *area = TheNavAreaGrid.GetNearestNavArea(&hostage->pev->origin);
if (area != NULL)
{
me->SetTask(CCSBot::GUARD_HOSTAGES);
me->Hide(area);
me->PrintIfWatched("I'm guarding hostages I found\n");
// don't chatter here - he'll tell us when he's in his hiding spot
return;
}
}
}
// decide if we want to hunt, or guard
const float huntChance = 70.0f + 25.0f * me->GetMorale();
// rogues just hunt, unless they want to snipe
// if the whole team has decided to rush, hunt
if (me->GetFriendsRemaining())
{
if (me->IsRogue() || ctrl->IsDefenseRushing() || RANDOM_FLOAT(0, 100) < huntChance)
{
me->Hunt();
return;
}
}
// decide whether to camp the hostages or the escape zones
const Vector *hostagePos = me->GetGameState()->GetRandomFreeHostagePosition();
if (hostagePos != NULL && campHostages)
{
CNavArea *area = TheNavAreaGrid.GetNearestNavArea(hostagePos);
if (area != NULL)
{
// guard the hostages - stay closer to hostages if our morale is low
me->SetTask(CCSBot::GUARD_HOSTAGES);
me->PrintIfWatched("I'm guarding hostages\n");
float hostageGuardRange = 750.0f + 250.0f * (me->GetMorale() + 3);
me->Hide(area, -1.0, hostageGuardRange);
me->SetDisposition(CCSBot::OPPORTUNITY_FIRE);
if (RANDOM_FLOAT(0, 100) < 50)
me->GetChatter()->GuardingHostages(area->GetPlace(), IS_PLAN);
return;
}
}
// guard rescue zone(s)
if (me->GuardRandomZone())
{
me->SetTask(CCSBot::GUARD_HOSTAGE_RESCUE_ZONE);
me->PrintIfWatched("I'm guarding a rescue zone\n");
me->SetDisposition(CCSBot::OPPORTUNITY_FIRE);
me->GetChatter()->GuardingHostageEscapeZone(IS_PLAN);
return;
}
}
// CT
else
{
// only decide to do something else if we aren't already rescuing hostages
if (!me->GetHostageEscortCount())
{
// small chance of sniper camping on offense
if (me->GetFriendsRemaining() && me->IsSniper() && RANDOM_FLOAT(0, 100.0f) < offenseSniperCampChance)
{
me->SetTask(CCSBot::MOVE_TO_SNIPER_SPOT);
me->Hide(me->GetLastKnownArea(), RANDOM_FLOAT(10.0f, 30.0f), sniperHideRange);
me->SetDisposition(CCSBot::OPPORTUNITY_FIRE);
me->PrintIfWatched("Sniping!\n");
return;
}
if (me->GetFriendsRemaining() && !me->GetHostageEscortCount())
{
// rogues just hunt, unless all friends are dead
// if we have friends left, we might go hunting instead of hostage rescuing
const float huntChance = 33.3f;
if (me->IsRogue() || RANDOM_FLOAT(0.0f, 100.0f) < huntChance)
{
me->Hunt();
return;
}
}
}
// look for free hostages - CT's have radar so they know where hostages are at all times
CHostage *hostage = me->GetGameState()->GetNearestFreeHostage();
// if we are not allowed to do the scenario, guard the hostages to clear the area for the human(s)
if (!me->IsDoingScenario())
{
if (hostage != NULL)
{
CNavArea *area = TheNavAreaGrid.GetNearestNavArea(&hostage->pev->origin);
if (area != NULL)
{
me->SetTask(CCSBot::GUARD_HOSTAGES);
me->Hide(area);
me->PrintIfWatched("I'm securing the hostages for a human to rescue\n");
return;
}
}
me->Hunt();
return;
}
bool fetchHostages = false;
bool rescueHostages = false;
const CCSBotManager::Zone *zone = NULL;
me->SetGoalEntity(NULL);
// if we are escorting hostages, determine where to take them
if (me->GetHostageEscortCount())
zone = ctrl->GetClosestZone(me->GetLastKnownArea(), PathCost(me, FASTEST_ROUTE));
// if we are escorting hostages and there are more hostages to rescue,
// determine whether it's faster to rescue the ones we have, or go get the remaining ones
if (hostage != NULL)
{
if (zone != NULL)
{
PathCost pathCost(me, FASTEST_ROUTE);
float toZone = NavAreaTravelDistance(me->GetLastKnownArea(), zone->m_area[0], pathCost);
float toHostage = NavAreaTravelDistance(me->GetLastKnownArea(), TheNavAreaGrid.GetNearestNavArea(&hostage->pev->origin), pathCost);
if (toHostage < 0.0f)
{
rescueHostages = true;
}
else
{
if (toZone < toHostage)
rescueHostages = true;
else
fetchHostages = true;
}
}
else
{
fetchHostages = true;
}
}
else if (zone != NULL)
{
rescueHostages = true;
}
if (fetchHostages)
{
// go get hostages
me->SetTask(CCSBot::COLLECT_HOSTAGES);
me->Run();
me->SetGoalEntity(hostage);
me->ResetWaitForHostagePatience();
// if we already have some hostages, move to the others by the quickest route
RouteType route = (me->GetHostageEscortCount()) ? FASTEST_ROUTE : SAFEST_ROUTE;
me->MoveTo(&hostage->pev->origin, route);
me->PrintIfWatched("I'm collecting hostages\n");
return;
}
if (rescueHostages)
{
me->SetTask(CCSBot::RESCUE_HOSTAGES);
me->Run();
me->SetDisposition(CCSBot::SELF_DEFENSE);
me->MoveTo(ctrl->GetRandomPositionInZone(zone), FASTEST_ROUTE);
me->PrintIfWatched("I'm rescuing hostages\n");
me->GetChatter()->EscortingHostages();
return;
}
}
break;
}
#endif
// deathmatch
//default:
{
// sniping check
if (me->GetFriendsRemaining() && me->IsSniper() && RANDOM_FLOAT(0, 100.0f) < offenseSniperCampChance)
{
me->SetTask(CCSBot::MOVE_TO_SNIPER_SPOT);
me->Hide(me->GetLastKnownArea(), RANDOM_FLOAT(10.0f, 30.0f), sniperHideRange);
me->SetDisposition(CCSBot::OPPORTUNITY_FIRE);
me->PrintIfWatched("Sniping!\n");
return;
}
//break;
}
//}
// if we have nothing special to do, go hunting for enemies
me->Hunt();
}

View File

@ -0,0 +1,113 @@
#include "bot_common.h"
// Move towards currently heard noise
void InvestigateNoiseState::AttendCurrentNoise(CCSBot *me)
{
if (!me->IsNoiseHeard() && me->GetNoisePosition())
return;
// remember where the noise we heard was
m_checkNoisePosition = *me->GetNoisePosition();
// tell our teammates (unless the noise is obvious, like gunfire)
if (me->IsWellPastSafe() && me->HasNotSeenEnemyForLongTime() && me->GetNoisePriority() != PRIORITY_HIGH)
me->GetChatter()->HeardNoise(me->GetNoisePosition());
// figure out how to get to the noise
me->PrintIfWatched("Attending to noise...\n");
me->ComputePath(me->GetNoiseArea(), &m_checkNoisePosition, SAFEST_ROUTE);
// consume the noise
me->ForgetNoise();
}
void InvestigateNoiseState::OnEnter(CCSBot *me)
{
AttendCurrentNoise(me);
}
// Use TravelDistance instead of distance...
void InvestigateNoiseState::OnUpdate(CCSBot *me)
{
float newNoiseDist;
if (me->ShouldInvestigateNoise(&newNoiseDist))
{
Vector toOldNoise = m_checkNoisePosition - me->pev->origin;
const float muchCloserDist = 100.0f;
if (toOldNoise.IsLengthGreaterThan(newNoiseDist + muchCloserDist))
{
// new sound is closer
AttendCurrentNoise(me);
}
}
// if the pathfind fails, give up
if (!me->HasPath())
{
me->Idle();
return;
}
// look around
me->UpdateLookAround();
// get distance remaining on our path until we reach the source of the noise
float noiseDist = (m_checkNoisePosition - me->pev->origin).Length();
if (me->IsUsingKnife())
{
if (me->IsHurrying())
me->Run();
else
me->Walk();
}
else
{
const float closeToNoiseRange = 1500.0f;
if (noiseDist < closeToNoiseRange)
{
// if we dont have many friends left, or we are alone, and we are near noise source, sneak quietly
if (me->GetFriendsRemaining() <= 2 && !me->IsHurrying())
{
me->Walk();
}
else
{
me->Run();
}
}
else
{
me->Run();
}
}
// if we can see the noise position and we're close enough to it and looking at it,
// we don't need to actually move there (it's checked enough)
const float closeRange = 200.0f;
if (noiseDist < closeRange)
{
if (me->IsLookingAtPosition(&m_checkNoisePosition) && me->IsVisible(&m_checkNoisePosition))
{
// can see noise position
me->PrintIfWatched("Noise location is clear.\n");
//me->ForgetNoise();
me->Idle();
return;
}
}
// move towards noise
if (me->UpdatePathMovement() != CCSBot::PROGRESSING)
{
me->Idle();
}
}
void InvestigateNoiseState::OnExit(CCSBot *me)
{
// reset to run mode in case we were sneaking about
me->Run();
}

View File

@ -0,0 +1,300 @@
#include "bot_common.h"
// Move to a potentially far away position.
void MoveToState::OnEnter(CCSBot *me)
{
if (me->IsUsingKnife() && me->IsWellPastSafe() && !me->IsHurrying())
{
me->Walk();
}
else
{
me->Run();
}
// if we need to find the bomb, get there as quick as we can
RouteType route;
switch (me->GetTask())
{
case CCSBot::FIND_TICKING_BOMB:
case CCSBot::DEFUSE_BOMB:
case CCSBot::MOVE_TO_LAST_KNOWN_ENEMY_POSITION:
route = FASTEST_ROUTE;
break;
default:
route = SAFEST_ROUTE;
break;
}
// build path to, or nearly to, goal position
me->ComputePath(TheNavAreaGrid.GetNavArea(&m_goalPosition), &m_goalPosition, route);
m_radioedPlan = false;
m_askedForCover = false;
}
// Move to a potentially far away position.
void MoveToState::OnUpdate(CCSBot *me)
{
CCSBotManager *ctrl = TheCSBots();
// assume that we are paying attention and close enough to know our enemy died
if (me->GetTask() == CCSBot::MOVE_TO_LAST_KNOWN_ENEMY_POSITION)
{
// TODO: Account for reaction time so we take some time to realized the enemy is dead
CBasePlayer *victim = static_cast<CBasePlayer *>(me->GetTaskEntity());
if (victim == NULL || !victim->IsAlive())
{
me->PrintIfWatched("The enemy I was chasing was killed - giving up.\n");
me->Idle();
return;
}
}
// look around
me->UpdateLookAround();
#if 0
// Scenario logic
switch (ctrl->GetScenario())
{
case CCSBotManager::SCENARIO_DEFUSE_BOMB:
{
// if the bomb has been planted, find it
// NOTE: This task is used by both CT and T's to find the bomb
if (me->GetTask() == CCSBot::FIND_TICKING_BOMB)
{
if (!me->GetGameState()->IsBombPlanted())
{
// the bomb is not planted - give up this task
me->Idle();
return;
}
if (me->GetGameState()->GetPlantedBombsite() != CSGameState::UNKNOWN)
{
// we know where the bomb is planted, stop searching
me->Idle();
return;
}
// check off bombsites that we explore or happen to stumble into
for (int z = 0; z < ctrl->GetZoneCount(); ++z)
{
// don't re-check zones
if (me->GetGameState()->IsBombsiteClear(z))
continue;
#if 0
if (ctrl->GetZone(z)->m_extent.Contains(&me->pev->origin))
{
// note this bombsite is clear
me->GetGameState()->ClearBombsite(z);
if (me->m_iTeam == CT)
{
// tell teammates this bombsite is clear
me->GetChatter()->BombsiteClear(z);
}
// find another zone to check
me->Idle();
return;
}
#endif
}
// move to a bombsite
break;
}
else if (me->m_iTeam == CT)
{
if (me->GetGameState()->IsBombPlanted())
{
switch (me->GetTask())
{
case CCSBot::DEFUSE_BOMB:
{
// if we are trying to defuse the bomb, and someone has started defusing, guard them instead
if (me->CanSeePlantedBomb() && ctrl->GetBombDefuser())
{
me->GetChatter()->Say("CoveringFriend");
me->Idle();
return;
}
break;
}
default:
{
// we need to find the bomb
me->Idle();
return;
}
}
}
}
// TERRORIST
else
{
if (me->GetTask() == CCSBot::PLANT_BOMB)
{
if (me->GetFriendsRemaining())
{
// if we are about to plant, radio for cover
if (!m_askedForCover)
{
const float nearPlantSite = 50.0f;
if (me->IsAtBombsite() && me->GetPathDistanceRemaining() < nearPlantSite)
{
// radio to the team
me->GetChatter()->PlantingTheBomb(me->GetPlace());
m_askedForCover = true;
}
// after we have started to move to the bombsite, tell team we're going to plant, and where
// don't do this if we have already radioed that we are starting to plant
if (!m_radioedPlan)
{
const float radioTime = 2.0f;
if (gpGlobals->time - me->GetStateTimestamp() > radioTime)
{
me->GetChatter()->GoingToPlantTheBomb(TheNavAreaGrid.GetPlace(&m_goalPosition));
m_radioedPlan = true;
}
}
}
}
}
}
break;
}
case CCSBotManager::SCENARIO_RESCUE_HOSTAGES:
{
if (me->GetTask() == CCSBot::COLLECT_HOSTAGES)
{
// Since CT's have a radar, they can directly look at the actual hostage state
// check if someone else collected our hostage, or the hostage died or was rescued
CHostage *hostage = static_cast<CHostage *>(me->GetGoalEntity());
if (hostage == NULL || !hostage->IsValid() || hostage->IsFollowingSomeone())
{
me->Idle();
return;
}
// if our hostage has moved, repath
const float repathToleranceSq = 75.0f * 75.0f;
float error = (hostage->pev->origin - m_goalPosition).LengthSquared();
if (error > repathToleranceSq)
{
m_goalPosition = hostage->pev->origin;
me->ComputePath(TheNavAreaGrid.GetNavArea(&m_goalPosition), &m_goalPosition, SAFEST_ROUTE);
}
// TODO: Generalize ladder priorities over other tasks
if (!me->IsUsingLadder())
{
Vector pos = hostage->pev->origin + Vector(0, 0, HumanHeight * 0.75f);
Vector to = pos - me->pev->origin;
// look at the hostage as we approach
const float watchHostageRange = 100.0f;
if (to.IsLengthLessThan(watchHostageRange))
{
me->SetLookAt("Hostage", &pos, PRIORITY_LOW, 0.5f);
// randomly move just a bit to avoid infinite use loops from bad hostage placement
NavRelativeDirType dir = (NavRelativeDirType)RANDOM_LONG(0, 3);
switch (dir)
{
case LEFT: me->StrafeLeft(); break;
case RIGHT: me->StrafeRight(); break;
case FORWARD: me->MoveForward(); break;
case BACKWARD: me->MoveBackward(); break;
}
// check if we are close enough to the hostage to talk to him
const float useRange = PLAYER_USE_RADIUS - 14.0f; // shave off a fudge factor to make sure we're within range
if (to.IsLengthLessThan(useRange))
{
me->UseEntity(me->GetGoalEntity());
return;
}
}
}
}
else if (me->GetTask() == CCSBot::RESCUE_HOSTAGES)
{
// periodically check if we lost all our hostages
if (me->GetHostageEscortCount() == 0)
{
// lost our hostages - go get 'em
me->Idle();
return;
}
}
break;
}
}
#endif
if (me->UpdatePathMovement() != CCSBot::PROGRESSING)
{
// reached destination
switch (me->GetTask())
{
case CCSBot::PLANT_BOMB:
{
// if we are at bombsite with the bomb, plant it
if (me->IsAtBombsite() && me->IsCarryingBomb())
{
me->PlantBomb();
return;
}
break;
}
case CCSBot::DEFUSE_BOMB:
{
if (!me->IsActiveWeaponReloading())
{
// if we are near the bomb, defuse it (if we are reloading, don't try to defuse until we finish)
const Vector *bombPos = me->GetGameState()->GetBombPosition();
if (bombPos != NULL)
{
const float defuseRange = 100.0f;
Vector toBomb = *bombPos - me->pev->origin + Vector(0, 0, me->GetFeetZ());
if (toBomb.IsLengthLessThan(defuseRange))
{
me->DefuseBomb();
return;
}
}
}
break;
}
case CCSBot::MOVE_TO_LAST_KNOWN_ENEMY_POSITION:
{
CBasePlayer *victim = static_cast<CBasePlayer *>(me->GetTaskEntity());
if (victim != NULL && victim->IsAlive())
{
// if we got here and haven't re-acquired the enemy, we lost him
me->GetChatter()->Say("LostEnemy");
}
break;
}
}
// default behavior when destination is reached
me->Idle();
return;
}
}
void MoveToState::OnExit(CCSBot *me)
{
// reset to run in case we were walking near our goal position
me->Run();
me->SetDisposition(CCSBot::ENGAGE_AND_INVESTIGATE);
//me->StopAiming();
}

View File

@ -0,0 +1,57 @@
#include "bot_common.h"
// Plant the bomb.
void PlantBombState::OnEnter(CCSBot *me)
{
me->Crouch();
me->SetDisposition(CCSBot::SELF_DEFENSE);
float yaw = me->pev->v_angle.y;
Vector2D dir(BotCOS(yaw), BotSIN(yaw));
Vector down(me->pev->origin.x + 10.0f * dir.x, me->pev->origin.y + 10.0f * dir.y, me->GetFeetZ());
me->SetLookAt("Plant bomb on floor", &down, PRIORITY_HIGH);
}
// Plant the bomb.
void PlantBombState::OnUpdate(CCSBot *me)
{
CBasePlayerWeapon *gun = me->GetActiveWeapon();
bool holdingC4 = false;
if (gun != NULL)
{
if (FStrEq(STRING(gun->pev->classname), "weapon_c4"))
holdingC4 = true;
}
// if we aren't holding the C4, grab it, otherwise plant it
if (holdingC4)
me->PrimaryAttack();
else
me->SelectItem("weapon_c4");
// if we no longer have the C4, we've successfully planted
if (!me->IsCarryingBomb())
{
// move to a hiding spot and watch the bomb
me->SetTask(CCSBot::GUARD_TICKING_BOMB);
me->Hide();
}
// if we time out, it's because we slipped into a non-plantable area
const float timeout = 5.0f;
if (gpGlobals->time - me->GetStateTimestamp() > timeout)
me->Idle();
}
void PlantBombState::OnExit(CCSBot *me)
{
// equip our rifle (in case we were interrupted while holding C4)
me->EquipBestWeapon();
me->StandUp();
me->ResetStuckMonitor();
me->SetDisposition(CCSBot::ENGAGE_AND_INVESTIGATE);
me->ClearLookAt();
}

View File

@ -0,0 +1,47 @@
#include "bot_common.h"
// Face the entity and "use" it
// NOTE: This state assumes we are standing in range of the entity to be used, with no obstructions.
void UseEntityState::OnEnter(CCSBot *me)
{
;
}
void UseEntityState::OnUpdate(CCSBot *me)
{
// in the very rare situation where two or more bots "used" a hostage at the same time,
// one bot will fail and needs to time out of this state
const float useTimeout = 5.0f;
if (me->GetStateTimestamp() - gpGlobals->time > useTimeout)
{
me->Idle();
return;
}
// look at the entity
Vector pos = m_entity->pev->origin + Vector(0, 0, HumanHeight * 0.5f);
me->SetLookAt("Use entity", &pos, PRIORITY_HIGH);
// if we are looking at the entity, "use" it and exit
// if (me->IsLookingAtPosition(&pos))
{
#if 0
if (TheCSBots()->GetScenario() == CCSBotManager::SCENARIO_RESCUE_HOSTAGES
&& me->m_iTeam == CT
&& me->GetTask() == CCSBot::COLLECT_HOSTAGES)
{
// we are collecting a hostage, assume we were successful - the update check will correct us if we weren't
me->IncreaseHostageEscortCount();
}
#endif
me->UseEnvironment();
me->Idle();
}
}
void UseEntityState::OnExit(CCSBot *me)
{
me->ClearLookAt();
me->ResetStuckMonitor();
}

83
dlls/bot/steam_util.h Normal file
View File

@ -0,0 +1,83 @@
/*
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* In addition, as a special exception, the author gives permission to
* link the code of this program with the Half-Life Game Engine ("HL
* Engine") and Modified Game Libraries ("MODs") developed by Valve,
* L.L.C ("Valve"). You must obey the GNU General Public License in all
* respects for all of the code used other than the HL Engine and MODs
* from Valve. If you modify this file, you may extend this exception
* to your version of the file, but you are not obligated to do so. If
* you do not wish to do so, delete this exception statement from your
* version.
*
*/
#ifndef STEAM_UTIL_H
#define STEAM_UTIL_H
#ifdef _WIN32
#pragma once
#endif
class SteamFile
{
public:
SteamFile(const char *filename);
~SteamFile();
bool IsValid() const { return (m_fileData) ? true : false; }
bool Read(void *data, int length);
private:
byte *m_fileData;
int m_fileDataLength;
byte *m_cursor;
int m_bytesLeft;
};
/* <4eb7b4> ../game_shared/steam_util.h:29 */
inline SteamFile::SteamFile(const char *filename)
{
m_fileData = (byte *)LOAD_FILE_FOR_ME(const_cast<char *>(filename), &m_fileDataLength);
m_cursor = m_fileData;
m_bytesLeft = m_fileDataLength;
}
/* <4eb65d> ../game_shared/steam_util.h:36 */
inline SteamFile::~SteamFile()
{
if (m_fileData)
FREE_FILE(m_fileData);
}
/* <4bfdfa> ../game_shared/steam_util.h:42 */
inline bool SteamFile::Read(void *data, int length)
{
if (length > m_bytesLeft || m_cursor == NULL || m_bytesLeft <= 0)
return false;
byte *readCursor = static_cast<byte *>(data);
for (int i = 0; i < length; ++i)
{
*readCursor++ = *m_cursor++;
--m_bytesLeft;
}
return true;
}
#endif // STEAM_UTIL_H

931
dlls/bot/utllinkedlist.h Normal file
View File

@ -0,0 +1,931 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Linked list container class
//
// $Revision: $
// $NoKeywords: $
//=============================================================================//
#ifndef UTLLINKEDLIST_H
#define UTLLINKEDLIST_H
#ifdef _WIN32
#pragma once
#endif
#include "utlmemory.h"
// define to enable asserts griping about things you shouldn't be doing with multilists
// #define MULTILIST_PEDANTIC_ASSERTS 1
// This is a useful macro to iterate from head to tail in a linked list.
#define FOR_EACH_LL( listName, iteratorName ) \
for( int iteratorName=(listName).Head(); (listName).IsUtlLinkedList && iteratorName != (listName).InvalidIndex(); iteratorName = (listName).Next( iteratorName ) )
//-----------------------------------------------------------------------------
// class CUtlLinkedList:
// description:
// A lovely index-based linked list! T is the class type, I is the index
// type, which usually should be an unsigned short or smaller. However,
// you must avoid using 16- or 8-bit arithmetic on PowerPC architectures;
// therefore you should not use UtlLinkedListElem_t::I as the type of
// a local variable... ever. PowerPC integer arithmetic must be 32- or
// 64-bit only; otherwise performance plummets.
//-----------------------------------------------------------------------------
template <class T, class I>
struct UtlLinkedListElem_t
{
T m_Element;
I m_Previous;
I m_Next;
private:
// No copy constructor for these...
UtlLinkedListElem_t( const UtlLinkedListElem_t& );
};
// Class S is the storage type; the type you can use to save off indices in
// persistent memory. Class I is the iterator type, which is what should be used
// in local scopes. I defaults to be S, but be aware that on the 360, 16-bit
// arithmetic is catastrophically slow. Therefore you should try to save shorts
// in memory, but always operate on 32's or 64's in local scope.
// The ideal parameter order would be TSMI (you are more likely to override M than I)
// but since M depends on I we can't have the defaults in that order, alas.
template <class T, class S = unsigned short, bool ML = false, class I = S, class M = CUtlMemory< UtlLinkedListElem_t<T, S>, I > >
class CUtlLinkedList
{
public:
typedef T ElemType_t;
typedef S IndexType_t; // should really be called IndexStorageType_t, but that would be a huge change
typedef I IndexLocalType_t;
typedef M MemoryAllocator_t;
static const bool IsUtlLinkedList = true; // Used to match this at compiletime
// constructor, destructor
CUtlLinkedList( int growSize = 0, int initSize = 0 );
~CUtlLinkedList();
// gets particular elements
T& Element( I i );
T const& Element( I i ) const;
T& operator[]( I i );
T const& operator[]( I i ) const;
// Make sure we have a particular amount of memory
void EnsureCapacity( int num );
void SetGrowSize( int growSize );
// Memory deallocation
void Purge();
// Delete all the elements then call Purge.
void PurgeAndDeleteElements();
// Insertion methods....
I InsertBefore( I before );
I InsertAfter( I after );
I AddToHead( );
I AddToTail( );
I InsertBefore( I before, T const& src );
I InsertAfter( I after, T const& src );
I AddToHead( T const& src );
I AddToTail( T const& src );
// Find an element and return its index or InvalidIndex() if it couldn't be found.
I Find( const T &src ) const;
// Look for the element. If it exists, remove it and return true. Otherwise, return false.
bool FindAndRemove( const T &src );
// Removal methods
void Remove( I elem );
void RemoveAll();
// Allocation/deallocation methods
// If multilist == true, then list list may contain many
// non-connected lists, and IsInList and Head + Tail are meaningless...
I Alloc( bool multilist = false );
void Free( I elem );
// list modification
void LinkBefore( I before, I elem );
void LinkAfter( I after, I elem );
void Unlink( I elem );
void LinkToHead( I elem );
void LinkToTail( I elem );
// invalid index (M will never allocate an element at this index)
inline static S InvalidIndex() { return ( S )M::InvalidIndex(); }
// Is a given index valid to use? (representible by S and not the invalid index)
static bool IndexInRange( I index );
inline static size_t ElementSize() { return sizeof( ListElem_t ); }
// list statistics
int Count() const;
I MaxElementIndex() const;
I NumAllocated( void ) const { return m_NumAlloced; }
// Traversing the list
I Head() const;
I Tail() const;
I Previous( I i ) const;
I Next( I i ) const;
// STL compatible const_iterator class
template < typename List_t >
class _CUtlLinkedList_constiterator_t
{
public:
typedef typename List_t::ElemType_t ElemType_t;
typedef typename List_t::IndexType_t IndexType_t;
// Default constructor -- gives a currently unusable iterator.
_CUtlLinkedList_constiterator_t()
: m_list( 0 )
, m_index( List_t::InvalidIndex() )
{
}
// Normal constructor.
_CUtlLinkedList_constiterator_t( const List_t& list, IndexType_t index )
: m_list( &list )
, m_index( index )
{
}
// Pre-increment operator++. This is the most efficient increment
// operator so it should always be used.
_CUtlLinkedList_constiterator_t& operator++()
{
m_index = m_list->Next( m_index );
return *this;
}
// Post-increment operator++. This is less efficient than pre-increment.
_CUtlLinkedList_constiterator_t operator++(int)
{
// Copy ourselves.
_CUtlLinkedList_constiterator_t temp = *this;
// Increment ourselves.
++*this;
// Return the copy.
return temp;
}
// Pre-decrement operator--. This is the most efficient decrement
// operator so it should always be used.
_CUtlLinkedList_constiterator_t& operator--()
{
Assert( m_index != m_list->Head() );
if ( m_index == m_list->InvalidIndex() )
{
m_index = m_list->Tail();
}
else
{
m_index = m_list->Previous( m_index );
}
return *this;
}
// Post-decrement operator--. This is less efficient than post-decrement.
_CUtlLinkedList_constiterator_t operator--(int)
{
// Copy ourselves.
_CUtlLinkedList_constiterator_t temp = *this;
// Decrement ourselves.
--*this;
// Return the copy.
return temp;
}
bool operator==( const _CUtlLinkedList_constiterator_t& other) const
{
Assert( m_list == other.m_list );
return m_index == other.m_index;
}
bool operator!=( const _CUtlLinkedList_constiterator_t& other) const
{
Assert( m_list == other.m_list );
return m_index != other.m_index;
}
const ElemType_t& operator*() const
{
return m_list->Element( m_index );
}
const ElemType_t* operator->() const
{
return (&**this);
}
protected:
// Use a pointer rather than a reference so that we can support
// assignment of iterators.
const List_t* m_list;
IndexType_t m_index;
};
// Are nodes in the list or valid?
bool IsValidIndex( I i ) const;
bool IsInList( I i ) const;
protected:
// What the linked list element looks like
typedef UtlLinkedListElem_t<T, S> ListElem_t;
// constructs the class
I AllocInternal( bool multilist = false );
void ConstructList();
// Gets at the list element....
ListElem_t& InternalElement( I i ) { return m_Memory[i]; }
ListElem_t const& InternalElement( I i ) const { return m_Memory[i]; }
// copy constructors not allowed
CUtlLinkedList( CUtlLinkedList<T, S, ML, I, M> const& list ) { Assert(0); }
M m_Memory;
I m_Head;
I m_Tail;
I m_FirstFree;
I m_ElementCount; // The number actually in the list
I m_NumAlloced; // The number of allocated elements
typename M::Iterator_t m_LastAlloc; // the last index allocated
// For debugging purposes;
// it's in release builds so this can be used in libraries correctly
ListElem_t *m_pElements;
inline M const &Memory( void ) const
{
return m_Memory;
}
void ResetDbgInfo()
{
m_pElements = m_Memory.Base();
}
private:
// Faster version of Next that can only be used from tested code internal
// to this class, such as Find(). It avoids the cost of checking the index
// validity, which is a big win on debug builds.
I PrivateNext( I i ) const;
};
//-----------------------------------------------------------------------------
// constructor, destructor
//-----------------------------------------------------------------------------
template <class T, class S, bool ML, class I, class M>
CUtlLinkedList<T,S,ML,I,M>::CUtlLinkedList( int growSize, int initSize ) :
m_Memory( growSize, initSize ), m_LastAlloc( m_Memory.InvalidIterator() )
{
// Prevent signed non-int datatypes
ConstructList();
ResetDbgInfo();
}
template <class T, class S, bool ML, class I, class M>
CUtlLinkedList<T,S,ML,I,M>::~CUtlLinkedList( )
{
RemoveAll();
}
template <class T, class S, bool ML, class I, class M>
void CUtlLinkedList<T,S,ML,I,M>::ConstructList()
{
m_Head = InvalidIndex();
m_Tail = InvalidIndex();
m_FirstFree = InvalidIndex();
m_ElementCount = 0;
m_NumAlloced = 0;
}
//-----------------------------------------------------------------------------
// gets particular elements
//-----------------------------------------------------------------------------
template <class T, class S, bool ML, class I, class M>
inline T& CUtlLinkedList<T,S,ML,I,M>::Element( I i )
{
return m_Memory[i].m_Element;
}
template <class T, class S, bool ML, class I, class M>
inline T const& CUtlLinkedList<T,S,ML,I,M>::Element( I i ) const
{
return m_Memory[i].m_Element;
}
template <class T, class S, bool ML, class I, class M>
inline T& CUtlLinkedList<T,S,ML,I,M>::operator[]( I i )
{
return m_Memory[i].m_Element;
}
template <class T, class S, bool ML, class I, class M>
inline T const& CUtlLinkedList<T,S,ML,I,M>::operator[]( I i ) const
{
return m_Memory[i].m_Element;
}
//-----------------------------------------------------------------------------
// list statistics
//-----------------------------------------------------------------------------
template <class T, class S, bool ML, class I, class M>
inline int CUtlLinkedList<T,S,ML,I,M>::Count() const
{
#ifdef MULTILIST_PEDANTIC_ASSERTS
AssertMsg( !ML, "CUtlLinkedList::Count() is meaningless for linked lists." );
#endif
return m_ElementCount;
}
template <class T, class S, bool ML, class I, class M>
inline I CUtlLinkedList<T,S,ML,I,M>::MaxElementIndex() const
{
return m_Memory.NumAllocated();
}
//-----------------------------------------------------------------------------
// Traversing the list
//-----------------------------------------------------------------------------
template <class T, class S, bool ML, class I, class M>
inline I CUtlLinkedList<T,S,ML,I,M>::Head() const
{
return m_Head;
}
template <class T, class S, bool ML, class I, class M>
inline I CUtlLinkedList<T,S,ML,I,M>::Tail() const
{
return m_Tail;
}
template <class T, class S, bool ML, class I, class M>
inline I CUtlLinkedList<T,S,ML,I,M>::Previous( I i ) const
{
Assert( IsValidIndex(i) );
return InternalElement(i).m_Previous;
}
template <class T, class S, bool ML, class I, class M>
inline I CUtlLinkedList<T,S,ML,I,M>::Next( I i ) const
{
Assert( IsValidIndex(i) );
return InternalElement(i).m_Next;
}
template <class T, class S, bool ML, class I, class M>
inline I CUtlLinkedList<T,S,ML,I,M>::PrivateNext( I i ) const
{
return InternalElement(i).m_Next;
}
//-----------------------------------------------------------------------------
// Are nodes in the list or valid?
//-----------------------------------------------------------------------------
#pragma warning(push)
#pragma warning( disable: 4310 ) // Allows "(I)(S)M::INVALID_INDEX" below
template <class T, class S, bool ML, class I, class M>
inline bool CUtlLinkedList<T,S,ML,I,M>::IndexInRange( I index ) // Static method
{
// Since S is not necessarily the type returned by M, we need to check that M returns indices
// which are representable by S. A common case is 'S === unsigned short', 'I == int', in which
// case CUtlMemory will have 'InvalidIndex == (int)-1' (which casts to 65535 in S), and will
// happily return elements at index 65535 and above.
return ( ( (S)index == index ) && ( (S)index != InvalidIndex() ) );
}
#pragma warning(pop)
template <class T, class S, bool ML, class I, class M>
inline bool CUtlLinkedList<T,S,ML,I,M>::IsValidIndex( I i ) const
{
if ( !m_Memory.IsIdxValid( i ) )
return false;
if ( m_Memory.IsIdxAfter( i, m_LastAlloc ) )
return false; // don't read values that have been allocated, but not constructed
return ( m_Memory[ i ].m_Previous != i ) || ( m_Memory[ i ].m_Next == i );
}
template <class T, class S, bool ML, class I, class M>
inline bool CUtlLinkedList<T,S,ML,I,M>::IsInList( I i ) const
{
if ( !m_Memory.IsIdxValid( i ) || m_Memory.IsIdxAfter( i, m_LastAlloc ) )
return false; // don't read values that have been allocated, but not constructed
return Previous( i ) != i;
}
/*
template <class T>
inline bool CUtlFixedLinkedList<T>::IsInList( int i ) const
{
return m_Memory.IsIdxValid( i ) && (Previous( i ) != i);
}
*/
//-----------------------------------------------------------------------------
// Makes sure we have enough memory allocated to store a requested # of elements
//-----------------------------------------------------------------------------
template< class T, class S, bool ML, class I, class M >
void CUtlLinkedList<T,S,ML,I,M>::EnsureCapacity( int num )
{
//MEM_ALLOC_CREDIT_CLASS();
m_Memory.EnsureCapacity(num);
ResetDbgInfo();
}
template< class T, class S, bool ML, class I, class M >
void CUtlLinkedList<T,S,ML,I,M>::SetGrowSize( int growSize )
{
RemoveAll();
m_Memory.Init( growSize );
ResetDbgInfo();
}
//-----------------------------------------------------------------------------
// Deallocate memory
//-----------------------------------------------------------------------------
template <class T, class S, bool ML, class I, class M>
void CUtlLinkedList<T,S,ML,I,M>::Purge()
{
RemoveAll();
m_Memory.Purge();
m_FirstFree = InvalidIndex();
m_NumAlloced = 0;
//Routing "m_LastAlloc = m_Memory.InvalidIterator();" through a local const to sidestep an internal compiler error on 360 builds
const typename M::Iterator_t scInvalidIterator = m_Memory.InvalidIterator();
m_LastAlloc = scInvalidIterator;
ResetDbgInfo();
}
template<class T, class S, bool ML, class I, class M>
void CUtlLinkedList<T,S,ML,I,M>::PurgeAndDeleteElements()
{
I iNext;
for( I i=Head(); i != InvalidIndex(); i=iNext )
{
iNext = Next(i);
delete Element(i);
}
Purge();
}
//-----------------------------------------------------------------------------
// Node allocation/deallocation
//-----------------------------------------------------------------------------
template <class T, class S, bool ML, class I, class M>
I CUtlLinkedList<T,S,ML,I,M>::AllocInternal( bool multilist )
{
Assert( !multilist || ML );
#ifdef MULTILIST_PEDANTIC_ASSERTS
Assert( multilist == ML );
#endif
I elem;
if ( m_FirstFree == InvalidIndex() )
{
Assert( m_Memory.IsValidIterator( m_LastAlloc ) || m_ElementCount == 0 );
typename M::Iterator_t it = m_Memory.IsValidIterator( m_LastAlloc ) ? m_Memory.Next( m_LastAlloc ) : m_Memory.First();
if ( !m_Memory.IsValidIterator( it ) )
{
m_Memory.Grow();
ResetDbgInfo();
it = m_Memory.IsValidIterator( m_LastAlloc ) ? m_Memory.Next( m_LastAlloc ) : m_Memory.First();
Assert( m_Memory.IsValidIterator( it ) );
if ( !m_Memory.IsValidIterator( it ) )
{
// We rarely if ever handle alloc failure. Continuing leads to corruption.
//Error( "CUtlLinkedList overflow! (exhausted memory allocator)\n" );
return InvalidIndex();
}
}
// We can overflow before the utlmemory overflows, since S != I
if ( !IndexInRange( m_Memory.GetIndex( it ) ) )
{
// We rarely if ever handle alloc failure. Continuing leads to corruption.
//Error( "CUtlLinkedList overflow! (exhausted index range)\n" );
return InvalidIndex();
}
m_LastAlloc = it;
elem = m_Memory.GetIndex( m_LastAlloc );
m_NumAlloced++;
}
else
{
elem = m_FirstFree;
m_FirstFree = InternalElement( m_FirstFree ).m_Next;
}
if ( !multilist )
{
InternalElement( elem ).m_Next = elem;
InternalElement( elem ).m_Previous = elem;
}
else
{
InternalElement( elem ).m_Next = InvalidIndex();
InternalElement( elem ).m_Previous = InvalidIndex();
}
return elem;
}
template <class T, class S, bool ML, class I, class M>
I CUtlLinkedList<T,S,ML,I,M>::Alloc( bool multilist )
{
I elem = AllocInternal( multilist );
if ( elem == InvalidIndex() )
return elem;
Construct( &Element(elem) );
return elem;
}
template <class T, class S, bool ML, class I, class M>
void CUtlLinkedList<T,S,ML,I,M>::Free( I elem )
{
Assert( IsValidIndex(elem) && IndexInRange( elem ) );
Unlink(elem);
ListElem_t &internalElem = InternalElement(elem);
Destruct( &internalElem.m_Element );
internalElem.m_Next = m_FirstFree;
m_FirstFree = elem;
}
//-----------------------------------------------------------------------------
// Insertion methods; allocates and links (uses default constructor)
//-----------------------------------------------------------------------------
template <class T, class S, bool ML, class I, class M>
I CUtlLinkedList<T,S,ML,I,M>::InsertBefore( I before )
{
// Make a new node
I newNode = AllocInternal();
if ( newNode == InvalidIndex() )
return newNode;
// Link it in
LinkBefore( before, newNode );
// Construct the data
Construct( &Element(newNode) );
return newNode;
}
template <class T, class S, bool ML, class I, class M>
I CUtlLinkedList<T,S,ML,I,M>::InsertAfter( I after )
{
// Make a new node
I newNode = AllocInternal();
if ( newNode == InvalidIndex() )
return newNode;
// Link it in
LinkAfter( after, newNode );
// Construct the data
Construct( &Element(newNode) );
return newNode;
}
template <class T, class S, bool ML, class I, class M>
inline I CUtlLinkedList<T,S,ML,I,M>::AddToHead( )
{
return InsertAfter( InvalidIndex() );
}
template <class T, class S, bool ML, class I, class M>
inline I CUtlLinkedList<T,S,ML,I,M>::AddToTail( )
{
return InsertBefore( InvalidIndex() );
}
//-----------------------------------------------------------------------------
// Insertion methods; allocates and links (uses copy constructor)
//-----------------------------------------------------------------------------
template <class T, class S, bool ML, class I, class M>
I CUtlLinkedList<T,S,ML,I,M>::InsertBefore( I before, T const& src )
{
// Make a new node
I newNode = AllocInternal();
if ( newNode == InvalidIndex() )
return newNode;
// Link it in
LinkBefore( before, newNode );
// Construct the data
CopyConstruct( &Element(newNode), src );
return newNode;
}
template <class T, class S, bool ML, class I, class M>
I CUtlLinkedList<T,S,ML,I,M>::InsertAfter( I after, T const& src )
{
// Make a new node
I newNode = AllocInternal();
if ( newNode == InvalidIndex() )
return newNode;
// Link it in
LinkAfter( after, newNode );
// Construct the data
CopyConstruct( &Element(newNode), src );
return newNode;
}
template <class T, class S, bool ML, class I, class M>
inline I CUtlLinkedList<T,S,ML,I,M>::AddToHead( T const& src )
{
return InsertAfter( InvalidIndex(), src );
}
template <class T, class S, bool ML, class I, class M>
inline I CUtlLinkedList<T,S,ML,I,M>::AddToTail( T const& src )
{
return InsertBefore( InvalidIndex(), src );
}
//-----------------------------------------------------------------------------
// Removal methods
//-----------------------------------------------------------------------------
template<class T, class S, bool ML, class I, class M>
I CUtlLinkedList<T,S,ML,I,M>::Find( const T &src ) const
{
// Cache the invalidIndex to avoid two levels of function calls on each iteration.
I invalidIndex = InvalidIndex();
for ( I i=Head(); i != invalidIndex; i = PrivateNext( i ) )
{
if ( Element( i ) == src )
return i;
}
return InvalidIndex();
}
template<class T, class S, bool ML, class I, class M>
bool CUtlLinkedList<T,S,ML,I,M>::FindAndRemove( const T &src )
{
I i = Find( src );
if ( i == InvalidIndex() )
{
return false;
}
else
{
Remove( i );
return true;
}
}
template <class T, class S, bool ML, class I, class M>
void CUtlLinkedList<T,S,ML,I,M>::Remove( I elem )
{
Free( elem );
}
template <class T, class S, bool ML, class I, class M>
void CUtlLinkedList<T,S,ML,I,M>::RemoveAll()
{
// Have to do some convoluted stuff to invoke the destructor on all
// valid elements for the multilist case (since we don't have all elements
// connected to each other in a list).
if ( m_LastAlloc == m_Memory.InvalidIterator() )
{
Assert( m_Head == InvalidIndex() );
Assert( m_Tail == InvalidIndex() );
Assert( m_FirstFree == InvalidIndex() );
Assert( m_ElementCount == 0 );
return;
}
if ( ML )
{
for ( typename M::Iterator_t it = m_Memory.First(); it != m_Memory.InvalidIterator(); it = m_Memory.Next( it ) )
{
I i = m_Memory.GetIndex( it );
if ( IsValidIndex( i ) ) // skip elements already in the free list
{
ListElem_t &internalElem = InternalElement( i );
Destruct( &internalElem.m_Element );
internalElem.m_Previous = i;
internalElem.m_Next = m_FirstFree;
m_FirstFree = i;
}
if ( it == m_LastAlloc )
break; // don't destruct elements that haven't ever been constructed
}
}
else
{
I i = Head();
I next;
while ( i != InvalidIndex() )
{
next = Next( i );
ListElem_t &internalElem = InternalElement( i );
Destruct( &internalElem.m_Element );
internalElem.m_Previous = i;
internalElem.m_Next = next == InvalidIndex() ? m_FirstFree : next;
i = next;
}
if ( Head() != InvalidIndex() )
{
m_FirstFree = Head();
}
}
// Clear everything else out
m_Head = InvalidIndex();
m_Tail = InvalidIndex();
m_ElementCount = 0;
}
//-----------------------------------------------------------------------------
// list modification
//-----------------------------------------------------------------------------
template <class T, class S, bool ML, class I, class M>
void CUtlLinkedList<T,S,ML,I,M>::LinkBefore( I before, I elem )
{
Assert( IsValidIndex(elem) );
// Unlink it if it's in the list at the moment
Unlink(elem);
ListElem_t *pNewElem = &InternalElement(elem);
// The element *after* our newly linked one is the one we linked before.
pNewElem->m_Next = before;
S newElem_mPrevious; // we need to hang on to this for the compairson against InvalidIndex()
// below; otherwise we get a a load-hit-store on pNewElem->m_Previous, even
// with RESTRICT
if (before == InvalidIndex())
{
// In this case, we're linking to the end of the list, so reset the tail
newElem_mPrevious = m_Tail;
pNewElem->m_Previous = m_Tail;
m_Tail = elem;
}
else
{
// Here, we're not linking to the end. Set the prev pointer to point to
// the element we're linking.
Assert( IsInList(before) );
ListElem_t *beforeElem = &InternalElement(before);
pNewElem->m_Previous = newElem_mPrevious = beforeElem->m_Previous;
beforeElem->m_Previous = elem;
}
// Reset the head if we linked to the head of the list
if (newElem_mPrevious == InvalidIndex())
m_Head = elem;
else
InternalElement(newElem_mPrevious).m_Next = elem;
// one more element baby
++m_ElementCount;
}
template <class T, class S, bool ML, class I, class M>
void CUtlLinkedList<T,S,ML,I,M>::LinkAfter( I after, I elem )
{
Assert( IsValidIndex(elem) );
// Unlink it if it's in the list at the moment
if ( IsInList(elem) )
Unlink(elem);
ListElem_t& newElem = InternalElement(elem);
// The element *before* our newly linked one is the one we linked after
newElem.m_Previous = after;
if (after == InvalidIndex())
{
// In this case, we're linking to the head of the list, reset the head
newElem.m_Next = m_Head;
m_Head = elem;
}
else
{
// Here, we're not linking to the end. Set the next pointer to point to
// the element we're linking.
Assert( IsInList(after) );
ListElem_t& afterElem = InternalElement(after);
newElem.m_Next = afterElem.m_Next;
afterElem.m_Next = elem;
}
// Reset the tail if we linked to the tail of the list
if (newElem.m_Next == InvalidIndex())
m_Tail = elem;
else
InternalElement(newElem.m_Next).m_Previous = elem;
// one more element baby
++m_ElementCount;
}
template <class T, class S, bool ML, class I, class M>
void CUtlLinkedList<T,S,ML,I,M>::Unlink( I elem )
{
Assert( IsValidIndex(elem) );
if (IsInList(elem))
{
ListElem_t *pOldElem = &m_Memory[ elem ];
// If we're the first guy, reset the head
// otherwise, make our previous node's next pointer = our next
if ( pOldElem->m_Previous != InvalidIndex() )
{
m_Memory[ pOldElem->m_Previous ].m_Next = pOldElem->m_Next;
}
else
{
m_Head = pOldElem->m_Next;
}
// If we're the last guy, reset the tail
// otherwise, make our next node's prev pointer = our prev
if ( pOldElem->m_Next != InvalidIndex() )
{
m_Memory[ pOldElem->m_Next ].m_Previous = pOldElem->m_Previous;
}
else
{
m_Tail = pOldElem->m_Previous;
}
// This marks this node as not in the list,
// but not in the free list either
pOldElem->m_Previous = pOldElem->m_Next = elem;
// One less puppy
--m_ElementCount;
}
}
template <class T, class S, bool ML, class I, class M>
inline void CUtlLinkedList<T,S,ML,I,M>::LinkToHead( I elem )
{
LinkAfter( InvalidIndex(), elem );
}
template <class T, class S, bool ML, class I, class M>
inline void CUtlLinkedList<T,S,ML,I,M>::LinkToTail( I elem )
{
LinkBefore( InvalidIndex(), elem );
}
//-----------------------------------------------------------------------------
#endif // UTLLINKEDLIST_H

594
dlls/bot/utlmemory.h Normal file
View File

@ -0,0 +1,594 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
// A growable memory class.
//===========================================================================//
#ifndef UTLMEMORY_H
#define UTLMEMORY_H
#ifdef _WIN32
#pragma once
#endif
#include "osconfig.h"
#include <new>
#include <string.h>
#pragma warning (disable:4100)
#pragma warning (disable:4514)
#define Assert(expr)
//-----------------------------------------------------------------------------
#ifdef UTLMEMORY_TRACK
#define UTLMEMORY_TRACK_ALLOC() MemAlloc_RegisterAllocation( "Sum of all UtlMemory", 0, m_nAllocationCount * sizeof(T), m_nAllocationCount * sizeof(T), 0 )
#define UTLMEMORY_TRACK_FREE() if ( !m_pMemory ) ; else MemAlloc_RegisterDeallocation( "Sum of all UtlMemory", 0, m_nAllocationCount * sizeof(T), m_nAllocationCount * sizeof(T), 0 )
#else
#define UTLMEMORY_TRACK_ALLOC() ((void)0)
#define UTLMEMORY_TRACK_FREE() ((void)0)
#endif
template <class T>
inline void Construct (T *pMemory)
{
::new(pMemory) T;
}
template <class T>
inline void CopyConstruct (T *pMemory, T const& src)
{
::new(pMemory) T (src);
}
template <class T>
inline void Destruct (T *pMemory)
{
pMemory->~T ();
#ifdef _DEBUG
memset (pMemory, 0xDD, sizeof (T));
#endif
}
//-----------------------------------------------------------------------------
// The CUtlMemory class:
// A growable memory class which doubles in size by default.
//-----------------------------------------------------------------------------
template< class T, class I = int >
class CUtlMemory
{
public:
// constructor, destructor
CUtlMemory (int nGrowSize = 0, int nInitSize = 0);
CUtlMemory (T* pMemory, int numElements);
CUtlMemory (const T* pMemory, int numElements);
~CUtlMemory ();
// Set the size by which the memory grows
void Init (int nGrowSize = 0, int nInitSize = 0);
class Iterator_t
{
public:
Iterator_t (I i) : index (i) {}
I index;
bool operator==(const Iterator_t it) const { return index == it.index; }
bool operator!=(const Iterator_t it) const { return index != it.index; }
};
Iterator_t First () const { return Iterator_t (IsIdxValid (0) ? 0 : InvalidIndex ()); }
Iterator_t Next (const Iterator_t &it) const { return Iterator_t (IsIdxValid (it.index + 1) ? it.index + 1 : InvalidIndex ()); }
I GetIndex (const Iterator_t &it) const { return it.index; }
bool IsIdxAfter (I i, const Iterator_t &it) const { return i > it.index; }
bool IsValidIterator (const Iterator_t &it) const { return IsIdxValid (it.index); }
Iterator_t InvalidIterator () const { return Iterator_t (InvalidIndex ()); }
// element access
T& operator[](I i);
const T& operator[](I i) const;
T& Element (I i);
const T& Element (I i) const;
// Can we use this index?
bool IsIdxValid (I i) const;
// Specify the invalid ('null') index that we'll only return on failure
static const I INVALID_INDEX = (I)-1; // For use with COMPILE_TIME_ASSERT
static I InvalidIndex () { return INVALID_INDEX; }
// Gets the base address (can change when adding elements!)
T* Base ();
const T* Base () const;
// Attaches the buffer to external memory....
void SetExternalBuffer (T* pMemory, int numElements);
void SetExternalBuffer (const T* pMemory, int numElements);
// Takes ownership of the passed memory, including freeing it when this buffer is destroyed.
void AssumeMemory (T *pMemory, int nSize);
// Switches the buffer from an external memory buffer to a reallocatable buffer
// Will copy the current contents of the external buffer to the reallocatable buffer
void ConvertToGrowableMemory (int nGrowSize);
// Size
int NumAllocated () const;
int Count () const;
// Grows the memory, so that at least allocated + num elements are allocated
void Grow (int num = 1);
// Makes sure we've got at least this much memory
void EnsureCapacity (int num);
// Memory deallocation
void Purge ();
// Purge all but the given number of elements
void Purge (int numElements);
// is the memory externally allocated?
bool IsExternallyAllocated () const;
// is the memory read only?
bool IsReadOnly () const;
// Set the size by which the memory grows
void SetGrowSize (int size);
protected:
void ValidateGrowSize ()
{
}
enum
{
EXTERNAL_BUFFER_MARKER = -1,
EXTERNAL_CONST_BUFFER_MARKER = -2,
};
T* m_pMemory;
int m_nAllocationCount;
int m_nGrowSize;
};
//-----------------------------------------------------------------------------
// constructor, destructor
//-----------------------------------------------------------------------------
template< class T, class I >
CUtlMemory<T, I>::CUtlMemory (int nGrowSize, int nInitAllocationCount) : m_pMemory (0),
m_nAllocationCount (nInitAllocationCount), m_nGrowSize (nGrowSize)
{
ValidateGrowSize ();
Assert (nGrowSize >= 0);
if (m_nAllocationCount)
{
UTLMEMORY_TRACK_ALLOC ();
m_pMemory = (T*)malloc (m_nAllocationCount * sizeof (T));
}
}
template< class T, class I >
CUtlMemory<T, I>::CUtlMemory (T* pMemory, int numElements) : m_pMemory (pMemory),
m_nAllocationCount (numElements)
{
// Special marker indicating externally supplied modifyable memory
m_nGrowSize = EXTERNAL_BUFFER_MARKER;
}
template< class T, class I >
CUtlMemory<T, I>::CUtlMemory (const T* pMemory, int numElements) : m_pMemory ((T*)pMemory),
m_nAllocationCount (numElements)
{
// Special marker indicating externally supplied modifyable memory
m_nGrowSize = EXTERNAL_CONST_BUFFER_MARKER;
}
template< class T, class I >
CUtlMemory<T, I>::~CUtlMemory ()
{
Purge ();
}
template< class T, class I >
void CUtlMemory<T, I>::Init (int nGrowSize /*= 0*/, int nInitSize /*= 0*/)
{
Purge ();
m_nGrowSize = nGrowSize;
m_nAllocationCount = nInitSize;
ValidateGrowSize ();
Assert (nGrowSize >= 0);
if (m_nAllocationCount)
{
UTLMEMORY_TRACK_ALLOC ();
m_pMemory = (T*)malloc (m_nAllocationCount * sizeof (T));
}
}
//-----------------------------------------------------------------------------
// Switches the buffer from an external memory buffer to a reallocatable buffer
//-----------------------------------------------------------------------------
template< class T, class I >
void CUtlMemory<T, I>::ConvertToGrowableMemory (int nGrowSize)
{
if (!IsExternallyAllocated ())
return;
m_nGrowSize = nGrowSize;
if (m_nAllocationCount)
{
UTLMEMORY_TRACK_ALLOC ();
//MEM_ALLOC_CREDIT_CLASS ();
int nNumBytes = m_nAllocationCount * sizeof (T);
T *pMemory = (T*)malloc (nNumBytes);
memcpy ((void*)pMemory, (void*)m_pMemory, nNumBytes);
m_pMemory = pMemory;
}
else
{
m_pMemory = NULL;
}
}
//-----------------------------------------------------------------------------
// Attaches the buffer to external memory....
//-----------------------------------------------------------------------------
template< class T, class I >
void CUtlMemory<T, I>::SetExternalBuffer (T* pMemory, int numElements)
{
// Blow away any existing allocated memory
Purge ();
m_pMemory = pMemory;
m_nAllocationCount = numElements;
// Indicate that we don't own the memory
m_nGrowSize = EXTERNAL_BUFFER_MARKER;
}
template< class T, class I >
void CUtlMemory<T, I>::SetExternalBuffer (const T* pMemory, int numElements)
{
// Blow away any existing allocated memory
Purge ();
m_pMemory = const_cast<T*>(pMemory);
m_nAllocationCount = numElements;
// Indicate that we don't own the memory
m_nGrowSize = EXTERNAL_CONST_BUFFER_MARKER;
}
template< class T, class I >
void CUtlMemory<T, I>::AssumeMemory (T* pMemory, int numElements)
{
// Blow away any existing allocated memory
Purge ();
// Simply take the pointer but don't mark us as external
m_pMemory = pMemory;
m_nAllocationCount = numElements;
}
//-----------------------------------------------------------------------------
// element access
//-----------------------------------------------------------------------------
template< class T, class I >
inline T& CUtlMemory<T, I>::operator[](I i)
{
// Avoid function calls in the asserts to improve debug build performance
Assert (m_nGrowSize != EXTERNAL_CONST_BUFFER_MARKER); //Assert( !IsReadOnly() );
Assert ((uint32)i < (uint32)m_nAllocationCount);
return m_pMemory[(uint32)i];
}
template< class T, class I >
inline const T& CUtlMemory<T, I>::operator[](I i) const
{
// Avoid function calls in the asserts to improve debug build performance
Assert ((uint32)i < (uint32)m_nAllocationCount);
return m_pMemory[(uint32)i];
}
template< class T, class I >
inline T& CUtlMemory<T, I>::Element (I i)
{
// Avoid function calls in the asserts to improve debug build performance
Assert (m_nGrowSize != EXTERNAL_CONST_BUFFER_MARKER); //Assert( !IsReadOnly() );
Assert ((uint32)i < (uint32)m_nAllocationCount);
return m_pMemory[(uint32)i];
}
template< class T, class I >
inline const T& CUtlMemory<T, I>::Element (I i) const
{
// Avoid function calls in the asserts to improve debug build performance
Assert ((uint32)i < (uint32)m_nAllocationCount);
return m_pMemory[(uint32)i];
}
//-----------------------------------------------------------------------------
// is the memory externally allocated?
//-----------------------------------------------------------------------------
template< class T, class I >
bool CUtlMemory<T, I>::IsExternallyAllocated () const
{
return (m_nGrowSize < 0);
}
//-----------------------------------------------------------------------------
// is the memory read only?
//-----------------------------------------------------------------------------
template< class T, class I >
bool CUtlMemory<T, I>::IsReadOnly () const
{
return (m_nGrowSize == EXTERNAL_CONST_BUFFER_MARKER);
}
template< class T, class I >
void CUtlMemory<T, I>::SetGrowSize (int nSize)
{
Assert (!IsExternallyAllocated ());
Assert (nSize >= 0);
m_nGrowSize = nSize;
ValidateGrowSize ();
}
//-----------------------------------------------------------------------------
// Gets the base address (can change when adding elements!)
//-----------------------------------------------------------------------------
template< class T, class I >
inline T* CUtlMemory<T, I>::Base ()
{
Assert (!IsReadOnly ());
return m_pMemory;
}
template< class T, class I >
inline const T *CUtlMemory<T, I>::Base () const
{
return m_pMemory;
}
//-----------------------------------------------------------------------------
// Size
//-----------------------------------------------------------------------------
template< class T, class I >
inline int CUtlMemory<T, I>::NumAllocated () const
{
return m_nAllocationCount;
}
template< class T, class I >
inline int CUtlMemory<T, I>::Count () const
{
return m_nAllocationCount;
}
//-----------------------------------------------------------------------------
// Is element index valid?
//-----------------------------------------------------------------------------
template< class T, class I >
inline bool CUtlMemory<T, I>::IsIdxValid (I i) const
{
// If we always cast 'i' and 'm_nAllocationCount' to unsigned then we can
// do our range checking with a single comparison instead of two. This gives
// a modest speedup in debug builds.
return (uint32)i < (uint32)m_nAllocationCount;
}
//-----------------------------------------------------------------------------
// Grows the memory
//-----------------------------------------------------------------------------
inline int UtlMemory_CalcNewAllocationCount (int nAllocationCount, int nGrowSize, int nNewSize, int nBytesItem)
{
if (nGrowSize)
{
nAllocationCount = ((1 + ((nNewSize - 1) / nGrowSize)) * nGrowSize);
}
else
{
if (!nAllocationCount)
{
// Compute an allocation which is at least as big as a cache line...
nAllocationCount = (31 + nBytesItem) / nBytesItem;
}
while (nAllocationCount < nNewSize)
{
#ifndef _X360
nAllocationCount *= 2;
#else
int nNewAllocationCount = (nAllocationCount * 9) / 8; // 12.5 %
if (nNewAllocationCount > nAllocationCount)
nAllocationCount = nNewAllocationCount;
else
nAllocationCount *= 2;
#endif
}
}
return nAllocationCount;
}
template< class T, class I >
void CUtlMemory<T, I>::Grow (int num)
{
Assert (num > 0);
if (IsExternallyAllocated ())
{
// Can't grow a buffer whose memory was externally allocated
Assert (0);
return;
}
// Make sure we have at least numallocated + num allocations.
// Use the grow rules specified for this memory (in m_nGrowSize)
int nAllocationRequested = m_nAllocationCount + num;
UTLMEMORY_TRACK_FREE ();
int nNewAllocationCount = UtlMemory_CalcNewAllocationCount (m_nAllocationCount, m_nGrowSize, nAllocationRequested, sizeof (T));
// if m_nAllocationRequested wraps index type I, recalculate
if ((int)(I)nNewAllocationCount < nAllocationRequested)
{
if ((int)(I)nNewAllocationCount == 0 && (int)(I)(nNewAllocationCount - 1) >= nAllocationRequested)
{
--nNewAllocationCount; // deal w/ the common case of m_nAllocationCount == MAX_USHORT + 1
}
else
{
if ((int)(I)nAllocationRequested != nAllocationRequested)
{
// we've been asked to grow memory to a size s.t. the index type can't address the requested amount of memory
Assert (0);
return;
}
while ((int)(I)nNewAllocationCount < nAllocationRequested)
{
nNewAllocationCount = (nNewAllocationCount + nAllocationRequested) / 2;
}
}
}
m_nAllocationCount = nNewAllocationCount;
UTLMEMORY_TRACK_ALLOC ();
if (m_pMemory)
{
m_pMemory = (T*)realloc (m_pMemory, m_nAllocationCount * sizeof (T));
Assert (m_pMemory);
}
else
{
m_pMemory = (T*)malloc (m_nAllocationCount * sizeof (T));
Assert (m_pMemory);
}
}
//-----------------------------------------------------------------------------
// Makes sure we've got at least this much memory
//-----------------------------------------------------------------------------
template< class T, class I >
inline void CUtlMemory<T, I>::EnsureCapacity (int num)
{
if (m_nAllocationCount >= num)
return;
if (IsExternallyAllocated ())
{
// Can't grow a buffer whose memory was externally allocated
Assert (0);
return;
}
UTLMEMORY_TRACK_FREE ();
m_nAllocationCount = num;
UTLMEMORY_TRACK_ALLOC ();
if (m_pMemory)
{
//MEM_ALLOC_CREDIT_CLASS ();
m_pMemory = (T*)realloc (m_pMemory, m_nAllocationCount * sizeof (T));
}
else
{
//MEM_ALLOC_CREDIT_CLASS ();
m_pMemory = (T*)malloc (m_nAllocationCount * sizeof (T));
}
}
//-----------------------------------------------------------------------------
// Memory deallocation
//-----------------------------------------------------------------------------
template< class T, class I >
void CUtlMemory<T, I>::Purge ()
{
if (!IsExternallyAllocated ())
{
if (m_pMemory)
{
UTLMEMORY_TRACK_FREE ();
free ((void*)m_pMemory);
m_pMemory = 0;
}
m_nAllocationCount = 0;
}
}
template< class T, class I >
void CUtlMemory<T, I>::Purge (int numElements)
{
Assert (numElements >= 0);
if (numElements > m_nAllocationCount)
{
// Ensure this isn't a grow request in disguise.
Assert (numElements <= m_nAllocationCount);
return;
}
// If we have zero elements, simply do a purge:
if (numElements == 0)
{
Purge ();
return;
}
if (IsExternallyAllocated ())
{
// Can't shrink a buffer whose memory was externally allocated, fail silently like purge
return;
}
// If the number of elements is the same as the allocation count, we are done.
if (numElements == m_nAllocationCount)
{
return;
}
if (!m_pMemory)
{
// Allocation count is non zero, but memory is null.
Assert (m_pMemory);
return;
}
UTLMEMORY_TRACK_FREE ();
m_nAllocationCount = numElements;
UTLMEMORY_TRACK_ALLOC ();
// Allocation count > 0, shrink it down.
//MEM_ALLOC_CREDIT_CLASS ();
m_pMemory = (T*)realloc (m_pMemory, m_nAllocationCount * sizeof (T));
}
#endif // UTLMEMORY_H

565
dlls/bot/utlvector.h Normal file
View File

@ -0,0 +1,565 @@
#ifndef UTLVECTOR_H
#define UTLVECTOR_H
#ifdef _WIN32
#pragma once
#endif
#include "utlmemory.h"
template<class T>
class CUtlVector
{
public:
typedef T ElemType_t;
// constructor, destructor
CUtlVector(int growSize = 0, int initSize = 0);
CUtlVector(T* pMemory, int numElements);
~CUtlVector();
// Copy the array.
CUtlVector<T>& operator=(const CUtlVector<T> &other);
// element access
T& operator[](int i);
T const& operator[](int i) const;
T& Element(int i);
T const& Element(int i) const;
// Gets the base address (can change when adding elements!)
T* Base();
T const* Base() const;
// Returns the number of elements in the vector
// SIZE IS DEPRECATED!
int Count() const;
int Size() const; // don't use me!
// Is element index valid?
bool IsValidIndex(int i) const;
static int InvalidIndex(void);
// Adds an element, uses default constructor
int AddToHead();
int AddToTail();
int InsertBefore(int elem);
int InsertAfter(int elem);
// Adds an element, uses copy constructor
int AddToHead(T const& src);
int AddToTail(T const& src);
int InsertBefore(int elem, T const& src);
int InsertAfter(int elem, T const& src);
// Adds multiple elements, uses default constructor
int AddMultipleToHead(int num);
int AddMultipleToTail(int num, const T *pToCopy=NULL);
int InsertMultipleBefore(int elem, int num, const T *pToCopy=NULL); // If pToCopy is set, then it's an array of length 'num' and
int InsertMultipleAfter(int elem, int num);
// Calls RemoveAll() then AddMultipleToTail.
void SetSize(int size);
void SetCount(int count);
// Calls SetSize and copies each element.
void CopyArray(T const *pArray, int size);
// Add the specified array to the tail.
int AddVectorToTail(CUtlVector<T> const &src);
// Finds an element (element needs operator== defined)
int Find(T const& src) const;
bool HasElement(T const& src);
// Makes sure we have enough memory allocated to store a requested # of elements
void EnsureCapacity(int num);
// Makes sure we have at least this many elements
void EnsureCount(int num);
// Element removal
void FastRemove(int elem); // doesn't preserve order
void Remove(int elem); // preserves order, shifts elements
void FindAndRemove(T const& src); // removes first occurrence of src, preserves order, shifts elements
void RemoveMultiple(int elem, int num); // preserves order, shifts elements
void RemoveAll(); // doesn't deallocate memory
// Memory deallocation
void Purge();
// Purges the list and calls delete on each element in it.
void PurgeAndDeleteElements();
// Set the size by which it grows when it needs to allocate more memory.
void SetGrowSize(int size);
protected:
// Can't copy this unless we explicitly do it!
CUtlVector(CUtlVector const& vec) { assert(0); }
// Grows the vector
void GrowVector(int num = 1);
// Shifts elements....
void ShiftElementsRight(int elem, int num = 1);
void ShiftElementsLeft(int elem, int num = 1);
// For easier access to the elements through the debugger
void ResetDbgInfo();
CUtlMemory<T> m_Memory;
int m_Size;
// For easier access to the elements through the debugger
// it's in release builds so this can be used in libraries correctly
T *m_pElements;
};
//-----------------------------------------------------------------------------
// For easier access to the elements through the debugger
//-----------------------------------------------------------------------------
template< class T >
inline void CUtlVector<T>::ResetDbgInfo()
{
m_pElements = m_Memory.Base();
}
//-----------------------------------------------------------------------------
// constructor, destructor
//-----------------------------------------------------------------------------
template< class T >
inline CUtlVector<T>::CUtlVector(int growSize, int initSize) :
m_Memory(growSize, initSize), m_Size(0)
{
ResetDbgInfo();
}
template< class T >
inline CUtlVector<T>::CUtlVector(T* pMemory, int numElements) :
m_Memory(pMemory, numElements), m_Size(0)
{
ResetDbgInfo();
}
template< class T >
inline CUtlVector<T>::~CUtlVector()
{
Purge();
}
template<class T>
inline CUtlVector<T>& CUtlVector<T>::operator=(const CUtlVector<T> &other)
{
CopyArray(other.Base(), other.Count());
return *this;
}
//-----------------------------------------------------------------------------
// element access
//-----------------------------------------------------------------------------
template< class T >
inline T& CUtlVector<T>::operator[](int i)
{
assert(IsValidIndex(i));
return m_Memory[i];
}
template< class T >
inline T const& CUtlVector<T>::operator[](int i) const
{
assert(IsValidIndex(i));
return m_Memory[i];
}
template< class T >
inline T& CUtlVector<T>::Element(int i)
{
assert(IsValidIndex(i));
return m_Memory[i];
}
template< class T >
inline T const& CUtlVector<T>::Element(int i) const
{
assert(IsValidIndex(i));
return m_Memory[i];
}
//-----------------------------------------------------------------------------
// Gets the base address (can change when adding elements!)
//-----------------------------------------------------------------------------
template< class T >
inline T* CUtlVector<T>::Base()
{
return m_Memory.Base();
}
template< class T >
inline T const* CUtlVector<T>::Base() const
{
return m_Memory.Base();
}
//-----------------------------------------------------------------------------
// Count
//-----------------------------------------------------------------------------
template< class T >
inline int CUtlVector<T>::Size() const
{
return m_Size;
}
template< class T >
inline int CUtlVector<T>::Count() const
{
return m_Size;
}
//-----------------------------------------------------------------------------
// Is element index valid?
//-----------------------------------------------------------------------------
template< class T >
inline bool CUtlVector<T>::IsValidIndex(int i) const
{
return (i >= 0) && (i < m_Size);
}
//-----------------------------------------------------------------------------
// Returns in invalid index
//-----------------------------------------------------------------------------
template< class T >
inline int CUtlVector<T>::InvalidIndex(void)
{
return -1;
}
//-----------------------------------------------------------------------------
// Grows the vector
//-----------------------------------------------------------------------------
template< class T >
void CUtlVector<T>::GrowVector(int num)
{
if (m_Size + num - 1 >= m_Memory.NumAllocated())
{
m_Memory.Grow(m_Size + num - m_Memory.NumAllocated());
}
m_Size += num;
ResetDbgInfo();
}
//-----------------------------------------------------------------------------
// Makes sure we have enough memory allocated to store a requested # of elements
//-----------------------------------------------------------------------------
template< class T >
void CUtlVector<T>::EnsureCapacity(int num)
{
m_Memory.EnsureCapacity(num);
ResetDbgInfo();
}
//-----------------------------------------------------------------------------
// Makes sure we have at least this many elements
//-----------------------------------------------------------------------------
template< class T >
void CUtlVector<T>::EnsureCount(int num)
{
if (Count() < num)
AddMultipleToTail(num - Count());
}
//-----------------------------------------------------------------------------
// Shifts elements
//-----------------------------------------------------------------------------
template< class T >
void CUtlVector<T>::ShiftElementsRight(int elem, int num)
{
assert(IsValidIndex(elem) || (m_Size == 0) || (num == 0));
int numToMove = m_Size - elem - num;
if ((numToMove > 0) && (num > 0))
memmove(&Element(elem+num), &Element(elem), numToMove * sizeof(T));
}
template< class T >
void CUtlVector<T>::ShiftElementsLeft(int elem, int num)
{
assert(IsValidIndex(elem) || (m_Size == 0) || (num == 0));
int numToMove = m_Size - elem - num;
if ((numToMove > 0) && (num > 0))
{
memmove(&Element(elem), &Element(elem+num), numToMove * sizeof(T));
#ifdef _DEBUG
memset(&Element(m_Size-num), 0xDD, num * sizeof(T));
#endif
}
}
//-----------------------------------------------------------------------------
// Adds an element, uses default constructor
//-----------------------------------------------------------------------------
template< class T >
inline int CUtlVector<T>::AddToHead()
{
return InsertBefore(0);
}
template< class T >
inline int CUtlVector<T>::AddToTail()
{
return InsertBefore(m_Size);
}
template< class T >
inline int CUtlVector<T>::InsertAfter(int elem)
{
return InsertBefore(elem + 1);
}
template< class T >
int CUtlVector<T>::InsertBefore(int elem)
{
// Can insert at the end
assert((elem == Count()) || IsValidIndex(elem));
GrowVector();
ShiftElementsRight(elem);
Construct(&Element(elem));
return elem;
}
//-----------------------------------------------------------------------------
// Adds an element, uses copy constructor
//-----------------------------------------------------------------------------
template< class T >
inline int CUtlVector<T>::AddToHead(T const& src)
{
return InsertBefore(0, src);
}
template< class T >
inline int CUtlVector<T>::AddToTail(T const& src)
{
return InsertBefore(m_Size, src);
}
template< class T >
inline int CUtlVector<T>::InsertAfter(int elem, T const& src)
{
return InsertBefore(elem + 1, src);
}
template< class T >
int CUtlVector<T>::InsertBefore(int elem, T const& src)
{
// Can insert at the end
assert((elem == Count()) || IsValidIndex(elem));
GrowVector();
ShiftElementsRight(elem);
CopyConstruct(&Element(elem), src);
return elem;
}
//-----------------------------------------------------------------------------
// Adds multiple elements, uses default constructor
//-----------------------------------------------------------------------------
template< class T >
inline int CUtlVector<T>::AddMultipleToHead(int num)
{
return InsertMultipleBefore(0, num);
}
template< class T >
inline int CUtlVector<T>::AddMultipleToTail(int num, const T *pToCopy)
{
return InsertMultipleBefore(m_Size, num, pToCopy);
}
template< class T >
int CUtlVector<T>::InsertMultipleAfter(int elem, int num)
{
return InsertMultipleBefore(elem + 1, num);
}
template< class T >
void CUtlVector<T>::SetCount(int count)
{
RemoveAll();
AddMultipleToTail(count);
}
template< class T >
inline void CUtlVector<T>::SetSize(int size)
{
SetCount(size);
}
template< class T >
void CUtlVector<T>::CopyArray(T const *pArray, int size)
{
SetSize(size);
for(int i=0; i < size; i++)
(*this)[i] = pArray[i];
}
template< class T >
int CUtlVector<T>::AddVectorToTail(CUtlVector const &src)
{
int base = Count();
// Make space.
AddMultipleToTail(src.Count());
// Copy the elements.
for (int i=0; i < src.Count(); i++)
(*this)[base + i] = src[i];
return base;
}
template< class T >
inline int CUtlVector<T>::InsertMultipleBefore(int elem, int num, const T *pToInsert)
{
if(num == 0)
return elem;
// Can insert at the end
assert((elem == Count()) || IsValidIndex(elem));
GrowVector(num);
ShiftElementsRight(elem, num);
// Invoke default constructors
for (int i = 0; i < num; ++i)
Construct(&Element(elem+i));
// Copy stuff in?
if (pToInsert)
{
for (int i=0; i < num; i++)
{
Element(elem+i) = pToInsert[i];
}
}
return elem;
}
//-----------------------------------------------------------------------------
// Finds an element (element needs operator== defined)
//-----------------------------------------------------------------------------
template< class T >
int CUtlVector<T>::Find(T const& src) const
{
for (int i = 0; i < Count(); ++i)
{
if (Element(i) == src)
return i;
}
return -1;
}
template< class T >
bool CUtlVector<T>::HasElement(T const& src)
{
return (Find(src) >= 0);
}
//-----------------------------------------------------------------------------
// Element removal
//-----------------------------------------------------------------------------
template< class T >
void CUtlVector<T>::FastRemove(int elem)
{
assert(IsValidIndex(elem));
Destruct(&Element(elem));
if (m_Size > 0)
{
Q_memcpy(&Element(elem), &Element(m_Size-1), sizeof(T));
--m_Size;
}
}
template< class T >
void CUtlVector<T>::Remove(int elem)
{
Destruct(&Element(elem));
ShiftElementsLeft(elem);
--m_Size;
}
template< class T >
void CUtlVector<T>::FindAndRemove(T const& src)
{
int elem = Find(src);
if (elem != -1)
{
Remove(elem);
}
}
template< class T >
void CUtlVector<T>::RemoveMultiple(int elem, int num)
{
assert(IsValidIndex(elem));
assert(elem + num <= Count());
for (int i = elem + num; --i >= elem;)
Destruct(&Element(i));
ShiftElementsLeft(elem, num);
m_Size -= num;
}
template< class T >
void CUtlVector<T>::RemoveAll()
{
for (int i = m_Size; --i >= 0;)
Destruct(&Element(i));
m_Size = 0;
}
//-----------------------------------------------------------------------------
// Memory deallocation
//-----------------------------------------------------------------------------
template< class T >
void CUtlVector<T>::Purge()
{
RemoveAll();
m_Memory.Purge();
ResetDbgInfo();
}
template<class T>
inline void CUtlVector<T>::PurgeAndDeleteElements()
{
for (int i = 0; i < m_Size; i++)
delete Element(i);
Purge();
}
template< class T >
void CUtlVector<T>::SetGrowSize(int size)
{
m_Memory.SetGrowSize(size);
}
#endif // CCVECTOR_H

View File

@ -474,6 +474,12 @@ EHANDLE::operator CBaseEntity *()
return (CBaseEntity *)GET_PRIVATE( Get() );
}
EHANDLE::operator CBasePlayer *()
{
return (CBasePlayer *)GET_PRIVATE( Get() );
}
CBaseEntity *EHANDLE::operator = ( CBaseEntity *pEntity )
{
if( pEntity )

View File

@ -125,6 +125,7 @@ public:
operator int ();
operator CBaseEntity *();
operator CBasePlayer *();
CBaseEntity *operator = ( CBaseEntity *pEntity );
CBaseEntity *operator ->();

View File

@ -38,6 +38,7 @@
#include "weaponinfo.h"
#include "usercmd.h"
#include "netadr.h"
#include "bot_exports.h"
extern DLL_GLOBAL ULONG g_ulModelIndexPlayer;
extern DLL_GLOBAL BOOL g_fGameOver;
@ -142,6 +143,8 @@ void ClientDisconnect( edict_t *pEntity )
if( pPlayer )
pPlayer->m_state = STATE_UNINITIALIZED;
}
TheBots->ClientDisconnect((CBasePlayer*)CBaseEntity::Instance( pEntity ));
}
@ -426,6 +429,9 @@ void ClientCommand( edict_t *pEntity )
entvars_t *pev = &pEntity->v;
if (TheBots->ClientCommand(GetClassPtr((CBasePlayer *)pev), pcmd))
return;
if ( FStrEq(pcmd, "say" ) )
{
Host_Say( pEntity, 0 );
@ -658,6 +664,7 @@ void ServerDeactivate( void )
// Peform any shutdown operations here...
//
TheBots->ServerDeactivate();
}
void CoopClearData( void );
@ -715,6 +722,7 @@ void ServerActivate( edict_t *pEdictList, int edictCount, int clientMax )
}
}
}
TheBots->ServerActivate();
}
@ -812,6 +820,7 @@ void StartFrame( void )
gpGlobals->teamplay = teamplay.value;
g_ulFrameCount++;
TheBots->StartFrame();
}

View File

@ -16,6 +16,7 @@
#include "eiface.h"
#include "util.h"
#include "game.h"
#include "bot_exports.h"
cvar_t displaysoundlist = {"displaysoundlist","0"};
@ -890,5 +891,6 @@ void GameDLLInit( void )
// END REGISTER CVARS FOR SKILL LEVEL STUFF
SERVER_COMMAND( "exec skill.cfg\n" );
Bot_RegisterCvars();
}

View File

@ -30,7 +30,7 @@
#include "voice_gamemgr.h"
#endif
#include "hltv.h"
#include "bot_exports.h"
extern DLL_GLOBAL CGameRules *g_pGameRules;
extern DLL_GLOBAL BOOL g_fGameOver;
extern int gmsgDeathMsg; // client dll messages
@ -215,6 +215,7 @@ CHalfLifeMultiplay::CHalfLifeMultiplay()
SERVER_COMMAND( szCommand );
}
}
InstallBotControl();
}
void CoopProcessMenu( CBasePlayer *pPlayer, int imenu );

View File

@ -4794,4 +4794,21 @@ void CInfoIntermission::Think( void )
}
}
#define NormalizeAngle UTIL_AngleMod
bool CBasePlayer::IsLookingAtPosition(Vector *pos, float angleTolerance)
{
Vector to = *pos - EyePosition();
Vector idealAngle = UTIL_VecToAngles(to);
idealAngle.x = 360.0 - idealAngle.x;
float deltaYaw = NormalizeAngle(idealAngle.y - pev->v_angle.y);
float deltaPitch = NormalizeAngle(idealAngle.x - pev->v_angle.x);
return (abs(deltaYaw) < angleTolerance
&& abs(deltaPitch) < angleTolerance);
}
LINK_ENTITY_TO_CLASS( info_intermission, CInfoIntermission )

View File

@ -324,6 +324,9 @@ public:
PlayerState m_state;
bool m_fTouchMenu;
virtual void Touch( CBaseEntity *pOther );
bool IsBot(){ return pev->flags & FL_FAKECLIENT; }
bool IsLookingAtPosition(Vector *pos, float angleTolerance = 20.0f);
};
#define AUTOAIM_2DEGREES 0.0348994967025

View File

@ -1,109 +1,399 @@
/***
/*
*
* Copyright (c) 1996-2002, Valve LLC. All rights reserved.
*
* This product contains software technology licensed from Id
* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc.
* All Rights Reserved.
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* Use, distribution, and modification of this source code and/or resulting
* object code is restricted to non-commercial enhancements to products from
* Valve LLC. All other use, distribution, or modification is prohibited
* without written permission from Valve LLC.
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
****/
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* In addition, as a special exception, the author gives permission to
* link the code of this program with the Half-Life Game Engine ("HL
* Engine") and Modified Game Libraries ("MODs") developed by Valve,
* L.L.C ("Valve"). You must obey the GNU General Public License in all
* respects for all of the code used other than the HL Engine and MODs
* from Valve. If you modify this file, you may extend this exception
* to your version of the file, but you are not obligated to do so. If
* you do not wish to do so, delete this exception statement from your
* version.
*
*/
#ifndef VECTOR_H
#define VECTOR_H
#ifdef _WIN32
#pragma once
#endif
//=========================================================
// 2DVector - used for many pathfinding and many other
// operations that are treated as planar rather than 3d.
//=========================================================
class Vector2D
{
public:
inline Vector2D(void) { }
inline Vector2D(float X, float Y) { x = X; y = Y; }
inline Vector2D operator+(const Vector2D& v) const { return Vector2D( x + v.x, y + v.y ); }
inline Vector2D operator-(const Vector2D& v) const { return Vector2D( x - v.x, y - v.y ); }
inline Vector2D operator*(float fl) const { return Vector2D( x * fl, y * fl ); }
inline Vector2D operator/(float fl) const { return Vector2D( x / fl, y / fl ); }
inline float Length(void) const { return sqrt(x * x + y * y ); }
inline Vector2D Normalize ( void ) const
vec_t x, y;
Vector2D() : x(0.0), y(0.0) {}
Vector2D(float X, float Y) : x(0.0), y(0.0)
{
Vector2D vec2;
x = X;
y = Y;
}
Vector2D operator+(const Vector2D &v) const
{
return Vector2D(x + v.x, y + v.y);
}
Vector2D operator-(const Vector2D &v) const
{
return Vector2D(x - v.x, y - v.y);
}
Vector2D operator*(float fl) const
{
return Vector2D((vec_t)(x * fl), (vec_t)(y * fl));
}
Vector2D operator/(float fl) const
{
return Vector2D((vec_t)(x / fl), (vec_t)(y / fl));
}
Vector2D operator/=(float fl) const
{
return Vector2D((vec_t)(x / fl), (vec_t)(y / fl));
}
float Length() const
{
return sqrt((float)(x * x + y * y));
}
float LengthSquared() const
{
return (x * x + y * y);
}
operator float*()
{
return &x;
}
operator const float*() const
{
return &x;
}
Vector2D Normalize() const
{
float flLen = Length();
if( flLen == 0 )
if (!flLen)
return Vector2D(0, 0);
flLen = 1 / flLen;
return Vector2D((vec_t)(x * flLen), (vec_t)(y * flLen));
}
bool IsLengthLessThan(float length) const
{
return (LengthSquared() < length * length);
}
bool IsLengthGreaterThan(float length) const
{
return (LengthSquared() > length * length);
}
float NormalizeInPlace()
{
float flLen = Length();
if (flLen > 0.0)
{
return Vector2D( 0, 0 );
x = (vec_t)(1 / flLen * x);
y = (vec_t)(1 / flLen * y);
}
else
{
flLen = 1 / flLen;
return Vector2D( x * flLen, y * flLen );
x = 1.0;
y = 0.0;
}
return flLen;
}
bool IsZero(float tolerance = 0.01f) const
{
return (x > -tolerance && x < tolerance &&
y > -tolerance && y < tolerance);
}
vec_t x, y;
};
inline float DotProduct( const Vector2D& a, const Vector2D& b ) { return( a.x * b.x + a.y * b.y ); }
inline Vector2D operator*( float fl, const Vector2D& v ) { return v * fl; }
inline float DotProduct(const Vector2D &a, const Vector2D &b)
{
return (a.x * b.x + a.y * b.y);
}
//=========================================================
// 3D Vector
//=========================================================
class Vector // same data-layout as engine's vec3_t,
{ // which is a vec_t[3]
inline Vector2D operator*(float fl, const Vector2D &v)
{
return v * fl;
}
class Vector
{
public:
// Construction/destruction
inline Vector( void ) { }
inline Vector( float X, float Y, float Z ) { x = X; y = Y; z = Z; }
//inline Vector( double X, double Y, double Z ) { x = (float)X; y = (float)Y; z = (float)Z; }
//inline Vector( int X, int Y, int Z ) { x = (float)X; y = (float)Y; z = (float)Z; }
inline Vector( const Vector& v ) { x = v.x; y = v.y; z = v.z; }
inline Vector( float rgfl[3] ) { x = rgfl[0]; y = rgfl[1]; z = rgfl[2]; }
vec_t x, y, z;
Vector() : x(0.0), y(0.0), z(0.0) {}
Vector(float X, float Y, float Z) : x(0.0), y(0.0), z(0.0)
{
x = X;
y = Y;
z = Z;
}
Vector(const Vector &v) : x(0.0), y(0.0), z(0.0)
{
x = v.x;
y = v.y;
z = v.z;
}
Vector(const float rgfl[3]) : x(0.0), y(0.0), z(0.0)
{
x = rgfl[0];
y = rgfl[1];
z = rgfl[2];
}
Vector operator-() const
{
return Vector(-x, -y, -z);
}
int operator==(const Vector &v) const
{
return x == v.x && y == v.y && z == v.z;
}
int operator!=(const Vector &v) const
{
return !(*this == v);
}
Vector operator+(const Vector &v) const
{
return Vector(x + v.x, y + v.y, z + v.z);
}
Vector operator-(const Vector &v) const
{
return Vector(x - v.x, y - v.y, z - v.z);
}
Vector operator*(float fl) const
{
return Vector((vec_t)(x * fl), (vec_t)(y * fl), (vec_t)(z * fl));
}
Vector operator/(float fl) const
{
return Vector((vec_t)(x / fl), (vec_t)(y / fl), (vec_t)(z / fl));
}
Vector operator/=(float fl) const
{
return Vector((vec_t)(x / fl), (vec_t)(y / fl), (vec_t)(z / fl));
}
void CopyToArray(float *rgfl) const
{
rgfl[0] = x;
rgfl[1] = y;
rgfl[2] = z;
}
float Length() const
{
float x1 = (float)x;
float y1 = (float)y;
float z1 = (float)z;
// Operators
inline Vector operator-( void ) const { return Vector( -x, -y, -z ); }
inline int operator==( const Vector& v ) const { return x==v.x && y==v.y && z==v.z; }
inline int operator!=( const Vector& v ) const { return !( *this==v ); }
inline Vector operator+( const Vector& v ) const { return Vector( x + v.x, y + v.y, z + v.z ); }
inline Vector operator-( const Vector& v ) const { return Vector( x - v.x, y - v.y, z - v.z ); }
inline Vector operator*( float fl) const { return Vector( x * fl, y * fl, z * fl ); }
inline Vector operator/( float fl) const { return Vector( x / fl, y / fl, z / fl ); }
return sqrt(x1 * x1 + y1 * y1 + z1 * z1);
// Methods
inline void CopyToArray( float* rgfl ) const { rgfl[0] = x, rgfl[1] = y, rgfl[2] = z; }
inline float Length( void ) const { return sqrt( x * x + y * y + z * z ); }
operator float *() { return &x; } // Vectors will now automatically convert to float * when needed
operator const float *() const { return &x; } // Vectors will now automatically convert to float * when needed
inline Vector Normalize( void ) const
//return sqrt((float)(x * x + y * y + z * z));
}
float LengthSquared() const
{
return (x * x + y * y + z * z);
}
operator float*()
{
return &x;
}
operator const float*() const
{
return &x;
}
Vector Normalize()
{
float flLen = Length();
if( flLen == 0 ) return Vector( 0, 0, 1 ); // ????
if (flLen == 0)
return Vector(0, 0, 1);
flLen = 1 / flLen;
return Vector( x * flLen, y * flLen, z * flLen );
return Vector(x * flLen, y * flLen, z * flLen);
}
inline Vector2D Make2D( void ) const
// for out precision normalize
Vector NormalizePrecision()
{
Vector2D Vec2;
return Normalize();
}
Vector2D Make2D() const
{
Vector2D Vec2;
Vec2.x = x;
Vec2.y = y;
return Vec2;
}
inline float Length2D( void ) const { return sqrt( x * x + y * y ); }
float Length2D() const
{
return sqrt((float)(x * x + y * y));
}
bool IsLengthLessThan(float length) const
{
return (LengthSquared() < length * length);
}
bool IsLengthGreaterThan(float length) const
{
return (LengthSquared() > length * length);
}
float NormalizeInPlace()
{
float flLen = Length();
// Members
vec_t x, y, z;
if (flLen > 0)
{
x = (vec_t)(1 / flLen * x);
y = (vec_t)(1 / flLen * y);
z = (vec_t)(1 / flLen * z);
}
else
{
x = 0;
y = 0;
z = 1;
}
return flLen;
}
template<typename T>
float NormalizeInPlace()
{
T flLen = Length();
if (flLen > 0)
{
x = (vec_t)(1 / flLen * x);
y = (vec_t)(1 / flLen * y);
z = (vec_t)(1 / flLen * z);
}
else
{
x = 0;
y = 0;
z = 1;
}
return flLen;
}
bool IsZero(float tolerance = 0.01f) const
{
return (x > -tolerance && x < tolerance &&
y > -tolerance && y < tolerance &&
z > -tolerance && z < tolerance);
}
};
inline Vector operator*( float fl, const Vector& v ) { return v * fl; }
inline float DotProduct( const Vector& a, const Vector& b ) { return( a.x * b.x + a.y * b.y + a.z * b.z); }
inline Vector CrossProduct( const Vector& a, const Vector& b ) { return Vector( a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x ); }
#endif
inline Vector operator*(float fl, const Vector &v)
{
return v * fl;
}
inline float DotProduct(const Vector &a, const Vector &b)
{
return (a.x * b.x + a.y * b.y + a.z * b.z);
}
inline float DotProduct2D(const Vector &a, const Vector &b)
{
return (a.x * b.x + a.y * b.y);
}
inline Vector CrossProduct(const Vector &a, const Vector &b)
{
return Vector(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x);
}
template<class T>
inline void SWAP(T &first, T &second)
{
T temp = first;
first = second;
second = temp;
}
template<
typename X,
typename Y,
typename Z,
typename LenType
>
inline LenType LengthSubtract(Vector vecStart, Vector vecDest)
{
X floatX = (vecDest.x - vecStart.x);
Y floatY = (vecDest.y - vecStart.y);
Z floatZ = (vecDest.z - vecStart.z);
return sqrt((float)(floatX * floatX + floatY * floatY + floatZ * floatZ));
}
template<
typename X,
typename Y,
typename Z,
typename LenType
>
inline Vector NormalizeSubtract(Vector vecStart, Vector vecDest)
{
Vector dir;
X floatX = (vecDest.x - vecStart.x);
Y floatY = (vecDest.y - vecStart.y);
Z floatZ = (vecDest.z - vecStart.z);
LenType flLen = sqrt((float)(floatX * floatX + floatY * floatY + floatZ * floatZ));
if (flLen == 0.0)
{
dir = Vector(0, 0, 1);
}
else
{
flLen = 1.0 / flLen;
dir.x = (vec_t)(floatX * flLen);
dir.y = (vec_t)(floatY * flLen);
dir.z = (vec_t)(floatZ * flLen);
}
return dir;
}
template<typename X, typename Y, typename LenType>
inline Vector NormalizeMulScalar(Vector2D vec, float scalar)
{
LenType flLen;
X floatX;
Y floatY;
flLen = (LenType)vec.Length();
if (flLen <= 0.0)
{
floatX = 1;
floatY = 0;
}
else
{
flLen = 1 / flLen;
floatX = vec.x * flLen;
floatY = vec.y * flLen;
}
return Vector((vec_t)(floatX * scalar), (vec_t)(floatY * scalar), 0);
}
#endif // VECTOR_H