diff --git a/include/compress.h b/include/compress.h new file mode 100644 index 0000000..680e3db --- /dev/null +++ b/include/compress.h @@ -0,0 +1,360 @@ +// +// Copyright (c) 2014, by YaPB Development Team. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// Version: $Id:$ +// + +#ifndef COMPRESS_INCLUDED +#define COMPRESS_INCLUDED + +const int N = 4096, F = 18, THRESHOLD = 2, NIL = N; + +class Compressor +{ +protected: + unsigned long int m_textSize; + unsigned long int m_codeSize; + + byte m_textBuffer[N + F - 1]; + int m_matchPosition; + int m_matchLength; + + int m_left[N + 1]; + int m_right[N + 257]; + int m_parent[N + 1]; + +private: + void InitTree (void) + { + for (int i = N + 1; i <= N + 256; i++) + m_right[i] = NIL; + + for (int j = 0; j < N; j++) + m_parent[j] = NIL; + } + + void InsertNode (int node) + { + int i; + + int compare = 1; + + byte *key = &m_textBuffer[node]; + int temp = N + 1 + key[0]; + + m_right[node] = m_left[node] = NIL; + m_matchLength = 0; + + for (;;) + { + if (compare >= 0) + { + if (m_right[temp] != NIL) + temp = m_right[temp]; + else + { + m_right[temp] = node; + m_parent[node] = temp; + return; + } + } + else + { + if (m_left[temp] != NIL) + temp = m_left[temp]; + else + { + m_left[temp] = node; + m_parent[node] = temp; + return; + } + } + + for (i = 1; i < F; i++) + if ((compare = key[i] - m_textBuffer[temp + i]) != 0) + break; + + if (i > m_matchLength) + { + m_matchPosition = temp; + + if ((m_matchLength = i) >= F) + break; + } + } + + m_parent[node] = m_parent[temp]; + m_left[node] = m_left[temp]; + m_right[node] = m_right[temp]; + m_parent[m_left[temp]] = node; + m_parent[m_right[temp]] = node; + + if (m_right[m_parent[temp]] == temp) + m_right[m_parent[temp]] = node; + else + m_left[m_parent[temp]] = node; + + m_parent[temp] = NIL; + } + + + void DeleteNode (int node) + { + int temp; + + if (m_parent[node] == NIL) + return; // not in tree + + if (m_right[node] == NIL) + temp = m_left[node]; + + else if (m_left[node] == NIL) + temp = m_right[node]; + + else + { + temp = m_left[node]; + + if (m_right[temp] != NIL) + { + do + temp = m_right[temp]; + while (m_right[temp] != NIL); + + m_right[m_parent[temp]] = m_left[temp]; + m_parent[m_left[temp]] = m_parent[temp]; + m_left[temp] = m_left[node]; + m_parent[m_left[node]] = temp; + } + + m_right[temp] = m_right[node]; + m_parent[m_right[node]] = temp; + } + + m_parent[temp] = m_parent[node]; + + if (m_right[m_parent[node]] == node) + m_right[m_parent[node]] = temp; + else + m_left[m_parent[node]] = temp; + + m_parent[node] = NIL; + } + +public: + Compressor (void) + { + m_textSize = 0; + m_codeSize = 0; + } + + ~Compressor (void) + { + m_textSize = 0; + m_codeSize = 0; + } + + int InternalEncode (char *fileName, byte *header, int headerSize, byte *buffer, int bufferSize) + { + int i, bit, length, node, strPtr, lastMatchLength, codeBufferPtr, bufferPtr = 0; + byte codeBuffer[17], mask; + + File fp (fileName, "wb"); + + if (!fp.IsValid ()) + return -1; + + fp.Write (header, headerSize, 1); + InitTree (); + + codeBuffer[0] = 0; + codeBufferPtr = mask = 1; + strPtr = 0; + node = N - F; + + for (i = strPtr; i < node; i++) + m_textBuffer[i] = ' '; + + for (length = 0; (length < F) && (bufferPtr < bufferSize); length++) + { + bit = buffer[bufferPtr++]; + m_textBuffer[node + length] = bit; + } + + if ((m_textSize = length) == 0) + return -1; + + for (i = 1; i <= F; i++) + InsertNode (node - i); + InsertNode (node); + + do + { + if (m_matchLength > length) + m_matchLength = length; + + if (m_matchLength <= THRESHOLD) + { + m_matchLength = 1; + codeBuffer[0] |= mask; + codeBuffer[codeBufferPtr++] = m_textBuffer[node]; + } + else + { + codeBuffer[codeBufferPtr++] = (unsigned char) m_matchPosition; + codeBuffer[codeBufferPtr++] = (unsigned char) (((m_matchPosition >> 4) & 0xf0) | (m_matchLength - (THRESHOLD + 1))); + } + + if ((mask <<= 1) == 0) + { + for (i = 0; i < codeBufferPtr; i++) + fp.PutChar (codeBuffer[i]); + + m_codeSize += codeBufferPtr; + codeBuffer[0] = 0; + codeBufferPtr = mask = 1; + } + lastMatchLength = m_matchLength; + + for (i = 0; (i < lastMatchLength) && (bufferPtr < bufferSize); i++) + { + bit = buffer[bufferPtr++]; + DeleteNode (strPtr); + + m_textBuffer[strPtr] = bit; + + if (strPtr < F - 1) + m_textBuffer[strPtr + N] = bit; + + strPtr = (strPtr + 1) & (N - 1); + node = (node + 1) & (N - 1); + InsertNode (node); + } + + while (i++ < lastMatchLength) + { + DeleteNode (strPtr); + + strPtr = (strPtr + 1) & (N - 1); + node = (node + 1) & (N - 1); + + if (length--) + InsertNode (node); + } + } while (length > 0); + + if (codeBufferPtr > 1) + { + for (i = 0; i < codeBufferPtr; i++) + fp.PutChar (codeBuffer[i]); + + m_codeSize += codeBufferPtr; + } + fp.Close (); + + return m_codeSize; + } + + int InternalDecode (char *fileName, int headerSize, byte *buffer, int bufferSize) + { + int i, j, k, node, bit; + unsigned int flags; + int bufferPtr = 0; + + File fp (fileName, "rb"); + + if (!fp.IsValid ()) + return -1; + + fp.Seek (headerSize, SEEK_SET); + + node = N - F; + for (i = 0; i < node; i++) + m_textBuffer[i] = ' '; + + flags = 0; + + for (;;) + { + if (((flags >>= 1) & 256) == 0) + { + if ((bit = fp.GetChar ()) == EOF) + break; + flags = bit | 0xff00; + } + + if (flags & 1) + { + if ((bit = fp.GetChar ()) == EOF) + break; + buffer[bufferPtr++] = bit; + + if (bufferPtr > bufferSize) + return -1; + + m_textBuffer[node++] = bit; + node &= (N - 1); + } + else + { + if ((i = fp.GetChar ()) == EOF) + break; + + if ((j = fp.GetChar ()) == EOF) + break; + + i |= ((j & 0xf0) << 4); + j = (j & 0x0f) + THRESHOLD; + + for (k = 0; k <= j; k++) + { + bit = m_textBuffer[(i + k) & (N - 1)]; + buffer[bufferPtr++] = bit; + + if (bufferPtr > bufferSize) + return -1; + + m_textBuffer[node++] = bit; + node &= (N - 1); + } + } + } + fp.Close (); + + return bufferPtr; + } + + // external decoder + static int Uncompress (char *fileName, int headerSize, byte *buffer, int bufferSize) + { + static Compressor compressor = Compressor (); + return compressor.InternalDecode (fileName, headerSize, buffer, bufferSize); + } + + // external encoder + static int Compress(char *fileName, byte *header, int headerSize, byte *buffer, int bufferSize) + { + static Compressor compressor = Compressor (); + return compressor.InternalEncode (fileName, header, headerSize, buffer, bufferSize); + } +}; + +#endif // COMPRESS_INCLUDED diff --git a/include/core.h b/include/core.h new file mode 100644 index 0000000..64e233b --- /dev/null +++ b/include/core.h @@ -0,0 +1,1678 @@ +// +// Copyright (c) 2014, by YaPB Development Team. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// Thanks to: +// CountFloyd - Original author of PODBot 2.6. +// Whistler - Original author of YaPB. +// KWo - Current developer of POD-Bot MM. +// Evilspy - Author of Metamod-p, and jk-botti. +// +// Version: $Id:$ +// + +#ifndef YAPB_INCLUDED +#define YAPB_INCLUDED + +#include +#include +#include + +#include +#include + +using namespace Math; + +// detects the build platform +#if defined (__linux__) || defined (__debian__) || defined (__linux) + #define PLATFORM_LINUX 1 +#elif defined (__APPLE__) + #define PLATFORM_OSX 1 +#elif defined (_WIN32) + #define PLATFORM_WIN32 1 +#endif + +// detects the compiler +#if defined (_MSC_VER) + #define COMPILER_VISUALC _MSC_VER +#elif defined (__MINGW32__) + #define COMPILER_MINGW32 __MINGW32__ +#endif + +// configure export macros +#if defined (COMPILER_VISUALC) || defined (COMPILER_MINGW32) + #define export extern "C" __declspec (dllexport) +#elif defined (PLATFORM_LINUX) || defined (PLATFORM_OSX) + #define export extern "C" __attribute__((visibility("default"))) +#else + #error "Can't configure export macros. Compiler unrecognized." +#endif + +// operating system specific macros, functions and typedefs +#ifdef PLATFORM_WIN32 + + #include + + #define DLL_ENTRYPOINT int STDCALL DllMain (HINSTANCE hModule, DWORD dwReason, LPVOID) + #define DLL_DETACHING (dwReason == DLL_PROCESS_DETACH) + #define DLL_RETENTRY return TRUE + + #if defined (COMPILER_VISUALC) + #define DLL_GIVEFNPTRSTODLL extern "C" void STDCALL + #elif defined (COMPILER_MINGW32) + #define DLL_GIVEFNPTRSTODLL export void STDCALL + #endif + + // specify export parameter + #if defined (COMPILER_VISUALC) && (COMPILER_VISUALC > 1000) + #pragma comment (linker, "/EXPORT:GiveFnptrsToDll=_GiveFnptrsToDll@8,@1") + #pragma comment (linker, "/SECTION:.data,RW") + #pragma warning (disable : 4288) + #endif + + typedef int (FAR *EntityAPI_t) (gamefuncs_t *, int); + typedef int (FAR *NewEntityAPI_t) (newgamefuncs_t *, int *); + typedef int (FAR *BlendAPI_t) (int, void **, void *, float (*)[3][4], float (*)[128][3][4]); + typedef void (STDCALL *FuncPointers_t) (enginefuncs_t *, globalvars_t *); + typedef void (FAR *EntityPtr_t) (entvars_t *); + +#elif defined (PLATFORM_LINUX) || defined (PLATFORM_OSX) + + #include + #include + #include + #include + #include + + #include + #include + #include + #include + #include + + #define DLL_ENTRYPOINT void _fini (void) + #define DLL_DETACHING TRUE + #define DLL_RETENTRY return + #define DLL_GIVEFNPTRSTODLL extern "C" void __attribute__((visibility("default"))) + + static inline uint32 _lrotl (uint32 x, int r) { return (x << r) | (x >> (sizeof (x) * 8 - r));} + + typedef int (*EntityAPI_t) (gamefuncs_t *, int); + typedef int (*NewEntityAPI_t) (newgamefuncs_t *, int *); + typedef int (*BlendAPI_t) (int, void **, void *, float (*)[3][4], float (*)[128][3][4]); + typedef void (*FuncPointers_t) (enginefuncs_t *, globalvars_t *); + typedef void (*EntityPtr_t) (entvars_t *); + +#else + #error "Platform unrecognized." +#endif + +// library wrapper +class Library +{ +private: + void *m_ptr; + +public: + + Library (const char *fileName) + { + if (fileName == NULL) + return; + +#ifdef PLATFORM_WIN32 + m_ptr = LoadLibrary (fileName); +#else + m_ptr = dlopen (fileName, RTLD_NOW); +#endif + } + + ~Library (void) + { + if (!IsLoaded ()) + return; + +#ifdef PLATFORM_WIN32 + FreeLibrary ((HMODULE) m_ptr); +#else + dlclose (m_ptr); +#endif + } + +public: + void *GetFunctionAddr (const char *functionName) + { + if (!IsLoaded ()) + return NULL; + +#ifdef PLATFORM_WIN32 + return GetProcAddress ((HMODULE) m_ptr, functionName); +#else + return dlsym (m_ptr, functionName); +#endif + } + + template R GetHandle (void) + { + return (R) m_ptr; + } + + inline bool IsLoaded (void) const + { + return m_ptr != NULL; + } +}; + +#include +#include +#include +#include +#include + +#include + +// defines bots tasks +enum TaskId_t +{ + TASK_NORMAL, + TASK_PAUSE, + TASK_MOVETOPOSITION, + TASK_FOLLOWUSER, + TASK_WAITFORGO, + TASK_PICKUPITEM, + TASK_CAMP, + TASK_PLANTBOMB, + TASK_DEFUSEBOMB, + TASK_ATTACK, + TASK_HUNTENEMY, + TASK_SEEKCOVER, + TASK_THROWHEGRENADE, + TASK_THROWFLASHBANG, + TASK_THROWSMOKE, + TASK_DOUBLEJUMP, + TASK_ESCAPEFROMBOMB, + TASK_SHOOTBREAKABLE, + TASK_HIDE, + TASK_BLINDED, + TASK_SPRAY +}; + +// supported cs's +enum CSVersion +{ + CSV_STEAM = 1, // Counter-Strike 1.6 and Above + CSV_CZERO = 2, // Counter-Strike: Condition Zero + CSV_OLD = 3 // Counter-Strike 1.3-1.5 with/without Steam +}; + +// log levels +enum LogLevel +{ + LL_DEFAULT = 1, // default log message + LL_WARNING = 2, // warning log message + LL_ERROR = 3, // error log message + LL_IGNORE = 4, // additional flag + LL_FATAL = 5 // fatal error log message (terminate the game!) + +}; + +// chat types id's +enum ChatType +{ + CHAT_KILLING = 0, // id to kill chat array + CHAT_DEAD, // id to dead chat array + CHAT_BOMBPLANT, // id to bomb chat array + CHAT_TEAMATTACK, // id to team-attack chat array + CHAT_TEAMKILL, // id to team-kill chat array + CHAT_WELCOME, // id to welcome chat array + CHAT_NOKW, // id to no keyword chat array + CHAT_TOTAL // number for array +}; + +// personalities defines +enum Personality +{ + PERSONALITY_NORMAL = 0, + PERSONALITY_RUSHER, + PERSONALITY_CAREFUL +}; + +// collision states +enum CollisionState +{ + COLLISION_NOTDECICED, + COLLISION_PROBING, + COLLISION_NOMOVE, + COLLISION_JUMP, + COLLISION_DUCK, + COLLISION_STRAFELEFT, + COLLISION_STRAFERIGHT +}; + +// counter-strike team id's +enum Team +{ + TEAM_TF = 0, + TEAM_CF +}; + +// client flags +enum ClientFlags +{ + CF_USED = (1 << 0), + CF_ALIVE = (1 << 1), + CF_ADMIN = (1 << 2) +}; + +// radio messages +enum RadioMessage_t +{ + Radio_CoverMe = 1, + Radio_YouTakePoint = 2, + Radio_HoldPosition = 3, + Radio_RegroupTeam = 4, + Radio_FollowMe = 5, + Radio_TakingFire = 6, + Radio_GoGoGo = 11, + Radio_Fallback = 12, + Radio_StickTogether = 13, + Radio_GetInPosition = 14, + Radio_StormTheFront = 15, + Radio_ReportTeam = 16, + Radio_Affirmative = 21, + Radio_EnemySpotted = 22, + Radio_NeedBackup = 23, + Radio_SectorClear = 24, + Radio_InPosition = 25, + Radio_ReportingIn = 26, + Radio_ShesGonnaBlow = 27, + Radio_Negative = 28, + Radio_EnemyDown = 29 +}; + +// voice system (extending enum above, messages 30-39 is reserved) +enum ChatterMessage +{ + Chatter_SpotTheBomber = 40, + Chatter_FriendlyFire, + Chatter_DiePain, + Chatter_GotBlinded, + Chatter_GoingToPlantBomb, + Chatter_RescuingHostages, + Chatter_GoingToCamp, + Chatter_HearSomething, + Chatter_TeamKill, + Chatter_ReportingIn, + Chatter_GuardDroppedC4, + Chatter_Camp, + Chatter_PlantingC4, + Chatter_DefusingC4, + Chatter_InCombat, + Chatter_SeeksEnemy, + Chatter_Nothing, + Chatter_EnemyDown, + Chatter_UseHostage, + Chatter_FoundC4, + Chatter_WonTheRound, + Chatter_ScaredEmotion, + Chatter_HeardEnemy, + Chatter_SniperWarning, + Chatter_SniperKilled, + Chatter_VIPSpotted, + Chatter_GuardingVipSafety, + Chatter_GoingToGuardVIPSafety, + Chatter_QuicklyWonTheRound, + Chatter_OneEnemyLeft, + Chatter_TwoEnemiesLeft, + Chatter_ThreeEnemiesLeft, + Chatter_NoEnemiesLeft, + Chatter_FoundBombPlace, + Chatter_WhereIsTheBomb, + Chatter_DefendingBombSite, + Chatter_BarelyDefused, + Chatter_NiceshotCommander, + Chatter_NiceshotPall, + Chatter_GoingToGuardHostages, + Chatter_GoingToGuardDoppedBomb, + Chatter_OnMyWay, + Chatter_LeadOnSir, + Chatter_Pinned_Down, + Chatter_GottaFindTheBomb, + Chatter_You_Heard_The_Man, + Chatter_Lost_The_Commander, + Chatter_NewRound, + Chatter_CoverMe, + Chatter_BehindSmoke, + Chatter_BombSiteSecured, + Chatter_Total + +}; + +// counter-strike weapon id's +enum Weapon +{ + WEAPON_P228 = 1, + WEAPON_SHIELD = 2, + WEAPON_SCOUT = 3, + WEAPON_EXPLOSIVE = 4, + WEAPON_XM1014 = 5, + WEAPON_C4 = 6, + WEAPON_MAC10 = 7, + WEAPON_AUG = 8, + WEAPON_SMOKE = 9, + WEAPON_ELITE = 10, + WEAPON_FIVESEVEN = 11, + WEAPON_UMP45 = 12, + WEAPON_SG550 = 13, + WEAPON_GALIL = 14, + WEAPON_FAMAS = 15, + WEAPON_USP = 16, + WEAPON_GLOCK = 17, + WEAPON_AWP = 18, + WEAPON_MP5 = 19, + WEAPON_M249 = 20, + WEAPON_M3 = 21, + WEAPON_M4A1 = 22, + WEAPON_TMP = 23, + WEAPON_G3SG1 = 24, + WEAPON_FLASHBANG = 25, + WEAPON_DEAGLE = 26, + WEAPON_SG552 = 27, + WEAPON_AK47 = 28, + WEAPON_KNIFE = 29, + WEAPON_P90 = 30, + WEAPON_ARMOR = 31, + WEAPON_ARMORHELM = 32, + WEAPON_DEFUSER = 33 +}; + +// defines for pickup items +enum PickupType +{ + PICKUP_NONE, + PICKUP_WEAPON, + PICKUP_DROPPED_C4, + PICKUP_PLANTED_C4, + PICKUP_HOSTAGE, + PICKUP_BUTTON, + PICKUP_SHIELD, + PICKUP_DEFUSEKIT +}; + +// reload state +enum ReloadState +{ + RELOAD_NONE = 0, // no reload state currrently + RELOAD_PRIMARY = 1, // primary weapon reload state + RELOAD_SECONDARY = 2 // secondary weapon reload state +}; + +// collision probes +enum CollisionProbe +{ + PROBE_JUMP = (1 << 0), // probe jump when colliding + PROBE_DUCK = (1 << 1), // probe duck when colliding + PROBE_STRAFE = (1 << 2) // probe strafing when colliding +}; + +// vgui menus (since latest steam updates is obsolete, but left for old cs) +enum VGuiMenu +{ + VMS_TEAM = 2, // menu select team + VMS_TF = 26, // terrorist select menu + VMS_CT = 27 // ct select menu +}; + +// lift usage states +enum LiftState +{ + LIFT_NO_NEARBY = 0, + LIFT_LOOKING_BUTTON_OUTSIDE, + LIFT_WAITING_FOR, + LIFT_ENTERING_IN, + LIFT_WAIT_FOR_TEAMMATES, + LIFT_LOOKING_BUTTON_INSIDE, + LIFT_TRAVELING_BY, + LIFT_LEAVING +}; + +// game start messages for counter-strike... +enum GameStartMessage +{ + GSM_IDLE = 1, + GSM_TEAM_SELECT = 2, + GSM_CLASS_SELECT = 3, + GSM_BUY_STUFF = 100, + GSM_RADIO = 200, + GSM_SAY = 10000, + GSM_SAY_TEAM = 10001 +}; + +// netmessage functions +enum NetworkMessage +{ + NETMSG_VGUI = 1, + NETMSG_SHOWMENU = 2, + NETMSG_WEAPONLIST = 3, + NETMSG_CURWEAPON = 4, + NETMSG_AMMOX = 5, + NETMSG_AMMOPICKUP = 6, + NETMSG_DAMAGE = 7, + NETMSG_MONEY = 8, + NETMSG_STATUSICON = 9, + NETMSG_DEATH = 10, + NETMSG_SCREENFADE = 11, + NETMSG_HLTV = 12, + NETMSG_TEXTMSG = 13, + NETMSG_SCOREINFO = 14, + NETMSG_BARTIME = 15, + NETMSG_SENDAUDIO = 17, + NETMSG_SAYTEXT = 18, + NETMSG_BOTVOICE = 19, + NETMSG_RESETHUD = 20, + NETMSG_UNDEFINED = 0 +}; + +// sensing states +enum SensingState +{ + STATE_SEEING_ENEMY = (1 << 0), // seeing an enemy + STATE_HEARING_ENEMY = (1 << 1), // hearing an enemy + STATE_SUSPECT_ENEMY = (1 << 2), // suspect enemy behind obstacle + STATE_PICKUP_ITEM = (1 << 3), // pickup item nearby + STATE_THROW_HE = (1 << 4), // could throw he grenade + STATE_THROW_FB = (1 << 5), // could throw flashbang + STATE_THROW_SG = (1 << 6) // could throw smokegrenade +}; + +// positions to aim at +enum AimPosition +{ + AIM_NAVPOINT = (1 << 0), // aim at nav point + AIM_CAMP = (1 << 1), // aim at camp vector + AIM_PREDICT_ENEMY = (1 << 2), // aim at predicted path + AIM_LAST_ENEMY = (1 << 3), // aim at last enemy + AIM_ENTITY = (1 << 4), // aim at entity like buttons, hostages + AIM_ENEMY = (1 << 5), // aim at enemy + AIM_GRENADE = (1 << 6), // aim for grenade throw + AIM_OVERRIDE = (1 << 7) // overrides all others (blinded) +}; + +// famas/glock burst mode status + m4a1/usp silencer +enum BurstMode +{ + BM_ON = 1, + BM_OFF = 2 +}; + +// visibility flags +enum Visibility +{ + VISIBLE_HEAD = (1 << 1), + VISIBLE_BODY = (1 << 2), + VISIBLE_OTHER = (1 << 3) +}; + +// defines map type +enum MapType +{ + MAP_AS = (1 << 0), + MAP_CS = (1 << 1), + MAP_DE = (1 << 2), + MAP_ES = (1 << 3), + MAP_KA = (1 << 4), + MAP_FY = (1 << 5) +}; + +// defines for waypoint flags field (32 bits are available) +enum WaypointFlag +{ + FLAG_LIFT = (1 << 1), // wait for lift to be down before approaching this waypoint + FLAG_CROUCH = (1 << 2), // must crouch to reach this waypoint + FLAG_CROSSING = (1 << 3), // a target waypoint + FLAG_GOAL = (1 << 4), // mission goal point (bomb, hostage etc.) + FLAG_LADDER = (1 << 5), // waypoint is on ladder + FLAG_RESCUE = (1 << 6), // waypoint is a hostage rescue point + FLAG_CAMP = (1 << 7), // waypoint is a camping point + FLAG_NOHOSTAGE = (1 << 8), // only use this waypoint if no hostage + FLAG_DOUBLEJUMP = (1 << 9), // bot help's another bot (requster) to get somewhere (using djump) + FLAG_SNIPER = (1 << 28), // it's a specific sniper point + FLAG_TF_ONLY = (1 << 29), // it's a specific terrorist point + FLAG_CF_ONLY = (1 << 30) // it's a specific ct point +}; + +// defines for waypoint connection flags field (16 bits are available) +enum PathFlag +{ + PATHFLAG_JUMP = (1 << 0), // must jump for this connection + PATHFLAG_DOUBLEJUMP = (1 << 1) // must use friend for this connection +}; + +// defines waypoint connection types +enum PathConnection +{ + CONNECTION_OUTGOING = 0, + CONNECTION_INCOMING, + CONNECTION_BOTHWAYS +}; + +// bot known file headers +const char FH_WAYPOINT[] = "PODWAY!"; +const char FH_EXPERIENCE[] = "PODEXP!"; +const char FH_VISTABLE[] = "PODVIS!"; + +const int FV_WAYPOINT = 7; +const int FV_EXPERIENCE = 3; +const int FV_VISTABLE = 1; + +// some hardcoded desire defines used to override calculated ones +const float TASKPRI_NORMAL = 35.0; +const float TASKPRI_PAUSE = 36.0; +const float TASKPRI_CAMP = 37.0; +const float TASKPRI_SPRAYLOGO = 38.0; +const float TASKPRI_FOLLOWUSER = 39.0; +const float TASKPRI_MOVETOPOSITION = 50.0; +const float TASKPRI_DEFUSEBOMB = 89.0; +const float TASKPRI_PLANTBOMB = 89.0; +const float TASKPRI_ATTACK = 90.0; +const float TASKPRI_SEEKCOVER = 91.0; +const float TASKPRI_HIDE = 92.0; +const float TASKPRI_THROWGRENADE = 99.0; +const float TASKPRI_DOUBLEJUMP = 99.0; +const float TASKPRI_BLINDED = 100.0; +const float TASKPRI_SHOOTBREAKABLE = 100.0; +const float TASKPRI_ESCAPEFROMBOMB = 100.0; + +const float MAX_GRENADE_TIMER = 2.34f; + +const int MAX_HOSTAGES = 8; +const int MAX_PATH_INDEX = 8; +const int MAX_DAMAGE_VALUE = 2040; +const int MAX_GOAL_VALUE = 2040; +const int MAX_KILL_HISTORY = 16; +const int MAX_REG_MESSAGES = 256; +const int MAX_WAYPOINTS = 1024; +const int MAX_WEAPONS = 32; +const int NUM_WEAPONS = 26; + +// weapon masks +const int WEAPON_PRIMARY = ((1 << WEAPON_XM1014) | (1 < keywords; + Array replies; + Array usedReplies; +}; + +// tasks definition +struct TaskItem +{ + TaskId_t id; // major task/action carried out + float desire; // desire (filled in) for this task + int data; // additional data (waypoint index) + float time; // time task expires + bool resume; // if task can be continued if interrupted +}; + +// wave structure +struct WavHeader +{ + char riffChunkId[4]; + unsigned long packageSize; + char chunkID[4]; + char formatChunkId[4]; + unsigned long formatChunkLength; + unsigned short dummy; + unsigned short channels; + unsigned long sampleRate; + unsigned long bytesPerSecond; + unsigned short bytesPerSample; + unsigned short bitsPerSample; + char dataChunkId[4]; + unsigned long dataChunkLength; +}; + +// botname structure definition +struct BotName +{ + String name; + bool used; +}; + +// voice config structure definition +struct ChatterItem +{ + String name; + float repeatTime; +}; + +// language config structure definition +struct LanguageItem +{ + char *original; // original string + char *translated; // string to replace for +}; + +struct WeaponSelect +{ + int id; // the weapon id value + char *weaponName; // name of the weapon when selecting it + char *modelName; // model name to separate cs weapons + int price; // price when buying + int minPrimaryAmmo; // minimum primary ammo + int teamStandard; // used by team (number) (standard map) + int teamAS; // used by team (as map) + int buyGroup; // group in buy menu (standard map) + int buySelect; // select item in buy menu (standard map) + int newBuySelectT; // for counter-strike v1.6 + int newBuySelectCT; // for counter-strike v1.6 + bool shootsThru; // can shoot thru walls + bool primaryFireHold; // hold down primary fire button to use? +}; + +// skill definitions +struct SkillDefinition +{ + float minSurpriseTime; // surprise delay (minimum) + float maxSurpriseTime; // surprise delay (maximum) + float campStartDelay; // delay befor start camping + float campEndDelay; // delay before end camping + float minTurnSpeed; // initial minimum turnspeed + float maxTurnSpeed; // initial maximum turnspeed + float aimOffs_X; // X/Y/Z maximum offsets + float aimOffs_Y; // X/Y/Z maximum offsets + float aimOffs_Z; // X/Y/Z maximum offsets + int headshotFrequency; // precent to aiming to player head + int heardShootThruProb; // precent to shooting throug wall when seen something + int seenShootThruProb; // precent to shooting throug wall when heard something + int recoilAmount; // amount of recoil when the bot should pause shooting +}; + +// fire delay definiton +struct FireDelay +{ + int weaponIndex; + int maxFireBullets; + float minBurstPauseFactor; + float primaryBaseDelay; + float primaryMinDelay[6]; + float primaryMaxDelay[6]; + float secondaryBaseDelay; + float secondaryMinDelay[5]; + float secondaryMaxDelay[5]; +}; + +// struct for menus +struct MenuText +{ + int validSlots; // ored together bits for valid keys + char *menuText; // ptr to actual string +}; + +// array of clients struct +struct Client +{ + MenuText *menu; // pointer to opened bot menu + edict_t *ent; // pointer to actual edict + Vector origin; // position in the world + Vector soundPosition; // position sound was played + int team; // bot team + int realTeam; // real bot team in free for all mode (csdm) + int flags; // client flags + float hearingDistance; // distance this sound is heared + float timeSoundLasting; // time sound is played/heared + float maxTimeSoundLasting; // max time sound is played/heared (to divide the difference between that above one and the current one) +}; + +// experience data hold in memory while playing +struct Experience +{ + unsigned short team0Damage; + unsigned short team1Damage; + signed short team0DangerIndex; + signed short team1DangerIndex; + signed short team0Value; + signed short team1Value; +}; + +// experience data when saving/loading +struct ExperienceSave +{ + unsigned char team0Damage; + unsigned char team1Damage; + signed char team0Value; + signed char team1Value; +}; + +// bot creation tab +struct CreateQueue +{ + int skill; + int team; + int member; + int personality; + String name; +}; + +// weapon properties structure +struct WeaponProperty +{ + char className[64]; + int ammo1; // ammo index for primary ammo + int ammo1Max; // max primary ammo + int slotID; // HUD slot (0 based) + int position; // slot position + int id; // weapon ID + int flags; // flags??? +}; + +// define chatting collection structure +struct ChatCollection +{ + char chatProbability; + float chatDelay; + float timeNextChat; + int entityIndex; + char sayText[512]; + Array lastUsedSentences; +}; + +// general waypoint header information structure +struct WaypointHeader +{ + char header[8]; + int32 fileVersion; + int32 pointNumber; + char mapName[32]; + char author[32]; +}; + +// general experience & vistable header information structure +struct ExtensionHeader +{ + char header[8]; + int32 fileVersion; + int32 pointNumber; +}; + +// define general waypoint structure +struct Path +{ + int32 pathNumber; + int32 flags; + Vector origin; + float radius; + + float campStartX; + float campStartY; + float campEndX; + float campEndY; + + int16 index[MAX_PATH_INDEX]; + uint16 connectionFlags[MAX_PATH_INDEX]; + Vector connectionVelocity[MAX_PATH_INDEX]; + int32 distances[MAX_PATH_INDEX]; + + struct Vis { uint16 stand, crouch; } vis; +}; + +// main bot class +class Bot +{ + friend class BotManager; + +private: + unsigned int m_states; // sensing bitstates + + float m_moveSpeed; // current speed forward/backward + float m_strafeSpeed; // current speed sideways + float m_minSpeed; // minimum speed in normal mode + float m_oldCombatDesire; // holds old desire for filtering + + bool m_isLeader; // bot is leader of his team + bool m_checkTerrain; // check for terrain + bool m_moveToC4; // ct is moving to bomb + + float m_prevTime; // time previously checked movement speed + float m_prevSpeed; // speed some frames before + Vector m_prevOrigin; // origin some frames before + + int m_messageQueue[32]; // stack for messages + char m_tempStrings[160]; // space for strings (say text...) + int m_radioSelect; // radio entry + + float m_blindRecognizeTime; // time to recognize enemy + float m_itemCheckTime; // time next search for items needs to be done + PickupType m_pickupType; // type of entity which needs to be used/picked up + Vector m_breakable; // origin of breakable + + edict_t *m_pickupItem; // pointer to entity of item to use/pickup + edict_t *m_itemIgnore; // pointer to entity to ignore for pickup + edict_t *m_liftEntity; // pointer to lift entity + edict_t *m_breakableEntity; // pointer to breakable entity + + float m_timeDoorOpen; // time to next door open check + float m_lastChatTime; // time bot last chatted + float m_timeLogoSpray; // time bot last spray logo + float m_knifeAttackTime; // time to rush with knife (at the beginning of the round) + bool m_defendedBomb; // defend action issued + + float m_askCheckTime; // time to ask team + float m_collideTime; // time last collision + float m_firstCollideTime; // time of first collision + float m_probeTime; // time of probing different moves + float m_lastCollTime; // time until next collision check + char m_collisionProbeBits; // bits of possible collision moves + char m_collideMoves[4]; // sorted array of movements + char m_collStateIndex; // index into collide moves + CollisionState m_collisionState; // collision State + + PathNode *m_navNode; // pointer to current node from path + PathNode *m_navNodeStart; // pointer to start of path finding nodes + Path *m_currentPath; // pointer to the current path waypoint + + unsigned char m_pathType; // which pathfinder to use + unsigned char m_visibility; // visibility flags + + int m_currentWaypointIndex; // current waypoint index + int m_travelStartIndex; // travel start index to double jump action + int m_prevWptIndex[5]; // previous waypoint indices from waypoint find + int m_waypointFlags; // current waypoint flags + int m_loosedBombWptIndex; // nearest to loosed bomb waypoint + int m_plantedBombWptIndex;// nearest to planted bomb waypoint + float m_skillOffset; // offset to bots skill + + unsigned short m_currentTravelFlags; // connection flags like jumping + bool m_jumpFinished; // has bot finished jumping + Vector m_desiredVelocity; // desired velocity for jump waypoints + float m_navTimeset; // time waypoint chosen by Bot + + unsigned int m_aimFlags; // aiming conditions + Vector m_lookAt; // vector bot should look at + Vector m_throw; // origin of waypoint to throw grenades + + Vector m_enemyOrigin; // target origin chosen for shooting + Vector m_grenade; // calculated vector for grenades + Vector m_entity; // origin of entities like buttons etc. + Vector m_camp; // aiming vector when camping. + + float m_timeWaypointMove; // last time bot followed waypoints + bool m_wantsToFire; // bot needs consider firing + float m_shootAtDeadTime; // time to shoot at dying players + edict_t *m_avoidGrenade; // pointer to grenade entity to avoid + char m_needAvoidGrenade; // which direction to strafe away + + float m_followWaitTime; // wait to follow time + edict_t *m_targetEntity; // the entity that the bot is trying to reach + edict_t *m_LeaderEntity; // the entity that the bot is picking as a leader + edict_t *m_hostages[MAX_HOSTAGES]; // pointer to used hostage entities + + bool m_isStuck; // bot is stuck + bool m_isReloading; // bot is reloading a gun + int m_reloadState; // current reload state + int m_voicePitch; // bot voice pitch + + bool m_duckDefuse; // should or not bot duck to defuse bomb + float m_duckDefuseCheckTime; // time to check for ducking for defuse + + byte m_msecVal; // calculated msec value + float m_msecInterval; // used for leon hartwig's method for msec calculation + + float m_frameInterval; // bot's frame interval + float m_lastThinkTime; // time bot last thinked + + float m_reloadCheckTime; // time to check reloading + float m_zoomCheckTime; // time to check zoom again + float m_shieldCheckTime; // time to check shiled drawing again + float m_grenadeCheckTime; // time to check grenade usage + + bool m_checkKnifeSwitch; // is time to check switch to knife action + bool m_checkWeaponSwitch; // is time to check weapon switch + bool m_isUsingGrenade; // bot currently using grenade?? + + unsigned char m_combatStrafeDir; // direction to strafe + unsigned char m_fightStyle; // combat style to use + float m_lastFightStyleCheck; // time checked style + float m_strafeSetTime; // time strafe direction was set + + float m_timeCamping; // time to camp + int m_campDirection; // camp Facing direction + float m_nextCampDirTime; // time next camp direction change + int m_campButtons; // buttons to press while camping + int m_doorOpenAttempt; // attempt's to open the door + int m_liftState; // state of lift handling + + float m_duckTime; // time to duck + float m_jumpTime; // time last jump happened + float m_voiceTimers[Chatter_Total]; // voice command timers + float m_soundUpdateTime; // time to update the sound + float m_heardSoundTime; // last time noise is heard + float m_buttonPushTime; // time to push the button + float m_liftUsageTime; // time to use lift + + Vector m_liftTravelPos; // lift travel position + Vector m_moveAngles; // bot move angles + bool m_moveToGoal; // bot currently moving to goal?? + + Vector m_idealAngles; // angle wanted + Vector m_randomizedIdealAngles; // angle wanted with noise + Vector m_angularDeviation; // angular deviation from current to ideal angles + Vector m_aimSpeed; // aim speed calculated + Vector m_targetOriginAngularSpeed; // target/enemy angular speed + + float m_randomizeAnglesTime; // time last randomized location + float m_playerTargetTime; // time last targeting + + void SwitchChatterIcon (bool show); + void InstantChatterMessage (int type); + void BotAI (void); + bool IsMorePowerfulWeaponCanBeBought (void); + void PerformWeaponPurchase (void); + + bool CanDuckUnder (const Vector &normal); + bool CanJumpUp (const Vector &normal); + bool CanStrafeLeft (TraceResult *tr); + bool CanStrafeRight (TraceResult *tr); + bool CantMoveForward (const Vector &normal, TraceResult *tr); + + void ChangePitch (float speed); + void ChangeYaw (float speed); + void CheckMessageQueue (void); + void CheckRadioCommands (void); + void CheckReload (void); + void AvoidGrenades (void); + void CheckBurstMode (float distance); + + void CheckSilencer (void); + bool CheckWallOnLeft (void); + bool CheckWallOnRight (void); + void ChooseAimDirection (void); + int ChooseBombWaypoint (void); + + bool DoWaypointNav (void); + bool EnemyIsThreat (void); + void FacePosition (void); + bool IsRestricted (int weaponIndex); + bool IsRestrictedAMX (int weaponIndex); + + bool IsInViewCone (const Vector &origin); + void ReactOnSound (void); + bool CheckVisibility (entvars_t *targetOrigin, Vector *origin, byte *bodyPart); + bool IsEnemyViewable (edict_t *player); + + edict_t *FindNearestButton (const char *className); + edict_t *FindBreakable (void); + int FindCoverWaypoint (float maxDistance); + int FindDefendWaypoint (Vector origin); + int FindGoal (void); + void FindItem (void); + + void GetCampDirection (Vector *dest); + void CollectGoalExperience (int damage, int team); + void CollectExperienceData (edict_t *attacker, int damage); + int GetMessageQueue (void); + bool GoalIsValid (void); + bool HeadTowardWaypoint (void); + int InFieldOfView (const Vector &dest); + + bool IsBombDefusing (Vector bombOrigin); + bool IsBlockedLeft (void); + bool IsBlockedRight (void); + bool IsWaypointUsed (int index); + inline bool IsOnLadder (void) { return pev->movetype == MOVETYPE_FLY; } + inline bool IsOnFloor (void) { return (pev->flags & (FL_ONGROUND | FL_PARTIALGROUND)) != 0; } + inline bool IsInWater (void) { return pev->waterlevel >= 2; } + inline float GetWalkSpeed (void) { return static_cast ((static_cast (pev->maxspeed) / 2) + (static_cast (pev->maxspeed) / 50)) - 18; } + + bool ItemIsVisible (const Vector &dest, char *itemName); + bool LastEnemyShootable (void); + bool IsLastEnemyViewable (void); + bool IsBehindSmokeClouds (edict_t *ent); + void RunTask (void); + + bool IsShootableBreakable (edict_t *ent); + bool RateGroundWeapon (edict_t *ent); + bool ReactOnEnemy (void); + void ResetCollideState (void); + void SetConditions (void); + void SetStrafeSpeed (Vector moveDir, float strafeSpeed); + void StartGame (void); + void TaskComplete (void); + bool GetBestNextWaypoint (void); + int GetBestWeaponCarried (void); + int GetBestSecondaryWeaponCarried (void); + + void RunPlayerMovement (void); + void GetValidWaypoint (void); + void ChangeWptIndex (int waypointIndex); + bool IsDeadlyDrop (Vector targetOriginPos); + bool OutOfBombTimer (void); + void SelectLeaderEachTeam (int team); + + Vector CheckToss (const Vector &start, Vector end); + Vector CheckThrow (const Vector &start, Vector end); + Vector GetAimPosition (void); + Vector CheckBombAudible (void); + + float GetZOffset (float distance); + + int CheckGrenades (void); + void CommandTeam (void); + void AttachToUser (void); + void CombatFight (void); + bool IsWeaponBadInDistance (int weaponIndex, float distance); + bool DoFirePause (float distance, FireDelay *fireDelay); + bool LookupEnemy (void); + void FireWeapon (void); + void FocusEnemy (void); + + void SelectBestWeapon (void); + void SelectPistol (void); + bool IsFriendInLineOfFire (float distance); + bool IsGroupOfEnemies (Vector location, int numEnemies = 1, int radius = 256); + bool IsShootableThruObstacle (Vector dest); + int GetNearbyEnemiesNearPosition (Vector origin, int radius); + int GetNearbyFriendsNearPosition (Vector origin, int radius); + void SelectWeaponByName (const char *name); + void SelectWeaponbyNumber (int num); + int GetHighestWeapon (void); + + bool IsEnemyProtectedByShield (edict_t *enemy); + bool ParseChat (char *reply); + bool RepliesToPlayer (void); + float GetBombTimeleft (void); + float GetEstimatedReachTime (void); + + int GetAimingWaypoint (void); + int GetAimingWaypoint (Vector targetOriginPos); + void FindShortestPath (int srcIndex, int destIndex); + void FindPath (int srcIndex, int destIndex, unsigned char pathType = 0); + void DebugMsg (const char *format, ...); + void SecondThink (void); + +public: + entvars_t *pev; + + int m_wantedTeam; // player team bot wants select + int m_wantedClass; // player model bot wants to select + + int m_skill; ;// bots play skill + int m_moneyAmount; // amount of money in bot's bank + + Personality m_personality; + float m_spawnTime; // time this bot spawned + float m_timeTeamOrder; // time of last radio command + + bool m_isVIP; // bot is vip? + bool m_bIsDefendingTeam; // bot in defending team on this map + + int m_startAction; // team/class selection state + bool m_notKilled; // has the player been killed or has he just respawned + bool m_notStarted; // team/class not chosen yet + + int m_voteKickIndex; // index of player to vote against + int m_lastVoteKick; // last index + int m_voteMap; // number of map to vote for + int m_logotypeIndex; // index for logotype + int m_burstShotsFired; // number of bullets fired + + bool m_inBombZone; // bot in the bomb zone or not + int m_buyState; // current Count in Buying + float m_nextBuyTime; // next buy time + + bool m_inBuyZone; // bot currently in buy zone + bool m_inVIPZone; // bot in the vip satefy zone + bool m_buyingFinished; // done with buying + bool m_buyPending; // bot buy is pending + bool m_hasDefuser; // does bot has defuser + bool m_hasProgressBar; // has progress bar on a HUD + bool m_jumpReady; // is double jump ready + bool m_canChooseAimDirection; // can choose aiming direction + + float m_blindTime; // time when bot is blinded + float m_blindMoveSpeed; // mad speeds when bot is blind + float m_blindSidemoveSpeed; // mad side move speeds when bot is blind + int m_blindButton; // buttons bot press, when blind + + edict_t *m_doubleJumpEntity; // pointer to entity that request double jump + edict_t *m_radioEntity; // pointer to entity issuing a radio command + int m_radioOrder; // actual command + + float m_duckForJump; // is bot needed to duck for double jump + float m_baseAgressionLevel; // base aggression level (on initializing) + float m_baseFearLevel; // base fear level (on initializing) + float m_agressionLevel; // dynamic aggression level (in game) + float m_fearLevel; // dynamic fear level (in game) + float m_nextEmotionUpdate; // next time to sanitize emotions + + int m_actMessageIndex; // current processed message + int m_pushMessageIndex; // offset for next pushed message + + int m_prevGoalIndex; // holds destination goal waypoint + int m_chosenGoalIndex; // used for experience, same as above + float m_goalValue; // ranking value for this waypoint + + Vector m_waypointOrigin; // origin of waypoint + Vector m_destOrigin; // origin of move destination + Vector m_position; // position to move to in move to position task + Vector m_doubleJumpOrigin; // origin of double jump + Vector m_lastBombPosition; // origin of last remembered bomb position + + float m_viewDistance; // current view distance + float m_maxViewDistance; // maximum view distance + Vector m_lastEnemyOrigin; // vector to last enemy origin + ChatCollection m_sayTextBuffer; // holds the index & the actual message of the last unprocessed text message of a player + BurstMode m_weaponBurstMode; // bot using burst mode? (famas/glock18, but also silencer mode) + + edict_t *m_enemy; // pointer to enemy entity + float m_enemyUpdateTime; // time to check for new enemies + float m_enemyReachableTimer; // time to recheck if Enemy reachable + bool m_isEnemyReachable; // direct line to enemy + + float m_seeEnemyTime; // time bot sees enemy + float m_enemySurpriseTime; // time of surprise + float m_idealReactionTime; // time of base reaction + float m_actualReactionTime; // time of current reaction time + + edict_t *m_lastEnemy; // pointer to last enemy entity + edict_t *m_lastVictim; // pointer to killed entity + edict_t *m_trackingEdict; // pointer to last tracked player when camping/hiding + float m_timeNextTracking; // time waypoint index for tracking player is recalculated + + float m_firePause; // time to pause firing + float m_shootTime; // time to shoot + float m_timeLastFired; // time to last firing + int m_lastDamageType; // stores last damage + + int m_currentWeapon; // one current weapon for each bot + int m_ammoInClip[MAX_WEAPONS]; // ammo in clip for each weapons + int m_ammo[MAX_AMMO_SLOTS]; // total ammo amounts + + Array m_tasks; + + Bot (edict_t *bot, int skill, int personality, int team, int member); + ~Bot (void); + + int GetAmmo (void); + inline int GetAmmoInClip (void) { return m_ammoInClip[m_currentWeapon]; } + + inline edict_t *GetEntity (void) { return ENT (pev); }; + inline EOFFSET GetOffset (void) { return OFFSET (pev); }; + inline int GetIndex (void) { return ENTINDEX (GetEntity ()); }; + + inline Vector Center (void) { return (pev->absmax + pev->absmin) * 0.5; }; + inline Vector EyePosition (void) { return pev->origin + pev->view_ofs; }; + + void Think (void); + void NewRound (void); + void EquipInBuyzone (int buyCount); + void PushMessageQueue (int message); + void PrepareChatMessage (char *text); + bool FindWaypoint (void); + bool EntityIsVisible (const Vector &dest, bool fromBody = false); + + void DeleteSearchNodes (void); + + void RemoveCertainTask (TaskId_t id); + void StartTask (TaskId_t id, float desire, int data, float time, bool canContinue); + void ResetTasks (void); + TaskItem *GetTask (void); + inline TaskId_t GetTaskId (void) { return GetTask ()->id; }; + + void TakeDamage (edict_t *inflictor, int damage, int armor, int bits); + void TakeBlinded (const Vector &fade, int alpha); + + void DiscardWeaponForUser (edict_t *user, bool discardC4); + + void SayText (const char *text); + void TeamSayText (const char *text); + + void ChatMessage (int type, bool isTeamSay = false); + void RadioMessage (int message); + void ChatterMessage (int message); + void HandleChatterMessage (const char *sz); + + void Kill (void); + void Kick (void); + void ResetDoubleJumpState (void); + void MoveToVector (Vector to); + int FindPlantedBomb(void); + int FindLoosedBomb (void); + + bool HasHostage (void); + bool UsesRifle (void); + bool UsesPistol (void); + bool UsesSniper (void); + bool UsesSubmachineGun (void); + bool UsesZoomableRifle (void); + bool UsesBadPrimary (void); + bool HasPrimaryWeapon (void); + bool HasSecondaryWeapon(void); + bool HasShield (void); + bool IsShieldDrawn (void); + + void ReleaseUsedName (void); +}; + +// manager class +class BotManager : public Singleton +{ +private: + Array m_creationTab; // bot creation tab + Bot *m_bots[32]; // all available bots + float m_maintainTime; // time to maintain bot creation quota + int m_lastWinner; // the team who won previous round + int m_roundCount; // rounds passed + bool m_economicsGood[2]; // is team able to buy anything + +protected: + int CreateBot (String name, int skill, int personality, int team, int member); + +public: + BotManager (void); + ~BotManager (void); + + bool EconomicsValid (int team) { return m_economicsGood[team]; } + + int GetLastWinner (void) const { return m_lastWinner; } + void SetLastWinner (int winner) { m_lastWinner = winner; } + + int GetIndex (edict_t *ent); + Bot *GetBot (int index); + Bot *GetBot (edict_t *ent); + Bot *FindOneValidAliveBot (void); + Bot *GetHighestFragsBot (int team); + + int GetHumansNum (void); + int GetHumansAliveNum(void); + int GetBotsNum (void); + + void Think (void); + void Free (void); + void Free (int index); + void CheckAutoVacate (edict_t *ent); + + void AddRandom (void) { AddBot ("", -1, -1, -1, -1); } + void AddBot (const String &name, int skill, int personality, int team, int member); + void AddBot (String name, String skill, String personality, String team, String member); + void FillServer (int selection, int personality = PERSONALITY_NORMAL, int skill = -1, int numToAdd = -1); + + void RemoveAll (void); + void RemoveRandom (void); + void RemoveFromTeam (Team team, bool removeAll = false); + void RemoveMenu (edict_t *ent, int selection); + void KillAll (int team = -1); + void MaintainBotQuota (void); + void InitQuota (void); + + void ListBots (void); + void SetWeaponMode (int selection); + void CheckTeamEconomics (int team); + + static void CallGameEntity (entvars_t *vars); +}; + +// texts localizer +class Localizer : public Singleton +{ +public: + Array m_langTab; + +public: + Localizer (void) { m_langTab.RemoveAll (); } + ~Localizer (void) { m_langTab.RemoveAll (); } + + char *TranslateInput (const char *input); +}; + +// netmessage handler class +class NetworkMsg : public Singleton +{ +private: + Bot *m_bot; + int m_state; + int m_message; + int m_registerdMessages[NETMSG_BOTVOICE + 1]; + +public: + NetworkMsg (void); + ~NetworkMsg (void) { }; + + void Execute (void *p); + void Reset (void) { m_message = NETMSG_UNDEFINED; m_state = 0; m_bot = NULL; }; + void HandleMessageIfRequired (int messageType, int requiredType); + + void SetMessage (int message) { m_message = message; } + void SetBot (Bot *bot) { m_bot = bot; } + + int GetId (int messageType) { return m_registerdMessages[messageType]; } + void SetId (int messageType, int messsageIdentifier) { m_registerdMessages[messageType] = messsageIdentifier; } +}; + +// waypoint operation class +class Waypoint : public Singleton +{ + friend class Bot; + +private: + Path *m_paths[MAX_WAYPOINTS]; + + bool m_waypointPaths; + bool m_isOnLadder; + bool m_endJumpPoint; + bool m_learnJumpWaypoint; + float m_timeJumpStarted; + + Vector m_learnVelocity; + Vector m_learnPosition; + Vector m_foundBombOrigin; + + int m_cacheWaypointIndex; + int m_lastJumpWaypoint; + int m_visibilityIndex; + Vector m_lastWaypoint; + byte m_visLUT[MAX_WAYPOINTS][MAX_WAYPOINTS / 4]; + + float m_pathDisplayTime; + float m_arrowDisplayTime; + float m_waypointDisplayTime[MAX_WAYPOINTS]; + float m_goalsScore[MAX_WAYPOINTS]; + int m_findWPIndex; + int m_facingAtIndex; + char m_infoBuffer[256]; + + int *m_distMatrix; + int *m_pathMatrix; + + Array m_terrorPoints; + Array m_ctPoints; + Array m_goalPoints; + Array m_campPoints; + Array m_sniperPoints; + Array m_rescuePoints; + Array m_visitedGoals; + +public: + bool m_redoneVisibility; + + Waypoint (void); + ~Waypoint (void); + + void Init (void); + void InitExperienceTab (void); + void InitVisibilityTab (void); + + void InitTypes (void); + void AddPath (short int addIndex, short int pathIndex, float distance); + + int GetFacingIndex (void); + int FindFarest (Vector origin, float maxDistance = 32.0); + int FindNearest (Vector origin, float minDistance = 9999.0, int flags = -1); + void FindInRadius (Vector origin, float radius, int *holdTab, int *count); + void FindInRadius (Array &queueID, float radius, Vector origin); + + void Add (int flags, const Vector &waypointOrigin = nullvec); + void Delete (void); + void ToggleFlags (int toggleFlag); + void SetRadius (int radius); + bool IsConnected (int pointA, int pointB); + bool IsConnected (int num); + void InitializeVisibility (void); + void CreatePath (char dir); + void DeletePath (void); + void CacheWaypoint (void); + + float GetTravelTime (float maxSpeed, Vector src, Vector origin); + bool IsVisible (int srcIndex, int destIndex); + bool IsStandVisible (int srcIndex, int destIndex); + bool IsDuckVisible (int srcIndex, int destIndex); + void CalculateWayzone (int index); + + bool Load (void); + void Save (void); + + bool Reachable (Bot *bot, int index); + bool IsNodeReachable (Vector src, Vector destination); + void Think (void); + bool NodesValid (void); + void SaveExperienceTab (void); + void SaveVisibilityTab (void); + void CreateBasic (void); + void EraseFromHardDisk (void); + + void InitPathMatrix (void); + void SavePathMatrix (void); + bool LoadPathMatrix (void); + + int GetPathDistance (int srcIndex, int destIndex); + Path *GetPath (int id); + char *GetWaypointInfo (int id); + char *GetInfo (void) { return m_infoBuffer; } + + int AddGoalScore (int index, int other[4]); + void SetFindIndex (int index); + void SetLearnJumpWaypoint (void); + void ClearGoalScore (void); + + bool IsGoalVisited (int index); + void SetGoalVisited (int index); + + inline Vector GetBombPosition (void) { return m_foundBombOrigin; } + void SetBombPosition (bool shouldReset = false); + String CheckSubfolderFile (void); +}; + +// wayponit auto-downloader +enum WaypointDownloadError +{ + WDE_SOCKET_ERROR, + WDE_CONNECT_ERROR, + WDE_NOTFOUND_ERROR, + WDE_NOERROR +}; + +class WaypointDownloader +{ +public: + + // free's socket handle + void FreeSocket (int sock); + + // do actually downloading of waypoint file + WaypointDownloadError DoDownload (void); +}; + +enum VarType +{ + VT_NORMAL = 0, + VT_READONLY, + VT_PASSWORD, + VT_NOREGISTER +}; + + +class ConVarWrapper : public Singleton +{ +private: + struct VarPair + { + VarType type; + cvar_t reg; + class ConVar *self; + } m_regVars[101]; + + int m_regCount; + +public: + void RegisterVariable (const char *variable, const char *value, VarType varType, ConVar *self); + void PushRegisteredConVarsToEngine (bool gameVars = false); +}; + +#define g_netMsg NetworkMsg::GetObject () +#define g_botManager BotManager::GetObject () +#define g_localizer Localizer::GetObject () +#define g_convarWrapper ConVarWrapper::GetObject () +#define g_waypoint Waypoint::GetObject () + +// simplify access for console variables +class ConVar +{ +public: + cvar_t *m_eptr; + +public: + ConVar (const char *name, const char *initval, VarType type = VT_NORMAL) + { + g_convarWrapper->RegisterVariable (name, initval, type, this); + } + + inline bool GetBool(void) + { + return m_eptr->value > 0.0f; + } + + inline int GetInt (void) + { + return static_cast (m_eptr->value); + } + + inline int GetFlags (void) + { + return m_eptr->flags; + } + + inline float GetFloat (void) + { + return m_eptr->value; + } + + inline const char *GetString (void) + { + return m_eptr->strval; + } + + inline const char *GetName(void) + { + return m_eptr->name; + } + + inline void SetFloat(float val) + { + g_engfuncs.pfnCVarSetFloat(m_eptr->name, val); + } + + inline void SetInt (int val) + { + SetFloat (static_cast (val)); + } + + inline void SetString (const char *val) + { + g_engfuncs.pfnCVarSetString (m_eptr->name, val); + } +}; + +// prototypes of bot functions... +extern int GetMaxClients (void); +extern int GetWeaponReturn (bool isString, const char *weaponAlias, int weaponIndex = -1); +extern int GetTeam (edict_t *ent); + +extern float GetShootingConeDeviation (edict_t *ent, Vector *position); +extern float GetWorldTime (void); +extern float GetWaveLength (const char *fileName); + +extern bool TryFileOpen (char *fileName); +extern bool IsDedicatedServer (void); +extern bool IsVisible (const Vector &origin, edict_t *ent); +extern bool IsAlive (edict_t *ent); +extern bool IsInViewCone (Vector origin, edict_t *ent); +extern bool IsWeaponShootingThroughWall (int id); +extern bool IsValidBot (edict_t *ent); +extern bool IsValidPlayer (edict_t *ent); +extern bool OpenConfig (const char *fileName, char *errorIfNotExists, File *outFile, bool languageDependant = false); +extern bool FindNearestPlayer (void **holder, edict_t *to, float searchDistance = 4096.0, bool sameTeam = false, bool needBot = false, bool needAlive = false, bool needDrawn = false); + +extern const char *GetMapName (void); +extern const char *GetWaypointDir (void); +extern const char *GetModName (void); +extern const char *GetField (const char *string, int fieldId, bool endLine = false); +extern char *FormatBuffer (char *format, ...); + +extern uint16 GenerateBuildNumber (void); +extern Vector GetEntityOrigin (edict_t *ent); + +extern void FreeLibraryMemory (void); +extern void RoundInit (void); +extern void FakeClientCommand (edict_t *fakeClient, const char *format, ...); +extern void strtrim (char *string); +extern void CreatePath (char *path); +extern void ServerCommand (const char *format, ...); +extern void RegisterCommand (char *command, void funcPtr (void)); +extern void CheckWelcomeMessage (void); +extern void DetectCSVersion (void); +extern void PlaySound (edict_t *ent, const char *soundName); +extern void ServerPrint (const char *format, ...); +extern void ChartPrint (const char *format, ...); +extern void ServerPrintNoTag (const char *format, ...); +extern void CenterPrint (const char *format, ...); +extern void ClientPrint (edict_t *ent, int dest, const char *format, ...); +extern void HudMessage (edict_t *ent, bool toCenter, Vector rgb, char *format, ...); + +extern void AddLogEntry (bool outputToConsole, int logLevel, const char *format, ...); +extern void TraceLine (const Vector &start, const Vector &end, bool ignoreMonsters, bool ignoreGlass, edict_t *ignoreEntity, TraceResult *ptr); +extern void TraceLine (const Vector &start, const Vector &end, bool ignoreMonsters, edict_t *ignoreEntity, TraceResult *ptr); +extern void TraceHull (const Vector &start, const Vector &end, bool ignoreMonsters, int hullNumber, edict_t *ignoreEntity, TraceResult *ptr); +extern void DrawLine (edict_t *ent, const Vector &start, const Vector &end, int width, int noise, int red, int green, int blue, int brightness, int speed, int life); +extern void DrawArrow (edict_t *ent, const Vector &start, const Vector &end, int width, int noise, int red, int green, int blue, int brightness, int speed, int life); +extern void DisplayMenuToClient (edict_t *ent, MenuText *menu); +extern void DecalTrace (entvars_t *pev, TraceResult *trace, int logotypeIndex); +extern void SoundAttachToThreat (edict_t *ent, const char *sample, float volume); +extern void SoundSimulateUpdate (int playerIndex); + +static inline bool IsNullString (const char *input) +{ + if (input == NULL) + return true; + + return *input == '\0'; +} + +// very global convars +extern ConVar yb_jasonmode; +extern ConVar yb_communication_type; +extern ConVar yb_hardcore_mode; +extern ConVar yb_csdm_mode; +extern ConVar yb_ignore_enemies; + +#include +#include +#include + +#endif // YAPB_INCLUDED diff --git a/include/corelib.h b/include/corelib.h new file mode 100644 index 0000000..8ec6af4 --- /dev/null +++ b/include/corelib.h @@ -0,0 +1,4168 @@ +// +// Copyright (c) 2014, by YaPB Development Team. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// Version: $Id:$ +// + +#ifndef __CORE_TOOL_LIBRARY__ +#define __CORE_TOOL_LIBRARY__ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#pragma warning (disable : 4100 4189 4239 4996 4244 383 473 981) + +// +// Title: Utility Classes Header +// + +// +// Function: Hash +// Hash template for , +// +template unsigned int Hash (const T &); + +// +// Function: Hash +// Hash for integer. +// +// Parameters: +// tag - Value that should be hashed. +// +// Returns: +// Hashed value. +// +template unsigned int Hash (const int &tag) +{ + unsigned int key = (unsigned int) tag; + + key += ~(key << 16); + key ^= (key >> 5); + + key += (key << 3); + key ^= (key >> 13); + + key += ~(key << 9); + key ^= (key >> 17); + + return key; +} + +// +// Namespace: Math +// Some math utility functions. +// +namespace Math +{ + const float MATH_ONEPSILON = 0.01f; + const float MATH_EQEPSILON = 0.001f; + const float MATH_FLEPSILON = 1.192092896e-07f; + + // + // Constant: MATH_PI + // Mathematical PI value. + // + const float MATH_PI = 3.1415926f; + + const float MATH_D2R = MATH_PI / 180.0f; + const float MATH_R2D = 180.0f / MATH_PI; + + // + // Function: FltZero + // + // Checks whether input entry float is zero. + // + // Parameters: + // entry - Input float. + // + // Returns: + // True if float is zero, false otherwise. + // + // See Also: + // + // + // Remarks: + // This eliminates Intel C++ Compiler's warning about float equality/inquality. + // + static inline bool FltZero (float entry) + { + return fabsf (entry) < MATH_ONEPSILON; + } + + // + // Function: FltEqual + // + // Checks whether input floats are equal. + // + // Parameters: + // entry1 - First entry float. + // entry2 - Second entry float. + // + // Returns: + // True if floats are equal, false otherwise. + // + // See Also: + // + // + // Remarks: + // This eliminates Intel C++ Compiler's warning about float equality/inquality. + // + static inline bool FltEqual (float entry1, float entry2) + { + return fabsf (entry1 - entry2) < MATH_EQEPSILON; + } + + // + // Function: RadianToDegree + // + // Converts radians to degrees. + // + // Parameters: + // radian - Input radian. + // + // Returns: + // Degree converted from radian. + // + // See Also: + // + // + static inline float RadianToDegree (float radian) + { + return radian * MATH_R2D; + } + + // + // Function: DegreeToRadian + // + // Converts degrees to radians. + // + // Parameters: + // degree - Input degree. + // + // Returns: + // Radian converted from degree. + // + // See Also: + // + // + static inline float DegreeToRadian (float degree) + { + return degree * MATH_D2R; + } + + // + // Function: AngleMod + // + // Adds or subtracts 360.0f enough times need to given angle in order to set it into the range [0.0, 360.0f). + // + // Parameters: + // angle - Input angle. + // + // Returns: + // Resulting angle. + // + static inline float AngleMod (float angle) + { + return 360.0f / 65536.0f * (static_cast (angle * (65536.0f / 360.0f)) & 65535); + } + + // + // Function: AngleNormalize + // + // Adds or subtracts 360.0f enough times need to given angle in order to set it into the range [-180.0, 180.0f). + // + // Parameters: + // angle - Input angle. + // + // Returns: + // Resulting angle. + // + static inline float AngleNormalize (float angle) + { + return 360.0f / 65536.0f * (static_cast ((angle + 180.0f) * (65536.0f / 360.0f)) & 65535) - 180.0f; + } + + + // + // Function: SineCosine + // Very fast platform-dependent sine and cosine calculation routine. + // + // Parameters: + // rad - Input degree. + // sin - Output for Sine. + // cos - Output for Cosine. + // + static inline void SineCosine (float rad, float *sin, float *cos) + { +#if defined (_WIN32) && defined (_MSC_VER) + __asm + { + fld dword ptr[rad] + fsincos + mov edx, dword ptr[cos] + mov eax, dword ptr[sin] + fstp dword ptr[edx] + fstp dword ptr[eax] + } +#elif defined (__linux__) || defined (GCC) || defined (__APPLE__) + register double _cos, _sin; + __asm __volatile__ ("fsincos" : "=t" (_cos), "=u" (_sin) : "0" (rad)); + + *cos = _cos; + *sin = _sin; +#else + *sin = sinf (rad); + *cos = cosf (rad); +#endif + } + + inline float AngleDiff (float destAngle, float srcAngle) + { + return AngleNormalize (destAngle - srcAngle); + } +} + +// +// Class: RandGen +// Random number generator used by the bot code. +// +class RandGen +{ +private: + enum GenerationConstants_t + { KK = 17, JJ = 10, R1 = 19, R2 = 27 }; + + int m_bufferIndex1; + int m_bufferIndex2; + + union + { + long double m_randomPtr; + uint32 m_randomBits[3]; + }; + + uint32 m_historyBuffer[KK][2]; + uint32 m_selfTestBuffer[KK * 2][2]; + +private: + + // + // Function: Random + // Generates random number. + // + long double Random (void); + + // + // Function: GetRandomBits + // Generates random bits for random number generator. + // + uint32 GetRandomBits (void); + +public: + + // + // Function: Initialize + // Initializes random number generator using specified seed. + // + // Parameters: + // seed - Seed for the random generator. + // + void Initialize (uint32 seed); + + // + // Function: Long + // Generates random 32bit long random number between specified bounds. + // + // Parameters: + // low - Lowest number. + // high - Higher number. + // + int Long (int low, int high); + + // + // Function: Float + // Generates random 32bit float random number between specified bounds. + // + // Parameters: + // low - Lowest number. + // high - Higher number. + // + float Float (float low, float high); +}; + +// +// Class: Vector +// Standard 3-dimensional vector. +// +class Vector +{ + // + // Group: Variables. + // +public: + // + // Variable: x,y,z + // X, Y and Z axis members. + // + float x, y, z; + + // + // Group: (Con/De)structors. + // +public: + // + // Function: Vector + // + // Constructs Vector from float, and assign it's value to all axises. + // + // Parameters: + // scaler - Value for axises. + // + inline Vector (float scaler = 0.0f) : x (scaler), y (scaler), z (scaler) + { + } + + // + // Function: Vector + // + // Standard Vector Constructor. + // + // Parameters: + // inputX - Input X axis. + // inputY - Input Y axis. + // inputZ - Input Z axis. + // + inline Vector (float inputX, float inputY, float inputZ) : x (inputX), y (inputY), z (inputZ) + { + } + + // + // Function: Vector + // + // Constructs Vector from float pointer. + // + // Parameters: + // other - Float pointer. + // + inline Vector (float *other) : x (other[0]), y (other[1]), z (other[2]) + { + } + + // + // Function: Vector + // + // Constructs Vector from another Vector. + // + // Parameters: + // right - Other Vector, that should be assigned. + // + inline Vector (const Vector &right) : x (right.x), y (right.y), z (right.z) + { + } + // + // Group: Operators. + // +public: + inline operator float * (void) + { + return &x; + } + + inline operator const float * (void) const + { + return &x; + } + + inline float &operator [] (int index) + { + return (&x)[index]; + } + + inline const float &operator [] (int index) const + { + return (&x)[index]; + } + + inline const Vector operator + (const Vector &right) const + { + return Vector (x + right.x, y + right.y, z + right.z); + } + + inline const Vector operator - (const Vector &right) const + { + return Vector (x - right.x, y - right.y, z - right.z); + } + + inline const Vector operator - (void) const + { + return Vector (-x, -y, -z); + } + + friend inline const Vector operator * (const float vec, const Vector &right) + { + return Vector (right.x * vec, right.y * vec, right.z * vec); + } + + inline const Vector operator * (float vec) const + { + return Vector (vec * x, vec * y, vec * z); + } + + inline const Vector operator / (float vec) const + { + const float inv = 1 / vec; + return Vector (inv * x, inv * y, inv * z); + } + + inline const Vector operator ^ (const Vector &right) const + { + return Vector (y * right.z - z * right.y, z * right.x - x * right.z, x * right.y - y * right.x); + } + + inline float operator | (const Vector &right) const + { + return x * right.x + y * right.y + z * right.z; + } + + inline const Vector &operator += (const Vector &right) + { + x += right.x; + y += right.y; + z += right.z; + + return *this; + } + + inline const Vector &operator -= (const Vector &right) + { + x -= right.x; + y -= right.y; + z -= right.z; + + return *this; + } + + inline const Vector &operator *= (float vec) + { + x *= vec; + y *= vec; + z *= vec; + + return *this; + } + + inline const Vector &operator /= (float vec) + { + const float inv = 1 / vec; + + x *= inv; + y *= inv; + z *= inv; + + return *this; + } + + inline bool operator == (const Vector &right) const + { + return Math::FltEqual (x, right.x) && Math::FltEqual (y, right.y) && Math::FltEqual (z, right.z); + } + + inline bool operator != (const Vector &right) const + { + return !Math::FltEqual (x, right.x) && !Math::FltEqual (y, right.y) && !Math::FltEqual (z, right.z); + } + + inline const Vector &operator = (const Vector &right) + { + x = right.x; + y = right.y; + z = right.z; + + return *this; + } + // + // Group: Functions. + // +public: + // + // Function: GetLength + // + // Gets length (magnitude) of 3D vector. + // + // Returns: + // Length (magnitude) of the 3D vector. + // + // See Also: + // + // + inline float GetLength (void) const + { + return sqrtf (x * x + y * y + z * z); + } + + // + // Function: GetLength2D + // + // Gets length (magnitude) of vector ignoring Z axis. + // + // Returns: + // 2D length (magnitude) of the vector. + // + // See Also: + // + // + inline float GetLength2D (void) const + { + return sqrtf (x * x + y * y); + } + + // + // Function: GetLengthSquared + // + // Gets squared length (magnitude) of 3D vector. + // + // Returns: + // Squared length (magnitude) of the 3D vector. + // + // See Also: + // + // + inline float GetLengthSquared (void) const + { + return x * x + y * y + z * z; + } + + // + // Function: GetLengthSquared2D + // + // Gets squared length (magnitude) of vector ignoring Z axis. + // + // Returns: + // 2D squared length (magnitude) of the vector. + // + // See Also: + // + // + inline float GetLengthSquared2D (void) const + { + return x * x + y * y; + } + + // + // Function: SkipZ + // + // Gets vector without Z axis. + // + // Returns: + // 2D vector from 3D vector. + // + inline Vector SkipZ (void) const + { + return Vector (x, y, 0.0f); + } + + // + // Function: Normalize + // + // Normalizes a vector. + // + // Returns: + // The previous length of the 3D vector. + // + inline Vector Normalize (void) const + { + float length = GetLength () + static_cast (Math::MATH_FLEPSILON); + + if (Math::FltZero (length)) + return Vector (0, 0, 1.0f); + + length = 1.0f / length; + + return Vector (x * length, y * length, z * length); + } + + // + // Function: Normalize + // + // Normalizes a 2D vector. + // + // Returns: + // The previous length of the 2D vector. + // + inline Vector Normalize2D (void) const + { + float length = GetLength2D () + static_cast (Math::MATH_FLEPSILON); + + if (Math::FltZero (length)) + return Vector (0, 1.0, 0); + + length = 1.0f / length; + + return Vector (x * length, y * length, 0.0f); + } + + // + // Function: IsNull + // + // Checks whether vector is null. + // + // Returns: + // True if this vector is (0.0f, 0.0f, 0.0f) within tolerance, false otherwise. + // + inline bool IsNull (void) const + { + return Math::FltZero (x) && Math::FltZero (y) && Math::FltZero (z); + } + + // + // Function: GetNull + // + // Gets a nulled vector. + // + // Returns: + // Nulled vector. + // + inline static const Vector &GetNull (void) + { + static const Vector &s_null = Vector (0.0, 0.0, 0.0f); + return s_null; + } + + // + // Function: ClampAngles + // + // Clamps the angles (ignore Z component). + // + // Returns: + // 3D vector with clamped angles (ignore Z component). + // + inline Vector ClampAngles (void) + { + x = Math::AngleNormalize (x); + y = Math::AngleNormalize (y); + z = 0.0f; + + return *this; + } + + // + // Function: ToPitch + // + // Converts a spatial location determined by the vector passed into an absolute X angle (pitch) from the origin of the world. + // + // Returns: + // Pitch angle. + // + inline float ToPitch (void) const + { + if (Math::FltZero (x) && Math::FltZero (y)) + return 0.0f; + + return Math::RadianToDegree (atan2f (z, GetLength2D ())); + } + + // + // Function: ToYaw + // + // Converts a spatial location determined by the vector passed into an absolute Y angle (yaw) from the origin of the world. + // + // Returns: + // Yaw angle. + // + inline float ToYaw (void) const + { + if (Math::FltZero (x) && Math::FltZero (y)) + return 0.0f; + + return Math::RadianToDegree (atan2f (y, x)); + } + + // + // Function: ToAngles + // + // Convert a spatial location determined by the vector passed in into constant absolute angles from the origin of the world. + // + // Returns: + // Converted from vector, constant angles. + // + inline Vector ToAngles (void) const + { + // is the input vector absolutely vertical? + if (Math::FltZero (x) && Math::FltZero (y)) + return Vector (z > 0.0f ? 90.0f : 270.0f, 0.0, 0.0f); + + // else it's another sort of vector compute individually the pitch and yaw corresponding to this vector. + return Vector (Math::RadianToDegree (atan2f (z, GetLength2D ())), Math::RadianToDegree (atan2f (y, x)), 0.0f); + } + + // + // Function: BuildVectors + // + // Builds a 3D referential from a view angle, that is to say, the relative "forward", "right" and "upward" direction + // that a player would have if he were facing this view angle. World angles are stored in Vector structs too, the + // "x" component corresponding to the X angle (horizontal angle), and the "y" component corresponding to the Y angle + // (vertical angle). + // + // Parameters: + // forward - Forward referential vector. + // right - Right referential vector. + // upward - Upward referential vector. + // + inline void BuildVectors (Vector *forward, Vector *right, Vector *upward) const + { + float sinePitch = 0.0f, cosinePitch = 0.0f, sineYaw = 0.0f, cosineYaw = 0.0f, sineRoll = 0.0f, cosineRoll = 0.0f; + + Math::SineCosine (Math::DegreeToRadian (x), &sinePitch, &cosinePitch); // compute the sine and cosine of the pitch component + Math::SineCosine (Math::DegreeToRadian (y), &sineYaw, &cosineYaw); // compute the sine and cosine of the yaw component + Math::SineCosine (Math::DegreeToRadian (z), &sineRoll, &cosineRoll); // compute the sine and cosine of the roll component + + if (forward != NULL) + { + forward->x = cosinePitch * cosineYaw; + forward->y = cosinePitch * sineYaw; + forward->z = -sinePitch; + } + + if (right != NULL) + { + right->x = -sineRoll * sinePitch * cosineYaw + cosineRoll * sineYaw; + right->y = -sineRoll * sinePitch * sineYaw - cosineRoll * cosineYaw; + right->z = -sineRoll * cosinePitch; + } + + if (upward != NULL) + { + upward->x = cosineRoll * sinePitch * cosineYaw + sineRoll * sineYaw; + upward->y = cosineRoll * sinePitch * sineYaw - sineRoll * cosineYaw; + upward->z = cosineRoll * cosinePitch; + } + } +}; + +static const Vector &nullvec = Vector::GetNull (); + + +// +// Class: List +// Simple linked list container. +// +template class List +{ +public: + template class Node + { + public: + Node *m_next; + Node *m_prev; + T *m_data; + + public: + Node (void) + { + m_next = NULL; + m_prev = NULL; + m_data = NULL; + } + }; + +private: + Node *m_first; + Node *m_last; + int m_count; + +// +// Group: (Con/De)structors +public: + List (void) + { + Destory (); + } + + virtual ~List (void) { }; + +// +// Group: Functions +// +public: + + // + // Function: Destory + // Resets list to empty state by abandoning contents. + // + void Destory (void) + { + m_first = NULL; + m_last = NULL; + m_count = 0; + } + + // + // Function: GetSize + // Gets the number of elements in linked list. + // + // Returns: + // Number of elements in list. + // + inline int GetSize (void) const + { + return m_count; + } + + // + // Function: GetFirst + // Gets the first list entry. NULL in case list is empty. + // + // Returns: + // First list entry. + // + inline T *GetFirst (void) const + { + if (m_first != NULL) + return m_first->m_data; + + return NULL; + }; + + // + // Function: GetLast + // Gets the last list entry. NULL in case list is empty. + // + // Returns: + // Last list entry. + // + inline T *GetLast (void) const + { + if (m_last != NULL) + return m_last->m_data; + + return NULL; + }; + + // + // Function: GetNext + // Gets the next element from linked list. + // + // Parameters: + // current - Current node. + // + // Returns: + // Node data. + // + T *GetNext (T *current) + { + if (current == NULL) + return GetFirst (); + + Node *next = FindNode (current)->m_next; + + if (next != NULL) + return next->m_data; + + return NULL; + } + + // + // Function: GetPrev + // Gets the previous element from linked list. + // + // Parameters: + // current - Current node. + // + // Returns: + // Node data. + // + T *GetPrev (T *current) + { + if (current == NULL) + return GetLast (); + + Node *prev = FindNode (current)->m_prev; + + if (prev != NULL) + return prev->m_prev; + + return NULL; + } + + // + // Function: Link + // Adds item to linked list. + // + // Parameters: + // entry - Node that should be inserted in linked list. + // next - Next node to be inserted into linked list. + // + // Returns: + // Item if operation success, NULL otherwise. + // + T *Link (T *entry, T *next = NULL) + { + Node *prevNode = NULL; + Node *nextNode = FindNode (next); + Node *newNode = new Node (); + + newNode->m_data = entry; + + if (nextNode == NULL) + { + prevNode = m_last; + m_last = newNode; + } + else + { + prevNode = nextNode->m_prev; + nextNode->m_prev = newNode; + } + + if (prevNode == NULL) + m_first = newNode; + else + prevNode->m_next = newNode; + + newNode->m_next = nextNode; + newNode->m_prev = prevNode; + + m_count++; + + return entry; + } + + // + // Function: Link + // Adds item to linked list (as reference). + // + // Parameters: + // entry - Node that should be inserted in linked list. + // next - Next node to be inserted into linked list. + // + // Returns: + // Item if operation success, NULL otherwise. + // + T *Link (T &entry, T *next = NULL) + { + T *newEntry = new T (); + *newEntry = entry; + + return Link (newEntry, next); + } + + // + // Function: Unlink + // Removes element from linked list. + // + // Parameters: + // entry - Element that should be moved out of list. + // + void Unlink (T *entry) + { + Node *entryNode = FindNode (entry); + + if (entryNode == NULL) + return; + + if (entryNode->m_prev == NULL) + m_first = entryNode->m_next; + else + entryNode->m_prev->m_next = entryNode->m_next; + + if (entryNode->m_next == NULL) + m_last = entryNode->m_prev; + else + entryNode->m_next->m_prev = entryNode->m_prev; + + delete entryNode; + m_count--; + } + + // + // Function: Allocate + // Inserts item into linked list, and allocating it automatically. + // + // Parameters: + // next - Optional next element. + // + // Returns: + // Item that was inserted. + // + T *Allocate (T *next = NULL) + { + T *entry = new T (); + + if (entry == NULL) + return NULL; + + return Link (entry, next); + } + + // + // Function: Destory + // Removes element from list, and destroys it. + // + // Parameters: + // entry - Entry to perform operation on. + // + void Destory (T *entry) + { + Unlink (entry); + delete entry; + } + + // + // Function: RemoveAll + // Removes all elements from list, and destroys them. + // + void RemoveAll (void) + { + Node *node = NULL; + + while ((node = GetIterator (node)) != NULL) + { + Node *nodeToKill = node; + node = node->m_prev; + + free (nodeToKill->m_data); + } + } + + // + // Function: FindNode + // Find node by it's entry. + // + // Parameters: + // entry - Entry to search. + // + // Returns: + // Node pointer. + // + Node *FindNode (T *entry) + { + Node *iter = NULL; + + while ((iter = GetIterator (iter)) != NULL) + { + if (iter->m_data == entry) + return iter; + } + return NULL; + } + + // + // Function: GetIterator + // Utility node iterator. + // + // Parameters: + // entry - Previous entry. + // + // Returns: + // Node pointer. + // + Node *GetIterator (Node *entry = NULL) + { + if (entry == NULL) + return m_first; + + return entry->m_next; + } +}; + +// +// Class: Array +// Universal template array container. +// +template class Array +{ +private: + T *m_elements; + T m_failed; + +private: + int m_resizeStep; + int m_itemSize; + int m_itemCount; + +// +// Group: (Con/De)structors +// +public: + + // + // Function: Array + // Default array constructor. + // + // Parameters: + // resizeStep - Array resize step, when new items added, or old deleted. + // + Array (int resizeStep = 0) + { + m_elements = NULL; + m_itemSize = 0; + m_itemCount = 0; + m_resizeStep = resizeStep; + + m_failed = T (); + } + + // + // Function: Array + // Array copying constructor. + // + // Parameters: + // other - Other array that should be assigned to this one. + // + Array (const Array &other) + { + m_elements = NULL; + m_itemSize = 0; + m_itemCount = 0; + m_resizeStep = 0; + m_failed = T (); + + AssignFrom (other); + } + + // + // Function: ~Array + // Default array destructor. + // + virtual ~Array(void) + { + Destory (); + } + +// +// Group: Functions +// +public: + + // + // Function: Destory + // Destroys array object, and all elements. + // + void Destory (void) + { + if (m_elements != NULL) + delete [] m_elements; + + m_elements = NULL; + m_itemSize = 0; + m_itemCount = 0; + } + + // + // Function: SetSize + // Sets the size of the array. + // + // Parameters: + // newSize - Size to what array should be resized. + // keepData - Keep exiting data, while resizing array or not. + // + // Returns: + // True if operation succeeded, false otherwise. + // + bool SetSize (int newSize, bool keepData = true) + { + if (newSize == 0) + { + Destory (); + return true; + } + + int checkSize = 0; + + if (m_resizeStep != 0) + checkSize = m_itemCount + m_resizeStep; + else + { + checkSize = m_itemCount / 8; + + if (checkSize < 4) + checkSize = 4; + + if (checkSize > 1024) + checkSize = 1024; + + checkSize += m_itemCount; + } + + if (newSize > checkSize) + checkSize = newSize; + + T *buffer = new T[checkSize]; + + if (buffer == NULL) + return false; + + if (keepData && m_elements != NULL) + { + if (checkSize < m_itemCount) + m_itemCount = checkSize; + + for (int i = 0; i < m_itemCount; i++) + buffer[i] = m_elements[i]; + } + delete [] m_elements; + + m_elements = buffer; + m_itemSize = checkSize; + + return true; + } + + // + // Function: GetSize + // Gets allocated size of array. + // + // Returns: + // Number of allocated items. + // + int GetSize (void) + { + return m_itemSize; + } + + // + // Function: GetElementNumber + // Gets real number currently in array. + // + // Returns: + // Number of elements. + // + int GetElementNumber (void) + { + return m_itemCount; + } + + // + // Function: SetEnlargeStep + // Sets step, which used while resizing array data. + // + // Parameters: + // resizeStep - Step that should be set. + // + void SetEnlargeStep (int resizeStep = 0) + { + m_resizeStep = resizeStep; + } + + // + // Function: GetEnlargeStep + // Gets the current enlarge step. + // + // Returns: + // Current resize step. + // + int GetEnlargeStep (void) + { + return m_resizeStep; + } + + // + // Function: SetAt + // Sets element data, at specified index. + // + // Parameters: + // index - Index where object should be assigned. + // object - Object that should be assigned. + // enlarge - Checks whether array must be resized in case, allocated size + enlarge step is exceeded. + // + // Returns: + // True if operation succeeded, false otherwise. + // + bool SetAt (int index, T object, bool enlarge = true) + { + if (index >= m_itemSize) + { + if (!enlarge || !SetSize (index + 1)) + return false; + } + m_elements[index] = object; + + if (index >= m_itemCount) + m_itemCount = index + 1; + + return true; + } + + // + // Function: GetAt + // Gets element from specified index + // + // Parameters: + // index - Element index to retrieve. + // + // Returns: + // Element object. + // + T &GetAt (int index) + { + if (index >= m_itemCount) + { + m_failed = T (); + return m_failed; + } + return m_elements[index]; + } + + // + // Function: GetAt + // Gets element at specified index, and store it in reference object. + // + // Parameters: + // index - Element index to retrieve. + // object - Holder for element reference. + // + // Returns: + // True if operation succeeded, false otherwise. + // + bool GetAt (int index, T &object) + { + if (index >= m_itemCount) + return false; + + object = m_elements[index]; + return true; + } + + // + // Function: InsertAt + // Inserts new element at specified index. + // + // Parameters: + // index - Index where element should be inserted. + // object - Object that should be inserted. + // enlarge - Checks whether array must be resized in case, allocated size + enlarge step is exceeded. + // + // Returns: + // True if operation succeeded, false otherwise. + // + bool InsertAt (int index, T object, bool enlarge = true) + { + return InsertAt (index, &object, 1, enlarge); + } + + // + // Function: InsertAt + // Inserts number of element at specified index. + // + // Parameters: + // index - Index where element should be inserted. + // objects - Pointer to object list. + // count - Number of element to insert. + // enlarge - Checks whether array must be resized in case, allocated size + enlarge step is exceeded. + // + // Returns: + // True if operation succeeded, false otherwise. + // + bool InsertAt (int index, T *objects, int count = 1, bool enlarge = true) + { + if (objects == NULL || count < 1) + return false; + + int newSize = 0; + + if (m_itemCount > index) + newSize = m_itemCount + count; + else + newSize = index + count; + + if (newSize >= m_itemSize) + { + if (!enlarge || !SetSize (newSize)) + return false; + } + + if (index >= m_itemCount) + { + for (int i = 0; i < count; i++) + m_elements[i + index] = objects[i]; + + m_itemCount = newSize; + } + else + { + int i = 0; + + for (i = m_itemCount; i > index; i--) + m_elements[i + count - 1] = m_elements[i - 1]; + + for (i = 0; i < count; i++) + m_elements[i + index] = objects[i]; + + m_itemCount += count; + } + return true; + } + + // + // Function: InsertAt + // Inserts other array reference into the our array. + // + // Parameters: + // index - Index where element should be inserted. + // objects - Pointer to object list. + // count - Number of element to insert. + // enlarge - Checks whether array must be resized in case, allocated size + enlarge step is exceeded. + // + // Returns: + // True if operation succeeded, false otherwise. + // + bool InsertAt (int index, Array &other, bool enlarge = true) + { + if (&other == this) + return false; + + return InsertAt (index, other.m_elements, other.m_itemCount, enlarge); + } + + // + // Function: RemoveAt + // Removes elements from specified index. + // + // Parameters: + // index - Index, where element should be removed. + // count - Number of elements to remove. + // + // Returns: + // True if operation succeeded, false otherwise. + // + bool RemoveAt (int index, int count = 1) + { + if (index + count > m_itemCount) + return false; + + if (count < 1) + return true; + + m_itemCount -= count; + + for (int i = index; i < m_itemCount; i++) + m_elements[i] = m_elements[i + count]; + + return true; + } + + // + // Function: Push + // Appends element to the end of array. + // + // Parameters: + // object - Object to append. + // enlarge - Checks whether array must be resized in case, allocated size + enlarge step is exceeded. + // + // Returns: + // True if operation succeeded, false otherwise. + // + bool Push (T object, bool enlarge = true) + { + return InsertAt (m_itemCount, &object, 1, enlarge); + } + + // + // Function: Push + // Appends number of elements to the end of array. + // + // Parameters: + // objects - Pointer to object list. + // count - Number of element to insert. + // enlarge - Checks whether array must be resized in case, allocated size + enlarge step is exceeded. + // + // Returns: + // True if operation succeeded, false otherwise. + // + bool Push (T *objects, int count = 1, bool enlarge = true) + { + return InsertAt (m_itemCount, objects, count, enlarge); + } + + // + // Function: Push + // Inserts other array reference into the our array. + // + // Parameters: + // objects - Pointer to object list. + // count - Number of element to insert. + // enlarge - Checks whether array must be resized in case, allocated size + enlarge step is exceeded. + // + // Returns: + // True if operation succeeded, false otherwise. + // + bool Push (Array &other, bool enlarge = true) + { + if (&other == this) + return false; + + return InsertAt (m_itemCount, other.m_elements, other.m_itemCount, enlarge); + } + + // + // Function: GetData + // Gets the pointer to all element in array. + // + // Returns: + // Pointer to object list. + // + T *GetData (void) + { + return m_elements; + } + + // + // Function: RemoveAll + // Resets array, and removes all elements out of it. + // + void RemoveAll (void) + { + m_itemCount = 0; + SetSize (m_itemCount); + } + + // + // Function: IsEmpty + // Checks whether element is empty. + // + // Returns: + // True if element is empty, false otherwise. + // + inline bool IsEmpty (void) + { + return m_itemCount <= 0; + } + + // + // Function: FreeExtra + // Frees unused space. + // + void FreeSpace (bool destroyIfEmpty = true) + { + if (m_itemCount == 0) + { + if (destroyIfEmpty) + Destory (); + + return; + } + + T *buffer = new T[m_itemCount]; + + if (buffer == NULL) + return; + + if (m_elements != NULL) + { + for (int i = 0; i < m_itemCount; i++) + buffer[i] = m_elements[i]; + } + + if (m_elements != NULL) + delete [] m_elements; + + m_elements = buffer; + m_itemSize = m_itemCount; + } + + // + // Function: Pop + // Pops element from array. + // + // Returns: + // Object popped from the end of array. + // + T Pop (void) + { + if (m_itemCount <= 0) + return m_failed; + + T element = m_elements[m_itemCount - 1]; + RemoveAt (m_itemCount - 1); + + return element; + } + + T &Last (void) + { + if (m_itemCount <= 0) + return m_failed; + + return m_elements[m_itemCount - 1]; + } + + bool GetLast (T &item) + { + if (m_itemCount <= 0) + return false; + + item = m_elements[m_itemCount - 1]; + + return true; + } + + // + // Function: AssignFrom + // Reassigns current array with specified one. + // + // Parameters: + // other - Other array that should be assigned. + // + // Returns: + // True if operation succeeded, false otherwise. + // + bool AssignFrom (const Array &other) + { + if (&other == this) + return true; + + if (!SetSize (other.m_itemCount, false)) + return false; + + for (int i = 0; i < other.m_itemCount; i++) + m_elements[i] = other.m_elements[i]; + + m_itemCount = other.m_itemCount; + m_resizeStep = other.m_resizeStep; + + return true; + } + + // + // Function: GetRandomElement + // Gets the random element from the array. + // + // Returns: + // Random element reference. + // + T &GetRandomElement (void) const + { + extern class RandGen g_randGen; + + return m_elements[g_randGen.Long (0, m_itemCount - 1)]; + } + + Array &operator = (const Array &other) + { + AssignFrom (other); + return *this; + } + + T &operator [] (int index) + { + if (index < m_itemSize && index >= m_itemCount) + m_itemCount = index + 1; + + return GetAt (index); + } +}; + + +// +// Class: Map +// Represents associative map container. +// +template class Map +{ +public: + struct MapItem + { + K key; + V value; + }; + +protected: + struct HashItem + { + public: + int m_index; + HashItem *m_next; + + public: + HashItem (void) + { + m_next = NULL; + } + + HashItem (int index, HashItem *next) + { + m_index = index; + m_next = next; + } + }; + + int m_hashSize; + HashItem **m_table; + Array m_mapTable; + +// Group: (Con/De)structors +public: + + // + // Function: Map + // Default constructor for map container. + // + // Parameters: + // hashSize - Initial hash size. + // + Map (int hashSize = 36) + { + m_hashSize = hashSize; + m_table = new HashItem *[hashSize]; + + for (int i = 0; i < hashSize; i++) + m_table[i] = 0; + } + + // + // Function: ~Map + // Default map container destructor. + // + // Parameters: + // hashSize - Initial hash size. + // + virtual ~Map (void) + { + RemoveAll (); + delete [] m_table; + } + +// Group: Functions +public: + + // + // Function: IsExists + // Checks whether specified element exists in container. + // + // Parameters: + // keyName - Key that should be looked up. + // + // Returns: + // True if key exists, false otherwise. + // + inline bool IsExists (const K &keyName) + { + return GetIndex (keyName, false) >= 0; + } + + // + // Function: SetupMap + // Initializes map, if not initialized automatically. + // + // Parameters: + // hashSize - Initial hash size. + // + inline void SetupMap (int hashSize) + { + m_hashSize = hashSize; + m_table = new HashItem *[hashSize]; + + for (int i = 0; i < hashSize; i++) + m_table[i] = 0; + } + + // + // Function: IsEmpty + // Checks whether map container is currently empty. + // + // Returns: + // True if no elements exists, false otherwise. + // + inline bool IsEmpty (void) const + { + return m_mapTable.GetSize () == 0; + } + + // + // Function: GetSize + // Retrieves size of the map container. + // + // Returns: + // Number of elements currently in map container. + // + inline int GetSize (void) const + { + return m_mapTable.GetSize (); + } + + // + // Function: GetKey + // Gets the key object, by it's index. + // + // Parameters: + // index - Index of key. + // + // Returns: + // Object containing the key. + // + inline const K &GetKey (int index) const + { + return m_mapTable[index].key; + } + + // + // Function: GetValue + // Gets the constant value object, by it's index. + // + // Parameters: + // index - Index of value. + // + // Returns: + // Object containing the value. + // + inline const V &GetValue (int index) const + { + return m_mapTable[index].value; + } + + // Function: GetValue + // Gets the value object, by it's index. + // + // Parameters: + // index - Index of value. + // + // Returns: + // Object containing the value. + // + inline V &GetValue (int index) + { + return m_mapTable[index].value; + } + + // + // Function: GetElements + // Gets the all elements of container. + // + // Returns: + // Array of elements, containing inside container. + // + // See Also: + // + // + inline Array &GetElements (void) + { + return m_mapTable; + } + + V &operator [] (const K &keyName) + { + int index = GetIndex (keyName, true); + return m_mapTable[index].value; + } + + // + // Function: Find + // Finds element by his key name. + // + // Parameters: + // keyName - Key name to be searched. + // element - Holder for element object. + // + // Returns: + // True if element found, false otherwise. + // + bool Find (const K &keyName, V &element) const + { + int index = const_cast *> (this)->GetIndex (keyName, false); + + if (index < 0) + return false; + + element = m_mapTable[index].value; + return true; + } + + // + // Function: Find + // Finds element by his key name. + // + // Parameters: + // keyName - Key name to be searched. + // elementPtr - Holder for element pointer. + // + // Returns: + // True if element found, false otherwise. + // + bool Find (const K &keyName, V *&elementPtr) const + { + int index = const_cast *> (this)->GetIndex (keyName, false); + + if (index < 0) + return false; + + elementPtr = const_cast (&m_mapTable[index].value); + return true; + } + + // + // Function: Remove + // Removes element from container. + // + // Parameters: + // keyName - Key name of element, that should be removed. + // + // Returns: + // True if key was removed successfully, false otherwise. + // + bool Remove (const K &keyName) + { + int hashId = Hash (keyName) % m_hashSize; + HashItem *hashItem = m_table[hashId], *nextHash = 0; + + while (hashItem != NULL) + { + if (m_mapTable[hashItem->m_index].key == keyName) + { + if (nextHash == 0) + m_table[hashId] = hashItem->m_next; + else + nextHash->m_next = hashItem->m_next; + + m_mapTable.RemoveAt (hashItem->m_index); + delete hashItem; + + return true; + } + nextHash = hashItem; + hashItem = hashItem->m_next; + } + return false; + } + + // + // Function: RemoveAll + // Removes all elements from container. + // + void RemoveAll (void) + { + for (int i = m_hashSize; --i >= 0;) + { + HashItem *ptr = m_table[i]; + + while (ptr != NULL) + { + HashItem *m_next = ptr->m_next; + + delete ptr; + ptr = m_next; + } + m_table[i] = 0; + } + m_mapTable.RemoveAll (); + } + + // + // Function: GetIndex + // Gets index of element. + // + // Parameters: + // keyName - Key of element. + // create - If true and no element found by a keyName, create new element. + // + // Returns: + // Either found index, created index, or -1 in case of error. + // + int GetIndex (const K &keyName, bool create) + { + int hashId = Hash (keyName) % m_hashSize; + + for (HashItem *ptr = m_table[hashId]; ptr != NULL; ptr = ptr->m_next) + { + if (m_mapTable[ptr->m_index].key == keyName) + return ptr->m_index; + } + + if (create) + { + int item = m_mapTable.GetSize (); + m_mapTable.SetSize (static_cast (item + 1)); + + m_table[hashId] = new HashItem (item, m_table[hashId]); + m_mapTable[item].key = keyName; + + return item; + } + return -1; + } +}; + +// +// Class: HashTable +// Represents Hash Table container. +// +template class HashTable +{ +protected: + struct HashItem + { + public: + K m_key; + int m_index; + + public: + HashItem (void) { m_index = 0; } + HashItem (const K &key, int index) { m_key = key; m_index = index; } + }; + + int m_hashSize; + Array *m_table; + Array m_symTable; + + V *InternalGet (const K &keyName, bool create) + { + int index = GetIndex (keyName, create); + + if (index < 0) + return 0; + + return &m_symTable[index]; + } + +// Group: (Con/De)structors +public: + + // + // Function: HashTable + // Default hash table container constructor. + // + // Parameters: + // hashSize - Initial hash size. + // + HashTable (int hashSize = 36) + { + m_hashSize = hashSize; + m_table = new Array [hashSize]; + } + + // + // Function: ~HashTable + // Default hash table container destructor. + // + virtual ~HashTable (void) + { + RemoveAll (); + delete [] m_table; + } + + // + // Function: IsEmpty + // Checks whether container is empty. + // + // Returns: + // True if no elements in container, false otherwise. + // + bool IsEmpty (void) + { + return m_symTable.GetSize () == 0; + } + + // + // Function: SetupHashTable + // Setups the hash table. + // + // Parameters: + // hashSize - Initial hash size. + // + void SetupHashTable (int hashSize) + { + m_hashSize = hashSize; + m_table = new Array [hashSize]; + } + + // + // Function: IsExists + // Checks whether element exists in container. + // + // Parameters: + // keyName - Key name of element, that should be checked. + // + // Returns: + // True if element exists, false otherwise. + // + bool IsExists (const K &keyName) + { + return InternalGet (keyName, false) != 0; + } + + // + // Function: GetSize + // Gets the size of container. + // + // Returns: + // Number of elements in container. + // + int GetSize (void) + { + return m_symTable.GetSize (); + } + + V &operator [] (const K &keyName) + { + return *InternalGet (keyName, true); + } + + // + // Function: Get + // Gets the value, by it's index. + // + // Parameters: + // index - Index of element. + // + // Returns: + // Reference to element. + // + V &Get (int index) + { + return m_symTable[index]; + } + + // + // Function: GetElements + // Gets the all elements of container. + // + // Returns: + // Array of elements, containing inside container. + // + // See Also: + // + // + const Array &GetElements (void) const + { + return m_symTable; + } + + // + // Function: Find + // Finds element by his key name. + // + // Parameters: + // keyName - Key name to be searched. + // element - Holder for element object. + // + // Returns: + // True if element found, false otherwise. + // + bool Find (const K &keyName, V &element) const + { + V *hashPtr = const_cast *> (this)->InternalGet (keyName, false); + + if (hashPtr != NULL) + { + element = *hashPtr; + return true; + } + return false; + } + + // + // Function: Find + // Finds element by his key name. + // + // Parameters: + // keyName - Key name to be searched. + // elementPtr - Holder for element pointer. + // + // Returns: + // True if element found, false otherwise. + // + bool Find (const K &keyName, V *&elementPtr) const + { + V *hashPtr = const_cast *> (this)->InternalGet (keyName, false); + + if (hashPtr != NULL) + { + elementPtr = hashPtr; + return true; + } + return false; + } + + // + // Function: Remove + // Removes element from container. + // + // Parameters: + // keyName - Key name of element, that should be removed. + // + // Returns: + // Removed element. + // + V Remove (const K &keyName) + { + V removeResult; + + int hashId = Hash (keyName) % m_hashSize; + Array &bucket = m_table[hashId]; + + for (int i = 0; i < bucket.GetSize (); i++) + { + const HashItem &item = bucket[i]; + + if (item.m_key == keyName) + { + int index = item.m_index; + + removeResult = m_symTable.RemoveAt (index); + bucket.RemoveAt (i); + + for (hashId = 0; hashId < m_hashSize; hashId++) + { + Array &bucket = m_table[hashId]; + + for (int j = 0; j < bucket.GetSize (); j++) + { + const HashItem &item = bucket[j]; + + if (item.m_index > index) + item.m_index--; + } + } + return removeResult; + } + } + return removeResult; + } + + // + // Function: GetIndex + // Gets index of element. + // + // Parameters: + // keyName - Key of element. + // create - If true and no element found by a keyName, create new element. + // + // Returns: + // Either found index, created index, or -1 in case of error. + // + int GetIndex (const K &keyName, bool create) + { + int hashId = Hash (keyName) % m_hashSize; + Array &bucket = m_table[hashId]; + + for (int i = 0; i < bucket.GetSize (); i++) + { + const HashItem &item = bucket[i]; + + if (item.m_key == keyName) + return item.m_index; + } + + if (create) + { + int symSize = m_symTable.GetSize (); + m_symTable.SetSize (static_cast (symSize + 1)); + + bucket.Push (HashItem (keyName, symSize)); + return symSize; + } + return -1; + } + + // + // Function: RemoveAll + // Removes all elements from container. + // + void RemoveAll (void) + { + for (int i = 0; i < m_hashSize; ++i) + m_table[i].RemoveAll (); + + m_symTable.RemoveAll (); + } +}; + +// +// Class: String +// Reference counted string class. +// +class String +{ +private: + char *m_bufferPtr; + int m_allocatedSize; + int m_stringLength; + +// +// Group: Private functions +// +private: + + // + // Function: UpdateBufferSize + // Updates the buffer size. + // + // Parameters: + // size - New size of buffer. + // + void UpdateBufferSize (int size) + { + if (size <= m_allocatedSize) + return; + + m_allocatedSize = size + 16; + char *tempBuffer = new char[size + 1]; + + if (m_bufferPtr != NULL) + { + strcpy (tempBuffer, m_bufferPtr); + tempBuffer[m_stringLength] = 0; + + delete [] m_bufferPtr; + } + m_bufferPtr = tempBuffer; + m_allocatedSize = size; + } + + // + // Function: MoveItems + // Moves characters inside buffer pointer. + // + // Parameters: + // destIndex - Destination index. + // sourceIndex - Source index. + // + void MoveItems (int destIndex, int sourceIndex) + { + memmove (m_bufferPtr + destIndex, m_bufferPtr + sourceIndex, sizeof (char) *(m_stringLength - sourceIndex + 1)); + } + + // + // Function: Initialize + // Initializes string buffer. + // + // Parameters: + // length - Initial length of string. + // + void Initialize (int length) + { + int freeSize = m_allocatedSize - m_stringLength - 1; + + if (length <= freeSize) + return; + + int delta = 4; + + if (m_allocatedSize > 64) + delta = m_allocatedSize / 2; + else if (m_allocatedSize > 8) + delta = 16; + + if (freeSize + delta < length) + delta = length - freeSize; + + UpdateBufferSize (m_allocatedSize + delta); + } + + // + // Function: CorrectIndex + // Gets the correct string end index. + // + // Parameters: + // index - Holder for index. + // + void CorrectIndex (int &index) const + { + if (index > m_stringLength) + index = m_stringLength; + } + + // + // Function: InsertSpace + // Inserts space at specified location, with specified length. + // + // Parameters: + // index - Location to insert space. + // size - Size of space insert. + // + void InsertSpace (int &index, int size) + { + CorrectIndex (index); + Initialize (size); + + MoveItems (index + size, index); + } + + // + // Function: IsTrimChar + // Checks whether input is trimming character. + // + // Parameters: + // input - Input to check for. + // + // Returns: + // True if it's a trim char, false otherwise. + // + bool IsTrimChar (char input) + { + return input == ' ' || input == '\t' || input == '\n'; + } + +// +// Group: (Con/De)structors +// +public: + String (void) + { + m_bufferPtr = NULL; + m_allocatedSize = 0; + m_stringLength = 0; + } + + ~String (void) + { + if (m_bufferPtr != NULL) + delete [] m_bufferPtr; + } + + String (const char *bufferPtr) + { + m_bufferPtr = NULL; + m_allocatedSize = 0; + m_stringLength = 0; + + Assign (bufferPtr); + } + + String (char input) + { + m_bufferPtr = NULL; + m_allocatedSize = 0; + m_stringLength = 0; + + Assign (input); + } + + String (const String &inputString) + { + m_bufferPtr = NULL; + m_allocatedSize = 0; + m_stringLength = 0; + + Assign (inputString.GetBuffer ()); + } + +// +// Group: Functions +// +public: + + // + // Function: GetBuffer + // Gets the string buffer. + // + // Returns: + // Pointer to buffer. + // + const char *GetBuffer (void) + { + if (m_bufferPtr == NULL || *m_bufferPtr == 0x0) + return ""; + + return &m_bufferPtr[0]; + } + + // + // Function: GetBuffer + // Gets the string buffer (constant). + // + // Returns: + // Pointer to constant buffer. + // + const char *GetBuffer (void) const + { + if (m_bufferPtr == NULL || *m_bufferPtr == 0x0) + return ""; + + return &m_bufferPtr[0]; + } + + // + // Function: ToString + // Gets the string buffer. + // + // Returns: + // Pointer to buffer. + // + const char *ToString (void) + { + if (m_bufferPtr == NULL || *m_bufferPtr == 0x0) + return ""; + + return &m_bufferPtr[0]; + } + + // + // Function: ToString + // Gets the string buffer (constant). + // + // Returns: + // Pointer to constant buffer. + // + const char *ToString (void) const + { + if (m_bufferPtr == NULL || *m_bufferPtr == 0x0) + return ""; + + return &m_bufferPtr[0]; + } + + // + // Function: ToFloat + // Gets the string as float, if possible. + // + // Returns: + // Float value of string. + // + float ToFloat (void) + { + return static_cast (atof (m_bufferPtr)); + } + + // + // Function: ToInt + // Gets the string as integer, if possible. + // + // Returns: + // Integer value of string. + // + int ToInt (void) + { + return atoi (m_bufferPtr); + } + + // + // Function: ReleaseBuffer + // Terminates the string with null character. + // + void ReleaseBuffer (void) + { + ReleaseBuffer (strlen (m_bufferPtr)); + } + + // + // Function: ReleaseBuffer + // Terminates the string with null character with specified buffer end. + // + // Parameters: + // newLength - End of buffer. + // + void ReleaseBuffer (int newLength) + { + m_bufferPtr[newLength] = 0; + m_stringLength = newLength; + } + + // + // Function: GetBuffer + // Gets the buffer with specified length. + // + // Parameters: + // minLength - Length to retrieve. + // + // Returns: + // Pointer to string buffer. + // + char *GetBuffer (int minLength) + { + if (minLength >= m_allocatedSize) + UpdateBufferSize (minLength + 1); + + return m_bufferPtr; + } + + // + // Function: GetBufferSetLength + // Gets the buffer with specified length, and terminates string with that length. + // + // Parameters: + // minLength - Length to retrieve. + // + // Returns: + // Pointer to string buffer. + // + char *GetBufferSetLength (int length) + { + char *buffer = GetBuffer (length); + + m_stringLength = length; + m_bufferPtr[length] = 0; + + return buffer; + } + + // + // Function: Append + // Appends the string to existing buffer. + // + // Parameters: + // bufferPtr - String buffer to append. + // + void Append (const char *bufferPtr) + { + UpdateBufferSize (m_stringLength + strlen (bufferPtr) + 1); + strcat (m_bufferPtr, bufferPtr); + + m_stringLength = strlen (m_bufferPtr); + } + + // + // Function: Append + // Appends the character to existing buffer. + // + // Parameters: + // input - Character to append. + // + void Append (const char input) + { + UpdateBufferSize (m_stringLength + 2); + + m_bufferPtr[m_stringLength] = input; + m_bufferPtr[m_stringLength++] = 0; + } + + // + // Function: Append + // Appends the string to existing buffer. + // + // Parameters: + // inputString - String buffer to append. + // + void Append (const String &inputString) + { + const char *bufferPtr = inputString.GetBuffer (); + UpdateBufferSize (m_stringLength + strlen (bufferPtr)); + + strcat (m_bufferPtr, bufferPtr); + m_stringLength = strlen (m_bufferPtr); + } + + // + // Function: AppendFormat + // Appends the formatted string to existing buffer. + // + // Parameters: + // fmt - Formatted, tring buffer to append. + // + void AppendFormat (const char *fmt, ...) + { + va_list ap; + char buffer[1024]; + + va_start (ap, fmt); + vsprintf (buffer, fmt, ap); + va_end (ap); + + Append (buffer); + } + + // + // Function: Assign + // Assigns the string to existing buffer. + // + // Parameters: + // inputString - String buffer to assign. + // + void Assign (const String &inputString) + { + Assign (inputString.GetBuffer ()); + } + + // + // Function: Assign + // Assigns the character to existing buffer. + // + // Parameters: + // input - Character to assign. + // + void Assign (char input) + { + char psz[2] = {input, 0}; + Assign (psz); + } + + // + // Function: Assign + // Assigns the string to existing buffer. + // + // Parameters: + // bufferPtr - String buffer to assign. + // + void Assign (const char *bufferPtr) + { + if (bufferPtr == NULL) + { + UpdateBufferSize (1); + m_stringLength = 0; + + return; + } + UpdateBufferSize (strlen (bufferPtr)); + + if (m_bufferPtr != NULL) + { + strcpy (m_bufferPtr, bufferPtr); + m_stringLength = strlen (m_bufferPtr); + } + else + m_stringLength = 0; + } + + // + // Function: Assign + // Assigns the formatted string to existing buffer. + // + // Parameters: + // fmt - Formatted string buffer to assign. + // + void AssignFormat (const char *fmt, ...) + { + va_list ap; + char buffer[1024]; + + va_start (ap, fmt); + vsprintf (buffer, fmt, ap); + va_end (ap); + + Assign (buffer); + } + + // + // Function: Empty + // Empties the string. + // + void Empty (void) + { + if (m_bufferPtr != NULL) + { + m_bufferPtr[0] = 0; + m_stringLength = 0; + } + } + + // + // Function: IsEmpty + // Checks whether string is empty. + // + // Returns: + // True if string is empty, false otherwise. + // + bool IsEmpty (void) + { + if (m_bufferPtr == NULL || m_stringLength == 0) + return true; + + return false; + } + + // + // Function: GetLength + // Gets the string length. + // + // Returns: + // Length of string, 0 in case of error. + // + int GetLength (void) + { + if (m_bufferPtr == NULL) + return 0; + + return m_stringLength; + } + + operator const char * (void) const + { + return GetBuffer (); + } + + operator char * (void) + { + return const_cast (GetBuffer ()); + } + + operator int (void) + { + return ToInt (); + } + + operator long (void) + { + return static_cast (ToInt ()); + } + + operator float (void) + { + return ToFloat (); + } + + operator double (void) + { + return static_cast (ToFloat ()); + } + + String *operator -> (void) const + { + return (String *const) this; + } + + friend String operator + (const String &s1, const String &s2) + { + String result (s1); + result += s2; + + return result; + } + + friend String operator + (const String &holder, char ch) + { + String result (holder); + result += ch; + + return result; + } + + friend String operator + (char ch, const String &holder) + { + String result (ch); + result += holder; + + return result; + } + + friend String operator + (const String &holder, const char *str) + { + String result (holder); + result += str; + + return result; + } + + friend String operator + (const char *str, const String &holder) + { + String result (const_cast (str)); + result += holder; + + return result; + } + + friend bool operator == (const String &s1, const String &s2) + { + return s1.Compare (s2) == 0; + } + + friend bool operator < (const String &s1, const String &s2) + { + return s1.Compare (s2) < 0; + } + + friend bool operator > (const String &s1, const String &s2) + { + return s1.Compare (s2) > 0; + } + + friend bool operator == (const char *s1, const String &s2) + { + return s2.Compare (s1) == 0; + } + + friend bool operator == (const String &s1, const char *s2) + { + return s1.Compare (s2) == 0; + } + + friend bool operator != (const String &s1, const String &s2) + { + return s1.Compare (s2) != 0; + } + + friend bool operator != (const char *s1, const String &s2) + { + return s2.Compare (s1) != 0; + } + + friend bool operator != (const String &s1, const char *s2) + { + return s1.Compare (s2) != 0; + } + + String &operator = (const String &inputString) + { + Assign (inputString); + return *this; + } + + String &operator = (const char *bufferPtr) + { + Assign (bufferPtr); + return *this; + } + + String &operator = (char input) + { + Assign (input); + return *this; + } + + String &operator += (const String &inputString) + { + Append (inputString); + return *this; + } + + String &operator += (const char *bufferPtr) + { + Append (bufferPtr); + return *this; + } + + char operator [] (int index) + { + if (index > m_stringLength) + return -1; + + return m_bufferPtr[index]; + } + + // + // Function: Mid + // Gets the substring by specified bounds. + // + // Parameters: + // startIndex - Start index to get from. + // count - Number of characters to get. + // + // Returns: + // Tokenized string. + // + String Mid (int startIndex, int count = -1) + { + String result; + + if (startIndex >= m_stringLength || !m_bufferPtr) + return result; + + if (count == -1) + count = m_stringLength - startIndex; + else if (startIndex+count >= m_stringLength) + count = m_stringLength - startIndex; + + int i = 0, j = 0; + char *holder = new char[m_stringLength+1]; + + for (i = startIndex; i < startIndex + count; i++) + holder[j++] = m_bufferPtr[i]; + + holder[j] = 0; + result.Assign(holder); + + delete [] holder; + return result; + } + + // + // Function: Mid + // Gets the substring by specified bounds. + // + // Parameters: + // startIndex - Start index to get from. + // + // Returns: + // Tokenized string. + // + String Mid (int startIndex) + { + return Mid (startIndex, m_stringLength - startIndex); + } + + // + // Function: Left + // Gets the string from left side. + // + // Parameters: + // count - Number of characters to get. + // + // Returns: + // Tokenized string. + // + String Left (int count) + { + return Mid (0, count); + } + + // + // Function: Right + // Gets the string from right side. + // + // Parameters: + // count - Number of characters to get. + // + // Returns: + // Tokenized string. + // + String Right (int count) + { + if (count > m_stringLength) + count = m_stringLength; + + return Mid (m_stringLength - count, count); + } + + // + // Function: ToUpper + // Gets the string in upper case. + // + // Returns: + // Upped sting. + // + String ToUpper (void) + { + String result; + + for (int i = 0; i < GetLength (); i++) + result += toupper (m_bufferPtr[i]); + + return result; + } + + // + // Function: ToUpper + // Gets the string in upper case. + // + // Returns: + // Lowered sting. + // + String ToLower (void) + { + String result; + + for (int i = 0; i < GetLength (); i++) + result += tolower (m_bufferPtr[i]); + + return result; + } + + // + // Function: ToReverse + // Reverses the string. + // + // Returns: + // Reversed string. + // + String ToReverse (void) + { + char *source = m_bufferPtr + GetLength () - 1; + char *dest = m_bufferPtr; + + while (source > dest) + { + if (*source == *dest) + { + source--; + dest++; + } + else + { + char ch = *source; + + *source-- = *dest; + *dest++ = ch; + } + } + return m_bufferPtr; + } + + // + // Function: MakeUpper + // Converts string to upper case. + // + void MakeUpper (void) + { + *this = ToUpper (); + } + + // + // Function: MakeLower + // Converts string to lower case. + // + void MakeLower (void) + { + *this = ToLower (); + } + + // + // Function: MakeReverse + // Converts string into reverse order. + // + void MakeReverse (void) + { + *this = ToReverse (); + } + + // + // Function: Compare + // Compares string with other string. + // + // Parameters: + // string - String t compare with. + // + // Returns: + // Zero if they are equal. + // + int Compare (const String &string) const + { + return strcmp (m_bufferPtr, string.m_bufferPtr); + } + + // + // Function: CompareI + // Compares string with other string without case check. + // + // Parameters: + // string - String t compare with. + // + // Returns: + // Zero if they are equal. + // + int CompareI (String &string) const + { + return strcmpi (m_bufferPtr, string.m_bufferPtr); + } + + // + // Function: Compare + // Compares string with other string. + // + // Parameters: + // str - String t compare with. + // + // Returns: + // Zero if they are equal. + // + int Compare (const char *str) const + { + return strcmp (m_bufferPtr, str); + } + + // + // Function: CompareI + // Compares string with other string without case check. + // + // Parameters: + // str - String to compare with. + // + // Returns: + // Zero if they are equal. + // + int CompareI (const char *str) const + { + return stricmp (m_bufferPtr, str); + } + + // + // Function: Collate + // Collate the string. + // + // Parameters: + // string - String to collate. + // + // Returns: + // One on success. + // + int Collate (const String &string) const + { + return strcoll (m_bufferPtr, string.m_bufferPtr); + } + + // + // Function: Find + // Find the character. + // + // Parameters: + // input - Character to search for. + // + // Returns: + // Index of character. + // + int Find (char input) const + { + return Find (input, 0); + } + + // + // Function: Find + // Find the character. + // + // Parameters: + // input - Character to search for. + // startIndex - Start index to search from. + // + // Returns: + // Index of character. + // + int Find (char input, int startIndex) const + { + char *str = m_bufferPtr + startIndex; + + for (;;) + { + if (*str == input) + return str - m_bufferPtr; + + if (*str == 0) + return -1; + + str++; + } + } + + // + // Function: Find + // Tries to find string. + // + // Parameters: + // string - String to search for. + // + // Returns: + // Position of found string. + // + int Find (const String &string) const + { + return Find (string, 0); + } + + // + // Function: Find + // Tries to find string from specified index. + // + // Parameters: + // string - String to search for. + // startIndex - Index to start search from. + // + // Returns: + // Position of found string. + // + int Find (const String &string, int startIndex) const + { + if (string.m_stringLength == 0) + return startIndex; + + for (; startIndex < m_stringLength; startIndex++) + { + int j; + + for (j = 0; j < string.m_stringLength && startIndex + j < m_stringLength; j++) + { + if (m_bufferPtr[startIndex + j] != string.m_bufferPtr[j]) + break; + } + + if (j == string.m_stringLength) + return startIndex; + } + return -1; + } + + // + // Function: ReverseFind + // Tries to find character in reverse order. + // + // Parameters: + // ch - Character to search for. + // + // Returns: + // Position of found character. + // + int ReverseFind (char ch) + { + if (m_stringLength == 0) + return -1; + + char *str = m_bufferPtr + m_stringLength - 1; + + for (;;) + { + if (*str == ch) + return str - m_bufferPtr; + + if (str == m_bufferPtr) + return -1; + str--; + } + } + + // + // Function: FindOneOf + // Find one of occurrences of string. + // + // Parameters: + // string - String to search for. + // + // Returns: + // -1 in case of nothing is found, start of string in buffer otherwise. + // + int FindOneOf (const String &string) + { + for (int i = 0; i < m_stringLength; i++) + { + if (string.Find (m_bufferPtr[i]) >= 0) + return i; + } + return -1; + } + + // + // Function: TrimRight + // Trims string from right side. + // + // Returns: + // Trimmed string. + // + String &TrimRight (void) + { + char *str = m_bufferPtr; + char *last = NULL; + + while (*str != 0) + { + if (IsTrimChar (*str)) + { + if (last == NULL) + last = str; + } + else + last = NULL; + str++; + } + + if (last != NULL) + Delete (last - m_bufferPtr); + + return *this; + } + + // + // Function: TrimLeft + // Trims string from left side. + // + // Returns: + // Trimmed string. + // + String &TrimLeft (void) + { + char *str = m_bufferPtr; + + while (IsTrimChar (*str)) + str++; + + if (str != m_bufferPtr) + { + int first = int (str - GetBuffer ()); + char *buffer = GetBuffer (GetLength ()); + + str = buffer + first; + int length = GetLength () - first; + + memmove (buffer, str, (length + 1) * sizeof (char)); + ReleaseBuffer (length); + } + return *this; + } + + // + // Function: Trim + // Trims string from both sides. + // + // Returns: + // Trimmed string. + // + String &Trim (void) + { + return TrimRight ().TrimLeft (); + } + + // + // Function: TrimRight + // Trims specified character at the right of the string. + // + // Parameters: + // ch - Character to trim. + // + void TrimRight (char ch) + { + const char *str = m_bufferPtr; + const char *last = NULL; + + while (*str != 0) + { + if (*str == ch) + { + if (last == NULL) + last = str; + } + else + last = NULL; + + str++; + } + if (last != NULL) + { + int i = last - m_bufferPtr; + Delete (i, m_stringLength - i); + } + } + + // + // Function: TrimLeft + // Trims specified character at the left of the string. + // + // Parameters: + // ch - Character to trim. + // + void TrimLeft (char ch) + { + char *str = m_bufferPtr; + + while (ch == *str) + str++; + + Delete (0, str - m_bufferPtr); + } + + // + // Function: Insert + // Inserts character at specified index. + // + // Parameters: + // index - Position to insert string. + // ch - Character to insert. + // + // Returns: + // New string length. + // + int Insert (int index, char ch) + { + InsertSpace (index, 1); + + m_bufferPtr[index] = ch; + m_stringLength++; + + return m_stringLength; + } + + // + // Function: Insert + // Inserts string at specified index. + // + // Parameters: + // index - Position to insert string. + // string - Text to insert. + // + // Returns: + // New string length. + // + int Insert (int index, const String &string) + { + CorrectIndex (index); + + if (string.m_stringLength == 0) + return m_stringLength; + + int numInsertChars = string.m_stringLength; + InsertSpace (index, numInsertChars); + + for(int i = 0; i < numInsertChars; i++) + m_bufferPtr[index + i] = string[i]; + m_stringLength += numInsertChars; + + return m_stringLength; + } + + // + // Function: Replace + // Replaces old characters with new one. + // + // Parameters: + // oldCharacter - Old character to replace. + // newCharacter - New character to replace with. + // + // Returns: + // Number of occurrences replaced. + // + int Replace (char oldCharacter, char newCharacter) + { + if (oldCharacter == newCharacter) + return 0; + + static int num = 0; + int position = 0; + + while (position < GetLength ()) + { + position = Find (oldCharacter, position); + + if (position < 0) + break; + + m_bufferPtr[position] = newCharacter; + + position++; + num++; + } + return num; + } + + // + // Function: Replace + // Replaces string in other string. + // + // Parameters: + // oldString - Old string to replace. + // newString - New string to replace with. + // + // Returns: + // Number of characters replaced. + // + int Replace (const String &oldString, const String &newString) + { + if (oldString.m_stringLength == 0) + return 0; + + if (newString.m_stringLength == 0) + return 0; + + int oldLength = oldString.m_stringLength; + int newLength = newString.m_stringLength; + + int num = 0; + int position = 0; + + while (position < m_stringLength) + { + position = Find (oldString, position); + + if (position < 0) + break; + + Delete (position, oldLength); + Insert (position, newString); + + position += newLength; + num++; + } + return num; + } + + // + // Function: Delete + // Deletes characters from string. + // + // Parameters: + // index - Start of characters remove. + // count - Number of characters to remove. + // + // Returns: + // New string length. + // + int Delete (int index, int count = 1) + { + if (index + count > m_stringLength) + count = m_stringLength - index; + + if (count > 0) + { + MoveItems (index, index + count); + m_stringLength -= count; + } + return m_stringLength; + } + + // + // Function: TrimQuotes + // Trims trailing quotes. + // + // Returns: + // Trimmed string. + // + String TrimQuotes (void) + { + TrimRight ('\"'); + TrimRight ('\''); + + TrimLeft ('\"'); + TrimLeft ('\''); + + return *this; + } + + // + // Function: Contains + // Checks whether string contains something. + // + // Parameters: + // what - String to check. + // + // Returns: + // True if string exists, false otherwise. + // + bool Contains (const String &what) + { + return strstr (m_bufferPtr, what.m_bufferPtr) != NULL; + } + + // + // Function: Hash + // Gets the string hash. + // + // Returns: + // Hash of the string. + // + unsigned long Hash (void) + { + unsigned long hash = 0; + const char *ptr = m_bufferPtr; + + while (*ptr) + { + hash = (hash << 5) + hash + (*ptr); + ptr++; + } + return hash; + } + + // + // Function: Split + // Splits string using string separator. + // + // Parameters: + // separator - Separator to split with. + // + // Returns: + // Array of slitted strings. + // + // See Also: + // + // + Array Split (const char *separator) + { + Array holder; + int tokenLength, index = 0; + + do + { + index += strspn (&m_bufferPtr[index], separator); + tokenLength = strcspn (&m_bufferPtr[index], separator); + + if (tokenLength > 0) + holder.Push (Mid (index, tokenLength)); + + index += tokenLength; + + } while (tokenLength > 0); + + return holder; + } + + // + // Function: Split + // Splits string using character. + // + // Parameters: + // separator - Separator to split with. + // + // Returns: + // Array of slitted strings. + // + // See Also: + // + // + Array Split (char separator) + { + char sep[2]; + + sep[0] = separator; + sep[1] = 0x0; + + return Split (sep); + } +}; + +// +// Class: File +// Simple STDIO file wrapper class. +// +class File +{ +protected: + FILE *m_handle; + int m_fileSize; + +// +// Group: (Con/De)structors +// +public: + + // + // Function: File + // Default file class, constructor. + // + File (void) + { + m_handle = NULL; + m_fileSize = 0; + } + + // + // Function: File + // Default file class, constructor, with file opening. + // + File (String fileName, String mode = "rt") + { + Open (fileName, mode); + } + + // + // Function: ~File + // Default file class, destructor. + // + ~File (void) + { + Close (); + } + + // + // Function: Open + // Opens file and gets it's size. + // + // Parameters: + // fileName - String containing file name. + // mode - String containing open mode for file. + // + // Returns: + // True if operation succeeded, false otherwise. + // + bool Open (String fileName, String mode) + { + if ((m_handle = fopen (fileName, mode)) == NULL) + return false; + + fseek (m_handle, 0L, SEEK_END); + m_fileSize = ftell (m_handle); + fseek (m_handle, 0L, SEEK_SET); + + return true; + } + + // + // Function: Close + // Closes file, and destroys STDIO file object. + // + void Close (void) + { + if (m_handle != NULL) + { + fclose (m_handle); + m_handle = NULL; + } + m_fileSize = 0; + } + + // + // Function: Eof + // Checks whether we reached end of file. + // + // Returns: + // True if reached, false otherwise. + // + bool Eof (void) + { + assert (m_handle != NULL); + return feof (m_handle) ? true : false; + } + + // + // Function: Flush + // Flushes file stream. + // + // Returns: + // True if operation succeeded, false otherwise. + // + bool Flush (void) + { + assert (m_handle != NULL); + return fflush (m_handle) ? false : true; + } + + // + // Function: GetChar + // Pops one character from the file stream. + // + // Returns: + // Popped from stream character + // + int GetChar (void) + { + assert (m_handle != NULL); + return fgetc (m_handle); + } + + // + // Function: GetBuffer + // Gets the single line, from the non-binary stream. + // + // Parameters: + // buffer - Pointer to buffer, that should receive string. + // count - Max. size of buffer. + // + // Returns: + // Pointer to string containing popped line. + // + char *GetBuffer (char *buffer, int count) + { + assert (m_handle != NULL); + return fgets (buffer, count, m_handle); + } + + // + // Function: GetBuffer + // Gets the line from file stream, and stores it inside string class. + // + // Parameters: + // buffer - String buffer, that should receive line. + // count - Max. size of buffer. + // + // Returns: + // True if operation succeeded, false otherwise. + // + bool GetBuffer (String &buffer, int count) + { + assert (m_handle != NULL); + return !String (fgets (buffer, count, m_handle)).IsEmpty (); + } + + // + // Function: Printf + // Puts formatted buffer, into stream. + // + // Parameters: + // format - + // + // Returns: + // Number of bytes, that was written. + // + int Printf (const char *format, ...) + { + assert (m_handle != NULL); + + va_list ap; + va_start (ap, format); + int written = vfprintf (m_handle, format, ap); + va_end (ap); + + if (written < 0) + return 0; + + return written; + } + + // + // Function: PutChar + // Puts character into file stream. + // + // Parameters: + // ch - Character that should be put into stream. + // + // Returns: + // Character that was putted into the stream. + // + char PutChar (char ch) + { + assert (m_handle != NULL); + return static_cast (fputc (ch, m_handle)); + } + + // + // Function: PutString + // Puts buffer into the file stream. + // + // Parameters: + // buffer - Buffer that should be put, into stream. + // + // Returns: + // True, if operation succeeded, false otherwise. + // + bool PutString (String buffer) + { + assert (m_handle != NULL); + + if (fputs (buffer.GetBuffer (), m_handle) < 0) + return false; + + return true; + } + + // + // Function: Read + // Reads buffer from file stream in binary format. + // + // Parameters: + // buffer - Holder for read buffer. + // size - Size of the buffer to read. + // count - Number of buffer chunks to read. + // + // Returns: + // Number of bytes red from file. + // + int Read (void *buffer, int size, int count = 1) + { + assert (m_handle != NULL); + return fread (buffer, size, count, m_handle); + } + + // + // Function: Write + // Writes binary buffer into file stream. + // + // Parameters: + // buffer - Buffer holder, that should be written into file stream. + // size - Size of the buffer that should be written. + // count - Number of buffer chunks to write. + // + // Returns: + // Numbers of bytes written to file. + // + int Write (void *buffer, int size, int count = 1) + { + assert (m_handle != NULL); + return fwrite (buffer, size, count, m_handle); + } + + // + // Function: Seek + // Seeks file stream with specified parameters. + // + // Parameters: + // offset - Offset where cursor should be set. + // origin - Type of offset set. + // + // Returns: + // True if operation success, false otherwise. + // + bool Seek (long offset, int origin) + { + assert (m_handle != NULL); + + if (fseek (m_handle, offset, origin) != 0) + return false; + + return true; + } + + // + // Function: Rewind + // Rewinds the file stream. + // + void Rewind (void) + { + assert (m_handle != NULL); + rewind (m_handle); + } + + // + // Function: GetSize + // Gets the file size of opened file stream. + // + // Returns: + // Number of bytes in file. + // + int GetSize (void) + { + return m_fileSize; + } + + // + // Function: IsValid + // Checks whether file stream is valid. + // + // Returns: + // True if file stream valid, false otherwise. + // + bool IsValid (void) + { + return m_handle != NULL; + } +}; + +// +// Class: RandomGenerator +// Random number generator. +// +class RandomGenerator +{ + +}; +// +// Type: StrVec +// Array of strings. +// +typedef Array StrVec; + +// +// Class: Exception +// Simple exception raiser. +// +class Exception +{ +private: + String m_exceptionText; + String m_fileName; + int m_line; + +// +// Group: (Con/De)structors +// +public: + + // + // Function: Exception + // Default exception constructor. + // + // Parameters: + // exceptionText - Text to throw. + // fileName - Debug file name. + // line - Debug line number. + // + Exception (String exceptionText, String fileName = "(no)", int line = -1) : m_exceptionText (exceptionText), m_fileName (fileName), m_line (line) { } + + // + // Function: ~Exception + // Default exception destructor. + // + virtual ~Exception (void) { }; + +// +// Group: Functions +// +public: + + // + // Function: GetDescription + // Gets the description from throw object. + // + // Returns: + // Exception text. + // + inline const String &GetDescription (void) + { + static String result; + + if (m_fileName != "(no)" && m_line != -1) + result.AssignFormat ("Exception: %s at %s:%i", m_exceptionText.ToString (), m_fileName.ToString (), m_line); + else + result = m_exceptionText; + + return result; + } +}; + +// +// Class: Singleton +// Implements no-copying singleton. +// +template class Singleton +{ +// +// Group: (Con/De)structors +// +protected: + + // + // Function: Singleton + // Default singleton constructor. + // + Singleton (void) { } + + // + // Function: ~Singleton + // Default singleton destructor. + // + virtual ~Singleton (void) { } + +public: + + // + // Function: GetObject + // Gets the object of singleton. + // + // Returns: + // Object pointer. + // + // + static inline T *GetObject (void) + { + static T reference; + return &reference; + } + + // + // Function: GetObject + // Gets the object of singleton as reference. + // + // Returns: + // Object reference. + // + // + static inline T &GetReference (void) + { + static T reference; + return reference; + }; +}; + +// +// Macro: ThrowException +// Throws debug exception. +// +// Parameters: +// text - Text of error. +// +#define ThrowException(text) \ + throw Exception (text, __FILE__, __LINE__) + + +// +// Macro: IterateArray +// Utility macro for iterating arrays. +// +// Parameters: +// iteratorName - Name of the iterator. +// arrayName - Name of the array that should be iterated. +// +// See Also: +// +// +#define IterateArray(arrayName, iteratorName) \ + for (int iteratorName = 0; iteratorName != arrayName.GetElementNumber (); iteratorName++) + + +#endif diff --git a/include/engine/archtypes.h b/include/engine/archtypes.h new file mode 100644 index 0000000..060383b --- /dev/null +++ b/include/engine/archtypes.h @@ -0,0 +1,228 @@ +/*** +* +* Copyright (c) 1999-2005, Valve Corporation. 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 source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#ifndef ARCHTYPES_H +#define ARCHTYPES_H + +#ifdef _WIN32 +#pragma once +#endif + +#include + +// detects the build platform +#if defined (__linux__) || defined (__debian__) || defined (__linux) +#define __linux__ 1 + +#endif + +// for when we care about how many bits we use +typedef signed char int8; +typedef signed short int16; +typedef signed long int32; + +#ifdef _WIN32 +#ifdef _MSC_VER +typedef signed __int64 int64; +#endif +#elif defined __linux__ +typedef long long int64; +#endif + +typedef unsigned char uint8; +typedef unsigned short uint16; +typedef unsigned long uint32; + +#ifdef _WIN32 +#ifdef _MSC_VER +typedef unsigned __int64 uint64; +#endif +#elif defined __linux__ +typedef unsigned long long uint64; +#endif + +typedef float float32; +typedef double float64; + +// for when we don't care about how many bits we use +typedef unsigned int uint; + +// This can be used to ensure the size of pointers to members when declaring +// a pointer type for a class that has only been forward declared +#ifdef _MSC_VER +#define SINGLE_INHERITANCE __single_inheritance +#define MULTIPLE_INHERITANCE __multiple_inheritance +#else +#define SINGLE_INHERITANCE +#define MULTIPLE_INHERITANCE +#endif + +// need these for the limits +#include +#include + +// Maximum and minimum representable values +#define INT8_MAX SCHAR_MAX +#define INT16_MAX SHRT_MAX +#define INT32_MAX LONG_MAX +#define INT64_MAX (((int64)~0) >> 1) + +#define INT8_MIN SCHAR_MIN +#define INT16_MIN SHRT_MIN +#define INT32_MIN LONG_MIN +#define INT64_MIN (((int64)1) << 63) + +#define UINT8_MAX ((uint8)~0) +#define UINT16_MAX ((uint16)~0) +#define UINT32_MAX ((uint32)~0) +#define UINT64_MAX ((uint64)~0) + +#define UINT8_MIN 0 +#define UINT16_MIN 0 +#define UINT32_MIN 0 +#define UINT64_MIN 0 + +#ifndef UINT_MIN +#define UINT_MIN UINT32_MIN +#endif + +#define FLOAT32_MAX FLT_MAX +#define FLOAT64_MAX DBL_MAX + +#define FLOAT32_MIN FLT_MIN +#define FLOAT64_MIN DBL_MIN + +// portability / compiler settings +#if defined(_WIN32) && !defined(WINDED) + +#if defined(_M_IX86) +#define __i386__ 1 +#endif + +#elif __linux__ +typedef unsigned int DWORD; +typedef unsigned short WORD; +typedef void *HINSTANCE; + +#define _MAX_PATH PATH_MAX +#endif // defined(_WIN32) && !defined(WINDED) + +// Defines MAX_PATH +#ifndef MAX_PATH +#define MAX_PATH 260 +#endif + +// Used to step into the debugger +#define DebuggerBreak() __asm { int 3 } + +// C functions for external declarations that call the appropriate C++ methods +#ifndef EXPORT +#ifdef _WIN32 +#define EXPORT __declspec( dllexport ) +#else +#define EXPORT /* */ +#endif +#endif + +#if defined __i386__ && !defined __linux__ +#define id386 1 +#else +#define id386 0 +#endif // __i386__ + +#ifdef _WIN32 +// Used for dll exporting and importing +#define DLL_EXPORT extern "C" __declspec( dllexport ) +#define DLL_IMPORT extern "C" __declspec( dllimport ) + +// Can't use extern "C" when DLL exporting a class +#define DLL_CLASS_EXPORT __declspec( dllexport ) +#define DLL_CLASS_IMPORT __declspec( dllimport ) + +// Can't use extern "C" when DLL exporting a global +#define DLL_GLOBAL_EXPORT extern __declspec( dllexport ) +#define DLL_GLOBAL_IMPORT extern __declspec( dllimport ) +#elif defined __linux__ || defined (__APPLE__) + +// Used for dll exporting and importing +#define DLL_EXPORT extern "C" +#define DLL_IMPORT extern "C" + +// Can't use extern "C" when DLL exporting a class +#define DLL_CLASS_EXPORT +#define DLL_CLASS_IMPORT + +// Can't use extern "C" when DLL exporting a global +#define DLL_GLOBAL_EXPORT extern +#define DLL_GLOBAL_IMPORT extern + +#else +#error "Unsupported Platform." +#endif + +#ifndef _WIN32 +#define FAKEFUNC (void *) 0 +#else +#define FAKEFUNC __noop +#endif + +// Used for standard calling conventions +#ifdef _WIN32 +#define STDCALL __stdcall +#define FASTCALL __fastcall +#ifndef FORCEINLINE +#define FORCEINLINE __forceinline +#endif +#else +#define STDCALL +#define FASTCALL +#define FORCEINLINE inline +#endif + +// Force a function call site -not- to inlined. (useful for profiling) +#define DONT_INLINE(a) (((int)(a)+1)?(a):(a)) + +// Pass hints to the compiler to prevent it from generating unnessecary / stupid code +// in certain situations. Several compilers other than MSVC also have an equivilent +// construct. +// +// Essentially the 'Hint' is that the condition specified is assumed to be true at +// that point in the compilation. If '0' is passed, then the compiler assumes that +// any subsequent code in the same 'basic block' is unreachable, and thus usually +// removed. +#ifdef _MSC_VER +#define HINT(THE_HINT) __assume((THE_HINT)) +#else +#define HINT(THE_HINT) 0 +#endif + +// Marks the codepath from here until the next branch entry point as unreachable, +// and asserts if any attempt is made to execute it. +#define UNREACHABLE() { ASSERT(0); HINT(0); } + +// In cases where no default is present or appropriate, this causes MSVC to generate +// as little code as possible, and throw an assertion in debug. +#define NO_DEFAULT default: UNREACHABLE(); + +#ifdef _WIN32 +// Alloca defined for this platform +#define stackalloc( _size ) _alloca( _size ) +#define stackfree( _p ) 0 +#elif __linux__ +// Alloca defined for this platform +#define stackalloc( _size ) alloca( _size ) +#define stackfree( _p ) 0 +#endif + +#endif diff --git a/include/engine/const.h b/include/engine/const.h new file mode 100644 index 0000000..58f9325 --- /dev/null +++ b/include/engine/const.h @@ -0,0 +1,774 @@ +/*** +* +* Copyright (c) 1999-2005, Valve Corporation. 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 source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#ifndef CONST_H +#define CONST_H +// +// Constants shared by the engine and dlls +// This header file included by engine files and DLL files. +// Most came from server.h + +// edict->flags +#define FL_FLY (1 << 0) // Changes the SV_Movestep() behavior to not need to be on ground +#define FL_SWIM (1 << 1) // Changes the SV_Movestep() behavior to not need to be on ground (but stay in water) +#define FL_CONVEYOR (1 << 2) +#define FL_CLIENT (1 << 3) +#define FL_INWATER (1 << 4) +#define FL_MONSTER (1 << 5) +#define FL_GODMODE (1 << 6) +#define FL_NOTARGET (1 << 7) +#define FL_SKIPLOCALHOST (1 << 8) // Don't send entity to local host, it's predicting this entity itself +#define FL_ONGROUND (1 << 9) // At rest / on the ground +#define FL_PARTIALGROUND (1 << 10) // not all corners are valid +#define FL_WATERJUMP (1 << 11) // player jumping out of water +#define FL_FROZEN (1 << 12) // Player is frozen for 3rd person camera +#define FL_FAKECLIENT (1 << 13) // JAC: fake client, simulated server side; don't send network messages to them +#define FL_DUCKING (1 << 14) // Player flag -- Player is fully crouched +#define FL_FLOAT (1 << 15) // Apply floating force to this entity when in water +#define FL_GRAPHED (1 << 16) // worldgraph has this ent listed as something that blocks a connection + +// UNDONE: Do we need these? +#define FL_IMMUNE_WATER (1 << 17) +#define FL_IMMUNE_SLIME (1 << 18) +#define FL_IMMUNE_LAVA (1 << 19) + +#define FL_PROXY (1 << 20) // This is a spectator proxy +#define FL_ALWAYSTHINK (1 << 21) // Brush model flag -- call think every frame regardless of nextthink - ltime (for constantly changing velocity/path) +#define FL_BASEVELOCITY (1 << 22) // Base velocity has been applied this frame (used to convert base velocity into momentum) +#define FL_MONSTERCLIP (1 << 23) // Only collide in with monsters who have FL_MONSTERCLIP set +#define FL_ONTRAIN (1 << 24) // Player is _controlling_ a train, so movement commands should be ignored on client during prediction. +#define FL_WORLDBRUSH (1 << 25) // Not moveable/removeable brush entity (really part of the world, but represented as an entity for transparency or something) +#define FL_SPECTATOR (1 << 26) // This client is a spectator, don't run touch functions, etc. +#define FL_CUSTOMENTITY (1 << 29) // This is a custom entity +#define FL_KILLME (1 << 30) // This entity is marked for death -- This allows the engine to kill ents at the appropriate time +#define FL_DORMANT (1 << 31) // Entity is dormant, no updates to client + +// Goes into globalvars_t.trace_flags +#define FTRACE_SIMPLEBOX (1 << 0) // Traceline with a simple box + +// walkmove modes +#define WALKMOVE_NORMAL 0 // normal walkmove +#define WALKMOVE_WORLDONLY 1 // doesn't hit ANY entities, no matter what the solid type +#define WALKMOVE_CHECKONLY 2 // move, but don't touch triggers + +// edict->movetype values +#define MOVETYPE_NONE 0 // never moves +#define MOVETYPE_WALK 3 // Player only - moving on the ground +#define MOVETYPE_STEP 4 // gravity, special edge handling -- monsters use this +#define MOVETYPE_FLY 5 // No gravity, but still collides with stuff +#define MOVETYPE_TOSS 6 // gravity/collisions +#define MOVETYPE_PUSH 7 // no clip to world, push and crush +#define MOVETYPE_NOCLIP 8 // No gravity, no collisions, still do velocity/avelocity +#define MOVETYPE_FLYMISSILE 9 // extra size to monsters +#define MOVETYPE_BOUNCE 10 // Just like Toss, but reflect velocity when contacting surfaces +#define MOVETYPE_BOUNCEMISSILE 11 // bounce w/o gravity +#define MOVETYPE_FOLLOW 12 // track movement of aiment +#define MOVETYPE_PUSHSTEP 13 // BSP model that needs physics/world collisions (uses nearest hull for world collision) + +// edict->solid values +// NOTE: Some movetypes will cause collisions independent of SOLID_NOT/SOLID_TRIGGER when the entity moves +// SOLID only effects OTHER entities colliding with this one when they move - UGH! +#define SOLID_NOT 0 // no interaction with other objects +#define SOLID_TRIGGER 1 // touch on edge, but not blocking +#define SOLID_BBOX 2 // touch on edge, block +#define SOLID_SLIDEBOX 3 // touch on edge, but not an onground +#define SOLID_BSP 4 // bsp clip, touch on edge, block + +// edict->deadflag values +#define DEAD_NO 0 // alive +#define DEAD_DYING 1 // playing death animation or still falling off of a ledge waiting to hit ground +#define DEAD_DEAD 2 // dead. lying still. +#define DEAD_RESPAWNABLE 3 +#define DEAD_DISCARDBODY 4 + +#define DAMAGE_NO 0 +#define DAMAGE_YES 1 +#define DAMAGE_AIM 2 + +// entity effects +#define EF_BRIGHTFIELD 1 // swirling cloud of particles +#define EF_MUZZLEFLASH 2 // single frame ELIGHT on entity attachment 0 +#define EF_BRIGHTLIGHT 4 // DLIGHT centered at entity origin +#define EF_DIMLIGHT 8 // player flashlight +#define EF_INVLIGHT 16 // get lighting from ceiling +#define EF_NOINTERP 32 // don't interpolate the next frame +#define EF_LIGHT 64 // rocket flare glow sprite +#define EF_NODRAW 128 // don't draw entity + +// entity flags +#define EFLAG_SLERP 1 // do studio interpolation of this entity + +// +// temp entity events +// +#define TE_BEAMPOINTS 0 // beam effect between two points +// coord coord coord (start position) +// coord coord coord (end position) +// short (sprite index) +// byte (starting frame) +// byte (frame rate in 0.1's) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte (noise amplitude in 0.01's) +// byte,byte,byte (color) +// byte (brightness) +// byte (scroll speed in 0.1's) + +#define TE_BEAMENTPOINT 1 // beam effect between point and entity +// short (start entity) +// coord coord coord (end position) +// short (sprite index) +// byte (starting frame) +// byte (frame rate in 0.1's) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte (noise amplitude in 0.01's) +// byte,byte,byte (color) +// byte (brightness) +// byte (scroll speed in 0.1's) + +#define TE_GUNSHOT 2 // particle effect plus ricochet sound +// coord coord coord (position) + +#define TE_EXPLOSION 3 // additive sprite, 2 dynamic lights, flickering particles, explosion sound, move vertically 8 pps +// coord coord coord (position) +// short (sprite index) +// byte (scale in 0.1's) +// byte (framerate) +// byte (flags) +// +// The Explosion effect has some flags to control performance/aesthetic features: +#define TE_EXPLFLAG_NONE 0 // all flags clear makes default Half-Life explosion +#define TE_EXPLFLAG_NOADDITIVE 1 // sprite will be drawn opaque (ensure that the sprite you send is a non-additive sprite) +#define TE_EXPLFLAG_NODLIGHTS 2 // do not render dynamic lights +#define TE_EXPLFLAG_NOSOUND 4 // do not play client explosion sound +#define TE_EXPLFLAG_NOPARTICLES 8 // do not draw particles + + +#define TE_TAREXPLOSION 4 // Quake1 "tarbaby" explosion with sound +// coord coord coord (position) + +#define TE_SMOKE 5 // alphablend sprite, move vertically 30 pps +// coord coord coord (position) +// short (sprite index) +// byte (scale in 0.1's) +// byte (framerate) + +#define TE_TRACER 6 // tracer effect from point to point +// coord, coord, coord (start) +// coord, coord, coord (end) + +#define TE_LIGHTNING 7 // TE_BEAMPOINTS with simplified parameters +// coord, coord, coord (start) +// coord, coord, coord (end) +// byte (life in 0.1's) +// byte (width in 0.1's) +// byte (amplitude in 0.01's) +// short (sprite model index) + +#define TE_BEAMENTS 8 +// short (start entity) +// short (end entity) +// short (sprite index) +// byte (starting frame) +// byte (frame rate in 0.1's) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte (noise amplitude in 0.01's) +// byte,byte,byte (color) +// byte (brightness) +// byte (scroll speed in 0.1's) + +#define TE_SPARKS 9 // 8 random tracers with gravity, ricochet sprite +// coord coord coord (position) + +#define TE_LAVASPLASH 10 // Quake1 lava splash +// coord coord coord (position) + +#define TE_TELEPORT 11 // Quake1 teleport splash +// coord coord coord (position) + +#define TE_EXPLOSION2 12 // Quake1 colormaped (base palette) particle explosion with sound +// coord coord coord (position) +// byte (starting color) +// byte (num colors) + +#define TE_BSPDECAL 13 // Decal from the .BSP file +// coord, coord, coord (x,y,z), decal position (center of texture in world) +// short (texture index of precached decal texture name) +// short (entity index) +// [optional - only included if previous short is non-zero (not the world)] short (index of model of above entity) + +#define TE_IMPLOSION 14 // tracers moving toward a point +// coord, coord, coord (position) +// byte (radius) +// byte (count) +// byte (life in 0.1's) + +#define TE_SPRITETRAIL 15 // line of moving glow sprites with gravity, fadeout, and collisions +// coord, coord, coord (start) +// coord, coord, coord (end) +// short (sprite index) +// byte (count) +// byte (life in 0.1's) +// byte (scale in 0.1's) +// byte (velocity along Vector in 10's) +// byte (randomness of velocity in 10's) + +#define TE_BEAM 16 // obsolete + +#define TE_SPRITE 17 // additive sprite, plays 1 cycle +// coord, coord, coord (position) +// short (sprite index) +// byte (scale in 0.1's) +// byte (brightness) + +#define TE_BEAMSPRITE 18 // A beam with a sprite at the end +// coord, coord, coord (start position) +// coord, coord, coord (end position) +// short (beam sprite index) +// short (end sprite index) + +#define TE_BEAMTORUS 19 // screen aligned beam ring, expands to max radius over lifetime +// coord coord coord (center position) +// coord coord coord (axis and radius) +// short (sprite index) +// byte (starting frame) +// byte (frame rate in 0.1's) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte (noise amplitude in 0.01's) +// byte,byte,byte (color) +// byte (brightness) +// byte (scroll speed in 0.1's) + +#define TE_BEAMDISK 20 // disk that expands to max radius over lifetime +// coord coord coord (center position) +// coord coord coord (axis and radius) +// short (sprite index) +// byte (starting frame) +// byte (frame rate in 0.1's) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte (noise amplitude in 0.01's) +// byte,byte,byte (color) +// byte (brightness) +// byte (scroll speed in 0.1's) + +#define TE_BEAMCYLINDER 21 // cylinder that expands to max radius over lifetime +// coord coord coord (center position) +// coord coord coord (axis and radius) +// short (sprite index) +// byte (starting frame) +// byte (frame rate in 0.1's) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte (noise amplitude in 0.01's) +// byte,byte,byte (color) +// byte (brightness) +// byte (scroll speed in 0.1's) + +#define TE_BEAMFOLLOW 22 // create a line of decaying beam segments until entity stops moving +// short (entity:attachment to follow) +// short (sprite index) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte,byte,byte (color) +// byte (brightness) + +#define TE_GLOWSPRITE 23 +// coord, coord, coord (pos) short (model index) byte (scale / 10) + +#define TE_BEAMRING 24 // connect a beam ring to two entities +// short (start entity) +// short (end entity) +// short (sprite index) +// byte (starting frame) +// byte (frame rate in 0.1's) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte (noise amplitude in 0.01's) +// byte,byte,byte (color) +// byte (brightness) +// byte (scroll speed in 0.1's) + +#define TE_STREAK_SPLASH 25 // oriented shower of tracers +// coord coord coord (start position) +// coord coord coord (direction Vector) +// byte (color) +// short (count) +// short (base speed) +// short (ramdon velocity) + +#define TE_BEAMHOSE 26 // obsolete + +#define TE_DLIGHT 27 // dynamic light, effect world, minor entity effect +// coord, coord, coord (pos) +// byte (radius in 10's) +// byte byte byte (color) +// byte (brightness) +// byte (life in 10's) +// byte (decay rate in 10's) + +#define TE_ELIGHT 28 // point entity light, no world effect +// short (entity:attachment to follow) +// coord coord coord (initial position) +// coord (radius) +// byte byte byte (color) +// byte (life in 0.1's) +// coord (decay rate) + +#define TE_TEXTMESSAGE 29 +// short 1.2.13 x (-1 = center) +// short 1.2.13 y (-1 = center) +// byte Effect 0 = fade in/fade out + // 1 is flickery credits + // 2 is write out (training room) + +// 4 bytes r,g,b,a color1 (text color) +// 4 bytes r,g,b,a color2 (effect color) +// ushort 8.8 fadein time +// ushort 8.8 fadeout time +// ushort 8.8 hold time +// optional ushort 8.8 fxtime (time the highlight lags behing the leading text in effect 2) +// string text message (512 chars max sz string) +#define TE_LINE 30 +// coord, coord, coord startpos +// coord, coord, coord endpos +// short life in 0.1 s +// 3 bytes r, g, b + +#define TE_BOX 31 +// coord, coord, coord boxmins +// coord, coord, coord boxmaxs +// short life in 0.1 s +// 3 bytes r, g, b + +#define TE_KILLBEAM 99 // kill all beams attached to entity +// short (entity) + +#define TE_LARGEFUNNEL 100 +// coord coord coord (funnel position) +// short (sprite index) +// short (flags) + +#define TE_BLOODSTREAM 101 // particle spray +// coord coord coord (start position) +// coord coord coord (spray Vector) +// byte (color) +// byte (speed) + +#define TE_SHOWLINE 102 // line of particles every 5 units, dies in 30 seconds +// coord coord coord (start position) +// coord coord coord (end position) + +#define TE_BLOOD 103 // particle spray +// coord coord coord (start position) +// coord coord coord (spray Vector) +// byte (color) +// byte (speed) + +#define TE_DECAL 104 // Decal applied to a brush entity (not the world) +// coord, coord, coord (x,y,z), decal position (center of texture in world) +// byte (texture index of precached decal texture name) +// short (entity index) + +#define TE_FIZZ 105 // create alpha sprites inside of entity, float upwards +// short (entity) +// short (sprite index) +// byte (density) + +#define TE_MODEL 106 // create a moving model that bounces and makes a sound when it hits +// coord, coord, coord (position) +// coord, coord, coord (velocity) +// angle (initial yaw) +// short (model index) +// byte (bounce sound type) +// byte (life in 0.1's) + +#define TE_EXPLODEMODEL 107 // spherical shower of models, picks from set +// coord, coord, coord (origin) +// coord (velocity) +// short (model index) +// short (count) +// byte (life in 0.1's) + +#define TE_BREAKMODEL 108 // box of models or sprites +// coord, coord, coord (position) +// coord, coord, coord (size) +// coord, coord, coord (velocity) +// byte (random velocity in 10's) +// short (sprite or model index) +// byte (count) +// byte (life in 0.1 secs) +// byte (flags) + +#define TE_GUNSHOTDECAL 109 // decal and ricochet sound +// coord, coord, coord (position) +// short (entity index???) +// byte (decal???) + +#define TE_SPRITE_SPRAY 110 // spay of alpha sprites +// coord, coord, coord (position) +// coord, coord, coord (velocity) +// short (sprite index) +// byte (count) +// byte (speed) +// byte (noise) + +#define TE_ARMOR_RICOCHET 111 // quick spark sprite, client ricochet sound. +// coord, coord, coord (position) +// byte (scale in 0.1's) + +#define TE_PLAYERDECAL 112 // ??? +// byte (playerindex) +// coord, coord, coord (position) +// short (entity???) +// byte (decal number???) +// [optional] short (model index???) + +#define TE_BUBBLES 113 // create alpha sprites inside of box, float upwards +// coord, coord, coord (min start position) +// coord, coord, coord (max start position) +// coord (float height) +// short (model index) +// byte (count) +// coord (speed) + +#define TE_BUBBLETRAIL 114 // create alpha sprites along a line, float upwards +// coord, coord, coord (min start position) +// coord, coord, coord (max start position) +// coord (float height) +// short (model index) +// byte (count) +// coord (speed) + +#define TE_BLOODSPRITE 115 // spray of opaque sprite1's that fall, single sprite2 for 1..2 secs (this is a high-priority tent) +// coord, coord, coord (position) +// short (sprite1 index) +// short (sprite2 index) +// byte (color) +// byte (scale) + +#define TE_WORLDDECAL 116 // Decal applied to the world brush +// coord, coord, coord (x,y,z), decal position (center of texture in world) +// byte (texture index of precached decal texture name) + +#define TE_WORLDDECALHIGH 117 // Decal (with texture index > 256) applied to world brush +// coord, coord, coord (x,y,z), decal position (center of texture in world) +// byte (texture index of precached decal texture name - 256) + +#define TE_DECALHIGH 118 // Same as TE_DECAL, but the texture index was greater than 256 +// coord, coord, coord (x,y,z), decal position (center of texture in world) +// byte (texture index of precached decal texture name - 256) +// short (entity index) + +#define TE_PROJECTILE 119 // Makes a projectile (like a nail) (this is a high-priority tent) +// coord, coord, coord (position) +// coord, coord, coord (velocity) +// short (modelindex) +// byte (life) +// byte (owner) projectile won't collide with owner (if owner == 0, projectile will hit any client). + +#define TE_SPRAY 120 // Throws a shower of sprites or models +// coord, coord, coord (position) +// coord, coord, coord (direction) +// short (modelindex) +// byte (count) +// byte (speed) +// byte (noise) +// byte (rendermode) + +#define TE_PLAYERSPRITES 121 // sprites emit from a player's bounding box (ONLY use for players!) +// byte (playernum) +// short (sprite modelindex) +// byte (count) +// byte (variance) (0 = no variance in size) (10 = 10% variance in size) + +#define TE_PARTICLEBURST 122 // very similar to lavasplash. +// coord (origin) +// short (radius) +// byte (particle color) +// byte (duration * 10) (will be randomized a bit) + +#define TE_FIREFIELD 123 // makes a field of fire. +// coord (origin) +// short (radius) (fire is made in a square around origin. -radius, -radius to radius, radius) +// short (modelindex) +// byte (count) +// byte (flags) +// byte (duration (in seconds) * 10) (will be randomized a bit) +// +// to keep network traffic low, this message has associated flags that fit into a byte: +#define TEFIRE_FLAG_ALLFLOAT 1 // all sprites will drift upwards as they animate +#define TEFIRE_FLAG_SOMEFLOAT 2 // some of the sprites will drift upwards. (50% chance) +#define TEFIRE_FLAG_LOOP 4 // if set, sprite plays at 15 fps, otherwise plays at whatever rate stretches the animation over the sprite's duration. +#define TEFIRE_FLAG_ALPHA 8 // if set, sprite is rendered alpha blended at 50% else, opaque +#define TEFIRE_FLAG_PLANAR 16 // if set, all fire sprites have same initial Z instead of randomly filling a cube. + +#define TE_PLAYERATTACHMENT 124 // attaches a TENT to a player (this is a high-priority tent) +// byte (entity index of player) +// coord (vertical offset) ( attachment origin.z = player origin.z + vertical offset ) +// short (model index) +// short (life * 10 ); + +#define TE_KILLPLAYERATTACHMENTS 125 // will expire all TENTS attached to a player. +// byte (entity index of player) + +#define TE_MULTIGUNSHOT 126 // much more compact shotgun message +// This message is used to make a client approximate a 'spray' of gunfire. +// Any weapon that fires more than one bullet per frame and fires in a bit of a spread is +// a good candidate for MULTIGUNSHOT use. (shotguns) +// +// NOTE: This effect makes the client do traces for each bullet, these client traces ignore +// entities that have studio models.Traces are 4096 long. +// +// coord (origin) +// coord (origin) +// coord (origin) +// coord (direction) +// coord (direction) +// coord (direction) +// coord (x noise * 100) +// coord (y noise * 100) +// byte (count) +// byte (bullethole decal texture index) + +#define TE_USERTRACER 127 // larger message than the standard tracer, but allows some customization. +// coord (origin) +// coord (origin) +// coord (origin) +// coord (velocity) +// coord (velocity) +// coord (velocity) +// byte ( life * 10 ) +// byte ( color ) this is an index into an array of color vectors in the engine. (0 - ) +// byte ( length * 10 ) + + + +#define MSG_BROADCAST 0 // unreliable to all +#define MSG_ONE 1 // reliable to one (msg_entity) +#define MSG_ALL 2 // reliable to all +#define MSG_INIT 3 // write to the init string +#define MSG_PVS 4 // Ents in PVS of org +#define MSG_PAS 5 // Ents in PAS of org +#define MSG_PVS_R 6 // Reliable to PVS +#define MSG_PAS_R 7 // Reliable to PAS +#define MSG_ONE_UNRELIABLE 8 // Send to one client, but don't put in reliable stream, put in unreliable datagram ( could be dropped ) +#define MSG_SPEC 9 // Sends to all spectator proxies + +// contents of a spot in the world +#define CONTENTS_EMPTY -1 +#define CONTENTS_SOLID -2 +#define CONTENTS_WATER -3 +#define CONTENTS_SLIME -4 +#define CONTENTS_LAVA -5 +#define CONTENTS_SKY -6 + +#define CONTENTS_LADDER -16 + +#define CONTENT_FLYFIELD -17 +#define CONTENT_GRAVITY_FLYFIELD -18 +#define CONTENT_FOG -19 + +#define CONTENT_EMPTY -1 +#define CONTENT_SOLID -2 +#define CONTENT_WATER -3 +#define CONTENT_SLIME -4 +#define CONTENT_LAVA -5 +#define CONTENT_SKY -6 + +// channels +#define CHAN_AUTO 0 +#define CHAN_WEAPON 1 +#define CHAN_VOICE 2 +#define CHAN_ITEM 3 +#define CHAN_BODY 4 +#define CHAN_STREAM 5 // allocate stream channel from the static or dynamic area +#define CHAN_STATIC 6 // allocate channel from the static area +#define CHAN_NETWORKVOICE_BASE 7 // voice data coming across the network +#define CHAN_NETWORKVOICE_END 500 // network voice data reserves slots (CHAN_NETWORKVOICE_BASE through CHAN_NETWORKVOICE_END). + +// attenuation values +#define ATTN_NONE 0 +#define ATTN_NORM (float)0.8 +#define ATTN_IDLE (float)2 +#define ATTN_STATIC (float)1.25 + +// pitch values +#define PITCH_NORM 100 // non-pitch shifted +#define PITCH_LOW 95 // other values are possible - 0-255, where 255 is very high +#define PITCH_HIGH 120 + +// volume values +#define VOL_NORM 1.0 + +// plats +#define PLAT_LOW_TRIGGER 1 + +// Trains +#define SF_TRAIN_WAIT_RETRIGGER 1 +#define SF_TRAIN_START_ON 4 // Train is initially moving +#define SF_TRAIN_PASSABLE 8 // Train is not solid -- used to make water trains + +// buttons +#define IN_ATTACK (1 << 0) +#define IN_JUMP (1 << 1) +#define IN_DUCK (1 << 2) +#define IN_FORWARD (1 << 3) +#define IN_BACK (1 << 4) +#define IN_USE (1 << 5) +#define IN_CANCEL (1 << 6) +#define IN_LEFT (1 << 7) +#define IN_RIGHT (1 << 8) +#define IN_MOVELEFT (1 << 9) +#define IN_MOVERIGHT (1 << 10) +#define IN_ATTACK2 (1 << 11) +#define IN_RUN (1 << 12) +#define IN_RELOAD (1 << 13) +#define IN_ALT1 (1 << 14) +#define IN_SCORE (1 << 15) + +// Break Model Defines + +#define BREAK_TYPEMASK 0x4F +#define BREAK_GLASS 0x01 +#define BREAK_METAL 0x02 +#define BREAK_FLESH 0x04 +#define BREAK_WOOD 0x08 + +#define BREAK_SMOKE 0x10 +#define BREAK_TRANS 0x20 +#define BREAK_CONCRETE 0x40 +#define BREAK_2 0x80 + +// Colliding temp entity sounds + +#define BOUNCE_GLASS BREAK_GLASS +#define BOUNCE_METAL BREAK_METAL +#define BOUNCE_FLESH BREAK_FLESH +#define BOUNCE_WOOD BREAK_WOOD +#define BOUNCE_SHRAP 0x10 +#define BOUNCE_SHELL 0x20 +#define BOUNCE_CONCRETE BREAK_CONCRETE +#define BOUNCE_SHOTSHELL 0x80 + +// Temp entity bounce sound types +#define TE_BOUNCE_NULL 0 +#define TE_BOUNCE_SHELL 1 +#define TE_BOUNCE_SHOTSHELL 2 + +// Rendering constants +enum +{ + kRenderNormal, // src + kRenderTransColor, // c*a+dest*(1-a) + kRenderTransTexture, // src*a+dest*(1-a) + kRenderGlow, // src*a+dest -- No Z buffer checks + kRenderTransAlpha, // src*srca+dest*(1-srca) + kRenderTransAdd, // src*a+dest +}; + +enum +{ + kRenderFxNone = 0, + kRenderFxPulseSlow, + kRenderFxPulseFast, + kRenderFxPulseSlowWide, + kRenderFxPulseFastWide, + kRenderFxFadeSlow, + kRenderFxFadeFast, + kRenderFxSolidSlow, + kRenderFxSolidFast, + kRenderFxStrobeSlow, + kRenderFxStrobeFast, + kRenderFxStrobeFaster, + kRenderFxFlickerSlow, + kRenderFxFlickerFast, + kRenderFxNoDissipation, + kRenderFxDistort, // Distort/scale/translate flicker + kRenderFxHologram, // kRenderFxDistort + distance fade + kRenderFxDeadPlayer, // kRenderAmt is the player index + kRenderFxExplode, // Scale up really big! + kRenderFxGlowShell, // Glowing Shell + kRenderFxClampMinScale, // Keep this sprite from getting very small (SPRITES only!) +}; + + +typedef int func_t; +typedef int string_t; + +typedef unsigned char byte; +typedef unsigned short word; + +#define _DEF_BYTE_ + +#undef true +#undef false + +#ifndef __cplusplus +typedef enum +{ false, true } int; +#else +#endif + +typedef struct +{ + byte r, g, b; +} color24; + +typedef struct +{ + unsigned r, g, b, a; +} colorVec; + +#ifdef _WIN32 +#pragma pack(push,2) +#endif + +typedef struct +{ + unsigned short r, g, b, a; +} PackedColorVec; + +#ifdef _WIN32 +#pragma pack(pop) +#endif +typedef struct link_s +{ + struct link_s *prev, *next; +} link_t; + +typedef struct edict_s edict_t; + +typedef struct +{ + vec3_t normal; + float dist; +} plane_t; + +typedef struct +{ + int allsolid; // if true, plane is not valid + int startsolid; // if true, the initial point was in a solid area + int inopen, inwater; + float fraction; // time completed, 1.0 = didn't hit anything + vec3_t endpos; // final position + plane_t plane; // surface normal at impact + edict_t *ent; // entity the surface is on + int hitgroup; // 0 == generic, non zero is specific body part +} trace_t; + +#endif diff --git a/include/engine/dllapi.h b/include/engine/dllapi.h new file mode 100644 index 0000000..2c4bd32 --- /dev/null +++ b/include/engine/dllapi.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2001-2006 Will Day + * + * This file is part of Metamod. + * + * Metamod 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. + * + * Metamod 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 Metamod; 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. + * + */ + +// Simplified version by Wei Mingzhi + +#ifndef DLLAPI_H +#define DLLAPI_H + +#undef DLLEXPORT +#ifdef _WIN32 +#define DLLEXPORT __declspec(dllexport) +#elif defined(linux) || defined (__APPLE__) +#define DLLEXPORT /* */ +#define WINAPI /* */ +#endif /* linux */ + +#define C_DLLEXPORT extern "C" DLLEXPORT + +#endif /* DLLAPI_H */ diff --git a/include/engine/eiface.h b/include/engine/eiface.h new file mode 100644 index 0000000..e159998 --- /dev/null +++ b/include/engine/eiface.h @@ -0,0 +1,398 @@ +/*** +* +* Copyright (c) 1999-2005, Valve Corporation. 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 source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#ifndef EIFACE_H +#define EIFACE_H + +#define INTERFACE_VERSION 140 + +#include +#include "archtypes.h" + +#define FCVAR_ARCHIVE (1 << 0) // set to cause it to be saved to vars.rc +#define FCVAR_USERINFO (1 << 1) // changes the client's info string +#define FCVAR_SERVER (1 << 2) // notifies players when changed +#define FCVAR_EXTDLL (1 << 3) // defined by external DLL +#define FCVAR_CLIENTDLL (1 << 4) // defined by the client dll +#define FCVAR_PROTECTED (1 << 5) // It's a server cvar, but we don't send the data since it's a password, etc. Sends 1 if it's not bland/zero, 0 otherwise as value +#define FCVAR_SPONLY (1 << 6) // This cvar cannot be changed by clients connected to a multiplayer server. +#define FCVAR_PRINTABLEONLY (1 << 7) // This cvar's string cannot contain unprintable characters ( e.g., used for player name etc ). +#define FCVAR_UNLOGGED (1 << 8) // If this is a FCVAR_SERVER, don't log changes to the log file / console if we are creating a log + +struct cvar_t +{ + char *name; +#if defined (YAPB_INCLUDED) + char *strval; // (dz): changed since genclass.h library #define +#else + char *string; +#endif + int flags; + float value; + cvar_t *next; +}; + +// +// Defines entity interface between engine and DLLs. +// This header file included by engine files and DLL files. +// +// Before including this header, DLLs must: +// include progdefs.h +// This is conveniently done for them in extdll.h +// + +#ifdef _WIN32 +#define DLLEXPORT __stdcall +#else +#define DLLEXPORT /* */ +#endif + +typedef enum +{ + at_notice, + at_console, // same as at_notice, but forces a ConPrintf, not a message box + at_aiconsole, // same as at_console, but only shown if developer level is 2! + at_warning, + at_error, + at_logged // Server print to console ( only in multiplayer games ). +} ALERT_TYPE; + +// 4-22-98 JOHN: added for use in pfnClientPrintf +typedef enum +{ + print_console, + print_center, + print_chat, +} PRINT_TYPE; + +#if defined (YAPB_INCLUDED) +typedef enum +{ + print_withtag = print_console | 0x3ff, +} PRINT_TYPE_EX; // (dz): added for bots needs +#endif + +// For integrity checking of content on clients +typedef enum +{ + force_exactfile, // File on client must exactly match server's file + force_model_samebounds, // For model files only, the geometry must fit in the same bbox + force_model_specifybounds, // For model files only, the geometry must fit in the specified bbox +} FORCE_TYPE; + +// Returned by TraceLine +typedef struct +{ + int fAllSolid; // if true, plane is not valid + int fStartSolid; // if true, the initial point was in a solid area + int fInOpen; + int fInWater; + float flFraction; // time completed, 1.0 = didn't hit anything + vec3_t vecEndPos; // final position + float flPlaneDist; + vec3_t vecPlaneNormal; // surface normal at impact + edict_t *pHit; // entity the surface is on + int iHitgroup; // 0 == generic, non zero is specific body part +} TraceResult; + +typedef uint32 CRC32_t; + +// Engine hands this to DLLs for functionality callbacks + +typedef struct enginefuncs_s +{ + int (*pfnPrecacheModel) (char *s); + int (*pfnPrecacheSound) (char *s); + void (*pfnSetModel) (edict_t *e, const char *m); + int (*pfnModelIndex) (const char *m); + int (*pfnModelFrames) (int modelIndex); + void (*pfnSetSize) (edict_t *e, const float *rgflMin, const float *rgflMax); + void (*pfnChangeLevel) (char *s1, char *s2); + void (*pfnGetSpawnParms) (edict_t *ent); + void (*pfnSaveSpawnParms) (edict_t *ent); + float (*pfnVecToYaw) (const float *rgflVector); + void (*pfnVecToAngles) (const float *rgflVectorIn, float *rgflVectorOut); + void (*pfnMoveToOrigin) (edict_t *ent, const float *pflGoal, float dist, int iMoveType); + void (*pfnChangeYaw) (edict_t *ent); + void (*pfnChangePitch) (edict_t *ent); + edict_t *(*pfnFindEntityByString) (edict_t *pentEdictStartSearchAfter, const char *pszField, const char *pszValue); + int (*pfnGetEntityIllum) (edict_t *pEnt); + edict_t *(*pfnFindEntityInSphere) (edict_t *pentEdictStartSearchAfter, const float *org, float rad); + edict_t *(*pfnFindClientInPVS) (edict_t *ent); + edict_t *(*pfnEntitiesInPVS) (edict_t *pplayer); + void (*pfnMakeVectors) (const float *rgflVector); + void (*pfnAngleVectors) (const float *rgflVector, float *forward, float *right, float *up); + edict_t *(*pfnCreateEntity) (void); + void (*pfnRemoveEntity) (edict_t *e); + edict_t *(*pfnCreateNamedEntity) (int className); + void (*pfnMakeStatic) (edict_t *ent); + int (*pfnEntIsOnFloor) (edict_t *e); + int (*pfnDropToFloor) (edict_t *e); + int (*pfnWalkMove) (edict_t *ent, float yaw, float dist, int mode); + void (*pfnSetOrigin) (edict_t *e, const float *rgflOrigin); + void (*pfnEmitSound) (edict_t *entity, int channel, const char *sample, float volume, float attenuation, int fFlags, int pitch); + void (*pfnEmitAmbientSound) (edict_t *entity, float *pos, const char *samp, float vol, float attenuation, int fFlags, int pitch); + void (*pfnTraceLine) (const float *v1, const float *v2, int fNoMonsters, edict_t *pentToSkip, TraceResult *ptr); + void (*pfnTraceToss) (edict_t *pent, edict_t *pentToIgnore, TraceResult *ptr); + int (*pfnTraceMonsterHull) (edict_t *ent, const float *v1, const float *v2, int fNoMonsters, edict_t *pentToSkip, TraceResult *ptr); + void (*pfnTraceHull) (const float *v1, const float *v2, int fNoMonsters, int hullNumber, edict_t *pentToSkip, TraceResult *ptr); + void (*pfnTraceModel) (const float *v1, const float *v2, int hullNumber, edict_t *pent, TraceResult *ptr); + const char *(*pfnTraceTexture) (edict_t *pTextureEntity, const float *v1, const float *v2); + void (*pfnTraceSphere) (const float *v1, const float *v2, int fNoMonsters, float radius, edict_t *pentToSkip, TraceResult *ptr); + void (*pfnGetAimVector) (edict_t *ent, float speed, float *rgflReturn); + void (*pfnServerCommand) (char *str); + void (*pfnServerExecute) (void); + void (*pfnClientCommand) (edict_t *ent, char *szFmt, ...); + void (*pfnParticleEffect) (const float *org, const float *dir, float color, float count); + void (*pfnLightStyle) (int style, char *val); + int (*pfnDecalIndex) (const char *name); + int (*pfnPointContents) (const float *rgflVector); + void (*pfnMessageBegin) (int msg_dest, int msg_type, const float *pOrigin, edict_t *ed); + void (*pfnMessageEnd) (void); + void (*pfnWriteByte) (int value); + void (*pfnWriteChar) (int value); + void (*pfnWriteShort) (int value); + void (*pfnWriteLong) (int value); + void (*pfnWriteAngle) (float flValue); + void (*pfnWriteCoord) (float flValue); + void (*pfnWriteString) (const char *sz); + void (*pfnWriteEntity) (int value); + void (*pfnCVarRegister) (cvar_t *pCvar); + float (*pfnCVarGetFloat) (const char *szVarName); + const char *(*pfnCVarGetString) (const char *szVarName); + void (*pfnCVarSetFloat) (const char *szVarName, float flValue); + void (*pfnCVarSetString) (const char *szVarName, const char *szValue); + void (*pfnAlertMessage) (ALERT_TYPE atype, char *szFmt, ...); + void (*pfnEngineFprintf) (void *pfile, char *szFmt, ...); + void *(*pfnPvAllocEntPrivateData) (edict_t *ent, int32 cb); + void *(*pfnPvEntPrivateData) (edict_t *ent); + void (*pfnFreeEntPrivateData) (edict_t *ent); + const char *(*pfnSzFromIndex) (int stingPtr); + int (*pfnAllostring) (const char *szValue); + struct entvars_s *(*pfnGetVarsOfEnt) (edict_t *ent); + edict_t *(*pfnPEntityOfEntOffset) (int iEntOffset); + int (*pfnEntOffsetOfPEntity) (const edict_t *ent); + int (*pfnIndexOfEdict) (const edict_t *ent); + edict_t *(*pfnPEntityOfEntIndex) (int entIndex); + edict_t *(*pfnFindEntityByVars) (struct entvars_s *pvars); + void *(*pfnGetModelPtr) (edict_t *ent); + int (*pfnRegUserMsg) (const char *pszName, int iSize); + void (*pfnAnimationAutomove) (const edict_t *ent, float flTime); + void (*pfnGetBonePosition) (const edict_t *ent, int iBone, float *rgflOrigin, float *rgflAngles); + uint32 (*pfnFunctionFromName) (const char *pName); + const char *(*pfnNameForFunction) (uint32 function); + void (*pfnClientPrintf) (edict_t *ent, PRINT_TYPE ptype, const char *szMsg); // JOHN: engine callbacks so game DLL can print messages to individual clients + void (*pfnServerPrint) (const char *szMsg); + const char *(*pfnCmd_Args) (void); // these 3 added + const char *(*pfnCmd_Argv) (int argc); // so game DLL can easily + int (*pfnCmd_Argc) (void); // access client 'cmd' strings + void (*pfnGetAttachment) (const edict_t *ent, int iAttachment, float *rgflOrigin, float *rgflAngles); + void (*pfnCRC32_Init) (CRC32_t *pulCRC); + void (*pfnCRC32_ProcessBuffer) (CRC32_t *pulCRC, void *p, int len); + void (*pfnCRC32_ProcessByte) (CRC32_t *pulCRC, unsigned char ch); + CRC32_t (*pfnCRC32_Final) (CRC32_t pulCRC); + int32 (*pfnRandomLong) (int32 lLow, int32 lHigh); + float (*pfnRandomFloat) (float flLow, float flHigh); + void (*pfnSetView) (const edict_t *client, const edict_t *pViewent); + float (*pfnTime) (void); + void (*pfnCrosshairAngle) (const edict_t *client, float pitch, float yaw); + byte *(*pfnLoadFileForMe) (char *szFilename, int *pLength); + void (*pfnFreeFile) (void *buffer); + void (*pfnEndSection) (const char *pszSectionName); // trigger_endsection + int (*pfnCompareFileTime) (char *filename1, char *filename2, int *compare); + void (*pfnGetGameDir) (char *szGetGameDir); + void (*pfnCvar_RegisterVariable) (cvar_t *variable); + void (*pfnFadeClientVolume) (const edict_t *ent, int fadePercent, int fadeOutSeconds, int holdTime, int fadeInSeconds); + void (*pfnSetClientMaxspeed) (const edict_t *ent, float fNewMaxspeed); + edict_t *(*pfnCreateFakeClient) (const char *netname); // returns NULL if fake client can't be created + void (*pfnRunPlayerMove) (edict_t *fakeclient, const float *viewangles, float forwardmove, float sidemove, float upmove, unsigned short buttons, byte impulse, byte msec); + int (*pfnNumberOfEntities) (void); + char *(*pfnGetInfoKeyBuffer) (edict_t *e); // passing in NULL gets the serverinfo + char *(*pfnInfoKeyValue) (char *infobuffer, char *key); + void (*pfnSetKeyValue) (char *infobuffer, char *key, char *value); + void (*pfnSetClientKeyValue) (int clientIndex, char *infobuffer, char *key, char *value); + int (*pfnIsMapValid) (char *szFilename); + void (*pfnStaticDecal) (const float *origin, int decalIndex, int entityIndex, int modelIndex); + int (*pfnPrecacheGeneric) (char *s); + int (*pfnGetPlayerUserId) (edict_t *e); // returns the server assigned userid for this player. useful for logging frags, etc. returns -1 if the edict couldn't be found in the list of clients + void (*pfnBuildSoundMsg) (edict_t *entity, int channel, const char *sample, float volume, float attenuation, int fFlags, int pitch, int msg_dest, int msg_type, const float *pOrigin, edict_t *ed); + int (*pfnIsDedicatedServer) (void); // is this a dedicated server? + cvar_t *(*pfnCVarGetPointer) (const char *szVarName); + unsigned int (*pfnGetPlayerWONId) (edict_t *e); // returns the server assigned WONid for this player. useful for logging frags, etc. returns -1 if the edict couldn't be found in the list of clients + + void (*pfnInfo_RemoveKey) (char *s, const char *key); + const char *(*pfnGetPhysicsKeyValue) (const edict_t *client, const char *key); + void (*pfnSetPhysicsKeyValue) (const edict_t *client, const char *key, const char *value); + const char *(*pfnGetPhysicsInfoString) (const edict_t *client); + unsigned short (*pfnPrecacheEvent) (int type, const char *psz); + void (*pfnPlaybackEvent) (int flags, const edict_t *pInvoker, unsigned short eventindex, float delay, float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2); + unsigned char *(*pfnSetFatPVS) (float *org); + unsigned char *(*pfnSetFatPAS) (float *org); + int (*pfnCheckVisibility) (const edict_t *entity, unsigned char *pset); + void (*pfnDeltaSetField) (struct delta_s *pFields, const char *fieldname); + void (*pfnDeltaUnsetField) (struct delta_s *pFields, const char *fieldname); + void (*pfnDeltaAddEncoder) (char *name, void (*conditionalencode) (struct delta_s *pFields, const unsigned char *from, const unsigned char *to)); + int (*pfnGetCurrentPlayer) (void); + int (*pfnCanSkipPlayer) (const edict_t *player); + int (*pfnDeltaFindField) (struct delta_s *pFields, const char *fieldname); + void (*pfnDeltaSetFieldByIndex) (struct delta_s *pFields, int fieldNumber); + void (*pfnDeltaUnsetFieldByIndex) (struct delta_s *pFields, int fieldNumber); + void (*pfnSetGroupMask) (int mask, int op); + int (*pfnCreateInstancedBaseline) (int classname, struct entity_state_s *baseline); + void (*pfnCvar_DirectSet) (struct cvar_s *var, char *value); + void (*pfnForceUnmodified) (FORCE_TYPE type, float *mins, float *maxs, const char *szFilename); + void (*pfnGetPlayerStats) (const edict_t *client, int *ping, int *packet_loss); + void (*pfnAddServerCommand) (char *cmd_name, void (*function) (void)); + + int (*pfnVoice_GetClientListening) (int iReceiver, int iSender); + int (*pfnVoice_SetClientListening) (int iReceiver, int iSender, int bListen); + + const char *(*pfnGetPlayerAuthId) (edict_t *e); + + struct sequenceEntry_s *(*pfnSequenceGet) (const char *fileName, const char *entryName); + struct sentenceEntry_s *(*pfnSequencePickSentence) (const char *groupName, int pickMethod, int *picked); + + int (*pfnGetFileSize) (char *szFilename); + unsigned int (*pfnGetApproxWavePlayLen) (const char *filepath); + + int (*pfnIsCareerMatch) (void); + int (*pfnGetLocalizedStringLength) (const char *label); + void (*pfnRegisterTutorMessageShown) (int mid); + int (*pfnGetTimesTutorMessageShown) (int mid); + void (*pfnProcessTutorMessageDecayBuffer) (int *buffer, int bufferLength); + void (*pfnConstructTutorMessageDecayBuffer) (int *buffer, int bufferLength); + void (*pfnResetTutorMessageDecayData) (void); + + void (*pfnQueryClientCVarValue) (const edict_t *player, const char *cvarName); + void (*pfnQueryClientCVarValue2) (const edict_t *player, const char *cvarName, int requestID); + int (*pfnCheckParm)(const char *pchCmdLineToken, char **ppnext); + +} enginefuncs_t; + +// Passed to pfnKeyValue +typedef struct KeyValueData_s +{ + char *szClassName; // in: entity classname + char *szKeyName; // in: name of key + char *szValue; // in: value of key + int32 fHandled; // out: DLL sets to true if key-value pair was understood +} KeyValueData; + + +#define ARRAYSIZE_HLSDK(p) (int) (sizeof(p)/sizeof(p[0])) +typedef struct customization_s customization_t; + +typedef struct +{ + // Initialize/shutdown the game (one-time call after loading of game .dll ) + void (*pfnGameInit) (void); + int (*pfnSpawn) (edict_t *pent); + void (*pfnThink) (edict_t *pent); + void (*pfnUse) (edict_t *pentUsed, edict_t *pentOther); + void (*pfnTouch) (edict_t *pentTouched, edict_t *pentOther); + void (*pfnBlocked) (edict_t *pentBlocked, edict_t *pentOther); + void (*pfnKeyValue) (edict_t *pentKeyvalue, KeyValueData *pkvd); + void (*pfnSave) (edict_t *pent, struct SAVERESTOREDATA *pSaveData); + int (*pfnRestore) (edict_t *pent, SAVERESTOREDATA *pSaveData, int globalEntity); + void (*pfnSetAbsBox) (edict_t *pent); + + void (*pfnSaveWriteFields) (SAVERESTOREDATA *, const char *, void *, struct TYPEDESCRIPTION *, int); + void (*pfnSaveReadFields) (SAVERESTOREDATA *, const char *, void *, TYPEDESCRIPTION *, int); + + void (*pfnSaveGlobalState) (SAVERESTOREDATA *); + void (*pfnRestoreGlobalState) (SAVERESTOREDATA *); + void (*pfnResetGlobalState) (void); + + int (*pfnClientConnect) (edict_t *ent, const char *pszName, const char *pszAddress, char szRejectReason[128]); + + void (*pfnClientDisconnect) (edict_t *ent); + void (*pfnClientKill) (edict_t *ent); + void (*pfnClientPutInServer) (edict_t *ent); + void (*pfnClientCommand) (edict_t *ent); + void (*pfnClientUserInfoChanged) (edict_t *ent, char *infobuffer); + + void (*pfnServerActivate) (edict_t *edictList, int edictCount, int clientMax); + void (*pfnServerDeactivate) (void); + + void (*pfnPlayerPreThink) (edict_t *ent); + void (*pfnPlayerPostThink) (edict_t *ent); + + void (*pfnStartFrame) (void); + void (*pfnParmsNewLevel) (void); + void (*pfnParmsChangeLevel) (void); + + // Returns string describing current .dll. E.g., TeamFotrress 2, Half-Life + const char *(*pfnGetGameDescription) (void); + + // Notify dll about a player customization. + void (*pfnPlayerCustomization) (edict_t *ent, struct customization_s *pCustom); + + // Spectator funcs + void (*pfnSpectatorConnect) (edict_t *ent); + void (*pfnSpectatorDisconnect) (edict_t *ent); + void (*pfnSpectatorThink) (edict_t *ent); + + // Notify game .dll that engine is going to shut down. Allows mod authors to set a breakpoint. + void (*pfnSys_Error) (const char *error_string); + + void (*pfnPM_Move) (struct playermove_s *ppmove, int server); + void (*pfnPM_Init) (struct playermove_s *ppmove); + char (*pfnPM_FindTextureType) (char *name); + void (*pfnSetupVisibility) (struct edict_s *pViewEntity, struct edict_s *client, unsigned char **pvs, unsigned char **pas); + void (*pfnUpdateClientData) (const struct edict_s *ent, int sendweapons, struct clientdata_s *cd); + int (*pfnAddToFullPack) (struct entity_state_s *state, int e, edict_t *ent, edict_t *host, int hostflags, int player, unsigned char *pSet); + void (*pfnCreateBaseline) (int player, int eindex, struct entity_state_s *baseline, struct edict_s *entity, int playermodelindex, vec3_t player_mins, vec3_t player_maxs); + void (*pfnRegisterEncoders) (void); + int (*pfnGetWeaponData) (struct edict_s *player, struct weapon_data_s *info); + + void (*pfnCmdStart) (const edict_t *player, const struct c *cmd, unsigned int random_seed); + void (*pfnCmdEnd) (const edict_t *player); + + // Return 1 if the packet is valid. Set response_buffer_size if you want to send a response packet. Incoming, it holds the max + // size of the response_buffer, so you must zero it out if you choose not to respond. + int (*pfnConnectionlessPacket) (const struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size); + + // Enumerates player hulls. Returns 0 if the hull number doesn't exist, 1 otherwise + int (*pfnGetHullBounds) (int hullnumber, float *mins, float *maxs); + + // Create baselines for certain "unplaced" items. + void (*pfnCreateInstancedBaselines) (void); + + // One of the pfnForceUnmodified files failed the consistency check for the specified player + // Return 0 to allow the client to continue, 1 to force immediate disconnection ( with an optional disconnect message of up to 256 characters ) + int (*pfnInconsistentFile) (const struct edict_s *player, const char *szFilename, char *disconnect_message); + + // The game .dll should return 1 if lag compensation should be allowed ( could also just set + // the sv_unlag cvar. + // Most games right now should return 0, until client-side weapon prediction code is written + // and tested for them. + int (*pfnAllowLagCompensation) (void); +} gamefuncs_t; + +// Current version. +#define NEWGAMEDLLFUNCS_VERSION 1 + +typedef struct +{ + // Called right before the object's memory is freed. + // Calls its destructor. + void (*pfnOnFreeEntPrivateData) (edict_t *pEnt); + void (*pfnGameShutdown) (void); + int (*pfnShouldCollide) (edict_t *pentTouched, edict_t *pentOther); + + void (*pfnCvarValue) (const edict_t *pEnt, const char *value); + void (*pfnCvarValue2) (const edict_t *pEnt, int requestID, const char *cvarName, const char *value); +} newgamefuncs_t; + +#endif /* EIFACE_H */ diff --git a/include/engine/enginecallback.h b/include/engine/enginecallback.h new file mode 100644 index 0000000..44f0f84 --- /dev/null +++ b/include/engine/enginecallback.h @@ -0,0 +1,145 @@ +/*** +* +* Copyright (c) 1999-2005, Valve Corporation. 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 source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#ifndef ENGINECALLBACK_H +#define ENGINECALLBACK_H +#pragma once + +// Must be provided by user of this code +extern enginefuncs_t g_engfuncs; + +// The actual engine callbacks +#define GETPLAYERUSERID (*g_engfuncs.pfnGetPlayerUserId) +#define PRECACHE_MODEL (*g_engfuncs.pfnPrecacheModel) +#define PRECACHE_SOUND (*g_engfuncs.pfnPrecacheSound) +#define PRECACHE_GENERIC (*g_engfuncs.pfnPrecacheGeneric) +#define SET_MODEL (*g_engfuncs.pfnSetModel) +#define MODEL_INDEX (*g_engfuncs.pfnModelIndex) +#define MODEL_FRAMES (*g_engfuncs.pfnModelFrames) +#define SET_SIZE (*g_engfuncs.pfnSetSize) +#define CHANGE_LEVEL (*g_engfuncs.pfnChangeLevel) +#define GET_INFOKEYBUFFER (*g_engfuncs.pfnGetInfoKeyBuffer) +#define INFOKEY_VALUE (*g_engfuncs.pfnInfoKeyValue) +#define SET_CLIENT_KEYVALUE (*g_engfuncs.pfnSetClientKeyValue) +#define REG_SVR_COMMAND (*g_engfuncs.pfnAddServerCommand) +#define SERVER_PRINT (*g_engfuncs.pfnServerPrint) +#define SET_SERVER_KEYVALUE (*g_engfuncs.pfnSetKeyValue) +#define GET_SPAWN_PARMS (*g_engfuncs.pfnGetSpawnParms) +#define SAVE_SPAWN_PARMS (*g_engfuncs.pfnSaveSpawnParms) +#define VEC_TO_YAW (*g_engfuncs.pfnVecToYaw) +#define VEC_TO_ANGLES (*g_engfuncs.pfnVecToAngles) +#define MOVE_TO_ORIGIN (*g_engfuncs.pfnMoveToOrigin) +#define oldCHANGE_YAW (*g_engfuncs.pfnChangeYaw) +#define CHANGE_PITCH (*g_engfuncs.pfnChangePitch) +#define MAKE_VECTORS (*g_engfuncs.pfnMakeVectors) +#define CREATE_ENTITY (*g_engfuncs.pfnCreateEntity) +#define REMOVE_ENTITY (*g_engfuncs.pfnRemoveEntity) +#define CREATE_NAMED_ENTITY (*g_engfuncs.pfnCreateNamedEntity) +#define MAKE_STATIC (*g_engfuncs.pfnMakeStatic) +#define ENT_IS_ON_FLOOR (*g_engfuncs.pfnEntIsOnFloor) +#define DROP_TO_FLOOR (*g_engfuncs.pfnDropToFloor) +#define WALK_MOVE (*g_engfuncs.pfnWalkMove) +#define SET_ORIGIN (*g_engfuncs.pfnSetOrigin) +#define EMIT_SOUND_DYN2 (*g_engfuncs.pfnEmitSound) +#define BUILD_SOUND_MSG (*g_engfuncs.pfnBuildSoundMsg) +#define TRACE_LINE (*g_engfuncs.pfnTraceLine) +#define TRACE_TOSS (*g_engfuncs.pfnTraceToss) +#define TRACE_MONSTER_HULL (*g_engfuncs.pfnTraceMonsterHull) +#define TRACE_HULL (*g_engfuncs.pfnTraceHull) +#define GET_AIM_VECTOR (*g_engfuncs.pfnGetAimVector) +#define SERVER_COMMAND (*g_engfuncs.pfnServerCommand) +#define SERVER_EXECUTE (*g_engfuncs.pfnServerExecute) +#define CLIENT_COMMAND (*g_engfuncs.pfnClientCommand) +#define PARTICLE_EFFECT (*g_engfuncs.pfnParticleEffect) +#define LIGHT_STYLE (*g_engfuncs.pfnLightStyle) +#define DECAL_INDEX (*g_engfuncs.pfnDecalIndex) +#define POINT_CONTENTS (*g_engfuncs.pfnPointContents) +#define CRC32_INIT (*g_engfuncs.pfnCRC32_Init) +#define CRC32_PROCESS_BUFFER (*g_engfuncs.pfnCRC32_ProcessBuffer) +#define CRC32_PROCESS_BYTE (*g_engfuncs.pfnCRC32_ProcessByte) +#define CRC32_FINAL (*g_engfuncs.pfnCRC32_Final) +#define RANDOM_LONG (*g_engfuncs.pfnRandomLong) +#define RANDOM_FLOAT (*g_engfuncs.pfnRandomFloat) +#define GETPLAYERAUTHID (*g_engfuncs.pfnGetPlayerAuthId) +static inline void MESSAGE_BEGIN (int msg_dest, int msg_type, const float *pOrigin = NULL, edict_t *ed = NULL) +{ + (*g_engfuncs.pfnMessageBegin) (msg_dest, msg_type, pOrigin, ed); +} + +#define MESSAGE_END (*g_engfuncs.pfnMessageEnd) +#define WRITE_BYTE (*g_engfuncs.pfnWriteByte) +#define WRITE_CHAR (*g_engfuncs.pfnWriteChar) +#define WRITE_SHORT (*g_engfuncs.pfnWriteShort) +#define WRITE_LONG (*g_engfuncs.pfnWriteLong) +#define WRITE_ANGLE (*g_engfuncs.pfnWriteAngle) +#define WRITE_COORD (*g_engfuncs.pfnWriteCoord) +#define WRITE_STRING (*g_engfuncs.pfnWriteString) +#define WRITE_ENTITY (*g_engfuncs.pfnWriteEntity) +#define CVAR_REGISTER (*g_engfuncs.pfnCVarRegister) +#define CVAR_GET_FLOAT (*g_engfuncs.pfnCVarGetFloat) +#define CVAR_GET_STRING (*g_engfuncs.pfnCVarGetString) +#define CVAR_SET_FLOAT (*g_engfuncs.pfnCVarSetFloat) +#define CVAR_SET_STRING (*g_engfuncs.pfnCVarSetString) +#define CVAR_GET_POINTER (*g_engfuncs.pfnCVarGetPointer) +#define ALERT (*g_engfuncs.pfnAlertMessage) +#define ENGINE_FPRINTF (*g_engfuncs.pfnEngineFprintf) +#define ALLOC_PRIVATE (*g_engfuncs.pfnPvAllocEntPrivateData) +#define GET_PRIVATE(pent) (pent ? (pent->pvPrivateData) : NULL); +#define FREE_PRIVATE (*g_engfuncs.pfnFreeEntPrivateData) +#define ALLOC_STRING (*g_engfuncs.pfnAllostring) +#define FIND_ENTITY_BY_STRING (*g_engfuncs.pfnFindEntityByString) +#define GETENTITYILLUM (*g_engfuncs.pfnGetEntityIllum) +#define FIND_ENTITY_IN_SPHERE (*g_engfuncs.pfnFindEntityInSphere) +#define FIND_CLIENT_IN_PVS (*g_engfuncs.pfnFindClientInPVS) +#define EMIT_AMBIENT_SOUND (*g_engfuncs.pfnEmitAmbientSound) +#define GET_MODEL_PTR (*g_engfuncs.pfnGetModelPtr) +#define REG_USER_MSG (*g_engfuncs.pfnRegUserMsg) +#define GET_BONE_POSITION (*g_engfuncs.pfnGetBonePosition) +#define FUNCTION_FROM_NAME (*g_engfuncs.pfnFunctionFromName) +#define NAME_FOR_FUNCTION (*g_engfuncs.pfnNameForFunction) +#define TRACE_TEXTURE (*g_engfuncs.pfnTraceTexture) +#define CLIENT_PRINTF (*g_engfuncs.pfnClientPrintf) +#define CMD_ARGS (*g_engfuncs.pfnCmd_Args) +#define CMD_ARGC (*g_engfuncs.pfnCmd_Argc) +#define CMD_ARGV (*g_engfuncs.pfnCmd_Argv) +#define GET_ATTACHMENT (*g_engfuncs.pfnGetAttachment) +#define SET_VIEW (*g_engfuncs.pfnSetView) +#define SET_CROSSHAIRANGLE (*g_engfuncs.pfnCrosshairAngle) +#define LOAD_FILE_FOR_ME (*g_engfuncs.pfnLoadFileForMe) +#define FREE_FILE (*g_engfuncs.pfnFreeFile) +#define COMPARE_FILE_TIME (*g_engfuncs.pfnCompareFileTime) +#define GET_GAME_DIR (*g_engfuncs.pfnGetGameDir) +#define IS_MAP_VALID (*g_engfuncs.pfnIsMapValid) +#define NUMBER_OF_ENTITIES (*g_engfuncs.pfnNumberOfEntities) +#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 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) + +#endif //ENGINECALLBACK_H diff --git a/include/engine/extdll.h b/include/engine/extdll.h new file mode 100644 index 0000000..e5e086e --- /dev/null +++ b/include/engine/extdll.h @@ -0,0 +1,122 @@ +/*** +* +* Copyright (c) 1999-2005, Valve Corporation. 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 source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ + +#ifndef EXTDLL_H +#define EXTDLL_H + +#ifdef DLL_DEBUG +#define DEBUG 1 +#endif + +#ifdef _WIN32 + #pragma warning (disable : 4244) // int or float down-conversion + #pragma warning (disable : 4305) // int or float data truncation + #pragma warning (disable : 4201) // nameless struct/union + #pragma warning (disable : 4514) // unreferenced inline function removed + #pragma warning (disable : 4100) // unreferenced formal parameter + #pragma warning (disable : 4715) // not all control paths return a value + #pragma warning (disable : 4996) // function was declared deprecated + #pragma warning (disable : 4702) // unreachable code + #pragma warning (disable : 4706) // assignment within conditional expression + + /* (dz): disable deprecation warnings concerning unsafe CRT functions */ + #if !defined _CRT_SECURE_NO_DEPRECATE + #define _CRT_SECURE_NO_DEPRECATE + #endif +#endif + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#define NOWINRES +#define NOSERVICE +#define NOMCX +#define NOIME +#include "windows.h" +#include "winsock2.h" +#else // _WIN32 +#define FALSE 0 +#define TRUE (!FALSE) +typedef unsigned long ULONG; +typedef unsigned char BYTE; +typedef int BOOL; + +#define MAX_PATH PATH_MAX +#include +#include +#ifndef min +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif +#ifndef max +#define max(a,b) (((a) > (b)) ? (a) : (b)) +#define _vsnprintf(a,b,c,d) vsnprintf(a,b,c,d) +#endif +#endif //_WIN32 + +#include "stdio.h" +#include "stdlib.h" +#include "math.h" + +#include + +typedef int func_t; // +typedef int string_t; // from engine's pr_comp.h; +typedef float vec_t; // needed before including progdefs.h + +#include "corelib.h" + +#define vec3_t Vector + +#include "const.h" +#include "progdefs.h" + +#define MAX_ENT_LEAFS 48 + +struct edict_s +{ + int free; + int serialnumber; + link_t area; // linked to a division node or leaf + int headnode; // -1 to use normal leaf check + int num_leafs; + short leafnums[MAX_ENT_LEAFS]; + float freetime; // sv.time when the object was freed + void *pvPrivateData; // Alloced and freed by engine, used by DLLs + entvars_t v; // C exported fields from progs +}; + +#include "eiface.h" + +#define MAX_WEAPON_SLOTS 5 // hud item selection slots +#define MAX_ITEM_TYPES 6 // hud item selection slots + +#define MAX_ITEMS 5 // hard coded item types + +#define HIDEHUD_WEAPONS ( 1 << 0 ) +#define HIDEHUD_FLASHLIGHT ( 1 << 1 ) +#define HIDEHUD_ALL ( 1 << 2 ) +#define HIDEHUD_HEALTH ( 1 << 3 ) + +#define MAX_AMMO_TYPES 32 // ??? +#define MAX_AMMO_SLOTS 32 // not really slots + +#define HUD_PRINTNOTIFY 1 +#define HUD_PRINTCONSOLE 2 +#define HUD_PRINTTALK 3 +#define HUD_PRINTCENTER 4 + +#define WEAPON_SUIT 31 +#define __USE_GNU 1 + +#endif //EXTDLL_H diff --git a/include/engine/meta_api.h b/include/engine/meta_api.h new file mode 100644 index 0000000..c4b9325 --- /dev/null +++ b/include/engine/meta_api.h @@ -0,0 +1,265 @@ +/* + * Copyright (c) 2001-2006 Will Day + * See the file "dllapi.h" in this folder for full information + */ + +// Simplified version by Wei Mingzhi + +#ifndef META_API_H +#define META_API_H + +typedef int (*GameAPI_t) (gamefuncs_t *, int); +typedef int (*GameAPI2_t) (gamefuncs_t *, int *); +typedef int (*NewAPI2_t) (gamefuncs_t *, int *); +typedef int (*EngineAPI_t) (enginefuncs_t *, int *); + +typedef int (*GETENTITYAPI_FN) (gamefuncs_t *pFunctionTable, int interfaceVersion); +typedef int (*GETENTITYAPI2_FN) (gamefuncs_t *pFunctionTable, int *interfaceVersion); +typedef int (*GETNEWDLLFUNCTIONS_FN) (newgamefuncs_t *pFunctionTable, int *interfaceVersion); +typedef int (*GET_ENGINE_FUNCTIONS_FN) (enginefuncs_t *pengfuncsFromEngine, int *interfaceVersion); + +C_DLLEXPORT int GetEntityAPI (gamefuncs_t *pFunctionTable, int interfaceVersion); +C_DLLEXPORT int GetEntityAPI2 (gamefuncs_t *pFunctionTable, int *interfaceVersion); +C_DLLEXPORT int GetNewDLLFunctions (newgamefuncs_t *pNewFunctionTable, int *interfaceVersion); + +#define META_INTERFACE_VERSION "5:13" + + +typedef enum +{ + PT_NEVER = 0, + PT_STARTUP, + PT_CHANGELEVEL, + PT_ANYTIME, + PT_ANYPAUSE, +} PLUG_LOADTIME; + + +typedef struct +{ + char *ifvers; + char *name; + char *version; + char *date; + char *author; + char *url; + char *logtag; + PLUG_LOADTIME loadable; + PLUG_LOADTIME unloadable; +} plugin_info_t; +extern plugin_info_t Plugin_info; + +typedef plugin_info_t *plid_t; + +#define PLID &Plugin_info + + +typedef enum +{ + PNL_NULL = 0, + PNL_INI_DELETED, + PNL_FILE_NEWER, + PNL_COMMAND, + PNL_CMD_FORCED, + PNL_DELAYED, + PNL_PLUGIN, + PNL_PLG_FORCED, + PNL_RELOAD, +} PL_UNLOAD_REASON; + +typedef enum +{ + MRES_UNSET = 0, + MRES_IGNORED, + MRES_HANDLED, + MRES_OVERRIDE, + MRES_SUPERCEDE, +} META_RES; + +typedef struct meta_globals_s +{ + META_RES mres; + META_RES prev_mres; + META_RES status; + void *orig_ret; + void *override_ret; +} meta_globals_t; + +extern meta_globals_t *gpMetaGlobals; + +#define SET_META_RESULT(result) gpMetaGlobals->mres=result +#define RETURN_META(result) { gpMetaGlobals->mres=result; return; } +#define RETURN_META_VALUE(result, value) { gpMetaGlobals->mres=result; return(value); } +#define META_RESULT_STATUS gpMetaGlobals->status +#define META_RESULT_PREVIOUS gpMetaGlobals->prev_mres +#define META_RESULT_ORIG_RET(type) *(type *)gpMetaGlobals->orig_ret +#define META_RESULT_OVERRIDE_RET(type) *(type *)gpMetaGlobals->override_ret + +typedef struct +{ + GETENTITYAPI_FN pfnGetEntityAPI; + GETENTITYAPI_FN pfnGetEntityAPI_Post; + GETENTITYAPI2_FN pfnGetEntityAPI2; + GETENTITYAPI2_FN pfnGetEntityAPI2_Post; + GETNEWDLLFUNCTIONS_FN pfnGetNewDLLFunctions; + GETNEWDLLFUNCTIONS_FN pfnGetNewDLLFunctions_Post; + GET_ENGINE_FUNCTIONS_FN pfnGetEngineFunctions; + GET_ENGINE_FUNCTIONS_FN pfnGetEngineFunctions_Post; +} metamod_funcs_t; + +#include "util.h" + + +// max buffer size for printed messages +#define MAX_LOGMSG_LEN 1024 + +// for getgameinfo: +typedef enum +{ + GINFO_NAME = 0, + GINFO_DESC, + GINFO_GAMEDIR, + GINFO_DLL_FULLPATH, + GINFO_DLL_FILENAME, + GINFO_REALDLL_FULLPATH, +} ginfo_t; + +// Meta Utility Function table type. +typedef struct meta_util_funcs_s +{ + void (*pfnLogConsole) (plid_t plid, const char *szFormat, ...); + void (*pfnLogMessage) (plid_t plid, const char *szFormat, ...); + void (*pfnLogError) (plid_t plid, const char *szFormat, ...); + void (*pfnLogDeveloper) (plid_t plid, const char *szFormat, ...); + void (*pfnCenterSay) (plid_t plid, const char *szFormat, ...); + void (*pfnCenterSayParms) (plid_t plid, hudtextparms_t tparms, const char *szFormat, ...); + void (*pfnCenterSayVarargs) (plid_t plid, hudtextparms_t tparms, const char *szFormat, va_list ap); + int (*pfnCallGameEntity) (plid_t plid, const char *entStr, entvars_t *pev); + int (*pfnGetUserMsgID) (plid_t plid, const char *msgname, int *size); + const char *(*pfnGetUserMsgName) (plid_t plid, int msgid, int *size); + const char *(*pfnGetPluginPath) (plid_t plid); + const char *(*pfnGetGameInfo) (plid_t plid, ginfo_t tag); + int (*pfnLoadPlugin) (plid_t plid, const char *cmdline, PLUG_LOADTIME now, void **plugin_handle); + int (*pfnUnloadPlugin) (plid_t plid, const char *cmdline, PLUG_LOADTIME now, PL_UNLOAD_REASON reason); + int (*pfnUnloadPluginByHandle) (plid_t plid, void *plugin_handle, PLUG_LOADTIME now, PL_UNLOAD_REASON reason); + const char *(*pfnIsQueryingClienCVar_t) (plid_t plid, const edict_t *player); + int (*pfnMakeRequestID) (plid_t plid); + void (*pfnGetHookTables) (plid_t plid, enginefuncs_t **peng, gamefuncs_t **pdll, newgamefuncs_t **pnewdll); +} mutil_funcs_t; + + +typedef struct +{ + gamefuncs_t *dllapi_table; + newgamefuncs_t *newapi_table; +} gamedll_funcs_t; + +extern gamedll_funcs_t *gpGamedllFuncs; +extern mutil_funcs_t *gpMetaUtilFuncs; +extern meta_globals_t *gpMetaGlobals; +extern metamod_funcs_t gMetaFunctionTable; + +C_DLLEXPORT void Meta_Init (void); +typedef void (*META_INIT_FN) (void); + +C_DLLEXPORT int Meta_Query (char *interfaceVersion, plugin_info_t **plinfo, mutil_funcs_t *pMetaUtilFuncs); +typedef int (*META_QUERY_FN) (char *interfaceVersion, plugin_info_t **plinfo, mutil_funcs_t *pMetaUtilFuncs); + +C_DLLEXPORT int Meta_Attach (PLUG_LOADTIME now, metamod_funcs_t *pFunctionTable, meta_globals_t *pMGlobals, gamedll_funcs_t *pGamedllFuncs); +typedef int (*META_ATTACH_FN) (PLUG_LOADTIME now, metamod_funcs_t *pFunctionTable, meta_globals_t *pMGlobals, gamedll_funcs_t *pGamedllFuncs); + +C_DLLEXPORT int Meta_Detach (PLUG_LOADTIME now, PL_UNLOAD_REASON reason); +typedef int (*META_DETACH_FN) (PLUG_LOADTIME now, PL_UNLOAD_REASON reason); + +C_DLLEXPORT int GetEntityAPI_Post (gamefuncs_t *pFunctionTable, int interfaceVersion); +C_DLLEXPORT int GetEntityAPI2_Post (gamefuncs_t *pFunctionTable, int *interfaceVersion); + +C_DLLEXPORT int GetNewDLLFunctions_Post (newgamefuncs_t *pNewFunctionTable, int *interfaceVersion); +C_DLLEXPORT int GetEngineFunctions (enginefuncs_t *pengfuncsFromEngine, int *interfaceVersion); +C_DLLEXPORT int GetEngineFunctions_Post (enginefuncs_t *pengfuncsFromEngine, int *interfaceVersion); + + + +#define MDLL_FUNC gpGamedllFuncs->dllapi_table + +#define MDLL_GameDLLInit MDLL_FUNC->pfnGameInit +#define MDLL_Spawn MDLL_FUNC->pfnSpawn +#define MDLL_Think MDLL_FUNC->pfnThink +#define MDLL_Use MDLL_FUNC->pfnUse +#define MDLL_Touch MDLL_FUNC->pfnTouch +#define MDLL_Blocked MDLL_FUNC->pfnBlocked +#define MDLL_KeyValue MDLL_FUNC->pfnKeyValue +#define MDLL_Save MDLL_FUNC->pfnSave +#define MDLL_Restore MDLL_FUNC->pfnRestore +#define MDLL_ObjectCollsionBox MDLL_FUNC->pfnAbsBox +#define MDLL_SaveWriteFields MDLL_FUNC->pfnSaveWriteFields +#define MDLL_SaveReadFields MDLL_FUNC->pfnSaveReadFields +#define MDLL_SaveGlobalState MDLL_FUNC->pfnSaveGlobalState +#define MDLL_RestoreGlobalState MDLL_FUNC->pfnRestoreGlobalState +#define MDLL_ResetGlobalState MDLL_FUNC->pfnResetGlobalState +#define MDLL_ClientConnect MDLL_FUNC->pfnClientConnect +#define MDLL_ClientDisconnect MDLL_FUNC->pfnClientDisconnect +#define MDLL_ClientKill MDLL_FUNC->pfnClientKill +#define MDLL_ClientPutInServer MDLL_FUNC->pfnClientPutInServer +#define MDLL_ClientCommand MDLL_FUNC->pfnClientCommand +#define MDLL_ClientUserInfoChanged MDLL_FUNC->pfnClientUserInfoChanged +#define MDLL_ServerActivate MDLL_FUNC->pfnServerActivate +#define MDLL_ServerDeactivate MDLL_FUNC->pfnServerDeactivate +#define MDLL_PlayerPreThink MDLL_FUNC->pfnPlayerPreThink +#define MDLL_PlayerPostThink MDLL_FUNC->pfnPlayerPostThink +#define MDLL_StartFrame MDLL_FUNC->pfnStartFrame +#define MDLL_ParmsNewLevel MDLL_FUNC->pfnParmsNewLevel +#define MDLL_ParmsChangeLevel MDLL_FUNC->pfnParmsChangeLevel +#define MDLL_GetGameDescription MDLL_FUNC->pfnGetGameDescription +#define MDLL_PlayerCustomization MDLL_FUNC->pfnPlayerCustomization +#define MDLL_SpectatorConnect MDLL_FUNC->pfnSpectatorConnect +#define MDLL_SpectatorDisconnect MDLL_FUNC->pfnSpectatorDisconnect +#define MDLL_SpectatorThink MDLL_FUNC->pfnSpectatorThink +#define MDLL_Sys_Error MDLL_FUNC->pfnSys_Error +#define MDLL_PM_Move MDLL_FUNC->pfnPM_Move +#define MDLL_PM_Init MDLL_FUNC->pfnPM_Init +#define MDLL_PM_FindTextureType MDLL_FUNC->pfnPM_FindTextureType +#define MDLL_SetupVisibility MDLL_FUNC->pfnSetupVisibility +#define MDLL_UpdateClientData MDLL_FUNC->pfnUpdateClientData +#define MDLL_AddToFullPack MDLL_FUNC->pfnAddToFullPack +#define MDLL_CreateBaseline MDLL_FUNC->pfnCreateBaseline +#define MDLL_RegisterEncoders MDLL_FUNC->pfnRegisterEncoders +#define MDLL_GetWeaponData MDLL_FUNC->pfnGetWeaponData +#define MDLL_CmdStart MDLL_FUNC->pfnCmdStart +#define MDLL_CmdEnd MDLL_FUNC->pfnCmdEnd +#define MDLL_ConnectionlessPacket MDLL_FUNC->pfnConnectionlessPacket +#define MDLL_GetHullBounds MDLL_FUNC->pfnGetHullBounds +#define MDLL_CreateInstancedBaselines MDLL_FUNC->pfnCreateInstancedBaselines +#define MDLL_InconsistentFile MDLL_FUNC->pfnInconsistentFile +#define MDLL_AllowLagCompensation MDLL_FUNC->pfnAllowLagCompensation + +#define MNEW_FUNC gpGamedllFuncs->newapi_table + +#define MNEW_OnFreeEntPrivateData MNEW_FUNC->pfnOnFreeEntPrivateData +#define MNEW_GameShutdown MNEW_FUNC->pfnGameShutdown +#define MNEW_ShouldCollide MNEW_FUNC->pfnShouldCollide +#define MNEW_CvarValue MNEW_FUNC->pfnCvarValue +#define MNEW_CvarValue2 MNEW_FUNC->pfnCvarValue2 + +// convenience macros for metautil functions +#define LOG_CONSOLE (*gpMetaUtilFuncs->pfnLogConsole) +#define LOG_MESSAGE (*gpMetaUtilFuncs->pfnLogMessage) +#define LOG_MMERROR (*gpMetaUtilFuncs->pfnLogError) +#define LOG_DEVELOPER (*gpMetaUtilFuncs->pfnLogDeveloper) +#define CENTER_SAY (*gpMetaUtilFuncs->pfnCenterSay) +#define CENTER_SAY_PARMS (*gpMetaUtilFuncs->pfnCenterSayParms) +#define CENTER_SAY_VARARGS (*gpMetaUtilFuncs->pfnCenterSayVarargs) +#define CALL_GAME_ENTITY (*gpMetaUtilFuncs->pfnCallGameEntity) +#define GET_USER_MSG_ID (*gpMetaUtilFuncs->pfnGetUserMsgID) +#define GET_USER_MSG_NAME (*gpMetaUtilFuncs->pfnGetUserMsgName) +#define GET_PLUGIN_PATH (*gpMetaUtilFuncs->pfnGetPluginPath) +#define GET_GAME_INFO (*gpMetaUtilFuncs->pfnGetGameInfo) +#define LOAD_PLUGIN (*gpMetaUtilFuncs->pfnLoadPlugin) +#define UNLOAD_PLUGIN (*gpMetaUtilFuncs->pfnUnloadPlugin) +#define UNLOAD_PLUGIN_BY_HANDLE (*gpMetaUtilFuncs->pfnUnloadPluginByHandle) +#define IS_QUERYING_CLIENT_CVAR (*gpMetaUtilFuncs->pfnIsQueryingClienCVar_t) +#define MAKE_REQUESTID (*gpMetaUtilFuncs->pfnMakeRequestID) +#define GET_HOOK_TABLES (*gpMetaUtilFuncs->pfnGetHookTables) +uint16 FixedUnsigned16 (float fValue, float fScale); +short FixedSigned16 (float fValue, float fScale); +#endif diff --git a/include/engine/mutil.h b/include/engine/mutil.h new file mode 100644 index 0000000..10b252e --- /dev/null +++ b/include/engine/mutil.h @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2001-2006 Will Day + * See the file "dllapi.h" in this folder for full information + */ + +// Simplified version by Wei Mingzhi + +#ifndef MUTIL_H +#define MUTIL_H + +#include "plinfo.h" +#include "sdk_util.h" + +#endif /* MUTIL_H */ \ No newline at end of file diff --git a/include/engine/plinfo.h b/include/engine/plinfo.h new file mode 100644 index 0000000..b93a33f --- /dev/null +++ b/include/engine/plinfo.h @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2001-2006 Will Day + * See the file "dllapi.h" in this folder for full information + */ + +// Simplified version by Wei Mingzhi + +#ifndef PLINFO_H +#define PLINFO_H + +#endif diff --git a/include/engine/progdefs.h b/include/engine/progdefs.h new file mode 100644 index 0000000..f6b6400 --- /dev/null +++ b/include/engine/progdefs.h @@ -0,0 +1,222 @@ +/*** +* +* Copyright (c) 1999-2005, Valve Corporation. 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 source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#ifndef PROGDEFS_H +#define PROGDEFS_H +#ifdef _WIN32 +#pragma once +#endif + +typedef struct +{ + float time; + float frametime; + float force_retouch; + string_t mapname; + string_t startspot; + float deathmatch; + float coop; + float teamplay; + float serverflags; + float found_secrets; + vec3_t v_forward; + vec3_t v_up; + vec3_t v_right; + float trace_allsolid; + float trace_startsolid; + float trace_fraction; + vec3_t trace_endpos; + vec3_t trace_plane_normal; + float trace_plane_dist; + edict_t *trace_ent; + float trace_inopen; + float trace_inwater; + int trace_hitgroup; + int trace_flags; + int msg_entity; + int cdAudioTrack; + int maxClients; + int maxEntities; + const char *pStringBase; + void *pSaveData; + vec3_t vecLandmarkOffset; +} globalvars_t; + + +typedef struct entvars_s +{ + string_t classname; + string_t globalname; + + vec3_t origin; + vec3_t oldorigin; + vec3_t velocity; + vec3_t basevelocity; + vec3_t clbasevelocity; // Base velocity that was passed in to server physics so + // client can predict conveyors correctly. Server zeroes it, so we need to store here, too. + vec3_t movedir; + + vec3_t angles; // Model angles + vec3_t avelocity; // angle velocity (degrees per second) + vec3_t punchangle; // auto-decaying view angle adjustment + vec3_t v_angle; // Viewing angle (player only) + + // For parametric entities + vec3_t endpos; + vec3_t startpos; + float impacttime; + float starttime; + + int fixangle; // 0:nothing, 1:force view angles, 2:add avelocity + float idealpitch; + float pitch_speed; + float ideal_yaw; + float yaw_speed; + + int modelindex; + string_t model; + + int viewmodel; // player's viewmodel + int weaponmodel; // what other players see + + vec3_t absmin; // BB max translated to world coord + vec3_t absmax; // BB max translated to world coord + vec3_t mins; // local BB min + vec3_t maxs; // local BB max + vec3_t size; // maxs - mins + + float ltime; + float nextthink; + + int movetype; + int solid; + + int skin; + int body; // sub-model selection for studiomodels + int effects; + + float gravity; // % of "normal" gravity + float friction; // inverse elasticity of MOVETYPE_BOUNCE + + int light_level; + + int sequence; // animation sequence + int gaitsequence; // movement animation sequence for player (0 for none) + float frame; // % playback position in animation sequences (0..255) + float animtime; // world time when frame was set + float framerate; // animation playback rate (-8x to 8x) + byte controller[4]; // bone controller setting (0..255) + byte blending[2]; // blending amount between sub-sequences (0..255) + + float scale; // sprite rendering scale (0..255) + + int rendermode; + float renderamt; + vec3_t rendercolor; + int renderfx; + + float health; + float frags; + int weapons; // bit mask for available weapons + float takedamage; + + int deadflag; + vec3_t view_ofs; // eye position + + int button; + int impulse; + + edict_t *chain; // Entity pointer when linked into a linked list + edict_t *dmg_inflictor; + edict_t *enemy; + edict_t *aiment; // entity pointer when MOVETYPE_FOLLOW + edict_t *owner; + edict_t *groundentity; + + int spawnflags; + int flags; + + int colormap; // lowbyte topcolor, highbyte bottomcolor + int team; + + float max_health; + float teleport_time; + float armortype; + float armorvalue; + int waterlevel; + int watertype; + + string_t target; + string_t targetname; + string_t netname; + string_t message; + + float dmg_take; + float dmg_save; + float dmg; + float dmgtime; + + string_t noise; + string_t noise1; + string_t noise2; + string_t noise3; + + float speed; + float air_finished; + float pain_finished; + float radsuit_finished; + + edict_t *pContainingEntity; + + int playerclass; + float maxspeed; + + float fov; + int weaponanim; + + int pushmsec; + + int bInDuck; + int flTimeStepSound; + int flSwimTime; + int flDuckTime; + int iStepLeft; + float flFallVelocity; + + int gamestate; + + int oldbuttons; + + int groupinfo; + + // For mods + int iuser1; + int iuser2; + int iuser3; + int iuser4; + float fuser1; + float fuser2; + float fuser3; + float fuser4; + vec3_t vuser1; + vec3_t vuser2; + vec3_t vuser3; + vec3_t vuser4; + edict_t *euser1; + edict_t *euser2; + edict_t *euser3; + edict_t *euser4; +} entvars_t; + +#endif diff --git a/include/engine/sdk_util.h b/include/engine/sdk_util.h new file mode 100644 index 0000000..41aec0b --- /dev/null +++ b/include/engine/sdk_util.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2001-2006 Will Day + * See the file "dllapi.h" in this folder for full information + */ + +// Simplified version by Wei Mingzhi + +#ifndef SDK_UTIL_H +#define SDK_UTIL_H + +#ifdef DEBUG +#undef DEBUG +#endif + +#include + + + + +short FixedSigned16 (float value, float scale); +unsigned short FixedUnsigned16 (float value, float scale); + +#endif diff --git a/include/engine/util.h b/include/engine/util.h new file mode 100644 index 0000000..d3a9a2b --- /dev/null +++ b/include/engine/util.h @@ -0,0 +1,346 @@ +/*** +* +* Copyright (c) 1999-2005, Valve Corporation. 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 source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ + +#ifndef SDKUTIL_H +#define SDKUTIL_H + +#ifndef ENGINECALLBACK_H +#include "enginecallback.h" +#endif +static inline void MESSAGE_BEGIN (int msg_dest, int msg_type, const float *pOrigin, entvars_t *ent); // implementation later in this file + +extern globalvars_t *g_pGlobals; + +#define DLL_GLOBAL + +extern DLL_GLOBAL const Vector g_vZero; + +// Use this instead of ALLOC_STRING on constant strings +#define STRING(offset) (const char *)(g_pGlobals->pStringBase + (int)offset) +#define MAKE_STRING(str) ((int)str - (int)STRING(0)) +#define ENGINE_STR(str) (const_cast (STRING (ALLOC_STRING (str)))) + +static inline edict_t *FIND_ENTITY_BY_CLASSNAME (edict_t *entStart, const char *pszName) +{ + return FIND_ENTITY_BY_STRING (entStart, "classname", pszName); +} + +static inline edict_t *FIND_ENTITY_BY_TARGETNAME (edict_t *entStart, const char *pszName) +{ + return FIND_ENTITY_BY_STRING (entStart, "targetname", pszName); +} + +// for doing a reverse lookup. Say you have a door, and want to find its button. +static inline edict_t *FIND_ENTITY_BY_TARGET (edict_t *entStart, const char *pszName) +{ + return FIND_ENTITY_BY_STRING (entStart, "target", pszName); +} + +// Keeps clutter down a bit, when using a float as a bit-Vector +#define SetBits(flBitVector, bits) ((flBitVector) = (int)(flBitVector) | (bits)) +#define ClearBits(flBitVector, bits) ((flBitVector) = (int)(flBitVector) & ~(bits)) +#define FBitSet(flBitVector, bit) ((int)(flBitVector) & (bit)) + +// Pointer operators +#define PTR_TO_BYTE(in) *(byte *) (in) +#define PTR_TO_FLT(in) *(float *) (in) +#define PTR_TO_INT(in) *(int *) (in) +#define PTR_TO_STR(in) (char *) (in) + +// Makes these more explicit, and easier to find +#define FILE_GLOBAL static + +// Until we figure out why "const" gives the compiler problems, we'll just have to use +// this bogus "empty" define to mark things as constant. +#define CONSTANT + +// More explicit than "int" +typedef int EOFFSET; + +// In case it's not alread defined +#ifndef BOOL +typedef int BOOL; +#endif + +// In case this ever changes +#define M_PI 3.1415926 + +// +// Conversion among the three types of "entity", including identity-conversions. +// +static inline edict_t *ENT (const entvars_t *pev) +{ + return pev->pContainingEntity; +} +static inline edict_t *ENT (edict_t *pent) +{ + return pent; +} +static inline edict_t *ENT (EOFFSET eoffset) +{ + return (*g_engfuncs.pfnPEntityOfEntOffset) (eoffset); +} +static inline EOFFSET OFFSET (EOFFSET eoffset) +{ + return eoffset; +} +static inline EOFFSET OFFSET (const edict_t *pent) +{ +#if _DEBUG + if (!pent) + ALERT (at_error, "Bad ent in OFFSET()\n"); +#endif + return (*g_engfuncs.pfnEntOffsetOfPEntity) (pent); +} +static inline EOFFSET OFFSET (entvars_t *pev) +{ +#if _DEBUG + if (!pev) + ALERT (at_error, "Bad pev in OFFSET()\n"); +#endif + return OFFSET (ENT (pev)); +} +static inline entvars_t *VARS (entvars_t *pev) +{ + return pev; +} + +static inline entvars_t *VARS (edict_t *pent) +{ + if (!pent) + return NULL; + + return &pent->v; +} + +static inline entvars_t *VARS (EOFFSET eoffset) +{ + return VARS (ENT (eoffset)); +} +static inline int ENTINDEX (edict_t *ent) +{ + return (*g_engfuncs.pfnIndexOfEdict) (ent); +} +static inline edict_t *INDEXENT (int iEdictNum) +{ + return (*g_engfuncs.pfnPEntityOfEntIndex) (iEdictNum); +} +static inline void MESSAGE_BEGIN (int msg_dest, int msg_type, const float *pOrigin, entvars_t *ent) +{ + (*g_engfuncs.pfnMessageBegin) (msg_dest, msg_type, pOrigin, ENT (ent)); +} + +// Testing the three types of "entity" for nullity +#define eoNullEntity 0 +static inline BOOL FNullEnt (EOFFSET eoffset) +{ + return eoffset == 0; +} +static inline BOOL FNullEnt (entvars_t *pev) +{ + return pev == NULL || FNullEnt (OFFSET (pev)); +} +static inline int FNullEnt (const edict_t *pent) +{ + return !pent || !(*g_engfuncs.pfnEntOffsetOfPEntity) (pent); +} + +// Testing strings for nullity +#define iStringNull 0 +static inline BOOL FStringNull (int stingPtr) +{ + return stingPtr == iStringNull; +} + +#define cchMapNameMost 32 + +#define SAFE_FUNCTION_CALL(pfn,args) try { pfn args; } catch (...) { } + +// Dot products for view cone checking +#define VIEW_FIELD_FULL (float)-1.0 // +-180 degrees +#define VIEW_FIELD_WIDE (float)-0.7 // +-135 degrees 0.1 // +-85 degrees, used for full FOV checks +#define VIEW_FIELD_NARROW (float)0.7 // +-45 degrees, more narrow check used to set up ranged attacks +#define VIEW_FIELD_ULTRA_NARROW (float)0.9 // +-25 degrees, more narrow check used to set up ranged attacks + +// Misc useful +static inline BOOL FStrEq (const char *sz1, const char *sz2) +{ + return (strcmp (sz1, sz2) == 0); +} +static inline BOOL FClassnameIs (edict_t *pent, const char *szClassname) +{ + return FStrEq (STRING (VARS (pent)->classname), szClassname); +} +static inline BOOL FClassnameIs (entvars_t *pev, const char *szClassname) +{ + return FStrEq (STRING (pev->classname), szClassname); +} + +typedef enum +{ ignore_monsters = 1, dont_ignore_monsters = 0, missile = 2 } IGNORE_MONSTERS; +typedef enum +{ ignore_glass = 1, dont_ignore_glass = 0 } IGNORE_GLASS; +typedef enum +{ point_hull = 0, human_hull = 1, large_hull = 2, head_hull = 3 } HULL; +typedef int (*tMenuCallback) (edict_t *, int); + + +typedef struct hudtextparms_s +{ + float x; + float y; + int effect; + byte r1, g1, b1, a1; + byte r2, g2, b2, a2; + float fadeinTime; + float fadeoutTime; + float holdTime; + float fxTime; + int channel; +} hudtextparms_t; + + +extern Vector GetEntityOrigin (entvars_t *pevBModel); + +#define AMBIENT_SOUND_STATIC 0 // medium radius attenuation +#define AMBIENT_SOUND_EVERYWHERE 1 +#define AMBIENT_SOUND_SMALLRADIUS 2 +#define AMBIENT_SOUND_MEDIUMRADIUS 4 +#define AMBIENT_SOUND_LARGERADIUS 8 +#define AMBIENT_SOUND_START_SILENT 16 +#define AMBIENT_SOUND_NOT_LOOPING 32 + +#define SPEAKER_START_SILENT 1 // wait for trigger 'on' to start announcements + +#define SND_SPAWNING (1 << 8) // duplicated in protocol.h we're spawing, used in some cases for ambients +#define SND_STOP (1 << 5) // duplicated in protocol.h stop sound +#define SND_CHANGE_VOL (1 << 6) // duplicated in protocol.h change sound vol +#define SND_CHANGE_PITCH (1 << 7) // duplicated in protocol.h change sound pitch + +#define LFO_SQUARE 1 +#define LFO_TRIANGLE 2 +#define LFO_RANDOM 3 + +// func_rotating +#define SF_BRUSH_ROTATE_Y_AXIS 0 +#define SF_BRUSH_ROTATE_INSTANT 1 +#define SF_BRUSH_ROTATE_BACKWARDS 2 +#define SF_BRUSH_ROTATE_Z_AXIS 4 +#define SF_BRUSH_ROTATE_X_AXIS 8 +#define SF_PENDULUM_AUTO_RETURN 16 +#define SF_PENDULUM_PASSABLE 32 + +#define SF_BRUSH_ROTATE_SMALLRADIUS 128 +#define SF_BRUSH_ROTATE_MEDIUMRADIUS 256 +#define SF_BRUSH_ROTATE_LARGERADIUS 512 + +#define PUSH_BLOCK_ONLY_X 1 +#define PUSH_BLOCK_ONLY_Y 2 + +#define VEC_HULL_MIN Vector(-16, -16, -36) +#define VEC_HULL_MAX Vector( 16, 16, 36) +#define VEC_HUMAN_HULL_MIN Vector( -16, -16, 0 ) +#define VEC_HUMAN_HULL_MAX Vector( 16, 16, 72 ) +#define VEC_HUMAN_HULL_DUCK Vector( 16, 16, 36 ) + +#define VEC_VIEW Vector( 0, 0, 28 ) + +#define VEC_DUCK_HULL_MIN Vector(-16, -16, -18 ) +#define VEC_DUCK_HULL_MAX Vector( 16, 16, 18) +#define VEC_DUCK_VIEW Vector( 0, 0, 12 ) + +#define SVC_TEMPENTITY 23 +#define SVC_INTERMISSION 30 +#define SVC_CDTRACK 32 +#define SVC_WEAPONANIM 35 +#define SVC_ROOMTYPE 37 +#define SVC_DIRECTOR 51 + +// triggers +#define SF_TRIGGER_ALLOWMONSTERS 1 // monsters allowed to fire this trigger +#define SF_TRIGGER_NOCLIENTS 2 // players not allowed to fire this trigger +#define SF_TRIGGER_PUSHABLES 4 // only pushables can fire this trigger + +// func breakable +#define SF_BREAK_TRIGGER_ONLY 1 // may only be broken by trigger +#define SF_BREAK_TOUCH 2 // can be 'crashed through' by running player (plate glass) +#define SF_BREAK_PRESSURE 4 // can be broken by a player standing on it +#define SF_BREAK_CROWBAR 256 // instant break if hit with crowbar + +// func_pushable (it's also func_breakable, so don't collide with those flags) +#define SF_PUSH_BREAKABLE 128 + +#define SF_LIGHT_START_OFF 1 + +#define SPAWNFLAG_NOMESSAGE 1 +#define SPAWNFLAG_NOTOUCH 1 +#define SPAWNFLAG_DROIDONLY 4 + +#define SPAWNFLAG_USEONLY 1 // can't be touched, must be used (buttons) + +#define TELE_PLAYER_ONLY 1 +#define TELE_SILENT 2 + +#define SF_TRIG_PUSH_ONCE 1 + +// NOTE: use EMIT_SOUND_DYN to set the pitch of a sound. Pitch of 100 +// is no pitch shift. Pitch > 100 up to 255 is a higher pitch, pitch < 100 +// down to 1 is a lower pitch. 150 to 70 is the realistic range. +// EMIT_SOUND_DYN with pitch != 100 should be used sparingly, as it's not quite as +// fast as EMIT_SOUND (the pitchshift mixer is not native coded). + +void EMIT_SOUND_DYN (edict_t *entity, int channel, const char *sample, float volume, float attenuation, int flags, int pitch); + + +static inline void EMIT_SOUND (edict_t *entity, int channel, const char *sample, float volume, float attenuation) +{ + EMIT_SOUND_DYN (entity, channel, sample, volume, attenuation, 0, PITCH_NORM); +} + +static inline void STOP_SOUND (edict_t *entity, int channel, const char *sample) +{ + EMIT_SOUND_DYN (entity, channel, sample, 0, 0, SND_STOP, PITCH_NORM); +} + +/// /// +// Bot Additions // +/// /// + +// removes linker warning when using msvcrt library +#if defined ( _MSC_VER ) +#define stricmp _stricmp +#define unlink _unlink +#define mkdir _mkdir +#endif + +// macro to handle memory allocation fails +#define TerminateOnMalloc() \ + AddLogEntry (true, LL_FATAL, "Memory Allocation Fail!\nFile: %s (Line: %d)", __FILE__, __LINE__) \ + +// internal assert function +#define InternalAssert(Expr) \ + if (!(Expr)) \ + { \ + AddLogEntry (true, LL_ERROR, "Assertion Fail! (Expression: %s, File: %s, Line: %d)", #Expr, __FILE__, __LINE__); \ + } \ + + +static inline void MakeVectors (const Vector &in) +{ + in.BuildVectors (&g_pGlobals->v_forward, &g_pGlobals->v_right, &g_pGlobals->v_up); +} + +#endif + diff --git a/include/globals.h b/include/globals.h new file mode 100644 index 0000000..ac0365e --- /dev/null +++ b/include/globals.h @@ -0,0 +1,101 @@ +// +// Copyright (c) 2014, by YaPB Development Team. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// Version: $Id:$ +// + +#ifndef GLOBALS_INCLUDED +#define GLOBALS_INCLUDED + +extern bool g_canSayBombPlanted; +extern bool g_bombPlanted; +extern bool g_bombSayString; +extern bool g_roundEnded; +extern bool g_radioInsteadVoice; +extern bool g_waypointOn; +extern bool g_waypointsChanged; +extern bool g_autoWaypoint; +extern bool g_botsCanPause; +extern bool g_editNoclip; +extern bool g_isMetamod; +extern bool g_isFakeCommand; +extern bool g_sendAudioFinished; +extern bool g_isCommencing; +extern bool g_leaderChoosen[2]; + +extern float g_autoPathDistance; +extern float g_timeBombPlanted; +extern float g_timeNextBombUpdate; +extern float g_lastChatTime; +extern float g_timeRoundEnd; +extern float g_timeRoundMid; +extern float g_timeNextBombUpdate; +extern float g_timeRoundStart; +extern float g_lastRadioTime[2]; + +extern int g_mapType; +extern int g_numWaypoints; +extern int g_gameVersion; +extern int g_fakeArgc; +extern int g_killHistory; + +extern int g_normalWeaponPrefs[NUM_WEAPONS]; +extern int g_rusherWeaponPrefs[NUM_WEAPONS]; +extern int g_carefulWeaponPrefs[NUM_WEAPONS]; +extern int g_grenadeBuyPrecent[NUM_WEAPONS - 23]; +extern int g_botBuyEconomyTable[NUM_WEAPONS - 15]; +extern int g_radioSelect[32]; +extern int g_lastRadio[2]; +extern int g_storeAddbotVars[4]; +extern int *g_weaponPrefs[]; + +extern short g_modelIndexLaser; +extern short g_modelIndexArrow; +extern char g_fakeArgv[256]; + +extern Array > g_chatFactory; +extern Array > g_chatterFactory; +extern Array g_botNames; +extern Array g_replyFactory; +extern RandGen g_randGen; + +extern FireDelay g_fireDelay[NUM_WEAPONS + 1]; +extern WeaponSelect g_weaponSelect[NUM_WEAPONS + 1]; +extern WeaponProperty g_weaponDefs[MAX_WEAPONS + 1]; + +extern Client g_clients[32]; +extern MenuText g_menus[21]; +extern SkillDefinition g_skillTab[6]; +extern TaskItem g_taskFilters[]; + +extern Experience *g_experienceData; + +extern edict_t *g_hostEntity; +extern edict_t *g_worldEdict; +extern Library *g_gameLib; + +extern gamefuncs_t g_functionTable; +extern EntityAPI_t g_entityAPI; +extern FuncPointers_t g_funcPointers; +extern NewEntityAPI_t g_getNewEntityAPI; +extern BlendAPI_t g_serverBlendingAPI; + +#endif // GLOBALS_INCLUDED diff --git a/include/resource.h b/include/resource.h new file mode 100644 index 0000000..76778df --- /dev/null +++ b/include/resource.h @@ -0,0 +1,64 @@ +// +// Copyright (c) 2014, by YaPB Development Team. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// Version: $Id:$ +// + +#ifndef RESOURCE_INCLUDED +#define RESOURCE_INCLUDED + +// general product information +#define PRODUCT_NAME "YaPB" +#define PRODUCT_VERSION "2.6" +#define PRODUCT_AUTHOR "YaPB Dev Team" +#define PRODUCT_URL "http://yapb.jeefo.net/" +#define PRODUCT_EMAIL "yapb@jeefo.net" +#define PRODUCT_LOGTAG "YAPB" +#define PRODUCT_DESCRIPTION PRODUCT_NAME " v" PRODUCT_VERSION " - The Counter-Strike 1.6 Bot" +#define PRODUCT_COPYRIGHT "Copyright © 2014, by " PRODUCT_AUTHOR +#define PRODUCT_LEGAL "Half-Life, Counter-Strike, Counter-Strike: Condition Zero, Steam, Valve is a trademark of Valve Corporation" +#define PRODUCT_ORIGINAL_NAME "yapb_.dll" +#define PRODUCT_INTERNAL_NAME "podbot" +#define PRODUCT_VERSION_DWORD 2,6,0 // major version, minor version, WIP (or Update) version, BUILD number (generated with RES file) +#define PRODUCT_SUPPORT_VERSION "1.4 - CZ" +#define PRODUCT_DATE __DATE__ + +// product optimization type (we're not using crt builds anymore) +#ifndef PRODUCT_OPT_TYPE +#if defined (_DEBUG) +# if defined (_AFXDLL) +# define PRODUCT_OPT_TYPE "Debug Build (CRT)" +# else +# define PRODUCT_OPT_TYPE "Debug Build" +# endif +#elif defined (NDEBUG) +# if defined (_AFXDLL) +# define PRODUCT_OPT_TYPE "Optimized Build (CRT)" +# else +# define PRODUCT_OPT_TYPE "Optimized Build" +# endif +#else +# define PRODUCT_OPT_TYPE "Default Release" +#endif +#endif + +#endif // RESOURCE_INCLUDED + diff --git a/project/makefile b/project/makefile new file mode 100644 index 0000000..2015919 --- /dev/null +++ b/project/makefile @@ -0,0 +1,71 @@ +# +# Copyright (c) 2014, by Yet Another POD-Bot Development Team. +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# $Id$ +# + +MODNAME = yapb +SYSTEM = ../source + +OBJ = ${SYSTEM}/basecode.o \ + ${SYSTEM}/botmanager.o \ + ${SYSTEM}/chatlib.o \ + ${SYSTEM}/combat.o \ + ${SYSTEM}/globals.o \ + ${SYSTEM}/interface.o \ + ${SYSTEM}/navigate.o \ + ${SYSTEM}/netmsg.o \ + ${SYSTEM}/support.o \ + ${SYSTEM}/waypoint.o \ + +CCOPT = -w -O3 -m32 -s -DNDEBUG=1 -fno-exceptions -fno-rtti -funroll-loops -fomit-frame-pointer -pipe -fvisibility-inlines-hidden -fvisibility=hidden +CCDEBUG = -ggdb -w -DDEBUG=1 -fpermissive + +CFLAGS = $(CCOPT) -I../include/engine -I../include +#CFLAGS = $(CCDEBUG) -I../include/engine -I../include + +BASEFLAGS = -Dstricmp=strcasecmp -Dstrcmpi=strcasecmp +CPPFLAGS = ${BASEFLAGS} ${CFLAGS} +OS := $(shell uname -s) + +ifeq "$(OS)" "Darwin" + CPP=clang + SUFFIX=dylib + LINK=-m32 -dynamiclib -mmacosx-version-min=10.5 + CPPLIB=-ldl -lm -lstdc++ +else + CPP=gcc + SUFFIX=so + LINK=-m32 -shared -static-libgcc + CPPLIB=-ldl -lm -lsupc++ +endif + +BINARY=${MODNAME}.${SUFFIX} + +${MODNAME}: ${OBJ} + ${CPP} ${LINK} ${OBJ} ${CPPLIB} -o ${BINARY} + +clean: + -rm -f ${SYSTEM}/*.o + -rm -f ${BINARY} + +%.o: %.cpp + ${CPP} ${CPPFLAGS} -c $< -o $@ diff --git a/project/yapb.sln b/project/yapb.sln new file mode 100644 index 0000000..5a11ce2 --- /dev/null +++ b/project/yapb.sln @@ -0,0 +1,40 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.21005.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "yapb", "yapb.vcxproj", "{C232645A-3B99-48F4-A1F3-F20CF0A9568B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug_Mmgr|Any CPU = Debug_Mmgr|Any CPU + Debug_Mmgr|Mixed Platforms = Debug_Mmgr|Mixed Platforms + Debug_Mmgr|Win32 = Debug_Mmgr|Win32 + Debug|Any CPU = Debug|Any CPU + Debug|Mixed Platforms = Debug|Mixed Platforms + Debug|Win32 = Debug|Win32 + Release|Any CPU = Release|Any CPU + Release|Mixed Platforms = Release|Mixed Platforms + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C232645A-3B99-48F4-A1F3-F20CF0A9568B}.Debug_Mmgr|Any CPU.ActiveCfg = Debug_Mmgr|Win32 + {C232645A-3B99-48F4-A1F3-F20CF0A9568B}.Debug_Mmgr|Mixed Platforms.ActiveCfg = Debug_Mmgr|Win32 + {C232645A-3B99-48F4-A1F3-F20CF0A9568B}.Debug_Mmgr|Mixed Platforms.Build.0 = Debug_Mmgr|Win32 + {C232645A-3B99-48F4-A1F3-F20CF0A9568B}.Debug_Mmgr|Win32.ActiveCfg = Debug_Mmgr|Win32 + {C232645A-3B99-48F4-A1F3-F20CF0A9568B}.Debug_Mmgr|Win32.Build.0 = Debug_Mmgr|Win32 + {C232645A-3B99-48F4-A1F3-F20CF0A9568B}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {C232645A-3B99-48F4-A1F3-F20CF0A9568B}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 + {C232645A-3B99-48F4-A1F3-F20CF0A9568B}.Debug|Mixed Platforms.Build.0 = Debug|Win32 + {C232645A-3B99-48F4-A1F3-F20CF0A9568B}.Debug|Win32.ActiveCfg = Debug|Win32 + {C232645A-3B99-48F4-A1F3-F20CF0A9568B}.Debug|Win32.Build.0 = Debug|Win32 + {C232645A-3B99-48F4-A1F3-F20CF0A9568B}.Release|Any CPU.ActiveCfg = Release|Win32 + {C232645A-3B99-48F4-A1F3-F20CF0A9568B}.Release|Mixed Platforms.ActiveCfg = Release|Win32 + {C232645A-3B99-48F4-A1F3-F20CF0A9568B}.Release|Mixed Platforms.Build.0 = Release|Win32 + {C232645A-3B99-48F4-A1F3-F20CF0A9568B}.Release|Win32.ActiveCfg = Release|Win32 + {C232645A-3B99-48F4-A1F3-F20CF0A9568B}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/project/yapb.vcxproj b/project/yapb.vcxproj new file mode 100644 index 0000000..df69c5f --- /dev/null +++ b/project/yapb.vcxproj @@ -0,0 +1,331 @@ + + + + + Debug_Mmgr + Win32 + + + Debug + Win32 + + + Release + Win32 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {C232645A-3B99-48F4-A1F3-F20CF0A9568B} + yapb + + + + DynamicLibrary + Intel C++ Compiler XE 14.0 + false + + + DynamicLibrary + Intel C++ Compiler XE 14.0 + false + + + DynamicLibrary + Intel C++ Compiler XE 14.0 + false + + + + + + + + + + + + + + + + <_ProjectFileVersion>12.0.21005.1 + + + .\debug\ + .\debug\inf\ + true + true + false + + + .\release\ + .\release\inf\ + false + false + false + false + + + $(Configuration)\ + $(Configuration)\ + true + true + false + + + + ..\..\tool\buildup\release\buildup.exe yapb.rc + + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Win32 + .\debug/yapb.tlb + + + + Disabled + ..\include\engine;..\include;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;%(PreprocessorDefinitions) + Async + MultiThreadedDebug + false + + core.h + .\debug\inf\yapb.pch + false + AssemblyAndSourceCode + .\debug\asm\ + .\debug\obj\ + .\debug\inf + + + Level4 + true + EditAndContinue + Default + false + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + + + + + + .\debug\yapb.dll + true + true + true + .\debug\inf\yapb.pdb + true + .\debug\inf\yapb.map + true + Windows + false + false + false + false + + .\debug\inf\yapb.lib + MachineX86 + ws2_32.lib;%(AdditionalDependencies) + + + copy "$(TargetPath)" "d:\steam\SteamApps\common\Half-Life\cstrike\addons\yapb\dlls" /y + + + + + ..\..\tool\buildup\release\buildup.exe yapb.rc + + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Win32 + .\Release/yapb.tlb + + + + OnlyExplicitInline + true + Speed + true + false + ..\mmgr;..\include\engine;..\include;%(AdditionalIncludeDirectories) + NDEBUG;WIN32;%(PreprocessorDefinitions) + true + Async + MultiThreaded + 8Bytes + true + false + NotSet + false + false + NotUsing + core.h + .\release\inf\yapb.pch + .\release\asm\ + .\release\obj\ + .\release\inf\ + Level4 + true + None + CompileAsCpp + true + SingleFile + true + false + MaxSpeedHighLevel + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + $(IntDir)%(Filename).res + + + + + + true + + + .\release\yapb.dll + true + user32.dll;ws2_32.dll;%(DelayLoadDLLs) + false + false + Windows + true + true + false + false + + true + .\release\inf\yapb.lib + MachineX86 + + ws2_32.lib;%(AdditionalDependencies) + + + copy "$(TargetPath)" "d:\steam\SteamApps\common\Half-Life\cstrike\addons\yapb\dlls" /y + + + + + ..\..\tool\buildup\release\buildup.exe yapb.rc + + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Win32 + .\debug/yapb.tlb + + + + Disabled + ..\include\engine;..\include;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;MEMORY_TEST;%(PreprocessorDefinitions) + Async + MultiThreadedDebug + false + + core.h + .\debug_mmgr\inf\yapb.pch + false + AssemblyAndSourceCode + .\debug_mmgr\asm\ + .\debug_mmgr\obj\ + .\debug_mmgr\inf + + + Level4 + true + EditAndContinue + Default + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + + + + + + .\debug_mmgr\yapb.dll + true + true + true + .\debug_mmgr\inf\yapb.pdb + true + .\debug_mmgr\inf\yapb.map + true + Windows + false + false + false + false + + .\debug_mmgr\inf\yapb.lib + MachineX86 + + + copy "$(TargetPath)" "d:\game\steam\steamapps\dmitryzhukov\counter-strike\cstrike\addons\yapb\dlls\" /y +copy "$(TargetPath)" "d:\game\steam\steamapps\dmitryzhukov\condition zero\czero\addons\yapb\dlls\" /y +copy "$(TargetPath)" "d:\game\steam\steamapps\dmitryzhukov\dedicated server\cstrike\addons\yapb\dlls\" /y +copy "$(TargetPath)" "d:\hlds\cs1\cstrike\addons\yapb\dlls\" /y + + + + + + + \ No newline at end of file diff --git a/source/basecode.cpp b/source/basecode.cpp new file mode 100644 index 0000000..2207d46 --- /dev/null +++ b/source/basecode.cpp @@ -0,0 +1,6459 @@ +// +// Copyright (c) 2014, by YaPB Development Team. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// Version: $Id:$ +// + +#include + +ConVar yb_debug ("yb_debug", "0"); +ConVar yb_debug_goal ("yb_debug_goal", "-1"); +ConVar yb_user_follow_percent ("yb_user_follow_percent", "20"); +ConVar yb_user_max_followers ("yb_user_max_followers", "2"); + +ConVar yb_jasonmode ("yb_jasonmode", "0"); +ConVar yb_communication_type ("yb_communication_type", "2"); +ConVar yb_economics_rounds ("yb_economics_rounds", "1"); +ConVar yb_hardcore_mode ("yb_hardcore_mode", "1"); +ConVar yb_walking_allowed ("yb_walking_allowed", "1"); + +ConVar yb_tkpunish ("yb_tkpunish", "1"); +ConVar yb_freeze_bots ("yb_freeze_bots", "0"); +ConVar yb_spraypaints ("yb_spraypaints", "1"); +ConVar yb_botbuy ("yb_botbuy", "1"); + +ConVar yb_timersound ("yb_timersound", "0.5"); +ConVar yb_timerpickup ("yb_timerpickup", "0.5"); +ConVar yb_timergrenade ("yb_timergrenade", "0.5"); + +ConVar yb_chatter_path ("yb_chatter_path", "sound/radio/bot"); +ConVar yb_restricted_weapons ("yb_restricted_weapons", "ump45;p90;elite;tmp;mac10;m3;xm1014"); + +// game console variables +ConVar mp_c4timer ("mp_c4timer", NULL, VT_NOREGISTER); +ConVar mp_buytime ("mp_buytime", NULL, VT_NOREGISTER); +ConVar mp_footsteps ("mp_footsteps", NULL, VT_NOREGISTER); +ConVar sv_gravity ("sv_gravity", NULL, VT_NOREGISTER); + +int Bot::GetMessageQueue (void) +{ + // this function get the current message from the bots message queue + + int message = m_messageQueue[m_actMessageIndex++]; + m_actMessageIndex &= 0x1f; // wraparound + + return message; +} + +void Bot::PushMessageQueue (int message) +{ + // this function put a message into the bot message queue + + if (message == GSM_SAY) + { + // notify other bots of the spoken text otherwise, bots won't respond to other bots (network messages aren't sent from bots) + int entityIndex = GetIndex (); + + for (int i = 0; i < GetMaxClients (); i++) + { + Bot *otherBot = g_botManager->GetBot (i); + + if (otherBot != NULL && otherBot->pev != pev) + { + if (IsAlive (GetEntity ()) == IsAlive (otherBot->GetEntity ())) + { + otherBot->m_sayTextBuffer.entityIndex = entityIndex; + strcpy (otherBot->m_sayTextBuffer.sayText, m_tempStrings); + } + otherBot->m_sayTextBuffer.timeNextChat = GetWorldTime () + otherBot->m_sayTextBuffer.chatDelay; + } + } + } + m_messageQueue[m_pushMessageIndex++] = message; + m_pushMessageIndex &= 0x1f; // wraparound +} + +int Bot::InFieldOfView (const Vector &destination) +{ + float entityAngle = AngleMod (destination.ToYaw ()); // find yaw angle from source to destination... + float viewAngle = AngleMod (pev->v_angle.y); // get bot's current view angle... + + // return the absolute value of angle to destination entity + // zero degrees means straight ahead, 45 degrees to the left or + // 45 degrees to the right is the limit of the normal view angle + int absoluteAngle = abs (static_cast (viewAngle) - static_cast (entityAngle)); + + if (absoluteAngle > 180) + absoluteAngle = 360 - absoluteAngle; + + return absoluteAngle; +} + +bool Bot::IsInViewCone (const Vector &origin) +{ + // this function returns true if the spatial vector location origin is located inside + // the field of view cone of the bot entity, false otherwise. It is assumed that entities + // have a human-like field of view, that is, about 90 degrees. + + return ::IsInViewCone (origin, GetEntity ()); +} + +bool Bot::CheckVisibility (entvars_t *targetEntity, Vector *origin, byte *bodyPart) +{ + // this function checks visibility of a bot target. + + Vector botHead = EyePosition (); + TraceResult tr; + + *bodyPart = 0; + + // check for the body + TraceLine (botHead, targetEntity->origin, true, true, GetEntity (), &tr); + + if (tr.flFraction >= 1.0) + { + *bodyPart |= VISIBLE_BODY; + *origin = targetEntity->origin; + } + + // check for the head + TraceLine (botHead, targetEntity->origin + targetEntity->view_ofs, true, true, GetEntity (), &tr); + + if (tr.flFraction >= 1.0) + { + *bodyPart |= VISIBLE_HEAD; + *origin = targetEntity->origin + targetEntity->view_ofs; + } + + if (*bodyPart != 0) + return true; + +#if 0 + // dimension table + const int8 dimensionTab[8][3] = + { + {1, 1, 1}, { 1, 1, -1}, + {1, -1, 1}, {-1, 1, 1}, + {1, -1, -1}, {-1, -1, 1}, + {-1, 1, -1}, {-1, -1, -1} + }; + + // check the box borders + for (int i = 7; i >= 0; i--) + { + Vector targetOrigin = petargetOrigin->origin + Vector (dimensionTab[i][0] * 24.0, dimensionTab[i][1] * 24.0, dimensionTab[i][2] * 24.0); + + // check direct line to random part of the player body + TraceLine (botHead, targetOrigin, true, true, GetEntity (), &tr); + + // check if we hit something + if (tr.flFraction >= 1.0) + { + *origin = tr.vecEndPos; + *bodyPart |= VISIBLE_OTHER; + + return true; + } + } +#else + // worst case, choose random position in enemy body + for (int i = 0; i < 5; i++) + { + Vector targetOrigin = targetEntity->origin; // get the player origin + + // find the vector beetwen mins and maxs of the player body + targetOrigin.x += g_randGen.Float (targetEntity->mins.x * 0.5, targetEntity->maxs.x * 0.5); + targetOrigin.y += g_randGen.Float (targetEntity->mins.y * 0.5, targetEntity->maxs.y * 0.5); + targetOrigin.z += g_randGen.Float (targetEntity->mins.z * 0.5, targetEntity->maxs.z * 0.5); + + // check direct line to random part of the player body + TraceLine (botHead, targetOrigin, true, true, GetEntity (), &tr); + + // check if we hit something + if (tr.flFraction >= 1.0) + { + *origin = tr.vecEndPos; + *bodyPart |= VISIBLE_OTHER; + + return true; + } + } +#endif + return false; +} + +bool Bot::IsEnemyViewable (edict_t *player) +{ + if (FNullEnt (player)) + return false; + + bool forceTrueIfVisible = false; + + if (IsValidPlayer (pev->dmg_inflictor) && GetTeam (pev->dmg_inflictor) != GetTeam (GetEntity ()) && ::IsInViewCone (EyePosition (), pev->dmg_inflictor)) + forceTrueIfVisible = true; + + if (CheckVisibility (VARS (player), &m_enemyOrigin, &m_visibility) && (IsInViewCone (player->v.origin + Vector (0, 0, 14)) || forceTrueIfVisible)) + { + m_seeEnemyTime = GetWorldTime (); + m_lastEnemy = player; + m_lastEnemyOrigin = player->v.origin; + + return true; + } + return false; +} + +bool Bot::ItemIsVisible (const Vector &destination, char *itemName) +{ + TraceResult tr; + + // trace a line from bot's eyes to destination.. + TraceLine (EyePosition (), destination, true, GetEntity (), &tr); + + // check if line of sight to object is not blocked (i.e. visible) + if (tr.flFraction != 1.0) + { + // check for standard items + if (strcmp (STRING (tr.pHit->v.classname), itemName) == 0) + return true; + + if (tr.flFraction > 0.98 && strncmp (STRING (tr.pHit->v.classname), "csdmw_", 6) == 0) + return true; + + return false; + } + return true; +} + +bool Bot::EntityIsVisible (const Vector &dest, bool fromBody) +{ + TraceResult tr; + + // trace a line from bot's eyes to destination... + TraceLine (fromBody ? pev->origin - Vector (0, 0, 1) : EyePosition (), dest, true, true, GetEntity (), &tr); + + // check if line of sight to object is not blocked (i.e. visible) + return tr.flFraction >= 1.0; +} + + +void Bot::AvoidGrenades (void) +{ + // checks if bot 'sees' a grenade, and avoid it + + edict_t *ent = m_avoidGrenade; + + // check if old pointers to grenade is invalid + if (FNullEnt (ent)) + { + m_avoidGrenade = NULL; + m_needAvoidGrenade = 0; + } + else if ((ent->v.flags & FL_ONGROUND) || (ent->v.effects & EF_NODRAW)) + { + m_avoidGrenade = NULL; + m_needAvoidGrenade = 0; + } + ent = NULL; + + // find all grenades on the map + while (!FNullEnt (ent = FIND_ENTITY_BY_CLASSNAME (ent, "grenade"))) + { + // if grenade is invisible don't care for it + if (ent->v.effects & EF_NODRAW) + continue; + + // check if visible to the bot + if (!EntityIsVisible (ent->v.origin) && InFieldOfView (ent->v.origin - EyePosition ()) > pev->fov / 3) + continue; + + // TODO: should be done once for grenade, instead of checking several times + if (m_skill >= 70 && strcmp (STRING (ent->v.model) + 9, "flashbang.mdl") == 0) + { + Vector position = (GetEntityOrigin (ent) - EyePosition ()).ToAngles (); + + // don't look at flashbang + if (((ent->v.owner == GetEntity () || g_randGen.Long (0, 100) < 85) && !(m_states & STATE_SEEING_ENEMY))) + { + pev->v_angle.y = AngleNormalize (position.y + 180.0); + m_canChooseAimDirection = false; + } + } + else + if (strcmp (STRING (ent->v.model) + 9, "hegrenade.mdl") == 0) + { + if (!FNullEnt (m_avoidGrenade)) + return; + + if (GetTeam (ent->v.owner) == GetTeam (GetEntity ()) && ent->v.owner != GetEntity ()) + return; + + if ((ent->v.flags & FL_ONGROUND) == 0) + { + float distance = (ent->v.origin - pev->origin).GetLength (); + float distanceMoved = ((ent->v.origin + ent->v.velocity * m_frameInterval) - pev->origin).GetLength (); + + if (distanceMoved < distance && distance < 500) + { + MakeVectors (pev->v_angle); + + Vector dirToPoint = (pev->origin - ent->v.origin).Normalize2D (); + Vector rightSide = g_pGlobals->v_right.Normalize2D (); + + if ((dirToPoint | rightSide) > 0) + m_needAvoidGrenade = -1; + else + m_needAvoidGrenade = 1; + + m_avoidGrenade = ent; + } + } + } + } +} + +bool Bot::IsBehindSmokeClouds (edict_t *ent) +{ + edict_t *pentGrenade = NULL; + Vector betweenUs = (ent->v.origin - pev->origin).Normalize (); + + while (!FNullEnt (pentGrenade = FIND_ENTITY_BY_CLASSNAME (pentGrenade, "grenade"))) + { + // if grenade is invisible don't care for it + if ((pentGrenade->v.effects & EF_NODRAW) || !(pentGrenade->v.flags & (FL_ONGROUND | FL_PARTIALGROUND)) || strcmp (STRING (pentGrenade->v.model) + 9, "smokegrenade.mdl")) + continue; + + // check if visible to the bot + if (!EntityIsVisible (ent->v.origin) && InFieldOfView (ent->v.origin - EyePosition ()) > pev->fov / 3) + continue; + + Vector betweenNade = (GetEntityOrigin (pentGrenade) - pev->origin).Normalize (); + Vector betweenResult = ((Vector (betweenNade.y, betweenNade.x, 0) * 150.0 + GetEntityOrigin (pentGrenade)) - pev->origin).Normalize (); + + if ((betweenNade | betweenUs) > (betweenNade | betweenResult)) + return true; + } + return false; +} + +int Bot::GetBestWeaponCarried (void) +{ + // this function returns the best weapon of this bot (based on personality prefs) + + int *ptr = g_weaponPrefs[m_personality]; + int weaponIndex = 0; + int weapons = pev->weapons; + + WeaponSelect *weaponTab = &g_weaponSelect[0]; + + // take the shield in account + if (HasShield ()) + weapons |= (1 << WEAPON_SHIELD); + + for (int i = 0; i < NUM_WEAPONS; i++) + { + if (weapons & (1 << weaponTab[*ptr].id)) + weaponIndex = i; + + ptr++; + } + return weaponIndex; +} + +int Bot::GetBestSecondaryWeaponCarried (void) +{ + // this function returns the best secondary weapon of this bot (based on personality prefs) + + int *ptr = g_weaponPrefs[m_personality]; + int weaponIndex = 0; + int weapons = pev->weapons; + + // take the shield in account + if (HasShield ()) + weapons |= (1 << WEAPON_SHIELD); + + WeaponSelect *weaponTab = &g_weaponSelect[0]; + + for (int i = 0; i < NUM_WEAPONS; i++) + { + int id = weaponTab[*ptr].id; + + if ((weapons & (1 << weaponTab[*ptr].id)) && (id == WEAPON_USP || id == WEAPON_GLOCK || id == WEAPON_DEAGLE || id == WEAPON_P228 || id == WEAPON_ELITE || id == WEAPON_FIVESEVEN)) + { + weaponIndex = i; + break; + } + ptr++; + } + return weaponIndex; +} + +bool Bot::RateGroundWeapon (edict_t *ent) +{ + // this function compares weapons on the ground to the one the bot is using + + int hasWeapon = 0; + int groundIndex = 0; + int *ptr = g_weaponPrefs[m_personality]; + + WeaponSelect *weaponTab = &g_weaponSelect[0]; + + for (int i = 0; i < NUM_WEAPONS; i++) + { + if (strcmp (weaponTab[*ptr].modelName, STRING (ent->v.model) + 9) == 0) + { + groundIndex = i; + break; + } + ptr++; + } + + if (groundIndex < 7) + hasWeapon = GetBestSecondaryWeaponCarried (); + else + hasWeapon = GetBestWeaponCarried (); + + return groundIndex > hasWeapon; +} + +edict_t *Bot::FindBreakable (void) +{ + // this function checks if bot is blocked by a shoot able breakable in his moving direction + + TraceResult tr; + TraceLine (pev->origin, pev->origin + (m_destOrigin - pev->origin).Normalize () * 64, false, false, GetEntity (), &tr); + + if (tr.flFraction != 1.0) + { + edict_t *ent = tr.pHit; + + // check if this isn't a triggered (bomb) breakable and if it takes damage. if true, shoot the crap! + if (IsShootableBreakable (ent)) + { + m_breakable = tr.vecEndPos; + return ent; + } + } + TraceLine (EyePosition (), EyePosition () + (m_destOrigin - EyePosition ()).Normalize () * 64, false, false, GetEntity (), &tr); + + if (tr.flFraction != 1.0) + { + edict_t *ent = tr.pHit; + + if (IsShootableBreakable (ent)) + { + m_breakable = tr.vecEndPos; + return ent; + } + } + m_breakableEntity = NULL; + m_breakable = nullvec; + + return NULL; +} + +void Bot::FindItem (void) +{ + // this function finds Items to collect or use in the near of a bot + + // don't try to pickup anything while on ladder or trying to escape from bomb... + if (IsOnLadder () || GetTaskId () == TASK_ESCAPEFROMBOMB) + { + m_pickupItem = NULL; + m_pickupType = PICKUP_NONE; + + return; + } + + edict_t *ent = NULL, *pickupItem = NULL; + Bot *bot = NULL; + + bool allowPickup= false; + float distance, minDistance = 341.0; + + + if (!FNullEnt (m_pickupItem)) + { + bool itemExists = false; + pickupItem = m_pickupItem; + + while (!FNullEnt (ent = FIND_ENTITY_IN_SPHERE (ent, pev->origin, 340.0))) + { + if ((ent->v.effects & EF_NODRAW) || IsValidPlayer (ent->v.owner)) + continue; // someone owns this weapon or it hasn't respawned yet + + if (ent == pickupItem) + { + if (ItemIsVisible (GetEntityOrigin (ent), const_cast (STRING (ent->v.classname)))) + itemExists = true; + break; + } + } + if (itemExists) + return; + else + { + m_pickupItem = NULL; + m_pickupType = PICKUP_NONE; + } + } + + ent = NULL; + pickupItem = NULL; + + PickupType pickupType = PICKUP_NONE; + + Vector pickupOrigin = nullvec; + Vector entityOrigin = nullvec; + + m_pickupItem = NULL; + m_pickupType = PICKUP_NONE; + + while (!FNullEnt (ent = FIND_ENTITY_IN_SPHERE (ent, pev->origin, 340.0))) + { + allowPickup = false; // assume can't use it until known otherwise + + if ((ent->v.effects & EF_NODRAW) || ent == m_itemIgnore) + continue; // someone owns this weapon or it hasn't respawned yet + + entityOrigin = GetEntityOrigin (ent); + + // check if line of sight to object is not blocked (i.e. visible) + if (ItemIsVisible (entityOrigin, const_cast (STRING (ent->v.classname)))) + { + if (strncmp ("hostage_entity", STRING (ent->v.classname), 14) == 0) + { + allowPickup = true; + pickupType = PICKUP_HOSTAGE; + } + else if (strncmp ("weaponbox", STRING (ent->v.classname), 9) == 0 && strcmp (STRING (ent->v.model) + 9, "backpack.mdl") == 0) + { + allowPickup = true; + pickupType = PICKUP_DROPPED_C4; + } + else if (strncmp ("weaponbox", STRING (ent->v.classname), 9) == 0 && strcmp (STRING (ent->v.model) + 9, "backpack.mdl") == 0 && !m_isUsingGrenade) + { + allowPickup = true; + pickupType = PICKUP_DROPPED_C4; + } + else if ((strncmp ("weaponbox", STRING (ent->v.classname), 9) == 0 || strncmp ("armoury_entity", STRING (ent->v.classname), 14) == 0 || strncmp ("csdm", STRING (ent->v.classname), 4) == 0) && !m_isUsingGrenade) + { + allowPickup = true; + pickupType = PICKUP_WEAPON; + } + else if (strncmp ("weapon_shield", STRING (ent->v.classname), 13) == 0 && !m_isUsingGrenade) + { + allowPickup = true; + pickupType = PICKUP_SHIELD; + } + else if (strncmp ("item_thighpack", STRING (ent->v.classname), 14) == 0 && GetTeam (GetEntity ()) == TEAM_CF && !m_hasDefuser) + { + allowPickup = true; + pickupType = PICKUP_DEFUSEKIT; + } + else if (strncmp ("grenade", STRING (ent->v.classname), 7) == 0 && strcmp (STRING (ent->v.model) + 9, "c4.mdl") == 0) + { + allowPickup = true; + pickupType = PICKUP_PLANTED_C4; + } + } + + if (allowPickup) // if the bot found something it can pickup... + { + distance = (entityOrigin - pev->origin).GetLength (); + + // see if it's the closest item so far... + if (distance < minDistance) + { + if (pickupType == PICKUP_WEAPON) // found weapon on ground? + { + int weaponCarried = GetBestWeaponCarried (); + int secondaryWeaponCarried = GetBestSecondaryWeaponCarried (); + + if (secondaryWeaponCarried < 7 && (m_ammo[g_weaponSelect[secondaryWeaponCarried].id] > 0.3 * g_weaponDefs[g_weaponSelect[secondaryWeaponCarried].id].ammo1Max) && strcmp (STRING (ent->v.model) + 9, "w_357ammobox.mdl") == 0) + allowPickup = false; + else if (!m_isVIP && weaponCarried >= 7 && (m_ammo[g_weaponSelect[weaponCarried].id] > 0.3 * g_weaponDefs[g_weaponSelect[weaponCarried].id].ammo1Max) && strncmp (STRING (ent->v.model) + 9, "w_", 2) == 0) + { + if (strcmp (STRING (ent->v.model) + 9, "w_9mmarclip.mdl") == 0 && !(weaponCarried == WEAPON_FAMAS || weaponCarried == WEAPON_AK47 || weaponCarried == WEAPON_M4A1 || weaponCarried == WEAPON_GALIL || weaponCarried == WEAPON_AUG || weaponCarried == WEAPON_SG552)) + allowPickup = false; + else if (strcmp (STRING (ent->v.model) + 9, "w_shotbox.mdl") == 0 && !(weaponCarried == WEAPON_M3 || weaponCarried == WEAPON_XM1014)) + allowPickup = false; + else if (strcmp (STRING (ent->v.model) + 9, "w_9mmclip.mdl") == 0 && !(weaponCarried == WEAPON_MP5 || weaponCarried == WEAPON_TMP || weaponCarried == WEAPON_P90 || weaponCarried == WEAPON_MAC10 || weaponCarried == WEAPON_UMP45)) + allowPickup = false; + else if (strcmp (STRING (ent->v.model) + 9, "w_crossbow_clip.mdl") == 0 && !(weaponCarried == WEAPON_AWP || weaponCarried == WEAPON_G3SG1 || weaponCarried == WEAPON_SCOUT || weaponCarried == WEAPON_SG550)) + allowPickup = false; + else if (strcmp (STRING (ent->v.model) + 9, "w_chainammo.mdl") == 0 && weaponCarried == WEAPON_M249) + allowPickup = false; + } + else if (m_isVIP || !RateGroundWeapon (ent)) + allowPickup = false; + else if (strcmp (STRING (ent->v.model) + 9, "medkit.mdl") == 0 && (pev->health >= 100)) + allowPickup = false; + else if ((strcmp (STRING (ent->v.model) + 9, "kevlar.mdl") == 0 || strcmp (STRING (ent->v.model) + 9, "battery.mdl") == 0) && pev->armorvalue >= 100) // armor vest + allowPickup = false; + else if (strcmp (STRING (ent->v.model) + 9, "flashbang.mdl") == 0 && (pev->weapons & (1 << WEAPON_FLASHBANG))) // concussion grenade + allowPickup = false; + else if (strcmp (STRING (ent->v.model) + 9, "hegrenade.mdl") == 0 && (pev->weapons & (1 << WEAPON_EXPLOSIVE))) // explosive grenade + allowPickup = false; + else if (strcmp (STRING (ent->v.model) + 9, "smokegrenade.mdl") == 0 && (pev->weapons & (1 << WEAPON_SMOKE))) // smoke grenade + allowPickup = false; + } + else if (pickupType == PICKUP_SHIELD) // found a shield on ground? + { + if ((pev->weapons & (1 << WEAPON_ELITE)) || HasShield () || m_isVIP || (HasPrimaryWeapon () && !RateGroundWeapon (ent))) + allowPickup = false; + } + else if (GetTeam (GetEntity ()) == TEAM_TF) // terrorist team specific + { + if (pickupType == PICKUP_DROPPED_C4) + { + allowPickup = true; + m_destOrigin = entityOrigin; // ensure we reached droped bomb + + ChatterMessage (Chatter_FoundC4); // play info about that + DeleteSearchNodes (); + } + else if (pickupType == PICKUP_HOSTAGE) + { + m_itemIgnore = ent; + allowPickup = false; + + if (m_skill > 80 && g_randGen.Long (0, 100) < 50 && GetTaskId () != TASK_MOVETOPOSITION && GetTaskId () != TASK_CAMP) + { + int index = FindDefendWaypoint (entityOrigin); + + StartTask (TASK_CAMP, TASKPRI_CAMP, -1, GetWorldTime () + g_randGen.Float (60.0, 120.0), true); // push camp task on to stack + StartTask (TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, index, GetWorldTime () + g_randGen.Float (3.0, 6.0), true); // push move command + + if (g_waypoint->GetPath (index)->vis.crouch <= g_waypoint->GetPath (index)->vis.stand) + m_campButtons |= IN_DUCK; + else + m_campButtons &= ~IN_DUCK; + + ChatterMessage (Chatter_GoingToGuardHostages); // play info about that + return; + } + } + else if (pickupType == PICKUP_PLANTED_C4) + { + allowPickup = false; + + if (!m_defendedBomb) + { + m_defendedBomb = true; + + int index = FindDefendWaypoint (entityOrigin); + Path *path = g_waypoint->GetPath (index); + + float bombTimer = mp_c4timer.GetFloat (); + float timeMidBlowup = g_timeBombPlanted + ((bombTimer / 2) + bombTimer / 4) - g_waypoint->GetTravelTime (pev->maxspeed, pev->origin, path->origin); + + if (timeMidBlowup > GetWorldTime ()) + { + RemoveCertainTask (TASK_MOVETOPOSITION); // remove any move tasks + + StartTask (TASK_CAMP, TASKPRI_CAMP, -1, timeMidBlowup, true); // push camp task on to stack + StartTask (TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, index, timeMidBlowup, true); // push move command + + if (path->vis.crouch <= path->vis.stand) + m_campButtons |= IN_DUCK; + else + m_campButtons &= ~IN_DUCK; + + if (g_randGen.Long (0, 100) < 90) + ChatterMessage (Chatter_DefendingBombSite); + } + else + RadioMessage (Radio_ShesGonnaBlow); // issue an additional radio message + } + } + } + else if (GetTeam (GetEntity ()) == TEAM_CF) + { + if (pickupType == PICKUP_HOSTAGE) + { + if (FNullEnt (ent) || (ent->v.health <= 0)) + allowPickup = false; // never pickup dead hostage + else for (int i = 0; i < GetMaxClients (); i++) + { + if ((bot = g_botManager->GetBot (i)) != NULL && IsAlive (bot->GetEntity ())) + { + for (int j = 0; j < MAX_HOSTAGES; j++) + { + if (bot->m_hostages[j] == ent) + { + allowPickup = false; + break; + } + } + } + } + } + else if (pickupType == PICKUP_PLANTED_C4 && !OutOfBombTimer ()) + { + if (m_states & (STATE_SEEING_ENEMY | STATE_SUSPECT_ENEMY)) + { + allowPickup = false; + return; + } + + if (g_randGen.Long (0, 100) < 90) + ChatterMessage (Chatter_FoundBombPlace); + + allowPickup = !IsBombDefusing (g_waypoint->GetBombPosition ()) || m_hasProgressBar; + pickupType = PICKUP_PLANTED_C4; + + if (!m_defendedBomb && !allowPickup) + { + m_defendedBomb = true; + + int index = FindDefendWaypoint (entityOrigin); + Path *path = g_waypoint->GetPath (index); + + float timeToExplode = g_timeBombPlanted + mp_c4timer.GetFloat () - g_waypoint->GetTravelTime (pev->maxspeed, pev->origin, path->origin); + + RemoveCertainTask (TASK_MOVETOPOSITION); // remove any move tasks + + StartTask (TASK_CAMP, TASKPRI_CAMP, -1, timeToExplode, true); // push camp task on to stack + StartTask (TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, index, timeToExplode, true); // push move command + + if (path->vis.crouch <= path->vis.stand) + m_campButtons |= IN_DUCK; + else + m_campButtons &= ~IN_DUCK; + + if (g_randGen.Long (0, 100) < 90) + ChatterMessage (Chatter_DefendingBombSite); + } + } + else if (pickupType == PICKUP_DROPPED_C4) + { + m_itemIgnore = ent; + allowPickup = false; + + if (m_skill > 80 && g_randGen.Long (0, 100) < 90) + { + int index = FindDefendWaypoint (entityOrigin); + + StartTask (TASK_CAMP, TASKPRI_CAMP, -1, GetWorldTime () + g_randGen.Float (60.0, 120.0), true); // push camp task on to stack + StartTask (TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, index, GetWorldTime () + g_randGen.Float (10.0, 30.0), true); // push move command + + if (g_waypoint->GetPath (index)->vis.crouch <= g_waypoint->GetPath (index)->vis.stand) + m_campButtons |= IN_DUCK; + else + m_campButtons &= ~IN_DUCK; + + ChatterMessage (Chatter_GoingToGuardDoppedBomb); // play info about that + return; + } + } + } + + // if condition valid + if (allowPickup) + { + minDistance = distance; // update the minimum distance + pickupOrigin = entityOrigin; // remember location of entity + pickupItem = ent; // remember this entity + + m_pickupType = pickupType; + } + else + pickupType = PICKUP_NONE; + } + } + } // end of the while loop + + if (!FNullEnt (pickupItem)) + { + for (int i = 0; i < GetMaxClients (); i++) + { + if ((bot = g_botManager->GetBot (i)) != NULL && IsAlive (bot->GetEntity ()) && bot->m_pickupItem == pickupItem) + { + m_pickupItem = NULL; + m_pickupType = PICKUP_NONE; + + return; + } + } + + if (pickupOrigin.z > EyePosition ().z + 3.0 || IsDeadlyDrop (pickupOrigin)) // check if item is too high to reach, check if getting the item would hurt bot + { + m_pickupItem = NULL; + m_pickupType = PICKUP_NONE; + + return; + } + m_pickupItem = pickupItem; // save pointer of picking up entity + } +} + +void Bot::GetCampDirection (Vector *dest) +{ + // this function check if view on last enemy position is blocked - replace with better vector then + // mostly used for getting a good camping direction vector if not camping on a camp waypoint + + TraceResult tr; + Vector src = EyePosition (); + + TraceLine (src, *dest, true, GetEntity (), &tr); + + // check if the trace hit something... + if (tr.flFraction < 1.0) + { + float length = (tr.vecEndPos - src).GetLengthSquared (); + + if (length > 10000) + return; + + float minDistance = FLT_MAX; + float maxDistance = FLT_MAX; + + int enemyIndex = -1, tempIndex = -1; + + // find nearest waypoint to bot and position + for (int i = 0; i < g_numWaypoints; i++) + { + float distance = (g_waypoint->GetPath (i)->origin - pev->origin).GetLengthSquared (); + + if (distance < minDistance) + { + minDistance = distance; + tempIndex = i; + } + distance = (g_waypoint->GetPath (i)->origin - *dest).GetLengthSquared (); + + if (distance < maxDistance) + { + maxDistance = distance; + enemyIndex = i; + } + } + + if (tempIndex == -1 || enemyIndex == -1) + return; + + minDistance = FLT_MAX; + + int lookAtWaypoint = -1; + Path *path = g_waypoint->GetPath (tempIndex); + + for (int i = 0; i < MAX_PATH_INDEX; i++) + { + if (path->index[i] == -1) + continue; + + float distance = g_waypoint->GetPathDistance (path->index[i], enemyIndex); + + if (distance < minDistance) + { + minDistance = distance; + lookAtWaypoint = path->index[i]; + } + } + if (lookAtWaypoint != -1 && lookAtWaypoint < g_numWaypoints) + *dest = g_waypoint->GetPath (lookAtWaypoint)->origin; + } +} + +void Bot::SwitchChatterIcon (bool show) +{ + // this function depending on show boolen, shows/remove chatter, icon, on the head of bot. + + if (g_gameVersion == CSV_OLD || g_pGlobals == NULL) + return; + + for (int i = 0; i < 32; i++) + { + edict_t *ent = INDEXENT (i); + + if (!IsValidPlayer (ent) || IsValidBot (ent) || GetTeam (ent) != GetTeam (GetEntity ())) + continue; + + MESSAGE_BEGIN (MSG_ONE, g_netMsg->GetId (NETMSG_BOTVOICE), NULL, ent); // begin message + WRITE_BYTE (show); // switch on/off + WRITE_BYTE (GetIndex ()); + MESSAGE_END (); + } +} + +void Bot::InstantChatterMessage (int type) +{ + // this function sends instant chatter messages. + + if (yb_communication_type.GetInt () != 2 || g_chatterFactory[type].IsEmpty () || g_gameVersion == CSV_OLD || !g_sendAudioFinished) + return; + + if (IsAlive (GetEntity ())) + SwitchChatterIcon (true); + + static float reportTime = GetWorldTime (); + + // delay only reportteam + if (type == Radio_ReportTeam) + { + if (reportTime >= GetWorldTime ()) + return; + + reportTime = GetWorldTime () + g_randGen.Float (30.0, 80.0); + } + + String defaultSound = g_chatterFactory[type].GetRandomElement ().name; + String painSound = g_chatterFactory[Chatter_DiePain].GetRandomElement ().name; + + for (int i = 0; i < GetMaxClients (); i++) + { + edict_t *ent = INDEXENT (i); + + if (!IsValidPlayer (ent) || IsValidBot (ent) || GetTeam (ent) != GetTeam (GetEntity ())) + continue; + + g_sendAudioFinished = false; + + MESSAGE_BEGIN (MSG_ONE, g_netMsg->GetId (NETMSG_SENDAUDIO), NULL, ent); // begin message + WRITE_BYTE (GetIndex ()); + + if (pev->deadflag & DEAD_DYING) + WRITE_STRING (FormatBuffer ("%s/%s.wav", yb_chatter_path.GetString (), painSound.ToString ())); + else if (!(pev->deadflag & DEAD_DEAD)) + WRITE_STRING (FormatBuffer ("%s/%s.wav", yb_chatter_path.GetString (), defaultSound.ToString ())); + + WRITE_SHORT (m_voicePitch); + MESSAGE_END (); + + g_sendAudioFinished = true; + } +} + +void Bot::RadioMessage (int message) +{ + // this function inserts the radio message into the message queue + + if (yb_communication_type.GetInt () == 0 || GetNearbyFriendsNearPosition (pev->origin, 9999) == 0) + return; + + if (g_chatterFactory[message].IsEmpty () || g_gameVersion == CSV_OLD || yb_communication_type.GetInt () != 2) + g_radioInsteadVoice = true; // use radio instead voice + + m_radioSelect = message; + PushMessageQueue (GSM_RADIO); +} + +void Bot::ChatterMessage (int message) +{ + // this function inserts the voice message into the message queue (mostly same as above) + + if (yb_communication_type.GetInt () != 2 || g_chatterFactory[message].IsEmpty () || GetNearbyFriendsNearPosition (pev->origin, 9999) == 0) + return; + + bool shouldExecute = false; + + if (m_voiceTimers[message] < GetWorldTime () || m_voiceTimers[message] == FLT_MAX) + { + if (m_voiceTimers[message] != FLT_MAX) + m_voiceTimers[message] = GetWorldTime () + g_chatterFactory[message][0].repeatTime; + + shouldExecute = true; + } + + if (!shouldExecute) + return; + + m_radioSelect = message; + PushMessageQueue (GSM_RADIO); +} + +void Bot::CheckMessageQueue (void) +{ + // this function checks and executes pending messages + + // no new message? + if (m_actMessageIndex == m_pushMessageIndex) + return; + + // get message from stack + int currentQueueMessage = GetMessageQueue (); + + // nothing to do? + if (currentQueueMessage == GSM_IDLE || (currentQueueMessage == GSM_RADIO && yb_csdm_mode.GetInt () == 2)) + return; + + int team = GetTeam (GetEntity ()); + + switch (currentQueueMessage) + { + case GSM_BUY_STUFF: // general buy message + + // buy weapon + if (m_nextBuyTime > GetWorldTime ()) + { + // keep sending message + PushMessageQueue (GSM_BUY_STUFF); + return; + } + + if (!m_inBuyZone || yb_csdm_mode.GetBool ()) + { + m_buyPending = true; + m_buyingFinished = true; + + break; + } + + m_buyPending = false; + m_nextBuyTime = GetWorldTime () + g_randGen.Float (0.3, 0.8); + + // if bot buying is off then no need to buy + if (!yb_botbuy.GetBool ()) + m_buyState = 6; + + // if fun-mode no need to buy + if (yb_jasonmode.GetBool ()) + { + m_buyState = 6; + SelectWeaponByName ("weapon_knife"); + } + + // prevent vip from buying + if (g_mapType & MAP_AS) + { + if (*(INFOKEY_VALUE (GET_INFOKEYBUFFER (GetEntity ()), "model")) == 'v') + { + m_isVIP = true; + m_buyState = 6; + m_pathType = 0; + } + } + + // prevent terrorists from buying on es maps + if ((g_mapType & MAP_ES) && GetTeam (GetEntity ()) == TEAM_TF) + m_buyState = 6; + + // prevent teams from buying on fun maps + if (g_mapType & (MAP_KA | MAP_FY)) + { + m_buyState = 6; + + if (g_mapType & MAP_KA) + yb_jasonmode.SetInt (1); + } + + if (m_buyState > 5) + { + m_buyingFinished = true; + return; + } + + PushMessageQueue (GSM_IDLE); + PerformWeaponPurchase (); + + break; + + case GSM_RADIO: // general radio message issued + // if last bot radio command (global) happened just a second ago, delay response + if (g_lastRadioTime[team] + 1.0 < GetWorldTime ()) + { + // if same message like previous just do a yes/no + if (m_radioSelect != Radio_Affirmative && m_radioSelect != Radio_Negative) + { + if (m_radioSelect == g_lastRadio[team] && g_lastRadioTime[team] + 1.5 > GetWorldTime ()) + m_radioSelect = -1; + else + { + if (m_radioSelect != Radio_ReportingIn) + g_lastRadio[team] = m_radioSelect; + else + g_lastRadio[team] = -1; + + for (int i = 0; i < GetMaxClients (); i++) + { + Bot *bot = g_botManager->GetBot (i); + + if (bot != NULL) + { + if (pev != bot->pev && GetTeam (bot->GetEntity ()) == team) + { + bot->m_radioOrder = m_radioSelect; + bot->m_radioEntity = GetEntity (); + } + } + } + } + } + + if (m_radioSelect == Radio_ReportingIn) + { + switch (GetTaskId ()) + { + case TASK_NORMAL: + if (GetTask ()->data != -1 && g_randGen.Long (0, 100) < 70) + { + Path *path = g_waypoint->GetPath (GetTask ()->data); + + if (path->flags & FLAG_GOAL) + { + if ((g_mapType & MAP_DE) && GetTeam (GetEntity ()) == TEAM_TF && (pev->weapons & (1 << WEAPON_C4))) + InstantChatterMessage (Chatter_GoingToPlantBomb); + else + InstantChatterMessage (Chatter_Nothing); + } + else if (path->flags & FLAG_RESCUE) + InstantChatterMessage (Chatter_RescuingHostages); + else if ((path->flags & FLAG_CAMP) && g_randGen.Long (0, 100) > 15) + InstantChatterMessage (Chatter_GoingToCamp); + else + InstantChatterMessage (Chatter_HearSomething); + } + else + InstantChatterMessage (Chatter_ReportingIn); + break; + + case TASK_MOVETOPOSITION: + InstantChatterMessage (Chatter_GoingToCamp); + break; + + case TASK_CAMP: + if (g_randGen.Long (0, 100) < 40) + { + + if (g_bombPlanted && GetTeam (GetEntity ()) == TEAM_TF) + InstantChatterMessage (Chatter_GuardDroppedC4); + else if (m_inVIPZone && GetTeam (GetEntity ()) == TEAM_TF) + InstantChatterMessage (Chatter_GuardingVipSafety); + else + InstantChatterMessage (Chatter_Camp); + } + break; + + case TASK_PLANTBOMB: + InstantChatterMessage (Chatter_PlantingC4); + break; + + case TASK_DEFUSEBOMB: + InstantChatterMessage (Chatter_DefusingC4); + break; + + case TASK_ATTACK: + InstantChatterMessage (Chatter_InCombat); + break; + + case TASK_HIDE: + case TASK_SEEKCOVER: + InstantChatterMessage (Chatter_SeeksEnemy); + break; + + default: + InstantChatterMessage (Chatter_Nothing); + break; + } + } + + if (m_radioSelect != Radio_ReportingIn && g_radioInsteadVoice || yb_communication_type.GetInt () != 2 || g_chatterFactory[m_radioSelect].IsEmpty () || g_gameVersion == CSV_OLD) + { + if (m_radioSelect < Radio_GoGoGo) + FakeClientCommand (GetEntity (), "radio1"); + else if (m_radioSelect < Radio_Affirmative) + { + m_radioSelect -= Radio_GoGoGo - 1; + FakeClientCommand (GetEntity (), "radio2"); + } + else + { + m_radioSelect -= Radio_Affirmative - 1; + FakeClientCommand (GetEntity (), "radio3"); + } + + // select correct menu item for this radio message + FakeClientCommand (GetEntity (), "menuselect %d", m_radioSelect); + } + else if (m_radioSelect != -1 && m_radioSelect != Radio_ReportingIn) + InstantChatterMessage (m_radioSelect); + + g_radioInsteadVoice = false; // reset radio to voice + g_lastRadioTime[team] = GetWorldTime (); // store last radio usage + } + else + PushMessageQueue (GSM_RADIO); + break; + + // team independent saytext + case GSM_SAY: + SayText (m_tempStrings); + break; + + // team dependent saytext + case GSM_SAY_TEAM: + TeamSayText (m_tempStrings); + break; + + case GSM_IDLE: + default: + return; + } +} + +bool Bot::IsRestricted (int weaponIndex) +{ + // this function checks for weapon restrictions. + + if (IsNullString (yb_restricted_weapons.GetString ())) + return false; // no banned weapons + + Array bannedWeapons = String (yb_restricted_weapons.GetString ()).Split (';'); + + IterateArray (bannedWeapons, i) + { + const char *banned = STRING (GetWeaponReturn (true, NULL, weaponIndex)); + + // check is this weapon is banned + if (strncmp (bannedWeapons[i].GetBuffer (), banned, bannedWeapons[i].GetLength ()) == 0) + return true; + } + return IsRestrictedAMX (weaponIndex); +} + +bool Bot::IsRestrictedAMX (int weaponIndex) +{ + // this function checks restriction set by AMX Mod, this function code is courtesy of KWo. + + const char *restrictedWeapons = CVAR_GET_STRING ("amx_restrweapons"); + const char *restrictedEquipment = CVAR_GET_STRING ("amx_restrequipammo"); + + // check for weapon restrictions + if ((1 << weaponIndex) & (WEAPON_PRIMARY | WEAPON_SECONDARY | WEAPON_SHIELD)) + { + if (IsNullString (restrictedWeapons)) + return false; + + int indices[] = {4, 25, 20, -1, 8, -1, 12, 19, -1, 5, 6, 13, 23, 17, 18, 1, 2, 21, 9, 24, 7, 16, 10, 22, -1, 3, 15, 14, 0, 11}; + + // find the weapon index + int index = indices[weaponIndex - 1]; + + // validate index range + if (index < 0 || index >= static_cast (strlen (restrictedWeapons))) + return false; + + return restrictedWeapons[index] != '0'; + } + else // check for equipment restrictions + { + if (IsNullString (restrictedEquipment)) + return false; + + int indices[] = {-1, -1, -1, 3, -1, -1, -1, -1, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, -1, -1, -1, -1, -1, 0, 1, 5}; + + // find the weapon index + int index = indices[weaponIndex - 1]; + + // validate index range + if (index < 0 || index >= static_cast (strlen (restrictedEquipment))) + return false; + + return restrictedEquipment[index] != '0'; + } +} + +bool Bot::IsMorePowerfulWeaponCanBeBought (void) +{ + // this function determines currently owned primary weapon, and checks if bot has + // enough money to buy more powerful weapon. + + // if bot is not rich enough or non-standard weapon mode enabled return false + if (g_weaponSelect[25].teamStandard != 1 || m_moneyAmount < 4000 || IsNullString (yb_restricted_weapons.GetString ())) + return false; + + // also check if bot has really bad weapon, maybe it's time to change it + if (UsesBadPrimary ()) + return true; + + Array bannedWeapons = String (yb_restricted_weapons.GetString ()).Split (';'); + + // check if its banned + IterateArray (bannedWeapons, i) + { + if (m_currentWeapon == GetWeaponReturn (false, bannedWeapons[i].ToString ())) + return true; + } + + if (m_currentWeapon == WEAPON_SCOUT && m_moneyAmount > 5000) + return true; + else if (m_currentWeapon == WEAPON_MP5 && m_moneyAmount > 6000) + return true; + else if ((m_currentWeapon == WEAPON_M3 || m_currentWeapon == WEAPON_XM1014) && m_moneyAmount > 4000) + return true; + + return false; +} + +void Bot::PerformWeaponPurchase (void) +{ + // this function does all the work in selecting correct buy menus for most weapons/items + + WeaponSelect *selectedWeapon = NULL; + m_nextBuyTime = GetWorldTime (); + + int count = 0, foundWeapons = 0, economicsPrice = 0; + int choices[NUM_WEAPONS]; + + // select the priority tab for this personality + int *ptr = g_weaponPrefs[m_personality] + NUM_WEAPONS; + int team = GetTeam (GetEntity ()); + + bool isPistolMode = (g_weaponSelect[25].teamStandard == -1) && (g_weaponSelect[3].teamStandard == 2); + + switch (m_buyState) + { + case 0: // if no primary weapon and bot has some money, buy a primary weapon + if ((!HasShield () && !HasPrimaryWeapon ()) && (g_botManager->EconomicsValid (team) || IsMorePowerfulWeaponCanBeBought ())) + { + do + { + bool ignoreWeapon = false; + + ptr--; + + InternalAssert (*ptr > -1); + InternalAssert (*ptr < NUM_WEAPONS); + + selectedWeapon = &g_weaponSelect[*ptr]; + count++; + + if (selectedWeapon->buyGroup == 1) + continue; + + // weapon available for every team? + if ((g_mapType & MAP_AS) && selectedWeapon->teamAS != 2 && selectedWeapon->teamAS != team) + continue; + + // ignore weapon if this weapon not supported by currently running cs version... + if (g_gameVersion == CSV_OLD && selectedWeapon->buySelect == -1) + continue; + + // ignore weapon if this weapon is not targeted to out team.... + if (selectedWeapon->teamStandard != 2 && selectedWeapon->teamStandard != team) + continue; + + // ignore weapon if this weapon is restricted + if (IsRestricted (selectedWeapon->id)) + continue; + + // filter out weapons with bot economics (thanks for idea to nicebot project) + switch (m_personality) + { + case PERSONALITY_RUSHER: + economicsPrice = g_botBuyEconomyTable[8]; + break; + + case PERSONALITY_CAREFUL: + economicsPrice = g_botBuyEconomyTable[7]; + break; + + case PERSONALITY_NORMAL: + economicsPrice = g_botBuyEconomyTable[9]; + break; + } + + if (team == TEAM_CF) + { + switch (selectedWeapon->id) + { + case WEAPON_TMP: + case WEAPON_UMP45: + case WEAPON_P90: + case WEAPON_MP5: + if (m_moneyAmount > g_botBuyEconomyTable[1] + economicsPrice) + ignoreWeapon = true; + break; + } + + if (selectedWeapon->id == WEAPON_SHIELD && m_moneyAmount > g_botBuyEconomyTable[10]) + ignoreWeapon = true; + } + else if (team == TEAM_TF) + { + switch (selectedWeapon->id) + { + case WEAPON_UMP45: + case WEAPON_MAC10: + case WEAPON_P90: + case WEAPON_MP5: + case WEAPON_SCOUT: + if (m_moneyAmount > g_botBuyEconomyTable[2] + economicsPrice) + ignoreWeapon = true; + break; + } + } + + switch (selectedWeapon->id) + { + case WEAPON_XM1014: + case WEAPON_M3: + if (m_moneyAmount < g_botBuyEconomyTable[3] || m_moneyAmount > g_botBuyEconomyTable[4]) + ignoreWeapon = true; + break; + } + + switch (selectedWeapon->id) + { + case WEAPON_SG550: + case WEAPON_G3SG1: + case WEAPON_AWP: + case WEAPON_M249: + if (m_moneyAmount < g_botBuyEconomyTable[5] || m_moneyAmount > g_botBuyEconomyTable[6]) + ignoreWeapon = true; + break; + } + + if (ignoreWeapon && g_weaponSelect[25].teamStandard == 1 && yb_economics_rounds.GetBool ()) + continue; + + int moneySave = g_randGen.Long (900, 1100); + + if (g_botManager->GetLastWinner () == team) + moneySave = 0; + + if (selectedWeapon->price <= (m_moneyAmount - moneySave)) + choices[foundWeapons++] = *ptr; + + } while (count < NUM_WEAPONS && foundWeapons < 4); + + // found a desired weapon? + if (foundWeapons > 0) + { + int chosenWeapon; + + // choose randomly from the best ones... + if (foundWeapons > 1) + chosenWeapon = choices[g_randGen.Long (0, foundWeapons - 1)]; + else + chosenWeapon = choices[foundWeapons - 1]; + + selectedWeapon = &g_weaponSelect[chosenWeapon]; + } + else + selectedWeapon = NULL; + + if (selectedWeapon != NULL) + { + FakeClientCommand (GetEntity (), "buy;menuselect %d", selectedWeapon->buyGroup); + + if (g_gameVersion == CSV_OLD) + FakeClientCommand(GetEntity (), "menuselect %d", selectedWeapon->buySelect); + else // SteamCS buy menu is different from the old one + { + if (GetTeam (GetEntity ()) == TEAM_TF) + FakeClientCommand(GetEntity (), "menuselect %d", selectedWeapon->newBuySelectT); + else + FakeClientCommand (GetEntity (), "menuselect %d", selectedWeapon->newBuySelectCT); + } + } + } + else if (HasPrimaryWeapon () && !HasShield ()) + { + m_reloadState = RELOAD_PRIMARY; + break; + } + else if ((HasSecondaryWeapon () && !HasShield ()) || HasShield()) + { + m_reloadState = RELOAD_SECONDARY; + break; + } + + case 1: // if armor is damaged and bot has some money, buy some armor + if (pev->armorvalue < g_randGen.Long (50, 80) && (isPistolMode || (g_botManager->EconomicsValid (team) && HasPrimaryWeapon ()))) + { + // if bot is rich, buy kevlar + helmet, else buy a single kevlar + if (m_moneyAmount > 1500 && !IsRestricted (WEAPON_ARMORHELM)) + FakeClientCommand (GetEntity (), "buyequip;menuselect 2"); + else if (!IsRestricted (WEAPON_ARMOR)) + FakeClientCommand (GetEntity (), "buyequip;menuselect 1"); + } + break; + + case 2: // if bot has still some money, buy a better secondary weapon + if (isPistolMode || (HasPrimaryWeapon () && (pev->weapons & ((1 << WEAPON_USP) | (1 << WEAPON_GLOCK))) && m_moneyAmount > g_randGen.Long (7500, 9000))) + { + do + { + ptr--; + + InternalAssert (*ptr > -1); + InternalAssert (*ptr < NUM_WEAPONS); + + selectedWeapon = &g_weaponSelect[*ptr]; + count++; + + if (selectedWeapon->buyGroup != 1) + continue; + + // ignore weapon if this weapon is restricted + if (IsRestricted (selectedWeapon->id)) + continue; + + // weapon available for every team? + if ((g_mapType & MAP_AS) && selectedWeapon->teamAS != 2 && selectedWeapon->teamAS != team) + continue; + + if (g_gameVersion == CSV_OLD && selectedWeapon->buySelect == -1) + continue; + + if (selectedWeapon->teamStandard != 2 && selectedWeapon->teamStandard != team) + continue; + + if (selectedWeapon->price <= (m_moneyAmount - g_randGen.Long (100, 200))) + choices[foundWeapons++] = *ptr; + + } while (count < NUM_WEAPONS && foundWeapons < 4); + + // found a desired weapon? + if (foundWeapons > 0) + { + int chosenWeapon; + + // choose randomly from the best ones... + if (foundWeapons > 1) + chosenWeapon = choices[g_randGen.Long (0, foundWeapons - 1)]; + else + chosenWeapon = choices[foundWeapons - 1]; + + selectedWeapon = &g_weaponSelect[chosenWeapon]; + } + else + selectedWeapon = NULL; + + if (selectedWeapon != NULL) + { + FakeClientCommand (GetEntity (), "buy;menuselect %d", selectedWeapon->buyGroup); + + if (g_gameVersion == CSV_OLD) + FakeClientCommand(GetEntity (), "menuselect %d", selectedWeapon->buySelect); + + else // steam cs buy menu is different from old one + { + if (GetTeam(GetEntity()) == TEAM_TF) + FakeClientCommand(GetEntity(), "menuselect %d", selectedWeapon->newBuySelectT); + else + FakeClientCommand(GetEntity(), "menuselect %d", selectedWeapon->newBuySelectCT); + } + } + } + break; + + case 3: // if bot has still some money, choose if bot should buy a grenade or not + if (g_randGen.Long (1, 100) < g_grenadeBuyPrecent[0] && m_moneyAmount >= 400 && !IsRestricted (WEAPON_EXPLOSIVE)) + { + // buy a he grenade + FakeClientCommand (GetEntity (), "buyequip"); + FakeClientCommand (GetEntity (), "menuselect 4"); + } + + if (g_randGen.Long (1, 100) < g_grenadeBuyPrecent[1] && m_moneyAmount >= 300 && g_botManager->EconomicsValid (team) && !IsRestricted (WEAPON_FLASHBANG)) + { + // buy a concussion grenade, i.e., 'flashbang' + FakeClientCommand (GetEntity (), "buyequip"); + FakeClientCommand (GetEntity (), "menuselect 3"); + } + + if (g_randGen.Long (1, 100) < g_grenadeBuyPrecent[2] && m_moneyAmount >= 400 && g_botManager->EconomicsValid (team) && !IsRestricted (WEAPON_SMOKE)) + { + // buy a smoke grenade + FakeClientCommand (GetEntity (), "buyequip"); + FakeClientCommand (GetEntity (), "menuselect 5"); + } + break; + + case 4: // if bot is CT and we're on a bomb map, randomly buy the defuse kit + if ((g_mapType & MAP_DE) && GetTeam (GetEntity ()) == TEAM_CF && g_randGen.Long (1, 100) < 80 && m_moneyAmount > 200 && !IsRestricted (WEAPON_DEFUSER)) + { + if (g_gameVersion == CSV_OLD) + FakeClientCommand (GetEntity (), "buyequip;menuselect 6"); + else + FakeClientCommand (GetEntity (), "defuser"); // use alias in SteamCS + } + break; + + case 5: // buy enough primary & secondary ammo (do not check for money here) + for (int i = 0; i <= 5; i++) + FakeClientCommand (GetEntity (), "buyammo%d", g_randGen.Long (1, 2)); // simulate human + + // buy enough secondary ammo + if (HasPrimaryWeapon ()) + FakeClientCommand (GetEntity (), "buy;menuselect 7"); + + // buy enough primary ammo + FakeClientCommand (GetEntity (), "buy;menuselect 6"); + + // try to reload secondary weapon + if (m_reloadState != RELOAD_PRIMARY) + m_reloadState = RELOAD_SECONDARY; + + break; + } + + m_buyState++; + PushMessageQueue (GSM_BUY_STUFF); +} + +TaskItem *ClampDesire (TaskItem *first, float min, float max) +{ + // this function iven some values min and max, clamp the inputs to be inside the [min, max] range. + + if (first->desire < min) + first->desire = min; + else if (first->desire > max) + first->desire = max; + + return first; +} + +TaskItem *MaxDesire (TaskItem *first, TaskItem *second) +{ + // this function returns the behavior having the higher activation level. + + if (first->desire > second->desire) + return first; + + return second; +} + +TaskItem *SubsumeDesire (TaskItem *first, TaskItem *second) +{ + // this function returns the first behavior if its activation level is anything higher than zero. + + if (first->desire > 0) + return first; + + return second; +} + +TaskItem *ThresholdDesire (TaskItem *first, float threshold, float desire) +{ + // this function returns the input behavior if it's activation level exceeds the threshold, or some default + // behavior otherwise. + + if (first->desire < threshold) + first->desire = desire; + + return first; +} + +float HysteresisDesire (float cur, float min, float max, float old) +{ + // this function clamp the inputs to be the last known value outside the [min, max] range. + + if (cur <= min || cur >= max) + old = cur; + + return old; +} + +void Bot::SetConditions (void) +{ + // this function carried out each frame. does all of the sensing, calculates emotions and finally sets the desired + // action after applying all of the Filters + + int team = GetTeam (GetEntity ()); + m_aimFlags = 0; + + // slowly increase/decrease dynamic emotions back to their base level + if (m_nextEmotionUpdate < GetWorldTime ()) + { + if (yb_hardcore_mode.GetBool ()) + { + m_agressionLevel *= 3; + m_fearLevel /= 2; + } + + if (m_agressionLevel > m_baseAgressionLevel) + m_agressionLevel -= 0.10; + else + m_agressionLevel += 0.10; + + if (m_fearLevel > m_baseFearLevel) + m_fearLevel -= 0.05; + else + m_fearLevel += 0.05; + + if (m_agressionLevel < 0.0) + m_agressionLevel = 0.0; + + if (m_fearLevel < 0.0) + m_fearLevel = 0.0; + + m_nextEmotionUpdate = GetWorldTime () + 1.0; + } + + // does bot see an enemy? + if (LookupEnemy ()) + m_states |= STATE_SEEING_ENEMY; + else + { + m_states &= ~STATE_SEEING_ENEMY; + m_enemy = NULL; + } + + // did bot just kill an enemy? + if (!FNullEnt (m_lastVictim)) + { + if (GetTeam (m_lastVictim) != team) + { + // add some aggression because we just killed somebody + m_agressionLevel += 0.1; + + if (m_agressionLevel > 1.0) + m_agressionLevel = 1.0; + + if (g_randGen.Long (1, 100) < 10) + ChatMessage (CHAT_KILLING); + + if (g_randGen.Long (1, 100) < 10) + RadioMessage (Radio_EnemyDown); + else + { + if ((m_lastVictim->v.weapons & (1 << WEAPON_AWP)) || (m_lastVictim->v.weapons & (1 << WEAPON_SCOUT)) || (m_lastVictim->v.weapons & (1 << WEAPON_G3SG1)) || (m_lastVictim->v.weapons & (1 << WEAPON_SG550))) + ChatterMessage (Chatter_SniperKilled); + else + { + switch (GetNearbyEnemiesNearPosition (pev->origin, 9999)) + { + case 0: + if (g_randGen.Long (0, 100) < 50) + ChatterMessage (Chatter_NoEnemiesLeft); + else + ChatterMessage (Chatter_EnemyDown); + break; + + case 1: + ChatterMessage (Chatter_OneEnemyLeft); + break; + + case 2: + ChatterMessage (Chatter_TwoEnemiesLeft); + break; + + case 3: + ChatterMessage (Chatter_ThreeEnemiesLeft); + break; + + default: + ChatterMessage (Chatter_EnemyDown); + } + } + } + + // if no more enemies found AND bomb planted, switch to knife to get to bombplace faster + if (GetTeam (GetEntity ()) == TEAM_CF && m_currentWeapon != WEAPON_KNIFE && GetNearbyEnemiesNearPosition (pev->origin, 9999) == 0 && g_bombPlanted) + { + SelectWeaponByName ("weapon_knife"); + m_plantedBombWptIndex = FindPlantedBomb (); + + if (IsWaypointUsed (m_plantedBombWptIndex)) + InstantChatterMessage (Chatter_BombSiteSecured); + + } + } + else + { + ChatMessage (CHAT_TEAMKILL, true); + ChatterMessage (Chatter_TeamKill); + } + m_lastVictim = NULL; + } + + // check if our current enemy is still valid + if (!FNullEnt (m_lastEnemy)) + { + if (!IsAlive (m_lastEnemy) && m_shootAtDeadTime < GetWorldTime ()) + { + m_lastEnemyOrigin = nullvec; + m_lastEnemy = NULL; + } + } + else + { + m_lastEnemyOrigin = nullvec; + m_lastEnemy = NULL; + } + + // don't listen if seeing enemy, just checked for sounds or being blinded (because its inhuman) + if (!yb_ignore_enemies.GetBool () && m_soundUpdateTime <= GetWorldTime () && m_blindTime < GetWorldTime ()) + { + ReactOnSound (); + m_soundUpdateTime = GetWorldTime () + yb_timersound.GetFloat (); + } + else if (m_heardSoundTime < GetWorldTime ()) + m_states &= ~STATE_HEARING_ENEMY; + + if (FNullEnt (m_enemy) && !FNullEnt (m_lastEnemy) && m_lastEnemyOrigin != nullvec) + { + TraceResult tr; + TraceLine (EyePosition (), m_lastEnemyOrigin, true, GetEntity (), &tr); + + if ((pev->origin - m_lastEnemyOrigin).GetLength () < 1600.0 && (tr.flFraction >= 0.2 || tr.pHit != g_worldEdict)) + { + m_aimFlags |= AIM_PREDICT_ENEMY; + + if (EntityIsVisible (m_lastEnemyOrigin)) + m_aimFlags |= AIM_LAST_ENEMY; + } + } + + // check if throwing a grenade is a good thing to do... + if (!yb_ignore_enemies.GetBool () && !yb_jasonmode.GetBool () && m_grenadeCheckTime < GetWorldTime () && !m_isUsingGrenade && GetTaskId () != TASK_PLANTBOMB && GetTaskId () != TASK_DEFUSEBOMB && !m_isReloading && IsAlive (m_lastEnemy)) + { + // check again in some seconds + m_grenadeCheckTime = GetWorldTime () + yb_timergrenade.GetFloat (); + + // check if we have grenades to throw + int grenadeToThrow = CheckGrenades (); + + // if we don't have grenades no need to check it this round again + if (grenadeToThrow == -1) + { + m_grenadeCheckTime = GetWorldTime () + 15.0; // changed since, conzero can drop grens from dead players + m_states &= ~(STATE_THROW_HE | STATE_THROW_FB | STATE_THROW_SG); + } + else + { + // care about different types of grenades + if ((grenadeToThrow == WEAPON_EXPLOSIVE || grenadeToThrow == WEAPON_SMOKE) && g_randGen.Long (0, 100) < 45 && !(m_states & (STATE_SEEING_ENEMY | STATE_THROW_HE | STATE_THROW_FB | STATE_THROW_SG))) + { + float distance = (m_lastEnemy->v.origin - pev->origin).GetLength (); + + // is enemy to high to throw + if ((m_lastEnemy->v.origin.z > (pev->origin.z + 650.0)) || !(m_lastEnemy->v.flags & FL_ONGROUND | FL_DUCKING)) + distance = FLT_MAX; // just some crazy value + + // enemy is within a good throwing distance ? + if (distance > (grenadeToThrow == WEAPON_SMOKE ? 400 : 600) && distance <= 1000) + { + // care about different types of grenades + if (grenadeToThrow == WEAPON_EXPLOSIVE) + { + bool allowThrowing = true; + + // check for teammates + if (GetNearbyFriendsNearPosition (m_lastEnemy->v.origin, 256) > 0) + allowThrowing = false; + + if (allowThrowing && m_seeEnemyTime + 2.0 < GetWorldTime ()) + { + Vector enemyPredict = ((m_lastEnemy->v.velocity * 0.5).SkipZ () + m_lastEnemy->v.origin); + int searchTab[4], count = 4; + + float searchRadius = m_lastEnemy->v.velocity.GetLength2D (); + + // check the search radius + if (searchRadius < 128.0) + searchRadius = 128.0; + + // search waypoints + g_waypoint->FindInRadius (enemyPredict, searchRadius, searchTab, &count); + + while (count > 0) + { + allowThrowing = true; + + // check the throwing + m_throw = g_waypoint->GetPath (searchTab[count--])->origin; + Vector src = CheckThrow (EyePosition (), m_throw); + + if (src.GetLengthSquared () < 100.0) + src = CheckToss (EyePosition (), m_throw); + + if (src == nullvec) + allowThrowing = false; + else + break; + } + } + + // start explosive grenade throwing? + if (allowThrowing) + m_states |= STATE_THROW_HE; + else + m_states &= ~STATE_THROW_HE; + } + else if (grenadeToThrow == WEAPON_SMOKE) + { + // start smoke grenade throwing? + if ((m_states & STATE_SEEING_ENEMY) && GetShootingConeDeviation (m_enemy, &pev->origin) >= 0.70 && m_seeEnemyTime + 2.0 < GetWorldTime ()) + m_states &= ~STATE_THROW_SG; + else + m_states |= STATE_THROW_SG; + } + } + } + else if (IsAlive (m_lastEnemy) && grenadeToThrow == WEAPON_FLASHBANG && (m_lastEnemy->v.origin - pev->origin).GetLength () < 800 && !(m_aimFlags & AIM_ENEMY) && g_randGen.Long (0, 100) < 50) + { + bool allowThrowing = true; + Array inRadius; + + g_waypoint->FindInRadius (inRadius, 256, m_lastEnemy->v.origin + (m_lastEnemy->v.velocity * 0.5).SkipZ ()); + + IterateArray (inRadius, i) + { + Path *path = g_waypoint->GetPath (i); + + if (GetNearbyFriendsNearPosition (path->origin, 256) != 0 || !(yb_hardcore_mode.GetBool () && GetNearbyFriendsNearPosition (path->origin, 195) != 0)) + continue; + + m_throw = path->origin; + Vector src = CheckThrow (EyePosition (), m_throw); + + if (src.GetLengthSquared () < 100) + src = CheckToss (EyePosition (), m_throw); + + if (src == nullvec) + continue; + + allowThrowing = true; + break; + } + + // start concussion grenade throwing? + if (allowThrowing && m_seeEnemyTime + 2.0 < GetWorldTime ()) + m_states |= STATE_THROW_FB; + else + m_states &= ~STATE_THROW_FB; + } + + if (m_states & STATE_THROW_HE) + StartTask (TASK_THROWHEGRENADE, TASKPRI_THROWGRENADE, -1, GetWorldTime () + 3.0, false); + else if (m_states & STATE_THROW_FB) + StartTask (TASK_THROWFLASHBANG, TASKPRI_THROWGRENADE, -1, GetWorldTime () + 3.0, false); + else if (m_states & STATE_THROW_SG) + StartTask (TASK_THROWSMOKE, TASKPRI_THROWGRENADE, -1, GetWorldTime () + 3.0, false); + + // delay next grenade throw + if (m_states & (STATE_THROW_HE | STATE_THROW_FB | STATE_THROW_SG)) + m_grenadeCheckTime = GetWorldTime () + MAX_GRENADE_TIMER; + } + } + else + m_states &= ~(STATE_THROW_HE | STATE_THROW_FB | STATE_THROW_SG); + + // check if there are items needing to be used/collected + if (m_itemCheckTime < GetWorldTime () || !FNullEnt (m_pickupItem)) + { + m_itemCheckTime = GetWorldTime () + yb_timerpickup.GetFloat (); + FindItem (); + } + + float tempFear = m_fearLevel; + float tempAgression = m_agressionLevel; + + // decrease fear if teammates near + int friendlyNum = 0; + + if (m_lastEnemyOrigin != nullvec) + friendlyNum = GetNearbyFriendsNearPosition (pev->origin, 500) - GetNearbyEnemiesNearPosition (m_lastEnemyOrigin, 500); + + if (friendlyNum > 0) + tempFear = tempFear * 0.5; + + // increase/decrease fear/aggression if bot uses a sniping weapon to be more careful + if (UsesSniper ()) + { + tempFear = tempFear * 1.5; + tempAgression = tempAgression * 0.5; + } + + // initialize & calculate the desire for all actions based on distances, emotions and other stuff + GetTask (); + + // bot found some item to use? + if (!FNullEnt (m_pickupItem) && GetTaskId () != TASK_ESCAPEFROMBOMB) + { + m_states |= STATE_PICKUP_ITEM; + + if (m_pickupType == PICKUP_BUTTON) + g_taskFilters[TASK_PICKUPITEM].desire = 50; // always pickup button + else + { + float distance = (500.0 - (GetEntityOrigin (m_pickupItem) - pev->origin).GetLength ()) * 0.2; + + if (distance > 50) + distance = 50; + + g_taskFilters[TASK_PICKUPITEM].desire = distance; + } + } + else + { + m_states &= ~STATE_PICKUP_ITEM; + g_taskFilters[TASK_PICKUPITEM].desire = 0.0; + } + + float desireLevel = 0.0; + + // calculate desire to attack + if ((m_states & STATE_SEEING_ENEMY) && ReactOnEnemy ()) + g_taskFilters[TASK_ATTACK].desire = TASKPRI_ATTACK; + else + g_taskFilters[TASK_ATTACK].desire = 0; + + // calculate desires to seek cover or hunt + if (IsValidPlayer (m_lastEnemy) && m_lastEnemyOrigin != nullvec && !((g_mapType & MAP_DE) && g_bombPlanted) && !(pev->weapons & (1 << WEAPON_C4)) && (m_loosedBombWptIndex == -1 && GetTeam (GetEntity ()) == TEAM_TF)) + { + float distance = (m_lastEnemyOrigin - pev->origin).GetLength (); + + // retreat level depends on bot health + float retreatLevel = (100.0 - (pev->health > 100.0 ? 100.0 : pev->health)) * tempFear; + float timeSeen = m_seeEnemyTime - GetWorldTime (); + float timeHeard = m_heardSoundTime - GetWorldTime (); + float ratio = 0.0; + + if (timeSeen > timeHeard) + { + timeSeen += 10.0; + ratio = timeSeen * 0.1; + } + else + { + timeHeard += 10.0; + ratio = timeHeard * 0.1; + } + + if (g_bombPlanted || m_isStuck) + ratio /= 3; // reduce the seek cover desire if bomb is planted + else if (m_isVIP || m_isReloading) + ratio *= 2; // triple the seek cover desire if bot is VIP or reloading + + if (distance > 500.0) + g_taskFilters[TASK_SEEKCOVER].desire = retreatLevel * ratio; + + // if half of the round is over, allow hunting + // FIXME: it probably should be also team/map dependant + if (FNullEnt (m_enemy) && (g_timeRoundMid < GetWorldTime ()) && !m_isUsingGrenade && m_currentWaypointIndex != g_waypoint->FindNearest (m_lastEnemyOrigin) && m_personality != PERSONALITY_CAREFUL) + { + desireLevel = 4096.0 - ((1.0 - tempAgression) * distance); + desireLevel = (100 * desireLevel) / 4096.0; + desireLevel -= retreatLevel; + + if (desireLevel > 89) + desireLevel = 89; + + g_taskFilters[TASK_HUNTENEMY].desire = desireLevel; + } + else + g_taskFilters[TASK_HUNTENEMY].desire = 0; + } + else + { + g_taskFilters[TASK_SEEKCOVER].desire = 0; + g_taskFilters[TASK_HUNTENEMY].desire = 0; + } + + // blinded behaviour + if (m_blindTime > GetWorldTime ()) + g_taskFilters[TASK_BLINDED].desire = TASKPRI_BLINDED; + else + g_taskFilters[TASK_BLINDED].desire = 0.0; + + // now we've initialized all the desires go through the hard work + // of filtering all actions against each other to pick the most + // rewarding one to the bot. + + // FIXME: instead of going through all of the actions it might be + // better to use some kind of decision tree to sort out impossible + // actions. + + // most of the values were found out by trial-and-error and a helper + // utility i wrote so there could still be some weird behaviors, it's + // hard to check them all out. + + m_oldCombatDesire = HysteresisDesire (g_taskFilters[TASK_ATTACK].desire, 40.0, 90.0, m_oldCombatDesire); + g_taskFilters[TASK_ATTACK].desire = m_oldCombatDesire; + + TaskItem *taskOffensive = &g_taskFilters[TASK_ATTACK]; + TaskItem *taskPickup = &g_taskFilters[TASK_PICKUPITEM]; + + // calc survive (cover/hide) + TaskItem *taskSurvive = ThresholdDesire (&g_taskFilters[TASK_SEEKCOVER], 40.0, 0.0); + taskSurvive = SubsumeDesire (&g_taskFilters[TASK_HIDE], taskSurvive); + + TaskItem *def = ThresholdDesire (&g_taskFilters[TASK_HUNTENEMY], 60.0, 0.0); // don't allow hunting if desire's 60< + taskOffensive = SubsumeDesire (taskOffensive, taskPickup); // if offensive task, don't allow picking up stuff + + TaskItem *taskSub = MaxDesire (taskOffensive, def); // default normal & careful tasks against offensive actions + TaskItem *final = SubsumeDesire (&g_taskFilters[TASK_BLINDED], MaxDesire (taskSurvive, taskSub)); // reason about fleeing instead + + if (!m_tasks.IsEmpty ()) + final = MaxDesire (final, GetTask ()); + + StartTask (final->id, final->desire, final->data, final->time, final->resume); // push the final behaviour in our task stack to carry out +} + +void Bot::ResetTasks (void) +{ + // this function resets bot tasks stack, by removing all entries from the stack. + + m_tasks.RemoveAll (); +} + +void Bot::StartTask (TaskId_t id, float desire, int data, float time, bool resume) +{ + if (!m_tasks.IsEmpty ()) + { + TaskItem &item = m_tasks.Last (); + + if (item.id == id) + { + item.desire = desire; + return; + } + } + TaskItem item; + + item.id = id; + item.desire = desire; + item.data = data; + item.time = time; + item.resume = resume; + + m_tasks.Push (item); + + DeleteSearchNodes (); + m_lastCollTime = GetWorldTime () + 0.5; + + // leader bot? + if (m_isLeader && GetTaskId () == TASK_SEEKCOVER) + CommandTeam (); // reorganize team if fleeing + + if (GetTaskId () == TASK_CAMP) + SelectBestWeapon (); + + // this is best place to handle some voice commands report team some info + if (g_randGen.Long (0, 100) < 95) + { + switch (GetTaskId ()) + { + case TASK_BLINDED: + InstantChatterMessage (Chatter_GotBlinded); + break; + + case TASK_PLANTBOMB: + InstantChatterMessage (Chatter_PlantingC4); + break; + } + } + + if (g_randGen.Long (0, 100) < 80 && GetTaskId () == TASK_CAMP) + { + if ((g_mapType & MAP_DE) && g_bombPlanted) + ChatterMessage (Chatter_GuardDroppedC4); + else + ChatterMessage (Chatter_GoingToCamp); + } + + if (yb_debug_goal.GetInt () != -1) + m_chosenGoalIndex = yb_debug_goal.GetInt (); + else + m_chosenGoalIndex = GetTask ()->data; + + if (g_randGen.Long (0, 100) < 80 && GetTaskId () == TASK_CAMP && GetTeam (GetEntity ()) == TEAM_TF && m_inVIPZone) + ChatterMessage (Chatter_GoingToGuardVIPSafety); +} + +TaskItem *Bot::GetTask (void) +{ + if (m_tasks.IsEmpty ()) + { + m_tasks.RemoveAll (); + + TaskItem task; + + task.id = TASK_NORMAL; + task.desire = TASKPRI_NORMAL; + task.data = -1; + task.time = 0.0; + task.resume = true; + + m_tasks.Push (task); + } + return &m_tasks.Last (); +} + +void Bot::RemoveCertainTask (TaskId_t id) +{ + // this function removes one task from the bot task stack. + + if (m_tasks.IsEmpty () || (!m_tasks.IsEmpty () && GetTaskId () == TASK_NORMAL)) + return; // since normal task can be only once on the stack, don't remove it... + + if (GetTaskId () == id) + { + DeleteSearchNodes (); + m_tasks.Pop (); + + return; + } + + IterateArray (m_tasks, i) + { + if (m_tasks[i].id == id) + m_tasks.RemoveAt (i); + } + DeleteSearchNodes (); +} + +void Bot::TaskComplete (void) +{ + // this function called whenever a task is completed. + + if (m_tasks.IsEmpty ()) + return; + + do + { + m_tasks.Pop (); + + } while (!m_tasks.IsEmpty () && !m_tasks.Last ().resume); + + DeleteSearchNodes (); +} + +bool Bot::EnemyIsThreat (void) +{ + if (FNullEnt (m_enemy) || GetTaskId () == TASK_SEEKCOVER) + return false; + + float distance = (m_enemy->v.origin - pev->origin).GetLength (); + + // if bot is camping, he should be firing anyway and not leaving his position + if (GetTaskId () == TASK_CAMP) + return false; + + // if enemy is near or facing us directly + if (distance < 256.0f || IsInViewCone (m_enemy->v.origin)) + return true; + + return false; +} + +bool Bot::ReactOnEnemy (void) +{ + // the purpose of this function is check if task has to be interrupted because an enemy is near (run attack actions then) + + if (!EnemyIsThreat ()) + return false; + + if (m_enemyReachableTimer < GetWorldTime ()) + { + int i = g_waypoint->FindNearest (pev->origin); + int enemyIndex = g_waypoint->FindNearest (m_enemy->v.origin); + + float lineDist = (m_enemy->v.origin - pev->origin).GetLength (); + float pathDist = g_waypoint->GetPathDistance (i, enemyIndex); + + if (pathDist - lineDist > 112.0) + m_isEnemyReachable = false; + else + m_isEnemyReachable = true; + + m_enemyReachableTimer = GetWorldTime () + 1.0; + } + + if (m_isEnemyReachable) + { + m_navTimeset = GetWorldTime (); // override existing movement by attack movement + return true; + } + return false; +} + +bool Bot::IsLastEnemyViewable (void) +{ + // this function checks if line of sight established to last enemy + + if (FNullEnt (m_lastEnemy) || m_lastEnemyOrigin == nullvec) + { + m_lastEnemy = NULL; + m_lastEnemyOrigin = nullvec; + + return false; + } + + // trace a line from bot's eyes to destination... + TraceResult tr; + TraceLine (EyePosition (), m_lastEnemyOrigin, true, GetEntity (), &tr); + + // check if line of sight to object is not blocked (i.e. visible) + return tr.flFraction >= 1.0; +} + +bool Bot::LastEnemyShootable (void) +{ + // don't allow shooting through walls when pausing or camping + if (!(m_aimFlags & AIM_LAST_ENEMY) || FNullEnt (m_lastEnemy) || GetTaskId () == TASK_PAUSE || GetTaskId () == TASK_CAMP) + return false; + + return GetShootingConeDeviation (GetEntity (), &m_lastEnemyOrigin) >= 0.90 && IsShootableThruObstacle (m_lastEnemy->v.origin); +} + +void Bot::CheckRadioCommands (void) +{ + // this function handling radio and reactings to it + + float distance = (m_radioEntity->v.origin - pev->origin).GetLength (); + + // don't allow bot listen you if bot is busy + if ((GetTaskId () == TASK_DEFUSEBOMB || GetTaskId () == TASK_PLANTBOMB || HasHostage ()) && m_radioOrder != Radio_ReportTeam) + { + m_radioOrder = 0; + return; + } + + switch (m_radioOrder) + { + case Radio_CoverMe: + case Radio_FollowMe: + case Radio_StickTogether: + case Chatter_GoingToPlantBomb: + case Chatter_CoverMe: + // check if line of sight to object is not blocked (i.e. visible) + if ((EntityIsVisible (m_radioEntity->v.origin)) || (m_radioOrder == Radio_StickTogether)) + { + if (FNullEnt (m_targetEntity) && FNullEnt (m_enemy) && g_randGen.Long (0, 100) < (m_personality == PERSONALITY_CAREFUL ? 80 : 50)) + { + int numFollowers = 0; + + // Check if no more followers are allowed + for (int i = 0; i < GetMaxClients (); i++) + { + Bot *bot = g_botManager->GetBot (i); + + if (bot != NULL) + { + if (IsAlive (bot->GetEntity ())) + { + if (bot->m_targetEntity == m_radioEntity) + numFollowers++; + } + } + } + + int allowedFollowers = yb_user_max_followers.GetInt (); + + if (m_radioEntity->v.weapons & (1 << WEAPON_C4)) + allowedFollowers = 1; + + if (numFollowers < allowedFollowers) + { + RadioMessage (Radio_Affirmative); + m_targetEntity = m_radioEntity; + + // don't pause/camp/follow anymore + TaskId_t taskID = GetTaskId (); + + if (taskID == TASK_PAUSE || taskID == TASK_CAMP) + GetTask ()->time = GetWorldTime (); + + StartTask (TASK_FOLLOWUSER, TASKPRI_FOLLOWUSER, -1, 0.0, true); + } + else if (numFollowers > allowedFollowers) + { + for (int i = 0; (i < GetMaxClients () && numFollowers > allowedFollowers) ; i++) + { + Bot *bot = g_botManager->GetBot (i); + + if (bot != NULL) + { + if (IsAlive (bot->GetEntity ())) + { + if (bot->m_targetEntity == m_radioEntity) + { + bot->m_targetEntity = NULL; + numFollowers--; + } + } + } + } + } + else if (m_radioOrder != Chatter_GoingToPlantBomb) + RadioMessage (Radio_Negative); + } + else if (m_radioOrder != Chatter_GoingToPlantBomb) + RadioMessage (Radio_Negative); + } + break; + + case Radio_HoldPosition: + if (!FNullEnt (m_targetEntity)) + { + if (m_targetEntity == m_radioEntity) + { + m_targetEntity = NULL; + RadioMessage (Radio_Affirmative); + + m_campButtons = 0; + + StartTask (TASK_PAUSE, TASKPRI_PAUSE, -1, GetWorldTime () + g_randGen.Float (30.0, 60.0), false); + } + } + break; + + case Chatter_NewRound: + ChatterMessage (Chatter_You_Heard_The_Man); + break; + + case Radio_TakingFire: + if (FNullEnt (m_targetEntity)) + { + if (FNullEnt (m_enemy)) + { + // Decrease Fear Levels to lower probability of Bot seeking Cover again + m_fearLevel -= 0.2; + + if (m_fearLevel < 0.0) + m_fearLevel = 0.0; + + if (g_randGen.Long (0, 100) < 45 && yb_communication_type.GetInt () == 2) + ChatterMessage (Chatter_OnMyWay); + else if (m_radioOrder == Radio_NeedBackup && yb_communication_type.GetInt () != 2) + RadioMessage (Radio_Affirmative); + + // don't pause/camp anymore + TaskId_t taskID = GetTaskId (); + + if (taskID == TASK_PAUSE || taskID == TASK_CAMP) + GetTask ()->time = GetWorldTime (); + + m_position = m_radioEntity->v.origin; + DeleteSearchNodes (); + + StartTask (TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, -1, 0.0, true); + } + else if (g_randGen.Long (0, 100) < 80 && m_radioOrder == Radio_TakingFire) + RadioMessage (Radio_Negative); + } + break; + + case Radio_YouTakePoint: + if (EntityIsVisible (m_radioEntity->v.origin) && m_isLeader) + RadioMessage (Radio_Affirmative); + break; + + case Radio_EnemySpotted: + case Radio_NeedBackup: + case Chatter_ScaredEmotion: + case Chatter_Pinned_Down: + if ((FNullEnt (m_enemy) && EntityIsVisible (m_radioEntity->v.origin)) || distance < 2048 || !m_moveToC4) + { + m_fearLevel -= 0.1; + + if (m_fearLevel < 0.0) + m_fearLevel = 0.0; + + if (g_randGen.Long (0, 100) < 45 && yb_communication_type.GetInt () == 2) + ChatterMessage (Chatter_OnMyWay); + else if (m_radioOrder == Radio_NeedBackup && yb_communication_type.GetInt () != 2) + RadioMessage (Radio_Affirmative); + + // don't pause/camp anymore + TaskId_t taskID = GetTaskId (); + + if (taskID == TASK_PAUSE || taskID == TASK_CAMP) + GetTask ()->time = GetWorldTime (); + + m_moveToC4 = true; + m_position = m_radioEntity->v.origin; + + DeleteSearchNodes (); + + // start move to position task + StartTask (TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, -1, 0.0, true); + } + else if (g_randGen.Long (0, 100) < 80 && m_radioOrder == Radio_NeedBackup) + RadioMessage (Radio_Negative); + break; + + case Radio_GoGoGo: + if (m_radioEntity == m_targetEntity) + { + if (g_randGen.Long (0, 100) < 45 && yb_communication_type.GetInt () == 2) + RadioMessage (Radio_Affirmative); + else if (m_radioOrder == Radio_NeedBackup && yb_communication_type.GetInt () != 2) + RadioMessage (Radio_Affirmative); + + m_targetEntity = NULL; + m_fearLevel -= 0.2; + + if (m_fearLevel < 0.0) + m_fearLevel = 0.0; + } + else if ((FNullEnt (m_enemy) && EntityIsVisible (m_radioEntity->v.origin)) || distance < 2048) + { + TaskId_t taskID = GetTaskId (); + + if (taskID == TASK_PAUSE || taskID == TASK_CAMP) + { + m_fearLevel -= 0.2; + + if (m_fearLevel < 0.0) + m_fearLevel = 0.0; + + RadioMessage (Radio_Affirmative); + // don't pause/camp anymore + GetTask ()->time = GetWorldTime (); + + m_targetEntity = NULL; + MakeVectors (m_radioEntity->v.v_angle); + + m_position = m_radioEntity->v.origin + g_pGlobals->v_forward * g_randGen.Long (1024, 2048); + + DeleteSearchNodes (); + StartTask (TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, -1, 0.0, true); + } + } + else if (!FNullEnt (m_doubleJumpEntity)) + { + RadioMessage (Radio_Affirmative); + ResetDoubleJumpState (); + } + else + RadioMessage (Radio_Negative); + break; + + case Radio_ShesGonnaBlow: + if (FNullEnt (m_enemy) && distance < 2048 && g_bombPlanted && GetTeam (GetEntity ()) == TEAM_TF) + { + RadioMessage (Radio_Affirmative); + + if (GetTaskId () == TASK_CAMP) + RemoveCertainTask (TASK_CAMP); + + m_targetEntity = NULL; + StartTask (TASK_ESCAPEFROMBOMB, TASKPRI_ESCAPEFROMBOMB, -1, 0.0, true); + } + else + RadioMessage (Radio_Negative); + + break; + + case Radio_RegroupTeam: + // if no more enemies found AND bomb planted, switch to knife to get to bombplace faster + if ((GetTeam (GetEntity ()) == TEAM_CF) && m_currentWeapon != WEAPON_KNIFE && GetNearbyEnemiesNearPosition (pev->origin, 9999) == 0 && g_bombPlanted && GetTaskId () != TASK_DEFUSEBOMB) + { + SelectWeaponByName ("weapon_knife"); + + DeleteSearchNodes (); + MoveToVector (g_waypoint->GetBombPosition ()); + + RadioMessage (Radio_Affirmative); + } + break; + + case Radio_StormTheFront: + if ((FNullEnt (m_enemy) && EntityIsVisible (m_radioEntity->v.origin)) || distance < 1024) + { + RadioMessage (Radio_Affirmative); + + // don't pause/camp anymore + TaskId_t taskID = GetTaskId (); + + if (taskID == TASK_PAUSE || taskID == TASK_CAMP) + GetTask ()->time = GetWorldTime (); + + m_targetEntity = NULL; + + MakeVectors (m_radioEntity->v.v_angle); + m_position = m_radioEntity->v.origin + g_pGlobals->v_forward * g_randGen.Long (1024, 2048); + + DeleteSearchNodes (); + StartTask (TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, -1, 0.0, true); + + m_fearLevel -= 0.3; + + if (m_fearLevel < 0.0) + m_fearLevel = 0.0; + + m_agressionLevel += 0.3; + + if (m_agressionLevel > 1.0) + m_agressionLevel = 1.0; + } + break; + + case Radio_Fallback: + if ((FNullEnt (m_enemy) && EntityIsVisible (m_radioEntity->v.origin)) || distance < 1024) + { + m_fearLevel += 0.5; + + if (m_fearLevel > 1.0) + m_fearLevel = 1.0; + + m_agressionLevel -= 0.5; + + if (m_agressionLevel < 0.0) + m_agressionLevel = 0.0; + + if (GetTaskId () == TASK_CAMP) + GetTask ()->time += g_randGen.Float (10.0, 15.0); + else + { + // don't pause/camp anymore + TaskId_t taskID = GetTaskId (); + + if (taskID == TASK_PAUSE) + GetTask ()->time = GetWorldTime (); + + m_targetEntity = NULL; + m_seeEnemyTime = GetWorldTime (); + + // if bot has no enemy + if (m_lastEnemyOrigin == nullvec) + { + int team = GetTeam (GetEntity ()); + float nearestDistance = FLT_MAX; + + // take nearest enemy to ordering player + for (int i = 0; i < GetMaxClients (); i++) + { + if (!(g_clients[i].flags & CF_USED) || !(g_clients[i].flags & CF_ALIVE) || g_clients[i].team == team) + continue; + + edict_t *enemy = g_clients[i].ent; + float distance = (m_radioEntity->v.origin - enemy->v.origin).GetLengthSquared (); + + if (distance < nearestDistance) + { + nearestDistance = distance; + m_lastEnemy = enemy; + m_lastEnemyOrigin = enemy->v.origin; + } + } + } + DeleteSearchNodes (); + } + } + break; + + case Radio_ReportTeam: + if (g_randGen.Long (0, 100) < 85) + RadioMessage ((GetNearbyEnemiesNearPosition (pev->origin, 400) == 0 && yb_communication_type.GetInt () != 2) ? Radio_SectorClear : Radio_ReportingIn); + break; + + case Radio_SectorClear: + // is bomb planted and it's a ct + if (g_bombPlanted) + { + int bombPoint = -1; + + // check if it's a ct command + if (GetTeam (m_radioEntity) == TEAM_CF && GetTeam (GetEntity ()) == TEAM_CF && IsValidBot (m_radioEntity)) + { + if (g_timeNextBombUpdate < GetWorldTime ()) + { + float minDistance = FLT_MAX; + + // find nearest bomb waypoint to player + IterateArray (g_waypoint->m_goalPoints, i) + { + distance = (g_waypoint->GetPath (g_waypoint->m_goalPoints[i])->origin - m_radioEntity->v.origin).GetLengthSquared (); + + if (distance < minDistance) + { + minDistance = distance; + bombPoint = g_waypoint->m_goalPoints[i]; + } + } + + // mark this waypoint as restricted point + if (bombPoint != -1 && !g_waypoint->IsGoalVisited (bombPoint)) + g_waypoint->SetGoalVisited (bombPoint); + + g_timeNextBombUpdate = GetWorldTime () + 0.5; + } + // Does this Bot want to defuse? + if (GetTaskId () == TASK_NORMAL) + { + // Is he approaching this goal? + if (GetTask ()->data == bombPoint) + { + GetTask ()->data = -1; + RadioMessage (Radio_Affirmative); + + } + } + } + } + break; + + case Radio_GetInPosition: + if ((FNullEnt (m_enemy) && EntityIsVisible (m_radioEntity->v.origin)) || distance < 1024) + { + RadioMessage (Radio_Affirmative); + + if (GetTaskId () == TASK_CAMP) + GetTask ()->time = GetWorldTime () + g_randGen.Float (30.0, 60.0); + else + { + // don't pause anymore + TaskId_t taskID = GetTaskId (); + + if (taskID == TASK_PAUSE) + GetTask ()->time = GetWorldTime (); + + m_targetEntity = NULL; + m_seeEnemyTime = GetWorldTime (); + + // If Bot has no enemy + if (m_lastEnemyOrigin == nullvec) + { + int team = GetTeam (GetEntity ()); + float nearestDistance = FLT_MAX; + + // Take nearest enemy to ordering Player + for (int i = 0; i < GetMaxClients (); i++) + { + if (!(g_clients[i].flags & CF_USED) || !(g_clients[i].flags & CF_ALIVE) || g_clients[i].team == team) + continue; + + edict_t *enemy = g_clients[i].ent; + float distance = (m_radioEntity->v.origin - enemy->v.origin).GetLengthSquared (); + + if (distance < nearestDistance) + { + nearestDistance = distance; + m_lastEnemy = enemy; + m_lastEnemyOrigin = enemy->v.origin; + } + } + } + DeleteSearchNodes (); + + int index = FindDefendWaypoint (m_radioEntity->v.origin); + + // push camp task on to stack + StartTask (TASK_CAMP, TASKPRI_CAMP, -1, GetWorldTime () + g_randGen.Float (30.0, 60.0), true); + // push move command + StartTask (TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, index, GetWorldTime () + g_randGen.Float (30.0, 60.0), true); + + if (g_waypoint->GetPath (index)->vis.crouch <= g_waypoint->GetPath (index)->vis.stand) + m_campButtons |= IN_DUCK; + else + m_campButtons &= ~IN_DUCK; + } + } + break; + } + m_radioOrder = 0; // radio command has been handled, reset +} + +void Bot::SelectLeaderEachTeam (int team) +{ + if (g_mapType & MAP_AS) + { + if (m_isVIP && !g_leaderChoosen[TEAM_CF]) + { + // vip bot is the leader + m_isLeader = true; + + if (g_randGen.Long (1, 100) < 50) + { + RadioMessage (Radio_FollowMe); + m_campButtons = 0; + } + g_leaderChoosen[TEAM_CF] = true; + } + else if ((team == TEAM_TF) && !g_leaderChoosen[TEAM_TF]) + { + Bot *botLeader = g_botManager->GetHighestFragsBot(team); + + if (botLeader != NULL && IsAlive (botLeader->GetEntity())) + { + botLeader->m_isLeader = true; + + if (g_randGen.Long (1, 100) < 45) + botLeader->RadioMessage (Radio_FollowMe); + } + g_leaderChoosen[TEAM_TF] = true; + } + } + else if (g_mapType & MAP_DE) + { + if (team == TEAM_TF && !g_leaderChoosen[TEAM_TF]) + { + if (pev->weapons & (1 << WEAPON_C4)) + { + // bot carrying the bomb is the leader + m_isLeader = true; + + // terrorist carrying a bomb needs to have some company + if (g_randGen.Long (1, 100) < 80) + { + if (yb_communication_type.GetInt () == 2) + ChatterMessage (Chatter_GoingToPlantBomb); + else + ChatterMessage (Radio_FollowMe); + + m_campButtons = 0; + } + g_leaderChoosen[TEAM_TF] = true; + } + } + else if (!g_leaderChoosen[TEAM_CF]) + { + Bot *botLeader = g_botManager->GetHighestFragsBot(team); + + if (botLeader != NULL) + { + botLeader->m_isLeader = true; + + if (g_randGen.Long (1, 100) < 30) + botLeader->RadioMessage (Radio_FollowMe); + } + g_leaderChoosen[TEAM_CF] = true; + } + } + else if (g_mapType & (MAP_ES | MAP_KA | MAP_FY)) + { + Bot *botLeader = g_botManager->GetHighestFragsBot (team); + + if (botLeader != NULL) + { + botLeader->m_isLeader = true; + + if (g_randGen.Long (1, 100) < 30) + botLeader->RadioMessage (Radio_FollowMe); + } + } + else + { + Bot *botLeader = g_botManager->GetHighestFragsBot(team); + + if (botLeader != NULL) + { + botLeader->m_isLeader = true; + + if (g_randGen.Long (1, 100) < (team == TEAM_TF ? 30 : 40)) + botLeader->RadioMessage (Radio_FollowMe); + } + } +} + +void Bot::ChooseAimDirection (void) +{ + if (!m_canChooseAimDirection) + return; + + TraceResult tr; + memset (&tr, 0, sizeof (TraceResult)); + + unsigned int flags = m_aimFlags; + + bool canChooseAimDirection = false; + bool currentPointValid = (m_currentWaypointIndex >= 0 && m_currentWaypointIndex < g_numWaypoints); + + if (!currentPointValid) + { + currentPointValid = true; + GetValidWaypoint (); + } + + // check if last enemy vector valid + if (m_lastEnemyOrigin != nullvec) + { + TraceLine (EyePosition (), m_lastEnemyOrigin, false, true, GetEntity (), &tr); + + if ((pev->origin - m_lastEnemyOrigin).GetLength () >= 1600 && FNullEnt (m_enemy) && !UsesSniper () || (tr.flFraction <= 0.2 && tr.pHit == g_hostEntity) && m_seeEnemyTime + 7.0 < GetWorldTime ()) + { + if ((m_aimFlags & (AIM_LAST_ENEMY | AIM_PREDICT_ENEMY)) && m_wantsToFire) + m_wantsToFire = false; + + m_lastEnemyOrigin = nullvec; + m_aimFlags &= ~(AIM_LAST_ENEMY | AIM_PREDICT_ENEMY); + + flags &= ~(AIM_LAST_ENEMY | AIM_PREDICT_ENEMY); + } + } + else + { + m_aimFlags &= ~(AIM_LAST_ENEMY | AIM_PREDICT_ENEMY); + flags &= ~(AIM_LAST_ENEMY | AIM_PREDICT_ENEMY); + } + + // don't allow bot to look at danger positions under certain circumstances + if (!(flags & (AIM_GRENADE | AIM_ENEMY | AIM_ENTITY))) + { + if (IsOnLadder () || IsInWater () || (m_waypointFlags & FLAG_LADDER) || (m_currentTravelFlags & PATHFLAG_JUMP)) + { + flags &= ~(AIM_LAST_ENEMY | AIM_PREDICT_ENEMY); + canChooseAimDirection = false; + } + } + + if (flags & AIM_OVERRIDE) + m_lookAt = m_camp; + else if (flags & AIM_GRENADE) + m_lookAt = m_throw + Vector (0, 0, 1.0 * m_grenade.z); + else if (flags & AIM_ENEMY) + FocusEnemy (); + else if (flags & AIM_ENTITY) + m_lookAt = m_entity; + else if (flags & AIM_LAST_ENEMY) + { + m_lookAt = m_lastEnemyOrigin; + + // did bot just see enemy and is quite aggressive? + if (m_seeEnemyTime + 3.0 - m_actualReactionTime + m_baseAgressionLevel > GetWorldTime ()) + { + // feel free to fire if shootable + if (!UsesSniper () && LastEnemyShootable ()) + m_wantsToFire = true; + } + else // forget an enemy far away + { + m_aimFlags &= ~AIM_LAST_ENEMY; + + if ((pev->origin - m_lastEnemyOrigin).GetLength () >= 1600.0) + m_lastEnemyOrigin = nullvec; + } + } + else if (flags & AIM_PREDICT_ENEMY) + { + if (((pev->origin - m_lastEnemyOrigin).GetLength () < 1600 || UsesSniper ()) && (tr.flFraction >= 0.2 || tr.pHit != g_worldEdict)) + { + bool recalcPath = true; + + if (!FNullEnt (m_lastEnemy) && m_trackingEdict == m_lastEnemy && m_timeNextTracking < GetWorldTime ()) + recalcPath = false; + + if (recalcPath) + { + m_lookAt = g_waypoint->GetPath (GetAimingWaypoint (m_lastEnemyOrigin))->origin; + m_camp = m_lookAt; + + m_timeNextTracking = GetWorldTime () + 0.5; + m_trackingEdict = m_lastEnemy; + + // feel free to fire if shoot able + if (LastEnemyShootable ()) + m_wantsToFire = true; + } + else + m_lookAt = m_camp; + } + else // forget an enemy far away + { + m_aimFlags &= ~AIM_PREDICT_ENEMY; + + if ((pev->origin - m_lastEnemyOrigin).GetLength () >= 1600.0) + m_lastEnemyOrigin = nullvec; + } + } + else if (flags & AIM_CAMP) + m_lookAt = m_camp; + else if ((flags & AIM_NAVPOINT) && !(flags & (AIM_ENTITY | AIM_ENEMY))) + { + m_lookAt = m_destOrigin; + + if (canChooseAimDirection && m_currentWaypointIndex != -1 && !(m_currentPath->flags & FLAG_LADDER)) + { + TraceResult tr; + int index = m_currentWaypointIndex; + + if (GetTeam (GetEntity ()) == TEAM_TF) + { + if ((g_experienceData + (index * g_numWaypoints) + index)->team0DangerIndex != -1) + { + Vector dest = g_waypoint->GetPath ((g_experienceData + (index * g_numWaypoints) + index)->team0DangerIndex)->origin; + TraceLine (pev->origin, dest, true, GetEntity (), &tr); + + if (tr.flFraction > 0.8 || tr.pHit != g_worldEdict) + m_lookAt = dest; + } + } + else + { + if ((g_experienceData + (index * g_numWaypoints) + index)->team1DangerIndex != -1) + { + Vector dest = g_waypoint->GetPath ((g_experienceData + (index * g_numWaypoints) + index)->team1DangerIndex)->origin; + TraceLine (pev->origin, dest, true, GetEntity (), &tr); + + if (tr.flFraction > 0.8 || tr.pHit != g_worldEdict) + m_lookAt = dest; + } + } + } + + if (canChooseAimDirection && m_prevWptIndex[0] >= 0 && m_prevWptIndex[0] < g_numWaypoints) + { + Path *path = g_waypoint->GetPath (m_prevWptIndex[0]); + + if (!(path->flags & FLAG_LADDER) && (fabsf (path->origin.z - m_destOrigin.z) < 30.0 || (m_waypointFlags & FLAG_CAMP))) + { + // trace forward + TraceLine (m_destOrigin, m_destOrigin + ((m_destOrigin - path->origin).Normalize () * 96), true, GetEntity (), &tr); + + if (tr.flFraction < 1.0 && tr.pHit == g_worldEdict) + m_lookAt = path->origin + pev->view_ofs; + } + } + } + if (m_lookAt == nullvec) + m_lookAt = m_destOrigin; +} + +void Bot::Think (void) +{ + pev->button = 0; + pev->flags |= FL_FAKECLIENT; // restore fake client bit, if it were removed by some evil action =) + + m_moveSpeed = 0.0; + m_strafeSpeed = 0.0; + m_moveAngles = nullvec; + + m_canChooseAimDirection = true; + m_notKilled = IsAlive (GetEntity ()); + + m_frameInterval = GetWorldTime () - m_lastThinkTime; + m_lastThinkTime = GetWorldTime (); + + // is bot movement enabled + bool botMovement = false; + + + if (m_notStarted) // if the bot hasn't selected stuff to start the game yet, go do that... + StartGame (); // select team & class + else if (!m_notKilled) + { + // no movement allowed in + if (m_voteKickIndex != m_lastVoteKick && yb_tkpunish.GetBool ()) // We got a Teamkiller? Vote him away... + { + FakeClientCommand (GetEntity (), "vote %d", m_voteKickIndex); + m_lastVoteKick = m_voteKickIndex; + + // if bot tk punishment is enabled slay the tk + if (yb_tkpunish.GetInt () != 2 || IsValidBot (INDEXENT (m_voteKickIndex))) + return; + + entvars_t *killer = VARS (INDEXENT (m_lastVoteKick)); + + MESSAGE_BEGIN (MSG_PAS, SVC_TEMPENTITY, killer->origin); + WRITE_BYTE (TE_TAREXPLOSION); + WRITE_COORD (killer->origin.x); + WRITE_COORD (killer->origin.y); + WRITE_COORD (killer->origin.z); + MESSAGE_END (); + + MESSAGE_BEGIN (MSG_PVS, SVC_TEMPENTITY, killer->origin); + WRITE_BYTE (TE_LAVASPLASH); + WRITE_COORD (killer->origin.x); + WRITE_COORD (killer->origin.y); + WRITE_COORD (killer->origin.z); + MESSAGE_END (); + + MESSAGE_BEGIN (MSG_ONE, g_netMsg->GetId (NETMSG_SCREENFADE), NULL, ENT (killer)); + WRITE_SHORT (1 << 15); + WRITE_SHORT (1 << 10); + WRITE_SHORT (1 << 1); + WRITE_BYTE (100); + WRITE_BYTE (0); + WRITE_BYTE (0); + WRITE_BYTE (255); + MESSAGE_END (); + + killer->frags++; + MDLL_ClientKill (ENT (killer)); + + HudMessage (ENT (killer), true, Vector (g_randGen.Long (33, 255), g_randGen.Long (33, 255), g_randGen.Long (33, 255)), "You was slayed, because of teamkilling a player. Please be careful."); + + // very fun thing + (*g_engfuncs.pfnClientCommand) (ENT (killer), "cd eject\n"); + } + else if (m_voteMap != 0) // host wants the bots to vote for a map? + { + FakeClientCommand (GetEntity (), "votemap %d", m_voteMap); + m_voteMap = 0; + } + extern ConVar yb_chat; + + if (yb_chat.GetBool () && !RepliesToPlayer () && m_lastChatTime + 10.0 < GetWorldTime () && g_lastChatTime + 5.0 < GetWorldTime ()) // bot chatting turned on? + { + // say a text every now and then + if (g_randGen.Long (1, 1500) < 2) + { + m_lastChatTime = GetWorldTime (); + g_lastChatTime = GetWorldTime (); + + char *pickedPhrase = const_cast (g_chatFactory[CHAT_DEAD].GetRandomElement ().GetBuffer ()); + bool sayBufferExists = false; + + // search for last messages, sayed + IterateArray (m_sayTextBuffer.lastUsedSentences, i) + { + if (strncmp (m_sayTextBuffer.lastUsedSentences[i].GetBuffer (), pickedPhrase, m_sayTextBuffer.lastUsedSentences[i].GetLength ()) == 0) + sayBufferExists = true; + } + + if (!sayBufferExists) + { + PrepareChatMessage (pickedPhrase); + PushMessageQueue (GSM_SAY); + + // add to ignore list + m_sayTextBuffer.lastUsedSentences.Push (pickedPhrase); + } + + // clear the used line buffer every now and then + if (m_sayTextBuffer.lastUsedSentences.GetElementNumber () > g_randGen.Long (4, 6)) + m_sayTextBuffer.lastUsedSentences.RemoveAll (); + } + } + } + else if (m_buyingFinished) + botMovement = true; + + int team = g_clients[ENTINDEX (GetEntity ()) - 1].realTeam;; + + // remove voice icon + if (g_lastRadioTime[team] + g_randGen.Float (0.8, 2.1) < GetWorldTime ()) + SwitchChatterIcon (false); // hide icon + + static float secondThinkTimer = 0.0; + + // check is it time to execute think (called once per second (not frame)) + if (secondThinkTimer < GetWorldTime ()) + { + SecondThink (); + + + // update timer to one second + secondThinkTimer = GetWorldTime () + 1.05; + } + CheckMessageQueue (); // check for pending messages + + if (pev->maxspeed < 10 && GetTaskId () != TASK_PLANTBOMB && GetTaskId () != TASK_DEFUSEBOMB) + botMovement = false; + + if (m_notKilled && botMovement && !yb_freeze_bots.GetBool ()) + BotAI (); // execute main code + + RunPlayerMovement (); // run the player movement +} + +void Bot::SecondThink (void) +{ + // this function is called from main think function every second (second not frame). + + if (g_bombPlanted && GetTeam (GetEntity ()) == TEAM_CF && (pev->origin - g_waypoint->GetBombPosition ()).GetLength () < 700 && !IsBombDefusing (g_waypoint->GetBombPosition ()) && !m_hasProgressBar && GetTaskId () != TASK_ESCAPEFROMBOMB) + ResetTasks (); +} + +void Bot::RunTask (void) +{ + // this is core function that handle task execution + + int team = GetTeam (GetEntity ()); + int destIndex, i; + + Vector src, destination; + TraceResult tr; + + bool exceptionCaught = false; + float fullDefuseTime, timeToBlowUp, defuseRemainingTime; + + switch (GetTaskId ()) + { + // normal task + case TASK_NORMAL: + m_aimFlags |= AIM_NAVPOINT; + + if ((g_mapType & MAP_DE) && team == TEAM_TF) + { + if (!g_bombPlanted) + { + m_loosedBombWptIndex = FindLoosedBomb (); + + if (m_loosedBombWptIndex != -1 && m_currentWaypointIndex != m_loosedBombWptIndex && g_randGen.Long (0, 100) < (GetNearbyFriendsNearPosition (g_waypoint->GetPath (m_loosedBombWptIndex)->origin, 650) >= 1 ? 40 : 90)) + GetTask ()->data = m_loosedBombWptIndex; + } + else if (!m_defendedBomb) + { + int plantedBombWptIndex = g_waypoint->FindNearest (g_waypoint->GetBombPosition ()); + + if (plantedBombWptIndex != -1 && m_currentWaypointIndex != plantedBombWptIndex) + GetTask ()->data = plantedBombWptIndex; + } + } + + // user forced a waypoint as a goal? + if (yb_debug_goal.GetInt () != -1) + { + // check if we reached it + if (((m_currentPath->origin - pev->origin).SkipZ ()).GetLengthSquared () < 16 && GetTask ()->data == yb_debug_goal.GetInt ()) + { + m_moveSpeed = 0.0; + m_strafeSpeed = 0.0; + + m_checkTerrain = false; + m_moveToGoal = false; + + return; // we can safely return here + } + + if (GetTask ()->data != yb_debug_goal.GetInt ()) + { + DeleteSearchNodes (); + GetTask ()->data = yb_debug_goal.GetInt (); + } + } + + // bots rushing with knife, when have no enemy (thanks for idea to nicebot project) + if (m_currentWeapon == WEAPON_KNIFE && (FNullEnt (m_lastEnemy) || !IsAlive (m_lastEnemy)) && FNullEnt (m_enemy) && m_knifeAttackTime < GetWorldTime () && !HasShield () && GetNearbyFriendsNearPosition (pev->origin, 96) == 0) + { + if (g_randGen.Long (0, 100) < 40) + pev->button |= IN_ATTACK; + else + pev->button |= IN_ATTACK2; + + m_knifeAttackTime = GetWorldTime () + g_randGen.Float (2.5, 6.0); + } + + if (m_reloadState == RELOAD_NONE && GetAmmo () != 0 && GetAmmoInClip () < 5 && g_weaponDefs[m_currentWeapon].ammo1 != -1) + m_reloadState = RELOAD_PRIMARY; + + // if bomb planted and it's a CT calculate new path to bomb point if he's not already heading for + if (g_bombPlanted && team == TEAM_CF && GetTask ()->data != -1 && !(g_waypoint->GetPath (GetTask ()->data)->flags & FLAG_GOAL) && GetTaskId () != TASK_ESCAPEFROMBOMB) + { + DeleteSearchNodes (); + GetTask ()->data = -1; + } + + if (!g_bombPlanted && m_currentWaypointIndex != -1 && (m_currentPath->flags & FLAG_GOAL) && g_randGen.Long (0, 100) < 80 && GetNearbyEnemiesNearPosition (pev->origin, 650) == 0) + RadioMessage (Radio_SectorClear); + + // reached the destination (goal) waypoint? + if (DoWaypointNav ()) + { + TaskComplete (); + m_prevGoalIndex = -1; + + // spray logo sometimes if allowed to do so + if (m_timeLogoSpray < GetWorldTime () && yb_spraypaints.GetBool () && g_randGen.Long (1, 100) < 80 && m_moveSpeed > GetWalkSpeed ()) + StartTask (TASK_SPRAY, TASKPRI_SPRAYLOGO, -1, GetWorldTime () + 1.0, false); + + // reached waypoint is a camp waypoint + if ((m_currentPath->flags & FLAG_CAMP) && !yb_csdm_mode.GetBool ()) + { + // check if bot has got a primary weapon and hasn't camped before + if (HasPrimaryWeapon () && m_timeCamping + 10.0 < GetWorldTime () && !HasHostage ()) + { + bool campingAllowed = true; + + // Check if it's not allowed for this team to camp here + if (team == TEAM_TF) + { + if (m_currentPath->flags & FLAG_CF_ONLY) + campingAllowed = false; + } + else + { + if (m_currentPath->flags & FLAG_TF_ONLY) + campingAllowed = false; + } + + // don't allow vip on as_ maps to camp + don't allow terrorist carrying c4 to camp + if (((g_mapType & MAP_AS) && *(INFOKEY_VALUE (GET_INFOKEYBUFFER (GetEntity ()), "model")) == 'v') || ((g_mapType & MAP_DE) && GetTeam (GetEntity ()) == TEAM_TF && !g_bombPlanted && (pev->weapons & (1 << WEAPON_C4)))) + campingAllowed = false; + + // check if another bot is already camping here + if (IsWaypointUsed (m_currentWaypointIndex)) + campingAllowed = false; + + if (campingAllowed) + { + // crouched camping here? + if (m_currentPath->flags & FLAG_CROUCH) + m_campButtons = IN_DUCK; + else + m_campButtons = 0; + + SelectBestWeapon (); + + if (!(m_states & (STATE_SEEING_ENEMY | STATE_HEARING_ENEMY)) && !m_reloadState) + m_reloadState = RELOAD_PRIMARY; + + MakeVectors (pev->v_angle); + + m_timeCamping = GetWorldTime () + g_randGen.Float (g_skillTab[m_skill / 20].campStartDelay, g_skillTab[m_skill / 20].campEndDelay); + StartTask (TASK_CAMP, TASKPRI_CAMP, -1, m_timeCamping, true); + + m_camp = Vector (m_currentPath->campStartX, m_currentPath->campStartY, 0.0f); + m_aimFlags |= AIM_CAMP; + m_campDirection = 0; + + // tell the world we're camping + if (g_randGen.Long (0, 100) < 95) + RadioMessage (Radio_InPosition); + + m_moveToGoal = false; + m_checkTerrain = false; + + m_moveSpeed = 0; + m_strafeSpeed = 0; + } + } + } + else + { + // some goal waypoints are map dependant so check it out... + if (g_mapType & MAP_CS) + { + // CT Bot has some hostages following? + if (HasHostage () && team == TEAM_CF) + { + // and reached a Rescue Point? + if (m_currentPath->flags & FLAG_RESCUE) + { + for (i = 0; i < MAX_HOSTAGES; i++) + m_hostages[i] = NULL; // clear array of hostage pointers + } + } + else if (team == TEAM_TF && g_randGen.Long (0, 100) < 80) + { + int index = FindDefendWaypoint (m_currentPath->origin); + + StartTask (TASK_CAMP, TASKPRI_CAMP, -1, GetWorldTime () + g_randGen.Float (60.0, 120.0), true); // push camp task on to stack + StartTask (TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, index, GetWorldTime () + g_randGen.Float (5.0, 10.0), true); // push move command + + if (g_waypoint->GetPath (index)->vis.crouch <= g_waypoint->GetPath (index)->vis.stand) + m_campButtons |= IN_DUCK; + else + m_campButtons &= ~IN_DUCK; + + ChatterMessage (Chatter_GoingToGuardVIPSafety); // play info about that + } + } + + if ((g_mapType & MAP_DE) && ((m_currentPath->flags & FLAG_GOAL) || m_inBombZone) && FNullEnt (m_enemy)) + { + // is it a terrorist carrying the bomb? + if (pev->weapons & (1 << WEAPON_C4)) + { + if ((m_states & STATE_SEEING_ENEMY) && GetNearbyFriendsNearPosition (pev->origin, 768) == 0) + { + // request an help also + RadioMessage (Radio_NeedBackup); + InstantChatterMessage (Chatter_ScaredEmotion); + + StartTask (TASK_CAMP, TASKPRI_CAMP, -1, GetWorldTime () + g_randGen.Float (4.0, 8.0), true); + } + else + StartTask (TASK_PLANTBOMB, TASKPRI_PLANTBOMB, -1, 0.0, false); + } + else if (team == TEAM_CF) + { + if (!g_bombPlanted && GetNearbyFriendsNearPosition (pev->origin, 120) <= 4 && g_randGen.Long (0, 100) < 65 && GetTaskId () == TASK_NORMAL && m_fearLevel > m_agressionLevel / 2) + { + int index = FindDefendWaypoint (m_currentPath->origin); + + StartTask (TASK_CAMP, TASKPRI_CAMP, -1, GetWorldTime () + g_randGen.Float (45.0, 60.0), true); // push camp task on to stack + StartTask (TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, index, GetWorldTime () + g_randGen.Float (10.0, 15.0), true); // push move command + + if (g_waypoint->GetPath (index)->vis.crouch <= g_waypoint->GetPath (index)->vis.stand) + m_campButtons |= IN_DUCK; + else + m_campButtons &= ~IN_DUCK; + + ChatterMessage (Chatter_DefendingBombSite); // play info about that + } + } + } + } + } + // no more nodes to follow - search new ones (or we have a momb) + else if (!GoalIsValid ()) + { + m_moveSpeed = pev->maxspeed; + DeleteSearchNodes (); + + // did we already decide about a goal before? + if (GetTask ()->data != -1) + destIndex = GetTask ()->data; + else + destIndex = FindGoal (); + + m_prevGoalIndex = destIndex; + + // remember index + GetTask ()->data = destIndex; + + // do pathfinding if it's not the current waypoint + if (destIndex != m_currentWaypointIndex) + FindPath (m_currentWaypointIndex, destIndex, ((g_bombPlanted && team == TEAM_CF) || yb_debug_goal.GetInt () != -1) ? 0 : m_pathType); + } + else + { + if (!(pev->flags & FL_DUCKING) && m_minSpeed != pev->maxspeed) + m_moveSpeed = m_minSpeed; + } + + if ((yb_walking_allowed.GetBool () && mp_footsteps.GetBool ()) && m_skill > 80 && !(m_aimFlags & AIM_ENEMY) && (m_heardSoundTime + 13.0 >= GetWorldTime () || (m_states & (STATE_HEARING_ENEMY | STATE_SUSPECT_ENEMY))) && GetNearbyEnemiesNearPosition (pev->origin, 1024) >= 1 && !(m_currentTravelFlags & PATHFLAG_JUMP) && !(pev->button & IN_DUCK) && !(pev->flags & FL_DUCKING) && !yb_jasonmode.GetBool () && !g_bombPlanted) + m_moveSpeed = GetWalkSpeed (); + + // bot hasn't seen anything in a long time and is asking his teammates to report in + if (m_seeEnemyTime != 0.0 && m_seeEnemyTime + g_randGen.Float (30.0, 80.0) < GetWorldTime () && g_randGen.Long (0, 100) < 70 && g_timeRoundStart + 20.0 < GetWorldTime () && m_askCheckTime + g_randGen.Float (20.0, 30.0) < GetWorldTime ()) + { + m_askCheckTime = GetWorldTime (); + RadioMessage (Radio_ReportTeam); + } + + break; + + // bot sprays messy logos all over the place... + case TASK_SPRAY: + m_aimFlags |= AIM_ENTITY; + + // bot didn't spray this round? + if (m_timeLogoSpray <= GetWorldTime () && GetTask ()->time > GetWorldTime ()) + { + MakeVectors (pev->v_angle); + Vector sprayOrigin = EyePosition () + (g_pGlobals->v_forward * 128); + + TraceLine (EyePosition (), sprayOrigin, true, GetEntity (), &tr); + + // no wall in front? + if (tr.flFraction >= 1.0) + sprayOrigin.z -= 128.0; + + m_entity = sprayOrigin; + + if (GetTask ()->time - 0.5 < GetWorldTime ()) + { + // emit spraycan sound + EMIT_SOUND_DYN2 (GetEntity (), CHAN_VOICE, "player/sprayer.wav", 1.0, ATTN_NORM, 0, 100); + TraceLine (EyePosition (), EyePosition () + g_pGlobals->v_forward * 128, true, GetEntity (), &tr); + + // paint the actual logo decal + DecalTrace (pev, &tr, m_logotypeIndex); + m_timeLogoSpray = GetWorldTime () + g_randGen.Float (30.0, 45.0); + } + } + else + TaskComplete (); + + m_moveToGoal = false; + m_checkTerrain = false; + + m_navTimeset = GetWorldTime (); + m_moveSpeed = 0; + m_strafeSpeed = 0.0; + + break; + + // hunt down enemy + case TASK_HUNTENEMY: + m_aimFlags |= AIM_NAVPOINT; + m_checkTerrain = true; + + // if we've got new enemy... + if (!FNullEnt (m_enemy) || FNullEnt (m_lastEnemy)) + { + // forget about it... + TaskComplete (); + m_prevGoalIndex = -1; + + m_lastEnemy = NULL; + m_lastEnemyOrigin = nullvec; + } + else if (GetTeam (m_lastEnemy) == team) + { + // don't hunt down our teammate... + RemoveCertainTask (TASK_HUNTENEMY); + m_prevGoalIndex = -1; + } + else if (DoWaypointNav ()) // reached last enemy pos? + { + // forget about it... + TaskComplete (); + m_prevGoalIndex = -1; + + m_lastEnemy = NULL; + m_lastEnemyOrigin = nullvec; + } + else if (!GoalIsValid ()) // do we need to calculate a new path? + { + DeleteSearchNodes (); + + // is there a remembered index? + if (GetTask ()->data != -1 && GetTask ()->data < g_numWaypoints) + destIndex = GetTask ()->data; + else // no. we need to find a new one + destIndex = g_waypoint->FindNearest (m_lastEnemyOrigin); + + // remember index + m_prevGoalIndex = destIndex; + GetTask ()->data = destIndex; + + if (destIndex != m_currentWaypointIndex) + FindPath (m_currentWaypointIndex, destIndex, m_pathType); + } + + // bots skill higher than 60? + if ((yb_walking_allowed.GetBool () && mp_footsteps.GetBool ()) && m_skill > 60) + { + // then make him move slow if near enemy + if (!(m_currentTravelFlags & PATHFLAG_JUMP)) + { + if (m_currentWaypointIndex != -1) + { + if (m_currentPath->radius < 32 && !IsOnLadder () && !IsInWater () && m_seeEnemyTime + 4.0 > GetWorldTime () && m_skill < 80) + pev->button |= IN_DUCK; + } + + if ((m_lastEnemyOrigin - pev->origin).GetLength () < 512.0 && !(pev->flags & FL_DUCKING)) + m_moveSpeed = GetWalkSpeed (); + } + } + break; + + // bot seeks cover from enemy + case TASK_SEEKCOVER: + m_aimFlags |= AIM_NAVPOINT; + + if (FNullEnt (m_lastEnemy) || !IsAlive (m_lastEnemy)) + { + TaskComplete (); + m_prevGoalIndex = -1; + } + else if (DoWaypointNav ()) // reached final cover waypoint? + { + // yep. activate hide behaviour + TaskComplete (); + + m_prevGoalIndex = -1; + m_pathType = 0; + + // start hide task + StartTask (TASK_HIDE, TASKPRI_HIDE, -1, GetWorldTime () + g_randGen.Float (5.0, 15.0), false); + destination = m_lastEnemyOrigin; + + // get a valid look direction + GetCampDirection (&destination); + + m_aimFlags |= AIM_CAMP; + m_camp = destination; + m_campDirection = 0; + + // chosen waypoint is a camp waypoint? + if (m_currentPath->flags & FLAG_CAMP) + { + // use the existing camp wpt prefs + if (m_currentPath->flags & FLAG_CROUCH) + m_campButtons = IN_DUCK; + else + m_campButtons = 0; + } + else + { + // choose a crouch or stand pos + if (m_currentPath->vis.crouch <= m_currentPath->vis.stand) + m_campButtons = IN_DUCK; + else + m_campButtons = 0; + + // enter look direction from previously calculated positions + m_currentPath->campStartX = destination.x; + m_currentPath->campStartY = destination.y; + + m_currentPath->campStartX = destination.x; + m_currentPath->campEndY = destination.y; + } + + if ((m_reloadState == RELOAD_NONE) && (GetAmmoInClip () < 8) && (GetAmmo () != 0)) + m_reloadState = RELOAD_PRIMARY; + + m_moveSpeed = 0; + m_strafeSpeed = 0; + + m_moveToGoal = false; + m_checkTerrain = true; + } + else if (!GoalIsValid ()) // we didn't choose a cover waypoint yet or lost it due to an attack? + { + DeleteSearchNodes (); + + if (GetTask ()->data != -1) + destIndex = GetTask ()->data; + else + { + destIndex = FindCoverWaypoint (1024); + + if (destIndex == -1) + destIndex = g_waypoint->FindNearest (pev->origin, 500); + } + + m_campDirection = 0; + m_prevGoalIndex = destIndex; + GetTask ()->data = destIndex; + + if (destIndex != m_currentWaypointIndex) + FindPath (m_currentWaypointIndex, destIndex, 0); + } + break; + + // plain attacking + case TASK_ATTACK: + m_moveToGoal = false; + m_checkTerrain = false; + + if (!FNullEnt (m_enemy)) + CombatFight (); + else + { + TaskComplete (); + FindWaypoint (); + + m_destOrigin = m_lastEnemyOrigin; + } + m_navTimeset = GetWorldTime (); + break; + + // Bot is pausing + case TASK_PAUSE: + m_moveToGoal = false; + m_checkTerrain = false; + + m_navTimeset = GetWorldTime (); + m_moveSpeed = 0.0; + m_strafeSpeed = 0.0; + + m_aimFlags |= AIM_NAVPOINT; + + // is bot blinded and above average skill? + if (m_viewDistance < 500.0 && m_skill > 60) + { + // go mad! + m_moveSpeed = -fabsf ((m_viewDistance - 500.0) / 2.0); + + if (m_moveSpeed < -pev->maxspeed) + m_moveSpeed = -pev->maxspeed; + + MakeVectors (pev->v_angle); + m_camp = EyePosition () + (g_pGlobals->v_forward * 500); + + m_aimFlags |= AIM_OVERRIDE; + m_wantsToFire = true; + } + else + pev->button |= m_campButtons; + + // stop camping if time over or gets hurt by something else than bullets + if (GetTask ()->time < GetWorldTime () || m_lastDamageType > 0) + TaskComplete (); + break; + + // blinded (flashbanged) behaviour + case TASK_BLINDED: + m_moveToGoal = false; + m_checkTerrain = false; + m_navTimeset = GetWorldTime (); + + // if bot remembers last enemy position + if (m_skill > 70 && m_lastEnemyOrigin != nullvec && IsValidPlayer (m_lastEnemy) && !UsesSniper ()) + { + m_lookAt = m_lastEnemyOrigin; // face last enemy + m_wantsToFire = true; // and shoot it + } + + m_moveSpeed = m_blindMoveSpeed; + m_strafeSpeed = m_blindSidemoveSpeed; + pev->button |= m_blindButton; + + if (m_blindTime < GetWorldTime ()) + TaskComplete (); + + break; + + // camping behaviour + case TASK_CAMP: + m_aimFlags |= AIM_CAMP; + m_checkTerrain = false; + m_moveToGoal = false; + + if (g_bombPlanted && m_defendedBomb && !IsBombDefusing(g_waypoint->GetBombPosition()) && !OutOfBombTimer() && team == TEAM_CF) + { + m_defendedBomb = false; + TaskComplete(); + } + + // half the reaction time if camping because you're more aware of enemies if camping + m_idealReactionTime = (g_randGen.Float (g_skillTab[m_skill / 20].minSurpriseTime, g_skillTab[m_skill / 20].maxSurpriseTime)) / 2; + m_navTimeset = GetWorldTime (); + + m_moveSpeed = 0; + m_strafeSpeed = 0.0; + + GetValidWaypoint (); + + if (m_nextCampDirTime < GetWorldTime ()) + { + m_nextCampDirTime = GetWorldTime () + g_randGen.Float (2.0, 5.0); + + if (m_currentPath->flags & FLAG_CAMP) + { + destination.z = 0; + + // switch from 1 direction to the other + if (m_campDirection < 1) + { + destination.x = m_currentPath->campStartX; + destination.y = m_currentPath->campStartY; + m_campDirection ^= 1; + } + else + { + destination.x = m_currentPath->campEndX; + destination.y = m_currentPath->campEndY; + m_campDirection ^= 1; + } + + // find a visible waypoint to this direction... + // i know this is ugly hack, but i just don't want to break compatiability :) + int numFoundPoints = 0; + int foundPoints[3]; + int distanceTab[3]; + + Vector dotA = (destination - pev->origin).Normalize2D (); + + for (i = 0; i < g_numWaypoints; i++) + { + // skip invisible waypoints or current waypoint + if (!g_waypoint->IsVisible (m_currentWaypointIndex, i) || (i == m_currentWaypointIndex)) + continue; + + Vector dotB = (g_waypoint->GetPath (i)->origin - pev->origin).Normalize2D (); + + if ((dotA | dotB) > 0.9) + { + int distance = static_cast ((pev->origin - g_waypoint->GetPath (i)->origin).GetLength ()); + + if (numFoundPoints >= 3) + { + for (int j = 0; j < 3; j++) + { + if (distance > distanceTab[j]) + { + distanceTab[j] = distance; + foundPoints[j] = i; + + break; + } + } + } + else + { + foundPoints[numFoundPoints] = i; + distanceTab[numFoundPoints] = distance; + + numFoundPoints++; + } + } + } + + if (--numFoundPoints >= 0) + m_camp = g_waypoint->GetPath (foundPoints[g_randGen.Long (0, numFoundPoints)])->origin; + else + m_camp = g_waypoint->GetPath (GetAimingWaypoint ())->origin; + } + else + m_camp = g_waypoint->GetPath (GetAimingWaypoint ())->origin; + } + // press remembered crouch button + pev->button |= m_campButtons; + + // stop camping if time over or gets hurt by something else than bullets + if ((GetTask ()->time < GetWorldTime ()) || (m_lastDamageType > 0)) + TaskComplete (); + break; + + // hiding behaviour + case TASK_HIDE: + m_aimFlags |= AIM_CAMP; + m_checkTerrain = false; + m_moveToGoal = false; + + // half the reaction time if camping + m_idealReactionTime = (g_randGen.Float (g_skillTab[m_skill / 20].minSurpriseTime, g_skillTab[m_skill / 20].maxSurpriseTime)) / 2; + + m_navTimeset = GetWorldTime (); + m_moveSpeed = 0; + m_strafeSpeed = 0.0; + + GetValidWaypoint (); + + if (HasShield () && !m_isReloading) + { + if (!IsShieldDrawn ()) + pev->button |= IN_ATTACK2; // draw the shield! + else + pev->button |= IN_DUCK; // duck under if the shield is already drawn + } + + // if we see an enemy and aren't at a good camping point leave the spot + if ((m_states & STATE_SEEING_ENEMY) || m_inBombZone) + { + if (!(m_currentPath->flags & FLAG_CAMP)) + { + TaskComplete (); + + m_campButtons = 0; + m_prevGoalIndex = -1; + + if (!FNullEnt (m_enemy)) + CombatFight (); + + break; + } + } + else if (m_lastEnemyOrigin == nullvec) // If we don't have an enemy we're also free to leave + { + TaskComplete (); + + m_campButtons = 0; + m_prevGoalIndex = -1; + + if (GetTaskId () == TASK_HIDE) + TaskComplete (); + + break; + } + + pev->button |= m_campButtons; + m_navTimeset = GetWorldTime (); + + // stop camping if time over or gets hurt by something else than bullets + if (GetTask ()->time < GetWorldTime () || m_lastDamageType > 0) + TaskComplete (); + + break; + + // moves to a position specified in position has a higher priority than task_normal + case TASK_MOVETOPOSITION: + m_aimFlags |= AIM_NAVPOINT; + + if (IsShieldDrawn ()) + pev->button |= IN_ATTACK2; + + if (DoWaypointNav ()) // reached destination? + { + TaskComplete (); // we're done + + m_prevGoalIndex = -1; + m_position = nullvec; + } + else if (!GoalIsValid ()) // didn't choose goal waypoint yet? + { + DeleteSearchNodes (); + + if (GetTask ()->data != -1 && GetTask ()->data < g_numWaypoints) + destIndex = GetTask ()->data; + else + destIndex = g_waypoint->FindNearest (m_position); + + if (destIndex >= 0 && destIndex < g_numWaypoints) + { + m_prevGoalIndex = destIndex; + GetTask ()->data = destIndex; + + FindPath (m_currentWaypointIndex, destIndex, m_pathType); + } + else + TaskComplete (); + } + break; + + // planting the bomb right now + case TASK_PLANTBOMB: + m_aimFlags |= AIM_CAMP; + + destination = m_lastEnemyOrigin; + GetCampDirection (&destination); + + if (pev->weapons & (1 << WEAPON_C4)) // we're still got the C4? + { + SelectWeaponByName ("weapon_c4"); + + if (IsAlive (m_enemy) || !m_inBombZone) + TaskComplete (); + else + { + m_moveToGoal = false; + m_checkTerrain = false; + m_navTimeset = GetWorldTime (); + + if (m_currentPath->flags & FLAG_CROUCH) + pev->button |= (IN_ATTACK | IN_DUCK); + else + pev->button |= IN_ATTACK; + + m_moveSpeed = 0; + m_strafeSpeed = 0; + } + } + else // done with planting + { + TaskComplete (); + + // tell teammates to move over here... + if (GetNearbyFriendsNearPosition (pev->origin, 1200) != 0) + RadioMessage (Radio_NeedBackup); + + DeleteSearchNodes (); + int index = FindDefendWaypoint (pev->origin); + + float bombTimer = mp_c4timer.GetFloat (); + + // push camp task on to stack + StartTask (TASK_CAMP, TASKPRI_CAMP, -1, GetWorldTime () + ((bombTimer / 2) + (bombTimer / 4)), true); + // push move command + StartTask (TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, index, GetWorldTime () + ((bombTimer / 2) + (bombTimer / 4)), true); + + if (g_waypoint->GetPath (index)->vis.crouch <= g_waypoint->GetPath (index)->vis.stand) + m_campButtons |= IN_DUCK; + else + m_campButtons &= ~IN_DUCK; + } + break; + + // bomb defusing behaviour + case TASK_DEFUSEBOMB: + fullDefuseTime = m_hasDefuser ? 6.0 : 11.0; + timeToBlowUp = GetBombTimeleft (); + defuseRemainingTime = fullDefuseTime; + + if (m_hasProgressBar /*&& IsOnFloor ()*/) + defuseRemainingTime = fullDefuseTime - GetWorldTime(); + + // exception: bomb has been defused + if (g_waypoint->GetBombPosition () == nullvec) + { + exceptionCaught = true; + g_bombPlanted = false; + + if (GetNearbyFriendsNearPosition (pev->origin, 9999) != 0 && g_randGen.Long (0, 100) < 50) + { + if (timeToBlowUp <= 3.0) + { + if (yb_communication_type.GetInt () == 2) + InstantChatterMessage (Chatter_BarelyDefused); + else if (yb_communication_type. GetInt () == 1) + RadioMessage (Radio_SectorClear); + } + else + RadioMessage (Radio_SectorClear); + } + } + else if (defuseRemainingTime > timeToBlowUp) // exception: not time left for defusing + exceptionCaught = true; + else if (m_states & STATE_SEEING_ENEMY) // exception: saw/seeing enemy + { + if (GetNearbyFriendsNearPosition (pev->origin, 128) == 0) + { + if (defuseRemainingTime > 0.75) + { + if (GetNearbyFriendsNearPosition (pev->origin, 128) > 0) + RadioMessage (Radio_NeedBackup); + + exceptionCaught = true; + } + } + else if (timeToBlowUp > fullDefuseTime + 3.0 && defuseRemainingTime > 1.0) + exceptionCaught = true; + } + else if (m_states & STATE_SUSPECT_ENEMY) // exception: suspect enemy + { + if (GetNearbyFriendsNearPosition (pev->origin, 128) == 0) + { + if (timeToBlowUp > fullDefuseTime + 10.0) + { + if (GetNearbyFriendsNearPosition (pev->origin, 128) > 0) + RadioMessage (Radio_NeedBackup); + + exceptionCaught = true; + } + } + } + + + // one of exceptions is thrown. finish task. + if (exceptionCaught) + { + m_checkTerrain = true; + m_moveToGoal = true; + + m_destOrigin = nullvec; + m_entity = nullvec; + + m_pickupItem = NULL; + m_pickupType = PICKUP_NONE; + + TaskComplete (); + break; + } + + // to revert from pause after reload waiting && just to be sure + m_moveToGoal = false; + m_checkTerrain = true; + + m_moveSpeed = pev->maxspeed; + m_strafeSpeed = 0.0; + + // bot is reloading and we close enough to start defusing + if (m_isReloading && (g_waypoint->GetBombPosition () - pev->origin).GetLength2D () < 80.0) + { + if (GetNearbyEnemiesNearPosition (pev->origin, 9999) == 0 || GetNearbyFriendsNearPosition (pev->origin, 768) > 2 || timeToBlowUp < fullDefuseTime + 7.0 || ((GetAmmoInClip () > 8 && m_reloadState == RELOAD_PRIMARY) || (GetAmmoInClip () > 5 && m_reloadState == RELOAD_SECONDARY))) + { + int weaponIndex = GetHighestWeapon (); + + // just select knife and then select weapon + SelectWeaponByName ("weapon_knife"); + + if (weaponIndex > 0 && weaponIndex < NUM_WEAPONS) + SelectWeaponbyNumber (weaponIndex); + + m_isReloading = false; + } + else // just wait here + { + m_moveToGoal = false; + m_checkTerrain = false; + + m_moveSpeed = 0.0; + m_strafeSpeed = 0.0; + } + } + + // head to bomb and press use button + m_aimFlags |= AIM_ENTITY; + + m_destOrigin = g_waypoint->GetBombPosition (); + m_entity = g_waypoint->GetBombPosition (); + + pev->button |= IN_USE; + + // if defusing is not already started, maybe crouch before + if (!m_hasProgressBar && m_duckDefuseCheckTime < GetWorldTime ()) + { + if (m_skill >= 80 && GetNearbyEnemiesNearPosition (pev->origin, 9999.0) != 0) + m_duckDefuse = true; + + Vector botDuckOrigin, botStandOrigin; + + if (pev->button & IN_DUCK) + { + botDuckOrigin = pev->origin; + botStandOrigin = pev->origin + Vector(0, 0, 18); + } + else + { + botDuckOrigin = pev->origin - Vector(0, 0, 18); + botStandOrigin = pev->origin; + } + + float duckLength = (m_entity - botDuckOrigin).GetLengthSquared (); + float standLength = (m_entity - botStandOrigin).GetLengthSquared (); + + if (duckLength > 5625 || standLength > 5625) + { + if (standLength < duckLength) + m_duckDefuse = false; // stand + else + m_duckDefuse = true; // duck + } + m_duckDefuseCheckTime = GetWorldTime () + 1.5; + } + + // press duck button + if (m_duckDefuse || (pev->oldbuttons & IN_DUCK)) + pev->button |= IN_DUCK; + else + pev->button &= ~IN_DUCK; + + // we are defusing bomb + if (m_hasProgressBar) + { + pev->button |= IN_USE; + + m_reloadState = RELOAD_NONE; + m_navTimeset = GetWorldTime (); + + // don't move when defusing + m_moveToGoal = false; + m_checkTerrain = false; + + m_moveSpeed = 0.0; + m_strafeSpeed = 0.0; + + // notify team + if (GetNearbyFriendsNearPosition (pev->origin, 9999) != 0) + { + ChatterMessage (Chatter_DefusingC4); + + if (GetNearbyFriendsNearPosition (pev->origin, 256) < 2) + RadioMessage (Radio_NeedBackup); + } + } + break; + + // follow user behaviour + case TASK_FOLLOWUSER: + if (FNullEnt (m_targetEntity) || !IsAlive (m_targetEntity)) + { + m_targetEntity = NULL; + TaskComplete (); + break; + } + + if (m_targetEntity->v.button & IN_ATTACK) + { + MakeVectors (m_targetEntity->v.v_angle); + TraceLine (m_targetEntity->v.origin + m_targetEntity->v.view_ofs, g_pGlobals->v_forward * 500, true, true, GetEntity (), &tr); + + if (!FNullEnt (tr.pHit) && IsValidPlayer (tr.pHit) && GetTeam (tr.pHit) != team) + { + m_targetEntity = NULL; + m_lastEnemy = tr.pHit; + m_lastEnemyOrigin = tr.pHit->v.origin; + + TaskComplete (); + break; + } + } + + if (m_targetEntity->v.maxspeed != 0 && m_targetEntity->v.maxspeed < pev->maxspeed) + m_moveSpeed = m_targetEntity->v.maxspeed; + + if (m_reloadState == RELOAD_NONE && GetAmmo () != 0) + m_reloadState = RELOAD_PRIMARY; + + if ((m_targetEntity->v.origin - pev->origin).GetLength () > 130) + m_followWaitTime = 0.0; + else + { + m_moveSpeed = 0.0; + + if (m_followWaitTime == 0.0) + m_followWaitTime = GetWorldTime (); + else + { + if (m_followWaitTime + 3.0 < GetWorldTime ()) + { + // stop following if we have been waiting too long + m_targetEntity = NULL; + + RadioMessage (Radio_YouTakePoint); + TaskComplete (); + + break; + } + } + } + m_aimFlags |= AIM_NAVPOINT; + + if (yb_walking_allowed.GetBool () && m_targetEntity->v.maxspeed < m_moveSpeed) + m_moveSpeed = GetWalkSpeed (); + + if (IsShieldDrawn ()) + pev->button |= IN_ATTACK2; + + if (DoWaypointNav ()) // reached destination? + GetTask ()->data = -1; + + if (!GoalIsValid ()) // didn't choose goal waypoint yet? + { + DeleteSearchNodes (); + + destIndex = g_waypoint->FindNearest (m_targetEntity->v.origin); + + Array points; + g_waypoint->FindInRadius (points, 200, m_targetEntity->v.origin); + + while (!points.IsEmpty ()) + { + int newIndex = points.Pop (); + + // if waypoint not yet used, assign it as dest + if (!IsWaypointUsed (newIndex) && (newIndex != m_currentWaypointIndex)) + destIndex = newIndex; + } + + if (destIndex >= 0 && destIndex < g_numWaypoints && destIndex != m_currentWaypointIndex) + { + m_prevGoalIndex = destIndex; + GetTask ()->data = destIndex; + + // always take the shortest path + FindShortestPath (m_currentWaypointIndex, destIndex); + } + else + { + m_targetEntity = NULL; + TaskComplete (); + } + } + break; + + // HE grenade throw behaviour + case TASK_THROWHEGRENADE: + m_aimFlags |= AIM_GRENADE; + destination = m_throw; + + if (!(m_states & STATE_SEEING_ENEMY)) + { + m_moveSpeed = 0.0; + m_strafeSpeed = 0.0; + + m_moveToGoal = false; + } + else if (!(m_states & STATE_SUSPECT_ENEMY) && !FNullEnt (m_enemy)) + destination = m_enemy->v.origin + (m_enemy->v.velocity.SkipZ () * 0.5); + + m_isUsingGrenade = true; + m_checkTerrain = false; + + if ((pev->origin - destination).GetLengthSquared () < 400 * 400) + { + // heck, I don't wanna blow up myself + m_grenadeCheckTime = GetWorldTime () + MAX_GRENADE_TIMER; + + SelectBestWeapon (); + TaskComplete (); + + break; + } + + m_grenade = CheckThrow (EyePosition (), destination); + + if (m_grenade.GetLengthSquared () < 100) + m_grenade = CheckToss (EyePosition (), destination); + + if (m_grenade.GetLengthSquared () <= 100) + { + m_grenadeCheckTime = GetWorldTime () + MAX_GRENADE_TIMER; + m_grenade = m_lookAt; + + SelectBestWeapon (); + TaskComplete (); + } + else + { + edict_t *ent = NULL; + + while (!FNullEnt (ent = FIND_ENTITY_BY_CLASSNAME (ent, "grenade"))) + { + if (ent->v.owner == GetEntity () && strcmp (STRING (ent->v.model) + 9, "hegrenade.mdl") == 0) + { + // set the correct velocity for the grenade + if (m_grenade.GetLengthSquared () > 100) + ent->v.velocity = m_grenade; + + m_grenadeCheckTime = GetWorldTime () + MAX_GRENADE_TIMER; + + SelectBestWeapon (); + TaskComplete (); + + break; + } + } + + if (FNullEnt (ent)) + { + if (m_currentWeapon != WEAPON_EXPLOSIVE) + { + if (pev->weapons & (1 << WEAPON_EXPLOSIVE)) + SelectWeaponByName ("weapon_hegrenade"); + } + else if (!(pev->oldbuttons & IN_ATTACK)) + pev->button |= IN_ATTACK; + } + } + pev->button |= m_campButtons; + break; + + // flashbang throw behavior (basically the same code like for HE's) + case TASK_THROWFLASHBANG: + m_aimFlags |= AIM_GRENADE; + destination = m_throw; + + if (!(m_states & STATE_SEEING_ENEMY)) + { + m_moveSpeed = 0.0; + m_strafeSpeed = 0.0; + + m_moveToGoal = false; + } + else if (!(m_states & STATE_SUSPECT_ENEMY) && !FNullEnt (m_enemy)) + destination = m_enemy->v.origin + (m_enemy->v.velocity.SkipZ () * 0.5); + + m_isUsingGrenade = true; + m_checkTerrain = false; + + m_grenade = CheckThrow (EyePosition (), destination); + + if (m_grenade.GetLengthSquared () < 100) + m_grenade = CheckToss (pev->origin, destination); + + if (m_grenade.GetLengthSquared () <= 100) + { + m_grenadeCheckTime = GetWorldTime () + MAX_GRENADE_TIMER; + m_grenade = m_lookAt; + + SelectBestWeapon (); + TaskComplete (); + } + else + { + edict_t *ent = NULL; + while (!FNullEnt (ent = FIND_ENTITY_BY_CLASSNAME (ent, "grenade"))) + { + if (ent->v.owner == GetEntity () && strcmp (STRING (ent->v.model) + 9, "flashbang.mdl") == 0) + { + // set the correct velocity for the grenade + if (m_grenade.GetLengthSquared () > 100) + ent->v.velocity = m_grenade; + + m_grenadeCheckTime = GetWorldTime () + MAX_GRENADE_TIMER; + + SelectBestWeapon (); + TaskComplete (); + break; + } + } + + if (FNullEnt (ent)) + { + if (m_currentWeapon != WEAPON_FLASHBANG) + { + if (pev->weapons & (1 << WEAPON_FLASHBANG)) + SelectWeaponByName ("weapon_flashbang"); + } + else if (!(pev->oldbuttons & IN_ATTACK)) + pev->button |= IN_ATTACK; + } + } + pev->button |= m_campButtons; + break; + + // smoke grenade throw behavior + // a bit different to the others because it mostly tries to throw the sg on the ground + case TASK_THROWSMOKE: + m_aimFlags |= AIM_GRENADE; + + if (!(m_states & STATE_SEEING_ENEMY)) + { + m_moveSpeed = 0.0; + m_strafeSpeed = 0.0; + + m_moveToGoal = false; + } + + m_checkTerrain = false; + m_isUsingGrenade = true; + + src = m_lastEnemyOrigin - pev->velocity; + + // predict where the enemy is in 0.5 secs + if (!FNullEnt (m_enemy)) + src = src + m_enemy->v.velocity * 0.5; + + m_grenade = (src - EyePosition ()).Normalize (); + + if (GetTask ()->time < GetWorldTime () + 0.5) + { + m_aimFlags &= ~AIM_GRENADE; + m_states &= ~STATE_THROW_SG; + + TaskComplete (); + break; + } + + if (m_currentWeapon != WEAPON_SMOKE) + { + if (pev->weapons & (1 << WEAPON_SMOKE)) + { + SelectWeaponByName ("weapon_smokegrenade"); + GetTask ()->time = GetWorldTime () + MAX_GRENADE_TIMER; + } + else + GetTask ()->time = GetWorldTime () + 0.1; + } + else if (!(pev->oldbuttons & IN_ATTACK)) + pev->button |= IN_ATTACK; + break; + + // bot helps human player (or other bot) to get somewhere + case TASK_DOUBLEJUMP: + if (FNullEnt (m_doubleJumpEntity) || !IsAlive (m_doubleJumpEntity) || (m_aimFlags & AIM_ENEMY) || (m_travelStartIndex != -1 && GetTask ()->time + (g_waypoint->GetTravelTime (pev->maxspeed, g_waypoint->GetPath (m_travelStartIndex)->origin, m_doubleJumpOrigin) + 11.0) < GetWorldTime ())) + { + ResetDoubleJumpState (); + break; + } + m_aimFlags |= AIM_NAVPOINT; + + if (m_jumpReady) + { + m_moveToGoal = false; + m_checkTerrain = false; + + m_navTimeset = GetWorldTime (); + m_moveSpeed = 0.0; + m_strafeSpeed = 0.0; + + if (m_duckForJump < GetWorldTime ()) + pev->button |= IN_DUCK; + + TraceResult tr; + MakeVectors (nullvec); + + Vector dest = EyePosition () + g_pGlobals->v_forward * 500; + dest.z = 180.0; + + TraceLine (EyePosition (), dest, false, true, GetEntity (), &tr); + + if ((tr.flFraction < 1.0) && (tr.pHit == m_doubleJumpEntity)) + { + if (m_doubleJumpEntity->v.button & IN_JUMP) + { + m_duckForJump = GetWorldTime () + g_randGen.Float (3.0, 5.0); + GetTask ()->time = GetWorldTime (); + } + } + break; + } + + if (m_currentWaypointIndex == m_prevGoalIndex) + { + m_waypointOrigin = m_doubleJumpOrigin; + m_destOrigin = m_doubleJumpOrigin; + } + + if (DoWaypointNav ()) // reached destination? + GetTask ()->data = -1; + + if (!GoalIsValid ()) // didn't choose goal waypoint yet? + { + DeleteSearchNodes (); + + destIndex = g_waypoint->FindNearest (m_doubleJumpOrigin); + + if (destIndex >= 0 && destIndex < g_numWaypoints) + { + m_prevGoalIndex = destIndex; + GetTask ()->data = destIndex; + m_travelStartIndex = m_currentWaypointIndex; + + // Always take the shortest path + FindShortestPath (m_currentWaypointIndex, destIndex); + + if (m_currentWaypointIndex == destIndex) + m_jumpReady = true; + } + else + ResetDoubleJumpState (); + } + break; + + // escape from bomb behaviour + case TASK_ESCAPEFROMBOMB: + m_aimFlags |= AIM_NAVPOINT; + + if (!g_bombPlanted) + TaskComplete (); + + if (IsShieldDrawn ()) + pev->button |= IN_ATTACK2; + + if (m_currentWeapon != WEAPON_KNIFE && GetNearbyEnemiesNearPosition (pev->origin, 9999) == 0) + SelectWeaponByName ("weapon_knife"); + + if (DoWaypointNav ()) // reached destination? + { + TaskComplete (); // we're done + + // press duck button if we still have some enemies + if (GetNearbyEnemiesNearPosition (pev->origin, 2048)) + m_campButtons = IN_DUCK; + + // we're reached destination point so just sit down and camp + StartTask (TASK_CAMP, TASKPRI_CAMP, -1, GetWorldTime () + 10.0, true); + } + else if (!GoalIsValid ()) // didn't choose goal waypoint yet? + { + DeleteSearchNodes (); + + int lastSelectedGoal = -1; + float safeRadius = g_randGen.Float (1024.0, 2048.0), minPathDistance = 4096.0; + + for (int i = 0; i < g_numWaypoints; i++) + { + if ((g_waypoint->GetPath (i)->origin - g_waypoint->GetBombPosition ()).GetLength () < safeRadius) + continue; + + float pathDistance = g_waypoint->GetPathDistance (m_currentWaypointIndex, i); + + if (minPathDistance > pathDistance) + { + minPathDistance = pathDistance; + lastSelectedGoal = i; + } + } + + if (lastSelectedGoal < 0) + lastSelectedGoal = g_waypoint->FindFarest (pev->origin, safeRadius); + + m_prevGoalIndex = lastSelectedGoal; + GetTask ()->data = lastSelectedGoal; + + FindShortestPath (m_currentWaypointIndex, lastSelectedGoal); + } + break; + + // shooting breakables in the way action + case TASK_SHOOTBREAKABLE: + m_aimFlags |= AIM_OVERRIDE; + + // Breakable destroyed? + if (FNullEnt (FindBreakable ())) + { + TaskComplete (); + break; + } + pev->button |= m_campButtons; + + m_checkTerrain = false; + m_moveToGoal = false; + m_navTimeset = GetWorldTime (); + + src = m_breakable; + m_camp = src; + + // is bot facing the breakable? + if (GetShootingConeDeviation (GetEntity (), &src) >= 0.90) + { + m_moveSpeed = 0.0; + m_strafeSpeed = 0.0; + + if (m_currentWeapon == WEAPON_KNIFE) + SelectBestWeapon (); + + m_wantsToFire = true; + } + else + { + m_checkTerrain = true; + m_moveToGoal = true; + } + break; + + // picking up items and stuff behaviour + case TASK_PICKUPITEM: + if (FNullEnt (m_pickupItem)) + { + m_pickupItem = NULL; + TaskComplete (); + + break; + } + + destination = GetEntityOrigin (m_pickupItem); + m_destOrigin = destination; + m_entity = destination; + + // find the distance to the item + float itemDistance = (destination - pev->origin).GetLength (); + + switch (m_pickupType) + { + case PICKUP_WEAPON: + m_aimFlags |= AIM_NAVPOINT; + + // near to weapon? + if (itemDistance < 50) + { + for (i = 0; i < 7; i++) + { + if (strcmp (g_weaponSelect[i].modelName, STRING (m_pickupItem->v.model) + 9) == 0) + break; + } + + if (i < 7) + { + // secondary weapon. i.e., pistol + int weaponID = 0; + + for (i = 0; i < 7; i++) + { + if (pev->weapons & (1 << g_weaponSelect[i].id)) + weaponID = i; + } + + if (weaponID > 0) + { + SelectWeaponbyNumber (weaponID); + FakeClientCommand (GetEntity (), "drop"); + + if (HasShield ()) // If we have the shield... + FakeClientCommand (GetEntity (), "drop"); // discard both shield and pistol + } + EquipInBuyzone (0); + } + else + { + // primary weapon + int weaponID = GetHighestWeapon (); + + if ((weaponID > 6) || HasShield ()) + { + SelectWeaponbyNumber (weaponID); + FakeClientCommand (GetEntity (), "drop"); + } + EquipInBuyzone (0); + } + CheckSilencer (); // check the silencer + } + break; + + case PICKUP_SHIELD: + m_aimFlags |= AIM_NAVPOINT; + + if (HasShield ()) + { + m_pickupItem = NULL; + break; + } + else if (itemDistance < 50) // near to shield? + { + // get current best weapon to check if it's a primary in need to be dropped + int weaponID = GetHighestWeapon (); + + if (weaponID > 6) + { + SelectWeaponbyNumber (weaponID); + FakeClientCommand (GetEntity (), "drop"); + } + } + break; + + case PICKUP_PLANTED_C4: + m_aimFlags |= AIM_ENTITY; + + if (team == TEAM_CF && itemDistance < 55) + { + ChatterMessage (Chatter_DefusingC4); + + // notify team of defusing + if (GetNearbyFriendsNearPosition (pev->origin, 9999) < 2) + RadioMessage (Radio_NeedBackup); + + m_moveToGoal = false; + m_checkTerrain = false; + + m_moveSpeed = 0; + m_strafeSpeed = 0; + + StartTask (TASK_DEFUSEBOMB, TASKPRI_DEFUSEBOMB, -1, 0.0, false); + } + break; + + case PICKUP_HOSTAGE: + m_aimFlags |= AIM_ENTITY; + src = EyePosition (); + + if (!IsAlive (m_pickupItem)) + { + // don't pickup dead hostages + m_pickupItem = NULL; + TaskComplete (); + + break; + } + + if (itemDistance < 50) + { + float angleToEntity = InFieldOfView (destination - src); + + if (angleToEntity <= 10) // bot faces hostage? + { + // use game dll function to make sure the hostage is correctly 'used' + MDLL_Use (m_pickupItem, GetEntity ()); + + if (g_randGen.Long (0, 100) < 80) + ChatterMessage (Chatter_UseHostage); + + for (i = 0; i < MAX_HOSTAGES; i++) + { + if (FNullEnt (m_hostages[i])) // store pointer to hostage so other bots don't steal from this one or bot tries to reuse it + { + m_hostages[i] = m_pickupItem; + m_pickupItem = NULL; + + break; + } + } + } + m_lastCollTime = GetWorldTime () + 0.1; // also don't consider being stuck + } + break; + + case PICKUP_DEFUSEKIT: + m_aimFlags |= AIM_NAVPOINT; + + if (m_hasDefuser) + { + m_pickupItem = NULL; + m_pickupType = PICKUP_NONE; + } + break; + + case PICKUP_BUTTON: + m_aimFlags |= AIM_ENTITY; + + if (FNullEnt (m_pickupItem) || m_buttonPushTime < GetWorldTime ()) // it's safer... + { + TaskComplete (); + m_pickupType = PICKUP_NONE; + + break; + } + + // find angles from bot origin to entity... + src = EyePosition (); + float angleToEntity = InFieldOfView (destination - src); + + if (itemDistance < 90) // near to the button? + { + m_moveSpeed = 0.0; + m_strafeSpeed = 0.0; + m_moveToGoal = false; + m_checkTerrain = false; + + if (angleToEntity <= 10) // facing it directly? + { + MDLL_Use (m_pickupItem, GetEntity ()); + + m_pickupItem = NULL; + m_pickupType = PICKUP_NONE; + m_buttonPushTime = GetWorldTime () + 3.0; + + TaskComplete (); + } + } + break; + } + break; + } +} + +void Bot::BotAI (void) +{ + // this function gets called each frame and is the core of all bot ai. from here all other subroutines are called + + float movedDistance; // length of different vector (distance bot moved) + TraceResult tr; + + int team = GetTeam (GetEntity ()); + + // switch to knife if time to do this + if (m_checkKnifeSwitch && m_buyingFinished && m_spawnTime + g_randGen.Float (4.0, 6.5) < GetWorldTime ()) + { + if (g_randGen.Long (1, 100) < 2 && yb_spraypaints.GetBool ()) + StartTask (TASK_SPRAY, TASKPRI_SPRAYLOGO, -1, GetWorldTime () + 1.0, false); + + if (m_skill > 75 && g_randGen.Long (0, 100) < (m_personality == PERSONALITY_RUSHER ? 99 : 50) && !m_isReloading && (g_mapType & (MAP_CS | MAP_DE | MAP_ES | MAP_AS))) + SelectWeaponByName ("weapon_knife"); + + m_checkKnifeSwitch = false; + + if (g_randGen.Long (0, 100) < yb_user_follow_percent.GetInt () && FNullEnt (m_targetEntity) && !m_isLeader && !(pev->weapons & (1 << WEAPON_C4))) + AttachToUser (); + } + + // check if we already switched weapon mode + if (m_checkWeaponSwitch && m_buyingFinished && m_spawnTime + g_randGen.Float (2.0, 3.5) < GetWorldTime ()) + { + if (HasShield () && IsShieldDrawn ()) + pev->button |= IN_ATTACK2; + else + { + switch (m_currentWeapon) + { + case WEAPON_M4A1: + case WEAPON_USP: + CheckSilencer (); + break; + + case WEAPON_FAMAS: + case WEAPON_GLOCK: + if (g_randGen.Long (0, 100) < 50) + pev->button |= IN_ATTACK2; + break; + } + } + + // select a leader bot for this team + SelectLeaderEachTeam (team); + m_checkWeaponSwitch = false; + + if (m_isLeader && m_moveToC4) + InstantChatterMessage (Chatter_CoverMe); + + if (IsBehindSmokeClouds (GetEntity ())) + InstantChatterMessage (Chatter_BehindSmoke); + } + // warning: the following timers aren't frame independent so it varies on slower/faster computers + + // increase reaction time + m_actualReactionTime += 0.3; + + if (m_actualReactionTime > m_idealReactionTime) + m_actualReactionTime = m_idealReactionTime; + + // bot could be blinded by flashbang or smoke, recover from it + m_viewDistance += 3.0; + + if (m_viewDistance > m_maxViewDistance) + m_viewDistance = m_maxViewDistance; + + if (m_blindTime > GetWorldTime ()) + m_maxViewDistance = 4096.0; + + m_moveSpeed = pev->maxspeed; + + if (m_prevTime <= GetWorldTime ()) + { + // see how far bot has moved since the previous position... + movedDistance = (m_prevOrigin - pev->origin).GetLength (); + + // save current position as previous + m_prevOrigin = pev->origin; + m_prevTime = GetWorldTime () + 0.2; + } + else + movedDistance = 2.0; + + // if there's some radio message to respond, check it + if (m_radioOrder != 0) + CheckRadioCommands (); + + // do all sensing, calculate/filter all actions here + SetConditions (); + + // some stuff required by by chatter engine + if ((m_states & STATE_SEEING_ENEMY) && !FNullEnt (m_enemy)) + { + if (g_randGen.Long (0, 100) < 45 && GetNearbyFriendsNearPosition (pev->origin, 512) == 0 && (m_enemy->v.weapons & (1 << WEAPON_C4))) + ChatterMessage (Chatter_SpotTheBomber); + + if (g_randGen.Long (0, 100) < 45 && GetTeam (GetEntity ()) == TEAM_TF && GetNearbyFriendsNearPosition (pev->origin, 512) == 0 && *g_engfuncs.pfnInfoKeyValue (g_engfuncs.pfnGetInfoKeyBuffer (m_enemy), "model") == 'v') + ChatterMessage (Chatter_VIPSpotted); + + if (g_randGen.Long (0, 100) < 50 && GetNearbyFriendsNearPosition (pev->origin, 450) == 0 && GetTeam (m_enemy) != GetTeam (GetEntity ()) && IsGroupOfEnemies (m_enemy->v.origin, 2, 384)) + ChatterMessage (Chatter_ScaredEmotion); + + if (g_randGen.Long (0, 100) < 40 && GetNearbyFriendsNearPosition (pev->origin, 1024) == 0 && ((m_enemy->v.weapons & (1 << WEAPON_AWP)) || (m_enemy->v.weapons & (1 << WEAPON_SCOUT)) || (m_enemy->v.weapons & (1 << WEAPON_G3SG1)) || (m_enemy->v.weapons & (1 << WEAPON_SG550)))) + ChatterMessage (Chatter_SniperWarning); + } + + // if bot is trapped under shield yell for help ! + if (GetTaskId () == TASK_CAMP && HasShield() && IsShieldDrawn () && GetNearbyEnemiesNearPosition (pev->origin, 650) >= 2 && IsEnemyViewable(m_enemy)) + InstantChatterMessage(Chatter_Pinned_Down); + + // if bomb planted warn teammates ! + if (g_canSayBombPlanted && g_bombPlanted && GetTeam (GetEntity()) == TEAM_CF) + { + g_canSayBombPlanted = false; + ChatterMessage (Chatter_GottaFindTheBomb); + } + + Vector src, destination; + + m_checkTerrain = true; + m_moveToGoal = true; + m_wantsToFire = false; + + AvoidGrenades (); // avoid flyings grenades + m_isUsingGrenade = false; + + RunTask (); // execute current task + ChooseAimDirection (); // choose aim direction + FacePosition (); // and turn to chosen aim direction + + // the bots wants to fire at something? + if (m_wantsToFire && !m_isUsingGrenade && m_shootTime <= GetWorldTime ()) + FireWeapon (); // if bot didn't fire a bullet try again next frame + + // check for reloading + if (m_reloadCheckTime <= GetWorldTime ()) + CheckReload (); + + // set the reaction time (surprise momentum) different each frame according to skill + m_idealReactionTime = g_randGen.Float (g_skillTab[m_skill / 20].minSurpriseTime, g_skillTab[m_skill / 20].maxSurpriseTime); + + // calculate 2 direction vectors, 1 without the up/down component + Vector directionOld = m_destOrigin - (pev->origin + pev->velocity * m_frameInterval); + Vector directionNormal = directionOld.Normalize (); + + Vector direction = directionNormal; + directionNormal.z = 0.0; + + m_moveAngles = directionOld.ToAngles (); + + m_moveAngles.ClampAngles (); + m_moveAngles.x *= -1.0; // invert for engine + +#if 0 + if (yb_hardcore_mode && GetTaskId () == TASK_NORMAL && ((m_aimFlags & AIM_ENEMY) || (m_states & STATE_SEEING_ENEMY)) && !IsOnLadder ()) + CombatFight (); +#else + if (yb_hardcore_mode.GetBool () && ((m_aimFlags & AIM_ENEMY) || (m_states & (STATE_SEEING_ENEMY | STATE_SUSPECT_ENEMY)) || (GetTaskId () == TASK_SEEKCOVER && (m_isReloading || m_isVIP))) && !yb_jasonmode.GetBool () && GetTaskId () != TASK_CAMP && !IsOnLadder ()) + { + m_moveToGoal = false; // don't move to goal + m_navTimeset = GetWorldTime (); + + if (IsValidPlayer (m_enemy)) + CombatFight (); + } +#endif + // check if we need to escape from bomb + if ((g_mapType & MAP_DE) && g_bombPlanted && m_notKilled && GetTaskId () != TASK_ESCAPEFROMBOMB && GetTaskId () != TASK_CAMP && OutOfBombTimer ()) + { + TaskComplete (); // complete current task + + // then start escape from bomb immidiate + StartTask (TASK_ESCAPEFROMBOMB, TASKPRI_ESCAPEFROMBOMB, -1, 0.0, true); + } + + // allowed to move to a destination position? + if (m_moveToGoal) + { + GetValidWaypoint (); + + // Press duck button if we need to + if ((m_currentPath->flags & FLAG_CROUCH) && !(m_currentPath->flags & FLAG_CAMP)) + pev->button |= IN_DUCK; + + m_timeWaypointMove = GetWorldTime (); + + if (IsInWater ()) // special movement for swimming here + { + // check if we need to go forward or back press the correct buttons + if (InFieldOfView (m_destOrigin - EyePosition ()) > 90) + pev->button |= IN_BACK; + else + pev->button |= IN_FORWARD; + + if (m_moveAngles.x > 60.0) + pev->button |= IN_DUCK; + else if (m_moveAngles.x < -60.0) + pev->button |= IN_JUMP; + } + } + + if (m_checkTerrain) // are we allowed to check blocking terrain (and react to it)? + { + m_isStuck = false; + edict_t *ent = NULL; + + // Test if there's a shootable breakable in our way + if (!FNullEnt (ent = FindBreakable ())) + { + m_breakableEntity = ent; + m_campButtons = pev->button & IN_DUCK; + + StartTask (TASK_SHOOTBREAKABLE, TASKPRI_SHOOTBREAKABLE, -1, 0.0, false); + } + else + { + ent = NULL; + edict_t *pentNearest = NULL; + + if (FindNearestPlayer (reinterpret_cast (&pentNearest), GetEntity (), pev->maxspeed, true, false, true, true)) // found somebody? + { + MakeVectors (m_moveAngles); // use our movement angles + + // try to predict where we should be next frame + Vector moved = pev->origin + g_pGlobals->v_forward * m_moveSpeed * m_frameInterval; + moved = moved + g_pGlobals->v_right * m_strafeSpeed * m_frameInterval; + moved = moved + pev->velocity * m_frameInterval; + + float nearestDistance = (pentNearest->v.origin - pev->origin).GetLength2D (); + float movedDistance = (pentNearest->v.origin - moved).GetLength2D (); + float nextFrameDistance = ((pentNearest->v.origin + pentNearest->v.velocity * m_frameInterval) - pev->origin).GetLength2D (); + + // is player that near now or in future that we need to steer away? + if (movedDistance <= 48.0 || (nearestDistance <= 56.0 && nextFrameDistance < nearestDistance)) + { + // to start strafing, we have to first figure out if the target is on the left side or right side + Vector dirToPoint = (pev->origin - pentNearest->v.origin).SkipZ (); + + if ((dirToPoint | g_pGlobals->v_right.SkipZ ()) > 0.0) + SetStrafeSpeed (directionNormal, pev->maxspeed); + else + SetStrafeSpeed (directionNormal, -pev->maxspeed); + + ResetCollideState (); + + if (nearestDistance < 56.0 && (dirToPoint | g_pGlobals->v_forward.SkipZ ()) < 0.0) + m_moveSpeed = -pev->maxspeed; + } + } + + // Standing still, no need to check? + // FIXME: doesn't care for ladder movement (handled separately) should be included in some way + if ((m_moveSpeed >= 10 || m_strafeSpeed >= 10) && m_lastCollTime < GetWorldTime ()) + { + if (movedDistance < 2.0 && m_prevSpeed >= 1.0) // didn't we move enough previously? + { + // Then consider being stuck + m_prevTime = GetWorldTime (); + m_isStuck = true; + + if (m_firstCollideTime == 0.0) + m_firstCollideTime = GetWorldTime () + 0.2; + } + else // not stuck yet + { + // test if there's something ahead blocking the way + if (CantMoveForward (directionNormal, &tr) && !IsOnLadder ()) + { + if (m_firstCollideTime == 0.0) + m_firstCollideTime = GetWorldTime () + 0.2; + + else if (m_firstCollideTime <= GetWorldTime ()) + m_isStuck = true; + } + else + m_firstCollideTime = 0.0; + } + + if (!m_isStuck) // not stuck? + { + if (m_probeTime + 0.5 < GetWorldTime ()) + ResetCollideState (); // reset collision memory if not being stuck for 0.5 secs + else + { + // remember to keep pressing duck if it was necessary ago + if (m_collideMoves[m_collStateIndex] == COLLISION_DUCK && IsOnFloor () || IsInWater ()) + pev->button |= IN_DUCK; + } + } + else // bot is stuck! + { + // not yet decided what to do? + if (m_collisionState == COLLISION_NOTDECICED) + { + char bits = 0; + + if (IsOnLadder ()) + bits = PROBE_STRAFE; + else if (IsInWater ()) + bits = (PROBE_JUMP | PROBE_STRAFE); + else + bits = ((g_randGen.Long (0, 10) > 7 ? PROBE_JUMP : 0) | PROBE_STRAFE | PROBE_DUCK); + + // collision check allowed if not flying through the air + if (IsOnFloor () || IsOnLadder () || IsInWater ()) + { + char state[8]; + int i = 0; + + // first 4 entries hold the possible collision states + state[i++] = COLLISION_JUMP; + state[i++] = COLLISION_DUCK; + state[i++] = COLLISION_STRAFELEFT; + state[i++] = COLLISION_STRAFERIGHT; + + // now weight all possible states + if (bits & PROBE_JUMP) + { + state[i] = 0; + + if (CanJumpUp (directionNormal)) + state[i] += 10; + + if (m_destOrigin.z >= pev->origin.z + 18.0) + state[i] += 5; + + if (EntityIsVisible (m_destOrigin)) + { + MakeVectors (m_moveAngles); + + src = EyePosition (); + src = src + (g_pGlobals->v_right * 15); + + TraceLine (src, m_destOrigin, true, true, GetEntity (), &tr); + + if (tr.flFraction >= 1.0) + { + src = EyePosition (); + src = src - (g_pGlobals->v_right * 15); + + TraceLine (src, m_destOrigin, true, true, GetEntity (), &tr); + + if (tr.flFraction >= 1.0) + state[i] += 5; + } + } + if (pev->flags & FL_DUCKING) + src = pev->origin; + else + src = pev->origin + Vector (0, 0, -17); + + destination = src + directionNormal * 30; + TraceLine (src, destination, true, true, GetEntity (), &tr); + + if (tr.flFraction != 1.0) + state[i] += 10; + } + else + state[i] = 0; + i++; + + if (bits & PROBE_DUCK) + { + state[i] = 0; + + if (CanDuckUnder (directionNormal)) + state[i] += 10; + + if ((m_destOrigin.z + 36.0 <= pev->origin.z) && EntityIsVisible (m_destOrigin)) + state[i] += 5; + } + else + state[i] = 0; + i++; + + if (bits & PROBE_STRAFE) + { + state[i] = 0; + state[i + 1] = 0; + + // to start strafing, we have to first figure out if the target is on the left side or right side + MakeVectors (m_moveAngles); + + Vector dirToPoint = (pev->origin - m_destOrigin).Normalize2D (); + Vector rightSide = g_pGlobals->v_right.Normalize2D (); + + bool dirRight = false; + bool dirLeft = false; + bool blockedLeft = false; + bool blockedRight = false; + + if ((dirToPoint | rightSide) > 0) + dirRight = true; + else + dirLeft = true; + + if (m_moveSpeed > 0) + direction = g_pGlobals->v_forward; + else + direction = -g_pGlobals->v_forward; + + // now check which side is blocked + src = pev->origin + (g_pGlobals->v_right * 32); + destination = src + (direction * 32); + + TraceHull (src, destination, true, head_hull, GetEntity (), &tr); + + if (tr.flFraction != 1.0) + blockedRight = true; + + src = pev->origin - (g_pGlobals->v_right * 32); + destination = src + (direction * 32); + + TraceHull (src, destination, true, head_hull, GetEntity (), &tr); + + if (tr.flFraction != 1.0) + blockedLeft = true; + + if (dirLeft) + state[i] += 5; + else + state[i] -= 5; + + if (blockedLeft) + state[i] -= 5; + + i++; + + if (dirRight) + state[i] += 5; + else + state[i] -= 5; + + if (blockedRight) + state[i] -= 5; + } + else + { + state[i] = 0; + i++; + + state[i] = 0; + } + + // weighted all possible moves, now sort them to start with most probable + int temp = 0; + bool isSorting = false; + + do + { + isSorting = false; + for (i = 0; i < 3; i++) + { + if (state[i + 4] < state[i + 5]) + { + temp = state[i]; + + state[i] = state[i + 1]; + state[i + 1] = temp; + + temp = state[i + 4]; + + state[i + 4] = state[i + 5]; + state[i + 5] = temp; + + isSorting = true; + } + } + } while (isSorting); + + for (i = 0; i < 4; i++) + m_collideMoves[i] = state[i]; + + m_collideTime = GetWorldTime (); + m_probeTime = GetWorldTime () + 0.5; + m_collisionProbeBits = bits; + m_collisionState = COLLISION_PROBING; + m_collStateIndex = 0; + } + } + + if (m_collisionState == COLLISION_PROBING) + { + if (m_probeTime < GetWorldTime ()) + { + m_collStateIndex++; + m_probeTime = GetWorldTime () + 0.5; + + if (m_collStateIndex > 4) + { + m_navTimeset = GetWorldTime () - 5.0; + ResetCollideState (); + } + } + + if (m_collStateIndex <= 4) + { + switch (m_collideMoves[m_collStateIndex]) + { + case COLLISION_JUMP: + if (IsOnFloor () || IsInWater ()) + pev->button |= IN_JUMP; + break; + + case COLLISION_DUCK: + if (IsOnFloor () || IsInWater ()) + pev->button |= IN_DUCK; + break; + + case COLLISION_STRAFELEFT: + pev->button |= IN_MOVELEFT; + SetStrafeSpeed (directionNormal, -pev->maxspeed); + break; + + case COLLISION_STRAFERIGHT: + pev->button |= IN_MOVERIGHT; + SetStrafeSpeed (directionNormal, pev->maxspeed); + break; + } + } + } + } + } + } + } + + // must avoid a grenade? + if (m_needAvoidGrenade != 0) + { + // Don't duck to get away faster + pev->button &= ~IN_DUCK; + + m_moveSpeed = -pev->maxspeed; + m_strafeSpeed = pev->maxspeed * m_needAvoidGrenade; + } + + // time to reach waypoint + if (m_navTimeset + GetEstimatedReachTime () < GetWorldTime () && FNullEnt (m_enemy)) + { + GetValidWaypoint (); + + // clear these pointers, bot mingh be stuck getting to them + if (!FNullEnt (m_pickupItem) && !m_hasProgressBar) + m_itemIgnore = m_pickupItem; + + m_pickupItem = NULL; + m_breakableEntity = NULL; + m_itemCheckTime = GetWorldTime () + 5.0; + m_pickupType = PICKUP_NONE; + } + + if (m_duckTime > GetWorldTime ()) + pev->button |= IN_DUCK; + + if (pev->button & IN_JUMP) + m_jumpTime = GetWorldTime (); + + if (m_jumpTime + 0.85 > GetWorldTime ()) + { + if (!IsOnFloor () && !IsInWater ()) + pev->button |= IN_DUCK; + } + + if (!(pev->button & (IN_FORWARD | IN_BACK))) + { + if (m_moveSpeed > 0) + pev->button |= IN_FORWARD; + else if (m_moveSpeed < 0) + pev->button |= IN_BACK; + } + + if (!(pev->button & (IN_MOVELEFT | IN_MOVERIGHT))) + { + if (m_strafeSpeed > 0) + pev->button |= IN_MOVERIGHT; + else if (m_strafeSpeed < 0) + pev->button |= IN_MOVELEFT; + } + + static float timeDebugUpdate = 0.0; + + if (!FNullEnt (g_hostEntity) && yb_debug.GetInt () >= 1) + { + int specIndex = g_hostEntity->v.iuser2; + + if (specIndex == ENTINDEX (GetEntity ())) + { + static int index, goal, taskID; + + if (!m_tasks.IsEmpty ()) + { + if (taskID != GetTaskId () || index != m_currentWaypointIndex || goal != GetTask ()->data || timeDebugUpdate < GetWorldTime ()) + { + taskID = GetTaskId (); + index = m_currentWaypointIndex; + goal = GetTask ()->data; + + char taskName [80]; + memset (taskName, 0, sizeof (taskName)); + + switch (taskID) + { + case TASK_NORMAL: + sprintf (taskName, "Normal"); + break; + + case TASK_PAUSE: + sprintf (taskName, "Pause"); + break; + + case TASK_MOVETOPOSITION: + sprintf (taskName, "MoveToPosition"); + break; + + case TASK_FOLLOWUSER: + sprintf (taskName, "FollowUser"); + break; + + case TASK_WAITFORGO: + sprintf (taskName, "WaitForGo"); + break; + + case TASK_PICKUPITEM: + sprintf (taskName, "PickupItem"); + break; + + case TASK_CAMP: + sprintf (taskName, "Camp"); + break; + + case TASK_PLANTBOMB: + sprintf (taskName, "PlantBomb"); + break; + + case TASK_DEFUSEBOMB: + sprintf (taskName, "DefuseBomb"); + break; + + case TASK_ATTACK: + sprintf (taskName, "AttackEnemy"); + break; + + case TASK_HUNTENEMY: + sprintf (taskName, "HuntEnemy"); + break; + + case TASK_SEEKCOVER: + sprintf (taskName, "SeekCover"); + break; + + case TASK_THROWHEGRENADE: + sprintf (taskName, "ThrowExpGrenade"); + break; + + case TASK_THROWFLASHBANG: + sprintf (taskName, "ThrowFlashGrenade"); + break; + + case TASK_THROWSMOKE: + sprintf (taskName, "ThrowSmokeGrenade"); + break; + + case TASK_DOUBLEJUMP: + sprintf (taskName, "PerformDoubleJump"); + break; + + case TASK_ESCAPEFROMBOMB: + sprintf (taskName, "EscapeFromBomb"); + break; + + case TASK_SHOOTBREAKABLE: + sprintf (taskName, "ShootBreakable"); + break; + + case TASK_HIDE: + sprintf (taskName, "Hide"); + break; + + case TASK_BLINDED: + sprintf (taskName, "Blinded"); + break; + + case TASK_SPRAY: + sprintf (taskName, "SprayLogo"); + break; + } + + char enemyName[80], weaponName[80], aimFlags[32], botType[32]; + + if (!FNullEnt (m_enemy)) + strcpy (enemyName, STRING (m_enemy->v.netname)); + else if (!FNullEnt (m_lastEnemy)) + { + strcpy (enemyName, " (L)"); + strcat (enemyName, STRING (m_lastEnemy->v.netname)); + } + else + strcpy (enemyName, " (null)"); + + char pickupName[80]; + memset (pickupName, 0, sizeof (pickupName)); + + if (!FNullEnt (m_pickupItem)) + strcpy (pickupName, STRING (m_pickupItem->v.classname)); + else + strcpy (pickupName, " (null)"); + + WeaponSelect *selectTab = &g_weaponSelect[0]; + char weaponCount = 0; + + while (m_currentWeapon != selectTab->id && weaponCount < NUM_WEAPONS) + { + selectTab++; + weaponCount++; + } + + // set the aim flags + sprintf (aimFlags, "%s%s%s%s%s%s%s%s", + m_aimFlags & AIM_NAVPOINT ? " NavPoint" : "", + m_aimFlags & AIM_CAMP ? " CampPoint" : "", + m_aimFlags & AIM_PREDICT_ENEMY ? " PredictEnemy" : "", + m_aimFlags & AIM_LAST_ENEMY ? " LastEnemy" : "", + m_aimFlags & AIM_ENTITY ? " Entity" : "", + m_aimFlags & AIM_ENEMY ? " Enemy" : "", + m_aimFlags & AIM_GRENADE ? " Grenade" : "", + m_aimFlags & AIM_OVERRIDE ? " Override" : ""); + + // set the bot type + sprintf (botType, "%s%s%s", m_personality == PERSONALITY_RUSHER ? " Rusher" : "", + m_personality == PERSONALITY_CAREFUL ? " Careful" : "", + m_personality == PERSONALITY_NORMAL ? " Normal" : ""); + + if (weaponCount >= NUM_WEAPONS) + { + // prevent printing unknown message from known weapons + switch (m_currentWeapon) + { + case WEAPON_EXPLOSIVE: + sprintf (weaponName, "weapon_hegrenade"); + break; + + case WEAPON_FLASHBANG: + sprintf (weaponName, "weapon_flashbang"); + break; + + case WEAPON_SMOKE: + sprintf (weaponName, "weapon_smokegrenade"); + break; + + case WEAPON_C4: + sprintf (weaponName, "weapon_c4"); + break; + + default: + sprintf (weaponName, "Unknown! (%d)", m_currentWeapon); + } + } + else + strcpy (weaponName, selectTab->weaponName); + + char outputBuffer[512]; + memset (outputBuffer, 0, sizeof (outputBuffer)); + + sprintf (outputBuffer, "\n\n\n\n%s (H:%.1f/A:%.1f)- Task: %d=%s Desire:%.02f\nItem: %s Clip: %d Ammo: %d%s Money: %d AimFlags: %s\nSP=%.02f SSP=%.02f I=%d PG=%d G=%d T: %.02f MT: %d\nEnemy=%s Pickup=%s Type=%s\n", STRING (pev->netname), pev->health, pev->armorvalue, taskID, taskName, GetTask ()->desire, &weaponName[7], GetAmmoInClip (), GetAmmo (), m_isReloading ? " (R)" : "", m_moneyAmount, aimFlags, m_moveSpeed, m_strafeSpeed, index, m_prevGoalIndex, goal, m_navTimeset - GetWorldTime (), pev->movetype, enemyName, pickupName, botType); + + MESSAGE_BEGIN (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, NULL, g_hostEntity); + WRITE_BYTE (TE_TEXTMESSAGE); + WRITE_BYTE (1); + WRITE_SHORT (FixedSigned16 (-1, 1 << 13)); + WRITE_SHORT (FixedSigned16 (0, 1 << 13)); + WRITE_BYTE (0); + WRITE_BYTE (GetTeam (GetEntity ()) == TEAM_CF ? 0 : 255); + WRITE_BYTE (100); + WRITE_BYTE (GetTeam (GetEntity ()) != TEAM_CF ? 0 : 255); + WRITE_BYTE (0); + WRITE_BYTE (255); + WRITE_BYTE (255); + WRITE_BYTE (255); + WRITE_BYTE (0); + WRITE_SHORT (FixedUnsigned16 (0, 1 << 8)); + WRITE_SHORT (FixedUnsigned16 (0, 1 << 8)); + WRITE_SHORT (FixedUnsigned16 (1.0, 1 << 8)); + WRITE_STRING (const_cast (&outputBuffer[0])); + MESSAGE_END (); + + timeDebugUpdate = GetWorldTime () + 1.0; + } + + // green = destination origin + // blue = ideal angles + // red = view angles + + DrawArrow (g_hostEntity, EyePosition (), m_destOrigin, 10, 0, 0, 255, 0, 250, 5, 1); + + MakeVectors (m_idealAngles); + DrawArrow (g_hostEntity, EyePosition (), EyePosition () + (g_pGlobals->v_forward * 300), 10, 0, 0, 0, 255, 250, 5, 1); + + MakeVectors (pev->v_angle); + DrawArrow (g_hostEntity, EyePosition (), EyePosition () + (g_pGlobals->v_forward * 300), 10, 0, 255, 0, 0, 250, 5, 1); + + // now draw line from source to destination + PathNode *node = &m_navNode[0]; + + while (node != NULL) + { + Vector src = g_waypoint->GetPath (node->index)->origin; + node = node->next; + + if (node != NULL) + { + Vector dest = g_waypoint->GetPath (node->index)->origin; + DrawArrow (g_hostEntity, src, dest, 15, 0, 255, 100, 55, 200, 5, 1); + } + } + } + } + } + + // save the previous speed (for checking if stuck) + m_prevSpeed = fabsf (m_moveSpeed); + m_lastDamageType = -1; // reset damage + + pev->angles.ClampAngles (); + pev->v_angle.ClampAngles (); +} + +bool Bot::HasHostage (void) +{ + for (int i = 0; i < MAX_HOSTAGES; i++) + { + if (!FNullEnt (m_hostages[i])) + { + // don't care about dead hostages + if (m_hostages[i]->v.health <= 0 || (pev->origin - m_hostages[i]->v.origin).GetLength () > 600) + { + m_hostages[i] = NULL; + continue; + } + return true; + } + } + return false; +} + +void Bot::ResetCollideState (void) +{ + m_collideTime = 0.0; + m_probeTime = 0.0; + + m_collisionProbeBits = 0; + m_collisionState = COLLISION_NOTDECICED; + m_collStateIndex = 0; + + for (int i = 0; i < 4; i++) + m_collideMoves[i] = 0; +} + +int Bot::GetAmmo (void) +{ + if (g_weaponDefs[m_currentWeapon].ammo1 == -1) + return 0; + + return m_ammo[g_weaponDefs[m_currentWeapon].ammo1]; +} + +void Bot::TakeDamage (edict_t *inflictor, int damage, int armor, int bits) +{ + // this function gets called from the network message handler, when bot's gets hurt from any + // other player. + + m_lastDamageType = bits; + + int team = GetTeam (GetEntity ()); + CollectGoalExperience (damage, team); + + if (IsValidPlayer (inflictor)) + { + if (GetTeam (inflictor) == team && yb_tkpunish.GetBool () && !g_botManager->GetBot (inflictor)) + { + // alright, die you teamkiller!!! + m_actualReactionTime = 0.0; + m_seeEnemyTime = GetWorldTime(); + m_enemy = inflictor; + m_lastEnemy = m_enemy; + m_lastEnemyOrigin = m_enemy->v.origin; + m_enemyOrigin = m_enemy->v.origin; + + ChatMessage(CHAT_TEAMATTACK); + HandleChatterMessage("#Bot_TeamAttack"); + ChatterMessage(Chatter_FriendlyFire); + } + else + { + // attacked by an enemy + if (pev->health > 60) + { + m_agressionLevel += 0.1; + + if (m_agressionLevel > 1.0) + m_agressionLevel += 1.0; + } + else + { + m_fearLevel += 0.03; + + if (m_fearLevel > 1.0) + m_fearLevel += 1.0; + } + RemoveCertainTask (TASK_CAMP); + + if (FNullEnt (m_enemy) && team != GetTeam (inflictor)) + { + m_lastEnemy = inflictor; + m_lastEnemyOrigin = inflictor->v.origin; + + // FIXME - Bot doesn't necessary sees this enemy + m_seeEnemyTime = GetWorldTime (); + } + CollectExperienceData (inflictor, armor + damage); + } + } + else // hurt by unusual damage like drowning or gas + { + // leave the camping/hiding position + if (!g_waypoint->Reachable (this, g_waypoint->FindNearest (m_destOrigin))) + { + DeleteSearchNodes (); + FindWaypoint (); + } + } +} + +void Bot::TakeBlinded (const Vector &fade, int alpha) +{ + // this function gets called by network message handler, when screenfade message get's send + // it's used to make bot blind froumd the grenade. + + extern ConVar yb_aim_method; + + if (fade.x != 255 || fade.y != 255 || fade.z != 255 || alpha <= 200 || yb_aim_method.GetInt () == 1) + return; + + m_enemy = NULL; + + m_maxViewDistance = g_randGen.Float (10, 20); + m_blindTime = GetWorldTime () + static_cast (alpha - 200) / 16; + + if (m_skill <= 80) + { + m_blindMoveSpeed = 0.0; + m_blindSidemoveSpeed = 0.0; + m_blindButton = IN_DUCK; + } + else if (m_skill < 99 || m_skill == 100) + { + m_blindMoveSpeed = -pev->maxspeed; + m_blindSidemoveSpeed = 0.0; + + float walkSpeed = GetWalkSpeed (); + + if (g_randGen.Long (0, 100) > 50) + m_blindSidemoveSpeed = walkSpeed; + else + m_blindSidemoveSpeed = -walkSpeed; + + if (pev->health < 85) + m_blindMoveSpeed = -GetWalkSpeed (); + else if (m_personality == PERSONALITY_CAREFUL) + { + m_blindMoveSpeed = 0.0; + m_blindButton = IN_DUCK; + } + else + m_blindMoveSpeed = walkSpeed; + } +} + +void Bot::CollectGoalExperience (int damage, int team) +{ + // gets called each time a bot gets damaged by some enemy. tries to achieve a statistic about most/less dangerous + // waypoints for a destination goal used for pathfinding + + if ((g_numWaypoints < 1) || g_waypointsChanged || (m_chosenGoalIndex < 0) || (m_prevGoalIndex < 0)) + return; + + // only rate goal waypoint if bot died because of the damage + // FIXME: could be done a lot better, however this cares most about damage done by sniping or really deadly weapons + if (pev->health - damage <= 0) + { + if (team == TEAM_TF) + { + int value = (g_experienceData + (m_chosenGoalIndex * g_numWaypoints) + m_prevGoalIndex)->team0Value; + value -= static_cast (pev->health / 20); + + if (value < -MAX_GOAL_VALUE) + value = -MAX_GOAL_VALUE; + + else if (value > MAX_GOAL_VALUE) + value = MAX_GOAL_VALUE; + + (g_experienceData + (m_chosenGoalIndex * g_numWaypoints) + m_prevGoalIndex)->team0Value = static_cast (value); + } + else + { + int value = (g_experienceData + (m_chosenGoalIndex * g_numWaypoints) + m_prevGoalIndex)->team1Value; + value -= static_cast (pev->health / 20); + + if (value < -MAX_GOAL_VALUE) + value = -MAX_GOAL_VALUE; + + else if (value > MAX_GOAL_VALUE) + value = MAX_GOAL_VALUE; + + (g_experienceData + (m_chosenGoalIndex * g_numWaypoints) + m_prevGoalIndex)->team1Value = static_cast (value); + } + } +} + +void Bot::CollectExperienceData (edict_t *attacker, int damage) +{ + // this function gets called each time a bot gets damaged by some enemy. sotores the damage (teamspecific) done by victim. + + if (!IsValidPlayer (attacker)) + return; + + int attackerTeam = GetTeam (attacker); + int victimTeam = GetTeam (GetEntity ()); + + if (attackerTeam == victimTeam) + return; + + // if these are bots also remember damage to rank destination of the bot + m_goalValue -= static_cast (damage); + + if (g_botManager->GetBot (attacker) != NULL) + g_botManager->GetBot (attacker)->m_goalValue += static_cast (damage); + + if (damage < 20) + return; // do not collect damage less than 20 + + int attackerIndex = g_waypoint->FindNearest (attacker->v.origin); + int victimIndex = g_waypoint->FindNearest (pev->origin); + + if (pev->health > 20) + { + if (victimTeam == TEAM_TF) + (g_experienceData + (victimIndex * g_numWaypoints) + victimIndex)->team0Damage++; + else + (g_experienceData + (victimIndex * g_numWaypoints) + victimIndex)->team1Damage++; + + if ((g_experienceData + (victimIndex * g_numWaypoints) + victimIndex)->team0Damage > MAX_DAMAGE_VALUE) + (g_experienceData + (victimIndex * g_numWaypoints) + victimIndex)->team0Damage = MAX_DAMAGE_VALUE; + + if ((g_experienceData + (victimIndex * g_numWaypoints) + victimIndex)->team1Damage > MAX_DAMAGE_VALUE) + (g_experienceData + (victimIndex * g_numWaypoints) + victimIndex)->team1Damage = MAX_DAMAGE_VALUE; + } + + float fUpdate = IsValidBot (attacker) ? 10.0 : 7.0; + + // store away the damage done + if (victimTeam == TEAM_TF) + { + int value = (g_experienceData + (victimIndex * g_numWaypoints) + attackerIndex)->team0Damage; + value += static_cast (damage / fUpdate); + + if (value > MAX_DAMAGE_VALUE) + value = MAX_DAMAGE_VALUE; + + (g_experienceData + (victimIndex * g_numWaypoints) + attackerIndex)->team0Damage = static_cast (value); + } + else + { + int value = (g_experienceData + (victimIndex * g_numWaypoints) + attackerIndex)->team1Damage; + value += static_cast (damage / fUpdate); + + if (value > MAX_DAMAGE_VALUE) + value = MAX_DAMAGE_VALUE; + + (g_experienceData + (victimIndex * g_numWaypoints) + attackerIndex)->team1Damage = static_cast (value); + } +} + +void Bot::HandleChatterMessage (const char *tempMessage) +{ + // this function is added to prevent engine crashes with: 'Message XX started, before message XX ended', or something. + + if (FStrEq (tempMessage, "#CTs_Win") && (GetTeam (GetEntity ()) == TEAM_CF)) + { + if (g_timeRoundMid > GetWorldTime ()) + ChatterMessage (Chatter_QuicklyWonTheRound); + else + ChatterMessage (Chatter_WonTheRound); + } + + if (FStrEq (tempMessage, "#Terrorists_Win") && (GetTeam (GetEntity ()) == TEAM_TF)) + { + if (g_timeRoundMid > GetWorldTime ()) + ChatterMessage (Chatter_QuicklyWonTheRound); + else + ChatterMessage (Chatter_WonTheRound); + } + + if (FStrEq (tempMessage, "#Bot_TeamAttack")) + ChatterMessage (Chatter_FriendlyFire); + + if (FStrEq (tempMessage, "#Bot_NiceShotCommander")) + ChatterMessage (Chatter_NiceshotCommander); + + if (FStrEq (tempMessage, "#Bot_NiceShotPall")) + ChatterMessage (Chatter_NiceshotPall); +} + +void Bot::ChatMessage (int type, bool isTeamSay) +{ + extern ConVar yb_chat; + + if (g_chatFactory[type].IsEmpty () || !yb_chat.GetBool ()) + return; + + const char *pickedPhrase = g_chatFactory[type].GetRandomElement ().GetBuffer (); + + if (IsNullString (pickedPhrase)) + return; + + PrepareChatMessage (const_cast (pickedPhrase)); + PushMessageQueue (isTeamSay ? GSM_SAY_TEAM : GSM_SAY); +} + +void Bot::DiscardWeaponForUser (edict_t *user, bool discardC4) +{ + // this function, asks bot to discard his current primary weapon (or c4) to the user that requsted it with /drop* + // command, very useful, when i'm don't have money to buy anything... ) + + if (IsAlive (user) && m_moneyAmount >= 2000 && HasPrimaryWeapon () && (user->v.origin - pev->origin).GetLength () <= 240) + { + m_aimFlags |= AIM_ENTITY; + m_lookAt = user->v.origin; + + if (discardC4) + { + SelectWeaponByName ("weapon_c4"); + FakeClientCommand (GetEntity (), "drop"); + + SayText (FormatBuffer ("Here! %s, and now go and setup it!", STRING (user->v.netname))); + } + else + { + SelectBestWeapon (); + FakeClientCommand (GetEntity (), "drop"); + + SayText (FormatBuffer ("Here the weapon! %s, feel free to use it ;)", STRING (user->v.netname))); + } + + m_pickupItem = NULL; + m_pickupType = PICKUP_NONE; + m_itemCheckTime = GetWorldTime () + 5.0; + + if (m_inBuyZone) + { + m_buyingFinished = false; + m_buyState = 0; + + PushMessageQueue (GSM_BUY_STUFF); + m_nextBuyTime = GetWorldTime (); + } + } + else + SayText (FormatBuffer ("Sorry %s, i don't want discard my %s to you!", STRING (user->v.netname), discardC4 ? "bomb" : "weapon")); +} + +void Bot::ResetDoubleJumpState (void) +{ + TaskComplete (); + + m_doubleJumpEntity = NULL; + m_duckForJump = 0.0; + m_doubleJumpOrigin = nullvec; + m_travelStartIndex = -1; + m_jumpReady = false; +} + +void Bot::DebugMsg (const char *format, ...) +{ + if (yb_debug.GetInt () < 2) + return; + + va_list ap; + char buffer[1024]; + + va_start (ap, format); + vsprintf (buffer, format, ap); + va_end (ap); + + ServerPrintNoTag ("%s: %s", STRING (pev->netname), buffer); + + if (yb_debug.GetInt () >= 3) + AddLogEntry (false, LL_DEFAULT, "%s: %s", STRING (pev->netname), buffer); +} + +Vector Bot::CheckToss (const Vector &start, Vector end) +{ + // this function returns the velocity at which an object should looped from start to land near end. + // returns null vector if toss is not feasible. + + TraceResult tr; + float gravity = sv_gravity.GetFloat () * 0.55; + + end = end - pev->velocity; + end.z -= 15.0; + + if (fabsf (end.z - start.z) > 500.0) + return nullvec; + + Vector midPoint = start + (end - start) * 0.5; + TraceHull (midPoint, midPoint + Vector (0, 0, 500), true, head_hull, ENT (pev), &tr); + + if (tr.flFraction < 1.0) + { + midPoint = tr.vecEndPos; + midPoint.z = tr.pHit->v.absmin.z - 1.0; + } + + if ((midPoint.z < start.z) || (midPoint.z < end.z)) + return nullvec; + + float timeOne = sqrtf ((midPoint.z - start.z) / (0.5 * gravity)); + float timeTwo = sqrtf ((midPoint.z - end.z) / (0.5 * gravity)); + + if (timeOne < 0.1) + return nullvec; + + Vector nadeVelocity = (end - start) / (timeOne + timeTwo); + nadeVelocity.z = gravity * timeOne; + + Vector apex = start + nadeVelocity * timeOne; + apex.z = midPoint.z; + + TraceHull (start, apex, false, head_hull, ENT (pev), &tr); + + if (tr.flFraction < 1.0 || tr.fAllSolid) + return nullvec; + + TraceHull (end, apex, true, head_hull, ENT (pev), &tr); + + if (tr.flFraction != 1.0) + { + float dot = -(tr.vecPlaneNormal | (apex - end).Normalize ()); + + if (dot > 0.7 || tr.flFraction < 0.8) // 60 degrees + return nullvec; + } + return nadeVelocity * 0.777; +} + +Vector Bot::CheckThrow (const Vector &start, Vector end) +{ + // this function returns the velocity vector at which an object should be thrown from start to hit end. + // returns null vector if throw is not feasible. + + Vector nadeVelocity = (end - start); + TraceResult tr; + + float gravity = sv_gravity.GetFloat () * 0.55; + float time = nadeVelocity.GetLength () / 195.0; + + if (time < 0.01) + return nullvec; + else if (time > 2.0) + time = 1.2; + + nadeVelocity = nadeVelocity * (1.0 / time); + nadeVelocity.z += gravity * time * 0.5; + + Vector apex = start + (end - start) * 0.5; + apex.z += 0.5 * gravity * (time * 0.5) * (time * 0.5); + + TraceHull (start, apex, false, head_hull, GetEntity (), &tr); + + if (tr.flFraction != 1.0) + return nullvec; + + TraceHull (end, apex, true, head_hull, GetEntity (), &tr); + + if (tr.flFraction != 1.0 || tr.fAllSolid) + { + float dot = -(tr.vecPlaneNormal | (apex - end).Normalize ()); + + if (dot > 0.7 || tr.flFraction < 0.8) + return nullvec; + } + return nadeVelocity * 0.7793; +} + +Vector Bot::CheckBombAudible (void) +{ + // this function checks if bomb is can be heard by the bot, calculations done by manual testing. + + if (!g_bombPlanted || (GetTaskId () == TASK_ESCAPEFROMBOMB)) + return nullvec; // reliability check + + if (m_skill > 80) + return g_waypoint->GetBombPosition(); + + Vector bombOrigin = g_waypoint->GetBombPosition (); + + float timeElapsed = ((GetWorldTime () - g_timeBombPlanted) / mp_c4timer.GetFloat ()) * 100; + float desiredRadius = 768.0; + + // start the manual calculations + if (timeElapsed > 85.0) + desiredRadius = 4096.0; + else if (timeElapsed > 68.0) + desiredRadius = 2048.0; + else if (timeElapsed > 52.0) + desiredRadius = 1280.0; + else if (timeElapsed > 28.0) + desiredRadius = 1024.0; + + // we hear bomb if length greater than radius + if (desiredRadius < (pev->origin - bombOrigin).GetLength2D ()) + return bombOrigin; + + return nullvec; +} + +void Bot::MoveToVector (Vector to) +{ + if (to == nullvec) + return; + + FindPath (m_currentWaypointIndex, g_waypoint->FindNearest (to), 0); +} + +void Bot::RunPlayerMovement (void) +{ + // the purpose of this function is to compute, according to the specified computation + // method, the msec value which will be passed as an argument of pfnRunPlayerMove. This + // function is called every frame for every bot, since the RunPlayerMove is the function + // that tells the engine to put the bot character model in movement. This msec value + // tells the engine how long should the movement of the model extend inside the current + // frame. It is very important for it to be exact, else one can experience bizarre + // problems, such as bots getting stuck into each others. That's because the model's + // bounding boxes, which are the boxes the engine uses to compute and detect all the + // collisions of the model, only exist, and are only valid, while in the duration of the + // movement. That's why if you get a pfnRunPlayerMove for one bot that lasts a little too + // short in comparison with the frame's duration, the remaining time until the frame + // elapses, that bot will behave like a ghost : no movement, but bullets and players can + // pass through it. Then, when the next frame will begin, the stucking problem will arise ! + + + m_msecVal = static_cast ((GetWorldTime () - m_msecInterval) * 1000.0f); + + // update msec interval for last calculation method + m_msecInterval = GetWorldTime (); + + // validate range of the msec value + if (m_msecVal > 2555) + m_msecVal = 255; + + (*g_engfuncs.pfnRunPlayerMove) (GetEntity (), m_moveAngles, m_moveSpeed, m_strafeSpeed, 0.0, pev->button, pev->impulse, m_msecVal); +} + +void Bot::CheckBurstMode (float distance) +{ + // this function checks burst mode, and switch it depending distance to to enemy. + + if (HasShield ()) + return; // no checking when shiled is active + + // if current weapon is glock, disable burstmode on long distances, enable it else + if (m_currentWeapon == WEAPON_GLOCK && distance < 300.0 && m_weaponBurstMode == BM_OFF) + pev->button |= IN_ATTACK2; + else if (m_currentWeapon == WEAPON_GLOCK && distance >= 300 && m_weaponBurstMode == BM_ON) + pev->button |= IN_ATTACK2; + + // if current weapon is famas, disable burstmode on short distances, enable it else + if (m_currentWeapon == WEAPON_FAMAS && distance > 400.0 && m_weaponBurstMode == BM_OFF) + pev->button |= IN_ATTACK2; + else if (m_currentWeapon == WEAPON_FAMAS && distance <= 400 && m_weaponBurstMode == BM_ON) + pev->button |= IN_ATTACK2; +} + +void Bot::CheckSilencer (void) +{ + if (((m_currentWeapon == WEAPON_USP && m_skill <= 80) || m_currentWeapon == WEAPON_M4A1) && !HasShield()) + { + int iRandomNum = (m_personality == PERSONALITY_RUSHER ? 35 : 65); + + // aggressive bots don't like the silencer + if (g_randGen.Long (1, 100) <= (m_currentWeapon == WEAPON_USP ? iRandomNum / 3 : iRandomNum)) + { + if (pev->weaponanim > 6) // is the silencer not attached... + pev->button |= IN_ATTACK2; // attach the silencer + } + else + { + if (pev->weaponanim <= 6) // is the silencer attached... + pev->button |= IN_ATTACK2; // detach the silencer + } + } +} + +float Bot::GetBombTimeleft (void) +{ + if (!g_bombPlanted) + return 0.0; + + float timeLeft = ((g_timeBombPlanted + mp_c4timer.GetFloat ()) - GetWorldTime ()); + + if (timeLeft < 0.0) + return 0.0; + + return timeLeft; +} + +float Bot::GetEstimatedReachTime (void) +{ + float estimatedTime = 5.0; // time to reach next waypoint + + // calculate 'real' time that we need to get from one waypoint to another + if (m_currentWaypointIndex >= 0 && m_currentWaypointIndex < g_numWaypoints && m_prevWptIndex[0] >= 0 && m_prevWptIndex[0] < g_numWaypoints) + { + float distance = (g_waypoint->GetPath (m_prevWptIndex[0])->origin - m_currentPath->origin).GetLength (); + + // caclulate estimated time + if (pev->maxspeed <= 0.0) + estimatedTime = 5.0 * distance / 240.0; + else + estimatedTime = 5.0 * distance / pev->maxspeed; + + // check for special waypoints, that can slowdown our movement + if ((m_currentPath->flags & FLAG_CROUCH) || (m_currentPath->flags & FLAG_LADDER) || (pev->button & IN_DUCK)) + estimatedTime *= 3.0; + + // check for too low values + if (estimatedTime < 3.0) + estimatedTime = 3.0; + + // check for too high values + if (estimatedTime > 8.0) + estimatedTime = 8.0; + } + return estimatedTime; +} + +bool Bot::OutOfBombTimer (void) +{ + if (m_currentWaypointIndex == -1 || ((g_mapType & MAP_DE) && (m_hasProgressBar || GetTaskId () == TASK_ESCAPEFROMBOMB))) + return false; // if CT bot already start defusing, or already escaping, return false + + // calculate left time + float timeLeft = GetBombTimeleft (); + + // if time left greater than 13, no need to do other checks + if (timeLeft > 16) + return false; + + Vector bombOrigin = g_waypoint->GetBombPosition (); + + // for terrorist, if timer is lower than eleven seconds, return true + if (static_cast (timeLeft) < 16 && GetTeam (GetEntity ()) == TEAM_TF && (bombOrigin - pev->origin).GetLength () < 1000) + return true; + + bool hasTeammatesWithDefuserKit = false; + + // check if our teammates has defusal kit + for (int i = 0; i < GetMaxClients (); i++) + { + Bot *bot = NULL; // temporaly pointer to bot + + // search players with defuse kit + if ((bot = g_botManager->GetBot (i)) != NULL && GetTeam (bot->GetEntity ()) == TEAM_CF && bot->m_hasDefuser && (bombOrigin - bot->pev->origin).GetLength () < 500) + { + hasTeammatesWithDefuserKit = true; + break; + } + } + + // add reach time to left time + float reachTime = g_waypoint->GetTravelTime (pev->maxspeed, m_currentPath->origin, bombOrigin); + + // for counter-terrorist check alos is we have time to reach position plus average defuse time + if ((timeLeft < reachTime + 6 && !m_hasDefuser && !hasTeammatesWithDefuserKit) || (timeLeft < reachTime + 2 && m_hasDefuser)) + return true; + + if (m_hasProgressBar && IsOnFloor () && (m_hasDefuser ? 10.0 : 15.0 > GetBombTimeleft ())) + return true; + + return false; // return false otherwise +} + +void Bot::ReactOnSound (void) +{ + int ownIndex = GetIndex (); + float ownSoundLast = 0.0; + + if (g_clients[ownIndex].timeSoundLasting > GetWorldTime ()) + { + if (g_clients[ownIndex].maxTimeSoundLasting <= 0.0) + g_clients[ownIndex].maxTimeSoundLasting = 0.5; + + ownSoundLast = (g_clients[ownIndex].hearingDistance * 0.2) * (g_clients[ownIndex].timeSoundLasting - GetWorldTime ()) / g_clients[ownIndex].maxTimeSoundLasting; + } + + edict_t *player = NULL; + + float maxVolume = 0.0, volume = 0.0; + int hearEnemyIndex = -1, hearEnemyDistance = 0; + + // loop through all enemy clients to check for hearable stuff + for (int i = 0; i < GetMaxClients (); i++) + { + if (!(g_clients[i].flags & CF_USED) || !(g_clients[i].flags & CF_ALIVE) || g_clients[i].ent == GetEntity () || g_clients[i].timeSoundLasting < GetWorldTime ()) + continue; + + float distance = (g_clients[i].soundPosition - pev->origin).GetLength (); + float hearingDistance = g_clients[i].hearingDistance; + + if (distance > hearingDistance) + continue; + + if (g_clients[i].maxTimeSoundLasting <= 0.0) + g_clients[i].maxTimeSoundLasting = 0.5; + + if (distance <= 0.5 * hearingDistance) + volume = hearingDistance * (g_clients[i].timeSoundLasting - GetWorldTime ()) / g_clients[i].maxTimeSoundLasting; + else + volume = 2.0 * hearingDistance * (1.0 - distance / hearingDistance) * (g_clients[i].timeSoundLasting - GetWorldTime ()) / g_clients[i].maxTimeSoundLasting; + + if (g_clients[hearEnemyIndex].team == GetTeam (GetEntity ()) && yb_csdm_mode.GetInt () != 2) + volume = 0.3 * volume; + + // we will care about the most hearable sound instead of the closest one - KWo + if (volume < maxVolume) + continue; + + maxVolume = volume; + + if (volume < ownSoundLast) + continue; + + hearEnemyIndex = i; + hearEnemyDistance = distance; + } + + if (hearEnemyIndex >= 0) + { + if (g_clients[hearEnemyIndex].team != GetTeam (GetEntity ()) && yb_csdm_mode.GetInt () != 2) + player = g_clients[hearEnemyIndex].ent; + } + + // did the bot hear someone ? + if (!FNullEnt (player)) + { + // change to best weapon if heard something + if (!(m_states & STATE_SEEING_ENEMY) && m_seeEnemyTime + 2.5 < GetWorldTime () && IsOnFloor () && m_currentWeapon != WEAPON_C4 && m_currentWeapon != WEAPON_EXPLOSIVE && m_currentWeapon != WEAPON_SMOKE && m_currentWeapon != WEAPON_FLASHBANG && !yb_jasonmode.GetBool ()) + SelectBestWeapon (); + + m_heardSoundTime = GetWorldTime () + 5.0; + m_states |= STATE_HEARING_ENEMY; + + if ((g_randGen.Long (0, 100) < 25) && FNullEnt (m_enemy) && FNullEnt (m_lastEnemy) && m_seeEnemyTime + 7.0 < GetWorldTime ()) + ChatterMessage (Chatter_HeardEnemy); + + m_aimFlags |= AIM_LAST_ENEMY; + + // didn't bot already have an enemy ? take this one... + if (m_lastEnemyOrigin == nullvec || m_lastEnemy == NULL) + { + m_lastEnemy = player; + m_lastEnemyOrigin = player->v.origin; + } + else // bot had an enemy, check if it's the heard one + { + if (player == m_lastEnemy) + { + // bot sees enemy ? then bail out ! + if (m_states & STATE_SEEING_ENEMY) + return; + + m_lastEnemyOrigin = player->v.origin; + } + else + { + // if bot had an enemy but the heard one is nearer, take it instead + float distance = (m_lastEnemyOrigin - pev->origin).GetLength (); + + if (distance > (player->v.origin - pev->origin).GetLength () && m_seeEnemyTime + 2.0 < GetWorldTime ()) + { + m_lastEnemy = player; + m_lastEnemyOrigin = player->v.origin; + } + else + return; + } + } + extern ConVar yb_shoots_thru_walls; + + // check if heard enemy can be seen + if (CheckVisibility (VARS (player), &m_lastEnemyOrigin, &m_visibility)) + { + m_enemy = player; + m_lastEnemy = player; + m_enemyOrigin = m_lastEnemyOrigin; + + m_states |= STATE_SEEING_ENEMY; + m_seeEnemyTime = GetWorldTime (); + } + else if (m_lastEnemy == player && m_seeEnemyTime + 1.0 > GetWorldTime () && yb_shoots_thru_walls.GetBool () && (g_randGen.Long (1, 100) < g_skillTab[m_skill / 20].heardShootThruProb) && IsShootableThruObstacle (player->v.origin)) + { + m_enemy = player; + m_lastEnemy = player; + m_lastEnemyOrigin = player->v.origin; + + m_states |= STATE_SEEING_ENEMY; + m_seeEnemyTime = GetWorldTime (); + } + } +} + +bool Bot::IsShootableBreakable (edict_t *ent) +{ + // this function is checking that pointed by ent pointer obstacle, can be destroyed. + + if (FClassnameIs (ent, "func_breakable") || (FClassnameIs (ent, "func_pushable") && (ent->v.spawnflags & SF_PUSH_BREAKABLE))) + return (ent->v.takedamage != DAMAGE_NO) && ent->v.impulse <= 0 && !(ent->v.flags & FL_WORLDBRUSH) && !(ent->v.spawnflags & SF_BREAK_TRIGGER_ONLY) && ent->v.health < 500; + + return false; +} + +void Bot::EquipInBuyzone (int iBuyCount) +{ + // this function is gets called when bot enters a buyzone, to allow bot to buy some stuff + + static float lastEquipTime = 0.0; + + // if bot is in buy zone, try to buy ammo for this weapon... + if (lastEquipTime + 15.0 < GetWorldTime () && m_inBuyZone && g_timeRoundStart + g_randGen.Float (10.0, 20.0) + mp_buytime.GetFloat () < GetWorldTime () && !g_bombPlanted && m_moneyAmount > g_botBuyEconomyTable[0]) + { + m_buyingFinished = false; + m_buyState = iBuyCount; + + // push buy message + PushMessageQueue (GSM_BUY_STUFF); + + m_nextBuyTime = GetWorldTime (); + lastEquipTime = GetWorldTime (); + } +} + +bool Bot::IsBombDefusing (Vector bombOrigin) +{ + // this function finds if somebody currently defusing the bomb. + + // @todo: need to check progress bar for non-bots clients. + bool defusingInProgress = false; + + for (int i = 0; i < GetMaxClients (); i++) + { + Bot *bot = g_botManager->GetBot (i); + + if (bot == NULL || bot == this) + continue; // skip invalid bots + + if (GetTeam (GetEntity ()) != GetTeam (bot->GetEntity ()) || bot->GetTaskId () == TASK_ESCAPEFROMBOMB) + continue; // skip other mess + + if ((bot->pev->origin - bombOrigin).GetLength () < 100 && (bot->GetTaskId () == TASK_DEFUSEBOMB || bot->m_hasProgressBar)) + { + defusingInProgress = true; + break; + } + + // take in account peoples too + if (defusingInProgress || !(g_clients[i].flags & CF_USED) || !(g_clients[i].flags & CF_ALIVE) || g_clients[i].team != GetTeam (GetEntity ()) || IsValidBot (g_clients[i].ent)) + continue; + + if ((g_clients[i].ent->v.origin - bombOrigin).GetLength () < 100) + { + defusingInProgress = true; + break; + } + } + return defusingInProgress; +} diff --git a/source/botmanager.cpp b/source/botmanager.cpp new file mode 100644 index 0000000..ad93f76 --- /dev/null +++ b/source/botmanager.cpp @@ -0,0 +1,1226 @@ +// +// Copyright (c) 2014, by YaPB Development Team. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// Version: $Id:$ +// + +#include + +ConVar yb_autovacate ("yb_autovacate", "-1"); + +ConVar yb_quota ("yb_quota", "0"); +ConVar yb_quota_match ("yb_quota_match", "0"); +ConVar yb_quota_match_max ("yb_quota_match_max", "0"); + +ConVar yb_join_team ("yb_join_team", "any"); +ConVar yb_name_prefix ("yb_name_prefix", ""); + +ConVar yb_minskill ("yb_minskill", "60"); +ConVar yb_maxskill ("yb_maxskill", "100"); + +ConVar yb_skill_tags ("yb_skill_tags", "0"); +ConVar yb_latency_display ("yb_latency_display", "0"); + +BotManager::BotManager (void) +{ + // this is a bot manager class constructor + + m_lastWinner = -1; + + m_economicsGood[TEAM_TF] = true; + m_economicsGood[TEAM_CF] = true; + + memset (m_bots, 0, sizeof (m_bots)); + + if (g_pGlobals != NULL) + InitQuota (); +} + +BotManager::~BotManager (void) +{ + // this is a bot manager class destructor, do not use GetMaxClients () here !! + + Free (); +} + +void BotManager::CallGameEntity (entvars_t *vars) +{ + // this function calls gamedll player() function, in case to create player entity in game + + if (g_isMetamod) + { + CALL_GAME_ENTITY (PLID, "player", vars); + return; + } + + static EntityPtr_t playerFunction = NULL; + + if (playerFunction == NULL) + playerFunction = (EntityPtr_t) g_gameLib->GetFunctionAddr ("player"); + + if (playerFunction != NULL) + (*playerFunction) (vars); +} + +int BotManager::CreateBot (String name, int skill, int personality, int team, int member) +{ + // this function completely prepares bot entity (edict) for creation, creates team, skill, sets name etc, and + // then sends result to bot constructor + + + edict_t *bot = NULL; + char outputName[33]; + + if (g_numWaypoints < 1) // don't allow creating bots with no waypoints loaded + { + CenterPrint ("Map is not waypointed. Cannot create bot"); + return 0; + } + else if (g_waypointsChanged) // don't allow creating bots with changed waypoints (distance tables are messed up) + { + CenterPrint ("Waypoints have been changed. Load waypoints again..."); + return 0; + } + + if (skill < 0 || skill > 100) + skill = g_randGen.Long (yb_minskill.GetInt (), yb_maxskill.GetInt ()); + + if (skill > 100 || skill < 0) + skill = g_randGen.Long (0, 100); + + if (personality < 0 || personality > 2) + { + if (g_randGen.Long (0, 100) < 50) + personality = PERSONALITY_NORMAL; + else + { + if (g_randGen.Long (0, 100) > 50) + personality = PERSONALITY_RUSHER; + else + personality = PERSONALITY_CAREFUL; + } + } + + // setup name + if (name.IsEmpty ()) + { + if (!g_botNames.IsEmpty ()) + { + bool nameFound = false; + + for (int i = 0; i < 8; i++) + { + if (nameFound) + break; + + BotName *name = &g_botNames.GetRandomElement (); + + if (name == NULL || (name != NULL && name->used)) + continue; + + name->used = nameFound = true; + strcpy (outputName, name->name); + } + } + else + sprintf (outputName, "bot%i", g_randGen.Long (0, 100)); // just pick ugly random name + } + else + strncpy (outputName, name, 21); + + if (!IsNullString (yb_name_prefix.GetString ()) || yb_skill_tags.GetBool ()) + { + char prefixedName[33]; // temp buffer for storing modified name + + if (!IsNullString (yb_name_prefix.GetString ())) + sprintf (prefixedName, "%s %s", yb_name_prefix.GetString (), outputName); + + else if (yb_skill_tags.GetBool ()) + sprintf (prefixedName, "%s (%d)", outputName, skill); + + else if (!IsNullString (yb_name_prefix.GetString ()) && yb_skill_tags.GetBool ()) + sprintf (prefixedName, "%s %s (%d)", yb_name_prefix.GetString (), outputName, skill); + + // buffer has been modified, copy to real name + if (!IsNullString (prefixedName)) + strcpy (outputName, prefixedName); + } + + if (FNullEnt ((bot = (*g_engfuncs.pfnCreateFakeClient) (outputName)))) + { + CenterPrint ("Maximum players reached (%d/%d). Unable to create Bot.", GetMaxClients (), GetMaxClients ()); + return 2; + } + + int index = ENTINDEX (bot) - 1; + + InternalAssert (index >= 0 && index <= 32); // check index + InternalAssert (m_bots[index] == NULL); // check bot slot + + m_bots[index] = new Bot (bot, skill, personality, team, member); + + if (m_bots == NULL) + TerminateOnMalloc (); + + ServerPrint ("Connecting Bot... (Skill %d)", skill); + + return 1; +} + +int BotManager::GetIndex (edict_t *ent) +{ + // this function returns index of bot (using own bot array) + if (FNullEnt (ent)) + return -1; + + int index = ENTINDEX (ent) - 1; + + if (index < 0 || index >= 32) + return -1; + + if (m_bots[index] != NULL) + return index; + + return -1; // if no edict, return -1; +} + +Bot *BotManager::GetBot (int index) +{ + // this function finds a bot specified by index, and then returns pointer to it (using own bot array) + + if (index < 0 || index >= 32) + return NULL; + + if (m_bots[index] != NULL) + return m_bots[index]; + + return NULL; // no bot +} + +Bot *BotManager::GetBot (edict_t *ent) +{ + // same as above, but using bot entity + + return GetBot (GetIndex (ent)); +} + +Bot *BotManager::FindOneValidAliveBot (void) +{ + // this function finds one bot, alive bot :) + + Array foundBots; + + for (int i = 0; i < GetMaxClients (); i++) + { + if (m_bots[i] != NULL && IsAlive (m_bots[i]->GetEntity ())) + foundBots.Push (i); + } + + if (!foundBots.IsEmpty ()) + return m_bots[foundBots.GetRandomElement ()]; + + return NULL; +} + +void BotManager::Think (void) +{ + // this function calls think () function for all available at call moment bots, and + // try to catch internal error if such shit occurs + + for (int i = 0; i < GetMaxClients (); i++) + { + if (m_bots[i] != NULL) + { + // use these try-catch blocks to prevent server crashes when error occurs +#if defined (NDEBUG) && !defined (PLATFORM_LINUX) && !defined (PLATFORM_OSX) + try + { + m_bots[i]->Think (); + } + catch (...) + { + // error occurred. kick off all bots and then print a warning message + RemoveAll (); + + ServerPrintNoTag ("**** INTERNAL BOT ERROR! PLEASE SHUTDOWN AND RESTART YOUR SERVER! ****"); + } +#else + m_bots[i]->Think (); +#endif + } + } +} + +void BotManager::AddBot (const String &name, int skill, int personality, int team, int member) +{ + // this function putting bot creation process to queue to prevent engine crashes + + CreateQueue bot; + + // fill the holder + bot.name = name; + bot.skill = skill; + bot.personality = personality; + bot.team = team; + bot.member = member; + + // put to queue + m_creationTab.Push (bot); + + // keep quota number up to date + if (GetBotsNum () + 1 > yb_quota.GetInt ()) + yb_quota.SetInt (GetBotsNum () + 1); +} + +void BotManager::AddBot (String name, String skill, String personality, String team, String member) +{ + // this function is same as the function above, but accept as parameters string instead of integers + + CreateQueue bot; + const String &any = "*"; + + bot.name = (name.IsEmpty () || (name == any)) ? String ("\0") : name; + bot.skill = (skill.IsEmpty () || (skill == any)) ? -1 : skill.ToInt (); + bot.team = (team.IsEmpty () || (team == any)) ? -1 : team.ToInt (); + bot.member = (member.IsEmpty () || (member == any)) ? -1 : member.ToInt (); + bot.personality = (personality.IsEmpty () || (personality == any)) ? -1 : personality.ToInt (); + + m_creationTab.Push (bot); + + // keep quota number up to date + if (GetBotsNum () + 1 > yb_quota.GetInt ()) + yb_quota.SetInt (GetBotsNum () + 1); +} + +void BotManager::CheckAutoVacate (edict_t *ent) +{ + // this function sets timer to kick one bot off. + + if (yb_autovacate.GetBool ()) + RemoveRandom (); +} + +void BotManager::MaintainBotQuota (void) +{ + // this function keeps number of bots up to date, and don't allow to maintain bot creation + // while creation process in process. + + if (!m_creationTab.IsEmpty () && m_maintainTime < GetWorldTime ()) + { + CreateQueue last = m_creationTab.Pop (); + int resultOfCall = CreateBot (last.name, last.skill, last.personality, last.team, last.member); + + // check the result of creation + if (resultOfCall == 0) + { + m_creationTab.RemoveAll (); // something worng with waypoints, reset tab of creation + yb_quota.SetInt (0); // reset quota + } + else if (resultOfCall == 2) + { + m_creationTab.RemoveAll (); // maximum players reached, so set quota to maximum players + yb_quota.SetInt (GetBotsNum ()); + } + m_maintainTime = GetWorldTime () + 0.2; + } + + // now keep bot number up to date + if (m_maintainTime < GetWorldTime ()) + { + int botNumber = GetBotsNum (); + int humanNumber = GetHumansNum (); + + if (botNumber > yb_quota.GetInt ()) + RemoveRandom (); + + if (yb_quota_match.GetInt () > 0) + { + int num = yb_quota_match.GetInt () * humanNumber; + + if (num >= GetMaxClients () - humanNumber) + num = GetMaxClients () - humanNumber; + + if (yb_quota_match_max.GetInt () > 0 && num > yb_quota_match_max.GetInt ()) + num = yb_quota_match_max.GetInt (); + + yb_quota.SetInt (num); + yb_autovacate.SetInt (0); + } + + if (yb_autovacate.GetBool ()) + { + if (botNumber < yb_quota.GetInt () && botNumber < GetMaxClients () - 1) + AddRandom (); + + if (humanNumber >= GetMaxClients ()) + RemoveRandom (); + } + else + { + if (botNumber < yb_quota.GetInt () && botNumber < GetMaxClients ()) + AddRandom (); + } + + int botQuota = yb_autovacate.GetBool () ? (GetMaxClients () - 1 - (humanNumber + 1)) : GetMaxClients (); + + // check valid range of quota + if (yb_quota.GetInt () > botQuota) + yb_quota.SetInt (botQuota); + + else if (yb_quota.GetInt () < 0) + yb_quota.SetInt (0); + + m_maintainTime = GetWorldTime () + 0.25; + } +} + +void BotManager::InitQuota (void) +{ + m_maintainTime = GetWorldTime () + 2.0; + m_creationTab.RemoveAll (); +} + +void BotManager::FillServer (int selection, int personality, int skill, int numToAdd) +{ + // this function fill server with bots, with specified team & personality + + if (GetBotsNum () >= GetMaxClients () - GetHumansNum ()) + return; + + if (selection == 1 || selection == 2) + { + CVAR_SET_STRING ("mp_limitteams", "0"); + CVAR_SET_STRING ("mp_autoteambalance", "0"); + } + else + selection = 5; + + char teamDesc[6][12] = + { + "", + {"Terrorists"}, + {"CTs"}, + "", + "", + {"Random"}, + }; + + int toAdd = numToAdd == -1 ? GetMaxClients () - (GetHumansNum () + GetBotsNum ()) : numToAdd; + + for (int i = 0; i <= toAdd; i++) + { + // since we got constant skill from menu (since creation process call automatic), we need to manually + // randomize skill here, on given skill there. + int randomizedSkill = 0; + + if (skill >= 0 && skill <= 20) + randomizedSkill = g_randGen.Long (0, 20); + else if (skill >= 20 && skill <= 40) + randomizedSkill = g_randGen.Long (20, 40); + else if (skill >= 40 && skill <= 60) + randomizedSkill = g_randGen.Long (40, 60); + else if (skill >= 60 && skill <= 80) + randomizedSkill = g_randGen.Long (60, 80); + else if (skill >= 80 && skill <= 99) + randomizedSkill = g_randGen.Long (80, 99); + else if (skill == 100) + randomizedSkill = skill; + + AddBot ("", randomizedSkill, personality, selection, -1); + } + + yb_quota.SetInt (toAdd); + yb_quota_match.SetInt (0); + + CenterPrint ("Fill Server with %s bots...", &teamDesc[selection][0]); +} + +void BotManager::RemoveAll (void) +{ + // this function drops all bot clients from server (this function removes only yapb's)`q + + CenterPrint ("Bots are removed from server."); + + for (int i = 0; i < GetMaxClients (); i++) + { + if (m_bots[i] != NULL) // is this slot used? + m_bots[i]->Kick (); + } + m_creationTab.RemoveAll (); + + // reset cvars + yb_quota.SetInt (0); + yb_autovacate.SetInt (0); +} + +void BotManager::RemoveFromTeam (Team team, bool removeAll) +{ + // this function remove random bot from specified team (if removeAll value = 1 then removes all players from team) + + for (int i = 0; i < GetMaxClients (); i++) + { + if (m_bots[i] != NULL && team == GetTeam (m_bots[i]->GetEntity ())) + { + m_bots[i]->Kick (); + + if (!removeAll) + break; + } + } +} + +void BotManager::RemoveMenu (edict_t *ent, int selection) +{ + // this function displays remove bot menu to specified entity (this function show's only yapb's). + + + if (selection > 4 || selection < 1) + return; + + static char tempBuffer[1024]; + char buffer[1024]; + + memset (tempBuffer, 0, sizeof (tempBuffer)); + memset (buffer, 0, sizeof (buffer)); + + int validSlots = (selection == 4) ? (1 << 9) : ((1 << 8) | (1 << 9)); + + for (int i = ((selection - 1) * 8); i < selection * 8; i++) + { + if ((m_bots[i] != NULL) && !FNullEnt (m_bots[i]->GetEntity ())) + { + validSlots |= 1 << (i - ((selection - 1) * 8)); + sprintf (buffer, "%s %1.1d. %s%s\n", buffer, i - ((selection - 1) * 8) + 1, STRING (m_bots[i]->pev->netname), GetTeam (m_bots[i]->GetEntity ()) == TEAM_CF ? " \\y(CT)\\w" : " \\r(T)\\w"); + } + else + sprintf (buffer, "%s\\d %1.1d. Not a YaPB\\w\n", buffer, i - ((selection - 1) * 8) + 1); + } + + sprintf (tempBuffer, "\\yYaPB Remove Menu (%d/4):\\w\n\n%s\n%s 0. Back", selection, buffer, (selection == 4) ? "" : " 9. More...\n"); + + switch (selection) + { + case 1: + g_menus[14].validSlots = validSlots & static_cast (-1); + g_menus[14].menuText = tempBuffer; + + DisplayMenuToClient (ent, &g_menus[14]); + break; + + case 2: + g_menus[15].validSlots = validSlots & static_cast (-1); + g_menus[15].menuText = tempBuffer; + + DisplayMenuToClient (ent, &g_menus[15]); + break; + + case 3: + g_menus[16].validSlots = validSlots & static_cast (-1); + g_menus[16].menuText = tempBuffer; + + DisplayMenuToClient (ent, &g_menus[16]); + break; + + case 4: + g_menus[17].validSlots = validSlots & static_cast (-1); + g_menus[17].menuText = tempBuffer; + + DisplayMenuToClient (ent, &g_menus[17]); + break; + } +} + +void BotManager::KillAll (int team) +{ + // this function kills all bots on server (only this dll controlled bots) + + for (int i = 0; i < GetMaxClients (); i++) + { + if (m_bots[i] != NULL) + { + if (team != -1 && team != GetTeam (m_bots[i]->GetEntity ())) + continue; + + m_bots[i]->Kill (); + } + } + CenterPrint ("All Bots died !"); +} + +void BotManager::RemoveRandom (void) +{ + // this function removes random bot from server (only yapb's) + + for (int i = 0; i < GetMaxClients (); i++) + { + if (m_bots[i] != NULL) // is this slot used? + { + m_bots[i]->Kick (); + break; + } + } +} + +void BotManager::SetWeaponMode (int selection) +{ + // this function sets bots weapon mode + + int tabMapStandart[7][NUM_WEAPONS] = + { + {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, // Knife only + {-1,-1,-1, 2, 2, 0, 1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, // Pistols only + {-1,-1,-1,-1,-1,-1,-1, 2, 2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, // Shotgun only + {-1,-1,-1,-1,-1,-1,-1,-1,-1, 2, 1, 2, 0, 2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 2,-1}, // Machine Guns only + {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 0, 0, 1, 0, 1, 1,-1,-1,-1,-1,-1,-1}, // Rifles only + {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 2, 2, 0, 1,-1,-1}, // Snipers only + {-1,-1,-1, 2, 2, 0, 1, 2, 2, 2, 1, 2, 0, 2, 0, 0, 1, 0, 1, 1, 2, 2, 0, 1, 2, 1} // Standard + }; + + int tabMapAS[7][NUM_WEAPONS] = + { + {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, // Knife only + {-1,-1,-1, 2, 2, 0, 1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, // Pistols only + {-1,-1,-1,-1,-1,-1,-1, 1, 1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, // Shotgun only + {-1,-1,-1,-1,-1,-1,-1,-1,-1, 1, 1, 1, 0, 2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 1,-1}, // Machine Guns only + {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 0,-1, 1, 0, 1, 1,-1,-1,-1,-1,-1,-1}, // Rifles only + {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 0, 0,-1, 1,-1,-1}, // Snipers only + {-1,-1,-1, 2, 2, 0, 1, 1, 1, 1, 1, 1, 0, 2, 0,-1, 1, 0, 1, 1, 0, 0,-1, 1, 1, 1} // Standard + }; + + char modeName[7][12] = + { + {"Knife"}, + {"Pistol"}, + {"Shotgun"}, + {"Machine Gun"}, + {"Rifle"}, + {"Sniper"}, + {"Standard"} + }; + selection--; + + for (int i = 0; i < NUM_WEAPONS; i++) + { + g_weaponSelect[i].teamStandard = tabMapStandart[selection][i]; + g_weaponSelect[i].teamAS = tabMapAS[selection][i]; + } + + if (selection == 0) + yb_jasonmode.SetInt (1); + else + yb_jasonmode.SetInt (0); + + CenterPrint ("%s weapon mode selected", &modeName[selection][0]); +} + +void BotManager::ListBots (void) +{ + // this function list's bots currently playing on the server + + ServerPrintNoTag ("%-3.5s %-9.13s %-17.18s %-3.4s %-3.4s %-3.4s", "index", "name", "personality", "team", "skill", "frags"); + + for (int i = 0; i < GetMaxClients (); i++) + { + edict_t *player = INDEXENT (i); + + // is this player slot valid + if (IsValidBot (player)) + { + Bot *bot = GetBot (player); + + if (bot != NULL) + ServerPrintNoTag ("[%-3.1d] %-9.13s %-17.18s %-3.4s %-3.1d %-3.1d", i, STRING (player->v.netname), bot->m_personality == PERSONALITY_RUSHER ? "rusher" : bot->m_personality == PERSONALITY_NORMAL ? "normal" : "careful", GetTeam (player) != 0 ? "CT" : "T", bot->m_skill, static_cast (player->v.frags)); + } + } +} + +int BotManager::GetBotsNum (void) +{ + // this function returns number of yapb's playing on the server + + int count = 0; + + for (int i = 0; i < GetMaxClients (); i++) + { + if (m_bots[i] != NULL) + count++; + } + return count; +} + +int BotManager::GetHumansNum (void) +{ + // this function returns number of humans playing on the server + + int count = 0; + + for (int i = 0; i < GetMaxClients (); i++) + { + if ((g_clients[i].flags & CF_USED) && m_bots[i] == NULL) + count++; + } + return count; +} + +Bot *BotManager::GetHighestFragsBot (int team) +{ + Bot *highFragBot = NULL; + + int bestIndex = 0; + float bestScore = -1; + + // search bots in this team + for (int i = 0; i < GetMaxClients (); i++) + { + highFragBot = g_botManager->GetBot (i); + + if (highFragBot != NULL && IsAlive (highFragBot->GetEntity ()) && GetTeam (highFragBot->GetEntity ()) == team) + { + if (highFragBot->pev->frags > bestScore) + { + bestIndex = i; + bestScore = highFragBot->pev->frags; + } + } + } + return GetBot (bestIndex); +} + +void BotManager::CheckTeamEconomics (int team) +{ + // this function decides is players on specified team is able to buy primary weapons by calculating players + // that have not enough money to buy primary (with economics), and if this result higher 80%, player is can't + // buy primary weapons. + + extern ConVar yb_economics_rounds; + + if (!yb_economics_rounds.GetBool ()) + { + m_economicsGood[team] = true; + return; // don't check economics while economics disable + } + + int numPoorPlayers = 0; + int numTeamPlayers = 0; + + // start calculating + for (int i = 0; i < GetMaxClients (); i++) + { + if (m_bots[i] != NULL && GetTeam (m_bots[i]->GetEntity ()) == team) + { + if (m_bots[i]->m_moneyAmount <= g_botBuyEconomyTable[0]) + numPoorPlayers++; + + numTeamPlayers++; // update count of team + } + } + m_economicsGood[team] = true; + + if (numTeamPlayers <= 1) + return; + + // if 80 percent of team have no enough money to purchase primary weapon + if ((numTeamPlayers * 80) / 100 <= numPoorPlayers) + m_economicsGood[team] = false; + + // winner must buy something! + if (m_lastWinner == team) + m_economicsGood[team] = true; +} + +void BotManager::Free (void) +{ + // this function free all bots slots (used on server shutdown) + + for (int i = 0; i < 32; i++) + { + if (m_bots[i] != NULL) + Free (i); + } +} + +void BotManager::Free (int index) +{ + // this function frees one bot selected by index (used on bot disconnect) + + m_bots[index]->SwitchChatterIcon (false); + m_bots[index]->ReleaseUsedName (); + + delete m_bots[index]; + m_bots[index] = NULL; +} + +Bot::Bot (edict_t *bot, int skill, int personality, int team, int member) +{ + // this function does core operation of creating bot, it's called by CreateBot (), + // when bot setup completed, (this is a bot class constructor) + + char rejectReason[128]; + int clientIndex = ENTINDEX (bot); + + memset (this, 0, sizeof (Bot)); + + pev = VARS (bot); + + if (bot->pvPrivateData != NULL) + FREE_PRIVATE (bot); + + bot->pvPrivateData = NULL; + bot->v.frags = 0; + + // create the player entity by calling MOD's player function + BotManager::CallGameEntity (&bot->v); + + // set all info buffer keys for this bot + char *buffer = GET_INFOKEYBUFFER (bot); + + SET_CLIENT_KEYVALUE (clientIndex, buffer, "model", ""); + SET_CLIENT_KEYVALUE (clientIndex, buffer, "rate", "3500.000000"); + SET_CLIENT_KEYVALUE (clientIndex, buffer, "cl_updaterate", "20"); + SET_CLIENT_KEYVALUE (clientIndex, buffer, "cl_lw", "0"); + SET_CLIENT_KEYVALUE (clientIndex, buffer, "cl_lc", "0"); + SET_CLIENT_KEYVALUE (clientIndex, buffer, "tracker", "0"); + SET_CLIENT_KEYVALUE (clientIndex, buffer, "cl_dlmax", "128"); + SET_CLIENT_KEYVALUE (clientIndex, buffer, "friends", "0"); + SET_CLIENT_KEYVALUE (clientIndex, buffer, "dm", "0"); + SET_CLIENT_KEYVALUE (clientIndex, buffer, "_ah", "0"); + SET_CLIENT_KEYVALUE (clientIndex, buffer, "_vgui_menus", "0"); + + if (g_gameVersion != CSV_OLD) + SET_CLIENT_KEYVALUE (clientIndex, buffer, "*bot", const_cast (yb_latency_display.GetBool () ? "1" : "0")); + + rejectReason[0] = 0; // reset the reject reason template string + MDLL_ClientConnect (bot, "BOT", FormatBuffer ("127.0.0.%d", ENTINDEX (bot) + 100), rejectReason); + + if (!IsNullString (rejectReason)) + { + AddLogEntry (true, LL_WARNING, "Server refused '%s' connection (%s)", STRING (bot->v.netname), rejectReason); + ServerCommand ("kick \"%s\"", STRING (bot->v.netname)); // kick the bot player if the server refused it + + bot->v.flags |= FL_KILLME; + } + + MDLL_ClientPutInServer (bot); + bot->v.flags |= FL_FAKECLIENT; // set this player as fakeclient + + // initialize all the variables for this bot... + m_notStarted = true; // hasn't joined game yet + + m_startAction = GSM_IDLE; + m_moneyAmount = 0; + + m_logotypeIndex = g_randGen.Long (0, 5); + + // initialize msec value + m_msecInterval = GetWorldTime (); + m_msecVal = static_cast (g_pGlobals->frametime * 1000.0); + + // assign how talkative this bot will be + m_sayTextBuffer.chatDelay = g_randGen.Float (3.8, 10.0); + m_sayTextBuffer.chatProbability = g_randGen.Long (1, 100); + + m_notKilled = false; + m_skill = skill; + m_weaponBurstMode = BM_OFF; + + m_lastThinkTime = GetWorldTime (); + m_frameInterval = GetWorldTime (); + + bot->v.idealpitch = bot->v.v_angle.x; + bot->v.ideal_yaw = bot->v.v_angle.y; + + bot->v.yaw_speed = g_randGen.Float (g_skillTab[m_skill / 20].minTurnSpeed, g_skillTab[m_skill / 20].maxTurnSpeed); + bot->v.pitch_speed = g_randGen.Float (g_skillTab[m_skill / 20].minTurnSpeed, g_skillTab[m_skill / 20].maxTurnSpeed); + + switch (personality) + { + case 1: + m_personality = PERSONALITY_RUSHER; + m_baseAgressionLevel = g_randGen.Float (0.7, 1.0); + m_baseFearLevel = g_randGen.Float (0.0, 0.4); + break; + + case 2: + m_personality = PERSONALITY_CAREFUL; + m_baseAgressionLevel = g_randGen.Float (0.2, 0.5); + m_baseFearLevel = g_randGen.Float (0.7, 1.0); + break; + + default: + m_personality = PERSONALITY_NORMAL; + m_baseAgressionLevel = g_randGen.Float (0.4, 0.7); + m_baseFearLevel = g_randGen.Float (0.4, 0.7); + break; + } + + memset (&m_ammoInClip, 0, sizeof (m_ammoInClip)); + memset (&m_ammo, 0, sizeof (m_ammo)); + + m_currentWeapon = 0; // current weapon is not assigned at start + m_voicePitch = g_randGen.Long (166, 250) / 2; // assign voice pitch + + // copy them over to the temp level variables + m_agressionLevel = m_baseAgressionLevel; + m_fearLevel = m_baseFearLevel; + m_nextEmotionUpdate = GetWorldTime () + 0.5; + + // just to be sure + m_actMessageIndex = 0; + m_pushMessageIndex = 0; + + // assign team and class + m_wantedTeam = team; + m_wantedClass = member; + + NewRound (); +} + +void Bot::ReleaseUsedName (void) +{ + IterateArray (g_botNames, j) + { + BotName &name = g_botNames[j]; + + if (strcmp (name.name, STRING (pev->netname)) == 0) + { + name.used = false; + break; + } + } +} + +Bot::~Bot (void) +{ + // this is bot destructor + + DeleteSearchNodes (); + ResetTasks (); +} + +int BotManager::GetHumansAliveNum (void) +{ + // this function returns number of humans playing on the server + + int count = 0; + + for (int i = 0; i < GetMaxClients (); i++) + { + if ((g_clients[i].flags & (CF_USED | CF_ALIVE)) && m_bots[i] == NULL) + count++; + } + return count; +} + +void Bot::NewRound (void) +{ + // this function initializes a bot after creation & at the start of each round + + g_canSayBombPlanted = true; + int i = 0; + + + // delete all allocated path nodes + DeleteSearchNodes (); + + m_waypointOrigin = nullvec; + m_destOrigin = nullvec; + m_currentWaypointIndex = -1; + m_currentPath = NULL; + m_currentTravelFlags = 0; + m_desiredVelocity = nullvec; + m_prevGoalIndex = -1; + m_chosenGoalIndex = -1; + m_loosedBombWptIndex = -1; + + m_moveToC4 = false; + m_duckDefuse = false; + m_duckDefuseCheckTime = 0.0; + + m_prevWptIndex[0] = -1; + m_prevWptIndex[1] = -1; + m_prevWptIndex[2] = -1; + m_prevWptIndex[3] = -1; + m_prevWptIndex[4] = -1; + + m_navTimeset = GetWorldTime (); + + switch (m_personality) + { + case PERSONALITY_NORMAL: + m_pathType = g_randGen.Long (0, 100) > 50 ? 1 : 2; + break; + + case PERSONALITY_RUSHER: + m_pathType = 0; + break; + + case PERSONALITY_CAREFUL: + m_pathType = 2; + break; + } + + // clear all states & tasks + m_states = 0; + ResetTasks (); + + m_isVIP = false; + m_isLeader = false; + m_hasProgressBar = false; + m_canChooseAimDirection = true; + + m_timeTeamOrder = 0.0; + m_askCheckTime = 0.0; + m_minSpeed = 260.0; + m_prevSpeed = 0.0; + m_prevOrigin = Vector (9999.0, 9999.0, 9999.0); + m_prevTime = GetWorldTime (); + m_blindRecognizeTime = GetWorldTime (); + + m_viewDistance = 4096.0; + m_maxViewDistance = 4096.0; + + m_liftEntity = NULL; + m_pickupItem = NULL; + m_itemIgnore = NULL; + m_itemCheckTime = 0.0; + + m_breakableEntity = NULL; + m_breakable = nullvec; + m_timeDoorOpen = 0.0; + + ResetCollideState (); + ResetDoubleJumpState (); + + m_enemy = NULL; + m_lastVictim = NULL; + m_lastEnemy = NULL; + m_lastEnemyOrigin = nullvec; + m_trackingEdict = NULL; + m_timeNextTracking = 0.0; + + m_buttonPushTime = 0.0; + m_enemyUpdateTime = 0.0; + m_seeEnemyTime = 0.0; + m_shootAtDeadTime = 0.0; + m_oldCombatDesire = 0.0; + m_liftUsageTime = 0.0; + + m_avoidGrenade = NULL; + m_needAvoidGrenade = 0; + + m_lastDamageType = -1; + m_voteKickIndex = 0; + m_lastVoteKick = 0; + m_voteMap = 0; + m_doorOpenAttempt = 0; + m_aimFlags = 0; + m_liftState = 0; + m_burstShotsFired = 0; + + m_position = nullvec; + m_liftTravelPos = nullvec; + + m_idealReactionTime = g_skillTab[m_skill / 20].minSurpriseTime; + m_actualReactionTime = g_skillTab[m_skill / 20].minSurpriseTime; + + m_targetEntity = NULL; + m_tasks = NULL; + m_followWaitTime = 0.0; + + for (i = 0; i < MAX_HOSTAGES; i++) + m_hostages[i] = NULL; + + for (i = 0; i < Chatter_Total; i++) + m_voiceTimers[i] = -1.0; + + m_isReloading = false; + m_reloadState = RELOAD_NONE; + + m_reloadCheckTime = 0.0; + m_shootTime = GetWorldTime (); + m_playerTargetTime = GetWorldTime (); + m_firePause = 0.0; + m_timeLastFired = 0.0; + + m_grenadeCheckTime = 0.0; + m_isUsingGrenade = false; + + m_skillOffset = static_cast ((100 - m_skill) / 100.0); + m_blindButton = 0; + m_blindTime = 0.0; + m_jumpTime = 0.0; + m_jumpFinished = false; + m_isStuck = false; + + m_sayTextBuffer.timeNextChat = GetWorldTime (); + m_sayTextBuffer.entityIndex = -1; + m_sayTextBuffer.sayText[0] = 0x0; + + m_buyState = 0; + + if (!m_notKilled) // if bot died, clear all weapon stuff and force buying again + { + memset (&m_ammoInClip, 0, sizeof (m_ammoInClip)); + memset (&m_ammo, 0, sizeof (m_ammo)); + + m_currentWeapon = 0; + } + + m_knifeAttackTime = GetWorldTime () + g_randGen.Float (1.3, 2.6); + m_nextBuyTime = GetWorldTime () + g_randGen.Float (0.6, 1.2); + + m_buyPending = false; + m_inBombZone = false; + + m_shieldCheckTime = 0.0; + m_zoomCheckTime = 0.0; + m_strafeSetTime = 0.0; + m_combatStrafeDir = 0; + m_fightStyle = 0; + m_lastFightStyleCheck = 0.0; + + m_checkWeaponSwitch = true; + m_checkKnifeSwitch = true; + m_buyingFinished = false; + + m_radioEntity = NULL; + m_radioOrder = 0; + m_defendedBomb = false; + + m_timeLogoSpray = GetWorldTime () + g_randGen.Float (0.5, 2.0); + m_spawnTime = GetWorldTime (); + m_lastChatTime = GetWorldTime (); + pev->v_angle.y = pev->ideal_yaw; + + m_timeCamping = 0; + m_campDirection = 0; + m_nextCampDirTime = 0; + m_campButtons = 0; + + m_soundUpdateTime = 0.0; + m_heardSoundTime = GetWorldTime (); + + // clear its message queue + for (i = 0; i < 32; i++) + m_messageQueue[i] = GSM_IDLE; + + m_actMessageIndex = 0; + m_pushMessageIndex = 0; + + // and put buying into its message queue + PushMessageQueue (GSM_BUY_STUFF); + StartTask (TASK_NORMAL, TASKPRI_NORMAL, -1, 0.0, true); + + if (g_randGen.Long (0, 100) < 50) + ChatterMessage (Chatter_NewRound); +} + +void Bot::Kill (void) +{ + // this function kills a bot (not just using ClientKill, but like the CSBot does) + // base code courtesy of Lazy (from bots-united forums!) + + edict_t *hurtEntity = (*g_engfuncs.pfnCreateNamedEntity) (MAKE_STRING ("trigger_hurt")); + + if (FNullEnt (hurtEntity)) + return; + + hurtEntity->v.classname = MAKE_STRING (g_weaponDefs[m_currentWeapon].className); + hurtEntity->v.dmg_inflictor = GetEntity (); + hurtEntity->v.dmg = 9999.0; + hurtEntity->v.dmg_take = 1.0; + hurtEntity->v.dmgtime = 2.0; + hurtEntity->v.effects |= EF_NODRAW; + + (*g_engfuncs.pfnSetOrigin) (hurtEntity, Vector (-4000, -4000, -4000)); + + KeyValueData kv; + kv.szClassName = const_cast (g_weaponDefs[m_currentWeapon].className); + kv.szKeyName = "damagetype"; + kv.szValue = FormatBuffer ("%d", (1 << 4)); + kv.fHandled = FALSE; + + MDLL_KeyValue (hurtEntity, &kv); + + MDLL_Spawn (hurtEntity); + MDLL_Touch (hurtEntity, GetEntity ()); + + (*g_engfuncs.pfnRemoveEntity) (hurtEntity); +} + +void Bot::Kick (void) +{ + // this function kick off one bot from the server. + + ServerCommand ("kick #%d", GETPLAYERUSERID (GetEntity ())); + CenterPrint ("Bot '%s' kicked", STRING (pev->netname)); + + // balances quota + if (g_botManager->GetBotsNum () - 1 < yb_quota.GetInt ()) + yb_quota.SetInt (g_botManager->GetBotsNum () - 1); +} + +void Bot::StartGame (void) +{ + // this function handles the selection of teams & class + + // handle counter-strike stuff here... + if (m_startAction == GSM_TEAM_SELECT) + { + m_startAction = GSM_IDLE; // switch back to idle + + if (yb_join_team.GetString ()[0] == 'C' || yb_join_team.GetString ()[0] == 'c') + m_wantedTeam = 2; + else if (yb_join_team.GetString ()[0] == 'T' || yb_join_team.GetString ()[0] == 't') + m_wantedTeam = 1; + + if (m_wantedTeam != 1 && m_wantedTeam != 2) + m_wantedTeam = 5; + + // select the team the bot wishes to join... + FakeClientCommand (GetEntity (), "menuselect %d", m_wantedTeam); + } + else if (m_startAction == GSM_CLASS_SELECT) + { + m_startAction = GSM_IDLE; // switch back to idle + + if (g_gameVersion == CSV_CZERO) // czero has spetsnaz and militia skins + { + if (m_wantedClass < 1 || m_wantedClass > 5) + m_wantedClass = g_randGen.Long (1, 5); // use random if invalid + } + else + { + if (m_wantedClass < 1 || m_wantedClass > 4) + m_wantedClass = g_randGen.Long (1, 4); // use random if invalid + } + + // select the class the bot wishes to use... + FakeClientCommand (GetEntity (), "menuselect %d", m_wantedClass); + + // bot has now joined the game (doesn't need to be started) + m_notStarted = false; + + // check for greeting other players, since we connected + if (g_randGen.Long (0, 100) < 20) + ChatMessage (CHAT_WELCOME); + } +} diff --git a/source/chatlib.cpp b/source/chatlib.cpp new file mode 100644 index 0000000..605ac3c --- /dev/null +++ b/source/chatlib.cpp @@ -0,0 +1,468 @@ +// +// Copyright (c) 2014, by YaPB Development Team. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// Version: $Id:$ +// + +#include + +ConVar yb_chat ("yb_chat", "1"); + +void StripTags (char *buffer) +{ + // this function strips 'clan' tags specified below in given string buffer + + // first three tags for Enhanced POD-Bot (e[POD], 3[POD], E[POD]) + char *tagOpen[] = {"e[P", "3[P", "E[P", "-=", "-[", "-]", "-}", "-{", "<[", "<]", "[-", "]-", "{-", "}-", "[[", "[", "{", "]", "}", "<", ">", "-", "|", "=", "+"}; + char *tagClose[] = {"]", "]", "]", "=-", "]-", "[-", "{-", "}-", "]>", "[>", "-]", "-[", "-}", "-{", "]]", "]", "}", "[", "{", ">", "<", "-", "|", "=", "+"}; + + int index, fieldStart, fieldStop, i; + int length = strlen (buffer); // get length of string + + // foreach known tag... + for (index = 0; index < ARRAYSIZE_HLSDK (tagOpen); index++) + { + fieldStart = strstr (buffer, tagOpen[index]) - buffer; // look for a tag start + + // have we found a tag start? + if (fieldStart >= 0 && fieldStart < 32) + { + fieldStop = strstr (buffer, tagClose[index]) - buffer; // look for a tag stop + + // have we found a tag stop? + if ((fieldStop > fieldStart) && (fieldStop < 32)) + { + int tagLength = strlen (tagClose[index]); + + for (i = fieldStart; i < length - (fieldStop + tagLength - fieldStart); i++) + buffer[i] = buffer[i + (fieldStop + tagLength - fieldStart)]; // overwrite the buffer with the stripped string + + buffer[i] = 0x0; // terminate the string + } + } + } + + // have we stripped too much (all the stuff)? + if (buffer[0] != '\0') + { + strtrim (buffer); // if so, string is just a tag + + // strip just the tag part... + for (index = 0; index < ARRAYSIZE_HLSDK(tagOpen); index++) + { + fieldStart = strstr (buffer, tagOpen[index]) - buffer; // look for a tag start + + // have we found a tag start? + if (fieldStart >= 0 && fieldStart < 32) + { + fieldStop = fieldStart + strlen (tagOpen[index]); // set the tag stop + int tagLength = strlen (tagOpen[index]); + + for (i = fieldStart; i < length - tagLength; i++) + buffer[i] = buffer[i + tagLength]; // overwrite the buffer with the stripped string + + buffer[i] = 0x0; // terminate the string + + fieldStart = strstr (buffer, tagClose[index]) - buffer; // look for a tag stop + + // have we found a tag stop ? + if (fieldStart >= 0 && fieldStart < 32) + { + fieldStop = fieldStart + strlen (tagClose[index]); // set the tag + int tagLength = strlen (tagClose[index]); + + for (i = fieldStart; i < length - tagLength; i++) + buffer[i] = buffer[i + tagLength]; // overwrite the buffer with the stripped string + + buffer[i] = 0; // terminate the string + } + } + } + } + strtrim (buffer); // to finish, strip eventual blanks after and before the tag marks +} + +char *HumanizeName (char *name) +{ + // this function humanize player name (i.e. trim clan and switch to lower case (sometimes)) + + static char outputName[256]; // create return name buffer + strcpy (outputName, name); // copy name to new buffer + + // drop tag marks, 80 percent of time + if (g_randGen.Long (1, 100) < 80) + StripTags (outputName); + else + strtrim (outputName); + + // sometimes switch name to lower characters + // note: since we're using russian names written in english, we reduce this shit to 6 percent + if (g_randGen.Long (1, 100) <= 6) + { + for (int i = 0; i < static_cast (strlen (outputName)); i++) + outputName[i] = tolower (outputName[i]); // to lower case + } + return &outputName[0]; // return terminated string +} + +void HumanizeChat (char *buffer) +{ + // this function humanize chat string to be more handwritten + + int length = strlen (buffer); // get length of string + int i = 0; + + // sometimes switch text to lowercase + // note: since we're using russian chat written in english, we reduce this shit to 4 percent + if (g_randGen.Long (1, 100) <= 4) + { + for (i = 0; i < length; i++) + buffer[i] = tolower (buffer[i]); // switch to lowercase + } + + if (length > 15) + { + // "length / 2" percent of time drop a character + if (g_randGen.Long (1, 100) < (length / 2)) + { + int pos = g_randGen.Long ((length / 8), length - (length / 8)); // chose random position in string + + for (i = pos; i < length - 1; i++) + buffer[i] = buffer[i + 1]; // overwrite the buffer with stripped string + + buffer[i] = 0x0; // terminate string; + length--; // update new string length + } + + // "length" / 4 precent of time swap character + if (g_randGen.Long (1, 100) < (length / 4)) + { + int pos = g_randGen.Long ((length / 8), ((3 * length) / 8)); // choose random position in string + char ch = buffer[pos]; // swap characters + + buffer[pos] = buffer[pos + 1]; + buffer[pos + 1] = ch; + } + } + buffer[length] = 0; // terminate string +} + +void Bot::PrepareChatMessage (char *text) +{ + // this function parses messages from the botchat, replaces keywords and converts names into a more human style + + if (!yb_chat.GetBool () || IsNullString (text)) + return; + + memset (&m_tempStrings, 0, sizeof (m_tempStrings)); + + char *textStart = text; + char *pattern = text; + + edict_t *talkEntity = NULL; + + while (pattern != NULL) + { + // all replacement placeholders start with a % + pattern = strstr (textStart, "%"); + + if (pattern != NULL) + { + int length = pattern - textStart; + + if (length > 0) + strncpy (m_tempStrings, textStart, length); + + pattern++; + + // player with most frags? + if (*pattern == 'f') + { + int highestFrags = -9000; // just pick some start value + int index = 0; + + for (int i = 0; i < GetMaxClients (); i++) + { + if (!(g_clients[i].flags & CF_USED) || g_clients[i].ent == GetEntity ()) + continue; + + int frags = static_cast (g_clients[i].ent->v.frags); + + if (frags > highestFrags) + { + highestFrags = frags; + index = i; + } + } + talkEntity = g_clients[index].ent; + + if (!FNullEnt (talkEntity)) + strcat (m_tempStrings, HumanizeName (const_cast (STRING (talkEntity->v.netname)))); + } + // mapname? + else if (*pattern == 'm') + strcat (m_tempStrings, GetMapName ()); + // roundtime? + else if (*pattern == 'r') + { + int time = static_cast (g_timeRoundEnd - GetWorldTime ()); + strcat (m_tempStrings, FormatBuffer ("%02d:%02d", time / 60, time % 60)); + } + // chat reply? + else if (*pattern == 's') + { + talkEntity = INDEXENT (m_sayTextBuffer.entityIndex); + + if (!FNullEnt (talkEntity)) + strcat (m_tempStrings, HumanizeName (const_cast (STRING (talkEntity->v.netname)))); + } + // teammate alive? + else if (*pattern == 't') + { + int team = GetTeam (GetEntity ()); + int i; + + for (i = 0; i < GetMaxClients (); i++) + { + if (!(g_clients[i].flags & CF_USED) || !(g_clients[i].flags & CF_ALIVE) || (g_clients[i].team != team) || (g_clients[i].ent == GetEntity ())) + continue; + + break; + } + + if (i < GetMaxClients ()) + { + if (!FNullEnt (pev->dmg_inflictor) && (GetTeam (GetEntity ()) == GetTeam (pev->dmg_inflictor))) + talkEntity = pev->dmg_inflictor; + else + talkEntity = g_clients[i].ent; + + if (!FNullEnt (talkEntity)) + strcat (m_tempStrings, HumanizeName (const_cast (STRING (talkEntity->v.netname)))); + } + else // no teammates alive... + { + for (i = 0; i < GetMaxClients (); i++) + { + if (!(g_clients[i].flags & CF_USED) || (g_clients[i].team != team) || (g_clients[i].ent == GetEntity ())) + continue; + + break; + } + + if (i < GetMaxClients ()) + { + talkEntity = g_clients[i].ent; + + if (!FNullEnt (talkEntity)) + strcat (m_tempStrings, HumanizeName (const_cast (STRING (talkEntity->v.netname)))); + } + } + } + else if (*pattern == 'e') + { + int team = GetTeam (GetEntity ()); + int i; + + for (i = 0; i < GetMaxClients (); i++) + { + if (!(g_clients[i].flags & CF_USED) || !(g_clients[i].flags & CF_ALIVE) || (g_clients[i].team == team) || (g_clients[i].ent == GetEntity ())) + continue; + break; + } + + if (i < GetMaxClients ()) + { + talkEntity = g_clients[i].ent; + + if (!FNullEnt (talkEntity)) + strcat (m_tempStrings, HumanizeName (const_cast (STRING (talkEntity->v.netname)))); + } + else // no teammates alive... + { + for (i = 0; i < GetMaxClients (); i++) + { + if (!(g_clients[i].flags & CF_USED) || (g_clients[i].team == team) || (g_clients[i].ent == GetEntity ())) + continue; + break; + } + if (i < GetMaxClients ()) + { + talkEntity = g_clients[i].ent; + + if (!FNullEnt (talkEntity)) + strcat (m_tempStrings, HumanizeName (const_cast (STRING (talkEntity->v.netname)))); + } + } + } + else if (*pattern == 'd') + { + if (g_gameVersion == CSV_CZERO) + { + if (g_randGen.Long (1, 100) < 30) + strcat (m_tempStrings, "CZ"); + else if (g_randGen.Long (1, 100) < 80) + strcat (m_tempStrings, "KoHTpa K3"); + else + strcat (m_tempStrings, "Condition Zero"); + } + else if ((g_gameVersion == CSV_STEAM) || (g_gameVersion == CSV_OLD)) + { + if (g_randGen.Long (1, 100) < 30) + strcat (m_tempStrings, "CS"); + else if (g_randGen.Long (1, 100) < 80) + strcat (m_tempStrings, "KoHTpa"); + else + strcat (m_tempStrings, "Counter-Strike"); + } + } + else if (*pattern == 'v') + { + talkEntity = m_lastVictim; + + if (!FNullEnt (talkEntity)) + strcat (m_tempStrings, HumanizeName (const_cast (STRING (talkEntity->v.netname)))); + } + pattern++; + textStart = pattern; + } + } + + // let the bots make some mistakes... + char tempString[160]; + strncpy (tempString, textStart, 159); + + HumanizeChat (tempString); + strcat (m_tempStrings, tempString); +} + +bool CheckKeywords (char *tempMessage, char *reply) +{ + // this function checks is string contain keyword, and generates relpy to it + + if (!yb_chat.GetBool () || IsNullString (tempMessage)) + return false; + + IterateArray (g_replyFactory, i) + { + IterateArray (g_replyFactory[i].keywords, j) + { + // check is keyword has occurred in message + if (strstr (tempMessage, g_replyFactory[i].keywords[j].GetBuffer ()) != NULL) + { + Array &replies = g_replyFactory[i].usedReplies; + + if (replies.GetElementNumber () >= g_replyFactory[i].replies.GetElementNumber () / 2) + replies.RemoveAll (); + + bool replyUsed = false; + const char *generatedReply = g_replyFactory[i].replies.GetRandomElement (); + + // don't say this twice + IterateArray (replies, k) + { + if (strstr (replies[k].GetBuffer (), generatedReply) != NULL) + replyUsed = true; + } + + // reply not used, so use it + if (!replyUsed) + { + strcpy (reply, generatedReply); // update final buffer + replies.Push (generatedReply); //add to ignore list + + return true; + } + } + } + } + + // didn't find a keyword? 70% of the time use some universal reply + if (g_randGen.Long (1, 100) < 70 && !g_chatFactory[CHAT_NOKW].IsEmpty ()) + { + strcpy (reply, g_chatFactory[CHAT_NOKW].GetRandomElement ().GetBuffer ()); + return true; + } + return false; +} + +bool Bot::ParseChat (char *reply) +{ + // this function parse chat buffer, and prepare buffer to keyword searching + + char tempMessage[512]; + strcpy (tempMessage, m_sayTextBuffer.sayText); // copy to safe place + + // text to uppercase for keyword parsing + for (int i = 0; i < static_cast (strlen (tempMessage)); i++) + tempMessage[i] = toupper (tempMessage[i]); + + return CheckKeywords (tempMessage, reply); +} + +bool Bot::RepliesToPlayer (void) +{ + // this function sends reply to a player + + if (m_sayTextBuffer.entityIndex != -1 && !IsNullString (m_sayTextBuffer.sayText)) + { + char text[256]; + + // check is time to chat is good + if (m_sayTextBuffer.timeNextChat < GetWorldTime ()) + { + if (g_randGen.Long (1, 100) < m_sayTextBuffer.chatProbability + g_randGen.Long (2, 10) && ParseChat (reinterpret_cast (&text))) + { + PrepareChatMessage (text); + PushMessageQueue (GSM_SAY); + + m_sayTextBuffer.entityIndex = -1; + m_sayTextBuffer.sayText[0] = 0x0; + m_sayTextBuffer.timeNextChat = GetWorldTime () + m_sayTextBuffer.chatDelay; + + return true; + } + m_sayTextBuffer.entityIndex = -1; + m_sayTextBuffer.sayText[0] = 0x0; + } + } + return false; +} + +void Bot::SayText (const char *text) +{ + // this function prints saytext message to all players + + if (IsNullString (text)) + return; + + FakeClientCommand (GetEntity (), "say \"%s\"", text); +} + +void Bot::TeamSayText (const char *text) +{ + // this function prints saytext message only for teammates + + if (IsNullString (text)) + return; + + FakeClientCommand (GetEntity (), "say_team \"%s\"", text); +} diff --git a/source/combat.cpp b/source/combat.cpp new file mode 100644 index 0000000..dfc867a --- /dev/null +++ b/source/combat.cpp @@ -0,0 +1,1539 @@ +// +// Copyright (c) 2014, by YaPB Development Team. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// Version: $Id:$ +// + +#include + +ConVar yb_shoots_thru_walls ("yb_shoots_thru_walls", "1"); +ConVar yb_ignore_enemies ("yb_ignore_enemies", "0"); +ConVar yb_csdm_mode ("yb_csdm_mode", "0"); + +ConVar mp_friendlyfire ("mp_friendlyfire", NULL, VT_NOREGISTER); + +int Bot::GetNearbyFriendsNearPosition (Vector origin, int radius) +{ + int count = 0, team = GetTeam (GetEntity ()); + + for (int i = 0; i < GetMaxClients (); i++) + { + if (!(g_clients[i].flags & CF_USED) || !(g_clients[i].flags & CF_ALIVE) || g_clients[i].team != team || g_clients[i].ent == GetEntity ()) + continue; + + if ((g_clients[i].origin - origin).GetLengthSquared () < static_cast (radius * radius)) + count++; + } + return count; +} + +int Bot::GetNearbyEnemiesNearPosition (Vector origin, int radius) +{ + int count = 0, team = GetTeam (GetEntity ()); + + for (int i = 0; i < GetMaxClients (); i++) + { + if (!(g_clients[i].flags & CF_USED) || !(g_clients[i].flags & CF_ALIVE) || g_clients[i].team == team) + continue; + + if ((g_clients[i].origin - origin).GetLengthSquared () < static_cast (radius * radius)) + count++; + } + return count; +} + +bool Bot::LookupEnemy (void) +{ + // this function tries to find the best suitable enemy for the bot + + m_visibility = 0; + + // do not search for enemies while we're blinded, or shooting disabled by user + if (m_blindTime > GetWorldTime () || yb_ignore_enemies.GetBool ()) + return false; + + // do not check for new enemy too fast + if (!FNullEnt (m_enemy) && m_enemyUpdateTime > GetWorldTime () && !(m_states & STATE_SUSPECT_ENEMY)) + { + m_aimFlags |= AIM_ENEMY; + return true; + } + edict_t *player, *newEnemy = NULL; + + float nearestDistance = m_viewDistance; + int i, team = GetTeam (GetEntity ()); + + // setup potentially visible set for this bot + Vector potentialVisibility = EyePosition (); + + if (pev->flags & FL_DUCKING) + potentialVisibility = potentialVisibility + (VEC_HULL_MIN - VEC_DUCK_HULL_MIN); + + byte *pvs = ENGINE_SET_PVS (reinterpret_cast (&potentialVisibility)); + + // clear suspected flag + if (m_seeEnemyTime + 4.0 < GetWorldTime ()) + m_states &= ~STATE_SUSPECT_ENEMY; + + if (!FNullEnt (m_enemy)) + { + player = m_enemy; + + // is player is alive + if (IsAlive (player) && IsEnemyViewable (player)) + newEnemy = player; + } + + // the old enemy is no longer visible or + if (FNullEnt (newEnemy)) + { + m_enemyUpdateTime = GetWorldTime () + 0.25; + + + // search the world for players... + for (i = 0; i < GetMaxClients (); i++) + { + if (!(g_clients[i].flags & CF_USED) || !(g_clients[i].flags & CF_ALIVE) || (g_clients[i].team == team) || (g_clients[i].ent == GetEntity ())) + continue; + + player = g_clients[i].ent; + + // let the engine check if this player is potentially visible + if (!ENGINE_CHECK_VISIBILITY (player, pvs)) + continue; + + // skip glowed players, in free for all mode, we can't hit them + if (player->v.renderfx == kRenderFxGlowShell && yb_csdm_mode.GetInt () >= 1) + continue; + + // do some blind by smoke grenade + if (IsBehindSmokeClouds (player) && m_blindRecognizeTime < GetWorldTime ()) + m_blindRecognizeTime = GetWorldTime () + g_randGen.Float (2.0, 3.0); + + if (player->v.button & (IN_ATTACK | IN_ATTACK2)) + m_blindRecognizeTime = GetWorldTime () - 0.1; + + // see if bot can see the player... + if (m_blindRecognizeTime < GetWorldTime () && IsEnemyViewable (player)) + { + float distance = (player->v.origin - pev->origin).GetLength (); + + if (distance < nearestDistance) + { + if (IsEnemyProtectedByShield (player)) + continue; + + nearestDistance = distance; + newEnemy = player; + + // aim VIP first on AS maps... + if ((g_mapType & MAP_AS) && *(INFOKEY_VALUE (GET_INFOKEYBUFFER (player), "model")) == 'v') + break; + } + } + } + } + + if (IsValidPlayer (newEnemy)) + { + g_botsCanPause = true; + m_aimFlags |= AIM_ENEMY; + + if (newEnemy == m_enemy) + { + // if enemy is still visible and in field of view, keep it keep track of when we last saw an enemy + m_seeEnemyTime = GetWorldTime (); + + // zero out reaction time + m_actualReactionTime = 0.0; + m_lastEnemy = newEnemy; + m_lastEnemyOrigin = newEnemy->v.origin; + + return true; + } + else + { + if (m_seeEnemyTime + 3.0 < GetWorldTime () && (pev->weapons & (1 << WEAPON_C4) || HasHostage () || !FNullEnt (m_targetEntity))) + RadioMessage (Radio_EnemySpotted); + + m_targetEntity = NULL; // stop following when we see an enemy... + + if (g_randGen.Long (0, 100) < m_skill) + m_enemySurpriseTime = GetWorldTime () + (m_actualReactionTime / 3); + else + m_enemySurpriseTime = GetWorldTime () + m_actualReactionTime; + + // zero out reaction time + m_actualReactionTime = 0.0; + m_enemy = newEnemy; + m_lastEnemy = newEnemy; + m_lastEnemyOrigin = newEnemy->v.origin; + m_enemyReachableTimer = 0.0; + + // keep track of when we last saw an enemy + m_seeEnemyTime = GetWorldTime (); + + // now alarm all teammates who see this bot & don't have an actual enemy of the bots enemy should simulate human players seeing a teammate firing + for (int j = 0; j < GetMaxClients (); j++) + { + if (!(g_clients[j].flags & CF_USED) || !(g_clients[j].flags & CF_ALIVE) || g_clients[j].team != team || g_clients[j].ent == GetEntity ()) + continue; + + Bot *friendBot = g_botManager->GetBot (g_clients[j].ent); + + if (friendBot != NULL) + { + if (friendBot->m_seeEnemyTime + 2.0 < GetWorldTime () || FNullEnt (friendBot->m_lastEnemy)) + { + if (IsVisible (pev->origin, ENT (friendBot->pev))) + { + friendBot->m_lastEnemy = newEnemy; + friendBot->m_lastEnemyOrigin = m_lastEnemyOrigin; + friendBot->m_seeEnemyTime = GetWorldTime (); + } + } + } + } + return true; + } + } + else if (!FNullEnt (m_enemy)) + { + newEnemy = m_enemy; + m_lastEnemy = newEnemy; + + if (!IsAlive (newEnemy)) + { + m_enemy = NULL; + + // shoot at dying players if no new enemy to give some more human-like illusion + if (m_seeEnemyTime + 0.1 > GetWorldTime ()) + { + if (!UsesSniper ()) + { + m_shootAtDeadTime = GetWorldTime () + 0.2; + m_actualReactionTime = 0.0; + m_states |= STATE_SUSPECT_ENEMY; + + return true; + } + return false; + } + else if (m_shootAtDeadTime > GetWorldTime ()) + { + m_actualReactionTime = 0.0; + m_states |= STATE_SUSPECT_ENEMY; + + return true; + } + return false; + } + + // if no enemy visible check if last one shoot able through wall + if (yb_shoots_thru_walls.GetBool () && g_randGen.Long (1, 100) < g_skillTab[m_skill / 20].seenShootThruProb) + { + if (IsShootableThruObstacle (newEnemy->v.origin)) + { + m_seeEnemyTime = GetWorldTime () - 0.35f; + + m_states |= STATE_SUSPECT_ENEMY; + m_aimFlags |= AIM_LAST_ENEMY; + + m_enemy = newEnemy; + m_lastEnemy = newEnemy; + m_lastEnemyOrigin = newEnemy->v.origin; + + return true; + } + } + } + + // check if bots should reload... + if ((m_aimFlags <= AIM_PREDICT_ENEMY && m_seeEnemyTime + 3.0 < GetWorldTime () && !(m_states & (STATE_SEEING_ENEMY | STATE_HEARING_ENEMY)) && FNullEnt (m_lastEnemy) && FNullEnt (m_enemy) && GetTaskId () != TASK_SHOOTBREAKABLE && GetTaskId () != TASK_PLANTBOMB && GetTaskId () != TASK_DEFUSEBOMB) || g_roundEnded) + { + if (!m_reloadState) + m_reloadState = RELOAD_PRIMARY; + } + + // is the bot using a sniper rifle or a zoomable rifle? + if ((UsesSniper () || UsesZoomableRifle ()) && m_zoomCheckTime + 1.0 < GetWorldTime ()) + { + if (pev->fov < 90) // let the bot zoom out + pev->button |= IN_ATTACK2; + else + m_zoomCheckTime = 0.0; + } + return false; +} + +Vector Bot::GetAimPosition (void) +{ + // the purpose of this function, is to make bot aiming not so ideal. it's mutate m_enemyOrigin enemy vector + // returned from visibility check function. + + float distance = (m_enemy->v.origin - pev->origin).GetLength (); + + // get enemy position initially + Vector targetOrigin = m_enemy->v.origin; + + // do not aim at head, at long distance (only if not using sniper weapon) + if ((m_visibility & VISIBLE_BODY) && !UsesSniper () && !UsesPistol () && (distance > (yb_hardcore_mode.GetBool () ? 3400.0 : 2600.0))) + m_visibility &= ~VISIBLE_HEAD; + + // if we only suspect an enemy behind a wall take the worst skill + if ((m_states & STATE_SUSPECT_ENEMY) && !(m_states & STATE_SEEING_ENEMY)) + targetOrigin = targetOrigin + Vector (g_randGen.Float (m_enemy->v.mins.x * 0.5f, m_enemy->v.maxs.x * 0.5f), g_randGen.Float (m_enemy->v.mins.y * 0.5f, m_enemy->v.maxs.y * 0.5f), g_randGen.Float (m_enemy->v.mins.z * 0.5f, m_enemy->v.maxs.z * 0.5f)); + else + { + // now take in account different parts of enemy body + if (m_visibility & (VISIBLE_HEAD | VISIBLE_BODY)) // visible head & body + { + // now check is our skill match to aim at head, else aim at enemy body + if ((g_randGen.Long (1, 100) < g_skillTab[m_skill / 20].headshotFrequency) || UsesPistol ()) + targetOrigin = targetOrigin + m_enemy->v.view_ofs + Vector (0.0f, 0.0f, GetZOffset (distance)); + else + targetOrigin = targetOrigin + Vector (0.0f, 0.0f, 3.52f); + } + else if (m_visibility & VISIBLE_HEAD) // visible only head + targetOrigin = targetOrigin + m_enemy->v.view_ofs + Vector (0.0f, 0.0f, GetZOffset (distance)); + else if (m_visibility & VISIBLE_BODY) // visible only body + targetOrigin = targetOrigin + Vector (0.0f, 0.0f, 3.52f); + else if (m_visibility & VISIBLE_OTHER) // random part of body is visible + targetOrigin = m_enemyOrigin; + else // something goes wrong, use last enemy origin + targetOrigin = m_lastEnemyOrigin; + + m_lastEnemyOrigin = targetOrigin; + } + + if (!yb_hardcore_mode.GetBool ()) + { + float divOffs, distance = (m_enemyOrigin - pev->origin).GetLength (); + + if (pev->fov < 40) + divOffs = distance / 2000; + else if (pev->fov < 90) + divOffs = distance / 1000; + else + divOffs = distance / 500; + + targetOrigin.x += divOffs * g_randGen.Float (-g_skillTab[m_skill / 20].aimOffs_X, g_skillTab[m_skill / 20].aimOffs_X); + targetOrigin.y += divOffs * g_randGen.Float (-g_skillTab[m_skill / 20].aimOffs_Y, g_skillTab[m_skill / 20].aimOffs_Y); + targetOrigin.z += divOffs * g_randGen.Float (-g_skillTab[m_skill / 20].aimOffs_Z, g_skillTab[m_skill / 20].aimOffs_Z); + + // randomize the target position + m_enemyOrigin = targetOrigin + ((pev->velocity - m_enemy->v.velocity).SkipZ () * m_frameInterval * 1.2); + } + else + m_enemyOrigin = targetOrigin; + + return m_enemyOrigin; +} + +float Bot::GetZOffset (float distance) +{ + // got it from pbmm + + bool sniper = UsesSniper (); + bool pistol = UsesPistol (); + bool rifle = UsesRifle (); + + bool zoomableRifle = UsesZoomableRifle (); + bool submachine = UsesSubmachineGun (); + bool shotgun = (m_currentWeapon == WEAPON_XM1014 || m_currentWeapon == WEAPON_M3); + bool m249 = m_currentWeapon == WEAPON_M249; + + const float BurstDistance = 300.0f; + const float DoubleBurstDistance = BurstDistance * 2; + + float result = 3.0f; + + if (distance < 2800.0f && distance > DoubleBurstDistance) + { + if (sniper) result = 3.5f; + else if (zoomableRifle) result = 4.5f; + else if (pistol) result = 6.5f; + else if (submachine) result = 5.5f; + else if (rifle) result = 5.5f; + else if (m249) result = 2.5f; + else if (shotgun) result = 10.5f; + } + else if (distance > BurstDistance && distance <= DoubleBurstDistance) + { + if (sniper) result = 3.5f; + else if (zoomableRifle) result = 3.5f; + else if (pistol) result = 6.5f; + else if (submachine) result = 3.5f; + else if (rifle) result = 1.0f; + else if (m249) result = -1.0f; + else if (shotgun) result = 10.0f; + } + else if (distance < BurstDistance) + { + if (sniper) result = 4.5f; + else if (zoomableRifle) result = -5.0f; + else if (pistol) result = 4.5f; + else if (submachine) result = -4.5f; + else if (rifle) result = -4.5f; + else if (m249) result = -6.0f; + else if (shotgun) result = -5.0f; + } + return result; +} + +bool Bot::IsFriendInLineOfFire (float distance) +{ + // bot can't hurt teammates, if friendly fire is not enabled... + if (!mp_friendlyfire.GetBool () || yb_csdm_mode.GetInt () > 0) + return false; + + MakeVectors (pev->v_angle); + + TraceResult tr; + TraceLine (EyePosition (), EyePosition () + pev->v_angle.Normalize () * distance, false, false, GetEntity (), &tr); + + // check if we hit something + if (!FNullEnt (tr.pHit)) + { + int playerIndex = ENTINDEX (tr.pHit) - 1; + + // check valid range + if (playerIndex >= 0 && playerIndex < GetMaxClients () && g_clients[playerIndex].team == GetTeam (GetEntity ()) && (g_clients[playerIndex].flags & CF_ALIVE)) + return true; + } + + // search the world for players + for (int i = 0; i < GetMaxClients (); i++) + { + if (!(g_clients[i].flags & CF_USED) || !(g_clients[i].flags & CF_ALIVE) || g_clients[i].team != GetTeam (GetEntity ()) || g_clients[i].ent == GetEntity ()) + continue; + + edict_t *ent = g_clients[i].ent; + + float friendDistance = (ent->v.origin - pev->origin).GetLength (); + float squareDistance = sqrtf (1089.0 + (friendDistance * friendDistance)); + + if (GetShootingConeDeviation (GetEntity (), &ent->v.origin) > (friendDistance * friendDistance) / (squareDistance * squareDistance) && friendDistance <= distance) + return true; + } + return false; +} + +bool Bot::IsShootableThruObstacle (Vector dest) +{ + // this function returns if enemy can be shoot through some obstacle + + if (m_skill <= 60 || !IsWeaponShootingThroughWall (m_currentWeapon)) + return false; + + Vector source = EyePosition (); + Vector direction = (dest - source).Normalize (); // 1 unit long + Vector point = nullvec; + + int thikness = 0; + int numHits = 0; + + TraceResult tr; + TraceLine (source, dest, true, true, GetEntity (), &tr); + + while (tr.flFraction != 1.0 && numHits < 3) + { + numHits++; + thikness++; + + point = tr.vecEndPos + direction; + + while (POINT_CONTENTS (point) == CONTENTS_SOLID && thikness < 98) + { + point = point + direction; + thikness++; + } + TraceLine (point, dest, true, true, GetEntity (), &tr); + } + + if (numHits < 3 && thikness < 98) + { + if ((dest - point).GetLengthSquared () < 13143) + return true; + } + return false; +} + +bool Bot::DoFirePause (float distance, FireDelay *fireDelay) +{ + // returns true if bot needs to pause between firing to compensate for punchangle & weapon spread + + if (UsesSniper ()) + { + m_shootTime = GetWorldTime (); + return false; + } + + if (m_firePause > GetWorldTime ()) + return true; + + // check if we need to compensate recoil + if (tanf ((fabsf (pev->punchangle.y) + fabsf (pev->punchangle.x)) * Math::MATH_PI / 360.0) * distance > 20 + m_skillOffset) + { + if (m_firePause < GetWorldTime () - 0.4) + m_firePause = GetWorldTime () + g_randGen.Float (0.4, 0.4 + 1.2 * m_skillOffset); + + return true; + } + + if (!yb_hardcore_mode.GetBool () && fireDelay->maxFireBullets + g_randGen.Long (0, 1) <= m_burstShotsFired) + { + float delayTime = 0.1 * distance / fireDelay->minBurstPauseFactor; + + if (delayTime > (125.0 / (m_skill + 1))) + delayTime = 125.0 / (m_skill + 1); + + m_firePause = GetWorldTime () + delayTime; + m_burstShotsFired = 0; + + return true; + } + return false; +} + +void Bot::FireWeapon (void) +{ + // this function will return true if weapon was fired, false otherwise + float distance = (m_lookAt - EyePosition ()).GetLength (); // how far away is the enemy? + + // if using grenade stop this + if (m_isUsingGrenade) + { + m_shootTime = GetWorldTime () + 0.1; + return; + } + + // or if friend in line of fire, stop this too but do not update shoot time + if (!FNullEnt (m_enemy) && IsFriendInLineOfFire (distance)) + return; + + FireDelay *delay = &g_fireDelay[0]; + WeaponSelect *selectTab = &g_weaponSelect[0]; + + edict_t *enemy = m_enemy; + + int selectId = WEAPON_KNIFE, selectIndex = 0, chosenWeaponIndex = 0; + int weapons = pev->weapons; + + // if jason mode use knife only + if (yb_jasonmode.GetBool ()) + goto WeaponSelectEnd; + + // use knife if near and good skill (l33t dude!) + if (m_skill > 80 && !FNullEnt (enemy) && distance < 80 && pev->health > 80 && pev->health >= enemy->v.health && !IsGroupOfEnemies (pev->origin)) + goto WeaponSelectEnd; + + // loop through all the weapons until terminator is found... + while (selectTab[selectIndex].id) + { + // is the bot carrying this weapon? + if (weapons & (1 << selectTab[selectIndex].id)) + { + // is enough ammo available to fire AND check is better to use pistol in our current situation... + if (m_ammoInClip[selectTab[selectIndex].id] > 0 && !IsWeaponBadInDistance (selectIndex, distance)) + chosenWeaponIndex = selectIndex; + } + selectIndex++; + } + selectId = selectTab[chosenWeaponIndex].id; + + // if no available weapon... + if (chosenWeaponIndex == 0) + { + selectIndex = 0; + + // loop through all the weapons until terminator is found... + while (selectTab[selectIndex].id) + { + int id = selectTab[selectIndex].id; + + // is the bot carrying this weapon? + if (weapons & (1 << id)) + { + if (g_weaponDefs[id].ammo1 != -1 && m_ammo[g_weaponDefs[id].ammo1] >= selectTab[selectIndex].minPrimaryAmmo) + { + // available ammo found, reload weapon + if (m_reloadState == RELOAD_NONE || m_reloadCheckTime > GetWorldTime ()) + { + m_isReloading = true; + m_reloadState = RELOAD_PRIMARY; + m_reloadCheckTime = GetWorldTime (); + + RadioMessage (Radio_NeedBackup); + } + return; + } + } + selectIndex++; + } + selectId = WEAPON_KNIFE; // no available ammo, use knife! + } + + // ignore enemies protected by shields + if (IsEnemyProtectedByShield (m_enemy) && !(m_currentWeapon == WEAPON_KNIFE) && IsEnemyViewable (m_enemy)) + { + if (!g_bombPlanted && g_randGen.Float (0, 100) < 50) + StartTask (TASK_CAMP, TASKPRI_CAMP, -1, GetWorldTime () + g_randGen.Float (10, 20), true); + + } + + // check if bot has shield + if (HasShield () && m_shieldCheckTime < GetWorldTime () && GetTaskId () != TASK_CAMP && IsEnemyViewable (m_enemy)) + { + if (IsGroupOfEnemies (pev->origin, 3, 750) && !IsShieldDrawn () && !g_bombPlanted) + StartTask (TASK_SEEKCOVER, TASKPRI_SEEKCOVER, -1, GetWorldTime () + g_randGen.Float (10, 20), true); + + if (distance >= 750 || ((m_enemy->v.button & IN_ATTACK) && !IsShieldDrawn())) + { + pev->button |= (IN_ATTACK2 | IN_DUCK); // draw the shield + pev->button &= ~IN_DUCK; + + if (IsGroupOfEnemies (pev->origin, 3, 550) || (GetNearbyEnemiesNearPosition (pev->origin, 550) >= 3 && IsShieldDrawn ())) + { + ChooseAimDirection(); + FacePosition (); + } + else if(!g_bombPlanted) + StartTask (TASK_CAMP, TASKPRI_PAUSE, -1, GetWorldTime () + g_randGen.Float (10, 20), true); + + if (IsEnemyProtectedByShield (m_lastEnemy) && !(m_currentWeapon == WEAPON_KNIFE) && IsEnemyViewable (m_lastEnemy)) + { + pev->button &= ~IN_ATTACK; + StartTask (TASK_CAMP, TASKPRI_CAMP, -1, GetWorldTime () + g_randGen.Float (10, 20), true); + } + } + else if (IsShieldDrawn () || (!FNullEnt (m_enemy) && (m_enemy->v.button & IN_RELOAD) || !IsEnemyViewable (m_enemy))) + { + pev->button |= (IN_ATTACK2 | IN_DUCK); // draw out the shield + if (!g_bombPlanted) + StartTask (TASK_SEEKCOVER, TASKPRI_SEEKCOVER, -1, GetWorldTime () + g_randGen.Float (10, 25), true); + } + m_shieldCheckTime = GetWorldTime () + 0.5; + } + +WeaponSelectEnd: + // we want to fire weapon, don't reload now + if (!m_isReloading) + { + m_reloadState = RELOAD_NONE; + m_reloadCheckTime = GetWorldTime () + 3.0; + } + + // select this weapon if it isn't already selected + if (m_currentWeapon != selectId) + { + SelectWeaponByName (g_weaponDefs[selectId].className); + + // reset burst fire variables + m_firePause = 0.0; + m_timeLastFired = 0.0; + m_burstShotsFired = 0; + + return; + } + + if (delay[chosenWeaponIndex].weaponIndex != selectId) + return; + + if (selectTab[chosenWeaponIndex].id != selectId) + { + chosenWeaponIndex = 0; + + // loop through all the weapons until terminator is found... + while (selectTab[chosenWeaponIndex].id) + { + if (selectTab[chosenWeaponIndex].id == selectId) + break; + + chosenWeaponIndex++; + } + } + + // if we're have a glock or famas vary burst fire mode + CheckBurstMode (distance); + + if (HasShield () && m_shieldCheckTime < GetWorldTime () && GetTaskId () != TASK_CAMP) // better shield gun usage + { + if ((distance >= 750) && !IsShieldDrawn ()) + pev->button |= IN_ATTACK2; // draw the shield + else if (IsShieldDrawn () || (!FNullEnt (m_enemy) && (m_enemy->v.button & IN_RELOAD) || !IsEnemyViewable(m_enemy))) + pev->button |= IN_ATTACK2; // draw out the shield + + m_shieldCheckTime = GetWorldTime () + 1.0; + } + + if (UsesSniper () && m_zoomCheckTime < GetWorldTime ()) // is the bot holding a sniper rifle? + { + if (distance > 1500 && pev->fov >= 40) // should the bot switch to the long-range zoom? + pev->button |= IN_ATTACK2; + + else if (distance > 150 && pev->fov >= 90) // else should the bot switch to the close-range zoom ? + pev->button |= IN_ATTACK2; + + else if (distance <= 150 && pev->fov < 90) // else should the bot restore the normal view ? + pev->button |= IN_ATTACK2; + + m_zoomCheckTime = GetWorldTime (); + + if (!FNullEnt (m_enemy) && (pev->velocity.x != 0 || pev->velocity.y != 0 || pev->velocity.z != 0) && (pev->basevelocity.x != 0 || pev->basevelocity.y != 0 || pev->basevelocity.z != 0)) + { + m_moveSpeed = 0.0; + m_strafeSpeed = 0.0; + m_navTimeset = GetWorldTime (); + } + } + else if (UsesZoomableRifle () && m_zoomCheckTime < GetWorldTime () && m_skill < 90) // else is the bot holding a zoomable rifle? + { + if (distance > 800 && pev->fov >= 90) // should the bot switch to zoomed mode? + pev->button |= IN_ATTACK2; + + else if (distance <= 800 && pev->fov < 90) // else should the bot restore the normal view? + pev->button |= IN_ATTACK2; + + m_zoomCheckTime = GetWorldTime (); + } + + if (HasPrimaryWeapon () && GetAmmoInClip () <= 0) + { + if (GetAmmo () <= 0 && !(m_states &= ~(STATE_THROW_HE | STATE_THROW_FB | STATE_THROW_SG))) + SelectPistol(); + + pev->button |= IN_RELOAD; + pev->button &= ~IN_ATTACK; + + return; + } + + const float baseDelay = delay[chosenWeaponIndex].primaryBaseDelay; + const float minDelay = delay[chosenWeaponIndex].primaryMinDelay[abs ((m_skill / 20) - 5)]; + const float maxDelay = delay[chosenWeaponIndex].primaryMaxDelay[abs ((m_skill / 20) - 5)]; + + // need to care for burst fire? + if (distance < 256.0 || m_blindTime > GetWorldTime ()) + { + if (selectId == WEAPON_KNIFE) + { + if (distance < 64.0) + { + if (g_randGen.Long (1, 100) < 30) + pev->button |= IN_ATTACK; // use primary attack + else + pev->button |= IN_ATTACK2; // use secondary attack + } + } + else + { + LookupEnemy(); + + if (selectTab[chosenWeaponIndex].primaryFireHold && m_ammo[g_weaponDefs[selectTab[selectIndex].id].ammo1] >= selectTab[selectIndex].minPrimaryAmmo) // if automatic weapon, just press attack + { + if (!IsEnemyProtectedByShield(m_lastEnemy)) + { + pev->button &= ~IN_ATTACK; + LookupEnemy(); + } + pev->button |= IN_ATTACK; + } + else // if not, toggle buttons + { + if ((pev->oldbuttons & IN_ATTACK) == 0) + { + if (!IsEnemyProtectedByShield (m_lastEnemy)) + { + pev->button &= ~IN_ATTACK; + LookupEnemy(); + } + pev->button |= IN_ATTACK; + } + } + } + + if (pev->button & IN_ATTACK) + m_shootTime = GetWorldTime (); + } + else + { + if (DoFirePause (distance, &delay[chosenWeaponIndex])) + return; + + // don't attack with knife over long distance + if (selectId == WEAPON_KNIFE) + return; + + if (selectTab[chosenWeaponIndex].primaryFireHold) + { + m_shootTime = GetWorldTime (); + m_zoomCheckTime = GetWorldTime (); + + + if (!IsEnemyProtectedByShield(m_lastEnemy)) + { + pev->button &= ~IN_ATTACK; + LookupEnemy(); + } + pev->button |= IN_ATTACK; // use primary attack + } + else + { + if (!IsEnemyProtectedByShield(m_lastEnemy)) + { + pev->button &= ~IN_ATTACK; + LookupEnemy();; + } + + pev->button |= IN_ATTACK; + m_shootTime = GetWorldTime () + baseDelay + g_randGen.Float (minDelay, maxDelay); + m_zoomCheckTime = GetWorldTime (); + } + + } +} + +bool Bot::IsWeaponBadInDistance (int weaponIndex, float distance) +{ + // this function checks, is it better to use pistol instead of current primary weapon + // to attack our enemy, since current weapon is not very good in this situation. + + int weaponID = g_weaponSelect[weaponIndex].id; + + // check is ammo available for secondary weapon + if (m_ammoInClip[g_weaponSelect[GetBestSecondaryWeaponCarried ()].id] >= 1) + return false; + + // better use pistol in short range distances, when using sniper weapons + if ((weaponID == WEAPON_SCOUT || weaponID == WEAPON_AWP || weaponID == WEAPON_G3SG1 || weaponID == WEAPON_SG550) && distance < 300.0) + return true; + + // shotguns is too inaccurate at long distances, so weapon is bad + if ((weaponID == WEAPON_M3 || weaponID == WEAPON_XM1014) && distance > 750.0) + return true; + + return false; +} + +void Bot::FocusEnemy (void) +{ + // aim for the head and/or body + Vector enemyOrigin = GetAimPosition (); + m_lookAt = enemyOrigin; + + if (m_enemySurpriseTime > GetWorldTime ()) + return; + + enemyOrigin = (enemyOrigin - EyePosition ()).SkipZ (); + + float distance = enemyOrigin.GetLength (); // how far away is the enemy scum? + + if (distance < 128) + { + if (m_currentWeapon == WEAPON_KNIFE) + { + if (distance < 80.0) + m_wantsToFire = true; + } + else + m_wantsToFire = true; + } + else + { + if (m_currentWeapon == WEAPON_KNIFE) + m_wantsToFire = true; + else + { + float dot = GetShootingConeDeviation (GetEntity (), &m_enemyOrigin); + + if (dot < 0.90) + m_wantsToFire = false; + else + { + float enemyDot = GetShootingConeDeviation (m_enemy, &pev->origin); + + // enemy faces bot? + if (enemyDot >= 0.90) + m_wantsToFire = true; + else + { + if (dot > 0.99) + m_wantsToFire = true; + else + m_wantsToFire = false; + } + } + } + } +} + +void Bot::CombatFight (void) +{ + // no enemy? no need to do strafing + if (FNullEnt (m_enemy)) + return; + + Vector enemyOrigin = m_lookAt; + + if (m_currentWeapon == WEAPON_KNIFE) + m_destOrigin = m_enemy->v.origin; + + enemyOrigin = (enemyOrigin - EyePosition ()).SkipZ (); // ignore z component (up & down) + + float distance = enemyOrigin.GetLength (); // how far away is the enemy scum? + + if (m_timeWaypointMove + m_frameInterval < GetWorldTime ()) + { + if (m_currentWeapon == WEAPON_KNIFE) + return; + + int approach; + + if ((m_states & STATE_SUSPECT_ENEMY) && !(m_states & STATE_SEEING_ENEMY)) // if suspecting enemy stand still + approach = 49; + else if (m_isReloading || m_isVIP) // if reloading or vip back off + approach = 29; + else if (m_currentWeapon == WEAPON_KNIFE) // knife? + approach = 100; + else + { + approach = static_cast (pev->health * m_agressionLevel); + + if (UsesSniper () && (approach > 49)) + approach = 49; + } + + if (UsesPistol() && !(m_enemy->v.weapons & (1 << WEAPON_ELITE) || m_enemy->v.weapons & (1 << WEAPON_FIVESEVEN) || m_enemy->v.weapons & (1 << WEAPON_GLOCK) || m_enemy->v.weapons & (1 << WEAPON_USP) || m_enemy->v.weapons & (1 << WEAPON_DEAGLE) || m_enemy->v.weapons & (1 << WEAPON_SG550)) && !g_bombPlanted) + { + m_fearLevel += 0.5; + + CheckGrenades(); + CheckThrow(EyePosition(),m_throw); + + if (m_states & (STATE_SEEING_ENEMY) && !(pev->weapons & (1 << WEAPON_C4))) + StartTask(TASK_SEEKCOVER, TASKPRI_SEEKCOVER,-1, g_randGen.Long (10, 20), true); + } + // If using sniper do not jump around ! + if (UsesSniper () && m_states & STATE_SEEING_ENEMY || IsEnemyViewable (m_enemy) && !m_isStuck) + pev->button &= ~IN_JUMP; + + // only take cover when bomb is not planted and enemy can see the bot or the bot is VIP + if (approach < 30 && !g_bombPlanted && (::IsInViewCone (pev->origin, m_enemy) || m_isVIP)) + { + m_moveSpeed = -pev->maxspeed; + + TaskItem *task = GetTask (); + + task->id = TASK_SEEKCOVER; + task->resume = true; + task->desire = TASKPRI_ATTACK + 1.0; + } + else if (approach < 50) + m_moveSpeed = 0.0; + else + m_moveSpeed = pev->maxspeed; + + if (distance < 96 && m_currentWeapon != WEAPON_KNIFE) + m_moveSpeed = -pev->maxspeed; + + if (UsesSniper ()) + { + m_fightStyle = 1; + m_lastFightStyleCheck = GetWorldTime (); + } + else if (UsesRifle () || UsesSubmachineGun ()) + { + if (m_lastFightStyleCheck + 3.0 < GetWorldTime ()) + { + int rand = g_randGen.Long (1, 100); + + if (distance < 450) + m_fightStyle = 0; + else if (distance < 1024) + { + if (rand < (UsesSubmachineGun () ? 50 : 30)) + m_fightStyle = 0; + else + m_fightStyle = 1; + } + else + { + if (rand < (UsesSubmachineGun () ? 80 : 93)) + m_fightStyle = 1; + else + m_fightStyle = 0; + } + m_lastFightStyleCheck = GetWorldTime (); + } + } + else + { + if (m_lastFightStyleCheck + 3.0 < GetWorldTime ()) + { + if (g_randGen.Long (0, 100) < 65) + m_fightStyle = 1; + else + m_fightStyle = 0; + + m_lastFightStyleCheck = GetWorldTime (); + } + } + + if ((m_skill > 50 && m_fightStyle == 0) || ((pev->button & IN_RELOAD) || m_isReloading) || (UsesPistol () && distance < 500.0)) + { + if (m_strafeSetTime < GetWorldTime ()) + { + // to start strafing, we have to first figure out if the target is on the left side or right side + MakeVectors (m_enemy->v.v_angle); + + Vector dirToPoint = (pev->origin - m_enemy->v.origin).Normalize2D (); + Vector rightSide = g_pGlobals->v_right.Normalize2D (); + + if ((dirToPoint | rightSide) < 0) + m_combatStrafeDir = 1; + else + m_combatStrafeDir = 0; + + if (g_randGen.Long (1, 100) < 30) + m_combatStrafeDir ^= 1; + + m_strafeSetTime = GetWorldTime () + g_randGen.Float (0.5, 2.5); + } + + if (m_combatStrafeDir == 0) + { + if (!CheckWallOnLeft ()) + m_strafeSpeed = -160.0; + else + { + m_combatStrafeDir ^= 1; + m_strafeSetTime = GetWorldTime () + 0.7; + } + } + else + { + if (!CheckWallOnRight ()) + m_strafeSpeed = 160.0; + else + { + m_combatStrafeDir ^= 1; + m_strafeSetTime = GetWorldTime () + 1.0; + } + } + + if (m_skill > 80 && (m_jumpTime + 5.0 < GetWorldTime () && IsOnFloor () && g_randGen.Long (0, 1000) < (m_isReloading ? 8 : 2) && pev->velocity.GetLength2D () > 150.0)) + pev->button |= IN_JUMP; + + if (m_moveSpeed != 0.0 && distance > 150.0) + m_moveSpeed = 0.0; + } + else if (m_fightStyle == 1) + { + bool shouldDuck = true; // should duck + + // check the enemy height + float enemyHalfHeight = ((m_enemy->v.flags & FL_DUCKING) == FL_DUCKING ? 36.0 : 72.0) / 2; + + // check center/feet + if (!IsVisible (m_enemy->v.origin, GetEntity ()) && !IsVisible (m_enemy->v.origin + Vector (0, 0, -enemyHalfHeight), GetEntity ())) + shouldDuck = false; + + int nearestToEnemyPoint = g_waypoint->FindNearest (m_enemy->v.origin); + + if (shouldDuck && GetTaskId () != TASK_SEEKCOVER && GetTaskId () != TASK_HUNTENEMY && (m_visibility & VISIBLE_BODY) && !(m_visibility & VISIBLE_OTHER) && g_waypoint->IsDuckVisible (m_currentWaypointIndex, nearestToEnemyPoint)) + m_duckTime = GetWorldTime () + (m_frameInterval * 3.5); + + m_moveSpeed = 0.0; + m_strafeSpeed = 0.0; + m_navTimeset = GetWorldTime (); + } + } + + if (m_duckTime > GetWorldTime ()) + { + m_moveSpeed = 0.0; + m_strafeSpeed = 0.0; + } + + if (m_moveSpeed != 0.0) + m_moveSpeed = GetWalkSpeed (); + + if (m_isReloading) + { + m_moveSpeed = -pev->maxspeed; + m_duckTime = GetWorldTime () - (m_frameInterval * 4.0); + } + + if (!IsInWater () && !IsOnLadder () && (m_moveSpeed != 0 || m_strafeSpeed != 0.0f)) + { + MakeVectors (pev->v_angle); + + if (IsDeadlyDrop (pev->origin + (g_pGlobals->v_forward * m_moveSpeed * 0.2) + (g_pGlobals->v_right * m_strafeSpeed * 0.2) + (pev->velocity * m_frameInterval))) + { + m_strafeSpeed = -m_strafeSpeed; + m_moveSpeed = -m_moveSpeed; + + pev->button &= ~IN_JUMP; + } + } +} + +bool Bot::HasPrimaryWeapon (void) +{ + // this function returns returns true, if bot has a primary weapon + + return (pev->weapons & WEAPON_PRIMARY) != 0; +} + +bool Bot::HasSecondaryWeapon (void) +{ + // this function returns returns true, if bot has a secondary weapon + + return (pev->weapons & WEAPON_SECONDARY) != 0; +} + +bool Bot::HasShield (void) +{ + // this function returns true, if bot has a tactical shield + + return strncmp (STRING (pev->viewmodel), "models/shield/v_shield_", 23) == 0; +} + +bool Bot::IsShieldDrawn (void) +{ + // this function returns true, is the tactical shield is drawn + + if (!HasShield ()) + return false; + + return pev->weaponanim == 6 || pev->weaponanim == 7; +} + +bool Bot::IsEnemyProtectedByShield (edict_t *enemy) +{ + // this function returns true, if enemy protected by the shield + + if (FNullEnt (enemy) || (HasShield () && IsShieldDrawn ())) + return false; + + // check if enemy has shield and this shield is drawn + if (strncmp (STRING (enemy->v.viewmodel), "models/shield/v_shield_", 23) == 0 && (enemy->v.weaponanim == 6 || enemy->v.weaponanim == 7)) + { + if (::IsInViewCone (pev->origin, enemy)) + return true; + } + return false; +} + +bool Bot::UsesSniper (void) +{ + // this function returns true, if returns if bot is using a sniper rifle + + return m_currentWeapon == WEAPON_AWP || m_currentWeapon == WEAPON_G3SG1 || m_currentWeapon == WEAPON_SCOUT || m_currentWeapon == WEAPON_SG550; +} + +bool Bot::UsesRifle (void) +{ + WeaponSelect *selectTab = &g_weaponSelect[0]; + int count = 0; + + while (selectTab->id) + { + if (m_currentWeapon == selectTab->id) + break; + + selectTab++; + count++; + } + + if (selectTab->id && count > 13) + return true; + + return false; +} + +bool Bot::UsesPistol (void) +{ + WeaponSelect *selectTab = &g_weaponSelect[0]; + int count = 0; + + // loop through all the weapons until terminator is found + while (selectTab->id) + { + if (m_currentWeapon == selectTab->id) + break; + + selectTab++; + count++; + } + + if (selectTab->id && count < 7) + return true; + + return false; +} + +bool Bot::UsesSubmachineGun (void) +{ + return m_currentWeapon == WEAPON_MP5 || m_currentWeapon == WEAPON_TMP || m_currentWeapon == WEAPON_P90 || m_currentWeapon == WEAPON_MAC10 || m_currentWeapon == WEAPON_UMP45; +} + +bool Bot::UsesZoomableRifle (void) +{ + return m_currentWeapon == WEAPON_AUG || m_currentWeapon == WEAPON_SG552; +} + +bool Bot::UsesBadPrimary (void) +{ + return m_currentWeapon == WEAPON_XM1014 || m_currentWeapon == WEAPON_M3 || m_currentWeapon == WEAPON_UMP45 || m_currentWeapon == WEAPON_MAC10 || m_currentWeapon == WEAPON_TMP || m_currentWeapon == WEAPON_P90; +} + +int Bot::CheckGrenades (void) +{ + if (pev->weapons & (1 << WEAPON_EXPLOSIVE)) + return WEAPON_EXPLOSIVE; + else if (pev->weapons & (1 << WEAPON_FLASHBANG)) + return WEAPON_FLASHBANG; + else if (pev->weapons & (1 << WEAPON_SMOKE)) + return WEAPON_SMOKE; + + return -1; +} + +void Bot::SelectBestWeapon (void) +{ + // this function chooses best weapon, from weapons that bot currently own, and change + // current weapon to best one. + + if (yb_jasonmode.GetBool ()) + { + // if knife mode activated, force bot to use knife + SelectWeaponByName ("weapon_knife"); + return; + } + + + if (m_isReloading) + return; + WeaponSelect *selectTab = &g_weaponSelect[0]; + + int selectIndex = 0; + int chosenWeaponIndex = 0; + + // loop through all the weapons until terminator is found... + while (selectTab[selectIndex].id) + { + // is the bot NOT carrying this weapon? + if (!(pev->weapons & (1 << selectTab[selectIndex].id))) + { + selectIndex++; // skip to next weapon + continue; + } + + int id = selectTab[selectIndex].id; + bool ammoLeft = false; + + // is the bot already holding this weapon and there is still ammo in clip? + if (selectTab[selectIndex].id == m_currentWeapon && (GetAmmoInClip () < 0 || GetAmmoInClip () >= selectTab[selectIndex].minPrimaryAmmo)) + ammoLeft = true; + + // is no ammo required for this weapon OR enough ammo available to fire + if (g_weaponDefs[id].ammo1 < 0 || m_ammo[g_weaponDefs[id].ammo1] >= selectTab[selectIndex].minPrimaryAmmo) + ammoLeft = true; + + if (ammoLeft) + chosenWeaponIndex = selectIndex; + + selectIndex++; + } + + chosenWeaponIndex %= NUM_WEAPONS + 1; + selectIndex = chosenWeaponIndex; + + int id = selectTab[selectIndex].id; + + // select this weapon if it isn't already selected + if (m_currentWeapon != id) + SelectWeaponByName (selectTab[selectIndex].weaponName); + + m_isReloading = false; + m_reloadState = RELOAD_NONE; +} + + +void Bot::SelectPistol (void) +{ + int oldWeapons = pev->weapons; + + pev->weapons &= ~WEAPON_PRIMARY; + SelectBestWeapon (); + + pev->weapons = oldWeapons; +} + +int Bot::GetHighestWeapon (void) +{ + WeaponSelect *selectTab = &g_weaponSelect[0]; + + int weapons = pev->weapons; + int num = 0; + int i = 0; + + // loop through all the weapons until terminator is found... + while (selectTab->id) + { + // is the bot carrying this weapon? + if (weapons & (1 << selectTab->id)) + num = i; + + i++; + selectTab++; + } + return num; +} + +void Bot::SelectWeaponByName (const char *name) +{ + FakeClientCommand (GetEntity (), name); +} + +void Bot::SelectWeaponbyNumber (int num) +{ + FakeClientCommand (GetEntity (), g_weaponSelect[num].weaponName); +} + +void Bot::AttachToUser (void) +{ + // this function forces bot to join to user + Array foundUsers; + + // search friends near us + for (int i = 0; i < GetMaxClients (); i++) + { + if (!(g_clients[i].flags & CF_USED) || !(g_clients[i].flags & CF_ALIVE) || g_clients[i].team != GetTeam (GetEntity ()) || g_clients[i].ent == GetEntity ()) + continue; + + if (EntityIsVisible (g_clients[i].origin) && !IsValidBot (g_clients[i].ent)) + foundUsers.Push (g_clients[i].ent); + } + + if (foundUsers.IsEmpty ()) + return; + + m_targetEntity = foundUsers.GetRandomElement (); + + ChatterMessage (Chatter_LeadOnSir); + StartTask (TASK_FOLLOWUSER, TASKPRI_FOLLOWUSER, -1, 0.0, true); +} + +void Bot::CommandTeam (void) +{ + // prevent spamming + if (m_timeTeamOrder > GetWorldTime () + 2 || yb_csdm_mode.GetInt () == 2 || yb_communication_type.GetInt () == 0) + return; + + bool memberNear = false; + bool memberExists = false; + + // search teammates seen by this bot + for (int i = 0; i < GetMaxClients (); i++) + { + if (!(g_clients[i].flags & CF_USED) || !(g_clients[i].flags & CF_ALIVE) || g_clients[i].team != GetTeam (GetEntity ()) || g_clients[i].ent == GetEntity ()) + continue; + + memberExists = true; + + if (EntityIsVisible (g_clients[i].origin)) + { + memberNear = true; + break; + } + } + + if (memberNear) // has teammates ? + { + if (m_personality == PERSONALITY_RUSHER && !(yb_communication_type.GetInt () == 2)) + RadioMessage (Radio_StormTheFront); + else if (!m_personality == PERSONALITY_RUSHER && !(yb_communication_type.GetInt () == 2)) + RadioMessage (Radio_Fallback); + } + else if (memberExists && yb_communication_type.GetInt () == 1) + RadioMessage (Radio_TakingFire); + else if (memberExists && yb_communication_type.GetInt () == 2) + ChatterMessage(Chatter_ScaredEmotion); + + m_timeTeamOrder = GetWorldTime () + g_randGen.Float (5.0, 30.0); +} + +bool Bot::IsGroupOfEnemies (Vector location, int numEnemies, int radius) +{ + int numPlayers = 0; + + // search the world for enemy players... + for (int i = 0; i < GetMaxClients (); i++) + { + if (!(g_clients[i].flags & CF_USED) || !(g_clients[i].flags & CF_ALIVE) || g_clients[i].ent == GetEntity ()) + continue; + + if ((g_clients[i].ent->v.origin - location).GetLength () < radius) + { + // don't target our teammates... + if (g_clients[i].team == GetTeam (GetEntity ())) + return false; + + if (numPlayers++ > numEnemies) + return true; + } + } + return false; +} + +void Bot::CheckReload (void) +{ + // check the reload state + if (GetTaskId () == TASK_PLANTBOMB || GetTaskId () == TASK_DEFUSEBOMB || GetTaskId () == TASK_PICKUPITEM || GetTaskId () == TASK_THROWFLASHBANG || GetTaskId () == TASK_THROWSMOKE || m_isUsingGrenade) + { + m_reloadState = RELOAD_NONE; + return; + } + + m_isReloading = false; // update reloading status + m_reloadCheckTime = GetWorldTime () + 3.0; + + if (m_reloadState != RELOAD_NONE) + { + int weaponIndex = 0, maxClip = 0; + int weapons = pev->weapons; + + if (m_reloadState == RELOAD_PRIMARY) + weapons &= WEAPON_PRIMARY; + else if (m_reloadState == RELOAD_SECONDARY) + weapons &= WEAPON_SECONDARY; + + if (weapons == 0) + { + m_reloadState++; + + if (m_reloadState > RELOAD_SECONDARY) + m_reloadState = RELOAD_NONE; + + return; + } + + for (int i = 1; i < MAX_WEAPONS; i++) + { + if (weapons & (1 << i)) + { + weaponIndex = i; + break; + } + } + InternalAssert (weaponIndex); + + switch (weaponIndex) + { + case WEAPON_M249: + maxClip = 100; + break; + + case WEAPON_P90: + maxClip = 50; + break; + + case WEAPON_GALIL: + maxClip = 35; + break; + + case WEAPON_ELITE: + case WEAPON_MP5: + case WEAPON_TMP: + case WEAPON_MAC10: + case WEAPON_M4A1: + case WEAPON_AK47: + case WEAPON_SG552: + case WEAPON_AUG: + case WEAPON_SG550: + maxClip = 30; + break; + + case WEAPON_UMP45: + case WEAPON_FAMAS: + maxClip = 25; + break; + + case WEAPON_GLOCK: + case WEAPON_FIVESEVEN: + case WEAPON_G3SG1: + maxClip = 20; + break; + + case WEAPON_P228: + maxClip = 13; + break; + + case WEAPON_USP: + maxClip = 12; + break; + + case WEAPON_AWP: + case WEAPON_SCOUT: + maxClip = 10; + break; + + case WEAPON_M3: + maxClip = 8; + break; + + case WEAPON_DEAGLE: + case WEAPON_XM1014: + maxClip = 7; + break; + } + + if (m_ammoInClip[weaponIndex] < (maxClip * 0.8) && m_ammo[g_weaponDefs[weaponIndex].ammo1] > 0) + { + if (m_currentWeapon != weaponIndex) + SelectWeaponByName (g_weaponDefs[weaponIndex].className); + pev->button &= ~IN_ATTACK; + + if ((pev->oldbuttons & IN_RELOAD) == RELOAD_NONE) + pev->button |= IN_RELOAD; // press reload button + + m_isReloading = true; + } + else + { + // if we have enemy don't reload next weapon + if ((m_states & (STATE_SEEING_ENEMY | STATE_HEARING_ENEMY)) || m_seeEnemyTime + 5.0 > GetWorldTime ()) + { + m_reloadState = RELOAD_NONE; + return; + } + m_reloadState++; + + if (m_reloadState > RELOAD_SECONDARY) + m_reloadState = RELOAD_NONE; + + return; + } + } +} diff --git a/source/globals.cpp b/source/globals.cpp new file mode 100644 index 0000000..b7e1e56 --- /dev/null +++ b/source/globals.cpp @@ -0,0 +1,489 @@ +// +// Copyright (c) 2014, by YaPB Development Team. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// Version: $Id:$ +// + +#include + +bool g_canSayBombPlanted = true; +bool g_isMetamod = false; +bool g_radioInsteadVoice = false; +bool g_roundEnded = true; +bool g_botsCanPause = false; +bool g_sendAudioFinished = true; +bool g_bombPlanted = false; +bool g_bombSayString = false; +bool g_isCommencing = false; +bool g_editNoclip = false; +bool g_isFakeCommand = false; +bool g_waypointOn = false; +bool g_waypointsChanged = true; +bool g_autoWaypoint = false; +bool g_bLearnJumpWaypoint = false; +bool g_leaderChoosen[2] = {false, false}; + +float g_lastChatTime = 0.0; +float g_timeRoundStart = 0.0; +float g_timeRoundEnd = 0.0; +float g_timeRoundMid = 0.0; +float g_timeNextBombUpdate = 0.0; +float g_timeBombPlanted = 0.0; +float g_lastRadioTime[2] = {0.0, 0.0}; +float g_autoPathDistance = 250.0; + +int g_lastRadio[2]; +int g_storeAddbotVars[4]; +int g_radioSelect[32]; +int g_fakeArgc = 0; +int g_gameVersion = CSV_STEAM; +int g_numWaypoints = 0; +int g_mapType = 0; +int g_killHistory = 0; + +short g_modelIndexLaser = 0; +short g_modelIndexArrow = 0; +char g_fakeArgv[256]; + +Array > g_chatFactory; +Array > g_chatterFactory; +Array g_botNames; +Array g_replyFactory; +RandGen g_randGen; +Library *g_gameLib = NULL; + +meta_globals_t *gpMetaGlobals = NULL; +gamedll_funcs_t *gpGamedllFuncs = NULL; +mutil_funcs_t *gpMetaUtilFuncs = NULL; + +gamefuncs_t g_functionTable; +EntityAPI_t g_entityAPI = NULL; +NewEntityAPI_t g_getNewEntityAPI = NULL; +BlendAPI_t g_serverBlendingAPI = NULL; +FuncPointers_t g_funcPointers = NULL; + +enginefuncs_t g_engfuncs; +Client g_clients[32]; +WeaponProperty g_weaponDefs[MAX_WEAPONS + 1]; + +edict_t *g_worldEdict = NULL; +edict_t *g_hostEntity = NULL; +globalvars_t *g_pGlobals = NULL; +Experience *g_experienceData = NULL; + + + +// default tables for personality weapon preferences, overridden by weapons.cfg +int g_normalWeaponPrefs[NUM_WEAPONS] = + {0, 2, 1, 4, 5, 6, 3, 12, 10, 24, 25, 13, 11, 8, 7, 22, 23, 18, 21, 17, 19, 15, 17, 9, 14, 16}; + +int g_rusherWeaponPrefs[NUM_WEAPONS] = + {0, 2, 1, 4, 5, 6, 3, 24, 19, 22, 23, 20, 21, 10, 12, 13, 7, 8, 11, 9, 18, 17, 19, 25, 15, 16}; + +int g_carefulWeaponPrefs[NUM_WEAPONS] = + {0, 2, 1, 4, 25, 6, 3, 7, 8, 12, 10, 13, 11, 9, 24, 18, 14, 17, 16, 15, 19, 20, 21, 22, 23, 5}; + +int g_grenadeBuyPrecent[NUM_WEAPONS - 23] = + {95, 85, 60}; + +int g_botBuyEconomyTable[NUM_WEAPONS - 15] = + {1900, 2100, 2100, 4000, 6000, 7000, 16000, 1200, 800, 1000, 3000}; + +SkillDefinition g_skillTab[6] = +{ + {0.8, 1.0, 45.0, 65.0, 2.0, 3.0, 40.0, 40.0, 50.0, 0, 0, 0, 50}, + {0.6, 0.8, 40.0, 60.0, 3.0, 4.0, 30.0, 30.0, 42.0, 10, 0, 0, 40}, + {0.4, 0.6, 35.0, 55.0, 4.0, 6.0, 20.0, 20.0, 32.0, 30, 0, 50, 35}, + {0.2, 0.3, 30.0, 50.0, 6.0, 8.0, 10.0, 10.0, 18.0, 0, 30, 80, 30}, + {0.1, 0.2, 25.0, 40.0, 8.0, 10.0, 5.0, 5.0, 10.0, 80, 50, 100, 23}, + {0.0, 0.1, 20.0, 30.0, 9.0, 12.0, 0.0, 5.0, 0.0, 100, 100, 100, 20} +}; + +int *g_weaponPrefs[] = +{ + g_normalWeaponPrefs, + g_rusherWeaponPrefs, + g_carefulWeaponPrefs +}; + +// metamod engine & dllapi function tables +metamod_funcs_t gMetaFunctionTable = +{ + NULL, // pfnEntityAPI_t () + NULL, // pfnEntityAPI_t_Post () + GetEntityAPI2, // pfnEntityAPI_t2 () + GetEntityAPI2_Post, // pfnEntityAPI_t2_Post () + NULL, // pfnGetNewDLLFunctions () + NULL, // pfnGetNewDLLFunctions_Post () + GetEngineFunctions, // pfnGetEngineFunctions () + NULL, // pfnGetEngineFunctions_Post () +}; + +// metamod plugin information +plugin_info_t Plugin_info = +{ + META_INTERFACE_VERSION, // interface version + PRODUCT_NAME, // plugin name + PRODUCT_VERSION, // plugin version + PRODUCT_DATE, // date of creation + PRODUCT_AUTHOR, // plugin author + PRODUCT_URL, // plugin URL + PRODUCT_LOGTAG, // plugin logtag + PT_CHANGELEVEL, // when loadable + PT_ANYTIME, // when unloadable +}; + +// table with all available actions for the bots (filtered in & out in Bot::SetConditions) some of them have subactions included +TaskItem g_taskFilters[] = +{ + {TASK_NORMAL, 0, -1, 0.0, true}, + {TASK_PAUSE, 0, -1, 0.0, false}, + {TASK_MOVETOPOSITION, 0, -1, 0.0, true}, + {TASK_FOLLOWUSER, 0, -1,0.0, true}, + {TASK_WAITFORGO, 0, -1, 0.0, true}, + {TASK_PICKUPITEM, 0, -1, 0.0, true}, + {TASK_CAMP, 0, -1, 0.0, true}, + {TASK_PLANTBOMB, 0, -1, 0.0, false}, + {TASK_DEFUSEBOMB, 0, -1, 0.0, false}, + {TASK_ATTACK, 0, -1, 0.0, false}, + {TASK_HUNTENEMY, 0, -1, 0.0, false}, + {TASK_SEEKCOVER, 0, -1, 0.0, false}, + {TASK_THROWHEGRENADE, 0, -1, 0.0, false}, + {TASK_THROWFLASHBANG, 0, -1, 0.0, false}, + {TASK_THROWSMOKE, 0, -1, 0.0, false}, + {TASK_DOUBLEJUMP, 0, -1, 0.0, false}, + {TASK_ESCAPEFROMBOMB, 0, -1, 0.0, false}, + {TASK_SHOOTBREAKABLE, 0, -1, 0.0, false}, + {TASK_HIDE, 0, -1, 0.0, false}, + {TASK_BLINDED, 0, -1, 0.0, false}, + {TASK_SPRAY, 0, -1, 0.0, false} +}; + +// weapons and their specifications +WeaponSelect g_weaponSelect[NUM_WEAPONS + 1] = +{ + {WEAPON_KNIFE, "weapon_knife", "knife.mdl", 0, 0, -1, -1, 0, 0, 0, 0, false, true }, + {WEAPON_USP, "weapon_usp", "usp.mdl", 500, 1, -1, -1, 1, 1, 2, 2, false, false}, + {WEAPON_GLOCK, "weapon_glock18", "glock18.mdl", 400, 1, -1, -1, 1, 2, 1, 1, false, false}, + {WEAPON_DEAGLE, "weapon_deagle", "deagle.mdl", 650, 1, 2, 2, 1, 3, 4, 4, true, false}, + {WEAPON_P228, "weapon_p228", "p228.mdl", 600, 1, 2, 2, 1, 4, 3, 3, false, false}, + {WEAPON_ELITE, "weapon_elite", "elite.mdl", 1000, 1, 0, 0, 1, 5, 5, 5, false, false}, + {WEAPON_FIVESEVEN, "weapon_fiveseven", "fiveseven.mdl", 750, 1, 1, 1, 1, 6, 5, 5, false, false}, + {WEAPON_M3, "weapon_m3", "m3.mdl", 1700, 1, 2, -1, 2, 1, 1, 1, false, false}, + {WEAPON_XM1014, "weapon_xm1014", "xm1014.mdl", 3000, 1, 2, -1, 2, 2, 2, 2, false, false}, + {WEAPON_MP5, "weapon_mp5navy", "mp5.mdl", 1500, 1, 2, 1, 3, 1, 2, 2, false, true }, + {WEAPON_TMP, "weapon_tmp", "tmp.mdl", 1250, 1, 1, 1, 3, 2, 1, 1, false, true }, + {WEAPON_P90, "weapon_p90", "p90.mdl", 2350, 1, 2, 1, 3, 3, 4, 4, false, true }, + {WEAPON_MAC10, "weapon_mac10", "mac10.mdl", 1400, 1, 0, 0, 3, 4, 1, 1, false, true }, + {WEAPON_UMP45, "weapon_ump45", "ump45.mdl", 1700, 1, 2, 2, 3, 5, 3, 3, false, true }, + {WEAPON_AK47, "weapon_ak47", "ak47.mdl", 2500, 1, 0, 0, 4, 1, 2, 2, true, true }, + {WEAPON_SG552, "weapon_sg552", "sg552.mdl", 3500, 1, 0, -1, 4, 2, 4, 4, true, true }, + {WEAPON_M4A1, "weapon_m4a1", "m4a1.mdl", 3100, 1, 1, 1, 4, 3, 3, 3, true, true }, + {WEAPON_GALIL, "weapon_galil", "galil.mdl", 2000, 1, 0, 0, 4, -1, 1, 1, true, true }, + {WEAPON_FAMAS, "weapon_famas", "famas.mdl", 2250, 1, 1, 1, 4, -1, 1, 1, true, true }, + {WEAPON_AUG, "weapon_aug", "aug.mdl", 3500, 1, 1, 1, 4, 4, 4, 4, true, true }, + {WEAPON_SCOUT, "weapon_scout", "scout.mdl", 2750, 1, 2, 0, 4, 5, 3, 2, true, false}, + {WEAPON_AWP, "weapon_awp", "awp.mdl", 4750, 1, 2, 0, 4, 6, 5, 6, true, false}, + {WEAPON_G3SG1, "weapon_g3sg1", "g3sg1.mdl", 5000, 1, 0, 2, 4, 7, 6, 6, true, false}, + {WEAPON_SG550, "weapon_sg550", "sg550.mdl", 4200, 1, 1, 1, 4, 8, 5, 5, true, false}, + {WEAPON_M249, "weapon_m249", "m249.mdl", 5750, 1, 2, 1, 5, 1, 1, 1, true, true }, + {WEAPON_SHIELD, "weapon_shield", "shield.mdl", 2200, 0, 1, 1, 8, -1, 8, 8, false, false}, + {0, "", "", 0, 0, 0, 0, 0, 0, 0, 0, false, false} +}; + +// weapon firing delay based on skill (min and max delay for each weapon) +FireDelay g_fireDelay[NUM_WEAPONS + 1] = +{ + {WEAPON_KNIFE, 255, 256, 0.10, {0.0, 0.2, 0.3, 0.4, 0.6, 0.8}, {0.1, 0.3, 0.5, 0.7, 1.0, 1.2}, 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {WEAPON_USP, 3, 853, 0.15, {0.0, 0.1, 0.2, 0.3, 0.4, 0.6}, {0.1, 0.2, 0.3, 0.4, 0.5, 0.7}, 0.2, {0.0, 0.0, 0.1, 0.1, 0.2}, {0.1, 0.1, 0.2, 0.2, 0.4}}, + {WEAPON_GLOCK, 5, 853, 0.15, {0.0, 0.1, 0.2, 0.3, 0.4, 0.6}, {0.1, 0.2, 0.3, 0.4, 0.5, 0.7}, 0.2, {0.0, 0.0, 0.1, 0.1, 0.2}, {0.1, 0.1, 0.2, 0.2, 0.4}}, + {WEAPON_DEAGLE, 2, 640, 0.20, {0.0, 0.1, 0.2, 0.3, 0.4, 0.6}, {0.1, 0.2, 0.3, 0.4, 0.5, 0.7}, 0.2, {0.0, 0.0, 0.1, 0.1, 0.2}, {0.1, 0.1, 0.2, 0.2, 0.4}}, + {WEAPON_P228, 4, 853, 0.14, {0.0, 0.1, 0.2, 0.3, 0.4, 0.6}, {0.1, 0.2, 0.3, 0.4, 0.5, 0.7}, 0.2, {0.0, 0.0, 0.1, 0.1, 0.2}, {0.1, 0.1, 0.2, 0.2, 0.4}}, + {WEAPON_ELITE, 3, 640, 0.20, {0.0, 0.1, 0.2, 0.3, 0.4, 0.6}, {0.1, 0.2, 0.3, 0.4, 0.5, 0.7}, 0.2, {0.0, 0.0, 0.1, 0.1, 0.2}, {0.1, 0.1, 0.2, 0.2, 0.4}}, + {WEAPON_FIVESEVEN, 4, 731, 0.14, {0.0, 0.1, 0.2, 0.3, 0.4, 0.6}, {0.1, 0.2, 0.3, 0.4, 0.5, 0.7}, 0.2, {0.0, 0.0, 0.1, 0.1, 0.2}, {0.1, 0.1, 0.2, 0.2, 0.4}}, + {WEAPON_M3, 8, 365, 0.86, {0.0, 0.1, 0.2, 0.3, 0.4, 0.6}, {0.1, 0.2, 0.3, 0.4, 0.5, 0.7}, 0.2, {0.0, 0.0, 0.1, 0.1, 0.2}, {0.1, 0.1, 0.2, 0.2, 0.4}}, + {WEAPON_XM1014, 7, 512, 0.15, {0.0, 0.1, 0.2, 0.3, 0.4, 0.6}, {0.1, 0.2, 0.3, 0.4, 0.5, 0.7}, 0.2, {0.0, 0.0, 0.1, 0.1, 0.2}, {0.1, 0.1, 0.2, 0.2, 0.4}}, + {WEAPON_MP5, 4, 731, 0.10, {0.0, 0.1, 0.2, 0.3, 0.4, 0.6}, {0.1, 0.2, 0.3, 0.4, 0.5, 0.7}, 0.2, {0.0, 0.0, 0.1, 0.1, 0.2}, {0.1, 0.1, 0.2, 0.2, 0.4}}, + {WEAPON_TMP, 3, 731, 0.05, {0.0, 0.1, 0.2, 0.3, 0.4, 0.6}, {0.1, 0.2, 0.3, 0.4, 0.5, 0.7}, 0.2, {0.0, 0.0, 0.1, 0.1, 0.2}, {0.1, 0.1, 0.2, 0.2, 0.4}}, + {WEAPON_P90, 4, 731, 0.10, {0.0, 0.1, 0.2, 0.3, 0.4, 0.6}, {0.1, 0.2, 0.3, 0.4, 0.5, 0.7}, 0.2, {0.0, 0.0, 0.1, 0.1, 0.2}, {0.1, 0.1, 0.2, 0.2, 0.4}}, + {WEAPON_MAC10, 3, 731, 0.06, {0.0, 0.1, 0.2, 0.3, 0.4, 0.6}, {0.1, 0.2, 0.3, 0.4, 0.5, 0.7}, 0.2, {0.0, 0.0, 0.1, 0.1, 0.2}, {0.1, 0.1, 0.2, 0.2, 0.4}}, + {WEAPON_UMP45, 4, 731, 0.15, {0.0, 0.1, 0.2, 0.3, 0.4, 0.6}, {0.1, 0.2, 0.3, 0.4, 0.5, 0.7}, 0.2, {0.0, 0.0, 0.1, 0.1, 0.2}, {0.1, 0.1, 0.2, 0.2, 0.4}}, + {WEAPON_AK47, 2, 512, 0.09, {0.0, 0.1, 0.2, 0.3, 0.4, 0.6}, {0.1, 0.2, 0.3, 0.4, 0.5, 0.7}, 0.2, {0.0, 0.0, 0.1, 0.1, 0.2}, {0.1, 0.1, 0.2, 0.2, 0.4}}, + {WEAPON_SG552, 3, 512, 0.11, {0.0, 0.1, 0.2, 0.3, 0.4, 0.6}, {0.1, 0.2, 0.3, 0.4, 0.5, 0.7}, 0.2, {0.0, 0.0, 0.1, 0.1, 0.2}, {0.1, 0.1, 0.2, 0.2, 0.4}}, + {WEAPON_M4A1, 3, 512, 0.08, {0.0, 0.1, 0.2, 0.3, 0.4, 0.6}, {0.1, 0.2, 0.3, 0.4, 0.5, 0.7}, 0.2, {0.0, 0.0, 0.1, 0.1, 0.2}, {0.1, 0.1, 0.2, 0.2, 0.4}}, + {WEAPON_GALIL, 4, 512, 0.09, {0.0, 0.1, 0.2, 0.3, 0.4, 0.6}, {0.1, 0.2, 0.3, 0.4, 0.5, 0.7}, 0.2, {0.0, 0.0, 0.1, 0.1, 0.2}, {0.1, 0.1, 0.2, 0.2, 0.4}}, + {WEAPON_FAMAS, 4, 512, 0.10, {0.0, 0.1, 0.2, 0.3, 0.4, 0.6}, {0.1, 0.2, 0.3, 0.4, 0.5, 0.7}, 0.2, {0.0, 0.0, 0.1, 0.1, 0.2}, {0.1, 0.1, 0.2, 0.2, 0.4}}, + {WEAPON_AUG, 3, 512, 0.11, {0.0, 0.1, 0.2, 0.3, 0.4, 0.6}, {0.1, 0.2, 0.3, 0.4, 0.5, 0.7}, 0.2, {0.0, 0.0, 0.1, 0.1, 0.2}, {0.1, 0.1, 0.2, 0.2, 0.4}}, + {WEAPON_SCOUT, 10, 256, 0.18, {0.0, 0.1, 0.2, 0.3, 0.4, 0.6}, {0.1, 0.2, 0.3, 0.4, 0.5, 0.7}, 0.2, {0.0, 0.0, 0.1, 0.1, 0.2}, {0.1, 0.1, 0.2, 0.2, 0.4}}, + {WEAPON_AWP, 10, 170, 0.22, {0.0, 0.1, 0.2, 0.3, 0.4, 0.6}, {0.1, 0.2, 0.3, 0.4, 0.5, 0.7}, 0.2, {0.0, 0.0, 0.1, 0.1, 0.2}, {0.1, 0.1, 0.2, 0.2, 0.4}}, + {WEAPON_G3SG1, 4, 256, 0.25, {0.0, 0.1, 0.2, 0.3, 0.4, 0.6}, {0.1, 0.2, 0.3, 0.4, 0.5, 0.7}, 0.2, {0.0, 0.0, 0.1, 0.1, 0.2}, {0.1, 0.1, 0.2, 0.2, 0.4}}, + {WEAPON_SG550, 4, 256, 0.25, {0.0, 0.1, 0.2, 0.3, 0.4, 0.6}, {0.1, 0.2, 0.3, 0.4, 0.5, 0.7}, 0.2, {0.0, 0.0, 0.1, 0.1, 0.2}, {0.1, 0.1, 0.2, 0.2, 0.4}}, + {WEAPON_M249, 3, 640, 0.10, {0.0, 0.1, 0.2, 0.3, 0.4, 0.6}, {0.1, 0.2, 0.3, 0.4, 0.5, 0.7}, 0.2, {0.0, 0.0, 0.1, 0.1, 0.2}, {0.1, 0.1, 0.2, 0.2, 0.4}}, + {WEAPON_SHIELD, 0, 256, 0.00, {0.0, 0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0, 0.0}, 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {0, 0, 256, 0.00, {0.0, 0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0, 0.0}, 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}} +}; + + +// bot menus +MenuText g_menus[21] = +{ + // main menu + { + 0x2ff, + "\\yYaPB Main Menu\\w\v\v" + "1. YaPB Control\v" + "2. Features\v\v" + "3. Fill Server\v" + "4. End Round\v\v" + "0. Exit" + }, + + // bot features menu + { + 0x25f, + "\\yYaPB Features\\w\v\v" + "1. Weapon Mode Menu\v" + "2. Waypoint Menu\v" + "3. Select Personality\v\v" + "4. Toggle Debug Mode\v" + "5. Command Menu\v\v" + "0. Exit" + }, + + // bot control menu + { + 0x2ff, + "\\yYaPB Control Menu\\w\v\v" + "1. Add a Bot, Quick\v" + "2. Add a Bot, Specified\v\v" + "3. Remove Random Bot\v" + "4. Remove All Bots\v\v" + "5. Remove Bot Menu\v\v" + "0. Exit" + }, + + // weapon mode select menu + { + 0x27f, + "\\yYaPB Weapon Mode\\w\v\v" + "1. Knives only\v" + "2. Pistols only\v" + "3. Shotguns only\v" + "4. Machine Guns only\v" + "5. Rifles only\v" + "6. Sniper Weapons only\v" + "7. All Weapons\v\v" + "0. Exit" + }, + + // personality select menu + { + 0x20f, + "\\yYaPB Personality\\w\v\v" + "1. Random\v" + "2. Normal\v" + "3. Aggressive\v" + "4. Careful\v\v" + "0. Exit" + }, + + // skill select menu + { + 0x23f, + "\\yYaPB Skill Level\\w\v\v" + "1. Stupid (0-20)\v" + "2. Newbie (20-40)\v" + "3. Average (40-60)\v" + "4. Advanced (60-80)\v" + "5. Professional (80-99)\v" + "6. Godlike (100)\v\v" + "0. Exit" + }, + + // team select menu + { + 0x213, + "\\ySelect a team\\w\v\v" + "1. Terrorist Force\v" + "2. Counter-Terrorist Force\v\v" + "5. Auto-select\v\v" + "0. Exit" + }, + + // terrorist model select menu + { + 0x21f, + "\\ySelect an appearance\\w\v\v" + "1. Phoenix Connexion\v" + "2. L337 Krew\v" + "3. Arctic Avengers\v" + "4. Guerilla Warfare\v\v" + "5. Auto-select\v\v" + "0. Exit" + }, + + // counter-terrirust model select menu + { + 0x21f, + "\\ySelect an appearance\\w\v\v" + "1. Seal Team 6 (DEVGRU)\v" + "2. German GSG-9\v" + "3. UK SAS\v" + "4. French GIGN\v\v" + "5. Auto-select\v\v" + "0. Exit" + }, + + // main waypoint menu + { + 0x3ff, + "\\yWaypoint Operations (Page 1)\\w\v\v" + "1. Show/Hide waypoints\v" + "2. Cache waypoint\v" + "3. Create path\v" + "4. Delete path\v" + "5. Add waypoint\v" + "6. Delete waypoint\v" + "7. Set Autopath Distance\v" + "8. Set Radius\v\v" + "9. Next...\v\v" + "0. Exit" + }, + + // main waypoint menu (page 2) + { + 0x3ff, + "\\yWaypoint Operations (Page 2)\\w\v\v" + "1. Waypoint stats\v" + "2. Autowaypoint on/off\v" + "3. Set flags\v" + "4. Save waypoints\v" + "5. Save without checking\v" + "6. Load waypoints\v" + "7. Check waypoints\v" + "8. Noclip cheat on/off\v\v" + "9. Previous...\v\v" + "0. Exit" + }, + + // select waypoint radius menu + { + 0x3ff, + "\\yWaypoint Radius\\w\v\v" + "1. SetRadius 0\v" + "2. SetRadius 8\v" + "3. SetRadius 16\v" + "4. SetRadius 32\v" + "5. SetRadius 48\v" + "6. SetRadius 64\v" + "7. SetRadius 80\v" + "8. SetRadius 96\v" + "9. SetRadius 128\v\v" + "0. Exit" + }, + + // waypoint add menu + { + 0x3ff, + "\\yWaypoint Type\\w\v\v" + "1. Normal\v" + "\\r2. Terrorist Important\v" + "3. Counter-Terrorist Important\v" + "\\w4. Block with hostage / Ladder\v" + "\\y5. Rescue Zone\v" + "\\w6. Camping\v" + "7. Camp End\v" + "\\r8. Map Goal\v" + "\\w9. Jump\v\v" + "0. Exit" + }, + + // set waypoint flag menu + { + 0x2ff, + "\\yToggle Waypoint Flags\\w\v\v" + "1. Block with Hostage\v" + "2. Terrorists Specific\v" + "3. CTs Specific\v" + "4. Use Elevator\v" + "5. Sniper Point (\\yFor Camp Points Only!\\w)\v\v" + "0. Exit" + }, + + // kickmenu #1 + { + 0x0, + NULL, + }, + + // kickmenu #2 + { + 0x0, + NULL, + }, + + // kickmenu #3 + { + 0x0, + NULL, + }, + + // kickmenu #4 + { + 0x0, + NULL, + }, + + // command menu + { + 0x23f, + "\\yBot Command Menu\\w\v\v" + "1. Make Double Jump\v" + "2. Finish Double Jump\v\v" + "3. Drop the C4 Bomb\v" + "4. Drop the Weapon\v\v" + "0. Exit" + }, + + // auto-path max distance + { + 0x27f, + "\\yAutoPath Distance\\w\v\v" + "1. Distance 0\v" + "2. Distance 100\v" + "3. Distance 130\v" + "4. Distance 160\v" + "5. Distance 190\v" + "6. Distance 220\v" + "7. Distance 250 (Default)\v\v" + "0. Exit" + }, + + // path connections + { + 0x207, + "\\yCreate Path (Choose Direction)\\w\v\v" + "1. Outgoing Path\v" + "2. Incoming Path\v" + "3. Bidirectional (Both Ways)\v\v" + "0. Exit" + } +}; \ No newline at end of file diff --git a/source/interface.cpp b/source/interface.cpp new file mode 100644 index 0000000..77abd9b --- /dev/null +++ b/source/interface.cpp @@ -0,0 +1,3540 @@ +// +// Copyright (c) 2014, by YaPB Development Team. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// Version: $Id:$ +// + +#include + +// console vars +ConVar yb_password ("yb_password", "", VT_PASSWORD); +ConVar yb_password_key ("yb_password_key", "_ybwp"); + +ConVar yb_language ("yb_language", "en"); +ConVar yb_version ("yb_version", PRODUCT_VERSION, VT_READONLY); + +ConVar mp_startmoney ("mp_startmoney", NULL, VT_NOREGISTER); + +int BotCommandHandler (edict_t *ent, const char *arg0, const char *arg1, const char *arg2, const char *arg3, const char *arg4, const char *arg5) +{ + // adding one bot with random parameters to random team + if (stricmp (arg0, "addbot") == 0 || stricmp (arg0, "add") == 0) + g_botManager->AddBot (arg4, arg1, arg2, arg3, arg5); + + // adding one bot with high skill parameters to random team + if (stricmp (arg0, "addbot_hs") == 0 || stricmp (arg0, "addhs") == 0) + g_botManager->AddBot (arg4, "100", "1", arg3, arg5); + + // adding one bot with random parameters to terrorist team + else if (stricmp (arg0, "addbot_t") == 0 || stricmp (arg0, "add_t") == 0) + g_botManager->AddBot (arg4, arg1, arg2, "1", arg5); + + // adding one bot with random parameters to counter-terrorist team + else if (stricmp (arg0, "addbot_ct") == 0 || stricmp (arg0, "add_ct") == 0) + g_botManager->AddBot (arg4, arg1, arg2, "2", arg5); + + // kicking off one bot from the terrorist team + else if (stricmp (arg0, "kickbot_t") == 0 || stricmp (arg0, "kick_t") == 0) + g_botManager->RemoveFromTeam (TEAM_TF); + + // kicking off one bot from the counter-terrorist team + else if (stricmp (arg0, "kickbot_ct") == 0 || stricmp (arg0, "kick_ct") == 0) + g_botManager->RemoveFromTeam (TEAM_CF); + + // kills all bots on the terrorist team + else if (stricmp (arg0, "killbots_t") == 0 || stricmp (arg0, "kill_t") == 0) + g_botManager->KillAll (TEAM_TF); + + // kills all bots on the counter-terrorist team + else if (stricmp (arg0, "killbots_ct") == 0 || stricmp (arg0, "kill_ct") == 0) + g_botManager->KillAll (TEAM_CF); + + // list all bots playeing on the server + else if (stricmp (arg0, "listbots") == 0 || stricmp (arg0, "list") == 0) + g_botManager->ListBots (); + + // kick off all bots from the played server + else if (stricmp (arg0, "kickbots") == 0 || stricmp (arg0, "kickall") == 0) + g_botManager->RemoveAll (); + + // kill all bots on the played server + else if (stricmp (arg0, "killbots") == 0 || stricmp (arg0, "killall") == 0) + g_botManager->KillAll (); + + // kick off one random bot from the played server + else if (stricmp (arg0, "kickone") == 0 || stricmp (arg0, "kick") == 0) + g_botManager->RemoveRandom (); + + // fill played server with bots + else if (stricmp (arg0, "fillserver") == 0 || stricmp (arg0, "fill") == 0) + g_botManager->FillServer (atoi (arg1), IsNullString (arg2) ? -1 : atoi (arg2), IsNullString (arg3) ? -1 : atoi (arg3), IsNullString (arg4) ? -1 : atoi (arg4)); + + // swap counter-terrorist and terrorist teams + else if (stricmp (arg0, "swaptteams") == 0 || stricmp (arg0, "swap") == 0) + { + for (int i = 0; i < GetMaxClients (); i++) + { + if (!(g_clients[i].flags & CF_USED)) + continue; + + if (IsValidBot (g_clients[i].ent)) + FakeClientCommand (g_clients[i].ent, "chooseteam; menuselect %d; menuselect 5", GetTeam (g_clients[i].ent) == TEAM_CF ? 1 : 2); + else + (*g_engfuncs.pfnClientCommand) (g_clients[i].ent, "chooseteam; menuselect %d", GetTeam (g_clients[i].ent) == TEAM_CF ? 1 : 2); + } + } + + // select the weapon mode for bots + else if (stricmp (arg0, "weaponmode") == 0 || stricmp (arg0, "wmode") == 0) + { + int selection = atoi (arg1); + + // check is selected range valid + if (selection >= 1 && selection <= 7) + g_botManager->SetWeaponMode (selection); + else + ClientPrint (ent, print_withtag, "Choose weapon from 1 to 7 range"); + } + + // force all bots to vote to specified map + else if (stricmp (arg0, "votemap") == 0) + { + if (!IsNullString (arg1)) + { + int nominatedMap = atoi (arg1); + + // loop through all players + for (int i = 0; i < GetMaxClients (); i++) + { + if (g_botManager->GetBot (i) != NULL) + g_botManager->GetBot (i)->m_voteMap = nominatedMap; + } + ClientPrint (ent, print_withtag, "All dead bots will vote for map #%d", nominatedMap); + } + } + + // force bots to execute client command + else if (stricmp (arg0, "sendcmd") == 0 || stricmp (arg0, "order") == 0) + { + if (IsNullString (arg1)) + return 1; + + edict_t *ent = INDEXENT (atoi (arg1) - 1); + + if (IsValidBot (ent)) + { + FakeClientCommand (ent, arg2); + ClientPrint (ent, print_withtag, "Bot %s executing command %s", STRING (ent->v.netname), arg2); + } + else + ClientPrint (ent, print_withtag, "Player is not BOT!"); + } + + // display current time on the server + else if (stricmp (arg0, "test") == 0) + { + ServerPrint ("mp_bt = %.2f", mp_startmoney.GetFloat ()); + } + + // displays bot about information + else if (stricmp (arg0, "about_bot") == 0 || stricmp (arg0, "about") == 0) + { + if (g_gameVersion == CSV_OLD) + { + ServerPrint ("Cannot do this on CS 1.5"); + return 1; + } + + char aboutData[] = + "+---------------------------------------------------------------------------------+\n" + " The YaPB for Counter-Strike Version " PRODUCT_SUPPORT_VERSION "\n" + " Created by " PRODUCT_AUTHOR ", Using PODBot Code\n" + " Website: " PRODUCT_URL "\n" + "+---------------------------------------------------------------------------------+\n"; + + HudMessage (ent, true, Vector (g_randGen.Long (33, 255), g_randGen.Long (33, 255), g_randGen.Long (33, 255)), aboutData); + } + + // displays version information + else if (stricmp (arg0, "version") == 0 || stricmp (arg0, "ver") == 0) + { + char versionData[] = + "------------------------------------------------\n" + "Name: %s\n" + "Version: %s (Build: %u)\n" + "Compiled by: %s\n" + "Compiled: %s, %s +300 (GMT)\n" + "Optimization: %s\n" + "Meta-Interface Version: %s\n" + "------------------------------------------------"; + + ClientPrint (ent, print_console, versionData, PRODUCT_NAME, PRODUCT_VERSION, GenerateBuildNumber (), PRODUCT_AUTHOR, __DATE__, __TIME__, PRODUCT_OPT_TYPE, META_INTERFACE_VERSION); + } + + // display some sort of help information + else if (stricmp (arg0, "?") == 0 || stricmp (arg0, "help") == 0) + { + ClientPrint (ent, print_console, "Bot Commands:"); + ClientPrint (ent, print_console, "yapb version - display version information."); + ClientPrint (ent, print_console, "yapb about - show bot about information."); + ClientPrint (ent, print_console, "yapb add - create a bot in current game."); + ClientPrint (ent, print_console, "yapb fill - fill the server with random bots."); + ClientPrint (ent, print_console, "yapb kickall - disconnects all bots from current game."); + ClientPrint (ent, print_console, "yapb killbots - kills all bots in current game."); + ClientPrint (ent, print_console, "yapb kick - disconnect one random bot from game."); + ClientPrint (ent, print_console, "yapb weaponmode - select bot weapon mode."); + ClientPrint (ent, print_console, "yapb votemap - allows dead bots to vote for specific map."); + ClientPrint (ent, print_console, "yapb cmenu - displaying bots command menu."); + + if (stricmp (arg1, "full") == 0 || stricmp (arg1, "f") == 0 || stricmp (arg1, "?") == 0) + { + ClientPrint (ent, print_console, "yapb add_t - creates one random bot to terrorist team."); + ClientPrint (ent, print_console, "yapb add_ct - creates one random bot to ct team."); + ClientPrint (ent, print_console, "yapb kick_t - disconnect one random bot from terrorist team."); + ClientPrint (ent, print_console, "yapb kick_ct - disconnect one random bot from ct team."); + ClientPrint (ent, print_console, "yapb kill_t - kills all bots on terrorist team."); + ClientPrint (ent, print_console, "yapb kill_ct - kills all bots on ct team."); + ClientPrint (ent, print_console, "yapb list - display list of bots currently playing."); + ClientPrint (ent, print_console, "yapb order - execute specific command on specified bot."); + ClientPrint (ent, print_console, "yapb time - displays current time on server."); + ClientPrint (ent, print_console, "yapb deletewp - erase waypoint file from hard disk (permanently)."); + + if (!IsDedicatedServer ()) + { + ServerPrintNoTag ("yapb autowp - toggle autowppointing."); + ServerPrintNoTag ("yapb wp - toggle waypoint showing."); + ServerPrintNoTag ("yapb wp on noclip - enable noclip cheat"); + ServerPrintNoTag ("yapb wp save nocheck - save waypoints without checking."); + ServerPrintNoTag ("yapb wp add - open menu for waypoint creation."); + ServerPrintNoTag ("yapb wp menu - open main waypoint menu."); + ServerPrintNoTag ("yapb wp addbasic - creates basic waypoints on map."); + ServerPrintNoTag ("yapb wp find - show direction to specified waypoint."); + ServerPrintNoTag ("yapb wp load - wload the waypoint file from hard disk."); + ServerPrintNoTag ("yapb wp check - checks if all waypoints connections are valid."); + ServerPrintNoTag ("yapb wp cache - cache nearest waypoint."); + ServerPrintNoTag ("yapb wp teleport - teleport hostile to specified waypoint."); + ServerPrintNoTag ("yapb wp setradius - manually sets the wayzone radius for this waypoint."); + ServerPrintNoTag ("yapb path autodistance - opens menu for setting autopath maximum distance."); + ServerPrintNoTag ("yapb path cache - remember the nearest to player waypoint."); + ServerPrintNoTag ("yapb path create - opens menu for path creation."); + ServerPrintNoTag ("yapb path delete - delete path from cached to nearest waypoint."); + ServerPrintNoTag ("yapb path create_in - creating incoming path connection."); + ServerPrintNoTag ("yapb path create_out - creating outgoing path connection."); + ServerPrintNoTag ("yapb path create_both - creating both-ways path connection."); + ServerPrintNoTag ("yapb exp save - save the experience data."); + } + } + } + + // sets health for all available bots + else if (stricmp (arg0, "sethealth") == 0 || stricmp (arg0, "health") == 0) + { + if (IsNullString (arg1)) + ClientPrint (ent, print_withtag, "Please specify health"); + else + { + ClientPrint (ent, print_withtag, "Bot health is set to %d%%", atoi (arg1)); + + for (int i = 0; i < GetMaxClients (); i++) + { + if (g_botManager->GetBot (i) != NULL) + g_botManager->GetBot (i)->pev->health = fabsf (atof (arg1)); + } + } + } + + // displays main bot menu + else if (stricmp (arg0, "botmenu") == 0 || stricmp (arg0, "menu") == 0) + DisplayMenuToClient (ent, &g_menus[0]); + + // display command menu + else if (stricmp (arg0, "cmdmenu") == 0 || stricmp (arg0, "cmenu") == 0) + { + if (IsAlive (ent)) + DisplayMenuToClient (ent, &g_menus[18]); + else + { + DisplayMenuToClient (ent, NULL); // reset menu display + CenterPrint ("You're dead, and have no access to this menu"); + } + } + + // some debug routines + else if (stricmp (arg0, "debug") == 0) + { + // test random number generator + if (stricmp (arg0, "randgen") == 0) + { + for (int i = 0; i < 500; i++) + ServerPrintNoTag ("Result Range[0 - 100]: %d", g_randGen.Long (0, 100)); + } + #if defined (MMGR_H) + // dump memory information + else if (stricmp (arg0, "memrep") == 0) + m_dumpMemoryReport (); + #endif + } + + // waypoint manimupulation (really obsolete, can be edited through menu) (supported only on listen server) + else if (stricmp (arg0, "waypoint") == 0 || stricmp (arg0, "wp") == 0 || stricmp (arg0, "wpt") == 0) + { + if (IsDedicatedServer () || FNullEnt (g_hostEntity)) + return 2; + + // enables or disable waypoint displaying + if (stricmp (arg1, "on") == 0) + { + g_waypointOn = true; + ServerPrint ("Waypoint Editing Enabled"); + + // enables noclip cheat + if (stricmp (arg2, "noclip") == 0) + { + if (g_editNoclip) + { + g_hostEntity->v.movetype = MOVETYPE_WALK; + ServerPrint ("Noclip Cheat Disabled"); + } + else + { + g_hostEntity->v.movetype = MOVETYPE_NOCLIP; + ServerPrint ("Noclip Cheat Enabled"); + } + g_editNoclip ^= true; // switch on/off (XOR it!) + } + ServerCommand ("yapb wp mdl on"); + } + + // switching waypoint editing off + else if (stricmp (arg1, "off") == 0) + { + g_waypointOn = false; + g_editNoclip = false; + g_hostEntity->v.movetype = MOVETYPE_WALK; + + ServerPrint ("Waypoint Editing Disabled"); + ServerCommand ("yapb wp mdl off"); + } + + // toggles displaying player models on spawn spots + else if (stricmp (arg1, "mdl") == 0 || stricmp (arg1, "models") == 0) + { + edict_t *spawnEntity = NULL; + + if (stricmp (arg2, "on") == 0) + { + while (!FNullEnt (spawnEntity = FIND_ENTITY_BY_CLASSNAME (spawnEntity, "info_player_start"))) + spawnEntity->v.effects &= ~EF_NODRAW; + while (!FNullEnt (spawnEntity = FIND_ENTITY_BY_CLASSNAME (spawnEntity, "info_player_deathmatch"))) + spawnEntity->v.effects &= ~EF_NODRAW; + while (!FNullEnt (spawnEntity = FIND_ENTITY_BY_CLASSNAME (spawnEntity, "info_vip_start"))) + spawnEntity->v.effects &= ~EF_NODRAW; + + ServerCommand ("mp_roundtime 9"); // reset round time to maximum + ServerCommand ("mp_timelimit 0"); // disable the time limit + ServerCommand ("mp_freezetime 0"); // disable freezetime + } + else if (stricmp (arg2, "off") == 0) + { + while (!FNullEnt (spawnEntity = FIND_ENTITY_BY_CLASSNAME (spawnEntity, "info_player_start"))) + spawnEntity->v.effects |= EF_NODRAW; + while (!FNullEnt (spawnEntity = FIND_ENTITY_BY_CLASSNAME (spawnEntity, "info_player_deathmatch"))) + spawnEntity->v.effects |= EF_NODRAW; + while (!FNullEnt (spawnEntity = FIND_ENTITY_BY_CLASSNAME (spawnEntity, "info_vip_start"))) + spawnEntity->v.effects |= EF_NODRAW; + } + } + + // show direction to specified waypoint + else if (stricmp (arg1, "find") == 0) + g_waypoint->SetFindIndex (atoi (arg2)); + + // opens adding waypoint menu + else if (stricmp (arg1, "add") == 0) + { + g_waypointOn = true; // turn waypoints on + DisplayMenuToClient (g_hostEntity, &g_menus[12]); + } + + // creates basic waypoints on the map (ladder/spawn points/goals) + else if (stricmp (arg1, "addbasic") == 0) + { + g_waypoint->CreateBasic (); + CenterPrint ("Basic waypoints was Created"); + } + + // delete nearest to host edict waypoint + else if (stricmp (arg1, "delete") == 0) + { + g_waypointOn = true; // turn waypoints on + g_waypoint->Delete (); + } + + // save waypoint data into file on hard disk + else if (stricmp (arg1, "save") == 0) + { + char *waypointSaveMessage = g_localizer->TranslateInput ("Waypoints Saved"); + + if (FStrEq (arg2, "nocheck")) + { + g_waypoint->Save (); + ServerPrint (waypointSaveMessage); + } + else if (g_waypoint->NodesValid ()) + { + g_waypoint->Save (); + ServerPrint (waypointSaveMessage); + } + } + + // remove waypoint and all corresponding files from hard disk + else if (stricmp (arg1, "erase") == 0) + g_waypoint->EraseFromHardDisk (); + + // load all waypoints again (overrides all changes, that wasn't saved) + else if (stricmp (arg1, "load") == 0) + { + if (g_waypoint->Load ()) + ServerPrint ("Waypoints loaded"); + } + + // check all nodes for validation + else if (stricmp (arg1, "check") == 0) + { + if (g_waypoint->NodesValid ()) + CenterPrint ("Nodes work Fine"); + } + + // opens menu for setting (removing) waypoint flags + else if (stricmp (arg1, "flags") == 0) + DisplayMenuToClient (g_hostEntity, &g_menus[13]); + + // setting waypoint radius + else if (stricmp (arg1, "setradius") == 0) + g_waypoint->SetRadius (atoi (arg2)); + + // remembers nearest waypoint + else if (stricmp (arg1, "cache") == 0) + g_waypoint->CacheWaypoint (); + + // teleport player to specified waypoint + else if (stricmp (arg1, "teleport") == 0) + { + int teleportPoint = atoi (arg2); + + if (teleportPoint < g_numWaypoints) + { + Path *path = g_waypoint->GetPath (teleportPoint); + + (*g_engfuncs.pfnSetOrigin) (g_hostEntity, path->origin); + g_waypointOn = true; + + ServerPrint ("Player '%s' teleported to waypoint #%d (x:%.1f, y:%.1f, z:%.1f)", STRING (g_hostEntity->v.netname), teleportPoint, path->origin.x, path->origin.y, path->origin.z); //-V807 + g_editNoclip = true; + } + } + + // displays waypoint menu + else if (stricmp (arg1, "menu") == 0) + DisplayMenuToClient (g_hostEntity, &g_menus[9]); + + // otherwise display waypoint current status + else + ServerPrint ("Waypoints are %s", g_waypointOn == true ? "Enabled" : "Disabled"); + } + + // path waypoint editing system (supported only on listen server) + else if (stricmp (arg0, "pathwaypoint") == 0 || stricmp (arg0, "path") == 0 || stricmp (arg0, "pwp") == 0) + { + if (IsDedicatedServer () || FNullEnt (g_hostEntity)) + return 2; + + // opens path creation menu + if (stricmp (arg1, "create") == 0) + DisplayMenuToClient (g_hostEntity, &g_menus[20]); + + // creates incoming path from the cached waypoint + else if (stricmp (arg1, "create_in") == 0) + g_waypoint->CreatePath (CONNECTION_INCOMING); + + // creates outgoing path from current waypoint + else if (stricmp (arg1, "create_out") == 0) + g_waypoint->CreatePath (CONNECTION_OUTGOING); + + // creates bidirectional path from cahed to current waypoint + else if (stricmp (arg1, "create_both") == 0) + g_waypoint->CreatePath (CONNECTION_BOTHWAYS); + + // delete special path + else if (stricmp (arg1, "delete") == 0) + g_waypoint->DeletePath (); + + // sets auto path maximum distance + else if (stricmp (arg1, "autodistance") == 0) + DisplayMenuToClient (g_hostEntity, &g_menus[19]); + } + + // automatic waypoint handling (supported only on listen server) + else if (stricmp (arg0, "autowaypoint") == 0 || stricmp (arg0, "autowp") == 0) + { + if (IsDedicatedServer () || FNullEnt (g_hostEntity)) + return 2; + + // enable autowaypointing + if (stricmp (arg1, "on") == 0) + { + g_autoWaypoint = true; + g_waypointOn = true; // turn this on just in case + } + + // disable autowaypointing + else if (stricmp (arg1, "off") == 0) + g_autoWaypoint = false; + + // display status + ServerPrint ("Auto-Waypoint %s", g_autoWaypoint ? "Enabled" : "Disabled"); + } + + // experience system handling (supported only on listen server) + else if (stricmp (arg0, "experience") == 0 || stricmp (arg0, "exp") == 0) + { + if (IsDedicatedServer () || FNullEnt (g_hostEntity)) + return 2; + + // write experience table (and visibility table) to hard disk + if (stricmp (arg1, "save") == 0) + { + g_waypoint->SaveExperienceTab (); + g_waypoint->SaveVisibilityTab (); + + ServerPrint ("Experience tab saved"); + } + } + else + return 0; // command is not handled by bot + + return 1; // command was handled by bot +} + +void CommandHandler (void) +{ + // this function is the dedicated server command handler for the new yapb server command we + // registered at game start. It will be called by the engine each time a server command that + // starts with "yapb" is entered in the server console. It works exactly the same way as + // ClientCommand() does, using the CmdArgc() and CmdArgv() facilities of the engine. Argv(0) + // is the server command itself (here "yapb") and the next ones are its arguments. Just like + // the stdio command-line parsing in C when you write "long main (long argc, char **argv)". + + // check status for dedicated server command + if (BotCommandHandler (g_hostEntity, IsNullString (CMD_ARGV (1)) ? "help" : CMD_ARGV (1), CMD_ARGV (2), CMD_ARGV (3), CMD_ARGV (4), CMD_ARGV (5), CMD_ARGV (6)) == 0) + ServerPrint ("Unknown command: %s", CMD_ARGV (1)); +} + +void ParseVoiceEvent (const String &base, int type, float timeToRepeat) +{ + // this function does common work of parsing single line of voice chatter + + Array temp = String (base).Split (','); + ChatterItem chatterItem; + + IterateArray (temp, i) + { + temp[i].Trim ().TrimQuotes (); + + if (GetWaveLength (temp[i]) == 0.0) + continue; + + chatterItem.name = temp[i]; + chatterItem.repeatTime = timeToRepeat; + + g_chatterFactory[type].Push (chatterItem); + } + temp.RemoveAll (); +} + +void InitConfig (void) +{ + File fp; + char command[80], line[256]; + + KeywordFactory replyKey; + int chatType = -1; + + // fixes for crashing if configs couldn't be accessed + g_chatFactory.SetSize (CHAT_TOTAL); + g_chatterFactory.SetSize (Chatter_Total); + + #define SKIP_COMMENTS() if ((line[0] == '/') || (line[0] == '\r') || (line[0] == '\n') || (line[0] == 0) || (line[0] == ' ') || (line[0] == '\t')) continue; + + // NAMING SYSTEM INITIALIZATION + if (OpenConfig ("names.cfg", "Name configuration file not found.", &fp , true)) + { + while (fp.GetBuffer (line, 255)) + { + SKIP_COMMENTS (); + + strtrim (line); + + BotName item; + memset (&item, 0, sizeof (item)); + + item.name = line; + item.used = false; + + g_botNames.Push (item); + } + fp.Close (); + } + + // CHAT SYSTEM CONFIG INITIALIZATION + if (OpenConfig ("chat.cfg", "Chat file not found.", &fp, true)) + { + while (fp.GetBuffer (line, 255)) + { + SKIP_COMMENTS (); + strcpy (command, GetField (line, 0, 1)); + + if (strcmp (command, "[KILLED]") == 0) + { + chatType = 0; + continue; + } + else if (strcmp (command, "[BOMBPLANT]") == 0) + { + chatType = 1; + continue; + } + else if (strcmp (command, "[DEADCHAT]") == 0) + { + chatType = 2; + continue; + } + else if (strcmp (command, "[REPLIES]") == 0) + { + chatType = 3; + continue; + } + else if (strcmp (command, "[UNKNOWN]") == 0) + { + chatType = 4; + continue; + } + else if (strcmp (command, "[TEAMATTACK]") == 0) + { + chatType = 5; + continue; + } + else if (strcmp (command, "[WELCOME]") == 0) + { + chatType = 6; + continue; + } + else if (strcmp (command, "[TEAMKILL]") == 0) + { + chatType = 7; + continue; + } + + if (chatType != 3) + line[79] = 0; + + strtrim (line); + + switch (chatType) + { + case 0: + g_chatFactory[CHAT_KILLING].Push (line); + break; + + case 1: + g_chatFactory[CHAT_BOMBPLANT].Push (line); + break; + + case 2: + g_chatFactory[CHAT_DEAD].Push (line); + break; + + case 3: + if (strstr (line, "@KEY") != NULL) + { + if (!replyKey.keywords.IsEmpty () && !replyKey.replies.IsEmpty ()) + { + g_replyFactory.Push (replyKey); + replyKey.replies.RemoveAll (); + } + + replyKey.keywords.RemoveAll (); + replyKey.keywords = String (&line[4]).Split (','); + + IterateArray (replyKey.keywords, i) + replyKey.keywords[i].Trim ().TrimQuotes (); + } + else if (!replyKey.keywords.IsEmpty ()) + replyKey.replies.Push (line); + + break; + + case 4: + g_chatFactory[CHAT_NOKW].Push (line); + break; + + case 5: + g_chatFactory[CHAT_TEAMATTACK].Push (line); + break; + + case 6: + g_chatFactory[CHAT_WELCOME].Push (line); + break; + + case 7: + g_chatFactory[CHAT_TEAMKILL].Push (line); + break; + } + } + fp.Close (); + } + else + { + extern ConVar yb_chat; + yb_chat.SetInt (0); + } + + + // GENERAL DATA INITIALIZATION + if (OpenConfig ("general.cfg", "General configuration file not found. Loading defaults", &fp)) + { + while (fp.GetBuffer (line, 255)) + { + SKIP_COMMENTS (); + + Array pair = String (line).Split ('='); + + if (pair.GetElementNumber () != 2) + continue; + + pair[0].Trim ().Trim (); + pair[1].Trim ().Trim (); + + Array splitted = pair[1].Split (','); + + if (pair[0] == "MapStandard") + { + if (splitted.GetElementNumber () != NUM_WEAPONS) + AddLogEntry (true, LL_FATAL, "%s entry in general config is not valid.", pair[0].GetBuffer ()); + + for (int i = 0; i < NUM_WEAPONS; i++) + g_weaponSelect[i].teamStandard = splitted[i].ToInt (); + } + else if (pair[0] == "MapAS") + { + if (splitted.GetElementNumber () != NUM_WEAPONS) + AddLogEntry (true, LL_FATAL, "%s entry in general config is not valid.", pair[0].GetBuffer ()); + + for (int i = 0; i < NUM_WEAPONS; i++) + g_weaponSelect[i].teamAS = splitted[i].ToInt (); + } + else if (pair[0] == "GrenadePercent") + { + if (splitted.GetElementNumber () != 3) + AddLogEntry (true, LL_FATAL, "%s entry in general config is not valid.", pair[0].GetBuffer ()); + + for (int i = 0; i < 3; i++) + g_grenadeBuyPrecent[i] = splitted[i].ToInt (); + } + else if (pair[0] == "Economics") + { + if (splitted.GetElementNumber () != 11) + AddLogEntry (true, LL_FATAL, "%s entry in general config is not valid.", pair[0].GetBuffer ()); + + for (int i = 0; i < 11; i++) + g_botBuyEconomyTable[i] = splitted[i].ToInt (); + } + else if (pair[0] == "PersonalityNormal") + { + if (splitted.GetElementNumber () != NUM_WEAPONS) + AddLogEntry (true, LL_FATAL, "%s entry in general config is not valid.", pair[0].GetBuffer ()); + + for (int i = 0; i < NUM_WEAPONS; i++) + g_normalWeaponPrefs[i] = splitted[i].ToInt (); + } + else if (pair[0] == "PersonalityRusher") + { + if (splitted.GetElementNumber () != NUM_WEAPONS) + AddLogEntry (true, LL_FATAL, "%s entry in general config is not valid.", pair[0].GetBuffer ()); + + for (int i = 0; i < NUM_WEAPONS; i++) + g_rusherWeaponPrefs[i] = splitted[i].ToInt (); + } + else if (pair[0] == "PersonalityCareful") + { + if (splitted.GetElementNumber () != NUM_WEAPONS) + AddLogEntry (true, LL_FATAL, "%s entry in general config is not valid.", pair[0].GetBuffer ()); + + for (int i = 0; i < NUM_WEAPONS; i++) + g_carefulWeaponPrefs[i] = splitted[i].ToInt (); + } + else if (pair[0].Contains ("Skill")) + { + if (splitted.GetElementNumber () != 8) + AddLogEntry (true, LL_FATAL, "%s entry in general config is not valid.", pair[0].GetBuffer ()); + + int parserState = 0; + + if (pair[0].Contains ("Stupid")) + parserState = 0; + else if (pair[0].Contains ("Newbie")) + parserState = 1; + else if (pair[0].Contains ("Average")) + parserState = 2; + else if (pair[0].Contains ("Advanced")) + parserState = 3; + else if (pair[0].Contains ("Professional")) + parserState = 4; + else if (pair[0].Contains ("Godlike")) + parserState = 5; + + for (int i = 0; i < 8; i++) + { + switch (i) + { + case 0: + g_skillTab[parserState].minSurpriseTime = splitted[i].ToFloat (); + break; + + case 1: + g_skillTab[parserState].maxSurpriseTime = splitted[i].ToFloat (); + break; + + case 2: + g_skillTab[parserState].minTurnSpeed = splitted[i].ToFloat (); + break; + + case 3: + g_skillTab[parserState].maxTurnSpeed = splitted[i].ToFloat (); + break; + + case 4: + g_skillTab[parserState].headshotFrequency = splitted[i].ToInt (); + break; + + case 5: + g_skillTab[parserState].heardShootThruProb = splitted[i].ToInt (); + break; + + case 6: + g_skillTab[parserState].seenShootThruProb = splitted[i].ToInt (); + break; + + case 7: + g_skillTab[parserState].recoilAmount = splitted[i].ToInt (); + break; + } + } + } + } + fp.Close (); + } + + // RADIO/VOICE SYSTEM INITIALIZATION + if (OpenConfig ("chatter.cfg", "Couldn't open chatter system configuration", &fp) && g_gameVersion != CSV_OLD && yb_communication_type.GetInt () == 2) + { + Array array; + + extern ConVar yb_chatter_path; + + while (fp.GetBuffer (line, 511)) + { + SKIP_COMMENTS (); + + if (strncmp (line, "RewritePath", 11) == 0) + yb_chatter_path.SetString (String (&line[12]).Trim ()); + else if (strncmp (line, "Event", 5) == 0) + { + array = String (&line[6]).Split ('='); + + if (array.GetElementNumber () != 2) + AddLogEntry (true, LL_FATAL, "Error in chatter config file syntax... Please correct all Errors."); + + IterateArray (array, i) + array[i].Trim ().Trim (); // double trim + + // just to be more unique :) + array[1].TrimLeft ('('); + array[1].TrimRight (';'); + array[1].TrimRight (')'); + + #define PARSE_VOICE_EVENT(type, timeToRepeatAgain) { if (strcmp (array[0], #type) == 0) ParseVoiceEvent (array[1], type, timeToRepeatAgain); } + + // radio system + PARSE_VOICE_EVENT (Radio_CoverMe, FLT_MAX); + PARSE_VOICE_EVENT (Radio_YouTakePoint, FLT_MAX); + PARSE_VOICE_EVENT (Radio_HoldPosition, FLT_MAX); + PARSE_VOICE_EVENT (Radio_RegroupTeam, FLT_MAX); + PARSE_VOICE_EVENT (Radio_FollowMe, FLT_MAX); + PARSE_VOICE_EVENT (Radio_TakingFire, FLT_MAX); + PARSE_VOICE_EVENT (Radio_GoGoGo, FLT_MAX); + PARSE_VOICE_EVENT (Radio_Fallback, FLT_MAX); + PARSE_VOICE_EVENT (Radio_StickTogether, FLT_MAX); + PARSE_VOICE_EVENT (Radio_GetInPosition, FLT_MAX); + PARSE_VOICE_EVENT (Radio_StormTheFront, FLT_MAX); + PARSE_VOICE_EVENT (Radio_ReportTeam, FLT_MAX); + PARSE_VOICE_EVENT (Radio_Affirmative, FLT_MAX); + PARSE_VOICE_EVENT (Radio_EnemySpotted, FLT_MAX); + PARSE_VOICE_EVENT (Radio_NeedBackup, FLT_MAX); + PARSE_VOICE_EVENT (Radio_SectorClear, FLT_MAX); + PARSE_VOICE_EVENT (Radio_InPosition, FLT_MAX); + PARSE_VOICE_EVENT (Radio_ReportingIn, FLT_MAX); + PARSE_VOICE_EVENT (Radio_ShesGonnaBlow, FLT_MAX); + PARSE_VOICE_EVENT (Radio_Negative, FLT_MAX); + PARSE_VOICE_EVENT (Radio_EnemyDown, FLT_MAX); + + // voice system + PARSE_VOICE_EVENT (Chatter_SpotTheBomber, 4.3); + PARSE_VOICE_EVENT (Chatter_VIPSpotted, 5.3); + PARSE_VOICE_EVENT (Chatter_FriendlyFire, 2.1); + PARSE_VOICE_EVENT (Chatter_DiePain, FLT_MAX); + PARSE_VOICE_EVENT (Chatter_GotBlinded, 5.0); + PARSE_VOICE_EVENT (Chatter_GoingToPlantBomb, FLT_MAX); + PARSE_VOICE_EVENT (Chatter_GoingToGuardVIPSafety, FLT_MAX); + PARSE_VOICE_EVENT (Chatter_RescuingHostages, FLT_MAX); + PARSE_VOICE_EVENT (Chatter_GoingToCamp, FLT_MAX); + PARSE_VOICE_EVENT (Chatter_TeamKill, FLT_MAX); + PARSE_VOICE_EVENT (Chatter_ReportingIn, FLT_MAX); + PARSE_VOICE_EVENT (Chatter_GuardDroppedC4, 3.0); + PARSE_VOICE_EVENT (Chatter_Camp, FLT_MAX); + PARSE_VOICE_EVENT (Chatter_GuardingVipSafety, FLT_MAX); + PARSE_VOICE_EVENT (Chatter_PlantingC4, FLT_MAX); + PARSE_VOICE_EVENT (Chatter_DefusingC4, 3.0); + PARSE_VOICE_EVENT (Chatter_InCombat, FLT_MAX); + PARSE_VOICE_EVENT (Chatter_SeeksEnemy, FLT_MAX); + PARSE_VOICE_EVENT (Chatter_Nothing, FLT_MAX); + PARSE_VOICE_EVENT (Chatter_EnemyDown, FLT_MAX); + PARSE_VOICE_EVENT (Chatter_UseHostage, FLT_MAX); + PARSE_VOICE_EVENT (Chatter_FoundC4, 5.5); + PARSE_VOICE_EVENT (Chatter_WonTheRound, FLT_MAX); + PARSE_VOICE_EVENT (Chatter_ScaredEmotion, 6.1); + PARSE_VOICE_EVENT (Chatter_HeardEnemy, 12.2); + PARSE_VOICE_EVENT (Chatter_SniperWarning, 4.3); + PARSE_VOICE_EVENT (Chatter_SniperKilled, 2.1); + PARSE_VOICE_EVENT (Chatter_QuicklyWonTheRound, FLT_MAX); + PARSE_VOICE_EVENT (Chatter_OneEnemyLeft, 2.5); + PARSE_VOICE_EVENT (Chatter_TwoEnemiesLeft, 2.5); + PARSE_VOICE_EVENT (Chatter_ThreeEnemiesLeft, 2.5); + PARSE_VOICE_EVENT (Chatter_NoEnemiesLeft, FLT_MAX); + PARSE_VOICE_EVENT (Chatter_FoundBombPlace, FLT_MAX); + PARSE_VOICE_EVENT (Chatter_WhereIsTheBomb, FLT_MAX); + PARSE_VOICE_EVENT (Chatter_DefendingBombSite, FLT_MAX); + PARSE_VOICE_EVENT (Chatter_BarelyDefused, FLT_MAX); + PARSE_VOICE_EVENT (Chatter_NiceshotCommander, FLT_MAX); + PARSE_VOICE_EVENT (Chatter_NiceshotPall, 2.0); + PARSE_VOICE_EVENT (Chatter_GoingToGuardHostages, 3.0); + PARSE_VOICE_EVENT (Chatter_GoingToGuardDoppedBomb, 3.0); + PARSE_VOICE_EVENT (Chatter_OnMyWay, 1.5); + PARSE_VOICE_EVENT (Chatter_LeadOnSir, 5.0); + PARSE_VOICE_EVENT (Chatter_Pinned_Down, 5.0); + PARSE_VOICE_EVENT (Chatter_GottaFindTheBomb, 3.0); + PARSE_VOICE_EVENT (Chatter_You_Heard_The_Man, 3.0); + PARSE_VOICE_EVENT (Chatter_Lost_The_Commander, 4.5); + PARSE_VOICE_EVENT (Chatter_NewRound, 3.5); + PARSE_VOICE_EVENT (Chatter_CoverMe, 3.5); + PARSE_VOICE_EVENT (Chatter_BehindSmoke, 3.5); + PARSE_VOICE_EVENT (Chatter_BombSiteSecured, 3.5); + } + } + fp.Close (); + } + else + { + yb_communication_type.SetInt (1); + AddLogEntry (true, LL_DEFAULT, "Voice Communication disabled."); + } + + // LOCALIZER INITITALIZATION + if (OpenConfig ("lang.cfg", "Specified language not found", &fp, true) && g_gameVersion != CSV_OLD) + { + if (IsDedicatedServer ()) + return; // dedicated server will use only english translation + + enum Lang_t { Lang_Original, Lang_Translate, Lang_Default } langState = Lang_Default; + + char buffer[1024]; + LanguageItem temp = {"", ""}; + + while (fp.GetBuffer (line, 255)) + { + if (strncmp (line, "[ORIGINAL]", 10) == 0) + { + langState = Lang_Original; + + if (!IsNullString (buffer)) + { + strtrim (buffer); + temp.translated = strdup (buffer); + buffer[0] = 0x0; + } + + if (!IsNullString (temp.translated) && !IsNullString (temp.original)) + g_localizer->m_langTab.Push (temp); + } + else if (strncmp (line, "[TRANSLATED]", 12) == 0) + { + strtrim (buffer); + temp.original = strdup (buffer); + buffer[0] = 0x0; + + langState = Lang_Translate; + } + else + { + switch (langState) + { + case Lang_Original: + strncat (buffer, line, 1024 - 1 - strlen (buffer)); + break; + + case Lang_Translate: + strncat (buffer, line, 1024 - 1 - strlen (buffer)); + break; + } + } + } + fp.Close (); + } + else if (g_gameVersion == CSV_OLD) + AddLogEntry (true, LL_DEFAULT, "Multilingual system disabled, due to your Counter-Strike Version!"); + else if (strcmp (yb_language.GetString (), "en") != 0) + AddLogEntry (true, LL_ERROR, "Couldn't load language configuration"); + + // set personality weapon pointers here + g_weaponPrefs[PERSONALITY_NORMAL] = reinterpret_cast (&g_normalWeaponPrefs); + g_weaponPrefs[PERSONALITY_RUSHER] = reinterpret_cast (&g_rusherWeaponPrefs); + g_weaponPrefs[PERSONALITY_CAREFUL] = reinterpret_cast (&g_carefulWeaponPrefs); +} + +void CommandHandler_NotMM (void) +{ + // this function is the dedicated server command handler for the new yapb server command we + // registered at game start. It will be called by the engine each time a server command that + // starts with "meta" is entered in the server console. It works exactly the same way as + // ClientCommand() does, using the CmdArgc() and CmdArgv() facilities of the engine. Argv(0) + // is the server command itself (here "meta") and the next ones are its arguments. Just like + // the stdio command-line parsing in C when you write "long main (long argc, char **argv)". + // this function is handler for non-metamod launch of yapb, it's just print error message. + + ServerPrint ("You're launched standalone version of yapb. Metamod is not installed or not enabled!"); +} + +void GameDLLInit (void) +{ + // this function is a one-time call, and appears to be the second function called in the + // DLL after FuncPointers_t() has been called. Its purpose is to tell the MOD DLL to + // initialize the game before the engine actually hooks into it with its video frames and + // clients connecting. Note that it is a different step than the *server* initialization. + // This one is called once, and only once, when the game process boots up before the first + // server is enabled. Here is a good place to do our own game session initialization, and + // to register by the engine side the server commands we need to administrate our bots. + + // register server command(s) + RegisterCommand ("yapb", CommandHandler); + RegisterCommand ("yb", CommandHandler); + + // execute main config + ServerCommand ("exec addons/yapb/config/yapb.cfg"); + + // register fake metamod command handler if we not! under mm + if (!g_isMetamod) + RegisterCommand ("meta", CommandHandler_NotMM); + + if (g_isMetamod) + RETURN_META (MRES_IGNORED); + + (*g_functionTable.pfnGameInit) (); +} + +int Spawn (edict_t *ent) +{ + // this function asks the game DLL to spawn (i.e, give a physical existence in the virtual + // world, in other words to 'display') the entity pointed to by ent in the game. The + // Spawn() function is one of the functions any entity is supposed to have in the game DLL, + // and any MOD is supposed to implement one for each of its entities. + + if (strcmp (STRING (ent->v.classname), "worldspawn") == 0) + { + DetectCSVersion (); + + PRECACHE_SOUND (ENGINE_STR ("weapons/xbow_hit1.wav")); // waypoint add + PRECACHE_SOUND (ENGINE_STR ("weapons/mine_activate.wav")); // waypoint delete + PRECACHE_SOUND (ENGINE_STR ("common/wpn_hudoff.wav")); // path add/delete start + PRECACHE_SOUND (ENGINE_STR ("common/wpn_hudon.wav")); // path add/delete done + PRECACHE_SOUND (ENGINE_STR ("common/wpn_moveselect.wav")); // path add/delete cancel + PRECACHE_SOUND (ENGINE_STR ("common/wpn_denyselect.wav")); // path add/delete error + + g_modelIndexLaser = PRECACHE_MODEL (ENGINE_STR ("sprites/laserbeam.spr")); + g_modelIndexArrow = PRECACHE_MODEL (ENGINE_STR ("sprites/arrow1.spr")); + g_roundEnded = true; + + RoundInit (); + + g_mapType = NULL; // reset map type as worldspawn is the first entity spawned + g_worldEdict = ent; // save the world entity for future use + } + else if (strcmp (STRING (ent->v.classname), "player_weaponstrip") == 0 && (STRING (ent->v.target))[0] == '0') + { + ent->v.target = ent->v.targetname = ALLOC_STRING ("fake"); + ent->v.targetname = ALLOC_STRING ("fake"); + } + else if (strcmp (STRING (ent->v.classname), "info_player_start") == 0) + { + SET_MODEL (ent, ENGINE_STR ("models/player/urban/urban.mdl")); + + ent->v.rendermode = kRenderTransAlpha; // set its render mode to transparency + ent->v.renderamt = 127; // set its transparency amount + ent->v.effects |= EF_NODRAW; + } + else if (strcmp (STRING (ent->v.classname), "info_player_deathmatch") == 0) + { + SET_MODEL (ent, ENGINE_STR ("models/player/terror/terror.mdl")); + + ent->v.rendermode = kRenderTransAlpha; // set its render mode to transparency + ent->v.renderamt = 127; // set its transparency amount + ent->v.effects |= EF_NODRAW; + } + + else if (strcmp (STRING (ent->v.classname), "info_vip_start") == 0) + { + SET_MODEL (ent, ENGINE_STR ("models/player/vip/vip.mdl")); + + ent->v.rendermode = kRenderTransAlpha; // set its render mode to transparency + ent->v.renderamt = 127; // set its transparency amount + ent->v.effects |= EF_NODRAW; + } + else if (strcmp (STRING (ent->v.classname), "func_vip_safetyzone") == 0 || strcmp (STRING (ent->v.classname), "info_vip_safetyzone") == 0) + g_mapType |= MAP_AS; // assassination map + + else if (strcmp (STRING (ent->v.classname), "hostage_entity") == 0) + g_mapType |= MAP_CS; // rescue map + + else if (strcmp (STRING (ent->v.classname), "func_bomb_target") == 0 || strcmp (STRING (ent->v.classname), "info_bomb_target") == 0) + g_mapType |= MAP_DE; // defusion map + + else if (strcmp (STRING (ent->v.classname), "func_escapezone") == 0) + g_mapType |= MAP_ES; + + // next maps doesn't have map-specific entities, so determine it by name + else if (strncmp (GetMapName (), "fy_", 3) == 0) // fun map + g_mapType |= MAP_FY; + else if (strncmp (GetMapName (), "ka_", 3) == 0) // knife arena map + g_mapType |= MAP_KA; + + if (g_isMetamod) + RETURN_META_VALUE (MRES_IGNORED, 0); + + int result = (*g_functionTable.pfnSpawn) (ent); // get result + + if (ent->v.rendermode == kRenderTransTexture) + ent->v.flags &= ~FL_WORLDBRUSH; // clear the FL_WORLDBRUSH flag out of transparent ents + + return result; +} + +void Touch (edict_t *pentTouched, edict_t *pentOther) +{ + // this function is called when two entities' bounding boxes enter in collision. For example, + // when a player walks upon a gun, the player entity bounding box collides to the gun entity + // bounding box, and the result is that this function is called. It is used by the game for + // taking the appropriate action when such an event occurs (in our example, the player who + // is walking upon the gun will "pick it up"). Entities that "touch" others are usually + // entities having a velocity, as it is assumed that static entities (entities that don't + // move) will never touch anything. Hence, in our example, the pentTouched will be the gun + // (static entity), whereas the pentOther will be the player (as it is the one moving). When + // the two entities both have velocities, for example two players colliding, this function + // is called twice, once for each entity moving. + + if (g_isMetamod) + RETURN_META (MRES_IGNORED); + + (*g_functionTable.pfnTouch) (pentTouched, pentOther); +} + +void ClientPutInServer (edict_t *ent) +{ + g_botManager->CheckAutoVacate (ent); + + if (g_isMetamod) + RETURN_META (MRES_IGNORED); + + (*g_functionTable.pfnClientPutInServer) (ent); +} + +int ClientConnect (edict_t *ent, const char *name, const char *addr, char rejectReason[128]) +{ + // this function is called in order to tell the MOD DLL that a client attempts to connect the + // game. The entity pointer of this client is ent, the name under which he connects is + // pointed to by the pszName pointer, and its IP address string is pointed by the pszAddress + // one. Note that this does not mean this client will actually join the game ; he could as + // well be refused connection by the server later, because of latency timeout, unavailable + // game resources, or whatever reason. In which case the reason why the game DLL (read well, + // the game DLL, *NOT* the engine) refuses this player to connect will be printed in the + // rejectReason string in all letters. Understand that a client connecting process is done + // in three steps. First, the client requests a connection from the server. This is engine + // internals. When there are already too many players, the engine will refuse this client to + // connect, and the game DLL won't even notice. Second, if the engine sees no problem, the + // game DLL is asked. This is where we are. Once the game DLL acknowledges the connection, + // the client downloads the resources it needs and synchronizes its local engine with the one + // of the server. And then, the third step, which comes *AFTER* ClientConnect (), is when the + // client officially enters the game, through the ClientPutInServer () function, later below. + // Here we hook this function in order to keep track of the listen server client entity, + // because a listen server client always connects with a "loopback" address string. Also we + // tell the bot manager to check the bot population, in order to always have one free slot on + // the server for incoming clients. + + // check if this client is the listen server client + if (strcmp (addr, "loopback") == 0) + g_hostEntity = ent; // save the edict of the listen server client... + + if (g_isMetamod) + RETURN_META_VALUE (MRES_IGNORED, 0); + + return (*g_functionTable.pfnClientConnect) (ent, name, addr, rejectReason); +} + +void ClientDisconnect (edict_t *ent) +{ + // this function is called whenever a client is VOLUNTARILY disconnected from the server, + // either because the client dropped the connection, or because the server dropped him from + // the game (latency timeout). The effect is the freeing of a client slot on the server. Note + // that clients and bots disconnected because of a level change NOT NECESSARILY call this + // function, because in case of a level change, it's a server shutdown, and not a normal + // disconnection. I find that completely stupid, but that's it. Anyway it's time to update + // the bots and players counts, and in case the client disconnecting is a bot, to back its + // brain(s) up to disk. We also try to notice when a listenserver client disconnects, so as + // to reset his entity pointer for safety. There are still a few server frames to go once a + // listen server client disconnects, and we don't want to send him any sort of message then. + + extern ConVar yb_autovacate; + extern ConVar yb_quota; + + if (yb_autovacate.GetBool () && IsValidPlayer (ent) && !IsValidBot (ent) && yb_quota.GetInt () < GetMaxClients () - 1) + yb_quota.SetInt (yb_quota.GetInt () + 1); + + int i = ENTINDEX (ent) - 1; + + InternalAssert (i >= 0 && i < 32); + + // check if its a bot + if (g_botManager->GetBot (i) != NULL) + { + if (g_botManager->GetBot (i)->pev == &ent->v) + g_botManager->Free (i); + } + + if (g_isMetamod) + RETURN_META (MRES_IGNORED); + + (*g_functionTable.pfnClientDisconnect) (ent); +} + +void ClientUserInfoChanged (edict_t *ent, char *infobuffer) +{ + // this function is called when a player changes model, or changes team. Occasionally it + // enforces rules on these changes (for example, some MODs don't want to allow players to + // change their player model). But most commonly, this function is in charge of handling + // team changes, recounting the teams population, etc... + + const char *passwordField = yb_password_key.GetString (); + const char *password = yb_password.GetString (); + + if (IsNullString (passwordField) || IsNullString (password) || IsValidBot (ent)) + { + if (g_isMetamod) + RETURN_META (MRES_IGNORED); + + (*g_functionTable.pfnClientUserInfoChanged) (ent, infobuffer); + } + + int clientIndex = ENTINDEX (ent) - 1; + + if (strcmp (password, INFOKEY_VALUE (infobuffer, const_cast (passwordField))) == 0) + g_clients[clientIndex].flags |= CF_ADMIN; + else + g_clients[clientIndex].flags &= ~CF_ADMIN; + + if (g_isMetamod) + RETURN_META (MRES_IGNORED); + + (*g_functionTable.pfnClientUserInfoChanged) (ent, infobuffer); +} + +void ClientCommand (edict_t *ent) +{ + // this function is called whenever the client whose player entity is ent issues a client + // command. How it works is that clients all have a global string in their client DLL that + // stores the command string; if ever that string is filled with characters, the client DLL + // sends it to the engine as a command to be executed. When the engine has executed that + // command, that string is reset to zero. By the server side, we can access this string + // by asking the engine with the CmdArgv(), CmdArgs() and CmdArgc() functions that work just + // like executable files argument processing work in C (argc gets the number of arguments, + // command included, args returns the whole string, and argv returns the wanted argument + // only). Here is a good place to set up either bot debug commands the listen server client + // could type in his game console, or real new client commands, but we wouldn't want to do + // so as this is just a bot DLL, not a MOD. The purpose is not to add functionality to + // clients. Hence it can lack of commenting a bit, since this code is very subject to change. + + const char *command = CMD_ARGV (0); + const char *arg1 = CMD_ARGV (1); + + static int fillServerTeam = 5; + static bool fillCommand = false; + + if (!g_isFakeCommand && (ent == g_hostEntity || (g_clients[ENTINDEX (ent) - 1].flags & CF_ADMIN))) + { + if (stricmp (command, "yapb") == 0 || stricmp (command, "yb") == 0) + { + int state = BotCommandHandler (ent, IsNullString (CMD_ARGV (1)) ? "help" : CMD_ARGV (1), CMD_ARGV (2), CMD_ARGV (3), CMD_ARGV (4), CMD_ARGV (5), CMD_ARGV (6)); + + switch (state) + { + case 0: + ClientPrint (ent, print_withtag, "Unknown command: %s", arg1); + break; + + case 3: + ClientPrint (ent, print_withtag, "CVar yb_%s, can be only set via RCON access.", CMD_ARGV (2)); + break; + + case 2: + ClientPrint (ent, print_withtag, "Command %s, can only be executed from server console.", arg1); + break; + } + if (g_isMetamod) + RETURN_META (MRES_SUPERCEDE); + + return; + } + else if (stricmp (command, "menuselect") == 0 && !IsNullString (arg1) && g_clients[ENTINDEX (ent) - 1].menu != NULL) + { + Client *client = &g_clients[ENTINDEX (ent) - 1]; + int selection = atoi (arg1); + + if (client->menu == &g_menus[12]) + { + DisplayMenuToClient (ent, NULL); // reset menu display + + switch (selection) + { + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + g_waypoint->Add (selection - 1); + break; + + case 8: + g_waypoint->Add (100); + break; + + case 9: + g_waypoint->SetLearnJumpWaypoint (); + break; + + case 10: + DisplayMenuToClient (ent, NULL); + break; + } + if (g_isMetamod) + RETURN_META (MRES_SUPERCEDE); + + return; + } + else if (client->menu == &g_menus[13]) + { + DisplayMenuToClient (ent, NULL); // reset menu display + + switch (selection) + { + case 1: + g_waypoint->ToggleFlags (FLAG_NOHOSTAGE); + break; + + case 2: + g_waypoint->ToggleFlags (FLAG_TF_ONLY); + break; + + case 3: + g_waypoint->ToggleFlags (FLAG_CF_ONLY); + break; + + case 4: + g_waypoint->ToggleFlags (FLAG_LIFT); + break; + + case 5: + g_waypoint->ToggleFlags (FLAG_SNIPER); + break; + } + if (g_isMetamod) + RETURN_META (MRES_SUPERCEDE); + + return; + } + else if (client->menu == &g_menus[9]) + { + DisplayMenuToClient (ent, NULL); // reset menu display + + switch (selection) + { + case 1: + if (g_waypointOn) + ServerCommand ("yapb waypoint off"); + else + ServerCommand ("yapb waypoint on"); + break; + + case 2: + g_waypointOn = true; + g_waypoint->CacheWaypoint (); + break; + + case 3: + g_waypointOn = true; + DisplayMenuToClient (ent, &g_menus[20]); + break; + + case 4: + g_waypointOn = true; + g_waypoint->DeletePath (); + break; + + case 5: + g_waypointOn = true; + DisplayMenuToClient (ent, &g_menus[12]); + break; + + case 6: + g_waypointOn = true; + g_waypoint->Delete (); + break; + + case 7: + g_waypointOn = true; + DisplayMenuToClient (ent, &g_menus[19]); + break; + + case 8: + g_waypointOn = true; + DisplayMenuToClient (ent, &g_menus[11]); + break; + + case 9: + DisplayMenuToClient (ent, &g_menus[10]); + break; + + case 10: + DisplayMenuToClient (ent, NULL); + break; + } + if (g_isMetamod) + RETURN_META (MRES_SUPERCEDE); + + return; + } + else if (client->menu == &g_menus[10]) + { + DisplayMenuToClient (ent, NULL); // reset menu display + + switch (selection) + { + case 1: + { + int terrPoints = 0; + int ctPoints = 0; + int goalPoints = 0; + int rescuePoints = 0; + int campPoints = 0; + int sniperPoints = 0; + int noHostagePoints = 0; + + for (int i = 0; i < g_numWaypoints; i++) + { + Path *path = g_waypoint->GetPath (i); + + if (path->flags & FLAG_TF_ONLY) + terrPoints++; + + if (path->flags & FLAG_CF_ONLY) + ctPoints++; + + if (path->flags & FLAG_GOAL) + goalPoints++; + + if (path->flags & FLAG_RESCUE) + rescuePoints++; + + if (path->flags & FLAG_CAMP) + campPoints++; + + if (path->flags & FLAG_SNIPER) + sniperPoints++; + + if (path->flags & FLAG_NOHOSTAGE) + noHostagePoints++; + } + ServerPrintNoTag ("Waypoints: %d - T Points: %d\n" + "CT Points: %d - Goal Points: %d\n" + "Rescue Points: %d - Camp Points: %d\n" + "Block Hostage Points: %d - Sniper Points: %d\n", g_numWaypoints, terrPoints, ctPoints, goalPoints, rescuePoints, campPoints, noHostagePoints, sniperPoints); + } + break; + + case 2: + g_waypointOn = true; + g_autoWaypoint &= 1; + g_autoWaypoint ^= 1; + + CenterPrint ("Auto-Waypoint %s", g_autoWaypoint ? "Enabled" : "Disabled"); + break; + + case 3: + g_waypointOn = true; + DisplayMenuToClient (ent, &g_menus[13]); + break; + + case 4: + if (g_waypoint->NodesValid ()) + g_waypoint->Save (); + else + CenterPrint ("Waypoint not saved\nThere are errors, see console"); + break; + + case 5: + g_waypoint->Save (); + break; + + case 6: + g_waypoint->Load (); + break; + + case 7: + if (g_waypoint->NodesValid ()) + CenterPrint ("Nodes work Find"); + else + CenterPrint ("There are errors, see console"); + break; + + case 8: + ServerCommand ("yapb wp on noclip"); + break; + + case 9: + DisplayMenuToClient (ent, &g_menus[9]); + break; + } + if (g_isMetamod) + RETURN_META (MRES_SUPERCEDE); + + return; + } + else if (client->menu == &g_menus[11]) + { + DisplayMenuToClient (ent, NULL); // reset menu display + + g_waypointOn = true; // turn waypoints on in case + + const int radiusValue[] = {0, 8, 16, 32, 48, 64, 80, 96, 128}; + + if ((selection >= 1) && (selection <= 9)) + g_waypoint->SetRadius (radiusValue[selection - 1]); + + if (g_isMetamod) + RETURN_META (MRES_SUPERCEDE); + + return; + } + else if (client->menu == &g_menus[0]) + { + DisplayMenuToClient (ent, NULL); // reset menu display + + switch (selection) + { + case 1: + fillCommand = false; + DisplayMenuToClient (ent, &g_menus[2]); + break; + + case 2: + DisplayMenuToClient (ent, &g_menus[1]); + break; + + case 3: + fillCommand = true; + DisplayMenuToClient (ent, &g_menus[6]); + break; + + case 4: + g_botManager->KillAll (); + break; + + case 10: + DisplayMenuToClient (ent, NULL); + break; + + } + if (g_isMetamod) + RETURN_META (MRES_SUPERCEDE); + + return; + } + else if (client->menu == &g_menus[2]) + { + DisplayMenuToClient (ent, NULL); // reset menu display + + switch (selection) + { + case 1: + g_botManager->AddRandom (); + break; + + case 2: + DisplayMenuToClient (ent, &g_menus[5]); + break; + + case 3: + g_botManager->RemoveRandom (); + break; + + case 4: + g_botManager->RemoveAll (); + break; + + case 5: + g_botManager->RemoveMenu (ent, 1); + break; + + case 10: + DisplayMenuToClient (ent, NULL); + break; + } + if (g_isMetamod) + RETURN_META (MRES_SUPERCEDE); + + return; + } + else if (client->menu == &g_menus[1]) + { + DisplayMenuToClient (ent, NULL); // reset menu display + + switch (selection) + { + case 1: + DisplayMenuToClient (ent, &g_menus[3]); + break; + + case 2: + DisplayMenuToClient (ent, &g_menus[9]); + break; + + case 3: + DisplayMenuToClient (ent, &g_menus[4]); + break; + + case 4: + extern ConVar yb_debug; + yb_debug.SetInt (yb_debug.GetInt () ^ 1); + + break; + + case 5: + if (IsAlive (ent)) + DisplayMenuToClient (ent, &g_menus[18]); + else + { + DisplayMenuToClient (ent, NULL); // reset menu display + CenterPrint ("You're dead, and have no access to this menu"); + } + break; + + case 10: + DisplayMenuToClient (ent, NULL); + break; + } + if (g_isMetamod) + RETURN_META (MRES_SUPERCEDE); + + return; + } + else if (client->menu == &g_menus[18]) + { + DisplayMenuToClient (ent, NULL); // reset menu display + Bot *bot = NULL; + + switch (selection) + { + case 1: + case 2: + if (FindNearestPlayer (reinterpret_cast (&bot), client->ent, 4096.0, true, true, true)) + { + if (!(bot->pev->weapons & (1 << WEAPON_C4)) && !bot->HasHostage () && (bot->GetTaskId () != TASK_PLANTBOMB) && (bot->GetTaskId () != TASK_DEFUSEBOMB)) + { + if (selection == 1) + { + bot->ResetDoubleJumpState (); + + bot->m_doubleJumpOrigin = client->ent->v.origin; + bot->m_doubleJumpEntity = client->ent; + + bot->StartTask (TASK_DOUBLEJUMP, TASKPRI_DOUBLEJUMP, -1, GetWorldTime (), true); + bot->TeamSayText (FormatBuffer ("Ok %s, i will help you!", STRING (ent->v.netname))); + } + else if (selection == 2) + bot->ResetDoubleJumpState (); + break; + } + } + break; + + case 3: + case 4: + if (FindNearestPlayer (reinterpret_cast (&bot), ent, 300.0, true, true, true)) + bot->DiscardWeaponForUser (ent, selection == 4 ? false : true); + break; + + case 10: + DisplayMenuToClient (ent, NULL); + break; + } + + if (g_isMetamod) + RETURN_META (MRES_SUPERCEDE); + + return; + } + else if (client->menu == &g_menus[19]) + { + DisplayMenuToClient (ent, NULL); // reset menu display + + const int autoDistanceValue[] = {0, 100, 130, 160, 190, 220, 250}; + + if (selection >= 1 && selection <= 7) + g_autoPathDistance = autoDistanceValue[selection - 1]; + + if (g_autoPathDistance == 0) + CenterPrint ("AutoPath disabled"); + else + CenterPrint ("AutoPath maximum distance set to %f", g_autoPathDistance); + + if (g_isMetamod) + RETURN_META (MRES_SUPERCEDE); + + return; + } + else if (client->menu == &g_menus[20]) + { + DisplayMenuToClient (ent, NULL); // reset menu display + + switch (selection) + { + case 1: + g_waypoint->CreatePath (CONNECTION_OUTGOING); + break; + + case 2: + g_waypoint->CreatePath (CONNECTION_INCOMING); + break; + + case 3: + g_waypoint->CreatePath (CONNECTION_BOTHWAYS); + break; + + case 10: + DisplayMenuToClient (ent, NULL); + break; + } + + if (g_isMetamod) + RETURN_META (MRES_SUPERCEDE); + + return; + } + else if (client->menu == &g_menus[5]) + { + DisplayMenuToClient (ent, NULL); // reset menu display + + client->menu = &g_menus[4]; + + switch (selection) + { + case 1: + g_storeAddbotVars[0] = g_randGen.Long (0, 20); + break; + + case 2: + g_storeAddbotVars[0] = g_randGen.Long (20, 40); + break; + + case 3: + g_storeAddbotVars[0] = g_randGen.Long (40, 60); + break; + + case 4: + g_storeAddbotVars[0] = g_randGen.Long (60, 80); + break; + + case 5: + g_storeAddbotVars[0] = g_randGen.Long (80, 99); + break; + + case 6: + g_storeAddbotVars[0] = 100; + break; + + case 10: + DisplayMenuToClient (ent, NULL); + break; + } + + if (client->menu == &g_menus[4]) + DisplayMenuToClient (ent, &g_menus[4]); + + if (g_isMetamod) + RETURN_META (MRES_SUPERCEDE); + + return; + } + else if (client->menu == &g_menus[6] && fillCommand) + { + DisplayMenuToClient (ent, NULL); // reset menu display + + switch (selection) + { + case 1: + case 2: + // turn off cvars if specified team + CVAR_SET_STRING ("mp_limitteams", "0"); + CVAR_SET_STRING ("mp_autoteambalance", "0"); + + case 5: + fillServerTeam = selection; + DisplayMenuToClient (ent, &g_menus[5]); + break; + + case 10: + DisplayMenuToClient (ent, NULL); + break; + } + if (g_isMetamod) + RETURN_META (MRES_SUPERCEDE); + + return; + } + else if (client->menu == &g_menus[4] && fillCommand) + { + DisplayMenuToClient (ent, NULL); // reset menu display + + switch (selection) + { + case 1: + case 2: + case 3: + case 4: + g_botManager->FillServer (fillServerTeam, selection - 2, g_storeAddbotVars[0]); + + case 10: + DisplayMenuToClient (ent, NULL); + break; + } + if (g_isMetamod) + RETURN_META (MRES_SUPERCEDE); + + return; + } + else if (client->menu == &g_menus[6]) + { + DisplayMenuToClient (ent, NULL); // reset menu display + + switch (selection) + { + case 1: + case 2: + case 5: + g_storeAddbotVars[1] = selection; + if (selection == 5) + { + g_storeAddbotVars[2] = 5; + g_botManager->AddBot ("", g_storeAddbotVars[0], g_storeAddbotVars[3], g_storeAddbotVars[1], g_storeAddbotVars[2]); + } + else + { + if (selection == 1) + DisplayMenuToClient (ent, &g_menus[7]); + else + DisplayMenuToClient (ent, &g_menus[8]); + } + break; + + case 10: + DisplayMenuToClient (ent, NULL); + break; + } + if (g_isMetamod) + RETURN_META (MRES_SUPERCEDE); + + return; + } + else if (client->menu == &g_menus[4]) + { + DisplayMenuToClient (ent, NULL); // reset menu display + + switch (selection) + { + case 1: + case 2: + case 3: + case 4: + g_storeAddbotVars[3] = selection - 2; + DisplayMenuToClient (ent, &g_menus[6]); + break; + + case 10: + DisplayMenuToClient (ent, NULL); + break; + } + if (g_isMetamod) + RETURN_META (MRES_SUPERCEDE); + + return; + } + else if (client->menu == &g_menus[7] || client->menu == &g_menus[8]) + { + DisplayMenuToClient (ent, NULL); // reset menu display + + switch (selection) + { + case 1: + case 2: + case 3: + case 4: + case 5: + g_storeAddbotVars[2] = selection; + g_botManager->AddBot ("", g_storeAddbotVars[0], g_storeAddbotVars[3], g_storeAddbotVars[1], g_storeAddbotVars[2]); + break; + + case 10: + DisplayMenuToClient (ent, NULL); + break; + } + if (g_isMetamod) + RETURN_META (MRES_SUPERCEDE); + + return; + } + else if (client->menu == &g_menus[3]) + { + DisplayMenuToClient (ent, NULL); // reset menu display + + switch (selection) + { + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + g_botManager->SetWeaponMode (selection); + break; + + case 10: + DisplayMenuToClient (ent, NULL); + break; + } + if (g_isMetamod) + RETURN_META (MRES_SUPERCEDE); + + return; + } + else if (client->menu == &g_menus[14]) + { + DisplayMenuToClient (ent, NULL); // reset menu display + + switch (selection) + { + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + g_botManager->GetBot (selection - 1)->Kick (); + break; + + case 9: + g_botManager->RemoveMenu (ent, 2); + break; + + case 10: + DisplayMenuToClient (ent, &g_menus[2]); + break; + } + if (g_isMetamod) + RETURN_META (MRES_SUPERCEDE); + + return; + } + else if (client->menu == &g_menus[15]) + { + DisplayMenuToClient (ent, NULL); // reset menu display + + switch (selection) + { + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + g_botManager->GetBot (selection + 8 - 1)->Kick (); + break; + + case 9: + g_botManager->RemoveMenu (ent, 3); + break; + + case 10: + g_botManager->RemoveMenu (ent, 1); + break; + } + if (g_isMetamod) + RETURN_META (MRES_SUPERCEDE); + + return; + } + else if (client->menu == &g_menus[16]) + { + DisplayMenuToClient (ent, NULL); // reset menu display + + switch (selection) + { + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + g_botManager->GetBot (selection + 16 - 1)->Kick (); + break; + + case 9: + g_botManager->RemoveMenu (ent, 4); + break; + + case 10: + g_botManager->RemoveMenu (ent, 2); + break; + } + if (g_isMetamod) + RETURN_META (MRES_SUPERCEDE); + + return; + } + else if (client->menu == &g_menus[17]) + { + DisplayMenuToClient (ent, NULL); // reset menu display + + switch (selection) + { + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + g_botManager->GetBot (selection + 24 - 1)->Kick (); + break; + + case 10: + g_botManager->RemoveMenu (ent, 3); + break; + } + if (g_isMetamod) + RETURN_META (MRES_SUPERCEDE); + + return; + } + } + } + + if (!g_isFakeCommand && (stricmp (command, "say") == 0 || stricmp (command, "say_team") == 0)) + { + Bot *bot = NULL; + + if (FStrEq (arg1, "dropme") || FStrEq (arg1, "dropc4")) + { + if (FindNearestPlayer (reinterpret_cast (&bot), ent, 300.0, true, true, true)) + bot->DiscardWeaponForUser (ent, IsNullString (strstr (arg1, "c4")) ? false : true); + + return; + } + + bool isAlive = IsAlive (ent); + int team = -1; + + if (FStrEq (command, "say_team")) + team = GetTeam (ent); + + for (int i = 0; i < GetMaxClients (); i++) + { + if (!(g_clients[i].flags & CF_USED) || (team != -1 && team != g_clients[i].team) || isAlive != IsAlive (g_clients[i].ent)) + continue; + + Bot *bot = g_botManager->GetBot (i); + + if (bot != NULL) + { + bot->m_sayTextBuffer.entityIndex = ENTINDEX (ent); + + if (IsNullString (CMD_ARGS ())) + continue; + + strcpy (bot->m_sayTextBuffer.sayText, CMD_ARGS ()); + bot->m_sayTextBuffer.timeNextChat = GetWorldTime () + bot->m_sayTextBuffer.chatDelay; + } + } + } + int clientIndex = ENTINDEX (ent) - 1; + + // check if this player alive, and issue something + if ((g_clients[clientIndex].flags & CF_ALIVE) && g_radioSelect[clientIndex] != 0 && strncmp (command, "menuselect", 10) == 0) + { + int radioCommand = atoi (arg1); + + if (radioCommand != 0) + { + radioCommand += 10 * (g_radioSelect[clientIndex] - 1); + + if (radioCommand != Radio_Affirmative && radioCommand != Radio_Negative && radioCommand != Radio_ReportingIn) + { + for (int i = 0; i < GetMaxClients (); i++) + { + Bot *bot = g_botManager->GetBot (i); + + // validate bot + if (bot != NULL && GetTeam (bot->GetEntity ()) == g_clients[clientIndex].team && VARS (ent) != bot->pev && bot->m_radioOrder == 0) + { + bot->m_radioOrder = radioCommand; + bot->m_radioEntity = ent; + } + } + } + g_lastRadioTime[g_clients[clientIndex].team] = GetWorldTime (); + } + g_radioSelect[clientIndex] = 0; + } + else if (strncmp (command, "radio", 5) == 0) + g_radioSelect[clientIndex] = atoi (&command[5]); + + if (g_isMetamod) + RETURN_META (MRES_IGNORED); + + (*g_functionTable.pfnClientCommand) (ent); +} + +void ServerActivate (edict_t *pentEdictList, int edictCount, int clientMax) +{ + // this function is called when the server has fully loaded and is about to manifest itself + // on the network as such. Since a mapchange is actually a server shutdown followed by a + // restart, this function is also called when a new map is being loaded. Hence it's the + // perfect place for doing initialization stuff for our bots, such as reading the BSP data, + // loading the bot profiles, and drawing the world map (ie, filling the navigation hashtable). + // Once this function has been called, the server can be considered as "running". + + FreeLibraryMemory (); + InitConfig (); // initialize all config files + + // do level initialization stuff here... + g_waypoint->Init (); + g_waypoint->Load (); + + // execute main config + ServerCommand ("exec addons/yapb/config/yapb.cfg"); + + if (TryFileOpen (FormatBuffer ("%s/maps/%s_yapb.cfg", GetModName (), GetMapName ()))) + { + ServerCommand ("exec maps/%s_yapb.cfg", GetMapName ()); + ServerPrint ("Executing Map-Specific config file"); + } + g_botManager->InitQuota (); + + if (g_isMetamod) + RETURN_META (MRES_IGNORED); + + (*g_functionTable.pfnServerActivate) (pentEdictList, edictCount, clientMax); + + g_waypoint->InitializeVisibility (); +} + +void ServerDeactivate (void) +{ + // this function is called when the server is shutting down. A particular note about map + // changes: changing the map means shutting down the server and starting a new one. Of course + // this process is transparent to the user, but either in single player when the hero reaches + // a new level and in multiplayer when it's time for a map change, be aware that what happens + // is that the server actually shuts down and restarts with a new map. Hence we can use this + // function to free and deinit anything which is map-specific, for example we free the memory + // space we m'allocated for our BSP data, since a new map means new BSP data to interpret. In + // any case, when the new map will be booting, ServerActivate() will be called, so we'll do + // the loading of new bots and the new BSP data parsing there. + + // save collected experience on shutdown + g_waypoint->SaveExperienceTab (); + g_waypoint->SaveVisibilityTab (); + + FreeLibraryMemory (); + + if (g_isMetamod) + RETURN_META (MRES_IGNORED); + + (*g_functionTable.pfnServerDeactivate) (); +} + +void KeyValue (edict_t *ent, KeyValueData *data) +{ + // this function is called when the game requests a pointer to some entity's keyvalue data. + // The keyvalue data is held in each entity's infobuffer (basically a char buffer where each + // game DLL can put the stuff it wants) under - as it says - the form of a key/value pair. A + // common example of key/value pair is the "model", "(name of player model here)" one which + // is often used for client DLLs to display player characters with the right model (else they + // would all have the dull "models/player.mdl" one). The entity for which the keyvalue data + // pointer is requested is pentKeyvalue, the pointer to the keyvalue data structure pkvd. + + if (g_isMetamod) + RETURN_META (MRES_IGNORED); + + (*g_functionTable.pfnKeyValue) (ent, data); +} + +void StartFrame (void) +{ + // this function starts a video frame. It is called once per video frame by the engine. If + // you run Half-Life at 90 fps, this function will then be called 90 times per second. By + // placing a hook on it, we have a good place to do things that should be done continuously + // during the game, for example making the bots think (yes, because no Think() function exists + // for the bots by the MOD side, remember). Also here we have control on the bot population, + // for example if a new player joins the server, we should disconnect a bot, and if the + // player population decreases, we should fill the server with other bots. + + // record some stats of all players on the server + for (int i = 0; i < GetMaxClients (); i++) + { + edict_t *player = INDEXENT (i + 1); + + if (!FNullEnt (player) && (player->v.flags & FL_CLIENT)) + { + g_clients[i].ent = player; + g_clients[i].flags |= CF_USED; + + if (IsAlive (player)) + g_clients[i].flags |= CF_ALIVE; + else + g_clients[i].flags &= ~CF_ALIVE; + + if (g_clients[i].flags & CF_ALIVE) + { + // keep the clipping mode enabled, or it can be turned off after new round has started + if (g_hostEntity == player && g_editNoclip) + g_hostEntity->v.movetype = MOVETYPE_NOCLIP; + + g_clients[i].origin = player->v.origin; + SoundSimulateUpdate (i); + } + } + else + { + g_clients[i].flags &= ~(CF_USED | CF_ALIVE); + g_clients[i].ent = NULL; + } + } + static float secondTimer = 0.0; + + // **** AI EXECUTION STARTS **** + g_botManager->Think (); + // **** AI EXECUTION FINISH **** + + if (!IsDedicatedServer () && !FNullEnt (g_hostEntity)) + { + if (g_waypointOn) + g_waypoint->Think (); + + CheckWelcomeMessage (); + } + + if (secondTimer < GetWorldTime ()) + { + for (int i = 0; i < GetMaxClients (); i++) + { + edict_t *player = INDEXENT (i + 1); + + // code below is executed only on dedicated server + if (IsDedicatedServer () && !FNullEnt (player) && (player->v.flags & FL_CLIENT) && !(player->v.flags & FL_FAKECLIENT)) + { + if (g_clients[i].flags & CF_ADMIN) + { + if (IsNullString (yb_password_key.GetString ()) && IsNullString (yb_password.GetString ())) + g_clients[i].flags &= ~CF_ADMIN; + else if (strcmp (yb_password.GetString (), INFOKEY_VALUE (GET_INFOKEYBUFFER (g_clients[i].ent), const_cast (yb_password_key.GetString ())))) + { + g_clients[i].flags &= ~CF_ADMIN; + ServerPrint ("Player %s had lost remote access to YaPB.", STRING (player->v.netname)); + } + } + else if (IsNullString (yb_password_key.GetString ()) && IsNullString (yb_password.GetString ())) + { + if (strcmp (yb_password.GetString (), INFOKEY_VALUE (GET_INFOKEYBUFFER (g_clients[i].ent), const_cast (yb_password_key.GetString ()))) == 0) + { + g_clients[i].flags |= CF_ADMIN; + ServerPrint ("Player %s had gained full remote access to YaPB.", STRING (player->v.netname)); + } + } + } + + if (g_isMetamod) + { + cvar_t *csdm_active = CVAR_GET_POINTER ("csdm_active"); + cvar_t *mp_freeforall = CVAR_GET_POINTER ("mp_freeforall"); + + if (csdm_active != NULL && csdm_active->value > 0) + yb_csdm_mode.SetInt (mp_freeforall != NULL && mp_freeforall->value > 0 ? 2 : 1); + } + } + + extern ConVar yb_danger_factor; + + if (yb_danger_factor.GetFloat () >= 4096) + yb_danger_factor.SetFloat (4096.0); + + secondTimer = GetWorldTime () + 1.5; + + if (g_bombPlanted) + g_waypoint->SetBombPosition (); + } + + // keep bot number up to date + g_botManager->MaintainBotQuota (); + + if (g_isMetamod) + RETURN_META (MRES_IGNORED); + + (*g_functionTable.pfnStartFrame) (); +} + +int Spawn_Post (edict_t *ent) +{ + // this function asks the game DLL to spawn (i.e, give a physical existence in the virtual + // world, in other words to 'display') the entity pointed to by ent in the game. The + // Spawn() function is one of the functions any entity is supposed to have in the game DLL, + // and any MOD is supposed to implement one for each of its entities. Post version called + // only by metamod. + + // solves the bots unable to see through certain types of glass bug. + if (ent->v.rendermode == kRenderTransTexture) + ent->v.flags &= ~FL_WORLDBRUSH; // clear the FL_WORLDBRUSH flag out of transparent ents + + RETURN_META_VALUE (MRES_IGNORED, 0); +} + +void ServerActivate_Post (edict_t *pentEdictList, int edictCount, int clientMax) +{ + // this function is called when the server has fully loaded and is about to manifest itself + // on the network as such. Since a mapchange is actually a server shutdown followed by a + // restart, this function is also called when a new map is being loaded. Hence it's the + // perfect place for doing initialization stuff for our bots, such as reading the BSP data, + // loading the bot profiles, and drawing the world map (ie, filling the navigation hashtable). + // Once this function has been called, the server can be considered as "running". Post version + // called only by metamod. + + g_waypoint->InitializeVisibility (); + + RETURN_META (MRES_IGNORED); +} + +void pfnChangeLevel (char *s1, char *s2) +{ + // the purpose of this function is to ask the engine to shutdown the server and restart a + // new one running the map whose name is s1. It is used ONLY IN SINGLE PLAYER MODE and is + // transparent to the user, because it saves the player state and equipment and restores it + // back in the new level. The "changelevel trigger point" in the old level is linked to the + // new level's spawn point using the s2 string, which is formatted as follows: "trigger_name + // to spawnpoint_name", without spaces (for example, "tr_1atotr_2lm" would tell the engine + // the player has reached the trigger point "tr_1a" and has to spawn in the next level on the + // spawn point named "tr_2lm". + + // save collected experience on map change + g_waypoint->SaveExperienceTab (); + g_waypoint->SaveVisibilityTab (); + + if (g_isMetamod) + RETURN_META (MRES_IGNORED); + + CHANGE_LEVEL (s1, s2); +} + +edict_t *pfnFindEntityByString (edict_t *edictStartSearchAfter, const char *field, const char *value) +{ + // round starts in counter-strike 1.5 + if (strcmp (value, "info_map_parameters") == 0) + RoundInit (); + + if (g_isMetamod) + RETURN_META_VALUE (MRES_IGNORED, 0); + + return FIND_ENTITY_BY_STRING (edictStartSearchAfter, field, value); +} + +void pfnEmitSound (edict_t *entity, int channel, const char *sample, float volume, float attenuation, int flags, int pitch) +{ + // this function tells the engine that the entity pointed to by "entity", is emitting a sound + // which fileName is "sample", at level "channel" (CHAN_VOICE, etc...), with "volume" as + // loudness multiplicator (normal volume VOL_NORM is 1.0), with a pitch of "pitch" (normal + // pitch PITCH_NORM is 100.0), and that this sound has to be attenuated by distance in air + // according to the value of "attenuation" (normal attenuation ATTN_NORM is 0.8 ; ATTN_NONE + // means no attenuation with distance). Optionally flags "fFlags" can be passed, which I don't + // know the heck of the purpose. After we tell the engine to emit the sound, we have to call + // SoundAttachToThreat() to bring the sound to the ears of the bots. Since bots have no client DLL + // to handle this for them, such a job has to be done manually. + + SoundAttachToThreat (entity, sample, volume); + + if (g_isMetamod) + RETURN_META (MRES_IGNORED); + + (*g_engfuncs.pfnEmitSound) (entity, channel, sample, volume, attenuation, flags, pitch); +} + +void pfnClientCommand (edict_t *ent, char *format, ...) +{ + // this function forces the client whose player entity is ent to issue a client command. + // How it works is that clients all have a g_xgv global string in their client DLL that + // stores the command string; if ever that string is filled with characters, the client DLL + // sends it to the engine as a command to be executed. When the engine has executed that + // command, this g_xgv string is reset to zero. Here is somehow a curious implementation of + // ClientCommand: the engine sets the command it wants the client to issue in his g_xgv, then + // the client DLL sends it back to the engine, the engine receives it then executes the + // command therein. Don't ask me why we need all this complicated crap. Anyhow since bots have + // no client DLL, be certain never to call this function upon a bot entity, else it will just + // make the server crash. Since hordes of uncautious, not to say stupid, programmers don't + // even imagine some players on their servers could be bots, this check is performed less than + // sometimes actually by their side, that's why we strongly recommend to check it here too. In + // case it's a bot asking for a client command, we handle it like we do for bot commands, ie + // using FakeClientCommand(). + + va_list ap; + char buffer[1024]; + + va_start (ap, format); + _vsnprintf (buffer, sizeof (buffer), format, ap); + va_end (ap); + + // is the target entity an official bot, or a third party bot ? + if (IsValidBot (ent) || (ent->v.flags & FL_DORMANT)) + { + if (g_isMetamod) + RETURN_META (MRES_SUPERCEDE); // prevent bots to be forced to issue client commands + + return; + } + + if (g_isMetamod) + RETURN_META (MRES_IGNORED); + + CLIENT_COMMAND (ent, buffer); +} + +void pfnMessageBegin (int msgDest, int msgType, const float *origin, edict_t *ed) +{ + // this function called each time a message is about to sent. + + // store the message type in our own variables, since the GET_USER_MSG_ID () will just do a lot of strcmp()'s... + if (g_isMetamod && g_netMsg->GetId (NETMSG_MONEY) == -1) + { + g_netMsg->SetId (NETMSG_VGUI, GET_USER_MSG_ID (PLID, "VGUIMenu", NULL)); + g_netMsg->SetId (NETMSG_SHOWMENU, GET_USER_MSG_ID (PLID, "ShowMenu", NULL)); + g_netMsg->SetId (NETMSG_WEAPONLIST, GET_USER_MSG_ID (PLID, "WeaponList", NULL)); + g_netMsg->SetId (NETMSG_CURWEAPON, GET_USER_MSG_ID (PLID, "CurWeapon", NULL)); + g_netMsg->SetId (NETMSG_AMMOX, GET_USER_MSG_ID (PLID, "AmmoX", NULL)); + g_netMsg->SetId (NETMSG_AMMOPICKUP, GET_USER_MSG_ID (PLID, "AmmoPickup", NULL)); + g_netMsg->SetId (NETMSG_DAMAGE, GET_USER_MSG_ID (PLID, "Damage", NULL)); + g_netMsg->SetId (NETMSG_MONEY, GET_USER_MSG_ID (PLID, "Money", NULL)); + g_netMsg->SetId (NETMSG_STATUSICON, GET_USER_MSG_ID (PLID, "StatusIcon", NULL)); + g_netMsg->SetId (NETMSG_DEATH, GET_USER_MSG_ID (PLID, "DeathMsg", NULL)); + g_netMsg->SetId (NETMSG_SCREENFADE, GET_USER_MSG_ID (PLID, "ScreenFade", NULL)); + g_netMsg->SetId (NETMSG_HLTV, GET_USER_MSG_ID (PLID, "HLTV", NULL)); + g_netMsg->SetId (NETMSG_TEXTMSG, GET_USER_MSG_ID (PLID, "TextMsg", NULL)); + g_netMsg->SetId (NETMSG_SCOREINFO, GET_USER_MSG_ID (PLID, "ScoreInfo", NULL)); + g_netMsg->SetId (NETMSG_BARTIME, GET_USER_MSG_ID (PLID, "BarTime", NULL)); + g_netMsg->SetId (NETMSG_SENDAUDIO, GET_USER_MSG_ID (PLID, "SendAudio", NULL)); + g_netMsg->SetId (NETMSG_SAYTEXT, GET_USER_MSG_ID (PLID, "SayText", NULL)); + g_netMsg->SetId (NETMSG_RESETHUD, GET_USER_MSG_ID (PLID, "ResetHUD", NULL)); + + if (g_gameVersion != CSV_OLD) + g_netMsg->SetId (NETMSG_BOTVOICE, GET_USER_MSG_ID (PLID, "BotVoice", NULL)); + } + g_netMsg->Reset (); + + if (msgDest == MSG_SPEC && msgType == g_netMsg->GetId (NETMSG_HLTV) && g_gameVersion != CSV_OLD) + g_netMsg->SetMessage (NETMSG_HLTV); + + g_netMsg->HandleMessageIfRequired (msgType, NETMSG_WEAPONLIST); + + if (!FNullEnt (ed)) + { + int index = g_botManager->GetIndex (ed); + + // is this message for a bot? + if (index != -1 && !(ed->v.flags & FL_DORMANT) && g_botManager->GetBot (index)->GetEntity () == ed) + { + g_netMsg->Reset (); + g_netMsg->SetBot (g_botManager->GetBot (index)); + + // message handling is done in usermsg.cpp + g_netMsg->HandleMessageIfRequired (msgType, NETMSG_VGUI); + g_netMsg->HandleMessageIfRequired (msgType, NETMSG_CURWEAPON); + g_netMsg->HandleMessageIfRequired (msgType, NETMSG_AMMOX); + g_netMsg->HandleMessageIfRequired (msgType, NETMSG_AMMOPICKUP); + g_netMsg->HandleMessageIfRequired (msgType, NETMSG_DAMAGE); + g_netMsg->HandleMessageIfRequired (msgType, NETMSG_MONEY); + g_netMsg->HandleMessageIfRequired (msgType, NETMSG_STATUSICON); + g_netMsg->HandleMessageIfRequired (msgType, NETMSG_SCREENFADE); + g_netMsg->HandleMessageIfRequired (msgType, NETMSG_BARTIME); + g_netMsg->HandleMessageIfRequired (msgType, NETMSG_TEXTMSG); + g_netMsg->HandleMessageIfRequired (msgType, NETMSG_SHOWMENU); + g_netMsg->HandleMessageIfRequired (msgType, NETMSG_RESETHUD); + } + } + else if (msgDest == MSG_ALL) + { + g_netMsg->Reset (); + + g_netMsg->HandleMessageIfRequired (msgType, NETMSG_SCOREINFO); + g_netMsg->HandleMessageIfRequired (msgType, NETMSG_DEATH); + g_netMsg->HandleMessageIfRequired (msgType, NETMSG_TEXTMSG); + + if (msgType == SVC_INTERMISSION) + { + for (int i = 0; i < GetMaxClients (); i++) + { + Bot *bot = g_botManager->GetBot (i); + + if (bot != NULL) + bot->m_notKilled = false; + } + } + } + + if (g_isMetamod) + RETURN_META (MRES_IGNORED); + + MESSAGE_BEGIN (msgDest, msgType, origin, ed); +} + +void pfnMessageEnd (void) +{ + g_netMsg->Reset (); + + if (g_isMetamod) + RETURN_META (MRES_IGNORED); + + MESSAGE_END (); +} + +void pfnWriteByte (int value) +{ + // if this message is for a bot, call the client message function... + g_netMsg->Execute ((void *) &value); + + if (g_isMetamod) + RETURN_META (MRES_IGNORED); + + WRITE_BYTE (value); +} + +void pfnWriteChar (int value) +{ + // if this message is for a bot, call the client message function... + g_netMsg->Execute ((void *) &value); + + if (g_isMetamod) + RETURN_META (MRES_IGNORED); + + WRITE_CHAR (value); +} + +void pfnWriteShort (int value) +{ + // if this message is for a bot, call the client message function... + g_netMsg->Execute ((void *) &value); + + if (g_isMetamod) + RETURN_META (MRES_IGNORED); + + WRITE_SHORT (value); +} + +void pfnWriteLong (int value) +{ + // if this message is for a bot, call the client message function... + g_netMsg->Execute ((void *) &value); + + if (g_isMetamod) + RETURN_META (MRES_IGNORED); + + WRITE_LONG (value); +} + +void pfnWriteAngle (float value) +{ + // if this message is for a bot, call the client message function... + g_netMsg->Execute ((void *) &value); + + if (g_isMetamod) + RETURN_META (MRES_IGNORED); + + WRITE_ANGLE (value); +} + +void pfnWriteCoord (float value) +{ + // if this message is for a bot, call the client message function... + g_netMsg->Execute ((void *) &value); + + if (g_isMetamod) + RETURN_META (MRES_IGNORED); + + WRITE_COORD (value); +} + +void pfnWriteString (const char *sz) +{ + Bot *bot = g_botManager->FindOneValidAliveBot (); + + if (bot != NULL && IsAlive (bot->GetEntity ())) + bot->HandleChatterMessage (sz); + + // if this message is for a bot, call the client message function... + g_netMsg->Execute ((void *) sz); + + if (g_isMetamod) + RETURN_META (MRES_IGNORED); + + WRITE_STRING (sz); +} + +void pfnWriteEntity (int value) +{ + // if this message is for a bot, call the client message function... + g_netMsg->Execute ((void *) &value); + + if (g_isMetamod) + RETURN_META (MRES_IGNORED); + + WRITE_ENTITY (value); +} + +int pfnCmd_Argc (void) +{ + // this function returns the number of arguments the current client command string has. Since + // bots have no client DLL and we may want a bot to execute a client command, we had to + // implement a g_xgv string in the bot DLL for holding the bots' commands, and also keep + // track of the argument count. Hence this hook not to let the engine ask an unexistent client + // DLL for a command we are holding here. Of course, real clients commands are still retrieved + // the normal way, by asking the engine. + + // is this a bot issuing that client command ? + if (g_isFakeCommand) + { + if (g_isMetamod) + RETURN_META_VALUE (MRES_SUPERCEDE, g_fakeArgc); + + return g_fakeArgc; // if so, then return the argument count we know + } + + if (g_isMetamod) + RETURN_META_VALUE (MRES_IGNORED, 0); + + return CMD_ARGC (); // ask the engine how many arguments there are +} + +const char *pfnCmd_Args (void) +{ + // this function returns a pointer to the whole current client command string. Since bots + // have no client DLL and we may want a bot to execute a client command, we had to implement + // a g_xgv string in the bot DLL for holding the bots' commands, and also keep track of the + // argument count. Hence this hook not to let the engine ask an unexistent client DLL for a + // command we are holding here. Of course, real clients commands are still retrieved the + // normal way, by asking the engine. + + // is this a bot issuing that client command? + if (g_isFakeCommand) + { + // is it a "say" or "say_team" client command? + if (strncmp ("say ", g_fakeArgv, 4) == 0) + { + if (g_isMetamod) + RETURN_META_VALUE (MRES_SUPERCEDE, g_fakeArgv + 4); + + return g_fakeArgv + 4; // skip the "say" bot client command + } + else if (strncmp ("say_team ", g_fakeArgv, 9) == 0) + { + if (g_isMetamod) + RETURN_META_VALUE (MRES_SUPERCEDE, g_fakeArgv + 9); + + return g_fakeArgv + 9; // skip the "say_team" bot client command + } + + if (g_isMetamod) + RETURN_META_VALUE (MRES_SUPERCEDE, g_fakeArgv); + + return g_fakeArgv; // else return the whole bot client command string we know + } + + if (g_isMetamod) + RETURN_META_VALUE (MRES_IGNORED, NULL); + + return CMD_ARGS (); // ask the client command string to the engine +} + +const char *pfnCmd_Argv (int argc) +{ + // this function returns a pointer to a certain argument of the current client command. Since + // bots have no client DLL and we may want a bot to execute a client command, we had to + // implement a g_xgv string in the bot DLL for holding the bots' commands, and also keep + // track of the argument count. Hence this hook not to let the engine ask an unexistent client + // DLL for a command we are holding here. Of course, real clients commands are still retrieved + // the normal way, by asking the engine. + + // is this a bot issuing that client command ? + if (g_isFakeCommand) + { + if (g_isMetamod) + RETURN_META_VALUE (MRES_SUPERCEDE, GetField (g_fakeArgv, argc)); + + return GetField (g_fakeArgv, argc); // if so, then return the wanted argument we know + } + if (g_isMetamod) + RETURN_META_VALUE (MRES_IGNORED, NULL); + + return CMD_ARGV (argc); // ask the argument number "argc" to the engine +} + +void pfnClientPrintf (edict_t *ent, PRINT_TYPE printType, const char *message) +{ + // this function prints the text message string pointed to by message by the client side of + // the client entity pointed to by ent, in a manner depending of printType (print_console, + // print_center or print_chat). Be certain never to try to feed a bot with this function, + // as it will crash your server. Why would you, anyway ? bots have no client DLL as far as + // we know, right ? But since stupidity rules this world, we do a preventive check :) + + if (IsValidBot (ent)) + { + if (g_isMetamod) + RETURN_META (MRES_SUPERCEDE); + + return; + } + + if (g_isMetamod) + RETURN_META (MRES_IGNORED); + + CLIENT_PRINTF (ent, printType, message); +} + +void pfnSetClientMaxspeed (const edict_t *ent, float newMaxspeed) +{ + Bot *bot = g_botManager->GetBot (const_cast (ent)); + + // check wether it's not a bot + if (bot != NULL) + bot->pev->maxspeed = newMaxspeed; + + if (g_isMetamod) + RETURN_META (MRES_IGNORED); + + (*g_engfuncs.pfnSetClientMaxspeed) (ent, newMaxspeed); +} + +int pfnRegUserMsg (const char *name, int size) +{ + // this function registers a "user message" by the engine side. User messages are network + // messages the game DLL asks the engine to send to clients. Since many MODs have completely + // different client features (Counter-Strike has a radar and a timer, for example), network + // messages just can't be the same for every MOD. Hence here the MOD DLL tells the engine, + // "Hey, you have to know that I use a network message whose name is pszName and it is size + // packets long". The engine books it, and returns the ID number under which he recorded that + // custom message. Thus every time the MOD DLL will be wanting to send a message named pszName + // using pfnMessageBegin (), it will know what message ID number to send, and the engine will + // know what to do, only for non-metamod version + + if (g_isMetamod) + RETURN_META_VALUE (MRES_IGNORED, 0); + + int message = REG_USER_MSG (name, size); + + if (strcmp (name, "VGUIMenu") == 0) + g_netMsg->SetId (NETMSG_VGUI, message); + else if (strcmp (name, "ShowMenu") == 0) + g_netMsg->SetId (NETMSG_SHOWMENU, message); + else if (strcmp (name, "WeaponList") == 0) + g_netMsg->SetId (NETMSG_WEAPONLIST, message); + else if (strcmp (name, "CurWeapon") == 0) + g_netMsg->SetId (NETMSG_CURWEAPON, message); + else if (strcmp (name, "AmmoX") == 0) + g_netMsg->SetId (NETMSG_AMMOX, message); + else if (strcmp (name, "AmmoPickup") == 0) + g_netMsg->SetId (NETMSG_AMMOPICKUP, message); + else if (strcmp (name, "Damage") == 0) + g_netMsg->SetId (NETMSG_DAMAGE, message); + else if (strcmp (name, "Money") == 0) + g_netMsg->SetId (NETMSG_MONEY, message); + else if (strcmp (name, "StatusIcon") == 0) + g_netMsg->SetId (NETMSG_STATUSICON, message); + else if (strcmp (name, "DeathMsg") == 0) + g_netMsg->SetId (NETMSG_DEATH, message); + else if (strcmp (name, "ScreenFade") == 0) + g_netMsg->SetId (NETMSG_SCREENFADE, message); + else if (strcmp (name, "HLTV") == 0) + g_netMsg->SetId (NETMSG_HLTV, message); + else if (strcmp (name, "TextMsg") == 0) + g_netMsg->SetId (NETMSG_TEXTMSG, message); + else if (strcmp (name, "ScoreInfo") == 0) + g_netMsg->SetId (NETMSG_SCOREINFO, message); + else if (strcmp (name, "BarTime") == 0) + g_netMsg->SetId (NETMSG_BARTIME, message); + else if (strcmp (name, "SendAudio") == 0) + g_netMsg->SetId (NETMSG_SENDAUDIO, message); + else if (strcmp (name, "SayText") == 0) + g_netMsg->SetId (NETMSG_SAYTEXT, message); + else if (strcmp (name, "BotVoice") == 0) + g_netMsg->SetId (NETMSG_BOTVOICE, message); + else if (strcmp (name, "ResetHUD") == 0) + g_netMsg->SetId (NETMSG_BOTVOICE, message); + + return message; +} + +void pfnAlertMessage (ALERT_TYPE alertType, char *format, ...) +{ + va_list ap; + char buffer[1024]; + + va_start (ap, format); + vsprintf (buffer, format, ap); + va_end (ap); + + if (strstr (buffer, "_Defuse_") != NULL) + { + // notify all terrorists that CT is starting bomb defusing + for (int i = 0; i < GetMaxClients (); i++) + { + Bot *bot = g_botManager->GetBot (i); + + if (bot != NULL && GetTeam (bot->GetEntity ()) == TEAM_TF && IsAlive (bot->GetEntity ())) + { + bot->ResetTasks (); + bot->MoveToVector (g_waypoint->GetBombPosition ()); + } + } + } + + if (g_isMetamod) + RETURN_META (MRES_IGNORED); + + (*g_engfuncs.pfnAlertMessage) (alertType, buffer); +} + +const char *pfnGetPlayerAuthId (edict_t *e) +{ + if (IsValidBot (e)) + { + if (g_isMetamod) + RETURN_META_VALUE (MRES_SUPERCEDE, "BOT"); + + return "BOT"; + } + + if (g_isMetamod) + RETURN_META_VALUE (MRES_IGNORED, 0); + + return (*g_engfuncs.pfnGetPlayerAuthId) (e); +} + +unsigned int pfnGetPlayerWONId (edict_t *e) +{ + if (IsValidBot (e)) + { + if (g_isMetamod) + RETURN_META_VALUE (MRES_SUPERCEDE, 0); + + return 0; + } + + if (g_isMetamod) + RETURN_META_VALUE (MRES_IGNORED, 0); + + return (*g_engfuncs.pfnGetPlayerWONId) (e); +} + +gamedll_funcs_t gameDLLFunc; + +export int GetEntityAPI2 (gamefuncs_t *functionTable, int *interfaceVersion) +{ + // this function is called right after FuncPointers_t() by the engine in the game DLL (or + // what it BELIEVES to be the game DLL), in order to copy the list of MOD functions that can + // be called by the engine, into a memory block pointed to by the functionTable pointer + // that is passed into this function (explanation comes straight from botman). This allows + // the Half-Life engine to call these MOD DLL functions when it needs to spawn an entity, + // connect or disconnect a player, call Think() functions, Touch() functions, or Use() + // functions, etc. The bot DLL passes its OWN list of these functions back to the Half-Life + // engine, and then calls the MOD DLL's version of GetEntityAPI to get the REAL gamedll + // functions this time (to use in the bot code). + + memset (functionTable, 0, sizeof (gamefuncs_t)); + + if (!g_isMetamod) + { + // pass other DLLs engine callbacks to function table... + if ((*g_entityAPI) (&g_functionTable, INTERFACE_VERSION) == 0) + { + AddLogEntry (true, LL_FATAL, "GetEntityAPI2: ERROR - Not Initialized."); + return FALSE; // error initializing function table!!! + } + + gameDLLFunc.dllapi_table = &g_functionTable; + gpGamedllFuncs = &gameDLLFunc; + + memcpy (functionTable, &g_functionTable, sizeof (gamefuncs_t)); + } + + functionTable->pfnGameInit = GameDLLInit; + functionTable->pfnSpawn = Spawn; + functionTable->pfnClientConnect = ClientConnect; + functionTable->pfnClientDisconnect = ClientDisconnect; + functionTable->pfnClientPutInServer = ClientPutInServer; + functionTable->pfnClientUserInfoChanged = ClientUserInfoChanged; + functionTable->pfnClientCommand = ClientCommand; + functionTable->pfnServerActivate = ServerActivate; + functionTable->pfnServerDeactivate = ServerDeactivate; + functionTable->pfnKeyValue = KeyValue; + functionTable->pfnStartFrame = StartFrame; + functionTable->pfnTouch = Touch; + + return TRUE; +} + +export int GetEntityAPI2_Post (gamefuncs_t *functionTable, int *interfaceVersion) +{ + // this function is called right after FuncPointers_t() by the engine in the game DLL (or + // what it BELIEVES to be the game DLL), in order to copy the list of MOD functions that can + // be called by the engine, into a memory block pointed to by the functionTable pointer + // that is passed into this function (explanation comes straight from botman). This allows + // the Half-Life engine to call these MOD DLL functions when it needs to spawn an entity, + // connect or disconnect a player, call Think() functions, Touch() functions, or Use() + // functions, etc. The bot DLL passes its OWN list of these functions back to the Half-Life + // engine, and then calls the MOD DLL's version of GetEntityAPI to get the REAL gamedll + // functions this time (to use in the bot code). Post version, called only by metamod. + + memset (functionTable, 0, sizeof (gamefuncs_t)); + + functionTable->pfnSpawn = Spawn_Post; + functionTable->pfnServerActivate = ServerActivate_Post; + + return TRUE; +} + +export int GetNewDLLFunctions (newgamefuncs_t *functionTable, int *interfaceVersion) +{ + // it appears that an extra function table has been added in the engine to gamedll interface + // since the date where the first enginefuncs table standard was frozen. These ones are + // facultative and we don't hook them, but since some MODs might be featuring it, we have to + // pass them too, else the DLL interfacing wouldn't be complete and the game possibly wouldn't + // run properly. + + if (g_getNewEntityAPI == NULL) + return FALSE; + + if (!(*g_getNewEntityAPI) (functionTable, interfaceVersion)) + { + AddLogEntry (true, LL_FATAL, "GetNewDLLFunctions: ERROR - Not Initialized."); + return FALSE; + } + + gameDLLFunc.newapi_table = functionTable; + return TRUE; +} + +export int GetEngineFunctions (enginefuncs_t *functionTable, int *interfaceVersion) +{ + if (g_isMetamod) + memset (functionTable, 0, sizeof (enginefuncs_t)); + + functionTable->pfnChangeLevel = pfnChangeLevel; + functionTable->pfnFindEntityByString = pfnFindEntityByString; + functionTable->pfnEmitSound = pfnEmitSound; + functionTable->pfnClientCommand = pfnClientCommand; + functionTable->pfnMessageBegin = pfnMessageBegin; + functionTable->pfnMessageEnd = pfnMessageEnd; + functionTable->pfnWriteByte = pfnWriteByte; + functionTable->pfnWriteChar = pfnWriteChar; + functionTable->pfnWriteShort = pfnWriteShort; + functionTable->pfnWriteLong = pfnWriteLong; + functionTable->pfnWriteAngle = pfnWriteAngle; + functionTable->pfnWriteCoord = pfnWriteCoord; + functionTable->pfnWriteString = pfnWriteString; + functionTable->pfnWriteEntity = pfnWriteEntity; + functionTable->pfnRegUserMsg = pfnRegUserMsg; + functionTable->pfnClientPrintf = pfnClientPrintf; + functionTable->pfnCmd_Args = pfnCmd_Args; + functionTable->pfnCmd_Argv = pfnCmd_Argv; + functionTable->pfnCmd_Argc = pfnCmd_Argc; + functionTable->pfnSetClientMaxspeed = pfnSetClientMaxspeed; + functionTable->pfnAlertMessage = pfnAlertMessage; + functionTable->pfnGetPlayerAuthId = pfnGetPlayerAuthId; + functionTable->pfnGetPlayerWONId = pfnGetPlayerWONId; + + return TRUE; +} + +export int Server_GetBlendingInterface (int version, void **ppinterface, void *pstudio, float (*rotationmatrix)[3][4], float (*bonetransform)[128][3][4]) +{ + // this function synchronizes the studio model animation blending interface (i.e, what parts + // of the body move, which bones, which hitboxes and how) between the server and the game DLL. + // some MODs can be using a different hitbox scheme than the standard one. + + if (g_serverBlendingAPI == NULL) + return FALSE; + + return (*g_serverBlendingAPI) (version, ppinterface, pstudio, rotationmatrix, bonetransform); +} + +export int Meta_Query (char *ifvers, plugin_info_t **pPlugInfo, mutil_funcs_t *pMetaUtilFuncs) +{ + // this function is the first function ever called by metamod in the plugin DLL. Its purpose + // is for metamod to retrieve basic information about the plugin, such as its meta-interface + // version, for ensuring compatibility with the current version of the running metamod. + + // keep track of the pointers to metamod function tables metamod gives us + gpMetaUtilFuncs = pMetaUtilFuncs; + *pPlugInfo = &Plugin_info; + + // check for interface version compatibility + if (strcmp (ifvers, Plugin_info.ifvers) != 0) + { + int metaMajor = 0, metaMinor = 0, pluginMajor = 0, pluginMinor = 0; + + LOG_CONSOLE (PLID, "%s: meta-interface version mismatch (metamod: %s, %s: %s)", Plugin_info.name, ifvers, Plugin_info.name, Plugin_info.ifvers); + LOG_MESSAGE (PLID, "%s: meta-interface version mismatch (metamod: %s, %s: %s)", Plugin_info.name, ifvers, Plugin_info.name, Plugin_info.ifvers); + + // if plugin has later interface version, it's incompatible (update metamod) + sscanf (ifvers, "%d:%d", &metaMajor, &metaMinor); + sscanf (META_INTERFACE_VERSION, "%d:%d", &pluginMajor, &pluginMinor); + + if (pluginMajor > metaMajor || (pluginMajor == metaMajor && pluginMinor > metaMinor)) + { + LOG_CONSOLE (PLID, "metamod version is too old for this plugin; update metamod"); + LOG_MMERROR (PLID, "metamod version is too old for this plugin; update metamod"); + + return FALSE; + } + + // if plugin has older major interface version, it's incompatible (update plugin) + else if (pluginMajor < metaMajor) + { + LOG_CONSOLE (PLID, "metamod version is incompatible with this plugin; please find a newer version of this plugin"); + LOG_MMERROR (PLID, "metamod version is incompatible with this plugin; please find a newer version of this plugin"); + + return FALSE; + } + } + return TRUE; // tell metamod this plugin looks safe +} + +export int Meta_Attach (PLUG_LOADTIME now, metamod_funcs_t *functionTable, meta_globals_t *pMGlobals, gamedll_funcs_t *pGamedllFuncs) +{ + // this function is called when metamod attempts to load the plugin. Since it's the place + // where we can tell if the plugin will be allowed to run or not, we wait until here to make + // our initialization stuff, like registering CVARs and dedicated server commands. + + // are we allowed to load this plugin now ? + if (now > Plugin_info.loadable) + { + LOG_CONSOLE (PLID, "%s: plugin NOT attaching (can't load plugin right now)", Plugin_info.name); + LOG_MMERROR (PLID, "%s: plugin NOT attaching (can't load plugin right now)", Plugin_info.name); + + return FALSE; // returning false prevents metamod from attaching this plugin + } + + // keep track of the pointers to engine function tables metamod gives us + gpMetaGlobals = pMGlobals; + memcpy (functionTable, &gMetaFunctionTable, sizeof (metamod_funcs_t)); + gpGamedllFuncs = pGamedllFuncs; + + return TRUE; // returning true enables metamod to attach this plugin +} + +export int Meta_Detach (PLUG_LOADTIME now, PL_UNLOAD_REASON reason) +{ + // this function is called when metamod unloads the plugin. A basic check is made in order + // to prevent unloading the plugin if its processing should not be interrupted. + + // is metamod allowed to unload the plugin? + if (now > Plugin_info.unloadable && reason != PNL_CMD_FORCED) + { + LOG_CONSOLE (PLID, "%s: plugin not detaching (can't unload plugin right now)", Plugin_info.name); + LOG_MMERROR (PLID, "%s: plugin not detaching (can't unload plugin right now)", Plugin_info.name); + + return FALSE; // returning false prevents metamod from unloading this plugin + } + + g_botManager->RemoveAll (); // kick all bots off this server + FreeLibraryMemory (); + + return TRUE; +} + +export void Meta_Init (void) +{ + // this function is called by metamod, before any other interface functions. Purpose of this + // function to give plugin a chance to determine is plugin running under metamod or not. + + g_isMetamod = true; +} + +DLL_GIVEFNPTRSTODLL GiveFnptrsToDll (enginefuncs_t *functionTable, globalvars_t *pGlobals) +{ + // this is the very first function that is called in the game DLL by the engine. Its purpose + // is to set the functions interfacing up, by exchanging the functionTable function list + // along with a pointer to the engine's global variables structure pGlobals, with the game + // DLL. We can there decide if we want to load the normal game DLL just behind our bot DLL, + // or any other game DLL that is present, such as Will Day's metamod. Also, since there is + // a known bug on Win32 platforms that prevent hook DLLs (such as our bot DLL) to be used in + // single player games (because they don't export all the stuff they should), we may need to + // build our own array of exported symbols from the actual game DLL in order to use it as + // such if necessary. Nothing really bot-related is done in this function. The actual bot + // initialization stuff will be done later, when we'll be certain to have a multilayer game. + + // initialize random number generator + g_randGen.Initialize (time (NULL)); + + static struct ModSupport_t + { + char name[10]; + char linuxLib[12]; + char osxLib[9]; + char winLib[8]; + char desc[39]; + int modType; + } s_supportedMods[] = + { + { "cstrike", "cs_i386.so", "cs.dylib", "mp.dll", "Counter-Strike v1.6", CSV_STEAM }, + { "cstrike", "cs.so", "cs.dylib", "mp.dll", "Counter-Strike v1.6 (Newer)", CSV_STEAM }, + { "czero", "cs_i386.so", "cs.dylib", "mp.dll", "Counter-Strike: Condition Zero", CSV_CZERO }, + { "czero", "cs.so", "cs.dylib", "mp.dll", "Counter-Strike: Condition Zero (Newer)", CSV_CZERO }, + { "csv15", "cs_i386.so", "cs.dylib", "mp.dll", "CS 1.5 for Steam", CSV_OLD }, + { "cs13", "cs_i386.so", "cs.dylib", "mp.dll", "Counter-Strike v1.3", CSV_OLD }, // assume cs13 = cs15 + { "retrocs", "rcs_i386.so", "cs.dylib", "rcs.dll", "Retro Counter-Strike", CSV_OLD }, + {"", "", "", "", 0} + }; + + // get the engine functions from the engine... + memcpy (&g_engfuncs, functionTable, sizeof (enginefuncs_t)); + g_pGlobals = pGlobals; + + // register our cvars + g_convarWrapper->PushRegisteredConVarsToEngine (); + + ModSupport_t *knownMod = NULL; + char gameDLLName[256]; + + for (int i = 0; s_supportedMods[i].name; i++) + { + ModSupport_t *mod = &s_supportedMods[i]; + + if (strcmp (mod->name, GetModName ()) == 0 && TryFileOpen (FormatBuffer ("%s/dlls/%s", mod->name, +#if defined (PLATFORM_WIN32) + mod->winLib +#elif defined (PLATFORM_LINUX) + mod->linuxLib +#elif defined (PLATFORM_OSX) + mod->osxLib +#endif + ))) + { + knownMod = mod; + break; + } + } + + if (knownMod != NULL) + { + g_gameVersion = knownMod->modType; + + if (g_isMetamod) + return; // we should stop the attempt for loading the real gamedll, since metamod handle this for us + + sprintf (gameDLLName, "%s/dlls/%s", knownMod->name, + +#if defined (PLATFORM_WIN32) + knownMod->winLib +#elif defined (PLATFORM_LINUX) + knownMod->linuxLib +#elif defined (PLATFORM_OSX) + knownMod->osxLib +#endif + ); + g_gameLib = new Library (gameDLLName); + + if (!g_gameLib->IsLoaded()) + { + // try to extract the game dll out of the steam cache + AddLogEntry (true, LL_WARNING | LL_IGNORE, "Trying to extract dll '%s' out of the steam cache", gameDLLName); + + int size; + unsigned char *buffer = (*g_engfuncs.pfnLoadFileForMe) (gameDLLName, &size); + + if (buffer) + { + CreatePath (FormatBuffer ("%s/dlls", GetModName ())); + File fp (gameDLLName, "wb"); + + if (fp.IsValid ()) + { + // dump the game dll file and then close it + fp.Write (buffer, size); + fp.Close (); + } + FREE_FILE (buffer); + } + g_gameLib = new Library (gameDLLName); + } + } + else + AddLogEntry (true, LL_FATAL | LL_IGNORE, "Mod that you has started, not supported by this bot (gamedir: %s)", GetModName ()); + + g_funcPointers = (FuncPointers_t) g_gameLib->GetFunctionAddr ("GiveFnptrsToDll"); + g_entityAPI = (EntityAPI_t) g_gameLib->GetFunctionAddr ("GetEntityAPI"); + g_getNewEntityAPI = (NewEntityAPI_t) g_gameLib->GetFunctionAddr ("GetNewDLLFunctions"); + g_serverBlendingAPI = (BlendAPI_t) g_gameLib->GetFunctionAddr ("Server_GetBlendingInterface"); + + if (!g_funcPointers || !g_entityAPI) + TerminateOnMalloc (); + + GetEngineFunctions (functionTable, NULL); + + // give the engine functions to the other DLL... + (*g_funcPointers) (functionTable, pGlobals); +} + +DLL_ENTRYPOINT +{ + // dynamic library entry point, can be used for uninitialization stuff. NOT for initializing + // anything because if you ever attempt to wander outside the scope of this function on a + // DLL attach, LoadLibrary() will simply fail. And you can't do I/Os here either. + + // dynamic library detaching ?? + if (DLL_DETACHING) + { + FreeLibraryMemory (); // free everything that's freeable + + delete g_gameLib; // if dynamic link library of mod is load, free it + } + DLL_RETENTRY; // the return data type is OS specific too +} + +void ConVarWrapper::RegisterVariable (const char *variable, const char *value, VarType varType, ConVar *self) +{ + VarPair newVariable; + memset (&newVariable, 0, sizeof (VarPair)); + + newVariable.reg.name = const_cast (variable); + newVariable.reg.strval = const_cast (value); + + int engineFlags = FCVAR_EXTDLL; + + if (varType == VT_NORMAL) + engineFlags |= FCVAR_SERVER; + else if (varType == VT_READONLY) + engineFlags |= FCVAR_SERVER | FCVAR_SPONLY | FCVAR_PRINTABLEONLY; + else if (varType == VT_PASSWORD) + engineFlags |= FCVAR_PROTECTED; + + newVariable.reg.flags = engineFlags; + newVariable.self = self; + newVariable.type = varType; + + memcpy (&m_regVars[m_regCount], &newVariable, sizeof (VarPair)); + m_regCount++; +} + + +void ConVarWrapper::PushRegisteredConVarsToEngine (bool gameVars) +{ + for (int i = 0; i < m_regCount; i++) + { + VarPair *ptr = &m_regVars[i]; + + if (ptr == NULL) + break; + + if (ptr->type != VT_NOREGISTER) + { + ptr->self->m_eptr = g_engfuncs.pfnCVarGetPointer (ptr->reg.name); + + if (ptr->self->m_eptr == NULL) + { + g_engfuncs.pfnCVarRegister (&ptr->reg); + ptr->self->m_eptr = g_engfuncs.pfnCVarGetPointer (ptr->reg.name); + } + } + else if (gameVars && ptr->type == VT_NOREGISTER) + ptr->self->m_eptr = g_engfuncs.pfnCVarGetPointer (ptr->reg.name); + } +} + +#define LINK_ENTITY(entityFunction) \ +export void entityFunction (entvars_t *pev) \ +{ \ + static EntityPtr_t funcPtr = NULL; \ + \ + if (funcPtr == NULL) \ + funcPtr = (EntityPtr_t) g_gameLib->GetFunctionAddr (#entityFunction); \ + \ + if (funcPtr == NULL) \ + return; \ + \ + (*funcPtr) (pev); \ +} \ + +// entities in counter-strike... +LINK_ENTITY (DelayedUse); +LINK_ENTITY (ambient_generic); +LINK_ENTITY (ammo_338magnum); +LINK_ENTITY (ammo_357sig); +LINK_ENTITY (ammo_45acp); +LINK_ENTITY (ammo_50ae); +LINK_ENTITY (ammo_556nato); +LINK_ENTITY (ammo_556natobox); +LINK_ENTITY (ammo_57mm); +LINK_ENTITY (ammo_762nato); +LINK_ENTITY (ammo_9mm); +LINK_ENTITY (ammo_buckshot); +LINK_ENTITY (armoury_entity); +LINK_ENTITY (beam); +LINK_ENTITY (bodyque); +LINK_ENTITY (button_target); +LINK_ENTITY (cycler); +LINK_ENTITY (cycler_prdroid); +LINK_ENTITY (cycler_sprite); +LINK_ENTITY (cycler_weapon); +LINK_ENTITY (cycler_wreckage); +LINK_ENTITY (env_beam); +LINK_ENTITY (env_beverage); +LINK_ENTITY (env_blood); +LINK_ENTITY (env_bombglow); +LINK_ENTITY (env_bubbles); +LINK_ENTITY (env_debris); +LINK_ENTITY (env_explosion); +LINK_ENTITY (env_fade); +LINK_ENTITY (env_funnel); +LINK_ENTITY (env_global); +LINK_ENTITY (env_glow); +LINK_ENTITY (env_laser); +LINK_ENTITY (env_lightning); +LINK_ENTITY (env_message); +LINK_ENTITY (env_rain); +LINK_ENTITY (env_render); +LINK_ENTITY (env_shake); +LINK_ENTITY (env_shooter); +LINK_ENTITY (env_snow); +LINK_ENTITY (env_sound); +LINK_ENTITY (env_spark); +LINK_ENTITY (env_sprite); +LINK_ENTITY (fireanddie); +LINK_ENTITY (func_bomb_target); +LINK_ENTITY (func_breakable); +LINK_ENTITY (func_button); +LINK_ENTITY (func_buyzone); +LINK_ENTITY (func_conveyor); +LINK_ENTITY (func_door); +LINK_ENTITY (func_door_rotating); +LINK_ENTITY (func_escapezone); +LINK_ENTITY (func_friction); +LINK_ENTITY (func_grencatch); +LINK_ENTITY (func_guntarget); +LINK_ENTITY (func_healthcharger); +LINK_ENTITY (func_hostage_rescue); +LINK_ENTITY (func_illusionary); +LINK_ENTITY (func_ladder); +LINK_ENTITY (func_monsterclip); +LINK_ENTITY (func_mortar_field); +LINK_ENTITY (func_pendulum); +LINK_ENTITY (func_plat); +LINK_ENTITY (func_platrot); +LINK_ENTITY (func_pushable); +LINK_ENTITY (func_rain); +LINK_ENTITY (func_recharge); +LINK_ENTITY (func_rot_button); +LINK_ENTITY (func_rotating); +LINK_ENTITY (func_snow); +LINK_ENTITY (func_tank); +LINK_ENTITY (func_tankcontrols); +LINK_ENTITY (func_tanklaser); +LINK_ENTITY (func_tankmortar); +LINK_ENTITY (func_tankrocket); +LINK_ENTITY (func_trackautochange); +LINK_ENTITY (func_trackchange); +LINK_ENTITY (func_tracktrain); +LINK_ENTITY (func_train); +LINK_ENTITY (func_traincontrols); +LINK_ENTITY (func_vehicle); +LINK_ENTITY (func_vehiclecontrols); +LINK_ENTITY (func_vip_safetyzone); +LINK_ENTITY (func_wall); +LINK_ENTITY (func_wall_toggle); +LINK_ENTITY (func_water); +LINK_ENTITY (func_weaponcheck); +LINK_ENTITY (game_counter); +LINK_ENTITY (game_counter_set); +LINK_ENTITY (game_end); +LINK_ENTITY (game_player_equip); +LINK_ENTITY (game_player_hurt); +LINK_ENTITY (game_player_team); +LINK_ENTITY (game_score); +LINK_ENTITY (game_team_master); +LINK_ENTITY (game_team_set); +LINK_ENTITY (game_text); +LINK_ENTITY (game_zone_player); +LINK_ENTITY (gibshooter); +LINK_ENTITY (grenade); +LINK_ENTITY (hostage_entity); +LINK_ENTITY (info_bomb_target); +LINK_ENTITY (info_hostage_rescue); +LINK_ENTITY (info_intermission); +LINK_ENTITY (info_landmark); +LINK_ENTITY (info_map_parameters); +LINK_ENTITY (info_null); +LINK_ENTITY (info_player_deathmatch); +LINK_ENTITY (info_player_start); +LINK_ENTITY (info_target); +LINK_ENTITY (info_teleport_destination); +LINK_ENTITY (info_vip_start); +LINK_ENTITY (infodecal); +LINK_ENTITY (item_airtank); +LINK_ENTITY (item_antidote); +LINK_ENTITY (item_assaultsuit); +LINK_ENTITY (item_battery); +LINK_ENTITY (item_healthkit); +LINK_ENTITY (item_kevlar); +LINK_ENTITY (item_longjump); +LINK_ENTITY (item_security); +LINK_ENTITY (item_sodacan); +LINK_ENTITY (item_suit); +LINK_ENTITY (item_thighpack); +LINK_ENTITY (light); +LINK_ENTITY (light_environment); +LINK_ENTITY (light_spot); +LINK_ENTITY (momentary_door); +LINK_ENTITY (momentary_rot_button); +LINK_ENTITY (monster_hevsuit_dead); +LINK_ENTITY (monster_mortar); +LINK_ENTITY (monster_scientist); +LINK_ENTITY (multi_manager); +LINK_ENTITY (multisource); +LINK_ENTITY (path_corner); +LINK_ENTITY (path_track); +LINK_ENTITY (player); +LINK_ENTITY (player_loadsaved); +LINK_ENTITY (player_weaponstrip); +LINK_ENTITY (soundent); +LINK_ENTITY (spark_shower); +LINK_ENTITY (speaker); +LINK_ENTITY (target_cdaudio); +LINK_ENTITY (test_effect); +LINK_ENTITY (trigger); +LINK_ENTITY (trigger_auto); +LINK_ENTITY (trigger_autosave); +LINK_ENTITY (trigger_camera); +LINK_ENTITY (trigger_cdaudio); +LINK_ENTITY (trigger_changelevel); +LINK_ENTITY (trigger_changetarget); +LINK_ENTITY (trigger_counter); +LINK_ENTITY (trigger_endsection); +LINK_ENTITY (trigger_gravity); +LINK_ENTITY (trigger_hurt); +LINK_ENTITY (trigger_monsterjump); +LINK_ENTITY (trigger_multiple); +LINK_ENTITY (trigger_once); +LINK_ENTITY (trigger_push); +LINK_ENTITY (trigger_relay); +LINK_ENTITY (trigger_teleport); +LINK_ENTITY (trigger_transition); +LINK_ENTITY (weapon_ak47); +LINK_ENTITY (weapon_aug); +LINK_ENTITY (weapon_awp); +LINK_ENTITY (weapon_c4); +LINK_ENTITY (weapon_deagle); +LINK_ENTITY (weapon_elite); +LINK_ENTITY (weapon_famas); +LINK_ENTITY (weapon_fiveseven); +LINK_ENTITY (weapon_flashbang); +LINK_ENTITY (weapon_g3sg1); +LINK_ENTITY (weapon_galil); +LINK_ENTITY (weapon_glock18); +LINK_ENTITY (weapon_hegrenade); +LINK_ENTITY (weapon_knife); +LINK_ENTITY (weapon_m249); +LINK_ENTITY (weapon_m3); +LINK_ENTITY (weapon_m4a1); +LINK_ENTITY (weapon_mac10); +LINK_ENTITY (weapon_mp5navy); +LINK_ENTITY (weapon_p228); +LINK_ENTITY (weapon_p90); +LINK_ENTITY (weapon_scout); +LINK_ENTITY (weapon_sg550); +LINK_ENTITY (weapon_sg552); +LINK_ENTITY (weapon_shield); +LINK_ENTITY (weapon_shieldgun); +LINK_ENTITY (weapon_smokegrenade); +LINK_ENTITY (weapon_tmp); +LINK_ENTITY (weapon_ump45); +LINK_ENTITY (weapon_usp); +LINK_ENTITY (weapon_xm1014); +LINK_ENTITY (weaponbox); +LINK_ENTITY (world_items); +LINK_ENTITY (worldspawn); diff --git a/source/navigate.cpp b/source/navigate.cpp new file mode 100644 index 0000000..583e08b --- /dev/null +++ b/source/navigate.cpp @@ -0,0 +1,2936 @@ +// +// Copyright (c) 2014, by YaPB Development Team. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// Version: $Id:$ +// + +#include + +ConVar yb_danger_factor ("yb_danger_factor", "800"); + +ConVar yb_aim_method ("yb_aim_method", "3"); + +ConVar yb_aim_damper_coefficient_x ("yb_aim_damper_coefficient_x", "0.22"); +ConVar yb_aim_damper_coefficient_y ("yb_aim_damper_coefficient_y", "0.22"); + +ConVar yb_aim_deviation_x ("yb_aim_deviation_x", "2.0"); +ConVar yb_aim_deviation_y ("yb_aim_deviation_y", "1.0"); + +ConVar yb_aim_influence_x_on_y ("yb_aim_influence_x_on_y", "0.25"); +ConVar yb_aim_influence_y_on_x ("yb_aim_influence_y_on_x", "0.17"); + +ConVar yb_aim_notarget_slowdown_ratio ("yb_aim_notarget_slowdown_ratio", "0.5"); +ConVar yb_aim_offset_delay ("yb_aim_offset_delay", "1.2"); + +ConVar yb_aim_spring_stiffness_x ("yb_aim_spring_stiffness_x", "13.0"); +ConVar yb_aim_spring_stiffness_y ("yb_aim_spring_stiffness_y", "13.0"); + +ConVar yb_aim_target_anticipation_ratio ("yb_aim_target_anticipation_ratio", "3.0"); + +int Bot::FindGoal (void) +{ + // chooses a destination (goal) waypoint for a bot + int team = GetTeam (GetEntity ()); + + if (team == TEAM_TF && (g_mapType & MAP_DE)) + { + edict_t *pent = NULL; + + while (!FNullEnt (pent = FIND_ENTITY_BY_STRING (pent, "classname", "weaponbox"))) + { + if (strcmp (STRING (pent->v.model), "models/w_backpack.mdl") == 0) + { + int index = g_waypoint->FindNearest (GetEntityOrigin (pent)); + + if (index >= 0 && index < g_numWaypoints) + return index; + + break; + } + } + } + + // path finding behaviour depending on map type + int tactic; + int offensive; + int defensive; + int goalDesire; + + int forwardDesire; + int campDesire; + int backoffDesire; + int tacticChoice; + + Array offensiveWpts; + Array defensiveWpts; + + switch (team) + { + case TEAM_TF: + offensiveWpts = g_waypoint->m_ctPoints; + defensiveWpts = g_waypoint->m_terrorPoints; + break; + + case TEAM_CF: + offensiveWpts = g_waypoint->m_terrorPoints; + defensiveWpts = g_waypoint->m_ctPoints; + break; + } + + // terrorist carrying the C4? + if (pev->weapons & (1 << WEAPON_C4) || m_isVIP) + { + tactic = 3; + goto TacticChoosen; + } + else if (HasHostage () && team == TEAM_CF) + { + tactic = 2; + offensiveWpts = g_waypoint->m_rescuePoints; + + goto TacticChoosen; + } + + offensive = static_cast (m_agressionLevel * 100); + defensive = static_cast (m_fearLevel * 100); + + if (g_mapType & (MAP_AS | MAP_CS)) + { + if (team == TEAM_TF) + { + defensive += 25.0f; + offensive -= 25.0f; + } + else if (team == TEAM_CF) + { + defensive -= 25.0f; + offensive += 25.0f; + } + } + else if ((g_mapType & MAP_DE) && team == TEAM_CF) + { + if (g_bombPlanted && GetTaskId () != TASK_ESCAPEFROMBOMB && g_waypoint->GetBombPosition () != nullvec) + { + if (g_bombSayString) + { + ChatMessage (CHAT_BOMBPLANT); + g_bombSayString = false; + } + return m_chosenGoalIndex = ChooseBombWaypoint (); + } + defensive += 40.0f; + offensive -= 25.0f; + } + else if ((g_mapType & MAP_DE) && team == TEAM_TF) + { + // send some terrorists to guard planter bomb + if (g_bombPlanted && GetTaskId () != TASK_ESCAPEFROMBOMB && GetBombTimeleft () >= 15.0) + return m_chosenGoalIndex = FindDefendWaypoint (g_waypoint->GetBombPosition ()); + + float leastPathDistance = 0.0; + int goalIndex = -1; + + IterateArray (g_waypoint->m_goalPoints, i) + { + float realPathDistance = g_waypoint->GetPathDistance (m_currentWaypointIndex, g_waypoint->m_goalPoints[i]) + g_randGen.Float (0.0, 128.0); + + if (leastPathDistance > realPathDistance) + { + goalIndex = g_waypoint->m_goalPoints[i]; + leastPathDistance = realPathDistance; + } + } + + if (goalIndex != -1 && !g_bombPlanted && (pev->weapons & (1 << WEAPON_C4))) + return m_chosenGoalIndex = goalIndex; + } + + goalDesire = g_randGen.Long (0, 100) + offensive; + forwardDesire = g_randGen.Long (0, 100) + offensive; + campDesire = g_randGen.Long (0, 85) + defensive; + backoffDesire = g_randGen.Long (0, 100) + defensive; + + tacticChoice = backoffDesire; + tactic = 0; + + if (campDesire > tacticChoice) + { + tacticChoice = campDesire; + tactic = 1; + } + if (forwardDesire > tacticChoice) + { + tacticChoice = forwardDesire; + tactic = 2; + } + if (goalDesire > tacticChoice) + tactic = 3; + +TacticChoosen: + int goalChoices[4] = {-1, -1, -1, -1}; + + if (tactic == 0 && !defensiveWpts.IsEmpty ()) // careful goal + { + for (int i = 0; i < 4; i++) + { + goalChoices[i] = defensiveWpts.GetRandomElement (); + InternalAssert (goalChoices[i] >= 0 && goalChoices[i] < g_numWaypoints); + } + } + else if (tactic == 1 && !g_waypoint->m_campPoints.IsEmpty ()) // camp waypoint goal + { + // pickup sniper points if possible for sniping bots + if (!g_waypoint->m_sniperPoints.IsEmpty () && ((pev->weapons & (1 << WEAPON_AWP)) || (pev->weapons & (1 << WEAPON_SCOUT)) || (pev->weapons & (1 << WEAPON_G3SG1)) || (pev->weapons & (1 << WEAPON_SG550)))) + { + for (int i = 0; i < 4; i++) + { + goalChoices[i] = g_waypoint->m_sniperPoints.GetRandomElement (); + InternalAssert ((goalChoices[i] >= 0) && (goalChoices[i] < g_numWaypoints)); + } + } + else + { + for (int i = 0; i < 4; i++) + { + goalChoices[i] = g_waypoint->m_campPoints.GetRandomElement (); + InternalAssert ((goalChoices[i] >= 0) && (goalChoices[i] < g_numWaypoints)); + } + } + } + else if (tactic == 2 && !offensiveWpts.IsEmpty ()) // offensive goal + { + for (int i = 0; i < 4; i++) + { + goalChoices[i] = offensiveWpts.GetRandomElement (); + InternalAssert ((goalChoices[i] >= 0) && (goalChoices[i] < g_numWaypoints)); + } + } + else if (tactic == 3 && !g_waypoint->m_goalPoints.IsEmpty ()) // map goal waypoint + { + for (int i = 0; i < 4; i++) + { + goalChoices[i] = g_waypoint->m_goalPoints.GetRandomElement (); + InternalAssert ((goalChoices[i] >= 0) && (goalChoices[i] < g_numWaypoints)); + } + } + + if (m_currentWaypointIndex == -1 || m_currentWaypointIndex >= g_numWaypoints) + ChangeWptIndex (g_waypoint->FindNearest (pev->origin)); + + if (goalChoices[0] == -1) + return m_chosenGoalIndex = g_randGen.Long (0, g_numWaypoints - 1); + + bool isSorting = false; + + do + { + isSorting = false; + + for (int i = 0; i < 3; i++) + { + int testIndex = goalChoices[i + 1]; + + if (testIndex < 0) + break; + + if (team == TEAM_TF) + { + if ((g_experienceData + (m_currentWaypointIndex * g_numWaypoints) + goalChoices[i])->team0Value < (g_experienceData + (m_currentWaypointIndex * g_numWaypoints) + goalChoices[i + 1])->team0Value) + { + goalChoices[i + 1] = goalChoices[i]; + goalChoices[i] = testIndex; + + isSorting = true; + } + } + else + { + if ((g_experienceData + (m_currentWaypointIndex * g_numWaypoints) + goalChoices[i])->team1Value < (g_experienceData + (m_currentWaypointIndex * g_numWaypoints) + goalChoices[i + 1])->team1Value) + { + goalChoices[i + 1] = goalChoices[i]; + goalChoices[i] = testIndex; + + isSorting = true; + } + } + } + } while (isSorting); + + // return and store goal + return m_chosenGoalIndex = goalChoices[0]; +} + +bool Bot::GoalIsValid (void) +{ + int goal = GetTask ()->data; + + if (goal == -1) // not decided about a goal + return false; + else if (goal == m_currentWaypointIndex) // no nodes needed + return true; + else if (m_navNode == NULL) // no path calculated + return false; + + // got path - check if still valid + PathNode *node = m_navNode; + + while (node->next != NULL) + node = node->next; + + if (node->index == goal) + return true; + + return false; +} + +bool Bot::DoWaypointNav (void) +{ + // this function is a main path navigation + + TraceResult tr, tr2; + + // check if we need to find a waypoint... + if (m_currentWaypointIndex == -1) + { + GetValidWaypoint (); + m_waypointOrigin = m_currentPath->origin; + + // if wayzone radios non zero vary origin a bit depending on the body angles + if (m_currentPath->radius > 0) + { + MakeVectors (Vector (pev->angles.x, AngleNormalize (pev->angles.y + g_randGen.Float (-90, 90)), 0.0)); + m_waypointOrigin = m_waypointOrigin + g_pGlobals->v_forward * g_randGen.Float (0, m_currentPath->radius); + } + m_navTimeset = GetWorldTime (); + } + + if (pev->flags & FL_DUCKING) + m_destOrigin = m_waypointOrigin; + else + m_destOrigin = m_waypointOrigin + pev->view_ofs; + + float waypointDistance = (pev->origin - m_waypointOrigin).GetLength (); + + // this waypoint has additional travel flags - care about them + if (m_currentTravelFlags & PATHFLAG_JUMP) + { + // bot is not jumped yet? + if (!m_jumpFinished) + { + // if bot's on the ground or on the ladder we're free to jump. actually setting the correct velocity is cheating. + // pressing the jump button gives the illusion of the bot actual jmping. + if (IsOnFloor () || IsOnLadder ()) + { + pev->velocity = m_desiredVelocity; + pev->button |= IN_JUMP; + + m_jumpFinished = true; + m_checkTerrain = false; + m_desiredVelocity = nullvec; + } + } + else if (!yb_jasonmode.GetBool () && m_currentWeapon == WEAPON_KNIFE && IsOnFloor ()) + SelectBestWeapon (); + } + + if (m_currentPath->flags & FLAG_LADDER) + { + if (m_waypointOrigin.z >= (pev->origin.z + 16.0)) + m_waypointOrigin = m_currentPath->origin + Vector (0, 0, 16); + else if (m_waypointOrigin.z < pev->origin.z + 16.0 && !IsOnLadder () && IsOnFloor () && !(pev->flags & FL_DUCKING)) + { + m_moveSpeed = waypointDistance; + + if (m_moveSpeed < 150.0) + m_moveSpeed = 150.0; + else if (m_moveSpeed > pev->maxspeed) + m_moveSpeed = pev->maxspeed; + } + } + + // special lift handling (code merged from podbotmm) + if (m_currentPath->flags & FLAG_LIFT) + { + bool liftClosedDoorExists = false; + + // update waypoint time set + m_navTimeset = GetWorldTime (); + + // trace line to door + TraceLine (pev->origin, m_currentPath->origin, true, true, GetEntity (), &tr2); + + if (tr2.flFraction < 1.0 && strcmp (STRING (tr2.pHit->v.classname), "func_door") == 0 && (m_liftState == LIFT_NO_NEARBY || m_liftState == LIFT_WAITING_FOR || m_liftState == LIFT_LOOKING_BUTTON_OUTSIDE) && pev->groundentity != tr2.pHit) + { + if (m_liftState == LIFT_NO_NEARBY) + { + m_liftState = LIFT_LOOKING_BUTTON_OUTSIDE; + m_liftUsageTime = GetWorldTime () + 7.0; + } + liftClosedDoorExists = true; + } + + // trace line down + TraceLine (m_currentPath->origin, m_currentPath->origin + Vector (0, 0, -50), true, true, GetEntity (), &tr); + + // if trace result shows us that it is a lift + if (!FNullEnt (tr.pHit) && m_navNode != NULL && (strcmp (STRING (tr.pHit->v.classname), "func_door") == 0 || strcmp (STRING (tr.pHit->v.classname), "func_plat") == 0 || strcmp (STRING (tr.pHit->v.classname), "func_train") == 0) && !liftClosedDoorExists) + { + if ((m_liftState == LIFT_NO_NEARBY || m_liftState == LIFT_WAITING_FOR || m_liftState == LIFT_LOOKING_BUTTON_OUTSIDE) && tr.pHit->v.velocity.z == 0) + { + if (fabsf (pev->origin.z - tr.vecEndPos.z) < 70.0) + { + m_liftEntity = tr.pHit; + m_liftState = LIFT_ENTERING_IN; + m_liftTravelPos = m_currentPath->origin; + m_liftUsageTime = GetWorldTime () + 5.0; + } + } + else if (m_liftState == LIFT_TRAVELING_BY) + { + m_liftState = LIFT_LEAVING; + m_liftUsageTime = GetWorldTime () + 7.0; + } + } + else if (m_navNode != NULL) // no lift found at waypoint + { + if ((m_liftState == LIFT_NO_NEARBY || m_liftState == LIFT_WAITING_FOR) && m_navNode->next != NULL) + { + if (m_navNode->next->index >= 0 && m_navNode->next->index < g_numWaypoints && (g_waypoint->GetPath (m_navNode->next->index)->flags & FLAG_LIFT)) + { + TraceLine (m_currentPath->origin, g_waypoint->GetPath (m_navNode->next->index)->origin, true, true, GetEntity (), &tr); + + if (!FNullEnt (tr.pHit) && (strcmp (STRING (tr.pHit->v.classname), "func_door") == 0 || strcmp (STRING (tr.pHit->v.classname), "func_plat") == 0 || strcmp (STRING (tr.pHit->v.classname), "func_train") == 0)) + m_liftEntity = tr.pHit; + } + m_liftState = LIFT_LOOKING_BUTTON_OUTSIDE; + m_liftUsageTime = GetWorldTime () + 15.0; + } + } + + // bot is going to enter the lift + if (m_liftState == LIFT_ENTERING_IN) + { + m_destOrigin = m_liftTravelPos; + + // check if we enough to destination + if ((pev->origin - m_destOrigin).GetLengthSquared () < 225) + { + m_moveSpeed = 0.0; + m_strafeSpeed = 0.0; + + // need to wait our following teammate ? + bool needWaitForTeammate = false; + + // if some bot is following a bot going into lift - he should take the same lift to go + for (int i = 0; i < GetMaxClients (); i++) + { + Bot *bot = g_botManager->GetBot (i); + + if (bot == NULL || bot == this) + continue; + + if (!IsAlive (bot->GetEntity ()) || GetTeam (bot->GetEntity ()) != GetTeam (GetEntity ()) || bot->m_targetEntity != GetEntity () || bot->GetTaskId () != TASK_FOLLOWUSER) + continue; + + if (bot->pev->groundentity == m_liftEntity && bot->IsOnFloor ()) + break; + + bot->m_liftEntity = m_liftEntity; + bot->m_liftState = LIFT_ENTERING_IN; + bot->m_liftTravelPos = m_liftTravelPos; + + needWaitForTeammate = true; + } + + if (needWaitForTeammate) + { + m_liftState = LIFT_WAIT_FOR_TEAMMATES; + m_liftUsageTime = GetWorldTime () + 8.0; + } + else + { + m_liftState = LIFT_LOOKING_BUTTON_INSIDE; + m_liftUsageTime = GetWorldTime () + 10.0; + } + } + } + + // bot is waiting for his teammates + if (m_liftState == LIFT_WAIT_FOR_TEAMMATES) + { + // need to wait our following teammate ? + bool needWaitForTeammate = false; + + for (int i = 0; i < GetMaxClients (); i++) + { + Bot *bot = g_botManager->GetBot (i); + + if (bot == NULL) + continue; // skip invalid bots + + if (!IsAlive (bot->GetEntity ()) || GetTeam (bot->GetEntity ()) != GetTeam (GetEntity ()) || bot->m_targetEntity != GetEntity () || bot->GetTaskId () != TASK_FOLLOWUSER || bot->m_liftEntity != m_liftEntity) + continue; + + if (bot->pev->groundentity == m_liftEntity || !bot->IsOnFloor ()) + { + needWaitForTeammate = true; + break; + } + } + + // need to wait for teammate + if (needWaitForTeammate) + { + m_destOrigin = m_liftTravelPos; + + if ((pev->origin - m_destOrigin).GetLengthSquared () < 225) + { + m_moveSpeed = 0.0; + m_strafeSpeed = 0.0; + } + } + + // else we need to look for button + if (!needWaitForTeammate || (m_liftUsageTime < GetWorldTime ())) + { + m_liftState = LIFT_LOOKING_BUTTON_INSIDE; + m_liftUsageTime = GetWorldTime () + 10.0; + } + } + + // bot is trying to find button inside a lift + if (m_liftState == LIFT_LOOKING_BUTTON_INSIDE) + { + edict_t *button = FindNearestButton (STRING (m_liftEntity->v.targetname)); + + // got a valid button entity ? + if (!FNullEnt (button) && pev->groundentity == m_liftEntity && m_buttonPushTime + 1.0 < GetWorldTime () && m_liftEntity->v.velocity.z == 0.0 && IsOnFloor ()) + { + m_pickupItem = button; + m_pickupType = PICKUP_BUTTON; + + m_navTimeset = GetWorldTime (); + } + } + + // is lift activated and bot is standing on it and lift is moving ? + if (m_liftState == LIFT_LOOKING_BUTTON_INSIDE || m_liftState == LIFT_ENTERING_IN || m_liftState == LIFT_WAIT_FOR_TEAMMATES || m_liftState == LIFT_WAITING_FOR) + { + if (pev->groundentity == m_liftEntity && m_liftEntity->v.velocity.z != 0 && IsOnFloor () && ((g_waypoint->GetPath (m_prevWptIndex[0])->flags & FLAG_LIFT) || !FNullEnt (m_targetEntity))) + { + m_liftState = LIFT_TRAVELING_BY; + m_liftUsageTime = GetWorldTime () + 14.0; + + if ((pev->origin - m_destOrigin).GetLengthSquared () < 225) + { + m_moveSpeed = 0.0; + m_strafeSpeed = 0.0; + } + } + } + + // bots is currently moving on lift + if (m_liftState == LIFT_TRAVELING_BY) + { + m_destOrigin = Vector (m_liftTravelPos.x, m_liftTravelPos.y, pev->origin.z); + + if ((pev->origin - m_destOrigin).GetLengthSquared () < 225) + { + m_moveSpeed = 0.0; + m_strafeSpeed = 0.0; + } + } + + // need to find a button outside the lift + if (m_liftState == LIFT_LOOKING_BUTTON_OUTSIDE) + { + // lift is already used ? + bool liftUsed = false; + + // button has been pressed, lift should come + if (m_buttonPushTime + 8.0 >= GetWorldTime ()) + { + if ((m_prevWptIndex[0] >= 0) && (m_prevWptIndex[0] < g_numWaypoints)) + m_destOrigin = g_waypoint->GetPath (m_prevWptIndex[0])->origin; + else + m_destOrigin = pev->origin; + + if ((pev->origin - m_destOrigin).GetLengthSquared () < 225) + { + m_moveSpeed = 0.0; + m_strafeSpeed = 0.0; + } + } + else + { + edict_t *button = FindNearestButton (STRING (m_liftEntity->v.targetname)); + + // if we got a valid button entity + if (!FNullEnt (button)) + { + // iterate though clients, and find if lift already used + for (int i = 0; i < GetMaxClients (); i++) + { + if (!(g_clients[i].flags & CF_USED) || !(g_clients[i].flags & CF_ALIVE) || g_clients[i].team != GetTeam (GetEntity ()) || g_clients[i].ent == GetEntity () || FNullEnt (g_clients[i].ent->v.groundentity)) + continue; + + if (g_clients[i].ent->v.groundentity == m_liftEntity) + { + liftUsed = true; + break; + } + } + + // lift is currently used + if (liftUsed) + { + if (m_prevWptIndex[0] >= 0 && m_prevWptIndex[0] < g_numWaypoints) + m_destOrigin = g_waypoint->GetPath (m_prevWptIndex[0])->origin; + else + m_destOrigin = button->v.origin; + + if ((pev->origin - m_destOrigin).GetLengthSquared () < 225) + { + m_moveSpeed = 0.0; + m_strafeSpeed = 0.0; + } + } + else + { + m_pickupItem = button; + m_pickupType = PICKUP_BUTTON; + m_liftState = LIFT_WAITING_FOR; + + m_navTimeset = GetWorldTime (); + m_liftUsageTime = GetWorldTime () + 20.0; + } + } + else + { + m_liftState = LIFT_WAITING_FOR; + m_liftUsageTime = GetWorldTime () + 15.0; + } + } + } + + // bot is waiting for lift + if (m_liftState == LIFT_WAITING_FOR) + { + if ((m_prevWptIndex[0] >= 0) && (m_prevWptIndex[0] < g_numWaypoints)) + { + if (!(g_waypoint->GetPath (m_prevWptIndex[0])->flags & FLAG_LIFT)) + m_destOrigin = g_waypoint->GetPath (m_prevWptIndex[0])->origin; + else if ((m_prevWptIndex[1] >= 0) && (m_prevWptIndex[0] < g_numWaypoints)) + m_destOrigin = g_waypoint->GetPath (m_prevWptIndex[1])->origin; + } + + if ((pev->origin - m_destOrigin).GetLengthSquared () < 100) + { + m_moveSpeed = 0.0; + m_strafeSpeed = 0.0; + } + } + + // if bot is waiting for lift, or going to it + if (m_liftState == LIFT_WAITING_FOR || m_liftState == LIFT_ENTERING_IN) + { + // bot fall down somewhere inside the lift's groove :) + if (pev->groundentity != m_liftEntity && m_prevWptIndex[0] >= 0 && m_prevWptIndex[0] < g_numWaypoints) + { + if ((g_waypoint->GetPath (m_prevWptIndex[0])->flags & FLAG_LIFT) && (m_currentPath->origin.z - pev->origin.z) > 50.0 && (g_waypoint->GetPath (m_prevWptIndex[0])->origin.z - pev->origin.z) > 50.0) + { + m_liftState = LIFT_NO_NEARBY; + m_liftEntity = NULL; + m_liftUsageTime = 0.0; + + DeleteSearchNodes (); + FindWaypoint (); + + if (m_prevWptIndex[2] >= 0 && m_prevWptIndex[2] < g_numWaypoints) + FindShortestPath (m_currentWaypointIndex, m_prevWptIndex[2]); + + return false; + } + } + } + } + + if (!FNullEnt (m_liftEntity) && !(m_currentPath->flags & FLAG_LIFT)) + { + if (m_liftState == LIFT_TRAVELING_BY) + { + m_liftState = LIFT_LEAVING; + m_liftUsageTime = GetWorldTime () + 10.0; + } + if (m_liftState == LIFT_LEAVING && m_liftUsageTime < GetWorldTime () && pev->groundentity != m_liftEntity) + { + m_liftState = LIFT_NO_NEARBY; + m_liftUsageTime = 0.0; + + m_liftEntity = NULL; + } + } + + if (m_liftUsageTime < GetWorldTime () && m_liftUsageTime != 0.0) + { + m_liftEntity = NULL; + m_liftState = LIFT_NO_NEARBY; + m_liftUsageTime = 0.0; + + DeleteSearchNodes (); + + if (m_prevWptIndex[0] >= 0 && m_prevWptIndex[0] < g_numWaypoints) + { + if (!(g_waypoint->GetPath (m_prevWptIndex[0])->flags & FLAG_LIFT)) + ChangeWptIndex (m_prevWptIndex[0]); + else + FindWaypoint (); + } + else + FindWaypoint (); + + return false; + } + + // check if we are going through a door... + TraceLine (pev->origin, m_waypointOrigin, true, GetEntity (), &tr); + + if (!FNullEnt (tr.pHit) && FNullEnt (m_liftEntity) && strncmp (STRING (tr.pHit->v.classname), "func_door", 9) == 0) + { + // if the door is near enough... + if ((GetEntityOrigin (tr.pHit) - pev->origin).GetLengthSquared () < 2500) + { + m_lastCollTime = GetWorldTime () + 0.5; // don't consider being stuck + + if (g_randGen.Long (1, 100) < 50) + MDLL_Use (tr.pHit, GetEntity ()); // also 'use' the door randomly + } + + // make sure we are always facing the door when going through it + m_aimFlags &= ~(AIM_LAST_ENEMY | AIM_PREDICT_ENEMY); + + edict_t *button = FindNearestButton (STRING (tr.pHit->v.targetname)); + + // check if we got valid button + if (!FNullEnt (button)) + { + m_pickupItem = button; + m_pickupType = PICKUP_BUTTON; + + m_navTimeset = GetWorldTime (); + } + + // if bot hits the door, then it opens, so wait a bit to let it open safely + if (pev->velocity.GetLength2D () < 2 && m_timeDoorOpen < GetWorldTime ()) + { + StartTask (TASK_PAUSE, TASKPRI_PAUSE, -1, GetWorldTime () + 1, false); + + m_doorOpenAttempt++; + m_timeDoorOpen = GetWorldTime () + 1.0; // retry in 1 sec until door is open + + edict_t *ent = NULL; + + if (m_doorOpenAttempt > 2 && !FNullEnt (ent = FIND_ENTITY_IN_SPHERE (ent, pev->origin, 100))) + { + if (IsValidPlayer (ent) && IsAlive (ent) && GetTeam (GetEntity ()) != GetTeam (ent) && IsWeaponShootingThroughWall (m_currentWeapon)) + { + m_seeEnemyTime = GetWorldTime (); + + m_states |= STATE_SUSPECT_ENEMY; + m_aimFlags |= AIM_LAST_ENEMY; + + m_lastEnemy = ent; + m_enemy = ent; + m_lastEnemyOrigin = ent->v.origin; + + } + else if (IsValidPlayer (ent) && IsAlive (ent) && GetTeam (GetEntity ()) == GetTeam (ent)) + { + DeleteSearchNodes (); + ResetTasks (); + } + else if (IsValidPlayer (ent) && (!IsAlive (ent) || (ent->v.deadflag & DEAD_DYING))) + m_doorOpenAttempt = 0; // reset count + } + } + } + + float desiredDistance = 0.0; + + // initialize the radius for a special waypoint type, where the wpt is considered to be reached + if (m_currentPath->flags & FLAG_LIFT) + desiredDistance = 50; + else if ((pev->flags & FL_DUCKING) || (m_currentPath->flags & FLAG_GOAL)) + desiredDistance = 25; + else if (m_currentPath->flags & FLAG_LADDER) + desiredDistance = 15; + else if (m_currentTravelFlags & PATHFLAG_JUMP) + desiredDistance = 0.0; + else + desiredDistance = m_currentPath->radius; + + // check if waypoint has a special travelflag, so they need to be reached more precisely + for (int i = 0; i < MAX_PATH_INDEX; i++) + { + if (m_currentPath->connectionFlags[i] != 0) + { + desiredDistance = 0; + break; + } + } + + // needs precise placement - check if we get past the point + if (desiredDistance < 16.0 && waypointDistance < 30) + { + Vector nextFrameOrigin = pev->origin + (pev->velocity * m_frameInterval); + + if ((nextFrameOrigin - m_waypointOrigin).GetLength () > waypointDistance) + desiredDistance = waypointDistance + 1.0; + } + + if (waypointDistance < desiredDistance) + { + // Did we reach a destination Waypoint? + if (GetTask ()->data == m_currentWaypointIndex) + { + // add goal values + if (m_chosenGoalIndex != -1) + { + int waypointValue; + int startIndex = m_chosenGoalIndex; + int goalIndex = m_currentWaypointIndex; + + if (GetTeam (GetEntity ()) == TEAM_TF) + { + waypointValue = (g_experienceData + (startIndex * g_numWaypoints) + goalIndex)->team0Value; + waypointValue += static_cast (pev->health * 0.5); + waypointValue += static_cast (m_goalValue * 0.5); + + if (waypointValue < -MAX_GOAL_VALUE) + waypointValue = -MAX_GOAL_VALUE; + else if (waypointValue > MAX_GOAL_VALUE) + waypointValue = MAX_GOAL_VALUE; + + (g_experienceData + (startIndex * g_numWaypoints) + goalIndex)->team0Value = waypointValue; + } + else + { + waypointValue = (g_experienceData + (startIndex * g_numWaypoints) + goalIndex)->team1Value; + waypointValue += static_cast (pev->health * 0.5); + waypointValue += static_cast (m_goalValue * 0.5); + + if (waypointValue < -MAX_GOAL_VALUE) + waypointValue = -MAX_GOAL_VALUE; + else if (waypointValue > MAX_GOAL_VALUE) + waypointValue = MAX_GOAL_VALUE; + + (g_experienceData + (startIndex * g_numWaypoints) + goalIndex)->team1Value = waypointValue; + } + } + return true; + } + else if (m_navNode == NULL) + return false; + + if ((g_mapType & MAP_DE) && g_bombPlanted && GetTeam (GetEntity ()) == TEAM_CF && GetTaskId () != TASK_ESCAPEFROMBOMB && GetTask ()->data != -1) + { + Vector bombOrigin = CheckBombAudible (); + + // bot within 'hearable' bomb tick noises? + if (bombOrigin != nullvec) + { + float distance = (bombOrigin - g_waypoint->GetPath (GetTask ()->data)->origin).GetLength (); + + if (distance > 512.0) + g_waypoint->SetGoalVisited (GetTask ()->data); // doesn't hear so not a good goal + } + else + g_waypoint->SetGoalVisited (GetTask ()->data); // doesn't hear so not a good goal + } + HeadTowardWaypoint (); // do the actual movement checking + } + return false; +} + + +void Bot::FindShortestPath (int srcIndex, int destIndex) +{ + // this function finds the shortest path from source index to destination index + + DeleteSearchNodes (); + + m_chosenGoalIndex = srcIndex; + m_goalValue = 0.0; + + PathNode *node = new PathNode; + + node->index = srcIndex; + node->next = NULL; + + m_navNodeStart = node; + m_navNode = m_navNodeStart; + + while (srcIndex != destIndex) + { + srcIndex = *(g_waypoint->m_pathMatrix + (srcIndex * g_numWaypoints) + destIndex); + + if (srcIndex < 0) + { + m_prevGoalIndex = -1; + GetTask ()->data = -1; + + return; + } + + node->next = new PathNode; + node = node->next; + + if (node == NULL) + TerminateOnMalloc (); + + node->index = srcIndex; + node->next = NULL; + } +} + +// Priority queue class (smallest item out first) +class PriorityQueue +{ +public: + PriorityQueue (void); + ~PriorityQueue (void); + + inline int Empty (void) { return m_size == 0; } + inline int Size (void) { return m_size; } + void Insert (int, float); + int Remove (void); + +private: + struct HeapNode_t + { + int id; + float priority; + } *m_heap; + + int m_size; + int m_heapSize; + + void HeapSiftDown (int); + void HeapSiftUp (void); +}; + +PriorityQueue::PriorityQueue (void) +{ + m_size = 0; + m_heapSize = MAX_WAYPOINTS * 4; + m_heap = new HeapNode_t[sizeof (HeapNode_t) * m_heapSize]; +} + +PriorityQueue::~PriorityQueue (void) +{ + delete [] m_heap; + m_heap = NULL; +} + +// inserts a value into the priority queue +void PriorityQueue::Insert (int value, float priority) +{ + if (m_size >= m_heapSize) + { + m_heapSize += 100; + m_heap = (HeapNode_t *)realloc (m_heap, sizeof (HeapNode_t) * m_heapSize); + + if (m_heap == NULL) + TerminateOnMalloc (); + } + + m_heap[m_size].priority = priority; + m_heap[m_size].id = value; + + m_size++; + HeapSiftUp (); +} + +// removes the smallest item from the priority queue +int PriorityQueue::Remove (void) +{ + int retID = m_heap[0].id; + + m_size--; + m_heap[0] = m_heap[m_size]; + + HeapSiftDown (0); + return retID; +} + +void PriorityQueue::HeapSiftDown (int subRoot) +{ + int parent = subRoot; + int child = (2 * parent) + 1; + + HeapNode_t ref = m_heap[parent]; + + while (child < m_size) + { + int rightChild = (2 * parent) + 2; + + if (rightChild < m_size) + { + if (m_heap[rightChild].priority < m_heap[child].priority) + child = rightChild; + } + if (ref.priority <= m_heap[child].priority) + break; + + m_heap[parent] = m_heap[child]; + + parent = child; + child = (2 * parent) + 1; + } + m_heap[parent] = ref; +} + + +void PriorityQueue::HeapSiftUp (void) +{ + int child = m_size - 1; + + while (child) + { + int parent = (child - 1) / 2; + + if (m_heap[parent].priority <= m_heap[child].priority) + break; + + HeapNode_t temp = m_heap[child]; + + m_heap[child] = m_heap[parent]; + m_heap[parent] = temp; + + child = parent; + } +} + +float gfunctionKillsDistT (int thisIndex, int parent, int skillOffset) +{ + // least kills and number of nodes to goal for a team + + float cost = (g_experienceData + (thisIndex * g_numWaypoints) + thisIndex)->team0Damage + g_killHistory; + + for (int i = 0; i < MAX_PATH_INDEX; i++) + { + int neighbour = g_waypoint->GetPath (thisIndex)->index[i]; + + if (neighbour != -1) + cost += (g_experienceData + (neighbour * g_numWaypoints) + neighbour)->team0Damage; + } + float pathDistance = static_cast (g_waypoint->GetPathDistance (parent, thisIndex)); + + if (g_waypoint->GetPath (thisIndex)->flags & FLAG_CROUCH) + cost += pathDistance * 2; + + return pathDistance + (cost * (yb_danger_factor.GetFloat () * 2 / skillOffset)); +} + +float gfunctionKillsT (int thisIndex, int parent, int skillOffset) +{ + // least kills to goal for a team + + float cost = (g_experienceData + (thisIndex * g_numWaypoints) + thisIndex)->team0Damage; + + for (int i = 0; i < MAX_PATH_INDEX; i++) + { + int neighbour = g_waypoint->GetPath (thisIndex)->index[i]; + + if (neighbour != -1) + cost += (g_experienceData + (neighbour * g_numWaypoints) + neighbour)->team0Damage; + } + float pathDistance = static_cast (g_waypoint->GetPathDistance (parent, thisIndex)); + + if (g_waypoint->GetPath (thisIndex)->flags & FLAG_CROUCH) + cost += pathDistance * 3; + + return pathDistance + (cost * (yb_danger_factor.GetFloat () * 2 / skillOffset)); +} + +float gfunctionKillsDistCT (int thisIndex, int parent, int skillOffset) +{ + // least kills and number of nodes to goal for a team + + float cost = (g_experienceData + (thisIndex * g_numWaypoints) + thisIndex)->team1Damage + g_killHistory; + + for (int i = 0; i < MAX_PATH_INDEX; i++) + { + int neighbour = g_waypoint->GetPath (thisIndex)->index[i]; + + if (neighbour != -1) + cost += (g_experienceData + (neighbour * g_numWaypoints) + neighbour)->team1Damage; + } + float pathDistance = static_cast (g_waypoint->GetPathDistance (parent, thisIndex)); + + if (g_waypoint->GetPath (thisIndex)->flags & FLAG_CROUCH) + cost += pathDistance * 2; + + return pathDistance + (cost * (yb_danger_factor.GetFloat () * 2 / skillOffset)); +} + +float gfunctionKillsCT (int thisIndex, int parent, int skillOffset) +{ + // least kills to goal for a team + + float cost = (g_experienceData + (thisIndex * g_numWaypoints) + thisIndex)->team1Damage; + + for (int i = 0; i < MAX_PATH_INDEX; i++) + { + int neighbour = g_waypoint->GetPath (thisIndex)->index[i]; + + if (neighbour != -1) + cost += (g_experienceData + (neighbour * g_numWaypoints) + neighbour)->team1Damage; + } + float pathDistance = static_cast (g_waypoint->GetPathDistance (parent, thisIndex)); + + if (g_waypoint->GetPath (thisIndex)->flags & FLAG_CROUCH) + cost += pathDistance * 3; + + return pathDistance + (cost * (yb_danger_factor.GetFloat () * 2 / skillOffset)); +} + +float gfunctionKillsDistCTNoHostage (int thisIndex, int parent, int skillOffset) +{ + if (g_waypoint->GetPath (thisIndex)->flags & FLAG_NOHOSTAGE) + return 65355; + else if (g_waypoint->GetPath (thisIndex)->flags & (FLAG_CROUCH | FLAG_LADDER)) + return gfunctionKillsDistCT (thisIndex, parent, skillOffset) * 500; + + return gfunctionKillsDistCT (thisIndex, parent, skillOffset); +} + +float gfunctionKillsCTNoHostage (int thisIndex, int parent, int skillOffset) +{ + if (g_waypoint->GetPath (thisIndex)->flags & FLAG_NOHOSTAGE) + return 65355; + else if (g_waypoint->GetPath (thisIndex)->flags & (FLAG_CROUCH | FLAG_LADDER)) + return gfunctionKillsCT (thisIndex, parent, skillOffset) * 500; + + return gfunctionKillsCT (thisIndex, parent, skillOffset); +} + +float hfunctionPathDist (int startIndex, int goalIndex, int) +{ + // using square heuristic + Path *goal = g_waypoint->GetPath (goalIndex); + Path *start = g_waypoint->GetPath (startIndex); + + float deltaX = fabsf (goal->origin.x - start->origin.x); + float deltaY = fabsf (goal->origin.y - start->origin.y); + float deltaZ = fabsf (goal->origin.z - start->origin.z); + + return deltaX + deltaY + deltaZ; +} + +float hfunctionNumberNodes (int startIndex, int goalIndex, int unused) +{ + return hfunctionPathDist (startIndex, goalIndex, unused) / 128 * g_killHistory; +} + +float hfunctionNone (int startIndex, int goalIndex, int unused) +{ + return hfunctionPathDist (startIndex, goalIndex, unused) / (128 * 100); +} + +void Bot::FindPath (int srcIndex, int destIndex, unsigned char pathType) +{ + // this function finds a path from srcIndex to destIndex + + if (srcIndex > g_numWaypoints - 1 || srcIndex < 0) + { + AddLogEntry (true, LL_ERROR, "Pathfinder source path index not valid (%d)", srcIndex); + return; + } + + if (destIndex > g_numWaypoints - 1 || destIndex < 0) + { + AddLogEntry (true, LL_ERROR, "Pathfinder destination path index not valid (%d)", destIndex); + return; + } + + DeleteSearchNodes (); + + m_chosenGoalIndex = srcIndex; + m_goalValue = 0.0; + + // A* Stuff + enum AStarState_t {OPEN, CLOSED, NEW}; + + struct AStar_t + { + double g; + double f; + short parentIndex; + + AStarState_t state; + } astar[MAX_WAYPOINTS]; + + PriorityQueue openList; + + for (int i = 0; i < MAX_WAYPOINTS; i++) + { + astar[i].g = 0; + astar[i].f = 0; + astar[i].parentIndex = -1; + astar[i].state = NEW; + } + + float (*gcalc) (int, int, int) = NULL; + float (*hcalc) (int, int, int) = NULL; + + int skillOffset = 1; + + if (pathType == 0) + { + if (GetTeam (GetEntity ()) == TEAM_TF) + gcalc = hfunctionNone; + else if (HasHostage ()) + gcalc = hfunctionNone; + else + gcalc = hfunctionNone; + + hcalc = hfunctionNumberNodes; + skillOffset = m_skill / 25; + } + else if (pathType == 1) + { + if (GetTeam (GetEntity ()) == TEAM_TF) + gcalc = gfunctionKillsDistT; + else if (HasHostage ()) + gcalc = gfunctionKillsDistCTNoHostage; + else + gcalc = gfunctionKillsDistCT; + + hcalc = hfunctionNumberNodes; + skillOffset = m_skill / 25; + } + else if (pathType == 2) + { + if (GetTeam (GetEntity ()) == TEAM_TF) + gcalc = gfunctionKillsT; + else if (HasHostage ()) + gcalc = gfunctionKillsCTNoHostage; + else + gcalc = gfunctionKillsCT; + + hcalc = hfunctionNone; + skillOffset = m_skill / 20; + } + + + // put start node into open list + astar[srcIndex].g = gcalc (srcIndex, -1, skillOffset); + astar[srcIndex].f = astar[srcIndex].g + hcalc (srcIndex, destIndex, skillOffset); + astar[srcIndex].state = OPEN; + + openList.Insert (srcIndex, astar[srcIndex].g); + + while (!openList.Empty ()) + { + // remove the first node from the open list + int currentIndex = openList.Remove (); + + // is the current node the goal node? + if (currentIndex == destIndex) + { + // build the complete path + m_navNode = NULL; + + do + { + PathNode *path = new PathNode; + + path->index = currentIndex; + path->next = m_navNode; + + m_navNode = path; + currentIndex = astar[currentIndex].parentIndex; + + } while (currentIndex != -1); + + m_navNodeStart = m_navNode; + return; + } + + if (astar[currentIndex].state != OPEN) + continue; + + // put current node into CLOSED list + astar[currentIndex].state = CLOSED; + + // now expand the current node + for (int i = 0; i < MAX_PATH_INDEX; i++) + { + int iCurChild = g_waypoint->GetPath (currentIndex)->index[i]; + + if (iCurChild == -1) + continue; + + // calculate the F value as F = G + H + float g = astar[currentIndex].g + gcalc (iCurChild, currentIndex, skillOffset); + float h = hcalc (srcIndex, destIndex, skillOffset); + float f = g + h; + + if ((astar[iCurChild].state == NEW) || (astar[iCurChild].f > f)) + { + // put the current child into open list + astar[iCurChild].parentIndex = currentIndex; + astar[iCurChild].state = OPEN; + + astar[iCurChild].g = g; + astar[iCurChild].f = f; + + openList.Insert (iCurChild, g); + } + } + } + FindShortestPath (srcIndex, destIndex); // A* found no path, try floyd pathfinder instead +} + +void Bot::DeleteSearchNodes (void) +{ + PathNode *deletingNode = NULL; + PathNode *node = m_navNodeStart; + + while (node != NULL) + { + deletingNode = node->next; + delete node; + + node = deletingNode; + } + m_navNodeStart = NULL; + m_navNode = NULL; + m_chosenGoalIndex = -1; +} + +int Bot::GetAimingWaypoint (Vector targetOriginPos) +{ + // return the most distant waypoint which is seen from the Bot to the Target and is within count + + if (m_currentWaypointIndex == -1) + ChangeWptIndex (g_waypoint->FindNearest (pev->origin)); + + int srcIndex = m_currentWaypointIndex; + int destIndex = g_waypoint->FindNearest (targetOriginPos); + int bestIndex = srcIndex; + + PathNode *node = new PathNode; + + node->index = destIndex; + node->next = NULL; + + PathNode *startNode = node; + + while (destIndex != srcIndex) + { + destIndex = *(g_waypoint->m_pathMatrix + (destIndex * g_numWaypoints) + srcIndex); + + if (destIndex < 0) + break; + + node->next = new PathNode; + node = node->next; + + if (node == NULL) + TerminateOnMalloc (); + + node->index = destIndex; + node->next = NULL; + + if (g_waypoint->IsVisible (m_currentWaypointIndex, destIndex)) + { + bestIndex = destIndex; + break; + } + } + + while (startNode != NULL) + { + node = startNode->next; + delete startNode; + + startNode = node; + } + return bestIndex; +} + +bool Bot::FindWaypoint (void) +{ + // this function find a waypoint in the near of the bot if bot had lost his path of pathfinder needs + // to be restarted over again. + + int waypointIndeces[3], coveredWaypoint = -1, i = 0; + float reachDistances[3]; + + // nullify all waypoint search stuff + for (int i = 0; i < 3; i++) + { + waypointIndeces[i] = -1; + reachDistances[i] = 9999.0; + } + + // do main search loop + for (int i = 0; i < g_numWaypoints; i++) + { + // ignore current waypoint and previous recent waypoints... + if (i == m_currentWaypointIndex || i == m_prevWptIndex[0] || i == m_prevWptIndex[1] || i == m_prevWptIndex[2] || i == m_prevWptIndex[3] || i == m_prevWptIndex[4]) + continue; + + // ignore non-reacheable waypoints... + if (!g_waypoint->Reachable (this, i)) + continue; + + // check if waypoint is already used by another bot... + if (IsWaypointUsed (i)) + { + coveredWaypoint = i; + continue; + } + + // now pick 1-2 random waypoints that near the bot + float distance = (g_waypoint->GetPath (i)->origin - pev->origin).GetLengthSquared (); + + // now fill the waypoint list + for (int j = 0; j < 3; j++) + { + if (distance < reachDistances[j]) + { + waypointIndeces[j] = i; + reachDistances[j] = distance; + } + } + } + + // now pick random one from choosen + if (waypointIndeces[2] != -1) + i = g_randGen.Long (0, 2); + + else if (waypointIndeces[1] != -1) + i = g_randGen.Long (0, 1); + + else if (waypointIndeces[0] != -1) + i = g_randGen.Long (0, 0); + + else if (coveredWaypoint != -1) + waypointIndeces[i = 0] = coveredWaypoint; + + else + { + Array found; + g_waypoint->FindInRadius (found, 256.0, pev->origin); + + if (!found.IsEmpty ()) + waypointIndeces[i = 0] = found.GetRandomElement (); + else + waypointIndeces[i = 0] = g_randGen.Long (0, g_numWaypoints - 1); + } + + m_collideTime = GetWorldTime (); + ChangeWptIndex (waypointIndeces[0]); + + return true; +} + +void Bot::GetValidWaypoint (void) +{ + // checks if the last waypoint the bot was heading for is still valid + + // if bot hasn't got a waypoint we need a new one anyway or if time to get there expired get new one as well + if (m_currentWaypointIndex == -1) + { + DeleteSearchNodes (); + FindWaypoint (); + + m_waypointOrigin = m_currentPath->origin; + + // FIXME: Do some error checks if we got a waypoint + } + else if ((m_navTimeset + GetEstimatedReachTime () < GetWorldTime ()) && FNullEnt (m_enemy)) + { + if (GetTeam (GetEntity ()) == TEAM_TF) + { + int value = (g_experienceData + (m_currentWaypointIndex * g_numWaypoints) + m_currentWaypointIndex)->team0Damage; + value += 100; + + if (value > MAX_DAMAGE_VALUE) + value = MAX_DAMAGE_VALUE; + + (g_experienceData + (m_currentWaypointIndex * g_numWaypoints) + m_currentWaypointIndex)->team0Damage = static_cast (value); + + // affect nearby connected with victim waypoints + for (int i = 0; i < MAX_PATH_INDEX; i++) + { + if ((m_currentPath->index[i] > -1) && (m_currentPath->index[i] < g_numWaypoints)) + { + value = (g_experienceData + (m_currentPath->index[i] * g_numWaypoints) + m_currentPath->index[i])->team0Damage; + value += 2; + + if (value > MAX_DAMAGE_VALUE) + value = MAX_DAMAGE_VALUE; + + (g_experienceData + (m_currentPath->index[i] * g_numWaypoints) + m_currentPath->index[i])->team0Damage = static_cast (value); + } + } + } + else + { + int value = (g_experienceData + (m_currentWaypointIndex * g_numWaypoints) + m_currentWaypointIndex)->team1Damage; + value += 100; + + if (value > MAX_DAMAGE_VALUE) + value = MAX_DAMAGE_VALUE; + + (g_experienceData + (m_currentWaypointIndex * g_numWaypoints) + m_currentWaypointIndex)->team1Damage = static_cast (value); + + // affect nearby connected with victim waypoints + for (int i = 0; i < MAX_PATH_INDEX; i++) + { + if ((m_currentPath->index[i] > -1) && (m_currentPath->index[i] < g_numWaypoints)) + { + value = (g_experienceData + (m_currentPath->index[i] * g_numWaypoints) + m_currentPath->index[i])->team1Damage; + value += 2; + + if (value > MAX_DAMAGE_VALUE) + value = MAX_DAMAGE_VALUE; + + (g_experienceData + (m_currentPath->index[i] * g_numWaypoints) + m_currentPath->index[i])->team1Damage = static_cast (value); + } + } + } + DeleteSearchNodes (); + FindWaypoint (); + + m_waypointOrigin = m_currentPath->origin; + } +} + +void Bot::ChangeWptIndex (int waypointIndex) +{ + m_prevWptIndex[4] = m_prevWptIndex[3]; + m_prevWptIndex[3] = m_prevWptIndex[2]; + m_prevWptIndex[2] = m_prevWptIndex[1]; + m_prevWptIndex[0] = waypointIndex; + + m_currentWaypointIndex = waypointIndex; + m_navTimeset = GetWorldTime (); + + m_currentPath = g_waypoint->GetPath (m_currentWaypointIndex); + + // get the current waypoint flags + if (m_currentWaypointIndex != -1) + m_waypointFlags = m_currentPath->flags; + else + m_waypointFlags = 0; +} + +int Bot::ChooseBombWaypoint (void) +{ + // this function finds the best goal (bomb) waypoint for CTs when searching for a planted bomb. + + Array &goals = g_waypoint->m_goalPoints; + + if (goals.IsEmpty ()) + return g_randGen.Long (0, g_numWaypoints - 1); // reliability check + + Vector bombOrigin = CheckBombAudible (); + + // if bomb returns no valid vector, return the current bot pos + if (bombOrigin == nullvec) + bombOrigin = pev->origin; + + int goal = 0, count = 0; + float lastDistance = FLT_MAX; + + // find nearest goal waypoint either to bomb (if "heard" or player) + IterateArray (goals, i) + { + float distance = (g_waypoint->GetPath (goals[i])->origin - bombOrigin).GetLengthSquared (); + + // check if we got more close distance + if (distance < lastDistance) + { + goal = goals[i]; + lastDistance = distance;; + } + } + + while (g_waypoint->IsGoalVisited (goal)) + { + goal = goals.GetRandomElement (); + + if (count++ >= goals.GetElementNumber ()) + break; + } + return goal; +} + +int Bot::FindDefendWaypoint (Vector origin) +{ + // this function tries to find a good position which has a line of sight to a position, + // provides enough cover point, and is far away from the defending position + + TraceResult tr; + + int waypointIndex[MAX_PATH_INDEX]; + int minDistance[MAX_PATH_INDEX]; + + for (int i = 0; i < MAX_PATH_INDEX; i++) + { + waypointIndex[i] = -1; + minDistance[i] = 128; + } + + int posIndex = g_waypoint->FindNearest (origin); + int srcIndex = g_waypoint->FindNearest (pev->origin); + + // some of points not found, return random one + if (srcIndex == -1 || posIndex == -1) + return g_randGen.Long (0, g_numWaypoints - 1); + + for (int i = 0; i < g_numWaypoints; i++) // find the best waypoint now + { + // exclude ladder & current waypoints + if ((g_waypoint->GetPath (i)->flags & FLAG_LADDER) || i == srcIndex || !g_waypoint->IsVisible (i, posIndex) || IsWaypointUsed (i)) + continue; + + // use the 'real' pathfinding distances + int distances = g_waypoint->GetPathDistance (srcIndex, i); + + // skip wayponts with distance more than 1024 units + if (distances > 1024.0f) + continue; + + TraceLine (g_waypoint->GetPath (i)->origin, g_waypoint->GetPath (posIndex)->origin, true, true, GetEntity (), &tr); + + // check if line not hit anything + if (tr.flFraction != 1.0) + continue; + + for (int j = 0; j < MAX_PATH_INDEX; j++) + { + if (distances > minDistance[j]) + { + waypointIndex[j] = i; + minDistance[j] = distances; + + break; + } + } + } + + // use statistic if we have them + for (int i = 0; i < MAX_PATH_INDEX; i++) + { + if (waypointIndex[i] != -1) + { + Experience *exp = (g_experienceData + (waypointIndex[i] * g_numWaypoints) + waypointIndex[i]); + int experience = -1; + + if (GetTeam (GetEntity ()) == TEAM_TF) + experience = exp->team0Damage; + else + experience = exp->team1Damage; + + experience = (experience * 100) / MAX_DAMAGE_VALUE; + minDistance[i] = (experience * 100) / 8192; + minDistance[i] += experience; + } + } + + bool isOrderChanged = false; + + // sort results waypoints for farest distance + do + { + isOrderChanged = false; + + // completely sort the data + for (int i = 0; i < 3 && waypointIndex[i] != -1 && waypointIndex[i + 1] != -1 && minDistance[i] > minDistance[i + 1]; i++) + { + int index = waypointIndex[i]; + + waypointIndex[i] = waypointIndex[i + 1]; + waypointIndex[i + 1] = index; + + index = minDistance[i]; + + minDistance[i] = minDistance[i + 1]; + minDistance[i + 1] = index; + + isOrderChanged = true; + } + } while (isOrderChanged); + + if (waypointIndex[0] == -1) + { + Array found; + g_waypoint->FindInRadius (found, 1024.0f, origin); + + if (found.IsEmpty ()) + return g_randGen.Long (0, g_numWaypoints - 1); // most worst case, since there a evil error in waypoints + + return found.GetRandomElement (); + } + int index = 0; + + for (; index < MAX_PATH_INDEX; index++) + { + if (waypointIndex[index] == -1) + break; + } + return waypointIndex[g_randGen.Long (0, (index - 1) / 2)]; +} + +int Bot::FindCoverWaypoint (float maxDistance) +{ + // this function tries to find a good cover waypoint if bot wants to hide + + // do not move to a position near to the enemy + if (maxDistance > (m_lastEnemyOrigin - pev->origin).GetLength ()) + maxDistance = (m_lastEnemyOrigin - pev->origin).GetLength (); + + if (maxDistance < 300.0) + maxDistance = 300.0; + + int srcIndex = m_currentWaypointIndex; + int enemyIndex = g_waypoint->FindNearest (m_lastEnemyOrigin); + Array enemyIndices; + + int waypointIndex[MAX_PATH_INDEX]; + int minDistance[MAX_PATH_INDEX]; + + for (int i = 0; i < MAX_PATH_INDEX; i++) + { + waypointIndex[i] = -1; + minDistance[i] = static_cast (maxDistance); + } + + if (enemyIndex == -1) + return -1; + + // now get enemies neigbouring points + for (int i = 0; i < MAX_PATH_INDEX; i++) + { + if (g_waypoint->GetPath (enemyIndex)->index[i] != -1) + enemyIndices.Push (g_waypoint->GetPath (enemyIndex)->index[i]); + } + + // ensure we're on valid point + ChangeWptIndex (srcIndex); + + // find the best waypoint now + for (int i = 0; i < g_numWaypoints; i++) + { + // exclude ladder, current waypoints and waypoints seen by the enemy + if ((g_waypoint->GetPath (i)->flags & FLAG_LADDER) || i == srcIndex || g_waypoint->IsVisible (enemyIndex, i)) + continue; + + bool neighbourVisible = false; // now check neighbour waypoints for visibility + + IterateArray (enemyIndices, j) + { + if (g_waypoint->IsVisible (enemyIndices[j], i)) + { + neighbourVisible = true; + break; + } + } + + // skip visible points + if (neighbourVisible) + continue; + + // use the 'real' pathfinding distances + int distances = g_waypoint->GetPathDistance (srcIndex, i); + int enemyDistance = g_waypoint->GetPathDistance (enemyIndex, i); + + if (distances >= enemyDistance) + continue; + + for (int j = 0; j < MAX_PATH_INDEX; j++) + { + if (distances < minDistance[j]) + { + waypointIndex[j] = i; + minDistance[j] = distances; + + break; + } + } + } + + // use statistic if we have them + for (int i = 0; i < MAX_PATH_INDEX; i++) + { + if (waypointIndex[i] != -1) + { + Experience *exp = (g_experienceData + (waypointIndex[i] * g_numWaypoints) + waypointIndex[i]); + int experience = -1; + + if (GetTeam (GetEntity ()) == TEAM_TF) + experience = exp->team0Damage; + else + experience = exp->team1Damage; + + experience = (experience * 100) / MAX_DAMAGE_VALUE; + minDistance[i] = (experience * 100) / 8192; + minDistance[i] += experience; + } + } + + bool isOrderChanged; + + // sort resulting waypoints for nearest distance + do + { + isOrderChanged = false; + + for (int i = 0; i < 3 && waypointIndex[i] != -1 && waypointIndex[i + 1] != -1 && minDistance[i] > minDistance[i + 1]; i++) + { + int index = waypointIndex[i]; + + waypointIndex[i] = waypointIndex[i + 1]; + waypointIndex[i + 1] = index; + index = minDistance[i]; + minDistance[i] = minDistance[i + 1]; + minDistance[i + 1] = index; + + isOrderChanged = true; + } + } while (isOrderChanged); + + TraceResult tr; + + // take the first one which isn't spotted by the enemy + for (int i = 0; i < MAX_PATH_INDEX; i++) + { + if (waypointIndex[i] != -1) + { + TraceLine (m_lastEnemyOrigin + Vector (0, 0, 36), g_waypoint->GetPath (waypointIndex[i])->origin, true, true, GetEntity (), &tr); + + if (tr.flFraction < 1.0) + return waypointIndex[i]; + } + } + + // if all are seen by the enemy, take the first one + if (waypointIndex[0] != -1) + return waypointIndex[0]; + + return -1; // do not use random points +} + +bool Bot::GetBestNextWaypoint (void) +{ + // this function does a realtime postprocessing of waypoints return from the + // pathfinder, to vary paths and find the best waypoint on our way + + InternalAssert (m_navNode != NULL); + InternalAssert (m_navNode->next != NULL); + + if (!IsWaypointUsed (m_navNode->index)) + return false; + + for (int i = 0; i < MAX_PATH_INDEX; i++) + { + int id = m_currentPath->index[i]; + + if (id != -1 && g_waypoint->IsConnected (id, m_navNode->next->index) && g_waypoint->IsConnected (m_currentWaypointIndex, id)) + { + if (g_waypoint->GetPath (id)->flags & FLAG_LADDER) // don't use ladder waypoints as alternative + continue; + + if (!IsWaypointUsed (id)) + { + m_navNode->index = id; + return true; + } + } + } + return false; +} + +bool Bot::HeadTowardWaypoint (void) +{ + // advances in our pathfinding list and sets the appropiate destination origins for this bot + + GetValidWaypoint (); // check if old waypoints is still reliable + + // no waypoints from pathfinding? + if (m_navNode == NULL) + return false; + + TraceResult tr; + + m_navNode = m_navNode->next; // advance in list + m_currentTravelFlags = 0; // reset travel flags (jumping etc) + + // we're not at the end of the list? + if (m_navNode != NULL) + { + // if in between a route, postprocess the waypoint (find better alternatives)... + if (m_navNode != m_navNodeStart && m_navNode->next != NULL) + { + GetBestNextWaypoint (); + int taskID= GetTaskId (); + + m_minSpeed = pev->maxspeed; + + // only if we in normal task and bomb is not planted + if (taskID == TASK_NORMAL && !g_bombPlanted && m_personality != PERSONALITY_RUSHER && !(pev->weapons & (1 << WEAPON_C4)) && !m_isVIP && (m_loosedBombWptIndex == -1) && !HasHostage ()) + { + m_campButtons = 0; + + int waypoint = m_navNode->next->index; + int kills = 0; + + if (GetTeam (GetEntity ()) == TEAM_TF) + kills = (g_experienceData + (waypoint * g_numWaypoints) + waypoint)->team0Damage; + else + kills = (g_experienceData + (waypoint * g_numWaypoints) + waypoint)->team1Damage; + + // if damage done higher than one + if (kills > 1 && g_timeRoundMid > GetWorldTime () && g_killHistory > 0) + { + kills = (kills * 100) / g_killHistory; + kills /= 100; + + switch (m_personality) + { + case PERSONALITY_NORMAL: + kills /= 2; + break; + + default: + kills /= 1.2; + break; + } + + if (m_baseAgressionLevel < static_cast (kills) && (m_skill < 98 || GetTeam (GetEntity ()) == TEAM_CF)) + { + StartTask (TASK_CAMP, TASKPRI_CAMP, -1, GetWorldTime () + (m_fearLevel * (g_timeRoundMid - GetWorldTime ()) * 0.5), true); // push camp task on to stack + + StartTask (TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, FindDefendWaypoint (g_waypoint->GetPath (waypoint)->origin), 0.0, true); + + if (m_skill > 95) + pev->button |= IN_DUCK; + } + } + else if (g_botsCanPause && !IsOnLadder () && !IsInWater () && !m_currentTravelFlags && IsOnFloor ()) + { + if (static_cast (kills) == m_baseAgressionLevel) + m_campButtons |= IN_DUCK; + else if (g_randGen.Long (1, 100) > (m_skill + g_randGen.Long (1, 20))) + m_minSpeed = GetWalkSpeed (); + } + } + } + + if (m_navNode != NULL) + { + int destIndex = m_navNode->index; + + // find out about connection flags + if (m_currentWaypointIndex != -1) + { + for (int i = 0; i < MAX_PATH_INDEX; i++) + { + if (m_currentPath->index[i] == m_navNode->index) + { + m_currentTravelFlags = m_currentPath->connectionFlags[i]; + m_desiredVelocity = m_currentPath->connectionVelocity[i]; + m_jumpFinished = false; + + break; + } + } + + // check if bot is going to jump + bool willJump = false; + float jumpDistance = 0.0; + + Vector src = nullvec; + Vector destination = nullvec; + + // try to find out about future connection flags + if (m_navNode->next != NULL) + { + for (int i = 0; i < MAX_PATH_INDEX; i++) + { + Path *path = g_waypoint->GetPath (m_navNode->index); + Path *next = g_waypoint->GetPath (m_navNode->next->index); + + if (path->index[i] == m_navNode->next->index && (path->connectionFlags[i] & PATHFLAG_JUMP)) + { + src = path->origin; + destination = next->origin; + + jumpDistance = (path->origin - next->origin).GetLength (); + willJump = true; + + break; + } + } + } + + // is there a jump waypoint right ahead and do we need to draw out the light weapon ? + if (willJump && (jumpDistance > 210 || (destination.z + 32.0 > src.z && jumpDistance > 150) || ((destination - src).GetLength2D () < 50 && jumpDistance > 60)) && !(m_states & STATE_SEEING_ENEMY) && m_currentWeapon != WEAPON_KNIFE && !m_isReloading) + SelectWeaponByName ("weapon_knife"); // draw out the knife if we needed + + // bot not already on ladder but will be soon? + if ((g_waypoint->GetPath (destIndex)->flags & FLAG_LADDER) && !IsOnLadder ()) + { + // get ladder waypoints used by other (first moving) bots + for (int c = 0; c < GetMaxClients (); c++) + { + Bot *otherBot = g_botManager->GetBot (c); + + // if another bot uses this ladder, wait 3 secs + if (otherBot != NULL && otherBot != this && IsAlive (otherBot->GetEntity ()) && otherBot->m_currentWaypointIndex == m_navNode->index) + { + StartTask (TASK_PAUSE, TASKPRI_PAUSE, -1, GetWorldTime () + 3.0, false); + return true; + } + } + } + } + ChangeWptIndex (destIndex); + } + } + m_waypointOrigin = m_currentPath->origin; + + // if wayzone radius non zero vary origin a bit depending on the body angles + if (m_currentPath->radius > 0.0f) + { + MakeVectors (Vector (pev->angles.x, AngleNormalize (pev->angles.y + g_randGen.Float (-90, 90)), 0.0)); + m_waypointOrigin = m_waypointOrigin + g_pGlobals->v_forward * g_randGen.Float (0, m_currentPath->radius); + } + + if (IsOnLadder ()) + { + TraceLine (Vector (pev->origin.x, pev->origin.y, pev->absmin.z), m_waypointOrigin, true, true, GetEntity (), &tr); + + if (tr.flFraction < 1.0) + m_waypointOrigin = m_waypointOrigin + (pev->origin - m_waypointOrigin) * 0.5 + Vector (0, 0, 32); + } + m_navTimeset = GetWorldTime (); + + return true; +} + +bool Bot::CantMoveForward (const Vector &normal, TraceResult *tr) +{ + // Checks if bot is blocked in his movement direction (excluding doors) + + // use some TraceLines to determine if anything is blocking the current path of the bot. + Vector center = Vector (0, pev->angles.y, 0); + + MakeVectors (center); + + // first do a trace from the bot's eyes forward... + Vector src = EyePosition (); + Vector forward = src + normal * 24; + + // trace from the bot's eyes straight forward... + TraceLine (src, forward, true, GetEntity (), tr); + + // check if the trace hit something... + if (tr->flFraction < 1.0) + { + if (strncmp ("func_door", STRING (tr->pHit->v.classname), 9) == 0) + return false; + + return true; // bot's head will hit something + } + + // bot's head is clear, check at shoulder level... + // trace from the bot's shoulder left diagonal forward to the right shoulder... + src = EyePosition () + Vector (0, 0, -16) - g_pGlobals->v_right * 16; + forward = EyePosition () + Vector (0, 0, -16) + g_pGlobals->v_right * 16 + normal * 24; + + TraceLine (src, forward, true, GetEntity (), tr); + + // check if the trace hit something... + if (tr->flFraction < 1.0 && strncmp ("func_door", STRING (tr->pHit->v.classname), 9) != 0) + return true; // bot's body will hit something + + // bot's head is clear, check at shoulder level... + // trace from the bot's shoulder right diagonal forward to the left shoulder... + src = EyePosition () + Vector (0, 0, -16) + g_pGlobals->v_right * 16; + forward = EyePosition () + Vector (0, 0, -16) - g_pGlobals->v_right * 16 + normal * 24; + + TraceLine (src, forward, true, GetEntity (), tr); + + // check if the trace hit something... + if (tr->flFraction < 1.0 && strncmp ("func_door", STRING (tr->pHit->v.classname), 9) != 0) + return true; // bot's body will hit something + + // now check below waist + if (pev->flags & FL_DUCKING) + { + src = pev->origin + Vector (0, 0, -19 + 19); + forward = src + Vector (0, 0, 10) + normal * 24; + + TraceLine (src, forward, true, GetEntity (), tr); + + // check if the trace hit something... + if (tr->flFraction < 1.0 && strncmp ("func_door", STRING (tr->pHit->v.classname), 9) != 0) + return true; // bot's body will hit something + + src = pev->origin; + forward = src + normal * 24; + + TraceLine (src, forward, true, GetEntity (), tr); + + // check if the trace hit something... + if (tr->flFraction < 1.0 && strncmp ("func_door", STRING (tr->pHit->v.classname), 9) != 0) + return true; // bot's body will hit something + } + else + { + // trace from the left waist to the right forward waist pos + src = pev->origin + Vector (0, 0, -17) - g_pGlobals->v_right * 16; + forward = pev->origin + Vector (0, 0, -17) + g_pGlobals->v_right * 16 + normal * 24; + + // trace from the bot's waist straight forward... + TraceLine (src, forward, true, GetEntity (), tr); + + // check if the trace hit something... + if (tr->flFraction < 1.0 && strncmp ("func_door", STRING (tr->pHit->v.classname), 9) != 0) + return true; // bot's body will hit something + + // trace from the left waist to the right forward waist pos + src = pev->origin + Vector (0, 0, -17) + g_pGlobals->v_right * 16; + forward = pev->origin + Vector (0, 0, -17) - g_pGlobals->v_right * 16 + normal * 24; + + TraceLine (src, forward, true, GetEntity (), tr); + + // check if the trace hit something... + if (tr->flFraction < 1.0 && strncmp ("func_door", STRING (tr->pHit->v.classname), 9) != 0) + return true; // bot's body will hit something + } + return false; // bot can move forward, return false +} + +bool Bot::CanStrafeLeft (TraceResult *tr) +{ + // this function checks if bot can move sideways + + MakeVectors (pev->v_angle); + + Vector src = pev->origin; + Vector left = src - g_pGlobals->v_right * -40; + + // trace from the bot's waist straight left... + TraceLine (src, left, true, GetEntity (), tr); + + // check if the trace hit something... + if (tr->flFraction < 1.0) + return false; // bot's body will hit something + + src = left; + left = left + g_pGlobals->v_forward * 40; + + // trace from the strafe pos straight forward... + TraceLine (src, left, true, GetEntity (), tr); + + // check if the trace hit something... + if (tr->flFraction < 1.0) + return false; // bot's body will hit something + + return true; +} + +bool Bot::CanStrafeRight (TraceResult * tr) +{ + // this function checks if bot can move sideways + + MakeVectors (pev->v_angle); + + Vector src = pev->origin; + Vector right = src + g_pGlobals->v_right * 40; + + // trace from the bot's waist straight right... + TraceLine (src, right, true, GetEntity (), tr); + + // check if the trace hit something... + if (tr->flFraction < 1.0) + return false; // bot's body will hit something + + src = right; + right = right + g_pGlobals->v_forward * 40; + + // trace from the strafe pos straight forward... + TraceLine (src, right, true, GetEntity (), tr); + + // check if the trace hit something... + if (tr->flFraction < 1.0) + return false; // bot's body will hit something + + return true; +} + +bool Bot::CanJumpUp (const Vector &normal) +{ + // this function check if bot can jump over some obstacle + + TraceResult tr; + + // can't jump if not on ground and not on ladder/swimming + if (!IsOnFloor () && (IsOnLadder () || !IsInWater ())) + return false; + + // convert current view angle to vectors for traceline math... + Vector jump = pev->angles; + jump.x = 0; // reset pitch to 0 (level horizontally) + jump.z = 0; // reset roll to 0 (straight up and down) + + MakeVectors (jump); + + // check for normal jump height first... + Vector src = pev->origin + Vector (0, 0, -36 + 45); + Vector dest = src + normal * 32; + + // trace a line forward at maximum jump height... + TraceLine (src, dest, true, GetEntity (), &tr); + + if (tr.flFraction < 1.0) + goto CheckDuckJump; + else + { + // now trace from jump height upward to check for obstructions... + src = dest; + dest.z = dest.z + 37; + + TraceLine (src, dest, true, GetEntity (), &tr); + + if (tr.flFraction < 1.0) + return false; + } + + // now check same height to one side of the bot... + src = pev->origin + g_pGlobals->v_right * 16 + Vector (0, 0, -36 + 45); + dest = src + normal * 32; + + // trace a line forward at maximum jump height... + TraceLine (src, dest, true, GetEntity (), &tr); + + // if trace hit something, return false + if (tr.flFraction < 1.0) + goto CheckDuckJump; + + // now trace from jump height upward to check for obstructions... + src = dest; + dest.z = dest.z + 37; + + TraceLine (src, dest, true, GetEntity (), &tr); + + // if trace hit something, return false + if (tr.flFraction < 1.0) + return false; + + // now check same height on the other side of the bot... + src = pev->origin + (-g_pGlobals->v_right * 16) + Vector (0, 0, -36 + 45); + dest = src + normal * 32; + + // trace a line forward at maximum jump height... + TraceLine (src, dest, true, GetEntity (), &tr); + + // if trace hit something, return false + if (tr.flFraction < 1.0) + goto CheckDuckJump; + + // now trace from jump height upward to check for obstructions... + src = dest; + dest.z = dest.z + 37; + + TraceLine (src, dest, true, GetEntity (), &tr); + + // if trace hit something, return false + return tr.flFraction > 1.0; + + // here we check if a duck jump would work... +CheckDuckJump: + + // use center of the body first... maximum duck jump height is 62, so check one unit above that (63) + src = pev->origin + Vector (0, 0, -36 + 63); + dest = src + normal * 32; + + // trace a line forward at maximum jump height... + TraceLine (src, dest, true, GetEntity (), &tr); + + if (tr.flFraction < 1.0) + return false; + else + { + // now trace from jump height upward to check for obstructions... + src = dest; + dest.z = dest.z + 37; + + TraceLine (src, dest, true, GetEntity (), &tr); + + // if trace hit something, check duckjump + if (tr.flFraction < 1.0) + return false; + } + + // now check same height to one side of the bot... + src = pev->origin + g_pGlobals->v_right * 16 + Vector (0, 0, -36 + 63); + dest = src + normal * 32; + + // trace a line forward at maximum jump height... + TraceLine (src, dest, true, GetEntity (), &tr); + + // if trace hit something, return false + if (tr.flFraction < 1.0) + return false; + + // now trace from jump height upward to check for obstructions... + src = dest; + dest.z = dest.z + 37; + + TraceLine (src, dest, true, GetEntity (), &tr); + + // if trace hit something, return false + if (tr.flFraction < 1.0) + return false; + + // now check same height on the other side of the bot... + src = pev->origin + (-g_pGlobals->v_right * 16) + Vector (0, 0, -36 + 63); + dest = src + normal * 32; + + // trace a line forward at maximum jump height... + TraceLine (src, dest, true, GetEntity (), &tr); + + // if trace hit something, return false + if (tr.flFraction < 1.0) + return false; + + // now trace from jump height upward to check for obstructions... + src = dest; + dest.z = dest.z + 37; + + TraceLine (src, dest, true, GetEntity (), &tr); + + // if trace hit something, return false + return tr.flFraction > 1.0; +} + +bool Bot::CanDuckUnder (const Vector &normal) +{ + // this function check if bot can duck under obstacle + + TraceResult tr; + Vector baseHeight; + + // convert current view angle to vectors for TraceLine math... + Vector duck = pev->angles; + duck.x = 0; // reset pitch to 0 (level horizontally) + duck.z = 0; // reset roll to 0 (straight up and down) + + MakeVectors (duck); + + // use center of the body first... + if (pev->flags & FL_DUCKING) + baseHeight = pev->origin + Vector (0, 0, -17); + else + baseHeight = pev->origin; + + Vector src = baseHeight; + Vector dest = src + normal * 32; + + // trace a line forward at duck height... + TraceLine (src, dest, true, GetEntity (), &tr); + + // if trace hit something, return false + if (tr.flFraction < 1.0) + return false; + + // now check same height to one side of the bot... + src = baseHeight + g_pGlobals->v_right * 16; + dest = src + normal * 32; + + // trace a line forward at duck height... + TraceLine (src, dest, true, GetEntity (), &tr); + + // if trace hit something, return false + if (tr.flFraction < 1.0) + return false; + + // now check same height on the other side of the bot... + src = baseHeight + (-g_pGlobals->v_right * 16); + dest = src + normal * 32; + + // trace a line forward at duck height... + TraceLine (src, dest, true, GetEntity (), &tr); + + // if trace hit something, return false + return tr.flFraction > 1.0; +} + +bool Bot::IsBlockedLeft (void) +{ + TraceResult tr; + int direction = 48; + + if (m_moveSpeed < 0) + direction = -48; + + MakeVectors (pev->angles); + + // do a trace to the left... + TraceLine (pev->origin, g_pGlobals->v_forward * direction - g_pGlobals->v_right * 48, true, GetEntity (), &tr); + + // check if the trace hit something... + if (tr.flFraction < 1.0 && strncmp ("func_door", STRING (tr.pHit->v.classname), 9) != 0) + return true; // bot's body will hit something + + return false; +} + +bool Bot::IsBlockedRight (void) +{ + TraceResult tr; + int direction = 48; + + if (m_moveSpeed < 0) + direction = -48; + + MakeVectors (pev->angles); + + // do a trace to the right... + TraceLine (pev->origin, pev->origin + g_pGlobals->v_forward * direction + g_pGlobals->v_right * 48, true, GetEntity (), &tr); + + // check if the trace hit something... + if ((tr.flFraction < 1.0) && (strncmp ("func_door", STRING (tr.pHit->v.classname), 9) != 0)) + return true; // bot's body will hit something + + return false; +} + +bool Bot::CheckWallOnLeft (void) +{ + TraceResult tr; + MakeVectors (pev->angles); + + TraceLine (pev->origin, pev->origin - g_pGlobals->v_right * 40, true, GetEntity (), &tr); + + // check if the trace hit something... + if (tr.flFraction < 1.0) + return true; + + return false; +} + +bool Bot::CheckWallOnRight (void) +{ + TraceResult tr; + MakeVectors (pev->angles); + + // do a trace to the right... + TraceLine (pev->origin, pev->origin + g_pGlobals->v_right * 40, true, GetEntity (), &tr); + + // check if the trace hit something... + if (tr.flFraction < 1.0) + return true; + + return false; +} + +bool Bot::IsDeadlyDrop (Vector targetOriginPos) +{ + // this function eturns if given location would hurt Bot with falling damage + + Vector botPos = pev->origin; + TraceResult tr; + + Vector move ((targetOriginPos - botPos).ToYaw (), 0, 0); + MakeVectors (move); + + Vector direction = (targetOriginPos - botPos).Normalize (); // 1 unit long + Vector check = botPos; + Vector down = botPos; + + down.z = down.z - 1000.0; // straight down 1000 units + + TraceHull (check, down, true, head_hull, GetEntity (), &tr); + + if (tr.flFraction > 0.036) // We're not on ground anymore? + tr.flFraction = 0.036; + + float height; + float lastHeight = tr.flFraction * 1000.0; // height from ground + + float distance = (targetOriginPos - check).GetLength (); // distance from goal + + while (distance > 16.0) + { + check = check + direction * 16.0; // move 10 units closer to the goal... + + down = check; + down.z = down.z - 1000.0; // straight down 1000 units + + TraceHull (check, down, true, head_hull, GetEntity (), &tr); + + if (tr.fStartSolid) // Wall blocking? + return false; + + height = tr.flFraction * 1000.0; // height from ground + + if (lastHeight < height - 100) // Drops more than 100 Units? + return true; + + lastHeight = height; + distance = (targetOriginPos - check).GetLength (); // distance from goal + } + return false; +} + +void Bot::ChangePitch (float speed) +{ + // this function turns a bot towards its ideal_pitch + + float idealPitch = AngleNormalize (pev->idealpitch); + + if (yb_aim_method.GetInt () == 1) + { + // turn to the ideal angle immediately + pev->v_angle.x = idealPitch; + pev->angles.x = -idealPitch / 3; + + return; + } + + float curent = AngleNormalize (pev->v_angle.x); + + // turn from the current v_angle pitch to the idealpitch by selecting + // the quickest way to turn to face that direction + + // find the difference in the curent and ideal angle + float normalizePitch = AngleNormalize (idealPitch - curent); + + if (normalizePitch > 0) + { + if (normalizePitch > speed) + normalizePitch = speed; + } + else + { + if (normalizePitch < -speed) + normalizePitch = -speed; + } + + pev->v_angle.x = AngleNormalize (curent + normalizePitch); + + if (pev->v_angle.x > 89.9) + pev->v_angle.x = 89.9; + + if (pev->v_angle.x < -89.9) + pev->v_angle.x = -89.9; + + pev->angles.x = -pev->v_angle.x / 3; +} + +void Bot::ChangeYaw (float speed) +{ + // this function turns a bot towards its ideal_yaw + + float idealPitch = AngleNormalize (pev->ideal_yaw); + + if (yb_aim_method.GetInt () == 1) + { + // turn to the ideal angle immediately + pev->angles.y = (pev->v_angle.y = idealPitch); + return; + } + + float curent = AngleNormalize (pev->v_angle.y); + + // turn from the current v_angle yaw to the ideal_yaw by selecting + // the quickest way to turn to face that direction + + // find the difference in the curent and ideal angle + float normalizePitch = AngleNormalize (idealPitch - curent); + + if (normalizePitch > 0) + { + if (normalizePitch > speed) + normalizePitch = speed; + } + else + { + if (normalizePitch < -speed) + normalizePitch = -speed; + } + pev->v_angle.y = AngleNormalize (curent + normalizePitch); + pev->angles.y = pev->v_angle.y; +} + +int Bot::GetAimingWaypoint (void) +{ + // Find a good WP to look at when camping + + int count = 0, indeces[3]; + float distTab[3]; + uint16 visibility[3]; + + int currentWaypoint = g_waypoint->FindNearest (pev->origin); + + for (int i = 0; i < g_numWaypoints; i++) + { + if (currentWaypoint == i || !g_waypoint->IsVisible (currentWaypoint, i)) + continue; + + Path *path = g_waypoint->GetPath (i); + + if (count < 3) + { + indeces[count] = i; + + distTab[count] = (pev->origin - path->origin).GetLengthSquared (); + visibility[count] = path->vis.crouch + path->vis.stand; + + count++; + } + else + { + float distance = (pev->origin - path->origin).GetLengthSquared (); + uint16 visBits = path->vis.crouch + path->vis.stand; + + for (int j = 0; j < 3; j++) + { + if (visBits >= visibility[j] && distance > distTab[j]) + { + indeces[j] = i; + + distTab[j] = distance; + visibility[j] = visBits; + + break; + } + } + } + } + count--; + + if (count >= 0) + return indeces[g_randGen.Long (0, count)]; + + return g_randGen.Long (0, g_numWaypoints - 1); +} + +void Bot::FacePosition (void) +{ + // adjust all body and view angles to face an absolute vector + Vector direction = (m_lookAt - EyePosition ()).ToAngles (); + direction = direction + pev->punchangle * m_skill / 100.0; + direction.x *= -1.0; // invert for engine + + Vector deviation = (direction - pev->v_angle); + + direction.ClampAngles (); + deviation.ClampAngles (); + + int forcedTurnMethod = yb_aim_method.GetInt (); + + if ((forcedTurnMethod < 1) || (forcedTurnMethod > 3)) + forcedTurnMethod = 3; + + if (forcedTurnMethod == 1) + pev->v_angle = direction; + else if (forcedTurnMethod == 2) + { + float turnSkill = static_cast (0.05 * m_skill) + 0.5; + float aimSpeed = 0.17 + turnSkill * 0.06; + float frameCompensation = g_pGlobals->frametime * 1000 * 0.01; + + if ((m_aimFlags & AIM_ENEMY) && !(m_aimFlags & AIM_ENTITY)) + aimSpeed *= 1.75; + + float momentum = (1.0 - aimSpeed) * 0.5; + + pev->pitch_speed = ((pev->pitch_speed * momentum) + aimSpeed * deviation.x * (1.0 - momentum)) * frameCompensation; + pev->yaw_speed = ((pev->yaw_speed * momentum) + aimSpeed * deviation.y * (1.0 - momentum)) * frameCompensation; + + if (m_skill < 100) + { + // influence of y movement on x axis, based on skill (less influence than x on y since it's + // easier and more natural for the bot to "move its mouse" horizontally than vertically) + pev->pitch_speed += pev->yaw_speed / (10.0 * turnSkill); + pev->yaw_speed += pev->pitch_speed / (10.0 * turnSkill); + } + + pev->v_angle.x += pev->pitch_speed; // change pitch angles + pev->v_angle.y += pev->yaw_speed; // change yaw angles + } + else if (forcedTurnMethod == 3) + { + Vector springStiffness (yb_aim_spring_stiffness_x.GetFloat (), yb_aim_spring_stiffness_y.GetFloat (), 0); + Vector damperCoefficient (yb_aim_damper_coefficient_x.GetFloat (), yb_aim_damper_coefficient_y.GetFloat (), 0); + Vector influence (yb_aim_influence_x_on_y.GetFloat (), yb_aim_influence_y_on_x.GetFloat (), 0); + Vector randomization (yb_aim_deviation_x.GetFloat (), yb_aim_deviation_y.GetFloat (), 0); + + Vector stiffness = nullvec; + Vector randomize = nullvec; + + m_idealAngles = direction.SkipZ (); + m_targetOriginAngularSpeed.ClampAngles (); + m_idealAngles.ClampAngles (); + + if (yb_hardcore_mode.GetBool ()) + { + influence = influence * m_skillOffset; + randomization = randomization * m_skillOffset; + } + + if (m_aimFlags & (AIM_ENEMY | AIM_ENTITY | AIM_GRENADE | AIM_LAST_ENEMY) || GetTaskId () == TASK_SHOOTBREAKABLE) + { + m_playerTargetTime = GetWorldTime (); + m_randomizedIdealAngles = m_idealAngles; + + if (IsValidPlayer (m_enemy)) + { + m_targetOriginAngularSpeed = ((m_enemyOrigin - pev->origin + 1.5 * m_frameInterval * (1.0 * m_enemy->v.velocity) - 0.0 * g_pGlobals->frametime * pev->velocity).ToAngles () - (m_enemyOrigin - pev->origin).ToAngles ()) * 0.45 * yb_aim_target_anticipation_ratio.GetFloat () * static_cast (m_skill / 100); + + if (m_angularDeviation.GetLength () < 5.0) + springStiffness = (5.0 - m_angularDeviation.GetLength ()) * 0.25 * static_cast (m_skill / 100) * springStiffness + springStiffness; + + m_targetOriginAngularSpeed.x = -m_targetOriginAngularSpeed.x; + + if ((pev->fov < 90) && (m_angularDeviation.GetLength () >= 5.0)) + springStiffness = springStiffness * 2; + + m_targetOriginAngularSpeed.ClampAngles (); + } + else + m_targetOriginAngularSpeed = nullvec; + + + if (m_skill >= 80) + stiffness = springStiffness; + else + stiffness = springStiffness * (0.2 + m_skill / 125.0); + } + else + { + // is it time for bot to randomize the aim direction again (more often where moving) ? + if (m_randomizeAnglesTime < GetWorldTime () && ((pev->velocity.GetLength () > 1.0 && m_angularDeviation.GetLength () < 5.0) || m_angularDeviation.GetLength () < 1.0)) + { + // is the bot standing still ? + if (pev->velocity.GetLength () < 1.0) + randomize = randomization * 0.2; // randomize less + else + randomize = randomization; + + // randomize targeted location a bit (slightly towards the ground) + m_randomizedIdealAngles = m_idealAngles + Vector (g_randGen.Float (-randomize.x * 0.5, randomize.x * 1.5), g_randGen.Float (-randomize.y, randomize.y), 0); + + // set next time to do this + m_randomizeAnglesTime = GetWorldTime () + g_randGen.Float (0.4, yb_aim_offset_delay.GetFloat ()); + } + float stiffnessMultiplier = yb_aim_notarget_slowdown_ratio.GetFloat (); + + // take in account whether the bot was targeting someone in the last N seconds + if (GetWorldTime () - (m_playerTargetTime + yb_aim_offset_delay.GetFloat ()) < yb_aim_notarget_slowdown_ratio.GetFloat () * 10.0) + { + stiffnessMultiplier = 1.0 - (GetWorldTime () - m_timeLastFired) * 0.1; + + // don't allow that stiffness multiplier less than zero + if (stiffnessMultiplier < 0.0) + stiffnessMultiplier = 0.5; + } + + // also take in account the remaining deviation (slow down the aiming in the last 10°) + if (!yb_hardcore_mode.GetBool () && (m_angularDeviation.GetLength () < 10.0)) + stiffnessMultiplier *= m_angularDeviation.GetLength () * 0.1; + + // slow down even more if we are not moving + if (!yb_hardcore_mode.GetBool () && pev->velocity.GetLength () < 1.0 && GetTaskId () != TASK_CAMP && GetTaskId () != TASK_ATTACK) + stiffnessMultiplier *= 0.5; + + // but don't allow getting below a certain value + if (stiffnessMultiplier < 0.35) + stiffnessMultiplier = 0.35; + + stiffness = springStiffness * stiffnessMultiplier; // increasingly slow aim + + // no target means no angular speed to take in account + m_targetOriginAngularSpeed = nullvec; + } + // compute randomized angle deviation this time + m_angularDeviation = m_randomizedIdealAngles + m_targetOriginAngularSpeed - pev->v_angle; + m_angularDeviation.ClampAngles (); + + // spring/damper model aiming + m_aimSpeed.x = (stiffness.x * m_angularDeviation.x) - (damperCoefficient.x * m_aimSpeed.x); + m_aimSpeed.y = (stiffness.y * m_angularDeviation.y) - (damperCoefficient.y * m_aimSpeed.y); + + // influence of y movement on x axis and vice versa (less influence than x on y since it's + // easier and more natural for the bot to "move its mouse" horizontally than vertically) + m_aimSpeed.x += m_aimSpeed.y * influence.y; + m_aimSpeed.y += m_aimSpeed.x * influence.x; + + // move the aim cursor + pev->v_angle = pev->v_angle + m_frameInterval * Vector (m_aimSpeed.x, m_aimSpeed.y, 0); + pev->v_angle.ClampAngles (); + } + + // set the body angles to point the gun correctly + pev->angles.x = -pev->v_angle.x * (1.0 / 3.0); + pev->angles.y = pev->v_angle.y; + + pev->v_angle.ClampAngles (); + pev->angles.ClampAngles (); + + pev->angles.z = pev->v_angle.z = 0.0; // ignore Z component +} + +void Bot::SetStrafeSpeed (Vector moveDir, float strafeSpeed) +{ + MakeVectors (pev->angles); + + Vector los = (moveDir - pev->origin).Normalize2D (); + float dot = los | g_pGlobals->v_forward.SkipZ (); + + if (dot > 0 && !CheckWallOnRight ()) + m_strafeSpeed = strafeSpeed; + else if (!CheckWallOnLeft ()) + m_strafeSpeed = -strafeSpeed; +} + +int Bot::FindLoosedBomb (void) +{ + // this function tries to find droped c4 on the defuse scenario map and returns nearest to it waypoint + + if ((GetTeam (GetEntity ()) != TEAM_TF) || !(g_mapType & MAP_DE)) + return -1; // don't search for bomb if the player is CT, or it's not defusing bomb + + edict_t *bombEntity = NULL; // temporaly pointer to bomb + + // search the bomb on the map + while (!FNullEnt (bombEntity = FIND_ENTITY_BY_CLASSNAME (bombEntity, "weaponbox"))) + { + if (strcmp (STRING (bombEntity->v.model) + 9, "backpack.mdl") == 0) + { + int nearestIndex = g_waypoint->FindNearest (GetEntityOrigin (bombEntity)); + + if ((nearestIndex >= 0) && (nearestIndex < g_numWaypoints)) + return nearestIndex; + + break; + } + } + return -1; +} + +int Bot::FindPlantedBomb (void) +{ + // this function tries to find planted c4 on the defuse scenario map and returns nearest to it waypoint + + if ((GetTeam (GetEntity ()) != TEAM_TF) || !(g_mapType & MAP_DE)) + return -1; // don't search for bomb if the player is CT, or it's not defusing bomb + + edict_t *bombEntity = NULL; // temporaly pointer to bomb + + // search the bomb on the map + while (!FNullEnt (bombEntity = FIND_ENTITY_BY_CLASSNAME (bombEntity, "grenade"))) + { + if (strcmp (STRING (bombEntity->v.model) + 9, "c4.mdl") == 0) + { + int nearestIndex = g_waypoint->FindNearest (GetEntityOrigin (bombEntity)); + + if ((nearestIndex >= 0) && (nearestIndex < g_numWaypoints)) + return nearestIndex; + + break; + } + } + return -1; +} + +bool Bot::IsWaypointUsed (int index) +{ + if (index < 0 || index >= g_numWaypoints) + return true; + + // first check if current waypoint of one of the bots is index waypoint + for (int i = 0; i < GetMaxClients (); i++) + { + Bot *bot = g_botManager->GetBot (i); + + if (bot == NULL || bot == this) + continue; + + // check if this waypoint is already used + if (IsAlive (bot->GetEntity ()) && (bot->m_currentWaypointIndex == index || bot->GetTask ()->data == index || (g_waypoint->GetPath (index)->origin - bot->pev->origin).GetLength2D () < 80.0)) + return true; + } + + // secondary check waypoint radius for any player + edict_t *ent = NULL; + + // search player entities in waypoint radius + while (!FNullEnt (ent = FIND_ENTITY_IN_SPHERE (ent, g_waypoint->GetPath (index)->origin, 80.0))) + { + if (ent != GetEntity () && IsValidBot (ent) && IsAlive (ent)) + return true; + } + return false; +} + +edict_t *Bot::FindNearestButton (const char *targetName) +{ + // this function tries to find nearest to current bot button, and returns pointer to + // it's entity, also here must be specified the target, that button must open. + + if (IsNullString (targetName)) + return NULL; + + float nearestDistance = FLT_MAX; + edict_t *searchEntity = NULL, *foundEntity = NULL; + + // find the nearest button which can open our target + while (!FNullEnt(searchEntity = FIND_ENTITY_BY_TARGET(searchEntity, targetName))) + { + Vector entityOrign = GetEntityOrigin (searchEntity); + + // check if this place safe + if (!IsDeadlyDrop (entityOrign)) + { + float distance = (pev->origin - entityOrign).GetLengthSquared (); + + // check if we got more close button + if (distance <= nearestDistance) + { + nearestDistance = distance; + foundEntity = searchEntity; + } + } + } + return foundEntity; +} \ No newline at end of file diff --git a/source/netmsg.cpp b/source/netmsg.cpp new file mode 100644 index 0000000..d2e2a0d --- /dev/null +++ b/source/netmsg.cpp @@ -0,0 +1,481 @@ +// +// Copyright (c) 2014, by YaPB Development Team. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// Version: $Id:$ +// + +#include + +NetworkMsg::NetworkMsg (void) +{ + m_message = NETMSG_UNDEFINED; + m_state = 0; + m_bot = NULL; + + for (int i = 0; i < NETMSG_BOTVOICE; i++) + m_registerdMessages[i] = -1; +} + +void NetworkMsg::HandleMessageIfRequired (int messageType, int requiredType) +{ + if (messageType == m_registerdMessages[requiredType]) + SetMessage (requiredType); +} + +void NetworkMsg::Execute (void *p) +{ + if (m_message == NETMSG_UNDEFINED) + return; // no message or not for bot, return + + // some needed variables + static byte r, g, b; + static byte enabled; + + static int damageArmor, damageTaken, damageBits; + static int killerIndex, victimIndex, playerIndex; + static int index, numPlayers; + static int state, id, clip; + + static Vector damageOrigin; + static WeaponProperty weaponProp; + + // now starts of netmessage execution + switch (m_message) + { + case NETMSG_VGUI: + // this message is sent when a VGUI menu is displayed. + + if (m_state == 0) + { + switch (PTR_TO_INT (p)) + { + case VMS_TEAM: + m_bot->m_startAction = GSM_TEAM_SELECT; + break; + + case VMS_TF: + case VMS_CT: + m_bot->m_startAction = GSM_CLASS_SELECT; + break; + } + } + break; + + case NETMSG_SHOWMENU: + // this message is sent when a text menu is displayed. + + if (m_state < 3) // ignore first 3 fields of message + break; + + if (strcmp (PTR_TO_STR (p), "#Team_Select") == 0) // team select menu? + m_bot->m_startAction = GSM_TEAM_SELECT; + else if (strcmp (PTR_TO_STR (p), "#Team_Select_Spect") == 0) // team select menu? + m_bot->m_startAction = GSM_TEAM_SELECT; + else if (strcmp (PTR_TO_STR (p), "#IG_Team_Select_Spect") == 0) // team select menu? + m_bot->m_startAction = GSM_TEAM_SELECT; + else if (strcmp (PTR_TO_STR (p), "#IG_Team_Select") == 0) // team select menu? + m_bot->m_startAction = GSM_TEAM_SELECT; + else if (strcmp (PTR_TO_STR (p), "#IG_VIP_Team_Select") == 0) // team select menu? + m_bot->m_startAction = GSM_TEAM_SELECT; + else if (strcmp (PTR_TO_STR (p), "#IG_VIP_Team_Select_Spect") == 0) // team select menu? + m_bot->m_startAction = GSM_TEAM_SELECT; + else if (strcmp (PTR_TO_STR (p), "#Terrorist_Select") == 0) // T model select? + m_bot->m_startAction = GSM_CLASS_SELECT; + else if (strcmp (PTR_TO_STR (p), "#CT_Select") == 0) // CT model select menu? + m_bot->m_startAction = GSM_CLASS_SELECT; + + break; + + case NETMSG_WEAPONLIST: + // this message is sent when a client joins the game. All of the weapons are sent with the weapon ID and information about what ammo is used. + + switch (m_state) + { + case 0: + strcpy (weaponProp.className, PTR_TO_STR (p)); + break; + + case 1: + weaponProp.ammo1 = PTR_TO_INT (p); // ammo index 1 + break; + + case 2: + weaponProp.ammo1Max = PTR_TO_INT (p); // max ammo 1 + break; + + case 5: + weaponProp.slotID = PTR_TO_INT (p); // slot for this weapon + break; + + case 6: + weaponProp.position = PTR_TO_INT (p); // position in slot + break; + + case 7: + weaponProp.id = PTR_TO_INT (p); // weapon ID + break; + + case 8: + weaponProp.flags = PTR_TO_INT (p); // flags for weapon (WTF???) + g_weaponDefs[weaponProp.id] = weaponProp; // store away this weapon with it's ammo information... + break; + } + break; + + case NETMSG_CURWEAPON: + // this message is sent when a weapon is selected (either by the bot chosing a weapon or by the server auto assigning the bot a weapon). In CS it's also called when Ammo is increased/decreased + + switch (m_state) + { + case 0: + state = PTR_TO_INT (p); // state of the current weapon (WTF???) + break; + + case 1: + id = PTR_TO_INT (p); // weapon ID of current weapon + break; + + case 2: + clip = PTR_TO_INT (p); // ammo currently in the clip for this weapon + + if (id <= 31) + { + if (state != 0) + m_bot->m_currentWeapon = id; + + // ammo amount decreased ? must have fired a bullet... + if (id == m_bot->m_currentWeapon && m_bot->m_ammoInClip[id] > clip) + { + // time fired with in burst firing time ? + if (m_bot->m_timeLastFired + 1.0 > GetWorldTime ()) + m_bot->m_burstShotsFired++; + + m_bot->m_timeLastFired = GetWorldTime (); // remember the last bullet time + } + m_bot->m_ammoInClip[id] = clip; + } + break; + } + break; + + case NETMSG_AMMOX: + // this message is sent whenever ammo amounts are adjusted (up or down). NOTE: Logging reveals that CS uses it very unreliable! + + switch (m_state) + { + case 0: + index = PTR_TO_INT (p); // ammo index (for type of ammo) + break; + + case 1: + m_bot->m_ammo[index] = PTR_TO_INT (p); // store it away + break; + } + break; + + case NETMSG_AMMOPICKUP: + // this message is sent when the bot picks up some ammo (AmmoX messages are also sent so this message is probably + // not really necessary except it allows the HUD to draw pictures of ammo that have been picked up. The bots + // don't really need pictures since they don't have any eyes anyway. + + switch (m_state) + { + case 0: + index = PTR_TO_INT (p); + break; + + case 1: + m_bot->m_ammo[index] = PTR_TO_INT (p); + break; + } + break; + + case NETMSG_DAMAGE: + // this message gets sent when the bots are getting damaged. + + switch (m_state) + { + case 0: + damageArmor = PTR_TO_INT (p); + break; + + case 1: + damageTaken = PTR_TO_INT (p); + break; + + case 2: + damageBits = PTR_TO_INT (p); + + if (m_bot && damageArmor > 0 || damageTaken > 0) + m_bot->TakeDamage (m_bot->pev->dmg_inflictor, damageTaken, damageArmor, damageBits); + break; + } + break; + + case NETMSG_MONEY: + // this message gets sent when the bots money amount changes + + if (m_state == 0) + m_bot->m_moneyAmount = PTR_TO_INT (p); // amount of money + break; + + case NETMSG_STATUSICON: + switch (m_state) + { + case 0: + enabled = PTR_TO_BYTE (p); + break; + + case 1: + if (strcmp (PTR_TO_STR (p), "defuser") == 0) + m_bot->m_hasDefuser = (enabled != 0); + else if (strcmp (PTR_TO_STR (p), "buyzone") == 0) + { + m_bot->m_inBuyZone = (enabled != 0); + + // try to equip in buyzone + m_bot->EquipInBuyzone (0); + } + else if (strcmp (PTR_TO_STR (p), "vipsafety") == 0) + m_bot->m_inVIPZone = (enabled != 0); + else if (strcmp (PTR_TO_STR (p), "c4") == 0) + m_bot->m_inBombZone = (enabled == 2); + break; + } + break; + + case NETMSG_DEATH: // this message sends on death + switch (m_state) + { + case 0: + killerIndex = PTR_TO_INT (p); + break; + + case 1: + victimIndex = PTR_TO_INT (p); + break; + + case 2: + if (killerIndex != 0 && killerIndex != victimIndex) + { + edict_t *killer = INDEXENT (killerIndex); + edict_t *victim = INDEXENT (victimIndex); + + if (FNullEnt (killer) || FNullEnt (victim)) + break; + + // need to send congrats on well placed shot + for (int i = 0; i < GetMaxClients (); i++) + { + Bot *bot = g_botManager->GetBot (i); + + if (bot != NULL && IsAlive (bot->GetEntity ()) && killer != bot->GetEntity () && bot->EntityIsVisible (victim->v.origin) && GetTeam (killer) == GetTeam (bot->GetEntity ()) && GetTeam (killer) != GetTeam (victim)) + { + if (killer == g_hostEntity) + bot->HandleChatterMessage ("#Bot_NiceShotCommander"); + else + bot->HandleChatterMessage ("#Bot_NiceShotPall"); + + break; + } + } + + // notice nearby to victim teammates, that attacker is near + for (int i = 0; i < GetMaxClients (); i++) + { + Bot *bot = g_botManager->GetBot (i); + + if (bot != NULL && IsAlive (bot->GetEntity ()) && GetTeam (bot->GetEntity ()) == GetTeam (victim) && IsVisible (killer->v.origin, bot->GetEntity ()) && FNullEnt (bot->m_enemy) && GetTeam (killer) != GetTeam (victim)) + { + bot->m_actualReactionTime = 0.0; + bot->m_seeEnemyTime = GetWorldTime (); + bot->m_enemy = killer; + bot->m_lastEnemy = killer; + bot->m_lastEnemyOrigin = killer->v.origin; + } + } + + Bot *bot = g_botManager->GetBot (killer); + + // is this message about a bot who killed somebody? + if (bot != NULL) + bot->m_lastVictim = victim; + + else // did a human kill a bot on his team? + { + Bot *bot = g_botManager->GetBot (victim); + + if (bot != NULL) + { + if (GetTeam (killer) == GetTeam (victim)) + bot->m_voteKickIndex = killerIndex; + + bot->m_notKilled = false; + } + } + } + break; + } + break; + + case NETMSG_SCREENFADE: // this message gets sent when the Screen fades (Flashbang) + switch (m_state) + { + case 3: + r = PTR_TO_BYTE (p); + break; + + case 4: + g = PTR_TO_BYTE (p); + break; + + case 5: + b = PTR_TO_BYTE (p); + break; + + case 6: + m_bot->TakeBlinded (Vector (r, g, b), PTR_TO_BYTE (p)); + break; + } + break; + + case NETMSG_HLTV: // round restart in steam cs + switch (m_state) + { + case 0: + numPlayers = PTR_TO_INT (p); + break; + + case 1: + if (numPlayers == 0 && PTR_TO_INT (p) == 0) + RoundInit (); + break; + } + break; + + + case NETMSG_RESETHUD: +#if 0 + if (m_bot != NULL) + m_bot->NewRound (); +#endif + break; + + case NETMSG_TEXTMSG: + if (m_state == 1) + { + if (FStrEq (PTR_TO_STR (p), "#CTs_Win") || + FStrEq (PTR_TO_STR (p), "#Bomb_Defused") || + FStrEq (PTR_TO_STR (p), "#Terrorists_Win") || + FStrEq (PTR_TO_STR (p), "#Round_Draw") || + FStrEq (PTR_TO_STR (p), "#All_Hostages_Rescued") || + FStrEq (PTR_TO_STR (p), "#Target_Saved") || + FStrEq (PTR_TO_STR (p), "#Hostages_Not_Rescued") || + FStrEq (PTR_TO_STR (p), "#Terrorists_Not_Escaped") || + FStrEq (PTR_TO_STR (p), "#VIP_Not_Escaped") || + FStrEq (PTR_TO_STR (p), "#Escaping_Terrorists_Neutralized") || + FStrEq (PTR_TO_STR (p), "#VIP_Assassinated") || + FStrEq (PTR_TO_STR (p), "#VIP_Escaped") || + FStrEq (PTR_TO_STR (p), "#Terrorists_Escaped") || + FStrEq (PTR_TO_STR (p), "#CTs_PreventEscape") || + FStrEq (PTR_TO_STR (p), "#Target_Bombed") || + FStrEq (PTR_TO_STR (p), "#Game_Commencing") || + FStrEq (PTR_TO_STR (p), "#Game_will_restart_in")) + { + g_roundEnded = true; + + if (FStrEq (PTR_TO_STR (p), "#Game_Commencing")) + g_isCommencing = true; + + if (FStrEq (PTR_TO_STR (p), "#CTs_Win")) + g_botManager->SetLastWinner (TEAM_CF); // update last winner for economics + + if (FStrEq (PTR_TO_STR (p), "#Terrorists_Win")) + g_botManager->SetLastWinner (TEAM_TF); // update last winner for economics + + g_waypoint->SetBombPosition (true); + } + else if (!g_bombPlanted && FStrEq (PTR_TO_STR (p), "#Bomb_Planted")) + { + g_bombPlanted = g_bombSayString = true; + g_timeBombPlanted = GetWorldTime (); + + for (int i = 0; i < GetMaxClients (); i++) + { + Bot *bot = g_botManager->GetBot (i); + + if (bot != NULL && IsAlive (bot->GetEntity ())) + { + bot->DeleteSearchNodes (); + bot->ResetTasks (); + + if (g_randGen.Long (0, 100) < 85 && GetTeam (bot->GetEntity ()) == TEAM_CF) + bot->ChatterMessage (Chatter_WhereIsTheBomb); + } + } + g_waypoint->SetBombPosition (); + } + else if (m_bot != NULL && FStrEq (PTR_TO_STR (p), "#Switch_To_BurstFire")) + m_bot->m_weaponBurstMode = BM_ON; + else if (m_bot != NULL && FStrEq (PTR_TO_STR (p), "#Switch_To_SemiAuto")) + m_bot->m_weaponBurstMode = BM_OFF; + } + break; + + case NETMSG_SCOREINFO: + switch (m_state) + { + case 0: + playerIndex = PTR_TO_INT (p); + break; + + case 4: + if (playerIndex >= 0 && playerIndex <= GetMaxClients ()) + { + if (PTR_TO_INT (p) == 1) + g_clients[playerIndex - 1].realTeam = TEAM_TF; + else if (PTR_TO_INT (p) == 2) + g_clients[playerIndex - 1].realTeam = TEAM_CF; + + if (yb_csdm_mode.GetInt () == 2) + g_clients[playerIndex - 1].team = playerIndex; + else + g_clients[playerIndex - 1].team = g_clients[playerIndex - 1].realTeam; + } + break; + } + break; + + case NETMSG_BARTIME: + if (m_state == 0) + { + if (PTR_TO_INT (p) > 0) + m_bot->m_hasProgressBar = true; // the progress bar on a hud + else if (PTR_TO_INT (p) == 0) + m_bot->m_hasProgressBar = false; // no progress bar or disappeared + } + break; + + default: + AddLogEntry (true, LL_FATAL, "Network message handler error. Call to unrecognized message id (%d).\n", m_message); + } + m_state++; // and finally update network message state +} diff --git a/source/support.cpp b/source/support.cpp new file mode 100644 index 0000000..e8d06ef --- /dev/null +++ b/source/support.cpp @@ -0,0 +1,1744 @@ +// +// Copyright (c) 2014, by YaPB Development Team. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// Version: $Id:$ +// + +#include + +// +// TODO: +// clean up the code. +// create classes: Tracer, PrintManager, GameManager +// +ConVar yb_listenserver_welcome ("yb_listenserver_welcome", "1"); + +ConVar mp_roundtime ("mp_roundtime", NULL, VT_NOREGISTER); +ConVar mp_freezetime ("mp_freezetime", NULL, VT_NOREGISTER); + +void TraceLine (const Vector &start, const Vector &end, bool ignoreMonsters, bool ignoreGlass, edict_t *ignoreEntity, TraceResult *ptr) +{ + // this function traces a line dot by dot, starting from vecStart in the direction of vecEnd, + // ignoring or not monsters (depending on the value of IGNORE_MONSTERS, true or false), and stops + // at the first obstacle encountered, returning the results of the trace in the TraceResult structure + // ptr. Such results are (amongst others) the distance traced, the hit surface, the hit plane + // vector normal, etc. See the TraceResult structure for details. This function allows to specify + // whether the trace starts "inside" an entity's polygonal model, and if so, to specify that entity + // in ignoreEntity in order to ignore it as a possible obstacle. + // this is an overloaded prototype to add IGNORE_GLASS in the same way as IGNORE_MONSTERS work. + + (*g_engfuncs.pfnTraceLine) (start, end, (ignoreMonsters ? TRUE : FALSE) | (ignoreGlass ? 0x100 : 0), ignoreEntity, ptr); +} + +void TraceLine (const Vector &start, const Vector &end, bool ignoreMonsters, edict_t *ignoreEntity, TraceResult *ptr) +{ + // this function traces a line dot by dot, starting from vecStart in the direction of vecEnd, + // ignoring or not monsters (depending on the value of IGNORE_MONSTERS, true or false), and stops + // at the first obstacle encountered, returning the results of the trace in the TraceResult structure + // ptr. Such results are (amongst others) the distance traced, the hit surface, the hit plane + // vector normal, etc. See the TraceResult structure for details. This function allows to specify + // whether the trace starts "inside" an entity's polygonal model, and if so, to specify that entity + // in ignoreEntity in order to ignore it as a possible obstacle. + + (*g_engfuncs.pfnTraceLine) (start, end, ignoreMonsters ? TRUE : FALSE, ignoreEntity, ptr); +} + +void TraceHull (const Vector &start, const Vector &end, bool ignoreMonsters, int hullNumber, edict_t *ignoreEntity, TraceResult *ptr) +{ + // this function traces a hull dot by dot, starting from vecStart in the direction of vecEnd, + // ignoring or not monsters (depending on the value of IGNORE_MONSTERS, true or + // false), and stops at the first obstacle encountered, returning the results + // of the trace in the TraceResult structure ptr, just like TraceLine. Hulls that can be traced + // (by parameter hull_type) are point_hull (a line), head_hull (size of a crouching player), + // human_hull (a normal body size) and large_hull (for monsters?). Not all the hulls in the + // game can be traced here, this function is just useful to give a relative idea of spatial + // reachability (i.e. can a hostage pass through that tiny hole ?) Also like TraceLine, this + // function allows to specify whether the trace starts "inside" an entity's polygonal model, + // and if so, to specify that entity in ignoreEntity in order to ignore it as an obstacle. + + (*g_engfuncs.pfnTraceHull) (start, end, ignoreMonsters ? TRUE : FALSE, hullNumber, ignoreEntity, ptr); +} + +uint16 FixedUnsigned16 (float value, float scale) +{ + int output = (static_cast (value * scale)); + + if (output < 0) + output = 0; + + if (output > 0xffff) + output = 0xffff; + + return static_cast (output); +} + +short FixedSigned16 (float value, float scale) +{ + int output = (static_cast (value * scale)); + + if (output > 32767) + output = 32767; + + if (output < -32768) + output = -32768; + + return static_cast (output); +} + +char *FormatBuffer (char *format, ...) +{ + va_list ap; + static char staticBuffer[3072]; + + va_start (ap, format); + vsprintf (staticBuffer, format, ap); + va_end (ap); + + return &staticBuffer[0]; +} + +bool IsAlive (edict_t *ent) +{ + if (FNullEnt (ent)) + return false; // reliability check + + return (ent->v.deadflag == DEAD_NO) && (ent->v.health > 0) && (ent->v.movetype != MOVETYPE_NOCLIP); +} + +float GetShootingConeDeviation (edict_t *ent, Vector *position) +{ + const Vector &dir = (*position - (ent->v.origin + ent->v.view_ofs)).Normalize (); + MakeVectors (ent->v.v_angle); + + // he's facing it, he meant it + return g_pGlobals->v_forward | dir; +} + +bool IsInViewCone (Vector origin, edict_t *ent) +{ + MakeVectors (ent->v.v_angle); + + if (((origin - (ent->v.origin + ent->v.view_ofs)).Normalize () | g_pGlobals->v_forward) >= cosf (((ent->v.fov > 0 ? ent->v.fov : 90.0) / 2) * Math::MATH_PI / 180.0)) + return true; + + return false; +} + +bool IsVisible (const Vector &origin, edict_t *ent) +{ + if (FNullEnt (ent)) + return false; + + TraceResult tr; + TraceLine (ent->v.origin + ent->v.view_ofs, origin, true, true, ent, &tr); + + if (tr.flFraction != 1.0) + return false; // line of sight is not established + + return true; // line of sight is valid. +} + +Vector GetEntityOrigin (edict_t *ent) +{ + // this expanded function returns the vector origin of a bounded entity, assuming that any + // entity that has a bounding box has its center at the center of the bounding box itself. + + if (FNullEnt (ent)) + return nullvec; + + if (ent->v.origin == nullvec) + return ent->v.absmin + (ent->v.size * 0.5); + + return ent->v.origin; +} + +void DisplayMenuToClient (edict_t *ent, MenuText *menu) +{ + if (!IsValidPlayer (ent)) + return; + + int clientIndex = ENTINDEX (ent) - 1; + + if (menu != NULL) + { + String tempText = String (menu->menuText); + tempText.Replace ("\v", "\n"); + + char *text = g_localizer->TranslateInput (tempText.ToString ()); + tempText = String (text); + + // make menu looks best + for (int i = 0; i <= 9; i++) + tempText.Replace (FormatBuffer ("%d.", i), FormatBuffer ("\\r%d.\\w", i)); + + text = (char *) tempText.ToString (); + + while (strlen (text) >= 64) + { + MESSAGE_BEGIN (MSG_ONE_UNRELIABLE, g_netMsg->GetId (NETMSG_SHOWMENU), NULL, ent); + WRITE_SHORT (menu->validSlots); + WRITE_CHAR (-1); + WRITE_BYTE (1); + + for (int i = 0; i <= 63; i++) + WRITE_CHAR (text[i]); + + MESSAGE_END (); + + text += 64; + } + + MESSAGE_BEGIN (MSG_ONE_UNRELIABLE, g_netMsg->GetId (NETMSG_SHOWMENU), NULL, ent); + WRITE_SHORT (menu->validSlots); + WRITE_CHAR (-1); + WRITE_BYTE (0); + WRITE_STRING (text); + MESSAGE_END(); + + g_clients[clientIndex].menu = menu; + } + else + { + MESSAGE_BEGIN (MSG_ONE_UNRELIABLE, g_netMsg->GetId (NETMSG_SHOWMENU), NULL, ent); + WRITE_SHORT (0); + WRITE_CHAR (0); + WRITE_BYTE (0); + WRITE_STRING (""); + MESSAGE_END(); + + g_clients[clientIndex].menu = NULL; + } + CLIENT_COMMAND (ent, "speak \"player/geiger1\"\n"); // Stops others from hearing menu sounds.. +} + +void DecalTrace (entvars_t *pev, TraceResult *trace, int logotypeIndex) +{ + // this function draw spraypaint depending on the tracing results. + + static Array logotypes; + + if (logotypes.IsEmpty ()) + logotypes = String ("{biohaz;{graf004;{graf005;{lambda06;{target;{hand1").Split (";"); + + int entityIndex = -1, message = TE_DECAL; + int decalIndex = (*g_engfuncs.pfnDecalIndex) (logotypes[logotypeIndex].ToString ()); + + if (decalIndex < 0) + decalIndex = (*g_engfuncs.pfnDecalIndex) ("{lambda06"); + + if (trace->flFraction == 1.0) + return; + + if (!FNullEnt (trace->pHit)) + { + if (trace->pHit->v.solid == SOLID_BSP || trace->pHit->v.movetype == MOVETYPE_PUSHSTEP) + entityIndex = ENTINDEX (trace->pHit); + else + return; + } + else + entityIndex = 0; + + if (entityIndex != 0) + { + if (decalIndex > 255) + { + message = TE_DECALHIGH; + decalIndex -= 256; + } + } + else + { + message = TE_WORLDDECAL; + + if (decalIndex > 255) + { + message = TE_WORLDDECALHIGH; + decalIndex -= 256; + } + } + + if (logotypes[logotypeIndex].Contains ("{")) + { + MESSAGE_BEGIN (MSG_BROADCAST, SVC_TEMPENTITY); + WRITE_BYTE (TE_PLAYERDECAL); + WRITE_BYTE (ENTINDEX (ENT (pev))); + WRITE_COORD (trace->vecEndPos.x); + WRITE_COORD (trace->vecEndPos.y); + WRITE_COORD (trace->vecEndPos.z); + WRITE_SHORT (static_cast (ENTINDEX (trace->pHit))); + WRITE_BYTE (decalIndex); + MESSAGE_END (); + } + else + { + MESSAGE_BEGIN (MSG_BROADCAST, SVC_TEMPENTITY); + WRITE_BYTE (message); + WRITE_COORD (trace->vecEndPos.x); + WRITE_COORD (trace->vecEndPos.y); + WRITE_COORD (trace->vecEndPos.z); + WRITE_BYTE (decalIndex); + + if (entityIndex) + WRITE_SHORT (entityIndex); + + MESSAGE_END(); + } +} + +void FreeLibraryMemory (void) +{ + // this function free's all allocated memory + g_waypoint->Init (); // frees waypoint data + + if (g_experienceData != NULL) + delete [] g_experienceData; + + g_experienceData = NULL; +} + +void FakeClientCommand (edict_t *fakeClient, const char *format, ...) +{ + // the purpose of this function is to provide fakeclients (bots) with the same client + // command-scripting advantages (putting multiple commands in one line between semicolons) + // as real players. It is an improved version of botman's FakeClientCommand, in which you + // supply directly the whole string as if you were typing it in the bot's "console". It + // is supposed to work exactly like the pfnClientCommand (server-sided client command). + + va_list ap; + static char command[256]; + int start, stop, i, index, stringIndex = 0; + + if (FNullEnt (fakeClient)) + return; // reliability check + + // concatenate all the arguments in one string + va_start (ap, format); + _vsnprintf (command, sizeof (command), format, ap); + va_end (ap); + + if (IsNullString (command)) + return; // if nothing in the command buffer, return + + g_isFakeCommand = true; // set the "fakeclient command" flag + int length = strlen (command); // get the total length of the command string + + // process all individual commands (separated by a semicolon) one each a time + while (stringIndex < length) + { + start = stringIndex; // save field start position (first character) + + while (stringIndex < length && command[stringIndex] != ';') + stringIndex++; // reach end of field + + if (command[stringIndex - 1] == '\n') + stop = stringIndex - 2; // discard any trailing '\n' if needed + else + stop = stringIndex - 1; // save field stop position (last character before semicolon or end) + + for (i = start; i <= stop; i++) + g_fakeArgv[i - start] = command[i]; // store the field value in the g_fakeArgv global string + + g_fakeArgv[i - start] = 0; // terminate the string + stringIndex++; // move the overall string index one step further to bypass the semicolon + + index = 0; + g_fakeArgc = 0; // let's now parse that command and count the different arguments + + // count the number of arguments + while (index < i - start) + { + while (index < i - start && g_fakeArgv[index] == ' ') + index++; // ignore spaces + + // is this field a group of words between quotes or a single word ? + if (g_fakeArgv[index] == '"') + { + index++; // move one step further to bypass the quote + + while (index < i - start && g_fakeArgv[index] != '"') + index++; // reach end of field + + index++; // move one step further to bypass the quote + } + else + while (index < i - start && g_fakeArgv[index] != ' ') + index++; // this is a single word, so reach the end of field + + g_fakeArgc++; // we have processed one argument more + } + + // tell now the MOD DLL to execute this ClientCommand... + MDLL_ClientCommand (fakeClient); + } + + g_fakeArgv[0] = 0; // when it's done, reset the g_fakeArgv field + g_isFakeCommand = false; // reset the "fakeclient command" flag + g_fakeArgc = 0; // and the argument count +} + +const char *GetField (const char *string, int fieldId, bool endLine) +{ + // This function gets and returns a particuliar field in a string where several szFields are + // concatenated. Fields can be words, or groups of words between quotes ; separators may be + // white space or tabs. A purpose of this function is to provide bots with the same Cmd_Argv + // convenience the engine provides to real clients. This way the handling of real client + // commands and bot client commands is exactly the same, just have a look in engine.cpp + // for the hooking of pfnCmd_Argc, pfnCmd_Args and pfnCmd_Argv, which redirects the call + // either to the actual engine functions (when the caller is a real client), either on + // our function here, which does the same thing, when the caller is a bot. + + static char field[256]; + + // reset the string + memset (field, 0, sizeof (field)); + + int length, i, index = 0, fieldCount = 0, start, stop; + + field[0] = 0; // reset field + length = strlen (string); // get length of string + + // while we have not reached end of line + while (index < length && fieldCount <= fieldId) + { + while (index < length && (string[index] == ' ' || string[index] == '\t')) + index++; // ignore spaces or tabs + + // is this field multi-word between quotes or single word ? + if (string[index] == '"') + { + index++; // move one step further to bypass the quote + start = index; // save field start position + + while ((index < length) && (string[index] != '"')) + index++; // reach end of field + + stop = index - 1; // save field stop position + index++; // move one step further to bypass the quote + } + else + { + start = index; // save field start position + + while (index < length && (string[index] != ' ' && string[index] != '\t')) + index++; // reach end of field + + stop = index - 1; // save field stop position + } + + // is this field we just processed the wanted one ? + if (fieldCount == fieldId) + { + for (i = start; i <= stop; i++) + field[i - start] = string[i]; // store the field value in a string + + field[i - start] = 0; // terminate the string + break; // and stop parsing + } + fieldCount++; // we have parsed one field more + } + + if (endLine) + field[strlen (field) - 1] = 0; + + strtrim (field); + + return (&field[0]); // returns the wanted field +} + +void strtrim (char *string) +{ + char *ptr = string; + + int length = 0, toggleFlag = 0, increment = 0; + int i = 0; + + while (*ptr++) + length++; + + for (i = length - 1; i >= 0; i--) + { +#if defined (PLATFORM_WIN32) + if (!iswspace (string[i])) +#else + if (!isspace (string[i])) +#endif + break; + else + { + string[i] = 0; + length--; + } + } + + for (i = 0; i < length; i++) + { +#if defined (PLATFORM_WIN32) + if (iswspace (string[i]) && !toggleFlag) // win32 crash fx +#else + if (isspace (string[i]) && !toggleFlag) +#endif + { + increment++; + + if (increment + i < length) + string[i] = string[increment + i]; + } + else + { + if (!toggleFlag) + toggleFlag = 1; + + if (increment) + string[i] = string[increment + i]; + } + } + string[length] = 0; +} + +const char *GetModName (void) +{ + static char modName[256]; + + GET_GAME_DIR (modName); // ask the engine for the MOD directory path + int length = strlen (modName); // get the length of the returned string + + // format the returned string to get the last directory name + int stop = length - 1; + while ((modName[stop] == '\\' || modName[stop] == '/') && stop > 0) + stop--; // shift back any trailing separator + + int start = stop; + while (modName[start] != '\\' && modName[start] != '/' && start > 0) + start--; // shift back to the start of the last subdirectory name + + if (modName[start] == '\\' || modName[start] == '/') + start++; // if we reached a separator, step over it + + // now copy the formatted string back onto itself character per character + for (length = start; length <= stop; length++) + modName[length - start] = modName[length]; + + modName[length - start] = 0; // terminate the string + + return &modName[0]; +} + +// Create a directory tree +void CreatePath (char *path) +{ + for (char *ofs = path + 1 ; *ofs ; ofs++) + { + if (*ofs == '/') + { + // create the directory + *ofs = 0; +#ifdef PLATFORM_WIN32 + mkdir (path); +#else + mkdir (path, 0777); +#endif + *ofs = '/'; + } + } +#ifdef PLATFORM_WIN32 + mkdir (path); +#else + mkdir (path, 0777); +#endif +} + +void DrawLine (edict_t *ent, const Vector &start, const Vector &end, int width, int noise, int red, int green, int blue, int brightness, int speed, int life) +{ + // this function draws a line visible from the client side of the player whose player entity + // is pointed to by ent, from the vector location start to the vector location end, + // which is supposed to last life tenths seconds, and having the color defined by RGB. + + if (!IsValidPlayer (ent)) + return; // reliability check + + MESSAGE_BEGIN (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, NULL, ent); + WRITE_BYTE (TE_BEAMPOINTS); + WRITE_COORD (start.x); + WRITE_COORD (start.y); + WRITE_COORD (start.z); + WRITE_COORD (end.x); + WRITE_COORD (end.y); + WRITE_COORD (end.z); + WRITE_SHORT (g_modelIndexLaser); + WRITE_BYTE (0); // framestart + WRITE_BYTE (10); // framerate + WRITE_BYTE (life); // life in 0.1's + WRITE_BYTE (width); // width + WRITE_BYTE (noise); // noise + + WRITE_BYTE (red); // r, g, b + WRITE_BYTE (green); // r, g, b + WRITE_BYTE (blue); // r, g, b + + WRITE_BYTE (brightness); // brightness + WRITE_BYTE (speed); // speed + MESSAGE_END (); +} + +void DrawArrow (edict_t *ent, const Vector &start, const Vector &end, int width, int noise, int red, int green, int blue, int brightness, int speed, int life) +{ + // this function draws a arrow visible from the client side of the player whose player entity + // is pointed to by ent, from the vector location start to the vector location end, + // which is supposed to last life tenths seconds, and having the color defined by RGB. + + if (!IsValidPlayer (ent)) + return; // reliability check + + MESSAGE_BEGIN (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, NULL, ent); + WRITE_BYTE (TE_BEAMPOINTS); + WRITE_COORD (end.x); + WRITE_COORD (end.y); + WRITE_COORD (end.z); + WRITE_COORD (start.x); + WRITE_COORD (start.y); + WRITE_COORD (start.z); + WRITE_SHORT (g_modelIndexArrow); + WRITE_BYTE (0); // framestart + WRITE_BYTE (10); // framerate + WRITE_BYTE (life); // life in 0.1's + WRITE_BYTE (width); // width + WRITE_BYTE (noise); // noise + + WRITE_BYTE (red); // r, g, b + WRITE_BYTE (green); // r, g, b + WRITE_BYTE (blue); // r, g, b + + WRITE_BYTE (brightness); // brightness + WRITE_BYTE (speed); // speed + MESSAGE_END (); +} + +void UpdateGlobalExperienceData (void) +{ + // this function called after each end of the round to update knowledge about most dangerous waypoints for each team. + + // no waypoints, no experience used or waypoints edited or being edited? + if ((g_numWaypoints < 1) || g_waypointsChanged) + return; // no action + + unsigned short maxDamage; // maximum damage + unsigned short actDamage; // actual damage + + int bestIndex; // best index to store + bool recalcKills = false; + + // get the most dangerous waypoint for this position for terrorist team + for (int i = 0; i < g_numWaypoints; i++) + { + maxDamage = 0; + bestIndex = -1; + + for (int j = 0; j < g_numWaypoints; j++) + { + if (i == j) + continue; + + actDamage = (g_experienceData + (i * g_numWaypoints) + j)->team0Damage; + + if (actDamage > maxDamage) + { + maxDamage = actDamage; + bestIndex = j; + } + } + + if (maxDamage > MAX_DAMAGE_VALUE) + recalcKills = true; + + (g_experienceData + (i * g_numWaypoints) + i)->team0DangerIndex = static_cast (bestIndex); + } + + // get the most dangerous waypoint for this position for counter-terrorist team + for (int i = 0; i < g_numWaypoints; i++) + { + maxDamage = 0; + bestIndex = -1; + + for (int j = 0; j < g_numWaypoints; j++) + { + if (i == j) + continue; + + actDamage = (g_experienceData + (i * g_numWaypoints) + j)->team1Damage; + + if (actDamage > maxDamage) + { + maxDamage = actDamage; + bestIndex = j; + } + } + + if (maxDamage > MAX_DAMAGE_VALUE) + recalcKills = true; + + (g_experienceData + (i * g_numWaypoints) + i)->team1DangerIndex = static_cast (bestIndex); + } + + // adjust values if overflow is about to happen + if (recalcKills) + { + for (int i = 0; i < g_numWaypoints; i++) + { + for (int j = 0; j < g_numWaypoints; j++) + { + if (i == j) + continue; + + int clip = (g_experienceData + (i * g_numWaypoints) + j)->team0Damage; + clip -= static_cast (MAX_DAMAGE_VALUE * 0.5); + + if (clip < 0) + clip = 0; + + (g_experienceData + (i * g_numWaypoints) + j)->team0Damage = static_cast (clip); + + clip = (g_experienceData + (i * g_numWaypoints) + j)->team1Damage; + clip -= static_cast (MAX_DAMAGE_VALUE * 0.5); + + if (clip < 0) + clip = 0; + + (g_experienceData + (i * g_numWaypoints) + j)->team1Damage = static_cast (clip); + } + } + } + g_killHistory++; + + if (g_killHistory == MAX_KILL_HISTORY) + { + for (int i = 0; i < g_numWaypoints; i++) + { + (g_experienceData + (i * g_numWaypoints) + i)->team0Damage /= static_cast (GetMaxClients () * 0.5); + (g_experienceData + (i * g_numWaypoints) + i)->team1Damage /= static_cast (GetMaxClients () * 0.5); + } + g_killHistory = 1; + } +} + +void RoundInit (void) +{ + // this is called at the start of each round + + g_roundEnded = false; + + // check team economics + g_botManager->CheckTeamEconomics (TEAM_TF); + g_botManager->CheckTeamEconomics (TEAM_CF); + + for (int i = 0; i < GetMaxClients (); i++) + { + if (g_botManager->GetBot (i)) + g_botManager->GetBot (i)->NewRound (); + + g_radioSelect[i] = 0; + } + g_waypoint->SetBombPosition (true); + g_waypoint->ClearGoalScore (); + + g_bombSayString = false; + g_timeBombPlanted = 0.0; + g_timeNextBombUpdate = 0.0; + + g_leaderChoosen[TEAM_CF] = false; + g_leaderChoosen[TEAM_TF] = false; + + g_lastRadioTime[0] = 0.0; + g_lastRadioTime[1] = 0.0; + g_botsCanPause = false; + + UpdateGlobalExperienceData (); // update experience data on round start + + // calculate the round mid/end in world time + g_timeRoundStart = GetWorldTime () + mp_freezetime.GetFloat (); + g_timeRoundMid = g_timeRoundStart + mp_roundtime.GetFloat () * 60 / 2; + g_timeRoundEnd = g_timeRoundStart + mp_roundtime.GetFloat () * 60; +} + +bool IsWeaponShootingThroughWall (int id) +{ + // returns if weapon can pierce through a wall + + int i = 0; + + while (g_weaponSelect[i].id) + { + if (g_weaponSelect[i].id == id) + { + if (g_weaponSelect[i].shootsThru) + return true; + + return false; + } + i++; + } + return false; +} + +int GetTeam (edict_t *ent) +{ + return g_clients[ENTINDEX (ent) - 1].team; +} + +bool IsValidPlayer (edict_t *ent) +{ + if (FNullEnt (ent)) + return false; + + if (ent->v.flags & FL_PROXY) + return false; + + if ((ent->v.flags & (FL_CLIENT | FL_FAKECLIENT)) || g_botManager->GetBot (ent) != NULL) + return !IsNullString (STRING (ent->v.netname)); + + return false; +} + +bool IsValidBot (edict_t *ent) +{ + if (g_botManager->GetBot (ent) != NULL || (!FNullEnt (ent) && (ent->v.flags & FL_FAKECLIENT))) + return true; + + return false; +} + +bool IsDedicatedServer (void) +{ + // return true if server is dedicated server, false otherwise + + return (IS_DEDICATED_SERVER () > 0); // ask engine for this +} + +bool TryFileOpen (char *fileName) +{ + // this function tests if a file exists by attempting to open it + + File fp; + + // check if got valid handle + if (fp.Open (fileName, "rb")) + { + fp.Close (); + return true; + } + return false; +} + +void HudMessage (edict_t *ent, bool toCenter, Vector rgb, char *format, ...) +{ + if (!IsValidPlayer (ent) || IsValidBot (ent)) + return; + + va_list ap; + char buffer[1024]; + + va_start (ap, format); + vsprintf (buffer, format, ap); + va_end (ap); + + MESSAGE_BEGIN (MSG_ONE, SVC_TEMPENTITY, NULL, ent); + WRITE_BYTE (TE_TEXTMESSAGE); + WRITE_BYTE (1); + WRITE_SHORT (FixedSigned16 (-1, 1 << 13)); + WRITE_SHORT (FixedSigned16 (toCenter ? -1 : 0, 1 << 13)); + WRITE_BYTE (2); + WRITE_BYTE (static_cast (rgb.x)); + WRITE_BYTE (static_cast (rgb.y)); + WRITE_BYTE (static_cast (rgb.z)); + WRITE_BYTE (0); + WRITE_BYTE (g_randGen.Long (230, 255)); + WRITE_BYTE (g_randGen.Long (230, 255)); + WRITE_BYTE (g_randGen.Long (230, 255)); + WRITE_BYTE (200); + WRITE_SHORT (FixedUnsigned16 (0.0078125, 1 << 8)); + WRITE_SHORT (FixedUnsigned16 (2, 1 << 8)); + WRITE_SHORT (FixedUnsigned16 (6, 1 << 8)); + WRITE_SHORT (FixedUnsigned16 (0.1, 1 << 8)); + WRITE_STRING (const_cast (&buffer[0])); + MESSAGE_END (); +} + +void ServerPrint (const char *format, ...) +{ + va_list ap; + char string[3072]; + + va_start (ap, format); + vsprintf (string, g_localizer->TranslateInput (format), ap); + va_end (ap); + + SERVER_PRINT (FormatBuffer ("[%s] %s\n", PRODUCT_LOGTAG, string)); +} + +void ServerPrintNoTag (const char *format, ...) +{ + va_list ap; + char string[3072]; + + va_start (ap, format); + vsprintf (string, g_localizer->TranslateInput (format), ap); + va_end (ap); + + SERVER_PRINT (FormatBuffer ("%s\n", string)); +} + +void CenterPrint (const char *format, ...) +{ + va_list ap; + char string[2048]; + + va_start (ap, format); + vsprintf (string, g_localizer->TranslateInput (format), ap); + va_end (ap); + + if (IsDedicatedServer ()) + { + ServerPrint (string); + return; + } + + MESSAGE_BEGIN (MSG_BROADCAST, g_netMsg->GetId (NETMSG_TEXTMSG)); + WRITE_BYTE (HUD_PRINTCENTER); + WRITE_STRING (FormatBuffer ("%s\n", string)); + MESSAGE_END (); +} + +void ChartPrint (const char *format, ...) +{ + va_list ap; + char string[2048]; + + va_start (ap, format); + vsprintf (string, g_localizer->TranslateInput (format), ap); + va_end (ap); + + if (IsDedicatedServer ()) + { + ServerPrint (string); + return; + } + strcat (string, "\n"); + + MESSAGE_BEGIN (MSG_BROADCAST, g_netMsg->GetId (NETMSG_TEXTMSG)); + WRITE_BYTE (HUD_PRINTTALK); + WRITE_STRING (string); + MESSAGE_END (); + +} + +void ClientPrint (edict_t *ent, int dest, const char *format, ...) +{ + va_list ap; + char string[2048]; + + va_start (ap, format); + vsprintf (string, g_localizer->TranslateInput (format), ap); + va_end (ap); + + if (FNullEnt (ent) || ent == g_hostEntity) + { + if (dest & 0x3ff) + ServerPrint (string); + else + ServerPrintNoTag (string); + + return; + } + strcat (string, "\n"); + + if (dest & 0x3ff) + (*g_engfuncs.pfnClientPrintf) (ent, static_cast (dest &= ~0x3ff), FormatBuffer ("[YAPB] %s", string)); + else + (*g_engfuncs.pfnClientPrintf) (ent, static_cast (dest), string); + +} + +void ServerCommand (const char *format, ...) +{ + // this function asks the engine to execute a server command + + va_list ap; + static char string[1024]; + + // concatenate all the arguments in one string + va_start (ap, format); + vsprintf (string, format, ap); + va_end (ap); + + SERVER_COMMAND (FormatBuffer ("%s\n", string)); // execute command +} + +float GetWorldTime (void) +{ + // this function returns engine current time on this server + + return g_pGlobals->time; +} + +int GetMaxClients (void) +{ + // this function returns current players on server + + return g_pGlobals->maxClients; +} + +const char *GetMapName (void) +{ + // this function gets the map name and store it in the map_name global string variable. + + static char mapName[256]; + strcpy (mapName, const_cast (g_pGlobals->pStringBase + static_cast (g_pGlobals->mapname))); + + return &mapName[0]; // and return a pointer to it +} + +bool OpenConfig (const char *fileName, char *errorIfNotExists, File *outFile, bool languageDependant) +{ + if (outFile->IsValid ()) + outFile->Close (); + + if (languageDependant) + { + extern ConVar yb_language; + + if (strcmp (fileName, "lang.cfg") == 0 && strcmp (yb_language.GetString (), "en") == 0) + return false; + + char *languageDependantConfigFile = FormatBuffer ("%s/addons/yapb/config/language/%s_%s", GetModName (), yb_language.GetString (), fileName); + + // check is file is exists for this language + if (TryFileOpen (languageDependantConfigFile)) + outFile->Open (languageDependantConfigFile, "rt"); + else + outFile->Open (FormatBuffer ("%s/addons/yapb/config/language/en_%s", GetModName (), fileName), "rt"); + } + else + outFile->Open (FormatBuffer ("%s/addons/yapb/config/%s", GetModName (), fileName), "rt"); + + if (!outFile->IsValid ()) + { + AddLogEntry (true, LL_ERROR, errorIfNotExists); + return false; + } + return true; +} + +const char *GetWaypointDir (void) +{ + return FormatBuffer ("%s/addons/yapb/wptdefault/", GetModName ()); +} + +void RegisterCommand (char *command, void funcPtr (void)) +{ + // this function tells the engine that a new server command is being declared, in addition + // to the standard ones, whose name is command_name. The engine is thus supposed to be aware + // that for every "command_name" server command it receives, it should call the function + // pointed to by "function" in order to handle it. + + if (IsNullString (command) || funcPtr == NULL) + return; // reliability check + + REG_SVR_COMMAND (command, funcPtr); // ask the engine to register this new command +} + +void CheckWelcomeMessage (void) +{ + // the purpose of this function, is to send quick welcome message, to the listenserver entity. + + static bool isReceived = false; + static float receiveTime = 0.0; + + if (isReceived || !yb_listenserver_welcome.GetBool ()) + return; + + Array sentences; + + // add default messages + sentences.Push ("hello user,communication is acquired"); + sentences.Push ("your presence is acknowledged"); + sentences.Push ("high man, your in command now"); + sentences.Push ("blast your hostile for good"); + sentences.Push ("high man, kill some idiot here"); + sentences.Push ("is there a doctor in the area"); + sentences.Push ("warning, experimental materials detected"); + sentences.Push ("high amigo, shoot some but"); + sentences.Push ("attention, hours of work software, detected"); + sentences.Push ("time for some bad ass explosion"); + sentences.Push ("bad ass son of a breach device activated"); + sentences.Push ("high, do not question this great service"); + sentences.Push ("engine is operative, hello and goodbye"); + sentences.Push ("high amigo, your administration has been great last day"); + sentences.Push ("attention, expect experimental armed hostile presence"); + sentences.Push ("warning, medical attention required"); + + if (IsAlive (g_hostEntity) && !isReceived && receiveTime < 1.0 && (g_numWaypoints > 0 ? g_isCommencing : true)) + receiveTime = GetWorldTime () + 4.0; // receive welcome message in four seconds after game has commencing + + if (receiveTime > 0.0 && receiveTime < GetWorldTime () && !isReceived && (g_numWaypoints > 0 ? g_isCommencing : true)) + { + ServerCommand ("speak \"%s\"", const_cast (sentences.GetRandomElement ().ToString ())); + + ChartPrint ("----- YaPB v%s (Build: %u), {%s}, (c) 2014, by %s -----", PRODUCT_VERSION, GenerateBuildNumber (), PRODUCT_DATE, PRODUCT_AUTHOR); + HudMessage (g_hostEntity, true, Vector (g_randGen.Long (33, 255), g_randGen.Long (33, 255), g_randGen.Long (33, 255)), "\nServer is running YaPB v%s (Build: %u)\nDeveloped by %s\n\n%s", PRODUCT_VERSION, GenerateBuildNumber (), PRODUCT_AUTHOR, g_waypoint->GetInfo ()); + + receiveTime = 0.0; + isReceived = true; + } +} + +void DetectCSVersion (void) +{ + byte *detection = NULL; + const char *const infoBuffer = "Game Registered: CS %s (0x%d)"; + + // switch version returned by dll loader + switch (g_gameVersion) + { + // counter-strike 1.x, WON ofcourse + case CSV_OLD: + ServerPrint (infoBuffer, "1.x (WON)", sizeof (Bot)); + break; + + // counter-strike 1.6 or higher (plus detects for non-steam versions of 1.5) + case CSV_STEAM: + detection = (*g_engfuncs.pfnLoadFileForMe) ("events/galil.sc", NULL); + + if (detection != NULL) + { + ServerPrint (infoBuffer, "1.6 (Steam)", sizeof (Bot)); + g_gameVersion = CSV_STEAM; // just to be sure + } + else if (detection == NULL) + { + ServerPrint (infoBuffer, " <= 1.5 (WON)", sizeof (Bot)); + g_gameVersion = CSV_OLD; // reset it to WON + } + + // if we have loaded the file free it + if (detection != NULL) + (*g_engfuncs.pfnFreeFile) (detection); + break; + + // counter-strike cz + case CSV_CZERO: + ServerPrint (infoBuffer, "CZ (Steam)", sizeof (Bot)); + break; + } + g_convarWrapper->PushRegisteredConVarsToEngine (true); +} + +void PlaySound (edict_t *ent, const char *name) +{ + // TODO: make this obsolete + EMIT_SOUND_DYN2 (ent, CHAN_WEAPON, name, 1.0, ATTN_NORM, 0, 100); + + return; +} + +float GetWaveLength (const char *fileName) +{ + WavHeader waveHdr; + memset (&waveHdr, 0, sizeof (waveHdr)); + + extern ConVar yb_chatter_path; + + File fp (FormatBuffer ("%s/%s/%s.wav", GetModName (), yb_chatter_path.GetString (), fileName), "rb"); + + // we're got valid handle? + if (!fp.IsValid ()) + return 0; + + if (fp.Read (&waveHdr, sizeof (WavHeader)) == 0) + { + AddLogEntry (true, LL_ERROR, "Wave File %s - has wrong or unsupported format", fileName); + return 0; + } + + if (strncmp (waveHdr.chunkID, "WAVE", 4) != 0) + { + AddLogEntry (true, LL_ERROR, "Wave File %s - has wrong wave chunk id", fileName); + return 0; + } + fp.Close (); + + if (waveHdr.dataChunkLength == 0) + { + AddLogEntry (true, LL_ERROR, "Wave File %s - has zero length!", fileName); + return 0; + } + + char ch[32]; + sprintf (ch, "0.%u", static_cast (waveHdr.dataChunkLength)); + + float secondLength = static_cast (waveHdr.dataChunkLength / waveHdr.bytesPerSecond); + float milliSecondLength = atof (ch); + + return (secondLength == 0.0 ? milliSecondLength : secondLength); +} + +void AddLogEntry (bool outputToConsole, int logLevel, const char *format, ...) +{ + // this function logs a message to the message log file root directory. + + va_list ap; + char buffer[512] = {0, }, levelString[32] = {0, }, logLine[1024] = {0, }; + + va_start (ap, format); + vsprintf (buffer, g_localizer->TranslateInput (format), ap); + va_end (ap); + + switch (logLevel) + { + case LL_DEFAULT: + strcpy (levelString, "Log: "); + break; + + case LL_WARNING: + strcpy (levelString, "Warning: "); + break; + + case LL_ERROR: + strcpy (levelString, "Error: "); + break; + + case LL_FATAL: + strcpy (levelString, "Critical: "); + break; + } + + if (outputToConsole) + ServerPrintNoTag ("%s%s", levelString, buffer); + + // now check if logging disabled + if (!(logLevel & LL_IGNORE)) + { + extern ConVar yb_debug; + + if (logLevel == LL_DEFAULT && yb_debug.GetInt () < 3) + return; // no log, default logging is disabled + + if (logLevel == LL_WARNING && yb_debug.GetInt () < 2) + return; // no log, warning logging is disabled + + if (logLevel == LL_ERROR && yb_debug.GetInt () < 1) + return; // no log, error logging is disabled + } + + // open file in a standard stream + File fp ("yapb.txt", "at"); + + // check if we got a valid handle + if (!fp.IsValid ()) + return; + + time_t tickTime = time (&tickTime); + tm *time = localtime (&tickTime); + + sprintf (logLine, "[%02d:%02d:%02d] %s%s", time->tm_hour, time->tm_min, time->tm_sec, levelString, buffer); + + fp.Printf ("%s\n", logLine); + fp.Close (); + + if (logLevel == LL_FATAL) + { +#if defined (PLATFORM_WIN32) + MessageBoxA (GetActiveWindow (), buffer, "YaPB Error", MB_ICONSTOP); +#else + printf (buffer); +#endif + FreeLibraryMemory (); + +#if defined (PLATFORM_WIN32) + _exit (1); +#else + exit (1); +#endif + } +} + +char *Localizer::TranslateInput (const char *input) +{ + if (IsDedicatedServer ()) + return const_cast (&input[0]); + + static char string[1024]; + const char *ptr = input + strlen (input) - 1; + + while (ptr > input && *ptr == '\n') + ptr--; + + if (ptr != input) + ptr++; + + strncpy (string, input, 1024); + strtrim (string); + + IterateArray (m_langTab, i) + { + if (strcmp (string, m_langTab[i].original) == 0) + { + strncpy (string, m_langTab[i].translated, 1024); + + if (ptr != input) + strncat (string, ptr, 1024 - 1 - strlen (string)); + + return &string[0]; + } + } + return const_cast (&input[0]); // nothing found +} + +uint32 RandGen::GetRandomBits (void) +{ + // generate next number + uint32 x = _lrotl (m_historyBuffer[m_bufferIndex1][0], R1) + m_historyBuffer[m_bufferIndex2][0]; + uint32 y = _lrotl (m_historyBuffer[m_bufferIndex1][1], R2) + m_historyBuffer[m_bufferIndex2][1]; + + m_historyBuffer[m_bufferIndex1][0] = y; + m_historyBuffer[m_bufferIndex1][1] = x; + + // rotate list pointers + if (m_bufferIndex1-- < 0) + m_bufferIndex1 = KK - 1; + + if (m_bufferIndex2-- < 0) + m_bufferIndex2 = KK - 1; + + // perform self-test + if (m_historyBuffer[m_bufferIndex1][0] == m_selfTestBuffer[0][0] && memcmp (m_historyBuffer, m_selfTestBuffer[KK - m_bufferIndex1], 2 * KK * sizeof (uint32)) == 0) + { + // self-test failed + if ((m_bufferIndex2 + KK - m_bufferIndex1) % KK != JJ) + AddLogEntry (false, LL_FATAL, "Random number generator could not be initialized."); + else + AddLogEntry (false, LL_FATAL, "Random number generator returned to initial state."); + } + m_randomBits[0] = y; + m_randomBits[1] = x; + + return y; +} + +long double RandGen::Random (void) +{ + return static_cast (GetRandomBits () * (1.0 / (static_cast (static_cast (-1L)) + 1.0))); +} + +int RandGen::Long (int low, int high) +{ + // this function returns a random integer number between (and including) the starting and + // ending values passed by parameters from and to. + + int interval = high - low + 1; + + if (interval <= 0) + return low; + + int truncate = static_cast (interval * Random ()); + + if (truncate >= interval) + truncate = interval - 1.0; + + return low + truncate; +} + +float RandGen::Float (float low, float high) +{ + // this function returns a random floating-point number between (and including) the starting + // and ending values passed by parameters from and to. + + float interval = high - low; + + if (interval <= 0.0) + return low; + + float truncate = static_cast (interval * Random ()); + + if (truncate >= interval) + truncate = interval; + + return low + truncate; +} + +void RandGen::Initialize (uint32 seed) +{ + // this function initializes the random number generator. + + // make random numbers and put them into the buffer + for (int i = 0; i < KK; i++) + { + for (int j = 0; j < 2; j++) + { + seed = seed * 2891336453UL + 1; + m_historyBuffer[i][j] = seed; + } + } + + // set exponent of randp + m_randomBits[2] = 0; + m_randomPtr = 1.0; + + // initialize pointers to circular buffer + m_bufferIndex1 = 0; + m_bufferIndex2 = JJ; + + // store state for self-test + memcpy (m_selfTestBuffer, m_historyBuffer, 2 * KK * sizeof (uint32)); + memcpy (m_selfTestBuffer[KK], m_historyBuffer, 2 * KK * sizeof (uint32)); + + // randomize some more + for (int i = 0; i < 128; i++) + GetRandomBits (); +} + +bool FindNearestPlayer (void **pvHolder, edict_t *to, float searchDistance, bool sameTeam, bool needBot, bool isAlive, bool needDrawn) +{ + // this function finds nearest to to, player with set of parameters, like his + // team, live status, search distance etc. if needBot is true, then pvHolder, will + // be filled with bot pointer, else with edict pointer(!). + + edict_t *ent = NULL, *survive = NULL; // pointer to temporaly & survive entity + float nearestPlayer = 4096.0; // nearest player + + while (!FNullEnt (ent = FIND_ENTITY_IN_SPHERE (ent, to->v.origin, searchDistance))) + { + if (FNullEnt (ent) || !IsValidPlayer (ent) || to == ent) + continue; // skip invalid players + + if ((sameTeam && GetTeam (ent) != GetTeam (to)) || (isAlive && !IsAlive (ent)) || (needBot && !IsValidBot (ent)) || (needDrawn && (ent->v.effects & EF_NODRAW))) + continue; // filter players with parameters + + float distance = (ent->v.origin - to->v.origin).GetLength (); + + if (distance < nearestPlayer) + { + nearestPlayer = distance; + survive = ent; + } + } + + if (FNullEnt (survive)) + return false; // nothing found + + // fill the holder + if (needBot) + *pvHolder = reinterpret_cast (g_botManager->GetBot (survive)); + else + *pvHolder = reinterpret_cast (survive); + + return true; +} + +void SoundAttachToThreat (edict_t *ent, const char *sample, float volume) +{ + // this function called by the sound hooking code (in emit_sound) enters the played sound into + // the array associated with the entity + + if (FNullEnt (ent) || IsNullString (sample)) + return; // reliability check + + Vector origin = GetEntityOrigin (ent); + int index = ENTINDEX (ent) - 1; + + if (index < 0 || index >= GetMaxClients ()) + { + float nearestDistance = FLT_MAX; + + // loop through all players + for (int i = 0; i < GetMaxClients (); i++) + { + if (!(g_clients[i].flags & CF_USED) || !(g_clients[i].flags & CF_ALIVE)) + continue; + + float distance = (g_clients[i].ent->v.origin - origin).GetLengthSquared (); + + // now find nearest player + if (distance < nearestDistance) + { + index = i; + nearestDistance = distance; + } + } + } + + if (strncmp ("player/bhit_flesh", sample, 17) == 0 || strncmp ("player/headshot", sample, 15) == 0) + { + // hit/fall sound? + g_clients[index].hearingDistance = 768.0 * volume; + g_clients[index].timeSoundLasting = GetWorldTime () + 0.5; + g_clients[index].maxTimeSoundLasting = 0.5; + g_clients[index].soundPosition = origin; + } + else if (strncmp ("items/gunpickup", sample, 15) == 0) + { + // weapon pickup? + g_clients[index].hearingDistance = 768.0 * volume; + g_clients[index].timeSoundLasting = GetWorldTime () + 0.5; + g_clients[index].maxTimeSoundLasting = 0.5; + g_clients[index].soundPosition = origin; + } + else if (strncmp ("weapons/zoom", sample, 12) == 0) + { + // sniper zooming? + g_clients[index].hearingDistance = 512.0 * volume; + g_clients[index].timeSoundLasting = GetWorldTime () + 0.1; + g_clients[index].maxTimeSoundLasting = 0.1; + g_clients[index].soundPosition = origin; + } + else if (strncmp ("items/9mmclip", sample, 13) == 0) + { + // ammo pickup? + g_clients[index].hearingDistance = 512.0 * volume; + g_clients[index].timeSoundLasting = GetWorldTime () + 0.1; + g_clients[index].maxTimeSoundLasting = 0.1; + g_clients[index].soundPosition = origin; + } + else if (strncmp ("hostage/hos", sample, 11) == 0) + { + // CT used hostage? + g_clients[index].hearingDistance = 1024.0 * volume; + g_clients[index].timeSoundLasting = GetWorldTime () + 5.0; + g_clients[index].maxTimeSoundLasting = 0.5; + g_clients[index].soundPosition = origin; + } + else if (strncmp ("debris/bustmetal", sample, 16) == 0 || strncmp ("debris/bustglass", sample, 16) == 0) + { + // broke something? + g_clients[index].hearingDistance = 1024.0 * volume; + g_clients[index].timeSoundLasting = GetWorldTime () + 2.0; + g_clients[index].maxTimeSoundLasting = 2.0; + g_clients[index].soundPosition = origin; + } + else if (strncmp ("doors/doormove", sample, 14) == 0) + { + // someone opened a door + g_clients[index].hearingDistance = 1024.0 * volume; + g_clients[index].timeSoundLasting = GetWorldTime () + 3.0; + g_clients[index].maxTimeSoundLasting = 3.0; + g_clients[index].soundPosition = origin; + } +#if 0 + else if (strncmp ("weapons/reload", sample, 14) == 0) + { + // reloading ? + g_clients[index].hearingDistance = 512.0 * volume; + g_clients[index].timeSoundLasting = GetWorldTime () + 0.5; + g_clients[index].maxTimeSoundLasting = 0.5; + g_clients[index].soundPosition = origin; + } +#endif +} + +void SoundSimulateUpdate (int playerIndex) +{ + // this function tries to simulate playing of sounds to let the bots hear sounds which aren't + // captured through server sound hooking + + InternalAssert (playerIndex >= 0); + InternalAssert (playerIndex < GetMaxClients ()); + + if (playerIndex < 0 || playerIndex >= GetMaxClients ()) + return; // reliability check + + edict_t *player = g_clients[playerIndex].ent; + + float velocity = player->v.velocity.GetLength2D (); + float hearDistance = 0.0; + float timeSound = 0.0; + float timeMaxSound = 0.5; + + if (player->v.oldbuttons & IN_ATTACK) // pressed attack button? + { + hearDistance = 3072.0; + timeSound = GetWorldTime () + 0.3; + timeMaxSound = 0.3; + } + else if (player->v.oldbuttons & IN_USE) // pressed used button? + { + hearDistance = 512.0; + timeSound = GetWorldTime () + 0.5; + timeMaxSound = 0.5; + } + else if (player->v.oldbuttons & IN_RELOAD) // pressed reload button? + { + hearDistance = 512.0; + timeSound = GetWorldTime () + 0.5; + timeMaxSound = 0.5; + } + else if (player->v.movetype == MOVETYPE_FLY) // uses ladder? + { + if (fabs (player->v.velocity.z) > 50.0) + { + hearDistance = 1024.0; + timeSound = GetWorldTime () + 0.3; + timeMaxSound = 0.3; + } + } + else + { + extern ConVar mp_footsteps; + + if (mp_footsteps.GetBool ()) + { + // moves fast enough? + hearDistance = 1280.0 * (velocity / 240); + timeSound = GetWorldTime () + 0.3; + timeMaxSound = 0.3; + } + } + + if (hearDistance <= 0.0) + return; // didn't issue sound? + + // some sound already associated + if (g_clients[playerIndex].timeSoundLasting > GetWorldTime ()) + { + // new sound louder (bigger range) than old one ? + if (g_clients[playerIndex].maxTimeSoundLasting <= 0.0) + g_clients[playerIndex].maxTimeSoundLasting = 0.5; + + if (g_clients[playerIndex].hearingDistance * (g_clients[playerIndex].timeSoundLasting - GetWorldTime ()) / g_clients[playerIndex].maxTimeSoundLasting <= hearDistance) + { + // override it with new + g_clients[playerIndex].hearingDistance = hearDistance; + g_clients[playerIndex].timeSoundLasting = timeSound; + g_clients[playerIndex].maxTimeSoundLasting = timeMaxSound; + g_clients[playerIndex].soundPosition = player->v.origin; + } + } + else + { + // just remember it + g_clients[playerIndex].hearingDistance = hearDistance; + g_clients[playerIndex].timeSoundLasting = timeSound; + g_clients[playerIndex].maxTimeSoundLasting = timeMaxSound; + g_clients[playerIndex].soundPosition = player->v.origin; + } +} + +uint16 GenerateBuildNumber (void) +{ + // this function generates build number from the compiler date macros + + // get compiling date using compiler macros + const char *date = __DATE__; + + // array of the month names + const char *months[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; + + // array of the month days + byte monthDays[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + + int day = 0; // day of the year + int year = 0; // year + int i = 0; + + // go through all monthes, and calculate, days since year start + for (i = 0; i < 11; i++) + { + if (strncmp (&date[0], months[i], 3) == 0) + break; // found current month break + + day += monthDays[i]; // add month days + } + day += atoi (&date[4]) - 1; // finally calculate day + year = atoi (&date[7]) - 2000; // get years since year 2000 + + uint16 buildNumber = day + static_cast ((year - 1) * 365.25); + + // if the year is a leap year? + if ((year % 4) == 0 && i > 1) + buildNumber += 1; // add one year more + + return buildNumber - 1114; +} + +int GetWeaponReturn (bool needString, const char *weaponAlias, int weaponIndex) +{ + // this function returning weapon id from the weapon alias and vice versa. + + // structure definition for weapon tab + struct WeaponTab_t + { + Weapon weaponIndex; // weapon id + const char *alias; // weapon alias + }; + + // weapon enumeration + WeaponTab_t weaponTab[] = + { + {WEAPON_USP, "usp"}, // HK USP .45 Tactical + {WEAPON_GLOCK, "glock"}, // Glock18 Select Fire + {WEAPON_DEAGLE, "deagle"}, // Desert Eagle .50AE + {WEAPON_P228, "p228"}, // SIG P228 + {WEAPON_ELITE, "elite"}, // Dual Beretta 96G Elite + {WEAPON_FIVESEVEN, "fn57"}, // FN Five-Seven + {WEAPON_M3, "m3"}, // Benelli M3 Super90 + {WEAPON_XM1014, "xm1014"}, // Benelli XM1014 + {WEAPON_MP5, "mp5"}, // HK MP5-Navy + {WEAPON_TMP, "tmp"}, // Steyr Tactical Machine Pistol + {WEAPON_P90, "p90"}, // FN P90 + {WEAPON_MAC10, "mac10"}, // Ingram MAC-10 + {WEAPON_UMP45, "ump45"}, // HK UMP45 + {WEAPON_AK47, "ak47"}, // Automat Kalashnikov AK-47 + {WEAPON_GALIL, "galil"}, // IMI Galil + {WEAPON_FAMAS, "famas"}, // GIAT FAMAS + {WEAPON_SG552, "sg552"}, // Sig SG-552 Commando + {WEAPON_M4A1, "m4a1"}, // Colt M4A1 Carbine + {WEAPON_AUG, "aug"}, // Steyr Aug + {WEAPON_SCOUT, "scout"}, // Steyr Scout + {WEAPON_AWP, "awp"}, // AI Arctic Warfare/Magnum + {WEAPON_G3SG1, "g3sg1"}, // HK G3/SG-1 Sniper Rifle + {WEAPON_SG550, "sg550"}, // Sig SG-550 Sniper + {WEAPON_M249, "m249"}, // FN M249 Para + {WEAPON_FLASHBANG, "flash"}, // Concussion Grenade + {WEAPON_EXPLOSIVE, "hegren"}, // High-Explosive Grenade + {WEAPON_SMOKE, "sgren"}, // Smoke Grenade + {WEAPON_ARMOR, "vest"}, // Kevlar Vest + {WEAPON_ARMORHELM, "vesthelm"}, // Kevlar Vest and Helmet + {WEAPON_DEFUSER, "defuser"}, // Defuser Kit + {WEAPON_SHIELD, "shield"}, // Tactical Shield + }; + + // if we need to return the string, find by weapon id + if (needString && weaponIndex != -1) + { + for (int i = 0; i < ARRAYSIZE_HLSDK (weaponTab); i++) + { + if (weaponTab[i].weaponIndex == weaponIndex) // is weapon id found? + return MAKE_STRING (weaponTab[i].alias); + } + return MAKE_STRING ("(none)"); // return none + } + + // else search weapon by name and return weapon id + for (int i = 0; i < ARRAYSIZE_HLSDK (weaponTab); i++) + { + if (strncmp (weaponTab[i].alias, weaponAlias, strlen (weaponTab[i].alias)) == 0) + return weaponTab[i].weaponIndex; + } + return -1; // no weapon was found return -1 +} \ No newline at end of file diff --git a/source/waypoint.cpp b/source/waypoint.cpp new file mode 100644 index 0000000..3ecedd5 --- /dev/null +++ b/source/waypoint.cpp @@ -0,0 +1,2611 @@ +// +// Copyright (c) 2014, by YaPB Development Team. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// Version: $Id:$ +// + +#include + +ConVar yb_wptsubfolder ("yb_wptsubfolder", ""); + +ConVar yb_waypoint_autodl_host ("yb_waypoint_autodl_host", "yapb.jeefo.net"); +ConVar yb_waypoint_autodl_enable ("yb_waypoint_autodl_enable", "1"); + +void Waypoint::Init (void) +{ + // this function initialize the waypoint structures.. + + // have any waypoint path nodes been allocated yet? + if (m_waypointPaths) + { + for (int i = 0; i < g_numWaypoints && m_paths[i] != NULL; i++) + { + delete m_paths[i]; + m_paths[i] = NULL; + } + } + g_numWaypoints = 0; + m_lastWaypoint = nullvec; +} + +void Waypoint::AddPath (short int addIndex, short int pathIndex, float distance) +{ + if (addIndex < 0 || addIndex >= g_numWaypoints || pathIndex < 0 || pathIndex >= g_numWaypoints || addIndex == pathIndex) + return; + + Path *path = m_paths[addIndex]; + + // don't allow paths get connected twice + for (int i = 0; i < MAX_PATH_INDEX; i++) + { + if (path->index[i] == pathIndex) + { + AddLogEntry (true, LL_WARNING, "Denied path creation from %d to %d (path already exists)", addIndex, pathIndex); + return; + } + } + + // check for free space in the connection indices + for (int i = 0; i < MAX_PATH_INDEX; i++) + { + if (path->index[i] == -1) + { + path->index[i] = pathIndex; + path->distances[i] = abs (static_cast (distance)); + + AddLogEntry (true, LL_DEFAULT, "Path added from %d to %d", addIndex, pathIndex); + return; + } + } + + // there wasn't any free space. try exchanging it with a long-distance path + int maxDistance = -9999; + int slotID = -1; + + for (int i = 0; i < MAX_PATH_INDEX; i++) + { + if (path->distances[i] > maxDistance) + { + maxDistance = path->distances[i]; + slotID = i; + } + } + + if (slotID != -1) + { + AddLogEntry (true, LL_DEFAULT, "Path added from %d to %d", addIndex, pathIndex); + + path->index[slotID] = pathIndex; + path->distances[slotID] = abs (static_cast (distance)); + } +} + +int Waypoint::FindFarest (Vector origin, float maxDistance) +{ + // find the farest waypoint to that Origin, and return the index to this waypoint + + int index = -1; + + for (int i = 0; i < g_numWaypoints; i++) + { + float distance = (m_paths[i]->origin - origin).GetLength (); + + if (distance > maxDistance) + { + index = i; + maxDistance = distance; + } + } + return index; +} + +int Waypoint::FindNearest (Vector origin, float minDistance, int flags) +{ + // find the nearest waypoint to that Origin and return the index + + int index = -1; + + for (int i = 0; i < g_numWaypoints; i++) + { + float distance = (m_paths[i]->origin - origin).GetLength (); + + if (flags != -1 && !(m_paths[i]->flags & flags)) + continue; // if flag not -1 and waypoint has no this flag, skip waypoint + + if (distance < minDistance) + { + index = i; + minDistance = distance; + } + } + return index; +} + +void Waypoint::FindInRadius (Vector origin, float radius, int *holdTab, int *count) +{ + // returns all waypoints within radius from position + + int maxCount = *count; + *count = 0; + + for (int i = 0; i < g_numWaypoints; i++) + { + if ((m_paths[i]->origin - origin).GetLength () < radius) + { + *holdTab++ = i; + *count += 1; + + if (*count >= maxCount) + break; + } + } + *count -= 1; +} + +void Waypoint::FindInRadius (Array &queueID, float radius, Vector origin) +{ + for (int i = 0; i < g_numWaypoints; i++) + { + if ((m_paths[i]->origin - origin).GetLength () <= radius) + queueID.Push (i); + } +} + +void Waypoint::Add (int flags, const Vector &waypointOrigin) +{ + if (FNullEnt (g_hostEntity)) + return; + + int index = -1, i; + float distance; + + Vector forward = nullvec; + Path *path = NULL; + + bool placeNew = true; + Vector newOrigin = waypointOrigin; + + if (waypointOrigin == nullvec) + newOrigin = g_hostEntity->v.origin; + + if (g_botManager->GetBotsNum () > 0) + g_botManager->RemoveAll (); + + g_waypointsChanged = true; + + switch (flags) + { + case 6: + index = FindNearest (g_hostEntity->v.origin, 50.0); + + if (index != -1) + { + path = m_paths[index]; + + if (!(path->flags & FLAG_CAMP)) + { + CenterPrint ("This is not Camping Waypoint"); + return; + } + + MakeVectors (g_hostEntity->v.v_angle); + forward = g_hostEntity->v.origin + g_hostEntity->v.view_ofs + g_pGlobals->v_forward * 640; + + path->campEndX = forward.x; + path->campEndY = forward.y; + + // play "done" sound... + PlaySound (g_hostEntity, "common/wpn_hudon.wav"); + } + return; + + case 9: + index = FindNearest (g_hostEntity->v.origin, 50.0); + + if (index != -1) + { + distance = (m_paths[index]->origin - g_hostEntity->v.origin).GetLength (); + + if (distance < 50) + { + placeNew = false; + path = m_paths[index]; + + if (flags == 9) + path->origin = (path->origin + m_learnPosition) / 2; + } + } + else + newOrigin = m_learnPosition; + break; + + case 10: + index = FindNearest (g_hostEntity->v.origin, 50.0); + + if (index != -1 && m_paths[index] != NULL) + { + distance = (m_paths[index]->origin - g_hostEntity->v.origin).GetLength (); + + if (distance < 50) + { + placeNew = false; + path = m_paths[index]; + + int flags = 0; + + for (i = 0; i < MAX_PATH_INDEX; i++) + flags += path->connectionFlags[i]; + + if (flags == 0) + path->origin = (path->origin + g_hostEntity->v.origin) / 2; + } + } + break; + } + + if (placeNew) + { + if (g_numWaypoints >= MAX_WAYPOINTS) + return; + + index = g_numWaypoints; + + m_paths[index] = new Path; + + if (m_paths[index] == NULL) + TerminateOnMalloc (); + + path = m_paths[index]; + + // increment total number of waypoints + g_numWaypoints++; + path->pathNumber = index; + path->flags = 0; + + // store the origin (location) of this waypoint + path->origin = newOrigin; + + path->campEndX = 0; + path->campEndY = 0; + path->campStartX = 0; + path->campStartY = 0; + + for (i = 0; i < MAX_PATH_INDEX; i++) + { + path->index[i] = -1; + path->distances[i] = 0; + + path->connectionFlags[i] = 0; + path->connectionVelocity[i] = nullvec; + } + + // store the last used waypoint for the auto waypoint code... + m_lastWaypoint = g_hostEntity->v.origin; + } + + // set the time that this waypoint was originally displayed... + m_waypointDisplayTime[index] = 0; + + if (flags == 9) + m_lastJumpWaypoint = index; + else if (flags == 10) + { + distance = (m_paths[m_lastJumpWaypoint]->origin - g_hostEntity->v.origin).GetLength (); + AddPath (m_lastJumpWaypoint, index, distance); + + for (i = 0; i < MAX_PATH_INDEX; i++) + { + if (m_paths[m_lastJumpWaypoint]->index[i] == index) + { + m_paths[m_lastJumpWaypoint]->connectionFlags[i] |= PATHFLAG_JUMP; + m_paths[m_lastJumpWaypoint]->connectionVelocity[i] = m_learnVelocity; + + break; + } + } + + CalculateWayzone (index); + return; + } + + if (g_hostEntity->v.flags & FL_DUCKING) + path->flags |= FLAG_CROUCH; // set a crouch waypoint + + if (g_hostEntity->v.movetype == MOVETYPE_FLY) + { + path->flags |= FLAG_LADDER; + MakeVectors (g_hostEntity->v.v_angle); + + forward = g_hostEntity->v.origin + g_hostEntity->v.view_ofs + g_pGlobals->v_forward * 640; + path->campStartY = forward.y; + } + else if (m_isOnLadder) + path->flags |= FLAG_LADDER; + + switch (flags) + { + case 1: + path->flags |= FLAG_CROSSING; + path->flags |= FLAG_TF_ONLY; + break; + + case 2: + path->flags |= FLAG_CROSSING; + path->flags |= FLAG_CF_ONLY; + break; + + case 3: + path->flags |= FLAG_NOHOSTAGE; + break; + + case 4: + path->flags |= FLAG_RESCUE; + break; + + case 5: + path->flags |= FLAG_CROSSING; + path->flags |= FLAG_CAMP; + + MakeVectors (g_hostEntity->v.v_angle); + forward = g_hostEntity->v.origin + g_hostEntity->v.view_ofs + g_pGlobals->v_forward * 640; + + path->campStartX = forward.x; + path->campStartY = forward.y; + break; + + case 100: + path->flags |= FLAG_GOAL; + break; + } + + // Ladder waypoints need careful connections + if (path->flags & FLAG_LADDER) + { + float minDistance = 9999.0; + int destIndex = -1; + + TraceResult tr; + + // calculate all the paths to this new waypoint + for (i = 0; i < g_numWaypoints; i++) + { + if (i == index) + continue; // skip the waypoint that was just added + + // Other ladder waypoints should connect to this + if (m_paths[i]->flags & FLAG_LADDER) + { + // check if the waypoint is reachable from the new one + TraceLine (newOrigin, m_paths[i]->origin, true, g_hostEntity, &tr); + + if (tr.flFraction == 1.0 && fabs (newOrigin.x - m_paths[i]->origin.x) < 64 && fabs (newOrigin.y - m_paths[i]->origin.y) < 64 && fabs (newOrigin.z - m_paths[i]->origin.z) < g_autoPathDistance) + { + distance = (m_paths[i]->origin - newOrigin).GetLength (); + + AddPath (index, i, distance); + AddPath (i, index, distance); + } + } + else + { + // check if the waypoint is reachable from the new one + if (IsNodeReachable (newOrigin, m_paths[i]->origin) || IsNodeReachable (m_paths[i]->origin, newOrigin)) + { + distance = (m_paths[i]->origin - newOrigin).GetLength (); + + if (distance < minDistance) + { + destIndex = i; + minDistance = distance; + } + } + } + } + + if (destIndex > -1 && destIndex < g_numWaypoints) + { + // check if the waypoint is reachable from the new one (one-way) + if (IsNodeReachable (newOrigin, m_paths[destIndex]->origin)) + { + distance = (m_paths[destIndex]->origin - newOrigin).GetLength (); + AddPath (index, destIndex, distance); + } + + // check if the new one is reachable from the waypoint (other way) + if (IsNodeReachable (m_paths[destIndex]->origin, newOrigin)) + { + distance = (m_paths[destIndex]->origin - newOrigin).GetLength (); + AddPath (destIndex, index, distance); + } + } + } + else + { + // calculate all the paths to this new waypoint + for (i = 0; i < g_numWaypoints; i++) + { + if (i == index) + continue; // skip the waypoint that was just added + + // check if the waypoint is reachable from the new one (one-way) + if (IsNodeReachable (newOrigin, m_paths[i]->origin)) + { + distance = (m_paths[i]->origin - newOrigin).GetLength (); + AddPath (index, i, distance); + } + + // check if the new one is reachable from the waypoint (other way) + if (IsNodeReachable (m_paths[i]->origin, newOrigin)) + { + distance = (m_paths[i]->origin - newOrigin).GetLength (); + AddPath (i, index, distance); + } + } + } + PlaySound (g_hostEntity, "weapons/xbow_hit1.wav"); + CalculateWayzone (index); // calculate the wayzone of this waypoint +} + +void Waypoint::Delete (void) +{ + g_waypointsChanged = true; + + if (g_numWaypoints < 1) + return; + + if (g_botManager->GetBotsNum () > 0) + g_botManager->RemoveAll (); + + int index = FindNearest (g_hostEntity->v.origin, 50.0); + + if (index == -1) + return; + + Path *path = NULL; + InternalAssert (m_paths[index] != NULL); + + int i, j; + + for (i = 0; i < g_numWaypoints; i++) // delete all references to Node + { + path = m_paths[i]; + + for (j = 0; j < MAX_PATH_INDEX; j++) + { + if (path->index[j] == index) + { + path->index[j] = -1; // unassign this path + path->connectionFlags[j] = 0; + path->distances[j] = 0; + path->connectionVelocity[j] = nullvec; + } + } + } + + for (i = 0; i < g_numWaypoints; i++) + { + path = m_paths[i]; + + if (path->pathNumber > index) // if pathnumber bigger than deleted node... + path->pathNumber--; + + for (j = 0; j < MAX_PATH_INDEX; j++) + { + if (path->index[j] > index) + path->index[j]--; + } + } + + // free deleted node + delete m_paths[index]; + m_paths[index] = NULL; + + // Rotate Path Array down + for (i = index; i < g_numWaypoints - 1; i++) + m_paths[i] = m_paths[i + 1]; + + g_numWaypoints--; + m_waypointDisplayTime[index] = 0; + + PlaySound (g_hostEntity, "weapons/mine_activate.wav"); +} + +void Waypoint::ToggleFlags (int toggleFlag) +{ + // this function allow manually changing flags + + int index = FindNearest (g_hostEntity->v.origin, 50.0); + + if (index != -1) + { + if (m_paths[index]->flags & toggleFlag) + m_paths[index]->flags &= ~toggleFlag; + + else if (!(m_paths[index]->flags & toggleFlag)) + { + if (toggleFlag == FLAG_SNIPER && !(m_paths[index]->flags & FLAG_CAMP)) + { + AddLogEntry (true, LL_ERROR, "Cannot assign sniper flag to waypoint #%d. This is not camp waypoint", index); + return; + } + m_paths[index]->flags |= toggleFlag; + } + + // play "done" sound... + PlaySound (g_hostEntity, "common/wpn_hudon.wav"); + } +} + +void Waypoint::SetRadius (int radius) +{ + // this function allow manually setting the zone radius + + int index = FindNearest (g_hostEntity->v.origin, 50.0); + + if (index != -1) + { + m_paths[index]->radius = static_cast (radius); + + // play "done" sound... + PlaySound (g_hostEntity, "common/wpn_hudon.wav"); + } +} + +bool Waypoint::IsConnected (int pointA, int pointB) +{ + // this function checks if waypoint A has a connection to waypoint B + + for (int i = 0; i < MAX_PATH_INDEX; i++) + { + if (m_paths[pointA]->index[i] == pointB) + return true; + } + return false; +} + +int Waypoint::GetFacingIndex (void) +{ + // this function finds waypoint the user is pointing at. + + int pointedIndex = -1; + float viewCone[3] = {0.0, 0.0, 0.0}; + + // find the waypoint the user is pointing at + for (int i = 0; i < g_numWaypoints; i++) + { + if ((m_paths[i]->origin - g_hostEntity->v.origin).GetLengthSquared () > 250000) + continue; + + // get the current view cone + viewCone[0] = GetShootingConeDeviation (g_hostEntity, &m_paths[i]->origin); + Vector bound = m_paths[i]->origin - Vector (0, 0, m_paths[i]->flags & FLAG_CROUCH ? 8 : 15); + + // get the current view cone + viewCone[1] = GetShootingConeDeviation (g_hostEntity, &bound); + bound = m_paths[i]->origin + Vector (0, 0, m_paths[i]->flags & FLAG_CROUCH ? 8 : 15); + + // get the current view cone + viewCone[2] = GetShootingConeDeviation (g_hostEntity, &bound); + + // check if we can see it + if (viewCone[0] < 0.998 && viewCone[1] < 0.997 && viewCone[2] < 0.997) + continue; + + pointedIndex = i; + } + return pointedIndex; +} + +void Waypoint::CreatePath (char dir) +{ + // this function allow player to manually create a path from one waypoint to another + + int nodeFrom = FindNearest (g_hostEntity->v.origin, 50.0); + + if (nodeFrom == -1) + { + CenterPrint ("Unable to find nearest waypoint in 50 units"); + return; + } + int nodeTo = m_facingAtIndex; + + if (nodeTo < 0 || nodeTo >= g_numWaypoints) + { + if (m_cacheWaypointIndex >= 0 && m_cacheWaypointIndex < g_numWaypoints) + nodeTo = m_cacheWaypointIndex; + else + { + CenterPrint ("Unable to find destination waypoint"); + return; + } + } + + if (nodeTo == nodeFrom) + { + CenterPrint ("Unable to connect waypoint with itself"); + return; + } + + float distance = (m_paths[nodeTo]->origin - m_paths[nodeFrom]->origin).GetLength (); + + if (dir == CONNECTION_OUTGOING) + AddPath (nodeFrom, nodeTo, distance); + else if (dir == CONNECTION_INCOMING) + AddPath (nodeTo, nodeFrom, distance); + else + { + AddPath (nodeFrom, nodeTo, distance); + AddPath (nodeTo, nodeFrom, distance); + } + + PlaySound (g_hostEntity, "common/wpn_hudon.wav"); + g_waypointsChanged = true; +} + + +void Waypoint::DeletePath (void) +{ + // this function allow player to manually remove a path from one waypoint to another + + int nodeFrom = FindNearest (g_hostEntity->v.origin, 50.0); + int index = 0; + + if (nodeFrom == -1) + { + CenterPrint ("Unable to find nearest waypoint in 50 units"); + return; + } + int nodeTo = m_facingAtIndex; + + if (nodeTo < 0 || nodeTo >= g_numWaypoints) + { + if (m_cacheWaypointIndex >= 0 && m_cacheWaypointIndex < g_numWaypoints) + nodeTo = m_cacheWaypointIndex; + else + { + CenterPrint ("Unable to find destination waypoint"); + return; + } + } + + for (index = 0; index < MAX_PATH_INDEX; index++) + { + if (m_paths[nodeFrom]->index[index] == nodeTo) + { + g_waypointsChanged = true; + + m_paths[nodeFrom]->index[index] = -1; // unassign this path + m_paths[nodeFrom]->connectionFlags[index] = 0; + m_paths[nodeFrom]->connectionVelocity[index] = nullvec; + m_paths[nodeFrom]->distances[index] = 0; + + PlaySound (g_hostEntity, "weapons/mine_activate.wav"); + return; + } + } + + // not found this way ? check for incoming connections then + index = nodeFrom; + nodeFrom = nodeTo; + nodeTo = index; + + for (index = 0; index < MAX_PATH_INDEX; index++) + { + if (m_paths[nodeFrom]->index[index] == nodeTo) + { + g_waypointsChanged = true; + + m_paths[nodeFrom]->index[index] = -1; // unassign this path + m_paths[nodeFrom]->connectionFlags[index] = 0; + m_paths[nodeFrom]->connectionVelocity[index] = nullvec; + m_paths[nodeFrom]->distances[index] = 0; + + PlaySound (g_hostEntity, "weapons/mine_activate.wav"); + return; + } + } + CenterPrint ("There is already no path on this waypoint"); +} + +void Waypoint::CacheWaypoint (void) +{ + int node = FindNearest (g_hostEntity->v.origin, 50.0); + + if (node == -1) + { + m_cacheWaypointIndex = -1; + CenterPrint ("Cached waypoint cleared (nearby point not found in 50 units range)"); + + return; + } + m_cacheWaypointIndex = node; + CenterPrint ("Waypoint #%d has been put into memory", m_cacheWaypointIndex); +} + +void Waypoint::CalculateWayzone (int index) +{ + // calculate "wayzones" for the nearest waypoint to pentedict (meaning a dynamic distance area to vary waypoint origin) + + Path *path = m_paths[index]; + Vector start, direction; + + TraceResult tr; + bool wayBlocked = false; + + if ((path->flags & (FLAG_LADDER | FLAG_GOAL | FLAG_CAMP | FLAG_RESCUE | FLAG_CROUCH)) || m_learnJumpWaypoint) + { + path->radius = 0; + return; + } + + for (int i = 0; i < MAX_PATH_INDEX; i++) + { + if (path->index[i] != -1 && (m_paths[path->index[i]]->flags & FLAG_LADDER)) + { + path->radius = 0; + return; + } + } + + for (int scanDistance = 16; scanDistance < 128; scanDistance += 16) + { + start = path->origin; + MakeVectors (nullvec); + + direction = g_pGlobals->v_forward * scanDistance; + direction = direction.ToAngles (); + + path->radius = scanDistance; + + for (float circleRadius = 0.0; circleRadius < 180.0; circleRadius += 5) + { + MakeVectors (direction); + + Vector radiusStart = start - g_pGlobals->v_forward * scanDistance; + Vector radiusEnd = start + g_pGlobals->v_forward * scanDistance; + + TraceHull (radiusStart, radiusEnd, true, head_hull, NULL, &tr); + + if (tr.flFraction < 1.0) + { + TraceLine (radiusStart, radiusEnd, true, NULL, &tr); + + if (FClassnameIs (tr.pHit, "func_door") || FClassnameIs (tr.pHit, "func_door_rotating")) + { + path->radius = 0; + wayBlocked = true; + + break; + } + + wayBlocked = true; + path->radius -= 16; + + break; + } + + Vector dropStart = start + (g_pGlobals->v_forward * scanDistance); + Vector dropEnd = dropStart - Vector (0, 0, scanDistance + 60); + + TraceHull (dropStart, dropEnd, true, head_hull, NULL, &tr); + + if (tr.flFraction >= 1.0) + { + wayBlocked = true; + path->radius -= 16; + + break; + } + dropStart = start - (g_pGlobals->v_forward * scanDistance); + dropEnd = dropStart - Vector (0, 0, scanDistance + 60); + + TraceHull (dropStart, dropEnd, true, head_hull, NULL, &tr); + + if (tr.flFraction >= 1.0) + { + wayBlocked = true; + path->radius -= 16; + break; + } + + radiusEnd.z += 34; + TraceHull (radiusStart, radiusEnd, true, head_hull, NULL, &tr); + + if (tr.flFraction < 1.0) + { + wayBlocked = true; + path->radius -= 16; + break; + } + + direction.y = AngleNormalize (direction.y + circleRadius); + } + if (wayBlocked) + break; + } + path->radius -= 16; + + if (path->radius < 0) + path->radius = 0; +} + +void Waypoint::SaveExperienceTab (void) +{ + ExtensionHeader header; + + if ((g_numWaypoints <= 0) || g_waypointsChanged) + return; + + memset (header.header, 0, sizeof (header.header)); + strcpy (header.header, FH_EXPERIENCE); + + header.fileVersion = FV_EXPERIENCE; + header.pointNumber = g_numWaypoints; + + ExperienceSave *experienceSave = new ExperienceSave[g_numWaypoints * g_numWaypoints]; + + for (int i = 0; i < g_numWaypoints; i++) + { + for (int j = 0; j < g_numWaypoints; j++) + { + (experienceSave + (i * g_numWaypoints) + j)->team0Damage = (g_experienceData + (i * g_numWaypoints) + j)->team0Damage >> 3; + (experienceSave + (i * g_numWaypoints) + j)->team1Damage = (g_experienceData + (i * g_numWaypoints) + j)->team1Damage >> 3; + (experienceSave + (i * g_numWaypoints) + j)->team0Value = (g_experienceData + (i * g_numWaypoints) + j)->team0Value / 8; + (experienceSave + (i * g_numWaypoints) + j)->team1Value = (g_experienceData + (i * g_numWaypoints) + j)->team1Value / 8; + } + } + + int result = Compressor::Compress (FormatBuffer ("%sdata/%s.exp", GetWaypointDir (), GetMapName ()), (unsigned char *)&header, sizeof (ExtensionHeader), (unsigned char *)experienceSave, g_numWaypoints * g_numWaypoints * sizeof (ExperienceSave)); + + delete [] experienceSave; + + if (result == -1) + { + AddLogEntry (true, LL_ERROR, "Couldn't save experience data"); + return; + } +} + +void Waypoint::InitExperienceTab (void) +{ + ExtensionHeader header; + int i, j; + + delete [] g_experienceData; + g_experienceData = NULL; + + if (g_numWaypoints < 1) + return; + + g_experienceData = new Experience[g_numWaypoints * g_numWaypoints]; + + // initialize table by hand to correct values, and NOT zero it out + for (i = 0; i < g_numWaypoints; i++) + { + for (j = 0; j < g_numWaypoints; j++) + { + (g_experienceData + (i * g_numWaypoints) + j)->team0DangerIndex = -1; + (g_experienceData + (i * g_numWaypoints) + j)->team1DangerIndex = -1; + (g_experienceData + (i * g_numWaypoints) + j)->team0Damage = 0; + (g_experienceData + (i * g_numWaypoints) + j)->team1Damage = 0; + (g_experienceData + (i * g_numWaypoints) + j)->team0Value = 0; + (g_experienceData + (i * g_numWaypoints) + j)->team1Value = 0; + } + } + File fp (FormatBuffer ("%sdata/%s.exp", GetWaypointDir (), GetMapName ()), "rb"); + + // if file exists, read the experience data from it + if (fp.IsValid ()) + { + fp.Read (&header, sizeof (ExtensionHeader)); + fp.Close (); + + if (strncmp (header.header, FH_EXPERIENCE, strlen (FH_EXPERIENCE)) == 0) + { + if (header.fileVersion == FV_EXPERIENCE && header.pointNumber == g_numWaypoints) + { + ExperienceSave *experienceLoad = new ExperienceSave[g_numWaypoints * g_numWaypoints]; + + Compressor::Uncompress (FormatBuffer ("%sdata/%s.exp", GetWaypointDir (), GetMapName ()), sizeof (ExtensionHeader), (unsigned char *)experienceLoad, g_numWaypoints * g_numWaypoints * sizeof (ExperienceSave)); + + for (i = 0; i < g_numWaypoints; i++) + { + for (j = 0; j < g_numWaypoints; j++) + { + if (i == j) + { + (g_experienceData + (i * g_numWaypoints) + j)->team0Damage = (unsigned short) ((experienceLoad + (i * g_numWaypoints) + j)->team0Damage); + (g_experienceData + (i * g_numWaypoints) + j)->team1Damage = (unsigned short) ((experienceLoad + (i * g_numWaypoints) + j)->team1Damage); + } + else + { + (g_experienceData + (i * g_numWaypoints) + j)->team0Damage = (unsigned short) ((experienceLoad + (i * g_numWaypoints) + j)->team0Damage) << 3; + (g_experienceData + (i * g_numWaypoints) + j)->team1Damage = (unsigned short) ((experienceLoad + (i * g_numWaypoints) + j)->team1Damage) << 3; + } + + (g_experienceData + (i * g_numWaypoints) + j)->team0Value = (signed short) ((experienceLoad + i * (g_numWaypoints) + j)->team0Value) * 8; + (g_experienceData + (i * g_numWaypoints) + j)->team1Value = (signed short) ((experienceLoad + i * (g_numWaypoints) + j)->team1Value) * 8; + } + } + delete [] experienceLoad; + } + else + AddLogEntry (true, LL_ERROR, "Experience data damaged (wrong version, or not for this map)"); + } + } +} + +void Waypoint::SaveVisibilityTab (void) +{ + if (g_numWaypoints == 0) + return; + + if (m_visLUT == NULL) + AddLogEntry (true, LL_FATAL, "Can't save visiblity tab. Bad data."); + + ExtensionHeader header; + + // parse header + memset (header.header, 0, sizeof (header.header)); + strcpy (header.header, FH_VISTABLE); + + header.fileVersion = FV_VISTABLE; + header.pointNumber = g_numWaypoints; + + File fp (FormatBuffer ("%sdata/%s.vis", GetWaypointDir (), GetMapName ()), "wb"); + + if (!fp.IsValid ()) + { + AddLogEntry (true, LL_ERROR, "Failed to open visiblity table for writing"); + return; + } + fp.Close (); + + Compressor::Compress (FormatBuffer ("%sdata/%s.vis", GetWaypointDir (), GetMapName ()), (unsigned char *) &header, sizeof (ExtensionHeader), (unsigned char *) m_visLUT, MAX_WAYPOINTS * (MAX_WAYPOINTS / 4) * sizeof (byte)); +} + +void Waypoint::InitVisibilityTab (void) +{ + if (g_numWaypoints == 0) + return; + + ExtensionHeader header; + + File fp (FormatBuffer ("%sdata/%s.vis", GetWaypointDir (), GetMapName ()), "rb"); + m_redoneVisibility = false; + + if (!fp.IsValid ()) + { + m_visibilityIndex = 0; + m_redoneVisibility = true; + + AddLogEntry (true, LL_DEFAULT, "Vistable, not exists, vistable will be rebuilded"); + return; + } + + // read the header of the file + fp.Read (&header, sizeof (ExtensionHeader)); + + if (strncmp (header.header, FH_VISTABLE, strlen (FH_VISTABLE)) != 0 || header.fileVersion != FV_VISTABLE || header.pointNumber != g_numWaypoints) + { + m_visibilityIndex = 0; + m_redoneVisibility = true; + + AddLogEntry (true, LL_WARNING, "Vistable damaged (wrong version, or not for this map), vistable will be rebuilded."); + fp.Close (); + + return; + } + int result = Compressor::Uncompress (FormatBuffer ("%sdata/%s.vis", GetWaypointDir (), GetMapName ()), sizeof (ExtensionHeader), (unsigned char *) m_visLUT, MAX_WAYPOINTS * (MAX_WAYPOINTS / 4) * sizeof (byte)); + + if (result == -1) + { + m_visibilityIndex = 0; + m_redoneVisibility = true; + + AddLogEntry (true, LL_ERROR, "Failed to decode vistable, vistable will be rebuilded."); + fp.Close (); + + return; + } + fp.Close (); +} + +void Waypoint::InitTypes (void) +{ + m_terrorPoints.RemoveAll (); + m_ctPoints.RemoveAll (); + m_goalPoints.RemoveAll (); + m_campPoints.RemoveAll (); + m_rescuePoints.RemoveAll (); + m_sniperPoints.RemoveAll (); + m_visitedGoals.RemoveAll (); + + for (int i = 0; i < g_numWaypoints; i++) + { + if (m_paths[i]->flags & FLAG_TF_ONLY) + m_terrorPoints.Push (i); + else if (m_paths[i]->flags & FLAG_CF_ONLY) + m_ctPoints.Push (i); + else if (m_paths[i]->flags & FLAG_GOAL) + m_goalPoints.Push (i); + else if (m_paths[i]->flags & FLAG_CAMP) + m_campPoints.Push (i); + else if (m_paths[i]->flags & FLAG_SNIPER) + m_sniperPoints.Push (i); + else if (m_paths[i]->flags & FLAG_RESCUE) + m_rescuePoints.Push (i); + } +} + +bool Waypoint::Load (void) +{ + WaypointHeader header; + File fp (CheckSubfolderFile (), "rb"); + + if (fp.IsValid ()) + { + fp.Read (&header, sizeof (header)); + + if (strncmp (header.header, FH_WAYPOINT, strlen (FH_WAYPOINT)) == 0) + { + if (header.fileVersion != FV_WAYPOINT) + { + sprintf (m_infoBuffer, "%s.pwf - incorrect waypoint file version (expected '%i' found '%i')", GetMapName (), FV_WAYPOINT, static_cast (header.fileVersion)); + AddLogEntry (true, LL_ERROR, m_infoBuffer); + + fp.Close (); + return false; + } + else if (stricmp (header.mapName, GetMapName ())) + { + sprintf (m_infoBuffer, "%s.pwf - hacked waypoint file, file name doesn't match waypoint header information (mapname: '%s', header: '%s')", GetMapName (), GetMapName (), header.mapName); + AddLogEntry (true, LL_ERROR, m_infoBuffer); + + fp.Close (); + return false; + } + else + { + Init (); + g_numWaypoints = header.pointNumber; + + for (int i = 0; i < g_numWaypoints; i++) + { + m_paths[i] = new Path; + + if (m_paths[i] == NULL) + TerminateOnMalloc (); + + fp.Read (m_paths[i], sizeof (Path)); + } + m_waypointPaths = true; + } + } + else + { + sprintf (m_infoBuffer, "%s.pwf is not a yapb waypoint file (header found '%s' needed '%s'", GetMapName (), header.header, FH_WAYPOINT); + AddLogEntry (true, LL_ERROR, m_infoBuffer); + + fp.Close (); + return false; + } + fp.Close (); + } + else + { + if (yb_waypoint_autodl_enable.GetBool ()) + { + AddLogEntry (true, LL_DEFAULT, "%s.pwf does not exist, trying to download from waypoint database", GetMapName ()); + + WaypointDownloader dl; + WaypointDownloadError status = dl.DoDownload (); + + if (status == WDE_SOCKET_ERROR) + { + sprintf (m_infoBuffer, "%s.pwf does not exist. Can't autodownload. Socket error.", GetMapName ()); + AddLogEntry (true, LL_ERROR, m_infoBuffer); + + yb_waypoint_autodl_enable.SetInt (0); + + return false; + } + else if (status == WDE_CONNECT_ERROR) + { + sprintf (m_infoBuffer, "%s.pwf does not exist. Can't autodownload. Connection problems.", GetMapName ()); + AddLogEntry (true, LL_ERROR, m_infoBuffer); + + yb_waypoint_autodl_enable.SetInt (0); + + return false; + } + else if (status == WDE_NOTFOUND_ERROR) + { + sprintf (m_infoBuffer, "%s.pwf does not exist. Can't autodownload. Waypoint not available.", GetMapName ()); + AddLogEntry (true, LL_ERROR, m_infoBuffer); + + return false; + } + else + { + AddLogEntry (true, LL_DEFAULT, "%s.pwf was downloaded from waypoint database. Trying to load...", GetMapName ()); + return Load (); + } + } + sprintf (m_infoBuffer, "%s.pwf does not exist", GetMapName ()); + AddLogEntry (true, LL_ERROR, m_infoBuffer); + + return false; + } + + if (strncmp (header.author, "official", 7) == 0) + sprintf (m_infoBuffer, "Using Official Waypoint File"); + else + sprintf (m_infoBuffer, "Using waypoint file by: %s", header.author); + + for (int i = 0; i < g_numWaypoints; i++) + m_waypointDisplayTime[i] = 0.0; + + InitPathMatrix (); + InitTypes (); + + g_waypointsChanged = false; + g_killHistory = 0; + + m_pathDisplayTime = 0.0; + m_arrowDisplayTime = 0.0; + + InitVisibilityTab (); + InitExperienceTab (); + + g_botManager->InitQuota (); + + extern ConVar yb_debug_goal; + yb_debug_goal.SetInt (-1); + + return true; +} + +void Waypoint::Save (void) +{ + WaypointHeader header; + + memset (header.mapName, 0, sizeof (header.mapName)); + memset (header.author , 0, sizeof (header.author)); + memset (header.header, 0, sizeof (header.header)); + + strcpy (header.header, FH_WAYPOINT); + strcpy (header.author, STRING (g_hostEntity->v.netname)); + strncpy (header.mapName, GetMapName (), 31); + + header.mapName[31] = 0; + header.fileVersion = FV_WAYPOINT; + header.pointNumber = g_numWaypoints; + + File fp (CheckSubfolderFile (), "wb"); + + // file was opened + if (fp.IsValid ()) + { + // write the waypoint header to the file... + fp.Write (&header, sizeof (header), 1); + + // save the waypoint paths... + for (int i = 0; i < g_numWaypoints; i++) + fp.Write (m_paths[i], sizeof (Path)); + + fp.Close (); + } + else + AddLogEntry (true, LL_ERROR, "Error writing '%s.pwf' waypoint file", GetMapName ()); +} + +String Waypoint::CheckSubfolderFile (void) +{ + String returnFile = ""; + + if (!IsNullString (yb_wptsubfolder.GetString ())) + returnFile += (String (yb_wptsubfolder.GetString ()) + "/"); + + returnFile = FormatBuffer ("%s%s%s.pwf", GetWaypointDir (), returnFile.GetBuffer (), GetMapName ()); + + if (TryFileOpen (returnFile)) + return returnFile; + + return FormatBuffer ("%s%s.pwf", GetWaypointDir (), GetMapName ()); +} + +float Waypoint::GetTravelTime (float maxSpeed, Vector src, Vector origin) +{ + // this function returns 2D traveltime to a position + + return (origin - src).GetLength2D () / maxSpeed; +} + +bool Waypoint::Reachable (Bot *bot, int index) +{ + // this function return wether bot able to reach index waypoint or not, depending on several factors. + + if (index < 0 || index >= g_numWaypoints) + return false; + + Vector src = bot->pev->origin; + Vector dest = GetPath (index)->origin; + + float distance = (dest - src).GetLength (); + float distance2D = (dest - src).GetLength2D (); + + // check is destination is close to us enoguh + if (distance >= 201) // Default: 201 + return false; + + if (bot->pev->waterlevel == 2 || bot->pev->waterlevel == 3) + { + // is destination waypoint higher that source (45 is max jump height), or destination waypoint higher that source + if ((dest.z > src.z + 40.0 || dest.z < src.z - 75.0) && (!(GetPath (index)->flags & FLAG_LADDER) || distance2D >= 16.0)) + return false; // unable to reach this one + } + + TraceResult tr; + TraceLine (src, dest, true, bot->GetEntity (), &tr); + + // if waypoint is visible from current position (even behind head)... + if (tr.flFraction >= 1.0) + return true; + + return false; +} + +bool Waypoint::IsNodeReachable (Vector src, Vector destination) +{ + TraceResult tr; + + float height, lastHeight; + float distance = (destination - src).GetLength (); + + // is the destination not close enough? + if (distance > g_autoPathDistance) + return false; + + // check if we go through a func_illusionary, in which case return false + TraceHull (src, destination, ignore_monsters, head_hull, g_hostEntity, &tr); + + if (!FNullEnt (tr.pHit) && strcmp ("func_illusionary", STRING (tr.pHit->v.classname)) == 0) + return false; // don't add pathwaypoints through func_illusionaries + + // check if this waypoint is "visible"... + TraceLine (src, destination, ignore_monsters, g_hostEntity, &tr); + + // if waypoint is visible from current position (even behind head)... + if (tr.flFraction >= 1.0 || strncmp ("func_door", STRING (tr.pHit->v.classname), 9) == 0) + { + // if it's a door check if nothing blocks behind + if (strncmp ("func_door", STRING (tr.pHit->v.classname), 9) == 0) + { + TraceLine (tr.vecEndPos, destination, ignore_monsters, tr.pHit, &tr); + + if (tr.flFraction < 1.0) + return false; + } + + // check for special case of both waypoints being in water... + if (POINT_CONTENTS (src) == CONTENTS_WATER && POINT_CONTENTS (destination) == CONTENTS_WATER) + return true; // then they're reachable each other + + // is dest waypoint higher than src? (45 is max jump height) + if (destination.z > src.z + 45.0) + { + Vector sourceNew = destination; + Vector destinationNew = destination; + destinationNew.z = destinationNew.z - 50; // straight down 50 units + + TraceLine (sourceNew, destinationNew, ignore_monsters, g_hostEntity, &tr); + + // check if we didn't hit anything, if not then it's in mid-air + if (tr.flFraction >= 1.0) + return false; // can't reach this one + } + + // check if distance to ground drops more than step height at points between source and destination... + Vector direction = (destination - src).Normalize(); // 1 unit long + Vector check = src, down = src; + + down.z = down.z - 1000.0; // straight down 1000 units + + TraceLine (check, down, ignore_monsters, g_hostEntity, &tr); + + lastHeight = tr.flFraction * 1000.0; // height from ground + distance = (destination - check).GetLength (); // distance from goal + + while (distance > 10.0) + { + // move 10 units closer to the goal... + check = check + (direction * 10.0); + + down = check; + down.z = down.z - 1000.0; // straight down 1000 units + + TraceLine (check, down, ignore_monsters, g_hostEntity, &tr); + + height = tr.flFraction * 1000.0; // height from ground + + // is the current height greater than the step height? + if (height < lastHeight - 18.0) + return false; // can't get there without jumping... + + lastHeight = height; + distance = (destination - check).GetLength (); // distance from goal + } + return true; + } + return false; +} + +void Waypoint::InitializeVisibility (void) +{ + if (m_redoneVisibility == false) + return; + + TraceResult tr; + byte res, shift; + + for (m_visibilityIndex = 0; m_visibilityIndex < g_numWaypoints; m_visibilityIndex++) + { + Vector sourceDuck = m_paths[m_visibilityIndex]->origin; + Vector sourceStand = m_paths[m_visibilityIndex]->origin; + + if (m_paths[m_visibilityIndex]->flags & FLAG_CROUCH) + { + sourceDuck.z += 12.0; + sourceStand.z += 18.0 + 28.0; + } + else + { + sourceDuck.z += -18.0 + 12.0; + sourceStand.z += 28.0; + } + uint16 standCount = 0, crouchCount = 0; + + for (int i = 0; i < g_numWaypoints; i++) + { + // first check ducked visibility + Vector dest = m_paths[i]->origin; + + TraceLine (sourceDuck, dest, true, NULL, &tr); + + // check if line of sight to object is not blocked (i.e. visible) + if ((tr.flFraction != 1.0) || tr.fStartSolid) + res = 1; + else + res = 0; + + res <<= 1; + + TraceLine (sourceStand, dest, true, NULL, &tr); + + // check if line of sight to object is not blocked (i.e. visible) + if ((tr.flFraction != 1.0) || tr.fStartSolid) + res |= 1; + + shift = (i % 4) << 1; + m_visLUT[m_visibilityIndex][i >> 2] &= ~(3 << shift); + m_visLUT[m_visibilityIndex][i >> 2] |= res << shift; + + if (!(res & 2)) + crouchCount++; + + if (!(res & 1)) + standCount++; + } + m_paths[m_visibilityIndex]->vis.crouch = crouchCount; + m_paths[m_visibilityIndex]->vis.stand = standCount; + } + m_redoneVisibility = false; +} + +bool Waypoint::IsVisible (int srcIndex, int destIndex) +{ + unsigned char res = m_visLUT[srcIndex][destIndex >> 2]; + res >>= (destIndex % 4) << 1; + + return !((res & 3) == 3); +} + +bool Waypoint::IsDuckVisible (int srcIndex, int destIndex) +{ + unsigned char res = m_visLUT[srcIndex][destIndex >> 2]; + res >>= (destIndex % 4) << 1; + + return !((res & 2) == 2); +} + +bool Waypoint::IsStandVisible (int srcIndex, int destIndex) +{ + unsigned char res = m_visLUT[srcIndex][destIndex >> 2]; + res >>= (destIndex % 4) << 1; + + return !((res & 1) == 1); +} + +char *Waypoint::GetWaypointInfo (int id) +{ + // this function returns path information for waypoint pointed by id. + + Path *path = GetPath (id); + + // if this path is null, return + if (path == NULL) + return "\0"; + + bool jumpPoint = false; + + // iterate through connections and find, if it's a jump path + for (int i = 0; i < MAX_PATH_INDEX; i++) + { + // check if we got a valid connection + if (path->index[i] != -1 && (path->connectionFlags[i] & PATHFLAG_JUMP)) + jumpPoint = true; + } + + static char messageBuffer[1024]; + sprintf (messageBuffer, "%s%s%s%s%s%s%s%s%s%s%s%s%s%s", (path->flags == 0 && !jumpPoint) ? " (none)" : "", path->flags & FLAG_LIFT ? " LIFT" : "", path->flags & FLAG_CROUCH ? " CROUCH" : "", path->flags & FLAG_CROSSING ? " CROSSING" : "", path->flags & FLAG_CAMP ? " CAMP" : "", path->flags & FLAG_TF_ONLY ? " TERRORIST" : "", path->flags & FLAG_CF_ONLY ? " CT" : "", path->flags & FLAG_SNIPER ? " SNIPER" : "", path->flags & FLAG_GOAL ? " GOAL" : "", path->flags & FLAG_LADDER ? " LADDER" : "", path->flags & FLAG_RESCUE ? " RESCUE" : "", path->flags & FLAG_DOUBLEJUMP ? " JUMPHELP" : "", path->flags & FLAG_NOHOSTAGE ? " NOHOSTAGE" : "", jumpPoint ? " JUMP" : ""); + + // return the message buffer + return messageBuffer; +} + +void Waypoint::Think (void) +{ + // this function executes frame of waypoint operation code. + + if (FNullEnt (g_hostEntity)) + return; // this function is only valid on listenserver, and in waypoint enabled mode. + + float nearestDistance = FLT_MAX; + int nearestIndex = -1; + + // check if it's time to add jump waypoint + if (m_learnJumpWaypoint) + { + if (!m_endJumpPoint) + { + if (g_hostEntity->v.button & IN_JUMP) + { + Add (9); + + m_timeJumpStarted = GetWorldTime (); + m_endJumpPoint = true; + } + else + { + m_learnVelocity = g_hostEntity->v.velocity; + m_learnPosition = g_hostEntity->v.origin; + } + } + else if (((g_hostEntity->v.flags & FL_ONGROUND) || g_hostEntity->v.movetype == MOVETYPE_FLY) && m_timeJumpStarted + 0.1 < GetWorldTime () && m_endJumpPoint) + { + Add (10); + + m_learnJumpWaypoint = false; + m_endJumpPoint = false; + } + } + + // check if it's a autowaypoint mode enabled + if (g_autoWaypoint && (g_hostEntity->v.flags & (FL_ONGROUND | FL_PARTIALGROUND))) + { + // find the distance from the last used waypoint + float distance = (m_lastWaypoint - g_hostEntity->v.origin).GetLengthSquared (); + + if (distance > 16384) + { + // check that no other reachable waypoints are nearby... + for (int i = 0; i < g_numWaypoints; i++) + { + if (IsNodeReachable (g_hostEntity->v.origin, m_paths[i]->origin)) + { + distance = (m_paths[i]->origin - g_hostEntity->v.origin).GetLengthSquared (); + + if (distance < nearestDistance) + nearestDistance = distance; + } + } + + // make sure nearest waypoint is far enough away... + if (nearestDistance >= 16384) + Add (0); // place a waypoint here + } + } + m_facingAtIndex = GetFacingIndex (); + + // reset the minimal distance changed before + nearestDistance = FLT_MAX; + + // now iterate through all waypoints in a map, and draw required ones + for (int i = 0; i < g_numWaypoints; i++) + { + float distance = (m_paths[i]->origin - g_hostEntity->v.origin).GetLengthSquared (); + + // check if waypoint is whitin a distance, and is visible + if (distance < 500 * 500 && ((::IsVisible (m_paths[i]->origin, g_hostEntity) && IsInViewCone (m_paths[i]->origin, g_hostEntity)) || !IsAlive (g_hostEntity) || distance < 2500)) + { + // check the distance + if (distance < nearestDistance) + { + nearestIndex = i; + nearestDistance = distance; + } + + if (m_waypointDisplayTime[i] + 1.0 < GetWorldTime ()) + { + float nodeHeight = 0.0; + + // check the node height + if (m_paths[i]->flags & FLAG_CROUCH) + nodeHeight = 36.0; + else + nodeHeight = 72.0; + + float nodeHalfHeight = nodeHeight * 0.5; + + // all waypoints are by default are green + Vector nodeColor = nullvec; + + // colorize all other waypoints + if (m_paths[i]->flags & FLAG_CAMP) + nodeColor = Vector (0, 255, 255); + else if (m_paths[i]->flags & FLAG_GOAL) + nodeColor = Vector (128, 0, 255); + else if (m_paths[i]->flags & FLAG_LADDER) + nodeColor = Vector (128, 64, 0); + else if (m_paths[i]->flags & FLAG_RESCUE) + nodeColor = Vector (255, 255, 255); + else + nodeColor = Vector (0, 255, 0); + + // colorize additional flags + Vector nodeFlagColor = Vector (-1, -1, -1); + + // check the colors + if (m_paths[i]->flags & FLAG_SNIPER) + nodeFlagColor = Vector (130, 87, 0); + else if (m_paths[i]->flags & FLAG_NOHOSTAGE) + nodeFlagColor = Vector (255, 255, 255); + else if (m_paths[i]->flags & FLAG_TF_ONLY) + nodeFlagColor = Vector (255, 0, 0); + else if (m_paths[i]->flags & FLAG_CF_ONLY) + nodeFlagColor = Vector (0, 0, 255); + + // draw node without additional flags + if (nodeFlagColor.x == -1) + DrawLine (g_hostEntity, m_paths[i]->origin - Vector (0, 0, nodeHalfHeight), m_paths[i]->origin + Vector (0, 0, nodeHalfHeight), 15, 0, static_cast (nodeColor.x), static_cast (nodeColor.y), static_cast (nodeColor.z), 250, 0, 10); + else // draw node with flags + { + DrawLine (g_hostEntity, m_paths[i]->origin - Vector (0, 0, nodeHalfHeight), m_paths[i]->origin - Vector (0, 0, nodeHalfHeight - nodeHeight * 0.75), 14, 0, static_cast (nodeColor.x), static_cast (nodeColor.y), static_cast (nodeColor.z), 250, 0, 10); // draw basic path + DrawLine (g_hostEntity, m_paths[i]->origin - Vector (0, 0, nodeHalfHeight - nodeHeight * 0.75), m_paths[i]->origin + Vector (0, 0, nodeHalfHeight), 14, 0, static_cast (nodeFlagColor.x), static_cast (nodeFlagColor.y), static_cast (nodeFlagColor.z), 250, 0, 10); // draw additional path + } + m_waypointDisplayTime[i] = GetWorldTime (); + } + } + } + + if (nearestIndex == -1) + return; + + // draw arrow to a some importaint waypoints + if ((m_findWPIndex != -1 && m_findWPIndex < g_numWaypoints) || (m_cacheWaypointIndex != -1 && m_cacheWaypointIndex < g_numWaypoints) || (m_facingAtIndex != -1 && m_facingAtIndex < g_numWaypoints)) + { + // check for drawing code + if (m_arrowDisplayTime + 0.5 < GetWorldTime ()) + { + // finding waypoint - pink arrow + if (m_findWPIndex != -1) + DrawArrow (g_hostEntity, g_hostEntity->v.origin, m_paths[m_findWPIndex]->origin, 10, 0, 128, 0, 128, 200, 0, 5); + + // cached waypoint - yellow arrow + if (m_cacheWaypointIndex != -1) + DrawArrow (g_hostEntity, g_hostEntity->v.origin, m_paths[m_cacheWaypointIndex]->origin, 10, 0, 255, 255, 0, 200, 0, 5); + + // waypoint user facing at - white arrow + if (m_facingAtIndex != -1) + DrawArrow (g_hostEntity, g_hostEntity->v.origin, m_paths[m_facingAtIndex]->origin, 10, 0, 255, 255, 255, 200, 0, 5); + + m_arrowDisplayTime = GetWorldTime (); + } + } + + // create path pointer for faster access + Path *path = m_paths[nearestIndex]; + + // draw a paths, camplines and danger directions for nearest waypoint + if (nearestDistance < 4096 && m_pathDisplayTime <= GetWorldTime ()) + { + m_pathDisplayTime = GetWorldTime () + 1.0; + + // draw the camplines + if (path->flags & FLAG_CAMP) + { + Vector campSourceOrigin = campSourceOrigin = path->origin + Vector (0, 0, 36); + + // check if it's a source + if (path->flags & FLAG_CROUCH) + campSourceOrigin = path->origin + Vector (0, 0, 18); + + Vector campStartOrigin = Vector (path->campStartX, path->campStartY, campSourceOrigin.z); // camp start + Vector campEndOrigin = Vector (path->campEndX, path->campEndY, campSourceOrigin.z); // camp end + + // draw it now + DrawLine (g_hostEntity, campSourceOrigin, campStartOrigin, 10, 0, 255, 0, 0, 200, 0, 10); + DrawLine (g_hostEntity, campSourceOrigin, campEndOrigin, 10, 0, 255, 0, 0, 200, 0, 10); + } + + // draw the connections + for (int i = 0; i < MAX_PATH_INDEX; i++) + { + if (path->index[i] == -1) + continue; + + // jump connection + if (path->connectionFlags[i] & PATHFLAG_JUMP) + DrawLine (g_hostEntity, path->origin, m_paths[path->index[i]]->origin, 5, 0, 255, 0, 128, 200, 0, 10); + else if (IsConnected (path->index[i], nearestIndex)) // twoway connection + DrawLine (g_hostEntity, path->origin, m_paths[path->index[i]]->origin, 5, 0, 255, 255, 0, 200, 0, 10); + else // oneway connection + DrawLine (g_hostEntity, path->origin, m_paths[path->index[i]]->origin, 5, 0, 250, 250, 250, 200, 0, 10); + } + + // now look for oneway incoming connections + for (int i = 0; i < g_numWaypoints; i++) + { + if (IsConnected (m_paths[i]->pathNumber, path->pathNumber) && !IsConnected (path->pathNumber, m_paths[i]->pathNumber)) + DrawLine (g_hostEntity, path->origin, m_paths[i]->origin, 5, 0, 0, 192, 96, 200, 0, 10); + } + + // draw the radius circle + Vector origin = (path->flags & FLAG_CROUCH) ? path->origin : path->origin - Vector (0, 0, 18); + + // if radius is nonzero, draw a full circle + if (path->radius > 0.0) + { + float squareRoot = sqrtf ((path->radius * path->radius) * 0.5); + + DrawLine (g_hostEntity, origin + Vector (path->radius, 0, 0), origin + Vector (squareRoot, -squareRoot, 0), 5, 0, 0, 0, 255, 200, 0, 10); + DrawLine (g_hostEntity, origin + Vector (squareRoot, -squareRoot, 0), origin + Vector (0, -path->radius, 0), 5, 0, 0, 0, 255, 200, 0, 10); + + DrawLine (g_hostEntity, origin + Vector (0, -path->radius, 0), origin + Vector (-squareRoot, -squareRoot, 0), 5, 0, 0, 0, 255, 200, 0, 10); + DrawLine (g_hostEntity, origin + Vector (-squareRoot, -squareRoot, 0), origin + Vector (-path->radius, 0, 0), 5, 0, 0, 0, 255, 200, 0, 10); + + DrawLine (g_hostEntity, origin + Vector (-path->radius, 0, 0), origin + Vector (-squareRoot, squareRoot, 0), 5, 0, 0, 0, 255, 200, 0, 10); + DrawLine (g_hostEntity, origin + Vector (-squareRoot, squareRoot, 0), origin + Vector (0, path->radius, 0), 5, 0, 0, 0, 255, 200, 0, 10); + + DrawLine (g_hostEntity, origin + Vector (0, path->radius, 0), origin + Vector (squareRoot, squareRoot, 0), 5, 0, 0, 0, 255, 200, 0, 10); + DrawLine (g_hostEntity, origin + Vector (squareRoot, squareRoot, 0), origin + Vector (path->radius, 0, 0), 5, 0, 0, 0, 255, 200, 0, 10); + } + else + { + float squareRoot = sqrtf (32.0); + + DrawLine (g_hostEntity, origin + Vector (squareRoot, -squareRoot, 0), origin + Vector (-squareRoot, squareRoot, 0), 5, 0, 255, 0, 0, 200, 0, 10); + DrawLine (g_hostEntity, origin + Vector (-squareRoot, -squareRoot, 0), origin + Vector (squareRoot, squareRoot, 0), 5, 0, 255, 0, 0, 200, 0, 10); + } + + // draw the danger directions + if (!g_waypointsChanged) + { + if ((g_experienceData + (nearestIndex * g_numWaypoints) + nearestIndex)->team0DangerIndex != -1 && GetTeam (g_hostEntity) == TEAM_TF) + DrawArrow (g_hostEntity, path->origin, m_paths[(g_experienceData + (nearestIndex * g_numWaypoints) + nearestIndex)->team0DangerIndex]->origin, 15, 0, 255, 0, 0, 200, 0, 10); // draw a red arrow to this index's danger point + + if ((g_experienceData + (nearestIndex * g_numWaypoints) + nearestIndex)->team1DangerIndex != -1 && GetTeam (g_hostEntity) == TEAM_CF) + DrawArrow (g_hostEntity, path->origin, m_paths[(g_experienceData + (nearestIndex * g_numWaypoints) + nearestIndex)->team1DangerIndex]->origin, 15, 0, 0, 0, 255, 200, 0, 10); // draw a blue arrow to this index's danger point + } + + // display some information + char tempMessage[4096]; + + // show the information about that point + int length = sprintf (tempMessage, "\n\n\n\n Waypoint Information:\n\n" + " Waypoint %d of %d, Radius: %.1f\n" + " Flags: %s\n\n", nearestIndex, g_numWaypoints, path->radius, GetWaypointInfo (nearestIndex)); + + + // if waypoint is not changed display experience also + if (!g_waypointsChanged) + { + int dangerIndexCT = (g_experienceData + nearestIndex * g_numWaypoints + nearestIndex)->team1DangerIndex; + int dangerIndexT = (g_experienceData + nearestIndex * g_numWaypoints + nearestIndex)->team0DangerIndex; + + length += sprintf (&tempMessage[length], + " Experience Info:\n" + " CT: %d / %d\n" + " T: %d / %d\n", dangerIndexCT, dangerIndexCT != -1 ? (g_experienceData + nearestIndex * g_numWaypoints + dangerIndexCT)->team1Damage : 0, dangerIndexT, dangerIndexT != -1 ? (g_experienceData + nearestIndex * g_numWaypoints + dangerIndexT)->team0Damage : 0); + } + + // check if we need to show the cached point index + if (m_cacheWaypointIndex != -1) + { + length += sprintf (&tempMessage[length], "\n Cached Waypoint Information:\n\n" + " Waypoint %d of %d, Radius: %.1f\n" + " Flags: %s\n", m_cacheWaypointIndex, g_numWaypoints, m_paths[m_cacheWaypointIndex]->radius, GetWaypointInfo (m_cacheWaypointIndex)); + } + + // check if we need to show the facing point index + if (m_facingAtIndex != -1) + { + length += sprintf (&tempMessage[length], "\n Facing Waypoint Information:\n\n" + " Waypoint %d of %d, Radius: %.1f\n" + " Flags: %s\n", m_facingAtIndex, g_numWaypoints, m_paths[m_facingAtIndex]->radius, GetWaypointInfo (m_facingAtIndex)); + } + + // draw entire message + MESSAGE_BEGIN (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, NULL, g_hostEntity); + WRITE_BYTE (TE_TEXTMESSAGE); + WRITE_BYTE (4); // channel + WRITE_SHORT (FixedSigned16 (0, 1 << 13)); // x + WRITE_SHORT (FixedSigned16 (0, 1 << 13)); // y + WRITE_BYTE (0); // effect + WRITE_BYTE (255); // r1 + WRITE_BYTE (255); // g1 + WRITE_BYTE (255); // b1 + WRITE_BYTE (1); // a1 + WRITE_BYTE (255); // r2 + WRITE_BYTE (255); // g2 + WRITE_BYTE (255); // b2 + WRITE_BYTE (255); // a2 + WRITE_SHORT (0); // fadeintime + WRITE_SHORT (0); // fadeouttime + WRITE_SHORT (FixedUnsigned16 (1.1, 1 << 8)); // holdtime + WRITE_STRING (tempMessage); + MESSAGE_END (); + } +} + +bool Waypoint::IsConnected (int index) +{ + for (int i = 0; i < g_numWaypoints; i++) + { + if (i != index) + { + for (int j = 0; j < MAX_PATH_INDEX; j++) + { + if (m_paths[i]->index[j] == index) + return true; + } + } + } + return false; +} + +bool Waypoint::NodesValid (void) +{ + int terrPoints = 0; + int ctPoints = 0; + int goalPoints = 0; + int rescuePoints = 0; + int connections; + int i, j; + + for (i = 0; i < g_numWaypoints; i++) + { + connections = 0; + + for (int j = 0; j < MAX_PATH_INDEX; j++) + { + if (m_paths[i]->index[j] != -1) + { + if (m_paths[i]->index[j] > g_numWaypoints) + { + AddLogEntry (true, LL_WARNING, "Waypoint %d connected with invalid Waypoint #%d!", i, m_paths[i]->index[j]); + return false; + } + connections++; + break; + } + } + + if (connections == 0) + { + if (!IsConnected (i)) + { + AddLogEntry (true, LL_WARNING, "Waypoint %d isn't connected with any other Waypoint!", i); + return false; + } + } + + if (m_paths[i]->pathNumber != i) + { + AddLogEntry (true, LL_WARNING, "Waypoint %d pathnumber differs from index!", i); + return false; + } + + if (m_paths[i]->flags & FLAG_CAMP) + { + if (m_paths[i]->campEndX == 0 && m_paths[i]->campEndY == 0) + { + AddLogEntry (true, LL_WARNING, "Waypoint %d Camp-Endposition not set!", i); + return false; + } + } + else if (m_paths[i]->flags & FLAG_TF_ONLY) + terrPoints++; + else if (m_paths[i]->flags & FLAG_CF_ONLY) + ctPoints++; + else if (m_paths[i]->flags & FLAG_GOAL) + goalPoints++; + else if (m_paths[i]->flags & FLAG_RESCUE) + rescuePoints++; + + for (int k = 0; k < MAX_PATH_INDEX; k++) + { + if (m_paths[i]->index[k] != -1) + { + if (m_paths[i]->index[k] >= g_numWaypoints || m_paths[i]->index[k] < -1) + { + AddLogEntry (true, LL_WARNING, "Waypoint %d - Pathindex %d out of Range!", i, k); + (*g_engfuncs.pfnSetOrigin) (g_hostEntity, m_paths[i]->origin); + + g_waypointOn = true; + g_editNoclip = true; + + return false; + } + else if (m_paths[i]->index[k] == i) + { + AddLogEntry (true, LL_WARNING, "Waypoint %d - Pathindex %d points to itself!", i, k); + (*g_engfuncs.pfnSetOrigin) (g_hostEntity, m_paths[i]->origin); + + g_waypointOn = true; + g_editNoclip = true; + + return false; + } + } + } + } + if (g_mapType & MAP_CS) + { + if (rescuePoints == 0) + { + AddLogEntry (true, LL_WARNING, "You didn't set a Rescue Point!"); + return false; + } + } + if (terrPoints == 0) + { + AddLogEntry (true, LL_WARNING, "You didn't set any Terrorist Important Point!"); + return false; + } + else if (ctPoints == 0) + { + AddLogEntry (true, LL_WARNING, "You didn't set any CT Important Point!"); + return false; + } + else if (goalPoints == 0) + { + AddLogEntry (true, LL_WARNING, "You didn't set any Goal Point!"); + return false; + } + + // perform DFS instead of floyd-warshall, this shit speedup this process in a bit + PathNode *stack = NULL; + bool visited[MAX_WAYPOINTS]; + + // first check incoming connectivity, initialize the "visited" table + for (i = 0; i < g_numWaypoints; i++) + visited[i] = false; + + // check from waypoint nr. 0 + stack = new PathNode; + stack->next = NULL; + stack->index = 0; + + while (stack != NULL) + { + // pop a node from the stack + PathNode *current = stack; + stack = stack->next; + + visited[current->index] = true; + + for (j = 0; j < MAX_PATH_INDEX; j++) + { + int index = m_paths[current->index]->index[j]; + + if (visited[index]) + continue; // skip this waypoint as it's already visited + + if (index >= 0 && index < g_numWaypoints) + { + PathNode *pNewNode = new PathNode; + + pNewNode->next = stack; + pNewNode->index = index; + stack = pNewNode; + } + } + delete current; + } + + for (i = 0; i < g_numWaypoints; i++) + { + if (!visited[i]) + { + AddLogEntry (true, LL_WARNING, "Path broken from Waypoint #0 to Waypoint #%d!", i); + (*g_engfuncs.pfnSetOrigin) (g_hostEntity, m_paths[i]->origin); + + g_waypointOn = true; + g_editNoclip = true; + + return false; + } + } + + // then check outgoing connectivity + Array outgoingPaths[MAX_WAYPOINTS]; // store incoming paths for speedup + + for (i = 0; i < g_numWaypoints; i++) + { + for (j = 0; j < MAX_PATH_INDEX; j++) + { + if (m_paths[i]->index[j] >= 0 && m_paths[i]->index[j] < g_numWaypoints) + outgoingPaths[m_paths[i]->index[j]].Push (i); + } + } + + // initialize the "visited" table + for (i = 0; i < g_numWaypoints; i++) + visited[i] = false; + + // check from Waypoint nr. 0 + stack = new PathNode; + stack->next = NULL; + stack->index = 0; + + while (stack != NULL) + { + // pop a node from the stack + PathNode *current = stack; + stack = stack->next; + + visited[current->index] = true; + + IterateArray (outgoingPaths[current->index], j) + { + if (visited[outgoingPaths[current->index][j]]) + continue; // skip this waypoint as it's already visited + + PathNode *pNewNode = new PathNode; + + pNewNode->next = stack; + pNewNode->index = outgoingPaths[current->index][j]; + stack = pNewNode; + } + delete current; + } + + for (i = 0; i < g_numWaypoints; i++) + { + if (!visited[i]) + { + AddLogEntry (true, LL_WARNING, "Path broken from Waypoint #%d to Waypoint #0!", i); + (*g_engfuncs.pfnSetOrigin) (g_hostEntity, m_paths[i]->origin); + + g_waypointOn = true; + g_editNoclip = true; + + return false; + } + } + return true; +} + +void Waypoint::InitPathMatrix (void) +{ + int i, j, k; + + delete [] m_distMatrix; + delete [] m_pathMatrix; + + m_distMatrix = NULL; + m_pathMatrix = NULL; + + m_distMatrix = new int [g_numWaypoints * g_numWaypoints]; + m_pathMatrix = new int [g_numWaypoints * g_numWaypoints]; + + if (LoadPathMatrix ()) + return; // matrix loaded from file + + for (i = 0; i < g_numWaypoints; i++) + { + for (j = 0; j < g_numWaypoints; j++) + { + *(m_distMatrix + i * g_numWaypoints + j) = 999999; + *(m_pathMatrix + i * g_numWaypoints + j) = -1; + } + } + + for (i = 0; i < g_numWaypoints; i++) + { + for (j = 0; j < MAX_PATH_INDEX; j++) + { + if (m_paths[i]->index[j] >= 0 && m_paths[i]->index[j] < g_numWaypoints) + { + *(m_distMatrix + (i * g_numWaypoints) + m_paths[i]->index[j]) = m_paths[i]->distances[j]; + *(m_pathMatrix + (i * g_numWaypoints) + m_paths[i]->index[j]) = m_paths[i]->index[j]; + } + } + } + + for (i = 0; i < g_numWaypoints; i++) + *(m_distMatrix + (i * g_numWaypoints) + i) = 0; + + for (k = 0; k < g_numWaypoints; k++) + { + for (i = 0; i < g_numWaypoints; i++) + { + for (j = 0; j < g_numWaypoints; j++) + { + if (*(m_distMatrix + (i * g_numWaypoints) + k) + *(m_distMatrix + (k * g_numWaypoints) + j) < (*(m_distMatrix + (i * g_numWaypoints) + j))) + { + *(m_distMatrix + (i * g_numWaypoints) + j) = *(m_distMatrix + (i * g_numWaypoints) + k) + *(m_distMatrix + (k * g_numWaypoints) + j); + *(m_pathMatrix + (i * g_numWaypoints) + j) = *(m_pathMatrix + (i * g_numWaypoints) + k); + } + } + } + } + + // save path matrix to file for faster access + SavePathMatrix (); +} + +void Waypoint::SavePathMatrix (void) +{ + File fp (FormatBuffer ("%sdata/%s.pmt", GetWaypointDir (), GetMapName ()), "wb"); + + // unable to open file + if (!fp.IsValid ()) + { + AddLogEntry (false, LL_FATAL, "Failed to open file for writing"); + return; + } + + // write number of waypoints + fp.Write (&g_numWaypoints, sizeof (int)); + + // write path & distance matrix + fp.Write (m_pathMatrix, sizeof (int), g_numWaypoints * g_numWaypoints); + fp.Write (m_distMatrix, sizeof (int), g_numWaypoints * g_numWaypoints); + + // and close the file + fp.Close (); +} + +bool Waypoint::LoadPathMatrix (void) +{ + File fp (FormatBuffer ("%sdata/%s.pmt", GetWaypointDir (), GetMapName ()), "rb"); + + // file doesn't exists return false + if (!fp.IsValid ()) + return false; + + int num = 0; + + // read number of waypoints + fp.Read (&num, sizeof (int)); + + if (num != g_numWaypoints) + { + AddLogEntry (true, LL_DEFAULT, "Wrong number of points (pmt:%d/cur:%d). Matrix will be rebuilded", num, g_numWaypoints); + fp.Close (); + + return false; + } + + // read path & distance matrixes + fp.Read (m_pathMatrix, sizeof (int), g_numWaypoints * g_numWaypoints); + fp.Read (m_distMatrix, sizeof (int), g_numWaypoints * g_numWaypoints); + + // and close the file + fp.Close (); + + return true; +} + +int Waypoint::GetPathDistance (int srcIndex, int destIndex) +{ + if (srcIndex < 0 || srcIndex >= g_numWaypoints || destIndex < 0 || destIndex >= g_numWaypoints) + return 1; + + return *(m_distMatrix + (srcIndex * g_numWaypoints) + destIndex); +} + +void Waypoint::SetGoalVisited (int index) +{ + if (index < 0 || index >= g_numWaypoints) + return; + + if (!IsGoalVisited (index) || !(m_paths[index]->flags & FLAG_GOAL)) + { +#if 0 + int bombPoint = FindNearest (GetBombPosition ()); + + Array markAsVisited; + FindInRadius (markAsVisited, 356.0, GetPath (index)->origin); + + IterateArray (markAsVisited, i) + { + // skip still valid bomb point + if (markAsVisited[i] == bombPoint) + continue; + + m_visitedGoals.Push (markAsVisited[i]); + } +#endif + m_visitedGoals.Push (index); + } +} + +bool Waypoint::IsGoalVisited (int index) +{ + IterateArray (m_visitedGoals, i) + { + if (m_visitedGoals[i] == index) + return true; + } + return false; +} + +void Waypoint::CreateBasic (void) +{ + // this function creates basic waypoint types on map + + edict_t *ent = NULL; + + // first of all, if map contains ladder points, create it + while (!FNullEnt (ent = FIND_ENTITY_BY_CLASSNAME (ent, "func_ladder"))) + { + Vector ladderLeft = ent->v.absmin; + Vector ladderRight = ent->v.absmax; + ladderLeft.z = ladderRight.z; + + TraceResult tr; + Vector up, down, front, back; + + Vector diff = ((ladderLeft - ladderRight) ^ Vector (0, 0, 0)).Normalize () * 15.0; + front = back = GetEntityOrigin (ent); + + front = front + diff; // front + back = back - diff; // back + + up = down = front; + down.z = ent->v.absmax.z; + + TraceHull (down, up, true, point_hull, NULL, &tr); + + if (POINT_CONTENTS (up) == CONTENTS_SOLID || tr.flFraction != 1.0) + { + up = down = back; + down.z = ent->v.absmax.z; + } + + TraceHull (down, up - Vector (0, 0, 1000), true, point_hull, NULL, &tr); + up = tr.vecEndPos; + + Vector pointOrigin = up + Vector (0, 0, 39); + m_isOnLadder = true; + + do + { + if (FindNearest (pointOrigin, 50.0) == -1) + Add (3, pointOrigin); + + pointOrigin.z += 160; + } while (pointOrigin.z < down.z - 40); + + pointOrigin = down + Vector (0, 0, 38); + + if (FindNearest (pointOrigin, 50.0) == -1) + Add (3, pointOrigin); + + m_isOnLadder = false; + } + + // then terrortist spawnpoints + while (!FNullEnt (ent = FIND_ENTITY_BY_CLASSNAME (ent, "info_player_deathmatch"))) + { + Vector origin = GetEntityOrigin (ent); + + if (FindNearest (origin, 50) == -1) + Add (0, origin); + } + + // then add ct spawnpoints + while (!FNullEnt (ent = FIND_ENTITY_BY_CLASSNAME (ent, "info_player_start"))) + { + Vector origin = GetEntityOrigin (ent); + + if (FindNearest (origin, 50) == -1) + Add (0, origin); + } + + // then vip spawnpoint + while (!FNullEnt (ent = FIND_ENTITY_BY_CLASSNAME (ent, "info_vip_start"))) + { + Vector origin = GetEntityOrigin (ent); + + if (FindNearest (origin, 50) == -1) + Add (0, origin); + } + + // hostage rescue zone + while (!FNullEnt (ent = FIND_ENTITY_BY_CLASSNAME (ent, "func_hostage_rescue"))) + { + Vector origin = GetEntityOrigin (ent); + + if (FindNearest (origin, 50) == -1) + Add (4, origin); + } + + // hostage rescue zone (same as above) + while (!FNullEnt (ent = FIND_ENTITY_BY_CLASSNAME (ent, "info_hostage_rescue"))) + { + Vector origin = GetEntityOrigin (ent); + + if (FindNearest (origin, 50) == -1) + Add (4, origin); + } + + // bombspot zone + while (!FNullEnt (ent = FIND_ENTITY_BY_CLASSNAME (ent, "func_bomb_target"))) + { + Vector origin = GetEntityOrigin (ent); + + if (FindNearest (origin, 50) == -1) + Add (100, origin); + } + + // bombspot zone (same as above) + while (!FNullEnt (ent = FIND_ENTITY_BY_CLASSNAME (ent, "info_bomb_target"))) + { + Vector origin = GetEntityOrigin (ent); + + if (FindNearest (origin, 50) == -1) + Add (100, origin); + } + + // hostage entities + while (!FNullEnt (ent = FIND_ENTITY_BY_CLASSNAME (ent, "hostage_entity"))) + { + // if already saved || moving skip it + if ((ent->v.effects & EF_NODRAW) && (ent->v.speed > 0)) + continue; + + Vector origin = GetEntityOrigin (ent); + + if (FindNearest (origin, 50) == -1) + Add (100, origin); + } + + // vip rescue (safety) zone + while (!FNullEnt (ent = FIND_ENTITY_BY_CLASSNAME (ent, "func_vip_safetyzone"))) + { + Vector origin = GetEntityOrigin (ent); + + if (FindNearest (origin, 50) == -1) + Add (100, origin); + } + + // terrorist escape zone + while (!FNullEnt (ent = FIND_ENTITY_BY_CLASSNAME (ent, "func_escapezone"))) + { + Vector origin = GetEntityOrigin (ent); + + if (FindNearest (origin, 50) == -1) + Add (100, origin); + } + + // weapons on the map ? + while (!FNullEnt (ent = FIND_ENTITY_BY_CLASSNAME (ent, "armoury_entity"))) + { + Vector origin = GetEntityOrigin (ent); + + if (FindNearest (origin, 50) == -1) + Add (0, origin); + } +} + +Path *Waypoint::GetPath (int id) +{ + Path *path = m_paths[id]; + + if (path == NULL) + return NULL; + + return path; +} + +void Waypoint::EraseFromHardDisk (void) +{ + // this function removes waypoint file from the hard disk + + String deleteList[5]; + + // if we're delete waypoint, delete all corresponding to it files + deleteList[0] = FormatBuffer ("%s%s.pwf", GetWaypointDir (), GetMapName ()); // waypoint itself + deleteList[1] = FormatBuffer ("%sdata/%s.exp", GetWaypointDir (), GetMapName ()); // corresponding to waypoint experience + deleteList[3] = FormatBuffer ("%sdata/%s.vis", GetWaypointDir (), GetMapName ()); // corresponding to waypoint vistable + deleteList[3] = FormatBuffer ("%sdata/%s.pmt", GetWaypointDir (), GetMapName ()); // corresponding to waypoint path matrix + deleteList[4] = FormatBuffer ("%sdata/%s.xml", GetWaypointDir (), GetMapName ()); // corresponding to waypoint xml database + + for (int i = 0; i < 4; i++) + { + if (TryFileOpen (const_cast (deleteList[i].GetBuffer ()))) + { + unlink (deleteList[i].GetBuffer ()); + AddLogEntry (true, LL_DEFAULT, "File %s, has been deleted from the hard disk", deleteList[i].GetBuffer ()); + } + else + AddLogEntry (true, LL_ERROR, "Unable to open %s", deleteList[i].GetBuffer ()); + } + Init (); // reintialize points +} + +void Waypoint::SetBombPosition (bool shouldReset) +{ + // this function stores the bomb position as a vector + + if (shouldReset) + { + m_foundBombOrigin = nullvec; + g_bombPlanted = false; + + return; + } + + edict_t *ent = NULL; + + while (!FNullEnt (ent = FIND_ENTITY_BY_CLASSNAME (ent, "grenade"))) + { + if (strcmp (STRING (ent->v.model) + 9, "c4.mdl") == 0) + { + m_foundBombOrigin = GetEntityOrigin (ent); + break; + } + } +} + +void Waypoint::SetLearnJumpWaypoint (void) +{ + m_learnJumpWaypoint = true; +} + +void Waypoint::SetFindIndex (int index) +{ + m_findWPIndex = index; + + if (m_findWPIndex < g_numWaypoints) + ServerPrint ("Showing Direction to Waypoint #%d", m_findWPIndex); + else + m_findWPIndex = -1; +} + +int Waypoint::AddGoalScore (int index, int other[4]) +{ + Array left; + + if (m_goalsScore[index] < 1024.0) + left.Push (index); + + for (int i = 0; i < 3; i++) + { + if (m_goalsScore[other[i]] < 1024.0) + left.Push (other[i]); + } + + if (left.IsEmpty ()) + index = other[g_randGen.Long (0, 3)]; + else + index = left.GetRandomElement (); + + if (m_paths[index]->flags & FLAG_GOAL) + m_goalsScore[index] += 384.0; + else if (m_paths[index]->flags & (FLAG_CF_ONLY | FLAG_TF_ONLY)) + m_goalsScore[index] += 768.0; + else if (m_paths[index]->flags & FLAG_CAMP) + m_goalsScore[index] += 1024.0; + + return index; +} + +void Waypoint::ClearGoalScore (void) +{ + // iterate though all waypoints + for (int i = 0; i < MAX_WAYPOINTS; i++) + m_goalsScore[i] = 0.0; +} + +Waypoint::Waypoint (void) +{ + m_waypointPaths = false; + m_endJumpPoint = false; + m_redoneVisibility = false; + m_learnJumpWaypoint = false; + m_timeJumpStarted = 0.0; + + m_learnVelocity = nullvec; + m_learnPosition = nullvec; + m_lastJumpWaypoint = -1; + m_cacheWaypointIndex = -1; + m_findWPIndex = -1; + m_visibilityIndex = 0; + + m_lastWaypoint = nullvec; + m_isOnLadder = false; + + m_pathDisplayTime = 0.0; + m_arrowDisplayTime = 0.0; + + m_terrorPoints.RemoveAll (); + m_ctPoints.RemoveAll (); + m_goalPoints.RemoveAll (); + m_campPoints.RemoveAll (); + m_rescuePoints.RemoveAll (); + m_sniperPoints.RemoveAll (); + + m_distMatrix = NULL; + m_pathMatrix = NULL; +} + +Waypoint::~Waypoint (void) +{ + delete [] m_distMatrix; + delete [] m_pathMatrix; + + m_distMatrix = NULL; + m_pathMatrix = NULL; +} + +void WaypointDownloader::FreeSocket (int sock) +{ +#if defined (PLATFORM_WIN32) + if (sock != -1) + closesocket (sock); + + WSACleanup (); +#else + if (sock != -1) + close (sock); +#endif +} + + +WaypointDownloadError WaypointDownloader::DoDownload (void) +{ +#if defined (PLATFORM_WIN32) + WORD requestedVersion = MAKEWORD (1, 1); + WSADATA wsaData; + + WSAStartup (requestedVersion, &wsaData); +#endif + + hostent *host = gethostbyname (yb_waypoint_autodl_host.GetString ()); + + if (host == NULL) + return WDE_SOCKET_ERROR; + + int socketHandle = socket (AF_INET, SOCK_STREAM, 0); + + if (socketHandle < 0) + { + FreeSocket (socketHandle); + return WDE_SOCKET_ERROR; + } + sockaddr_in dest; + +#if defined (PLATFORM_WIN32) + unsigned long mode = 0; + ioctlsocket (socketHandle, FIONBIO, &mode); +#else + int flags = fcntl (socketHandle, F_GETFL, 0) | O_NONBLOCK; + fcntl (socketHandle, F_SETFL, flags); +#endif + + timeval timeout; + timeout.tv_sec = 2; + timeout.tv_usec = 0; + + setsockopt (socketHandle, SOL_SOCKET, SO_RCVTIMEO, (char *) &timeout, sizeof(timeout)); + setsockopt (socketHandle, SOL_SOCKET, SO_SNDTIMEO, (char *) &timeout, sizeof(timeout)); + + memset (&dest, 0, sizeof (dest)); + dest.sin_family = AF_INET; + dest.sin_port = htons (80); + dest.sin_addr.s_addr = inet_addr (inet_ntoa (*((struct in_addr *) host->h_addr))); + + if (connect (socketHandle, (struct sockaddr*) &dest, (int) sizeof (dest)) == -1) + { + FreeSocket (socketHandle); + return WDE_CONNECT_ERROR; + } + + String request; + request.AssignFormat ("GET /wpdb/%s.pwf HTTP/1.0\r\nUser-Agent: YaPB/%s\r\nHost: %s\r\n\r\n", GetMapName (), PRODUCT_VERSION, yb_waypoint_autodl_host.GetString ()); + + if (send (socketHandle, request.GetBuffer (), request.GetLength (), 0) < 1) + { + FreeSocket (socketHandle); + return WDE_SOCKET_ERROR; + } + + File fp (g_waypoint->CheckSubfolderFile (), "wb"); + + if (!fp.IsValid ()) + { + FreeSocket (socketHandle); + return WDE_SOCKET_ERROR; + } + char buffer[4096]; + + bool finished = false; + int recvPosition = 0; + int symbolsInLine = 0; + + // scan for the end of the header + while (!finished && recvPosition < static_cast (sizeof (buffer))) + { + if (recv (socketHandle, &buffer[recvPosition], 1, 0) < 0) + finished = true; + + switch (buffer[recvPosition]) + { + case '\r': + break; + + case '\n': + if (symbolsInLine == 0) + finished = true; + + symbolsInLine = 0; + break; + + default: + symbolsInLine++; + break; + } + + recvPosition++; + } + if (strstr (buffer, "HTTP/1.0 404") != NULL) + { + FreeSocket (socketHandle); + return WDE_NOTFOUND_ERROR; + } + memset (buffer, 0, sizeof (buffer)); + + int size = 0; + + while ((size = recv (socketHandle, buffer, sizeof (buffer) -1, 0)) > 0) + fp.Write (buffer, 1, size); + + fp.Close (); + + FreeSocket (socketHandle); + return WDE_NOERROR; +} \ No newline at end of file