mirror of https://github.com/FWGS/hlsdk-xash3d
Add zbots (undone, only spawn and jump)
This commit is contained in:
parent
6ef6d0d27a
commit
8041c4fe81
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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"
|
|
@ -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, ...);
|
|
@ -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)
|
||||
|
|
@ -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", ¢er, 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;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -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
|
|
@ -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
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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(¢er, 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;
|
||||
}
|
|
@ -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;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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
|
|
@ -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;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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");
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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();
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
File diff suppressed because it is too large
Load Diff
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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)
|
||||
{
|
||||
;
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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)
|
||||
{
|
||||
;
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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 )
|
||||
|
|
|
@ -125,6 +125,7 @@ public:
|
|||
operator int ();
|
||||
|
||||
operator CBaseEntity *();
|
||||
operator CBasePlayer *();
|
||||
|
||||
CBaseEntity *operator = ( CBaseEntity *pEntity );
|
||||
CBaseEntity *operator ->();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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 )
|
||||
|
|
|
@ -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
|
||||
|
|
436
dlls/vector.h
436
dlls/vector.h
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue