From a04f0b296e86b47fe78abe90898a8234acbb934b Mon Sep 17 00:00:00 2001 From: Wei Mingzhi Date: Sun, 19 Aug 2018 10:39:27 +0800 Subject: [PATCH] Hello, world --- HPB_bot_mm-1.dsp | 568 ++++++++ HPB_bot_mm.vcproj | 268 ++++ Makefile | 39 + ReadMe.txt | 1278 +++++++++++++++++ bot.cpp | 2711 +++++++++++++++++++++++++++++++++++ bot.h | 399 ++++++ bot_chat.cpp | 525 +++++++ bot_client.cpp | 1173 +++++++++++++++ bot_client.h | 109 ++ bot_combat.cpp | 1946 +++++++++++++++++++++++++ bot_func.h | 61 + bot_models.cpp | 277 ++++ bot_navigate.cpp | 2452 +++++++++++++++++++++++++++++++ bot_start.cpp | 839 +++++++++++ bot_weapons.h | 177 +++ dll.cpp | 2089 +++++++++++++++++++++++++++ engine.cpp | 692 +++++++++ h_export.cpp | 125 ++ hpb_bot_mm.def | 5 + hpb_bot_mm.dsp | 568 ++++++++ hpb_bot_mm.rc | 40 + tools/bot_logo/bot_logo.cpp | 1297 +++++++++++++++++ tools/bot_logo/bot_logo.dsp | 123 ++ tools/bot_logo/bot_logo.h | 100 ++ tools/bot_logo/bot_logo.rc | 107 ++ tools/bot_logo/file.cpp | 643 +++++++++ tools/bot_logo/resource.h | 25 + tools/exports.c | 418 ++++++ tools/floyd.c | 158 ++ util.cpp | 662 +++++++++ waypoint.cpp | 2231 ++++++++++++++++++++++++++++ waypoint.h | 114 ++ 32 files changed, 22219 insertions(+) create mode 100644 HPB_bot_mm-1.dsp create mode 100644 HPB_bot_mm.vcproj create mode 100644 Makefile create mode 100644 ReadMe.txt create mode 100644 bot.cpp create mode 100644 bot.h create mode 100644 bot_chat.cpp create mode 100644 bot_client.cpp create mode 100644 bot_client.h create mode 100644 bot_combat.cpp create mode 100644 bot_func.h create mode 100644 bot_models.cpp create mode 100644 bot_navigate.cpp create mode 100644 bot_start.cpp create mode 100644 bot_weapons.h create mode 100644 dll.cpp create mode 100644 engine.cpp create mode 100644 h_export.cpp create mode 100644 hpb_bot_mm.def create mode 100644 hpb_bot_mm.dsp create mode 100644 hpb_bot_mm.rc create mode 100644 tools/bot_logo/bot_logo.cpp create mode 100644 tools/bot_logo/bot_logo.dsp create mode 100644 tools/bot_logo/bot_logo.h create mode 100644 tools/bot_logo/bot_logo.rc create mode 100644 tools/bot_logo/file.cpp create mode 100644 tools/bot_logo/resource.h create mode 100644 tools/exports.c create mode 100644 tools/floyd.c create mode 100644 util.cpp create mode 100644 waypoint.cpp create mode 100644 waypoint.h diff --git a/HPB_bot_mm-1.dsp b/HPB_bot_mm-1.dsp new file mode 100644 index 0000000..5b0e2ce --- /dev/null +++ b/HPB_bot_mm-1.dsp @@ -0,0 +1,568 @@ +# Microsoft Developer Studio Project File - Name="HPB_bot_mm" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=HPB_bot_mm - Win32 Release +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "HPB_bot_mm.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "HPB_bot_mm.mak" CFG="HPB_bot_mm - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "HPB_bot_mm - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName ""$/SDKSrc/Public/dlls", NVGBAAAA" +# PROP Scc_LocalPath "." +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir ".\Release" +# PROP BASE Intermediate_Dir ".\Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir ".\Release" +# PROP Intermediate_Dir ".\Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /c +# ADD CPP /nologo /G5 /MT /W3 /WX /GX /O2 /I "../metamod" /I "../../devtools/hlsdk-2.3/multiplayer/dlls" /I "../../devtools/hlsdk-2.3/multiplayer/engine" /I "../../devtools/hlsdk-2.3/multiplayer/pm_shared" /I "../../devtools/hlsdk-2.3/multiplayer/common" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fr /YX /c +# ADD BASE MTL /nologo /D "NDEBUG" /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /dll /machine:I386 /def:".\HPB_bot_mm.def" +# SUBTRACT LINK32 /profile /incremental:yes /map /debug +# Begin Target + +# Name "HPB_bot_mm - Win32 Release" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat;for;f90" +# Begin Source File + +SOURCE=.\bot.cpp +DEP_CPP_BOT_C=\ + "..\metamod\dllapi.h"\ + "..\metamod\engine_api.h"\ + "..\metamod\h_export.h"\ + "..\metamod\log_meta.h"\ + "..\metamod\meta_api.h"\ + "..\metamod\mhook.h"\ + "..\metamod\mreg.h"\ + "..\metamod\mutil.h"\ + "..\metamod\osdep.h"\ + "..\metamod\plinfo.h"\ + "..\metamod\sdk_util.h"\ + "..\metamod\types_meta.h"\ + ".\bot.h"\ + ".\bot_func.h"\ + ".\bot_weapons.h"\ + ".\waypoint.h"\ + "c:\program files\microsoft visual studio\vc98\include\basetsd.h"\ + +NODEP_CPP_BOT_C=\ + "..\..\devtools\sdk\single-player source\common\const.h"\ + "..\..\devtools\sdk\single-player source\common\crc.h"\ + "..\..\devtools\sdk\single-player source\common\cvardef.h"\ + "..\..\devtools\sdk\single-player source\common\event_flags.h"\ + "..\..\devtools\sdk\single-player source\common\in_buttons.h"\ + "..\..\devtools\sdk\single-player source\dlls\activity.h"\ + "..\..\devtools\sdk\single-player source\dlls\cdll_dll.h"\ + "..\..\devtools\sdk\single-player source\dlls\enginecallback.h"\ + "..\..\devtools\sdk\single-player source\dlls\extdll.h"\ + "..\..\devtools\sdk\single-player source\dlls\vector.h"\ + "..\..\devtools\sdk\single-player source\engine\archtypes.h"\ + "..\..\devtools\sdk\single-player source\engine\custom.h"\ + "..\..\devtools\sdk\single-player source\engine\edict.h"\ + "..\..\devtools\sdk\single-player source\engine\eiface.h"\ + "..\..\devtools\sdk\single-player source\engine\progdefs.h"\ + "..\..\devtools\sdk\single-player source\engine\sequence.h"\ + +# End Source File +# Begin Source File + +SOURCE=.\bot_chat.cpp +DEP_CPP_BOT_CH=\ + "..\metamod\dllapi.h"\ + "..\metamod\engine_api.h"\ + "..\metamod\h_export.h"\ + "..\metamod\log_meta.h"\ + "..\metamod\meta_api.h"\ + "..\metamod\mhook.h"\ + "..\metamod\mreg.h"\ + "..\metamod\mutil.h"\ + "..\metamod\osdep.h"\ + "..\metamod\plinfo.h"\ + "..\metamod\sdk_util.h"\ + "..\metamod\types_meta.h"\ + ".\bot.h"\ + "c:\program files\microsoft visual studio\vc98\include\basetsd.h"\ + +NODEP_CPP_BOT_CH=\ + "..\..\devtools\sdk\single-player source\common\const.h"\ + "..\..\devtools\sdk\single-player source\common\crc.h"\ + "..\..\devtools\sdk\single-player source\common\cvardef.h"\ + "..\..\devtools\sdk\single-player source\common\event_flags.h"\ + "..\..\devtools\sdk\single-player source\common\in_buttons.h"\ + "..\..\devtools\sdk\single-player source\dlls\activity.h"\ + "..\..\devtools\sdk\single-player source\dlls\cdll_dll.h"\ + "..\..\devtools\sdk\single-player source\dlls\enginecallback.h"\ + "..\..\devtools\sdk\single-player source\dlls\extdll.h"\ + "..\..\devtools\sdk\single-player source\dlls\vector.h"\ + "..\..\devtools\sdk\single-player source\engine\archtypes.h"\ + "..\..\devtools\sdk\single-player source\engine\custom.h"\ + "..\..\devtools\sdk\single-player source\engine\edict.h"\ + "..\..\devtools\sdk\single-player source\engine\eiface.h"\ + "..\..\devtools\sdk\single-player source\engine\progdefs.h"\ + "..\..\devtools\sdk\single-player source\engine\sequence.h"\ + +# End Source File +# Begin Source File + +SOURCE=.\bot_client.cpp +DEP_CPP_BOT_CL=\ + "..\metamod\dllapi.h"\ + "..\metamod\engine_api.h"\ + "..\metamod\h_export.h"\ + "..\metamod\log_meta.h"\ + "..\metamod\meta_api.h"\ + "..\metamod\mhook.h"\ + "..\metamod\mreg.h"\ + "..\metamod\mutil.h"\ + "..\metamod\osdep.h"\ + "..\metamod\plinfo.h"\ + "..\metamod\sdk_util.h"\ + "..\metamod\types_meta.h"\ + ".\bot.h"\ + ".\bot_client.h"\ + ".\bot_func.h"\ + ".\bot_weapons.h"\ + "c:\program files\microsoft visual studio\vc98\include\basetsd.h"\ + +NODEP_CPP_BOT_CL=\ + "..\..\devtools\sdk\single-player source\common\const.h"\ + "..\..\devtools\sdk\single-player source\common\crc.h"\ + "..\..\devtools\sdk\single-player source\common\cvardef.h"\ + "..\..\devtools\sdk\single-player source\common\event_flags.h"\ + "..\..\devtools\sdk\single-player source\common\in_buttons.h"\ + "..\..\devtools\sdk\single-player source\dlls\activity.h"\ + "..\..\devtools\sdk\single-player source\dlls\cdll_dll.h"\ + "..\..\devtools\sdk\single-player source\dlls\enginecallback.h"\ + "..\..\devtools\sdk\single-player source\dlls\extdll.h"\ + "..\..\devtools\sdk\single-player source\dlls\vector.h"\ + "..\..\devtools\sdk\single-player source\engine\archtypes.h"\ + "..\..\devtools\sdk\single-player source\engine\custom.h"\ + "..\..\devtools\sdk\single-player source\engine\edict.h"\ + "..\..\devtools\sdk\single-player source\engine\eiface.h"\ + "..\..\devtools\sdk\single-player source\engine\progdefs.h"\ + "..\..\devtools\sdk\single-player source\engine\sequence.h"\ + +# End Source File +# Begin Source File + +SOURCE=.\bot_combat.cpp +DEP_CPP_BOT_CO=\ + "..\metamod\dllapi.h"\ + "..\metamod\engine_api.h"\ + "..\metamod\h_export.h"\ + "..\metamod\log_meta.h"\ + "..\metamod\meta_api.h"\ + "..\metamod\mhook.h"\ + "..\metamod\mreg.h"\ + "..\metamod\mutil.h"\ + "..\metamod\osdep.h"\ + "..\metamod\plinfo.h"\ + "..\metamod\sdk_util.h"\ + "..\metamod\types_meta.h"\ + ".\bot.h"\ + ".\bot_func.h"\ + ".\bot_weapons.h"\ + "c:\program files\microsoft visual studio\vc98\include\basetsd.h"\ + +NODEP_CPP_BOT_CO=\ + "..\..\devtools\sdk\single-player source\common\const.h"\ + "..\..\devtools\sdk\single-player source\common\crc.h"\ + "..\..\devtools\sdk\single-player source\common\cvardef.h"\ + "..\..\devtools\sdk\single-player source\common\event_flags.h"\ + "..\..\devtools\sdk\single-player source\common\in_buttons.h"\ + "..\..\devtools\sdk\single-player source\dlls\activity.h"\ + "..\..\devtools\sdk\single-player source\dlls\cdll_dll.h"\ + "..\..\devtools\sdk\single-player source\dlls\enginecallback.h"\ + "..\..\devtools\sdk\single-player source\dlls\extdll.h"\ + "..\..\devtools\sdk\single-player source\dlls\vector.h"\ + "..\..\devtools\sdk\single-player source\engine\archtypes.h"\ + "..\..\devtools\sdk\single-player source\engine\custom.h"\ + "..\..\devtools\sdk\single-player source\engine\edict.h"\ + "..\..\devtools\sdk\single-player source\engine\eiface.h"\ + "..\..\devtools\sdk\single-player source\engine\progdefs.h"\ + "..\..\devtools\sdk\single-player source\engine\sequence.h"\ + +# End Source File +# Begin Source File + +SOURCE=.\bot_models.cpp +DEP_CPP_BOT_M=\ + "..\metamod\dllapi.h"\ + "..\metamod\engine_api.h"\ + "..\metamod\h_export.h"\ + "..\metamod\log_meta.h"\ + "..\metamod\meta_api.h"\ + "..\metamod\mhook.h"\ + "..\metamod\mreg.h"\ + "..\metamod\mutil.h"\ + "..\metamod\osdep.h"\ + "..\metamod\plinfo.h"\ + "..\metamod\sdk_util.h"\ + "..\metamod\types_meta.h"\ + ".\bot.h"\ + "c:\program files\microsoft visual studio\vc98\include\basetsd.h"\ + +NODEP_CPP_BOT_M=\ + "..\..\devtools\sdk\single-player source\common\const.h"\ + "..\..\devtools\sdk\single-player source\common\crc.h"\ + "..\..\devtools\sdk\single-player source\common\cvardef.h"\ + "..\..\devtools\sdk\single-player source\common\event_flags.h"\ + "..\..\devtools\sdk\single-player source\common\in_buttons.h"\ + "..\..\devtools\sdk\single-player source\dlls\activity.h"\ + "..\..\devtools\sdk\single-player source\dlls\cdll_dll.h"\ + "..\..\devtools\sdk\single-player source\dlls\enginecallback.h"\ + "..\..\devtools\sdk\single-player source\dlls\extdll.h"\ + "..\..\devtools\sdk\single-player source\dlls\vector.h"\ + "..\..\devtools\sdk\single-player source\engine\archtypes.h"\ + "..\..\devtools\sdk\single-player source\engine\custom.h"\ + "..\..\devtools\sdk\single-player source\engine\edict.h"\ + "..\..\devtools\sdk\single-player source\engine\eiface.h"\ + "..\..\devtools\sdk\single-player source\engine\progdefs.h"\ + "..\..\devtools\sdk\single-player source\engine\sequence.h"\ + +# End Source File +# Begin Source File + +SOURCE=.\bot_navigate.cpp +DEP_CPP_BOT_N=\ + "..\metamod\dllapi.h"\ + "..\metamod\engine_api.h"\ + "..\metamod\h_export.h"\ + "..\metamod\log_meta.h"\ + "..\metamod\meta_api.h"\ + "..\metamod\mhook.h"\ + "..\metamod\mreg.h"\ + "..\metamod\mutil.h"\ + "..\metamod\osdep.h"\ + "..\metamod\plinfo.h"\ + "..\metamod\sdk_util.h"\ + "..\metamod\types_meta.h"\ + ".\bot.h"\ + ".\bot_func.h"\ + ".\bot_weapons.h"\ + ".\waypoint.h"\ + "c:\program files\microsoft visual studio\vc98\include\basetsd.h"\ + +NODEP_CPP_BOT_N=\ + "..\..\devtools\sdk\single-player source\common\const.h"\ + "..\..\devtools\sdk\single-player source\common\crc.h"\ + "..\..\devtools\sdk\single-player source\common\cvardef.h"\ + "..\..\devtools\sdk\single-player source\common\event_flags.h"\ + "..\..\devtools\sdk\single-player source\common\in_buttons.h"\ + "..\..\devtools\sdk\single-player source\dlls\activity.h"\ + "..\..\devtools\sdk\single-player source\dlls\cdll_dll.h"\ + "..\..\devtools\sdk\single-player source\dlls\enginecallback.h"\ + "..\..\devtools\sdk\single-player source\dlls\extdll.h"\ + "..\..\devtools\sdk\single-player source\dlls\vector.h"\ + "..\..\devtools\sdk\single-player source\engine\archtypes.h"\ + "..\..\devtools\sdk\single-player source\engine\custom.h"\ + "..\..\devtools\sdk\single-player source\engine\edict.h"\ + "..\..\devtools\sdk\single-player source\engine\eiface.h"\ + "..\..\devtools\sdk\single-player source\engine\progdefs.h"\ + "..\..\devtools\sdk\single-player source\engine\sequence.h"\ + +# End Source File +# Begin Source File + +SOURCE=.\bot_start.cpp +DEP_CPP_BOT_S=\ + "..\metamod\dllapi.h"\ + "..\metamod\engine_api.h"\ + "..\metamod\h_export.h"\ + "..\metamod\log_meta.h"\ + "..\metamod\meta_api.h"\ + "..\metamod\mhook.h"\ + "..\metamod\mreg.h"\ + "..\metamod\mutil.h"\ + "..\metamod\osdep.h"\ + "..\metamod\plinfo.h"\ + "..\metamod\sdk_util.h"\ + "..\metamod\types_meta.h"\ + ".\bot.h"\ + ".\bot_func.h"\ + ".\bot_weapons.h"\ + "c:\program files\microsoft visual studio\vc98\include\basetsd.h"\ + +NODEP_CPP_BOT_S=\ + "..\..\devtools\sdk\single-player source\common\const.h"\ + "..\..\devtools\sdk\single-player source\common\crc.h"\ + "..\..\devtools\sdk\single-player source\common\cvardef.h"\ + "..\..\devtools\sdk\single-player source\common\event_flags.h"\ + "..\..\devtools\sdk\single-player source\common\in_buttons.h"\ + "..\..\devtools\sdk\single-player source\dlls\activity.h"\ + "..\..\devtools\sdk\single-player source\dlls\cdll_dll.h"\ + "..\..\devtools\sdk\single-player source\dlls\enginecallback.h"\ + "..\..\devtools\sdk\single-player source\dlls\extdll.h"\ + "..\..\devtools\sdk\single-player source\dlls\vector.h"\ + "..\..\devtools\sdk\single-player source\engine\archtypes.h"\ + "..\..\devtools\sdk\single-player source\engine\custom.h"\ + "..\..\devtools\sdk\single-player source\engine\edict.h"\ + "..\..\devtools\sdk\single-player source\engine\eiface.h"\ + "..\..\devtools\sdk\single-player source\engine\progdefs.h"\ + "..\..\devtools\sdk\single-player source\engine\sequence.h"\ + +# End Source File +# Begin Source File + +SOURCE=.\dll.cpp +DEP_CPP_DLL_C=\ + "..\metamod\dllapi.h"\ + "..\metamod\engine_api.h"\ + "..\metamod\h_export.h"\ + "..\metamod\log_meta.h"\ + "..\metamod\meta_api.h"\ + "..\metamod\mhook.h"\ + "..\metamod\mreg.h"\ + "..\metamod\mutil.h"\ + "..\metamod\osdep.h"\ + "..\metamod\plinfo.h"\ + "..\metamod\sdk_util.h"\ + "..\metamod\types_meta.h"\ + ".\bot.h"\ + ".\bot_func.h"\ + ".\waypoint.h"\ + "c:\program files\microsoft visual studio\vc98\include\basetsd.h"\ + +NODEP_CPP_DLL_C=\ + "..\..\devtools\sdk\single-player source\common\const.h"\ + "..\..\devtools\sdk\single-player source\common\crc.h"\ + "..\..\devtools\sdk\single-player source\common\cvardef.h"\ + "..\..\devtools\sdk\single-player source\common\entity_state.h"\ + "..\..\devtools\sdk\single-player source\common\event_flags.h"\ + "..\..\devtools\sdk\single-player source\common\in_buttons.h"\ + "..\..\devtools\sdk\single-player source\common\weaponinfo.h"\ + "..\..\devtools\sdk\single-player source\dlls\activity.h"\ + "..\..\devtools\sdk\single-player source\dlls\cdll_dll.h"\ + "..\..\devtools\sdk\single-player source\dlls\enginecallback.h"\ + "..\..\devtools\sdk\single-player source\dlls\extdll.h"\ + "..\..\devtools\sdk\single-player source\dlls\vector.h"\ + "..\..\devtools\sdk\single-player source\engine\archtypes.h"\ + "..\..\devtools\sdk\single-player source\engine\custom.h"\ + "..\..\devtools\sdk\single-player source\engine\edict.h"\ + "..\..\devtools\sdk\single-player source\engine\eiface.h"\ + "..\..\devtools\sdk\single-player source\engine\progdefs.h"\ + "..\..\devtools\sdk\single-player source\engine\sequence.h"\ + "..\..\devtools\sdk\single-player source\pm_shared\pm_info.h"\ + +# End Source File +# Begin Source File + +SOURCE=.\engine.cpp +DEP_CPP_ENGIN=\ + "..\metamod\dllapi.h"\ + "..\metamod\engine_api.h"\ + "..\metamod\h_export.h"\ + "..\metamod\log_meta.h"\ + "..\metamod\meta_api.h"\ + "..\metamod\mhook.h"\ + "..\metamod\mreg.h"\ + "..\metamod\mutil.h"\ + "..\metamod\osdep.h"\ + "..\metamod\plinfo.h"\ + "..\metamod\sdk_util.h"\ + "..\metamod\types_meta.h"\ + ".\bot.h"\ + ".\bot_client.h"\ + "c:\program files\microsoft visual studio\vc98\include\basetsd.h"\ + +NODEP_CPP_ENGIN=\ + "..\..\devtools\sdk\single-player source\common\const.h"\ + "..\..\devtools\sdk\single-player source\common\crc.h"\ + "..\..\devtools\sdk\single-player source\common\cvardef.h"\ + "..\..\devtools\sdk\single-player source\common\event_flags.h"\ + "..\..\devtools\sdk\single-player source\common\in_buttons.h"\ + "..\..\devtools\sdk\single-player source\dlls\activity.h"\ + "..\..\devtools\sdk\single-player source\dlls\cdll_dll.h"\ + "..\..\devtools\sdk\single-player source\dlls\enginecallback.h"\ + "..\..\devtools\sdk\single-player source\dlls\extdll.h"\ + "..\..\devtools\sdk\single-player source\dlls\vector.h"\ + "..\..\devtools\sdk\single-player source\engine\archtypes.h"\ + "..\..\devtools\sdk\single-player source\engine\custom.h"\ + "..\..\devtools\sdk\single-player source\engine\edict.h"\ + "..\..\devtools\sdk\single-player source\engine\eiface.h"\ + "..\..\devtools\sdk\single-player source\engine\progdefs.h"\ + "..\..\devtools\sdk\single-player source\engine\sequence.h"\ + +# End Source File +# Begin Source File + +SOURCE=.\h_export.cpp +DEP_CPP_H_EXP=\ + "..\metamod\dllapi.h"\ + "..\metamod\engine_api.h"\ + "..\metamod\h_export.h"\ + "..\metamod\log_meta.h"\ + "..\metamod\meta_api.h"\ + "..\metamod\mhook.h"\ + "..\metamod\mreg.h"\ + "..\metamod\mutil.h"\ + "..\metamod\osdep.h"\ + "..\metamod\plinfo.h"\ + "..\metamod\sdk_util.h"\ + "..\metamod\types_meta.h"\ + ".\bot.h"\ + "c:\program files\microsoft visual studio\vc98\include\basetsd.h"\ + +NODEP_CPP_H_EXP=\ + "..\..\devtools\sdk\single-player source\common\const.h"\ + "..\..\devtools\sdk\single-player source\common\crc.h"\ + "..\..\devtools\sdk\single-player source\common\cvardef.h"\ + "..\..\devtools\sdk\single-player source\common\event_flags.h"\ + "..\..\devtools\sdk\single-player source\common\in_buttons.h"\ + "..\..\devtools\sdk\single-player source\dlls\activity.h"\ + "..\..\devtools\sdk\single-player source\dlls\cdll_dll.h"\ + "..\..\devtools\sdk\single-player source\dlls\enginecallback.h"\ + "..\..\devtools\sdk\single-player source\dlls\extdll.h"\ + "..\..\devtools\sdk\single-player source\dlls\vector.h"\ + "..\..\devtools\sdk\single-player source\engine\archtypes.h"\ + "..\..\devtools\sdk\single-player source\engine\custom.h"\ + "..\..\devtools\sdk\single-player source\engine\edict.h"\ + "..\..\devtools\sdk\single-player source\engine\eiface.h"\ + "..\..\devtools\sdk\single-player source\engine\progdefs.h"\ + "..\..\devtools\sdk\single-player source\engine\sequence.h"\ + +# End Source File +# Begin Source File + +SOURCE=.\util.cpp +DEP_CPP_UTIL_=\ + "..\metamod\dllapi.h"\ + "..\metamod\engine_api.h"\ + "..\metamod\h_export.h"\ + "..\metamod\log_meta.h"\ + "..\metamod\meta_api.h"\ + "..\metamod\mhook.h"\ + "..\metamod\mreg.h"\ + "..\metamod\mutil.h"\ + "..\metamod\osdep.h"\ + "..\metamod\plinfo.h"\ + "..\metamod\sdk_util.h"\ + "..\metamod\types_meta.h"\ + ".\bot.h"\ + ".\bot_func.h"\ + "c:\program files\microsoft visual studio\vc98\include\basetsd.h"\ + +NODEP_CPP_UTIL_=\ + "..\..\devtools\sdk\single-player source\common\const.h"\ + "..\..\devtools\sdk\single-player source\common\crc.h"\ + "..\..\devtools\sdk\single-player source\common\cvardef.h"\ + "..\..\devtools\sdk\single-player source\common\event_flags.h"\ + "..\..\devtools\sdk\single-player source\common\in_buttons.h"\ + "..\..\devtools\sdk\single-player source\common\usercmd.h"\ + "..\..\devtools\sdk\single-player source\dlls\activity.h"\ + "..\..\devtools\sdk\single-player source\dlls\cdll_dll.h"\ + "..\..\devtools\sdk\single-player source\dlls\enginecallback.h"\ + "..\..\devtools\sdk\single-player source\dlls\extdll.h"\ + "..\..\devtools\sdk\single-player source\dlls\vector.h"\ + "..\..\devtools\sdk\single-player source\engine\archtypes.h"\ + "..\..\devtools\sdk\single-player source\engine\custom.h"\ + "..\..\devtools\sdk\single-player source\engine\edict.h"\ + "..\..\devtools\sdk\single-player source\engine\eiface.h"\ + "..\..\devtools\sdk\single-player source\engine\progdefs.h"\ + "..\..\devtools\sdk\single-player source\engine\sequence.h"\ + +# End Source File +# Begin Source File + +SOURCE=.\waypoint.cpp +DEP_CPP_WAYPO=\ + "..\metamod\dllapi.h"\ + "..\metamod\engine_api.h"\ + "..\metamod\h_export.h"\ + "..\metamod\log_meta.h"\ + "..\metamod\meta_api.h"\ + "..\metamod\mhook.h"\ + "..\metamod\mreg.h"\ + "..\metamod\mutil.h"\ + "..\metamod\osdep.h"\ + "..\metamod\plinfo.h"\ + "..\metamod\sdk_util.h"\ + "..\metamod\types_meta.h"\ + ".\bot.h"\ + ".\waypoint.h"\ + "c:\program files\microsoft visual studio\vc98\include\basetsd.h"\ + +NODEP_CPP_WAYPO=\ + "..\..\devtools\sdk\single-player source\common\const.h"\ + "..\..\devtools\sdk\single-player source\common\crc.h"\ + "..\..\devtools\sdk\single-player source\common\cvardef.h"\ + "..\..\devtools\sdk\single-player source\common\event_flags.h"\ + "..\..\devtools\sdk\single-player source\common\in_buttons.h"\ + "..\..\devtools\sdk\single-player source\dlls\activity.h"\ + "..\..\devtools\sdk\single-player source\dlls\cdll_dll.h"\ + "..\..\devtools\sdk\single-player source\dlls\enginecallback.h"\ + "..\..\devtools\sdk\single-player source\dlls\extdll.h"\ + "..\..\devtools\sdk\single-player source\dlls\vector.h"\ + "..\..\devtools\sdk\single-player source\engine\archtypes.h"\ + "..\..\devtools\sdk\single-player source\engine\custom.h"\ + "..\..\devtools\sdk\single-player source\engine\edict.h"\ + "..\..\devtools\sdk\single-player source\engine\eiface.h"\ + "..\..\devtools\sdk\single-player source\engine\progdefs.h"\ + "..\..\devtools\sdk\single-player source\engine\sequence.h"\ + +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd" +# Begin Source File + +SOURCE=.\bot.h +# End Source File +# Begin Source File + +SOURCE=.\bot_client.h +# End Source File +# Begin Source File + +SOURCE=.\bot_func.h +# End Source File +# Begin Source File + +SOURCE=.\bot_weapons.h +# End Source File +# Begin Source File + +SOURCE=.\waypoint.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/HPB_bot_mm.vcproj b/HPB_bot_mm.vcproj new file mode 100644 index 0000000..e573ed3 --- /dev/null +++ b/HPB_bot_mm.vcproj @@ -0,0 +1,268 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7e95539 --- /dev/null +++ b/Makefile @@ -0,0 +1,39 @@ +CPP = gcc-2.95.3 +TARGET = hpb_bot_mm +ARCHFLAG = i586 +BASEFLAGS = -Dstricmp=strcasecmp -Dstrcmpi=strcasecmp +OPTFLAGS = +CPPFLAGS = ${BASEFLAGS} ${OPTFLAGS} -march=${ARCHFLAG} -O2 -w -I"../metamod" -I"../../devtools/hlsdk-2.3/multiplayer/common" -I"../../devtools/hlsdk-2.3/multiplayer/dlls" -I"../../devtools/hlsdk-2.3/multiplayer/engine" -I"../../devtools/hlsdk-2.3/multiplayer/pm_shared" + +OBJ = bot.o \ + bot_chat.o \ + bot_client.o \ + bot_combat.o \ + bot_models.o \ + bot_navigate.o \ + bot_start.o \ + dll.o \ + engine.o \ + h_export.o \ + util.o \ + waypoint.o + +${TARGET}_i386.so: ${OBJ} + ${CPP} -fPIC -shared -o $@ ${OBJ} -Xlinker -Map -Xlinker ${TARGET}.map -ldl + mv *.o Release + mv *.map Release + mv $@ Release + +clean: + rm -f Release/*.o + rm -f Release/*.map + +distclean: + rm -rf Release + mkdir Release + +%.o: %.cpp + ${CPP} ${CPPFLAGS} -c $< -o $@ + +%.o: %.c + ${CPP} ${CPPFLAGS} -c $< -o $@ diff --git a/ReadMe.txt b/ReadMe.txt new file mode 100644 index 0000000..4b74c0e --- /dev/null +++ b/ReadMe.txt @@ -0,0 +1,1278 @@ +botman's HPB bot source code - January 7th, 2001 + +This source code is provided as an example of how to create a bot for MODs +for the game Half-Life by Valve software. The use of this source code is +unrestricted as long as you provide credit to me in any ReadMe.txt file that +is distributed with your bot or MOD and also on any website that is used to +distribute your bot or MOD. The source code included here will allow you to +add a bot to Half-Life deathmatch, Gearbox's Opposing Force MOD, Counter- +Strike, Team Fortress Classic, Deathmatch Classic, the Holy Wars MOD and the +Front Line Force MOD. This source code allows you to build a bot DLL file +for the Windows version of Half-Life as well as the Linux dedicated server +version of Half-Life. + + +What you need to run the bot: + +To run the bot on a Windows machine, you will need the full retail version +of Half-Life, Counter-Strike, or Opposing Force. The demo version of Half- +Life will not allow you to run a bot or any MOD. To run the bot on a Linux +machine you will need to download and install the Linux dedicated server +version of Half-Life and the Linux version of the MOD that you wish to use. +The Linux version of the Half-Life dedicated server can be found on +www.fileplanet.com (in the Half-Life Official section) as well as on many +other game download websites. + + +What you will need to build the bot: + +You will need one of the following C++ compilers: + +Microsoft Visual C++ 5.0 (with service pack 3) or Visual C++ 6.0 +(available for purchase from www.microsoft.com) + +When using Microsoft Visual C++, you will need to open the HPB_bot.dsw +workspace file in the HPB_bot\dlls directory then use "Build->Rebuild All" +to build the HPB_bot.dll file. + +To build the HPB bot on a Linux machine, FTP the files to a Linux machine +(making SURE to use ASCII mode to transfer the files), then run "make" +to build the HPB bot shared library (.so file). If you unzip the HPB bot +source code directly on a Linux box, make SURE to run dos2unix or some +other utility to convert the carriage return and linefeed pairs (CR/LF) +to newline (NL) format, otherwise the make utility and the C++ compiler +will complain. + + +"Gee, Mr. Wizard. How does the HPB bot work?" + +Traditionally MODs for Half-Life could not have bots unless the MOD authors +created bot code embedded in the MOD itself. You were not simply able to +combine bots with MODs. For example, you couldn't use The Jumbot with +Counter-Strike to get bots in Counter-Strike. The Jumbot is a MOD. Counter- +Strike is a MOD. Two MODs can not be loaded by Half-Life at the same time. +This would be like trying to load Counter-Strike and Team Fortress Classic +at the same time. It just don't work. In order to add bots to a MOD you +needed to have access to the source code to that MOD. + +The HPB bot allows you to add a bot to Half-Life MODs without requiring you +to have access to the source code for the MOD. In fact, for most MODs, you +will not be able to get access to the source code of the MOD. Most MOD teams +do not make the source code to the MOD available to the general public. The +source code for many MODs (like Counter-Strike, Team Fortress Classic, +Opposing Force, or Gunman) is owned by a company or corporation and is +considered proprietary and confidential and can not be distributed to the +general public. Even though you don't have access to the source code of the +MOD you can still create a bot for the MOD. The reason you can do this is +that the Half-Life engine has an internal structure used for entities. This +same structure is used for all MODs when they create entities in a game. +When a player connects to a server, the Half-Life engine creates an entity +for this player and calls functions in the MOD when actions occur with that +entity. Bots are created in exactly the same way that players are created, +except that bots don't have a network connection since they don't exist +outside of the server. + +I started trying to develop a method of adding bots to Team Fortress Classic +and Counter-Strike in early 2000 (late January/early February). I started +by trying to remove things from the Valve Standard SDK that weren't needed +by bots and somehow get a modified SDK and MOD to load at the same time. +After many weeks of crashing my Half-Life server over and over and over +again, I decided it would be easier to start almost completely from scratch +and create 99% of the code myself. After working on my Half-Life deathmatch +bot for many months, I had a pretty good understanding of how the Half-Life +engine interfaced to a MOD. There was a list of functions in the Half-Life +engine that the MOD could call and there was a list of functions in the MOD +that the Half-Life engine would call. All of the other code in the MOD +was pretty well self-contained and didn't need any direct hooks into the +Half-Life engine (other than the previously mentioned list of functions). + +I felt that it should be possible to create a DLL that sits between the +Half-Life engine and the MOD and pass function calls from one to the other. +When the Half-Life engine wanted to call a MOD function it would call this +intermediate DLL and the intermediate DLL would pass the call along to the +MOD. If the MOD needed to call a Half-Life engine function, it would call +the intermediate MOD version of this function and the intermediate MOD +would pass the call along to the real Half-Life engine function. After +some weeks with playing around with this idea, I was able to create my +Stripper Add-On which allowed you to select weapons that you did not want +to spawn in a level (strip them out), without having to modify the map +file itself. You could strip out just the gauss gun and crossbow if you +didn't want those weapons to appear and every map that you loaded would +not have those weapons available. This was done by intercepting the +engine "spawn" request to the MOD. I would compare the name of the item +trying to be spawned and if it was one of the ones that I didn't want, +I would simply return to the engine and not pass this request on to +the MOD DLL thus the item would never be spawned in the game. + +Perhaps this would be a good time to explain how entities get spawned in +Half-Life. When you create a map (using Worldcraft or other map editing +tools), you specify the name of an entity that you want to create at a +specific location in the level. These names are stored as ASCII text +strings (like "weapon_shotgun", "item_healthkit", or "ammo_crossbow"). +When the Half-Life engine wants to create one of these items, it calls +a function in the MOD DLL that has the same name as the item. For +example, in Half-Life deathmatch one of the functions exported from the +DLL file is called "weapon_shotgun". You won't find this function in +the Standard SDK source code if you go looking for it. It actually gets +created by a macro called "LINK_ENTITY_TO_CLASS()". This macro is defined +in the util.h file in the Standard SDK. This macro creates a function +that calls GetClassPtr(). GetClassPtr() is defined in the cbase.h file +in the Standard SDK. GetClassPtr() will allocate space for an entity if +it does not exist yet and then will return a pointer to the private data +portion of that entity. What this means is that whenever you have an +entity in a map, you will have to have an exported function in the MOD DLL +with the same name as that entity. This becomes important later on when +you create the intermediate DLL that I referred to above. You will have +to create one function in your bot DLL for each exported function in the +MOD DLL. This way when the engine tries to create a "weapon_shotgun", +it will call the weapon_shotgun() function in the bot DLL and the bot +DLL weapon_shotgun() will call the MOD DLL's weapon_shotgun() function +(thus completing the chain). + +Once I was able to intercept calls from the Half-Life engine to the MOD +DLL, I began working on trying to intercept calls from the MOD DLL to the +Half-Life engine. This turned out to be fairly easy to do since the +Half-Life engine calls a function in the MOD with a list of engine functions +that the MOD can call. The function that does this in the Standard SDK is +"GiveFnptrsToDll()", which can be found in the h_export.cpp file. In the +Standard SDK, all this function does is copy this structure to a globally +accessible structure called "g_engfuncs". In the HPB bot version of +GiveFnptrsToDll (also found in h_export.cpp), it does a little more than +this. It make a local copy of the Half-Life engine's functions (also stored +in a variable called g_engfuncs) so that the HPB bot DLL can call the +engine's version of these function when needed. The HPB bot version of +GiveFnptrsToDll then determines which MOD is being loaded (by calling an +engine function to get the game directory and using that to determine which +MOD is being played). Once the MOD is known, the HPB bot code loads the DLL +file that is required for this MOD (for example TFC uses a DLL file called +tfc.dll). Once the MOD DLL has been loaded by the HPB bot code, it needs to +load the address of 2 functions exported by the MOD DLL (GiveFnptrsToDll and +GetEntityAPI). The HPB bot code overrides the function addresses in the +engine's function table with HPB bot versions of these functions (so that +the MOD DLL will call the HPB bot version of these functions instead of +calling the Half-Life engine's version) and passes this "engine" function +table on to the newly loaded MOD DLL by calling it's version of the +GiveFnptrsToDll() function. (You may want to re-read the previous paragraph +again SLOWLY if you don't fully understand what I just said). + +After the Half-Life engine has called GiveFnptrsToDll(), it calls a function +named "GetEntityAPI()". GetEntityAPI() will copy the list of MOD functions +that can be called by the engine, into a memory block pointed to by a pointer +passed into this function. 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 HPB bot passes it's list of these functions back to the Half-Life +engine and then calls the MOD DLL's version of GetEntityAPI (passing in a +pointer to the HPB bot's structure that will hold the MOD DLL's function +list). + +After GiveFnptrsToDll() and GetEntityAPI() have been called, the Half-Life +engine will call GameDLLInit() which can be used by the MOD DLL to initialize +any variables that it needs. Each of these functions (GiveFnptrsToDll, +GetEntityAPI, and GameDLLInit) will only be called one time by the Half-Life +engine (as long as that MOD is running). They don't get called each time a +map is loaded. + +You may be wondering "How does Half-Life know what the DLL file for a MOD is +anyway?". Each MOD exists in a subdirectory (or folder) inside the Half-Life +directory. For example all of TFC's files will be found in the Half-Life\tfc +folder. All of Counter-Strike's files will be found in the Half-Life\cstrike +folder. Within this MOD folder is a file called "liblist.gam". This file is +used by Half-Life to load a MOD. You can view or edit this file with any +text editor (like Notepad). One of the entries in this file is called +"gamedll". The gamedll entry tells Half-Life the directory and filename of +the MOD DLL file. For example, in the tfc directory the liblist.gam file has +"dlls\tfc.dll" as the gamedll entry. When Half-Life starts to load the TFC +MOD, it scans this liblist.gam file and loads the MOD DLL file indicated by +the "gamedll" entry (in this case dlls\tfc.dll which would actually be +"C:\SIERRA\Half-Life\tfc\dlls\tfc.dll" if you have Half-Life installed in the +default directory of C:\SIERRA\Half-Life). + +So, the engine loads the DLL specified by "gamedll", then calls the +GiveFnptrsToDll() function in this DLL. The engine then calls GetEntityAPI() +from this DLL and then it calls GameDLLInit() in this DLL. Once this is done, +the engine proceeds to load the map and begins spawning entities that are +contained within that map. It does this by calling the functions in the DLL +with the same name as the entity (like in the "weapon_shotgun" example above). + +One of the entities that will be found in every map is called "worldspawn". +This entity is always the first entity created and is "the world" in a map. +The worldspawn entity is handled in world.cpp in the Standard SDK. In the +Standard SDK, the Spawn() function for the worldspawn entity will precache +all of the entities needed by the MOD as well as read the sv_cheats CVAR and +store the value of sv_cheats. This worldspawn entity will only be created +once and there will only be one worldspawn entity for each map. You can see +in the HPB bot source code where I check if the entity being spawned is this +worldspawn entity (in the DispatchSpawn function found in dll.cpp). I use +this time to initialize map specific variables (like waypoint file loading +and global variable initialization) and also precache some additional sounds +and sprites needed by my waypoint editing code. Precaching an entity more +than once does not cause any problems so it's okay if I precache it and then +the MOD precaches the same sound, model or sprite in it's Spawn() function. + +Once all of the entities in the .bsp map file have been spawned, the Half-Life +engine allows clients to connect to the game. The Half-Life engine will call +a function called "StartFrame()" at the beginning of every video frame while +the MOD is running. So if you have a video card that gives you 30 frames per +second (FPS), the StartFrame() function will get called 30 times every second. +You can use this StartFrame() function to perform periodic functions that you +want to execute. One of the functions that you will want to execute every +frame is to call a Think() function for the bots. + +In the Standard SDK, when you create an entity, you can assign a Think() +function for that entity that will get called periodically by the Half-Life +engine. For fake clients (bots) the engine will not call the Think() function +and you must do this yourself. That's why this is done in the StartFrame() +function found in the HPB bot dll.cpp file. There are lots of other functions +that are executed in the HPB bot StartFrame() function, but I won't go into +any detail about each one of those. You can read through the code and figure +these things out on your own. + +Okay, so now we know how things are started up and we know how entities get +created. We know how to get the bot's Think() function to be called during +every frame. How do you actually get a bot into the game? + +Look at the BotCreate() function in the bot.cpp file of the HPB bot source +code. In it's simplest form, you need to do the following... + +void BotCreate(void) +{ + edict_t *BotEnt; // this is a pointer to the engine's edict structure + char ptr[128]; // allocate space for message from ClientConnect + char *infobuffer; // pointer to buffer for key/value data + int clientIndex; // engine's player index (1-32) + + BotEnt = g_engfuncs.pfnCreateFakeClient)( "Bot" ); + player( VARS(BotEnt) ); + infobuffer = g_engfuncs.pfnGetInfoKeyBuffer)( BotEnt ) + clientIndex = ENTINDEX( BotEnt ); + g_engfuncs.pfnSetClientKeyValue)( clientIndex, infobuffer, "model", "gina" ); + ClientConnect( BotEnt, "BotName", "127.0.0.1", ptr ); + ClientPutInServer( BotEnt ); + BotEnt->v.flags |= FL_FAKECLIENT; +} + +Let's analyze this line by line to understand what it does... + +The pfnCreateFakeClient() function is an engine function that creates a fake +client (bot) on the server. + +The player() function is one of the functions exported by the MOD DLL that +initializes the player entity. You have to pass in an entvars_t pointer to +this function and that's what the VARS() macros does for you (it converts an +edict_t pointer to the associated entvars_t pointer). + +The pfnGetInfoKeyBuffer() function is an engine function that returns a +pointer to a text string that contains key value information about this +entity (things like left handed or right handed models in Counter-Strike use +the infobuffer key "lefthand" to store which model version to use). + +The ENTINDEX() function is an engine function that returns the client index +for the player (the client index ranges from 1 to 32 depending on which +client slot this player is using). + +The pfnSetClientKeyValue() function is an engine function that modifies a +key/value pair in the infobuffer that was retrieved previously. Here we +are setting the player model to use the "gina" Half-Life deathmatch model. +This model may get changed by the MOD once the player joins the game. + +The ClientConnect() function is a MOD DLL function that normally gets called +by the Half-Life engine when a human player is connecting to the server. The +"BotName" parameter is the name of the bot and this is the name that will show +up in the scoreboard. The "127.0.0.1" parameter is the IP address of the bot. +Since bots don't have a network connection, I use the standard TCP/IP loopback +address to indicate to the engine that this is a bot connecting to the server. +The bot will appear on the scoreboard at this point, but the bot is not quite +connected to the game yet. + +ClientPutInServer() is another MOD DLL function that we call to actually +put the bot into the game. At this point the bot will appear like any other +player would appear. If the MOD does not require the bot to select a team +or class or weapon before joining the game, the bot will be spawned into +the level and will be visible to other players. + +The last thing we do is set the FL_FAKECLIENT bit in the entity's flags +variable. We do this to let the engine know that this player is a bot and +does not have a network connection. If the engine tries to send a network +message to a bot it will crash. The FL_FAKECLIENT bit tells the engine "Do +not send any network messages to this entity". + +Now we've got the bot into the game, we need to be able to get the bot to +run around and shoot at stuff. We do this by calling an engine function +named "pfnRunPlayerMove()". This function is only used by fake clients (bots) +and is required to get the engine to update the bot's origin (location) as +well as to get the bot to be able to fire weapons, jump, duck, use items, +and perform all of the other functions that normal human players can do. + +The pfnRunPlayerMove() function is called during every frame for every bot +that is connected to the server. The HPB bot StartFrame() function will +loop through a global array of bot structures (called "bots") to determine +which bots are connect to the server and will call the BotThink() function +for each one of these bots. The BotThink() function (found in bot.cpp) will +call pfnRunPlayerMove() when it is ready to have the engine update the bot's +location and "keyboard" keys that have been pressed. The pfnRunPlayerMove() +function takes the following arguments... + +void pfnRunPlayerMove( edict_t *fakeclient, const float *viewangles, + float forwardmove, float sidemove, float upmove, unsigned short buttons, + byte impulse, byte msec ); + +...this function prototype can be found in the engine.h file included with +the HPB bot or in the Standard SDK source code. Here's a description of +the arguments to the pfnRunPlayerMove() function... + +edict_t *fakeclient - This is the edict pointer of the fake client (bot) that +was created with the pfnCreateFakeClient() function in BotCreate(). + +float *viewangles - This is the view angles for the bot. The view angles +are the direction in which the bot is facing (viewing and aiming). + +float forwardmove - This is the speed which you wish the bot to use to move +forward during the next frame. The maximum default forward speed in +Half-Life deathmatch is 270 units/second. Other MODs will have different +maximum speeds (sometimes the maximum speed depends on which class the bot +is playing). Passing in a negative value for the speed will cause the bot +to move backwards. + +float sidemove - This is the speed to use when moving sideways (strafing). +Again, different MODs will have different maximum strafe speeds (possibly +depending on the player class). Positive values will move the bot to its +right, and negative values will move the bot to its left. + +float upmove - From what I have found, this parameter seems to do nothing. +I assumed it was used to move up and down ladders, but this is actually +handled by getting the bot to look in the direction it wants to go (using +the view angles) and "pressing" the IN_FORWARD key to move in that direction. + +unsigned short buttons - This is what is used by the bot to press "keys" on +the "keyboard". The button variable is a bit mask of values that can be set +to get the bot to perform various actions (like shooting, ducking, jumping, +etc.). These values are all defined in the common\in_buttons.h file found +in the HPB bot template source code or the Standard SDK source code. + +byte impulse - There are "impulse commands" that can be set by the player +which get handled by the MOD DLL code. In Half-Life deathmatch, for example, +the flashlight is an impulse command (100) setting impulse to 100 will turn +the flashlight on or off. See the CBasePlayer::ImpulseCommands() in the +player.cpp file from the Standard SDK for more details. The HPB bot template +source code doesn't use this parameter for anything. + +byte msec - This value (in milliseconds) is used to tell the engine what the +"duration" of the pfnRunPlayerMove() command should be. The duration of the +movement for a bot needs to be based on what the frame rate of the server is. +Faster frame rates means less time per frame is spent moving a player at X +units per second. Ideally you want to send a total of 1000 milliseconds for +every second of real time. This means if you are getting exactly 10 frames +per second, and the BotThink function is getting called 10 times a second, +every pfnRunPlayerMove() command should use 100 as the msec value (since 10 +times 100 equals 1000). What you are trying to do is predict how long you +think the next frame will actually take for the engine to render so that the +bot moves at the desired speed during that period of rendering time. Since +you can't predict the future, you have to guess at what this value will be. +The msec value that I am using comes from modified code from TheFatal's +(author of The Jumbot) advanced bot framework. You can see this code at the +top of the BotThink() function in the bot.cpp file. + +Okay, so now we understand how to get a bot into the game and how to get it +to move around in a level. What to we need to do to get the HPB bot code to +work for a MOD that isn't already supported by the HPB bot? + +I mentioned above that the MOD DLL exports a list of functions that have the +same name as the entity. In the HPB bot template source code "linkfunc.cpp" +contains a list of functions that are the hooks between the Half-Life engine +and the MOD DLL. The HPB bot DLL will export these function so that the +Half-Life engine can call them when an entity is spawned. The HPB bot version +of these functions will call the MOD DLL's version of these same functions +to get the MOD to create these entities. I have included all of the entities +from Half-Life deathmatch, Team Fortress 1.5, Counter-Strike 1.0, Opposing +Force, and Front Line Force 1.1. To support any other MODs (or to support +updates to the currently supported MODs) you may have to add entities to +the linkfunc.cpp file. If you have Microsoft Visual C++ you can use the +"dumpbin" utility (found in the bin directory) to dump the exported symbols +in a DLL file. Start up an MS-DOS window and run "dumpbin /exports mp.dll" +(or whatever the MOD DLL name is) and it will give you the list of exported +symbols (most of which will be the entities that you need). You can ignore +any of these symbols that begin with a '?'. These are C++ functions that +do not need to be exported from the bot DLL code (since they never will be +called by the Half-Life engine). All of the other symbols need to be included +in linkfunc.cpp in order for entities in the MOD maps to be properly spawned. + +If you don't have Microsoft Visual C++, I have written a small C utility +which will allow you to dump symbols from a DLL file called "exports.c". You +can find this program here: http://planethalflife.com/botman/exports.c +Compile this using any C or C++ compiler and run exports.exe with the DLL +filename as an argument and it will print out a list of exported symbols. + +You will notice that some of the exported symbols are the same as function +names that we have discussed previously (GetEntityAPI and GiveFnptrsToDll). +These and a few others (GetEntityAPI2 and GetNewDLLFunctions) are already +defined in the HPB bot source code and do not need to be included in the +linkfunc.cpp file. If you include a symbol that has already been defined +(either in linkfunc.cpp or in any of the other HPB bot files) you will get +an error message from the compiler or linker complaining about duplicate +function names. To fix this just remove the entry that you added to the +linkfunc.cpp file that caused the duplication. + +After updating linkfunc.cpp you will also need to change h_export.cpp to +let it know about the MOD directory that you are using. Modify the +GiveFnptrsToDll() function to know what library (DLL file) needs to be +loaded based on the directory name for the MOD. There are already examples +in this function for Half-Life deathmatch, Team Fortress Classic, Counter- +Strike, Opposing Force, and Front Line Force. + +Once you have updated linkfunc.cpp with all of the necessary entity names +and modified h_export.cpp to load the MOD DLL file, you should rebuild the +HPB bot DLL, copy it to the MOD dlls directory, modify the liblist.gam file +in the MOD directory to use your bot DLL file instead of the MOD DLL file +(in the gamedll setting), and start up the MOD. You should be able to create +a LAN game and spawn into the game just like you normally would without the +bot code. Try running around in various maps to make sure that all entities +are showing up and make sure that everything behaves the way that it normally +would (i.e. you can use items, push buttons, fire weapons, open doors, climb +ladders, swim, jump, etc.) If the game crashes at any point then you probably +haven't added all the entities that should be added. It is a good idea to +turn on developer mode when starting up Half-Life (use "hl.exe -dev" in a +shortcut to start Half-Life in developer mode). This will print out extra +information on the console when loading a level. Any entities that you failed +to add to linkfunc.cpp will cause an error message to be printed out while +the Half-Life engine is loading the map. You should make a note of these +missing entities, quit the game, and edit linkfunc.cpp to add these missing +entities, then rebuild the bot DLL file, start the MOD and try that map out +again. + +Okay, you've got all the MOD entities added to the linkfunc.cpp file. How +do you create a bot and get it to join a team? + +For some MODs the bot will immediately spawn into the game without having to +choose anything from a menu (like Half-Life deathmatch). For other MODs you +will have to create code so that the bot can make selections from any menus +that get presented to players when they join the game. Now would be a good +time to talk about how network packets are sent between the server and the +client. + +When the MOD DLL wants to display something on the client's HUD, it must send +a network packet to that client. This is done using several Half-Life engine +functions. Every network message from the MOD to the client will begin by +calling the pfnMessageBegin() engine function (found in the engine.cpp file +in the HPB bot template source code). Every network message will end by +calling pfnMessageEnd() (also in engine.cpp). Between the pfnMessageBegin() +and pfnMessageEnd(), the MOD will sent BYTEs, CHARacters, SHORT integers, +LONG integers, ANGLEs, COORDinates, STRINGs or ENTITY values. Each of these +data types has a pfnWriteXXX() function (where XXX is Byte, Char, Short, Long, +Angle, etc.). You can see the prototypes for these functions in the +engine\eiface.h file in the Standard SDK or the HPB bot template source code. + +The network packet is made up of this data in the same order that the MOD +calls these functions. For example if the MOD wants to send a network packet +with a byte, followed by a string, followed by a long, it would issue the +following Half-Life engine function calls... + +pfnMessageBegin(MSG_ONE, gmsgShowIt, NULL, pEdict); +pfnWriteByte(1); +pfnWriteString("You've got the flag!"); +pfnWriteLong(100000); +pfnMessageEnd(); + +Here's what the arguments to pfnMessageBegin() are: + +int msg_dest - This is the destination of this message. Messages can be sent to +all players (MSG_BROADCAST or MSG_ALL), one player (MSG_ONE_UNRELIABLE or +MSG_ONE), players in the potentially visible set (MSG_PVS or MSG_PVS_R), or +players in the potentially audible set (MSG_PAS or MSG_PAS_R). There is a +"unreliable" and "reliable" version of each type of message. Unreliable means +that the Half-Life engine sends the message once, but it is not guaranteed to +get to the client. Reliable messages will keep begin sent to the client until +the client replies back that it has successfully received this message. These +msg_dest types can be found in the common\const.h file. + +int msg_type - The message type is a number assigned to the message when the +MOD is started up by the Half-Life engine. For each custom message that the +MOD wants to send to the client, the MOD will have to "register" this message +with the Half-Life engine. This is done in the MOD using the REG_USER_MSG() +macro (which calls the pfnRegUserMsg engine function). You pass REG_USER_MSG +the network message name (for example "ShowIt") and the size of the message +in bytes (or -1 if the size will vary). The size is the number of bytes that +will be sent between the pfnMessageBegin() and pfnMessageEnd(). You can find +many examples of the REG_USER_MSG in the Half-Life Standard SDK source code. + +float *pOrigin - This parameter can be used to pass an origin Vector (an array +of 3 floating point values) to the client. The origin is the location in 3D +space that you want to use for this network message (for example to display +a sprite at a specific location in a map). + +edict_t *ed - The edict parameter informs the Half-Life engine which entity +(player) you want to send a message to when you use MSG_ONE_UNRELIABLE or +MSG_ONE. The message will only get sent to this specific player. + +When the MOD DLL sends a network message, the HPB bot engine.cpp code will +intercept these pfnMessageBegin(), pfnMessageEnd() and pfnWriteXXX() functions +before they are sent to the engine. This allows bots to monitor what network +messages are being sent to other clients and to themselves. Since the bots +don't have a client, the engine will simply throw away any messages that are +destined for a bot (as long as the FL_FAKECLIENT bit is set in the flags field +of that player). + +So, in order to know when things are being displayed on the bot's "HUD", we +must intercept the MOD network messages and recognize what data is being sent +as part of the network packet. The first thing we must do is know what all +of the "msg_type" values are going to be. Since each message has a unique +msg_type value and since no two MODs will use the same msg_type values +(because each MOD has it's own list of custom network messages), we need some +way to know which msg_types correspond to which messages. This is done in +the pfnRegUserMsg() function in the HPB bot engine.cpp file. pfnRegUserMsg() +will get passed a text string (the network message name) and the number of +bytes (which we don't really care about). I have a global integer variable +used to store the msg_type assigned to each message as it is created by the +Half-Life engine. Notice that I first call the Half-Life engine's version +of pfnRegUserMsg() and store the return value for my use like this... + + msg = (*g_engfuncs.pfnRegUserMsg)(pszName, iSize); + +Then I compare the pszName parameter to specific strings that I know the MOD +will use for certain types of network messages. How do I know what these +strings are? I have to start the MOD up and let the HPB bot write out all +of these values to a text file that I can go back and look at later. If you +are using Microsoft Visual C++, this will be done automatically for you if +you build the HPB bot DLL in Debug mode. Building in Debug mode will define +the _DEBUG symbol which will cause this code in pfnRegUserMsg() to be added +to the HPB bot DLL... + +#ifdef _DEBUG + fp=fopen("bot.txt","a"); + fprintf(fp,"pfnRegUserMsg: pszName=%s msg=%d\n",pszName,msg); + fclose(fp); +#endif + +If you don't have Microsoft Visual C++ you can just comment out the "#ifdef +_DEBUG" and "#endif" lines and rebuild the bot to always print this data +out. + +This creates a text file in the Half-Life directory called "bot.txt" and +will write out a text string each time pfnRegUserMsg() gets called by the +MOD DLL. For example, here's some of the output from Team Fortress Classic +with Debug mode enabled... + +pfnRegUserMsg: pszName=SelAmmo msg=64 +pfnRegUserMsg: pszName=CurWeapon msg=65 +pfnRegUserMsg: pszName=Geiger msg=66 +pfnRegUserMsg: pszName=Flashlight msg=67 +pfnRegUserMsg: pszName=FlashBat msg=68 +pfnRegUserMsg: pszName=Health msg=69 +pfnRegUserMsg: pszName=Damage msg=70 +pfnRegUserMsg: pszName=Battery msg=71 +pfnRegUserMsg: pszName=SecAmmoVal msg=72 +pfnRegUserMsg: pszName=SecAmmoIcon msg=73 +pfnRegUserMsg: pszName=Train msg=74 +pfnRegUserMsg: pszName=Items msg=75 +pfnRegUserMsg: pszName=Concuss msg=76 +pfnRegUserMsg: pszName=HudText msg=77 +pfnRegUserMsg: pszName=SayText msg=78 + +Team Fortress Classic has about 50 of these custom network messages. What +do they all do? We don't know that yet. You have to figure out what they +do by looking to see where they get used in the game. Finding out where they +appear in the game will be explained a little later on in this ReadMe.txt +file. You'll also notice that the msg_type assigned by the Half-Life engine +doesn't begin with 1 (or 0). It starts with 64. There are some network +messages that are reserved by the Half-Life engine. Some of these can be +seen in the util.h file in the Standard SDK or the HPB bot template source +code. All of the reserved msg_types begin with "SVC_". The other reserved +messages are unknown (at least by me). + +Most MODs, that use the same network messages that are defined in the Valve +Standard SDK, also use the same format for the data part of those network +messages. For example the "Health" message shown above is used to tell +the HUD to change the current health value displayed on the HUD. This network +message can be found in the player.cpp file in the Standard SDK... + + MESSAGE_BEGIN( MSG_ONE, gmsgHealth, NULL, pev ); + WRITE_BYTE( m_iClientHealth ); + MESSAGE_END(); + +...this is the same as... + + pfnMessageBegin( MSG_ONE, gmsgHealth, NULL, pev ); + pfnWriteByte( m_iClientHealth ); + pfnMessageEnd(); + +...in the case of TFC, gmsgHealth will always be 69 (unless Valve modifies +Team Fortress Classic to include a new custom network command that shuffles +all of these value around). It's best not to "hard-code" these values since +it makes things much more difficult to debug if a new version of a MOD is +released. By saving the value that pfnRegUserMsg() returns and comparing +that to the value passed into pfnMessageBegin(), we can know which message +is being sent to the client (in this case, the health of a player is being +adjusted up or down and needs to be displayed on the HUD). + +Now that we know how to intercept network messages, we can begin to try to +figure out how to get a bot to select something in a menu so that it can +choose a team or class or weapon before joining the game. How do we go about +doing this? We need to know what msg_type is used when a menu is displayed. +The easiest way to find this out is to start a game with the "engine_debug" +flag enabled and join a game ourselves, then go back and look at the "bot.txt" +file to see what network messages got sent to our HUD. The "engine_debug" +flag can be found in the engine.cpp file and is normally enabled by pulling +down the console and using the command "debug_engine" to turn it on. From +that point on, any messages going from the MOD to the engine will be logged +in this bot.txt file. Since we want to display messages that occur before +we get connected to the game (and have a chance to pull the console down), +we will temporarily hard code the engine_debug flag to be enabled at startup +time. In engine.cpp, change this line... + +int debug_engine = 0; + +...to this... + +int debug_engine = 1; + +...and rebuild the HPB bot DLL file. Copy the HPB bot DLL file to the MOD +dlls directory and start a LAN game up like you did before. Join a game by +picking one of the options in a menu then exit from the game and look at the +bot.txt file. You will see a BUNCH of output in there. This is why you don't +want to leave the debug_engine flag set to 1 all the time (because it will +wind up filling up the user's hard disk with a bunch of data). + +Here's an example of me joining a Team Fortress game by selecting the Red Team +from the VGUI menu... + +Skip down past the point where the pfnRegUserMsg output occurs. You'll see +some entities being created and some stuff being precached. You want to find +a pfnMessageBegin() with a msg_type that is 64 or larger. The first one I see +looks like this... + +pfnMessageBegin: edict=0 dest=2 type=89 +pfnWriteByte: 1 +pfnWriteString: +pfnMessageEnd: + +...msg_type 89 is a "TeamInfo" message. This probably isn't what I want. +Skip on down to the next msg_type that is 64 or larger... + +pfnMessageBegin: edict=1df7410 dest=1 type=80 +pfnWriteByte: 1 +pfnWriteString: #Observer +pfnMessageEnd: + +...msg_type 80 is "TextMsg". This is probably displaying some kind of text +on the HUD. This isn't what we want yet so keep looking... + +pfnMessageBegin: edict=1df7410 dest=1 type=84 +pfnWriteByte: 0 +pfnMessageEnd: + +...msg_type 84 is "ResetHUD". This isn't what we want... + +pfnMessageBegin: edict=1df7410 dest=1 type=85 +pfnMessageEnd: + +...msg_type 85 is "InitHUD". This isn't what we want... + +pfnMessageBegin: edict=1df7410 dest=1 type=73 +pfnWriteString: grenade +pfnMessageEnd: + +...msg_type 73 is "SecAmmoIcon". Something to do with secondary ammo. Not +what we want so keep searching... + +pfnMessageBegin: edict=0 dest=2 type=80 +pfnWriteByte: 1 +pfnWriteString: #Game_playerjoin +pfnWriteString: botman +pfnMessageEnd: + +...msg_type 80 is "TextMsg" (we've seen this before). I see "#Game_playerjoin" +and then "botman" (hey!, that's me!). I'll bet this message is displaying +text on player's HUDs letting them know that I have just joined the game! Keep +searching... + +pfnMessageBegin: edict=1df7410 dest=1 type=91 +pfnWriteByte: 1 +pfnMessageEnd: + +...msg_type 91 is "GameMode". Hmmm, I'm not sure what that is. Let's keep +searching... + +pfnMessageBegin: edict=1df7410 dest=1 type=88 +pfnWriteByte: 1 +pfnWriteShort: 0 +pfnWriteShort: 0 +pfnWriteShort: 0 +pfnWriteShort: 0 +pfnMessageEnd: +pfnMessageBegin: edict=1df7410 dest=1 type=88 +pfnWriteByte: 1 +pfnWriteShort: 0 +pfnWriteShort: 0 +pfnWriteShort: 0 +pfnWriteShort: 0 +pfnMessageEnd: + +...we get 2 msg_type 88's in a row. 88 is "ScoreInfo". This is sending +something to the HUD to update the score information. Probably since I +just joined the game the MOD is letting all the clients know that my score +is 0. Keep searching... + +pfnMessageBegin: edict=1df7410 dest=1 type=104 +pfnWriteByte: 1 +pfnWriteByte: 0 +pfnMessageEnd: + +...msg_type 104 is "Spectator". Yes, I'm in spectator mode until I choose +a team and a class. Keep searching... + +pfnMessageBegin: edict=1df7410 dest=1 type=89 +pfnWriteByte: 1 +pfnWriteString: +pfnMessageEnd: + +...msg_type 89 is "TeamInfo" again. I'm not sure what that is so I'll keep +searching... + +pfnMessageBegin: edict=0 dest=2 type=90 +pfnWriteString: Blue +pfnWriteShort: 0 +pfnWriteShort: 0 +pfnMessageEnd: +pfnMessageBegin: edict=0 dest=2 type=90 +pfnWriteString: Red +pfnWriteShort: 0 +pfnWriteShort: 0 +pfnMessageEnd: + +...msg_type 90 is "TeamScore". Hey, there's something in there I recognize! +It's got "Blue" and "Red", those are the team names. Not quite what we want +so we keep searching... + +pfnMessageBegin: edict=1df7410 dest=1 type=97 +pfnWriteByte: 6 +pfnMessageEnd: + +...msg_type 97 is "HideWeapon". I don't even have any weapons yet, so I'll +just ignore this. This is probably something to prevent some weapon cheat +when switching teams or something. Keep searching... + +pfnMessageBegin: edict=1df7410 dest=1 type=98 +pfnWriteByte: 0 +pfnMessageEnd: + +...msg_type 98 is "SetFOV". I know from the Standard SDK that the FOV is +the field of view (used to zoom in when using the crossbow). Setting the +FOV to zero (the pfnWriteByte value) resets the FOV back to the normal 90 +degrees. Keep searching... + +pfnMessageBegin: edict=1df7410 dest=1 type=69 +pfnWriteByte: 1 +pfnMessageEnd: + +...msg_type 69 is "Health". This is resetting my health to 1! I guess +I've only got 1 unit of health while I'm in observer mode. Keep searching... + +pfnMessageBegin: edict=1df7410 dest=1 type=70 +pfnWriteByte: 0 +pfnWriteByte: 0 +pfnWriteLong: 0 +pfnWriteCoord: 384.000000 +pfnWriteCoord: -640.000000 +pfnWriteCoord: -255.000000 +pfnMessageEnd: + +...msg_type 70 is "Damage". I know from the Standard SDK that this message +is sent when the player is damaged by another player, explosions or by the +world (like falling damage). It looks like it's just resetting things to +zero (the pfnWriteBytes and pfnWriteLong). The Coord is probably not used +here. Keep searching... + +pfnMessageBegin: edict=1df7410 dest=1 type=71 +pfnWriteShort: 0 +pfnMessageEnd: + +...msg_type 71 is "Battery". I know in the Standard SDK that the Battery +message is used to increase (or decrease) the armor. I guess this must be +setting my armor to zero. Keep searching... + +pfnMessageBegin: edict=1df7410 dest=1 type=72 +pfnWriteByte: 0 +pfnWriteByte: 0 +pfnMessageEnd: +pfnMessageBegin: edict=1df7410 dest=1 type=72 +pfnWriteByte: 1 +pfnWriteByte: 0 +pfnMessageEnd: + +...Here we have 2 msg_type 72's in a row. msg_type 72 is "SecAmmoVal". This +is probably resetting my secondary ammo value to zero. Keep searching... + +pfnMessageBegin: edict=1df7410 dest=1 type=74 +pfnWriteByte: 0 +pfnMessageEnd: + +msg_type 72 is a "Train" message. I know from the Standard SDK that sending +a zero here means that this player is not currently using a Train (so the HUD +won't display the little forward and backward arrows in the middle of the +display). Keep searching... + +pfnMessageBegin: edict=1df7410 dest=1 type=83 +pfnWriteString: tf_weapon_medikit +pfnWriteByte: -1 +pfnWriteByte: -1 +pfnWriteByte: -1 +pfnWriteByte: -1 +pfnWriteByte: 0 +pfnWriteByte: 1 +pfnWriteByte: 3 +pfnWriteByte: 0 +pfnMessageEnd: +pfnMessageBegin: edict=1df7410 dest=1 type=83 +pfnWriteString: tf_weapon_spanner +pfnWriteByte: 2 +pfnWriteByte: -1 +pfnWriteByte: -1 +pfnWriteByte: -1 +pfnWriteByte: 0 +pfnWriteByte: 2 +pfnWriteByte: 4 +pfnWriteByte: 0 +pfnMessageEnd: + + + +...Whoa! What's this? msg_type 83 is "WeaponList". I see the names of +weapons in each of these packets with a bunch of numbers. I know from the +Standard SDK that the weapon list messages contain the weapon name, weapon +ID, max amount of primary ammo for the weapon, max amount of secondary +ammo, and the slot and position in the slot to display this weapon on the +HUD. This is interesting, but it's not what we want. Keep searching... + +pfnMessageBegin: edict=1df7410 dest=1 type=102 +pfnWriteByte: 0 +pfnWriteByte: 0 +pfnMessageEnd: +pfnMessageBegin: edict=1df7410 dest=1 type=102 +pfnWriteByte: 1 +pfnWriteByte: 0 +pfnMessageEnd: + + + +...msg_type 102 is "AmmoX" messages. I know from the Standard SDK that AmmoX +messages are sent when the ammo amount is increased or decreased. The first +byte in the message is the ammo index value and the second byte in the message +is the current amount of ammo available. All of these are being reset to +zero. Not what we want, keep searching... + +pfnMessageBegin: edict=1df7410 dest=1 type=106 +pfnWriteShort: 0 +pfnWriteShort: 0 +pfnWriteShort: 0 +pfnWriteShort: 0 +pfnWriteShort: 0 +pfnMessageEnd: + +...msg_type 106 is a "ValClass" message. I'm not sure what this is so I'll +just ignore it for now. Keep searching... + +pfnMessageBegin: edict=1df7410 dest=1 type=107 +pfnWriteByte: 2 +pfnWriteString: #Team_Blue +pfnWriteString: #Team_Red +pfnWriteString: #Team_Yellow +pfnWriteString: #Team_Green +pfnMessageEnd: + +...msg_type 107 is a "TeamNames" message. This must be telling my HUD what +the names of the teams are for this map. Getting closer, keep searching... + +pfnMessageBegin: edict=1df7410 dest=1 type=105 +pfnWriteByte: 1 +pfnMessageEnd: +pfnMessageBegin: edict=1df7410 dest=1 type=108 +pfnWriteByte: 0 +pfnMessageEnd: +pfnMessageBegin: edict=1df7410 dest=1 type=109 +pfnWriteByte: 0 +pfnMessageEnd: +pfnMessageBegin: edict=1df7410 dest=1 type=111 +pfnWriteByte: 0 +pfnMessageEnd: +pfnMessageBegin: edict=1df7410 dest=1 type=112 +pfnWriteByte: 0 +pfnMessageEnd: + +msg_types 105, 108, 109, 111, and 112 are "AllowSpec", "Feign", "Detpack", +"BuildSt", and "RandomPC" respectively. I'm not sure what these are used +for so I'll keep searching... + +pfnMessageBegin: edict=1df7410 dest=1 type=93 +pfnWriteString: botman +pfnMessageEnd: + +...msg_type 93 is "ServerName". I guess this is storing the name of the +server on the HUD. In this case, since I created the server, the server +has my name. Shortly after this we find something... + +pfnMessageBegin: edict=1df7410 dest=1 type=110 +pfnWriteByte: 2 +pfnMessageEnd: +pfnMessageBegin: edict=1df7410 dest=1 type=110 +pfnWriteByte: 2 +pfnMessageEnd: + +ClientCommand: jointeam 2 + +pfnMessageBegin: edict=0 dest=2 type=80 +pfnWriteByte: 1 +pfnWriteString: #Game_joinedteam +pfnWriteString: botman +pfnWriteString: 2 +pfnMessageEnd: + +...we have 2 msg_type 110s "VGUIMenu". Hey! That's what we've been looking +for! The MOD is displaying a menu and sending a 2 as the first byte. We'll +have to figure out what that 2 is used for a little later on. Right after +the "VGUIMenu" messages we see a ClientCommand() function being called. What +has happened here is the client has sent a command "jointeam 2". This was +sent by my client when I selected the Red team from the menu. At this point +I'm guessing that "jointeam" assigns me to a team and "2" is used to indicate +which team I want (1=Blue, 2=Red, 3=Yellow, 4=Green). We can verify this by +starting a game and choosing other teams to see how this command changes. + +After receiving the "jointeam" command, the MOD DLL sends a network packet to +the client with a text string (msg_type 80 = "TextMsg") that says +"#Game_joinedteam" and has my player name and a "2". This must be displaying +a message on player's HUDs to let them know that I have joined the Red team. + +So we now know that when we see msg_type = 110 we need to send "jointeam" +followed by the team number that we wish to join. Since the bot doesn't have +a client, it can't issue client commands in the same way that players do. I +have created a function called "FakeClientCommand()" that allows you to pass +a command to the MOD DLL just as though the command had come from a real +client. Here's an example of FakeClientCommand() being used in the BotStart() +function found in the bot_start.cpp file... + + // select the team the bot wishes to join... + if (pBot->bot_team == 1) + strcpy(c_team, "1"); + else if (pBot->bot_team == 2) + strcpy(c_team, "2"); + else if (pBot->bot_team == 3) + strcpy(c_team, "3"); + else if (pBot->bot_team == 4) + strcpy(c_team, "4"); + else + strcpy(c_team, "5"); + + FakeClientCommand(pEdict, "jointeam", c_team, NULL); + +This fills in the c_team with the team number then calls FakeClientCommand() +passing in the command and 2 optional arguments. If additional arguments +aren't needed for the command you should pass NULL instead. + +You can go through a similar process to find the commands needed to select +a class or select a weapon as I did above for joining a team. + +I never did explain what the byte data for msg_type 110 (VGUIMenu) was used +to indicate. This byte value selects what type of VGUI menu is going to be +displayed on the HUD. For example there is one menu for selecting a team, +there is a completely different menu for selecting a class, and another +completely different menu for selecting a weapon (or special skill, etc.) +Each of these VGUI menus will have a byte value assigned to them and they +won't be the same between different MODs. TFC uses 2 as the byte value to +identify the team selection menu. TFC uses 3 as the byte value to identify +the class selection menu. Anytime a msg_type 110 is sent with a byte value +of 3 means the player (or bot) should send a command to select a class. + +Okay so now we know what network messages to look for. How does the HPB bot +code intercept these messages and interpret what is being sent? + +Let's take the case of the "VGUIMenu" message. The first thing we want to +do is store what the msg_type value will be for this network message. This +is done in the pfnRegUserMsg() function in the engine.cpp file. I store this +in a global variable called "message_VGUI". Whenever pfnMessageBegin() gets +called, I compare the msg_type to message_VGUI. If they are the same then I +know that the MOD is sending a VGUI message to one of the clients. I want +to make sure that I only intercept messages that are being sent to bots, +since I don't want to interfere with message being sent to real players. I +mentioned above that one of the parameters to pfnMessageBegin() was the player +edict. This is the same value that gets returned by CreateFakeClient() when +the bot was created. If we store these edict values in some global array, +we can scan this array to see if a network message is being sent to a bot. +This is done by a function called UTIL_GetBotIndex() which is found in the +util.cpp file. This function will return the index in a global array (called +"bots") that stores the bot edict values. If the edict value passed into +UTIL_GetBotIndex() is not found in this array then UTIL_GetBotIndex() will +return -1 indicating that this edict is NOT a bot. If the return value is +zero or greater than this network message is being sent to a bot. I will +need this index value (from the bots array) in several other places during +the network command so I store this value in a global variable called +"botMsgIndex". + +When I know that a network message is being sent to a bot (index >= 0), +and I know which type of message is being sent (msg_type == message_VGUI), +I store a pointer to a function that will be used to handle all of the data +sent during this network message. For example, for TFC, this function is +called "BotClient_TFC_VGUI". This function can be found in "bot_client.cpp". +I store a pointer to this function in the variable "botMsgFunction". The +functions in bot_client.cpp work like state machines. The function is called +over and over for every value passed between the pfnMessageBegin() and the +pfnMessageEnd(). The BotClient_XXX() function must keep track of what data +has already been sent by incrementing a "state" variable that indicates what +type of value it is expecting next. When the last data field in a network +packet is sent, the state variable is reset back to 0 so that the next packet +of this type will start with the first data field again. For example, the +"BotClient_Valve_WeaponList()" function has 9 states (0-8). Here's an example +of a WeaponList network message so that you can see the 8 different data +fields... + +pfnMessageBegin: edict=1e01e40 dest=1 type=76 +pfnWriteString: weapon_9mmhandgun // state = 0 +pfnWriteByte: 2 // state = 1 +pfnWriteByte: 250 // state = 2 +pfnWriteByte: -1 // state = 3 +pfnWriteByte: -1 // state = 4 +pfnWriteByte: 1 // state = 5 +pfnWriteByte: 0 // state = 6 +pfnWriteByte: 2 // state = 7 +pfnWriteByte: 0 // state = 8 +pfnMessageEnd: + +Once the pfnMessageBegin() function has been called, and the botMsgFunction +variable has been initialized, I will call this "botMsgFunction" function +each time pfnWriteByte(), pfnWriteChar(), pfnWriteShort(), pfnWriteLong(), +pfnWriteAngle(), pfnWriteCoord(), pfnWriteString(), or pfnWriteEntity() is +called. It is up to the state machine code in the bot_client.cpp function +to remember what type of data is being passed into this function (byte, char, +string, angle, etc.). The state machine code will copy the value from the +network message into a local variable and store away anything that is +needed in the bots array (using the botMsgIndex as the index). This can be +used to set a "flag" in the bots structure to indicate that a certain +network message has been sent to the bot. + +For example, this is done in the BotClient_TFC_VGUI() function. When it sees +a VGUIMenu message with a 2 in the byte field, it sets "start_action" to +MSG_TFC_TEAM_SELECT. This indicates to the bot that it needs to use the +FakeClientCommand() to send the "teamselect" command to select a team. The +BotClient_TFC_VGUI() function will set "start_action" to MSG_TFC_CLASS_SELECT +when it sees a 3 in the byte field, indicating to the bot that it needs use +the FakeClientCommand() to select the class. + +You will have to add (or modify) functions in the bot_client.cpp file to match +the types of network messages being sent by the MOD you wish to add a bot to. +There should be enough examples in engine.cpp and bot_client.cpp to figure out +how to intercept and store fields from most types of network messages. + +Okay, let's get on to another area. Let's figure out what weapons are used +in a MOD and how to select them and how to get the bot to fire those weapons. + +The first thing we will need is the Weapon ID for each of the weapons in the +MOD. You saw in one of the network packets a "WeaponList" message. This +contains the weapon name, weapon ID, and other stuff. Let's break one of +these network messages down into it's components. Here's one from Half-Life +deathmatch... + +pfnMessageBegin: edict=1e01e40 dest=1 type=76 +pfnWriteString: weapon_9mmAR +pfnWriteByte: 2 +pfnWriteByte: 250 +pfnWriteByte: 3 +pfnWriteByte: 10 +pfnWriteByte: 2 +pfnWriteByte: 0 +pfnWriteByte: 4 +pfnWriteByte: 0 +pfnMessageEnd: + +The string "weapon_9mmAR" is the weapon name. You will need to use this to +select the weapon when you want to change from one weapon to another. Just +use the weapon name as the command like this... + +FakeClientCommand(pEdict, "weapon_9mmAR", NULL, NULL); + +...and the MOD will switch to that weapon (as long as you are carrying it +and have enough ammo, and are allowed to switch to it). + +The first byte value is the primary ammo index value. Ammo is represented +using an integer for the type of ammo (for example 1 might be 9mm bullets, +2 might be AR grenades, 3 might be handgrenades, etc.). In this case, the +primary ammo type for the 9mmAR is type 2 (we know it's 9mm bullets because +we can see that when we play the game). The next byte is the maximum number +of primary ammo that can be carried by the player at one time. In this case +it's 250 rounds of ammo. The next byte is the secondary ammo type. This is +3 in this case (which is the AR grenades that we see in the game). The next +byte is the maximum number of secondary rounds that the player can carry (in +this case it's 10 rounds). The next byte is the HUD slot where this weapon +will be displayed (slot number 2). Since the bots don't have a HUD they +don't care what the slot is (this is ignored). The next byte is the position +within the slot (how far down in the slot) the weapon will be displayed. In +this case it's 0 which means at the top of the slot. The next byte (the 4) +is the weapon ID. You will need to keep track of this number since some of +the other network messages will only use the weapon ID (not the weapon name) +when weapon messages are sent. The last byte is a flags field which does +not get used by the bot. + +As you receive each one of these WeaponList messages you can store the +weapon name, primary and secondary ammo type, maximum value for the primary +and secondary ammo (if you need them for anything), and the weapon ID. + +For the MOD that are already supported by the HPB bot, each of these will +be handled by the BotClient_Valve_WeaponList() function (in bot_client.cpp). +Each MOD actually has it's own function (BotClient_TFC_WeaponList, +BotClient_CS_WeaponList, BotClient_Gearbox_WeaponList, etc.), but since +they all pass the same network data, they all call the Valve version. This +is true for many of the bot_client.cpp functions. There is a separate one +for each MOD but most of them call the Valve version since they all use the +same data in the network packets (it just makes the code a little smaller). +If the MOD you are adding a bot to has the same network data as one of the +existing functions you can just have it call the Valve version as well. + +Once you know what the weapon ID is for each of the weapons in the MOD, you +can add these to the bot_weapons.h file. These weapon IDs are defined as +constants in this file to make it easier to identify what weapon a certain +weapon ID corresponds to. The weapon IDs are rarely (if ever) changed when +a new version of a MOD is released. If new weapons are added the MOD authors +will usually just pick one of the unused IDs to assign to this new weapon +and not shuffle all the other weapons IDs around, but it is possible that +you might encounter a MOD that does shuffle the IDs around when a new MOD is +released. For this reason you may want to store the weapon IDs in global +variables (using the weapon name string to identify the weapon) so that if +they do change you won't have to modify the bot_weapons.h file later on. + +The weapon IDs are used by the bot to keep track of which weapon it is +currently carrying. You will see in the bot_combat.cpp code where I check +the weapon ID before trying to switch weapons so that I don't attempt to +switch to a weapon that the bot has already previously selected. + +So now you know how to find out the weapon IDs for a MOD and how to get the +bot to switch weapons. The only other thing left when using a weapon is +getting the bot to fire the weapon. There is a primary fire and secondary +fire on weapons (for most MODs). You get the bot to use the primary or +secondary fire by setting one of the bits in the "buttons" argument of +pfnRunPlayerMove(). By setting the IN_ATTACK bit, the bot will use the +primary fire button. By setting the IN_ATTACK2 bit, the bot will use the +secondary fire button. You can set these bits by ORing the IN_ATTACK or +IN_ATTACK2 value with the current pEdict->v.button value like this... + + pEdict->v.button = pEdict->v.button | IN_ATTACK; + +This will set this bit without changing any of the other bits already set +in the "button" field. If you just set pEdict->v.button equal to IN_ATTACK, +you would clear any previously set bits. For example if you wanted to jump +AND shoot you can't do this... + + pEdict->v.button = IN_JUMP; + pEdcit->v.button = IN_ATTACK; + +The second statement would wipe out the value set by the first statement. +You would need to do this instead... + + pEdict->v.button = pEdict->v.button | IN_JUMP; + pEdict->v.button = pEdict->v.button | IN_ATTACK; + +(or use this shorter format...) + + pEdict->v.button |= IN_JUMP; + pEdict->v.button |= IN_ATTACK; + +(it does the same thing.) + +Some weapons need to have the fire button held down while they are being +used (like the egon gun in Half-Life deathmatch). To do this you must set +the IN_ATTACK or IN_ATTACK2 bit on every frame. Which means that you must +keep setting this bit every time the BotThink() function is called. + +Some weapons may require you to charge the weapon up and then fire the +weapon. To do this set the IN_ATTACK or IN_ATTACK2 bit on every frame for +some small amount of time then don't set the IN_ATTACK or IN_ATTACK2 bit +to get the bot to fire the weapon. + +Another topic that comes up frequently is "How do I get the bot to detect + in a level?" + +This can usually be done one of two ways. But they both have advantages +and disadvantages. When an entity is created in a level, you have several +different techniques to "find" that entity. One of the simplest methods +is to search a radius around the bot for entities. This can be done like +this... + +float radius = 100.0; +edict_t *pent = NULL; + +while ((pent = UTIL_FindEntityInSphere( pent, pEdict->v.origin, + radius )) != NULL) +{ + if (strcmp(STRING(pent->v.classname), "entity_name") == 0) + { + // found an "entity_name" entity near the bot... + } +} + +UTIL_FindEntityInSphere() will return a pointer to an entity that is within +"radius" units of the origin (location) of the bot. You pass in NULL for +the pent value on the first call to tell the engine to start the search and +you pass in each returned value on the next call to tell it to return the +next one in the list. The engine will return NULL when no more entities +are within "radius" units of the bot. + +The advantage to this is that it is fairly simple to use. The disadvantage +to this is that it can be very slow if the search radius is very large or +if there are lots of entities near the bot. This will especially slow things +down if you are doing this type of search during every single BotThink() call. +If you are going to use this technique, you may want to limit the searches to +once every 0.1 seconds or so to reduce the load on slower CPUs. + +Another technique for keeping track of entities is to watch for them as they +are spawned (by intercepting the engine messages) and keep track of them in +a global array. For example, here's a handgrende being thrown in Half-Life +deathmatch... + +pfnCreateEntity: 1e11ca4 +pfnPvAllocEntPrivateData: +pfnSetModel: edict=1e11ca4 models/grenade.mdl +pfnSetSize: 1e11ca4 + +We see the entity being created (the edict is 1e11ca4 hexadecimal). Then we +see the model of this edict being set to "models/grenade.mdl" so we know that +it is a hand grenade. The only thing we don't see is a network message when +this entity gets removed from the game. The Half-Life engine actually deletes +entities without the MOD having to call a function to do this. One of the +bits that can be set in the edict's flags field is "FL_KILLME". When this +flag is set it tells the engine that this entity needs to be removed from the +level. So you would have to constantly scan through any entities that you +are keeping track of to see if any of them have the FL_KILLME bit set. This +would indicate that the MOD has "removed" them. This is obviously more +complicated than the previous method of searching for entities, but might +be more CPU efficient in some cases. The more CPU time you can save the more +bots somebody with a slower CPU will be able to create on their machine. + +One of the problems that you may run into is that some events don't have any +network messages at all. With the SDK 2.0 release Valve provided the ability +to create effects on the client. This means that the server may send one +network message to the client when something is created (like a smoke bomb), +but the client may be the one to create the sprites, sounds, and decals. +You may never see a network message telling the client to create a smoke +screen. The client may do this on it's own several seconds after the smoke +grenade is actually thrown. However, one thing that is always true is that +if there is something on the HUD that changes then there will be a network +message that tells the client to display that thing or turn that thing off +or change the sprite used to display that thing. For example in Counter- +Strike, when you enter or leave a buy zone, a message will get sent to the +HUD to tell it to display the buy zone shopping cart or to turn off the buy +zone shopping cart. + +Also don't assume that something will be created or sent to the client in +a way that makes the most sense to you. Sometimes you have to be creative +about how things are detected or intercepted. Sometimes the MOD developers +will use a TextMsg network message to play a sound on the HUD, you might be +looking for a sound message for something and never find it, because it was +never actually sent. + +I hope this document helps you understand how some things are done in the +HPB bot. However, don't try to understand everything at one time. You will +very quickly get overwhelmed by the amount of things that you must know to +get the bot to perform the same tasks as a human player. Take things one +step at a time. Work on one section until you understand how that one little +piece works, then move on to another section. Remember that the HPB bot +wasn't created in a single day (or week or month) and any bot that you are +working on won't be created in a single day either. Take things slow, try +to learn as you go and HAVE FUN!!! + +botman \ No newline at end of file diff --git a/bot.cpp b/bot.cpp new file mode 100644 index 0000000..0ebee99 --- /dev/null +++ b/bot.cpp @@ -0,0 +1,2711 @@ +// +// HPB bot - botman's High Ping Bastard bot +// +// (http://planethalflife.com/botman/) +// +// bot.cpp +// + +#ifndef _WIN32 +#include +#endif + +#include +#include +#include +#include + +#include "bot.h" +#include "bot_func.h" +#include "waypoint.h" +#include "bot_weapons.h" + +#include +#include + + +extern edict_t *clients[32]; +extern int mod_id; +extern WAYPOINT waypoints[MAX_WAYPOINTS]; +extern int num_waypoints; // number of waypoints currently in use +extern int default_bot_skill; +extern int bot_strafe_percent; +extern int bot_chat_percent; +extern int bot_taunt_percent; +extern int bot_whine_percent; +extern int bot_logo_percent; +extern int bot_chat_tag_percent; +extern int bot_chat_drop_percent; +extern int bot_chat_swap_percent; +extern int bot_chat_lower_percent; +extern bool b_random_color; +extern edict_t *pent_info_ctfdetect; +extern int bot_reaction_time; +extern int IsDedicatedServer; +extern int holywars_gamemode; + +extern int max_team_players[4]; +extern int team_class_limits[4]; +extern int team_allies[4]; +extern int max_teams; +extern bot_chat_t bot_chat[MAX_BOT_CHAT]; +extern bot_chat_t bot_whine[MAX_BOT_CHAT]; +extern int bot_chat_count; +extern int bot_whine_count; +extern int recent_bot_chat[]; +extern int recent_bot_whine[]; +extern bool checked_teamplay; +extern bool is_team_play; + +extern int number_skins; +extern skin_t bot_skins[MAX_SKINS]; + + +static FILE *fp; + + +#define PLAYER_SEARCH_RADIUS 40.0 +#define FLF_PLAYER_SEARCH_RADIUS 60.0 + + +#define MAX_BOT_NAMES 100 + +int number_names = 0; +char bot_names[MAX_BOT_NAMES][BOT_NAME_LEN+1]; + +#define MAX_BOT_LOGOS 100 +int num_logos = 0; +char bot_logos[MAX_BOT_LOGOS][16]; + +bot_t bots[32]; // max of 32 bots in a game +bool b_observer_mode = FALSE; +bool b_botdontshoot = FALSE; + + +// how often (out of 1000 times) the bot will pause, based on bot skill +float pause_frequency[5] = {4, 7, 10, 15, 20}; + +float pause_time[5][2] = { + {0.2, 0.5}, {0.5, 1.0}, {0.7, 1.3}, {1.0, 1.7}, {1.2, 2.0}}; + + + +void BotSpawnInit( bot_t *pBot ) +{ + pBot->v_prev_origin = Vector(9999.0, 9999.0, 9999.0); + pBot->f_speed_check_time = gpGlobals->time; + + pBot->waypoint_origin = Vector(0, 0, 0); + pBot->f_waypoint_time = 0.0; + pBot->curr_waypoint_index = -1; + pBot->prev_waypoint_index[0] = -1; + pBot->prev_waypoint_index[1] = -1; + pBot->prev_waypoint_index[2] = -1; + pBot->prev_waypoint_index[3] = -1; + pBot->prev_waypoint_index[4] = -1; + + pBot->f_random_waypoint_time = gpGlobals->time; + pBot->waypoint_goal = -1; + pBot->f_waypoint_goal_time = 0.0; + pBot->waypoint_near_flag = FALSE; + pBot->waypoint_flag_origin = Vector(0, 0, 0); + pBot->prev_waypoint_distance = 0.0; + + pBot->weapon_points[0] = 0; + pBot->weapon_points[1] = 0; + pBot->weapon_points[2] = 0; + pBot->weapon_points[3] = 0; + pBot->weapon_points[4] = 0; + pBot->weapon_points[5] = 0; + + pBot->blinded_time = 0.0; + + pBot->f_max_speed = CVAR_GET_FLOAT("sv_maxspeed"); + + pBot->f_prev_speed = 0.0; // fake "paused" since bot is NOT stuck + + pBot->f_find_item = 0.0; + + pBot->ladder_dir = LADDER_UNKNOWN; + pBot->f_start_use_ladder_time = 0.0; + pBot->f_end_use_ladder_time = 0.0; + pBot->waypoint_top_of_ladder = FALSE; + + pBot->f_wall_check_time = 0.0; + pBot->f_wall_on_right = 0.0; + pBot->f_wall_on_left = 0.0; + pBot->f_dont_avoid_wall_time = 0.0; + pBot->f_look_for_waypoint_time = 0.0; + pBot->f_jump_time = 0.0; + pBot->f_drop_check_time = 0.0; + + // pick a wander direction (50% of the time to the left, 50% to the right) + if (RANDOM_LONG(1, 100) <= 50) + pBot->wander_dir = WANDER_LEFT; + else + pBot->wander_dir = WANDER_RIGHT; + + pBot->f_exit_water_time = 0.0; + + pBot->pBotEnemy = NULL; + pBot->f_bot_see_enemy_time = gpGlobals->time; + pBot->f_bot_find_enemy_time = gpGlobals->time; + pBot->f_aim_tracking_time = 0.0; + pBot->f_aim_x_angle_delta = 0.0; + pBot->f_aim_y_angle_delta = 0.0; + + pBot->pBotUser = NULL; + pBot->f_bot_use_time = 0.0; + pBot->b_bot_say = FALSE; + pBot->f_bot_say = 0.0; + pBot->bot_say_msg[0] = 0; + pBot->f_bot_chat_time = gpGlobals->time; + pBot->enemy_attack_count = 0; + pBot->f_duck_time = 0.0; + + pBot->f_sniper_aim_time = 0.0; + + pBot->f_shoot_time = gpGlobals->time; + pBot->f_primary_charging = -1.0; + pBot->f_secondary_charging = -1.0; + pBot->charging_weapon_id = 0; + pBot->f_gren_throw_time = -1.0; + pBot->f_gren_check_time = 0.0; + pBot->b_grenade_primed = FALSE; + pBot->grenade_type = 0; + pBot->f_grenade_search_time = 0.0; + pBot->f_grenade_found_time = 0.0; + + pBot->f_medic_check_time = 0.0; + pBot->f_medic_pause_time = 0.0; + pBot->f_medic_yell_time = 0.0; + + pBot->f_pause_time = 0.0; + pBot->f_sound_update_time = 0.0; + pBot->bot_has_flag = FALSE; + + pBot->b_see_tripmine = FALSE; + pBot->b_shoot_tripmine = FALSE; + pBot->v_tripmine = Vector(0,0,0); + + pBot->b_use_health_station = FALSE; + pBot->f_use_health_time = 0.0; + pBot->b_use_HEV_station = FALSE; + pBot->f_use_HEV_time = 0.0; + + pBot->b_use_button = FALSE; + pBot->f_use_button_time = 0; + pBot->b_lift_moving = FALSE; + + pBot->b_use_capture = FALSE; + pBot->f_use_capture_time = 0.0; + pBot->pCaptureEdict = NULL; + + pBot->b_spray_logo = FALSE; + + pBot->f_engineer_build_time = 0.0; + pBot->b_build_sentrygun = FALSE; + pBot->b_build_dispenser = FALSE; + pBot->f_other_sentry_time = 0.0; + pBot->b_upgrade_sentry = FALSE; + + pBot->f_medic_check_health_time = 0.0; + + pBot->f_reaction_target_time = 0.0; + + memset(&(pBot->current_weapon), 0, sizeof(pBot->current_weapon)); + memset(&(pBot->m_rgAmmo), 0, sizeof(pBot->m_rgAmmo)); +} + + +void BotNameInit( void ) +{ + FILE *bot_name_fp; + char bot_name_filename[256]; + int str_index; + char name_buffer[80]; + int length, index; + + UTIL_BuildFileName(bot_name_filename, "HPB_bot_names.txt", NULL); + + bot_name_fp = fopen(bot_name_filename, "r"); + + if (bot_name_fp != NULL) + { + while ((number_names < MAX_BOT_NAMES) && + (fgets(name_buffer, 80, bot_name_fp) != NULL)) + { + length = strlen(name_buffer); + + if (name_buffer[length-1] == '\n') + { + name_buffer[length-1] = 0; // remove '\n' + length--; + } + + str_index = 0; + while (str_index < length) + { + if ((name_buffer[str_index] < ' ') || (name_buffer[str_index] > '~') || + (name_buffer[str_index] == '"')) + for (index=str_index; index < length; index++) + name_buffer[index] = name_buffer[index+1]; + + str_index++; + } + + if (name_buffer[0] != 0) + { + strncpy(bot_names[number_names], name_buffer, BOT_NAME_LEN); + + number_names++; + } + } + + fclose(bot_name_fp); + } +} + + +void BotPickName( char *name_buffer ) +{ + int name_index, index; + bool used; + edict_t *pPlayer; + int attempts = 0; + + name_index = RANDOM_LONG(1, number_names) - 1; // zero based + + // check make sure this name isn't used + used = TRUE; + + while (used) + { + used = FALSE; + + for (index = 1; index <= gpGlobals->maxClients; index++) + { + pPlayer = INDEXENT(index); + + if (pPlayer && !pPlayer->free) + { + if (strcmp(bot_names[name_index], STRING(pPlayer->v.netname)) == 0) + { + used = TRUE; + break; + } + } + } + + if (used) + { + name_index++; + + if (name_index == number_names) + name_index = 0; + + attempts++; + + if (attempts == number_names) + used = FALSE; // break out of loop even if already used + } + } + + strcpy(name_buffer, bot_names[name_index]); +} + + +void BotCreate( edict_t *pPlayer, const char *arg1, const char *arg2, + const char *arg3, const char *arg4, const char *arg5 ) +{ + edict_t *BotEnt; + bot_t *pBot; + char c_skin[BOT_SKIN_LEN+1]; + char c_name[BOT_NAME_LEN+1]; + int skill; + int index; + int i, j, length; + bool found = FALSE; + int top_color, bottom_color; + char c_topcolor[4], c_bottomcolor[4]; + + top_color = -1; + bottom_color = -1; + + if ((mod_id == VALVE_DLL) || + ((mod_id == GEARBOX_DLL) && (pent_info_ctfdetect == NULL)) || + (mod_id == HOLYWARS_DLL) || (mod_id == DMC_DLL)) + { + int max_skin_index; + + max_skin_index = number_skins; + + if ((arg1 == NULL) || (*arg1 == 0)) + { + index = RANDOM_LONG(0, number_skins-1); + + // check if this skin has already been used... + while (bot_skins[index].skin_used == TRUE) + { + index++; + + if (index == max_skin_index) + index = 0; + } + + bot_skins[index].skin_used = TRUE; + + // check if all skins are now used... + for (i = 0; i < max_skin_index; i++) + { + if (bot_skins[i].skin_used == FALSE) + break; + } + + // if all skins are used, reset used to FALSE for next selection + if (i == max_skin_index) + { + for (i = 0; i < max_skin_index; i++) + bot_skins[i].skin_used = FALSE; + } + + strcpy(c_skin, bot_skins[index].model_name); + } + else + { + strncpy( c_skin, arg1, BOT_SKIN_LEN-1 ); + c_skin[BOT_SKIN_LEN] = 0; // make sure c_skin is null terminated + } + + for (i = 0; c_skin[i] != 0; i++) + c_skin[i] = tolower( c_skin[i] ); // convert to all lowercase + + index = 0; + + while ((!found) && (index < max_skin_index)) + { + if (strcmp(c_skin, bot_skins[index].model_name) == 0) + found = TRUE; + else + index++; + } + + if (found == TRUE) + { + if ((arg2 != NULL) && (*arg2 != 0)) + { + strncpy( c_name, arg2, BOT_SKIN_LEN-1 ); + c_name[BOT_SKIN_LEN] = 0; // make sure c_name is null terminated + } + else + { + if (number_names > 0) + BotPickName( c_name ); + else + strcpy(c_name, bot_skins[index].bot_name); + } + } + else + { + char dir_name[32]; + char filename[128]; + + struct stat stat_str; + + GetGameDir(dir_name); + + sprintf(filename, "%s/models/player/%s", dir_name, c_skin); + + if (stat(filename, &stat_str) != 0) + { + sprintf(filename, "valve/models/player/%s", c_skin); + + if (stat(filename, &stat_str) != 0) + { + char err_msg[80]; + + sprintf( err_msg, "model \"%s\" is unknown.\n", c_skin ); + if (pPlayer) + ClientPrint(pPlayer, HUD_PRINTNOTIFY, err_msg ); + if (IsDedicatedServer) + printf(err_msg); + + if (pPlayer) + ClientPrint(pPlayer, HUD_PRINTNOTIFY, + "use barney, gina, gman, gordon, helmet, hgrunt,\n"); + if (IsDedicatedServer) + printf("use barney, gina, gman, gordon, helmet, hgrunt,\n"); + if (pPlayer) + ClientPrint(pPlayer, HUD_PRINTNOTIFY, + " recon, robo, scientist, or zombie\n"); + if (IsDedicatedServer) + printf(" recon, robo, scientist, or zombie\n"); + return; + } + } + + if ((arg2 != NULL) && (*arg2 != 0)) + { + strncpy( c_name, arg2, BOT_NAME_LEN-1 ); + c_name[BOT_NAME_LEN] = 0; // make sure c_name is null terminated + } + else + { + if (number_names > 0) + BotPickName( c_name ); + else + { + // copy the name of the model to the bot's name... + strncpy( c_name, arg1, BOT_NAME_LEN-1 ); + c_name[BOT_NAME_LEN] = 0; // make sure c_skin is null terminated + } + } + } + + skill = 0; + + if ((arg3 != NULL) && (*arg3 != 0)) + skill = atoi(arg3); + + if ((skill < 1) || (skill > 5)) + skill = default_bot_skill; + + if ((arg4 != NULL) && (*arg4 != 0)) + top_color = atoi(arg4); + + if ((top_color < 0) || (top_color > 255)) + top_color = -1; + else + sprintf(c_topcolor, "%d", top_color); + + if ((arg5 != NULL) && (*arg5 != 0)) + bottom_color = atoi(arg5); + + if ((bottom_color < 0) || (bottom_color > 255)) + bottom_color = -1; + else + sprintf(c_bottomcolor, "%d", bottom_color); + + if ((top_color == -1) && (bottom_color == -1) && (b_random_color)) + { + top_color = RANDOM_LONG(0, 255); + sprintf(c_topcolor, "%d", top_color); + + bottom_color = RANDOM_LONG(0, 255); + sprintf(c_bottomcolor, "%d", bottom_color); + } + } + else + { + if ((arg3 != NULL) && (*arg3 != 0)) + { + strncpy( c_name, arg3, BOT_NAME_LEN-1 ); + c_name[BOT_NAME_LEN] = 0; // make sure c_name is null terminated + } + else + { + if (number_names > 0) + BotPickName( c_name ); + else + strcpy(c_name, "Bot"); + } + + skill = 0; + + if ((arg4 != NULL) && (*arg4 != 0)) + skill = atoi(arg4); + + if ((skill < 1) || (skill > 5)) + skill = default_bot_skill; + } + + length = strlen(c_name); + + // remove any illegal characters from name... + for (i = 0; i < length; i++) + { + if ((c_name[i] <= ' ') || (c_name[i] > '~') || + (c_name[i] == '"')) + { + for (j = i; j < length; j++) // shuffle chars left (and null) + c_name[j] = c_name[j+1]; + length--; + } + } + + BotEnt = (*g_engfuncs.pfnCreateFakeClient)( c_name ); + + if (FNullEnt( BotEnt )) + { + if (pPlayer) + ClientPrint( pPlayer, HUD_PRINTNOTIFY, "Max. Players reached. Can't create HPB bot!\n"); + } + else + { + char ptr[128]; // allocate space for message from ClientConnect + char *infobuffer; + int clientIndex; + int index; + + if (IsDedicatedServer) + printf("Creating HPB bot...\n"); + else if (pPlayer) + ClientPrint( pPlayer, HUD_PRINTNOTIFY, "Creating HPB bot...\n"); + + index = 0; + while ((bots[index].is_used) && (index < 32)) + index++; + + if (index == 32) + { + ClientPrint( pPlayer, HUD_PRINTNOTIFY, "Can't create HPB bot!\n"); + return; + } + + // create the player entity by calling MOD's player function + // (from LINK_ENTITY_TO_CLASS for player object) + + CALL_GAME_ENTITY (PLID, "player", VARS(BotEnt)); + + infobuffer = GET_INFOKEYBUFFER( BotEnt ); + clientIndex = ENTINDEX( BotEnt ); + + if (!checked_teamplay) // check for team play... + BotCheckTeamplay(); + + // is this a MOD that supports model colors AND it's not teamplay? + if (((mod_id == VALVE_DLL) || (mod_id == DMC_DLL) || + (mod_id == GEARBOX_DLL) || (mod_id == HOLYWARS_DLL)) && + (is_team_play == FALSE)) + { + SET_CLIENT_KEYVALUE( clientIndex, infobuffer, "model", c_skin ); + + if (top_color != -1) + SET_CLIENT_KEYVALUE( clientIndex, infobuffer, "topcolor", c_topcolor ); + + if (bottom_color != -1) + SET_CLIENT_KEYVALUE( clientIndex, infobuffer, "bottomcolor", c_bottomcolor ); + } + else // other mods + SET_CLIENT_KEYVALUE( clientIndex, infobuffer, "model", "" ); // bugfix, thanks Whistler + + if (mod_id == CSTRIKE_DLL) + { + SET_CLIENT_KEYVALUE( clientIndex, infobuffer, "rate", "3500.000000"); + SET_CLIENT_KEYVALUE( clientIndex, infobuffer, "cl_updaterate", "20"); + SET_CLIENT_KEYVALUE( clientIndex, infobuffer, "cl_lw", "1"); + SET_CLIENT_KEYVALUE( clientIndex, infobuffer, "cl_lc", "1"); + SET_CLIENT_KEYVALUE( clientIndex, infobuffer, "tracker", "0"); + SET_CLIENT_KEYVALUE( clientIndex, infobuffer, "cl_dlmax", "128"); + SET_CLIENT_KEYVALUE( clientIndex, infobuffer, "lefthand", "1"); + SET_CLIENT_KEYVALUE( clientIndex, infobuffer, "friends", "0"); + SET_CLIENT_KEYVALUE( clientIndex, infobuffer, "dm", "0"); + SET_CLIENT_KEYVALUE( clientIndex, infobuffer, "ah", "1"); + } + + MDLL_ClientConnect( BotEnt, c_name, "127.0.0.1", ptr ); + + // HPB_bot metamod fix - START + + // we have to do the ClientPutInServer() hook's job ourselves since calling the MDLL_ + // function calls directly the gamedll one, and not ours. You are allowed to call this + // an "awful hack". + + while ((i < 32) && (clients[i] != NULL)) + i++; + + if (i < 32) + clients[i] = BotEnt; // store this clients edict in the clients array + + // HPB_bot metamod fix - END + + // Pieter van Dijk - use instead of DispatchSpawn() - Hip Hip Hurray! + MDLL_ClientPutInServer( BotEnt ); + + BotEnt->v.flags |= FL_THIRDPARTYBOT; + + // initialize all the variables for this bot... + + pBot = &bots[index]; + + pBot->is_used = TRUE; + pBot->respawn_state = RESPAWN_IDLE; + pBot->f_create_time = gpGlobals->time; + pBot->name[0] = 0; // name not set by server yet + pBot->bot_money = 0; + pBot->sentrygun_waypoint = -1; + pBot->dispenser_waypoint = -1; + pBot->sentrygun_level = 0; + pBot->dispenser_built = 0; + + strcpy(pBot->skin, c_skin); + + pBot->top_color = top_color; + pBot->bottom_color = bottom_color; + + pBot->pEdict = BotEnt; + + BotPickLogo(pBot); + + pBot->not_started = 1; // hasn't joined game yet + + if (mod_id == TFC_DLL) + pBot->start_action = MSG_TFC_IDLE; + else if (mod_id == CSTRIKE_DLL) + pBot->start_action = MSG_CS_IDLE; + else if ((mod_id == GEARBOX_DLL) && (pent_info_ctfdetect != NULL)) + pBot->start_action = MSG_OPFOR_IDLE; + else if (mod_id == FRONTLINE_DLL) + pBot->start_action = MSG_FLF_IDLE; + else + pBot->start_action = 0; // not needed for non-team MODs + + + BotSpawnInit(pBot); + + pBot->need_to_initialize = FALSE; // don't need to initialize yet + + BotEnt->v.idealpitch = BotEnt->v.v_angle.x; + BotEnt->v.ideal_yaw = BotEnt->v.v_angle.y; + + // these should REALLY be MOD dependant... + BotEnt->v.pitch_speed = 270; // slightly faster than HLDM of 225 + BotEnt->v.yaw_speed = 250; // slightly faster than HLDM of 210 + + pBot->warmup = 0; // for Front Line Force + pBot->idle_angle = 0.0; + pBot->idle_angle_time = 0.0; + pBot->round_end = 0; + pBot->defender = 0; + + pBot->strafe_percent = bot_strafe_percent; + pBot->chat_percent = bot_chat_percent; + pBot->taunt_percent = bot_taunt_percent; + pBot->whine_percent = bot_whine_percent; + pBot->chat_tag_percent = bot_chat_tag_percent; + pBot->chat_drop_percent = bot_chat_drop_percent; + pBot->chat_swap_percent = bot_chat_swap_percent; + pBot->chat_lower_percent = bot_chat_lower_percent; + pBot->logo_percent = bot_logo_percent; + pBot->f_strafe_direction = 0.0; // not strafing + pBot->f_strafe_time = 0.0; + pBot->reaction_time = bot_reaction_time; + + pBot->f_start_vote_time = gpGlobals->time + RANDOM_LONG(120, 600); + pBot->vote_in_progress = FALSE; + pBot->f_vote_time = 0.0; + + pBot->bot_skill = skill - 1; // 0 based for array indexes + + pBot->bot_team = -1; + pBot->bot_class = -1; + + if ((mod_id == TFC_DLL) || (mod_id == CSTRIKE_DLL) || + ((mod_id == GEARBOX_DLL) && (pent_info_ctfdetect != NULL)) || + (mod_id == FRONTLINE_DLL)) + { + if ((arg1 != NULL) && (arg1[0] != 0)) + { + pBot->bot_team = atoi(arg1); + + if ((arg2 != NULL) && (arg2[0] != 0)) + { + pBot->bot_class = atoi(arg2); + } + } + } + } +} + + +int BotInFieldOfView(bot_t *pBot, Vector dest) +{ + // find angles from source to destination... + Vector entity_angles = UTIL_VecToAngles( dest ); + + // make yaw angle 0 to 360 degrees if negative... + if (entity_angles.y < 0) + entity_angles.y += 360; + + // get bot's current view angle... + float view_angle = pBot->pEdict->v.v_angle.y; + + // make view angle 0 to 360 degrees if negative... + if (view_angle < 0) + view_angle += 360; + + // return the absolute value of angle to destination entity + // zero degrees means straight ahead, 45 degrees to the left or + // 45 degrees to the right is the limit of the normal view angle + + // rsm - START angle bug fix + int angle = abs((int)view_angle - (int)entity_angles.y); + + if (angle > 180) + angle = 360 - angle; + + return angle; + // rsm - END +} + + +bool BotEntityIsVisible( bot_t *pBot, Vector dest ) +{ + TraceResult tr; + + // trace a line from bot's eyes to destination... + UTIL_TraceLine( pBot->pEdict->v.origin + pBot->pEdict->v.view_ofs, + dest, ignore_monsters, + pBot->pEdict->v.pContainingEntity, &tr ); + + // check if line of sight to object is not blocked (i.e. visible) + if (tr.flFraction >= 1.0) + return TRUE; + else + return FALSE; +} + +void BotLogoInit(void) +{ + FILE *bot_logo_fp; + char bot_logo_filename[256]; + char logo_buffer[80]; + int length, index; + +#ifndef __linux__ + HANDLE h_logo; + char dir_name[32]; + char decal_filename[256]; + DWORD dwDummy; + + struct stat stat_str; + wadinfo_t wad_header; + lumpinfo_t lump_info; + + GetGameDir(dir_name); + + sprintf(decal_filename, "%s/decals.wad", dir_name); + + if (stat(decal_filename, &stat_str) != 0) + { + strcpy(decal_filename, "valve/decals.wad"); + + if (stat(decal_filename, &stat_str) != 0) + return; // file not found + } + + h_logo = CreateFile(decal_filename, GENERIC_READ, FILE_SHARE_READ, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); + + if (h_logo == INVALID_HANDLE_VALUE) + return; // can't open file + + if (!ReadFile(h_logo, &wad_header, sizeof(wadinfo_t), &dwDummy, NULL)) + { + CloseHandle(h_logo); // can't read wad header + return; + } + + if (SetFilePointer(h_logo, wad_header.infotableofs, NULL, FILE_BEGIN) == 0) + { + CloseHandle(h_logo); + return; // can't seek to lump info table + } + + for (index=0; index < wad_header.numlumps; index++) + { + if (!ReadFile(h_logo, &lump_info, sizeof(lumpinfo_t), &dwDummy, NULL)) + { + CloseHandle(h_logo); // can't read lump info + return; + } + + if (strncmp(lump_info.name, "{__", 3) == 0) + { + strcpy(bot_logos[num_logos], lump_info.name); + num_logos++; + } + + if (strncmp(lump_info.name, "__", 2) == 0) + { + strcpy(bot_logos[num_logos], lump_info.name); + num_logos++; + } + } + + CloseHandle(h_logo); + +#endif + + UTIL_BuildFileName(bot_logo_filename, "HPB_bot_logo.cfg", NULL); + + bot_logo_fp = fopen(bot_logo_filename, "r"); + + if (bot_logo_fp != NULL) + { + while ((num_logos < MAX_BOT_LOGOS) && + (fgets(logo_buffer, 80, bot_logo_fp) != NULL)) + { + length = strlen(logo_buffer); + + if (logo_buffer[length-1] == '\n') + { + logo_buffer[length-1] = 0; // remove '\n' + length--; + } + + if (logo_buffer[0] != 0) + { + strncpy(bot_logos[num_logos], logo_buffer, 16); + + num_logos++; + } + } + + fclose(bot_logo_fp); + } +} + + +void BotPickLogo(bot_t *pBot) +{ + bool used; + int logo_index; + int check_count; + int index; + + pBot->logo_name[0] = 0; + + if (num_logos == 0) + return; + + logo_index = RANDOM_LONG(1, num_logos) - 1; // zero based + + // check make sure this name isn't used + used = TRUE; + check_count = 0; + + while ((used) && (check_count < MAX_BOT_LOGOS)) + { + used = FALSE; + + for (index=0; index < 32; index++) + { + if (bots[index].is_used) + { + if (strcmp(bots[index].logo_name, bot_logos[logo_index]) == 0) + { + used = TRUE; + } + } + } + + if (used) + logo_index++; + + if (logo_index == MAX_BOT_LOGOS) + logo_index = 0; + + check_count++; + } + + strcpy(pBot->logo_name, bot_logos[logo_index]); +} + + +void BotSprayLogo(edict_t *pEntity, char *logo_name) +{ + int index; + TraceResult pTrace; + Vector v_src, v_dest; + MAKE_VECTORS(pEntity->v.v_angle); + v_src = pEntity->v.origin + pEntity->v.view_ofs; + v_dest = v_src + gpGlobals->v_forward * 80; + UTIL_TraceLine( v_src, v_dest, ignore_monsters, pEntity->v.pContainingEntity, &pTrace ); + + index = DECAL_INDEX(logo_name); + + if (index < 0) + return; + + if ((pTrace.pHit) && (pTrace.flFraction < 1.0)) + { + if (pTrace.pHit->v.solid != SOLID_BSP) + return; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + + if (index > 255) + { + WRITE_BYTE( TE_WORLDDECALHIGH); + index -= 256; + } + else + WRITE_BYTE( TE_WORLDDECAL ); + + WRITE_COORD( pTrace.vecEndPos.x ); + WRITE_COORD( pTrace.vecEndPos.y ); + WRITE_COORD( pTrace.vecEndPos.z ); + WRITE_BYTE( index ); + + MESSAGE_END(); + + EMIT_SOUND_DYN2(pEntity, CHAN_VOICE, "player/sprayer.wav", 1.0, ATTN_NORM, 0, 100); + } +} + +void BotFindItem( bot_t *pBot ) +{ + edict_t *pent = NULL; + edict_t *pPickupEntity = NULL; + Vector pickup_origin; + Vector entity_origin; + float radius = 500; + bool can_pickup; + float min_distance; + char item_name[40]; + TraceResult tr; + Vector vecStart; + Vector vecEnd; + int angle_to_entity; + edict_t *pEdict = pBot->pEdict; + + pBot->pBotPickupItem = NULL; + + // use a MUCH smaller search radius when waypoints are available + if ((num_waypoints > 0) && (pBot->curr_waypoint_index != -1)) + radius = 100.0; + else + radius = 500.0; + + min_distance = radius + 1.0; + + while ((pent = UTIL_FindEntityInSphere( pent, pEdict->v.origin, radius )) != NULL) + { + can_pickup = FALSE; // assume can't use it until known otherwise + + strcpy(item_name, STRING(pent->v.classname)); + + // see if this is a "func_" type of entity (func_button, etc.)... + if (strncmp("func_", item_name, 5) == 0) + { + // BModels have 0,0,0 for origin so must use VecBModelOrigin... + entity_origin = VecBModelOrigin(pent); + + vecStart = pEdict->v.origin + pEdict->v.view_ofs; + vecEnd = entity_origin; + + angle_to_entity = BotInFieldOfView( pBot, vecEnd - vecStart ); + + // check if entity is outside field of view (+/- 45 degrees) + if (angle_to_entity > 45) + continue; // skip this item if bot can't "see" it + + // check if entity is a ladder (ladders are a special case) + // DON'T search for ladders if there are waypoints in this level... + if ((strcmp("func_ladder", item_name) == 0) && (num_waypoints == 0)) + { + // force ladder origin to same z coordinate as bot since + // the VecBModelOrigin is the center of the ladder. For + // LONG ladders, the center MAY be hundreds of units above + // the bot. Fake an origin at the same level as the bot... + + entity_origin.z = pEdict->v.origin.z; + vecEnd = entity_origin; + + // trace a line from bot's eyes to func_ladder entity... + UTIL_TraceLine( vecStart, vecEnd, dont_ignore_monsters, + pEdict->v.pContainingEntity, &tr); + + // check if traced all the way up to the entity (didn't hit wall) + if (tr.flFraction >= 1.0) + { + // find distance to item for later use... + float distance = (vecEnd - vecStart).Length( ); + + // use the ladder about 100% of the time, if haven't + // used a ladder in at least 5 seconds... + if ((RANDOM_LONG(1, 100) <= 100) && + ((pBot->f_end_use_ladder_time + 5.0) < gpGlobals->time)) + { + // if close to ladder... + if (distance < 100) + { + // don't avoid walls for a while + pBot->f_dont_avoid_wall_time = gpGlobals->time + 5.0; + } + + can_pickup = TRUE; + } + } + } + else + { + // trace a line from bot's eyes to func_ entity... + UTIL_TraceLine( vecStart, vecEnd, dont_ignore_monsters, + pEdict->v.pContainingEntity, &tr); + + // check if traced all the way up to the entity (didn't hit wall) + if (strcmp(item_name, STRING(tr.pHit->v.classname)) == 0) + { + // find distance to item for later use... + float distance = (vecEnd - vecStart).Length( ); + + // check if entity is wall mounted health charger... + if (strcmp("func_healthcharger", item_name) == 0) + { + // check if the bot can use this item and + // check if the recharger is ready to use (has power left)... + if ((pEdict->v.health < 100) && (pent->v.frame == 0)) + { + // check if flag not set... + if (!pBot->b_use_health_station) + { + // check if close enough and facing it directly... + if ((distance < PLAYER_SEARCH_RADIUS) && + (angle_to_entity <= 10)) + { + pBot->b_use_health_station = TRUE; + pBot->f_use_health_time = gpGlobals->time; + } + + // if close to health station... + if (distance < 100) + { + // don't avoid walls for a while + pBot->f_dont_avoid_wall_time = gpGlobals->time + 5.0; + } + + can_pickup = TRUE; + } + } + else + { + // don't need or can't use this item... + pBot->b_use_health_station = FALSE; + } + } + + // check if entity is wall mounted HEV charger... + else if (strcmp("func_recharge", item_name) == 0) + { + // check if the bot can use this item and + // check if the recharger is ready to use (has power left)... + if ((pEdict->v.armorvalue < VALVE_MAX_NORMAL_BATTERY) && + (pent->v.frame == 0)) + { + // check if flag not set and facing it... + if (!pBot->b_use_HEV_station) + { + // check if close enough and facing it directly... + if ((distance < PLAYER_SEARCH_RADIUS) && + (angle_to_entity <= 10)) + { + pBot->b_use_HEV_station = TRUE; + pBot->f_use_HEV_time = gpGlobals->time; + } + + // if close to HEV recharger... + if (distance < 100) + { + // don't avoid walls for a while + pBot->f_dont_avoid_wall_time = gpGlobals->time + 5.0; + } + + can_pickup = TRUE; + } + } + else + { + // don't need or can't use this item... + pBot->b_use_HEV_station = FALSE; + } + } + + // check if entity is a button... + else if (strcmp("func_button", item_name) == 0) + { + // use the button about 100% of the time, if haven't + // used a button in at least 5 seconds... + if ((RANDOM_LONG(1, 100) <= 100) && + ((pBot->f_use_button_time + 5) < gpGlobals->time)) + { + // check if flag not set and facing it... + if (!pBot->b_use_button) + { + // check if close enough and facing it directly... + if ((distance < PLAYER_SEARCH_RADIUS) && + (angle_to_entity <= 10)) + { + pBot->b_use_button = TRUE; + pBot->b_lift_moving = FALSE; + pBot->f_use_button_time = gpGlobals->time; + } + + // if close to button... + if (distance < 100) + { + // don't avoid walls for a while + pBot->f_dont_avoid_wall_time = gpGlobals->time + 5.0; + } + + can_pickup = TRUE; + } + } + else + { + // don't need or can't use this item... + pBot->b_use_button = FALSE; + } + } + } + } + } + else // everything else... + { + entity_origin = pent->v.origin; + + vecStart = pEdict->v.origin + pEdict->v.view_ofs; + vecEnd = entity_origin; + + // find angles from bot origin to entity... + angle_to_entity = BotInFieldOfView( pBot, vecEnd - vecStart ); + + // check if entity is outside field of view (+/- 45 degrees) + if (angle_to_entity > 45) + continue; // skip this item if bot can't "see" it + + // check if line of sight to object is not blocked (i.e. visible) + if (BotEntityIsVisible( pBot, vecEnd )) + { + // check if entity is a weapon... + if (strncmp("weapon_", item_name, 7) == 0) + { + if (pent->v.effects & EF_NODRAW) + { + // someone owns this weapon or it hasn't respawned yet + continue; + } + + can_pickup = TRUE; + } + + // check if entity is ammo... + else if (strncmp("ammo_", item_name, 5) == 0) + { + // check if the item is not visible (i.e. has not respawned) + if (pent->v.effects & EF_NODRAW) + continue; + + can_pickup = TRUE; + } + + // check if entity is a battery... + else if (strcmp("item_battery", item_name) == 0) + { + // check if the item is not visible (i.e. has not respawned) + if (pent->v.effects & EF_NODRAW) + continue; + + // check if the bot can use this item... + if (pEdict->v.armorvalue < VALVE_MAX_NORMAL_BATTERY) + { + can_pickup = TRUE; + } + } + + // check if entity is a healthkit... + else if (strcmp("item_healthkit", item_name) == 0) + { + // check if the item is not visible (i.e. has not respawned) + if (pent->v.effects & EF_NODRAW) + continue; + + // check if the bot can use this item... + if (pEdict->v.health < 100) + { + can_pickup = TRUE; + } + } + + // check if entity is a packed up weapons box... + else if (strcmp("weaponbox", item_name) == 0) + { + can_pickup = TRUE; + } + + // check if entity is the spot from RPG laser + else if (strcmp("laser_spot", item_name) == 0) + { + } + + // check if entity is an armed tripmine + else if (strcmp("monster_tripmine", item_name) == 0) + { + float distance = (pent->v.origin - pEdict->v.origin).Length( ); + + if (pBot->b_see_tripmine) + { + // see if this tripmine is closer to bot... + if (distance < (pBot->v_tripmine - pEdict->v.origin).Length()) + { + pBot->v_tripmine = pent->v.origin; + pBot->b_shoot_tripmine = FALSE; + + // see if bot is far enough to shoot the tripmine... + if (distance >= 375) + { + pBot->b_shoot_tripmine = TRUE; + } + } + } + else + { + pBot->b_see_tripmine = TRUE; + pBot->v_tripmine = pent->v.origin; + pBot->b_shoot_tripmine = FALSE; + + // see if bot is far enough to shoot the tripmine... + if (distance >= 375) // 375 is damage radius + { + pBot->b_shoot_tripmine = TRUE; + } + } + } + + // check if entity is an armed satchel charge + else if (strcmp("monster_satchel", item_name) == 0) + { + } + + // check if entity is a snark (squeak grenade) + else if (strcmp("monster_snark", item_name) == 0) + { + } + + else if ((mod_id == FRONTLINE_DLL) && (!pBot->defender) && + (strcmp("capture_point", item_name) == 0)) + { + int team = UTIL_GetTeam(pEdict); // skin and team must match + + // check if flag not set and point not captured... + if ((!pBot->b_use_capture) && (pent->v.skin == (1 - team))) + { + float distance = (pent->v.origin - pEdict->v.origin).Length( ); + + // check if close enough and facing it directly... + if ((distance < FLF_PLAYER_SEARCH_RADIUS) && + (angle_to_entity <= 20)) + { + pBot->b_use_capture = TRUE; + pBot->f_use_capture_time = gpGlobals->time + 3.0; + pBot->pCaptureEdict = pent; + } + + // if close to capture point... + if (distance < 160) + { + // don't avoid walls for a while + pBot->f_dont_avoid_wall_time = gpGlobals->time + 5.0; + } + + can_pickup = TRUE; + } + } + + else if ((mod_id == HOLYWARS_DLL) && + (strcmp("halo", item_name) == 0)) + { + // make sure halo isn't owned by somebody + if (pent->v.owner == NULL) + { + can_pickup = TRUE; + } + } + + } // end if object is visible + } // end else not "func_" entity + + if (can_pickup) // if the bot found something it can pickup... + { + float distance = (entity_origin - pEdict->v.origin).Length( ); + + // see if it's the closest item so far... + if (distance < min_distance) + { + min_distance = distance; // update the minimum distance + pPickupEntity = pent; // remember this entity + pickup_origin = entity_origin; // remember location of entity + } + } + } // end while loop + + if (pPickupEntity != NULL) + { + // let's head off toward that item... + Vector v_item = pickup_origin - pEdict->v.origin; + + Vector bot_angles = UTIL_VecToAngles( v_item ); + + pEdict->v.ideal_yaw = bot_angles.y; + + BotFixIdealYaw(pEdict); + + pBot->pBotPickupItem = pPickupEntity; // save the item bot is trying to get + } +} + + +bool BotLookForMedic( bot_t *pBot ) +{ + int i; + Vector vecEnd; + edict_t *pEdict = pBot->pEdict; + + if (mod_id == TFC_DLL) + { + // search the world for players... + for (i = 1; i <= gpGlobals->maxClients; i++) + { + edict_t *pPlayer = INDEXENT(i); + + // skip invalid players and skip self (i.e. this bot) + if ((pPlayer) && (!pPlayer->free) && (pPlayer != pEdict)) + { + // skip this player if not alive (i.e. dead or dying) + if (!IsAlive(pPlayer)) + continue; + + int player_team = UTIL_GetTeam(pPlayer); + int bot_team = UTIL_GetTeam(pEdict); + + // don't look for your enemies... + if ((bot_team != player_team) && + !(team_allies[bot_team] & (1<v.playerclass != TFC_CLASS_MEDIC) + continue; // continue if player not a medic + + vecEnd = pPlayer->v.origin + pPlayer->v.view_ofs; + + // see if bot can see the player... + if (FInViewCone( &vecEnd, pEdict ) && + FVisible( vecEnd, pEdict )) + { + float distance = (pPlayer->v.origin - pEdict->v.origin).Length(); + + if (distance < 1000) + { + return TRUE; + } + } + } + } + } + + return FALSE; +} + + +bool BotLookForGrenades( bot_t *pBot ) +{ + edict_t *pent; + Vector entity_origin; + float radius = 500; + char classname[40]; + edict_t *pEdict = pBot->pEdict; + + pent = NULL; + while ((pent = UTIL_FindEntityInSphere( pent, pEdict->v.origin, radius )) != NULL) + { + strcpy(classname, STRING(pent->v.classname)); + + entity_origin = pent->v.origin; + + if (mod_id == VALVE_DLL) + { + if (FInViewCone( &entity_origin, pEdict ) && + FVisible( entity_origin, pEdict )) + { + if (strcmp("grenade", classname) == 0) + return TRUE; + if (strcmp("monster_satchel", classname) == 0) + return TRUE; + if (strcmp("monster_snark", classname) == 0) + return TRUE; + } + } + else if (mod_id == TFC_DLL) + { + if (FInViewCone( &entity_origin, pEdict ) && + FVisible( entity_origin, pEdict )) + { + if (strcmp("tf_weapon_normalgrenade", classname) == 0) + return TRUE; + if (strcmp("tf_weapon_nailgrenade", classname) == 0) + return TRUE; + if (strcmp("tf_weapon_mirvgrenade", classname) == 0) + return TRUE; + if (strcmp("tf_weapon_concussiongrenade", classname) == 0) + return TRUE; + if (strcmp("tf_weapon_napalmgrenade", classname) == 0) + return TRUE; + if (strcmp("tf_weapon_gasgrenade", classname) == 0) + return TRUE; + if (strcmp("tf_weapon_empgrenade", classname) == 0) + return TRUE; + } + } + else if (mod_id == FRONTLINE_DLL) + { + if (FInViewCone( &entity_origin, pEdict ) && + FVisible( entity_origin, pEdict )) + { + if (strcmp("grenade", classname) == 0) + return TRUE; + } + } + else + { + return FALSE; // all other non-supported MODs + } + } + + return FALSE; // no grenades were found +} + + +void BotThink( bot_t *pBot ) +{ + int index = 0; + Vector v_diff; // vector from previous to current location + float pitch_degrees; + float yaw_degrees; + float moved_distance; // length of v_diff vector (distance bot moved) + TraceResult tr; + bool found_waypoint; + bool is_idle; + float f_strafe_speed; + bool bCrouchJump; + char chat_text[81]; + char chat_name[64]; + char temp_name[64]; + const char *bot_name; + + edict_t *pEdict = pBot->pEdict; + + + pEdict->v.flags |= FL_THIRDPARTYBOT; + + if (pBot->name[0] == 0) // name filled in yet? + strcpy(pBot->name, STRING(pBot->pEdict->v.netname)); + + +// TheFatal - START from Advanced Bot Framework (Thanks Rich!) + + // adjust the millisecond delay based on the frame rate interval... + if (pBot->msecdel <= gpGlobals->time) + { + pBot->msecdel = gpGlobals->time + 0.5; + if (pBot->msecnum > 0) + pBot->msecval = 450.0/pBot->msecnum; + pBot->msecnum = 0; + } + else + pBot->msecnum++; + + if (pBot->msecval < 1) // don't allow msec to be less than 1... + pBot->msecval = 1; + + if (pBot->msecval > 100) // ...or greater than 100 + pBot->msecval = 100; + +// TheFatal - END + + pBot->f_frame_time = pBot->msecval / 1000; // calculate frame time + + + pEdict->v.button = 0; + pBot->f_move_speed = 0.0; + + // if the bot hasn't selected stuff to start the game yet, go do that... + if (pBot->not_started) + { + BotStartGame( pBot ); + + g_engfuncs.pfnRunPlayerMove( pEdict, pEdict->v.v_angle, pBot->f_move_speed, + 0, 0, pEdict->v.button, 0, pBot->msecval); + + return; + } + +// does bot need to say a message and time to say a message? + if ((pBot->b_bot_say) && (pBot->f_bot_say < gpGlobals->time)) + { + pBot->b_bot_say = FALSE; + + UTIL_HostSay(pEdict, 0, pBot->bot_say_msg); + } + + // if the bot is dead, randomly press fire to respawn... + if ((pEdict->v.health < 1) || (pEdict->v.deadflag != DEAD_NO)) + { + if (pBot->need_to_initialize) + { + BotSpawnInit(pBot); + + pBot->need_to_initialize = FALSE; + + // did another player kill this bot AND bot whine messages loaded AND + // has the bot been alive for at least 15 seconds AND + if ((pBot->killer_edict != NULL) && (bot_whine_count > 0) && + ((pBot->f_bot_spawn_time + 15.0) <= gpGlobals->time)) + { + int whine_index; + bool used; + int i, recent_count; + + if ((RANDOM_LONG(1,100) <= pBot->whine_percent)) + { + // set chat flag and time to chat... + pBot->b_bot_say = TRUE; + pBot->f_bot_say = gpGlobals->time + 5.0 + RANDOM_FLOAT(0.0, 5.0); + + recent_count = 0; + + while (recent_count < 5) + { + whine_index = RANDOM_LONG(0, bot_whine_count-1); + + used = FALSE; + + for (i=0; i < 5; i++) + { + if (recent_bot_whine[i] == whine_index) + used = TRUE; + } + + if (used) + recent_count++; + else + break; + } + + for (i=4; i > 0; i--) + recent_bot_whine[i] = recent_bot_whine[i-1]; + + recent_bot_whine[0] = whine_index; + + if (bot_whine[whine_index].can_modify) + BotChatText(bot_whine[whine_index].text, chat_text); + else + strcpy(chat_text, bot_whine[whine_index].text); + + if (pBot->killer_edict->v.netname) + { + strncpy(temp_name, STRING(pBot->killer_edict->v.netname), 31); + temp_name[31] = 0; + + BotChatName(temp_name, chat_name); + } + else + strcpy(chat_name, "NULL"); + + bot_name = STRING(pEdict->v.netname); + + BotChatFillInName(pBot->bot_say_msg, chat_text, chat_name, bot_name); + } + } + } + + if (RANDOM_LONG(1, 100) > 50) + pEdict->v.button = IN_ATTACK; + + g_engfuncs.pfnRunPlayerMove( pEdict, pEdict->v.v_angle, pBot->f_move_speed, + 0, 0, pEdict->v.button, 0, pBot->msecval); + + return; + } + + if ((bot_chat_count > 0) && (pBot->f_bot_chat_time < gpGlobals->time)) + { + pBot->f_bot_chat_time = gpGlobals->time + 30.0; + + if (RANDOM_LONG(1,100) <= pBot->chat_percent) + { + int chat_index; + bool used; + int i, recent_count; + + // set chat flag and time to chat... + pBot->b_bot_say = TRUE; + pBot->f_bot_say = gpGlobals->time + 5.0 + RANDOM_FLOAT(0.0, 5.0); + + recent_count = 0; + + while (recent_count < 5) + { + chat_index = RANDOM_LONG(0, bot_chat_count-1); + + used = FALSE; + + for (i=0; i < 5; i++) + { + if (recent_bot_chat[i] == chat_index) + used = TRUE; + } + + if (used) + recent_count++; + else + break; + } + + for (i=4; i > 0; i--) + recent_bot_chat[i] = recent_bot_chat[i-1]; + + recent_bot_chat[0] = chat_index; + + if (bot_chat[chat_index].can_modify) + BotChatText(bot_chat[chat_index].text, chat_text); + else + strcpy(chat_text, bot_chat[chat_index].text); + + strcpy(chat_name, STRING(pBot->pEdict->v.netname)); + + bot_name = STRING(pEdict->v.netname); + + BotChatFillInName(pBot->bot_say_msg, chat_text, chat_name, bot_name); + } + } + + + // set this for the next time the bot dies so it will initialize stuff + if (pBot->need_to_initialize == FALSE) + { + pBot->need_to_initialize = TRUE; + pBot->f_bot_spawn_time = gpGlobals->time; + } + + is_idle = FALSE; + + if ((mod_id == FRONTLINE_DLL) && (pBot->round_end)) + { + if (pBot->warmup) // has warmup started (i.e. start of round?) + { + pBot->round_end = 0; + + BotSpawnInit(pBot); + } + + is_idle = TRUE; + } + + if ((mod_id == FRONTLINE_DLL) && (pBot->warmup) && (!pBot->defender)) + { + if (pBot->curr_waypoint_index == -1) + { + // find the nearest visible waypoint + int i = WaypointFindNearest(pEdict, REACHABLE_RANGE, pBot->defender); + + if (i != -1) + { + pBot->curr_waypoint_index = i; + pBot->f_waypoint_time = gpGlobals->time; + + Vector v_direction = waypoints[i].origin - pEdict->v.origin; + + Vector bot_angles = UTIL_VecToAngles( v_direction ); + + pBot->idle_angle = bot_angles.y; + } + else + pBot->idle_angle = pEdict->v.v_angle.y; + } + + is_idle = TRUE; + } + + // is a vote in progress and it's time to vote now? + if ((mod_id == HOLYWARS_DLL) && (pBot->vote_in_progress) && + (pBot->f_vote_time <= gpGlobals->time)) + { + pBot->vote_in_progress = FALSE; + + if (RANDOM_LONG(1, 100) <= 80) // only vote 80% of the time + { + if (RANDOM_LONG(1, 100) <= 50) + FakeClientCommand(pEdict, "vote_yes", NULL, NULL); + else + FakeClientCommand(pEdict, "vote_no", NULL, NULL); + } + + pBot->f_start_vote_time = gpGlobals->time + RANDOM_LONG(120, 600); + } + + if ((mod_id == HOLYWARS_DLL) && + (pBot->f_start_vote_time < gpGlobals->time)) + { + if (holywars_gamemode == 0) // currently deathmatch? + FakeClientCommand(pEdict, "callvote_instagib", NULL, NULL); + else + FakeClientCommand(pEdict, "callvote_deathmatch", NULL, NULL); + + pBot->f_start_vote_time = gpGlobals->time + RANDOM_LONG(120, 600); + } + + if (pBot->blinded_time > gpGlobals->time) + { + is_idle = TRUE; // don't do anything while blinded + } + + if (is_idle) + { + if (pBot->idle_angle_time <= gpGlobals->time) + { + pBot->idle_angle_time = gpGlobals->time + RANDOM_FLOAT(0.5, 2.0); + + pEdict->v.ideal_yaw = pBot->idle_angle + RANDOM_FLOAT(0.0, 60.0) - 30.0; + + BotFixIdealYaw(pEdict); + } + + // turn towards ideal_yaw by yaw_speed degrees (slower than normal) + BotChangeYaw( pBot, pEdict->v.yaw_speed / 2 ); + + g_engfuncs.pfnRunPlayerMove( pEdict, pEdict->v.v_angle, pBot->f_move_speed, + 0, 0, pEdict->v.button, 0, pBot->msecval); + + return; + } + else + { + pBot->idle_angle = pEdict->v.v_angle.y; + } + + // check if time to check for player sounds (if don't already have enemy) + if ((pBot->f_sound_update_time <= gpGlobals->time) && + (pBot->pBotEnemy == NULL)) + { + int ind; + edict_t *pPlayer; + + pBot->f_sound_update_time = gpGlobals->time + 1.0; + + for (ind = 1; ind <= gpGlobals->maxClients; ind++) + { + pPlayer = INDEXENT(ind); + + // is this player slot is valid and it's not this bot... + if ((pPlayer) && (!pPlayer->free) && (pPlayer != pEdict)) + { + // if observer mode enabled, don't listen to this player... + if ((b_observer_mode) && !(pPlayer->v.flags & FL_FAKECLIENT) && !(pPlayer->v.flags & FL_THIRDPARTYBOT)) + continue; + + if (IsAlive(pPlayer) && + (FBitSet(pPlayer->v.flags, FL_CLIENT))) + { + // check for sounds being made by other players... + if (UpdateSounds(pEdict, pPlayer)) + { + // don't check for sounds for another 30 seconds + pBot->f_sound_update_time = gpGlobals->time + 30.0; + } + } + } + } + } + + pBot->f_move_speed = pBot->f_max_speed; // set to max speed + + if (pBot->f_speed_check_time <= gpGlobals->time) + { + // see how far bot has moved since the previous position... + v_diff = pBot->v_prev_origin - pEdict->v.origin; + moved_distance = v_diff.Length(); + + // save current position as previous + pBot->v_prev_origin = pEdict->v.origin; + pBot->f_speed_check_time = gpGlobals->time + 0.2; + } + else + { + moved_distance = 2.0; + } + + // if the bot is not underwater AND not in the air (or on ladder), + // check if the bot is about to fall off high ledge or into water... + if ((pEdict->v.waterlevel != 3) && + (pEdict->v.flags & FL_ONGROUND) && + (pEdict->v.movetype != MOVETYPE_FLY) && + (pBot->f_drop_check_time < gpGlobals->time)) + { + pBot->f_drop_check_time = gpGlobals->time + 0.05; + + BotLookForDrop( pBot ); + } + + // turn towards ideal_pitch by pitch_speed degrees + pitch_degrees = BotChangePitch( pBot, pEdict->v.pitch_speed ); + + // turn towards ideal_yaw by yaw_speed degrees + yaw_degrees = BotChangeYaw( pBot, pEdict->v.yaw_speed ); + + if ((pitch_degrees >= 45) || (yaw_degrees >= 45)) + { + pBot->f_move_speed = 0.0; // don't move while turning a lot + } + else if ((pitch_degrees >= 30) || (yaw_degrees >= 30)) + { + pBot->f_move_speed = pBot->f_move_speed / 4; // slow down while turning + } + else if ((pitch_degrees >= 20) || (yaw_degrees >= 20)) + { + pBot->f_move_speed = pBot->f_move_speed / 3; // slow down while turning + } + +// else // else handle movement related actions... + + { + if (b_botdontshoot == 0) + { + if ((mod_id == TFC_DLL) && (pBot->bot_has_flag == TRUE)) + { + // is it time to check whether bot should look for enemies yet? + if (pBot->f_bot_find_enemy_time <= gpGlobals->time) + { + pBot->f_bot_find_enemy_time = gpGlobals->time + 5.0; + + if (RANDOM_LONG(1, 100) <= 40) + pBot->pBotEnemy = BotFindEnemy( pBot ); + } + } + else + { + pBot->pBotEnemy = BotFindEnemy( pBot ); + } + } + else + pBot->pBotEnemy = NULL; // clear enemy pointer (no ememy for you!) + + if (pBot->pBotEnemy != NULL) // does an enemy exist? + { + BotShootAtEnemy( pBot ); // shoot at the enemy + + pBot->f_pause_time = 0; // dont't pause if enemy exists + } + + else if (pBot->f_pause_time > gpGlobals->time) // is bot "paused"? + { + // you could make the bot look left then right, or look up + // and down, to make it appear that the bot is hunting for + // something (don't do anything right now) + } + + // is bot being "used" and can still follow "user"? + else if ((pBot->pBotUser != NULL) && BotFollowUser( pBot )) + { + // do nothing here! + ; + } + + else + { + // no enemy, let's just wander around... + + // took too long trying to spray logo?... + if ((pBot->b_spray_logo) && + ((pBot->f_spray_logo_time + 3.0) < gpGlobals->time)) + { + pBot->b_spray_logo = FALSE; + pEdict->v.idealpitch = 0.0f; + } + + if (pBot->b_spray_logo) // trying to spray a logo? + { + Vector v_src, v_dest, angle; + TraceResult tr; + + // find the nearest wall to spray logo on (or floor)... + + angle = pEdict->v.v_angle; + angle.x = 0; // pitch is level horizontally + + MAKE_VECTORS( angle ); + + v_src = pEdict->v.origin + pEdict->v.view_ofs; + v_dest = v_src + gpGlobals->v_forward * 100; + + UTIL_TraceLine( v_src, v_dest, dont_ignore_monsters, + pEdict->v.pContainingEntity, &tr); + + if (tr.flFraction < 1.0) + { + // already facing the correct yaw, just set pitch... + pEdict->v.idealpitch = RANDOM_FLOAT(0.0, 30.0) - 15.0; + } + else + { + v_dest = v_src + gpGlobals->v_right * 100; // to the right + + UTIL_TraceLine( v_src, v_dest, dont_ignore_monsters, + pEdict->v.pContainingEntity, &tr); + + if (tr.flFraction < 1.0) + { + // set the ideal yaw and pitch... + Vector bot_angles = UTIL_VecToAngles(v_dest - v_src); + + pEdict->v.ideal_yaw = bot_angles.y; + + BotFixIdealYaw(pEdict); + + pEdict->v.idealpitch = RANDOM_FLOAT(0.0, 30.0) - 15.0; + } + else + { + v_dest = v_src + gpGlobals->v_right * -100; // to the left + + UTIL_TraceLine( v_src, v_dest, dont_ignore_monsters, + pEdict->v.pContainingEntity, &tr); + + if (tr.flFraction < 1.0) + { + // set the ideal yaw and pitch... + Vector bot_angles = UTIL_VecToAngles(v_dest - v_src); + + pEdict->v.ideal_yaw = bot_angles.y; + + BotFixIdealYaw(pEdict); + + pEdict->v.idealpitch = RANDOM_FLOAT(0.0, 30.0) - 15.0; + } + else + { + v_dest = v_src + gpGlobals->v_forward * -100; // behind + + UTIL_TraceLine( v_src, v_dest, dont_ignore_monsters, + pEdict->v.pContainingEntity, &tr); + + if (tr.flFraction < 1.0) + { + // set the ideal yaw and pitch... + Vector bot_angles = UTIL_VecToAngles(v_dest - v_src); + + pEdict->v.ideal_yaw = bot_angles.y; + + BotFixIdealYaw(pEdict); + + pEdict->v.idealpitch = RANDOM_FLOAT(0.0, 30.0) - 15.0; + } + else + { + // on the ground... + + angle = pEdict->v.v_angle; + angle.x = 85.0f; // 85 degrees is downward + + MAKE_VECTORS( angle ); + + v_src = pEdict->v.origin + pEdict->v.view_ofs; + v_dest = v_src + gpGlobals->v_forward * 80; + + UTIL_TraceLine( v_src, v_dest, dont_ignore_monsters, + pEdict->v.pContainingEntity, &tr); + + if (tr.flFraction < 1.0) + { + // set the pitch... + + pEdict->v.idealpitch = 85.0f; + + BotFixIdealPitch(pEdict); + } + } + } + } + } + + pBot->f_dont_avoid_wall_time = gpGlobals->time + 5.0; + + // is there a wall close to us? + + MAKE_VECTORS( pEdict->v.v_angle ); + + v_src = pEdict->v.origin + pEdict->v.view_ofs; + v_dest = v_src + gpGlobals->v_forward * 80; + + UTIL_TraceLine( v_src, v_dest, dont_ignore_monsters, + pEdict->v.pContainingEntity, &tr); + + if (tr.flFraction < 1.0) + { + BotSprayLogo(pEdict, pBot->logo_name); + + pBot->b_spray_logo = FALSE; + + pEdict->v.idealpitch = 0.0f; + } + } + + if ((pEdict->v.waterlevel != 2) && // is bot NOT under water? + (pEdict->v.waterlevel != 3) && + (!pBot->b_spray_logo)) // AND not trying to spray a logo + { + // reset pitch to 0 (level horizontally) + pEdict->v.idealpitch = 0; + pEdict->v.v_angle.x = 0; + } + + // check if bot should look for items now or not... + if (pBot->f_find_item <= gpGlobals->time) + { + pBot->f_find_item = gpGlobals->time + 0.1; + + BotFindItem( pBot ); // see if there are any visible items + } + + // check if bot sees a tripmine... + if (pBot->b_see_tripmine) + { + // check if bot can shoot the tripmine... + if ((pBot->b_shoot_tripmine) && BotShootTripmine( pBot )) + { + // shot at tripmine, may or may not have hit it, clear + // flags anyway, next BotFindItem will see it again if + // it is still there... + + pBot->b_shoot_tripmine = FALSE; + pBot->b_see_tripmine = FALSE; + + // pause for a while to allow tripmine to explode... + pBot->f_pause_time = gpGlobals->time + 0.5; + } + else // run away!!! + { + Vector tripmine_angles; + + tripmine_angles = UTIL_VecToAngles( pBot->v_tripmine - pEdict->v.origin ); + + // face away from the tripmine + pEdict->v.ideal_yaw += 180; // rotate 180 degrees + + BotFixIdealYaw(pEdict); + + pBot->b_see_tripmine = FALSE; + + pBot->f_move_speed = 0; // don't run while turning + } + } + + // check if should use wall mounted health station... + else if (pBot->b_use_health_station) + { + if ((pBot->f_use_health_time + 10.0) > gpGlobals->time) + { + pBot->f_move_speed = 0; // don't move while using health station + + pEdict->v.button = IN_USE; + } + else + { + // bot is stuck trying to "use" a health station... + + pBot->b_use_health_station = FALSE; + + // don't look for items for a while since the bot + // could be stuck trying to get to an item + pBot->f_find_item = gpGlobals->time + 0.5; + } + } + + // check if should use wall mounted HEV station... + else if (pBot->b_use_HEV_station) + { + if ((pBot->f_use_HEV_time + 10.0) > gpGlobals->time) + { + pBot->f_move_speed = 0; // don't move while using HEV station + + pEdict->v.button = IN_USE; + } + else + { + // bot is stuck trying to "use" a HEV station... + + pBot->b_use_HEV_station = FALSE; + + // don't look for items for a while since the bot + // could be stuck trying to get to an item + pBot->f_find_item = gpGlobals->time + 0.5; + } + } + + // check if should capture a point by using it... + else if (pBot->b_use_capture) + { + int team = UTIL_GetTeam(pEdict); // skin and team must match + + // still capturing and hasn't captured yet... + if ((pBot->f_use_capture_time > gpGlobals->time) && + (pBot->pCaptureEdict->v.skin == (1 - team))) + { + pBot->f_move_speed = 0; // don't move while capturing + + pEdict->v.button = IN_USE; + } + else + { + // bot is stuck trying to "use" a capture point... + + pBot->b_use_capture = FALSE; + pBot->f_use_capture_time = 0.0; + + // don't look for items for a while since the bot + // could be stuck trying to get to an item + pBot->f_find_item = gpGlobals->time + 0.5; + } + } + + else if (pBot->b_build_sentrygun) + { + if (pBot->sentrygun_level == 0) + { + pBot->f_move_speed = 0; // don't move while building + + // check if the bot is facing the correct direction yet... + if (yaw_degrees <= 1.0) + { + FakeClientCommand(pEdict, "build", "2", NULL); + + pBot->b_build_sentrygun = FALSE; + + pBot->f_engineer_build_time = gpGlobals->time + 20.0; + pBot->f_pause_time = gpGlobals->time + 5.0; + } + } + else // need to "attack" this sentrygun to upgrade it... + { + edict_t *pent = NULL; + + pBot->b_build_sentrygun = FALSE; + + while ((pent = UTIL_FindEntityInSphere( pent, pEdict->v.origin, 150.0 )) != NULL) + { + if (strcmp(STRING(pent->v.classname), "building_sentrygun") == 0) + { + pBot->pBotEnemy = pent; + + pBot->enemy_attack_count = 3; // give it 3 whacks + pBot->f_engineer_build_time = gpGlobals->time + 20.0; + + // face the enemy + Vector v_enemy = pent->v.origin - pEdict->v.origin; + Vector bot_angles = UTIL_VecToAngles( v_enemy ); + + pEdict->v.ideal_yaw = bot_angles.y; + + BotFixIdealYaw(pEdict); + } + } + } + + pBot->curr_waypoint_index = -1; // forget this waypoint + } + + else if (pBot->b_build_dispenser) + { + if (pBot->dispenser_built == 0) + { + pBot->f_move_speed = 0; // don't move while building + + // check if the bot is facing the correct direction yet... + if (yaw_degrees <= 1.0) + { + FakeClientCommand(pEdict, "build", "1", NULL); + + pBot->b_build_dispenser = FALSE; + + pBot->f_engineer_build_time = gpGlobals->time + 20.0; + pBot->f_pause_time = gpGlobals->time + 3.0; + } + } + else // need to "attack" this dispenser to refill it... + { + edict_t *pent = NULL; + + pBot->b_build_dispenser = FALSE; + + while ((pent = UTIL_FindEntityInSphere( pent, pEdict->v.origin, 150.0 )) != NULL) + { + if (strcmp(STRING(pent->v.classname), "building_dispenser") == 0) + { + pBot->pBotEnemy = pent; + + pBot->enemy_attack_count = 3; // give it 3 whacks + pBot->f_engineer_build_time = gpGlobals->time + 20.0; + + // face the enemy + Vector v_enemy = pent->v.origin - pEdict->v.origin; + Vector bot_angles = UTIL_VecToAngles( v_enemy ); + + pEdict->v.ideal_yaw = bot_angles.y; + + BotFixIdealYaw(pEdict); + } + } + } + + pBot->curr_waypoint_index = -1; // forget this waypoint + } + + else if (pBot->b_use_button) + { + pBot->f_move_speed = 0; // don't move while using elevator + + BotUseLift( pBot, moved_distance ); + } + + else + { + if (pEdict->v.waterlevel == 3) // check if the bot is underwater... + { + BotUnderWater( pBot ); + } + + found_waypoint = FALSE; + + // if the bot is not trying to get to something AND + // it is time to look for a waypoint AND + // there are waypoints in this level... + + if ((pBot->pBotPickupItem == NULL) && + (pBot->f_look_for_waypoint_time <= gpGlobals->time) && + (num_waypoints != 0)) + { + found_waypoint = BotHeadTowardWaypoint(pBot); + } + + // check if the bot is on a ladder... + if (pEdict->v.movetype == MOVETYPE_FLY) + { + // check if bot JUST got on the ladder... + if ((pBot->f_end_use_ladder_time + 1.0) < gpGlobals->time) + pBot->f_start_use_ladder_time = gpGlobals->time; + + // go handle the ladder movement + BotOnLadder( pBot, moved_distance ); + + pBot->f_dont_avoid_wall_time = gpGlobals->time + 2.0; + pBot->f_end_use_ladder_time = gpGlobals->time; + } + else + { + // check if the bot JUST got off the ladder... + if ((pBot->f_end_use_ladder_time + 1.0) > gpGlobals->time) + { + pBot->ladder_dir = LADDER_UNKNOWN; + } + } + + // if the bot isn't headed toward a waypoint... + if (found_waypoint == FALSE) + { + TraceResult tr; + + // check if we should be avoiding walls + if (pBot->f_dont_avoid_wall_time <= gpGlobals->time) + { + // let's just randomly wander around + if (BotStuckInCorner( pBot )) + { + pEdict->v.ideal_yaw += 180; // turn 180 degrees + + BotFixIdealYaw(pEdict); + + pBot->f_move_speed = 0; // don't move while turning + pBot->f_dont_avoid_wall_time = gpGlobals->time + 1.0; + + moved_distance = 2.0; // dont use bot stuck code + } + else + { + // check if there is a wall on the left... + if (!BotCheckWallOnLeft( pBot )) + { + // if there was a wall on the left over 1/2 a second ago then + // 20% of the time randomly turn between 45 and 60 degrees + + if ((pBot->f_wall_on_left != 0) && + (pBot->f_wall_on_left <= gpGlobals->time - 0.5) && + (RANDOM_LONG(1, 100) <= 20)) + { + pEdict->v.ideal_yaw += RANDOM_LONG(45, 60); + + BotFixIdealYaw(pEdict); + + pBot->f_move_speed = 0; // don't move while turning + pBot->f_dont_avoid_wall_time = gpGlobals->time + 1.0; + } + + pBot->f_wall_on_left = 0; // reset wall detect time + } + else if (!BotCheckWallOnRight( pBot )) + { + // if there was a wall on the right over 1/2 a second ago then + // 20% of the time randomly turn between 45 and 60 degrees + + if ((pBot->f_wall_on_right != 0) && + (pBot->f_wall_on_right <= gpGlobals->time - 0.5) && + (RANDOM_LONG(1, 100) <= 20)) + { + pEdict->v.ideal_yaw -= RANDOM_LONG(45, 60); + + BotFixIdealYaw(pEdict); + + pBot->f_move_speed = 0; // don't move while turning + pBot->f_dont_avoid_wall_time = gpGlobals->time + 1.0; + } + + pBot->f_wall_on_right = 0; // reset wall detect time + } + } + } + + // check if bot is about to hit a wall. TraceResult gets returned + if ((pBot->f_dont_avoid_wall_time <= gpGlobals->time) && + BotCantMoveForward( pBot, &tr )) + { + // ADD LATER + // need to check if bot can jump up or duck under here... + // ADD LATER + + BotTurnAtWall( pBot, &tr, TRUE ); + } + } + + // check if bot is on a ladder and has been on a ladder for + // more than 5 seconds... + if ((pEdict->v.movetype == MOVETYPE_FLY) && + (pBot->f_start_use_ladder_time > 0.0) && + ((pBot->f_start_use_ladder_time + 5.0) <= gpGlobals->time)) + { + // bot is stuck on a ladder... + + BotRandomTurn(pBot); + + // don't look for items for 2 seconds + pBot->f_find_item = gpGlobals->time + 2.0; + + pBot->f_start_use_ladder_time = 0.0; // reset start ladder time + } + + // check if the bot hasn't moved much since the last location + // (and NOT on a ladder since ladder stuck handled elsewhere) + if ((moved_distance <= 1.0) && (pBot->f_prev_speed >= 1.0) && + (pEdict->v.movetype != MOVETYPE_FLY)) + { + // the bot must be stuck! + + pBot->f_dont_avoid_wall_time = gpGlobals->time + 1.0; + pBot->f_look_for_waypoint_time = gpGlobals->time + 1.0; + + if (BotCanJumpUp( pBot, &bCrouchJump )) // can the bot jump onto something? + { + if ((pBot->f_jump_time + 2.0) <= gpGlobals->time) + { + pBot->f_jump_time = gpGlobals->time; + pEdict->v.button |= IN_JUMP; // jump up and move forward + + if (bCrouchJump) + pEdict->v.button |= IN_DUCK; // also need to duck + } + else + { + // bot already tried jumping less than two seconds ago, just turn + BotRandomTurn(pBot); + } + } + else if (BotCanDuckUnder( pBot )) // can the bot duck under something? + { + pEdict->v.button |= IN_DUCK; // duck down and move forward + } + else + { + BotRandomTurn(pBot); + + // is the bot trying to get to an item?... + if (pBot->pBotPickupItem != NULL) + { + // don't look for items for a while since the bot + // could be stuck trying to get to an item + pBot->f_find_item = gpGlobals->time + 0.5; + } + } + } + + // should the bot pause for a while here? + // (don't pause on ladders or while being "used"... + if ((RANDOM_LONG(1, 1000) <= pause_frequency[pBot->bot_skill]) && + (pEdict->v.movetype != MOVETYPE_FLY) && + (pBot->pBotUser == NULL)) + { + // set the time that the bot will stop "pausing" + pBot->f_pause_time = gpGlobals->time + + RANDOM_FLOAT(pause_time[pBot->bot_skill][0], + pause_time[pBot->bot_skill][1]); + } + } + } + } + + if (pBot->curr_waypoint_index != -1) // does the bot have a waypoint? + { + // check if the next waypoint is a door waypoint... + if (waypoints[pBot->curr_waypoint_index].flags & W_FL_DOOR) + { + pBot->f_move_speed = pBot->f_max_speed / 3; // slow down for doors + } + + // check if the next waypoint is a ladder waypoint... + if (waypoints[pBot->curr_waypoint_index].flags & W_FL_LADDER) + { + // check if the waypoint is at the top of a ladder AND + // the bot isn't currenly on a ladder... + if ((pBot->waypoint_top_of_ladder) && + (pEdict->v.movetype != MOVETYPE_FLY)) + { + // is the bot on "ground" above the ladder? + if (pEdict->v.flags & FL_ONGROUND) + { + float waypoint_distance = (pEdict->v.origin - pBot->waypoint_origin).Length(); + + if (waypoint_distance <= 20.0) // if VERY close... + pBot->f_move_speed = 20.0; // go VERY slow + else if (waypoint_distance <= 100.0) // if fairly close... + pBot->f_move_speed = 50.0; // go fairly slow + + pBot->ladder_dir = LADDER_DOWN; + } + else // bot must be in mid-air, go BACKWARDS to touch ladder... + { + pBot->f_move_speed = -pBot->f_max_speed; + } + } + else + { + // don't avoid walls for a while + pBot->f_dont_avoid_wall_time = gpGlobals->time + 5.0; + + pBot->waypoint_top_of_ladder = FALSE; + } + } + + // check if the next waypoint is a crouch waypoint... + if (waypoints[pBot->curr_waypoint_index].flags & W_FL_CROUCH) + pEdict->v.button |= IN_DUCK; // duck down while moving forward + + // check if the waypoint is a sniper waypoint AND + // bot isn't currently aiming at an ememy... + if ((waypoints[pBot->curr_waypoint_index].flags & W_FL_SNIPER) && + (pBot->pBotEnemy == NULL)) + { + if ((mod_id != TFC_DLL) || + ((mod_id == TFC_DLL) && (pEdict->v.playerclass == TFC_CLASS_SNIPER))) + { + // check if the bot need to move back closer to the waypoint... + + float distance = (pEdict->v.origin - waypoints[pBot->curr_waypoint_index].origin).Length(); + + if (distance > 40) + { + // turn towards the sniper waypoint and move there... + + Vector v_direction = waypoints[pBot->curr_waypoint_index].origin - pEdict->v.origin; + + Vector bot_angles = UTIL_VecToAngles( v_direction ); + + pEdict->v.ideal_yaw = bot_angles.y; + + BotFixIdealYaw(pEdict); + + // go slow to prevent the "loop the loop" problem... + pBot->f_move_speed = pBot->f_max_speed / 3; + + pBot->f_sniper_aim_time = 0.0; // reset aiming time + + pEdict->v.v_angle.z = 0; // reset roll to 0 (straight up and down) + + // set the body angles the same way the bot is looking/aiming + pEdict->v.angles.x = -pEdict->v.v_angle.x / 3; + pEdict->v.angles.y = pEdict->v.v_angle.y; + pEdict->v.angles.z = pEdict->v.v_angle.z; + + // save the previous speed (for checking if stuck) + pBot->f_prev_speed = pBot->f_move_speed; + + f_strafe_speed = 0.0; + + g_engfuncs.pfnRunPlayerMove( pEdict, pEdict->v.v_angle, pBot->f_move_speed, + f_strafe_speed, 0, pEdict->v.button, 0, pBot->msecval); + + return; + } + + // check if it's time to adjust aim yet... + if (pBot->f_sniper_aim_time <= gpGlobals->time) + { + int aim_index; + + aim_index = WaypointFindNearestAiming(waypoints[pBot->curr_waypoint_index].origin); + + if (aim_index != -1) + { + Vector v_aim = waypoints[aim_index].origin - waypoints[pBot->curr_waypoint_index].origin; + + Vector aim_angles = UTIL_VecToAngles( v_aim ); + + aim_angles.y += RANDOM_LONG(0, 30) - 15; + + pEdict->v.ideal_yaw = aim_angles.y; + + BotFixIdealYaw(pEdict); + } + + // don't adjust aim again until after a few seconds... + pBot->f_sniper_aim_time = gpGlobals->time + RANDOM_FLOAT(3.0, 5.0); + } + } + } + + // check if the waypoint is a sentry gun waypoint and this waypoint + // is the bot's goal... + if ((waypoints[pBot->curr_waypoint_index].flags & W_FL_SENTRYGUN) && + (pBot->waypoint_goal == pBot->curr_waypoint_index) && + (pEdict->v.playerclass == TFC_CLASS_ENGINEER)) + { + // go slowly when approaching sentry gun waypoints + pBot->f_move_speed = pBot->f_max_speed / 3; + } + + // check if the waypoint is a dispenser waypoint and this waypoint + // is the bot's goal... + if ((waypoints[pBot->curr_waypoint_index].flags & W_FL_DISPENSER) && + (pBot->waypoint_goal == pBot->curr_waypoint_index) && + (pEdict->v.playerclass == TFC_CLASS_ENGINEER)) + { + // go slowly when approaching dispenser waypoints + pBot->f_move_speed = pBot->f_max_speed / 3; + } + } + + if (pBot->f_pause_time > gpGlobals->time) // is the bot "paused"? + pBot->f_move_speed = 0; // don't move while pausing + + if (pBot->f_strafe_time < gpGlobals->time) // time to strafe yet? + { + pBot->f_strafe_time = gpGlobals->time + RANDOM_FLOAT(0.1, 1.0); + + if (RANDOM_LONG(1, 100) <= pBot->strafe_percent) + { + if (RANDOM_LONG(1, 100) <= 50) + pBot->f_strafe_direction = -1 * RANDOM_FLOAT(0.5, 1.0); + else + pBot->f_strafe_direction = RANDOM_FLOAT(0.5, 1.0); + } + else + pBot->f_strafe_direction = 0.0; + } + + if (pBot->f_duck_time > gpGlobals->time) + pEdict->v.button |= IN_DUCK; // need to duck (crowbar attack) + + if ((mod_id == TFC_DLL) && + (pEdict->v.playerclass != TFC_CLASS_MEDIC)) + { + if (pBot->f_medic_check_time <= gpGlobals->time) + { + pBot->f_medic_check_time = gpGlobals->time + 0.1; + + // is bot's health less than 50% AND bot can see a medic? + if (((pEdict->v.health / pEdict->v.max_health) < 0.50) && + BotLookForMedic( pBot )) + { + if (RANDOM_LONG(1, 100) <= 50) + { + // yell "saveme" and stand still for 3.0 seconds + FakeClientCommand(pEdict, "saveme", NULL, NULL); + pBot->f_medic_pause_time = gpGlobals->time + 3.0; + } + + pBot->f_medic_check_time = gpGlobals->time + 3.0; + } + else + { + // is the bot's health less than 20%? + if ((pEdict->v.health / pEdict->v.max_health) < 0.20) + { + if ((RANDOM_LONG(1, 100) <= 20) && + (pBot->f_medic_yell_time <= gpGlobals->time)) + { + // yell "saveme" in case there's a medic within earshot + FakeClientCommand(pEdict, "saveme", NULL, NULL); + + pBot->f_medic_yell_time = gpGlobals->time + 10.0; + } + } + } + } + } + + if (pBot->f_medic_pause_time > gpGlobals->time) + { + pBot->f_move_speed = 0.0; + pBot->f_strafe_direction = 0.0; + } + + if (pBot->f_grenade_search_time <= gpGlobals->time) + { + pBot->f_grenade_search_time = gpGlobals->time + 0.1; + + // does the bot see any grenades laying about? + if (BotLookForGrenades( pBot )) + pBot->f_grenade_found_time = gpGlobals->time; + } + + // do we have a grenade primed and ready to throw.. + if (pBot->f_gren_throw_time > gpGlobals->time) + { + pBot->f_move_speed = 0.0; // don't move while priming + } + else if (pBot->f_gren_throw_time + 5.0 > gpGlobals->time) + { + // move backwards for 5.0 seconds after throwing grenade... + pBot->f_move_speed = -1.0 * pBot->f_move_speed; + } + else if (pBot->f_grenade_found_time + 1.0 > gpGlobals->time) + { + // move backwards for 1.0 second after seeing a grenade... + pBot->f_move_speed = -1.0 * pBot->f_move_speed; + } + + pEdict->v.v_angle.z = 0; // reset roll to 0 (straight up and down) + + // set the body angles the same way the bot is looking/aiming + pEdict->v.angles.x = -pEdict->v.v_angle.x / 3; + pEdict->v.angles.y = pEdict->v.v_angle.y; + pEdict->v.angles.z = pEdict->v.v_angle.z; + + // save the previous speed (for checking if stuck) + pBot->f_prev_speed = pBot->f_move_speed; + + f_strafe_speed = pBot->f_strafe_direction * (pBot->f_move_speed / 2.0); + + g_engfuncs.pfnRunPlayerMove( pEdict, pEdict->v.v_angle, pBot->f_move_speed, + f_strafe_speed, 0, pEdict->v.button, 0, pBot->msecval); + + return; +} + diff --git a/bot.h b/bot.h new file mode 100644 index 0000000..cffb0d9 --- /dev/null +++ b/bot.h @@ -0,0 +1,399 @@ +// +// HPB_bot - botman's High Ping Bastard bot +// +// (http://planethalflife.com/botman/) +// +// bot.h +// + +#ifndef BOT_H +#define BOT_H + +// stuff for Win32 vs. Linux builds + +#ifdef __linux__ +#define Sleep sleep +typedef int BOOL; +#endif + + +// define a new bit flag for bot identification + +#define FL_THIRDPARTYBOT (1 << 27) + + +// define constants used to identify the MOD we are playing... + +#define VALVE_DLL 1 +#define TFC_DLL 2 +#define CSTRIKE_DLL 3 +#define GEARBOX_DLL 4 +#define FRONTLINE_DLL 5 +#define HOLYWARS_DLL 6 +#define DMC_DLL 7 + + +// define some function prototypes... +void FakeClientCommand(edict_t *pBot, char *arg1, char *arg2, char *arg3); + + +#define LADDER_UNKNOWN 0 +#define LADDER_UP 1 +#define LADDER_DOWN 2 + +#define WANDER_LEFT 1 +#define WANDER_RIGHT 2 + +#define RESPAWN_IDLE 1 +#define RESPAWN_NEED_TO_RESPAWN 2 +#define RESPAWN_IS_RESPAWNING 3 + +// game start messages for TFC... +#define MSG_TFC_IDLE 1 +#define MSG_TFC_TEAM_SELECT 2 +#define MSG_TFC_CLASS_SELECT 3 + +// game start messages for CS... +#define MSG_CS_IDLE 1 +#define MSG_CS_TEAM_SELECT 2 +#define MSG_CS_CT_SELECT 3 +#define MSG_CS_T_SELECT 4 + +// game start messages for OpFor... +#define MSG_OPFOR_IDLE 1 +#define MSG_OPFOR_TEAM_SELECT 2 +#define MSG_OPFOR_CLASS_SELECT 3 + +// game start messages for FrontLineForce... +#define MSG_FLF_IDLE 1 +#define MSG_FLF_TEAM_SELECT 2 +#define MSG_FLF_CLASS_SELECT 3 +#define MSG_FLF_PISTOL_SELECT 4 +#define MSG_FLF_WEAPON_SELECT 5 +#define MSG_FLF_RIFLE_SELECT 6 +#define MSG_FLF_SHOTGUN_SELECT 7 +#define MSG_FLF_SUBMACHINE_SELECT 8 +#define MSG_FLF_HEAVYWEAPONS_SELECT 9 + + +#define TFC_CLASS_CIVILIAN 0 +#define TFC_CLASS_SCOUT 1 +#define TFC_CLASS_SNIPER 2 +#define TFC_CLASS_SOLDIER 3 +#define TFC_CLASS_DEMOMAN 4 +#define TFC_CLASS_MEDIC 5 +#define TFC_CLASS_HWGUY 6 +#define TFC_CLASS_PYRO 7 +#define TFC_CLASS_SPY 8 +#define TFC_CLASS_ENGINEER 9 + +// halo status for holy wars +#define HW_WAIT_SPAWN 0 +#define HW_NEW_SAINT 2 + + +// instant damage (from cbase.h) +#define DMG_CRUSH (1 << 0) // crushed by falling or moving object +#define DMG_BURN (1 << 3) // heat burned +#define DMG_FREEZE (1 << 4) // frozen +#define DMG_FALL (1 << 5) // fell too far +#define DMG_SHOCK (1 << 8) // electric shock +#define DMG_DROWN (1 << 14) // Drowning +#define DMG_NERVEGAS (1 << 16) // nerve toxins, very bad +#define DMG_RADIATION (1 << 18) // radiation exposure +#define DMG_DROWNRECOVER (1 << 19) // drowning recovery +#define DMG_ACID (1 << 20) // toxic chemicals or acid burns +#define DMG_SLOWBURN (1 << 21) // in an oven +#define DMG_SLOWFREEZE (1 << 22) // in a subzero freezer + + +#define BOT_SKIN_LEN 32 +#define BOT_NAME_LEN 32 + +#define MAX_BOT_CHAT 100 + + +typedef struct +{ + int iId; // weapon ID + int iClip; // amount of ammo in the clip + int iAmmo1; // amount of ammo in primary reserve + int iAmmo2; // amount of ammo in secondary reserve +} bot_current_weapon_t; + + +typedef struct +{ + bool is_used; + int respawn_state; + edict_t *pEdict; + bool need_to_initialize; + char name[BOT_NAME_LEN+1]; + char skin[BOT_SKIN_LEN+1]; + int bot_skill; + int not_started; + int start_action; + float f_kick_time; + float f_create_time; + float f_frame_time; + + int chat_percent; + int taunt_percent; + int whine_percent; + int chat_tag_percent; + int chat_drop_percent; + int chat_swap_percent; + int chat_lower_percent; + +// TheFatal - START + int msecnum; + float msecdel; + float msecval; +// TheFatal - END + + // things from pev in CBasePlayer... + int bot_team; + int bot_class; + int bot_money; // for Counter-Strike + int primary_weapon; // for Front Line Force + int secondary_weapon; // for Front Line Force + int defender; // for Front Line Force + int warmup; // for Front Line Force + float idle_angle; + float idle_angle_time; // for Front Line Force + int round_end; // round has ended (in round based games) + float blinded_time; + int gren1; // primary grenade total for TFC + int gren2; // secondary grenade total for TFC + + float f_max_speed; + float f_prev_speed; + float f_speed_check_time; + Vector v_prev_origin; + + float f_find_item; + edict_t *pBotPickupItem; + + int ladder_dir; + float f_start_use_ladder_time; + float f_end_use_ladder_time; + bool waypoint_top_of_ladder; + + float f_wall_check_time; + float f_wall_on_right; + float f_wall_on_left; + float f_dont_avoid_wall_time; + float f_look_for_waypoint_time; + float f_jump_time; + float f_drop_check_time; + + int wander_dir; + int strafe_percent; + float f_strafe_direction; // 0 = none, negative = left, positive = right + float f_strafe_time; + float f_exit_water_time; + + Vector waypoint_origin; + float f_waypoint_time; + int curr_waypoint_index; + int prev_waypoint_index[5]; + float f_random_waypoint_time; + int waypoint_goal; + float f_waypoint_goal_time; + bool waypoint_near_flag; + Vector waypoint_flag_origin; + float prev_waypoint_distance; + int weapon_points[6]; // five weapon locations + 1 null + + edict_t *pBotEnemy; + float f_bot_see_enemy_time; + float f_bot_find_enemy_time; + + Vector v_enemy_previous_origin; + float f_aim_tracking_time; + float f_aim_x_angle_delta; + float f_aim_y_angle_delta; + + edict_t *pBotUser; + float f_bot_use_time; + float f_bot_spawn_time; + + edict_t *killer_edict; + bool b_bot_say; + float f_bot_say; + char bot_say_msg[256]; + float f_bot_chat_time; + + int enemy_attack_count; + float f_duck_time; + + float f_sniper_aim_time; + + float f_shoot_time; + float f_primary_charging; + float f_secondary_charging; + int charging_weapon_id; + int grenade_time; // min time between grenade throws + float f_gren_throw_time; + float f_gren_check_time; + bool b_grenade_primed; + int grenade_type; // 0 = primary, 1 = secondary + float f_grenade_search_time; + float f_grenade_found_time; + + float f_medic_check_time; + float f_medic_pause_time; + float f_medic_yell_time; + + float f_move_speed; + float f_pause_time; + float f_sound_update_time; + bool bot_has_flag; + + bool b_see_tripmine; + bool b_shoot_tripmine; + Vector v_tripmine; + + bool b_use_health_station; + float f_use_health_time; + bool b_use_HEV_station; + float f_use_HEV_time; + + bool b_use_button; + float f_use_button_time; + bool b_lift_moving; + + bool b_use_capture; + float f_use_capture_time; + edict_t *pCaptureEdict; + + int logo_percent; + bool b_spray_logo; + float f_spray_logo_time; + char logo_name[16]; + int top_color; + int bottom_color; + + float f_engineer_build_time; + + int sentrygun_waypoint; + bool b_build_sentrygun; + int sentrygun_level; + int sentrygun_attack_count; + float f_other_sentry_time; + bool b_upgrade_sentry; + + int dispenser_waypoint; + bool b_build_dispenser; + int dispenser_built; + int dispenser_attack_count; + + float f_medic_check_health_time; + float f_heal_percent; + + float f_start_vote_time; + bool vote_in_progress; + float f_vote_time; + + int reaction_time; + float f_reaction_target_time; // time when enemy targeting starts + + bot_current_weapon_t current_weapon; // one current weapon for each bot + int m_rgAmmo[MAX_AMMO_SLOTS]; // total ammo amounts (1 array for each bot) + +} bot_t; + + +#define MAX_TEAMS 32 +#define MAX_TEAMNAME_LENGTH 16 + + +#define MAX_FLAGS 5 + +typedef struct { + edict_t *edict; + int team_no; +} FLAG_S; + +#define MAX_BACKPACKS 100 + +typedef struct { + edict_t *edict; + int armor; // 0=none + int health; // 0=none + int ammo; // 0=none + int team; // 0=all, 1=Blue, 2=Red, 3=Yellow, 4=Green +} BACKPACK_S; + + +#define MAX_SKINS 200 + +typedef struct +{ + bool skin_used; + char model_name[32]; + char bot_name[32]; +} skin_t; + +typedef struct +{ + bool can_modify; + char text[81]; +} bot_chat_t; + +typedef struct +{ + char identification[4]; // should be WAD2 (or 2DAW) or WAD3 (or 3DAW) + int numlumps; + int infotableofs; +} wadinfo_t; + +typedef struct +{ + int filepos; + int disksize; + int size; // uncompressed + char type; + char compression; + char pad1, pad2; + char name[16]; // must be null terminated +} lumpinfo_t; + + +// new UTIL.CPP functions... +edict_t *UTIL_FindEntityInSphere( edict_t *pentStart, const Vector &vecCenter, float flRadius ); +edict_t *UTIL_FindEntityByString( edict_t *pentStart, const char *szKeyword, const char *szValue ); +edict_t *UTIL_FindEntityByClassname( edict_t *pentStart, const char *szName ); +edict_t *UTIL_FindEntityByTarget( edict_t *pentStart, const char *szName ); +edict_t *UTIL_FindEntityByTargetname( edict_t *pentStart, const char *szName ); +void ClientPrint( edict_t *pEdict, int msg_dest, const char *msg_name); +void UTIL_SayText( const char *pText, edict_t *pEdict ); +void UTIL_HostSay( edict_t *pEntity, int teamonly, char *message ); +int UTIL_GetTeam(edict_t *pEntity); +int UTIL_GetClass(edict_t *pEntity); +int UTIL_GetBotIndex(edict_t *pEdict); +bot_t *UTIL_GetBotPointer(edict_t *pEdict); +bool IsAlive(edict_t *pEdict); +bool FInViewCone(Vector *pOrigin, edict_t *pEdict); +bool FVisible( const Vector &vecOrigin, edict_t *pEdict ); +Vector Center(edict_t *pEdict); +Vector GetGunPosition(edict_t *pEdict); +void UTIL_SelectItem(edict_t *pEdict, char *item_name); +void UTIL_SelectWeapon(edict_t *pEdict, int weapon_index); +Vector VecBModelOrigin(edict_t *pEdict); +bool UpdateSounds(edict_t *pEdict, edict_t *pPlayer); +void UTIL_ShowMenu( edict_t *pEdict, int slots, int displaytime, bool needmore, char *pText ); +void UTIL_BuildFileName(char *filename, char *arg1, char *arg2); +void GetGameDir (char *game_dir); + +void LoadBotChat(void); +void BotTrimBlanks(char *in_string, char *out_string); +int BotChatTrimTag(char *original_name, char *out_name); +void BotDropCharacter(char *in_string, char *out_string); +void BotSwapCharacter(char *in_string, char *out_string); +void BotChatName(char *original_name, char *out_name); +void BotChatText(char *in_text, char *out_text); +void BotChatFillInName(char *bot_say_msg, char *chat_text, char *chat_name, const char *bot_name); + +#endif // BOT_H + diff --git a/bot_chat.cpp b/bot_chat.cpp new file mode 100644 index 0000000..3525890 --- /dev/null +++ b/bot_chat.cpp @@ -0,0 +1,525 @@ +// +// HPB bot - botman's High Ping Bastard bot +// +// (http://planethalflife.com/botman/) +// +// bot_combat.cpp +// + +#ifndef _WIN32 +#include +#endif + +#include +#include +#include +#include + +#include "bot.h" + + +#define NUM_TAGS 22 + +char *tag1[NUM_TAGS]={ +"-=","-[","-]","-}","-{","<[","<]","[-","]-","{-","}-","[[","[","{","]","}","<",">","-","|","=","+"}; +char *tag2[NUM_TAGS]={ +"=-","]-","[-","{-","}-","]>","[>","-]","-[","-}","-{","]]","]","}","[","{",">","<","-","|","=","+"}; + +int bot_chat_count; +int bot_taunt_count; +int bot_whine_count; + +bot_chat_t bot_chat[MAX_BOT_CHAT]; +bot_chat_t bot_taunt[MAX_BOT_CHAT]; +bot_chat_t bot_whine[MAX_BOT_CHAT]; + +int recent_bot_chat[5]; +int recent_bot_taunt[5]; +int recent_bot_whine[5]; + +int player_count; +char player_names[32][33]; // 32 players max, 32 chars + null + +extern int bot_chat_tag_percent; +extern int bot_chat_drop_percent; +extern int bot_chat_swap_percent; +extern int bot_chat_lower_percent; + + +void LoadBotChat(void) +{ + FILE *bfp; + char filename[256]; + char buffer[256]; + char *stat; + int section = -1; + int i, length; + + bot_chat_count = 0; + bot_taunt_count = 0; + bot_whine_count = 0; + + for (i=0; i < 5; i++) + { + recent_bot_chat[i] = -1; + recent_bot_taunt[i] = -1; + recent_bot_whine[i] = -1; + } + + UTIL_BuildFileName(filename, "HPB_bot_chat.txt", NULL); + + bfp = fopen(filename, "r"); + + while (bfp != NULL) + { + stat = fgets(buffer, 80, bfp); + + if (stat == NULL) + { + fclose(bfp); + bfp = NULL; + continue; + } + + buffer[80] = 0; // truncate lines longer than 80 characters + + length = strlen(buffer); + + if (buffer[length-1] == '\n') + { + buffer[length-1] = 0; // remove '\n' + length--; + } + + if (strcmp(buffer, "[bot_chat]") == 0) + { + section = 0; + continue; + } + + if (strcmp(buffer, "[bot_taunt]") == 0) + { + section = 1; + continue; + } + + if (strcmp(buffer, "[bot_whine]") == 0) + { + section = 2; + continue; + } + + if ((length > 0) && (section == 0) && // bot chat + (bot_chat_count < MAX_BOT_CHAT)) + { + if (buffer[0] == '!') + { + strcpy(bot_chat[bot_chat_count].text, &buffer[1]); + bot_chat[bot_chat_count].can_modify = FALSE; + } + else + { + strcpy(bot_chat[bot_chat_count].text, buffer); + bot_chat[bot_chat_count].can_modify = TRUE; + } + + bot_chat_count++; + } + + if ((length > 0) && (section == 1) && // bot taunt + (bot_taunt_count < MAX_BOT_CHAT)) + { + if (buffer[0] == '!') + { + strcpy(bot_taunt[bot_taunt_count].text, &buffer[1]); + bot_taunt[bot_taunt_count].can_modify = FALSE; + } + else + { + strcpy(bot_taunt[bot_taunt_count].text, buffer); + bot_taunt[bot_taunt_count].can_modify = TRUE; + } + + bot_taunt_count++; + } + + if ((length > 0) && (section == 2) && // bot whine + (bot_whine_count < MAX_BOT_CHAT)) + { + if (buffer[0] == '!') + { + strcpy(bot_whine[bot_whine_count].text, &buffer[1]); + bot_whine[bot_whine_count].can_modify = FALSE; + } + else + { + strcpy(bot_whine[bot_whine_count].text, buffer); + bot_whine[bot_whine_count].can_modify = TRUE; + } + + bot_whine_count++; + } + } +} + + +void BotTrimBlanks(char *in_string, char *out_string) +{ + int i, pos; + char *dest; + + pos=0; + while ((pos < 80) && (in_string[pos] == ' ')) // skip leading blanks + pos++; + + dest=&out_string[0]; + + while ((pos < 80) && (in_string[pos])) + { + *dest++ = in_string[pos]; + pos++; + } + *dest = 0; // store the null + + i = strlen(out_string) - 1; + while ((i > 0) && (out_string[i] == ' ')) // remove trailing blanks + { + out_string[i] = 0; + i--; + } +} + + +int BotChatTrimTag(char *original_name, char *out_name) +{ + int i; + char *pos1, *pos2, *src, *dest; + char in_name[80]; + int result = 0; + + strncpy(in_name, original_name, 31); + in_name[32] = 0; + + for (i=0; i < NUM_TAGS; i++) + { + pos1=strstr(in_name, tag1[i]); + if (pos1) + pos2=strstr(pos1+strlen(tag1[i]), tag2[i]); + else + pos2 = NULL; + + if (pos1 && pos2 && pos1 < pos2) + { + src = pos2+strlen(tag2[i]); + dest = pos1; + while (*src) + *dest++ = *src++; + *dest = *src; // copy the null; + + result = 1; + } + } + + strcpy(out_name, in_name); + + BotTrimBlanks(out_name, in_name); + + if (strlen(in_name) == 0) // is name just a tag? + { + strncpy(in_name, original_name, 31); + in_name[32] = 0; + + // strip just the tag part... + for (i=0; i < NUM_TAGS; i++) + { + pos1=strstr(in_name, tag1[i]); + if (pos1) + pos2=strstr(pos1+strlen(tag1[i]), tag2[i]); + else + pos2 = NULL; + + if (pos1 && pos2 && pos1 < pos2) + { + src = pos1 + strlen(tag1[i]); + dest = pos1; + while (*src) + *dest++ = *src++; + *dest = *src; // copy the null; + + src = pos2 - strlen(tag2[i]); + *src = 0; // null out the rest of the string + } + } + } + + BotTrimBlanks(in_name, out_name); + + out_name[31] = 0; + + return (result); +} + + +void BotDropCharacter(char *in_string, char *out_string) +{ + int len, pos; + int count = 0; + char *src, *dest; + bool is_bad; + + strcpy(out_string, in_string); + + len = strlen(out_string); + pos = RANDOM_LONG(1, len-1); // don't drop position zero + + is_bad = !isalpha(out_string[pos]) || (out_string[pos-1] == '%'); + + while ((is_bad) && (count < 20)) + { + pos = RANDOM_LONG(1, len-1); + is_bad = !isalpha(out_string[pos]) || (out_string[pos-1] == '%'); + count++; + } + + if (count < 20) + { + src = &out_string[pos+1]; + dest = &out_string[pos]; + while (*src) + *dest++ = *src++; + *dest = *src; // copy the null; + } +} + + +void BotSwapCharacter(char *in_string, char *out_string) +{ + int len, pos; + int count = 0; + char temp; + bool is_bad; + + strcpy(out_string, in_string); + + len = strlen(out_string); + pos = RANDOM_LONG(1, len-2); // don't swap position zero + + is_bad = !isalpha(out_string[pos]) || !isalpha(out_string[pos+1]) || + (out_string[pos-1] == '%'); + + while ((is_bad) && (count < 20)) + { + pos = RANDOM_LONG(1, len-2); + is_bad = !isalpha(out_string[pos]) || !isalpha(out_string[pos+1]) || + (out_string[pos-1] == '%'); + count++; + } + + if (count < 20) + { + temp = out_string[pos]; + out_string[pos] = out_string[pos+1]; + out_string[pos+1] = temp; + } +} + + +void BotChatName(char *original_name, char *out_name) +{ + int pos; + + if (RANDOM_LONG(1, 100) <= bot_chat_tag_percent) + { + char temp_name[80]; + + strncpy(temp_name, original_name, 31); + temp_name[31] = 0; + + while (BotChatTrimTag(temp_name, out_name)) + { + strcpy(temp_name, out_name); + } + } + else + { + strncpy(out_name, original_name, 31); + out_name[31] = 0; + } + + if (RANDOM_LONG(1, 100) <= bot_chat_lower_percent) + { + pos=0; + while ((pos < 80) && (out_name[pos])) + { + out_name[pos] = tolower(out_name[pos]); + pos++; + } + } +} + + +void BotChatText(char *in_text, char *out_text) +{ + int pos; + char temp_text[81]; + int count; + + strncpy(temp_text, in_text, 79); + temp_text[80] = 0; + + if (RANDOM_LONG(1, 100) <= bot_chat_drop_percent) + { + count = RANDOM_LONG(1, 3); + + while (count) + { + BotDropCharacter(temp_text, out_text); + strcpy(temp_text, out_text); + count--; + } + } + + if (RANDOM_LONG(1, 100) <= bot_chat_swap_percent) + { + count = RANDOM_LONG(1, 2); + + while (count) + { + BotSwapCharacter(temp_text, out_text); + strcpy(temp_text, out_text); + count--; + } + } + + if (RANDOM_LONG(1, 100) <= bot_chat_lower_percent) + { + pos=0; + while (temp_text[pos]) + { + temp_text[pos] = tolower(temp_text[pos]); + pos++; + } + } + + strcpy(out_text, temp_text); +} + + +void BotChatGetPlayers(void) +{ + int index; + const char *pName; + + player_count = 0; + + for (index = 1; index <= gpGlobals->maxClients; index++) + { + edict_t *pPlayer = INDEXENT(index); + + // skip invalid players + if ((pPlayer) && (!pPlayer->free)) + { + if (pPlayer->v.netname) + { + pName = STRING(pPlayer->v.netname); + + if (*pName != 0) + { + strncpy(player_names[player_count], pName, 32); + + player_count++; + } + } + } + } +} + + +void BotChatFillInName(char *bot_say_msg, char *chat_text, + char *chat_name, const char *bot_name) +{ + int chat_index, say_index; + char *name_pos, *rand_pos; + char random_name[64]; + int index, name_offset, rand_offset; + bool is_bad; + + chat_index = 0; + say_index = 0; + bot_say_msg[0] = 0; + + name_pos = strstr(&chat_text[chat_index], "%n"); + rand_pos = strstr(&chat_text[chat_index], "%r"); + + while ((name_pos != NULL) || (rand_pos != NULL)) + { + if (name_pos != NULL) + name_offset = name_pos - chat_text; + if (rand_pos != NULL) + rand_offset = rand_pos - chat_text; + + if ((rand_pos == NULL) || + ((name_offset < rand_offset) && (name_pos != NULL))) + { + while (&chat_text[chat_index] < name_pos) + bot_say_msg[say_index++] = chat_text[chat_index++]; + + bot_say_msg[say_index] = 0; // add null terminator + + chat_index += 2; // skip the "%n" + + strcat(bot_say_msg, chat_name); + say_index += strlen(chat_name); + + bot_say_msg[say_index] = 0; + } + else // use random player name... + { + int count = 0; + + BotChatGetPlayers(); + + // pick a name at random from the list of players... + + index = RANDOM_LONG(0, player_count-1); + + is_bad = (strcmp(player_names[index], chat_name) == 0) || + (strcmp(player_names[index], bot_name) == 0); + + while ((is_bad) && (count < 20)) + { + index = RANDOM_LONG(0, player_count-1); + + is_bad = (strcmp(player_names[index], chat_name) == 0) || + (strcmp(player_names[index], bot_name) == 0); + + count++; + } + + BotChatName(player_names[index], random_name); + + while (&chat_text[chat_index] < rand_pos) + bot_say_msg[say_index++] = chat_text[chat_index++]; + + bot_say_msg[say_index] = 0; // add null terminator + + chat_index += 2; // skip the "%r" + + strcat(bot_say_msg, random_name); + say_index += strlen(random_name); + + bot_say_msg[say_index] = 0; + } + + name_pos = strstr(&chat_text[chat_index], "%n"); + rand_pos = strstr(&chat_text[chat_index], "%r"); + } + + // copy the rest of the chat_text into the bot_say_msg... + + while (chat_text[chat_index]) + bot_say_msg[say_index++] = chat_text[chat_index++]; + + bot_say_msg[say_index] = 0; // add null terminator +} + diff --git a/bot_client.cpp b/bot_client.cpp new file mode 100644 index 0000000..0387a36 --- /dev/null +++ b/bot_client.cpp @@ -0,0 +1,1173 @@ +// +// HPB bot - botman's High Ping Bastard bot +// +// (http://planethalflife.com/botman/) +// +// bot_client.cpp +// + +#ifndef _WIN32 +#include +#endif + +#include +#include +#include +#include + +#include "bot.h" +#include "bot_func.h" +#include "bot_client.h" +#include "bot_weapons.h" + +// types of damage to ignore... +#define IGNORE_DAMAGE (DMG_CRUSH | DMG_BURN | DMG_FREEZE | DMG_FALL | \ + DMG_SHOCK | DMG_DROWN | DMG_NERVEGAS | DMG_RADIATION | \ + DMG_DROWNRECOVER | DMG_ACID | DMG_SLOWBURN | \ + DMG_SLOWFREEZE | 0xFF000000) + +extern int mod_id; +extern bot_t bots[32]; +extern int num_logos; +extern edict_t *holywars_saint; +extern int halo_status; +extern int holywars_gamemode; + +extern int bot_taunt_count; +extern int recent_bot_taunt[]; +extern bot_chat_t bot_taunt[MAX_BOT_CHAT]; + +bot_weapon_t weapon_defs[MAX_WEAPONS]; // array of weapon definitions + + +// This message is sent when the TFC VGUI menu is displayed. +void BotClient_TFC_VGUI(void *p, int bot_index) +{ + static int state = 0; // current state machine state + + if (state == 0) + { + if ((*(int *)p) == 2) // is it a team select menu? + + bots[bot_index].start_action = MSG_TFC_TEAM_SELECT; + + else if ((*(int *)p) == 3) // is is a class selection menu? + + bots[bot_index].start_action = MSG_TFC_CLASS_SELECT; + } + + state++; + + if (state == 1) + state = 0; +} + + +// This message is sent when the Counter-Strike VGUI menu is displayed. +void BotClient_CS_VGUI(void *p, int bot_index) +{ + static int state = 0; // current state machine state + + if (state == 0) + { + if ((*(int *)p) == 2) // is it a team select menu? + + bots[bot_index].start_action = MSG_CS_TEAM_SELECT; + + else if ((*(int *)p) == 26) // is is a terrorist model select menu? + + bots[bot_index].start_action = MSG_CS_T_SELECT; + + else if ((*(int *)p) == 27) // is is a counter-terrorist model select menu? + + bots[bot_index].start_action = MSG_CS_CT_SELECT; + } + + state++; + + if (state == 5) // ignore other fields in VGUI message + state = 0; +} + + +// This message is sent when a menu is being displayed in Counter-Strike. +void BotClient_CS_ShowMenu(void *p, int bot_index) +{ + static int state = 0; // current state machine state + + if (state < 3) + { + state++; // ignore first 3 fields of message + return; + } + + if (strcmp((char *)p, "#Team_Select") == 0) // team select menu? + { + bots[bot_index].start_action = MSG_CS_TEAM_SELECT; + } + else if (strcmp((char *)p, "#Terrorist_Select") == 0) // T model select? + { + bots[bot_index].start_action = MSG_CS_T_SELECT; + } + else if (strcmp((char *)p, "#CT_Select") == 0) // CT model select menu? + { + bots[bot_index].start_action = MSG_CS_CT_SELECT; + } + + state = 0; // reset state machine +} + + +// This message is sent when the OpFor VGUI menu is displayed. +void BotClient_Gearbox_VGUI(void *p, int bot_index) +{ + static int state = 0; // current state machine state + + if (state == 0) + { + if ((*(int *)p) == 2) // is it a team select menu? + + bots[bot_index].start_action = MSG_OPFOR_TEAM_SELECT; + + else if ((*(int *)p) == 3) // is is a class selection menu? + + bots[bot_index].start_action = MSG_OPFOR_CLASS_SELECT; + } + + state++; + + if (state == 1) + state = 0; +} + + +// This message is sent when the FrontLineForce VGUI menu is displayed. +void BotClient_FLF_VGUI(void *p, int bot_index) +{ + static int state = 0; // current state machine state + + if (p == NULL) // handle pfnMessageEnd case + { + state = 0; + return; + } + + if (state == 0) + { + if ((*(int *)p) == 2) // is it a team select menu? + bots[bot_index].start_action = MSG_FLF_TEAM_SELECT; + else if ((*(int *)p) == 3) // is it a class selection menu? + bots[bot_index].start_action = MSG_FLF_CLASS_SELECT; + else if ((*(int *)p) == 70) // is it a weapon selection menu? + bots[bot_index].start_action = MSG_FLF_WEAPON_SELECT; + else if ((*(int *)p) == 72) // is it a submachine gun selection menu? + bots[bot_index].start_action = MSG_FLF_SUBMACHINE_SELECT; + else if ((*(int *)p) == 73) // is it a shotgun selection menu? + bots[bot_index].start_action = MSG_FLF_SHOTGUN_SELECT; + else if ((*(int *)p) == 75) // is it a rifle selection menu? + bots[bot_index].start_action = MSG_FLF_RIFLE_SELECT; + else if ((*(int *)p) == 76) // is it a pistol selection menu? + bots[bot_index].start_action = MSG_FLF_PISTOL_SELECT; + else if ((*(int *)p) == 78) // is it a heavyweapons selection menu? + bots[bot_index].start_action = MSG_FLF_HEAVYWEAPONS_SELECT; + } + + state++; +} + + +// 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. +void BotClient_Valve_WeaponList(void *p, int bot_index) +{ + static int state = 0; // current state machine state + static bot_weapon_t bot_weapon; + + if (state == 0) + { + state++; + strcpy(bot_weapon.szClassname, (char *)p); + } + else if (state == 1) + { + state++; + bot_weapon.iAmmo1 = *(int *)p; // ammo index 1 + } + else if (state == 2) + { + state++; + bot_weapon.iAmmo1Max = *(int *)p; // max ammo1 + } + else if (state == 3) + { + state++; + bot_weapon.iAmmo2 = *(int *)p; // ammo index 2 + } + else if (state == 4) + { + state++; + bot_weapon.iAmmo2Max = *(int *)p; // max ammo2 + } + else if (state == 5) + { + state++; + bot_weapon.iSlot = *(int *)p; // slot for this weapon + } + else if (state == 6) + { + state++; + bot_weapon.iPosition = *(int *)p; // position in slot + } + else if (state == 7) + { + state++; + bot_weapon.iId = *(int *)p; // weapon ID + } + else if (state == 8) + { + state = 0; + + bot_weapon.iFlags = *(int *)p; // flags for weapon (WTF???) + + // store away this weapon with it's ammo information... + if (mod_id == DMC_DLL) + weapon_defs[bot_weapon.iSlot] = bot_weapon; + else + weapon_defs[bot_weapon.iId] = bot_weapon; + } +} + +void BotClient_TFC_WeaponList(void *p, int bot_index) +{ + // this is just like the Valve Weapon List message + BotClient_Valve_WeaponList(p, bot_index); +} + +void BotClient_CS_WeaponList(void *p, int bot_index) +{ + // this is just like the Valve Weapon List message + BotClient_Valve_WeaponList(p, bot_index); +} + +void BotClient_Gearbox_WeaponList(void *p, int bot_index) +{ + // this is just like the Valve Weapon List message + BotClient_Valve_WeaponList(p, bot_index); +} + +void BotClient_FLF_WeaponList(void *p, int bot_index) +{ + // this is just like the Valve Weapon List message + BotClient_Valve_WeaponList(p, bot_index); +} + +void BotClient_DMC_WeaponList(void *p, int bot_index) +{ + // this is just like the Valve Weapon List message + BotClient_Valve_WeaponList(p, bot_index); +} + +// 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). +void BotClient_Valve_CurrentWeapon(void *p, int bot_index) +{ + static int state = 0; // current state machine state + static int iState; + static int iId; + static int iClip; + + if (state == 0) + { + state++; + iState = *(int *)p; // state of the current weapon + } + else if (state == 1) + { + state++; + iId = *(int *)p; // weapon ID of current weapon + } + else if (state == 2) + { + state = 0; + + iClip = *(int *)p; // ammo currently in the clip for this weapon + + if (mod_id == DMC_DLL) + { + if ((iState == 1) && (iId <= 128)) + bots[bot_index].pEdict->v.weapons |= iId; // BUGFIX: since DMC doesn't use the "weapons" bitmap in entvars_t, we need to fill it ourselves -- Pierre-Marie Baty + } + else + { + if (iId <= 31) + { + if (iState == 1) + { + bots[bot_index].current_weapon.iId = iId; + bots[bot_index].current_weapon.iClip = iClip; + + // update the ammo counts for this weapon... + bots[bot_index].current_weapon.iAmmo1 = + bots[bot_index].m_rgAmmo[weapon_defs[iId].iAmmo1]; + bots[bot_index].current_weapon.iAmmo2 = + bots[bot_index].m_rgAmmo[weapon_defs[iId].iAmmo2]; + } + } + } + } +} + +void BotClient_TFC_CurrentWeapon(void *p, int bot_index) +{ + // this is just like the Valve Current Weapon message + BotClient_Valve_CurrentWeapon(p, bot_index); +} + +void BotClient_CS_CurrentWeapon(void *p, int bot_index) +{ + // this is just like the Valve Current Weapon message + BotClient_Valve_CurrentWeapon(p, bot_index); +} + +void BotClient_Gearbox_CurrentWeapon(void *p, int bot_index) +{ + // this is just like the Valve Current Weapon message + BotClient_Valve_CurrentWeapon(p, bot_index); +} + +void BotClient_FLF_CurrentWeapon(void *p, int bot_index) +{ + // this is just like the Valve Current Weapon message + BotClient_Valve_CurrentWeapon(p, bot_index); +} + +void BotClient_DMC_CurrentWeapon(void *p, int bot_index) +{ + // this is just like the Valve Current Weapon message + BotClient_Valve_CurrentWeapon(p, bot_index); +} + + +void BotClient_DMC_QItems (void *p, int bot_index) +{ + // this DMC-specific message updates a client data with all the stuff the player is carrying, + // be it weapons, armor, powerups and ammo, each item being represented by a bit in a long + // integer bitmap. The first 8 (LSB) bits are the weapons. + + // since DMC doesn't use the "weapons" bitmap in entvars_t, we need to fill it ourselves + bots[bot_index].pEdict->v.weapons |= (*(int *) p & 0x000000FF); // lazy Valve programmers... +} + + +// This message is sent whenever ammo ammounts are adjusted (up or down). +void BotClient_Valve_AmmoX(void *p, int bot_index) +{ + static int state = 0; // current state machine state + static int index; + static int ammount; + int ammo_index; + + if (state == 0) + { + state++; + index = *(int *)p; // ammo index (for type of ammo) + } + else if (state == 1) + { + state = 0; + + ammount = *(int *)p; // the ammount of ammo currently available + + bots[bot_index].m_rgAmmo[index] = ammount; // store it away + + ammo_index = bots[bot_index].current_weapon.iId; + + // update the ammo counts for this weapon... + bots[bot_index].current_weapon.iAmmo1 = + bots[bot_index].m_rgAmmo[weapon_defs[ammo_index].iAmmo1]; + bots[bot_index].current_weapon.iAmmo2 = + bots[bot_index].m_rgAmmo[weapon_defs[ammo_index].iAmmo2]; + } +} + +void BotClient_TFC_AmmoX(void *p, int bot_index) +{ + // this is just like the Valve AmmoX message + BotClient_Valve_AmmoX(p, bot_index); +} + +void BotClient_CS_AmmoX(void *p, int bot_index) +{ + // this is just like the Valve AmmoX message + BotClient_Valve_AmmoX(p, bot_index); +} + +void BotClient_Gearbox_AmmoX(void *p, int bot_index) +{ + // this is just like the Valve AmmoX message + BotClient_Valve_AmmoX(p, bot_index); +} + +void BotClient_FLF_AmmoX(void *p, int bot_index) +{ + // this is just like the Valve AmmoX message + BotClient_Valve_AmmoX(p, bot_index); +} + +void BotClient_DMC_AmmoX(void *p, int bot_index) +{ + // this is just like the Valve AmmoX message + BotClient_Valve_AmmoX(p, bot_index); +} + +// 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. +void BotClient_Valve_AmmoPickup(void *p, int bot_index) +{ + static int state = 0; // current state machine state + static int index; + static int ammount; + int ammo_index; + + if (state == 0) + { + state++; + index = *(int *)p; + } + else if (state == 1) + { + state = 0; + + ammount = *(int *)p; + + bots[bot_index].m_rgAmmo[index] = ammount; + + ammo_index = bots[bot_index].current_weapon.iId; + + // update the ammo counts for this weapon... + bots[bot_index].current_weapon.iAmmo1 = + bots[bot_index].m_rgAmmo[weapon_defs[ammo_index].iAmmo1]; + bots[bot_index].current_weapon.iAmmo2 = + bots[bot_index].m_rgAmmo[weapon_defs[ammo_index].iAmmo2]; + } +} + +void BotClient_TFC_AmmoPickup(void *p, int bot_index) +{ + // this is just like the Valve Ammo Pickup message + BotClient_Valve_AmmoPickup(p, bot_index); +} + +void BotClient_CS_AmmoPickup(void *p, int bot_index) +{ + // this is just like the Valve Ammo Pickup message + BotClient_Valve_AmmoPickup(p, bot_index); +} + +void BotClient_Gearbox_AmmoPickup(void *p, int bot_index) +{ + // this is just like the Valve Ammo Pickup message + BotClient_Valve_AmmoPickup(p, bot_index); +} + +void BotClient_FLF_AmmoPickup(void *p, int bot_index) +{ + // this is just like the Valve Ammo Pickup message + BotClient_Valve_AmmoPickup(p, bot_index); +} + +void BotClient_DMC_AmmoPickup(void *p, int bot_index) +{ + // this is just like the Valve Ammo Pickup message + BotClient_Valve_AmmoPickup(p, bot_index); +} + +// This message is sent whenever grenade ammounts are adjusted. +void BotClient_TFC_SecAmmoVal(void *p, int bot_index) +{ + static int state = 0; // current state machine state + static int type; + static int ammount; + + if (state == 0) + { + state++; + type = *(int *)p; // 0 = primary, 1 = secondary + } + else if (state == 1) + { + state = 0; + + ammount = *(int *)p; // the ammount of ammo currently available + + if (type == 0) + bots[bot_index].gren1 = ammount; // store it away + else if (type == 1) + bots[bot_index].gren2 = ammount; // store it away + } +} + + +// This message gets sent when the bot picks up a weapon. +void BotClient_Valve_WeaponPickup(void *p, int bot_index) +{ +} + +void BotClient_TFC_WeaponPickup(void *p, int bot_index) +{ + // this is just like the Valve Weapon Pickup message + BotClient_Valve_WeaponPickup(p, bot_index); +} + +void BotClient_CS_WeaponPickup(void *p, int bot_index) +{ + // this is just like the Valve Weapon Pickup message + BotClient_Valve_WeaponPickup(p, bot_index); +} + +void BotClient_Gearbox_WeaponPickup(void *p, int bot_index) +{ + // this is just like the Valve Weapon Pickup message + BotClient_Valve_WeaponPickup(p, bot_index); +} + +void BotClient_FLF_WeaponPickup(void *p, int bot_index) +{ + // this is just like the Valve Weapon Pickup message + BotClient_Valve_WeaponPickup(p, bot_index); +} + +void BotClient_DMC_WeaponPickup(void *p, int bot_index) +{ + // this is just like the Valve Weapon Pickup message + BotClient_Valve_WeaponPickup(p, bot_index); +} + +// This message gets sent when the bot picks up an item (like a battery +// or a healthkit) +void BotClient_Valve_ItemPickup(void *p, int bot_index) +{ +} + +void BotClient_TFC_ItemPickup(void *p, int bot_index) +{ + // this is just like the Valve Item Pickup message + BotClient_Valve_ItemPickup(p, bot_index); +} + +void BotClient_CS_ItemPickup(void *p, int bot_index) +{ + // this is just like the Valve Item Pickup message + BotClient_Valve_ItemPickup(p, bot_index); +} + +void BotClient_Gearbox_ItemPickup(void *p, int bot_index) +{ + // this is just like the Valve Item Pickup message + BotClient_Valve_ItemPickup(p, bot_index); +} + +void BotClient_FLF_ItemPickup(void *p, int bot_index) +{ + // this is just like the Valve Item Pickup message + BotClient_Valve_ItemPickup(p, bot_index); +} + +void BotClient_DMC_ItemPickup(void *p, int bot_index) +{ + // this is just like the Valve Item Pickup message + BotClient_Valve_ItemPickup(p, bot_index); +} + +// This message gets sent when the bots health changes. +void BotClient_Valve_Health(void *p, int bot_index) +{ +} + +void BotClient_TFC_Health(void *p, int bot_index) +{ + // this is just like the Valve Health message + BotClient_Valve_Health(p, bot_index); +} + +void BotClient_CS_Health(void *p, int bot_index) +{ + // this is just like the Valve Health message + BotClient_Valve_Health(p, bot_index); +} + +void BotClient_Gearbox_Health(void *p, int bot_index) +{ + // this is just like the Valve Health message + BotClient_Valve_Health(p, bot_index); +} + +void BotClient_FLF_Health(void *p, int bot_index) +{ + // this is just like the Valve Health message + BotClient_Valve_Health(p, bot_index); +} + +void BotClient_DMC_Health(void *p, int bot_index) +{ + // this is just like the Valve Health message + BotClient_Valve_Health(p, bot_index); +} + +// This message gets sent when the bots armor changes. +void BotClient_Valve_Battery(void *p, int bot_index) +{ +} + +void BotClient_TFC_Battery(void *p, int bot_index) +{ + // this is just like the Valve Battery message + BotClient_Valve_Battery(p, bot_index); +} + +void BotClient_CS_Battery(void *p, int bot_index) +{ + // this is just like the Valve Battery message + BotClient_Valve_Battery(p, bot_index); +} + +void BotClient_Gearbox_Battery(void *p, int bot_index) +{ + // this is just like the Valve Battery message + BotClient_Valve_Battery(p, bot_index); +} + +void BotClient_FLF_Battery(void *p, int bot_index) +{ + // this is just like the Valve Battery message + BotClient_Valve_Battery(p, bot_index); +} + +void BotClient_DMC_Battery(void *p, int bot_index) +{ + // this is just like the Valve Battery message + BotClient_Valve_Battery(p, bot_index); +} + +// This message gets sent when the bots are getting damaged. +void BotClient_Valve_Damage(void *p, int bot_index) +{ + static int state = 0; // current state machine state + static int damage_armor; + static int damage_taken; + static int damage_bits; // type of damage being done + static Vector damage_origin; + + if (state == 0) + { + state++; + damage_armor = *(int *)p; + } + else if (state == 1) + { + state++; + damage_taken = *(int *)p; + } + else if (state == 2) + { + state++; + damage_bits = *(int *)p; + } + else if (state == 3) + { + state++; + damage_origin.x = *(float *)p; + } + else if (state == 4) + { + state++; + damage_origin.y = *(float *)p; + } + else if (state == 5) + { + state = 0; + + damage_origin.z = *(float *)p; + + if ((damage_armor > 0) || (damage_taken > 0)) + { + // ignore certain types of damage... + if (damage_bits & IGNORE_DAMAGE) + return; + + // if the bot doesn't have an enemy and someone is shooting at it then + // turn in the attacker's direction... + if (bots[bot_index].pBotEnemy == NULL) + { + // face the attacker... + Vector v_enemy = damage_origin - bots[bot_index].pEdict->v.origin; + Vector bot_angles = UTIL_VecToAngles( v_enemy ); + + bots[bot_index].pEdict->v.ideal_yaw = bot_angles.y; + + BotFixIdealYaw(bots[bot_index].pEdict); + + // stop using health or HEV stations... + bots[bot_index].b_use_health_station = FALSE; + bots[bot_index].b_use_HEV_station = FALSE; + bots[bot_index].b_use_capture = FALSE; + } + } + } +} + +void BotClient_TFC_Damage(void *p, int bot_index) +{ + // this is just like the Valve Damage message + BotClient_Valve_Damage(p, bot_index); +} + +void BotClient_CS_Damage(void *p, int bot_index) +{ + // this is just like the Valve Damage message + BotClient_Valve_Damage(p, bot_index); +} + +void BotClient_Gearbox_Damage(void *p, int bot_index) +{ + // this is just like the Valve Damage message + BotClient_Valve_Damage(p, bot_index); +} + +void BotClient_FLF_Damage(void *p, int bot_index) +{ + // this is just like the Valve Damage message + BotClient_Valve_Damage(p, bot_index); +} + +void BotClient_DMC_Damage(void *p, int bot_index) +{ + // this is just like the Valve Damage message + BotClient_Valve_Damage(p, bot_index); +} + +// This message gets sent when the bots money ammount changes (for CS) +void BotClient_CS_Money(void *p, int bot_index) +{ + static int state = 0; // current state machine state + + if (state == 0) + { + state++; + + bots[bot_index].bot_money = *(int *)p; // amount of money + } + else + { + state = 0; // ingore this field + } +} + + +// This message gets sent when the bots get killed +void BotClient_Valve_DeathMsg(void *p, int bot_index) +{ + static int state = 0; // current state machine state + static int killer_index; + static int victim_index; + static edict_t *killer_edict; + static edict_t *victim_edict; + static int index; + + char chat_text[81]; + char chat_name[64]; + char temp_name[64]; + const char *bot_name; + + if (state == 0) + { + state++; + killer_index = *(int *)p; // ENTINDEX() of killer + } + else if (state == 1) + { + state++; + victim_index = *(int *)p; // ENTINDEX() of victim + } + else if (state == 2) + { + state = 0; + + killer_edict = INDEXENT(killer_index); + victim_edict = INDEXENT(victim_index); + + // get the bot index of the killer... + index = UTIL_GetBotIndex(killer_edict); + + // is this message about a bot killing someone? + if (index != -1) + { + if (killer_index != victim_index) // didn't kill self... + { + if ((RANDOM_LONG(1, 100) <= bots[index].logo_percent) && (num_logos)) + { + bots[index].b_spray_logo = TRUE; // this bot should spray logo now + bots[index].f_spray_logo_time = gpGlobals->time; + } + } + + if (victim_edict != NULL) + { + // are there any taunt messages and should the bot taunt? + if ((bot_taunt_count > 0) && + (RANDOM_LONG(1,100) <= bots[index].taunt_percent)) + { + int taunt_index; + bool used; + int i, recent_count; + + // set chat flag and time to chat... + bots[index].b_bot_say = TRUE; + bots[index].f_bot_say = gpGlobals->time + 5.0 + RANDOM_FLOAT(0.0, 5.0); + + recent_count = 0; + + while (recent_count < 5) + { + taunt_index = RANDOM_LONG(0, bot_taunt_count-1); + + used = FALSE; + + for (i=0; i < 5; i++) + { + if (recent_bot_taunt[i] == taunt_index) + used = TRUE; + } + + if (used) + recent_count++; + else + break; + } + + for (i=4; i > 0; i--) + recent_bot_taunt[i] = recent_bot_taunt[i-1]; + + recent_bot_taunt[0] = taunt_index; + + if (bot_taunt[taunt_index].can_modify) + BotChatText(bot_taunt[taunt_index].text, chat_text); + else + strcpy(chat_text, bot_taunt[taunt_index].text); + + if (victim_edict->v.netname) + { + strncpy(temp_name, STRING(victim_edict->v.netname), 31); + temp_name[31] = 0; + + BotChatName(temp_name, chat_name); + } + else + strcpy(chat_name, "NULL"); + + bot_name = STRING(bots[index].pEdict->v.netname); + + BotChatFillInName(bots[index].bot_say_msg, chat_text, chat_name, bot_name); + } + } + } + + // get the bot index of the victim... + index = UTIL_GetBotIndex(victim_edict); + + // is this message about a bot being killed? + if (index != -1) + { + if ((killer_index == 0) || (killer_index == victim_index)) + { + // bot killed by world (worldspawn) or bot killed self... + bots[index].killer_edict = NULL; + } + else + { + // store edict of player that killed this bot... + bots[index].killer_edict = INDEXENT(killer_index); + } + } + } +} + +void BotClient_TFC_DeathMsg(void *p, int bot_index) +{ + // this is just like the Valve DeathMsg message + BotClient_Valve_DeathMsg(p, bot_index); +} + +void BotClient_CS_DeathMsg(void *p, int bot_index) +{ + // this is just like the Valve DeathMsg message + BotClient_Valve_DeathMsg(p, bot_index); +} + +void BotClient_Gearbox_DeathMsg(void *p, int bot_index) +{ + // this is just like the Valve DeathMsg message + BotClient_Valve_DeathMsg(p, bot_index); +} + +void BotClient_FLF_DeathMsg(void *p, int bot_index) +{ + // this is just like the Valve DeathMsg message + BotClient_Valve_DeathMsg(p, bot_index); +} + +void BotClient_DMC_DeathMsg(void *p, int bot_index) +{ + // this is just like the Valve DeathMsg message + BotClient_Valve_DeathMsg(p, bot_index); +} + +// This message gets sent when a text message is displayed +void BotClient_TFC_TextMsg(void *p, int bot_index) +{ + static int state = 0; // current state machine state + static int msg_dest = 0; + + if (p == NULL) // handle pfnMessageEnd case + { + state = 0; + return; + } + + if (state == 0) + { + state++; + msg_dest = *(int *)p; // HUD_PRINTCENTER, etc. + } + else if (state == 1) + { + if (strcmp((char *)p, "#Sentry_finish") == 0) // sentry gun built + { + bots[bot_index].sentrygun_level = 1; + } + else if (strcmp((char *)p, "#Sentry_upgrade") == 0) // sentry gun upgraded + { + bots[bot_index].sentrygun_level += 1; + + bots[bot_index].pBotEnemy = NULL; // don't attack it anymore + bots[bot_index].enemy_attack_count = 0; + } + else if (strcmp((char *)p, "#Sentry_destroyed") == 0) // sentry gun destroyed + { + bots[bot_index].sentrygun_waypoint = -1; + bots[bot_index].sentrygun_level = 0; + } + else if (strcmp((char *)p, "#Dispenser_finish") == 0) // dispenser built + { + bots[bot_index].dispenser_built = 1; + } + else if (strcmp((char *)p, "#Dispenser_destroyed") == 0) // dispenser destroyed + { + bots[bot_index].dispenser_waypoint = -1; + bots[bot_index].dispenser_built = 0; + } + } +} + +// This message gets sent when a text message is displayed +void BotClient_FLF_TextMsg(void *p, int bot_index) +{ + static int state = 0; // current state machine state + static int msg_dest = 0; + + if (p == NULL) + { + state = 0; + return; + } + + if (state == 0) + { + state++; + msg_dest = *(int *)p; // HUD_PRINTCENTER, etc. + } + else if (state == 1) + { + if (strcmp((char *)p, "You are Attacking\n") == 0) // attacker msg + { + bots[bot_index].defender = 0; // attacker + } + else if (strcmp((char *)p, "You are Defending\n") == 0) // defender msg + { + bots[bot_index].defender = 1; // defender + } + } +} + + +// This message gets sent when the WarmUpTime is enabled/disabled +void BotClient_FLF_WarmUp(void *p, int bot_index) +{ + bots[bot_index].warmup = *(int *)p; +} + + +// This message gets sent to ALL when the WarmUpTime is enabled/disabled +void BotClient_FLF_WarmUpAll(void *p, int bot_index) +{ + for (int i=0; i < 32; i++) + { + if (bots[i].is_used) // count the number of bots in use + bots[i].warmup = *(int *)p; + } +} + + +// This message gets sent when the round is over +void BotClient_FLF_WinMessage(void *p, int bot_index) +{ + for (int i=0; i < 32; i++) + { + if (bots[i].is_used) // count the number of bots in use + bots[i].round_end = 1; + } +} + + +// This message gets sent when a weapon is hidden or restored +void BotClient_FLF_HideWeapon(void *p, int bot_index) +{ + int hide; + + hide = *(int *)p; + + if ((hide == 0) && (bots[bot_index].b_use_capture)) + { + bots[bot_index].b_use_capture = FALSE; + bots[bot_index].f_use_capture_time = 0.0; + } + + if ((hide) && (bots[bot_index].b_use_capture)) + bots[bot_index].f_use_capture_time = gpGlobals->time + 30; +} + + +void BotClient_Valve_ScreenFade(void *p, int bot_index) +{ + static int state = 0; // current state machine state + static int duration; + static int hold_time; + static int fade_flags; + int length; + + if (state == 0) + { + state++; + duration = *(int *)p; + } + else if (state == 1) + { + state++; + hold_time = *(int *)p; + } + else if (state == 2) + { + state++; + fade_flags = *(int *)p; + } + else if (state == 6) + { + state = 0; + + length = (duration + hold_time) / 4096; + bots[bot_index].blinded_time = gpGlobals->time + length - 2.0; + } + else + { + state++; + } +} + +void BotClient_TFC_ScreenFade(void *p, int bot_index) +{ + // this is just like the Valve ScreenFade message + BotClient_Valve_ScreenFade(p, bot_index); +} + +void BotClient_CS_ScreenFade(void *p, int bot_index) +{ + // this is just like the Valve ScreenFade message + BotClient_Valve_ScreenFade(p, bot_index); +} + +void BotClient_Gearbox_ScreenFade(void *p, int bot_index) +{ + // this is just like the Valve ScreenFade message + BotClient_Valve_ScreenFade(p, bot_index); +} + +void BotClient_FLF_ScreenFade(void *p, int bot_index) +{ + // this is just like the Valve ScreenFade message + BotClient_Valve_ScreenFade(p, bot_index); +} + + +void BotClient_HolyWars_Halo(void *p, int edict) +{ + int type = *(int *)p; + + if (type == 0) // wait for halo to respawn + { + holywars_saint = NULL; + halo_status = HW_WAIT_SPAWN; + } + else if (type == 1) // there's no saint + { + holywars_saint = NULL; + } + else if (type == 2) // there's a new saint + { + halo_status = HW_NEW_SAINT; + } + else if (type == 3) // you are the new saint + { + holywars_saint = (edict_t *)edict; + halo_status = HW_NEW_SAINT; + } +} + +void BotClient_HolyWars_GameMode(void *p, int bot_index) +{ + int mode = *(int *)p; + + holywars_gamemode = mode; +} + +void BotClient_HolyWars_HudText(void *p, int bot_index) +{ + if (strncmp((char *)p, "Voting for", 10) == 0) + { + bots[bot_index].vote_in_progress = TRUE; + bots[bot_index].f_vote_time = gpGlobals->time + RANDOM_LONG(2.0, 5.0); + } +} + +void BotClient_CS_HLTV(void *p, int bot_index) +{ + static int state = 0; // current state machine state + static int players; + int index; + + if (state == 0) + players = *(int *) p; + else if (state == 1) + { + // new round in CS 1.6 + if ((players == 0) && (*(int *) p == 0)) + { + for (index = 0; index < 32; index++) + { + if (bots[index].is_used) + BotSpawnInit (&bots[index]); // reset bots for new round + } + } + } +} diff --git a/bot_client.h b/bot_client.h new file mode 100644 index 0000000..b005b50 --- /dev/null +++ b/bot_client.h @@ -0,0 +1,109 @@ +// +// HPB_bot - botman's High Ping Bastard bot +// +// (http://planethalflife.com/botman/) +// +// bot_client.h +// + +void BotClient_TFC_VGUI(void *p, int bot_index); +void BotClient_CS_VGUI(void *p, int bot_index); +void BotClient_CS_ShowMenu(void *p, int bot_index); +void BotClient_Gearbox_VGUI(void *p, int bot_index); +void BotClient_FLF_VGUI(void *p, int bot_index); + +void BotClient_Valve_WeaponList(void *p, int bot_index); +void BotClient_TFC_WeaponList(void *p, int bot_index); +void BotClient_CS_WeaponList(void *p, int bot_index); +void BotClient_Gearbox_WeaponList(void *p, int bot_index); +void BotClient_FLF_WeaponList(void *p, int bot_index); +void BotClient_DMC_WeaponList(void *p, int bot_index); + +void BotClient_Valve_CurrentWeapon(void *p, int bot_index); +void BotClient_TFC_CurrentWeapon(void *p, int bot_index); +void BotClient_CS_CurrentWeapon(void *p, int bot_index); +void BotClient_Gearbox_CurrentWeapon(void *p, int bot_index); +void BotClient_FLF_CurrentWeapon(void *p, int bot_index); +void BotClient_DMC_CurrentWeapon(void *p, int bot_index); + +void BotClient_DMC_QItems (void *p, int bot_index); + +void BotClient_Valve_AmmoX(void *p, int bot_index); +void BotClient_TFC_AmmoX(void *p, int bot_index); +void BotClient_CS_AmmoX(void *p, int bot_index); +void BotClient_Gearbox_AmmoX(void *p, int bot_index); +void BotClient_FLF_AmmoX(void *p, int bot_index); +void BotClient_DMC_AmmoX(void *p, int bot_index); + +void BotClient_Valve_AmmoPickup(void *p, int bot_index); +void BotClient_TFC_AmmoPickup(void *p, int bot_index); +void BotClient_CS_AmmoPickup(void *p, int bot_index); +void BotClient_Gearbox_AmmoPickup(void *p, int bot_index); +void BotClient_FLF_AmmoPickup(void *p, int bot_index); +void BotClient_DMC_AmmoPickup(void *p, int bot_index); + +void BotClient_TFC_SecAmmoVal(void *p, int bot_index); + +void BotClient_Valve_WeaponPickup(void *p, int bot_index); +void BotClient_TFC_WeaponPickup(void *p, int bot_index); +void BotClient_CS_WeaponPickup(void *p, int bot_index); +void BotClient_Gearbox_WeaponPickup(void *p, int bot_index); +void BotClient_FLF_WeaponPickup(void *p, int bot_index); +void BotClient_DMC_WeaponPickup(void *p, int bot_index); + +void BotClient_Valve_ItemPickup(void *p, int bot_index); +void BotClient_TFC_ItemPickup(void *p, int bot_index); +void BotClient_CS_ItemPickup(void *p, int bot_index); +void BotClient_Gearbox_ItemPickup(void *p, int bot_index); +void BotClient_FLF_ItemPickup(void *p, int bot_index); +void BotClient_DMC_ItemPickup(void *p, int bot_index); + +void BotClient_Valve_Health(void *p, int bot_index); +void BotClient_TFC_Health(void *p, int bot_index); +void BotClient_CS_Health(void *p, int bot_index); +void BotClient_Gearbox_Health(void *p, int bot_index); +void BotClient_FLF_Health(void *p, int bot_index); +void BotClient_DMC_Health(void *p, int bot_index); + +void BotClient_Valve_Battery(void *p, int bot_index); +void BotClient_TFC_Battery(void *p, int bot_index); +void BotClient_CS_Battery(void *p, int bot_index); +void BotClient_Gearbox_Battery(void *p, int bot_index); +void BotClient_FLF_Battery(void *p, int bot_index); +void BotClient_DMC_Battery(void *p, int bot_index); + +void BotClient_Valve_Damage(void *p, int bot_index); +void BotClient_TFC_Damage(void *p, int bot_index); +void BotClient_CS_Damage(void *p, int bot_index); +void BotClient_Gearbox_Damage(void *p, int bot_index); +void BotClient_FLF_Damage(void *p, int bot_index); +void BotClient_DMC_Damage(void *p, int bot_index); + +void BotClient_CS_Money(void *p, int bot_index); + +void BotClient_Valve_DeathMsg(void *p, int bot_index); +void BotClient_TFC_DeathMsg(void *p, int bot_index); +void BotClient_CS_DeathMsg(void *p, int bot_index); +void BotClient_Gearbox_DeathMsg(void *p, int bot_index); +void BotClient_FLF_DeathMsg(void *p, int bot_index); +void BotClient_DMC_DeathMsg(void *p, int bot_index); + +void BotClient_TFC_TextMsg(void *p, int bot_index); + +void BotClient_FLF_TextMsg(void *p, int bot_index); +void BotClient_FLF_WarmUp(void *p, int bot_index); +void BotClient_FLF_WarmUpAll(void *p, int bot_index); +void BotClient_FLF_WinMessage(void *p, int bot_index); +void BotClient_FLF_HideWeapon(void *p, int bot_index); + +void BotClient_Valve_ScreenFade(void *p, int bot_index); +void BotClient_TFC_ScreenFade(void *p, int bot_index); +void BotClient_CS_ScreenFade(void *p, int bot_index); +void BotClient_Gearbox_ScreenFade(void *p, int bot_index); +void BotClient_FLF_ScreenFade(void *p, int bot_index); + +void BotClient_HolyWars_Halo(void *p, int bot_index); +void BotClient_HolyWars_GameMode(void *p, int bot_index); +void BotClient_HolyWars_HudText(void *p, int bot_index); + +void BotClient_CS_HLTV(void *p, int bot_index); diff --git a/bot_combat.cpp b/bot_combat.cpp new file mode 100644 index 0000000..cb3eece --- /dev/null +++ b/bot_combat.cpp @@ -0,0 +1,1946 @@ +// +// HPB bot - botman's High Ping Bastard bot +// +// (http://planethalflife.com/botman/) +// +// bot_combat.cpp +// + +#ifndef _WIN32 +#include +#endif + +#include +#include +#include +#include + +#include "bot.h" +#include "bot_func.h" +#include "bot_weapons.h" + +extern int mod_id; +extern bot_weapon_t weapon_defs[MAX_WEAPONS]; +extern bool b_observer_mode; +extern int team_allies[4]; +extern edict_t *pent_info_ctfdetect; +extern bool is_team_play; +extern bool checked_teamplay; +extern int num_logos; + +FILE *fp; + +int tfc_max_armor[10] = {0, 50, 50, 200, 120, 100, 300, 150, 100, 50}; +edict_t *holywars_saint = NULL; +int halo_status = HW_WAIT_SPAWN; +int holywars_gamemode = 0; // 0=deathmatch, 1=halo, 2=instagib + +float react_delay_min[3][5] = { + {0.01, 0.02, 0.03, 0.04, 0.05}, + {0.07, 0.09, 0.12, 0.14, 0.17}, + {0.10, 0.12, 0.15, 0.18, 0.21}}; +float react_delay_max[3][5] = { + {0.04, 0.06, 0.08, 0.10, 0.12}, + {0.11, 0.14, 0.18, 0.21, 0.25}, + {0.15, 0.18, 0.22, 0.25, 0.30}}; + +float aim_tracking_x_scale[5] = {5.0, 4.0, 3.2, 2.5, 2.0}; +float aim_tracking_y_scale[5] = {5.0, 4.0, 3.2, 2.5, 2.0}; + + +typedef struct +{ + int iId; // the weapon ID value + char weapon_name[64]; // name of the weapon when selecting it + int skill_level; // bot skill must be less than or equal to this value + float primary_min_distance; // 0 = no minimum + float primary_max_distance; // 9999 = no maximum + float secondary_min_distance; // 0 = no minimum + float secondary_max_distance; // 9999 = no maximum + int use_percent; // times out of 100 to use this weapon when available + bool can_use_underwater; // can use this weapon underwater + int primary_fire_percent; // times out of 100 to use primary fire + int min_primary_ammo; // minimum ammout of primary ammo needed to fire + int min_secondary_ammo; // minimum ammout of seconday ammo needed to fire + bool primary_fire_hold; // hold down primary fire button to use? + bool secondary_fire_hold; // hold down secondary fire button to use? + bool primary_fire_charge; // charge weapon using primary fire? + bool secondary_fire_charge; // charge weapon using secondary fire? + float primary_charge_delay; // time to charge weapon + float secondary_charge_delay; // time to charge weapon +} bot_weapon_select_t; + +typedef struct +{ + int iId; + float primary_base_delay; + float primary_min_delay[5]; + float primary_max_delay[5]; + float secondary_base_delay; + float secondary_min_delay[5]; + float secondary_max_delay[5]; +} bot_fire_delay_t; + + +// weapons are stored in priority order, most desired weapon should be at +// the start of the array and least desired should be at the end + +bot_weapon_select_t valve_weapon_select[] = { + {VALVE_WEAPON_CROWBAR, "weapon_crowbar", 2, 0.0, 50.0, 0.0, 0.0, + 100, TRUE, 100, 0, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {VALVE_WEAPON_HANDGRENADE, "weapon_handgrenade", 5, 250.0, 750.0, 0.0, 0.0, + 30, TRUE, 100, 1, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {VALVE_WEAPON_SNARK, "weapon_snark", 5, 150.0, 500.0, 0.0, 0.0, + 50, FALSE, 100, 1, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {VALVE_WEAPON_EGON, "weapon_egon", 5, 0.0, 9999.0, 0.0, 0.0, + 100, FALSE, 100, 1, 0, TRUE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {VALVE_WEAPON_GAUSS, "weapon_gauss", 5, 0.0, 9999.0, 0.0, 9999.0, + 100, FALSE, 80, 1, 10, FALSE, FALSE, FALSE, TRUE, 0.0, 0.8}, + {VALVE_WEAPON_SHOTGUN, "weapon_shotgun", 5, 30.0, 150.0, 30.0, 150.0, + 100, FALSE, 70, 1, 2, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {VALVE_WEAPON_PYTHON, "weapon_357", 5, 30.0, 700.0, 0.0, 0.0, + 100, FALSE, 100, 1, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {VALVE_WEAPON_HORNETGUN, "weapon_hornetgun", 5, 30.0, 1000.0, 30.0, 1000.0, + 100, TRUE, 50, 1, 4, FALSE, TRUE, FALSE, FALSE, 0.0, 0.0}, + {VALVE_WEAPON_MP5, "weapon_9mmAR", 5, 0.0, 250.0, 300.0, 600.0, + 100, FALSE, 90, 1, 1, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {VALVE_WEAPON_CROSSBOW, "weapon_crossbow", 5, 100.0, 1000.0, 0.0, 0.0, + 100, TRUE, 100, 1, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {VALVE_WEAPON_RPG, "weapon_rpg", 5, 300.0, 9999.0, 0.0, 0.0, + 100, TRUE, 100, 1, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {VALVE_WEAPON_GLOCK, "weapon_9mmhandgun", 5, 0.0, 1200.0, 0.0, 1200.0, + 100, TRUE, 70, 1, 1, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + /* terminator */ + {0, "", 0, 0.0, 0.0, 0.0, 0.0, 0, TRUE, 0, 1, 1, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0} +}; + +bot_weapon_select_t tfc_weapon_select[] = { + {TF_WEAPON_AXE, "tf_weapon_axe", 5, 0.0, 50.0, 0.0, 0.0, + 100, TRUE, 100, 0, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {TF_WEAPON_KNIFE, "tf_weapon_knife", 5, 0.0, 40.0, 0.0, 0.0, + 100, TRUE, 100, 0, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {TF_WEAPON_SPANNER, "tf_weapon_spanner", 5, 0.0, 40.0, 0.0, 0.0, + 100, TRUE, 100, 0, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {TF_WEAPON_MEDIKIT, "tf_weapon_medikit", 5, 0.0, 40.0, 0.0, 0.0, + 100, TRUE, 100, 0, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {TF_WEAPON_SNIPERRIFLE, "tf_weapon_sniperrifle", 5, 300.0, 9999.0, 0.0, 0.0, + 100, TRUE, 100, 1, 0, FALSE, FALSE, TRUE, FALSE, 1.0, 0.0}, + {TF_WEAPON_FLAMETHROWER, "tf_weapon_flamethrower", 5, 100.0, 500.0, 0.0, 0.0, + 100, FALSE, 100, 1, 0, TRUE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {TF_WEAPON_AC, "tf_weapon_ac", 5, 50.0, 1000.0, 0.0, 0.0, + 100, TRUE, 100, 1, 0, TRUE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {TF_WEAPON_GL, "tf_weapon_gl", 5, 300.0, 900.0, 0.0, 0.0, + 100, TRUE, 100, 1, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {TF_WEAPON_RPG, "tf_weapon_rpg", 5, 300.0, 900.0, 0.0, 0.0, + 100, TRUE, 100, 1, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {TF_WEAPON_IC, "tf_weapon_ic", 5, 300.0, 800.0, 0.0, 0.0, + 100, TRUE, 100, 1, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {TF_WEAPON_TRANQ, "tf_weapon_tranq", 5, 40.0, 1000.0, 0.0, 0.0, + 100, TRUE, 100, 1, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {TF_WEAPON_RAILGUN, "tf_weapon_railgun", 5, 40.0, 800.0, 0.0, 0.0, + 100, TRUE, 100, 1, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {TF_WEAPON_SUPERNAILGUN, "tf_weapon_superng", 5, 40.0, 800.0, 0.0, 0.0, + 100, TRUE, 100, 1, 0, TRUE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {TF_WEAPON_SUPERSHOTGUN, "tf_weapon_supershotgun", 5, 40.0, 500.0, 0.0, 0.0, + 100, TRUE, 100, 2, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {TF_WEAPON_AUTORIFLE, "tf_weapon_autorifle", 5, 0.0, 800.0, 0.0, 0.0, + 100, TRUE, 100, 1, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {TF_WEAPON_SHOTGUN, "tf_weapon_shotgun", 5, 40.0, 400.0, 0.0, 0.0, + 100, TRUE, 100, 1, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {TF_WEAPON_NAILGUN, "tf_weapon_ng", 5, 40.0, 600.0, 0.0, 0.0, + 100, TRUE, 100, 1, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + /* terminator */ + {0, "", 0, 0.0, 0.0, 0.0, 0.0, 0, TRUE, 0, 1, 1, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0} +}; + +bot_weapon_select_t cs_weapon_select[] = { + {CS_WEAPON_KNIFE, "weapon_knife", 5, 0.0, 50.0, 0.0, 0.0, + 100, TRUE, 100, 0, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {CS_WEAPON_M4A1, "weapon_m4a1", 5, 0.0, 1500.0, 0.0, 0.0, + 100, TRUE, 100, 0, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {CS_WEAPON_SG552, "weapon_sg552", 5, 0.0, 1200.0, 0.0, 0.0, + 100, TRUE, 100, 0, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {CS_WEAPON_P90, "weapon_p90", 5, 0.0, 1000.0, 0.0, 0.0, + 100, TRUE, 100, 0, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {CS_WEAPON_MP5NAVY, "weapon_mp5navy", 5, 0.0, 1000.0, 0.0, 0.0, + 100, TRUE, 100, 0, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {CS_WEAPON_AK47, "weapon_ak47", 5, 0.0, 1500.0, 0.0, 0.0, + 100, TRUE, 100, 0, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {CS_WEAPON_AUG, "weapon_aug", 5, 0.0, 1500.0, 0.0, 0.0, + 100, TRUE, 100, 0, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {CS_WEAPON_MAC10, "weapon_mac10", 5, 0.0, 600.0, 0.0, 0.0, + 100, TRUE, 100, 0, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {CS_WEAPON_UMP45, "weapon_ump45", 5, 0.0, 800.0, 0.0, 0.0, + 100, TRUE, 100, 0, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {CS_WEAPON_XM1014, "weapon_xm1014", 5, 0.0, 400.0, 0.0, 0.0, + 100, TRUE, 100, 0, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {CS_WEAPON_AWP, "weapon_awp", 5, 0.0, 3500.0, 0.0, 0.0, + 100, TRUE, 100, 0, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {CS_WEAPON_G3SG1, "weapon_g3sg1", 5, 0.0, 3500.0, 0.0, 0.0, + 100, TRUE, 100, 0, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {CS_WEAPON_M249, "weapon_m249", 5, 0.0, 1000.0, 0.0, 0.0, + 100, TRUE, 100, 0, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {CS_WEAPON_M3, "weapon_m3", 5, 0.0, 500.0, 0.0, 0.0, + 100, TRUE, 100, 0, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {CS_WEAPON_TMP, "weapon_tmp", 5, 0.0, 800.0, 0.0, 0.0, + 100, TRUE, 100, 0, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {CS_WEAPON_SCOUT, "weapon_scout", 5, 0.0, 3500.0, 0.0, 0.0, + 100, TRUE, 100, 0, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {CS_WEAPON_DEAGLE, "weapon_deagle", 5, 0.0, 900.0, 0.0, 0.0, + 100, TRUE, 100, 0, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {CS_WEAPON_USP, "weapon_usp", 5, 0.0, 1200.0, 0.0, 0.0, + 100, TRUE, 100, 0, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {CS_WEAPON_P228, "weapon_p228", 5, 0.0, 900.0, 0.0, 0.0, + 100, TRUE, 100, 0, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {CS_WEAPON_GLOCK18, "weapon_glock18", 5, 0.0, 1200.0, 0.0, 0.0, + 100, TRUE, 100, 0, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {CS_WEAPON_ELITE, "weapon_usp", 5, 0.0, 900.0, 0.0, 0.0, + 100, TRUE, 100, 1, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {CS_WEAPON_FIVESEVEN, "weapon_fiveseven", 5, 0.0, 900.0, 0.0, 0.0, + 100, TRUE, 100, 1, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + /* terminator */ + {0, "", 0, 0.0, 0.0, 0.0, 0.0, 0, TRUE, 0, 1, 1, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0} +}; + +bot_weapon_select_t gearbox_weapon_select[] = { + {GEARBOX_WEAPON_PIPEWRENCH, "weapon_pipewrench", 3, 0.0, 50.0, 0.0, 0.0, + 100, TRUE, 100, 0, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {GEARBOX_WEAPON_KNIFE, "weapon_knife", 4, 0.0, 50.0, 0.0, 0.0, + 100, TRUE, 100, 0, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {GEARBOX_WEAPON_CROWBAR, "weapon_crowbar", 2, 0.0, 50.0, 0.0, 0.0, + 100, TRUE, 100, 0, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {GEARBOX_WEAPON_DISPLACER, "weapon_displacer", 5, 100.0, 1000.0, 0.0, 0.0, + 100, TRUE, 100, 0, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {GEARBOX_WEAPON_SPORELAUNCHER, "weapon_sporelauncher", 5, 500.0, 1000.0, 0.0, 0.0, + 100, TRUE, 100, 0, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {GEARBOX_WEAPON_SHOCKRIFLE, "weapon_shockrifle", 5, 50.0, 800.0, 0.0, 0.0, + 100, TRUE, 100, 0, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {GEARBOX_WEAPON_SNIPERRIFLE, "weapon_sniperrifle", 5, 50.0, 2500.0, 0.0, 0.0, + 100, FALSE, 100, 0, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {GEARBOX_WEAPON_HANDGRENADE, "weapon_handgrenade", 5, 250.0, 750.0, 0.0, 0.0, + 30, TRUE, 100, 1, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {GEARBOX_WEAPON_SNARK, "weapon_snark", 5, 150.0, 500.0, 0.0, 0.0, + 50, FALSE, 100, 1, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {GEARBOX_WEAPON_EGON, "weapon_egon", 5, 0.0, 9999.0, 0.0, 0.0, + 100, FALSE, 100, 1, 0, TRUE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {GEARBOX_WEAPON_GAUSS, "weapon_gauss", 5, 0.0, 9999.0, 0.0, 9999.0, + 100, FALSE, 80, 1, 10, FALSE, FALSE, FALSE, TRUE, 0.0, 0.8}, + {GEARBOX_WEAPON_M249, "weapon_m249", 5, 0.0, 400.0, 0.0, 0.0, + 100, FALSE, 100, 1, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {GEARBOX_WEAPON_SHOTGUN, "weapon_shotgun", 5, 30.0, 150.0, 30.0, 150.0, + 100, FALSE, 70, 1, 2, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {GEARBOX_WEAPON_EAGLE, "weapon_eagle", 5, 0.0, 1200.0, 0.0, 0.0, + 100, FALSE, 100, 1, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {GEARBOX_WEAPON_PYTHON, "weapon_357", 5, 30.0, 700.0, 0.0, 0.0, + 100, FALSE, 100, 1, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {GEARBOX_WEAPON_HORNETGUN, "weapon_hornetgun", 5, 30.0, 1000.0, 30.0, 1000.0, + 100, TRUE, 50, 1, 4, FALSE, TRUE, FALSE, FALSE, 0.0, 0.0}, + {GEARBOX_WEAPON_MP5, "weapon_9mmAR", 5, 0.0, 250.0, 300.0, 600.0, + 100, FALSE, 90, 1, 1, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {GEARBOX_WEAPON_CROSSBOW, "weapon_crossbow", 5, 100.0, 1000.0, 0.0, 0.0, + 100, TRUE, 100, 1, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {GEARBOX_WEAPON_RPG, "weapon_rpg", 5, 300.0, 9999.0, 0.0, 0.0, + 100, TRUE, 100, 1, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {GEARBOX_WEAPON_GLOCK, "weapon_9mmhandgun", 5, 0.0, 1200.0, 0.0, 1200.0, + 100, TRUE, 70, 1, 1, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + /* terminator */ + {0, "", 0, 0.0, 0.0, 0.0, 0.0, 0, TRUE, 0, 1, 1, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0} +}; + +bot_weapon_select_t frontline_weapon_select[] = { + {FLF_WEAPON_HEGRENADE, "weapon_hegrenade", 3, 200.0, 1000.0, 0.0, 0.0, + 100, TRUE, 100, 1, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {FLF_WEAPON_FLASHBANG, "weapon_flashbang", 3, 100.0, 800.0, 0.0, 0.0, + 100, TRUE, 100, 1, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, +// {FLF_WEAPON_KNIFE, "weapon_knife", 3, 0.0, 60.0, 0.0, 0.0, +// 100, TRUE, 100, 0, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {FLF_WEAPON_HK21, "weapon_hk21", 5, 0.0, 900.0, 0.0, 0.0, + 100, TRUE, 100, 1, 0, TRUE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {FLF_WEAPON_UMP45, "weapon_ump45", 5, 0.0, 900.0, 0.0, 0.0, + 100, TRUE, 100, 1, 0, TRUE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {FLF_WEAPON_FAMAS, "weapon_famas", 5, 0.0, 500.0, 0.0, 0.0, + 100, TRUE, 100, 1, 0, TRUE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {FLF_WEAPON_MSG90, "weapon_msg90", 5, 0.0, 2500.0, 0.0, 0.0, + 100, TRUE, 100, 1, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {FLF_WEAPON_SAKO, "weapon_sako", 5, 0.0, 1800.0, 0.0, 0.0, + 100, TRUE, 100, 1, 0, TRUE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {FLF_WEAPON_MP5A2, "weapon_mp5a2", 5, 0.0, 900.0, 0.0, 0.0, + 100, TRUE, 100, 1, 0, TRUE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {FLF_WEAPON_AK5, "weapon_ak5", 5, 0.0, 900.0, 0.0, 0.0, + 100, TRUE, 100, 1, 0, TRUE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {FLF_WEAPON_RS202M2, "weapon_rs202m2", 5, 0.0, 900.0, 0.0, 0.0, + 100, TRUE, 100, 1, 0, TRUE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {FLF_WEAPON_MP5SD, "weapon_mp5sd", 5, 0.0, 900.0, 0.0, 0.0, + 100, TRUE, 100, 1, 0, TRUE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {FLF_WEAPON_M4, "weapon_m4", 5, 0.0, 900.0, 0.0, 0.0, + 100, TRUE, 100, 1, 0, TRUE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {FLF_WEAPON_SPAS12, "weapon_spas12", 5, 0.0, 900.0, 0.0, 0.0, + 100, TRUE, 100, 1, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {FLF_WEAPON_MAC10, "weapon_mac10", 5, 0.0, 500.0, 0.0, 0.0, + 100, TRUE, 100, 1, 0, TRUE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {FLF_WEAPON_BERETTA, "weapon_beretta", 5, 0.0, 1200.0, 0.0, 1200.0, + 100, TRUE, 100, 1, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {FLF_WEAPON_MK23, "weapon_mk23", 5, 0.0, 1200.0, 0.0, 1200.0, + 100, TRUE, 100, 1, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + /* terminator */ + {0, "", 0, 0.0, 0.0, 0.0, 0.0, 0, TRUE, 0, 1, 1, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0} +}; + +bot_weapon_select_t holywars_weapon_select[] = { + {HW_WEAPON_JACKHAMMER, "weapon_jackhammer", 5, 0.0, 50.0, 0.0, 0.0, + 100, TRUE, 100, 0, 0, TRUE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {HW_WEAPON_MACHINEGUN, "weapon_machinegun", 5, 50.0, 1500.0, 0.0, 0.0, + 100, TRUE, 100, 1, 0, TRUE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {HW_WEAPON_RAILGUN, "weapon_railgun", 5, 50.0, 9999.0, 0.0, 0.0, + 100, TRUE, 100, 1, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {HW_WEAPON_DOUBLESHOTGUN, "weapon_doubleshotgun", 5, 50.0, 1000.0, 0.0, 0.0, + 100, TRUE, 100, 3, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {HW_WEAPON_ROCKETLAUNCHER, "weapon_rocketlauncher", 5, 50.0, 1500.0, 0.0, 0.0, + 100, TRUE, 100, 1, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + /* terminator */ + {0, "", 0, 0.0, 0.0, 0.0, 0.0, 0, TRUE, 0, 1, 1, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0} +}; + +bot_weapon_select_t dmc_weapon_select[] = { + {DMC_WEAPON_AXE, "weapon_axe", 2, 0.0, 50.0, 0.0, 0.0, + 100, TRUE, 100, 0, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {DMC_WEAPON_LIGHTNING, "weapon_lightning", 5, 30.0, 1500.0, 0.0, 0.0, + 100, TRUE, 100, 1, 0, TRUE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {DMC_WEAPON_ROCKET1, "weapon_rocket1", 5, 150.0, 1000.0, 0.0, 0.0, + 100, TRUE, 100, 1, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {DMC_WEAPON_SUPERNAIL, "weapon_supernail", 5, 50.0, 400.0, 0.0, 0.0, + 100, TRUE, 100, 1, 0, TRUE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {DMC_WEAPON_DOUBLESHOTGUN, "weapon_doubleshotgun", 5, 50.0, 250.0, 0.0, 0.0, + 100, TRUE, 100, 2, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {DMC_WEAPON_GRENADE1, "weapon_grenade1", 5, 200.0, 800.0, 0.0, 0.0, + 100, TRUE, 100, 1, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {DMC_WEAPON_NAILGUN, "weapon_nailgun", 5, 50.0, 400.0, 0.0, 0.0, + 100, TRUE, 100, 1, 0, TRUE, FALSE, FALSE, FALSE, 0.0, 0.0}, + {DMC_WEAPON_SHOTGUN, "weapon_shotgun", 5, 0.0, 250.0, 0.0, 0.0, + 100, TRUE, 100, 1, 0, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0}, + /* terminator */ + {0, "", 0, 0.0, 0.0, 0.0, 0.0, 0, TRUE, 0, 1, 1, FALSE, FALSE, FALSE, FALSE, 0.0, 0.0} +}; + +// weapon firing delay based on skill (min and max delay for each weapon) +// THESE MUST MATCH THE SAME ORDER AS THE WEAPON SELECT ARRAY!!! + +bot_fire_delay_t valve_fire_delay[] = { + {VALVE_WEAPON_CROWBAR, + 0.3, {0.0, 0.2, 0.3, 0.4, 0.6}, {0.1, 0.3, 0.5, 0.7, 1.0}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {VALVE_WEAPON_HANDGRENADE, + 0.1, {1.0, 2.0, 3.0, 4.0, 5.0}, {3.0, 4.0, 5.0, 6.0, 7.0}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {VALVE_WEAPON_SNARK, + 0.1, {0.0, 0.1, 0.2, 0.4, 0.6}, {0.1, 0.2, 0.5, 0.7, 1.0}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {VALVE_WEAPON_EGON, + 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}}, + {VALVE_WEAPON_GAUSS, + 0.2, {0.0, 0.2, 0.3, 0.5, 1.0}, {0.1, 0.3, 0.5, 0.8, 1.2}, + 1.0, {0.2, 0.3, 0.5, 0.8, 1.2}, {0.5, 0.7, 1.0, 1.5, 2.0}}, + {VALVE_WEAPON_SHOTGUN, + 0.75, {0.0, 0.2, 0.4, 0.6, 0.8}, {0.25, 0.5, 0.8, 1.2, 2.0}, + 1.5, {0.0, 0.2, 0.4, 0.6, 0.8}, {0.25, 0.5, 0.8, 1.2, 2.0}}, + {VALVE_WEAPON_PYTHON, + 0.75, {0.0, 0.2, 0.4, 1.0, 1.5}, {0.25, 0.5, 0.8, 1.3, 2.2}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {VALVE_WEAPON_HORNETGUN, + 0.25, {0.0, 0.25, 0.4, 0.6, 1.0}, {0.1, 0.4, 0.7, 1.0, 1.5}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {VALVE_WEAPON_MP5, + 0.1, {0.0, 0.1, 0.25, 0.4, 0.5}, {0.1, 0.3, 0.45, 0.65, 0.8}, + 1.0, {0.0, 0.4, 0.7, 1.0, 1.4}, {0.3, 0.7, 1.0, 1.6, 2.0}}, + {VALVE_WEAPON_CROSSBOW, + 0.75, {0.0, 0.2, 0.5, 0.8, 1.0}, {0.25, 0.4, 0.7, 1.0, 1.3}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {VALVE_WEAPON_RPG, + 1.5, {1.0, 2.0, 3.0, 4.0, 5.0}, {3.0, 4.0, 5.0, 6.0, 7.0}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {VALVE_WEAPON_GLOCK, + 0.3, {0.0, 0.1, 0.2, 0.3, 0.4}, {0.1, 0.2, 0.3, 0.4, 0.5}, + 0.2, {0.0, 0.0, 0.1, 0.1, 0.2}, {0.1, 0.1, 0.2, 0.2, 0.4}}, + /* terminator */ + {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_fire_delay_t tfc_fire_delay[] = { + {TF_WEAPON_AXE, + 0.3, {0.0, 0.2, 0.3, 0.4, 0.6}, {0.1, 0.3, 0.5, 0.7, 1.0}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {TF_WEAPON_KNIFE, + 0.3, {0.0, 0.2, 0.3, 0.4, 0.6}, {0.1, 0.3, 0.5, 0.7, 1.0}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {TF_WEAPON_SPANNER, + 0.3, {0.0, 0.2, 0.3, 0.4, 0.6}, {0.1, 0.3, 0.5, 0.7, 1.0}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {TF_WEAPON_MEDIKIT, + 0.3, {0.0, 0.2, 0.3, 0.4, 0.6}, {0.1, 0.3, 0.5, 0.7, 1.0}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {TF_WEAPON_SNIPERRIFLE, + 1.0, {0.0, 0.4, 0.7, 1.0, 1.4}, {0.3, 0.7, 1.0, 1.6, 2.0}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {TF_WEAPON_FLAMETHROWER, + 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}}, + {TF_WEAPON_AC, + 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}}, + {TF_WEAPON_GL, + 0.6, {0.0, 0.2, 0.5, 0.8, 1.0}, {0.25, 0.4, 0.7, 1.0, 1.3}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {TF_WEAPON_RPG, + 0.5, {0.0, 0.1, 0.3, 0.6, 1.0}, {0.1, 0.2, 0.7, 1.0, 2.0}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {TF_WEAPON_IC, + 2.0, {1.0, 2.0, 3.0, 4.0, 5.0}, {3.0, 4.0, 5.0, 6.0, 7.0}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {TF_WEAPON_TRANQ, + 1.5, {1.0, 2.0, 3.0, 4.0, 5.0}, {3.0, 4.0, 5.0, 6.0, 7.0}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {TF_WEAPON_RAILGUN, + 0.4, {0.0, 0.1, 0.2, 0.3, 0.4}, {0.1, 0.2, 0.3, 0.4, 0.5}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {TF_WEAPON_SUPERNAILGUN, + 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}}, + {TF_WEAPON_SUPERSHOTGUN, + 0.6, {0.0, 0.2, 0.5, 0.8, 1.0}, {0.25, 0.4, 0.7, 1.0, 1.3}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {TF_WEAPON_AUTORIFLE, + 0.1, {0.0, 0.1, 0.2, 0.4, 0.6}, {0.1, 0.2, 0.5, 0.7, 1.0}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {TF_WEAPON_SHOTGUN, + 0.5, {0.0, 0.2, 0.4, 0.6, 0.8}, {0.25, 0.5, 0.8, 1.2, 2.0}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {TF_WEAPON_NAILGUN, + 0.1, {0.0, 0.1, 0.2, 0.4, 0.6}, {0.1, 0.2, 0.5, 0.7, 1.0}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + /* terminator */ + {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_fire_delay_t cs_fire_delay[] = { + {CS_WEAPON_KNIFE, + 0.3, {0.0, 0.2, 0.3, 0.4, 0.6}, {0.1, 0.3, 0.5, 0.7, 1.0}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {CS_WEAPON_M4A1, + 0.1, {0.0, 0.1, 0.25, 0.4, 0.5}, {0.1, 0.3, 0.45, 0.65, 0.8}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {CS_WEAPON_SG552, + 0.1, {0.0, 0.1, 0.25, 0.4, 0.5}, {0.1, 0.3, 0.45, 0.65, 0.8}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {CS_WEAPON_P90, + 0.1, {0.0, 0.1, 0.25, 0.4, 0.5}, {0.1, 0.3, 0.45, 0.65, 0.8}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {CS_WEAPON_MP5NAVY, + 0.1, {0.0, 0.1, 0.25, 0.4, 0.5}, {0.1, 0.3, 0.45, 0.65, 0.8}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {CS_WEAPON_AK47, + 0.1, {0.0, 0.1, 0.25, 0.4, 0.5}, {0.1, 0.3, 0.45, 0.65, 0.8}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {CS_WEAPON_AUG, + 0.1, {0.0, 0.1, 0.25, 0.4, 0.5}, {0.1, 0.3, 0.45, 0.65, 0.8}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {CS_WEAPON_MAC10, + 0.1, {0.0, 0.1, 0.25, 0.4, 0.5}, {0.1, 0.3, 0.45, 0.65, 0.8}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {CS_WEAPON_UMP45, + 0.1, {0.0, 0.1, 0.25, 0.4, 0.5}, {0.1, 0.3, 0.45, 0.65, 0.8}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {CS_WEAPON_XM1014, + 0.1, {0.0, 0.1, 0.25, 0.4, 0.5}, {0.1, 0.3, 0.45, 0.65, 0.8}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {CS_WEAPON_AWP, + 0.1, {0.0, 0.1, 0.25, 0.4, 0.5}, {0.1, 0.3, 0.45, 0.65, 0.8}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {CS_WEAPON_G3SG1, + 0.1, {0.0, 0.1, 0.25, 0.4, 0.5}, {0.1, 0.3, 0.45, 0.65, 0.8}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {CS_WEAPON_M249, + 0.1, {0.0, 0.1, 0.25, 0.4, 0.5}, {0.1, 0.3, 0.45, 0.65, 0.8}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {CS_WEAPON_M3, + 0.1, {0.0, 0.1, 0.25, 0.4, 0.5}, {0.1, 0.3, 0.45, 0.65, 0.8}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {CS_WEAPON_TMP, + 0.1, {0.0, 0.1, 0.25, 0.4, 0.5}, {0.1, 0.3, 0.45, 0.65, 0.8}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {CS_WEAPON_SCOUT, + 0.1, {0.0, 0.1, 0.25, 0.4, 0.5}, {0.1, 0.3, 0.45, 0.65, 0.8}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {CS_WEAPON_DEAGLE, + 0.1, {0.0, 0.1, 0.25, 0.4, 0.5}, {0.1, 0.3, 0.45, 0.65, 0.8}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {CS_WEAPON_USP, + 0.3, {0.0, 0.1, 0.2, 0.3, 0.4}, {0.1, 0.2, 0.3, 0.4, 0.5}, + 0.2, {0.0, 0.0, 0.1, 0.1, 0.2}, {0.1, 0.1, 0.2, 0.2, 0.4}}, + {CS_WEAPON_P228, + 0.1, {0.0, 0.1, 0.25, 0.4, 0.5}, {0.1, 0.3, 0.45, 0.65, 0.8}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {CS_WEAPON_GLOCK18, + 0.3, {0.0, 0.1, 0.2, 0.3, 0.4}, {0.1, 0.2, 0.3, 0.4, 0.5}, + 0.2, {0.0, 0.0, 0.1, 0.1, 0.2}, {0.1, 0.1, 0.2, 0.2, 0.4}}, + {CS_WEAPON_ELITE, + 0.1, {0.0, 0.1, 0.25, 0.4, 0.5}, {0.1, 0.3, 0.45, 0.65, 0.8}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {CS_WEAPON_FIVESEVEN, + 0.1, {0.0, 0.1, 0.25, 0.4, 0.5}, {0.1, 0.3, 0.45, 0.65, 0.8}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + /* terminator */ + {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_fire_delay_t gearbox_fire_delay[] = { + {GEARBOX_WEAPON_PIPEWRENCH, + 0.5, {0.0, 0.2, 0.3, 0.4, 0.6}, {0.1, 0.3, 0.5, 0.7, 1.0}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {GEARBOX_WEAPON_KNIFE, + 0.4, {0.0, 0.2, 0.3, 0.4, 0.6}, {0.1, 0.3, 0.5, 0.7, 1.0}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {GEARBOX_WEAPON_CROWBAR, + 0.3, {0.0, 0.2, 0.3, 0.4, 0.6}, {0.1, 0.3, 0.5, 0.7, 1.0}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {GEARBOX_WEAPON_DISPLACER, + 5.0, {0.0, 0.5, 0.8, 1.6, 2.5}, {0.3, 0.8, 1.4, 2.2, 3.5}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {GEARBOX_WEAPON_SPORELAUNCHER, + 0.5, {0.0, 0.2, 0.3, 0.4, 0.6}, {0.1, 0.3, 0.5, 0.7, 1.0}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {GEARBOX_WEAPON_SHOCKRIFLE, + 0.1, {0.0, 0.1, 0.2, 0.3, 0.4}, {0.1, 0.2, 0.3, 0.4, 0.5}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {GEARBOX_WEAPON_SNIPERRIFLE, + 1.5, {0.0, 0.2, 0.4, 0.6, 0.8}, {0.25, 0.5, 0.8, 1.2, 2.0}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {GEARBOX_WEAPON_HANDGRENADE, + 0.1, {1.0, 2.0, 3.0, 4.0, 5.0}, {3.0, 4.0, 5.0, 6.0, 7.0}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {GEARBOX_WEAPON_SNARK, + 0.1, {0.0, 0.1, 0.2, 0.4, 0.6}, {0.1, 0.2, 0.5, 0.7, 1.0}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {GEARBOX_WEAPON_EGON, + 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}}, + {GEARBOX_WEAPON_GAUSS, + 0.2, {0.0, 0.2, 0.3, 0.5, 1.0}, {0.1, 0.3, 0.5, 0.8, 1.2}, + 1.0, {0.2, 0.3, 0.5, 0.8, 1.2}, {0.5, 0.7, 1.0, 1.5, 2.0}}, + {GEARBOX_WEAPON_M249, + 0.1, {0.0, 0.1, 0.25, 0.4, 0.5}, {0.1, 0.3, 0.45, 0.65, 0.8}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {GEARBOX_WEAPON_SHOTGUN, + 0.75, {0.0, 0.2, 0.4, 0.6, 0.8}, {0.25, 0.5, 0.8, 1.2, 2.0}, + 1.5, {0.0, 0.2, 0.4, 0.6, 0.8}, {0.25, 0.5, 0.8, 1.2, 2.0}}, + {GEARBOX_WEAPON_EAGLE, + 0.25, {0.0, 0.1, 0.2, 0.3, 0.5}, {0.1, 0.25, 0.4, 0.7, 1.0}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {GEARBOX_WEAPON_PYTHON, + 0.75, {0.0, 0.2, 0.4, 1.0, 1.5}, {0.25, 0.5, 0.8, 1.3, 2.2}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {GEARBOX_WEAPON_HORNETGUN, + 0.25, {0.0, 0.25, 0.4, 0.6, 1.0}, {0.1, 0.4, 0.7, 1.0, 1.5}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {GEARBOX_WEAPON_MP5, + 0.1, {0.0, 0.1, 0.25, 0.4, 0.5}, {0.1, 0.3, 0.45, 0.65, 0.8}, + 1.0, {0.0, 0.4, 0.7, 1.0, 1.4}, {0.3, 0.7, 1.0, 1.6, 2.0}}, + {GEARBOX_WEAPON_CROSSBOW, + 0.75, {0.0, 0.2, 0.5, 0.8, 1.0}, {0.25, 0.4, 0.7, 1.0, 1.3}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {GEARBOX_WEAPON_RPG, + 1.5, {1.0, 2.0, 3.0, 4.0, 5.0}, {3.0, 4.0, 5.0, 6.0, 7.0}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {GEARBOX_WEAPON_GLOCK, + 0.3, {0.0, 0.1, 0.2, 0.3, 0.4}, {0.1, 0.2, 0.3, 0.4, 0.5}, + 0.2, {0.0, 0.0, 0.1, 0.1, 0.2}, {0.1, 0.1, 0.2, 0.2, 0.4}}, + /* terminator */ + {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_fire_delay_t frontline_fire_delay[] = { + {FLF_WEAPON_HEGRENADE, + 0.3, {0.0, 0.1, 0.2, 0.3, 0.4}, {0.1, 0.2, 0.3, 0.4, 0.5}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {FLF_WEAPON_FLASHBANG, + 0.3, {0.0, 0.1, 0.2, 0.3, 0.4}, {0.1, 0.2, 0.3, 0.4, 0.5}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, +// {FLF_WEAPON_KNIFE, +// 0.3, {0.0, 0.1, 0.2, 0.3, 0.4}, {0.1, 0.2, 0.3, 0.4, 0.5}, +// 0.2, {0.0, 0.0, 0.1, 0.1, 0.2}, {0.1, 0.1, 0.2, 0.2, 0.4}}, + {FLF_WEAPON_HK21, + 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}}, + {FLF_WEAPON_UMP45, + 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}}, + {FLF_WEAPON_FAMAS, + 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}}, + {FLF_WEAPON_MSG90, + 1.2, {0.0, 0.1, 0.2, 0.3, 0.4}, {0.1, 0.2, 0.3, 0.4, 0.5}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {FLF_WEAPON_SAKO, + 2.0, {0.0, 0.1, 0.2, 0.3, 0.4}, {0.1, 0.2, 0.3, 0.4, 0.5}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {FLF_WEAPON_MP5A2, + 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}}, + {FLF_WEAPON_AK5, + 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}}, + {FLF_WEAPON_RS202M2, + 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}}, + {FLF_WEAPON_MP5SD, + 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}}, + {FLF_WEAPON_M4, + 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}}, + {FLF_WEAPON_SPAS12, + 0.9, {0.0, 0.1, 0.2, 0.3, 0.4}, {0.1, 0.2, 0.3, 0.4, 0.5}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {FLF_WEAPON_MAC10, + 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}}, + {FLF_WEAPON_BERETTA, + 0.4, {0.0, 0.1, 0.2, 0.3, 0.4}, {0.1, 0.2, 0.3, 0.4, 0.5}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {FLF_WEAPON_MK23, + 0.4, {0.0, 0.1, 0.2, 0.3, 0.4}, {0.1, 0.2, 0.3, 0.4, 0.5}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + /* terminator */ + {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_fire_delay_t holywars_fire_delay[] = { + {HW_WEAPON_JACKHAMMER, + 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}}, + {HW_WEAPON_MACHINEGUN, + 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}}, + {HW_WEAPON_RAILGUN, + 0.9, {0.0, 0.1, 0.2, 0.3, 0.4}, {0.1, 0.2, 0.3, 0.5, 0.7}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {HW_WEAPON_DOUBLESHOTGUN, + 2.1, {0.0, 0.1, 0.2, 0.3, 0.4}, {0.1, 0.2, 0.3, 0.5, 0.7}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {HW_WEAPON_ROCKETLAUNCHER, + 1.0, {0.0, 0.1, 0.2, 0.3, 0.4}, {0.1, 0.2, 0.3, 0.5, 0.7}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + /* terminator */ + {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_fire_delay_t dmc_fire_delay[] = { + {DMC_WEAPON_AXE, + 0.3, {0.0, 0.2, 0.3, 0.4, 0.6}, {0.1, 0.3, 0.5, 0.7, 1.0}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {DMC_WEAPON_LIGHTNING, + 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}}, + {DMC_WEAPON_ROCKET1, + 0.8, {0.0, 0.2, 0.3, 0.4, 0.6}, {0.1, 0.3, 0.5, 0.7, 1.0}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {DMC_WEAPON_SUPERNAIL, + 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}}, + {DMC_WEAPON_DOUBLESHOTGUN, + 0.7, {0.0, 0.1, 0.1, 0.2, 0.3}, {0.1, 0.2, 0.3, 0.5, 0.7}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {DMC_WEAPON_GRENADE1, + 0.6, {0.0, 0.1, 0.2, 0.3, 0.4}, {0.1, 0.2, 0.3, 0.5, 0.7}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + {DMC_WEAPON_NAILGUN, + 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}}, + {DMC_WEAPON_SHOTGUN, + 0.5, {0.0, 0.2, 0.3, 0.4, 0.6}, {0.1, 0.3, 0.5, 0.7, 1.0}, + 0.0, {0.0, 0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0, 0.0}}, + /* terminator */ + {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}} +}; + + +void BotCheckTeamplay(void) +{ + float f_team_play = 0.0; + + // is this TFC or Counter-Strike? + if ((mod_id == TFC_DLL) || (mod_id == CSTRIKE_DLL) || + ((mod_id == GEARBOX_DLL) && (pent_info_ctfdetect != NULL)) || + (mod_id == FRONTLINE_DLL)) + f_team_play = 1.0; + else + f_team_play = CVAR_GET_FLOAT("mp_teamplay"); // teamplay enabled? + + if (f_team_play > 0.0) + is_team_play = TRUE; + else + is_team_play = FALSE; + + checked_teamplay = TRUE; +} + + +edict_t *BotFindEnemy( bot_t *pBot ) +{ + static bool flag=TRUE; + edict_t *pent = NULL; + edict_t *pNewEnemy; + float nearestdistance; + int i; + + edict_t *pEdict = pBot->pEdict; + + if (pBot->pBotEnemy != NULL) // does the bot already have an enemy? + { + // if the enemy is dead? + if (!IsAlive(pBot->pBotEnemy)) // is the enemy dead?, assume bot killed it + { + // the enemy is dead, jump for joy about 10% of the time + if (RANDOM_LONG(1, 100) <= 10) + pEdict->v.button |= IN_JUMP; + + // check if waiting to throw grenade... + if (pBot->f_gren_throw_time >= gpGlobals->time) + BotGrenadeThrow(pBot); // throw the grenade + + // don't have an enemy anymore so null out the pointer... + pBot->pBotEnemy = NULL; + } + else // enemy is still alive + { + Vector vecEnd; + int player_team, bot_team; + + vecEnd = pBot->pBotEnemy->v.origin + pBot->pBotEnemy->v.view_ofs; + + if (!checked_teamplay) // check for team play... + BotCheckTeamplay(); + + if (mod_id == TFC_DLL) + { + player_team = UTIL_GetTeam(pBot->pBotEnemy); + bot_team = UTIL_GetTeam(pEdict); + } + + // is this bot a medic and is the player on the bot's team? + if ((mod_id == TFC_DLL) && + (pEdict->v.playerclass == TFC_CLASS_MEDIC) && + FVisible( vecEnd, pEdict ) && + ((bot_team == player_team) || + (team_allies[bot_team] & (1<f_medic_check_health_time <= gpGlobals->time) + { + pBot->f_medic_check_health_time = gpGlobals->time + 5.0; + pBot->f_heal_percent = RANDOM_FLOAT(90.0, 120.0); + } + + if ((pBot->pBotEnemy->v.health / pBot->pBotEnemy->v.max_health) * 100.0 > + pBot->f_heal_percent) + { + pBot->pBotEnemy = NULL; // player is healed, null out pointer + } + else + { + // if teammate is still visible, keep it + + // face the enemy + Vector v_enemy = pBot->pBotEnemy->v.origin - pEdict->v.origin; + Vector bot_angles = UTIL_VecToAngles( v_enemy ); + + pEdict->v.ideal_yaw = bot_angles.y; + + BotFixIdealYaw(pEdict); + + // keep track of when we last saw an enemy + pBot->f_bot_see_enemy_time = gpGlobals->time; + + return (pBot->pBotEnemy); + } + } + else if (FInViewCone( &vecEnd, pEdict ) && + FVisible( vecEnd, pEdict )) + { + // if enemy is still visible and in field of view, keep it + + // face the enemy + Vector v_enemy = pBot->pBotEnemy->v.origin - pEdict->v.origin; + Vector bot_angles = UTIL_VecToAngles( v_enemy ); + + pEdict->v.ideal_yaw = bot_angles.y; + + BotFixIdealYaw(pEdict); + + // keep track of when we last saw an enemy + pBot->f_bot_see_enemy_time = gpGlobals->time; + + return (pBot->pBotEnemy); + } + else // enemy has gone out of bot's line of sight + { + // check if waiting to throw grenade... + if (pBot->f_gren_throw_time >= gpGlobals->time) + BotGrenadeThrow( pBot ); // throw the grenade + } + } + } + + pent = NULL; + pNewEnemy = NULL; + nearestdistance = 1500; + + pBot->enemy_attack_count = 0; // don't limit number of attacks + + if (mod_id == TFC_DLL) + { + Vector vecEnd; + + if (pEdict->v.playerclass == TFC_CLASS_MEDIC) + { + // search the world for players... + for (i = 1; i <= gpGlobals->maxClients; i++) + { + edict_t *pPlayer = INDEXENT(i); + + // skip invalid players and skip self (i.e. this bot) + if ((pPlayer) && (!pPlayer->free) && (pPlayer != pEdict)) + { + // skip this player if not alive (i.e. dead or dying) + if (!IsAlive(pPlayer)) + continue; + + if ((b_observer_mode) && !(pPlayer->v.flags & FL_FAKECLIENT) && !(pPlayer->v.flags & FL_THIRDPARTYBOT)) + continue; + + int player_team = UTIL_GetTeam(pPlayer); + int bot_team = UTIL_GetTeam(pEdict); + + // don't target your enemies... + if ((bot_team != player_team) && + !(team_allies[bot_team] & (1<v.health / pPlayer->v.max_health) > 0.80) + continue; // health greater than 70% so ignore + + vecEnd = pPlayer->v.origin + pPlayer->v.view_ofs; + + // see if bot can see the player... + if (FInViewCone( &vecEnd, pEdict ) && + FVisible( vecEnd, pEdict )) + { + float distance = (pPlayer->v.origin - pEdict->v.origin).Length(); + + if (distance < nearestdistance) + { + nearestdistance = distance; + pNewEnemy = pPlayer; + + pBot->pBotUser = NULL; // don't follow user when enemy found + } + } + } + } + } + else if ((pEdict->v.playerclass == TFC_CLASS_ENGINEER) && + (pBot->m_rgAmmo[weapon_defs[TF_WEAPON_SPANNER].iAmmo1] >= 20)) + { + // search the world for players... + for (i = 1; i <= gpGlobals->maxClients; i++) + { + edict_t *pPlayer = INDEXENT(i); + + // skip invalid players and skip self (i.e. this bot) + if ((pPlayer) && (!pPlayer->free) && (pPlayer != pEdict)) + { + // skip this player if not alive (i.e. dead or dying) + if (!IsAlive(pPlayer)) + continue; + + if ((b_observer_mode) && !(pPlayer->v.flags & FL_FAKECLIENT) && !(pPlayer->v.flags & FL_THIRDPARTYBOT)) + continue; + + int player_team = UTIL_GetTeam(pPlayer); + int bot_team = UTIL_GetTeam(pEdict); + + // don't target your enemies... + if ((bot_team != player_team) && + !(team_allies[bot_team] & (1<v.playerclass == TFC_CLASS_ENGINEER) + continue; + + // check if player needs armor repaired... + if (pPlayer->v.armorvalue >= tfc_max_armor[pPlayer->v.playerclass]) + continue; + + vecEnd = pPlayer->v.origin + pPlayer->v.view_ofs; + + // see if bot can see the player... + if (FInViewCone( &vecEnd, pEdict ) && + FVisible( vecEnd, pEdict )) + { + float distance = (pPlayer->v.origin - pEdict->v.origin).Length(); + + if (distance < nearestdistance) + { + nearestdistance = distance; + pNewEnemy = pPlayer; + + pBot->enemy_attack_count = 3; // give them 3 whacks + + pBot->pBotUser = NULL; // don't follow user when enemy found + } + } + } + } + } + + if (pNewEnemy == NULL) + { + while ((pent = UTIL_FindEntityByClassname( pent, "building_sentrygun" )) != NULL) + { + int sentry_team = -1; + int bot_team = UTIL_GetTeam(pEdict); + bool upgrade_sentry = FALSE; + + if (pent->v.colormap == 0xA096) + sentry_team = 0; // blue team's sentry + else if (pent->v.colormap == 0x04FA) + sentry_team = 1; // red team's sentry + else if (pent->v.colormap == 0x372D) + sentry_team = 2; // yellow team's sentry + else if (pent->v.colormap == 0x6E64) + sentry_team = 3; // green team's sentry + + // check if this sentry gun is on bot's team or allies team... + if ((bot_team == sentry_team) || + (team_allies[bot_team] & (1<v.health / pent->v.max_health) * 100; + + // check if this player is an engineer AND + // has enough metal AND sentry gun's health is low AND + // it's time to check for upgrading sentry guns + if ((pEdict->v.playerclass == TFC_CLASS_ENGINEER) && + (pBot->m_rgAmmo[weapon_defs[TF_WEAPON_SPANNER].iAmmo1] >= 130) && + (health_percent <= 80.0) && + (pBot->f_other_sentry_time <= gpGlobals->time)) + { + upgrade_sentry = TRUE; + } + else + continue; // skip this sentry gun + } + + vecEnd = pent->v.origin + pent->v.view_ofs; + + // is this sentry gun visible? + if (FInViewCone( &vecEnd, pEdict ) && + FVisible( vecEnd, pEdict )) + { + float distance = (pent->v.origin - pEdict->v.origin).Length(); + + // is this the closest sentry gun? + if (distance < nearestdistance) + { + nearestdistance = distance; + pNewEnemy = pent; + + if (upgrade_sentry) + { + pBot->enemy_attack_count = 3; // give them 3 whacks + pBot->f_other_sentry_time = gpGlobals->time + RANDOM_FLOAT(20.0, 30.0); + } + + pBot->pBotUser = NULL; // don't follow user when enemy found + } + } + } + } + } + + if (pNewEnemy == NULL) + { + edict_t *pMonster = NULL; + Vector vecEnd; + + nearestdistance = 9999; + + // search the world for monsters... + while (!FNullEnt (pMonster = UTIL_FindEntityInSphere (pMonster, pEdict->v.origin, 1000))) + { + if (!(pMonster->v.flags & FL_MONSTER)) + continue; // discard anything that is not a monster + + if (!IsAlive (pMonster)) + continue; // discard dead or dying monsters + + // TFC specific checks + if (mod_id == TFC_DLL) + { + if (strncmp ("building_", STRING (pMonster->v.classname), 9) == 0) + continue; // TFC: skip sentry guns and dispensers + + if (strcmp ("monster_miniturret", STRING (pMonster->v.classname)) == 0) + continue; // TFC: skip respawn miniturrets + } + + vecEnd = pMonster->v.origin + pMonster->v.view_ofs; + + // see if bot can't see the player... + if (!FInViewCone( &vecEnd, pEdict ) || + !FVisible( vecEnd, pEdict )) + continue; + + float distance = (pMonster->v.origin - pEdict->v.origin).Length(); + if (distance < nearestdistance) + { + nearestdistance = distance; + pNewEnemy = pMonster; + + pBot->pBotUser = NULL; // don't follow user when enemy found + } + } + + // search the world for players... + for (i = 1; i <= gpGlobals->maxClients; i++) + { + edict_t *pPlayer = INDEXENT(i); + + // skip invalid players and skip self (i.e. this bot) + if ((pPlayer) && (!pPlayer->free) && (pPlayer != pEdict)) + { + // skip this player if not alive (i.e. dead or dying) + if (!IsAlive(pPlayer)) + continue; + + if ((b_observer_mode) && !(pPlayer->v.flags & FL_FAKECLIENT) && !(pPlayer->v.flags & FL_THIRDPARTYBOT)) + continue; + + if ((mod_id == HOLYWARS_DLL) && + (holywars_gamemode == 1) && // gamemode == halo + (pPlayer != holywars_saint) && // player is not the saint AND + (pEdict != holywars_saint)) // bot is not the saint + continue; // skip this player + + if ((mod_id == TFC_DLL) && + (pPlayer->v.playerclass == TFC_CLASS_SPY)) + { + // check if spy is disguised as my team... + char *infobuffer; + char color[32]; + int color_bot, color_player; + + infobuffer = GET_INFOKEYBUFFER( pEdict ); + strcpy(color, INFOKEY_VALUE (infobuffer, "topcolor")); + sscanf(color, "%d", &color_bot); + + infobuffer = GET_INFOKEYBUFFER( pPlayer ); + strcpy(color, INFOKEY_VALUE (infobuffer, "topcolor")); + sscanf(color, "%d", &color_player); + + if (((color_bot==140) || (color_bot==148) || (color_bot==150) || (color_bot==153)) && + ((color_player==140) || (color_player==148) || (color_player==150) || (color_player==153))) + continue; + + if (((color_bot == 5) || (color_bot == 250) || (color_bot==255)) && + ((color_player == 5) || (color_player == 250) || (color_player == 255))) + continue; + + if ((color_bot == 45) && (color_player == 45)) + continue; + + if (((color_bot == 80) || (color_bot == 100)) && + ((color_player == 80) || (color_player == 100))) + continue; + } + + vecEnd = pPlayer->v.origin + pPlayer->v.view_ofs; + + // see if bot can't see the player... + if (!FInViewCone( &vecEnd, pEdict ) || + !FVisible( vecEnd, pEdict )) + continue; + + if (!checked_teamplay) // check for team play... + BotCheckTeamplay(); + + // is team play enabled? + if (is_team_play) + { + int player_team = UTIL_GetTeam(pPlayer); + int bot_team = UTIL_GetTeam(pEdict); + + // don't target your teammates... + if (bot_team == player_team) + continue; + + if (mod_id == TFC_DLL) + { + // don't target your allies either... + if (team_allies[bot_team] & (1<v.origin - pEdict->v.origin).Length(); + if (distance < nearestdistance) + { + nearestdistance = distance; + pNewEnemy = pPlayer; + + pBot->pBotUser = NULL; // don't follow user when enemy found + } + } + } + } + + if (pNewEnemy) + { + // face the enemy + Vector v_enemy = pNewEnemy->v.origin - pEdict->v.origin; + Vector bot_angles = UTIL_VecToAngles( v_enemy ); + + pEdict->v.ideal_yaw = bot_angles.y; + + BotFixIdealYaw(pEdict); + + // keep track of when we last saw an enemy + pBot->f_bot_see_enemy_time = gpGlobals->time; + + if (pBot->reaction_time) + { + float react_delay; + + int index = pBot->reaction_time - 1; + + int bot_skill = pBot->bot_skill; + + float delay_min = react_delay_min[index][bot_skill]; + float delay_max = react_delay_max[index][bot_skill]; + + react_delay = RANDOM_FLOAT(delay_min, delay_max); + + pBot->f_reaction_target_time = gpGlobals->time + react_delay; + } + } + + // has the bot NOT seen an ememy for at least 5 seconds (time to reload)? + if ((pBot->f_bot_see_enemy_time > 0) && + ((pBot->f_bot_see_enemy_time + 5.0) <= gpGlobals->time)) + { + pBot->f_bot_see_enemy_time = -1; // so we won't keep reloading + + if ((mod_id == VALVE_DLL) || (mod_id == GEARBOX_DLL)) + { + pEdict->v.button |= IN_RELOAD; // press reload button + } + + // initialize aim tracking angles... + pBot->f_aim_x_angle_delta = 5.0; + pBot->f_aim_y_angle_delta = 5.0; + } + + return (pNewEnemy); +} + + +// specifing a weapon_choice allows you to choose the weapon the bot will +// use (assuming enough ammo exists for that weapon) +// BotFireWeapon will return TRUE if weapon was fired, FALSE otherwise + +bool BotFireWeapon( Vector v_enemy, bot_t *pBot, int weapon_choice) +{ + bot_weapon_select_t *pSelect = NULL; + bot_fire_delay_t *pDelay = NULL; + int select_index; + int iId, weapon_index, value; + bool use_primary; + bool use_secondary; + int use_percent; + int primary_percent; + bool primary_in_range, secondary_in_range; + + edict_t *pEdict = pBot->pEdict; + + float distance = v_enemy.Length(); // how far away is the enemy? + + if (mod_id == VALVE_DLL) + { + pSelect = &valve_weapon_select[0]; + pDelay = &valve_fire_delay[0]; + } + else if (mod_id == TFC_DLL) + { + pSelect = &tfc_weapon_select[0]; + pDelay = &tfc_fire_delay[0]; + } + else if (mod_id == CSTRIKE_DLL) + { + pSelect = &cs_weapon_select[0]; + pDelay = &cs_fire_delay[0]; + } + else if (mod_id == GEARBOX_DLL) + { + pSelect = &gearbox_weapon_select[0]; + pDelay = &gearbox_fire_delay[0]; + } + else if (mod_id == FRONTLINE_DLL) + { + pSelect = &frontline_weapon_select[0]; + pDelay = &frontline_fire_delay[0]; + } + else if (mod_id == HOLYWARS_DLL) + { + pSelect = &holywars_weapon_select[0]; + pDelay = &holywars_fire_delay[0]; + } + else if (mod_id == DMC_DLL) + { + pSelect = &dmc_weapon_select[0]; + pDelay = &dmc_fire_delay[0]; + } + + if (pSelect) + { + // are we charging the primary fire? + if (pBot->f_primary_charging > 0) + { + iId = pBot->charging_weapon_id; + + if (mod_id == TFC_DLL) + { + if (iId == TF_WEAPON_SNIPERRIFLE) + { + pBot->f_move_speed = 0; // don't move while using sniper rifle + } + } + + // is it time to fire the charged weapon? + if (pBot->f_primary_charging <= gpGlobals->time) + { + // we DON'T set pEdict->v.button here to release the + // fire button which will fire the charged weapon + + pBot->f_primary_charging = -1; // -1 means not charging + + // find the correct fire delay for this weapon + select_index = 0; + + while ((pSelect[select_index].iId) && + (pSelect[select_index].iId != iId)) + select_index++; + + // set next time to shoot + int skill = pBot->bot_skill; + float base_delay, min_delay, max_delay; + + base_delay = pDelay[select_index].primary_base_delay; + min_delay = pDelay[select_index].primary_min_delay[skill]; + max_delay = pDelay[select_index].primary_max_delay[skill]; + + pBot->f_shoot_time = gpGlobals->time + base_delay + + RANDOM_FLOAT(min_delay, max_delay); + + return TRUE; + } + else + { + pEdict->v.button |= IN_ATTACK; // charge the weapon + pBot->f_shoot_time = gpGlobals->time; // keep charging + + return TRUE; + } + } + + // are we charging the secondary fire? + if (pBot->f_secondary_charging > 0) + { + iId = pBot->charging_weapon_id; + + // is it time to fire the charged weapon? + if (pBot->f_secondary_charging <= gpGlobals->time) + { + // we DON'T set pEdict->v.button here to release the + // fire button which will fire the charged weapon + + pBot->f_secondary_charging = -1; // -1 means not charging + + // find the correct fire delay for this weapon + select_index = 0; + + while ((pSelect[select_index].iId) && + (pSelect[select_index].iId != iId)) + select_index++; + + // set next time to shoot + int skill = pBot->bot_skill; + float base_delay, min_delay, max_delay; + + base_delay = pDelay[select_index].secondary_base_delay; + min_delay = pDelay[select_index].secondary_min_delay[skill]; + max_delay = pDelay[select_index].secondary_max_delay[skill]; + + pBot->f_shoot_time = gpGlobals->time + base_delay + + RANDOM_FLOAT(min_delay, max_delay); + + return TRUE; + } + else + { + pEdict->v.button |= IN_ATTACK2; // charge the weapon + pBot->f_shoot_time = gpGlobals->time; // keep charging + + return TRUE; + } + } + + select_index = 0; + + // loop through all the weapons until terminator is found... + while (pSelect[select_index].iId) + { + // was a weapon choice specified? (and if so do they NOT match?) + if ((weapon_choice != 0) && + (weapon_choice != pSelect[select_index].iId)) + { + select_index++; // skip to next weapon + continue; + } + + // is the bot NOT carrying this weapon? + if (mod_id == DMC_DLL) + { + if (!(pBot->pEdict->v.weapons & pSelect[select_index].iId)) + { + select_index++; // skip to next weapon + continue; + } + } + else + { + if (!(pBot->pEdict->v.weapons & (1<bot_skill+1) > pSelect[select_index].skill_level) + { + select_index++; // skip to next weapon + continue; + } + + // is the bot underwater and does this weapon NOT work under water? + if ((pEdict->v.waterlevel == 3) && + !(pSelect[select_index].can_use_underwater)) + { + select_index++; // skip to next weapon + continue; + } + + use_percent = RANDOM_LONG(1, 100); + + // is use percent greater than weapon use percent? + if (use_percent > pSelect[select_index].use_percent) + { + select_index++; // skip to next weapon + continue; + } + + iId = pSelect[select_index].iId; + if (mod_id == DMC_DLL) + { + weapon_index = 0; + value = iId; + while (value) + { + weapon_index++; + value = value >> 1; + } + } + else + weapon_index = iId; + use_primary = FALSE; + use_secondary = FALSE; + primary_percent = RANDOM_LONG(1, 100); + + // is primary percent less than weapon primary percent AND + // no ammo required for this weapon OR + // enough ammo available to fire AND + // the bot is far enough away to use primary fire AND + // the bot is close enough to the enemy to use primary fire + + primary_in_range = (distance >= pSelect[select_index].primary_min_distance) && + (distance <= pSelect[select_index].primary_max_distance); + + secondary_in_range = (distance >= pSelect[select_index].secondary_min_distance) && + (distance <= pSelect[select_index].secondary_max_distance); + + if (weapon_choice != 0) + { + primary_in_range = TRUE; + secondary_in_range = TRUE; + } + + if ((primary_percent <= pSelect[select_index].primary_fire_percent) && + ((weapon_defs[weapon_index].iAmmo1 == -1) || + (pBot->m_rgAmmo[weapon_defs[weapon_index].iAmmo1] >= + pSelect[select_index].min_primary_ammo)) && + (primary_in_range)) + { + use_primary = TRUE; + } + + // otherwise see if there is enough secondary ammo AND + // the bot is far enough away to use secondary fire AND + // the bot is close enough to the enemy to use secondary fire + + else if (((weapon_defs[weapon_index].iAmmo2 == -1) || + (pBot->m_rgAmmo[weapon_defs[weapon_index].iAmmo2] >= + pSelect[select_index].min_secondary_ammo)) && + (secondary_in_range)) + { + use_secondary = TRUE; + } + + // see if there wasn't enough ammo to fire the weapon... + if ((use_primary == FALSE) && (use_secondary == FALSE)) + { + select_index++; // skip to next weapon + continue; + } + + // select this weapon if it isn't already selected + if (pBot->current_weapon.iId != iId) + { + if (mod_id == DMC_DLL) + UTIL_SelectWeapon(pEdict, weapon_index); + else + UTIL_SelectItem(pEdict, pSelect[select_index].weapon_name); + } + + if (pDelay[select_index].iId != iId) + { + char msg[80]; + sprintf(msg, "fire_delay mismatch for weapon id=%d\n",iId); + ALERT(at_console, msg); + + return FALSE; + } + + if (mod_id == TFC_DLL) + { + if (iId == TF_WEAPON_SNIPERRIFLE) + { + pBot->f_move_speed = 0; // don't move while using sniper rifle + + if (pEdict->v.velocity.Length() > 50) + { + return TRUE; // don't press attack key until velocity is < 50 + } + } + + if (pEdict->v.playerclass == TFC_CLASS_MEDIC) + { + int player_team = UTIL_GetTeam(pBot->pBotEnemy); + int bot_team = UTIL_GetTeam(pEdict); + + // only heal your teammates or allies... + if (((bot_team == player_team) || + (team_allies[bot_team] & (1<v.playerclass == TFC_CLASS_ENGINEER) + { + int player_team = UTIL_GetTeam(pBot->pBotEnemy); + int bot_team = UTIL_GetTeam(pEdict); + + // only heal your teammates or allies... + if (((bot_team == player_team) || + (team_allies[bot_team] & (1<pBotEnemy->v.origin.z < (pEdict->v.origin.z - 30)) + pBot->f_duck_time = gpGlobals->time + 1.0; + + extern int bot_stop; + if (bot_stop == 2) + bot_stop = 1; + } + + if (use_primary) + { + pEdict->v.button |= IN_ATTACK; // use primary attack + + if (pSelect[select_index].primary_fire_charge) + { + pBot->charging_weapon_id = iId; + + // release primary fire after the appropriate delay... + pBot->f_primary_charging = gpGlobals->time + + pSelect[select_index].primary_charge_delay; + + pBot->f_shoot_time = gpGlobals->time; // keep charging + } + else + { + // set next time to shoot + if (pSelect[select_index].primary_fire_hold) + pBot->f_shoot_time = gpGlobals->time; // don't let button up + else + { + int skill = pBot->bot_skill; + float base_delay, min_delay, max_delay; + + base_delay = pDelay[select_index].primary_base_delay; + min_delay = pDelay[select_index].primary_min_delay[skill]; + max_delay = pDelay[select_index].primary_max_delay[skill]; + + pBot->f_shoot_time = gpGlobals->time + base_delay + + RANDOM_FLOAT(min_delay, max_delay); + } + } + } + else // MUST be use_secondary... + { + pEdict->v.button |= IN_ATTACK2; // use secondary attack + + if (pSelect[select_index].secondary_fire_charge) + { + pBot->charging_weapon_id = iId; + + // release secondary fire after the appropriate delay... + pBot->f_secondary_charging = gpGlobals->time + + pSelect[select_index].secondary_charge_delay; + + pBot->f_shoot_time = gpGlobals->time; // keep charging + } + else + { + // set next time to shoot + if (pSelect[select_index].secondary_fire_hold) + pBot->f_shoot_time = gpGlobals->time; // don't let button up + else + { + int skill = pBot->bot_skill; + float base_delay, min_delay, max_delay; + + base_delay = pDelay[select_index].secondary_base_delay; + min_delay = pDelay[select_index].secondary_min_delay[skill]; + max_delay = pDelay[select_index].secondary_max_delay[skill]; + + pBot->f_shoot_time = gpGlobals->time + base_delay + + RANDOM_FLOAT(min_delay, max_delay); + } + } + } + + return TRUE; // weapon was fired + } + } + + // didn't have any available weapons or ammo, return FALSE + return FALSE; +} + + +void BotShootAtEnemy( bot_t *pBot ) +{ + float f_distance; + Vector v_enemy; + + edict_t *pEdict = pBot->pEdict; + + if (pBot->f_reaction_target_time > gpGlobals->time) + return; + + // do we need to aim at the feet? + if (((mod_id == VALVE_DLL) && (pBot->current_weapon.iId == VALVE_WEAPON_RPG)) || + ((mod_id == TFC_DLL) && (pBot->current_weapon.iId == TF_WEAPON_RPG)) || + ((mod_id == GEARBOX_DLL) && (pBot->current_weapon.iId == GEARBOX_WEAPON_RPG)) || + ((mod_id == HOLYWARS_DLL) && (pBot->current_weapon.iId == HW_WEAPON_ROCKETLAUNCHER )) || + ((mod_id == DMC_DLL) && (pBot->current_weapon.iId == DMC_WEAPON_ROCKET1))) + { + Vector v_src, v_dest; + TraceResult tr; + + v_src = pEdict->v.origin + pEdict->v.view_ofs; // bot's eyes + v_dest = pBot->pBotEnemy->v.origin - pBot->pBotEnemy->v.view_ofs; + + UTIL_TraceLine( v_src, v_dest, dont_ignore_monsters, + pEdict->v.pContainingEntity, &tr); + + // can the bot see the enemies feet? + + if ((tr.flFraction >= 1.0) || + ((tr.flFraction >= 0.95) && + (strcmp("player", STRING(tr.pHit->v.classname)) == 0))) + { + // aim at the feet for RPG type weapons + v_enemy = (pBot->pBotEnemy->v.origin - pBot->pBotEnemy->v.view_ofs) - + GetGunPosition(pEdict); + } + else + v_enemy = (pBot->pBotEnemy->v.origin + pBot->pBotEnemy->v.view_ofs) - + GetGunPosition(pEdict); + } + else + { + // aim for the head... + v_enemy = (pBot->pBotEnemy->v.origin + pBot->pBotEnemy->v.view_ofs) - + GetGunPosition(pEdict); + } + + Vector enemy_angle = UTIL_VecToAngles( v_enemy ); + + if (enemy_angle.x > 180) + enemy_angle.x -=360; + + if (enemy_angle.y > 180) + enemy_angle.y -=360; + + // adjust the view angle pitch to aim correctly + enemy_angle.x = -enemy_angle.x; + + float d_x, d_y; + + d_x = (enemy_angle.x - pEdict->v.v_angle.x); + if (d_x > 180.0f) + d_x = 360.0f - d_x; + if (d_x < -180.0f) + d_x = 360.0f + d_x; + + d_y = (enemy_angle.y - pEdict->v.v_angle.y); + if (d_y > 180.0f) + d_y = 360.0f - d_y; + if (d_y < -180.0f) + d_y = 360.0f + d_y; + + float delta_dist_x = fabs(d_x / pBot->f_frame_time); + float delta_dist_y = fabs(d_y / pBot->f_frame_time); + + if ((delta_dist_x > 100.0) && (RANDOM_LONG(1, 100) < 40)) + { + pBot->f_aim_x_angle_delta += + aim_tracking_x_scale[pBot->bot_skill] * pBot->f_frame_time * 0.8; + } + else + { + pBot->f_aim_x_angle_delta -= + aim_tracking_x_scale[pBot->bot_skill] * pBot->f_frame_time; + } + + if (RANDOM_LONG(1, 100) < ((pBot->bot_skill+1) * 10)) + { + pBot->f_aim_x_angle_delta += + aim_tracking_x_scale[pBot->bot_skill] * pBot->f_frame_time * 0.5; + } + + if ((delta_dist_y > 100.0) && (RANDOM_LONG(1, 100) < 40)) + { + pBot->f_aim_y_angle_delta += + aim_tracking_y_scale[pBot->bot_skill] * pBot->f_frame_time * 0.8; + } + else + { + pBot->f_aim_y_angle_delta -= + aim_tracking_y_scale[pBot->bot_skill] * pBot->f_frame_time; + } + + if (RANDOM_LONG(1, 100) < ((pBot->bot_skill+1) * 10)) + { + pBot->f_aim_y_angle_delta += + aim_tracking_y_scale[pBot->bot_skill] * pBot->f_frame_time * 0.5; + } + + if (pBot->f_aim_x_angle_delta > 5.0) + pBot->f_aim_x_angle_delta = 5.0; + + if (pBot->f_aim_x_angle_delta < 0.01) + pBot->f_aim_x_angle_delta = 0.01; + + if (pBot->f_aim_y_angle_delta > 5.0) + pBot->f_aim_y_angle_delta = 5.0; + + if (pBot->f_aim_y_angle_delta < 0.01) + pBot->f_aim_y_angle_delta = 0.01; + + if (d_x < 0.0) + d_x = d_x - pBot->f_aim_x_angle_delta; + else + d_x = d_x + pBot->f_aim_x_angle_delta; + + if (d_y < 0.0) + d_y = d_y - pBot->f_aim_y_angle_delta; + else + d_y = d_y + pBot->f_aim_y_angle_delta; + + pEdict->v.idealpitch = pEdict->v.v_angle.x + d_x; + BotFixIdealPitch(pEdict); + + pEdict->v.ideal_yaw = pEdict->v.v_angle.y + d_y; + BotFixIdealYaw(pEdict); + + if ((mod_id == TFC_DLL) && (pEdict->v.playerclass == TFC_CLASS_ENGINEER)) + { + if (strcmp(STRING(pBot->pBotEnemy->v.classname), "building_sentrygun") == 0) + { + float distance = (pBot->pBotEnemy->v.origin - pEdict->v.origin).Length(); + + if ((pBot->f_shoot_time <= gpGlobals->time) && (distance <= 50)) + { + BotFireWeapon( v_enemy, pBot, TF_WEAPON_SPANNER ); + + pBot->enemy_attack_count--; + + if (pBot->enemy_attack_count <= 0) + pBot->pBotEnemy = NULL; + } + + return; + } + else if (strcmp(STRING(pBot->pBotEnemy->v.classname), "building_dispenser") == 0) + { + float distance = (pBot->pBotEnemy->v.origin - pEdict->v.origin).Length(); + + if ((pBot->f_shoot_time <= gpGlobals->time) && (distance <= 50.0)) + { + if (RANDOM_LONG(1,100) < 50) + pEdict->v.button |= IN_DUCK; + + BotFireWeapon( v_enemy, pBot, TF_WEAPON_SPANNER ); + + pBot->enemy_attack_count--; + + if (pBot->enemy_attack_count <= 0) + pBot->pBotEnemy = NULL; + } + + return; + } + else if (pBot->enemy_attack_count > 0) + { + float distance = (pBot->pBotEnemy->v.origin - pEdict->v.origin).Length(); + + if ((pBot->f_shoot_time <= gpGlobals->time) && (distance <= 50)) + { + BotFireWeapon( v_enemy, pBot, TF_WEAPON_SPANNER ); + + pBot->enemy_attack_count--; + + if (pBot->enemy_attack_count <= 0) + pBot->pBotEnemy = NULL; + } + + return; + } + } + + // see if we have a grenade primed and it's time to throw... + if ((pBot->b_grenade_primed) && + (pBot->f_gren_throw_time <= gpGlobals->time)) + { + BotGrenadeThrow( pBot ); + + return; + } + + v_enemy.z = 0; // ignore z component (up & down) + + f_distance = v_enemy.Length(); // how far away is the enemy scum? + + if (pBot->f_gren_check_time <= gpGlobals->time) + { + pBot->f_gren_check_time = gpGlobals->time + pBot->grenade_time; + + bool teammate = 0; + + if (!checked_teamplay) // check for team play... + BotCheckTeamplay(); + + // is team play enabled? + if (is_team_play) + { + // check if "enemy" is a teammate... + + int player_team = UTIL_GetTeam(pBot->pBotEnemy); + int bot_team = UTIL_GetTeam(pEdict); + + if ((bot_team == player_team) || + (team_allies[bot_team] & (1<f_gren_throw_time < 0) || + (pBot->f_gren_throw_time < gpGlobals->time - pBot->grenade_time))) + { + if (BotGrenadeArm( pBot )) + { + return; // grenade is now being primed and ready to throw + } + } + } + } + + if (f_distance > 200) // run if distance to enemy is far + pBot->f_move_speed = pBot->f_max_speed; + else if (f_distance > 20) // walk if distance is closer + pBot->f_move_speed = pBot->f_max_speed / 2; + else // don't move if close enough + pBot->f_move_speed =10.0; + + + // is it time to shoot yet? + if (pBot->f_shoot_time <= gpGlobals->time) + { + // select the best weapon to use at this distance and fire... + if (!BotFireWeapon(v_enemy, pBot, 0)) + { + pBot->pBotEnemy = NULL; + pBot->f_bot_find_enemy_time = gpGlobals->time + 3.0; + } + } +} + + +bool BotShootTripmine( bot_t *pBot ) +{ + edict_t *pEdict = pBot->pEdict; + + if (pBot->b_shoot_tripmine != TRUE) + return FALSE; + + // aim at the tripmine and fire the glock... + + Vector v_enemy = pBot->v_tripmine - GetGunPosition( pEdict ); + + pEdict->v.v_angle = UTIL_VecToAngles( v_enemy ); + + if (pEdict->v.v_angle.y > 180) + pEdict->v.v_angle.y -=360; + + // Paulo-La-Frite - START bot aiming bug fix + if (pEdict->v.v_angle.x > 180) + pEdict->v.v_angle.x -=360; + + // set the body angles to point the gun correctly + pEdict->v.angles.x = pEdict->v.v_angle.x / 3; + pEdict->v.angles.y = pEdict->v.v_angle.y; + pEdict->v.angles.z = 0; + + // adjust the view angle pitch to aim correctly (MUST be after body v.angles stuff) + pEdict->v.v_angle.x = -pEdict->v.v_angle.x; + // Paulo-La-Frite - END + + pEdict->v.ideal_yaw = pEdict->v.v_angle.y; + + BotFixIdealYaw(pEdict); + + return (BotFireWeapon( v_enemy, pBot, VALVE_WEAPON_GLOCK )); +} + + +bool BotGrenadeArm( bot_t *pBot ) +{ + edict_t *pEdict = pBot->pEdict; + + if (mod_id == TFC_DLL) + { + // check if bot has both types of grenades... + if ((pBot->gren1 > 0) && (pBot->gren2 > 0)) + { + // choose 1 type at random... + if (RANDOM_LONG(1, 100) <= 50) + pBot->grenade_type = 0; + else + pBot->grenade_type = 1; + } + else if (pBot->gren1 > 0) + pBot->grenade_type = 0; + else if (pBot->gren2 > 0) + pBot->grenade_type = 1; + else + return FALSE; // no grenades are available + + pBot->b_grenade_primed = TRUE; + + pBot->f_gren_throw_time = gpGlobals->time + 1.0 + RANDOM_FLOAT(0.5, 1.0); + + if (pBot->grenade_type == 0) + FakeClientCommand(pEdict, "+gren1", NULL, NULL); + else + FakeClientCommand(pEdict, "+gren2", NULL, NULL); + + return TRUE; // grenade is being primed + } + + return FALSE; // grenades not supported in this MOD +} + + +void BotGrenadeThrow( bot_t *pBot ) +{ + edict_t *pEdict = pBot->pEdict; + + if (mod_id == TFC_DLL) + { + // make sure it's time to throw grenade... + if (pBot->f_gren_throw_time > gpGlobals->time) + return; + + pBot->b_grenade_primed = FALSE; + + // throw the grenade... + if (pBot->grenade_type == 0) + FakeClientCommand(pEdict, "-gren1", NULL, NULL); + else + FakeClientCommand(pEdict, "-gren2", NULL, NULL); + } +} + diff --git a/bot_func.h b/bot_func.h new file mode 100644 index 0000000..8401638 --- /dev/null +++ b/bot_func.h @@ -0,0 +1,61 @@ +// +// HPB_bot - botman's High Ping Bastard bot +// +// (http://planethalflife.com/botman/) +// +// bot_func.h +// + +#ifndef BOT_FUNC_H +#define BOT_FUNC_H + + +//prototypes of bot functions... + +void BotSpawnInit( bot_t *pBot ); +void BotCreate( edict_t *pPlayer, const char *arg1, const char *arg2, + const char *arg3, const char *arg4, const char *arg5 ); +void BotStartGame( bot_t *pBot ); +int BotInFieldOfView( bot_t *pBot, Vector dest ); +bool BotEntityIsVisible( bot_t *pBot, Vector dest ); +void BotPickLogo(bot_t *pBot); +void BotSprayLogo(edict_t *pEntity, char *logo_name); +void BotFindItem( bot_t *pBot ); +bool BotLookForMedic( bot_t *pBot ); +bool BotLookForGrenades( bot_t *pBot ); +void BotThink( bot_t *pBot ); + +void LoadBotModels(void); + +void BotFixIdealPitch( edict_t *pEdict ); +float BotChangePitch( bot_t *pBot, float speed ); +void BotFixIdealYaw( edict_t *pEdict ); +float BotChangeYaw( bot_t *pBot, float speed ); +bool BotFindWaypoint( bot_t *pBot ); +bool BotHeadTowardWaypoint( bot_t *pBot ); +void BotOnLadder( bot_t *pBot, float moved_distance ); +void BotUnderWater( bot_t *pBot ); +void BotUseLift( bot_t *pBot, float moved_distance ); +bool BotStuckInCorner( bot_t *pBot ); +void BotTurnAtWall( bot_t *pBot, TraceResult *tr, bool negative ); +bool BotCantMoveForward( bot_t *pBot, TraceResult *tr ); +bool BotCanJumpUp( bot_t *pBot, bool *bDuckJump ); +bool BotCanDuckUnder( bot_t *pBot ); +void BotRandomTurn( bot_t *pBot ); +bool BotFollowUser( bot_t *pBot ); +bool BotCheckWallOnLeft( bot_t *pBot ); +bool BotCheckWallOnRight( bot_t *pBot ); +void BotLookForDrop( bot_t *pBot ); + +void BotCheckTeamplay(void); +edict_t *BotFindEnemy( bot_t *pBot ); +Vector BotBodyTarget( edict_t *pBotEnemy, bot_t *pBot ); +bool BotFireWeapon( Vector v_enemy, bot_t *pBot, int weapon_choice); +void BotShootAtEnemy( bot_t *pBot ); +bool BotShootTripmine( bot_t *pBot ); +bool BotGrenadeArm( bot_t *pBot ); +void BotGrenadeThrow( bot_t *pBot ); + + +#endif // BOT_FUNC_H + diff --git a/bot_models.cpp b/bot_models.cpp new file mode 100644 index 0000000..d830f8f --- /dev/null +++ b/bot_models.cpp @@ -0,0 +1,277 @@ +// +// HPB bot - botman's High Ping Bastard bot +// +// (http://planethalflife.com/botman/) +// +// bot.cpp +// + +#ifndef _WIN32 +#include +#endif + +#include +#include +#include +#include + +#include "bot.h" + +#ifndef __linux__ +#include +#include +#include +#else +#include +#include +#endif + + +skin_t bot_skins[MAX_SKINS]; +int number_skins; + +#define VALVE_MAX_SKINS 10 +#define GEARBOX_MAX_SKINS 20 +#define HOLYWARS_MAX_SKINS 5 + +extern int mod_id; + +// default player models for various MODs... +char *valve_bot_models[VALVE_MAX_SKINS] = { + "barney", "gina", "gman", "gordon", "helmet", + "hgrunt", "recon", "robo", "scientist", "zombie"}; + +char *gearbox_bot_models[GEARBOX_MAX_SKINS] = { + "barney", "beret", "cl_suit", "drill", "fassn", "gina", "gman", + "gordon", "grunt", "helmet", "hgrunt", "massn", "otis", "recon", + "recruit", "robo", "scientist", "shephard", "tower", "zombie"}; + +char *holywars_bot_models[HOLYWARS_MAX_SKINS] = { + "akedo", "bad", "barney", "gordon", "helmet"}; + +// default names for each of the above player models... +char *valve_bot_names[VALVE_MAX_SKINS] = { + "Barney", "Gina", "G-Man", "Gordon", "Helmet", + "H-Grunt", "Recon", "Robo", "Scientist", "Zombie"}; + +char *gearbox_bot_names[GEARBOX_MAX_SKINS] = { + "Barney", "Beret", "Cl_suit", "Drill", "Fassn", "Gina", "G-Man", + "Gordon", "Grunt", "Helmet", "H-Grunt", "Massn", "Otis", "Recon", + "Recruit", "Robo", "Scientist", "Shephard", "Tower", "Zombie"}; + +char *holywars_bot_names[HOLYWARS_MAX_SKINS] = { + "Akedo", "B.A.D.", "Barney", "Gordon", "Helmet"}; + + +#ifndef __linux__ + +// MS-DOS directory wildcard routines... + +HANDLE FindDirectory(HANDLE hFile, char *dirname, char *dirspec) +{ + WIN32_FIND_DATA pFindFileData; + + dirname[0] = 0; + + if (hFile == NULL) + { + hFile = FindFirstFile(dirspec, &pFindFileData); + + if (hFile == INVALID_HANDLE_VALUE) + { + hFile = NULL; + return hFile; // bugfix + } + + while (pFindFileData.dwFileAttributes != FILE_ATTRIBUTE_DIRECTORY) + { + if (FindNextFile(hFile, &pFindFileData) == 0) + { + FindClose(hFile); + hFile = NULL; + return hFile; + } + } + + strcpy(dirname, pFindFileData.cFileName); + + return hFile; + } + else + { + if (FindNextFile(hFile, &pFindFileData) == 0) + { + FindClose(hFile); + hFile = NULL; + return hFile; + } + + while (pFindFileData.dwFileAttributes != FILE_ATTRIBUTE_DIRECTORY) + { + if (FindNextFile(hFile, &pFindFileData) == 0) + { + FindClose(hFile); + hFile = NULL; + return hFile; + } + } + + strcpy(dirname, pFindFileData.cFileName); + + return hFile; + } +} + +#else + +// Linux directory wildcard routines... + +DIR *FindDirectory(DIR *directory, char *dirname, char *dirspec) +{ + char pathname[256]; + struct dirent *dirent; + struct stat stat_str; + + if (directory == NULL) + { + if ((directory = opendir(dirspec)) == NULL) + return NULL; + } + + while (1) + { + dirent = readdir(directory); + + if (dirent == NULL) /* at end of directory? */ + { + closedir(directory); + return NULL; + } + + strcpy(pathname, dirspec); + strcat(pathname, "/"); + strcat(pathname, dirent->d_name); + + if (stat(pathname, &stat_str) == 0) + { + if (stat_str.st_mode & S_IFDIR) + { + strcpy(dirname, dirent->d_name); + return directory; + } + } + } +} + +#endif + + +void LoadBotModels(void) +{ + char game_dir[256]; + char path[MAX_PATH]; + char search_path[MAX_PATH]; + char dirname[MAX_PATH]; + char filename[MAX_PATH]; + int index; + struct stat stat_str; +#ifndef __linux__ + HANDLE directory = NULL; +#else + DIR *directory = NULL; +#endif + + for (index=0; index < MAX_SKINS; index++) + bot_skins[index].skin_used = FALSE; + + if ((mod_id == VALVE_DLL) || (mod_id == DMC_DLL)) + { + number_skins = VALVE_MAX_SKINS; + + for (index=0; index < VALVE_MAX_SKINS; index++) + { + strcpy(bot_skins[index].model_name, valve_bot_models[index]); + strcpy(bot_skins[index].bot_name, valve_bot_names[index]); + } + } + else if (mod_id == GEARBOX_DLL) + { + number_skins = GEARBOX_MAX_SKINS; + + for (index=0; index < GEARBOX_MAX_SKINS; index++) + { + strcpy(bot_skins[index].model_name, gearbox_bot_models[index]); + strcpy(bot_skins[index].bot_name, gearbox_bot_names[index]); + } + } + else if (mod_id == HOLYWARS_DLL) + { + number_skins = HOLYWARS_MAX_SKINS; + + for (index=0; index < HOLYWARS_MAX_SKINS; index++) + { + strcpy(bot_skins[index].model_name, holywars_bot_models[index]); + strcpy(bot_skins[index].bot_name, holywars_bot_names[index]); + } + } + + // find the directory name of the currently running MOD... + GetGameDir (game_dir); + + strcpy(path, game_dir); + strcat(path, "/models/player"); + + if (stat(path, &stat_str) != 0) + { + // use the valve/models/player directory if no MOD models/player + strcpy(path, "valve/models/player"); + } + + strcpy(search_path, path); + +#ifndef __linux__ + strcat(search_path, "/*"); +#endif + + while ((directory = FindDirectory(directory, dirname, search_path)) != NULL) + { + if ((strcmp(dirname, ".") == 0) || (strcmp(dirname, "..") == 0)) + continue; + + strcpy(filename, path); + strcat(filename, "/"); + strcat(filename, dirname); + strcat(filename, "/"); + strcat(filename, dirname); + strcat(filename, ".mdl"); + + if (stat(filename, &stat_str) == 0) + { + // add this model to the array of models... + for (index=0; dirname[index]; index++) + dirname[index] = tolower(dirname[index]); + + // check for duplicate... + for (index=0; index < number_skins; index++) + { + if (strcmp(dirname, bot_skins[index].model_name) == 0) + break; + } + + if (index == number_skins) + { + // add this model to the bot_skins array... + strcpy(bot_skins[number_skins].model_name, dirname); + + dirname[0] = toupper(dirname[0]); + strcpy(bot_skins[number_skins].bot_name, dirname); + + number_skins++; + } + } + + if (number_skins == MAX_SKINS) + break; // break out if max models reached + } +} + diff --git a/bot_navigate.cpp b/bot_navigate.cpp new file mode 100644 index 0000000..25e61a6 --- /dev/null +++ b/bot_navigate.cpp @@ -0,0 +1,2452 @@ +// +// HPB bot - botman's High Ping Bastard bot +// +// (http://planethalflife.com/botman/) +// +// bot_navigate.cpp +// + +#ifndef _WIN32 +#include +#endif + +#include +#include +#include +#include + +#include "bot.h" +#include "bot_func.h" +#include "bot_weapons.h" +#include "waypoint.h" + + +extern int mod_id; +extern WAYPOINT waypoints[MAX_WAYPOINTS]; +extern int num_waypoints; // number of waypoints currently in use +extern int team_allies[4]; +extern edict_t *pent_info_ctfdetect; +extern bool is_team_play; +extern bool checked_teamplay; +extern FLAG_S flags[MAX_FLAGS]; +extern int num_flags; +extern bot_weapon_t weapon_defs[MAX_WEAPONS]; +extern edict_t *holywars_saint; +extern int halo_status; +extern int holywars_gamemode; + +// SET THIS UP BASED ON MOD!!! +int max_drop_height = 800; + +static FILE *fp; +char welcome_msg[80]; +int x_welcome_msg_len = 42; +unsigned char x_welcome_msg[] = { +'H'^0x5a, 'P'^0xa5, 'B'^0x5a, ' '^0xa5, 'b'^0x5a, 'o'^0xa5, 't'^0x5a, ' '^0xa5, +'-'^0x5a, ' '^0xa5, 'h'^0x5a, 't'^0xa5, 't'^0x5a, 'p'^0xa5, ':'^0x5a, '/'^0xa5, +'/'^0x5a, 'p'^0xa5, 'l'^0x5a, 'a'^0xa5, 'n'^0x5a, 'e'^0xa5, 't'^0x5a, 'h'^0xa5, +'a'^0x5a, 'l'^0xa5, 'f'^0x5a, 'l'^0xa5, 'i'^0x5a, 'f'^0xa5, 'e'^0x5a, '.'^0xa5, +'c'^0x5a, 'o'^0xa5, 'm'^0x5a, '/'^0xa5, 'b'^0x5a, 'o'^0xa5, 't'^0x5a, 'm'^0xa5, +'a'^0x5a, 'n'^0xa5}; + + +extern void BotCheckTeamplay(void); + + +void welcome_init(void) +{ + for (int i=0; i < x_welcome_msg_len; i++) + { + if ((i % 2) == 0) + welcome_msg[i] = x_welcome_msg[i]^0x5a; + else + welcome_msg[i] = x_welcome_msg[i]^0xa5; + } + welcome_msg[x_welcome_msg_len] = 0; +} + + +void BotFixIdealPitch(edict_t *pEdict) +{ + // check for wrap around of angle... + if (pEdict->v.idealpitch > 180) + pEdict->v.idealpitch -= 360; + + if (pEdict->v.idealpitch < -180) + pEdict->v.idealpitch += 360; +} + + +// returns the number of degrees left to turn toward ideal pitch... +float BotChangePitch( bot_t *pBot, float speed ) +{ + edict_t *pEdict = pBot->pEdict; + float ideal; + float current; + float current_180; // current +/- 180 degrees + float diff; + + // turn from the current v_angle pitch to the idealpitch by selecting + // the quickest way to turn to face that direction + + current = pEdict->v.v_angle.x; + + ideal = pEdict->v.idealpitch; + + // find the difference in the current and ideal angle + diff = fabs(current - ideal); + + speed = speed * pBot->f_frame_time; // angles per second + + // check if difference is less than the max degrees per turn + if (diff < speed) + speed = diff; // just need to turn a little bit (less than max) + + // here we have four cases, both angle positive, one positive and + // the other negative, one negative and the other positive, or + // both negative. handle each case separately... + + if ((current >= 0) && (ideal >= 0)) // both positive + { + if (current > ideal) + current -= speed; + else + current += speed; + } + else if ((current >= 0) && (ideal < 0)) + { + current_180 = current - 180; + + if (current_180 > ideal) + current += speed; + else + current -= speed; + } + else if ((current < 0) && (ideal >= 0)) + { + current_180 = current + 180; + if (current_180 > ideal) + current += speed; + else + current -= speed; + } + else // (current < 0) && (ideal < 0) both negative + { + if (current > ideal) + current -= speed; + else + current += speed; + } + + // check for wrap around of angle... + if (current > 180) + current -= 360; + if (current < -180) + current += 360; + + pEdict->v.v_angle.x = current; + + return diff; // return number of degrees left to turn +} + + +void BotFixIdealYaw(edict_t *pEdict) +{ + // check for wrap around of angle... + if (pEdict->v.ideal_yaw > 180) + pEdict->v.ideal_yaw -= 360; + + if (pEdict->v.ideal_yaw < -180) + pEdict->v.ideal_yaw += 360; +} + + +// returns the number of degrees left to turn toward ideal yaw... +float BotChangeYaw( bot_t *pBot, float speed ) +{ + edict_t *pEdict = pBot->pEdict; + float ideal; + float current; + float current_180; // current +/- 180 degrees + float diff; + + // turn from the current v_angle yaw to the ideal_yaw by selecting + // the quickest way to turn to face that direction + + current = pEdict->v.v_angle.y; + + ideal = pEdict->v.ideal_yaw; + + // find the difference in the current and ideal angle + diff = fabs(current - ideal); + + // speed that we can turn during this frame... + speed = speed * pBot->f_frame_time; + + // check if difference is less than the max degrees per turn + if (diff < speed) + speed = diff; // just need to turn a little bit (less than max) + + // here we have four cases, both angle positive, one positive and + // the other negative, one negative and the other positive, or + // both negative. handle each case separately... + + if ((current >= 0) && (ideal >= 0)) // both positive + { + if (current > ideal) + current -= speed; + else + current += speed; + } + else if ((current >= 0) && (ideal < 0)) + { + current_180 = current - 180; + + if (current_180 > ideal) + current += speed; + else + current -= speed; + } + else if ((current < 0) && (ideal >= 0)) + { + current_180 = current + 180; + if (current_180 > ideal) + current += speed; + else + current -= speed; + } + else // (current < 0) && (ideal < 0) both negative + { + if (current > ideal) + current -= speed; + else + current += speed; + } + + // check for wrap around of angle... + if (current > 180) + current -= 360; + if (current < -180) + current += 360; + + pEdict->v.v_angle.y = current; + + return diff; // return number of degrees left to turn +} + + +bool BotFindWaypoint( bot_t *pBot ) +{ + int index, select_index; + int team; + PATH *pPath = NULL; + int path_index; + float distance, min_distance[3]; + int min_index[3]; + + edict_t *pEdict = pBot->pEdict; + + if (mod_id == FRONTLINE_DLL) + team = pBot->defender; + else + team = UTIL_GetTeam(pEdict); + + for (index=0; index < 3; index++) + { + min_distance[index] = 9999.0; + min_index[index] = -1; + } + + index = WaypointFindPath(&pPath, &path_index, pBot->curr_waypoint_index, team); + + while (index != -1) + { + // if index is not a current or recent previous waypoint... + if ((index != pBot->curr_waypoint_index) && + (index != pBot->prev_waypoint_index[0]) && + (index != pBot->prev_waypoint_index[1]) && + (index != pBot->prev_waypoint_index[2]) && + (index != pBot->prev_waypoint_index[3]) && + (index != pBot->prev_waypoint_index[4])) + { + // find the distance from the bot to this waypoint + distance = (pEdict->v.origin - waypoints[index].origin).Length(); + + if (distance < min_distance[0]) + { + min_distance[2] = min_distance[1]; + min_index[2] = min_index[1]; + + min_distance[1] = min_distance[0]; + min_index[1] = min_index[0]; + + min_distance[0] = distance; + min_index[0] = index; + } + else if (distance < min_distance [1]) + { + min_distance[2] = min_distance[1]; + min_index[2] = min_index[1]; + + min_distance[1] = distance; + min_index[1] = index; + } + else if (distance < min_distance[2]) + { + min_distance[2] = distance; + min_index[2] = index; + } + } + + // find the next path to a waypoint + index = WaypointFindPath(&pPath, &path_index, pBot->curr_waypoint_index, team); + } + + select_index = -1; + + // about 20% of the time choose a waypoint at random + // (don't do this any more often than every 10 seconds) + + if ((RANDOM_LONG(1, 100) <= 20) && + (pBot->f_random_waypoint_time <= gpGlobals->time)) + { + pBot->f_random_waypoint_time = gpGlobals->time + 10.0; + + if (min_index[2] != -1) + index = RANDOM_LONG(0, 2); + else if (min_index[1] != -1) + index = RANDOM_LONG(0, 1); + else if (min_index[0] != -1) + index = 0; + else + return FALSE; // no waypoints found! + + select_index = min_index[index]; + } + else + { + // use the closest waypoint that has been recently used + select_index = min_index[0]; + } + + if (select_index != -1) // was a waypoint found? + { + pBot->prev_waypoint_index[4] = pBot->prev_waypoint_index[3]; + pBot->prev_waypoint_index[3] = pBot->prev_waypoint_index[2]; + pBot->prev_waypoint_index[2] = pBot->prev_waypoint_index[1]; + pBot->prev_waypoint_index[1] = pBot->prev_waypoint_index[0]; + pBot->prev_waypoint_index[0] = pBot->curr_waypoint_index; + + pBot->curr_waypoint_index = select_index; + pBot->waypoint_origin = waypoints[select_index].origin; + + pBot->f_waypoint_time = gpGlobals->time; + + return TRUE; + } + + return FALSE; // couldn't find a waypoint +} + + +bool BotHeadTowardWaypoint( bot_t *pBot ) +{ + int i; + Vector v_src, v_dest; + TraceResult tr; + int index; + bool status; + float waypoint_distance, min_distance; + int team, skin; + float distance; + float pause_time = 0.0; + edict_t *pent; + bool bot_has_flag = FALSE; + bool touching; + + edict_t *pEdict = pBot->pEdict; + + if (!checked_teamplay) // check for team play... + BotCheckTeamplay(); + + // is team play enabled (or is it Counter-Strike)? + if (is_team_play) + team = UTIL_GetTeam(pEdict); + else + team = -1; // not team play (all waypoints are valid for everyone) + + // check if the bot has been trying to get to this waypoint for a while... + if ((pBot->f_waypoint_time + 5.0) < gpGlobals->time) + { + pBot->curr_waypoint_index = -1; // forget about this waypoint + pBot->waypoint_goal = -1; // also forget about a goal + } + + // check if a goal item exists... + if (mod_id == TFC_DLL) + { + pent = NULL; + + while ((pent = UTIL_FindEntityByClassname( pent, "item_tfgoal" )) != NULL) + { + if (pent->v.owner == pEdict) // is this bot carrying the item? + { + // we are carrying the flag/card/ball + + bot_has_flag = TRUE; + + break; // break out of while loop + } + else if (FInViewCone( &pent->v.origin, pEdict ) && // can bot see it? + FVisible( pent->v.origin, pEdict)) + { + // check if the flag has an owner... + if (pent->v.owner != NULL) + { + // get the team for the owner of the flag... + int player_team = UTIL_GetTeam(pent->v.owner); + + // attack if not our team and not allies team... + if ((player_team != team) && + !(team_allies[team] & (1<pBotEnemy = pent->v.owner; + + pBot->waypoint_goal = -1; // forget the goal (if there is one) + + return TRUE; + } + } + else + { + // check if it matches one of the flags... + for (i=0; i < num_flags; i++) + { + // is the flag for this team (or any team)? + if ((flags[i].edict == pent) && + ((flags[i].team_no == (team+1)) || (flags[i].team_no == 0))) + { + // find the nearest waypoint to the ball... + index = WaypointFindNearest(pent->v.origin, pEdict, 500, team); + + if (index == -1) + { + // no waypoint is close enough, just head straight towards the ball + Vector v_flag = pent->v.origin - pEdict->v.origin; + + Vector bot_angles = UTIL_VecToAngles( v_flag ); + + pEdict->v.ideal_yaw = bot_angles.y; + + BotFixIdealYaw(pEdict); + + return TRUE; + } + else + { + waypoint_distance = (waypoints[index].origin - pent->v.origin).Length(); + distance = (pent->v.origin - pEdict->v.origin).Length(); + + // is the bot closer to the ball than the waypoint is? + if (distance < waypoint_distance) + { + // just head towards the ball + Vector v_flag = pent->v.origin - pEdict->v.origin; + + Vector bot_angles = UTIL_VecToAngles( v_flag ); + + pEdict->v.ideal_yaw = bot_angles.y; + + BotFixIdealYaw(pEdict); + + return TRUE; + } + else + { + // head towards this waypoint + pBot->waypoint_goal = index; + + // remember where the ball is located... + pBot->waypoint_near_flag = TRUE; + pBot->waypoint_flag_origin = pent->v.origin; + } + } + } + } + } + } + } + } + else if ((mod_id == GEARBOX_DLL) && (pent_info_ctfdetect != NULL)) + { + pent = NULL; + + while ((pent = UTIL_FindEntityByClassname( pent, "item_ctfflag" )) != NULL) + { + // is this bot carrying the item? (after capture bug fix by Whistler) + if ((pent->v.owner == pEdict) && (pent->v.origin == pEdict->v.origin)) + { + // we are carrying the flag + + bot_has_flag = TRUE; + + break; // break out of while loop + } + else if (FInViewCone( &pent->v.origin, pEdict ) && + FVisible( pent->v.origin, pEdict)) + { + // the bot can see it, check what type of model it is... + + skin = pent->v.skin; + + if (skin == 0) // Opposing Force team (these are BACKASSWARDS!) + skin = 1; + else if (skin == 1) // Black Mesa team + skin = 0; + + // see if the flag matches the bot's team... + if (skin == team) + { + // is and enemy carrying our flag/card? + if (pent->v.owner != NULL) + { + // kill the man with the flag/card! + pBot->pBotEnemy = pent->v.owner; + + pBot->waypoint_goal = -1; // forget the goal (if there is one) + + return TRUE; + } + } + else // flag/card is for another team! + { + // check if someone is NOT carrying the flag/card... + if (pent->v.owner == NULL) + { + // find the nearest waypoint to the flag/card... + index = WaypointFindNearest(pent->v.origin, pEdict, 500, team); + + if (index == -1) + { + // no waypoint is close enough, just head straight towards the flag/card + Vector v_flag = pent->v.origin - pEdict->v.origin; + + Vector bot_angles = UTIL_VecToAngles( v_flag ); + + pEdict->v.ideal_yaw = bot_angles.y; + + BotFixIdealYaw(pEdict); + + return TRUE; + } + else + { + waypoint_distance = (waypoints[index].origin - pent->v.origin).Length(); + distance = (pent->v.origin - pEdict->v.origin).Length(); + + // is the bot closer to the flag/card than the waypoint is? + if (distance < waypoint_distance) + { + // just head towards the flag/card + Vector v_flag = pent->v.origin - pEdict->v.origin; + + Vector bot_angles = UTIL_VecToAngles( v_flag ); + + pEdict->v.ideal_yaw = bot_angles.y; + + BotFixIdealYaw(pEdict); + + return TRUE; + } + else + { + // head towards this waypoint + pBot->waypoint_goal = index; + + // remember where the flag/card is located... + pBot->waypoint_near_flag = TRUE; + pBot->waypoint_flag_origin = pent->v.origin; + } + } + } + } + } + } + } + else if (mod_id == FRONTLINE_DLL) + { + if ((pBot->waypoint_goal != -1) && // does bot have a goal? + (pBot->pCaptureEdict)) + { + int team = UTIL_GetTeam(pEdict); // skin and team must match + + if (!pBot->defender) // if attacker... + { + if (pBot->pCaptureEdict->v.skin == team) // was point captured? + { + pBot->waypoint_goal = -1; + } + } + else + { + if (pBot->pCaptureEdict->v.skin != team) // was point captured? + { + pBot->waypoint_goal = -1; + } + } + } + } + else if (mod_id == HOLYWARS_DLL) + { + if (pBot->waypoint_goal != -1) + { + // is the bot currently heading toward halo AND halo is gone? + if ((waypoints[pBot->waypoint_goal].flags & W_FL_FLAG) && + (holywars_saint != NULL) && (holywars_gamemode == 1)) + { + pBot->waypoint_goal = -1; + } + } + } + + // check if we need to find a waypoint... + if (pBot->curr_waypoint_index == -1) + { + pBot->waypoint_top_of_ladder = FALSE; + + // did we just come off of a ladder or are we underwater? + if (((pBot->f_end_use_ladder_time + 2.0) > gpGlobals->time) || + (pBot->pEdict->v.waterlevel == 3)) + { + // find the nearest visible waypoint + if (mod_id == FRONTLINE_DLL) + i = WaypointFindNearest(pEdict, REACHABLE_RANGE, pBot->defender); + else + i = WaypointFindNearest(pEdict, REACHABLE_RANGE, team); + } + else + { + // find the nearest reachable waypoint + if (mod_id == FRONTLINE_DLL) + i = WaypointFindReachable(pEdict, REACHABLE_RANGE, pBot->defender); + else + i = WaypointFindReachable(pEdict, REACHABLE_RANGE, team); + } + + if (i == -1) + { + pBot->curr_waypoint_index = -1; + return FALSE; + } + + pBot->curr_waypoint_index = i; + pBot->waypoint_origin = waypoints[i].origin; + + pBot->f_waypoint_time = gpGlobals->time; + } + else + { + // skip this part if bot is trying to get out of water... + if (pBot->f_exit_water_time < gpGlobals->time) + { + // check if we can still see our target waypoint... + + v_src = pEdict->v.origin + pEdict->v.view_ofs; + v_dest = waypoints[pBot->curr_waypoint_index].origin; + + // trace a line from bot's eyes to destination... + UTIL_TraceLine( v_src, v_dest, ignore_monsters, + pEdict->v.pContainingEntity, &tr ); + + // check if line of sight to object is blocked (i.e. not visible) + if (tr.flFraction < 1.0) + { + // did we just come off of a ladder or are we under water? + if (((pBot->f_end_use_ladder_time + 2.0) > gpGlobals->time) || + (pBot->pEdict->v.waterlevel == 3)) + { + // find the nearest visible waypoint + if (mod_id == FRONTLINE_DLL) + i = WaypointFindNearest(pEdict, REACHABLE_RANGE, pBot->defender); + else + i = WaypointFindNearest(pEdict, REACHABLE_RANGE, team); + } + else + { + // find the nearest reachable waypoint + if (mod_id == FRONTLINE_DLL) + i = WaypointFindReachable(pEdict, REACHABLE_RANGE, pBot->defender); + else + i = WaypointFindReachable(pEdict, REACHABLE_RANGE, team); + } + + if (i == -1) + { + pBot->curr_waypoint_index = -1; + return FALSE; + } + + pBot->curr_waypoint_index = i; + pBot->waypoint_origin = waypoints[i].origin; + + pBot->f_waypoint_time = gpGlobals->time; + } + } + } + + // find the distance to the target waypoint + waypoint_distance = (pEdict->v.origin - pBot->waypoint_origin).Length(); + + // set the minimum distance from waypoint to be considered "touching" it + min_distance = 50.0; + + // if this is a crouch waypoint, bot must be fairly close... + if (waypoints[pBot->curr_waypoint_index].flags & W_FL_CROUCH) + min_distance = 20.0; + + if (waypoints[pBot->curr_waypoint_index].flags & W_FL_JUMP) + min_distance = 25.0; + + if (waypoints[pBot->curr_waypoint_index].flags & W_FL_SENTRYGUN) + min_distance = 20.0; + + if (waypoints[pBot->curr_waypoint_index].flags & W_FL_DISPENSER) + min_distance = 20.0; + + // if this is a ladder waypoint, bot must be fairly close to get on ladder + if (waypoints[pBot->curr_waypoint_index].flags & W_FL_LADDER) + min_distance = 20.0; + + // if this is a defenders waypoint, bot must be fairly close... + if ((mod_id == FRONTLINE_DLL) && + (waypoints[pBot->curr_waypoint_index].flags & W_FL_FLF_DEFEND)) + min_distance = 20.0; + + // if trying to get out of water, need to get very close to waypoint... + if (pBot->f_exit_water_time >= gpGlobals->time) + min_distance = 20.0; + + touching = FALSE; + + // did the bot run past the waypoint? (prevent the loop-the-loop problem) + if ((pBot->prev_waypoint_distance > 1.0) && + (waypoint_distance > pBot->prev_waypoint_distance)) + touching = TRUE; + + // are we close enough to a target waypoint... + if (waypoint_distance < min_distance) + touching = TRUE; + + // save current distance as previous + pBot->prev_waypoint_distance = waypoint_distance; + + if (touching) + { + bool waypoint_found = FALSE; + + pBot->prev_waypoint_distance = 0.0; + + // check if the waypoint is a door waypoint + if (waypoints[pBot->curr_waypoint_index].flags & W_FL_DOOR) + { + pBot->f_dont_avoid_wall_time = gpGlobals->time + 5.0; + } + + // check if the next waypoint is a jump waypoint... + if (waypoints[pBot->curr_waypoint_index].flags & W_FL_JUMP) + { + pEdict->v.button |= IN_JUMP; // jump here + } + + // check if the waypoint is a sniper waypoint... + if (waypoints[pBot->curr_waypoint_index].flags & W_FL_SNIPER) + { + if (((mod_id == TFC_DLL) && (pEdict->v.playerclass == TFC_CLASS_SNIPER)) || + (mod_id != TFC_DLL)) + { + int aim_index; + + aim_index = WaypointFindNearestAiming(waypoints[pBot->curr_waypoint_index].origin); + + if (aim_index != -1) + { + Vector v_aim = waypoints[aim_index].origin - waypoints[pBot->curr_waypoint_index].origin; + + Vector aim_angles = UTIL_VecToAngles( v_aim ); + + pEdict->v.ideal_yaw = aim_angles.y; + + BotFixIdealYaw(pEdict); + } + + pBot->f_pause_time = gpGlobals->time + RANDOM_FLOAT(20.0, 30.0); + + // fix f_waypoint_time so bot won't think it is stuck + pBot->f_waypoint_time = pBot->f_pause_time; + + return TRUE; + } + } + + // check if the bot has reached the goal waypoint... + if (pBot->curr_waypoint_index == pBot->waypoint_goal) + { + pBot->waypoint_goal = -1; // forget this goal waypoint + + if (pBot->waypoint_near_flag) + { + pBot->waypoint_near_flag = FALSE; + + // just head towards the flag/card/ball + Vector v_flag = pBot->waypoint_flag_origin - pEdict->v.origin; + + Vector bot_angles = UTIL_VecToAngles( v_flag ); + + pEdict->v.ideal_yaw = bot_angles.y; + + BotFixIdealYaw(pEdict); + + return TRUE; + } + + // see if this waypoint is a sentry gun waypoint... + if ((waypoints[pBot->curr_waypoint_index].flags & W_FL_SENTRYGUN) && + (mod_id == TFC_DLL) && (pEdict->v.playerclass == TFC_CLASS_ENGINEER)) + { + if (pBot->m_rgAmmo[weapon_defs[TF_WEAPON_SPANNER].iAmmo1] >= 130) + { + int aim_index; + + aim_index = WaypointFindNearestAiming(waypoints[pBot->curr_waypoint_index].origin); + + if (aim_index != -1) + { + Vector v_aim = waypoints[aim_index].origin - waypoints[pBot->curr_waypoint_index].origin; + + Vector aim_angles = UTIL_VecToAngles( v_aim ); + + pEdict->v.ideal_yaw = aim_angles.y; + + BotFixIdealYaw(pEdict); + + pBot->b_build_sentrygun = TRUE; + + pBot->sentrygun_waypoint = pBot->curr_waypoint_index; + + pBot->f_look_for_waypoint_time = gpGlobals->time + 5.0; + + return TRUE; + } + } + } + + // see if this waypoint is a dispenser waypoint... + if ((waypoints[pBot->curr_waypoint_index].flags & W_FL_DISPENSER) && + (mod_id == TFC_DLL) && (pEdict->v.playerclass == TFC_CLASS_ENGINEER)) + { + // does bot have enough metal to build a dispenser? + if (pBot->m_rgAmmo[weapon_defs[TF_WEAPON_SPANNER].iAmmo1] >= 100) + { + int aim_index; + + aim_index = WaypointFindNearestAiming(waypoints[pBot->curr_waypoint_index].origin); + + if (aim_index != -1) + { + Vector v_aim = waypoints[aim_index].origin - waypoints[pBot->curr_waypoint_index].origin; + + Vector aim_angles = UTIL_VecToAngles( v_aim ); + + pEdict->v.ideal_yaw = aim_angles.y; + + BotFixIdealYaw(pEdict); + + pBot->b_build_dispenser = TRUE; + + pBot->dispenser_waypoint = pBot->curr_waypoint_index; + + pBot->f_look_for_waypoint_time = gpGlobals->time + 5.0; + + return TRUE; + } + } + } + + if ((mod_id == FRONTLINE_DLL) && + (waypoints[pBot->curr_waypoint_index].flags & W_FL_FLF_CAP)) + { + // it's a capture point + pent = NULL; + + while ((pent = UTIL_FindEntityInSphere( pent, pEdict->v.origin, 100.0 )) != NULL) + { + if (strcmp(STRING(pent->v.classname), "capture_point") == 0) + { + if (pent->v.skin != (1 - team)) // already captured? + return TRUE; // can't do anything here, just return + + // turn to face the capture entity + Vector v_aim = pent->v.origin - pEdict->v.origin; + + Vector aim_angles = UTIL_VecToAngles( v_aim ); + + pEdict->v.ideal_yaw = aim_angles.y; + + BotFixIdealYaw(pEdict); + + return TRUE; + } + } + } + + if ((mod_id == FRONTLINE_DLL) && + (waypoints[pBot->curr_waypoint_index].flags & W_FL_FLF_DEFEND)) + { + // it's a defend point + + int aim_index; + + aim_index = WaypointFindNearestAiming(waypoints[pBot->curr_waypoint_index].origin); + + if (aim_index != -1) + { + Vector v_aim = waypoints[aim_index].origin - waypoints[pBot->curr_waypoint_index].origin; + + Vector aim_angles = UTIL_VecToAngles( v_aim ); + + pEdict->v.ideal_yaw = aim_angles.y; + + BotFixIdealYaw(pEdict); + + pBot->f_pause_time = gpGlobals->time + RANDOM_FLOAT(30.0, 45.0); + + // fix f_waypoint_time so bot won't think it is stuck + pBot->f_waypoint_time = pBot->f_pause_time; + + return TRUE; + } + } + } + + // check if the bot is carrying the flag/card/ball... + if (bot_has_flag) + { + pBot->bot_has_flag = TRUE; + + // find the nearest flag goal waypoint... + + index = WaypointFindNearestGoal(pEdict, pBot->curr_waypoint_index, + team, W_FL_FLAG_GOAL); + + pBot->waypoint_goal = index; // goal index or -1 + + pBot->waypoint_near_flag = FALSE; + } + else + { + pBot->bot_has_flag = FALSE; + } + + // test special case of bot underwater and close to surface... + if (pBot->pEdict->v.waterlevel == 3) + { + Vector v_src, v_dest; + TraceResult tr; + int contents; + + // trace a line straight up 100 units... + v_src = pEdict->v.origin; + v_dest = v_src; + v_dest.z = v_dest.z + 100.0; + + // trace a line to destination... + UTIL_TraceLine( v_src, v_dest, ignore_monsters, + pEdict->v.pContainingEntity, &tr ); + + if (tr.flFraction >= 1.0) + { + // find out what the contents is of the end of the trace... + contents = POINT_CONTENTS( tr.vecEndPos ); + + // check if the trace endpoint is in open space... + if (contents == CONTENTS_EMPTY) + { + // find the nearest visible waypoint + if (mod_id == FRONTLINE_DLL) + i = WaypointFindNearest(tr.vecEndPos, pEdict, 100, pBot->defender); + else + i = WaypointFindNearest(tr.vecEndPos, pEdict, 100, team); + + if (i != -1) + { + waypoint_found = TRUE; + pBot->curr_waypoint_index = i; + pBot->waypoint_origin = waypoints[i].origin; + + pBot->f_waypoint_time = gpGlobals->time; + + // keep trying to exit water for next 3 seconds + pBot->f_exit_water_time = gpGlobals->time + 3.0; + } + } + } + } + + // if the bot doesn't have a goal waypoint then pick one... + if ((pBot->waypoint_goal == -1) && + (pBot->f_waypoint_goal_time < gpGlobals->time)) + { + // don't pick a goal more often than every 10 seconds... + pBot->f_waypoint_goal_time = gpGlobals->time + 10.0; + + pBot->waypoint_near_flag = FALSE; + + +// IF HEALTH LESS THAN CRITICAL LEVEL, GO FIND HEALTH!!! + +// IF AMMO LESS THAN CRITICAL LEVEL, GO FIND AMMO!!! + +// GO FIND WEAPONS HERE!!! + + + if ((mod_id == VALVE_DLL) || (mod_id == DMC_DLL)) + { + if (RANDOM_LONG(1, 100) <= 50) + { + index = WaypointFindNearestGoal(pEdict, pBot->curr_waypoint_index, + team, W_FL_WEAPON, pBot->weapon_points); + } + else + { + int count = 0; + + index = -1; + + while ((index == -1) && (count < 3)) + { + index = WaypointFindRandomGoal(pEdict, team, W_FL_WEAPON, pBot->weapon_points); + count++; + } + } + + if (index != -1) + { + pBot->waypoint_goal = index; + + pBot->weapon_points[4] = pBot->weapon_points[3]; + pBot->weapon_points[3] = pBot->weapon_points[2]; + pBot->weapon_points[2] = pBot->weapon_points[1]; + pBot->weapon_points[1] = pBot->weapon_points[0]; + pBot->weapon_points[0] = pBot->waypoint_goal; + } + } + else if (mod_id == TFC_DLL) + { + if (pEdict->v.playerclass == TFC_CLASS_SNIPER) + { + if (RANDOM_LONG(1, 100) <= 10) + { + // find the nearest flag waypoint... + + index = WaypointFindNearestGoal(pEdict, pBot->curr_waypoint_index, + team, W_FL_FLAG); + } + else + { + // find a random sniper waypoint... + + index = WaypointFindRandomGoal(pEdict, team, W_FL_SNIPER); + } + + if (index != -1) + pBot->waypoint_goal = index; + } + else if (pEdict->v.playerclass == TFC_CLASS_ENGINEER) + { + // is it time to build sentry gun or dispenser yet? + if (pBot->f_engineer_build_time <= gpGlobals->time) + { + int value = RANDOM_LONG(1, 100); + + if (((value <= 70) && (pBot->sentrygun_level < 3)) || + (value <= 40)) + { + // build or upgrade a sentry gun... + + index = -1; + + // do we need more metal to build a sentry gun? + if (pBot->m_rgAmmo[weapon_defs[TF_WEAPON_SPANNER].iAmmo1] < 130) + { + // go find some metal... + index = WaypointFindNearestGoal(pEdict, pBot->curr_waypoint_index, + team, W_FL_ARMOR); + } + else // otherwise we have enough metal... + { + if (pBot->sentrygun_waypoint == -1) + { + // find a random sentry gun waypoint... + + index = WaypointFindRandomGoal(pEdict, team, W_FL_SENTRYGUN); + } + else + index = pBot->sentrygun_waypoint; + } + + if (index != -1) + pBot->waypoint_goal = index; + } + else + { + // build a dispenser... + index = -1; + + // do we need more metal to build a dispenser? + if (pBot->m_rgAmmo[weapon_defs[TF_WEAPON_SPANNER].iAmmo1] < 100) + { + // go find some metal... + index = WaypointFindNearestGoal(pEdict, pBot->curr_waypoint_index, + team, W_FL_ARMOR); + } + else // otherwise we have enough metal... + { + if (pBot->dispenser_waypoint == -1) + { + index = WaypointFindRandomGoal(pEdict, team, W_FL_DISPENSER); + } + else + index = pBot->dispenser_waypoint; + } + + if (index != -1) + pBot->waypoint_goal = index; + } + } + else + { + if (RANDOM_LONG(1, 100) <= 20) + { + // find the nearest flag waypoint... + + index = WaypointFindNearestGoal(pEdict, pBot->curr_waypoint_index, + team, W_FL_FLAG); + + if (index != -1) + pBot->waypoint_goal = index; + } + } + } + else + { + if (RANDOM_LONG(1, 100) <= 40) + { + // find the nearest flag waypoint... + + index = WaypointFindNearestGoal(pEdict, pBot->curr_waypoint_index, + team, W_FL_FLAG); + + if (index != -1) + pBot->waypoint_goal = index; + } + } + } + else if (mod_id == FRONTLINE_DLL) + { + edict_t *caps[20]; + int select, index = 0; + int capture_color; + + // find a capture point that hasn't been captured yet... + + if (pBot->defender) + capture_color = team; // defenders + else + capture_color = 1 - team; // attackers + + pent = NULL; + + while ((pent = UTIL_FindEntityByClassname( pent, "capture_point" )) != NULL) + { + if (pent->v.skin != capture_color) // skip it if already captured + continue; + + caps[index++] = pent; + } + + if (index > 0) // are any capture points left?... + { + if (!pBot->defender) // if attacker... + { + select = RANDOM_LONG(0, index-1); + + index = WaypointFindNearestGoal(caps[select]->v.origin, pEdict, + 100, pBot->defender, W_FL_FLF_CAP); + + if (index != -1) + { + pBot->waypoint_goal = index; + pBot->pCaptureEdict = caps[select]; + } + } + else // defender... + { + select = RANDOM_LONG(0, index-1); + + index = WaypointFindRandomGoal(caps[select]->v.origin, pEdict, + 800, pBot->defender, W_FL_FLF_DEFEND); + + if (index != -1) + { + pBot->waypoint_goal = index; + pBot->pCaptureEdict = caps[select]; + } + } + } + } + else if (mod_id == HOLYWARS_DLL) + { + if (holywars_gamemode == 0) // deathmatch? + { + if (RANDOM_LONG(1, 100) <= 50) + { + index = WaypointFindNearestGoal(pEdict, pBot->curr_waypoint_index, + team, W_FL_WEAPON, pBot->weapon_points); + } + else + { + index = WaypointFindRandomGoal(pEdict, team, W_FL_WEAPON, pBot->weapon_points); + } + + if (index != -1) + { + pBot->waypoint_goal = index; + + pBot->weapon_points[4] = pBot->weapon_points[3]; + pBot->weapon_points[3] = pBot->weapon_points[2]; + pBot->weapon_points[2] = pBot->weapon_points[1]; + pBot->weapon_points[1] = pBot->weapon_points[0]; + pBot->weapon_points[0] = pBot->waypoint_goal; + } + } + else if (holywars_gamemode == 1) // halo mode? + { + // if waiting for halo to spawn then head for halo spawn point... + if (halo_status == HW_WAIT_SPAWN) + { + // find the nearest flag waypoint... + + index = WaypointFindNearestGoal(pEdict, pBot->curr_waypoint_index, + team, W_FL_FLAG); + + if (index != -1) + pBot->waypoint_goal = index; + else + { + // can't get to the halo from here, search for a weapon + + index = WaypointFindNearestGoal(pEdict, pBot->curr_waypoint_index, + team, W_FL_WEAPON); + + if (index != -1) + pBot->waypoint_goal = index; + } + } + else // there's already a saint, search for a weapon... + { + if (RANDOM_LONG(1, 100) <= 50) + { + index = WaypointFindNearestGoal(pEdict, pBot->curr_waypoint_index, + team, W_FL_WEAPON, pBot->weapon_points); + } + else + { + index = WaypointFindRandomGoal(pEdict, team, W_FL_WEAPON, pBot->weapon_points); + } + + if (index != -1) + { + pBot->waypoint_goal = index; + + pBot->weapon_points[4] = pBot->weapon_points[3]; + pBot->weapon_points[3] = pBot->weapon_points[2]; + pBot->weapon_points[2] = pBot->weapon_points[1]; + pBot->weapon_points[1] = pBot->weapon_points[0]; + pBot->weapon_points[0] = pBot->waypoint_goal; + } + } + } + else // assume instagib... + { + // don't do anything, just randomly wander around + } + } + } + + // check if the bot has a goal waypoint... + if (pBot->waypoint_goal != -1) + { + // get the next waypoint to reach goal... + if (mod_id == FRONTLINE_DLL) + { + i = WaypointRouteFromTo(pBot->curr_waypoint_index, + pBot->waypoint_goal, pBot->defender); + } + else + { + i = WaypointRouteFromTo(pBot->curr_waypoint_index, + pBot->waypoint_goal, team); + } + + if (i != WAYPOINT_UNREACHABLE) // can we get to the goal from here? + { + waypoint_found = TRUE; + pBot->curr_waypoint_index = i; + pBot->waypoint_origin = waypoints[i].origin; + + pBot->f_waypoint_time = gpGlobals->time; + } + } + + if (waypoint_found == FALSE) + { + index = 4; + + // try to find the next waypoint + while (((status = BotFindWaypoint( pBot )) == FALSE) && + (index > 0)) + { + // if waypoint not found, clear oldest prevous index and try again + + pBot->prev_waypoint_index[index] = -1; + index--; + } + + if (status == FALSE) + { + pBot->curr_waypoint_index = -1; // indicate no waypoint found + + // clear all previous waypoints... + for (index=0; index < 5; index++) + pBot->prev_waypoint_index[index] = -1; + + return FALSE; + } + } + } + + // check if the next waypoint is on a ladder AND + // the bot it not currently on a ladder... + if ((waypoints[pBot->curr_waypoint_index].flags & W_FL_LADDER) && + (pEdict->v.movetype != MOVETYPE_FLY)) + { + // if it's origin is lower than the bot... + if (waypoints[pBot->curr_waypoint_index].origin.z < pEdict->v.origin.z) + { + pBot->waypoint_top_of_ladder = TRUE; + } + } + else + { + pBot->waypoint_top_of_ladder = FALSE; + } + + // keep turning towards the waypoint... + + Vector v_direction = pBot->waypoint_origin - pEdict->v.origin; + + Vector v_angles = UTIL_VecToAngles(v_direction); + + // if the bot is NOT on a ladder, change the yaw... + if (pEdict->v.movetype != MOVETYPE_FLY) + { + pEdict->v.idealpitch = -v_angles.x; + BotFixIdealPitch(pEdict); + + pEdict->v.ideal_yaw = v_angles.y; + BotFixIdealYaw(pEdict); + } + + return TRUE; +} + + +void BotOnLadder( bot_t *pBot, float moved_distance ) +{ + Vector v_src, v_dest, view_angles; + TraceResult tr; + float angle = 0.0; + bool done = FALSE; + + edict_t *pEdict = pBot->pEdict; + + // check if the bot has JUST touched this ladder... + if (pBot->ladder_dir == LADDER_UNKNOWN) + { + // try to square up the bot on the ladder... + while ((!done) && (angle < 180.0)) + { + // try looking in one direction (forward + angle) + view_angles = pEdict->v.v_angle; + view_angles.y = pEdict->v.v_angle.y + angle; + + if (view_angles.y < 0.0) + view_angles.y += 360.0; + if (view_angles.y > 360.0) + view_angles.y -= 360.0; + + MAKE_VECTORS( view_angles ); + + v_src = pEdict->v.origin + pEdict->v.view_ofs; + v_dest = v_src + gpGlobals->v_forward * 30; + + UTIL_TraceLine( v_src, v_dest, dont_ignore_monsters, + pEdict->v.pContainingEntity, &tr); + + if (tr.flFraction < 1.0) // hit something? + { + if (strcmp("func_wall", STRING(tr.pHit->v.classname)) == 0) + { + // square up to the wall... + view_angles = UTIL_VecToAngles(tr.vecPlaneNormal); + + // Normal comes OUT from wall, so flip it around... + view_angles.y += 180; + + if (view_angles.y > 180) + view_angles.y -= 360; + + pEdict->v.ideal_yaw = view_angles.y; + + BotFixIdealYaw(pEdict); + + done = TRUE; + } + } + else + { + // try looking in the other direction (forward - angle) + view_angles = pEdict->v.v_angle; + view_angles.y = pEdict->v.v_angle.y - angle; + + if (view_angles.y < 0.0) + view_angles.y += 360.0; + if (view_angles.y > 360.0) + view_angles.y -= 360.0; + + MAKE_VECTORS( view_angles ); + + v_src = pEdict->v.origin + pEdict->v.view_ofs; + v_dest = v_src + gpGlobals->v_forward * 30; + + UTIL_TraceLine( v_src, v_dest, dont_ignore_monsters, + pEdict->v.pContainingEntity, &tr); + + if (tr.flFraction < 1.0) // hit something? + { + if (strcmp("func_wall", STRING(tr.pHit->v.classname)) == 0) + { + // square up to the wall... + view_angles = UTIL_VecToAngles(tr.vecPlaneNormal); + + // Normal comes OUT from wall, so flip it around... + view_angles.y += 180; + + if (view_angles.y > 180) + view_angles.y -= 360; + + pEdict->v.ideal_yaw = view_angles.y; + + BotFixIdealYaw(pEdict); + + done = TRUE; + } + } + } + + angle += 10; + } + + if (!done) // if didn't find a wall, just reset ideal_yaw... + { + // set ideal_yaw to current yaw (so bot won't keep turning) + pEdict->v.ideal_yaw = pEdict->v.v_angle.y; + + BotFixIdealYaw(pEdict); + } + } + + // moves the bot up or down a ladder. if the bot can't move + // (i.e. get's stuck with someone else on ladder), the bot will + // change directions and go the other way on the ladder. + + if (pBot->ladder_dir == LADDER_UP) // is the bot currently going up? + { + pEdict->v.v_angle.x = -60; // look upwards + + // check if the bot hasn't moved much since the last location... + if ((moved_distance <= 1) && (pBot->f_prev_speed >= 1.0)) + { + // the bot must be stuck, change directions... + + pEdict->v.v_angle.x = 60; // look downwards + pBot->ladder_dir = LADDER_DOWN; + } + } + else if (pBot->ladder_dir == LADDER_DOWN) // is the bot currently going down? + { + pEdict->v.v_angle.x = 60; // look downwards + + // check if the bot hasn't moved much since the last location... + if ((moved_distance <= 1) && (pBot->f_prev_speed >= 1.0)) + { + // the bot must be stuck, change directions... + + pEdict->v.v_angle.x = -60; // look upwards + pBot->ladder_dir = LADDER_UP; + } + } + else // the bot hasn't picked a direction yet, try going up... + { + pEdict->v.v_angle.x = -60; // look upwards + pBot->ladder_dir = LADDER_UP; + } + + // move forward (i.e. in the direction the bot is looking, up or down) + pEdict->v.button |= IN_FORWARD; +} + + +void BotUnderWater( bot_t *pBot ) +{ + bool found_waypoint = FALSE; + + edict_t *pEdict = pBot->pEdict; + + // are there waypoints in this level (and not trying to exit water)? + if ((num_waypoints > 0) && + (pBot->f_exit_water_time < gpGlobals->time)) + { + // head towards a waypoint + found_waypoint = BotHeadTowardWaypoint(pBot); + } + + if (found_waypoint == FALSE) + { + // handle movements under water. right now, just try to keep from + // drowning by swimming up towards the surface and look to see if + // there is a surface the bot can jump up onto to get out of the + // water. bots DON'T like water! + + Vector v_src, v_forward; + TraceResult tr; + int contents; + + // swim up towards the surface + pEdict->v.v_angle.x = -60; // look upwards + + // move forward (i.e. in the direction the bot is looking, up or down) + pEdict->v.button |= IN_FORWARD; + + // set gpGlobals angles based on current view angle (for TraceLine) + MAKE_VECTORS( pEdict->v.v_angle ); + + // look from eye position straight forward (remember: the bot is looking + // upwards at a 60 degree angle so TraceLine will go out and up... + + v_src = pEdict->v.origin + pEdict->v.view_ofs; // EyePosition() + v_forward = v_src + gpGlobals->v_forward * 90; + + // trace from the bot's eyes straight forward... + UTIL_TraceLine( v_src, v_forward, dont_ignore_monsters, + pEdict->v.pContainingEntity, &tr); + + // check if the trace didn't hit anything (i.e. nothing in the way)... + if (tr.flFraction >= 1.0) + { + // find out what the contents is of the end of the trace... + contents = POINT_CONTENTS( tr.vecEndPos ); + + // check if the trace endpoint is in open space... + if (contents == CONTENTS_EMPTY) + { + // ok so far, we are at the surface of the water, continue... + + v_src = tr.vecEndPos; + v_forward = v_src; + v_forward.z -= 90; + + // trace from the previous end point straight down... + UTIL_TraceLine( v_src, v_forward, dont_ignore_monsters, + pEdict->v.pContainingEntity, &tr); + + // check if the trace hit something... + if (tr.flFraction < 1.0) + { + contents = POINT_CONTENTS( tr.vecEndPos ); + + // if contents isn't water then assume it's land, jump! + if (contents != CONTENTS_WATER) + { + pEdict->v.button |= IN_JUMP; + } + } + } + } + } +} + + +void BotUseLift( bot_t *pBot, float moved_distance ) +{ + edict_t *pEdict = pBot->pEdict; + + // just need to press the button once, when the flag gets set... + if (pBot->f_use_button_time == gpGlobals->time) + { + pEdict->v.button = IN_USE; + + // face opposite from the button + pEdict->v.ideal_yaw += 180; // rotate 180 degrees + + BotFixIdealYaw(pEdict); + } + + // check if the bot has waited too long for the lift to move... + if (((pBot->f_use_button_time + 2.0) < gpGlobals->time) && + (!pBot->b_lift_moving)) + { + // clear use button flag + pBot->b_use_button = FALSE; + + // bot doesn't have to set f_find_item since the bot + // should already be facing away from the button + + pBot->f_move_speed = pBot->f_max_speed; + } + + // check if lift has started moving... + if ((moved_distance > 1) && (!pBot->b_lift_moving)) + { + pBot->b_lift_moving = TRUE; + } + + // check if lift has stopped moving... + if ((moved_distance <= 1) && (pBot->b_lift_moving)) + { + TraceResult tr1, tr2; + Vector v_src, v_forward, v_right, v_left; + Vector v_down, v_forward_down, v_right_down, v_left_down; + + pBot->b_use_button = FALSE; + + // TraceLines in 4 directions to find which way to go... + + MAKE_VECTORS( pEdict->v.v_angle ); + + v_src = pEdict->v.origin + pEdict->v.view_ofs; + v_forward = v_src + gpGlobals->v_forward * 90; + v_right = v_src + gpGlobals->v_right * 90; + v_left = v_src + gpGlobals->v_right * -90; + + v_down = pEdict->v.v_angle; + v_down.x = v_down.x + 45; // look down at 45 degree angle + + MAKE_VECTORS( v_down ); + + v_forward_down = v_src + gpGlobals->v_forward * 100; + v_right_down = v_src + gpGlobals->v_right * 100; + v_left_down = v_src + gpGlobals->v_right * -100; + + // try tracing forward first... + UTIL_TraceLine( v_src, v_forward, dont_ignore_monsters, + pEdict->v.pContainingEntity, &tr1); + UTIL_TraceLine( v_src, v_forward_down, dont_ignore_monsters, + pEdict->v.pContainingEntity, &tr2); + + // check if we hit a wall or didn't find a floor... + if ((tr1.flFraction < 1.0) || (tr2.flFraction >= 1.0)) + { + // try tracing to the RIGHT side next... + UTIL_TraceLine( v_src, v_right, dont_ignore_monsters, + pEdict->v.pContainingEntity, &tr1); + UTIL_TraceLine( v_src, v_right_down, dont_ignore_monsters, + pEdict->v.pContainingEntity, &tr2); + + // check if we hit a wall or didn't find a floor... + if ((tr1.flFraction < 1.0) || (tr2.flFraction >= 1.0)) + { + // try tracing to the LEFT side next... + UTIL_TraceLine( v_src, v_left, dont_ignore_monsters, + pEdict->v.pContainingEntity, &tr1); + UTIL_TraceLine( v_src, v_left_down, dont_ignore_monsters, + pEdict->v.pContainingEntity, &tr2); + + // check if we hit a wall or didn't find a floor... + if ((tr1.flFraction < 1.0) || (tr2.flFraction >= 1.0)) + { + // only thing to do is turn around... + pEdict->v.ideal_yaw += 180; // turn all the way around + } + else + { + pEdict->v.ideal_yaw += 90; // turn to the LEFT + } + } + else + { + pEdict->v.ideal_yaw -= 90; // turn to the RIGHT + } + + BotFixIdealYaw(pEdict); + } + + BotChangeYaw( pBot, pEdict->v.yaw_speed ); + + pBot->f_move_speed = pBot->f_max_speed; + } +} + + +bool BotStuckInCorner( bot_t *pBot ) +{ + TraceResult tr; + Vector v_src, v_dest; + edict_t *pEdict = pBot->pEdict; + + MAKE_VECTORS( pEdict->v.v_angle ); + + // trace 45 degrees to the right... + v_src = pEdict->v.origin; + v_dest = v_src + gpGlobals->v_forward*20 + gpGlobals->v_right*20; + + UTIL_TraceLine( v_src, v_dest, dont_ignore_monsters, + pEdict->v.pContainingEntity, &tr); + + if (tr.flFraction >= 1.0) + return FALSE; // no wall, so not in a corner + + // trace 45 degrees to the left... + v_src = pEdict->v.origin; + v_dest = v_src + gpGlobals->v_forward*20 - gpGlobals->v_right*20; + + UTIL_TraceLine( v_src, v_dest, dont_ignore_monsters, + pEdict->v.pContainingEntity, &tr); + + if (tr.flFraction >= 1.0) + return FALSE; // no wall, so not in a corner + + return TRUE; // bot is in a corner +} + + +void BotTurnAtWall( bot_t *pBot, TraceResult *tr, bool negative ) +{ + edict_t *pEdict = pBot->pEdict; + Vector Normal; + float Y, Y1, Y2, D1, D2, Z; + + // Find the normal vector from the trace result. The normal vector will + // be a vector that is perpendicular to the surface from the TraceResult. + + Normal = UTIL_VecToAngles(tr->vecPlaneNormal); + + // Since the bot keeps it's view angle in -180 < x < 180 degrees format, + // and since TraceResults are 0 < x < 360, we convert the bot's view + // angle (yaw) to the same format at TraceResult. + + Y = pEdict->v.v_angle.y; + Y = Y + 180; + if (Y > 359) Y -= 360; + + if (negative) + { + // Turn the normal vector around 180 degrees (i.e. make it point towards + // the wall not away from it. That makes finding the angles that the + // bot needs to turn a little easier. + + Normal.y = Normal.y - 180; + } + + if (Normal.y < 0) + Normal.y += 360; + + // Here we compare the bots view angle (Y) to the Normal - 90 degrees (Y1) + // and the Normal + 90 degrees (Y2). These two angles (Y1 & Y2) represent + // angles that are parallel to the wall surface, but heading in opposite + // directions. We want the bot to choose the one that will require the + // least amount of turning (saves time) and have the bot head off in that + // direction. + + Y1 = Normal.y - 90; + if (RANDOM_LONG(1, 100) <= 50) + { + Y1 = Y1 - RANDOM_FLOAT(5.0, 20.0); + } + if (Y1 < 0) Y1 += 360; + + Y2 = Normal.y + 90; + if (RANDOM_LONG(1, 100) <= 50) + { + Y2 = Y2 + RANDOM_FLOAT(5.0, 20.0); + } + if (Y2 > 359) Y2 -= 360; + + // D1 and D2 are the difference (in degrees) between the bot's current + // angle and Y1 or Y2 (respectively). + + D1 = fabs(Y - Y1); + if (D1 > 179) D1 = fabs(D1 - 360); + D2 = fabs(Y - Y2); + if (D2 > 179) D2 = fabs(D2 - 360); + + // If difference 1 (D1) is more than difference 2 (D2) then the bot will + // have to turn LESS if it heads in direction Y1 otherwise, head in + // direction Y2. I know this seems backwards, but try some sample angles + // out on some graph paper and go through these equations using a + // calculator, you'll see what I mean. + + if (D1 > D2) + Z = Y1; + else + Z = Y2; + + // convert from TraceResult 0 to 360 degree format back to bot's + // -180 to 180 degree format. + + if (Z > 180) + Z -= 360; + + // set the direction to head off into... + pEdict->v.ideal_yaw = Z; + + BotFixIdealYaw(pEdict); +} + + +bool BotCantMoveForward( bot_t *pBot, TraceResult *tr ) +{ + edict_t *pEdict = pBot->pEdict; + + // use some TraceLines to determine if anything is blocking the current + // path of the bot. + + Vector v_src, v_forward; + + MAKE_VECTORS( pEdict->v.v_angle ); + + // first do a trace from the bot's eyes forward... + + v_src = pEdict->v.origin + pEdict->v.view_ofs; // EyePosition() + v_forward = v_src + gpGlobals->v_forward * 40; + + // trace from the bot's eyes straight forward... + UTIL_TraceLine( v_src, v_forward, dont_ignore_monsters, + pEdict->v.pContainingEntity, tr); + + // check if the trace hit something... + if (tr->flFraction < 1.0) + { + return TRUE; // bot's head will hit something + } + + // bot's head is clear, check at waist level... + + v_src = pEdict->v.origin; + v_forward = v_src + gpGlobals->v_forward * 40; + + // trace from the bot's waist straight forward... + UTIL_TraceLine( v_src, v_forward, dont_ignore_monsters, + pEdict->v.pContainingEntity, tr); + + // check if the trace hit something... + if (tr->flFraction < 1.0) + { + return TRUE; // bot's body will hit something + } + + return FALSE; // bot can move forward, return false +} + + +bool BotCanJumpUp( bot_t *pBot, bool *bDuckJump) +{ + // What I do here is trace 3 lines straight out, one unit higher than + // the highest normal jumping distance. I trace once at the center of + // the body, once at the right side, and once at the left side. If all + // three of these TraceLines don't hit an obstruction then I know the + // area to jump to is clear. I then need to trace from head level, + // above where the bot will jump to, downward to see if there is anything + // blocking the jump. There could be a narrow opening that the body + // will not fit into. These horizontal and vertical TraceLines seem + // to catch most of the problems with falsely trying to jump on something + // that the bot can not get onto. + + TraceResult tr; + bool check_duck = FALSE; + Vector v_jump, v_source, v_dest; + edict_t *pEdict = pBot->pEdict; + + *bDuckJump = FALSE; + + // convert current view angle to vectors for TraceLine math... + + v_jump = pEdict->v.v_angle; + v_jump.x = 0; // reset pitch to 0 (level horizontally) + v_jump.z = 0; // reset roll to 0 (straight up and down) + + MAKE_VECTORS( v_jump ); + + // use center of the body first... + + // maximum normal jump height is 45, so check one unit above that (46) + v_source = pEdict->v.origin + Vector(0, 0, -36 + 46); + v_dest = v_source + gpGlobals->v_forward * 24; + + // trace a line forward at maximum jump height... + UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, + pEdict->v.pContainingEntity, &tr); + + // if trace hit something, check duck jump... + if (tr.flFraction < 1.0) + check_duck = TRUE; + + if (!check_duck) + { + // now check same height to one side of the bot... + v_source = pEdict->v.origin + gpGlobals->v_right * 16 + Vector(0, 0, -36 + 46); + v_dest = v_source + gpGlobals->v_forward * 24; + + // trace a line forward at maximum jump height... + UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, + pEdict->v.pContainingEntity, &tr); + + // if trace hit something, check duck jump... + if (tr.flFraction < 1.0) + check_duck = TRUE; + } + + if (!check_duck) + { + // now check same height on the other side of the bot... + v_source = pEdict->v.origin + gpGlobals->v_right * -16 + Vector(0, 0, -36 + 46); + v_dest = v_source + gpGlobals->v_forward * 24; + + // trace a line forward at maximum jump height... + UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, + pEdict->v.pContainingEntity, &tr); + + // if trace hit something, check duck jump... + if (tr.flFraction < 1.0) + check_duck = TRUE; + } + + if (check_duck) + { + // maximum crouch jump height is 63, so check one unit above that (64) + v_source = pEdict->v.origin + Vector(0, 0, -36 + 64); + v_dest = v_source + gpGlobals->v_forward * 24; + + // trace a line forward at maximum jump height... + UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, + pEdict->v.pContainingEntity, &tr); + + // if trace hit something, return FALSE + if (tr.flFraction < 1.0) + return FALSE; + + // now check same height on the other side of the bot... + v_source = pEdict->v.origin + gpGlobals->v_right * -16 + Vector(0, 0, -36 + 64); + v_dest = v_source + gpGlobals->v_forward * 24; + + // trace a line forward at maximum jump height... + UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, + pEdict->v.pContainingEntity, &tr); + + // if trace hit something, return FALSE + if (tr.flFraction < 1.0) + return FALSE; + + // now check same height on the other side of the bot... + v_source = pEdict->v.origin + gpGlobals->v_right * -16 + Vector(0, 0, -36 + 64); + v_dest = v_source + gpGlobals->v_forward * 24; + + // trace a line forward at maximum jump height... + UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, + pEdict->v.pContainingEntity, &tr); + + // if trace hit something, return FALSE + if (tr.flFraction < 1.0) + return FALSE; + } + + // now trace from head level downward to check for obstructions... + + // start of trace is 24 units in front of bot... + v_source = pEdict->v.origin + gpGlobals->v_forward * 24; + + if (check_duck) + // offset 36 units if crouch-jump (36 + 36) + v_source.z = v_source.z + 72; + else + // offset 72 units from top of head (72 + 36) + v_source.z = v_source.z + 108; + + + if (check_duck) + // end point of trace is 27 units straight down from start... + // (27 is 72 minus the jump limit height which is 63 - 18 = 45) + v_dest = v_source + Vector(0, 0, -27); + else + // end point of trace is 99 units straight down from start... + // (99 is 108 minus the jump limit height which is 45 - 36 = 9) + v_dest = v_source + Vector(0, 0, -99); + + + // trace a line straight down toward the ground... + UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, + pEdict->v.pContainingEntity, &tr); + + // if trace hit something, return FALSE + if (tr.flFraction < 1.0) + return FALSE; + + // now check same height to one side of the bot... + v_source = pEdict->v.origin + gpGlobals->v_right * 16 + gpGlobals->v_forward * 24; + + if (check_duck) + v_source.z = v_source.z + 72; + else + v_source.z = v_source.z + 108; + + if (check_duck) + v_dest = v_source + Vector(0, 0, -27); + else + v_dest = v_source + Vector(0, 0, -99); + + // trace a line straight down toward the ground... + UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, + pEdict->v.pContainingEntity, &tr); + + // if trace hit something, return FALSE + if (tr.flFraction < 1.0) + return FALSE; + + // now check same height on the other side of the bot... + v_source = pEdict->v.origin + gpGlobals->v_right * -16 + gpGlobals->v_forward * 24; + + if (check_duck) + v_source.z = v_source.z + 72; + else + v_source.z = v_source.z + 108; + + if (check_duck) + v_dest = v_source + Vector(0, 0, -27); + else + v_dest = v_source + Vector(0, 0, -99); + + // trace a line straight down toward the ground... + UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, + pEdict->v.pContainingEntity, &tr); + + // if trace hit something, return FALSE + if (tr.flFraction < 1.0) + return FALSE; + + return TRUE; +} + + +bool BotCanDuckUnder( bot_t *pBot ) +{ + // What I do here is trace 3 lines straight out, one unit higher than + // the ducking height. I trace once at the center of the body, once + // at the right side, and once at the left side. If all three of these + // TraceLines don't hit an obstruction then I know the area to duck to + // is clear. I then need to trace from the ground up, 72 units, to make + // sure that there is something blocking the TraceLine. Then we know + // we can duck under it. + + TraceResult tr; + Vector v_duck, v_source, v_dest; + edict_t *pEdict = pBot->pEdict; + + // convert current view angle to vectors for TraceLine math... + + v_duck = pEdict->v.v_angle; + v_duck.x = 0; // reset pitch to 0 (level horizontally) + v_duck.z = 0; // reset roll to 0 (straight up and down) + + MAKE_VECTORS( v_duck ); + + // use center of the body first... + + // duck height is 36, so check one unit above that (37) + v_source = pEdict->v.origin + Vector(0, 0, -36 + 37); + v_dest = v_source + gpGlobals->v_forward * 24; + + // trace a line forward at duck height... + UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, + pEdict->v.pContainingEntity, &tr); + + // if trace hit something, return FALSE + if (tr.flFraction < 1.0) + return FALSE; + + // now check same height to one side of the bot... + v_source = pEdict->v.origin + gpGlobals->v_right * 16 + Vector(0, 0, -36 + 37); + v_dest = v_source + gpGlobals->v_forward * 24; + + // trace a line forward at duck height... + UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, + pEdict->v.pContainingEntity, &tr); + + // if trace hit something, return FALSE + if (tr.flFraction < 1.0) + return FALSE; + + // now check same height on the other side of the bot... + v_source = pEdict->v.origin + gpGlobals->v_right * -16 + Vector(0, 0, -36 + 37); + v_dest = v_source + gpGlobals->v_forward * 24; + + // trace a line forward at duck height... + UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, + pEdict->v.pContainingEntity, &tr); + + // if trace hit something, return FALSE + if (tr.flFraction < 1.0) + return FALSE; + + // now trace from the ground up to check for object to duck under... + + // start of trace is 24 units in front of bot near ground... + v_source = pEdict->v.origin + gpGlobals->v_forward * 24; + v_source.z = v_source.z - 35; // offset to feet + 1 unit up + + // end point of trace is 72 units straight up from start... + v_dest = v_source + Vector(0, 0, 72); + + // trace a line straight up in the air... + UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, + pEdict->v.pContainingEntity, &tr); + + // if trace didn't hit something, return FALSE + if (tr.flFraction >= 1.0) + return FALSE; + + // now check same height to one side of the bot... + v_source = pEdict->v.origin + gpGlobals->v_right * 16 + gpGlobals->v_forward * 24; + v_source.z = v_source.z - 35; // offset to feet + 1 unit up + v_dest = v_source + Vector(0, 0, 72); + + // trace a line straight up in the air... + UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, + pEdict->v.pContainingEntity, &tr); + + // if trace didn't hit something, return FALSE + if (tr.flFraction >= 1.0) + return FALSE; + + // now check same height on the other side of the bot... + v_source = pEdict->v.origin + gpGlobals->v_right * -16 + gpGlobals->v_forward * 24; + v_source.z = v_source.z - 35; // offset to feet + 1 unit up + v_dest = v_source + Vector(0, 0, 72); + + // trace a line straight up in the air... + UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, + pEdict->v.pContainingEntity, &tr); + + // if trace didn't hit something, return FALSE + if (tr.flFraction >= 1.0) + return FALSE; + + return TRUE; +} + + +void BotRandomTurn( bot_t *pBot ) +{ + pBot->f_move_speed = 0; // don't move while turning + + if (RANDOM_LONG(1, 100) <= 10) + { + // 10 percent of the time turn completely around... + pBot->pEdict->v.ideal_yaw += 180; + } + else + { + // turn randomly between 30 and 60 degress + if (pBot->wander_dir == WANDER_LEFT) + pBot->pEdict->v.ideal_yaw += RANDOM_LONG(30, 60); + else + pBot->pEdict->v.ideal_yaw -= RANDOM_LONG(30, 60); + } + + BotFixIdealYaw(pBot->pEdict); +} + + +bool BotFollowUser( bot_t *pBot ) +{ + bool user_visible; + float f_distance; + edict_t *pEdict = pBot->pEdict; + + Vector vecEnd = pBot->pBotUser->v.origin + pBot->pBotUser->v.view_ofs; + + if (!IsAlive( pBot->pBotUser )) + { + // the bot's user is dead! + pBot->pBotUser = NULL; + return FALSE; + } + + user_visible = FInViewCone( &vecEnd, pEdict ) && + FVisible( vecEnd, pEdict ); + + // check if the "user" is still visible or if the user has been visible + // in the last 5 seconds (or the player just starting "using" the bot) + + if (user_visible || (pBot->f_bot_use_time + 5 > gpGlobals->time)) + { + if (user_visible) + pBot->f_bot_use_time = gpGlobals->time; // reset "last visible time" + + // face the user + Vector v_user = pBot->pBotUser->v.origin - pEdict->v.origin; + Vector bot_angles = UTIL_VecToAngles( v_user ); + + pEdict->v.ideal_yaw = bot_angles.y; + + BotFixIdealYaw(pEdict); + + f_distance = v_user.Length( ); // how far away is the "user"? + + if (f_distance > 200) // run if distance to enemy is far + pBot->f_move_speed = pBot->f_max_speed; + else if (f_distance > 50) // walk if distance is closer + pBot->f_move_speed = pBot->f_max_speed / 2; + else // don't move if close enough + pBot->f_move_speed = 0.0; + + return TRUE; + } + else + { + // person to follow has gone out of sight... + pBot->pBotUser = NULL; + + return FALSE; + } +} + + +bool BotCheckWallOnLeft( bot_t *pBot ) +{ + edict_t *pEdict = pBot->pEdict; + Vector v_src, v_left; + TraceResult tr; + + MAKE_VECTORS( pEdict->v.v_angle ); + + // do a trace to the left... + + v_src = pEdict->v.origin; + v_left = v_src + gpGlobals->v_right * -40; // 40 units to the left + + UTIL_TraceLine( v_src, v_left, dont_ignore_monsters, + pEdict->v.pContainingEntity, &tr); + + // check if the trace hit something... + if (tr.flFraction < 1.0) + { + if (pBot->f_wall_on_left < 1.0) + pBot->f_wall_on_left = gpGlobals->time; + + return TRUE; + } + + return FALSE; +} + + +bool BotCheckWallOnRight( bot_t *pBot ) +{ + edict_t *pEdict = pBot->pEdict; + Vector v_src, v_right; + TraceResult tr; + + MAKE_VECTORS( pEdict->v.v_angle ); + + // do a trace to the right... + + v_src = pEdict->v.origin; + v_right = v_src + gpGlobals->v_right * 40; // 40 units to the right + + UTIL_TraceLine( v_src, v_right, dont_ignore_monsters, + pEdict->v.pContainingEntity, &tr); + + // check if the trace hit something... + if (tr.flFraction < 1.0) + { + if (pBot->f_wall_on_right < 1.0) + pBot->f_wall_on_right = gpGlobals->time; + + return TRUE; + } + + return FALSE; +} + + +void BotLookForDrop( bot_t *pBot ) +{ + edict_t *pEdict = pBot->pEdict; + + Vector v_src, v_dest, v_ahead; + float scale, direction; + TraceResult tr; + int contents; + bool need_to_turn, done; + int turn_count; + + scale = 80 + (pBot->f_max_speed / 10); + + v_ahead = pEdict->v.v_angle; + v_ahead.x = 0; // set pitch to level horizontally + MAKE_VECTORS(v_ahead); + + v_src = pEdict->v.origin; + v_dest = v_src + gpGlobals->v_forward * scale; + + UTIL_TraceLine( v_src, v_dest, ignore_monsters, + pEdict->v.pContainingEntity, &tr ); + + // check if area in front of bot was clear... + if (tr.flFraction >= 1.0) + { + v_src = v_dest; // start downward trace from endpoint of open trace + v_dest.z = v_dest.z - max_drop_height; + + UTIL_TraceLine( v_src, v_dest, ignore_monsters, + pEdict->v.pContainingEntity, &tr ); + + need_to_turn = FALSE; + + // if trace did not hit anything then drop is TOO FAR... + if (tr.flFraction >= 1.0) + { + need_to_turn = TRUE; + } + else + { + // we've hit something, see if it's water or lava + contents = POINT_CONTENTS( tr.vecEndPos ); + + if (contents == CONTENTS_LAVA) + { + need_to_turn = TRUE; + } + + if (contents == CONTENTS_WATER) + { + // if you don't like water, set need_to_turn = TRUE here + } + } + + if (need_to_turn) + { + + // if we have an enemy, stop heading towards enemy... + if (pBot->pBotEnemy) + { + pBot->pBotEnemy = NULL; + pBot->f_bot_find_enemy_time = gpGlobals->time + 1.0; + } + + // don't look for items for a while... + pBot->f_find_item = gpGlobals->time + 1.0; + + // change the bot's ideal yaw by finding surface normal + // slightly below where the bot is standing + + v_dest = pEdict->v.origin; + + if (pEdict->v.flags & FL_DUCKING) + { + v_src.z = v_src.z - 22; // (36/2) + 4 units + v_dest.z = v_dest.z - 22; + } + else + { + v_src.z = v_src.z - 40; // (72/2) + 4 units + v_dest.z = v_dest.z - 40; + } + + UTIL_TraceLine( v_src, v_dest, ignore_monsters, + pEdict->v.pContainingEntity, &tr ); + + if (tr.flFraction < 1.0) + { + // hit something the bot is standing on... + BotTurnAtWall( pBot, &tr, FALSE ); + } + else + { + // pick a random direction to turn... + if (RANDOM_LONG(1, 100) <= 50) + direction = 1.0f; + else + direction = -1.0f; + + // turn 30 degrees at a time until bot is on solid ground + v_ahead = pEdict->v.v_angle; + v_ahead.x = 0; // set pitch to level horizontally + + done = FALSE; + turn_count = 0; + + while (!done) + { + v_ahead.y += 30.0 * direction; + + if (v_ahead.y > 360.0f) + v_ahead.y -= 360.0f; + if (v_ahead.y < -360.0f) + v_ahead.y += 360.0f; + + MAKE_VECTORS(v_ahead); + + v_src = pEdict->v.origin; + v_dest = v_src + gpGlobals->v_forward * scale; + + UTIL_TraceLine( v_src, v_dest, ignore_monsters, + pEdict->v.pContainingEntity, &tr ); + + // check if area in front of bot was clear... + if (tr.flFraction >= 1.0) + { + v_src = v_dest; // start downward trace from endpoint of open trace + v_dest.z = v_dest.z - max_drop_height; + + UTIL_TraceLine( v_src, v_dest, ignore_monsters, + pEdict->v.pContainingEntity, &tr ); + + // if trace hit something then drop is NOT TOO FAR... + if (tr.flFraction >= 1.0) + done = TRUE; + } + + turn_count++; + if (turn_count == 6) // 180 degrees? (30 * 6 = 180) + done = TRUE; + } + + pBot->pEdict->v.ideal_yaw = v_ahead.y; + BotFixIdealYaw(pEdict); + } + } + } +} + diff --git a/bot_start.cpp b/bot_start.cpp new file mode 100644 index 0000000..017e33e --- /dev/null +++ b/bot_start.cpp @@ -0,0 +1,839 @@ +// +// HPB bot - botman's High Ping Bastard bot +// +// (http://planethalflife.com/botman/) +// +// bot_start.cpp +// + +#ifndef _WIN32 +#include +#endif + +#include +#include +#include +#include + +#include "bot.h" +#include "bot_func.h" +#include "bot_weapons.h" + +extern int mod_id; +extern edict_t *pent_info_ctfdetect; + +extern int max_team_players[4]; +extern int team_class_limits[4]; +extern int max_teams; + + +void BotStartGame( bot_t *pBot ) +{ + char c_team[32]; + char c_class[32]; + char c_item[32]; + int index, count, retry_count; + edict_t *pPlayer; + int team; + int class_not_allowed; + + edict_t *pEdict = pBot->pEdict; + + if (mod_id == TFC_DLL) + { + if ((pBot->start_action == MSG_TFC_IDLE) && + (pBot->f_create_time + 3.0 <= gpGlobals->time)) + { + pBot->start_action = MSG_TFC_TEAM_SELECT; // force team selection + } + + // handle Team Fortress Classic stuff here... + + if (pBot->start_action == MSG_TFC_TEAM_SELECT) + { + pBot->start_action = MSG_TFC_IDLE; // switch back to idle + pBot->f_create_time = gpGlobals->time; // reset + + if ((pBot->bot_team != 1) && (pBot->bot_team != 2) && + (pBot->bot_team != 3) && (pBot->bot_team != 4) && + (pBot->bot_team != 5)) + pBot->bot_team = -1; + + if (pBot->bot_team == -1) + pBot->bot_team = RANDOM_LONG(1, max_teams); + + retry_count = 0; + + while ((retry_count < 4) && + (max_team_players[pBot->bot_team-1] > 0)) // not unlimited? + { + count = 0; + + // count number of players on this team... + for (index = 1; index <= gpGlobals->maxClients; index++) + { + pPlayer = INDEXENT(index); + + if (pPlayer && !pPlayer->free) + { + if (UTIL_GetTeam(pPlayer) == (pBot->bot_team - 1)) + count++; + } + } + + if (count < max_team_players[pBot->bot_team-1]) + break; // haven't reached limit yet, continue + else + { + pBot->bot_team++; + + if (pBot->bot_team > max_teams) + pBot->bot_team = 1; + + retry_count++; + } + } + + // select the team the bot wishes to join... + if (pBot->bot_team == 1) + strcpy(c_team, "1"); + else if (pBot->bot_team == 2) + strcpy(c_team, "2"); + else if (pBot->bot_team == 3) + strcpy(c_team, "3"); + else if (pBot->bot_team == 4) + strcpy(c_team, "4"); + else + strcpy(c_team, "5"); + + FakeClientCommand(pEdict, "jointeam", c_team, NULL); + + return; + } + + if (pBot->start_action == MSG_TFC_CLASS_SELECT) + { + pBot->start_action = MSG_TFC_IDLE; // switch back to idle + pBot->f_create_time = gpGlobals->time; // reset + + if ((pBot->bot_class < 0) || (pBot->bot_class > 10)) + pBot->bot_class = -1; + + if (pBot->bot_class == -1) + pBot->bot_class = RANDOM_LONG(1, 10); + + team = UTIL_GetTeam(pEdict); + + if (team_class_limits[team] == -1) // civilian only? + { + pBot->bot_class = 0; // civilian + } + else + { + if (pBot->bot_class == 10) + class_not_allowed = team_class_limits[team] & (1<<7); + else if (pBot->bot_class <= 7) + class_not_allowed = team_class_limits[team] & (1<<(pBot->bot_class-1)); + else + class_not_allowed = team_class_limits[team] & (1<<(pBot->bot_class)); + + while (class_not_allowed) + { + pBot->bot_class = RANDOM_LONG(1, 10); + + if (pBot->bot_class == 10) + class_not_allowed = team_class_limits[team] & (1<<7); + else if (pBot->bot_class <= 7) + class_not_allowed = team_class_limits[team] & (1<<(pBot->bot_class-1)); + else + class_not_allowed = team_class_limits[team] & (1<<(pBot->bot_class)); + } + } + + // select the class the bot wishes to use... + if (pBot->bot_class == 0) + strcpy(c_class, "civilian"); + else if (pBot->bot_class == 1) + strcpy(c_class, "scout"); + else if (pBot->bot_class == 2) + strcpy(c_class, "sniper"); + else if (pBot->bot_class == 3) + strcpy(c_class, "soldier"); + else if (pBot->bot_class == 4) + strcpy(c_class, "demoman"); + else if (pBot->bot_class == 5) + strcpy(c_class, "medic"); + else if (pBot->bot_class == 6) + strcpy(c_class, "hwguy"); + else if (pBot->bot_class == 7) + strcpy(c_class, "pyro"); + else if (pBot->bot_class == 8) + strcpy(c_class, "spy"); + else if (pBot->bot_class == 9) + strcpy(c_class, "engineer"); + else + strcpy(c_class, "randompc"); + + FakeClientCommand(pEdict, c_class, NULL, NULL); + + // bot has now joined the game (doesn't need to be started) + pBot->not_started = 0; + + return; + } + } + else if (mod_id == CSTRIKE_DLL) + { + // handle Counter-Strike stuff here... + + if (pBot->start_action == MSG_CS_TEAM_SELECT) + { + pBot->start_action = MSG_CS_IDLE; // switch back to idle + + if ((pBot->bot_team != 1) && (pBot->bot_team != 2) && + (pBot->bot_team != 5)) + pBot->bot_team = -1; + + if (pBot->bot_team == -1) + pBot->bot_team = RANDOM_LONG(1, 2); + + // select the team the bot wishes to join... + if (pBot->bot_team == 1) + strcpy(c_team, "1"); + else if (pBot->bot_team == 2) + strcpy(c_team, "2"); + else + strcpy(c_team, "5"); + + FakeClientCommand(pEdict, "menuselect", c_team, NULL); + + return; + } + + if (pBot->start_action == MSG_CS_CT_SELECT) // counter terrorist + { + pBot->start_action = MSG_CS_IDLE; // switch back to idle + + if ((pBot->bot_class < 1) || (pBot->bot_class > 4)) + pBot->bot_class = -1; // use random if invalid + + if (pBot->bot_class == -1) + pBot->bot_class = RANDOM_LONG(1, 4); + + // select the class the bot wishes to use... + if (pBot->bot_class == 1) + strcpy(c_class, "1"); + else if (pBot->bot_class == 2) + strcpy(c_class, "2"); + else if (pBot->bot_class == 3) + strcpy(c_class, "3"); + else if (pBot->bot_class == 4) + strcpy(c_class, "4"); + else + strcpy(c_class, "5"); // random + + FakeClientCommand(pEdict, "menuselect", c_class, NULL); + + // bot has now joined the game (doesn't need to be started) + pBot->not_started = 0; + + return; + } + + if (pBot->start_action == MSG_CS_T_SELECT) // terrorist select + { + pBot->start_action = MSG_CS_IDLE; // switch back to idle + + if ((pBot->bot_class < 1) || (pBot->bot_class > 4)) + pBot->bot_class = -1; // use random if invalid + + if (pBot->bot_class == -1) + pBot->bot_class = RANDOM_LONG(1, 4); + + // select the class the bot wishes to use... + if (pBot->bot_class == 1) + strcpy(c_class, "1"); + else if (pBot->bot_class == 2) + strcpy(c_class, "2"); + else if (pBot->bot_class == 3) + strcpy(c_class, "3"); + else if (pBot->bot_class == 4) + strcpy(c_class, "4"); + else + strcpy(c_class, "5"); // random + + FakeClientCommand(pEdict, "menuselect", c_class, NULL); + + // bot has now joined the game (doesn't need to be started) + pBot->not_started = 0; + + return; + } + } + else if ((mod_id == GEARBOX_DLL) && (pent_info_ctfdetect != NULL)) + { + // handle Opposing Force CTF stuff here... + + if (pBot->start_action == MSG_OPFOR_TEAM_SELECT) + { + pBot->start_action = MSG_OPFOR_IDLE; // switch back to idle + + if ((pBot->bot_team != 1) && (pBot->bot_team != 2) && + (pBot->bot_team != 3)) + pBot->bot_team = -1; + + if (pBot->bot_team == -1) + pBot->bot_team = RANDOM_LONG(1, 2); + + // select the team the bot wishes to join... + if (pBot->bot_team == 1) + strcpy(c_team, "1"); + else if (pBot->bot_team == 2) + strcpy(c_team, "2"); + else + strcpy(c_team, "3"); + + FakeClientCommand(pEdict, "jointeam", c_team, NULL); + + return; + } + + if (pBot->start_action == MSG_OPFOR_CLASS_SELECT) + { + pBot->start_action = MSG_OPFOR_IDLE; // switch back to idle + + if ((pBot->bot_class < 0) || (pBot->bot_class > 10)) + pBot->bot_class = -1; + + if (pBot->bot_class == -1) + pBot->bot_class = RANDOM_LONG(1, 10); + + // select the class the bot wishes to use... + if (pBot->bot_class == 1) + strcpy(c_class, "1"); + else if (pBot->bot_class == 2) + strcpy(c_class, "2"); + else if (pBot->bot_class == 3) + strcpy(c_class, "3"); + else if (pBot->bot_class == 4) + strcpy(c_class, "4"); + else if (pBot->bot_class == 5) + strcpy(c_class, "5"); + else if (pBot->bot_class == 6) + strcpy(c_class, "6"); + else + strcpy(c_class, "7"); + + FakeClientCommand(pEdict, "selectchar", c_class, NULL); + + // bot has now joined the game (doesn't need to be started) + pBot->not_started = 0; + + return; + } + } + else if (mod_id == FRONTLINE_DLL) + { + // handle FrontLineForce stuff here... + + if (pBot->start_action == MSG_FLF_TEAM_SELECT) + { + pBot->start_action = MSG_FLF_IDLE; // switch back to idle + + if ((pBot->bot_team != 1) && (pBot->bot_team != 2) && + (pBot->bot_team != 5)) + pBot->bot_team = -1; + + if (pBot->bot_team == -1) + pBot->bot_team = RANDOM_LONG(1, 2); + + // select the team the bot wishes to join... + if (pBot->bot_team == 1) + strcpy(c_team, "1"); + else if (pBot->bot_team == 2) + strcpy(c_team, "2"); + else + strcpy(c_team, "5"); + + FakeClientCommand(pEdict, "jointeam", c_team, NULL); + + return; + } + + if (pBot->start_action == MSG_FLF_CLASS_SELECT) + { + pBot->start_action = MSG_FLF_IDLE; // switch back to idle + + team = UTIL_GetTeam(pEdict); + + if (team == 0) // rebels + { + if ((pBot->bot_class < 0) || (pBot->bot_class > 3)) + pBot->bot_class = -1; + + if (pBot->bot_class == -1) + pBot->bot_class = RANDOM_LONG(1, 3); + + // select the class the bot wishes to use... + if (pBot->bot_class == 1) + strcpy(c_class, "rebelsrecon"); + else if (pBot->bot_class == 2) + strcpy(c_class, "rebelsassault"); + else + strcpy(c_class, "rebelssupport"); + } + else // commandos + { + if ((pBot->bot_class < 0) || (pBot->bot_class > 3)) + pBot->bot_class = -1; + + if (pBot->bot_class == -1) + pBot->bot_class = RANDOM_LONG(1, 3); + + // select the class the bot wishes to use... + if (pBot->bot_class == 1) + strcpy(c_class, "commandosrecon"); + else if (pBot->bot_class == 2) + strcpy(c_class, "commandosassault"); + else + strcpy(c_class, "commandossupport"); + } + + FakeClientCommand(pEdict, c_class, NULL, NULL); + + return; + } + + if (pBot->start_action == MSG_FLF_PISTOL_SELECT) + { + int prim_weapon_group, sec_weapon_group; + + pBot->start_action = MSG_FLF_IDLE; // switch back to idle + + int flf_class = UTIL_GetClass(pEdict); + + if (flf_class == 0) // recon + { + prim_weapon_group = RANDOM_LONG(1, 3); + + if (prim_weapon_group == 1) // shotguns + { + int weapon = RANDOM_LONG(1, 2); + + if (weapon == 1) + pBot->primary_weapon = FLF_WEAPON_SPAS12; + else + pBot->primary_weapon = FLF_WEAPON_RS202M2; + } + else if (prim_weapon_group == 2) // submachine + { + int weapon = RANDOM_LONG(1, 4); + + if (weapon == 1) + pBot->primary_weapon = FLF_WEAPON_MP5A2; + else if (weapon == 2) + pBot->primary_weapon = FLF_WEAPON_MP5SD; + else if (weapon == 3) + pBot->primary_weapon = FLF_WEAPON_MAC10; + else + pBot->primary_weapon = FLF_WEAPON_UMP45; + } + else // rifles + { + int weapon = RANDOM_LONG(1, 2); + + if (weapon == 1) + pBot->primary_weapon = FLF_WEAPON_MSG90; + else + pBot->primary_weapon = FLF_WEAPON_SAKO; + } + + if (prim_weapon_group == 1) + sec_weapon_group = RANDOM_LONG(2, 3); + else if (prim_weapon_group == 3) + sec_weapon_group = RANDOM_LONG(1, 2); + else + { + if (RANDOM_LONG(1, 100) <= 50) + sec_weapon_group = 1; + else + sec_weapon_group = 3; + } + + if (sec_weapon_group == 1) // shotguns + { + int weapon = RANDOM_LONG(1, 2); + + if (weapon == 1) + pBot->secondary_weapon = FLF_WEAPON_SPAS12; + else + pBot->secondary_weapon = FLF_WEAPON_RS202M2; + } + else if (sec_weapon_group == 2) // submachine + { + int weapon = RANDOM_LONG(1, 4); + + if (weapon == 1) + pBot->secondary_weapon = FLF_WEAPON_MP5A2; + else if (weapon == 2) + pBot->secondary_weapon = FLF_WEAPON_MP5SD; + else if (weapon == 3) + pBot->secondary_weapon = FLF_WEAPON_MAC10; + else + pBot->secondary_weapon = FLF_WEAPON_UMP45; + } + else // rifles + { + int weapon = RANDOM_LONG(1, 2); + + if (weapon == 1) + pBot->secondary_weapon = FLF_WEAPON_MSG90; + else + pBot->secondary_weapon = FLF_WEAPON_SAKO; + } + } + else if (flf_class == 1) // assault + { + prim_weapon_group = RANDOM_LONG(1, 3); + + if (prim_weapon_group == 1) // shotguns + { + int weapon = RANDOM_LONG(1, 2); + + if (weapon == 1) + pBot->primary_weapon = FLF_WEAPON_SPAS12; + else + pBot->primary_weapon = FLF_WEAPON_RS202M2; + } + else if (prim_weapon_group == 2) // submachine + { + int weapon = RANDOM_LONG(1, 4); + + if (weapon == 1) + pBot->primary_weapon = FLF_WEAPON_MP5A2; + else if (weapon == 2) + pBot->primary_weapon = FLF_WEAPON_MP5SD; + else if (weapon == 3) + pBot->primary_weapon = FLF_WEAPON_MAC10; + else + pBot->primary_weapon = FLF_WEAPON_UMP45; + } + else // rifles + { + int weapon = RANDOM_LONG(1, 3); + + if (weapon == 1) + pBot->primary_weapon = FLF_WEAPON_M4; + else if (weapon == 2) + pBot->primary_weapon = FLF_WEAPON_FAMAS; + else + pBot->primary_weapon = FLF_WEAPON_AK5; + } + + if (prim_weapon_group == 1) + sec_weapon_group = RANDOM_LONG(2, 3); + else if (prim_weapon_group == 2) + { + if (RANDOM_LONG(1, 100) <= 50) + sec_weapon_group = 1; + else + sec_weapon_group = 3; + } + else // prim == 3 + sec_weapon_group = RANDOM_LONG(1, 2); + + if (sec_weapon_group == 1) // shotguns + { + int weapon = RANDOM_LONG(1, 2); + + if (weapon == 1) + pBot->secondary_weapon = FLF_WEAPON_SPAS12; + else + pBot->secondary_weapon = FLF_WEAPON_RS202M2; + } + else if (sec_weapon_group == 2) // submachine + { + int weapon = RANDOM_LONG(1, 4); + + if (weapon == 1) + pBot->secondary_weapon = FLF_WEAPON_MP5A2; + else if (weapon == 2) + pBot->secondary_weapon = FLF_WEAPON_MP5SD; + else if (weapon == 3) + pBot->secondary_weapon = FLF_WEAPON_MAC10; + else + pBot->secondary_weapon = FLF_WEAPON_UMP45; + } + else // rifles + { + int weapon = RANDOM_LONG(1, 3); + + if (weapon == 1) + pBot->secondary_weapon = FLF_WEAPON_M4; + else if (weapon == 2) + pBot->secondary_weapon = FLF_WEAPON_FAMAS; + else + pBot->secondary_weapon = FLF_WEAPON_AK5; + } + } + else // support + { + prim_weapon_group = RANDOM_LONG(1, 3); + + if (prim_weapon_group == 1) // shotguns + { + int weapon = RANDOM_LONG(1, 2); + + if (weapon == 1) + pBot->primary_weapon = FLF_WEAPON_SPAS12; + else + pBot->primary_weapon = FLF_WEAPON_RS202M2; + } + else if (prim_weapon_group == 2) // submachine + { + int weapon = RANDOM_LONG(1, 4); + + if (weapon == 1) + pBot->primary_weapon = FLF_WEAPON_MP5A2; + else if (weapon == 2) + pBot->primary_weapon = FLF_WEAPON_MP5SD; + else if (weapon == 3) + pBot->primary_weapon = FLF_WEAPON_MAC10; + else + pBot->primary_weapon = FLF_WEAPON_UMP45; + } + else if (prim_weapon_group == 3) // rifles & heavyweapons + { + if (RANDOM_LONG(1, 100) <= 50) + { + int weapon = RANDOM_LONG(1, 3); // rifles + + if (weapon == 1) + pBot->primary_weapon = FLF_WEAPON_M4; + else if (weapon == 2) + pBot->primary_weapon = FLF_WEAPON_FAMAS; + else + pBot->primary_weapon = FLF_WEAPON_AK5; + } + else // heavy weapons + { + pBot->primary_weapon = FLF_WEAPON_HK21; + } + } + + if (prim_weapon_group == 1) + sec_weapon_group = RANDOM_LONG(2, 3); + else if (prim_weapon_group == 2) + { + if (RANDOM_LONG(1, 100) <= 50) + sec_weapon_group = 1; + else + sec_weapon_group = 3; + } + else // prim == 3 + sec_weapon_group = RANDOM_LONG(1, 2); + + if (sec_weapon_group == 1) // shotguns + { + int weapon = RANDOM_LONG(1, 2); + + if (weapon == 1) + pBot->secondary_weapon = FLF_WEAPON_SPAS12; + else + pBot->secondary_weapon = FLF_WEAPON_RS202M2; + } + else if (sec_weapon_group == 2) // submachine + { + int weapon = RANDOM_LONG(1, 4); + + if (weapon == 1) + pBot->secondary_weapon = FLF_WEAPON_MP5A2; + else if (weapon == 2) + pBot->secondary_weapon = FLF_WEAPON_MP5SD; + else if (weapon == 3) + pBot->secondary_weapon = FLF_WEAPON_MAC10; + else + pBot->secondary_weapon = FLF_WEAPON_UMP45; + } + else if (sec_weapon_group == 3) // rifles & heavyweapons + { + if (RANDOM_LONG(1, 100) <= 50) + { + int weapon = RANDOM_LONG(1, 3); // rifles + + if (weapon == 1) + pBot->secondary_weapon = FLF_WEAPON_M4; + else if (weapon == 2) + pBot->secondary_weapon = FLF_WEAPON_FAMAS; + else + pBot->secondary_weapon = FLF_WEAPON_AK5; + } + else // heavy weapons + { + pBot->secondary_weapon = FLF_WEAPON_HK21; + } + } + } + + int pistol = RANDOM_LONG(1, 2); + + if (pistol == 1) + strcpy(c_item, "26"); // mk23 + else + strcpy(c_item, "23"); // beretta + + FakeClientCommand(pEdict, "pistols", c_item, NULL); + + return; + } + + if (pBot->start_action == MSG_FLF_WEAPON_SELECT) + { + int weapon_class; + + pBot->start_action = MSG_FLF_IDLE; // switch back to idle + + if (pBot->primary_weapon) + weapon_class = pBot->primary_weapon; + else + weapon_class = pBot->secondary_weapon; + + if ((weapon_class == FLF_WEAPON_SPAS12) || // shotguns + (weapon_class == FLF_WEAPON_RS202M2)) + strcpy(c_item, "shotgun"); + else if ((weapon_class == FLF_WEAPON_MP5A2) || // submachine + (weapon_class == FLF_WEAPON_MP5SD) || + (weapon_class == FLF_WEAPON_MAC10) || + (weapon_class == FLF_WEAPON_UMP45)) + strcpy(c_item, "submachine"); + else if ((weapon_class == FLF_WEAPON_M4) || + (weapon_class == FLF_WEAPON_FAMAS) || + (weapon_class == FLF_WEAPON_AK5) || + (weapon_class == FLF_WEAPON_MSG90) || + (weapon_class == FLF_WEAPON_SAKO)) + strcpy(c_item, "rifles"); + else + strcpy(c_item, "heavyweapons"); + + FakeClientCommand(pEdict, "wpnclass", c_item, NULL); + + return; + } + + if (pBot->start_action == MSG_FLF_SHOTGUN_SELECT) + { + int weapon_class; + + pBot->start_action = MSG_FLF_IDLE; // switch back to idle + + if (pBot->primary_weapon) + { + weapon_class = pBot->primary_weapon; + pBot->primary_weapon = 0; + } + else + { + weapon_class = pBot->secondary_weapon; + + // bot has now joined the game (doesn't need to be started) + pBot->not_started = 0; + } + + sprintf(c_item, "%d", weapon_class); + + FakeClientCommand(pEdict, "shotgun", c_item, NULL); + + return; + } + + if (pBot->start_action == MSG_FLF_SUBMACHINE_SELECT) + { + int weapon_class; + + pBot->start_action = MSG_FLF_IDLE; // switch back to idle + + if (pBot->primary_weapon) + { + weapon_class = pBot->primary_weapon; + pBot->primary_weapon = 0; + } + else + { + weapon_class = pBot->secondary_weapon; + + // bot has now joined the game (doesn't need to be started) + pBot->not_started = 0; + } + + sprintf(c_item, "%d", weapon_class); + + FakeClientCommand(pEdict, "submach", c_item, NULL); + + return; + } + + if (pBot->start_action == MSG_FLF_RIFLE_SELECT) + { + int weapon_class; + + pBot->start_action = MSG_FLF_IDLE; // switch back to idle + + if (pBot->primary_weapon) + { + weapon_class = pBot->primary_weapon; + pBot->primary_weapon = 0; + } + else + { + weapon_class = pBot->secondary_weapon; + + // bot has now joined the game (doesn't need to be started) + pBot->not_started = 0; + } + + sprintf(c_item, "%d", weapon_class); + + FakeClientCommand(pEdict, "rifles", c_item, NULL); + + return; + } + + if (pBot->start_action == MSG_FLF_HEAVYWEAPONS_SELECT) + { + int weapon_class; + + pBot->start_action = MSG_FLF_IDLE; // switch back to idle + + if (pBot->primary_weapon) + { + weapon_class = pBot->primary_weapon; + pBot->primary_weapon = 0; + } + else + { + weapon_class = pBot->secondary_weapon; + + // bot has now joined the game (doesn't need to be started) + pBot->not_started = 0; + } + + sprintf(c_item, "%d", weapon_class); + + FakeClientCommand(pEdict, "heavyweapons", c_item, NULL); + + return; + } + } + else if (mod_id == DMC_DLL) + { + FakeClientCommand(pEdict, "_firstspawn", NULL, NULL); + + pBot->not_started = 0; + } + else + { + // otherwise, don't need to do anything to start game... + pBot->not_started = 0; + } +} + diff --git a/bot_weapons.h b/bot_weapons.h new file mode 100644 index 0000000..33b6ae5 --- /dev/null +++ b/bot_weapons.h @@ -0,0 +1,177 @@ +// +// HPB_bot - botman's High Ping Bastard bot +// +// (http://planethalflife.com/botman/) +// +// bot_weapons.h +// + +#ifndef BOT_WEAPONS_H +#define BOT_WEAPONS_H + +// weapon ID values for Valve's Half-Life Deathmatch +#define VALVE_WEAPON_CROWBAR 1 +#define VALVE_WEAPON_GLOCK 2 +#define VALVE_WEAPON_PYTHON 3 +#define VALVE_WEAPON_MP5 4 +#define VALVE_WEAPON_CHAINGUN 5 +#define VALVE_WEAPON_CROSSBOW 6 +#define VALVE_WEAPON_SHOTGUN 7 +#define VALVE_WEAPON_RPG 8 +#define VALVE_WEAPON_GAUSS 9 +#define VALVE_WEAPON_EGON 10 +#define VALVE_WEAPON_HORNETGUN 11 +#define VALVE_WEAPON_HANDGRENADE 12 +#define VALVE_WEAPON_TRIPMINE 13 +#define VALVE_WEAPON_SATCHEL 14 +#define VALVE_WEAPON_SNARK 15 + +#define VALVE_MAX_NORMAL_BATTERY 100 +#define VALVE_HORNET_MAX_CARRY 8 + + +// weapon ID values for Valve's Team Fortress Classic & 1.5 +#define TF_WEAPON_UNKNOWN1 1 +#define TF_WEAPON_UNKNOWN2 2 +#define TF_WEAPON_MEDIKIT 3 +#define TF_WEAPON_SPANNER 4 +#define TF_WEAPON_AXE 5 +#define TF_WEAPON_SNIPERRIFLE 6 +#define TF_WEAPON_AUTORIFLE 7 +#define TF_WEAPON_SHOTGUN 8 +#define TF_WEAPON_SUPERSHOTGUN 9 +#define TF_WEAPON_NAILGUN 10 +#define TF_WEAPON_SUPERNAILGUN 11 +#define TF_WEAPON_GL 12 +#define TF_WEAPON_FLAMETHROWER 13 +#define TF_WEAPON_RPG 14 +#define TF_WEAPON_IC 15 +#define TF_WEAPON_UNKNOWN16 16 +#define TF_WEAPON_AC 17 +#define TF_WEAPON_UNKNOWN18 18 +#define TF_WEAPON_UNKNOWN19 19 +#define TF_WEAPON_TRANQ 20 +#define TF_WEAPON_RAILGUN 21 +#define TF_WEAPON_PL 22 +#define TF_WEAPON_KNIFE 23 + + +// weapon ID values for Counter-Strike +#define CS_WEAPON_P228 1 +#define CS_WEAPON_UNKNOWN2 2 +#define CS_WEAPON_SCOUT 3 +#define CS_WEAPON_HEGRENADE 4 +#define CS_WEAPON_XM1014 5 +#define CS_WEAPON_C4 6 +#define CS_WEAPON_MAC10 7 +#define CS_WEAPON_AUG 8 +#define CS_WEAPON_SMOKEGRENADE 9 +#define CS_WEAPON_ELITE 10 +#define CS_WEAPON_FIVESEVEN 11 +#define CS_WEAPON_UMP45 12 +#define CS_WEAPON_SG550 13 +#define CS_WEAPON_UNKNOWN14 14 +#define CS_WEAPON_UNKNOWN15 15 +#define CS_WEAPON_USP 16 +#define CS_WEAPON_GLOCK18 17 +#define CS_WEAPON_AWP 18 +#define CS_WEAPON_MP5NAVY 19 +#define CS_WEAPON_M249 20 +#define CS_WEAPON_M3 21 +#define CS_WEAPON_M4A1 22 +#define CS_WEAPON_TMP 23 +#define CS_WEAPON_G3SG1 24 +#define CS_WEAPON_FLASHBANG 25 +#define CS_WEAPON_DEAGLE 26 +#define CS_WEAPON_SG552 27 +#define CS_WEAPON_AK47 28 +#define CS_WEAPON_KNIFE 29 +#define CS_WEAPON_P90 30 + + +// weapon ID values for Gearbox's OpFor Deathmatch +#define GEARBOX_WEAPON_CROWBAR 1 +#define GEARBOX_WEAPON_GLOCK 2 +#define GEARBOX_WEAPON_PYTHON 3 +#define GEARBOX_WEAPON_MP5 4 +#define GEARBOX_WEAPON_CHAINGUN 5 +#define GEARBOX_WEAPON_CROSSBOW 6 +#define GEARBOX_WEAPON_SHOTGUN 7 +#define GEARBOX_WEAPON_RPG 8 +#define GEARBOX_WEAPON_GAUSS 9 +#define GEARBOX_WEAPON_EGON 10 +#define GEARBOX_WEAPON_HORNETGUN 11 +#define GEARBOX_WEAPON_HANDGRENADE 12 +#define GEARBOX_WEAPON_TRIPMINE 13 +#define GEARBOX_WEAPON_SATCHEL 14 +#define GEARBOX_WEAPON_SNARK 15 +#define GEARBOX_WEAPON_GRAPPLE 16 +#define GEARBOX_WEAPON_EAGLE 17 +#define GEARBOX_WEAPON_PIPEWRENCH 18 +#define GEARBOX_WEAPON_M249 19 +#define GEARBOX_WEAPON_DISPLACER 20 +#define GEARBOX_WEAPON_UNKNOWN21 21 +#define GEARBOX_WEAPON_SHOCKRIFLE 22 +#define GEARBOX_WEAPON_SPORELAUNCHER 23 +#define GEARBOX_WEAPON_SNIPERRIFLE 24 +#define GEARBOX_WEAPON_KNIFE 25 + + +// weapon ID values for FrontLineForce +#define FLF_WEAPON_SAKO 9 +#define FLF_WEAPON_AK5 10 +#define FLF_WEAPON_RS202M2 11 +#define FLF_WEAPON_UNKNOWN12 12 +#define FLF_WEAPON_UNKNOWN13 13 +#define FLF_WEAPON_UNKNOWN14 14 +#define FLF_WEAPON_UNKNOWN15 15 +#define FLF_WEAPON_MP5SD 16 +#define FLF_WEAPON_M4 17 +#define FLF_WEAPON_FLASHBANG 18 +#define FLF_WEAPON_HEGRENADE 19 +#define FLF_WEAPON_MP5A2 20 +#define FLF_WEAPON_UMP45 21 +#define FLF_WEAPON_SPAS12 22 +#define FLF_WEAPON_BERETTA 23 +#define FLF_WEAPON_KNIFE 24 +#define FLF_WEAPON_MAC10 25 +#define FLF_WEAPON_MK23 26 +#define FLF_WEAPON_MSG90 27 +#define FLF_WEAPON_FAMAS 28 +#define FLF_WEAPON_HK21 29 + +// weapon ID values for HolyWars +#define HW_WEAPON_JACKHAMMER 15 +#define HW_WEAPON_DOUBLESHOTGUN 16 +#define HW_WEAPON_MACHINEGUN 17 +#define HW_WEAPON_ROCKETLAUNCHER 18 +#define HW_WEAPON_UNKNOWN19 19 +#define HW_WEAPON_RAILGUN 20 + +// weapon ID values for Valve's DMC +#define DMC_WEAPON_AXE 1 +#define DMC_WEAPON_SHOTGUN 2 +#define DMC_WEAPON_DOUBLESHOTGUN 4 +#define DMC_WEAPON_NAILGUN 8 +#define DMC_WEAPON_SUPERNAIL 16 +#define DMC_WEAPON_GRENADE1 32 +#define DMC_WEAPON_ROCKET1 64 +#define DMC_WEAPON_LIGHTNING 128 + + +typedef struct +{ + char szClassname[64]; + int iAmmo1; // ammo index for primary ammo + int iAmmo1Max; // max primary ammo + int iAmmo2; // ammo index for secondary ammo + int iAmmo2Max; // max secondary ammo + int iSlot; // HUD slot (0 based) + int iPosition; // slot position + int iId; // weapon ID + int iFlags; // flags??? +} bot_weapon_t; + + +#endif // BOT_WEAPONS_H + diff --git a/dll.cpp b/dll.cpp new file mode 100644 index 0000000..3d3b6ff --- /dev/null +++ b/dll.cpp @@ -0,0 +1,2089 @@ +// +// HPB bot - botman's High Ping Bastard bot +// +// (http://planethalflife.com/botman/) +// +// dll.cpp +// + +#ifndef _WIN32 +#include +#endif + +#include +#include +#include +#include +#include + +#include "bot.h" +#include "bot_func.h" +#include "waypoint.h" + +#define VER_MAJOR 4 +#define VER_MINOR 0 + + +#define MENU_NONE 0 +#define MENU_1 1 +#define MENU_2 2 +#define MENU_3 3 +#define MENU_4 4 + + +extern DLL_FUNCTIONS gFunctionTable; +extern enginefuncs_t g_engfuncs; +extern globalvars_t *gpGlobals; +extern char g_argv[1024]; +extern bool g_waypoint_on; +extern bool g_auto_waypoint; +extern bool g_path_waypoint; +extern bool g_path_waypoint_enable; +extern int num_waypoints; // number of waypoints currently in use +extern WAYPOINT waypoints[MAX_WAYPOINTS]; +extern float wp_display_time[MAX_WAYPOINTS]; +extern bot_t bots[32]; +extern bool b_observer_mode; +extern bool b_botdontshoot; +extern char welcome_msg[80]; + +static FILE *fp; + +int mod_id = 0; +int m_spriteTexture = 0; +int default_bot_skill = 2; +int bot_strafe_percent = 20; // percent of time to strafe +int bot_chat_percent = 10; // percent of time to chat +int bot_taunt_percent = 20; // percent of time to taunt after kill +int bot_whine_percent = 10; // percent of time to whine after death +int bot_grenade_time = 15; // seconds between grenade throws +int bot_logo_percent = 40; // percent of time to spray logo after kill + +int bot_chat_tag_percent = 80; // percent of the time to drop clan tag +int bot_chat_drop_percent = 10; // percent of the time to drop characters +int bot_chat_swap_percent = 10; // percent of the time to swap characters +int bot_chat_lower_percent = 50; // percent of the time to lowercase chat + +bool b_random_color = TRUE; +bool isFakeClientCommand = FALSE; +int fake_arg_count; +int IsDedicatedServer; +float bot_check_time = 60.0; +int bot_reaction_time = 2; +int min_bots = -1; +int max_bots = -1; +int num_bots = 0; +int prev_num_bots = 0; +bool g_GameRules = FALSE; +edict_t *clients[32]; +edict_t *listenserver_edict = NULL; +float welcome_time = 0.0; +bool welcome_sent = FALSE; +int g_menu_waypoint; +int g_menu_state = 0; +int bot_stop = 0; +int jumppad_off = 0; + +bool is_team_play = FALSE; +char team_names[MAX_TEAMS][MAX_TEAMNAME_LENGTH]; +int num_teams = 0; +bool checked_teamplay = FALSE; +edict_t *pent_info_tfdetect = NULL; +edict_t *pent_info_ctfdetect = NULL; +edict_t *pent_info_frontline = NULL; +edict_t *pent_item_tfgoal = NULL; +edict_t *pent_info_tfgoal = NULL; +int max_team_players[4]; +int team_class_limits[4]; +int team_allies[4]; // bit mapped allies BLUE, RED, YELLOW, and GREEN +int max_teams = 0; +int num_flags = 0; +FLAG_S flags[MAX_FLAGS]; +int num_backpacks = 0; +BACKPACK_S backpacks[MAX_BACKPACKS]; + +FILE *bot_cfg_fp = NULL; +bool need_to_open_cfg = TRUE; +float bot_cfg_pause_time = 0.0; +float respawn_time = 0.0; +bool spawn_time_reset = FALSE; + +char *show_menu_none = {" "}; +char *show_menu_1 = + {"Waypoint Tags\n\n1. Team Specific\n2. Wait for Lift\n3. Door\n4. Sniper Spot\n5. More..."}; +char *show_menu_2 = + {"Waypoint Tags\n\n1. Team 1\n2. Team 2\n3. Team 3\n4. Team 4\n5. CANCEL"}; +char *show_menu_2_flf = + {"Waypoint Tags\n\n1. Attackers\n2. Defenders\n\n5. CANCEL"}; +char *show_menu_3 = + {"Waypoint Tags\n\n1. Flag Location\n2. Flag Goal Location\n3. Sentry gun\n4. Dispenser\n5. More"}; +char *show_menu_3_flf = + {"Waypoint Tags\n\n1. Capture Point\n2. Defend Point\n3. Prone\n\n5. CANCEL"}; +char *show_menu_3_hw = + {"Waypoint Tags\n\n1. Halo Location\n\n\n\n5. More"}; +char *show_menu_4 = + {"Waypoint Tags\n\n1. Health\n2. Armor\n3. Ammo\n4. Jump\n5. CANCEL"}; + + +void BotNameInit(void); +void BotLogoInit(void); +void UpdateClientData(const struct edict_s *ent, int sendweapons, struct clientdata_s *cd); +void ProcessBotCfgFile(void); +void HPB_Bot_ServerCommand (void); + +extern void welcome_init(void); + + + +// START of Metamod stuff + +enginefuncs_t meta_engfuncs; +gamedll_funcs_t *gpGamedllFuncs; +mutil_funcs_t *gpMetaUtilFuncs; +meta_globals_t *gpMetaGlobals; + +META_FUNCTIONS gMetaFunctionTable = +{ + NULL, // pfnGetEntityAPI() + NULL, // pfnGetEntityAPI_Post() + GetEntityAPI2, // pfnGetEntityAPI2() + NULL, // pfnGetEntityAPI2_Post() + NULL, // pfnGetNewDLLFunctions() + NULL, // pfnGetNewDLLFunctions_Post() + GetEngineFunctions, // pfnGetEngineFunctions() + NULL, // pfnGetEngineFunctions_Post() +}; + +plugin_info_t Plugin_info = { + META_INTERFACE_VERSION, // interface version + "HPB_Bot", // plugin name + "4.0.4", // plugin version + "09/11/2004", // date of creation + "botman && Pierre-Marie Baty", // plugin author + "http://hpb-bot.bots-united.com/", // plugin URL + "HPB_BOT", // plugin logtag + PT_STARTUP, // when loadable + PT_ANYTIME, // when unloadable +}; + + +C_DLLEXPORT 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 mmajor = 0, mminor = 0, pmajor = 0, pminor = 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", &mmajor, &mminor); + sscanf (META_INTERFACE_VERSION, "%d:%d", &pmajor, &pminor); + + if ((pmajor > mmajor) || ((pmajor == mmajor) && (pminor > mminor))) + { + LOG_CONSOLE (PLID, "metamod version is too old for this plugin; update metamod"); + LOG_ERROR (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 (pmajor < mmajor) + { + LOG_CONSOLE (PLID, "metamod version is incompatible with this plugin; please find a newer version of this plugin"); + LOG_ERROR (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 +} + + +C_DLLEXPORT int Meta_Attach (PLUG_LOADTIME now, META_FUNCTIONS *pFunctionTable, 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_ERROR (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 (pFunctionTable, &gMetaFunctionTable, sizeof (META_FUNCTIONS)); + gpGamedllFuncs = pGamedllFuncs; + + // print a message to notify about plugin attaching + LOG_CONSOLE (PLID, "%s: plugin attaching", Plugin_info.name); + LOG_MESSAGE (PLID, "%s: plugin attaching", Plugin_info.name); + + // ask the engine to register the server commands this plugin uses + REG_SVR_COMMAND ("HPB_Bot", HPB_Bot_ServerCommand); + + return (TRUE); // returning TRUE enables metamod to attach this plugin +} + + +C_DLLEXPORT 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_ERROR (PLID, "%s: plugin NOT detaching (can't unload plugin right now)", Plugin_info.name); + return (FALSE); // returning FALSE prevents metamod from unloading this plugin + } + + return (TRUE); // returning TRUE enables metamod to unload this plugin +} + +// END of Metamod stuff + + + +void GameDLLInit( void ) +{ + int i; + + IsDedicatedServer = IS_DEDICATED_SERVER(); + + for (i=0; i<32; i++) + clients[i] = NULL; + + welcome_init(); + + // initialize the bots array of structures... + memset(bots, 0, sizeof(bots)); + + BotNameInit(); + BotLogoInit(); + + LoadBotChat(); + LoadBotModels(); + + RETURN_META (MRES_IGNORED); +} + +int Spawn( edict_t *pent ) +{ + int index; + + if (gpGlobals->deathmatch) + { + char *pClassname = (char *)STRING(pent->v.classname); + + if (strcmp(pClassname, "worldspawn") == 0) + { + // do level initialization stuff here... + + WaypointInit(); + WaypointLoad(NULL); + + pent_info_tfdetect = NULL; + pent_info_ctfdetect = NULL; + pent_info_frontline = NULL; + pent_item_tfgoal = NULL; + pent_info_tfgoal = NULL; + + for (index=0; index < 4; index++) + { + max_team_players[index] = 0; // no player limit + team_class_limits[index] = 0; // no class limits + team_allies[index] = 0; + } + + max_teams = 0; + num_flags = 0; + + for (index=0; index < MAX_FLAGS; index++) + { + flags[index].edict = NULL; + flags[index].team_no = 0; // any team unless specified + } + + num_backpacks = 0; + + for (index=0; index < MAX_BACKPACKS; index++) + { + backpacks[index].edict = NULL; + backpacks[index].armor = 0; + backpacks[index].health = 0; + backpacks[index].ammo = 0; + backpacks[index].team = 0; // any team unless specified + } + + PRECACHE_SOUND("weapons/xbow_hit1.wav"); // waypoint add + PRECACHE_SOUND("weapons/mine_activate.wav"); // waypoint delete + PRECACHE_SOUND("common/wpn_hudoff.wav"); // path add/delete start + PRECACHE_SOUND("common/wpn_hudon.wav"); // path add/delete done + PRECACHE_SOUND("common/wpn_moveselect.wav"); // path add/delete cancel + PRECACHE_SOUND("common/wpn_denyselect.wav"); // path add/delete error + PRECACHE_SOUND("player/sprayer.wav"); // logo spray sound + + m_spriteTexture = PRECACHE_MODEL( "sprites/lgtning.spr"); + + g_GameRules = TRUE; + + is_team_play = FALSE; + memset(team_names, 0, sizeof(team_names)); + num_teams = 0; + checked_teamplay = FALSE; + + bot_cfg_pause_time = 0.0; + respawn_time = 0.0; + spawn_time_reset = FALSE; + + prev_num_bots = num_bots; + num_bots = 0; + + bot_check_time = gpGlobals->time + 60.0; + } + + if ((mod_id == HOLYWARS_DLL) && (jumppad_off) && + (strcmp(pClassname, "trigger_jumppad") == 0)) + { + RETURN_META_VALUE (MRES_SUPERCEDE, -1); // disable jumppads + } + } + + RETURN_META_VALUE (MRES_IGNORED, 0); +} + +void KeyValue( edict_t *pentKeyvalue, KeyValueData *pkvd ) +{ + static edict_t *temp_pent; + static int flag_index; + static int backpack_index; + + if (mod_id == TFC_DLL) + { + if (pentKeyvalue == pent_info_tfdetect) + { + if (strcmp(pkvd->szKeyName, "ammo_medikit") == 0) // max BLUE players + max_team_players[0] = atoi(pkvd->szValue); + else if (strcmp(pkvd->szKeyName, "ammo_detpack") == 0) // max RED players + max_team_players[1] = atoi(pkvd->szValue); + else if (strcmp(pkvd->szKeyName, "maxammo_medikit") == 0) // max YELLOW players + max_team_players[2] = atoi(pkvd->szValue); + else if (strcmp(pkvd->szKeyName, "maxammo_detpack") == 0) // max GREEN players + max_team_players[3] = atoi(pkvd->szValue); + + else if (strcmp(pkvd->szKeyName, "maxammo_shells") == 0) // BLUE class limits + team_class_limits[0] = atoi(pkvd->szValue); + else if (strcmp(pkvd->szKeyName, "maxammo_nails") == 0) // RED class limits + team_class_limits[1] = atoi(pkvd->szValue); + else if (strcmp(pkvd->szKeyName, "maxammo_rockets") == 0) // YELLOW class limits + team_class_limits[2] = atoi(pkvd->szValue); + else if (strcmp(pkvd->szKeyName, "maxammo_cells") == 0) // GREEN class limits + team_class_limits[3] = atoi(pkvd->szValue); + + else if (strcmp(pkvd->szKeyName, "team1_allies") == 0) // BLUE allies + team_allies[0] = atoi(pkvd->szValue); + else if (strcmp(pkvd->szKeyName, "team2_allies") == 0) // RED allies + team_allies[1] = atoi(pkvd->szValue); + else if (strcmp(pkvd->szKeyName, "team3_allies") == 0) // YELLOW allies + team_allies[2] = atoi(pkvd->szValue); + else if (strcmp(pkvd->szKeyName, "team4_allies") == 0) // GREEN allies + team_allies[3] = atoi(pkvd->szValue); + } + else if (pent_info_tfdetect == NULL) + { + if ((strcmp(pkvd->szKeyName, "classname") == 0) && + (strcmp(pkvd->szValue, "info_tfdetect") == 0)) + { + pent_info_tfdetect = pentKeyvalue; + } + } + + if (pentKeyvalue == pent_item_tfgoal) + { + if (strcmp(pkvd->szKeyName, "team_no") == 0) + flags[flag_index].team_no = atoi(pkvd->szValue); + + if ((strcmp(pkvd->szKeyName, "mdl") == 0) && + ((strcmp(pkvd->szValue, "models/flag.mdl") == 0) || + (strcmp(pkvd->szValue, "models/keycard.mdl") == 0) || + (strcmp(pkvd->szValue, "models/ball.mdl") == 0))) + { + num_flags++; + } + } + else if (pent_item_tfgoal == NULL) + { + if ((strcmp(pkvd->szKeyName, "classname") == 0) && + (strcmp(pkvd->szValue, "item_tfgoal") == 0)) + { + if (num_flags < MAX_FLAGS) + { + pent_item_tfgoal = pentKeyvalue; + + flags[num_flags].edict = pentKeyvalue; + + flag_index = num_flags; // in case the mdl comes before team_no + } + } + } + else + { + pent_item_tfgoal = NULL; // reset for non-flag item_tfgoal's + } + + + if (pentKeyvalue != pent_info_tfgoal) // different edict? + { + pent_info_tfgoal = NULL; // reset + } + + if (pentKeyvalue == pent_info_tfgoal) + { + if (strcmp(pkvd->szKeyName, "team_no") == 0) + backpacks[backpack_index].team = atoi(pkvd->szValue); + + if (strcmp(pkvd->szKeyName, "armorvalue") == 0) + backpacks[backpack_index].armor = atoi(pkvd->szValue); + + if (strcmp(pkvd->szKeyName, "health") == 0) + backpacks[backpack_index].health = atoi(pkvd->szValue); + + if ((strcmp(pkvd->szKeyName, "ammo_nails") == 0) || + (strcmp(pkvd->szKeyName, "ammo_rockets") == 0) || + (strcmp(pkvd->szKeyName, "ammo_cells") == 0) || + (strcmp(pkvd->szKeyName, "ammo_shells") == 0)) + backpacks[backpack_index].ammo = atoi(pkvd->szValue); + + if ((strcmp(pkvd->szKeyName, "mdl") == 0) && + (strcmp(pkvd->szValue, "models/backpack.mdl") == 0)) + { + num_backpacks++; + } + } + else if (pent_info_tfgoal == NULL) + { + if (((strcmp(pkvd->szKeyName, "classname") == 0) && + (strcmp(pkvd->szValue, "info_tfgoal") == 0)) || + ((strcmp(pkvd->szKeyName, "classname") == 0) && + (strcmp(pkvd->szValue, "i_t_g") == 0))) + { + if (num_backpacks < MAX_BACKPACKS) + { + pent_info_tfgoal = pentKeyvalue; + + backpacks[num_backpacks].edict = pentKeyvalue; + + // in case the mdl comes before the other fields + backpack_index = num_backpacks; + } + } + } + + if ((strcmp(pkvd->szKeyName, "classname") == 0) && + ((strcmp(pkvd->szValue, "info_player_teamspawn") == 0) || + (strcmp(pkvd->szValue, "i_p_t") == 0))) + { + temp_pent = pentKeyvalue; + } + else if (pentKeyvalue == temp_pent) + { + if (strcmp(pkvd->szKeyName, "team_no") == 0) + { + int value = atoi(pkvd->szValue); + + if (value > max_teams) + max_teams = value; + } + } + } + else if (mod_id == GEARBOX_DLL) + { + if (pent_info_ctfdetect == NULL) + { + if ((strcmp(pkvd->szKeyName, "classname") == 0) && + (strcmp(pkvd->szValue, "info_ctfdetect") == 0)) + { + pent_info_ctfdetect = pentKeyvalue; + } + } + } + + RETURN_META (MRES_IGNORED); +} + +BOOL ClientConnect( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ) +{ + if (gpGlobals->deathmatch) + { + int i; + int count = 0; + + // check if this client is the listen server client + if (strcmp(pszAddress, "loopback") == 0) + { + // save the edict of the listen server client... + listenserver_edict = pEntity; + } + + // check if this is NOT a bot joining the server... + if (strcmp(pszAddress, "127.0.0.1") != 0) + { + // don't try to add bots for 60 seconds, give client time to get added + bot_check_time = gpGlobals->time + 60.0; + + for (i=0; i < 32; i++) + { + if (bots[i].is_used) // count the number of bots in use + count++; + } + + // if there are currently more than the minimum number of bots running + // then kick one of the bots off the server... + if ((count > min_bots) && (min_bots != -1)) + { + for (i=0; i < 32; i++) + { + if (bots[i].is_used) // is this slot used? + { + char cmd[80]; + + sprintf(cmd, "kick \"%s\"\n", bots[i].name); + + SERVER_COMMAND(cmd); // kick the bot using (kick "name") + + break; + } + } + } + } + } + + RETURN_META_VALUE (MRES_IGNORED, 0); +} + +void ClientDisconnect( edict_t *pEntity ) +{ + if (gpGlobals->deathmatch) + { + int i; + + i = 0; + while ((i < 32) && (clients[i] != pEntity)) + i++; + + if (i < 32) + clients[i] = NULL; + + + for (i=0; i < 32; i++) + { + if (bots[i].pEdict == pEntity) + { + // someone kicked this bot off of the server... + + bots[i].is_used = FALSE; // this slot is now free to use + + bots[i].f_kick_time = gpGlobals->time; // save the kicked time + + break; + } + } + } + + RETURN_META (MRES_IGNORED); +} + +void ClientPutInServer( edict_t *pEntity ) +{ + int i = 0; + + while ((i < 32) && (clients[i] != NULL)) + i++; + + if (i < 32) + clients[i] = pEntity; // store this clients edict in the clients array + + RETURN_META (MRES_IGNORED); +} + + +void ClientCommand( edict_t *pEntity ) +{ + const char *pcmd = CMD_ARGV (0); + const char *arg1 = CMD_ARGV (1); + const char *arg2 = CMD_ARGV (2); + const char *arg3 = CMD_ARGV (3); + const char *arg4 = CMD_ARGV (4); + const char *arg5 = CMD_ARGV (5); + + // only allow custom commands if deathmatch mode and NOT dedicated server and + // client sending command is the listen server client... + + if ((gpGlobals->deathmatch) && (!IsDedicatedServer) && + (pEntity == listenserver_edict)) + { + char msg[80]; + + if (FStrEq(pcmd, "addbot")) + { + BotCreate( pEntity, arg1, arg2, arg3, arg4, arg5 ); + + bot_check_time = gpGlobals->time + 5.0; + + RETURN_META (MRES_SUPERCEDE); + } + else if (FStrEq(pcmd, "observer")) + { + if ((arg1 != NULL) && (*arg1 != 0)) + { + int temp = atoi(arg1); + if (temp) + b_observer_mode = TRUE; + else + b_observer_mode = FALSE; + } + + if (b_observer_mode) + ClientPrint(pEntity, HUD_PRINTNOTIFY, "observer mode ENABLED\n"); + else + ClientPrint(pEntity, HUD_PRINTNOTIFY, "observer mode DISABLED\n"); + + RETURN_META (MRES_SUPERCEDE); + } + else if (FStrEq(pcmd, "botskill")) + { + if ((arg1 != NULL) && (*arg1 != 0)) + { + int temp = atoi(arg1); + + if ((temp < 1) || (temp > 5)) + ClientPrint(pEntity, HUD_PRINTNOTIFY, "invalid botskill value!\n"); + else + default_bot_skill = temp; + } + + sprintf(msg, "botskill is %d\n", default_bot_skill); + ClientPrint(pEntity, HUD_PRINTNOTIFY, msg); + + RETURN_META (MRES_SUPERCEDE); + } + else if (FStrEq(pcmd, "bot_strafe_percent")) + { + if ((arg1 != NULL) && (*arg1 != 0)) + { + int temp = atoi(arg1); + + if ((temp < 0) || (temp > 100)) + ClientPrint(pEntity, HUD_PRINTNOTIFY, "invalid bot_strafe_percent value!\n"); + else + bot_strafe_percent = temp; + } + + sprintf(msg, "bot_strafe_percent is %d\n", bot_strafe_percent); + ClientPrint(pEntity, HUD_PRINTNOTIFY, msg); + + RETURN_META (MRES_SUPERCEDE); + } + else if (FStrEq(pcmd, "bot_chat_percent")) + { + if ((arg1 != NULL) && (*arg1 != 0)) + { + int temp = atoi(arg1); + + if ((temp < 0) || (temp > 100)) + ClientPrint(pEntity, HUD_PRINTNOTIFY, "invalid bot_chat_percent value!\n"); + else + bot_chat_percent = temp; + } + + sprintf(msg, "bot_chat_percent is %d\n", bot_chat_percent); + ClientPrint(pEntity, HUD_PRINTNOTIFY, msg); + + RETURN_META (MRES_SUPERCEDE); + } + else if (FStrEq(pcmd, "bot_taunt_percent")) + { + if ((arg1 != NULL) && (*arg1 != 0)) + { + int temp = atoi(arg1); + + if ((temp < 0) || (temp > 100)) + ClientPrint(pEntity, HUD_PRINTNOTIFY, "invalid bot_taunt_percent value!\n"); + else + bot_taunt_percent = temp; + } + + sprintf(msg, "bot_taunt_percent is %d\n", bot_taunt_percent); + ClientPrint(pEntity, HUD_PRINTNOTIFY, msg); + + RETURN_META (MRES_SUPERCEDE); + } + else if (FStrEq(pcmd, "bot_whine_percent")) + { + if ((arg1 != NULL) && (*arg1 != 0)) + { + int temp = atoi(arg1); + + if ((temp < 0) || (temp > 100)) + ClientPrint(pEntity, HUD_PRINTNOTIFY, "invalid bot_whine_percent value!\n"); + else + bot_whine_percent = temp; + } + + sprintf(msg, "bot_whine_percent is %d\n", bot_whine_percent); + ClientPrint(pEntity, HUD_PRINTNOTIFY, msg); + + RETURN_META (MRES_SUPERCEDE); + } + else if (FStrEq(pcmd, "bot_chat_tag_percent")) + { + if ((arg1 != NULL) && (*arg1 != 0)) + { + int temp = atoi(arg1); + + if ((temp < 0) || (temp > 100)) + ClientPrint(pEntity, HUD_PRINTNOTIFY, "invalid bot_chat_tag_percent value!\n"); + else + bot_chat_tag_percent = temp; + } + + sprintf(msg, "bot_chat_tag_percent is %d\n", bot_chat_tag_percent); + ClientPrint(pEntity, HUD_PRINTNOTIFY, msg); + + RETURN_META (MRES_SUPERCEDE); + } + else if (FStrEq(pcmd, "bot_chat_drop_percent")) + { + if ((arg1 != NULL) && (*arg1 != 0)) + { + int temp = atoi(arg1); + + if ((temp < 0) || (temp > 100)) + ClientPrint(pEntity, HUD_PRINTNOTIFY, "invalid bot_chat_drop_percent value!\n"); + else + bot_chat_drop_percent = temp; + } + + sprintf(msg, "bot_chat_drop_percent is %d\n", bot_chat_drop_percent); + ClientPrint(pEntity, HUD_PRINTNOTIFY, msg); + + RETURN_META (MRES_SUPERCEDE); + } + else if (FStrEq(pcmd, "bot_chat_swap_percent")) + { + if ((arg1 != NULL) && (*arg1 != 0)) + { + int temp = atoi(arg1); + + if ((temp < 0) || (temp > 100)) + ClientPrint(pEntity, HUD_PRINTNOTIFY, "invalid bot_chat_swap_percent value!\n"); + else + bot_chat_swap_percent = temp; + } + + sprintf(msg, "bot_chat_swap_percent is %d\n", bot_chat_swap_percent); + ClientPrint(pEntity, HUD_PRINTNOTIFY, msg); + + RETURN_META (MRES_SUPERCEDE); + } + else if (FStrEq(pcmd, "bot_chat_lower_percent")) + { + if ((arg1 != NULL) && (*arg1 != 0)) + { + int temp = atoi(arg1); + + if ((temp < 0) || (temp > 100)) + ClientPrint(pEntity, HUD_PRINTNOTIFY, "invalid bot_chat_lower_percent value!\n"); + else + bot_chat_lower_percent = temp; + } + + sprintf(msg, "bot_chat_lower_percent is %d\n", bot_chat_lower_percent); + ClientPrint(pEntity, HUD_PRINTNOTIFY, msg); + + RETURN_META (MRES_SUPERCEDE); + } + else if (FStrEq(pcmd, "bot_grenade_time")) + { + if ((arg1 != NULL) && (*arg1 != 0)) + { + int temp = atoi(arg1); + + if ((temp < 0) || (temp > 60)) + ClientPrint(pEntity, HUD_PRINTNOTIFY, "invalid bot_grenade_time value!\n"); + else + bot_grenade_time = temp; + } + + sprintf(msg, "bot_grenade_time is %d\n", bot_grenade_time); + ClientPrint(pEntity, HUD_PRINTNOTIFY, msg); + + RETURN_META (MRES_SUPERCEDE); + } + else if (FStrEq(pcmd, "bot_logo_percent")) + { + if ((arg1 != NULL) && (*arg1 != 0)) + { + int temp = atoi(arg1); + + if ((temp < 0) || (temp > 100)) + ClientPrint(pEntity, HUD_PRINTNOTIFY, "invalid bot_logo_percent value!\n"); + else + bot_logo_percent = temp; + } + + sprintf(msg, "bot_logo_percent is %d\n", bot_logo_percent); + ClientPrint(pEntity, HUD_PRINTNOTIFY, msg); + + RETURN_META (MRES_SUPERCEDE); + } + else if (FStrEq(pcmd, "bot_reaction_time")) + { + if ((arg1 != NULL) && (*arg1 != 0)) + { + int temp = atoi(arg1); + + if ((temp < 0) || (temp > 3)) + ClientPrint(pEntity, HUD_PRINTNOTIFY, "invalid bot_reaction_time value!\n"); + else + bot_reaction_time = temp; + } + + if (bot_reaction_time) + sprintf(msg, "bot_reaction_time is %d\n", bot_reaction_time); + else + sprintf(msg, "bot_reaction_time is DISABLED\n"); + + ClientPrint(pEntity, HUD_PRINTNOTIFY, msg); + + RETURN_META (MRES_SUPERCEDE); + } + else if (FStrEq(pcmd, "random_color")) + { + if ((arg1 != NULL) && (*arg1 != 0)) + { + int temp = atoi(arg1); + + if (temp) + b_random_color = TRUE; + else + b_random_color = FALSE; + } + + if (b_random_color) + ClientPrint(pEntity, HUD_PRINTNOTIFY, "random_color ENABLED\n"); + else + ClientPrint(pEntity, HUD_PRINTNOTIFY, "random_color DISABLED\n"); + + RETURN_META (MRES_SUPERCEDE); + } + else if (FStrEq(pcmd, "botdontshoot")) + { + if ((arg1 != NULL) && (*arg1 != 0)) + { + int temp = atoi(arg1); + if (temp) + b_botdontshoot = TRUE; + else + b_botdontshoot = FALSE; + } + + if (b_botdontshoot) + ClientPrint(pEntity, HUD_PRINTNOTIFY, "botdontshoot ENABLED\n"); + else + ClientPrint(pEntity, HUD_PRINTNOTIFY, "botdontshoot DISABLED\n"); + + RETURN_META (MRES_SUPERCEDE); + } + else if (FStrEq(pcmd, "waypoint")) + { + if (FStrEq(arg1, "on")) + { + g_waypoint_on = TRUE; + + ClientPrint(pEntity, HUD_PRINTNOTIFY, "waypoints are ON\n"); + } + else if (FStrEq(arg1, "off")) + { + g_waypoint_on = FALSE; + + ClientPrint(pEntity, HUD_PRINTNOTIFY, "waypoints are OFF\n"); + } + else if (FStrEq(arg1, "add")) + { + if (!g_waypoint_on) + g_waypoint_on = TRUE; // turn waypoints on if off + + WaypointAdd(pEntity); + } + else if (FStrEq(arg1, "delete")) + { + if (!g_waypoint_on) + g_waypoint_on = TRUE; // turn waypoints on if off + + WaypointDelete(pEntity); + } + else if (FStrEq(arg1, "save")) + { + WaypointSave(); + + ClientPrint(pEntity, HUD_PRINTNOTIFY, "waypoints saved\n"); + } + else if (FStrEq(arg1, "load")) + { + if (WaypointLoad(pEntity)) + ClientPrint(pEntity, HUD_PRINTNOTIFY, "waypoints loaded\n"); + } + else if (FStrEq(arg1, "menu")) + { + int index; + + if (num_waypoints < 1) + RETURN_META (MRES_SUPERCEDE); + + index = WaypointFindNearest(pEntity, 50.0, -1); + + if (index == -1) + RETURN_META (MRES_SUPERCEDE); + + g_menu_waypoint = index; + g_menu_state = MENU_1; + + UTIL_ShowMenu(pEntity, 0x1F, -1, FALSE, show_menu_1); + } + else if (FStrEq(arg1, "info")) + { + WaypointPrintInfo(pEntity); + } + else if (FStrEq(arg1, "update")) + { + ClientPrint(pEntity, HUD_PRINTNOTIFY, "updating waypoint tags...\n"); + + WaypointUpdate(pEntity); + + ClientPrint(pEntity, HUD_PRINTNOTIFY, "...update done! (don't forget to save!)\n"); + } + else + { + if (g_waypoint_on) + ClientPrint(pEntity, HUD_PRINTNOTIFY, "waypoints are ON\n"); + else + ClientPrint(pEntity, HUD_PRINTNOTIFY, "waypoints are OFF\n"); + } + + RETURN_META (MRES_SUPERCEDE); + } + else if (FStrEq(pcmd, "autowaypoint")) + { + if (FStrEq(arg1, "on")) + { + g_auto_waypoint = TRUE; + g_waypoint_on = TRUE; // turn this on just in case + } + else if (FStrEq(arg1, "off")) + { + g_auto_waypoint = FALSE; + } + + if (g_auto_waypoint) + sprintf(msg, "autowaypoint is ON\n"); + else + sprintf(msg, "autowaypoint is OFF\n"); + + ClientPrint(pEntity, HUD_PRINTNOTIFY, msg); + + RETURN_META (MRES_SUPERCEDE); + } + else if (FStrEq(pcmd, "pathwaypoint")) + { + if (FStrEq(arg1, "on")) + { + g_path_waypoint = TRUE; + g_waypoint_on = TRUE; // turn this on just in case + + ClientPrint(pEntity, HUD_PRINTNOTIFY, "pathwaypoint is ON\n"); + } + else if (FStrEq(arg1, "off")) + { + g_path_waypoint = FALSE; + + ClientPrint(pEntity, HUD_PRINTNOTIFY, "pathwaypoint is OFF\n"); + } + else if (FStrEq(arg1, "enable")) + { + g_path_waypoint_enable = TRUE; + + ClientPrint(pEntity, HUD_PRINTNOTIFY, "pathwaypoint is ENABLED\n"); + } + else if (FStrEq(arg1, "disable")) + { + g_path_waypoint_enable = FALSE; + + ClientPrint(pEntity, HUD_PRINTNOTIFY, "pathwaypoint is DISABLED\n"); + } + else if (FStrEq(arg1, "create1")) + { + WaypointCreatePath(pEntity, 1); + } + else if (FStrEq(arg1, "create2")) + { + WaypointCreatePath(pEntity, 2); + } + else if (FStrEq(arg1, "remove1")) + { + WaypointRemovePath(pEntity, 1); + } + else if (FStrEq(arg1, "remove2")) + { + WaypointRemovePath(pEntity, 2); + } + + RETURN_META (MRES_SUPERCEDE); + } + else if (FStrEq(pcmd, "menuselect") && (g_menu_state != MENU_NONE)) + { + if (g_menu_state == MENU_1) // main menu... + { + if (FStrEq(arg1, "1")) // team specific... + { + g_menu_state = MENU_2; // display team specific menu... + + if (mod_id == FRONTLINE_DLL) + UTIL_ShowMenu(pEntity, 0x13, -1, FALSE, show_menu_2_flf); + else + UTIL_ShowMenu(pEntity, 0x1F, -1, FALSE, show_menu_2); + + RETURN_META (MRES_SUPERCEDE); + } + else if (FStrEq(arg1, "2")) // wait for lift... + { + if (waypoints[g_menu_waypoint].flags & W_FL_LIFT) + waypoints[g_menu_waypoint].flags &= ~W_FL_LIFT; // off + else + waypoints[g_menu_waypoint].flags |= W_FL_LIFT; // on + } + else if (FStrEq(arg1, "3")) // door waypoint + { + if (waypoints[g_menu_waypoint].flags & W_FL_DOOR) + waypoints[g_menu_waypoint].flags &= ~W_FL_DOOR; // off + else + waypoints[g_menu_waypoint].flags |= W_FL_DOOR; // on + } + else if (FStrEq(arg1, "4")) // sniper spot + { + if (waypoints[g_menu_waypoint].flags & W_FL_SNIPER) + waypoints[g_menu_waypoint].flags &= ~W_FL_SNIPER; // off + else + { + waypoints[g_menu_waypoint].flags |= W_FL_SNIPER; // on + + // set the aiming waypoint... + + WaypointAddAiming(pEntity); + } + } + else if (FStrEq(arg1, "5")) // more... + { + g_menu_state = MENU_3; + + if (mod_id == FRONTLINE_DLL) + UTIL_ShowMenu(pEntity, 0x17, -1, FALSE, show_menu_3_flf); + else if (mod_id == HOLYWARS_DLL) + UTIL_ShowMenu(pEntity, 0x11, -1, FALSE, show_menu_3_hw); + else + UTIL_ShowMenu(pEntity, 0x1F, -1, FALSE, show_menu_3); + + RETURN_META (MRES_SUPERCEDE); + } + } + else if (g_menu_state == MENU_2) // team specific menu + { + if (waypoints[g_menu_waypoint].flags & W_FL_TEAM_SPECIFIC) + { + waypoints[g_menu_waypoint].flags &= ~W_FL_TEAM; + waypoints[g_menu_waypoint].flags &= ~W_FL_TEAM_SPECIFIC; // off + } + else + { + int team = atoi(arg1); + + team--; // make 0 to 3 + + // this is kind of a kludge (team bits MUST be LSB!!!) + waypoints[g_menu_waypoint].flags |= team; + waypoints[g_menu_waypoint].flags |= W_FL_TEAM_SPECIFIC; // on + } + } + else if (g_menu_state == MENU_3) // third menu... + { + if (mod_id == FRONTLINE_DLL) + { + if (FStrEq(arg1, "1")) // capture point + { + if (waypoints[g_menu_waypoint].flags & W_FL_FLF_CAP) + waypoints[g_menu_waypoint].flags &= ~W_FL_FLF_CAP; // off + else + waypoints[g_menu_waypoint].flags |= W_FL_FLF_CAP; // on + } + else if (FStrEq(arg1, "2")) // defend point + { + if (waypoints[g_menu_waypoint].flags & W_FL_FLF_DEFEND) + waypoints[g_menu_waypoint].flags &= ~W_FL_FLF_DEFEND; // off + else + { + waypoints[g_menu_waypoint].flags |= W_FL_FLF_DEFEND; // on + + // set the aiming waypoint... + + WaypointAddAiming(pEntity); + } + } + else if (FStrEq(arg1, "3")) // go prone + { + if (waypoints[g_menu_waypoint].flags & W_FL_PRONE) + waypoints[g_menu_waypoint].flags &= ~W_FL_PRONE; // off + else + waypoints[g_menu_waypoint].flags |= W_FL_PRONE; // on + } + } + else if (mod_id == HOLYWARS_DLL) + { + if (FStrEq(arg1, "1")) // flag location + { + if (waypoints[g_menu_waypoint].flags & W_FL_FLAG) + waypoints[g_menu_waypoint].flags &= ~W_FL_FLAG; // off + else + waypoints[g_menu_waypoint].flags |= W_FL_FLAG; // on + } + else if (FStrEq(arg1, "5")) + { + g_menu_state = MENU_4; + + UTIL_ShowMenu(pEntity, 0x1F, -1, FALSE, show_menu_4); + + RETURN_META (MRES_SUPERCEDE); + } + } + else + { + if (FStrEq(arg1, "1")) // flag location + { + if (waypoints[g_menu_waypoint].flags & W_FL_FLAG) + waypoints[g_menu_waypoint].flags &= ~W_FL_FLAG; // off + else + waypoints[g_menu_waypoint].flags |= W_FL_FLAG; // on + } + else if (FStrEq(arg1, "2")) // flag goal + { + if (waypoints[g_menu_waypoint].flags & W_FL_FLAG_GOAL) + waypoints[g_menu_waypoint].flags &= ~W_FL_FLAG_GOAL; // off + else + waypoints[g_menu_waypoint].flags |= W_FL_FLAG_GOAL; // on + } + else if (FStrEq(arg1, "3")) // sentry gun + { + if (waypoints[g_menu_waypoint].flags & W_FL_SENTRYGUN) + waypoints[g_menu_waypoint].flags &= ~W_FL_SENTRYGUN; // off + else + { + waypoints[g_menu_waypoint].flags |= W_FL_SENTRYGUN; // on + + // set the aiming waypoint... + + WaypointAddAiming(pEntity); + } + } + else if (FStrEq(arg1, "4")) // dispenser + { + if (waypoints[g_menu_waypoint].flags & W_FL_DISPENSER) + waypoints[g_menu_waypoint].flags &= ~W_FL_DISPENSER; // off + else + { + waypoints[g_menu_waypoint].flags |= W_FL_DISPENSER; // on + + // set the aiming waypoint... + + WaypointAddAiming(pEntity); + } + } + else if (FStrEq(arg1, "5")) + { + g_menu_state = MENU_4; + + UTIL_ShowMenu(pEntity, 0x1F, -1, FALSE, show_menu_4); + + RETURN_META (MRES_SUPERCEDE); + } + } + } + else if (g_menu_state == MENU_4) // fourth menu... + { + if (FStrEq(arg1, "1")) // health + { + if (waypoints[g_menu_waypoint].flags & W_FL_HEALTH) + waypoints[g_menu_waypoint].flags &= ~W_FL_HEALTH; // off + else + waypoints[g_menu_waypoint].flags |= W_FL_HEALTH; // on + } + else if (FStrEq(arg1, "2")) // armor + { + if (waypoints[g_menu_waypoint].flags & W_FL_ARMOR) + waypoints[g_menu_waypoint].flags &= ~W_FL_ARMOR; // off + else + waypoints[g_menu_waypoint].flags |= W_FL_ARMOR; // on + } + else if (FStrEq(arg1, "3")) // ammo + { + if (waypoints[g_menu_waypoint].flags & W_FL_AMMO) + waypoints[g_menu_waypoint].flags &= ~W_FL_AMMO; // off + else + waypoints[g_menu_waypoint].flags |= W_FL_AMMO; // on + } + else if (FStrEq(arg1, "4")) // jump + { + if (waypoints[g_menu_waypoint].flags & W_FL_JUMP) + waypoints[g_menu_waypoint].flags &= ~W_FL_JUMP; // off + else + { + waypoints[g_menu_waypoint].flags |= W_FL_JUMP; // on + + // set the aiming waypoint... + + WaypointAddAiming(pEntity); + } + } + } + + g_menu_state = MENU_NONE; + + // turn off the text menu if the mod doesn't do this automatically + if ((mod_id == HOLYWARS_DLL) || (mod_id == DMC_DLL)) + UTIL_ShowMenu(pEntity, 0x0, -1, FALSE, show_menu_none); + + RETURN_META (MRES_SUPERCEDE); + } + else if (FStrEq(pcmd, "search")) + { + edict_t *pent = NULL; + float radius = 100; + char str[80]; + + ClientPrint(pEntity, HUD_PRINTNOTIFY, "searching...\n"); + + while ((pent = UTIL_FindEntityInSphere( pent, pEntity->v.origin, radius )) != NULL) + { + sprintf(str, "Found %s at %5.2f %5.2f %5.2f\n", + STRING(pent->v.classname), + pent->v.origin.x, pent->v.origin.y, + pent->v.origin.z); + ClientPrint(pEntity, HUD_PRINTNOTIFY, str); + + FILE *fp=fopen("HPB_bot.txt", "a"); + fprintf(fp, "ClientCommmand: search %s", str); + fclose(fp); + } + + RETURN_META (MRES_SUPERCEDE); + } + else if (FStrEq(pcmd, "jumppad")) + { + char str[80]; + + if (FStrEq(arg1, "off")) + jumppad_off = 1; + if (FStrEq(arg1, "on")) + jumppad_off = 0; + + if (jumppad_off) + sprintf(str,"jumppad is OFF\n"); + else + sprintf(str,"jumppad is ON\n"); + + ClientPrint(pEntity, HUD_PRINTNOTIFY, str); + + RETURN_META (MRES_SUPERCEDE); + } +#if _DEBUG + else if (FStrEq(pcmd, "botstop")) + { + bot_stop = 1; + + RETURN_META (MRES_SUPERCEDE); + } + else if (FStrEq(pcmd, "botstart")) + { + bot_stop = 0; + + RETURN_META (MRES_SUPERCEDE); + } +#endif + } + + RETURN_META (MRES_IGNORED); +} + +void StartFrame( void ) +{ + if (gpGlobals->deathmatch) + { + edict_t *pPlayer; + static int i, index, player_index, bot_index; + static float previous_time = -1.0; + char msg[256]; + int count; + + // if a new map has started then (MUST BE FIRST IN StartFrame)... + if ((gpGlobals->time + 0.1) < previous_time) + { + char filename[256]; + char mapname[64]; + + // check if mapname_bot.cfg file exists... + + strcpy(mapname, STRING(gpGlobals->mapname)); + strcat(mapname, "_HPB_bot.cfg"); + + UTIL_BuildFileName(filename, "maps", mapname); + + if ((bot_cfg_fp = fopen(filename, "r")) != NULL) + { + sprintf(msg, "Executing %s\n", filename); + ALERT( at_console, msg ); + + for (index = 0; index < 32; index++) + { + bots[index].is_used = FALSE; + bots[index].respawn_state = 0; + bots[index].f_kick_time = 0.0; + } + + if (IsDedicatedServer) + bot_cfg_pause_time = gpGlobals->time + 5.0; + else + bot_cfg_pause_time = gpGlobals->time + 20.0; + } + else + { + count = 0; + + // mark the bots as needing to be respawned... + for (index = 0; index < 32; index++) + { + if (count >= prev_num_bots) + { + bots[index].is_used = FALSE; + bots[index].respawn_state = 0; + bots[index].f_kick_time = 0.0; + } + + if (bots[index].is_used) // is this slot used? + { + bots[index].respawn_state = RESPAWN_NEED_TO_RESPAWN; + count++; + } + + // check for any bots that were very recently kicked... + if ((bots[index].f_kick_time + 5.0) > previous_time) + { + bots[index].respawn_state = RESPAWN_NEED_TO_RESPAWN; + count++; + } + else + bots[index].f_kick_time = 0.0; // reset to prevent false spawns later + } + + // set the respawn time + if (IsDedicatedServer) + respawn_time = gpGlobals->time + 5.0; + else + respawn_time = gpGlobals->time + 20.0; + } + + bot_check_time = gpGlobals->time + 60.0; + } + + if (!IsDedicatedServer) + { + if ((listenserver_edict != NULL) && (welcome_sent == FALSE) && + (welcome_time < 1.0)) + { + // are they out of observer mode yet? + if (IsAlive(listenserver_edict)) + welcome_time = gpGlobals->time + 5.0; // welcome in 5 seconds + } + + if ((welcome_time > 0.0) && (welcome_time < gpGlobals->time) && + (welcome_sent == FALSE)) + { + char version[80]; + + sprintf(version,"%s Version %d.%d\n", welcome_msg, VER_MAJOR, VER_MINOR); + + // let's send a welcome message to this client... + UTIL_SayText(version, listenserver_edict); + + welcome_sent = TRUE; // clear this so we only do it once + } + } + + count = 0; + + if (bot_stop == 0) + { + for (bot_index = 0; bot_index < gpGlobals->maxClients; bot_index++) + { + if ((bots[bot_index].is_used) && // is this slot used AND + (bots[bot_index].respawn_state == RESPAWN_IDLE)) // not respawning + { + BotThink(&bots[bot_index]); + + count++; + } + } + } + + if (count > num_bots) + num_bots = count; + + for (player_index = 1; player_index <= gpGlobals->maxClients; player_index++) + { + pPlayer = INDEXENT(player_index); + + if (pPlayer && !pPlayer->free) + { + if ((g_waypoint_on) && + FBitSet(pPlayer->v.flags, FL_CLIENT) && !FBitSet(pPlayer->v.flags, FL_FAKECLIENT)) + { + WaypointThink(pPlayer); + } + } + } + + // are we currently respawning bots and is it time to spawn one yet? + if ((respawn_time > 1.0) && (respawn_time <= gpGlobals->time)) + { + int index = 0; + + // find bot needing to be respawned... + while ((index < 32) && + (bots[index].respawn_state != RESPAWN_NEED_TO_RESPAWN)) + index++; + + if (index < 32) + { + int strafe = bot_strafe_percent; // save global strafe percent + int chat = bot_chat_percent; // save global chat percent + int taunt = bot_taunt_percent; // save global taunt percent + int whine = bot_whine_percent; // save global whine percent + int grenade = bot_grenade_time; // save global grenade time + int logo = bot_logo_percent; // save global logo percent + int tag = bot_chat_tag_percent; // save global clan tag percent + int drop = bot_chat_drop_percent; // save global chat drop percent + int swap = bot_chat_swap_percent; // save global chat swap percent + int lower = bot_chat_lower_percent; // save global chat lower percent + int react = bot_reaction_time; + + bots[index].respawn_state = RESPAWN_IS_RESPAWNING; + bots[index].is_used = FALSE; // free up this slot + + bot_strafe_percent = bots[index].strafe_percent; + bot_chat_percent = bots[index].chat_percent; + bot_taunt_percent = bots[index].taunt_percent; + bot_whine_percent = bots[index].whine_percent; + bot_grenade_time = bots[index].grenade_time; + bot_logo_percent = bots[index].logo_percent; + bot_chat_tag_percent = bots[index].chat_tag_percent; + bot_chat_drop_percent = bots[index].chat_drop_percent; + bot_chat_swap_percent = bots[index].chat_swap_percent; + bot_chat_lower_percent = bots[index].chat_lower_percent; + bot_reaction_time = bots[index].reaction_time; + + // respawn 1 bot then wait a while (otherwise engine crashes) + if ((mod_id == VALVE_DLL) || + ((mod_id == GEARBOX_DLL) && (pent_info_ctfdetect == NULL)) || + (mod_id == HOLYWARS_DLL) || (mod_id == DMC_DLL)) + { + char c_skill[2]; + char c_topcolor[4]; + char c_bottomcolor[4]; + + sprintf(c_skill, "%d", bots[index].bot_skill); + sprintf(c_topcolor, "%d", bots[index].top_color); + sprintf(c_bottomcolor, "%d", bots[index].bottom_color); + + BotCreate(NULL, bots[index].skin, bots[index].name, c_skill, c_topcolor, c_bottomcolor); + } + else + { + char c_skill[2]; + char c_team[2]; + char c_class[3]; + + sprintf(c_skill, "%d", bots[index].bot_skill); + sprintf(c_team, "%d", bots[index].bot_team); + sprintf(c_class, "%d", bots[index].bot_class); + + if ((mod_id == TFC_DLL) || (mod_id == GEARBOX_DLL)) + BotCreate(NULL, NULL, NULL, bots[index].name, c_skill, NULL); + else + BotCreate(NULL, c_team, c_class, bots[index].name, c_skill, NULL); + } + + bot_strafe_percent = strafe; // restore global strafe percent + bot_chat_percent = chat; // restore global chat percent + bot_taunt_percent = taunt; // restore global taunt percent + bot_whine_percent = whine; // restore global whine percent + bot_grenade_time = grenade; // restore global grenade time + bot_logo_percent = logo; // restore global logo percent + bot_chat_tag_percent = tag; // restore global chat percent + bot_chat_drop_percent = drop; // restore global chat percent + bot_chat_swap_percent = swap; // restore global chat percent + bot_chat_lower_percent = lower; // restore global chat percent + bot_reaction_time = react; + + respawn_time = gpGlobals->time + 2.0; // set next respawn time + + bot_check_time = gpGlobals->time + 5.0; + } + else + { + respawn_time = 0.0; + } + } + + if (g_GameRules) + { + if (need_to_open_cfg) // have we open HPB_bot.cfg file yet? + { + char filename[256]; + char mapname[64]; + + need_to_open_cfg = FALSE; // only do this once!!! + + // check if mapname_HPB_bot.cfg file exists... + + strcpy(mapname, STRING(gpGlobals->mapname)); + strcat(mapname, "_HPB_bot.cfg"); + + UTIL_BuildFileName(filename, "maps", mapname); + + if ((bot_cfg_fp = fopen(filename, "r")) != NULL) + { + sprintf(msg, "Executing %s\n", filename); + ALERT( at_console, msg ); + } + else + { + UTIL_BuildFileName(filename, "HPB_bot.cfg", NULL); + + sprintf(msg, "Executing %s\n", filename); + ALERT( at_console, msg ); + + bot_cfg_fp = fopen(filename, "r"); + + if (bot_cfg_fp == NULL) + ALERT( at_console, "HPB_bot.cfg file not found\n" ); + } + + if (IsDedicatedServer) + bot_cfg_pause_time = gpGlobals->time + 5.0; + else + bot_cfg_pause_time = gpGlobals->time + 20.0; + } + + if (!IsDedicatedServer && !spawn_time_reset) + { + if (listenserver_edict != NULL) + { + if (IsAlive(listenserver_edict)) + { + spawn_time_reset = TRUE; + + if (respawn_time >= 1.0) + respawn_time = min(respawn_time, gpGlobals->time + (float)1.0); + + if (bot_cfg_pause_time >= 1.0) + bot_cfg_pause_time = min(bot_cfg_pause_time, gpGlobals->time + (float)1.0); + } + } + } + + if ((bot_cfg_fp) && + (bot_cfg_pause_time >= 1.0) && (bot_cfg_pause_time <= gpGlobals->time)) + { + // process HPB_bot.cfg file options... + ProcessBotCfgFile(); + } + + } + + // check if time to see if a bot needs to be created... + if (bot_check_time < gpGlobals->time) + { + int count = 0; + + bot_check_time = gpGlobals->time + 5.0; + + for (i = 0; i < 32; i++) + { + if (clients[i] != NULL) + count++; + } + + // if there are currently less than the maximum number of "players" + // then add another bot using the default skill level... + if ((count < max_bots) && (max_bots != -1)) + { + BotCreate( NULL, NULL, NULL, NULL, NULL, NULL ); + } + } + + previous_time = gpGlobals->time; + } + + RETURN_META (MRES_IGNORED); +} + +void FakeClientCommand(edict_t *pBot, char *arg1, char *arg2, char *arg3) +{ + int length; + + memset(g_argv, 0, 1024); + + isFakeClientCommand = TRUE; + + if ((arg1 == NULL) || (*arg1 == 0)) + return; + + if ((arg2 == NULL) || (*arg2 == 0)) + { + length = sprintf(&g_argv[0], "%s", arg1); + fake_arg_count = 1; + } + else if ((arg3 == NULL) || (*arg3 == 0)) + { + length = sprintf(&g_argv[0], "%s %s", arg1, arg2); + fake_arg_count = 2; + } + else + { + length = sprintf(&g_argv[0], "%s %s %s", arg1, arg2, arg3); + fake_arg_count = 3; + } + + g_argv[length] = 0; // null terminate just in case + + strcpy(&g_argv[64], arg1); + + if (arg2) + strcpy(&g_argv[128], arg2); + + if (arg3) + strcpy(&g_argv[192], arg3); + + // allow the MOD DLL to execute the ClientCommand... + MDLL_ClientCommand(pBot); + + isFakeClientCommand = FALSE; +} + + +void ProcessBotCfgFile(void) +{ + int ch; + char cmd_line[256]; + int cmd_index; + static char server_cmd[80]; + char *cmd, *arg1, *arg2, *arg3, *arg4, *arg5; + char msg[80]; + + if (bot_cfg_pause_time > gpGlobals->time) + return; + + if (bot_cfg_fp == NULL) + return; + + cmd_index = 0; + cmd_line[cmd_index] = 0; + + ch = fgetc(bot_cfg_fp); + + // skip any leading blanks + while (ch == ' ') + ch = fgetc(bot_cfg_fp); + + while ((ch != EOF) && (ch != '\r') && (ch != '\n')) + { + if (ch == '\t') // convert tabs to spaces + ch = ' '; + + cmd_line[cmd_index] = ch; + + ch = fgetc(bot_cfg_fp); + + // skip multiple spaces in input file + while ((cmd_line[cmd_index] == ' ') && + (ch == ' ')) + ch = fgetc(bot_cfg_fp); + + cmd_index++; + } + + if (ch == '\r') // is it a carriage return? + { + ch = fgetc(bot_cfg_fp); // skip the linefeed + } + + // if reached end of file, then close it + if (ch == EOF) + { + fclose(bot_cfg_fp); + + bot_cfg_fp = NULL; + + bot_cfg_pause_time = 0.0; + } + + cmd_line[cmd_index] = 0; // terminate the command line + + // copy the command line to a server command buffer... + strcpy(server_cmd, cmd_line); + strcat(server_cmd, "\n"); + + cmd_index = 0; + cmd = cmd_line; + arg1 = arg2 = arg3 = arg4 = arg5 = NULL; + + // skip to blank or end of string... + while ((cmd_line[cmd_index] != ' ') && (cmd_line[cmd_index] != 0)) + cmd_index++; + + if (cmd_line[cmd_index] == ' ') + { + cmd_line[cmd_index++] = 0; + arg1 = &cmd_line[cmd_index]; + + // skip to blank or end of string... + while ((cmd_line[cmd_index] != ' ') && (cmd_line[cmd_index] != 0)) + cmd_index++; + + if (cmd_line[cmd_index] == ' ') + { + cmd_line[cmd_index++] = 0; + arg2 = &cmd_line[cmd_index]; + + // skip to blank or end of string... + while ((cmd_line[cmd_index] != ' ') && (cmd_line[cmd_index] != 0)) + cmd_index++; + + if (cmd_line[cmd_index] == ' ') + { + cmd_line[cmd_index++] = 0; + arg3 = &cmd_line[cmd_index]; + + // skip to blank or end of string... + while ((cmd_line[cmd_index] != ' ') && (cmd_line[cmd_index] != 0)) + cmd_index++; + + if (cmd_line[cmd_index] == ' ') + { + cmd_line[cmd_index++] = 0; + arg4 = &cmd_line[cmd_index]; + + // skip to blank or end of string... + while ((cmd_line[cmd_index] != ' ') && (cmd_line[cmd_index] != 0)) + cmd_index++; + + if (cmd_line[cmd_index] == ' ') + { + cmd_line[cmd_index++] = 0; + arg5 = &cmd_line[cmd_index]; + } + } + } + } + } + + if ((cmd_line[0] == '#') || (cmd_line[0] == 0)) + return; // return if comment or blank line + + if (strcmp(cmd, "addbot") == 0) + { + BotCreate( NULL, arg1, arg2, arg3, arg4, arg5 ); + + // have to delay here or engine gives "Tried to write to + // uninitialized sizebuf_t" error and crashes... + + bot_cfg_pause_time = gpGlobals->time + 2.0; + bot_check_time = gpGlobals->time + 5.0; + + return; + } + + if (strcmp(cmd, "botskill") == 0) + { + int temp = atoi(arg1); + + if ((temp >= 1) && (temp <= 5)) + default_bot_skill = atoi( arg1 ); // set default bot skill level + + return; + } + + if (strcmp(cmd, "random_color") == 0) + { + int temp = atoi(arg1); + + if (temp) + b_random_color = TRUE; + else + b_random_color = FALSE; + + return; + } + + if (strcmp(cmd, "bot_strafe_percent") == 0) + { + int temp = atoi(arg1); + + if ((temp >= 0) && (temp <= 100)) + bot_strafe_percent = atoi( arg1 ); // set bot strafe percent + + return; + } + + if (strcmp(cmd, "bot_chat_percent") == 0) + { + int temp = atoi(arg1); + + if ((temp >= 0) && (temp <= 100)) + bot_chat_percent = atoi( arg1 ); // set bot chat percent + + return; + } + + if (strcmp(cmd, "bot_taunt_percent") == 0) + { + int temp = atoi(arg1); + + if ((temp >= 0) && (temp <= 100)) + bot_taunt_percent = atoi( arg1 ); // set bot taunt percent + + return; + } + + if (strcmp(cmd, "bot_whine_percent") == 0) + { + int temp = atoi(arg1); + + if ((temp >= 0) && (temp <= 100)) + bot_whine_percent = atoi( arg1 ); // set bot whine percent + + return; + } + + if (strcmp(cmd, "bot_chat_tag_percent") == 0) + { + int temp = atoi(arg1); + + if ((temp >= 0) && (temp <= 100)) + bot_chat_tag_percent = atoi( arg1 ); // set bot chat percent + + return; + } + + if (strcmp(cmd, "bot_chat_drop_percent") == 0) + { + int temp = atoi(arg1); + + if ((temp >= 0) && (temp <= 100)) + bot_chat_drop_percent = atoi( arg1 ); // set bot chat percent + + return; + } + + if (strcmp(cmd, "bot_chat_swap_percent") == 0) + { + int temp = atoi(arg1); + + if ((temp >= 0) && (temp <= 100)) + bot_chat_swap_percent = atoi( arg1 ); // set bot chat percent + + return; + } + + if (strcmp(cmd, "bot_chat_lower_percent") == 0) + { + int temp = atoi(arg1); + + if ((temp >= 0) && (temp <= 100)) + bot_chat_lower_percent = atoi( arg1 ); // set bot chat percent + + return; + } + + if (strcmp(cmd, "bot_grenade_time") == 0) + { + int temp = atoi(arg1); + + if ((temp >= 0) && (temp <= 60)) + bot_grenade_time = atoi( arg1 ); // set bot grenade time + + return; + } + + if (strcmp(cmd, "bot_logo_percent") == 0) + { + int temp = atoi(arg1); + + if ((temp >= 0) && (temp <= 100)) + bot_logo_percent = atoi( arg1 ); // set bot strafe percent + + return; + } + + if (strcmp(cmd, "bot_reaction_time") == 0) + { + int temp = atoi(arg1); + + if ((temp >= 0) && (temp <= 3)) + bot_reaction_time = atoi( arg1 ); // set bot reaction time + + return; + } + + if (strcmp(cmd, "observer") == 0) + { + int temp = atoi(arg1); + + if (temp) + b_observer_mode = TRUE; + else + b_observer_mode = FALSE; + + return; + } + + if (strcmp(cmd, "botdontshoot") == 0) + { + int temp = atoi(arg1); + + if (temp) + b_botdontshoot = TRUE; + else + b_botdontshoot = FALSE; + + return; + } + + if (strcmp(cmd, "min_bots") == 0) + { + min_bots = atoi( arg1 ); + + if ((min_bots < 0) || (min_bots > 31)) + min_bots = 1; + + if (IsDedicatedServer) + { + sprintf(msg, "min_bots set to %d\n", min_bots); + printf(msg); + } + + return; + } + + if (strcmp(cmd, "max_bots") == 0) + { + max_bots = atoi( arg1 ); + + if ((max_bots < 0) || (max_bots > 31)) + max_bots = 1; + + if (IsDedicatedServer) + { + sprintf(msg, "max_bots set to %d\n", max_bots); + printf(msg); + } + + return; + } + + if (strcmp(cmd, "pause") == 0) + { + bot_cfg_pause_time = gpGlobals->time + atoi( arg1 ); + + return; + } + + sprintf(msg, "executing server command: %s\n", server_cmd); + ALERT( at_console, msg ); + + if (IsDedicatedServer) + printf(msg); + + SERVER_COMMAND(server_cmd); +} + + +void HPB_Bot_ServerCommand (void) +{ + char msg[128]; + + if (strcmp(CMD_ARGV (1), "addbot") == 0) + { + BotCreate( NULL, CMD_ARGV (2), CMD_ARGV (3), CMD_ARGV (4), CMD_ARGV (5), CMD_ARGV (6) ); + + bot_check_time = gpGlobals->time + 5.0; + } + else if (strcmp(CMD_ARGV (1), "min_bots") == 0) + { + min_bots = atoi( CMD_ARGV (2) ); + + if ((min_bots < 0) || (min_bots > 31)) + min_bots = 1; + + sprintf(msg, "min_bots set to %d\n", min_bots); + SERVER_PRINT(msg); + } + else if (strcmp(CMD_ARGV (1), "max_bots") == 0) + { + max_bots = atoi( CMD_ARGV (2) ); + + if ((max_bots < 0) || (max_bots > 31)) + max_bots = 1; + + sprintf(msg, "max_bots set to %d\n", max_bots); + SERVER_PRINT(msg); + } +} + + +C_DLLEXPORT int GetEntityAPI2 (DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion) +{ + gFunctionTable.pfnGameInit = GameDLLInit; + gFunctionTable.pfnSpawn = Spawn; + gFunctionTable.pfnKeyValue = KeyValue; + gFunctionTable.pfnClientConnect = ClientConnect; + gFunctionTable.pfnClientDisconnect = ClientDisconnect; + gFunctionTable.pfnClientPutInServer = ClientPutInServer; + gFunctionTable.pfnClientCommand = ClientCommand; + gFunctionTable.pfnStartFrame = StartFrame; + + memcpy (pFunctionTable, &gFunctionTable, sizeof (DLL_FUNCTIONS)); + return (TRUE); +} diff --git a/engine.cpp b/engine.cpp new file mode 100644 index 0000000..b951ab6 --- /dev/null +++ b/engine.cpp @@ -0,0 +1,692 @@ +// +// HPB bot - botman's High Ping Bastard bot +// +// (http://planethalflife.com/botman/) +// +// engine.cpp +// + +#ifndef _WIN32 +#include +#endif + +#include +#include +#include +#include + +#include "bot.h" +#include "bot_client.h" +#include "bot_func.h" + + +extern enginefuncs_t g_engfuncs; +extern bot_t bots[32]; +extern int mod_id; +extern int team_allies[4]; +extern char g_argv[1024]; +extern bool isFakeClientCommand; +extern int fake_arg_count; + + +void (*botMsgFunction)(void *, int) = NULL; +void (*botMsgEndFunction)(void *, int) = NULL; +int botMsgIndex; + +static FILE *fp; + + +void pfnChangeLevel(char* s1, char* s2) +{ + // kick any bot off of the server after time/frag limit... + for (int index = 0; index < 32; index++) + { + if (bots[index].is_used) // is this slot used? + { + char cmd[40]; + + sprintf(cmd, "kick \"%s\"\n", bots[index].name); + + bots[index].respawn_state = RESPAWN_NEED_TO_RESPAWN; + + SERVER_COMMAND(cmd); // kick the bot using (kick "name") + } + } + + RETURN_META (MRES_IGNORED); +} + + +edict_t* pfnFindEntityByString(edict_t *pEdictStartSearchAfter, const char *pszField, const char *pszValue) +{ + if (gpGlobals->deathmatch) + { + int bot_index; + bot_t *pBot; + + if (mod_id == CSTRIKE_DLL) + { + // new round in CS 1.5 + if (strcmp ("info_map_parameters", pszValue) == 0) + { + for (bot_index = 0; bot_index < 32; bot_index++) + { + pBot = &bots[bot_index]; + + if (pBot->is_used) + BotSpawnInit (pBot); // reset bots for new round + } + } + } + } + + RETURN_META_VALUE (MRES_IGNORED, NULL); +} + + +void pfnEmitSound(edict_t *entity, int channel, const char *sample, /*int*/float volume, float attenuation, int fFlags, int pitch) +{ + int index; + float distance; + Vector vecEnd; + edict_t *pEdict; + + if (gpGlobals->deathmatch) + { + if (mod_id == TFC_DLL) + { + // is someone yelling for a medic? + if ((strcmp(sample, "speech/saveme1.wav") == 0) || + (strcmp(sample, "speech/saveme2.wav") == 0)) + { + for (index=0; index < 32; index++) + { + if (bots[index].is_used) // is this slot used? + { + pEdict = bots[index].pEdict; + + if ((pEdict->v.playerclass != TFC_CLASS_MEDIC) && + (pEdict->v.playerclass != TFC_CLASS_ENGINEER)) + continue; + + int player_team = UTIL_GetTeam(entity); + int bot_team = UTIL_GetTeam(pEdict); + + // don't heal your enemies... + if ((bot_team != player_team) && + !(team_allies[bot_team] & (1<v.origin - entity->v.origin).Length(); + + vecEnd = entity->v.origin + entity->v.view_ofs; + + if ((distance < 1000) && FVisible(vecEnd, pEdict) && + (bots[index].pBotEnemy == NULL)) + { + bots[index].pBotEnemy = entity; + + bots[index].enemy_attack_count = 3; // hit 'em 3 times + + bots[index].pBotUser = NULL; // don't follow user when enemy found + } + } + } + } + } + } + + RETURN_META (MRES_IGNORED); +} + + +void pfnClientCommand(edict_t* pEdict, char* szFmt, ...) +{ + if ((pEdict->v.flags & FL_FAKECLIENT) || (pEdict->v.flags & FL_THIRDPARTYBOT)) + RETURN_META (MRES_SUPERCEDE); + RETURN_META (MRES_IGNORED); +} + + +void pfnMessageBegin(int msg_dest, int msg_type, const float *pOrigin, edict_t *ed) +{ + if (gpGlobals->deathmatch) + { + int index = -1; + + if (ed) + { + if ((mod_id == HOLYWARS_DLL) && + (msg_type == GET_USER_MSG_ID (PLID, "Halo", NULL))) + { + // catch this message for ALL players, NOT just bots... + botMsgFunction = BotClient_HolyWars_Halo; + + botMsgIndex = (int)ed; // save the edict instead of the bot index + } + else + { + index = UTIL_GetBotIndex(ed); + + // is this message for a bot? + if (index != -1) + { + botMsgFunction = NULL; // no msg function until known otherwise + botMsgEndFunction = NULL; // no msg end function until known otherwise + botMsgIndex = index; // index of bot receiving message + + if (mod_id == VALVE_DLL) + { + if (msg_type == GET_USER_MSG_ID (PLID, "WeaponList", NULL)) + botMsgFunction = BotClient_Valve_WeaponList; + else if (msg_type == GET_USER_MSG_ID (PLID, "CurWeapon", NULL)) + botMsgFunction = BotClient_Valve_CurrentWeapon; + else if (msg_type == GET_USER_MSG_ID (PLID, "AmmoX", NULL)) + botMsgFunction = BotClient_Valve_AmmoX; + else if (msg_type == GET_USER_MSG_ID (PLID, "AmmoPickup", NULL)) + botMsgFunction = BotClient_Valve_AmmoPickup; + else if (msg_type == GET_USER_MSG_ID (PLID, "WeapPickup", NULL)) + botMsgFunction = BotClient_Valve_WeaponPickup; + else if (msg_type == GET_USER_MSG_ID (PLID, "ItemPickup", NULL)) + botMsgFunction = BotClient_Valve_ItemPickup; + else if (msg_type == GET_USER_MSG_ID (PLID, "Health", NULL)) + botMsgFunction = BotClient_Valve_Health; + else if (msg_type == GET_USER_MSG_ID (PLID, "Battery", NULL)) + botMsgFunction = BotClient_Valve_Battery; + else if (msg_type == GET_USER_MSG_ID (PLID, "Damage", NULL)) + botMsgFunction = BotClient_Valve_Damage; + else if (msg_type == GET_USER_MSG_ID (PLID, "ScreenFade", NULL)) + botMsgFunction = BotClient_Valve_ScreenFade; + } + else if (mod_id == TFC_DLL) + { + if (msg_type == GET_USER_MSG_ID (PLID, "VGUIMenu", NULL)) + botMsgFunction = BotClient_TFC_VGUI; + else if (msg_type == GET_USER_MSG_ID (PLID, "WeaponList", NULL)) + botMsgFunction = BotClient_TFC_WeaponList; + else if (msg_type == GET_USER_MSG_ID (PLID, "CurWeapon", NULL)) + botMsgFunction = BotClient_TFC_CurrentWeapon; + else if (msg_type == GET_USER_MSG_ID (PLID, "AmmoX", NULL)) + botMsgFunction = BotClient_TFC_AmmoX; + else if (msg_type == GET_USER_MSG_ID (PLID, "AmmoPickup", NULL)) + botMsgFunction = BotClient_TFC_AmmoPickup; + else if (msg_type == GET_USER_MSG_ID (PLID, "SecAmmoVal", NULL)) + botMsgFunction = BotClient_TFC_SecAmmoVal; + else if (msg_type == GET_USER_MSG_ID (PLID, "WeapPickup", NULL)) + botMsgFunction = BotClient_TFC_WeaponPickup; + else if (msg_type == GET_USER_MSG_ID (PLID, "ItemPickup", NULL)) + botMsgFunction = BotClient_TFC_ItemPickup; + else if (msg_type == GET_USER_MSG_ID (PLID, "Health", NULL)) + botMsgFunction = BotClient_TFC_Health; + else if (msg_type == GET_USER_MSG_ID (PLID, "Battery", NULL)) + botMsgFunction = BotClient_TFC_Battery; + else if (msg_type == GET_USER_MSG_ID (PLID, "Damage", NULL)) + botMsgFunction = BotClient_TFC_Damage; + else if (msg_type == GET_USER_MSG_ID (PLID, "TextMsg", NULL)) + { + botMsgFunction = BotClient_TFC_TextMsg; + botMsgEndFunction = BotClient_TFC_TextMsg; + } + else if (msg_type == GET_USER_MSG_ID (PLID, "ScreenFade", NULL)) + botMsgFunction = BotClient_TFC_ScreenFade; + } + else if (mod_id == CSTRIKE_DLL) + { + if (msg_type == GET_USER_MSG_ID (PLID, "VGUIMenu", NULL)) + botMsgFunction = BotClient_CS_VGUI; + else if (msg_type == GET_USER_MSG_ID (PLID, "ShowMenu", NULL)) + botMsgFunction = BotClient_CS_ShowMenu; + else if (msg_type == GET_USER_MSG_ID (PLID, "WeaponList", NULL)) + botMsgFunction = BotClient_CS_WeaponList; + else if (msg_type == GET_USER_MSG_ID (PLID, "CurWeapon", NULL)) + botMsgFunction = BotClient_CS_CurrentWeapon; + else if (msg_type == GET_USER_MSG_ID (PLID, "AmmoX", NULL)) + botMsgFunction = BotClient_CS_AmmoX; + else if (msg_type == GET_USER_MSG_ID (PLID, "WeapPickup", NULL)) + botMsgFunction = BotClient_CS_WeaponPickup; + else if (msg_type == GET_USER_MSG_ID (PLID, "AmmoPickup", NULL)) + botMsgFunction = BotClient_CS_AmmoPickup; + else if (msg_type == GET_USER_MSG_ID (PLID, "ItemPickup", NULL)) + botMsgFunction = BotClient_CS_ItemPickup; + else if (msg_type == GET_USER_MSG_ID (PLID, "Health", NULL)) + botMsgFunction = BotClient_CS_Health; + else if (msg_type == GET_USER_MSG_ID (PLID, "Battery", NULL)) + botMsgFunction = BotClient_CS_Battery; + else if (msg_type == GET_USER_MSG_ID (PLID, "Damage", NULL)) + botMsgFunction = BotClient_CS_Damage; + else if (msg_type == GET_USER_MSG_ID (PLID, "Money", NULL)) + botMsgFunction = BotClient_CS_Money; + else if (msg_type == GET_USER_MSG_ID (PLID, "ScreenFade", NULL)) + botMsgFunction = BotClient_CS_ScreenFade; + } + else if (mod_id == GEARBOX_DLL) + { + if (msg_type == GET_USER_MSG_ID (PLID, "VGUIMenu", NULL)) + botMsgFunction = BotClient_Gearbox_VGUI; + else if (msg_type == GET_USER_MSG_ID (PLID, "WeaponList", NULL)) + botMsgFunction = BotClient_Gearbox_WeaponList; + else if (msg_type == GET_USER_MSG_ID (PLID, "CurWeapon", NULL)) + botMsgFunction = BotClient_Gearbox_CurrentWeapon; + else if (msg_type == GET_USER_MSG_ID (PLID, "AmmoX", NULL)) + botMsgFunction = BotClient_Gearbox_AmmoX; + else if (msg_type == GET_USER_MSG_ID (PLID, "AmmoPickup", NULL)) + botMsgFunction = BotClient_Gearbox_AmmoPickup; + else if (msg_type == GET_USER_MSG_ID (PLID, "WeapPickup", NULL)) + botMsgFunction = BotClient_Gearbox_WeaponPickup; + else if (msg_type == GET_USER_MSG_ID (PLID, "ItemPickup", NULL)) + botMsgFunction = BotClient_Gearbox_ItemPickup; + else if (msg_type == GET_USER_MSG_ID (PLID, "Health", NULL)) + botMsgFunction = BotClient_Gearbox_Health; + else if (msg_type == GET_USER_MSG_ID (PLID, "Battery", NULL)) + botMsgFunction = BotClient_Gearbox_Battery; + else if (msg_type == GET_USER_MSG_ID (PLID, "Damage", NULL)) + botMsgFunction = BotClient_Gearbox_Damage; + else if (msg_type == GET_USER_MSG_ID (PLID, "ScreenFade", NULL)) + botMsgFunction = BotClient_Gearbox_ScreenFade; + } + else if (mod_id == FRONTLINE_DLL) + { + if (msg_type == GET_USER_MSG_ID (PLID, "VGUIMenu", NULL)) + { + botMsgFunction = BotClient_FLF_VGUI; + botMsgEndFunction = BotClient_FLF_VGUI; + } + else if (msg_type == GET_USER_MSG_ID (PLID, "WeaponList", NULL)) + botMsgFunction = BotClient_FLF_WeaponList; + else if (msg_type == GET_USER_MSG_ID (PLID, "CurWeapon", NULL)) + botMsgFunction = BotClient_FLF_CurrentWeapon; + else if (msg_type == GET_USER_MSG_ID (PLID, "AmmoX", NULL)) + botMsgFunction = BotClient_FLF_AmmoX; + else if (msg_type == GET_USER_MSG_ID (PLID, "AmmoPickup", NULL)) + botMsgFunction = BotClient_FLF_AmmoPickup; + else if (msg_type == GET_USER_MSG_ID (PLID, "WeapPickup", NULL)) + botMsgFunction = BotClient_FLF_WeaponPickup; + else if (msg_type == GET_USER_MSG_ID (PLID, "ItemPickup", NULL)) + botMsgFunction = BotClient_FLF_ItemPickup; + else if (msg_type == GET_USER_MSG_ID (PLID, "Health", NULL)) + botMsgFunction = BotClient_FLF_Health; + else if (msg_type == GET_USER_MSG_ID (PLID, "Battery", NULL)) + botMsgFunction = BotClient_FLF_Battery; + else if (msg_type == GET_USER_MSG_ID (PLID, "Damage", NULL)) + botMsgFunction = BotClient_FLF_Damage; + else if (msg_type == GET_USER_MSG_ID (PLID, "TextMsg", NULL)) + { + botMsgFunction = BotClient_FLF_TextMsg; + botMsgEndFunction = BotClient_FLF_TextMsg; + } + else if (msg_type == GET_USER_MSG_ID (PLID, "WarmUp", NULL)) + botMsgFunction = BotClient_FLF_WarmUp; + else if (msg_type == GET_USER_MSG_ID (PLID, "ScreenFade", NULL)) + botMsgFunction = BotClient_FLF_ScreenFade; + else if (msg_type == GET_USER_MSG_ID (PLID, "HideWeapon", NULL)) + botMsgFunction = BotClient_FLF_HideWeapon; + } + else if (mod_id == HOLYWARS_DLL) + { + if (msg_type == GET_USER_MSG_ID (PLID, "WeaponList", NULL)) + botMsgFunction = BotClient_Valve_WeaponList; + else if (msg_type == GET_USER_MSG_ID (PLID, "CurWeapon", NULL)) + botMsgFunction = BotClient_Valve_CurrentWeapon; + else if (msg_type == GET_USER_MSG_ID (PLID, "AmmoX", NULL)) + botMsgFunction = BotClient_Valve_AmmoX; + else if (msg_type == GET_USER_MSG_ID (PLID, "AmmoPickup", NULL)) + botMsgFunction = BotClient_Valve_AmmoPickup; + else if (msg_type == GET_USER_MSG_ID (PLID, "WeapPickup", NULL)) + botMsgFunction = BotClient_Valve_WeaponPickup; + else if (msg_type == GET_USER_MSG_ID (PLID, "ItemPickup", NULL)) + botMsgFunction = BotClient_Valve_ItemPickup; + else if (msg_type == GET_USER_MSG_ID (PLID, "Health", NULL)) + botMsgFunction = BotClient_Valve_Health; + else if (msg_type == GET_USER_MSG_ID (PLID, "Battery", NULL)) + botMsgFunction = BotClient_Valve_Battery; + else if (msg_type == GET_USER_MSG_ID (PLID, "Damage", NULL)) + botMsgFunction = BotClient_Valve_Damage; + else if (msg_type == GET_USER_MSG_ID (PLID, "ScreenFade", NULL)) + botMsgFunction = BotClient_Valve_ScreenFade; + else if (msg_type == GET_USER_MSG_ID (PLID, "GameMode", NULL)) + botMsgFunction = BotClient_HolyWars_GameMode; + else if (msg_type == GET_USER_MSG_ID (PLID, "HudText", NULL)) + botMsgFunction = BotClient_HolyWars_HudText; + } + else if (mod_id == DMC_DLL) + { + if (msg_type == GET_USER_MSG_ID (PLID, "WeaponList", NULL)) + botMsgFunction = BotClient_DMC_WeaponList; + else if (msg_type == GET_USER_MSG_ID (PLID, "CurWeapon", NULL)) + botMsgFunction = BotClient_DMC_CurrentWeapon; + else if (msg_type == GET_USER_MSG_ID (PLID, "AmmoX", NULL)) + botMsgFunction = BotClient_DMC_AmmoX; + else if (msg_type == GET_USER_MSG_ID (PLID, "AmmoPickup", NULL)) + botMsgFunction = BotClient_DMC_AmmoPickup; + else if (msg_type == GET_USER_MSG_ID (PLID, "WeapPickup", NULL)) + botMsgFunction = BotClient_DMC_WeaponPickup; + else if (msg_type == GET_USER_MSG_ID (PLID, "ItemPickup", NULL)) + botMsgFunction = BotClient_DMC_ItemPickup; + else if (msg_type == GET_USER_MSG_ID (PLID, "Health", NULL)) + botMsgFunction = BotClient_DMC_Health; + else if (msg_type == GET_USER_MSG_ID (PLID, "Battery", NULL)) + botMsgFunction = BotClient_DMC_Battery; + else if (msg_type == GET_USER_MSG_ID (PLID, "Damage", NULL)) + botMsgFunction = BotClient_DMC_Damage; + else if (msg_type == GET_USER_MSG_ID (PLID, "QItems", NULL)) + botMsgFunction = BotClient_DMC_QItems; + } + } + } + } + else if (msg_dest == MSG_ALL) + { + botMsgFunction = NULL; // no msg function until known otherwise + botMsgIndex = -1; // index of bot receiving message (none) + + if (mod_id == VALVE_DLL) + { + if (msg_type == GET_USER_MSG_ID (PLID, "DeathMsg", NULL)) + botMsgFunction = BotClient_Valve_DeathMsg; + } + else if (mod_id == TFC_DLL) + { + if (msg_type == GET_USER_MSG_ID (PLID, "DeathMsg", NULL)) + botMsgFunction = BotClient_TFC_DeathMsg; + } + else if (mod_id == CSTRIKE_DLL) + { + if (msg_type == GET_USER_MSG_ID (PLID, "DeathMsg", NULL)) + botMsgFunction = BotClient_CS_DeathMsg; + } + else if (mod_id == GEARBOX_DLL) + { + if (msg_type == GET_USER_MSG_ID (PLID, "DeathMsg", NULL)) + botMsgFunction = BotClient_Gearbox_DeathMsg; + } + else if (mod_id == FRONTLINE_DLL) + { + if (msg_type == GET_USER_MSG_ID (PLID, "DeathMsg", NULL)) + botMsgFunction = BotClient_FLF_DeathMsg; + else if (msg_type == GET_USER_MSG_ID (PLID, "WarmUp", NULL)) + botMsgFunction = BotClient_FLF_WarmUpAll; + else if (msg_type == GET_USER_MSG_ID (PLID, "WinMessage", NULL)) + botMsgFunction = BotClient_FLF_WinMessage; + } + else if (mod_id == HOLYWARS_DLL) + { + if (msg_type == GET_USER_MSG_ID (PLID, "DeathMsg", NULL)) + botMsgFunction = BotClient_Valve_DeathMsg; + } + else if (mod_id == DMC_DLL) + { + if (msg_type == GET_USER_MSG_ID (PLID, "DeathMsg", NULL)) + botMsgFunction = BotClient_DMC_DeathMsg; + } + } + else + { + // Steam makes the WeaponList message be sent differently + + botMsgFunction = NULL; // no msg function until known otherwise + botMsgIndex = -1; // index of bot receiving message (none) + + if (mod_id == VALVE_DLL) + { + if (msg_type == GET_USER_MSG_ID (PLID, "WeaponList", NULL)) + botMsgFunction = BotClient_Valve_WeaponList; + } + else if (mod_id == TFC_DLL) + { + if (msg_type == GET_USER_MSG_ID (PLID, "WeaponList", NULL)) + botMsgFunction = BotClient_TFC_WeaponList; + } + else if (mod_id == CSTRIKE_DLL) + { + if (msg_type == GET_USER_MSG_ID (PLID, "WeaponList", NULL)) + botMsgFunction = BotClient_CS_WeaponList; + else if (msg_type == GET_USER_MSG_ID (PLID, "HLTV", NULL)) + botMsgFunction = BotClient_CS_HLTV; + } + else if (mod_id == GEARBOX_DLL) + { + if (msg_type == GET_USER_MSG_ID (PLID, "WeaponList", NULL)) + botMsgFunction = BotClient_Gearbox_WeaponList; + } + else if (mod_id == FRONTLINE_DLL) + { + if (msg_type == GET_USER_MSG_ID (PLID, "WeaponList", NULL)) + botMsgFunction = BotClient_FLF_WeaponList; + } + } + } + + RETURN_META (MRES_IGNORED); +} + + +void pfnMessageEnd(void) +{ + if (gpGlobals->deathmatch) + { + if (botMsgEndFunction) + (*botMsgEndFunction)(NULL, botMsgIndex); // NULL indicated msg end + + // clear out the bot message function pointers... + botMsgFunction = NULL; + botMsgEndFunction = NULL; + } + + RETURN_META (MRES_IGNORED); +} + + +void pfnWriteByte(int iValue) +{ + if (gpGlobals->deathmatch) + { + // if this message is for a bot, call the client message function... + if (botMsgFunction) + (*botMsgFunction)((void *)&iValue, botMsgIndex); + } + + RETURN_META (MRES_IGNORED); +} + + +void pfnWriteChar(int iValue) +{ + if (gpGlobals->deathmatch) + { + // if this message is for a bot, call the client message function... + if (botMsgFunction) + (*botMsgFunction)((void *)&iValue, botMsgIndex); + } + + RETURN_META (MRES_IGNORED); +} + + +void pfnWriteShort(int iValue) +{ + if (gpGlobals->deathmatch) + { + // if this message is for a bot, call the client message function... + if (botMsgFunction) + (*botMsgFunction)((void *)&iValue, botMsgIndex); + } + + RETURN_META (MRES_IGNORED); +} + + +void pfnWriteLong(int iValue) +{ + if (gpGlobals->deathmatch) + { + // if this message is for a bot, call the client message function... + if (botMsgFunction) + (*botMsgFunction)((void *)&iValue, botMsgIndex); + } + + RETURN_META (MRES_IGNORED); +} + + +void pfnWriteAngle(float flValue) +{ + if (gpGlobals->deathmatch) + { + // if this message is for a bot, call the client message function... + if (botMsgFunction) + (*botMsgFunction)((void *)&flValue, botMsgIndex); + } + + RETURN_META (MRES_IGNORED); +} + + +void pfnWriteCoord(float flValue) +{ + if (gpGlobals->deathmatch) + { + // if this message is for a bot, call the client message function... + if (botMsgFunction) + (*botMsgFunction)((void *)&flValue, botMsgIndex); + } + + RETURN_META (MRES_IGNORED); +} + + +void pfnWriteString(const char *sz) +{ + if (gpGlobals->deathmatch) + { + // if this message is for a bot, call the client message function... + if (botMsgFunction) + (*botMsgFunction)((void *)sz, botMsgIndex); + } + + RETURN_META (MRES_IGNORED); +} + + +void pfnWriteEntity(int iValue) +{ + if (gpGlobals->deathmatch) + { + // if this message is for a bot, call the client message function... + if (botMsgFunction) + (*botMsgFunction)((void *)&iValue, botMsgIndex); + } + + RETURN_META (MRES_IGNORED); +} + + +void pfnClientPrintf( edict_t* pEdict, PRINT_TYPE ptype, const char *szMsg ) +{ + if ((pEdict->v.flags & FL_FAKECLIENT) || (pEdict->v.flags & FL_THIRDPARTYBOT)) + RETURN_META (MRES_SUPERCEDE); + RETURN_META (MRES_IGNORED); +} + + +const char *pfnCmd_Args( void ) +{ + if (isFakeClientCommand) + RETURN_META_VALUE (MRES_SUPERCEDE, &g_argv[0]); + + RETURN_META_VALUE (MRES_IGNORED, NULL); +} + + +const char *pfnCmd_Argv( int argc ) +{ + if (isFakeClientCommand) + { + if (argc == 0) + RETURN_META_VALUE (MRES_SUPERCEDE, &g_argv[64]); + else if (argc == 1) + RETURN_META_VALUE (MRES_SUPERCEDE, &g_argv[128]); + else if (argc == 2) + RETURN_META_VALUE (MRES_SUPERCEDE, &g_argv[192]); + else + RETURN_META_VALUE (MRES_SUPERCEDE, NULL); + } + + RETURN_META_VALUE (MRES_IGNORED, NULL); +} + + +int pfnCmd_Argc( void ) +{ + if (isFakeClientCommand) + RETURN_META_VALUE (MRES_SUPERCEDE, fake_arg_count); + + RETURN_META_VALUE (MRES_IGNORED, 0); +} + + +void pfnSetClientMaxspeed(const edict_t *pEdict, float fNewMaxspeed) +{ + int index; + + index = UTIL_GetBotIndex((edict_t *)pEdict); + + // is this message for a bot? + if (index != -1) + bots[index].f_max_speed = fNewMaxspeed; + + RETURN_META (MRES_IGNORED); +} + + +int pfnGetPlayerUserId(edict_t *e ) +{ + if (gpGlobals->deathmatch) + { + if (mod_id == GEARBOX_DLL) + { + // is this edict a bot? + if (UTIL_GetBotPointer( e )) + RETURN_META_VALUE (MRES_SUPERCEDE, 0); // don't return a valid index (so bot won't get kicked) + } + } + + RETURN_META_VALUE (MRES_IGNORED, 0); +} + + +const char *pfnGetPlayerAuthId (edict_t *e) +{ + if ((e->v.flags & FL_FAKECLIENT) || (e->v.flags & FL_THIRDPARTYBOT)) + RETURN_META_VALUE (MRES_SUPERCEDE, "0"); + + RETURN_META_VALUE (MRES_IGNORED, NULL); +} + + +C_DLLEXPORT int GetEngineFunctions (enginefuncs_t *pengfuncsFromEngine, int *interfaceVersion) +{ + meta_engfuncs.pfnChangeLevel = pfnChangeLevel; + meta_engfuncs.pfnFindEntityByString = pfnFindEntityByString; + meta_engfuncs.pfnEmitSound = pfnEmitSound; + meta_engfuncs.pfnClientCommand = pfnClientCommand; + meta_engfuncs.pfnMessageBegin = pfnMessageBegin; + meta_engfuncs.pfnMessageEnd = pfnMessageEnd; + meta_engfuncs.pfnWriteByte = pfnWriteByte; + meta_engfuncs.pfnWriteChar = pfnWriteChar; + meta_engfuncs.pfnWriteShort = pfnWriteShort; + meta_engfuncs.pfnWriteLong = pfnWriteLong; + meta_engfuncs.pfnWriteAngle = pfnWriteAngle; + meta_engfuncs.pfnWriteCoord = pfnWriteCoord; + meta_engfuncs.pfnWriteString = pfnWriteString; + meta_engfuncs.pfnWriteEntity = pfnWriteEntity; + meta_engfuncs.pfnClientPrintf = pfnClientPrintf; + meta_engfuncs.pfnCmd_Args = pfnCmd_Args; + meta_engfuncs.pfnCmd_Argv = pfnCmd_Argv; + meta_engfuncs.pfnCmd_Argc = pfnCmd_Argc; + meta_engfuncs.pfnSetClientMaxspeed = pfnSetClientMaxspeed; + meta_engfuncs.pfnGetPlayerUserId = pfnGetPlayerUserId; + meta_engfuncs.pfnGetPlayerAuthId = pfnGetPlayerAuthId; + + memcpy (pengfuncsFromEngine, &meta_engfuncs, sizeof (enginefuncs_t)); + return TRUE; +} diff --git a/h_export.cpp b/h_export.cpp new file mode 100644 index 0000000..3502b89 --- /dev/null +++ b/h_export.cpp @@ -0,0 +1,125 @@ +// +// HPB bot - botman's High Ping Bastard bot +// +// (http://planethalflife.com/botman/) +// +// h_export.cpp +// + +#ifndef _WIN32 +#include +#endif + +#include +#include +#include +#include + +#include "bot.h" + +char g_argv[1024]; + +DLL_FUNCTIONS gFunctionTable; +enginefuncs_t g_engfuncs; +globalvars_t *gpGlobals; + +static FILE *fp; +char z_welcome_msg[] = "HPB bot - http://planethalflife.com/botman"; + + +extern int mod_id; + + +#ifndef __linux__ + +// Required DLL entry point +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) +{ + return TRUE; +} + +#endif + +void WINAPI GiveFnptrsToDll( enginefuncs_t* pengfuncsFromEngine, globalvars_t *pGlobals ) +{ + char game_dir[256]; + char mod_name[32]; + char game_dll_filename[256]; + + // get the engine functions from the engine... + + memcpy(&g_engfuncs, pengfuncsFromEngine, sizeof(enginefuncs_t)); + gpGlobals = pGlobals; + + // find the directory name of the currently running MOD... + GetGameDir (game_dir); + + strcpy(mod_name, game_dir); + + game_dll_filename[0] = 0; + + if (strcmpi(mod_name, "valve") == 0) + { + mod_id = VALVE_DLL; +#ifndef __linux__ + strcpy(game_dll_filename, "valve\\dlls\\hl.dll"); +#else + strcpy(game_dll_filename, "valve/dlls/hl_i386.so"); +#endif + } + else if (strcmpi(mod_name, "tfc") == 0) + { + mod_id = TFC_DLL; +#ifndef __linux__ + strcpy(game_dll_filename, "tfc\\dlls\\tfc.dll"); +#else + strcpy(game_dll_filename, "tfc/dlls/tfc_i386.so"); +#endif + } + else if (strcmpi(mod_name, "cstrike") == 0) + { + mod_id = CSTRIKE_DLL; +#ifndef __linux__ + strcpy(game_dll_filename, "cstrike\\dlls\\mp.dll"); +#else + strcpy(game_dll_filename, "cstrike/dlls/cs_i386.so"); +#endif + } + else if (strcmpi(mod_name, "gearbox") == 0) + { + mod_id = GEARBOX_DLL; +#ifndef __linux__ + strcpy(game_dll_filename, "gearbox\\dlls\\opfor.dll"); +#else + strcpy(game_dll_filename, "gearbox/dlls/opfor_i386.so"); +#endif + } + else if (strcmpi(mod_name, "frontline") == 0) + { + mod_id = FRONTLINE_DLL; +#ifndef __linux__ + strcpy(game_dll_filename, "frontline\\dlls\\frontline.dll"); +#else + strcpy(game_dll_filename, "frontline/dlls/front_i386.so"); +#endif + } + else if (strcmpi(mod_name, "holywars") == 0) + { + mod_id = HOLYWARS_DLL; +#ifndef __linux__ + strcpy(game_dll_filename, "holywars\\dlls\\holywars.dll"); +#else + strcpy(game_dll_filename, "holywars/dlls/holywars_i386.so"); +#endif + } + else if (strcmpi(mod_name, "dmc") == 0) + { + mod_id = DMC_DLL; +#ifndef __linux__ + strcpy(game_dll_filename, "dmc\\dlls\\dmc.dll"); +#else + strcpy(game_dll_filename, "dmc/dlls/dmc_i386.so"); +#endif + } +} + diff --git a/hpb_bot_mm.def b/hpb_bot_mm.def new file mode 100644 index 0000000..de0f8aa --- /dev/null +++ b/hpb_bot_mm.def @@ -0,0 +1,5 @@ +LIBRARY HPB_bot_mm +EXPORTS + GiveFnptrsToDll @1 +SECTIONS + .data READ WRITE diff --git a/hpb_bot_mm.dsp b/hpb_bot_mm.dsp new file mode 100644 index 0000000..df7036e --- /dev/null +++ b/hpb_bot_mm.dsp @@ -0,0 +1,568 @@ +# Microsoft Developer Studio Project File - Name="hpb_bot_mm" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=hpb_bot_mm - Win32 Release +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "hpb_bot_mm.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "hpb_bot_mm.mak" CFG="hpb_bot_mm - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "hpb_bot_mm - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName ""$/SDKSrc/Public/dlls", NVGBAAAA" +# PROP Scc_LocalPath "." +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir ".\Release" +# PROP BASE Intermediate_Dir ".\Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir ".\Release" +# PROP Intermediate_Dir ".\Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /c +# ADD CPP /nologo /G5 /MT /W3 /WX /GX /O2 /I "../metamod" /I "../../devtools/hlsdk-2.3/singleplayer/dlls" /I "../../devtools/hlsdk-2.3/singleplayer/engine" /I "../../devtools/hlsdk-2.3/singleplayer/pm_shared" /I "../../devtools/hlsdk-2.3/singleplayer/common" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /Fr /YX /c +# ADD BASE MTL /nologo /D "NDEBUG" /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /dll /machine:I386 /def:".\hpb_bot_mm.def" +# SUBTRACT LINK32 /profile /incremental:yes /map /debug +# Begin Target + +# Name "hpb_bot_mm - Win32 Release" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat;for;f90" +# Begin Source File + +SOURCE=.\bot.cpp +DEP_CPP_BOT_C=\ + "..\metamod\dllapi.h"\ + "..\metamod\engine_api.h"\ + "..\metamod\h_export.h"\ + "..\metamod\log_meta.h"\ + "..\metamod\meta_api.h"\ + "..\metamod\mhook.h"\ + "..\metamod\mreg.h"\ + "..\metamod\mutil.h"\ + "..\metamod\osdep.h"\ + "..\metamod\plinfo.h"\ + "..\metamod\sdk_util.h"\ + "..\metamod\types_meta.h"\ + ".\bot.h"\ + ".\bot_func.h"\ + ".\bot_weapons.h"\ + ".\waypoint.h"\ + "c:\program files\microsoft visual studio\vc98\include\basetsd.h"\ + +NODEP_CPP_BOT_C=\ + "..\..\devtools\sdk\single-player source\common\const.h"\ + "..\..\devtools\sdk\single-player source\common\crc.h"\ + "..\..\devtools\sdk\single-player source\common\cvardef.h"\ + "..\..\devtools\sdk\single-player source\common\event_flags.h"\ + "..\..\devtools\sdk\single-player source\common\in_buttons.h"\ + "..\..\devtools\sdk\single-player source\dlls\activity.h"\ + "..\..\devtools\sdk\single-player source\dlls\cdll_dll.h"\ + "..\..\devtools\sdk\single-player source\dlls\enginecallback.h"\ + "..\..\devtools\sdk\single-player source\dlls\extdll.h"\ + "..\..\devtools\sdk\single-player source\dlls\vector.h"\ + "..\..\devtools\sdk\single-player source\engine\archtypes.h"\ + "..\..\devtools\sdk\single-player source\engine\custom.h"\ + "..\..\devtools\sdk\single-player source\engine\edict.h"\ + "..\..\devtools\sdk\single-player source\engine\eiface.h"\ + "..\..\devtools\sdk\single-player source\engine\progdefs.h"\ + "..\..\devtools\sdk\single-player source\engine\sequence.h"\ + +# End Source File +# Begin Source File + +SOURCE=.\bot_chat.cpp +DEP_CPP_BOT_CH=\ + "..\metamod\dllapi.h"\ + "..\metamod\engine_api.h"\ + "..\metamod\h_export.h"\ + "..\metamod\log_meta.h"\ + "..\metamod\meta_api.h"\ + "..\metamod\mhook.h"\ + "..\metamod\mreg.h"\ + "..\metamod\mutil.h"\ + "..\metamod\osdep.h"\ + "..\metamod\plinfo.h"\ + "..\metamod\sdk_util.h"\ + "..\metamod\types_meta.h"\ + ".\bot.h"\ + "c:\program files\microsoft visual studio\vc98\include\basetsd.h"\ + +NODEP_CPP_BOT_CH=\ + "..\..\devtools\sdk\single-player source\common\const.h"\ + "..\..\devtools\sdk\single-player source\common\crc.h"\ + "..\..\devtools\sdk\single-player source\common\cvardef.h"\ + "..\..\devtools\sdk\single-player source\common\event_flags.h"\ + "..\..\devtools\sdk\single-player source\common\in_buttons.h"\ + "..\..\devtools\sdk\single-player source\dlls\activity.h"\ + "..\..\devtools\sdk\single-player source\dlls\cdll_dll.h"\ + "..\..\devtools\sdk\single-player source\dlls\enginecallback.h"\ + "..\..\devtools\sdk\single-player source\dlls\extdll.h"\ + "..\..\devtools\sdk\single-player source\dlls\vector.h"\ + "..\..\devtools\sdk\single-player source\engine\archtypes.h"\ + "..\..\devtools\sdk\single-player source\engine\custom.h"\ + "..\..\devtools\sdk\single-player source\engine\edict.h"\ + "..\..\devtools\sdk\single-player source\engine\eiface.h"\ + "..\..\devtools\sdk\single-player source\engine\progdefs.h"\ + "..\..\devtools\sdk\single-player source\engine\sequence.h"\ + +# End Source File +# Begin Source File + +SOURCE=.\bot_client.cpp +DEP_CPP_BOT_CL=\ + "..\metamod\dllapi.h"\ + "..\metamod\engine_api.h"\ + "..\metamod\h_export.h"\ + "..\metamod\log_meta.h"\ + "..\metamod\meta_api.h"\ + "..\metamod\mhook.h"\ + "..\metamod\mreg.h"\ + "..\metamod\mutil.h"\ + "..\metamod\osdep.h"\ + "..\metamod\plinfo.h"\ + "..\metamod\sdk_util.h"\ + "..\metamod\types_meta.h"\ + ".\bot.h"\ + ".\bot_client.h"\ + ".\bot_func.h"\ + ".\bot_weapons.h"\ + "c:\program files\microsoft visual studio\vc98\include\basetsd.h"\ + +NODEP_CPP_BOT_CL=\ + "..\..\devtools\sdk\single-player source\common\const.h"\ + "..\..\devtools\sdk\single-player source\common\crc.h"\ + "..\..\devtools\sdk\single-player source\common\cvardef.h"\ + "..\..\devtools\sdk\single-player source\common\event_flags.h"\ + "..\..\devtools\sdk\single-player source\common\in_buttons.h"\ + "..\..\devtools\sdk\single-player source\dlls\activity.h"\ + "..\..\devtools\sdk\single-player source\dlls\cdll_dll.h"\ + "..\..\devtools\sdk\single-player source\dlls\enginecallback.h"\ + "..\..\devtools\sdk\single-player source\dlls\extdll.h"\ + "..\..\devtools\sdk\single-player source\dlls\vector.h"\ + "..\..\devtools\sdk\single-player source\engine\archtypes.h"\ + "..\..\devtools\sdk\single-player source\engine\custom.h"\ + "..\..\devtools\sdk\single-player source\engine\edict.h"\ + "..\..\devtools\sdk\single-player source\engine\eiface.h"\ + "..\..\devtools\sdk\single-player source\engine\progdefs.h"\ + "..\..\devtools\sdk\single-player source\engine\sequence.h"\ + +# End Source File +# Begin Source File + +SOURCE=.\bot_combat.cpp +DEP_CPP_BOT_CO=\ + "..\metamod\dllapi.h"\ + "..\metamod\engine_api.h"\ + "..\metamod\h_export.h"\ + "..\metamod\log_meta.h"\ + "..\metamod\meta_api.h"\ + "..\metamod\mhook.h"\ + "..\metamod\mreg.h"\ + "..\metamod\mutil.h"\ + "..\metamod\osdep.h"\ + "..\metamod\plinfo.h"\ + "..\metamod\sdk_util.h"\ + "..\metamod\types_meta.h"\ + ".\bot.h"\ + ".\bot_func.h"\ + ".\bot_weapons.h"\ + "c:\program files\microsoft visual studio\vc98\include\basetsd.h"\ + +NODEP_CPP_BOT_CO=\ + "..\..\devtools\sdk\single-player source\common\const.h"\ + "..\..\devtools\sdk\single-player source\common\crc.h"\ + "..\..\devtools\sdk\single-player source\common\cvardef.h"\ + "..\..\devtools\sdk\single-player source\common\event_flags.h"\ + "..\..\devtools\sdk\single-player source\common\in_buttons.h"\ + "..\..\devtools\sdk\single-player source\dlls\activity.h"\ + "..\..\devtools\sdk\single-player source\dlls\cdll_dll.h"\ + "..\..\devtools\sdk\single-player source\dlls\enginecallback.h"\ + "..\..\devtools\sdk\single-player source\dlls\extdll.h"\ + "..\..\devtools\sdk\single-player source\dlls\vector.h"\ + "..\..\devtools\sdk\single-player source\engine\archtypes.h"\ + "..\..\devtools\sdk\single-player source\engine\custom.h"\ + "..\..\devtools\sdk\single-player source\engine\edict.h"\ + "..\..\devtools\sdk\single-player source\engine\eiface.h"\ + "..\..\devtools\sdk\single-player source\engine\progdefs.h"\ + "..\..\devtools\sdk\single-player source\engine\sequence.h"\ + +# End Source File +# Begin Source File + +SOURCE=.\bot_models.cpp +DEP_CPP_BOT_M=\ + "..\metamod\dllapi.h"\ + "..\metamod\engine_api.h"\ + "..\metamod\h_export.h"\ + "..\metamod\log_meta.h"\ + "..\metamod\meta_api.h"\ + "..\metamod\mhook.h"\ + "..\metamod\mreg.h"\ + "..\metamod\mutil.h"\ + "..\metamod\osdep.h"\ + "..\metamod\plinfo.h"\ + "..\metamod\sdk_util.h"\ + "..\metamod\types_meta.h"\ + ".\bot.h"\ + "c:\program files\microsoft visual studio\vc98\include\basetsd.h"\ + +NODEP_CPP_BOT_M=\ + "..\..\devtools\sdk\single-player source\common\const.h"\ + "..\..\devtools\sdk\single-player source\common\crc.h"\ + "..\..\devtools\sdk\single-player source\common\cvardef.h"\ + "..\..\devtools\sdk\single-player source\common\event_flags.h"\ + "..\..\devtools\sdk\single-player source\common\in_buttons.h"\ + "..\..\devtools\sdk\single-player source\dlls\activity.h"\ + "..\..\devtools\sdk\single-player source\dlls\cdll_dll.h"\ + "..\..\devtools\sdk\single-player source\dlls\enginecallback.h"\ + "..\..\devtools\sdk\single-player source\dlls\extdll.h"\ + "..\..\devtools\sdk\single-player source\dlls\vector.h"\ + "..\..\devtools\sdk\single-player source\engine\archtypes.h"\ + "..\..\devtools\sdk\single-player source\engine\custom.h"\ + "..\..\devtools\sdk\single-player source\engine\edict.h"\ + "..\..\devtools\sdk\single-player source\engine\eiface.h"\ + "..\..\devtools\sdk\single-player source\engine\progdefs.h"\ + "..\..\devtools\sdk\single-player source\engine\sequence.h"\ + +# End Source File +# Begin Source File + +SOURCE=.\bot_navigate.cpp +DEP_CPP_BOT_N=\ + "..\metamod\dllapi.h"\ + "..\metamod\engine_api.h"\ + "..\metamod\h_export.h"\ + "..\metamod\log_meta.h"\ + "..\metamod\meta_api.h"\ + "..\metamod\mhook.h"\ + "..\metamod\mreg.h"\ + "..\metamod\mutil.h"\ + "..\metamod\osdep.h"\ + "..\metamod\plinfo.h"\ + "..\metamod\sdk_util.h"\ + "..\metamod\types_meta.h"\ + ".\bot.h"\ + ".\bot_func.h"\ + ".\bot_weapons.h"\ + ".\waypoint.h"\ + "c:\program files\microsoft visual studio\vc98\include\basetsd.h"\ + +NODEP_CPP_BOT_N=\ + "..\..\devtools\sdk\single-player source\common\const.h"\ + "..\..\devtools\sdk\single-player source\common\crc.h"\ + "..\..\devtools\sdk\single-player source\common\cvardef.h"\ + "..\..\devtools\sdk\single-player source\common\event_flags.h"\ + "..\..\devtools\sdk\single-player source\common\in_buttons.h"\ + "..\..\devtools\sdk\single-player source\dlls\activity.h"\ + "..\..\devtools\sdk\single-player source\dlls\cdll_dll.h"\ + "..\..\devtools\sdk\single-player source\dlls\enginecallback.h"\ + "..\..\devtools\sdk\single-player source\dlls\extdll.h"\ + "..\..\devtools\sdk\single-player source\dlls\vector.h"\ + "..\..\devtools\sdk\single-player source\engine\archtypes.h"\ + "..\..\devtools\sdk\single-player source\engine\custom.h"\ + "..\..\devtools\sdk\single-player source\engine\edict.h"\ + "..\..\devtools\sdk\single-player source\engine\eiface.h"\ + "..\..\devtools\sdk\single-player source\engine\progdefs.h"\ + "..\..\devtools\sdk\single-player source\engine\sequence.h"\ + +# End Source File +# Begin Source File + +SOURCE=.\bot_start.cpp +DEP_CPP_BOT_S=\ + "..\metamod\dllapi.h"\ + "..\metamod\engine_api.h"\ + "..\metamod\h_export.h"\ + "..\metamod\log_meta.h"\ + "..\metamod\meta_api.h"\ + "..\metamod\mhook.h"\ + "..\metamod\mreg.h"\ + "..\metamod\mutil.h"\ + "..\metamod\osdep.h"\ + "..\metamod\plinfo.h"\ + "..\metamod\sdk_util.h"\ + "..\metamod\types_meta.h"\ + ".\bot.h"\ + ".\bot_func.h"\ + ".\bot_weapons.h"\ + "c:\program files\microsoft visual studio\vc98\include\basetsd.h"\ + +NODEP_CPP_BOT_S=\ + "..\..\devtools\sdk\single-player source\common\const.h"\ + "..\..\devtools\sdk\single-player source\common\crc.h"\ + "..\..\devtools\sdk\single-player source\common\cvardef.h"\ + "..\..\devtools\sdk\single-player source\common\event_flags.h"\ + "..\..\devtools\sdk\single-player source\common\in_buttons.h"\ + "..\..\devtools\sdk\single-player source\dlls\activity.h"\ + "..\..\devtools\sdk\single-player source\dlls\cdll_dll.h"\ + "..\..\devtools\sdk\single-player source\dlls\enginecallback.h"\ + "..\..\devtools\sdk\single-player source\dlls\extdll.h"\ + "..\..\devtools\sdk\single-player source\dlls\vector.h"\ + "..\..\devtools\sdk\single-player source\engine\archtypes.h"\ + "..\..\devtools\sdk\single-player source\engine\custom.h"\ + "..\..\devtools\sdk\single-player source\engine\edict.h"\ + "..\..\devtools\sdk\single-player source\engine\eiface.h"\ + "..\..\devtools\sdk\single-player source\engine\progdefs.h"\ + "..\..\devtools\sdk\single-player source\engine\sequence.h"\ + +# End Source File +# Begin Source File + +SOURCE=.\dll.cpp +DEP_CPP_DLL_C=\ + "..\metamod\dllapi.h"\ + "..\metamod\engine_api.h"\ + "..\metamod\h_export.h"\ + "..\metamod\log_meta.h"\ + "..\metamod\meta_api.h"\ + "..\metamod\mhook.h"\ + "..\metamod\mreg.h"\ + "..\metamod\mutil.h"\ + "..\metamod\osdep.h"\ + "..\metamod\plinfo.h"\ + "..\metamod\sdk_util.h"\ + "..\metamod\types_meta.h"\ + ".\bot.h"\ + ".\bot_func.h"\ + ".\waypoint.h"\ + "c:\program files\microsoft visual studio\vc98\include\basetsd.h"\ + +NODEP_CPP_DLL_C=\ + "..\..\devtools\sdk\single-player source\common\const.h"\ + "..\..\devtools\sdk\single-player source\common\crc.h"\ + "..\..\devtools\sdk\single-player source\common\cvardef.h"\ + "..\..\devtools\sdk\single-player source\common\entity_state.h"\ + "..\..\devtools\sdk\single-player source\common\event_flags.h"\ + "..\..\devtools\sdk\single-player source\common\in_buttons.h"\ + "..\..\devtools\sdk\single-player source\common\weaponinfo.h"\ + "..\..\devtools\sdk\single-player source\dlls\activity.h"\ + "..\..\devtools\sdk\single-player source\dlls\cdll_dll.h"\ + "..\..\devtools\sdk\single-player source\dlls\enginecallback.h"\ + "..\..\devtools\sdk\single-player source\dlls\extdll.h"\ + "..\..\devtools\sdk\single-player source\dlls\vector.h"\ + "..\..\devtools\sdk\single-player source\engine\archtypes.h"\ + "..\..\devtools\sdk\single-player source\engine\custom.h"\ + "..\..\devtools\sdk\single-player source\engine\edict.h"\ + "..\..\devtools\sdk\single-player source\engine\eiface.h"\ + "..\..\devtools\sdk\single-player source\engine\progdefs.h"\ + "..\..\devtools\sdk\single-player source\engine\sequence.h"\ + "..\..\devtools\sdk\single-player source\pm_shared\pm_info.h"\ + +# End Source File +# Begin Source File + +SOURCE=.\engine.cpp +DEP_CPP_ENGIN=\ + "..\metamod\dllapi.h"\ + "..\metamod\engine_api.h"\ + "..\metamod\h_export.h"\ + "..\metamod\log_meta.h"\ + "..\metamod\meta_api.h"\ + "..\metamod\mhook.h"\ + "..\metamod\mreg.h"\ + "..\metamod\mutil.h"\ + "..\metamod\osdep.h"\ + "..\metamod\plinfo.h"\ + "..\metamod\sdk_util.h"\ + "..\metamod\types_meta.h"\ + ".\bot.h"\ + ".\bot_client.h"\ + "c:\program files\microsoft visual studio\vc98\include\basetsd.h"\ + +NODEP_CPP_ENGIN=\ + "..\..\devtools\sdk\single-player source\common\const.h"\ + "..\..\devtools\sdk\single-player source\common\crc.h"\ + "..\..\devtools\sdk\single-player source\common\cvardef.h"\ + "..\..\devtools\sdk\single-player source\common\event_flags.h"\ + "..\..\devtools\sdk\single-player source\common\in_buttons.h"\ + "..\..\devtools\sdk\single-player source\dlls\activity.h"\ + "..\..\devtools\sdk\single-player source\dlls\cdll_dll.h"\ + "..\..\devtools\sdk\single-player source\dlls\enginecallback.h"\ + "..\..\devtools\sdk\single-player source\dlls\extdll.h"\ + "..\..\devtools\sdk\single-player source\dlls\vector.h"\ + "..\..\devtools\sdk\single-player source\engine\archtypes.h"\ + "..\..\devtools\sdk\single-player source\engine\custom.h"\ + "..\..\devtools\sdk\single-player source\engine\edict.h"\ + "..\..\devtools\sdk\single-player source\engine\eiface.h"\ + "..\..\devtools\sdk\single-player source\engine\progdefs.h"\ + "..\..\devtools\sdk\single-player source\engine\sequence.h"\ + +# End Source File +# Begin Source File + +SOURCE=.\h_export.cpp +DEP_CPP_H_EXP=\ + "..\metamod\dllapi.h"\ + "..\metamod\engine_api.h"\ + "..\metamod\h_export.h"\ + "..\metamod\log_meta.h"\ + "..\metamod\meta_api.h"\ + "..\metamod\mhook.h"\ + "..\metamod\mreg.h"\ + "..\metamod\mutil.h"\ + "..\metamod\osdep.h"\ + "..\metamod\plinfo.h"\ + "..\metamod\sdk_util.h"\ + "..\metamod\types_meta.h"\ + ".\bot.h"\ + "c:\program files\microsoft visual studio\vc98\include\basetsd.h"\ + +NODEP_CPP_H_EXP=\ + "..\..\devtools\sdk\single-player source\common\const.h"\ + "..\..\devtools\sdk\single-player source\common\crc.h"\ + "..\..\devtools\sdk\single-player source\common\cvardef.h"\ + "..\..\devtools\sdk\single-player source\common\event_flags.h"\ + "..\..\devtools\sdk\single-player source\common\in_buttons.h"\ + "..\..\devtools\sdk\single-player source\dlls\activity.h"\ + "..\..\devtools\sdk\single-player source\dlls\cdll_dll.h"\ + "..\..\devtools\sdk\single-player source\dlls\enginecallback.h"\ + "..\..\devtools\sdk\single-player source\dlls\extdll.h"\ + "..\..\devtools\sdk\single-player source\dlls\vector.h"\ + "..\..\devtools\sdk\single-player source\engine\archtypes.h"\ + "..\..\devtools\sdk\single-player source\engine\custom.h"\ + "..\..\devtools\sdk\single-player source\engine\edict.h"\ + "..\..\devtools\sdk\single-player source\engine\eiface.h"\ + "..\..\devtools\sdk\single-player source\engine\progdefs.h"\ + "..\..\devtools\sdk\single-player source\engine\sequence.h"\ + +# End Source File +# Begin Source File + +SOURCE=.\util.cpp +DEP_CPP_UTIL_=\ + "..\metamod\dllapi.h"\ + "..\metamod\engine_api.h"\ + "..\metamod\h_export.h"\ + "..\metamod\log_meta.h"\ + "..\metamod\meta_api.h"\ + "..\metamod\mhook.h"\ + "..\metamod\mreg.h"\ + "..\metamod\mutil.h"\ + "..\metamod\osdep.h"\ + "..\metamod\plinfo.h"\ + "..\metamod\sdk_util.h"\ + "..\metamod\types_meta.h"\ + ".\bot.h"\ + ".\bot_func.h"\ + "c:\program files\microsoft visual studio\vc98\include\basetsd.h"\ + +NODEP_CPP_UTIL_=\ + "..\..\devtools\sdk\single-player source\common\const.h"\ + "..\..\devtools\sdk\single-player source\common\crc.h"\ + "..\..\devtools\sdk\single-player source\common\cvardef.h"\ + "..\..\devtools\sdk\single-player source\common\event_flags.h"\ + "..\..\devtools\sdk\single-player source\common\in_buttons.h"\ + "..\..\devtools\sdk\single-player source\common\usercmd.h"\ + "..\..\devtools\sdk\single-player source\dlls\activity.h"\ + "..\..\devtools\sdk\single-player source\dlls\cdll_dll.h"\ + "..\..\devtools\sdk\single-player source\dlls\enginecallback.h"\ + "..\..\devtools\sdk\single-player source\dlls\extdll.h"\ + "..\..\devtools\sdk\single-player source\dlls\vector.h"\ + "..\..\devtools\sdk\single-player source\engine\archtypes.h"\ + "..\..\devtools\sdk\single-player source\engine\custom.h"\ + "..\..\devtools\sdk\single-player source\engine\edict.h"\ + "..\..\devtools\sdk\single-player source\engine\eiface.h"\ + "..\..\devtools\sdk\single-player source\engine\progdefs.h"\ + "..\..\devtools\sdk\single-player source\engine\sequence.h"\ + +# End Source File +# Begin Source File + +SOURCE=.\waypoint.cpp +DEP_CPP_WAYPO=\ + "..\metamod\dllapi.h"\ + "..\metamod\engine_api.h"\ + "..\metamod\h_export.h"\ + "..\metamod\log_meta.h"\ + "..\metamod\meta_api.h"\ + "..\metamod\mhook.h"\ + "..\metamod\mreg.h"\ + "..\metamod\mutil.h"\ + "..\metamod\osdep.h"\ + "..\metamod\plinfo.h"\ + "..\metamod\sdk_util.h"\ + "..\metamod\types_meta.h"\ + ".\bot.h"\ + ".\waypoint.h"\ + "c:\program files\microsoft visual studio\vc98\include\basetsd.h"\ + +NODEP_CPP_WAYPO=\ + "..\..\devtools\sdk\single-player source\common\const.h"\ + "..\..\devtools\sdk\single-player source\common\crc.h"\ + "..\..\devtools\sdk\single-player source\common\cvardef.h"\ + "..\..\devtools\sdk\single-player source\common\event_flags.h"\ + "..\..\devtools\sdk\single-player source\common\in_buttons.h"\ + "..\..\devtools\sdk\single-player source\dlls\activity.h"\ + "..\..\devtools\sdk\single-player source\dlls\cdll_dll.h"\ + "..\..\devtools\sdk\single-player source\dlls\enginecallback.h"\ + "..\..\devtools\sdk\single-player source\dlls\extdll.h"\ + "..\..\devtools\sdk\single-player source\dlls\vector.h"\ + "..\..\devtools\sdk\single-player source\engine\archtypes.h"\ + "..\..\devtools\sdk\single-player source\engine\custom.h"\ + "..\..\devtools\sdk\single-player source\engine\edict.h"\ + "..\..\devtools\sdk\single-player source\engine\eiface.h"\ + "..\..\devtools\sdk\single-player source\engine\progdefs.h"\ + "..\..\devtools\sdk\single-player source\engine\sequence.h"\ + +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd" +# Begin Source File + +SOURCE=.\bot.h +# End Source File +# Begin Source File + +SOURCE=.\bot_client.h +# End Source File +# Begin Source File + +SOURCE=.\bot_func.h +# End Source File +# Begin Source File + +SOURCE=.\bot_weapons.h +# End Source File +# Begin Source File + +SOURCE=.\waypoint.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/hpb_bot_mm.rc b/hpb_bot_mm.rc new file mode 100644 index 0000000..3a1cf16 --- /dev/null +++ b/hpb_bot_mm.rc @@ -0,0 +1,40 @@ +// Auto-generated, so don't bother. + +#include + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 2004,3,2,0 + PRODUCTVERSION 2004,3,2,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "Comments", "\0" + VALUE "CompanyName", "\0" + VALUE "FileDescription", "metamod plugin\0" + VALUE "FileVersion", "20040302\0" + VALUE "InternalName", "\0" + VALUE "LegalCopyright", "\0" + VALUE "LegalTrademarks", "\0" + VALUE "OriginalFilename", "\0" + VALUE "PrivateBuild", "\0" + VALUE "ProductName", "\0" + VALUE "ProductVersion", "20040302\0" + VALUE "SpecialBuild", "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/tools/bot_logo/bot_logo.cpp b/tools/bot_logo/bot_logo.cpp new file mode 100644 index 0000000..05c6ce4 --- /dev/null +++ b/tools/bot_logo/bot_logo.cpp @@ -0,0 +1,1297 @@ +// +// bot_logo - botman@planethalflife.com +// +// bot_logo.cpp - misc. file I/O routines +// + +#include "windows.h" +#include "resource.h" +#include "bot_logo.h" + +#include +#include +#include + + +BYTE colors[8][3]= +{ + {255,180,24}, // orange + {0,60,255}, // blue + {0,167,255}, // light blue + {0,167,0}, // green + {255,73,0}, // red + {123,73,0}, // brown + {100,100,100}, // grey + {36,36,36} // black +}; + + +PBITMAPINFO pbmi = NULL; +HBITMAP hBitmap = NULL; +HBRUSH h_background = NULL; + +HPALETTE hPalette = NULL; +RGBQUAD OriginalPalette[256]; +RGBQUAD Palette[256]; +BOOL g_monochrome; + +BYTE *pOriginalData = NULL; +LPBYTE pBitmapData = NULL; + +CHAR g_szBitmapName[MAX_PATH]; + +BYTE g_palette_color = 0; // currently selected palette color +int offset_x = 0; +int offset_y = 0; +int size_x, size_y; + +CHAR g_szFileName[MAX_PATH]; +UINT g_ReturnStatus; +CHAR g_szMODdir[MAX_PATH]; + +TCHAR szExePathName[MAX_PATH]; // full pathname of the application +TCHAR szPath[MAX_PATH]; // path where the running application resides +TCHAR szParentPath[MAX_PATH]; // path to parent directory + + +//extern lumpinfo_t *pLumpInfo; +extern miptex_t *pMips[MAX_MIPS]; +extern int num_mips; + +extern BYTE pixdata[256]; +extern float d_red, d_green, d_blue; +extern float linearpalette[256][3]; +extern int colors_used; +extern int color_used[256]; +extern BYTE lbmpalette[256 * 3]; +extern float maxdistortion; + +BOOL changes = FALSE; + +BOOL CALLBACK DialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); +VOID NEAR GoModalDialogBoxParam( HINSTANCE hInstance, LPCSTR lpszTemplate, + HWND hWnd, DLGPROC lpDlgProc, LPARAM lParam ); +BOOL CALLBACK MODDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); + + +int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, + LPSTR lpCmdLine, int nCmdShow) +{ + HWND hWnd; + MSG msg; + int length; + + GetModuleFileName( hInstance, szExePathName, + sizeof(szExePathName)/sizeof(TCHAR) ); + length = GetPathFromFullPathName( szExePathName, szPath, + sizeof(szPath)/sizeof(TCHAR) ); + + if ((strcmp(&szPath[length-8], "\\hpb_bot") != 0) && + (strcmp(&szPath[length-8], "\\release") != 0) && + (strcmp(&szPath[length-6], "\\debug") != 0)) + { + MessageBox (NULL, "HPB bot is not installed properly", NULL, MB_OK); + return 1; + } + + GetPathFromFullPathName( szPath, szParentPath, + sizeof(szParentPath)/sizeof(TCHAR) ); + + GoModalDialogBoxParam( hInstance, MAKEINTRESOURCE( MODDLGBOX ), + NULL, (DLGPROC) MODDlgProc, 0L ); + + if (g_ReturnStatus == IDCANCEL) + return 1; + + hWnd = CreateDialog(hInstance, MAKEINTRESOURCE( IDD_DIALOG1 ), 0, DialogProc); + + if (!hWnd) + { + MessageBox (NULL, "Can't create Dialog", NULL, MB_OK); + return 1; + } + + while (GetMessage(&msg, NULL, 0, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + return msg.wParam; +} + + +void GetBitmapPosition(HWND hWnd) +{ + HWND hDlgItem; + RECT listRect; + RECT sRect; + int listbox_height; + int listbox_offset; + int dialog_height; + int dialog_offset; + int scrn_x, scrn_y; + + hDlgItem = GetDlgItem(hWnd, IDC_LIST1); + + GetWindowRect(hDlgItem, &listRect); // get the listbox screen position + GetWindowRect(hWnd, &sRect); // get the dialog box screen position + + listbox_offset = listRect.top - sRect.top; + listbox_height = listRect.bottom - listRect.top; + + dialog_height = sRect.bottom - sRect.top; + + GetClientRect(hWnd, &sRect); + + if (pbmi->bmiHeader.biHeight <= listbox_height) + { + dialog_offset = dialog_height - (sRect.bottom - sRect.top); + + scrn_x = ((sRect.right - sRect.left) / 2) - pbmi->bmiHeader.biWidth/2; + scrn_y = listbox_offset + (listbox_height/2) - + dialog_offset - (pbmi->bmiHeader.biHeight/2); + } + else + { + scrn_x = ((sRect.right - sRect.left) / 2) - pbmi->bmiHeader.biWidth/2; + scrn_y = ((sRect.bottom - sRect.top) / 2) - pbmi->bmiHeader.biHeight / 2; + } + + offset_x = scrn_x; + offset_y = scrn_y; + + size_x = pbmi->bmiHeader.biWidth; + size_y = pbmi->bmiHeader.biHeight; +} + + +void RedrawClientArea(HWND hWnd, HDC hDC) +{ + HDC hDCMem; + + if (hBitmap) + { + GetBitmapPosition(hWnd); + + hDCMem = CreateCompatibleDC(hDC); + + SelectObject (hDCMem, hBitmap); + SetMapMode (hDCMem, GetMapMode(hDC)); + + if (hPalette) + { + SelectPalette(hDC, hPalette, FALSE); + RealizePalette(hDC); + SelectPalette(hDCMem, hPalette, FALSE); + RealizePalette(hDCMem); + } + + BitBlt(hDC, offset_x, offset_y, pbmi->bmiHeader.biWidth, pbmi->bmiHeader.biHeight, + hDCMem, 0, 0, SRCCOPY); + + DeleteDC(hDCMem); + } + + return; +} + + +void MakeBitmap(HDC hDC, RGBQUAD *pPalette, BYTE *pData) +{ + int palette_size; + int dwSize; + LPLOGPALETTE lpMem; // A mem pointer to the LOGPALETTE + HANDLE hMem; // A handle to global memory + int index; + + palette_size = (1 << pbmi->bmiHeader.biBitCount); + + memcpy(pbmi->bmiColors, pPalette, palette_size * 4); + + dwSize = sizeof(LOGPALETTE) + (palette_size * sizeof(PALETTEENTRY)); + + hMem = GlobalAlloc(GHND, dwSize); + lpMem = (LPLOGPALETTE)GlobalLock(hMem); + + lpMem->palVersion = 0x0300; + lpMem->palNumEntries = palette_size; + + for (index=0; index < palette_size; index++) + { + lpMem->palPalEntry[index].peRed = pPalette[index].rgbRed; + lpMem->palPalEntry[index].peGreen = pPalette[index].rgbGreen; + lpMem->palPalEntry[index].peBlue = pPalette[index].rgbBlue; + lpMem->palPalEntry[index].peFlags = 0; + } + + if (hPalette) + DeleteObject(hPalette); + + hPalette = CreatePalette(lpMem); + + GlobalUnlock(hMem); + GlobalFree(hMem); + + if (hBitmap) + DeleteObject(hBitmap); + + hBitmap = CreateDIBSection(hDC, pbmi, DIB_RGB_COLORS, + (VOID **)&pBitmapData, NULL, 0); + + memcpy(pBitmapData, pData, pbmi->bmiHeader.biSizeImage); +} + + +BOOL CenterWindow (HWND hWnd) +{ + RECT rRect, rParentRect; + HWND hParentWnd; + int wParent, hParent, xNew, yNew; + int w, h; + + GetWindowRect (hWnd, &rRect); + w = rRect.right - rRect.left; + h = rRect.bottom - rRect.top; + + if (NULL == (hParentWnd = GetParent( hWnd ))) + hParentWnd = GetDesktopWindow(); + + GetWindowRect( hParentWnd, &rParentRect ); + + wParent = rParentRect.right - rParentRect.left; + hParent = rParentRect.bottom - rParentRect.top; + + xNew = wParent/2 - w/2 + rParentRect.left; + yNew = hParent/2 - h/2 + rParentRect.top; + + return SetWindowPos (hWnd, NULL, xNew, yNew, 0, 0, SWP_NOSIZE | SWP_NOZORDER); +} + + +BOOL CALLBACK DialogProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + HDC hDC; + TEXTMETRIC tm; + PAINTSTRUCT ps; + RECT rect; + COLORREF clrref; + LPDRAWITEMSTRUCT pDIS; + HBRUSH hbr; + int lineHeight; + HWND hWndList; + int nItem; + HANDLE hFile; + char filename[MAX_PATH]; + char file[MAX_PATH]; + char full_filename[MAX_PATH]; + int length; + int index; + HWND hItemWnd; + BYTE palette_color; + BYTE red, green, blue; + float r_scale, g_scale, b_scale; + CHAR item_name[16]; + CHAR wad_name[16]; + int x,y; + int width, height; + int difference, min_difference, min_index; + DWORD dwSize; + int mip_index; + CHAR mip_name[16]; + int value, max_value; + palette_t *pMipPalette; + char src_filename[MAX_PATH], dest_filename[MAX_PATH]; + int miplevel, mipstep; + unsigned char *pLump, *pLump0; + int mip_width, mip_height; + int count, xx, yy; + int testpixel; + + switch (message) + { + case WM_INITDIALOG: + + if (!CenterWindow( hWnd )) + return( FALSE ); + + clrref = GetSysColor(COLOR_BTNFACE); + h_background = CreateSolidBrush(clrref); + + hItemWnd = GetDlgItem(hWnd, IDC_ADD); + EnableWindow(hItemWnd, FALSE); + + hItemWnd = GetDlgItem(hWnd, IDC_REMOVE); + EnableWindow(hItemWnd, FALSE); + + hItemWnd = GetDlgItem(hWnd, IDC_COMBO1); + EnableWindow(hItemWnd, FALSE); + + g_palette_color = 0; + + SendDlgItemMessage(hWnd, IDC_COMBO1, CB_ADDSTRING, 0, (LPARAM)((LPCSTR)" ")); + SendDlgItemMessage(hWnd, IDC_COMBO1, CB_ADDSTRING, 0, (LPARAM)((LPCSTR)" ")); + SendDlgItemMessage(hWnd, IDC_COMBO1, CB_ADDSTRING, 0, (LPARAM)((LPCSTR)" ")); + SendDlgItemMessage(hWnd, IDC_COMBO1, CB_ADDSTRING, 0, (LPARAM)((LPCSTR)" ")); + SendDlgItemMessage(hWnd, IDC_COMBO1, CB_ADDSTRING, 0, (LPARAM)((LPCSTR)" ")); + SendDlgItemMessage(hWnd, IDC_COMBO1, CB_ADDSTRING, 0, (LPARAM)((LPCSTR)" ")); + SendDlgItemMessage(hWnd, IDC_COMBO1, CB_ADDSTRING, 0, (LPARAM)((LPCSTR)" ")); + SendDlgItemMessage(hWnd, IDC_COMBO1, CB_ADDSTRING, 0, (LPARAM)((LPCSTR)" ")); + SendDlgItemMessage(hWnd, IDC_COMBO1, CB_ADDSTRING, 0, (LPARAM)((LPCSTR)" ")); + + hFile = NULL; + strcpy(filename, szParentPath); + strcat(filename, "\\logos\\*.bmp"); + + // display the .bmp files in the logos folder... + while ((hFile = FindFile(hFile, file, filename)) != NULL) + { + strcpy(full_filename, szParentPath); + strcat(full_filename, "\\logos\\"); + strcat(full_filename, file); + + if (CheckBitmapFormat(full_filename)) + { + length = strlen(file); + + file[length-4] = 0; + + SendDlgItemMessage(hWnd, IDC_LIST1, LB_ADDSTRING, 0, (LPARAM)((LPCSTR)file)); + } + } + + SendDlgItemMessage(hWnd, IDC_LIST1, LB_SETCURSEL, -1, 0L); + + strcpy(filename, szParentPath); + strcat(filename, "\\"); + strcat(filename, g_szMODdir); + strcat(filename, "\\decals.wad"); + + if (!LoadWADFile(filename)) + { + strcpy(filename, szParentPath); + strcat(filename, "\\"); + strcat(filename, "valve"); + strcat(filename, "\\decals.wad"); + + if (!LoadWADFile(filename)) + { + MessageBox(hWnd, "Error loading WAD file!", "Error", MB_OK); + SendMessage(hWnd, WM_COMMAND, IDCLOSE, 0L); + + return FALSE; + } + + strcpy(g_szMODdir, "valve"); + + MessageBox(hWnd, "This MOD uses the decals.wad file\nfrom the Half-Life valve directory", "Warning", MB_OK); + } + + // display the lumps from the WAD file... + for (index=0; index < MAX_MIPS; index++) + { + if (pMips[index]) + { + if (strncmp(pMips[index]->name, "{__", 3) == 0) + { + SendDlgItemMessage(hWnd, IDC_LIST2, LB_ADDSTRING, 0, + (LPARAM)((LPCSTR)&(pMips[index]->name[3]))); + } + + if (strncmp(pMips[index]->name, "__", 2) == 0) + { + SendDlgItemMessage(hWnd, IDC_LIST2, LB_ADDSTRING, 0, + (LPARAM)((LPCSTR)&(pMips[index]->name[2]))); + } + } + } + + + SendDlgItemMessage(hWnd, IDC_LIST2, LB_SETCURSEL, -1, 0L); + + break; + + case WM_MEASUREITEM: + + hDC = GetDC( hWnd ); + GetTextMetrics( hDC, &tm ); + ReleaseDC( hWnd, hDC ); + + lineHeight = tm.tmExternalLeading + + tm.tmInternalLeading + tm.tmHeight; + + ((MEASUREITEMSTRUCT FAR *)(lParam))->itemHeight = lineHeight; + + return TRUE; + + case WM_DRAWITEM: + + pDIS = (LPDRAWITEMSTRUCT) lParam; + + // Draw the focus rectangle for an empty list box or an + // empty combo box to indicate that the control has the focus + + if ((int)(pDIS->itemID) < 0) + { +// if ((pDIS->itemState) & (ODS_FOCUS)) +// DrawFocusRect (pDIS->hDC, &pDIS->rcItem); + return TRUE; + } + + if ((pDIS->itemAction == ODA_SELECT) || + (pDIS->itemAction == ODA_DRAWENTIRE)) + { + hbr = NULL; + + palette_color = 0; + + if ((pDIS->itemID >= 1) && (pDIS->itemID <= 8)) + { + palette_color = pDIS->itemID; + + red = colors[palette_color-1][0]; + green = colors[palette_color-1][1]; + blue = colors[palette_color-1][2]; + +// hDC = CreateCompatibleDC(pDIS->hDC); + hbr = CreateSolidBrush(RGB(red,green,blue)); + + SelectObject(pDIS->hDC, hbr); + Rectangle(pDIS->hDC, pDIS->rcItem.left, pDIS->rcItem.top, + pDIS->rcItem.right, pDIS->rcItem.bottom); + DeleteObject(hbr); +// DeleteDC(hDC); + } + +// if (pDIS->itemState & ODS_FOCUS) +// DrawFocusRect (pDIS->hDC, &pDIS->rcItem); + +// if (pDIS->itemState & ODS_SELECTED) +// { +// DrawFocusRect(pDIS->hDC, &pDIS->rcItem); +// } + } +// else if (pDIS->itemAction == ODA_FOCUS) +// { +// DrawFocusRect (pDIS->hDC, &pDIS->rcItem); +// } + + return TRUE; + + case WM_PAINT: + + hDC = BeginPaint(hWnd, &ps); + + RedrawClientArea(hWnd, hDC); + + EndPaint(hWnd, &ps); + + return TRUE; + + case WM_COMMAND: + + switch (LOWORD(wParam)) + { + case IDC_LIST1: + if (HIWORD(wParam) == LBN_SELCHANGE) + { + index = SendDlgItemMessage(hWnd, IDC_LIST1, LB_GETCURSEL, 0, 0L); + + SendDlgItemMessage(hWnd, IDC_LIST1, LB_GETTEXT, index, (LPARAM)((LPSTR)filename)); + + strcpy(g_szBitmapName, filename); + + strcpy(g_szFileName, szParentPath); + strcat(g_szFileName, "\\logos\\"); + strcat(g_szFileName, filename); + strcat(g_szFileName, ".bmp"); + + hDC = GetDC(hWnd); + + if (hBitmap) + { + if ((pbmi->bmiHeader.biHeight > 128) || + (pbmi->bmiHeader.biWidth > 128)) + { + GetClientRect(hWnd, &rect); + InvalidateRect(hWnd, &rect, TRUE); + } + } + + LoadBitmapFromFile(hDC, g_szFileName); + + if ((hBitmap) && (h_background)) + { + rect.left = offset_x; + rect.top = offset_y; + rect.right = offset_x + size_x; + rect.bottom = offset_y + size_y; + + FillRect(hDC, &rect, h_background); + } + + MakeBitmap(hDC, OriginalPalette, pOriginalData); + + ReleaseDC(hWnd, hDC); + + + SendDlgItemMessage(hWnd, IDC_LIST2, LB_SETCURSEL, -1, 0L); + + SendDlgItemMessage(hWnd, IDC_COMBO1, CB_SETCURSEL, 0, 0L); + + g_palette_color = 0; + + hItemWnd = GetDlgItem(hWnd, IDC_ADD); + EnableWindow(hItemWnd, TRUE); + + hItemWnd = GetDlgItem(hWnd, IDC_REMOVE); + EnableWindow(hItemWnd, FALSE); + + hItemWnd = GetDlgItem(hWnd, IDC_COMBO1); + EnableWindow(hItemWnd, TRUE); + + // Force an update of the screen + GetBitmapPosition(hWnd); + + rect.left = offset_x; + rect.top = offset_y; + rect.right = offset_x + size_x; + rect.bottom = offset_y + size_y; + + InvalidateRect(hWnd, &rect, TRUE); + UpdateWindow(hWnd); + } + + return TRUE; + + case IDC_LIST2: + if (HIWORD(wParam) == LBN_SELCHANGE) + { + index = SendDlgItemMessage(hWnd, IDC_LIST2, LB_GETCURSEL, 0, 0L); + + SendDlgItemMessage(hWnd, IDC_LIST2, LB_GETTEXT, index, (LPARAM)((LPSTR)item_name)); + + strcpy(wad_name, "{__"); + strcat(wad_name, item_name); + + // find the correct index based on name... + + index = 0; + while (index < MAX_MIPS) + { + if (pMips[index]) + { + if (strcmp(wad_name, pMips[index]->name) == 0) + break; + } + + index++; + } + + if (index == MAX_MIPS) + { + strcpy(wad_name, "__"); + strcat(wad_name, item_name); + + // find the correct index based on name... + + index = 0; + while (index < MAX_MIPS) + { + if (pMips[index]) + { + if (strcmp(wad_name, pMips[index]->name) == 0) + break; + } + + index++; + } + } + + if (index == MAX_MIPS) + { + MessageBox(hWnd, "Error finding mip!", "Error", MB_OK); + return TRUE; + } + + hDC = GetDC(hWnd); + + if (hBitmap) + { + if ((pbmi->bmiHeader.biHeight > 128) || + (pbmi->bmiHeader.biWidth > 128)) + { + GetClientRect(hWnd, &rect); + InvalidateRect(hWnd, &rect, TRUE); + } + } + + LoadBitmapFromMip(index); + + if ((hBitmap) && (h_background)) + { + rect.left = offset_x; + rect.top = offset_y; + rect.right = offset_x + size_x; + rect.bottom = offset_y + size_y; + + FillRect(hDC, &rect, h_background); + } + + memcpy(Palette, OriginalPalette, (1 << pbmi->bmiHeader.biBitCount) * 4); + + if ((Palette[255].rgbRed != 0) || + (Palette[255].rgbGreen != 0) || + (Palette[255].rgbBlue != 255)) + { + r_scale = (float)Palette[255].rgbRed / (float)255.0; + g_scale = (float)Palette[255].rgbGreen / (float)255.0; + b_scale = (float)Palette[255].rgbBlue / (float)255.0; + + // set palette 255 index back to pure white + Palette[255].rgbRed = 255; + Palette[255].rgbGreen = 255; + Palette[255].rgbBlue = 255; + + for (index = 0; index < (1 << pbmi->bmiHeader.biBitCount); index++) + { + Palette[index].rgbRed = (BYTE)((float)Palette[index].rgbRed * r_scale); + Palette[index].rgbGreen = (BYTE)((float)Palette[index].rgbGreen * g_scale); + Palette[index].rgbBlue = (BYTE)((float)Palette[index].rgbBlue * b_scale); + Palette[index].rgbReserved = 0; + } + } + + MakeBitmap(hDC, Palette, pOriginalData); + + ReleaseDC(hWnd, hDC); + + SendDlgItemMessage(hWnd, IDC_LIST1, LB_SETCURSEL, -1, 0L); + + SendDlgItemMessage(hWnd, IDC_COMBO1, CB_SETCURSEL, 0, 0L); + + g_palette_color = 0; + + hItemWnd = GetDlgItem(hWnd, IDC_ADD); + EnableWindow(hItemWnd, FALSE); + + hItemWnd = GetDlgItem(hWnd, IDC_REMOVE); + EnableWindow(hItemWnd, TRUE); + + hItemWnd = GetDlgItem(hWnd, IDC_COMBO1); + EnableWindow(hItemWnd, FALSE); + + // Force an update of the screen + GetBitmapPosition(hWnd); + + rect.left = offset_x; + rect.top = offset_y; + rect.right = offset_x + size_x; + rect.bottom = offset_y + size_y; + + InvalidateRect(hWnd, &rect, TRUE); + UpdateWindow(hWnd); + } + + return TRUE; + + case IDC_COMBO1: + switch(HIWORD(wParam)) + { + case CBN_SELCHANGE: + hWndList = GetDlgItem(hWnd, IDC_COMBO1); + nItem = SendMessage(hWndList, CB_GETCURSEL, 0, 0); + + if (nItem == 0) + { + // restore palette to original color... + hDC = GetDC(hWnd); + MakeBitmap(hDC, OriginalPalette, pOriginalData); + ReleaseDC(hWnd, hDC); + } + else + { + g_palette_color = nItem; + + red = colors[g_palette_color-1][0]; + green = colors[g_palette_color-1][1]; + blue = colors[g_palette_color-1][2]; + + // set the palette color + hDC = GetDC(hWnd); + + memcpy(Palette, OriginalPalette, (1 << pbmi->bmiHeader.biBitCount) * 4); + + r_scale = (float)red / (float)255.0; + g_scale = (float)green / (float)255.0; + b_scale = (float)blue / (float)255.0; + + for (index = 0; index < (1 << pbmi->bmiHeader.biBitCount); index++) + { + Palette[index].rgbRed = (BYTE)((float)Palette[index].rgbRed * r_scale); + Palette[index].rgbGreen = (BYTE)((float)Palette[index].rgbGreen * g_scale); + Palette[index].rgbBlue = (BYTE)((float)Palette[index].rgbBlue * b_scale); + Palette[index].rgbReserved = 0; + } + + MakeBitmap(hDC, Palette, pOriginalData); + + ReleaseDC(hWnd, hDC); + } + + // Force an update of the screen + GetBitmapPosition(hWnd); + + rect.left = offset_x; + rect.top = offset_y; + rect.right = offset_x + size_x; + rect.bottom = offset_y + size_y; + + InvalidateRect(hWnd, &rect, TRUE); + UpdateWindow(hWnd); + } + + return TRUE; + + case IDC_ADD: + + if (num_mips >= MAX_MIPS) + return TRUE; + + changes = TRUE; + + width = pbmi->bmiHeader.biWidth; + height = pbmi->bmiHeader.biHeight; + + // assume all of the colors are used for mip mapping... + colors_used = 256; + for (x=0; x < 256; x++) color_used[x] = 1; + + // write the bitmap data and palette to the lump array... + index = 0; + while (pMips[index]) + index++; + + mip_index = index; + + dwSize = sizeof(miptex_t) + (width * height) + + (width * height / 4) + (width * height / 16) + + (width * height / 64) + sizeof(palette_t); + + pMips[mip_index] = (miptex_t *)LocalAlloc(LPTR, dwSize); + + pMips[mip_index]->height = pbmi->bmiHeader.biHeight; + pMips[mip_index]->width = pbmi->bmiHeader.biWidth; + pMips[mip_index]->offsets[0] = sizeof(miptex_t); + pMips[mip_index]->offsets[1] = pMips[mip_index]->offsets[0] + width * height; + pMips[mip_index]->offsets[2] = pMips[mip_index]->offsets[1] + (width * height) / 4; + pMips[mip_index]->offsets[3] = pMips[mip_index]->offsets[2] + (width * height) / 16; + + pLump = (BYTE *)pMips[mip_index] + pMips[mip_index]->offsets[0]; + + index = height - 1; + + for (y=0; y < height; y++) + { + memcpy(pLump+(width * y), pOriginalData+(width * index), width); + index--; + } + + pMipPalette = (palette_t *)((char *)pMips[mip_index] + + pMips[mip_index]->offsets[3] + (width * height / 64)); + + pMipPalette->palette_size = 256; + + for (index = 0; index < 256; index++) + { + pMipPalette->palette[index * 3 + 0] = OriginalPalette[index].rgbRed; + pMipPalette->palette[index * 3 + 1] = OriginalPalette[index].rgbGreen; + pMipPalette->palette[index * 3 + 2] = OriginalPalette[index].rgbBlue; + } + + pMipPalette->padding = 0; + + memcpy(lbmpalette, pMipPalette->palette, 256 * 3); + + // calculate gamma corrected linear palette + for (x = 0; x < 256; x++) + { + for (y = 0; y < 3; y++) + { + float f; + f = (float)(lbmpalette[x*3+y] / 255.0); +// linearpalette[x][y] = (float)pow(f, 2.2 ); // assume textures are done at 2.2, we want to remap them at 1.0 + linearpalette[x][y] = (float)pow(f, 1.0); + } + } + + pLump0 = (BYTE *)pMips[mip_index] + pMips[mip_index]->offsets[0]; + + maxdistortion = 0; + + for (miplevel = 1; miplevel < 4; miplevel++) + { + int pixTest; + d_red = d_green = d_blue = 0; // no distortion yet + + pLump = (BYTE *)pMips[mip_index] + pMips[mip_index]->offsets[miplevel]; + + mipstep = 1 << miplevel; + pixTest = (int)((float)(mipstep * mipstep) * 0.4); // 40% of pixels + + for (y = 0; y < height ; y += mipstep) + { + for (x = 0 ; x < width ; x += mipstep) + { + count = 0; + + for (yy = 0; yy < mipstep; yy++) + { + for (xx = 0; xx < mipstep; xx++) + { + testpixel = pLump0[(y+yy)*width + x + xx ]; + + // If this isn't a transparent pixel, add it in to the image filter + if ( testpixel != 255 ) + { + pixdata[count] = testpixel; + count++; + } + } + } + + if ( count <= pixTest ) // Solid pixels account for < 40% of this pixel, make it transparent + { + *pLump++ = 255; + } + else + { + *pLump++ = AveragePixels(count); + } + } + } + } + + + if (!g_monochrome) + { + // assume none of the colors are used for palette 255 setting... + colors_used = 0; + + for (x=0; x < 256; x++) + color_used[x] = 0; + + for (x=0; x < width; x++) + { + for (y=0; y < height; y++) + { + if (!color_used[pOriginalData[x*height + y]]) + { + color_used[pOriginalData[x*height + y]] = 1; + colors_used++; + } + } + } + + // if all colors used and index 255 is NOT pure blue... + if ((colors_used == 256) && + ((OriginalPalette[255].rgbRed !=0) || + (OriginalPalette[255].rgbGreen != 0) || + (OriginalPalette[255].rgbBlue != 255))) + { + // replace index 255 color with closest color... + + red = OriginalPalette[255].rgbRed; + green = OriginalPalette[255].rgbGreen; + blue = OriginalPalette[255].rgbBlue; + + min_difference = 256*3; + min_index = 0; + + for (index=0; index < 255; index++) + { + difference = abs(OriginalPalette[index].rgbRed - red) + + abs(OriginalPalette[index].rgbGreen - green) + + abs(OriginalPalette[index].rgbBlue - blue); + + if (difference < min_difference) + { + min_difference = difference; + min_index = index; + } + } + + for (miplevel = 0; miplevel < 4; miplevel++) + { + mipstep = 1 << miplevel; + pLump = (BYTE *)pMips[mip_index] + pMips[mip_index]->offsets[miplevel]; + + mip_width = width/mipstep; + mip_height = height/mipstep; + + for (x=0; x < mip_width; x++) + { + for (y=0; y < mip_height; y++) + { + if (pLump[x*mip_height + y] == 255) + pLump[x*mip_height + y] = min_index; + } + } + } + + colors_used--; + color_used[255] = 0; + } + + if ((colors_used < 256) && (color_used[255])) + { + // move index 255 to first unused position... + + index = 0; + while (color_used[index]) + index++; + + pMipPalette->palette[index * 3 + 0] = OriginalPalette[255].rgbRed; + pMipPalette->palette[index * 3 + 1] = OriginalPalette[255].rgbGreen; + pMipPalette->palette[index * 3 + 2] = OriginalPalette[255].rgbBlue; + + for (miplevel = 0; miplevel < 4; miplevel++) + { + mipstep = 1 << miplevel; + pLump = (BYTE *)pMips[mip_index] + pMips[mip_index]->offsets[miplevel]; + + mip_width = width/mipstep; + mip_height = height/mipstep; + + for (x=0; x < mip_width; x++) + { + for (y=0; y < mip_height; y++) + { + if (pLump[x*mip_height + y] == 255) + pLump[x*mip_height + y] = index; + } + } + } + } + + pMipPalette->palette[255 * 3 + 0] = 0; + pMipPalette->palette[255 * 3 + 1] = 0; + pMipPalette->palette[255 * 3 + 2] = 255; + } + + if (g_palette_color != 0) + { + pMipPalette->palette[255 * 3 + 0] = colors[g_palette_color-1][0]; + pMipPalette->palette[255 * 3 + 1] = colors[g_palette_color-1][1]; + pMipPalette->palette[255 * 3 + 2] = colors[g_palette_color-1][2]; + } + + + if (strlen(g_szBitmapName) > 9) + g_szBitmapName[10] = 0; + + if (g_monochrome) + strcpy(mip_name, "__"); + else + strcpy(mip_name, "{__"); + + strcat(mip_name, g_szBitmapName); + + length = strlen(mip_name); + + max_value = 0; + + // find the highest number for this name... + for (index = 0; index < num_mips; index++) + { + if (pMips[index]) + { + if (strncmp(pMips[index]->name, mip_name, length) == 0) + { + sscanf(&(pMips[index]->name[length+1]), "%d", &value); + + if (value > max_value) + max_value = value; + } + } + } + + max_value++; + + if (max_value > 99) + { + MessageBox(hWnd, "Too many mips for this file!", "Error", MB_OK); + LocalFree(pMips[mip_index]); + pMips[mip_index] = NULL; + return TRUE; + } + + sprintf(pMips[mip_index]->name, "%s_%02d", mip_name, max_value); + + if (g_monochrome) + SendDlgItemMessage(hWnd, IDC_LIST2, LB_ADDSTRING, 0, + (LPARAM)((LPCSTR)&(pMips[mip_index]->name[2]))); + else + SendDlgItemMessage(hWnd, IDC_LIST2, LB_ADDSTRING, 0, + (LPARAM)((LPCSTR)&(pMips[mip_index]->name[3]))); + + num_mips++; + + + return TRUE; + + case IDC_REMOVE: + + changes = TRUE; + + index = SendDlgItemMessage(hWnd, IDC_LIST2, LB_GETCURSEL, 0, 0L); + + SendDlgItemMessage(hWnd, IDC_LIST2, LB_GETTEXT, index, (LPARAM)((LPSTR)item_name)); + + SendDlgItemMessage(hWnd, IDC_LIST2, LB_DELETESTRING, index, 0L); + + strcpy(wad_name, "{__"); + strcat(wad_name, item_name); + + // find the correct index based on name... + + index = 0; + while (index < MAX_MIPS) + { + if (pMips[index]) + { + if (strcmp(wad_name, pMips[index]->name) == 0) + break; + } + + index++; + } + + if (index == MAX_MIPS) + { + strcpy(wad_name, "__"); + strcat(wad_name, item_name); + + // find the correct index based on name... + + index = 0; + while (index < MAX_MIPS) + { + if (pMips[index]) + { + if (strcmp(wad_name, pMips[index]->name) == 0) + break; + } + + index++; + } + } + + if (index == MAX_MIPS) + { + MessageBox(hWnd, "Error finding mip!", "Error", MB_OK); + return FALSE; + } + + LocalFree(pMips[index]); + + pMips[index] = NULL; + + + hDC = GetDC(hWnd); + + if (hBitmap) + { + if ((pbmi->bmiHeader.biHeight > 128) || + (pbmi->bmiHeader.biWidth > 128)) + { + GetClientRect(hWnd, &rect); + InvalidateRect(hWnd, &rect, TRUE); + } + } + + if ((hBitmap) && (h_background)) + { + rect.left = offset_x; + rect.top = offset_y; + rect.right = offset_x + size_x; + rect.bottom = offset_y + size_y; + + FillRect(hDC, &rect, h_background); + InvalidateRect(hWnd, &rect, TRUE); + } + + ReleaseDC(hWnd, hDC); + + if (hBitmap) + DeleteObject(hBitmap); + + UpdateWindow(hWnd); + + + SendDlgItemMessage(hWnd, IDC_LIST1, LB_SETCURSEL, -1, 0L); + SendDlgItemMessage(hWnd, IDC_LIST2, LB_SETCURSEL, -1, 0L); + SendDlgItemMessage(hWnd, IDC_COMBO1, CB_SETCURSEL, 0, 0L); + + g_palette_color = 0; + + hItemWnd = GetDlgItem(hWnd, IDC_ADD); + EnableWindow(hItemWnd, FALSE); + + hItemWnd = GetDlgItem(hWnd, IDC_REMOVE); + EnableWindow(hItemWnd, FALSE); + + hItemWnd = GetDlgItem(hWnd, IDC_COMBO1); + EnableWindow(hItemWnd, FALSE); + + return TRUE; + + case IDCLOSE: + + if (changes) + { + if (MessageBox(hWnd, "Do you wish to save your changes?", "Warning", MB_YESNO) == IDYES) + { + // check if backup file is needed + + strcpy(src_filename, szParentPath); + strcat(src_filename, "\\"); + strcat(src_filename, g_szMODdir); + strcat(src_filename, "\\decals.wad"); + + strcpy(dest_filename, szParentPath); + strcat(dest_filename, "\\"); + strcat(dest_filename, g_szMODdir); + strcat(dest_filename, "\\decals_old.wad"); + + if (CopyFile(src_filename, dest_filename, TRUE)) + MessageBox(hWnd, "A backup copy of decals.wad was\ncreated called decals_old.wad", "Warning", MB_OK); + + if (!WriteWADFile(src_filename)) + MessageBox(hWnd, "An error occured writing decals.wad", "Error", MB_OK); + } + } + + for (index = 0; index < MAX_MIPS; index++) + { + if (pMips[index]) + LocalFree(pMips[index]); + } + + if (pbmi) + LocalFree(pbmi); + + if (pOriginalData) + LocalFree(pOriginalData); + + if (hBitmap) + DeleteObject(hBitmap); + + if (hPalette) + DeleteObject(hPalette); + + if (h_background) + DeleteObject(h_background); + + PostQuitMessage(0); + return TRUE; + } + + break; + + case WM_CLOSE: + + SendMessage(hWnd, WM_COMMAND, IDCLOSE, 0L); + + break; + } + + return FALSE; +} + + +VOID NEAR GoModalDialogBoxParam( HINSTANCE hInstance, LPCSTR lpszTemplate, + HWND hWnd, DLGPROC lpDlgProc, LPARAM lParam ) +{ + DLGPROC lpProcInstance ; + + lpProcInstance = (DLGPROC) MakeProcInstance( (FARPROC) lpDlgProc, + hInstance ) ; + + DialogBoxParam( hInstance, lpszTemplate, hWnd, lpProcInstance, lParam ) ; + + FreeProcInstance( (FARPROC) lpProcInstance ) ; + +} + + +BOOL CALLBACK MODDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + HANDLE hFile = NULL; + char path[MAX_PATH]; + char dirname[MAX_PATH]; + char filename[MAX_PATH]; + int i; + int count = 0; + struct stat stat_str; + + switch (uMsg) + { + case WM_INITDIALOG: + + if (!CenterWindow( hDlg )) + return( FALSE ); + + strcpy(path, szParentPath); + strcat(path, "\\*"); + + while ((hFile = FindDirectory(hFile, dirname, path)) != NULL) + { + if ((strcmp(dirname, ".") == 0) || (strcmp(dirname, "..") == 0)) + continue; + + strcpy(filename, szParentPath); + strcat(filename, "\\"); + strcat(filename, dirname); + strcat(filename, "\\liblist.gam"); + + if (stat(filename, &stat_str) == 0) + { + SendDlgItemMessage(hDlg, IDC_MOD, CB_ADDSTRING, 0, (LPARAM)((LPCSTR)dirname)); + count++; + } + } + + if (count == 0) + { + MessageBox (NULL, "Can't find any MOD directories\nAre you sure you have things installed right?", NULL, MB_OK); + g_ReturnStatus = IDCANCEL; + EndDialog(hDlg, TRUE); + return ( TRUE ); + } + + SendDlgItemMessage(hDlg, IDC_MOD, CB_SETCURSEL, 0, 0L); + + return TRUE; + + case WM_COMMAND: + switch ((WORD) wParam) + { + case IDOK: + i = SendDlgItemMessage(hDlg, IDC_MOD, CB_GETCURSEL, 0, 0L); + + SendDlgItemMessage(hDlg, IDC_MOD, CB_GETLBTEXT, i, (LPARAM)((LPCSTR)g_szMODdir)); + + g_ReturnStatus = IDOK; + EndDialog(hDlg, TRUE); + return ( TRUE ); + + case IDCANCEL: + g_ReturnStatus = IDCANCEL; + EndDialog(hDlg, TRUE); + return ( TRUE ); + } + break; + } + + return FALSE; +} diff --git a/tools/bot_logo/bot_logo.dsp b/tools/bot_logo/bot_logo.dsp new file mode 100644 index 0000000..379c814 --- /dev/null +++ b/tools/bot_logo/bot_logo.dsp @@ -0,0 +1,123 @@ +# Microsoft Developer Studio Project File - Name="bot_logo" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Application" 0x0101 + +CFG=bot_logo - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "bot_logo.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "bot_logo.mak" CFG="bot_logo - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "bot_logo - Win32 Release" (based on "Win32 (x86) Application") +!MESSAGE "bot_logo - Win32 Debug" (based on "Win32 (x86) Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "bot_logo - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /Yu"stdafx.h" /FD /c +# ADD CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /FD /c +# SUBTRACT CPP /YX /Yc /Yu +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 + +!ELSEIF "$(CFG)" == "bot_logo - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /Yu"stdafx.h" /FD /GZ /c +# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /FD /GZ /c +# SUBTRACT CPP /YX /Yc /Yu +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "bot_logo - Win32 Release" +# Name "bot_logo - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\bot_logo.cpp +# End Source File +# Begin Source File + +SOURCE=.\bot_logo.rc +# End Source File +# Begin Source File + +SOURCE=.\file.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\bot_logo.h +# End Source File +# Begin Source File + +SOURCE=.\resource.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/tools/bot_logo/bot_logo.h b/tools/bot_logo/bot_logo.h new file mode 100644 index 0000000..c267f48 --- /dev/null +++ b/tools/bot_logo/bot_logo.h @@ -0,0 +1,100 @@ +#ifndef FUNCS_H +#define FUNCS_H + +/* + + WAD3 Header + { + char identification[4]; // should be WAD3 or 3DAW + int numlumps; + int infotableofs; + } + + Mip section + { + First mip + { + char name[16]; + unsigned width, height; + unsigned offsets[4]; // mip offsets relative to start of this mip + byte first_mip[width*height]; + byte second_mip[width*height/4]; + byte third_mip[width*height/16]; + byte fourth_mip[width*height/64]; + short int palette_size; + byte palette[palette_size*3]; + short int padding; + } + [...] + Last mip + } + + Lump table + { + First lump entry + { + int filepos; + int disksize; + int size; // uncompressed + char type; // 0x43 - WAD3 mip (Half-Life) + char compression; + char pad1, pad2; + char name[16]; // must be null terminated + } + [...] + Last lump entry + } + +*/ + +typedef struct +{ + char identification[4]; // should be WAD2 (or 2DAW) or WAD3 (or 3DAW) + int numlumps; + int infotableofs; +} wadinfo_t; + +typedef struct +{ + int filepos; + int disksize; + int size; // uncompressed + char type; + char compression; + char pad1, pad2; + char name[16]; // must be null terminated +} lumpinfo_t; + + +#define MAX_MIPS 1000 +#define MIPLEVELS 4 + +typedef struct miptex_s +{ + char name[16]; + unsigned width, height; + unsigned offsets[MIPLEVELS]; // four mip maps stored +} miptex_t; + +#define PALETTE_SIZE 256 + +typedef struct palette_s +{ + short int palette_size; + unsigned char palette[PALETTE_SIZE * 3]; + short int padding; +} palette_t; + + +UINT GetPathFromFullPathName( LPCTSTR lpFullPathName, LPTSTR lpPathBuffer, + int nPathBufferLength ); +HANDLE FindFile(HANDLE hFile, CHAR *file, CHAR *filename); +HANDLE FindDirectory(HANDLE hFile, CHAR *dirname, CHAR *filename); +BOOL CheckBitmapFormat(LPTSTR lpszFileName); +BOOL LoadBitmapFromFile(HDC hDC, LPTSTR lpszFileName); +BOOL LoadWADFile(LPTSTR lpszFileName); +BOOL LoadBitmapFromMip(int index); +BOOL WriteWADFile(LPTSTR lpszFileName); +BYTE AveragePixels (int count); + +#endif \ No newline at end of file diff --git a/tools/bot_logo/bot_logo.rc b/tools/bot_logo/bot_logo.rc new file mode 100644 index 0000000..459ec82 --- /dev/null +++ b/tools/bot_logo/bot_logo.rc @@ -0,0 +1,107 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#define APSTUDIO_HIDDEN_SYMBOLS +#include "windows.h" +#undef APSTUDIO_HIDDEN_SYMBOLS +#include "resource.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_DIALOG1 DIALOG DISCARDABLE 0, 0, 260, 170 +STYLE DS_MODALFRAME | WS_MINIMIZEBOX | WS_POPUP | WS_VISIBLE | WS_CAPTION | + WS_SYSMENU +CAPTION "HPB bot Logo Manager" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "Add",IDC_ADD,72,124,50,14 + PUSHBUTTON "Remove",IDC_REMOVE,72,143,50,14 + COMBOBOX IDC_COMBO1,138,124,50,77,CBS_DROPDOWNLIST | + CBS_OWNERDRAWFIXED | WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "Exit",IDCLOSE,138,143,50,14 + LISTBOX IDC_LIST1,15,25,60,83,LBS_SORT | LBS_NOINTEGRALHEIGHT | + WS_VSCROLL | WS_TABSTOP + LISTBOX IDC_LIST2,185,25,60,83,LBS_SORT | LBS_NOINTEGRALHEIGHT | + WS_VSCROLL | WS_TABSTOP + CTEXT "Logo BMP Files",IDC_STATIC,20,10,50,8,SS_CENTERIMAGE + CTEXT "Bot Logos",IDC_STATIC,190,10,50,8,SS_CENTERIMAGE +END + +MODDLGBOX DIALOG DISCARDABLE 0, 0, 160, 81 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION +CAPTION "Choose MOD" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "OK",IDOK,22,60,50,14 + PUSHBUTTON "Cancel",IDCANCEL,88,60,50,14 + COMBOBOX IDC_MOD,45,36,68,48,CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | + WS_TABSTOP + LTEXT "Choose the MOD that you wish to\ncreate bot custom spray logos for...", + IDC_STATIC,22,12,115,18 +END + + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#define APSTUDIO_HIDDEN_SYMBOLS\r\n" + "#include ""windows.h""\r\n" + "#undef APSTUDIO_HIDDEN_SYMBOLS\r\n" + "#include ""resource.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "\r\n" + "\0" +END + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +#endif // APSTUDIO_INVOKED + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/tools/bot_logo/file.cpp b/tools/bot_logo/file.cpp new file mode 100644 index 0000000..95da88e --- /dev/null +++ b/tools/bot_logo/file.cpp @@ -0,0 +1,643 @@ +// +// bot_logo - botman@planethalflife.com +// +// file.cpp - misc. file I/O routines +// + +#include "windows.h" +#include "resource.h" + +#include + +#include "bot_logo.h" + + +int num_mips = 0; +miptex_t *pMips[MAX_MIPS]; + +BYTE pixdata[256]; +float linearpalette[256][3]; +BYTE lbmpalette[256 * 3]; +float d_red, d_green, d_blue; +int colors_used; +int color_used[256]; +float maxdistortion; + +extern PBITMAPINFO pbmi; + +extern RGBQUAD OriginalPalette[256]; +extern RGBQUAD Palette[256]; + +extern BYTE *pOriginalData; +extern BOOL g_monochrome; + + +UINT GetPathFromFullPathName( LPCTSTR lpFullPathName, LPTSTR lpPathBuffer, + int nPathBufferLength ) +{ + int nLength; + int i; + + if ((nLength = lstrlen( lpFullPathName )) > nPathBufferLength) + return( nLength ); + + lstrcpy( lpPathBuffer, lpFullPathName ); + + for( i = lstrlen( lpPathBuffer ); + (lpPathBuffer[i] != '\\') && (lpPathBuffer[i] != ':'); i-- ) + ; + if (':' == lpPathBuffer[i]) + lpPathBuffer[i+1] = '\0'; + else + lpPathBuffer[i] = '\0'; + + nLength = i; + + for (i = 0; i < nLength; i++) + lpPathBuffer[i] = tolower(lpPathBuffer[i]); + + return( (UINT) nLength ); +} + +HANDLE FindFile(HANDLE hFile, CHAR *file, CHAR *filename) +{ + WIN32_FIND_DATA pFindFileData; + + if (hFile == NULL) + { + hFile = FindFirstFile(filename, &pFindFileData); + + if (hFile == INVALID_HANDLE_VALUE) + hFile = NULL; + } + else + { + if (FindNextFile(hFile, &pFindFileData) == 0) + { + FindClose(hFile); + hFile = NULL; + } + } + + if (hFile != NULL) + strcpy(file, pFindFileData.cFileName); + + return hFile; +} + + +HANDLE FindDirectory(HANDLE hFile, CHAR *dirname, CHAR *filename) +{ + WIN32_FIND_DATA pFindFileData; + + dirname[0] = 0; + + if (hFile == NULL) + { + hFile = FindFirstFile(filename, &pFindFileData); + + if (hFile == INVALID_HANDLE_VALUE) + hFile = NULL; + + while (pFindFileData.dwFileAttributes != FILE_ATTRIBUTE_DIRECTORY) + { + if (FindNextFile(hFile, &pFindFileData) == 0) + { + FindClose(hFile); + hFile = NULL; + return hFile; + } + } + + strcpy(dirname, pFindFileData.cFileName); + + return hFile; + } + else + { + if (FindNextFile(hFile, &pFindFileData) == 0) + { + FindClose(hFile); + hFile = NULL; + return hFile; + } + + while (pFindFileData.dwFileAttributes != FILE_ATTRIBUTE_DIRECTORY) + { + if (FindNextFile(hFile, &pFindFileData) == 0) + { + FindClose(hFile); + hFile = NULL; + return hFile; + } + } + + strcpy(dirname, pFindFileData.cFileName); + + return hFile; + } +} + + +BOOL CheckBitmapFormat(LPTSTR lpszFileName) +{ + HANDLE hFile; // Bitmap filename + BITMAPINFOHEADER sBIH; // Used if this is a Windows bitmap + BITMAPFILEHEADER sBFH; // This is always filled in + DWORD dwDummy; + + hFile = CreateFile(lpszFileName, GENERIC_READ, FILE_SHARE_READ, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); + + if(hFile == INVALID_HANDLE_VALUE) + return FALSE; + + if (!ReadFile(hFile, &sBFH, sizeof(BITMAPFILEHEADER), &dwDummy, NULL)) + return FALSE; + + if(sBFH.bfType != (UINT)0x4D42) + return FALSE; + + SetFilePointer(hFile, sizeof(BITMAPFILEHEADER), NULL, FILE_BEGIN); + + if (!ReadFile(hFile, &sBIH, sizeof(BITMAPINFOHEADER), &dwDummy, NULL)) + return FALSE; + + if ((sBIH.biBitCount != 8) || (sBIH.biCompression != BI_RGB)) + return FALSE; + + if (((sBIH.biWidth % 16) != 0) || ((sBIH.biHeight % 16) != 0)) + return FALSE; + + if ((sBIH.biWidth > 256) || (sBIH.biHeight > 256)) + return FALSE; + + CloseHandle(hFile); + + return TRUE; +} + + +BOOL LoadBitmapFromFile(HDC hDC, LPTSTR lpszFileName) +{ + HANDLE hFile; // Bitmap filename + DWORD dwBitmapType, // The type of the bitmap + dwSize; // A size of memory to allocate + int nPalEntries; // # of entries in the palette + BITMAPINFOHEADER sBIH; // Used if this is a Windows bitmap + BITMAPFILEHEADER sBFH; // This is always filled in + DWORD dwDummy; + BOOL status; + int index; + + status = FALSE; + + hFile = CreateFile(lpszFileName, GENERIC_READ, FILE_SHARE_READ, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); + + if(hFile != INVALID_HANDLE_VALUE) + { + // Read the BITMAPFILEHEADER from the file + if (ReadFile(hFile, &sBFH, sizeof(BITMAPFILEHEADER), &dwDummy, NULL)) + { + // Any valid bitmap file should have filetype = 'BM' + if(sBFH.bfType == (UINT)0x4D42) + { + ReadFile(hFile, &dwBitmapType, sizeof(DWORD), &dwDummy, NULL); + + SetFilePointer(hFile, sizeof(BITMAPFILEHEADER), NULL, FILE_BEGIN); + + // Read in the INFOHEADER + ReadFile(hFile, &sBIH, sizeof(BITMAPINFOHEADER), &dwDummy, NULL); + + // Figure out the number of entries in the palette + // i.e. if the number of Bits per pixel (biBitCount) is 8, + // the number of palette entries will be 1 << 8 = 256 + + nPalEntries = ((UINT)1 << sBIH.biBitCount); + + // If the bitmap is not compressed, set the BI_RGB flag. + sBIH.biCompression = BI_RGB; + + // Compute the number of bytes in the array of color + // indices and store the result in biSizeImage. + // For Windows NT/2000, the width must be DWORD aligned unless + // the bitmap is RLE compressed. This example shows this. + // For Windows 95/98, the width must be WORD aligned unless the + // bitmap is RLE compressed. + sBIH.biSizeImage = ((sBIH.biWidth * sBIH.biBitCount +31) & ~31) /8 * sBIH.biHeight; + + // Set biClrImportant to 0, indicating that all of the + // device colors are important. + sBIH.biClrImportant = 0; + + // The INFOHEADER version of a bitmap file uses + // RGB Quads to store the color map + dwSize = (nPalEntries * sizeof(RGBQUAD)); + + // Read the palette data... + ReadFile(hFile, OriginalPalette, dwSize, &dwDummy, NULL); + + memcpy(Palette, OriginalPalette, dwSize); + + g_monochrome = TRUE; + + for (index = 0; index < nPalEntries; index++) + { + if ((Palette[index].rgbRed != Palette[index].rgbGreen) || + (Palette[index].rgbGreen != Palette[index].rgbBlue)) + g_monochrome = FALSE; + } + + if (pbmi) + LocalFree(pbmi); + + pbmi = (PBITMAPINFO) LocalAlloc(LPTR, sizeof(BITMAPINFOHEADER) + + sizeof(RGBQUAD) * (nPalEntries)); + + memcpy(pbmi, &sBIH, sizeof(BITMAPINFOHEADER)); + + memcpy((BYTE *)pbmi + sizeof(BITMAPINFOHEADER), OriginalPalette, + sizeof(RGBQUAD) * (nPalEntries)); + + if (pOriginalData) + LocalFree(pOriginalData); + + pOriginalData = (BYTE *)LocalAlloc(LPTR, sBIH.biSizeImage); + + // Read the bitmap data into memory + ReadFile(hFile, pOriginalData, sBIH.biSizeImage, &dwDummy, NULL); + + status = TRUE; + } + } + + CloseHandle(hFile); + } + + return(status); +} + + +BOOL LoadWADFile(LPTSTR lpszFileName) +{ + HANDLE hFile; + DWORD dwDummy; + DWORD dwSize; + int index; + wadinfo_t WadHeader; + lumpinfo_t *pLumpInfo = NULL; + lumpinfo_t *pLump; + + for (index = 0; index < MAX_MIPS; index++) + pMips[index] = NULL; + + hFile = CreateFile(lpszFileName, GENERIC_READ, FILE_SHARE_READ, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); + + if (hFile == INVALID_HANDLE_VALUE) + return FALSE; + + if (!ReadFile(hFile, &WadHeader, sizeof(WadHeader), &dwDummy, NULL)) + { + CloseHandle(hFile); // can't read WAD header + return FALSE; + } + + if (strncmp(WadHeader.identification,"WAD3",4)) + { + CloseHandle(hFile); + return FALSE; // not a WAD3 file + } + + if (WadHeader.numlumps >= MAX_MIPS) + { + CloseHandle(hFile); + return FALSE; // too many lumps + } + + if (SetFilePointer(hFile, WadHeader.infotableofs, NULL, FILE_BEGIN) == 0) + { + CloseHandle(hFile); + return FALSE; // can't seek to lump info table + } + + dwSize = WadHeader.numlumps * sizeof(lumpinfo_t); + + pLumpInfo = (lumpinfo_t *)LocalAlloc(LPTR, dwSize); + + if (!ReadFile(hFile, pLumpInfo, dwSize, &dwDummy, NULL)) + { + CloseHandle(hFile); // can read lump info table + return FALSE; + } + + pLump = pLumpInfo; + num_mips = 0; + + for (index = 0; index < WadHeader.numlumps; index++) + { + if (SetFilePointer(hFile, pLump->filepos, NULL, FILE_BEGIN) == 0) + { + CloseHandle(hFile); + return FALSE; // can't seek to lump table + } + + pMips[index] = (miptex_t *)LocalAlloc(LPTR, pLump->size); + + if (!ReadFile(hFile, pMips[index], pLump->size, &dwDummy, NULL)) + { + CloseHandle(hFile); + return FALSE; + } + + pLump++; + num_mips++; + } + + LocalFree(pLumpInfo); + + CloseHandle(hFile); + + return TRUE; +} + + +BOOL LoadBitmapFromMip(int index) +{ + BYTE *pMipData; + palette_t *pMipPalette; + int idx; + unsigned int row; + int mip1_size, mip2_size, mip3_size, mip4_size; + + mip1_size = pMips[index]->width * pMips[index]->height; + mip2_size = (pMips[index]->width * pMips[index]->height) / 4; + mip3_size = (pMips[index]->width * pMips[index]->height) / 16; + mip4_size = (pMips[index]->width * pMips[index]->height) / 64; + + pMipPalette = (palette_t *)((BYTE *)pMips[index] + sizeof(miptex_t) + + mip1_size + mip2_size + mip3_size + mip4_size); + + if (pbmi) + LocalFree(pbmi); + + pbmi = (PBITMAPINFO) LocalAlloc(LPTR, sizeof(BITMAPINFOHEADER) + + 256 * sizeof(RGBQUAD)); + + memset(pbmi, 0, sizeof(BITMAPINFOHEADER)); + + pbmi->bmiHeader.biSize = sizeof (BITMAPINFOHEADER); + pbmi->bmiHeader.biWidth = pMips[index]->width; + pbmi->bmiHeader.biHeight = pMips[index]->height; + pbmi->bmiHeader.biPlanes = 1; + pbmi->bmiHeader.biBitCount = 8; + pbmi->bmiHeader.biCompression = BI_RGB; + pbmi->bmiHeader.biSizeImage = pMips[index]->offsets[1] - pMips[index]->offsets[0]; + pbmi->bmiHeader.biXPelsPerMeter = 0; + pbmi->bmiHeader.biYPelsPerMeter = 0; + pbmi->bmiHeader.biClrUsed = 256; + pbmi->bmiHeader.biClrImportant = 0; + + for (idx = 0; idx < 256; idx++) + { + pbmi->bmiColors[idx].rgbRed = pMipPalette->palette[idx * 3 + 0]; + pbmi->bmiColors[idx].rgbGreen = pMipPalette->palette[idx * 3 + 1]; + pbmi->bmiColors[idx].rgbBlue = pMipPalette->palette[idx * 3 + 2]; + pbmi->bmiColors[idx].rgbReserved = 0; + } + + memcpy(OriginalPalette, pbmi->bmiColors, 256 * sizeof(RGBQUAD)); + + if (pOriginalData) + LocalFree(pOriginalData); + + pOriginalData = (BYTE *)LocalAlloc(LPTR, pbmi->bmiHeader.biSizeImage); + + pMipData = (BYTE *)pMips[index] + sizeof(miptex_t); + + idx = pMips[index]->height - 1; + + for (row = 0; row < pMips[index]->height; row++) + { + memcpy(pOriginalData+(pMips[index]->width * row), + pMipData+(pMips[index]->width * idx), + pMips[index]->width); + + idx--; + } + + return TRUE; +} + + +BOOL WriteWADFile(LPTSTR lpszFileName) +{ + HANDLE hFile; + DWORD dwDummy; + wadinfo_t WadHeader; + int index; + int num_lumps; + int offset; + DWORD dwSize; + lumpinfo_t lump; + + hFile = CreateFile(lpszFileName, GENERIC_WRITE, FILE_SHARE_READ, + NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); + + if (hFile == INVALID_HANDLE_VALUE) + return FALSE; + + strcpy(WadHeader.identification,"WAD3"); + + num_lumps = 0; + + offset = sizeof(wadinfo_t); + + for (index=0; index < MAX_MIPS; index++) + { + if (pMips[index]) + { + offset = offset + pMips[index]->offsets[3] + + (pMips[index]->height * pMips[index]->width / 64) + + sizeof(palette_t); + + num_lumps++; + } + } + + WadHeader.numlumps = num_lumps; + WadHeader.infotableofs = offset; + + if (!WriteFile(hFile, &WadHeader, sizeof(WadHeader), &dwDummy, NULL)) + { + CloseHandle(hFile); // can't write WAD header + return FALSE; + } + + for (index = 0; index < MAX_MIPS; index++) + { + if (pMips[index]) + { + dwSize = pMips[index]->offsets[3] + + (pMips[index]->height * pMips[index]->width / 64) + + sizeof(palette_t); + + if (!WriteFile(hFile, pMips[index], dwSize, &dwDummy, NULL)) + { + CloseHandle(hFile); // can't write WAD header + return FALSE; + } + } + } + + offset = sizeof(wadinfo_t); + + for (index = 0; index < MAX_MIPS; index++) + { + if (pMips[index]) + { + dwSize = pMips[index]->offsets[3] + + (pMips[index]->height * pMips[index]->width / 64) + + sizeof(palette_t); + + lump.filepos = offset; + lump.disksize = dwSize; + lump.size = dwSize; + lump.type = 0x43; // Half-Life WAD3 mip + lump.compression = 0; + lump.pad1 = 0; + lump.pad2 = 0; + memset(lump.name, 0, sizeof(lump.name)); + strcpy(lump.name, pMips[index]->name); + + if (!WriteFile(hFile, &lump, sizeof(lumpinfo_t), &dwDummy, NULL)) + { + CloseHandle(hFile); // can't write WAD header + return FALSE; + } + + offset += dwSize; + } + } + + CloseHandle(hFile); + + return TRUE; +} + + +BYTE AddColor( float r, float g, float b ) +{ + int i; + + for (i = 0; i < 255; i++) + { + if (!color_used[i]) + { + linearpalette[i][0] = r; + linearpalette[i][1] = g; + linearpalette[i][2] = b; + + if (r < 0) r = 0.0; + if (r > 1.0) r = 1.0; + lbmpalette[i*3+0] = (BYTE)(pow( r, 1.0 / 2.2) * 255); + + if (g < 0) g = 0.0; + if (g > 1.0) g = 1.0; + lbmpalette[i*3+1] = (BYTE)(pow( g, 1.0 / 2.2) * 255); + + if (b < 0) b = 0.0; + if (b > 1.0) b = 1.0; + lbmpalette[i*3+2] = (BYTE)(pow( b, 1.0 / 2.2) * 255); + + color_used[i] = 1; + colors_used++; + + return i; + } + } + + return 0; +} + + +BYTE AveragePixels (int count) +{ + float r,g,b; + int i; + int vis; + int pix; + float dr, dg, db; + float bestdistortion, distortion; + int bestcolor; + + vis = 0; + r = g = b = 0; + + for (i = 0; i < count; i++) + { + pix = pixdata[i]; + r += linearpalette[pix][0]; + g += linearpalette[pix][1]; + b += linearpalette[pix][2]; + } + + r /= count; + g /= count; + b /= count; + + r += d_red; + g += d_green; + b += d_blue; + +// +// find the best color +// + bestdistortion = 3.0; + bestcolor = -1; + + for (i = 0; i < 255; i++) + { + if (color_used[i]) + { + pix = i; //pixdata[i]; + + dr = r - linearpalette[i][0]; + dg = g - linearpalette[i][1]; + db = b - linearpalette[i][2]; + + distortion = dr*dr + dg*dg + db*db; + if (distortion < bestdistortion) + { + if (!distortion) + { + d_red = d_green = d_blue = 0; // no distortion yet + return pix; // perfect match + } + + bestdistortion = distortion; + bestcolor = pix; + } + } + } + + if (bestdistortion > 0.001 && colors_used < 255) + { + bestcolor = AddColor( r, g, b ); + d_red = d_green = d_blue = 0; + bestdistortion = 0; + } + else + { + // error diffusion + d_red = r - linearpalette[bestcolor][0]; + d_green = g - linearpalette[bestcolor][1]; + d_blue = b - linearpalette[bestcolor][2]; + } + + if (bestdistortion > maxdistortion) + maxdistortion = bestdistortion; + + return bestcolor; +} diff --git a/tools/bot_logo/resource.h b/tools/bot_logo/resource.h new file mode 100644 index 0000000..ebc4611 --- /dev/null +++ b/tools/bot_logo/resource.h @@ -0,0 +1,25 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by bot_logo.rc +// +#define IDD_DIALOG1 100 +#define IDM_EXIT 101 +#define MODDLGBOX 103 +#define IDC_REMOVE 1000 +#define IDC_LIST1 1002 +#define IDC_LIST2 1003 +#define IDC_COMBO1 1004 +#define IDC_MOD 1005 +#define IDC_ADD 1006 +#define IDC_STATIC -1 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 104 +#define _APS_NEXT_COMMAND_VALUE 32771 +#define _APS_NEXT_CONTROL_VALUE 1007 +#define _APS_NEXT_SYMED_VALUE 110 +#endif +#endif diff --git a/tools/exports.c b/tools/exports.c new file mode 100644 index 0000000..5f73352 --- /dev/null +++ b/tools/exports.c @@ -0,0 +1,418 @@ +// +// (http://planethalflife.com/botman/) +// +// exports.c +// + +#include +#include +#include +#include + + +#define DOS_SIGNATURE 0x5A4D /* MZ */ +#define NT_SIGNATURE 0x00004550 /* PE00 */ + + +// globals +WORD *p_Ordinals = NULL; +DWORD *p_Names = NULL; +DWORD *p_Functions = NULL; +int num_ordinals; + + +typedef struct { // DOS .EXE header + WORD e_magic; // Magic number + WORD e_cblp; // Bytes on last page of file + WORD e_cp; // Pages in file + WORD e_crlc; // Relocations + WORD e_cparhdr; // Size of header in paragraphs + WORD e_minalloc; // Minimum extra paragraphs needed + WORD e_maxalloc; // Maximum extra paragraphs needed + WORD e_ss; // Initial (relative) SS value + WORD e_sp; // Initial SP value + WORD e_csum; // Checksum + WORD e_ip; // Initial IP value + WORD e_cs; // Initial (relative) CS value + WORD e_lfarlc; // File address of relocation table + WORD e_ovno; // Overlay number + WORD e_res[4]; // Reserved words + WORD e_oemid; // OEM identifier (for e_oeminfo) + WORD e_oeminfo; // OEM information; e_oemid specific + WORD e_res2[10]; // Reserved words + LONG e_lfanew; // File address of new exe header + } DOS_HEADER, *P_DOS_HEADER; + +typedef struct { + WORD Machine; + WORD NumberOfSections; + DWORD TimeDateStamp; + DWORD PointerToSymbolTable; + DWORD NumberOfSymbols; + WORD SizeOfOptionalHeader; + WORD Characteristics; +} PE_HEADER, *P_PE_HEADER; + +#define SIZEOF_SHORT_NAME 8 + +typedef struct { + BYTE Name[SIZEOF_SHORT_NAME]; + union { + DWORD PhysicalAddress; + DWORD VirtualSize; + } Misc; + DWORD VirtualAddress; + DWORD SizeOfRawData; + DWORD PointerToRawData; + DWORD PointerToRelocations; + DWORD PointerToLinenumbers; + WORD NumberOfRelocations; + WORD NumberOfLinenumbers; + DWORD Characteristics; +} SECTION_HEADER, *P_SECTION_HEADER; + +typedef struct { + DWORD VirtualAddress; + DWORD Size; +} DATA_DIRECTORY, *P_DATA_DIRECTORY; + +#define NUMBEROF_DIRECTORY_ENTRIES 16 + +typedef struct { + WORD Magic; + BYTE MajorLinkerVersion; + BYTE MinorLinkerVersion; + DWORD SizeOfCode; + DWORD SizeOfInitializedData; + DWORD SizeOfUninitializedData; + DWORD AddressOfEntryPoint; + DWORD BaseOfCode; + DWORD BaseOfData; + DWORD ImageBase; + DWORD SectionAlignment; + DWORD FileAlignment; + WORD MajorOperatingSystemVersion; + WORD MinorOperatingSystemVersion; + WORD MajorImageVersion; + WORD MinorImageVersion; + WORD MajorSubsystemVersion; + WORD MinorSubsystemVersion; + DWORD Win32VersionValue; + DWORD SizeOfImage; + DWORD SizeOfHeaders; + DWORD CheckSum; + WORD Subsystem; + WORD DllCharacteristics; + DWORD SizeOfStackReserve; + DWORD SizeOfStackCommit; + DWORD SizeOfHeapReserve; + DWORD SizeOfHeapCommit; + DWORD LoaderFlags; + DWORD NumberOfRvaAndSizes; + DATA_DIRECTORY DataDirectory[NUMBEROF_DIRECTORY_ENTRIES]; +} OPTIONAL_HEADER, *P_OPTIONAL_HEADER; + +typedef struct { + DWORD Characteristics; + DWORD TimeDateStamp; + WORD MajorVersion; + WORD MinorVersion; + DWORD Name; + DWORD Base; + DWORD NumberOfFunctions; + DWORD NumberOfNames; + DWORD AddressOfFunctions; // RVA from base of image + DWORD AddressOfNames; // RVA from base of image + DWORD AddressOfNameOrdinals; // RVA from base of image +} EXPORT_DIRECTORY, *P_EXPORT_DIRECTORY; + + +void FreeNameFuncGlobals(void) +{ + if (p_Ordinals) + free(p_Ordinals); + if (p_Functions) + free(p_Functions); + if (p_Names) + free(p_Names); +} + + +void FgetString(char *str, FILE *bfp) +{ + char ch; + + while ((ch = fgetc(bfp)) != EOF) + { + *str++ = ch; + if (ch == 0) + break; + } +} + + +int main(int argc, char *argv[]) +{ + FILE *bfp; + char filename[80]; + BOOL extended = FALSE; + DOS_HEADER dos_header; + LONG nt_signature; + PE_HEADER pe_header; + SECTION_HEADER section_header; + BOOL edata_found; + OPTIONAL_HEADER optional_header; + LONG edata_offset; + LONG edata_delta; + EXPORT_DIRECTORY export_directory; + LONG name_offset; + LONG ordinal_offset; + LONG function_offset; + char function_name[256]; + int i, index; + BOOL error; + char msg[80]; + + + if (argc < 2) + { + printf("usage: exports [-e] filename.dll\n"); + return -1; + } + + if (argc > 2) + { + if (argv[1][0] == '-') + { + if (argv[1][1] == 'e') + { + strcpy(filename, argv[2]); + extended = TRUE; + } + else + { + printf("unknown option \"%s\"\n\n", argv[1]); + printf("usage: exports [-e] filename.dll\n"); + return -1; + } + } + else + { + printf("usage: exports [-e] filename.dll\n"); + return -1; + } + } + else + strcpy(filename, argv[1]); + + if ((bfp=fopen(filename, "rb")) == NULL) + { + printf("DLL file %s not found!", filename); + return -1; + } + + if (fread(&dos_header, sizeof(dos_header), 1, bfp) != 1) + { + printf("%s is NOT a valid DLL file!", filename); + return -1; + } + + if (dos_header.e_magic != DOS_SIGNATURE) + { + printf("file does not have a valid DLL signature!"); + return -1; + } + + if (fseek(bfp, dos_header.e_lfanew, SEEK_SET) == -1) + { + printf("error seeking to new exe header!"); + return -1; + } + + if (fread(&nt_signature, sizeof(nt_signature), 1, bfp) != 1) + { + printf("file does not have a valid NT signature!"); + return -1; + } + + if (nt_signature != NT_SIGNATURE) + { + printf("file does not have a valid NT signature!"); + return -1; + } + + if (fread(&pe_header, sizeof(pe_header), 1, bfp) != 1) + { + printf("file does not have a valid PE header!"); + return -1; + } + + if (pe_header.SizeOfOptionalHeader == 0) + { + printf("file does not have an optional header!"); + return -1; + } + + if (fread(&optional_header, sizeof(optional_header), 1, bfp) != 1) + { + printf("file does not have a valid optional header!"); + return -1; + } + + edata_found = FALSE; + + for (i=0; i < pe_header.NumberOfSections; i++) + { + if (fread(§ion_header, sizeof(section_header), 1, bfp) != 1) + { + printf("error reading section header!"); + return -1; + } + + if (strcmp((char *)section_header.Name, ".edata") == 0) + { + edata_found = TRUE; + break; + } + } + + if (edata_found) + { + edata_offset = section_header.PointerToRawData; + edata_delta = section_header.VirtualAddress - section_header.PointerToRawData; + } + else + { + edata_offset = optional_header.DataDirectory[0].VirtualAddress; + edata_delta = 0L; + } + + + if (fseek(bfp, edata_offset, SEEK_SET) == -1) + { + printf("file does not have a valid exports section!"); + return -1; + } + + if (fread(&export_directory, sizeof(export_directory), 1, bfp) != 1) + { + printf("file does not have a valid optional header!"); + return -1; + } + + num_ordinals = export_directory.NumberOfNames; // also number of ordinals + + + ordinal_offset = export_directory.AddressOfNameOrdinals - edata_delta; + + if (fseek(bfp, ordinal_offset, SEEK_SET) == -1) + { + printf("file does not have a valid ordinals section!"); + return -1; + } + + if ((p_Ordinals = (WORD *)malloc(num_ordinals * sizeof(WORD))) == NULL) + { + printf("error allocating memory for ordinals section!"); + return -1; + } + + if (fread(p_Ordinals, num_ordinals * sizeof(WORD), 1, bfp) != 1) + { + FreeNameFuncGlobals(); + + printf("error reading ordinals table!"); + return -1; + } + + + function_offset = export_directory.AddressOfFunctions - edata_delta; + + if (fseek(bfp, function_offset, SEEK_SET) == -1) + { + FreeNameFuncGlobals(); + + printf("file does not have a valid export address section!"); + return -1; + } + + if ((p_Functions = (DWORD *)malloc(num_ordinals * sizeof(DWORD))) == NULL) + { + FreeNameFuncGlobals(); + + printf("error allocating memory for export address section!"); + return -1; + } + + if (fread(p_Functions, num_ordinals * sizeof(DWORD), 1, bfp) != 1) + { + FreeNameFuncGlobals(); + + printf("error reading export address section!"); + return -1; + } + + + name_offset = export_directory.AddressOfNames - edata_delta; + + if (fseek(bfp, name_offset, SEEK_SET) == -1) + { + FreeNameFuncGlobals(); + + printf("file does not have a valid names section!"); + return -1; + } + + if ((p_Names = (DWORD *)malloc(num_ordinals * sizeof(DWORD))) == NULL) + { + FreeNameFuncGlobals(); + + printf("error allocating memory for names section!"); + return -1; + } + + if (fread(p_Names, num_ordinals * sizeof(DWORD), 1, bfp) != 1) + { + FreeNameFuncGlobals(); + + printf("error reading names table!"); + return -1; + } + + error = FALSE; + + for (i=0; (i < num_ordinals) && (error==FALSE); i++) + { + name_offset = p_Names[i] - edata_delta; + + if (name_offset != 0) + { + if (fseek(bfp, name_offset, SEEK_SET) == -1) + { + printf("error in loading names section!\n"); + error = TRUE; + } + else + { + FgetString(function_name, bfp); + + if (extended) + { + index = p_Ordinals[i]; + + printf("ordinal=%3d addr=%08lX name=%s\n", + (p_Ordinals[i]+1), p_Functions[index], function_name); + } + else + printf("%s\n", function_name); + } + } + } + + FreeNameFuncGlobals(); + + fclose(bfp); + + return 0; +} + diff --git a/tools/floyd.c b/tools/floyd.c new file mode 100644 index 0000000..54a16f5 --- /dev/null +++ b/tools/floyd.c @@ -0,0 +1,158 @@ +#include + +/* here's what the graph looks like (numbers in parenthesis are distance)... + + (50) (40) + 0 ------> 1 ------> 2 ---\ + | ^ | \ + | | | \(50) + | | |(45) \ + \ (45)| | \ + (30)\ | | | + \ | V V + \--> 3 ------> 4 ------> 5 + (55) (35) + +Note that the only 2-way path is from 2 to 3 (and 3 to 2). +All other paths are one-way paths. + +Here's the paths and distances: + + 0 -----> 1 (distance = 50 units) + 1 -----> 2 (distance = 40 units) + 1 -----> 3 (distance = 30 units) + 2 -----> 3 (distance = 45 units) + 2 -----> 4 (distance = 50 units) + 3 -----> 2 (distance = 45 units) + 3 -----> 4 (distance = 55 units) + 4 -----> 5 (distance = 35 units) + +All paths which aren't possible are indicated by -1 in the shortest_path table + +The shortest_path array is a 2 dimensional array of all waypoints (i.e. if +there are 50 waypoints then the table will be 50 X 50 in size. The rows +(running down the table) indicate the starting index (0 to N) and the columns +(running across the table) indicate the ending index. So if you wanted to +know the distance between waypoint 37 and waypoint 42 you would look at +shortest_path[37][42]. If you wanted to know the distance between waypoints +42 and 37, you would look at shortest_path[42][37]. NOTE!, these 2 DO NOT +HAVE TO BE THE SAME!!! One way paths may mean that shortest_path[37][42] +is 150 units and shortest_path[42][37] is -1 (indicating you can't go that +way!!!) + +*/ + +#define MAX 6 + +int shortest_path[MAX][MAX] = { + { 0, 50, -1, -1, -1, -1}, + {-1, 0, 40, 30, -1, -1}, + {-1, -1, 0, 45, 50, -1}, + {-1, -1, 45, 0, 55, -1}, + {-1, -1, -1, -1, 0, 35}, + {-1, -1, -1, -1, -1, 0} +}; + + +int from_to[MAX][MAX]; + +void floyd(void) +{ + int x, y, z; + int changed = 1; + int distance; + + for (y=0; y < MAX; y++) + { + for (z=0; z < MAX; z++) + { + from_to[y][z] = z; + } + } + + while (changed) + { + changed = 0; + + for (x=0; x < MAX; x++) + { + for (y=0; y < MAX; y++) + { + for (z=0; z < MAX; z++) + { + if ((shortest_path[y][x] == -1) || (shortest_path[x][z] == -1)) + continue; + + distance = shortest_path[y][x] + shortest_path[x][z]; + + if ((distance < shortest_path[y][z]) || + (shortest_path[y][z] == -1)) + { + shortest_path[y][z] = distance; + from_to[y][z] = from_to[y][x]; + changed = 1; + } + } + } + } + } +} + +void main(void) +{ + int a, b; + char buffer[20]; + + // run Floyd-Warshall algorithm on the shortest_path table... + floyd(); + + // initialize any unreachable paths based on shortest_path table... + for (a=0; a < MAX; a++) + { + for (b=0; b < MAX; b++) + { + if (shortest_path[a][b] == -1) + from_to[a][b] = -1; + } + } + + printf("shortest_path:\n"); + for (a=0; a < MAX; a++) + { + for (b=0; b < MAX; b++) + printf("%5d,", shortest_path[a][b]); + printf("\n"); + } + printf("\n"); + + printf("from_to:\n"); + for (a=0; a < MAX; a++) + { + for (b=0; b < MAX; b++) + printf("%2d,", from_to[a][b]); + printf("\n"); + } + printf("\n"); + + printf("enter start node (0-5):"); + gets(buffer); + a = atoi(buffer); + + printf("\nenter end node (0-5):"); + gets(buffer); + b = atoi(buffer); + + if (shortest_path[a][b] != -1) + { + printf("\n\nShortest path from %d to %d is:\n", a, b); + + while (a != b) + { + printf("from %d goto %d (total distance remaining=%d)\n", + a, from_to[a][b], shortest_path[a][b]); + a = from_to[a][b]; + } + } + else + printf("You can't get from %d to %d!\n", a, b); +} diff --git a/util.cpp b/util.cpp new file mode 100644 index 0000000..cd3d87d --- /dev/null +++ b/util.cpp @@ -0,0 +1,662 @@ +/*** +* +* Copyright (c) 1999, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +// +// HPB_bot - botman's High Ping Bastard bot +// +// (http://planethalflife.com/botman/) +// +// util.cpp +// + +#ifndef _WIN32 +#include +#endif + +#include +#include +#include +#include + +#include "usercmd.h" +#include "bot.h" +#include "bot_func.h" + + +extern int mod_id; +extern bot_t bots[32]; +extern edict_t *pent_info_ctfdetect; +extern char team_names[MAX_TEAMS][MAX_TEAMNAME_LENGTH]; +extern int num_teams; + +Vector UTIL_VecToAngles( const Vector &vec ) +{ + float rgflVecOut[3]; + VEC_TO_ANGLES(vec, rgflVecOut); + return Vector(rgflVecOut); +} + + +// Overloaded to add IGNORE_GLASS +void UTIL_TraceLine( const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, IGNORE_GLASS ignoreGlass, edict_t *pentIgnore, TraceResult *ptr ) +{ + TRACE_LINE( vecStart, vecEnd, (igmon == ignore_monsters ? TRUE : FALSE) | (ignoreGlass?0x100:0), pentIgnore, ptr ); +} + + +void UTIL_TraceLine( const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, edict_t *pentIgnore, TraceResult *ptr ) +{ + TRACE_LINE( vecStart, vecEnd, (igmon == ignore_monsters ? TRUE : FALSE), pentIgnore, ptr ); +} + + +edict_t *UTIL_FindEntityInSphere( edict_t *pentStart, const Vector &vecCenter, float flRadius ) +{ + edict_t *pentEntity; + + pentEntity = FIND_ENTITY_IN_SPHERE( pentStart, vecCenter, flRadius); + + if (!FNullEnt(pentEntity)) + return pentEntity; + + return NULL; +} + + +edict_t *UTIL_FindEntityByString( edict_t *pentStart, const char *szKeyword, const char *szValue ) +{ + edict_t *pentEntity; + + pentEntity = FIND_ENTITY_BY_STRING( pentStart, szKeyword, szValue ); + + if (!FNullEnt(pentEntity)) + return pentEntity; + return NULL; +} + + +edict_t *UTIL_FindEntityByClassname( edict_t *pentStart, const char *szName ) +{ + return UTIL_FindEntityByString( pentStart, "classname", szName ); +} + +edict_t *UTIL_FindEntityByTarget( edict_t *pentStart, const char *szName ) +{ + return UTIL_FindEntityByString( pentStart, "target", szName ); +} + + +edict_t *UTIL_FindEntityByTargetname( edict_t *pentStart, const char *szName ) +{ + return UTIL_FindEntityByString( pentStart, "targetname", szName ); +} + + +void ClientPrint( edict_t *pEntity, int msg_dest, const char *msg_name) +{ + if (GET_USER_MSG_ID (PLID, "TextMsg", NULL) <= 0) + REG_USER_MSG ("TextMsg", -1); + + MESSAGE_BEGIN( MSG_ONE, GET_USER_MSG_ID (PLID, "TextMsg", NULL), NULL, pEntity ); + + WRITE_BYTE( msg_dest ); + WRITE_STRING( msg_name ); + MESSAGE_END(); +} + +void UTIL_SayText( const char *pText, edict_t *pEdict ) +{ + if (GET_USER_MSG_ID (PLID, "SayText", NULL) <= 0) + REG_USER_MSG ("SayText", -1); + + MESSAGE_BEGIN( MSG_ONE, GET_USER_MSG_ID (PLID, "SayText", NULL), NULL, pEdict ); + WRITE_BYTE( ENTINDEX(pEdict) ); + if (mod_id == FRONTLINE_DLL) + WRITE_SHORT(0); + WRITE_STRING( pText ); + MESSAGE_END(); +} + + +void UTIL_HostSay( edict_t *pEntity, int teamonly, char *message ) +{ + int j; + char text[128]; + char *pc; + int sender_team, player_team; + edict_t *client; + + // make sure the text has content + for ( pc = message; pc != NULL && *pc != 0; pc++ ) + { + if ( isprint( *pc ) && !isspace( *pc ) ) + { + pc = NULL; // we've found an alphanumeric character, so text is valid + break; + } + } + + if ( pc != NULL ) + return; // no character found, so say nothing + + // turn on color set 2 (color on, no sound) + if ( teamonly ) + sprintf( text, "%c(TEAM) %s: ", 2, STRING( pEntity->v.netname ) ); + else + sprintf( text, "%c%s: ", 2, STRING( pEntity->v.netname ) ); + + j = sizeof(text) - 2 - strlen(text); // -2 for /n and null terminator + if ( (int)strlen(message) > j ) + message[j] = 0; + + strcat( text, message ); + strcat( text, "\n" ); + + // loop through all players + // Start with the first player. + // This may return the world in single player if the client types something between levels or during spawn + // so check it, or it will infinite loop + + if (GET_USER_MSG_ID (PLID, "SayText", NULL) <= 0) + REG_USER_MSG ("SayText", -1); + + sender_team = UTIL_GetTeam(pEntity); + + client = NULL; + while ( ((client = UTIL_FindEntityByClassname( client, "player" )) != NULL) && + (!FNullEnt(client)) ) + { + if ( client == pEntity ) // skip sender of message + continue; + + player_team = UTIL_GetTeam(client); + + if ( teamonly && (sender_team != player_team) ) + continue; + + MESSAGE_BEGIN( MSG_ONE, GET_USER_MSG_ID (PLID, "SayText", NULL), NULL, client ); + WRITE_BYTE( ENTINDEX(pEntity) ); + if (mod_id == FRONTLINE_DLL) + WRITE_SHORT(0); + WRITE_STRING( text ); + MESSAGE_END(); + } + + // print to the sending client + MESSAGE_BEGIN( MSG_ONE, GET_USER_MSG_ID (PLID, "SayText", NULL), NULL, pEntity ); + WRITE_BYTE( ENTINDEX(pEntity) ); + if (mod_id == FRONTLINE_DLL) + WRITE_SHORT(0); + WRITE_STRING( text ); + MESSAGE_END(); + + // echo to server console + SERVER_PRINT( text ); +} + + +#ifdef DEBUG +edict_t *DBG_EntOfVars( const entvars_t *pev ) +{ + if (pev->pContainingEntity != NULL) + return pev->pContainingEntity; + ALERT(at_console, "entvars_t pContainingEntity is NULL, calling into engine"); + edict_t* pent = (*g_engfuncs.pfnFindEntityByVars)((entvars_t*)pev); + if (pent == NULL) + ALERT(at_console, "DAMN! Even the engine couldn't FindEntityByVars!"); + ((entvars_t *)pev)->pContainingEntity = pent; + return pent; +} +#endif //DEBUG + + +// return team number 0 through 3 based what MOD uses for team numbers +int UTIL_GetTeam(edict_t *pEntity) +{ + if (mod_id == TFC_DLL) + { + return pEntity->v.team - 1; // TFC teams are 1-4 based + } + else if (mod_id == CSTRIKE_DLL) + { + char *infobuffer; + char model_name[32]; + + infobuffer = GET_INFOKEYBUFFER( pEntity ); + strcpy(model_name, INFOKEY_VALUE (infobuffer, "model")); + + if ((strcmp(model_name, "terror") == 0) || // Phoenix Connektion + (strcmp(model_name, "arab") == 0) || // old L337 Krew + (strcmp(model_name, "leet") == 0) || // L337 Krew + (strcmp(model_name, "arctic") == 0) || // Arctic Avenger + (strcmp(model_name, "guerilla") == 0)) // Gorilla Warfare + { + return 0; + } + else if ((strcmp(model_name, "urban") == 0) || // Seal Team 6 + (strcmp(model_name, "gsg9") == 0) || // German GSG-9 + (strcmp(model_name, "sas") == 0) || // UK SAS + (strcmp(model_name, "gign") == 0) || // French GIGN + (strcmp(model_name, "vip") == 0)) // VIP + { + return 1; + } + + return 0; // return zero if team is unknown + } + else if ((mod_id == GEARBOX_DLL) && (pent_info_ctfdetect != NULL)) + { + // OpFor CTF map... + + char *infobuffer; + char model_name[32]; + + infobuffer = GET_INFOKEYBUFFER( pEntity ); + strcpy(model_name, INFOKEY_VALUE (infobuffer, "model")); + + if ((strcmp(model_name, "ctf_barney") == 0) || + (strcmp(model_name, "cl_suit") == 0) || + (strcmp(model_name, "ctf_gina") == 0) || + (strcmp(model_name, "ctf_gordon") == 0) || + (strcmp(model_name, "otis") == 0) || + (strcmp(model_name, "ctf_scientist") == 0)) + { + return 0; + } + else if ((strcmp(model_name, "beret") == 0) || + (strcmp(model_name, "drill") == 0) || + (strcmp(model_name, "grunt") == 0) || + (strcmp(model_name, "recruit") == 0) || + (strcmp(model_name, "shephard") == 0) || + (strcmp(model_name, "tower") == 0)) + { + return 1; + } + + return 0; // return zero if team is unknown + } + else if (mod_id == FRONTLINE_DLL) + { + return pEntity->v.team - 1; // Front Line Force teams are 1-4 based + } + else // must be HL or OpFor deathmatch... + { + char *infobuffer; + char model_name[32]; + + if (team_names[0][0] == 0) + { + char *pName; + char teamlist[MAX_TEAMS*MAX_TEAMNAME_LENGTH]; + int i; + + num_teams = 0; + strcpy(teamlist, CVAR_GET_STRING("mp_teamlist")); + pName = teamlist; + pName = strtok(pName, ";"); + + while (pName != NULL && *pName) + { + // check that team isn't defined twice + for (i=0; i < num_teams; i++) + if (stricmp(pName, team_names[i]) == 0) + break; + if (i == num_teams) + { + strcpy(team_names[num_teams], pName); + num_teams++; + } + pName = strtok(NULL, ";"); + } + } + + infobuffer = GET_INFOKEYBUFFER( pEntity ); + strcpy(model_name, INFOKEY_VALUE (infobuffer, "model")); + + for (int index=0; index < num_teams; index++) + { + if (stricmp(model_name, team_names[index]) == 0) + return index; + } + + return 0; + } +} + + +// return class number 0 through N +int UTIL_GetClass(edict_t *pEntity) +{ + char *infobuffer; + char model_name[32]; + + infobuffer = GET_INFOKEYBUFFER( pEntity ); + strcpy(model_name, INFOKEY_VALUE (infobuffer, "model")); + + if (mod_id == FRONTLINE_DLL) + { + if ((strcmp(model_name, "natorecon") == 0) || + (strcmp(model_name, "axisrecon") == 0)) + { + return 0; // recon + } + else if ((strcmp(model_name, "natoassault") == 0) || + (strcmp(model_name, "axisassault") == 0)) + { + return 1; // assault + } + else if ((strcmp(model_name, "natosupport") == 0) || + (strcmp(model_name, "axissupport") == 0)) + { + return 2; // support + } + } + + return 0; +} + + +int UTIL_GetBotIndex(edict_t *pEdict) +{ + int index; + + for (index=0; index < 32; index++) + { + if (bots[index].pEdict == pEdict) + { + return index; + } + } + + return -1; // return -1 if edict is not a bot +} + + +bot_t *UTIL_GetBotPointer(edict_t *pEdict) +{ + int index; + + for (index=0; index < 32; index++) + { + if (bots[index].pEdict == pEdict) + { + break; + } + } + + if (index < 32) + return (&bots[index]); + + return NULL; // return NULL if edict is not a bot +} + + +bool IsAlive(edict_t *pEdict) +{ + return ((pEdict->v.deadflag == DEAD_NO) && + (pEdict->v.health > 0) && + !(pEdict->v.flags & FL_NOTARGET) && + (pEdict->v.takedamage != 0)); +} + + +bool FInViewCone(Vector *pOrigin, edict_t *pEdict) +{ + Vector2D vec2LOS; + float flDot; + + MAKE_VECTORS ( pEdict->v.angles ); + + vec2LOS = ( *pOrigin - pEdict->v.origin ).Make2D(); + vec2LOS = vec2LOS.Normalize(); + + flDot = DotProduct (vec2LOS , gpGlobals->v_forward.Make2D() ); + + if ( flDot > 0.50 ) // 60 degree field of view + { + return TRUE; + } + else + { + return FALSE; + } +} + + +bool FVisible( const Vector &vecOrigin, edict_t *pEdict ) +{ + TraceResult tr; + Vector vecLookerOrigin; + + // look through caller's eyes + vecLookerOrigin = pEdict->v.origin + pEdict->v.view_ofs; + + int bInWater = (POINT_CONTENTS (vecOrigin) == CONTENTS_WATER); + int bLookerInWater = (POINT_CONTENTS (vecLookerOrigin) == CONTENTS_WATER); + + // don't look through water + if (bInWater != bLookerInWater) + return FALSE; + + UTIL_TraceLine(vecLookerOrigin, vecOrigin, ignore_monsters, ignore_glass, pEdict, &tr); + + if (tr.flFraction != 1.0) + { + return FALSE; // Line of sight is not established + } + else + { + return TRUE; // line of sight is valid. + } +} + + +Vector GetGunPosition(edict_t *pEdict) +{ + return (pEdict->v.origin + pEdict->v.view_ofs); +} + + +void UTIL_SelectItem(edict_t *pEdict, char *item_name) +{ + FakeClientCommand(pEdict, item_name, NULL, NULL); +} + + +void UTIL_SelectWeapon(edict_t *pEdict, int weapon_index) +{ + usercmd_t user; + + user.lerp_msec = 0; + user.msec = 0; + user.viewangles = pEdict->v.v_angle; + user.forwardmove = 0; + user.sidemove = 0; + user.upmove = 0; + user.lightlevel = 127; + user.buttons = 0; + user.impulse = 0; + user.weaponselect = weapon_index; + user.impact_index = 0; + user.impact_position = Vector(0, 0, 0); + + MDLL_CmdStart(pEdict, &user, 0); + MDLL_CmdEnd(pEdict); +} + + +Vector VecBModelOrigin(edict_t *pEdict) +{ + return pEdict->v.absmin + (pEdict->v.size * 0.5); +} + + +bool UpdateSounds(edict_t *pEdict, edict_t *pPlayer) +{ + float distance; + static bool check_footstep_sounds = TRUE; + static float footstep_sounds_on; + float sensitivity = 1.0; + float volume; + + // update sounds made by this player, alert bots if they are nearby... + + if (check_footstep_sounds) + { + check_footstep_sounds = FALSE; + footstep_sounds_on = CVAR_GET_FLOAT("mp_footsteps"); + } + + if (footstep_sounds_on > 0.0) + { + // check if this player is moving fast enough to make sounds... + if (pPlayer->v.velocity.Length2D() > 220.0) + { + volume = 500.0; // volume of sound being made (just pick something) + + Vector v_sound = pPlayer->v.origin - pEdict->v.origin; + + distance = v_sound.Length(); + + // is the bot close enough to hear this sound? + if (distance < (volume * sensitivity)) + { + Vector bot_angles = UTIL_VecToAngles( v_sound ); + + pEdict->v.ideal_yaw = bot_angles.y; + + BotFixIdealYaw(pEdict); + + return TRUE; + } + } + } + + return FALSE; +} + + +void UTIL_ShowMenu( edict_t *pEdict, int slots, int displaytime, bool needmore, char *pText ) +{ + if (GET_USER_MSG_ID (PLID, "ShowMenu", NULL) <= 0) + REG_USER_MSG ("ShowMenu", -1); + + MESSAGE_BEGIN( MSG_ONE, GET_USER_MSG_ID (PLID, "ShowMenu", NULL), NULL, pEdict ); + + WRITE_SHORT( slots ); + WRITE_CHAR( displaytime ); + WRITE_BYTE( needmore ); + WRITE_STRING( pText ); + + MESSAGE_END(); +} + +void UTIL_BuildFileName(char *filename, char *arg1, char *arg2) +{ + filename[0] = 0; + + if (mod_id == VALVE_DLL) + strcpy(filename, "valve/"); + else if (mod_id == TFC_DLL) + strcpy(filename, "tfc/"); + else if (mod_id == CSTRIKE_DLL) + strcpy(filename, "cstrike/"); + else if (mod_id == GEARBOX_DLL) + strcpy(filename, "gearbox/"); + else if (mod_id == FRONTLINE_DLL) + strcpy(filename, "frontline/"); + else if (mod_id == HOLYWARS_DLL) + strcpy(filename, "holywars/"); + else if (mod_id == DMC_DLL) + strcpy(filename, "dmc/"); + else + { + ALERT( at_error, "HPB_bot - Error in UTIL_BuildFileName (mod ID is unknown)!" ); + + filename[0] = 0; + return; + } + + if ((arg1 != NULL) && (arg2 != NULL)) + { + if (*arg1 && *arg2) + { + strcat(filename, arg1); + strcat(filename, "/"); + strcat(filename, arg2); + } + + return; + } + + if (arg1 != NULL) + { + if (*arg1) + { + strcat(filename, arg1); + } + } +} + +//========================================================= +// UTIL_LogPrintf - Prints a logged message to console. +// Preceded by LOG: ( timestamp ) < message > +//========================================================= +void UTIL_LogPrintf( char *fmt, ... ) +{ + va_list argptr; + static char string[1024]; + + va_start ( argptr, fmt ); + vsprintf ( string, fmt, argptr ); + va_end ( argptr ); + + // Print to server console + ALERT( at_logged, "%s", string ); +} + + +void GetGameDir (char *game_dir) +{ + // This function fixes the erratic behaviour caused by the use of the GET_GAME_DIR engine + // macro, which returns either an absolute directory path, or a relative one, depending on + // whether the game server is run standalone or not. This one always return a RELATIVE path. + + unsigned char length, fieldstart, fieldstop; + + GET_GAME_DIR (game_dir); // call the engine macro and let it mallocate for the char pointer + + length = strlen (game_dir); // get the length of the returned string + length--; // ignore the trailing string terminator + + // format the returned string to get the last directory name + fieldstop = length; + while (((game_dir[fieldstop] == '\\') || (game_dir[fieldstop] == '/')) && (fieldstop > 0)) + fieldstop--; // shift back any trailing separator + + fieldstart = fieldstop; + while ((game_dir[fieldstart] != '\\') && (game_dir[fieldstart] != '/') && (fieldstart > 0)) + fieldstart--; // shift back to the start of the last subdirectory name + + if ((game_dir[fieldstart] == '\\') || (game_dir[fieldstart] == '/')) + fieldstart++; // if we reached a separator, step over it + + // now copy the formatted string back onto itself character per character + for (length = fieldstart; length <= fieldstop; length++) + game_dir[length - fieldstart] = game_dir[length]; + game_dir[length - fieldstart] = 0; // terminate the string + + return; +} diff --git a/waypoint.cpp b/waypoint.cpp new file mode 100644 index 0000000..3219e9d --- /dev/null +++ b/waypoint.cpp @@ -0,0 +1,2231 @@ +// HPB bot - botman's High Ping Bastard bot +// +// (http://planethalflife.com/botman/) +// +// waypoint.cpp +// + +#ifndef __linux__ +#include +#endif + +#include + +#ifndef __linux__ +#include +#else +#include +#include +#endif + +#include +#include +#include +#include + +#include "bot.h" +#include "waypoint.h" + + +extern int mod_id; +extern int m_spriteTexture; +extern int IsDedicatedServer; + +extern int num_backpacks; +extern BACKPACK_S backpacks[MAX_BACKPACKS]; + +// waypoints with information bits (flags) +WAYPOINT waypoints[MAX_WAYPOINTS]; + +// number of waypoints currently in use +int num_waypoints; + +// declare the array of head pointers to the path structures... +PATH *paths[MAX_WAYPOINTS]; + +// time that this waypoint was displayed (while editing) +float wp_display_time[MAX_WAYPOINTS]; + +bool g_waypoint_paths = FALSE; // have any paths been allocated? +bool g_waypoint_on = FALSE; +bool g_auto_waypoint = FALSE; +bool g_path_waypoint = FALSE; +bool g_path_waypoint_enable = TRUE; +Vector last_waypoint; +float f_path_time = 0.0; + +unsigned int route_num_waypoints; +unsigned short *shortest_path[4] = {NULL, NULL, NULL, NULL}; +unsigned short *from_to[4] = {NULL, NULL, NULL, NULL}; + +static FILE *fp; + + +void WaypointDebug(void) +{ + int y = 1, x = 1; + + fp=fopen("HPB_bot.txt","a"); + fprintf(fp,"WaypointDebug: LINKED LIST ERROR!!!\n"); + fclose(fp); + + x = x - 1; // x is zero + y = y / x; // cause an divide by zero exception + + return; +} + + +// free the linked list of waypoint path nodes... +void WaypointFree(void) +{ + for (int i=0; i < MAX_WAYPOINTS; i++) + { + int count = 0; + + if (paths[i]) + { + PATH *p = paths[i]; + PATH *p_next; + + while (p) // free the linked list + { + p_next = p->next; // save the link to next + free(p); + p = p_next; + +#ifdef _DEBUG + count++; + if (count > 1000) WaypointDebug(); +#endif + } + + paths[i] = NULL; + } + } +} + + +// initialize the waypoint structures... +void WaypointInit(void) +{ + int i; + + // have any waypoint path nodes been allocated yet? + if (g_waypoint_paths) + WaypointFree(); // must free previously allocated path memory + + for (i=0; i < 4; i++) + { + if (shortest_path[i] != NULL) + free(shortest_path[i]); + + if (from_to[i] != NULL) + free(from_to[i]); + } + + for (i=0; i < MAX_WAYPOINTS; i++) + { + waypoints[i].flags = 0; + waypoints[i].origin = Vector(0,0,0); + + wp_display_time[i] = 0.0; + + paths[i] = NULL; // no paths allocated yet + } + + f_path_time = 0.0; // reset waypoint path display time + + num_waypoints = 0; + + last_waypoint = Vector(0,0,0); + + for (i=0; i < 4; i++) + { + shortest_path[i] = NULL; + from_to[i] = NULL; + } +} + + +void WaypointAddPath(short int add_index, short int path_index) +{ + PATH *p, *prev; + int i; + int count = 0; + + p = paths[add_index]; + prev = NULL; + + // find an empty slot for new path_index... + while (p != NULL) + { + i = 0; + + while (i < MAX_PATH_INDEX) + { + if (p->index[i] == -1) + { + p->index[i] = path_index; + + return; + } + + i++; + } + + prev = p; // save the previous node in linked list + p = p->next; // go to next node in linked list + +#ifdef _DEBUG + count++; + if (count > 100) WaypointDebug(); +#endif + } + + p = (PATH *)malloc(sizeof(PATH)); + + if (p == NULL) + { + ALERT(at_error, "HPB_bot - Error allocating memory for path!"); + } + + p->index[0] = path_index; + p->index[1] = -1; + p->index[2] = -1; + p->index[3] = -1; + p->next = NULL; + + if (prev != NULL) + prev->next = p; // link new node into existing list + + if (paths[add_index] == NULL) + paths[add_index] = p; // save head point if necessary +} + + +void WaypointDeletePath(short int del_index) +{ + PATH *p; + int index, i; + + // search all paths for this index... + for (index=0; index < num_waypoints; index++) + { + p = paths[index]; + + int count = 0; + + // search linked list for del_index... + while (p != NULL) + { + i = 0; + + while (i < MAX_PATH_INDEX) + { + if (p->index[i] == del_index) + { + p->index[i] = -1; // unassign this path + } + + i++; + } + + p = p->next; // go to next node in linked list + +#ifdef _DEBUG + count++; + if (count > 100) WaypointDebug(); +#endif + } + } +} + + +void WaypointDeletePath(short int path_index, short int del_index) +{ + PATH *p; + int i; + int count = 0; + + p = paths[path_index]; + + // search linked list for del_index... + while (p != NULL) + { + i = 0; + + while (i < MAX_PATH_INDEX) + { + if (p->index[i] == del_index) + { + p->index[i] = -1; // unassign this path + } + + i++; + } + + p = p->next; // go to next node in linked list + +#ifdef _DEBUG + count++; + if (count > 100) WaypointDebug(); +#endif + } +} + + +// find a path from the current waypoint. (pPath MUST be NULL on the +// initial call. subsequent calls will return other paths if they exist.) +int WaypointFindPath(PATH **pPath, int *path_index, int waypoint_index, int team) +{ + int index; + int count = 0; + + if (*pPath == NULL) + { + *pPath = paths[waypoint_index]; + *path_index = 0; + } + + if (*path_index == MAX_PATH_INDEX) + { + *path_index = 0; + + *pPath = (*pPath)->next; // go to next node in linked list + } + + while (*pPath != NULL) + { + while (*path_index < MAX_PATH_INDEX) + { + if ((*pPath)->index[*path_index] != -1) // found a path? + { + // save the return value + index = (*pPath)->index[*path_index]; + + // skip this path if next waypoint is team specific and NOT this team + if ((team != -1) && (waypoints[index].flags & W_FL_TEAM_SPECIFIC) && + ((waypoints[index].flags & W_FL_TEAM) != team)) + { + (*path_index)++; + continue; + } + + // set up stuff for subsequent calls... + (*path_index)++; + + return index; + } + + (*path_index)++; + } + + *path_index = 0; + + *pPath = (*pPath)->next; // go to next node in linked list + +#ifdef _DEBUG + count++; + if (count > 100) WaypointDebug(); +#endif + } + + return -1; +} + +// find the nearest waypoint to the player and return the index (-1 if not found) +int WaypointFindNearest(edict_t *pEntity, float range, int team) +{ + int i, min_index; + float distance; + float min_distance; + TraceResult tr; + + if (num_waypoints < 1) + return -1; + + // find the nearest waypoint... + + min_index = -1; + min_distance = 9999.0; + + for (i=0; i < num_waypoints; i++) + { + if (waypoints[i].flags & W_FL_DELETED) + continue; // skip any deleted waypoints + + if (waypoints[i].flags & W_FL_AIMING) + continue; // skip any aiming waypoints + + // skip this waypoint if it's team specific and teams don't match... + if ((team != -1) && (waypoints[i].flags & W_FL_TEAM_SPECIFIC) && + ((waypoints[i].flags & W_FL_TEAM) != team)) + continue; + + distance = (waypoints[i].origin - pEntity->v.origin).Length(); + + if ((distance < min_distance) && (distance < range)) + { + // if waypoint is visible from current position (even behind head)... + UTIL_TraceLine( pEntity->v.origin + pEntity->v.view_ofs, waypoints[i].origin, + ignore_monsters, pEntity->v.pContainingEntity, &tr ); + + if (tr.flFraction >= 1.0) + { + min_index = i; + min_distance = distance; + } + } + } + + return min_index; +} + + +// find the nearest waypoint to the source postition and return the index +int WaypointFindNearest(Vector v_src, edict_t *pEntity, float range, int team) +{ + int index, min_index; + float distance; + float min_distance; + TraceResult tr; + + if (num_waypoints < 1) + return -1; + + // find the nearest waypoint... + + min_index = -1; + min_distance = 9999.0; + + for (index=0; index < num_waypoints; index++) + { + if (waypoints[index].flags & W_FL_DELETED) + continue; // skip any deleted waypoints + + if (waypoints[index].flags & W_FL_AIMING) + continue; // skip any aiming waypoints + + // skip this waypoint if it's team specific and teams don't match... + if ((team != -1) && (waypoints[index].flags & W_FL_TEAM_SPECIFIC) && + ((waypoints[index].flags & W_FL_TEAM) != team)) + continue; + + distance = (waypoints[index].origin - v_src).Length(); + + if ((distance < min_distance) && (distance < range)) + { + // if waypoint is visible from source position... + UTIL_TraceLine( v_src, waypoints[index].origin, ignore_monsters, + pEntity->v.pContainingEntity, &tr ); + + if (tr.flFraction >= 1.0) + { + min_index = index; + min_distance = distance; + } + } + } + + return min_index; +} + + +int WaypointFindNearestGoal(edict_t *pEntity, int src, int team, int flags) +{ + int index, min_index; + int distance, min_distance; + + if (num_waypoints < 1) + return -1; + + // find the nearest waypoint with the matching flags... + + min_index = -1; + min_distance = 99999; + + for (index=0; index < num_waypoints; index++) + { + if (index == src) + continue; // skip the source waypoint + + if (waypoints[index].flags & W_FL_DELETED) + continue; // skip any deleted waypoints + + if (waypoints[index].flags & W_FL_AIMING) + continue; // skip any aiming waypoints + + // skip this waypoint if it's team specific and teams don't match... + if ((team != -1) && (waypoints[index].flags & W_FL_TEAM_SPECIFIC) && + ((waypoints[index].flags & W_FL_TEAM) != team)) + continue; + + if ((waypoints[index].flags & flags) != flags) + continue; // skip this waypoint if the flags don't match + + distance = WaypointDistanceFromTo(src, index, team); + + if (distance < min_distance) + { + min_index = index; + min_distance = distance; + } + } + + return min_index; +} + + +int WaypointFindNearestGoal(edict_t *pEntity, int src, int team, int flags, int exclude[]) +{ + int index, min_index; + int distance, min_distance; + int exclude_index; + + if (num_waypoints < 1) + return -1; + + // find the nearest waypoint with the matching flags... + + min_index = -1; + min_distance = 99999; + + for (index=0; index < num_waypoints; index++) + { + if (index == src) + continue; // skip the source waypoint + + if (waypoints[index].flags & W_FL_DELETED) + continue; // skip any deleted waypoints + + if (waypoints[index].flags & W_FL_AIMING) + continue; // skip any aiming waypoints + + // skip this waypoint if it's team specific and teams don't match... + if ((team != -1) && (waypoints[index].flags & W_FL_TEAM_SPECIFIC) && + ((waypoints[index].flags & W_FL_TEAM) != team)) + continue; + + if ((waypoints[index].flags & flags) != flags) + continue; // skip this waypoint if the flags don't match + + exclude_index = 0; + while (exclude[exclude_index]) + { + if (index == exclude[exclude_index]) + break; // found a match, break out of while loop + + exclude_index++; + } + + if (index == exclude[exclude_index]) + continue; // skip any index that matches exclude list + + distance = WaypointDistanceFromTo(src, index, team); + + if (distance < min_distance) + { + min_index = index; + min_distance = distance; + } + } + + return min_index; +} + + +int WaypointFindNearestGoal(Vector v_src, edict_t *pEntity, float range, int team, int flags) +{ + int index, min_index; + int distance, min_distance; + + if (num_waypoints < 1) + return -1; + + // find the nearest waypoint with the matching flags... + + min_index = -1; + min_distance = 99999; + + for (index=0; index < num_waypoints; index++) + { + if (waypoints[index].flags & W_FL_DELETED) + continue; // skip any deleted waypoints + + if (waypoints[index].flags & W_FL_AIMING) + continue; // skip any aiming waypoints + + // skip this waypoint if it's team specific and teams don't match... + if ((team != -1) && (waypoints[index].flags & W_FL_TEAM_SPECIFIC) && + ((waypoints[index].flags & W_FL_TEAM) != team)) + continue; + + if ((waypoints[index].flags & flags) != flags) + continue; // skip this waypoint if the flags don't match + + distance = (waypoints[index].origin - v_src).Length(); + + if ((distance < range) && (distance < min_distance)) + { + min_index = index; + min_distance = distance; + } + } + + return min_index; +} + + +int WaypointFindRandomGoal(edict_t *pEntity, int team, int flags) +{ + int index; + int indexes[200]; + int count = 0; + + if (num_waypoints < 1) + return -1; + + // find all the waypoints with the matching flags... + + for (index=0; index < num_waypoints; index++) + { + if (waypoints[index].flags & W_FL_DELETED) + continue; // skip any deleted waypoints + + if (waypoints[index].flags & W_FL_AIMING) + continue; // skip any aiming waypoints + + // skip this waypoint if it's team specific and teams don't match... + if ((team != -1) && (waypoints[index].flags & W_FL_TEAM_SPECIFIC) && + ((waypoints[index].flags & W_FL_TEAM) != team)) + continue; + + if ((waypoints[index].flags & flags) != flags) + continue; // skip this waypoint if the flags don't match + + if (count < 200) + { + indexes[count] = index; + + count++; + } + } + + if (count == 0) // no matching waypoints found + return -1; + + index = RANDOM_LONG(1, count) - 1; + + return indexes[index]; +} + + +int WaypointFindRandomGoal(edict_t *pEntity, int team, int flags, int exclude[]) +{ + int index; + int indexes[200]; + int count = 0; + int exclude_index; + + if (num_waypoints < 1) + return -1; + + // find all the waypoints with the matching flags... + + for (index=0; index < num_waypoints; index++) + { + if (waypoints[index].flags & W_FL_DELETED) + continue; // skip any deleted waypoints + + if (waypoints[index].flags & W_FL_AIMING) + continue; // skip any aiming waypoints + + // skip this waypoint if it's team specific and teams don't match... + if ((team != -1) && (waypoints[index].flags & W_FL_TEAM_SPECIFIC) && + ((waypoints[index].flags & W_FL_TEAM) != team)) + continue; + + if ((waypoints[index].flags & flags) != flags) + continue; // skip this waypoint if the flags don't match + + exclude_index = 0; + while (exclude[exclude_index]) + { + if (index == exclude[exclude_index]) + break; // found a match, break out of while loop + + exclude_index++; + } + + if (index == exclude[exclude_index]) + continue; // skip any index that matches exclude list + + if (count < 200) + { + indexes[count] = index; + + count++; + } + } + + if (count == 0) // no matching waypoints found + return -1; + + index = RANDOM_LONG(1, count) - 1; + + return indexes[index]; +} + + +int WaypointFindRandomGoal(Vector v_src, edict_t *pEntity, float range, int team, int flags) +{ + int index; + int indexes[200]; + int count = 0; + float distance; + + if (num_waypoints < 1) + return -1; + + // find all the waypoints with the matching flags... + + for (index=0; index < num_waypoints; index++) + { + if (waypoints[index].flags & W_FL_DELETED) + continue; // skip any deleted waypoints + + if (waypoints[index].flags & W_FL_AIMING) + continue; // skip any aiming waypoints + + // skip this waypoint if it's team specific and teams don't match... + if ((team != -1) && (waypoints[index].flags & W_FL_TEAM_SPECIFIC) && + ((waypoints[index].flags & W_FL_TEAM) != team)) + continue; + + if ((waypoints[index].flags & flags) != flags) + continue; // skip this waypoint if the flags don't match + + distance = (waypoints[index].origin - v_src).Length(); + + if ((distance < range) && (count < 200)) + { + indexes[count] = index; + + count++; + } + } + + if (count == 0) // no matching waypoints found + return -1; + + index = RANDOM_LONG(1, count) - 1; + + return indexes[index]; +} + + +int WaypointFindNearestAiming(Vector v_origin) +{ + int index; + int min_index = -1; + int min_distance = 9999.0; + float distance; + + if (num_waypoints < 1) + return -1; + + // search for nearby aiming waypoint... + for (index=0; index < num_waypoints; index++) + { + if (waypoints[index].flags & W_FL_DELETED) + continue; // skip any deleted waypoints + + if ((waypoints[index].flags & W_FL_AIMING) == 0) + continue; // skip any NON aiming waypoints + + distance = (v_origin - waypoints[index].origin).Length(); + + if ((distance < min_distance) && (distance < 40)) + { + min_index = index; + min_distance = distance; + } + } + + return min_index; +} + + +void WaypointDrawBeam(edict_t *pEntity, Vector start, Vector end, int width, + int noise, int red, int green, int blue, int brightness, int speed) +{ + MESSAGE_BEGIN(MSG_ONE, SVC_TEMPENTITY, NULL, pEntity); + 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( m_spriteTexture ); + WRITE_BYTE( 1 ); // framestart + WRITE_BYTE( 10 ); // framerate + WRITE_BYTE( 10 ); // 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 WaypointSearchItems(edict_t *pEntity, Vector origin, int wpt_index) +{ + edict_t *pent = NULL; + float radius = 40; + TraceResult tr; + float distance; + float min_distance; + char item_name[64]; + char nearest_name[64]; + edict_t *nearest_pent; + int bck_index; + int tfc_backpack_index; + + nearest_name[0] = 0; // null out nearest_name string + tfc_backpack_index = -1; // "null" out backpack index + nearest_pent = NULL; + + min_distance = 9999.0; + + //******************************************************** + // look for the nearest health, armor, ammo, weapon, etc. + //******************************************************** + + while ((pent = UTIL_FindEntityInSphere( pent, origin, radius )) != NULL) + { + if (pEntity) + UTIL_TraceLine( origin, pent->v.origin, ignore_monsters, + pEntity->v.pContainingEntity, &tr ); + else + UTIL_TraceLine( origin, pent->v.origin, ignore_monsters, NULL, &tr ); + + // make sure entity is visible... + if (tr.flFraction >= 1.0) + { + strcpy(item_name, STRING(pent->v.classname)); + + if ((strncmp("item_health", item_name, 11) == 0) || + (strncmp("item_armor", item_name, 10) == 0) || + (strncmp("ammo_", item_name, 5) == 0) || + (strcmp("item_cells", item_name) == 0) || + (strcmp("item_shells", item_name) == 0) || + (strcmp("item_spikes", item_name) == 0) || + (strcmp("item_rockets", item_name) == 0) || + ((strncmp("weapon_", item_name, 7) == 0) && + (pent->v.owner == NULL))) + { + distance = (pent->v.origin - origin).Length(); + + if (distance < min_distance) + { + strcpy(nearest_name, item_name); + + tfc_backpack_index = -1; // "null" out backpack index + + nearest_pent = pent; + + min_distance = distance; + } + } + + if (mod_id == TFC_DLL) + { + for (bck_index=0; bck_index < num_backpacks; bck_index++) + { + distance = (pent->v.origin - origin).Length(); + + if ((backpacks[bck_index].edict == pent) && + (distance < min_distance)) + { + tfc_backpack_index = bck_index; + + nearest_pent = pent; + + nearest_name[0] = 0; // null out nearest_name string + + min_distance = distance; + + break; + } + } + } + } + } + + if (nearest_name[0]) // found an entity name + { + if (strncmp("item_health", nearest_name, 11) == 0) + { + if (pEntity) + ClientPrint(pEntity, HUD_PRINTCONSOLE, "found a healthkit!\n"); + waypoints[wpt_index].flags |= W_FL_HEALTH; + } + + if (strncmp("item_armor", nearest_name, 10) == 0) + { + if (pEntity) + ClientPrint(pEntity, HUD_PRINTCONSOLE, "found some armor!\n"); + waypoints[wpt_index].flags |= W_FL_ARMOR; + } + + if ((strncmp("ammo_", nearest_name, 5) == 0) || + (strcmp("item_cells", nearest_name) == 0) || + (strcmp("item_shells", nearest_name) == 0) || + (strcmp("item_spikes", nearest_name) == 0) || + (strcmp("item_rockets", nearest_name) == 0)) + { + if (pEntity) + ClientPrint(pEntity, HUD_PRINTCONSOLE, "found some ammo!\n"); + waypoints[wpt_index].flags |= W_FL_AMMO; + } + + if ((strncmp("weapon_", nearest_name, 7) == 0) && + (nearest_pent->v.owner == NULL)) + { + if (pEntity) + ClientPrint(pEntity, HUD_PRINTCONSOLE, "found a weapon!\n"); + waypoints[wpt_index].flags |= W_FL_WEAPON; + } + } + + if ((mod_id == TFC_DLL) && + (tfc_backpack_index != -1)) // found a TFC backpack + { + if (backpacks[tfc_backpack_index].health) + { + if (pEntity) + ClientPrint(pEntity, HUD_PRINTCONSOLE, "found health!\n"); + waypoints[wpt_index].flags |= W_FL_HEALTH; + } + + if (backpacks[tfc_backpack_index].armor) + { + if (pEntity) + ClientPrint(pEntity, HUD_PRINTCONSOLE, "found some armor!\n"); + waypoints[wpt_index].flags |= W_FL_ARMOR; + } + + if (backpacks[tfc_backpack_index].ammo) + { + if (pEntity) + ClientPrint(pEntity, HUD_PRINTCONSOLE, "found some ammo!\n"); + waypoints[wpt_index].flags |= W_FL_AMMO; + } + } +} + + +void WaypointAdd(edict_t *pEntity) +{ + int index; + + if (num_waypoints >= MAX_WAYPOINTS) + return; + + index = 0; + + // find the next available slot for the new waypoint... + while (index < num_waypoints) + { + if (waypoints[index].flags & W_FL_DELETED) + break; + + index++; + } + + waypoints[index].flags = 0; + + // store the origin (location) of this waypoint (use entity origin) + waypoints[index].origin = pEntity->v.origin; + + // store the last used waypoint for the auto waypoint code... + last_waypoint = pEntity->v.origin; + + // set the time that this waypoint was originally displayed... + wp_display_time[index] = gpGlobals->time; + + + Vector start, end; + + start = pEntity->v.origin - Vector(0, 0, 34); + end = start + Vector(0, 0, 68); + + if ((pEntity->v.flags & FL_DUCKING) == FL_DUCKING) + { + waypoints[index].flags |= W_FL_CROUCH; // crouching waypoint + + start = pEntity->v.origin - Vector(0, 0, 17); + end = start + Vector(0, 0, 34); + } + + if (pEntity->v.movetype == MOVETYPE_FLY) + waypoints[index].flags |= W_FL_LADDER; // waypoint on a ladder + + + // search the area near the waypoint for items (HEALTH, AMMO, WEAPON, etc.) + WaypointSearchItems(pEntity, waypoints[index].origin, index); + + + // draw a blue waypoint + WaypointDrawBeam(pEntity, start, end, 30, 0, 0, 0, 255, 250, 5); + + EMIT_SOUND_DYN2(pEntity, CHAN_WEAPON, "weapons/xbow_hit1.wav", 1.0, + ATTN_NORM, 0, 100); + + // increment total number of waypoints if adding at end of array... + if (index == num_waypoints) + num_waypoints++; + + // calculate all the paths to this new waypoint + for (int i=0; i < num_waypoints; i++) + { + if (i == index) + continue; // skip the waypoint that was just added + + if (waypoints[i].flags & W_FL_AIMING) + continue; // skip any aiming waypoints + + // check if the waypoint is reachable from the new one (one-way) + if ( WaypointReachable(pEntity->v.origin, waypoints[i].origin, pEntity) && + g_path_waypoint_enable) + { + WaypointAddPath(index, i); + } + + // check if the new one is reachable from the waypoint (other way) + if ( WaypointReachable(waypoints[i].origin, pEntity->v.origin, pEntity) && + g_path_waypoint_enable) + { + WaypointAddPath(i, index); + } + } +} + + +void WaypointAddAiming(edict_t *pEntity) +{ + int index; + edict_t *pent = NULL; + + if (num_waypoints >= MAX_WAYPOINTS) + return; + + index = 0; + + // find the next available slot for the new waypoint... + while (index < num_waypoints) + { + if (waypoints[index].flags & W_FL_DELETED) + break; + + index++; + } + + waypoints[index].flags = W_FL_AIMING; // aiming waypoint + + Vector v_angle = pEntity->v.v_angle; + + v_angle.x = 0; // reset pitch to horizontal + v_angle.z = 0; // reset roll to level + + MAKE_VECTORS(v_angle); + + // store the origin (location) of this waypoint (use entity origin) + waypoints[index].origin = pEntity->v.origin + gpGlobals->v_forward * 25; + + // set the time that this waypoint was originally displayed... + wp_display_time[index] = gpGlobals->time; + + + Vector start, end; + + start = pEntity->v.origin - Vector(0, 0, 10); + end = start + Vector(0, 0, 14); + + // draw a blue waypoint + WaypointDrawBeam(pEntity, start, end, 30, 0, 0, 0, 255, 250, 5); + + EMIT_SOUND_DYN2(pEntity, CHAN_WEAPON, "weapons/xbow_hit1.wav", 1.0, + ATTN_NORM, 0, 100); + + // increment total number of waypoints if adding at end of array... + if (index == num_waypoints) + num_waypoints++; +} + + +void WaypointDelete(edict_t *pEntity) +{ + int index; + int count = 0; + + if (num_waypoints < 1) + return; + + index = WaypointFindNearest(pEntity, 50.0, -1); + + if (index == -1) + return; + + if ((waypoints[index].flags & W_FL_SNIPER) || + (waypoints[index].flags & W_FL_SENTRYGUN) || + (waypoints[index].flags & W_FL_DISPENSER) || + (waypoints[index].flags & W_FL_JUMP) || + ((mod_id == FRONTLINE_DLL) && (waypoints[index].flags & W_FL_FLF_DEFEND))) + { + int i; + int min_index = -1; + int min_distance = 9999.0; + float distance; + + // search for nearby aiming waypoint and delete it also... + for (i=0; i < num_waypoints; i++) + { + if (waypoints[i].flags & W_FL_DELETED) + continue; // skip any deleted waypoints + + if ((waypoints[i].flags & W_FL_AIMING) == 0) + continue; // skip any NON aiming waypoints + + distance = (waypoints[i].origin - waypoints[index].origin).Length(); + + if ((distance < min_distance) && (distance < 40)) + { + min_index = i; + min_distance = distance; + } + } + + if (min_index != -1) + { + waypoints[min_index].flags = W_FL_DELETED; // not being used + waypoints[min_index].origin = Vector(0,0,0); + + wp_display_time[min_index] = 0.0; + } + } + + // delete any paths that lead to this index... + WaypointDeletePath(index); + + // free the path for this index... + + if (paths[index] != NULL) + { + PATH *p = paths[index]; + PATH *p_next; + + while (p) // free the linked list + { + p_next = p->next; // save the link to next + free(p); + p = p_next; + +#ifdef _DEBUG + count++; + if (count > 100) WaypointDebug(); +#endif + } + + paths[index] = NULL; + } + + waypoints[index].flags = W_FL_DELETED; // not being used + waypoints[index].origin = Vector(0,0,0); + + wp_display_time[index] = 0.0; + + EMIT_SOUND_DYN2(pEntity, CHAN_WEAPON, "weapons/mine_activate.wav", 1.0, + ATTN_NORM, 0, 100); +} + + +void WaypointUpdate(edict_t *pEntity) +{ + int index; + int mask; + + mask = W_FL_HEALTH | W_FL_ARMOR | W_FL_AMMO | W_FL_WEAPON; + + for (index=0; index < num_waypoints; index++) + { + waypoints[index].flags &= ~mask; // clear the mask bits + + WaypointSearchItems(NULL, waypoints[index].origin, index); + } +} + + +// allow player to manually create a path from one waypoint to another +void WaypointCreatePath(edict_t *pEntity, int cmd) +{ + static int waypoint1 = -1; // initialized to unassigned + static int waypoint2 = -1; // initialized to unassigned + + if (cmd == 1) // assign source of path + { + waypoint1 = WaypointFindNearest(pEntity, 50.0, -1); + + if (waypoint1 == -1) + { + // play "cancelled" sound... + EMIT_SOUND_DYN2(pEntity, CHAN_WEAPON, "common/wpn_moveselect.wav", 1.0, + ATTN_NORM, 0, 100); + + return; + } + + // play "start" sound... + EMIT_SOUND_DYN2(pEntity, CHAN_WEAPON, "common/wpn_hudoff.wav", 1.0, + ATTN_NORM, 0, 100); + + return; + } + + if (cmd == 2) // assign dest of path and make path + { + waypoint2 = WaypointFindNearest(pEntity, 50.0, -1); + + if ((waypoint1 == -1) || (waypoint2 == -1)) + { + // play "error" sound... + EMIT_SOUND_DYN2(pEntity, CHAN_WEAPON, "common/wpn_denyselect.wav", 1.0, + ATTN_NORM, 0, 100); + + return; + } + + WaypointAddPath(waypoint1, waypoint2); + + // play "done" sound... + EMIT_SOUND_DYN2(pEntity, CHAN_WEAPON, "common/wpn_hudon.wav", 1.0, + ATTN_NORM, 0, 100); + } +} + + +// allow player to manually remove a path from one waypoint to another +void WaypointRemovePath(edict_t *pEntity, int cmd) +{ + static int waypoint1 = -1; // initialized to unassigned + static int waypoint2 = -1; // initialized to unassigned + + if (cmd == 1) // assign source of path + { + waypoint1 = WaypointFindNearest(pEntity, 50.0, -1); + + if (waypoint1 == -1) + { + // play "cancelled" sound... + EMIT_SOUND_DYN2(pEntity, CHAN_WEAPON, "common/wpn_moveselect.wav", 1.0, + ATTN_NORM, 0, 100); + + return; + } + + // play "start" sound... + EMIT_SOUND_DYN2(pEntity, CHAN_WEAPON, "common/wpn_hudoff.wav", 1.0, + ATTN_NORM, 0, 100); + + return; + } + + if (cmd == 2) // assign dest of path and make path + { + waypoint2 = WaypointFindNearest(pEntity, 50.0, -1); + + if ((waypoint1 == -1) || (waypoint2 == -1)) + { + // play "error" sound... + EMIT_SOUND_DYN2(pEntity, CHAN_WEAPON, "common/wpn_denyselect.wav", 1.0, + ATTN_NORM, 0, 100); + + return; + } + + WaypointDeletePath(waypoint1, waypoint2); + + // play "done" sound... + EMIT_SOUND_DYN2(pEntity, CHAN_WEAPON, "common/wpn_hudon.wav", 1.0, + ATTN_NORM, 0, 100); + } +} + + +bool WaypointLoad(edict_t *pEntity) +{ + FILE *bfp; + char mapname[64]; + char filename[256]; + char new_filename[256]; +#ifdef __linux__ + char cmd[512]; +#endif + WAYPOINT_HDR header; + char msg[80]; + int index, i; + short int num; + short int path_index; + bool need_rename; + + strcpy(mapname, STRING(gpGlobals->mapname)); + strcat(mapname, ".HPB_wpt"); + + UTIL_BuildFileName(filename, "maps", mapname); + + if (IsDedicatedServer) + printf("loading waypoint file: %s\n", filename); + + bfp = fopen(filename, "rb"); + + need_rename = FALSE; + + // if .HBP_wpt files doesn't exist, check .wpt file... + if (bfp == NULL) + { + need_rename = TRUE; + + strcpy(mapname, STRING(gpGlobals->mapname)); + strcat(mapname, ".wpt"); + + UTIL_BuildFileName(filename, "maps", mapname); + + if (IsDedicatedServer) + printf("loading waypoint file: %s\n", filename); + + bfp = fopen(filename, "rb"); + } + + // if file exists, read the waypoint structure from it + if (bfp != NULL) + { + fread(&header, sizeof(header), 1, bfp); + + header.filetype[7] = 0; + if (strcmp(header.filetype, "HPB_bot") == 0) + { + if (header.waypoint_file_version != WAYPOINT_VERSION) + { + if (pEntity) + ClientPrint(pEntity, HUD_PRINTNOTIFY, "Incompatible HPB bot waypoint file version!\nWaypoints not loaded!\n"); + + fclose(bfp); + return FALSE; + } + + header.mapname[31] = 0; + + if (strcmp(header.mapname, STRING(gpGlobals->mapname)) == 0) + { + WaypointInit(); // remove any existing waypoints + + for (i=0; i < header.number_of_waypoints; i++) + { + fread(&waypoints[i], sizeof(waypoints[0]), 1, bfp); + num_waypoints++; + } + + // read and add waypoint paths... + for (index=0; index < num_waypoints; index++) + { + // read the number of paths from this node... + fread(&num, sizeof(num), 1, bfp); + + for (i=0; i < num; i++) + { + fread(&path_index, sizeof(path_index), 1, bfp); + + WaypointAddPath(index, path_index); + } + } + + g_waypoint_paths = TRUE; // keep track so path can be freed + } + else + { + if (pEntity) + { + sprintf(msg, "%s HPB bot waypoints are not for this map!\n", filename); + ClientPrint(pEntity, HUD_PRINTNOTIFY, msg); + } + + fclose(bfp); + return FALSE; + } + } + else + { + if (pEntity) + { + sprintf(msg, "%s is not a HPB bot waypoint file!\n", filename); + ClientPrint(pEntity, HUD_PRINTNOTIFY, msg); + } + + fclose(bfp); + return FALSE; + } + + fclose(bfp); + + if (need_rename) + { + strcpy(mapname, STRING(gpGlobals->mapname)); + strcat(mapname, ".HPB_wpt"); + + UTIL_BuildFileName(new_filename, "maps", mapname); + +#ifndef __linux__ + rename(filename, new_filename); +#else + sprintf(cmd, "/bin/mv -f %s %s", filename, new_filename); + system(cmd); +#endif + } + + WaypointRouteInit(); + } + else + { + if (pEntity) + { + sprintf(msg, "Waypoint file %s does not exist!\n", filename); + ClientPrint(pEntity, HUD_PRINTNOTIFY, msg); + } + + if (IsDedicatedServer) + printf("waypoint file %s not found!\n", filename); + + return FALSE; + } + + return TRUE; +} + + +void WaypointSave(void) +{ + char filename[256]; + char mapname[64]; + WAYPOINT_HDR header; + int index, i; + short int num; + PATH *p; + + strcpy(header.filetype, "HPB_bot"); + + header.waypoint_file_version = WAYPOINT_VERSION; + + header.waypoint_file_flags = 0; // not currently used + + header.number_of_waypoints = num_waypoints; + + memset(header.mapname, 0, sizeof(header.mapname)); + strncpy(header.mapname, STRING(gpGlobals->mapname), 31); + header.mapname[31] = 0; + + strcpy(mapname, STRING(gpGlobals->mapname)); + strcat(mapname, ".HPB_wpt"); + + UTIL_BuildFileName(filename, "maps", mapname); + + FILE *bfp = fopen(filename, "wb"); + + // write the waypoint header to the file... + fwrite(&header, sizeof(header), 1, bfp); + + // write the waypoint data to the file... + for (index=0; index < num_waypoints; index++) + { + fwrite(&waypoints[index], sizeof(waypoints[0]), 1, bfp); + } + + // save the waypoint paths... + for (index=0; index < num_waypoints; index++) + { + // count the number of paths from this node... + + p = paths[index]; + num = 0; + + while (p != NULL) + { + i = 0; + + while (i < MAX_PATH_INDEX) + { + if (p->index[i] != -1) + num++; // count path node if it's used + + i++; + } + + p = p->next; // go to next node in linked list + } + + fwrite(&num, sizeof(num), 1, bfp); // write the count + + // now write out each path index... + + p = paths[index]; + + while (p != NULL) + { + i = 0; + + while (i < MAX_PATH_INDEX) + { + if (p->index[i] != -1) // save path node if it's used + fwrite(&p->index[i], sizeof(p->index[0]), 1, bfp); + + i++; + } + + p = p->next; // go to next node in linked list + } + } + + fclose(bfp); +} + + +bool WaypointReachable(Vector v_src, Vector v_dest, edict_t *pEntity) +{ + TraceResult tr; + float curr_height, last_height; + + float distance = (v_dest - v_src).Length(); + + // is the destination close enough? + if (distance < REACHABLE_RANGE) + { + // check if this waypoint is "visible"... + + UTIL_TraceLine( v_src, v_dest, ignore_monsters, + pEntity->v.pContainingEntity, &tr ); + + // if waypoint is visible from current position (even behind head)... + if (tr.flFraction >= 1.0) + { + // check for special case of both waypoints being underwater... + if ((POINT_CONTENTS( v_src ) == CONTENTS_WATER) && + (POINT_CONTENTS( v_dest ) == CONTENTS_WATER)) + { + return TRUE; + } + + // check for special case of waypoint being suspended in mid-air... + + // is dest waypoint higher than src? (45 is max jump height) + if (v_dest.z > (v_src.z + 45.0)) + { + Vector v_new_src = v_dest; + Vector v_new_dest = v_dest; + + v_new_dest.z = v_new_dest.z - 50; // straight down 50 units + + UTIL_TraceLine(v_new_src, v_new_dest, dont_ignore_monsters, + pEntity->v.pContainingEntity, &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 increases more than jump height + // at points between source and destination... + + Vector v_direction = (v_dest - v_src).Normalize(); // 1 unit long + Vector v_check = v_src; + Vector v_down = v_src; + + v_down.z = v_down.z - 1000.0; // straight down 1000 units + + UTIL_TraceLine(v_check, v_down, ignore_monsters, + pEntity->v.pContainingEntity, &tr); + + last_height = tr.flFraction * 1000.0; // height from ground + + distance = (v_dest - v_check).Length(); // distance from goal + + while (distance > 10.0) + { + // move 10 units closer to the goal... + v_check = v_check + (v_direction * 10.0); + + v_down = v_check; + v_down.z = v_down.z - 1000.0; // straight down 1000 units + + UTIL_TraceLine(v_check, v_down, ignore_monsters, + pEntity->v.pContainingEntity, &tr); + + curr_height = tr.flFraction * 1000.0; // height from ground + + // is the difference in the last height and the current height + // higher that the jump height? + if ((last_height - curr_height) > 45.0) + { + // can't get there from here... + return FALSE; + } + + last_height = curr_height; + + distance = (v_dest - v_check).Length(); // distance from goal + } + + return TRUE; + } + } + + return FALSE; +} + + +// find the nearest reachable waypoint +int WaypointFindReachable(edict_t *pEntity, float range, int team) +{ + int i, min_index; + float distance; + float min_distance; + TraceResult tr; + + // find the nearest waypoint... + + min_distance = 9999.0; + + for (i=0; i < num_waypoints; i++) + { + if (waypoints[i].flags & W_FL_DELETED) + continue; // skip any deleted waypoints + + if (waypoints[i].flags & W_FL_AIMING) + continue; // skip any aiming waypoints + + // skip this waypoint if it's team specific and teams don't match... + if ((team != -1) && (waypoints[i].flags & W_FL_TEAM_SPECIFIC) && + ((waypoints[i].flags & W_FL_TEAM) != team)) + continue; + + distance = (waypoints[i].origin - pEntity->v.origin).Length(); + + if (distance < min_distance) + { + // if waypoint is visible from current position (even behind head)... + UTIL_TraceLine( pEntity->v.origin + pEntity->v.view_ofs, waypoints[i].origin, + ignore_monsters, pEntity->v.pContainingEntity, &tr ); + + if (tr.flFraction >= 1.0) + { + if (WaypointReachable(pEntity->v.origin, waypoints[i].origin, pEntity)) + { + min_index = i; + min_distance = distance; + } + } + } + } + + // if not close enough to a waypoint then just return + if (min_distance > range) + return -1; + + return min_index; + +} + + +void WaypointPrintInfo(edict_t *pEntity) +{ + char msg[80]; + int index; + int flags; + + // find the nearest waypoint... + index = WaypointFindNearest(pEntity, 50.0, -1); + + if (index == -1) + return; + + sprintf(msg,"Waypoint %d of %d total\n", index, num_waypoints); + ClientPrint(pEntity, HUD_PRINTNOTIFY, msg); + + flags = waypoints[index].flags; + + if (flags & W_FL_TEAM_SPECIFIC) + { + if (mod_id == FRONTLINE_DLL) + { + if ((flags & W_FL_TEAM) == 0) + strcpy(msg, "Waypoint is for Attackers\n"); + else if ((flags & W_FL_TEAM) == 1) + strcpy(msg, "Waypoint is for Defenders\n"); + } + else + { + if ((flags & W_FL_TEAM) == 0) + strcpy(msg, "Waypoint is for TEAM 1\n"); + else if ((flags & W_FL_TEAM) == 1) + strcpy(msg, "Waypoint is for TEAM 2\n"); + else if ((flags & W_FL_TEAM) == 2) + strcpy(msg, "Waypoint is for TEAM 3\n"); + else if ((flags & W_FL_TEAM) == 3) + strcpy(msg, "Waypoint is for TEAM 4\n"); + } + + ClientPrint(pEntity, HUD_PRINTNOTIFY, msg); + } + + if (flags & W_FL_LIFT) + ClientPrint(pEntity, HUD_PRINTNOTIFY, "Bot will wait for lift before approaching\n"); + + if (flags & W_FL_LADDER) + ClientPrint(pEntity, HUD_PRINTNOTIFY, "This waypoint is on a ladder\n"); + + if (flags & W_FL_DOOR) + ClientPrint(pEntity, HUD_PRINTNOTIFY, "This is a door waypoint\n"); + + if (flags & W_FL_HEALTH) + ClientPrint(pEntity, HUD_PRINTNOTIFY, "There is health near this waypoint\n"); + + if (flags & W_FL_ARMOR) + ClientPrint(pEntity, HUD_PRINTNOTIFY, "There is armor near this waypoint\n"); + + if (flags & W_FL_AMMO) + ClientPrint(pEntity, HUD_PRINTNOTIFY, "There is ammo near this waypoint\n"); + + if (flags & W_FL_WEAPON) + ClientPrint(pEntity, HUD_PRINTNOTIFY, "There is a weapon near this waypoint\n"); + + if (flags & W_FL_JUMP) + ClientPrint(pEntity, HUD_PRINTNOTIFY, "Bot will jump here\n"); + + if (flags & W_FL_SNIPER) + ClientPrint(pEntity, HUD_PRINTNOTIFY, "This is a sniper waypoint\n"); + + if (flags & W_FL_FLAG) + { + if (mod_id == FRONTLINE_DLL) + ClientPrint(pEntity, HUD_PRINTNOTIFY, "There is a capture point near this waypoint\n"); + else if (mod_id == HOLYWARS_DLL) + ClientPrint(pEntity, HUD_PRINTNOTIFY, "There is a halo spawn point near this waypoint\n"); + else + ClientPrint(pEntity, HUD_PRINTNOTIFY, "There is a flag near this waypoint\n"); + } + + if (flags & W_FL_FLAG_GOAL) + { + if (mod_id == FRONTLINE_DLL) + ClientPrint(pEntity, HUD_PRINTNOTIFY, "This is a defender location\n"); + else + ClientPrint(pEntity, HUD_PRINTNOTIFY, "There is a flag goal near this waypoint\n"); + } + + if (flags & W_FL_PRONE) + ClientPrint(pEntity, HUD_PRINTNOTIFY, "Bot will go prone here\n"); + + if (flags & W_FL_SENTRYGUN) + ClientPrint(pEntity, HUD_PRINTNOTIFY, "Engineers will build a sentry gun here\n"); + + if (flags & W_FL_DISPENSER) + ClientPrint(pEntity, HUD_PRINTNOTIFY, "Engineers will build a dispenser here\n"); +} + + +void WaypointThink(edict_t *pEntity) +{ + float distance, min_distance; + Vector start, end; + int i, index; + + if (g_auto_waypoint) // is auto waypoint on? + { + // find the distance from the last used waypoint + distance = (last_waypoint - pEntity->v.origin).Length(); + + if (distance > 200) + { + min_distance = 9999.0; + + // check that no other reachable waypoints are nearby... + for (i=0; i < num_waypoints; i++) + { + if (waypoints[i].flags & W_FL_DELETED) + continue; + + if (waypoints[i].flags & W_FL_AIMING) + continue; + + if (WaypointReachable(pEntity->v.origin, waypoints[i].origin, pEntity)) + { + distance = (waypoints[i].origin - pEntity->v.origin).Length(); + + if (distance < min_distance) + min_distance = distance; + } + } + + // make sure nearest waypoint is far enough away... + if (min_distance >= 200) + WaypointAdd(pEntity); // place a waypoint here + } + } + + min_distance = 9999.0; + + if (g_waypoint_on) // display the waypoints if turned on... + { + for (i=0; i < num_waypoints; i++) + { + if ((waypoints[i].flags & W_FL_DELETED) == W_FL_DELETED) + continue; + + distance = (waypoints[i].origin - pEntity->v.origin).Length(); + + if (distance < 500) + { + if (distance < min_distance) + { + index = i; // store index of nearest waypoint + min_distance = distance; + } + + if ((wp_display_time[i] + 1.0) < gpGlobals->time) + { + if (waypoints[i].flags & W_FL_CROUCH) + { + start = waypoints[i].origin - Vector(0, 0, 17); + end = start + Vector(0, 0, 34); + } + else if (waypoints[i].flags & W_FL_AIMING) + { + start = waypoints[i].origin + Vector(0, 0, 10); + end = start + Vector(0, 0, 14); + } + else + { + start = waypoints[i].origin - Vector(0, 0, 34); + end = start + Vector(0, 0, 68); + } + + // draw a blue waypoint + WaypointDrawBeam(pEntity, start, end, 30, 0, 0, 0, 255, 250, 5); + + wp_display_time[i] = gpGlobals->time; + } + } + } + + // check if path waypointing is on... + if (g_path_waypoint) + { + // check if player is close enough to a waypoint and time to draw path... + if ((min_distance <= 50) && (f_path_time <= gpGlobals->time)) + { + PATH *p; + + f_path_time = gpGlobals->time + 1.0; + + p = paths[index]; + + while (p != NULL) + { + i = 0; + + while (i < MAX_PATH_INDEX) + { + if (p->index[i] != -1) + { + Vector v_src = waypoints[index].origin; + Vector v_dest = waypoints[p->index[i]].origin; + + // draw a white line to this index's waypoint + WaypointDrawBeam(pEntity, v_src, v_dest, 10, 2, 250, 250, 250, 200, 10); + } + + i++; + } + + p = p->next; // go to next node in linked list + } + } + } + } +} + + +void WaypointFloyds(unsigned short *shortest_path, unsigned short *from_to) +{ + unsigned int x, y, z; + int changed = 1; + int distance; + + for (y=0; y < route_num_waypoints; y++) + { + for (z=0; z < route_num_waypoints; z++) + { + from_to[y * route_num_waypoints + z] = z; + } + } + + while (changed) + { + changed = 0; + + for (x=0; x < route_num_waypoints; x++) + { + for (y=0; y < route_num_waypoints; y++) + { + for (z=0; z < route_num_waypoints; z++) + { + if ((shortest_path[y * route_num_waypoints + x] == WAYPOINT_UNREACHABLE) || + (shortest_path[x * route_num_waypoints + z] == WAYPOINT_UNREACHABLE)) + continue; + + distance = shortest_path[y * route_num_waypoints + x] + + shortest_path[x * route_num_waypoints + z]; + + if (distance > WAYPOINT_MAX_DISTANCE) + distance = WAYPOINT_MAX_DISTANCE; + + if ((distance < shortest_path[y * route_num_waypoints + z]) || + (shortest_path[y * route_num_waypoints + z] == WAYPOINT_UNREACHABLE)) + { + shortest_path[y * route_num_waypoints + z] = distance; + from_to[y * route_num_waypoints + z] = from_to[y * route_num_waypoints + x]; + changed = 1; + } + } + } + } + } +} + + +void WaypointRouteInit(void) +{ + unsigned int index; + bool build_matrix[4]; + int matrix; + unsigned int array_size; + unsigned int row; + int i, offset; + unsigned int a, b; + float distance; + unsigned short *pShortestPath, *pFromTo; + char msg[80]; + unsigned int num_items; + FILE *bfp; + char filename[256]; + char filename2[256]; + char mapname[64]; + + if (num_waypoints == 0) + return; + + // save number of current waypoints in case waypoints get added later + route_num_waypoints = num_waypoints; + + strcpy(mapname, STRING(gpGlobals->mapname)); + strcat(mapname, ".HPB_wpt"); + + UTIL_BuildFileName(filename, "maps", mapname); + + build_matrix[0] = TRUE; // always build matrix 0 (non-team and team 1) + build_matrix[1] = FALSE; + build_matrix[2] = FALSE; + build_matrix[3] = FALSE; + + // find out how many route matrixes to create... + for (index=0; index < route_num_waypoints; index++) + { + if (waypoints[index].flags & W_FL_TEAM_SPECIFIC) + { + if ((waypoints[index].flags & W_FL_TEAM) == 0x01) // team 2? + build_matrix[1] = TRUE; + + if ((waypoints[index].flags & W_FL_TEAM) == 0x02) // team 3? + build_matrix[2] = TRUE; + + if ((waypoints[index].flags & W_FL_TEAM) == 0x03) // team 4? + build_matrix[3] = TRUE; + } + } + + array_size = route_num_waypoints * route_num_waypoints; + + for (matrix=0; matrix < 4; matrix++) + { + if (build_matrix[matrix]) + { + char ext_str[16]; // ".HPB_wpX\0" + int file1, file2; + struct stat stat1, stat2; + + sprintf(ext_str, ".HPB_wp%d", matrix+1); + + strcpy(mapname, STRING(gpGlobals->mapname)); + strcat(mapname, ext_str); + + UTIL_BuildFileName(filename2, "maps", mapname); + + if (access(filename2, 0) == 0) // does the .HPB_wpX file exist? + { + file1 = open(filename, O_RDONLY); + file2 = open(filename2, O_RDONLY); + + fstat(file1, &stat1); + fstat(file2, &stat2); + + close(file1); + close(file2); + + if (stat1.st_mtime < stat2.st_mtime) // is .HPB_wpt older than .HPB_wpX file? + { + sprintf(msg, "loading HPB bot waypoint paths for team %d\n", matrix+1); + ALERT(at_console, msg); + + shortest_path[matrix] = (unsigned short *)malloc(sizeof(unsigned short) * array_size); + + if (shortest_path[matrix] == NULL) + ALERT(at_error, "HPB_bot - Error allocating memory for shortest path!"); + + from_to[matrix] = (unsigned short *)malloc(sizeof(unsigned short) * array_size); + + if (from_to[matrix] == NULL) + ALERT(at_error, "HPB_bot - Error allocating memory for from to matrix!"); + + bfp = fopen(filename2, "rb"); + + if (bfp != NULL) + { + num_items = fread(shortest_path[matrix], sizeof(unsigned short), array_size, bfp); + + if (num_items != array_size) + { + // if couldn't read enough data, free memory to recalculate it + + ALERT(at_console, "error reading enough path items, recalculating...\n"); + + free(shortest_path[matrix]); + shortest_path[matrix] = NULL; + + free(from_to[matrix]); + from_to[matrix] = NULL; + } + else + { + num_items = fread(from_to[matrix], sizeof(unsigned short), array_size, bfp); + + if (num_items != array_size) + { + // if couldn't read enough data, free memory to recalculate it + + ALERT(at_console, "error reading enough path items, recalculating...\n"); + + free(shortest_path[matrix]); + shortest_path[matrix] = NULL; + + free(from_to[matrix]); + from_to[matrix] = NULL; + } + } + } + else + { + ALERT(at_console, "HPB_bot - Error reading waypoint paths!\n"); + + free(shortest_path[matrix]); + shortest_path[matrix] = NULL; + + free(from_to[matrix]); + from_to[matrix] = NULL; + } + + fclose(bfp); + } + } + + if (shortest_path[matrix] == NULL) + { + sprintf(msg, "calculating HPB bot waypoint paths for team %d...\n", matrix+1); + ALERT(at_console, msg); + + shortest_path[matrix] = (unsigned short *)malloc(sizeof(unsigned short) * array_size); + + if (shortest_path[matrix] == NULL) + ALERT(at_error, "HPB_bot - Error allocating memory for shortest path!"); + + from_to[matrix] = (unsigned short *)malloc(sizeof(unsigned short) * array_size); + + if (from_to[matrix] == NULL) + ALERT(at_error, "HPB_bot - Error allocating memory for from to matrix!"); + + pShortestPath = shortest_path[matrix]; + pFromTo = from_to[matrix]; + + for (index=0; index < array_size; index++) + pShortestPath[index] = WAYPOINT_UNREACHABLE; + + for (index=0; index < route_num_waypoints; index++) + pShortestPath[index * route_num_waypoints + index] = 0; // zero diagonal + + for (row=0; row < route_num_waypoints; row++) + { + if (paths[row] != NULL) + { + PATH *p = paths[row]; + + while (p) + { + i = 0; + + while (i < MAX_PATH_INDEX) + { + if (p->index[i] != -1) + { + index = p->index[i]; + + // check if this is NOT team specific OR matches this team + if (!(waypoints[index].flags & W_FL_TEAM_SPECIFIC) || + ((waypoints[index].flags & W_FL_TEAM) == matrix)) + { + distance = (waypoints[row].origin - waypoints[index].origin).Length(); + + if (distance > (float)WAYPOINT_MAX_DISTANCE) + distance = (float)WAYPOINT_MAX_DISTANCE; + + if (distance > REACHABLE_RANGE) + { + sprintf(msg, "Waypoint path distance > %4.1f at from %d to %d\n", + REACHABLE_RANGE, row, index); + ALERT(at_console, msg); + } + else + { + offset = row * route_num_waypoints + index; + + pShortestPath[offset] = (unsigned short)distance; + } + } + } + + i++; + } + + p = p->next; // go to next node in linked list + } + } + } + + // run Floyd's Algorithm to generate the from_to matrix... + WaypointFloyds(pShortestPath, pFromTo); + + for (a=0; a < route_num_waypoints; a++) + { + for (b=0; b < route_num_waypoints; b++) + if (pShortestPath[a * route_num_waypoints + b] == WAYPOINT_UNREACHABLE) + pFromTo[a * route_num_waypoints + b] = WAYPOINT_UNREACHABLE; + } + + bfp = fopen(filename2, "wb"); + + if (bfp != NULL) + { + num_items = fwrite(shortest_path[matrix], sizeof(unsigned short), array_size, bfp); + + if (num_items != array_size) + { + // if couldn't write enough data, close file and delete it + + fclose(bfp); + unlink(filename2); + } + else + { + num_items = fwrite(from_to[matrix], sizeof(unsigned short), array_size, bfp); + + fclose(bfp); + + if (num_items != array_size) + { + // if couldn't write enough data, delete file + unlink(filename2); + } + } + } + else + { + ALERT(at_console, "HPB_bot - Error writing waypoint paths!\n"); + } + + sprintf(msg, "HPB bot waypoint path calculations for team %d complete!\n",matrix+1); + ALERT(at_console, msg); + } + } + } + +} + + +unsigned short WaypointRouteFromTo(int src, int dest, int team) +{ + unsigned short *pFromTo; + + if ((team < -1) || (team > 3)) + return -1; + + if (team == -1) // -1 means non-team play + team = 0; + + if (from_to[team] == NULL) // if no team specific waypoints use team 0 + team = 0; + + if (from_to[team] == NULL) // if no route information just return + return -1; + + pFromTo = from_to[team]; + + return pFromTo[src * route_num_waypoints + dest]; +} + + +int WaypointDistanceFromTo(int src, int dest, int team) +{ + unsigned short *pShortestPath; + + if ((team < -1) || (team > 3)) + return -1; + + if (team == -1) // -1 means non-team play + team = 0; + + if (from_to[team] == NULL) // if no team specific waypoints use team 0 + team = 0; + + if (from_to[team] == NULL) // if no route information just return + return -1; + + pShortestPath = shortest_path[team]; + + return (int)(pShortestPath[src * route_num_waypoints + dest]); +} + diff --git a/waypoint.h b/waypoint.h new file mode 100644 index 0000000..0011b4a --- /dev/null +++ b/waypoint.h @@ -0,0 +1,114 @@ +// +// HPB_bot - botman's High Ping Bastard bot +// +// (http://planethalflife.com/botman/) +// +// waypoint.h +// + +#ifndef WAYPOINT_H +#define WAYPOINT_H + +#include + +#define MAX_WAYPOINTS 1024 + +#define REACHABLE_RANGE 400.0 + +// defines for waypoint flags field (32 bits are available) +#define W_FL_TEAM ((1<<0) + (1<<1)) /* allow for 4 teams (0-3) */ +#define W_FL_TEAM_SPECIFIC (1<<2) /* waypoint only for specified team */ +#define W_FL_CROUCH (1<<3) /* must crouch to reach this waypoint */ +#define W_FL_LADDER (1<<4) /* waypoint on a ladder */ +#define W_FL_LIFT (1<<5) /* wait for lift to be down before approaching this waypoint */ +#define W_FL_DOOR (1<<6) /* wait for door to open */ +#define W_FL_HEALTH (1<<7) /* health kit (or wall mounted) location */ +#define W_FL_ARMOR (1<<8) /* armor (or HEV) location */ +#define W_FL_AMMO (1<<9) /* ammo location */ +#define W_FL_SNIPER (1<<10) /* sniper waypoint (a good sniper spot) */ + +#define W_FL_FLAG (1<<11) /* flag position (or hostage or president) */ +#define W_FL_FLF_CAP (1<<11) /* Front Line Force capture point */ + +#define W_FL_FLAG_GOAL (1<<12) /* flag return position (or rescue zone) */ +#define W_FL_FLF_DEFEND (1<<12) /* Front Line Force defend point */ + +#define W_FL_PRONE (1<<13) /* go prone (laying down) */ +#define W_FL_AIMING (1<<14) /* aiming waypoint */ + +#define W_FL_SENTRYGUN (1<<15) /* sentry gun waypoint for TFC */ +#define W_FL_DISPENSER (1<<16) /* dispenser waypoint for TFC */ + +#define W_FL_WEAPON (1<<17) /* weapon_ entity location */ +#define W_FL_JUMP (1<<18) /* jump waypoint */ + +#define W_FL_DELETED (1<<31) /* used by waypoint allocation code */ + + +#define WAYPOINT_VERSION 4 + +// define the waypoint file header structure... +typedef struct { + char filetype[8]; // should be "HPB_bot\0" + int waypoint_file_version; + int waypoint_file_flags; // not currently used + int number_of_waypoints; + char mapname[32]; // name of map for these waypoints +} WAYPOINT_HDR; + + +// define the structure for waypoints... +typedef struct { + int flags; // button, lift, flag, health, ammo, etc. + Vector origin; // location +} WAYPOINT; + + + +#define WAYPOINT_UNREACHABLE USHRT_MAX +#define WAYPOINT_MAX_DISTANCE (USHRT_MAX-1) + +#define MAX_PATH_INDEX 4 + +// define the structure for waypoint paths (paths are connections between +// two waypoint nodes that indicates the bot can get from point A to point B. +// note that paths DON'T have to be two-way. sometimes they are just one-way +// connections between two points. There is an array called "paths" that +// contains head pointers to these structures for each waypoint index. +typedef struct path { + short int index[MAX_PATH_INDEX]; // indexes of waypoints (index -1 means not used) + struct path *next; // link to next structure +} PATH; + + +// waypoint function prototypes... +void WaypointInit(void); +int WaypointFindPath(PATH **pPath, int *path_index, int waypoint_index, int team); +int WaypointFindNearest(edict_t *pEntity, float distance, int team); +int WaypointFindNearest(Vector v_src, edict_t *pEntity, float range, int team); +int WaypointFindNearestGoal(edict_t *pEntity, int src, int team, int flags); +int WaypointFindNearestGoal(edict_t *pEntity, int src, int team, int flags, int exclude[]); +int WaypointFindNearestGoal(Vector v_src, edict_t *pEntity, float range, int team, int flags); +int WaypointFindRandomGoal(edict_t *pEntity, int team, int flags); +int WaypointFindRandomGoal(edict_t *pEntity, int team, int flags, int exclude[]); +int WaypointFindRandomGoal(Vector v_src, edict_t *pEntity, float range, int team, int flags); +int WaypointFindNearestAiming(Vector v_origin); +void WaypointSearchItems(edict_t *pEntity, Vector origin, int wpt_index); +void WaypointAdd(edict_t *pEntity); +void WaypointAddAiming(edict_t *pEntity); +void WaypointDelete(edict_t *pEntity); +void WaypointUpdate(edict_t *pEntity); +void WaypointCreatePath(edict_t *pEntity, int cmd); +void WaypointRemovePath(edict_t *pEntity, int cmd); +bool WaypointLoad(edict_t *pEntity); +void WaypointSave(void); +bool WaypointReachable(Vector v_srv, Vector v_dest, edict_t *pEntity); +int WaypointFindReachable(edict_t *pEntity, float range, int team); +void WaypointPrintInfo(edict_t *pEntity); +void WaypointThink(edict_t *pEntity); +void WaypointFloyds(short *shortest_path, short *from_to); +void WaypointRouteInit(void); +unsigned short WaypointRouteFromTo(int src, int dest, int team); +int WaypointDistanceFromTo(int src, int dest, int team); + +#endif // WAYPOINT_H