Bots are now able to destroy random breakables around him, without touching them.

Added cvars descriptions and yapb.cfg generation.
Finally fixed bomb-defuse problems.
Fixed unaligned access in bot compression/decompression.
Fixed low-fps aim code falure.
This commit is contained in:
jeefo 2019-08-24 12:43:42 +03:00
commit 91c4d9ce1f
21 changed files with 523 additions and 293 deletions

View file

@ -73,6 +73,7 @@ private:
int cmdNodeMenu (); int cmdNodeMenu ();
int cmdMenu (); int cmdMenu ();
int cmdList (); int cmdList ();
int cmdCvars ();
int cmdNode (); int cmdNode ();
int cmdNodeOn (); int cmdNodeOn ();
int cmdNodeOff (); int cmdNodeOff ();

View file

@ -66,6 +66,9 @@ namespace detail {
// basic dictionary // basic dictionary
template <class K, class V, class H = StringHash <K>, size_t HashSize = 36> class Dictionary final : public DenyCopying { template <class K, class V, class H = StringHash <K>, size_t HashSize = 36> class Dictionary final : public DenyCopying {
private:
using DictBucket = detail::DictionaryBucket <K, V>;
public: public:
enum : size_t { enum : size_t {
InvalidIndex = static_cast <size_t> (-1) InvalidIndex = static_cast <size_t> (-1)
@ -73,7 +76,7 @@ public:
private: private:
Array <detail::DictionaryList *> m_table; Array <detail::DictionaryList *> m_table;
Array <detail::DictionaryBucket <K, V>> m_buckets; Array <DictBucket> m_buckets;
H m_hasher; H m_hasher;
private: private:
@ -226,19 +229,19 @@ public:
// for range-based loops // for range-based loops
public: public:
detail::DictionaryBucket <K, V> *begin () { DictBucket *begin () {
return m_buckets.begin (); return m_buckets.begin ();
} }
detail::DictionaryBucket <K, V> *begin () const { DictBucket *begin () const {
return m_buckets.begin (); return m_buckets.begin ();
} }
detail::DictionaryBucket <K, V> *end () { DictBucket *end () {
return m_buckets.end (); return m_buckets.end ();
} }
detail::DictionaryBucket <K, V> *end () const { DictBucket *end () const {
return m_buckets.end (); return m_buckets.end ();
} }
}; };

View file

@ -90,11 +90,11 @@ public:
return fprintf (m_handle, fmt, cr::forward <Args> (args)...); return fprintf (m_handle, fmt, cr::forward <Args> (args)...);
} }
bool puts (const String &buffer) { bool puts (const char *buffer) {
if (!*this) { if (!*this) {
return 0; return 0;
} }
if (fputs (buffer.chars (), m_handle) < 0) { if (fputs (buffer, m_handle) < 0) {
return false; return false;
} }
return true; return true;

View file

@ -74,6 +74,9 @@ public:
public: public:
bool patch (void *address, void *replacement) { bool patch (void *address, void *replacement) {
if (plat.isArm) {
return false;
}
uint8 *ptr = reinterpret_cast <uint8 *> (address); uint8 *ptr = reinterpret_cast <uint8 *> (address);
while (*reinterpret_cast <uint16 *> (ptr) == 0x25ff) { while (*reinterpret_cast <uint16 *> (ptr) == 0x25ff) {

View file

@ -51,6 +51,13 @@ CR_NAMESPACE_BEGIN
# define CR_ARCH_X86 # define CR_ARCH_X86
#endif #endif
#if defined(__arm__)
# define CR_ARCH_ARM
# if defined(__aarch64__)
# define CR_ARCH_ARM64
# endif
#endif
#if (defined(CR_ARCH_X86) || defined(CR_ARCH_X64)) && !defined(CR_DEBUG) #if (defined(CR_ARCH_X86) || defined(CR_ARCH_X64)) && !defined(CR_DEBUG)
# define CR_HAS_SSE2 # define CR_HAS_SSE2
#endif #endif
@ -85,6 +92,7 @@ struct Platform : public Singleton <Platform> {
bool isAndroid = false; bool isAndroid = false;
bool isAndroidHardFP = false; bool isAndroidHardFP = false;
bool isX64 = false; bool isX64 = false;
bool isArm = false;
Platform () { Platform () {
#if defined(CR_WINDOWS) #if defined(CR_WINDOWS)
@ -94,22 +102,27 @@ struct Platform : public Singleton <Platform> {
#if defined(CR_ANDROID) #if defined(CR_ANDROID)
isAndroid = true; isAndroid = true;
# if defined (CR_ANDROID_HARD_FP) # if defined (CR_ANDROID_HARD_FP)
isAndroidHardFP = true; isAndroidHardFP = true;
# endif # endif
#endif #endif
#if defined(CR_LINUX) #if defined(CR_LINUX)
isLinux = true; isLinux = true;
#endif #endif
#if defined (CR_OSX) #if defined(CR_OSX)
isOSX = true; isOSX = true;
#endif #endif
#if defined (CR_ARCH_X64) #if defined(CR_ARCH_X64) || defined(CR_ARCH_ARM64)
isX64 = true; isX64 = true;
#endif #endif
#if defined(CR_ARCH_ARM)
isArm = true;
isAndroid = true;
#endif
} }
// helper platform-dependant functions // helper platform-dependant functions

View file

@ -52,7 +52,7 @@ public:
for (auto &htb : m_hashTable) { for (auto &htb : m_hashTable) {
htb = EmptyHash; htb = EmptyHash;
} }
uint8 *op = out; auto op = out;
int32 anchor = 0; int32 anchor = 0;
int32 cur = 0; int32 cur = 0;
@ -99,9 +99,9 @@ public:
} }
if (bestLength >= MinMatch && bestLength < maxMatch && (cur - anchor) != 6) { if (bestLength >= MinMatch && bestLength < maxMatch && (cur - anchor) != 6) {
const int32 next = cur + 1; const auto next = cur + 1;
const int32 target = bestLength + 1; const auto target = bestLength + 1;
const int32 limit = cr::max <int32> (next - WindowSize, EmptyHash); const auto limit = cr::max <int32> (next - WindowSize, EmptyHash);
int32 chainLength = MaxChain; int32 chainLength = MaxChain;
int32 lookup = m_hashTable[hash32 (&in[next])]; int32 lookup = m_hashTable[hash32 (&in[next])];
@ -128,11 +128,11 @@ public:
} }
if (bestLength >= MinMatch) { if (bestLength >= MinMatch) {
const int32 length = bestLength - MinMatch; const auto length = bestLength - MinMatch;
const int32 token = ((dist >> 12) & 16) + cr::min <int32> (length, 15); const auto token = ((dist >> 12) & 16) + cr::min <int32> (length, 15);
if (anchor != cur) { if (anchor != cur) {
const int32 run = cur - anchor; const auto run = cur - anchor;
if (run >= 7) { if (run >= 7) {
add (op, (7 << 5) + token); add (op, (7 << 5) + token);
@ -155,7 +155,7 @@ public:
op += 2; op += 2;
while (bestLength-- != 0) { while (bestLength-- != 0) {
const uint32 hash = hash32 (&in[cur]); const auto hash = hash32 (&in[cur]);
m_prevTable[cur & WindowMask] = m_hashTable[hash]; m_prevTable[cur & WindowMask] = m_hashTable[hash];
m_hashTable[hash] = cur++; m_hashTable[hash] = cur++;
@ -163,7 +163,7 @@ public:
anchor = cur; anchor = cur;
} }
else { else {
const uint32 hash = hash32 (&in[cur]); const auto hash = hash32 (&in[cur]);
m_prevTable[cur & WindowMask] = m_hashTable[hash]; m_prevTable[cur & WindowMask] = m_hashTable[hash];
m_hashTable[hash] = cur++; m_hashTable[hash] = cur++;
@ -171,7 +171,7 @@ public:
} }
if (anchor != cur) { if (anchor != cur) {
const int32 run = cur - anchor; const auto run = cur - anchor;
if (run >= 7) { if (run >= 7) {
add (op, 7 << 5); add (op, 7 << 5);
@ -187,17 +187,17 @@ public:
} }
int32 uncompress (uint8 *in, int32 inputLength, uint8 *out, int32 outLength) { int32 uncompress (uint8 *in, int32 inputLength, uint8 *out, int32 outLength) {
uint8 *op = out; auto op = out;
uint8 *ip = in; auto ip = in;
const uint8 *opEnd = op + outLength; const auto opEnd = op + outLength;
const uint8 *ipEnd = ip + inputLength; const auto ipEnd = ip + inputLength;
while (ip < ipEnd) { while (ip < ipEnd) {
const int32 token = *ip++; const auto token = *ip++;
if (token >= 32) { if (token >= 32) {
int32 run = token >> 5; auto run = token >> 5;
if (run == 7) { if (run == 7) {
run += decode (ip); run += decode (ip);
@ -215,7 +215,7 @@ public:
break; break;
} }
} }
int32 length = (token & 15) + MinMatch; auto length = (token & 15) + MinMatch;
if (length == (15 + MinMatch)) { if (length == (15 + MinMatch)) {
length += decode (ip); length += decode (ip);
@ -224,10 +224,10 @@ public:
if ((opEnd - op) < length) { if ((opEnd - op) < length) {
return UncompressFailure; return UncompressFailure;
} }
const int32 dist = ((token & 16) << 12) + load16 (ip); const auto dist = ((token & 16) << 12) + load16 (ip);
ip += 2; ip += 2;
uint8 *cp = op - dist; auto cp = op - dist;
if ((op - out) < dist) { if ((op - out) < dist) {
return UncompressFailure; return UncompressFailure;
@ -237,8 +237,7 @@ public:
copy (op, cp, length); copy (op, cp, length);
op += length; op += length;
} }
else else {
{
for (int32 i = 0; i < 4; ++i) { for (int32 i = 0; i < 4; ++i) {
*op++ = *cp++; *op++ = *cp++;
} }
@ -252,27 +251,33 @@ public:
} }
private: private:
inline uint16 load16 (void *ptr) { uint16 load16 (void *ptr) {
return *reinterpret_cast <const uint16 *> (ptr); uint16 ret;
memcpy (&ret, ptr, sizeof (uint16));
return ret;
} }
inline uint32 load32 (void *ptr) { uint32 load32 (void *ptr) {
return *reinterpret_cast <const uint32 *> (ptr); uint32 ret;
memcpy (&ret, ptr, sizeof (uint32));
return ret;
} }
inline void store16 (void *ptr, int32 val) { void store16 (void *ptr, int32 val) {
*reinterpret_cast <uint16 *> (ptr) = static_cast <uint16> (val); memcpy (ptr, &val, sizeof (uint16));
} }
inline void copy64 (void *dst, void *src) { void copy64 (void *dst, void *src) {
*reinterpret_cast <uint64 *> (dst) = *reinterpret_cast <const uint64 *> (src); memcpy (dst, src, sizeof (uint64));
} }
inline uint32 hash32 (void *ptr) { uint32 hash32 (void *ptr) {
return (load32 (ptr) * 0x9E3779B9) >> (32 - HashBits); return (load32 (ptr) * 0x9e3779b9) >> (32 - HashBits);
} }
inline void copy (uint8 *dst, uint8 *src, int32 count) { void copy (uint8 *dst, uint8 *src, int32 count) {
copy64 (dst, src); copy64 (dst, src);
for (int32 i = 8; i < count; i += 8) { for (int32 i = 8; i < count; i += 8) {
@ -280,11 +285,11 @@ private:
} }
} }
inline void add (uint8 *&dst, int32 val) { void add (uint8 *&dst, int32 val) {
*dst++ = static_cast <uint8> (val); *dst++ = static_cast <uint8> (val);
} }
inline void encode (uint8 *&ptr, uint32 val) { void encode (uint8 *&ptr, uint32 val) {
while (val >= 128) { while (val >= 128) {
val -= 128; val -= 128;
@ -294,7 +299,7 @@ private:
*ptr++ = static_cast <uint8> (val); *ptr++ = static_cast <uint8> (val);
} }
inline uint32 decode (uint8 *&ptr) { uint32 decode (uint8 *&ptr) {
uint32 val = 0; uint32 val = 0;
for (int32 i = 0; i <= 21; i += 7) { for (int32 i = 0; i <= 21; i += 7) {
@ -309,6 +314,4 @@ private:
} }
}; };
CR_NAMESPACE_END CR_NAMESPACE_END

View file

@ -74,6 +74,9 @@ struct VarPair {
bool missing; bool missing;
const char *regval; const char *regval;
class ConVar *self; class ConVar *self;
String info;
float initial, min, max;
bool bounded;
}; };
// entity prototype // entity prototype
@ -95,6 +98,7 @@ private:
edict_t *m_startEntity; edict_t *m_startEntity;
edict_t *m_localEntity; edict_t *m_localEntity;
Array <edict_t *> m_breakables;
SmallArray <VarPair> m_cvars; SmallArray <VarPair> m_cvars;
SharedLibrary m_gameLib; SharedLibrary m_gameLib;
@ -137,10 +141,10 @@ public:
const char *getMapName (); const char *getMapName ();
// get the "any" entity origin // get the "any" entity origin
Vector getAbsPos (edict_t *ent); Vector getEntityWorldOrigin (edict_t *ent);
// registers a server command // registers a server command
void registerEngineCommand (const char *command, void func_ ()); void registerEngineCommand (const char *command, void func ());
// play's sound to client // play's sound to client
void playSound (edict_t *ent, const char *sound); void playSound (edict_t *ent, const char *sound);
@ -149,7 +153,10 @@ public:
void prepareBotArgs (edict_t *ent, String str); void prepareBotArgs (edict_t *ent, String str);
// adds cvar to registration stack // adds cvar to registration stack
void addNewCvar (const char *variable, const char *value, Var varType, bool regMissing, const char *regVal, class ConVar *self); void addNewCvar (const char *name, const char *value, const char *info, bool bounded, float min, float max, Var varType, bool missingAction, const char *regval, class ConVar *self);
// check the cvar bounds
void checkCvarsBounds ();
// sends local registration stack for engine registration // sends local registration stack for engine registration
void registerCvars (bool gameVars = false); void registerCvars (bool gameVars = false);
@ -175,6 +182,9 @@ public:
// search entities in sphere // search entities in sphere
void searchEntities (const Vector &position, const float radius, EntitySearch functor); void searchEntities (const Vector &position, const float radius, EntitySearch functor);
// this function is checking that pointed by ent pointer obstacle, can be destroyed
bool isShootableBreakable (edict_t *ent);
// public inlines // public inlines
public: public:
// get the current time on server // get the current time on server
@ -294,6 +304,21 @@ public:
return m_gameLib; return m_gameLib;
} }
// get registered cvars list
const SmallArray <VarPair> &getCvars () {
return m_cvars;
}
// check if map has breakables
const Array <edict_t *> &getBreakables () {
return m_breakables;
}
// map has breakables ?
bool hasBreakables () const {
return !m_breakables.empty ();
}
// helper to sending the client message // helper to sending the client message
void sendClientMessage (bool console, edict_t *ent, const char *message); void sendClientMessage (bool console, edict_t *ent, const char *message);
@ -332,33 +357,41 @@ public:
}; };
// simplify access for console variables // simplify access for console variables
class ConVar final { class ConVar final : public DenyCopying {
public: public:
cvar_t *eptr; cvar_t *ptr;
public: public:
ConVar (const char *name, const char *initval, Var type = Var::NoServer, bool regMissing = false, const char *regVal = nullptr) : eptr (nullptr) { ConVar () = delete;
Game::get ().addNewCvar (name, initval, type, regMissing, regVal, this); ~ConVar () = default;
public:
ConVar (const char *name, const char *initval, Var type = Var::NoServer, bool regMissing = false, const char *regVal = nullptr) : ptr (nullptr) {
Game::get ().addNewCvar (name, initval, "", false, 0.0f, 0.0f, type, regMissing, regVal, this);
}
ConVar (const char *name, const char *initval, const char *info, bool bounded = true, float min = 0.0f, float max = 1.0f, Var type = Var::NoServer, bool regMissing = false, const char *regVal = nullptr) : ptr (nullptr) {
Game::get ().addNewCvar (name, initval, info, bounded, min, max, type, regMissing, regVal, this);
} }
bool bool_ () const { bool bool_ () const {
return eptr->value > 0.0f; return ptr->value > 0.0f;
} }
int int_ () const { int int_ () const {
return static_cast <int> (eptr->value); return static_cast <int> (ptr->value);
} }
float float_ () const { float float_ () const {
return eptr->value; return ptr->value;
} }
const char *str () const { const char *str () const {
return eptr->string; return ptr->string;
} }
void set (float val) { void set (float val) {
engfuncs.pfnCVarSetFloat (eptr->name, val); engfuncs.pfnCVarSetFloat (ptr->name, val);
} }
void set (int val) { void set (int val) {
@ -366,7 +399,7 @@ public:
} }
void set (const char *val) { void set (const char *val) {
engfuncs.pfnCvar_DirectSet (eptr, const_cast <char *> (val)); engfuncs.pfnCvar_DirectSet (ptr, const_cast <char *> (val));
} }
}; };
@ -541,7 +574,7 @@ public:
}; };
// for android // for android
#if defined (CR_ANDROID) #if defined (CR_ANDROID) && defined(CR_ARCH_ARM)
extern "C" void player (entvars_t *pev); extern "C" void player (entvars_t *pev);
#endif #endif
@ -588,7 +621,7 @@ public:
public: public:
void initialize () { void initialize () {
if (plat.isAndroid) { if (plat.isArm) {
return; return;
} }
m_dlsym.patch (reinterpret_cast <void *> (&LookupSymbol), reinterpret_cast <void *> (&DynamicEntityLink::replacement)); m_dlsym.patch (reinterpret_cast <void *> (&LookupSymbol), reinterpret_cast <void *> (&DynamicEntityLink::replacement));
@ -596,7 +629,7 @@ public:
} }
EntityFunction getPlayerFunction () { EntityFunction getPlayerFunction () {
#if defined (CR_ANDROID) #if defined (CR_ANDROID) && defined(CR_ARCH_ARM)
return player; return player;
#else #else
return reinterpret_cast <EntityFunction> (search (Game::get ().lib ().handle (), "player")); return reinterpret_cast <EntityFunction> (search (Game::get ().lib ().handle (), "player"));

View file

@ -259,7 +259,7 @@ private:
Vector m_learnVelocity; Vector m_learnVelocity;
Vector m_learnPosition; Vector m_learnPosition;
Vector m_bombPos; Vector m_bombOrigin;
Vector m_lastNode; Vector m_lastNode;
IntArray m_terrorPoints; IntArray m_terrorPoints;
@ -344,7 +344,7 @@ public:
void initBuckets (); void initBuckets ();
void addToBucket (const Vector &pos, int index); void addToBucket (const Vector &pos, int index);
void eraseFromBucket (const Vector &pos, int index); void eraseFromBucket (const Vector &pos, int index);
void setBombPos (bool reset = false, const Vector &pos = nullptr); void setBombOrigin (bool reset = false, const Vector &pos = nullptr);
void updateGlobalPractice (); void updateGlobalPractice ();
void unassignPath (int from, int to); void unassignPath (int from, int to);
void setDangerValue (int team, int start, int goal, int value); void setDangerValue (int team, int start, int goal, int value);
@ -393,8 +393,8 @@ public:
m_autoPathDistance = distance; m_autoPathDistance = distance;
} }
const Vector &getBombPos () const { const Vector &getBombOrigin () const {
return m_bombPos; return m_bombOrigin;
} }
// access paths // access paths

View file

@ -118,11 +118,11 @@ public:
bool kickRandom (bool decQuota = true, Team fromTeam = Team::Unassigned); bool kickRandom (bool decQuota = true, Team fromTeam = Team::Unassigned);
public: public:
Array <edict_t *> &searchActiveGrenades () { const Array <edict_t *> &searchActiveGrenades () {
return m_activeGrenades; return m_activeGrenades;
} }
Array <edict_t *> &searchIntrestingEntities () { const Array <edict_t *> &searchIntrestingEntities () {
return m_intrestingEntities; return m_intrestingEntities;
} }

View file

@ -268,13 +268,13 @@ CR_DECLARE_SCOPED_ENUM (Weapon,
// buy counts // buy counts
CR_DECLARE_SCOPED_ENUM (BuyState, CR_DECLARE_SCOPED_ENUM (BuyState,
PrimaryWeapon = 0, PrimaryWeapon = 0,
ArmorVestHelm , ArmorVestHelm,
SecondaryWeapon, SecondaryWeapon,
Grenades, Grenades,
DefusalKit, DefusalKit,
Ammo, Ammo,
NightVision, NightVision,
Done Done
) )
// economics limits // economics limits
@ -284,7 +284,7 @@ CR_DECLARE_SCOPED_ENUM (EcoLimit,
SmgTEGreater, SmgTEGreater,
ShotgunGreater, ShotgunGreater,
ShotgunLess, ShotgunLess,
HeavyGreater , HeavyGreater,
HeavyLess, HeavyLess,
ProstockNormal, ProstockNormal,
ProstockRusher, ProstockRusher,
@ -513,11 +513,17 @@ public:
{ } { }
}; };
// clients noise
struct ClientNoise {
Vector pos;
float dist;
float last;
};
// array of clients struct // array of clients struct
struct Client { struct Client {
edict_t *ent; // pointer to actual edict edict_t *ent; // pointer to actual edict
Vector origin; // position in the world Vector origin; // position in the world
Vector sound; // position sound was played
int team; // bot team int team; // bot team
int team2; // real bot team in free for all mode (csdm) int team2; // real bot team in free for all mode (csdm)
int flags; // client flags int flags; // client flags
@ -526,9 +532,8 @@ struct Client {
int ping; // when bot latency is enabled, client ping stored here int ping; // when bot latency is enabled, client ping stored here
int iconFlags[kGameMaxPlayers]; // flag holding chatter icons int iconFlags[kGameMaxPlayers]; // flag holding chatter icons
float iconTimestamp[kGameMaxPlayers]; // timers for chatter icons float iconTimestamp[kGameMaxPlayers]; // timers for chatter icons
float hearingDistance; // distance this sound is heared
float timeSoundLasting; // time sound is played/heared
bool pingUpdate; // update ping ? bool pingUpdate; // update ping ?
ClientNoise noise;
}; };
// define chatting collection structure // define chatting collection structure
@ -733,7 +738,6 @@ private:
bool isOccupiedNode (int index); bool isOccupiedNode (int index);
bool seesItem (const Vector &dest, const char *itemName); bool seesItem (const Vector &dest, const char *itemName);
bool lastEnemyShootable (); bool lastEnemyShootable ();
bool isShootableBreakable (edict_t *ent);
bool rateGroundWeapon (edict_t *ent); bool rateGroundWeapon (edict_t *ent);
bool reactOnEnemy (); bool reactOnEnemy ();
bool selectBestNextNode (); bool selectBestNextNode ();
@ -975,6 +979,7 @@ public:
void showChaterIcon (bool show); void showChaterIcon (bool show);
void clearSearchNodes (); void clearSearchNodes ();
void checkBreakable (edict_t *touch); void checkBreakable (edict_t *touch);
void checkBreablesAround ();
void startTask (Task id, float desire, int data, float time, bool resume); void startTask (Task id, float desire, int data, float time, bool resume);
void clearTask (Task id); void clearTask (Task id);
void filterTasks (); void filterTasks ();

View file

@ -10,7 +10,7 @@
#include <yapb.h> #include <yapb.h>
// until hook code will be compatible with ARM, it's here // until hook code will be compatible with ARM, it's here
#if defined (CR_ANDROID) #if defined (CR_ANDROID) && defined(CR_ARCH_ARM)
void android_LinkEntity (EntityFunction &addr, const char *name, entvars_t *pev) { void android_LinkEntity (EntityFunction &addr, const char *name, entvars_t *pev) {
if (!addr) { if (!addr) {
addr = game.lib ().resolve <EntityFunction> (name); addr = game.lib ().resolve <EntityFunction> (name);

View file

@ -9,25 +9,26 @@
#include <yapb.h> #include <yapb.h>
ConVar yb_debug ("yb_debug", "0"); ConVar yb_debug ("yb_debug", "0", "Enables or disables useful messages about bot states. Not required for end users", true, 0.0f, 4.0f);
ConVar yb_debug_goal ("yb_debug_goal", "-1"); ConVar yb_debug_goal ("yb_debug_goal", "-1", "Forces all alive bots to build path and go to the specified here graph node.", true, -1.0f, kMaxNodes);
ConVar yb_user_follow_percent ("yb_user_follow_percent", "20"); ConVar yb_user_follow_percent ("yb_user_follow_percent", "20", "Specifies the percent of bots, than can follow leader on each round start.", true, 0.0f, 100.0f);
ConVar yb_user_max_followers ("yb_user_max_followers", "1"); ConVar yb_user_max_followers ("yb_user_max_followers", "1", "Specifies how many bots can follow a single user.", true, 0.0f, static_cast <float> (kGameMaxPlayers / 2));
ConVar yb_jasonmode ("yb_jasonmode", "0"); ConVar yb_jasonmode ("yb_jasonmode", "0", "If enabled, all bots will be forced only the knife, skipping weapon buying routines.");
ConVar yb_radio_mode ("yb_radio_mode", "2"); ConVar yb_radio_mode ("yb_radio_mode", "2", "Allows bots to use radio or chattter.\nAllowed values: '0', '1', '2'.\nIf '0', radio and chatter is disabled.\nIf '1', only radio allowed.\nIf '2' chatter and radio allowed.", true, 0.0f, 2.0f);
ConVar yb_economics_rounds ("yb_economics_rounds", "1");
ConVar yb_walking_allowed ("yb_walking_allowed", "1");
ConVar yb_camping_allowed ("yb_camping_allowed", "1");
ConVar yb_tkpunish ("yb_tkpunish", "1"); ConVar yb_economics_rounds ("yb_economics_rounds", "1", "Specifies whether bots able to use team economics, like do not buy any weapons for whole team to keep money for better guns.");
ConVar yb_freeze_bots ("yb_freeze_bots", "0"); ConVar yb_walking_allowed ("yb_walking_allowed", "1", "Sepcifies whether bots able to use 'shift' if they thinks that enemy is near.");
ConVar yb_spraypaints ("yb_spraypaints", "1"); ConVar yb_camping_allowed ("yb_camping_allowed", "1", "Allows or disallows bots to camp. Doesn't affects bomb/hostage defending tasks");
ConVar yb_botbuy ("yb_botbuy", "1");
ConVar yb_chatter_path ("yb_chatter_path", "sound/radio/bot"); ConVar yb_tkpunish ("yb_tkpunish", "1", "Allows or disallows bots to take revenge of teamkillers / team attacks.");
ConVar yb_restricted_weapons ("yb_restricted_weapons", ""); ConVar yb_freeze_bots ("yb_freeze_bots", "0", "If enables bots think function is disabled, so bots will not move anywhere from their spawn spots.");
ConVar yb_best_weapon_picker_type ("yb_best_weapon_picker_type", "0"); ConVar yb_spraypaints ("yb_spraypaints", "1", "Allows or disallows the use of spay paints.");
ConVar yb_botbuy ("yb_botbuy", "1", "Allows or disallows bots weapon buying routines.");
ConVar yb_destroy_breakables_around ("yb_destroy_breakables_around", "1", "Allows bots to destroy breakables around him, even without touching with them.");
ConVar yb_chatter_path ("yb_chatter_path", "sound/radio/bot", "Specifies the paths for the bot chatter sound files.", false);
ConVar yb_restricted_weapons ("yb_restricted_weapons", "", "Specifies semicolon separated list of weapons that are not allowed to buy / pickup.", false);
// game console variables // game console variables
ConVar mp_c4timer ("mp_c4timer", nullptr, Var::NoRegister); ConVar mp_c4timer ("mp_c4timer", nullptr, Var::NoRegister);
@ -334,7 +335,7 @@ void Bot::avoidGrenades () {
if (m_preventFlashing < game.time () && m_personality == Personality::Rusher && m_difficulty == 4 && strcmp (model, "flashbang.mdl") == 0) { if (m_preventFlashing < game.time () && m_personality == Personality::Rusher && m_difficulty == 4 && strcmp (model, "flashbang.mdl") == 0) {
// don't look at flash bang // don't look at flash bang
if (!(m_states & Sense::SeeingEnemy)) { if (!(m_states & Sense::SeeingEnemy)) {
pev->v_angle.y = cr::normalizeAngles ((game.getAbsPos (pent) - getEyesPos ()).angles ().y + 180.0f); pev->v_angle.y = cr::normalizeAngles ((game.getEntityWorldOrigin (pent) - getEyesPos ()).angles ().y + 180.0f);
m_canChooseAimDirection = false; m_canChooseAimDirection = false;
m_preventFlashing = game.time () + rg.float_ (1.0f, 2.0f); m_preventFlashing = game.time () + rg.float_ (1.0f, 2.0f);
@ -385,8 +386,7 @@ void Bot::avoidGrenades () {
} }
void Bot::checkBreakable (edict_t *touch) { void Bot::checkBreakable (edict_t *touch) {
if (!game.isShootableBreakable (touch)) {
if (!isShootableBreakable (touch)) {
return; return;
} }
m_breakableEntity = lookupBreakable (); m_breakableEntity = lookupBreakable ();
@ -395,10 +395,36 @@ void Bot::checkBreakable (edict_t *touch) {
return; return;
} }
m_campButtons = pev->button & IN_DUCK; m_campButtons = pev->button & IN_DUCK;
startTask (Task::ShootBreakable, TaskPri::ShootBreakable, kInvalidNodeIndex, 0.0f, false); startTask (Task::ShootBreakable, TaskPri::ShootBreakable, kInvalidNodeIndex, 0.0f, false);
} }
void Bot::checkBreablesAround () {
if (!yb_destroy_breakables_around.bool_ () || m_currentWeapon == Weapon::Knife || rg.chance (25) || !game.hasBreakables () || m_seeEnemyTime + 4.0f > game.time () || !game.isNullEntity (m_enemy) || !hasPrimaryWeapon ()) {
return;
}
// check if we're have some breakbles in 450 units range
for (const auto &breakable : game.getBreakables ()) {
if (!game.isShootableBreakable (breakable)) {
continue;
}
const auto &origin = game.getEntityWorldOrigin (breakable);
if ((origin - pev->origin).lengthSq () > cr::square (450.0f)) {
continue;
}
if (isInFOV (origin - getEyesPos ()) < pev->fov && seesEntity (origin)) {
m_breakableOrigin = origin;
m_breakableEntity = breakable;
m_campButtons = pev->button & IN_DUCK;
startTask (Task::ShootBreakable, TaskPri::ShootBreakable, kInvalidNodeIndex, 0.0f, false);
break;
}
}
}
edict_t *Bot::lookupBreakable () { edict_t *Bot::lookupBreakable () {
// this function checks if bot is blocked by a shoot able breakable in his moving direction // this function checks if bot is blocked by a shoot able breakable in his moving direction
@ -406,26 +432,26 @@ edict_t *Bot::lookupBreakable () {
game.testLine (pev->origin, pev->origin + (m_destOrigin - pev->origin).normalize () * 72.0f, TraceIgnore::None, ent (), &tr); game.testLine (pev->origin, pev->origin + (m_destOrigin - pev->origin).normalize () * 72.0f, TraceIgnore::None, ent (), &tr);
if (tr.flFraction != 1.0f) { if (tr.flFraction != 1.0f) {
edict_t *ent = tr.pHit; auto ent = tr.pHit;
// check if this isn't a triggered (bomb) breakable and if it takes damage. if true, shoot the crap! // check if this isn't a triggered (bomb) breakable and if it takes damage. if true, shoot the crap!
if (isShootableBreakable (ent)) { if (game.isShootableBreakable (ent)) {
m_breakableOrigin = game.getAbsPos (ent); m_breakableOrigin = game.getEntityWorldOrigin (ent);
return ent; return ent;
} }
} }
game.testLine (getEyesPos (), getEyesPos () + (m_destOrigin - getEyesPos ()).normalize () * 72.0f, TraceIgnore::None, ent (), &tr); game.testLine (getEyesPos (), getEyesPos () + (m_destOrigin - getEyesPos ()).normalize () * 72.0f, TraceIgnore::None, ent (), &tr);
if (tr.flFraction != 1.0f) { if (tr.flFraction != 1.0f) {
edict_t *ent = tr.pHit; auto ent = tr.pHit;
if (isShootableBreakable (ent)) { if (game.isShootableBreakable (ent)) {
m_breakableOrigin = game.getAbsPos (ent); m_breakableOrigin = game.getEntityWorldOrigin (ent);
return ent; return ent;
} }
} }
m_breakableEntity = nullptr; m_breakableEntity = nullptr;
m_breakableOrigin= nullptr; m_breakableOrigin = nullptr;
return nullptr; return nullptr;
} }
@ -459,14 +485,14 @@ void Bot::updatePickups () {
} }
auto &intresting = bots.searchIntrestingEntities (); auto &intresting = bots.searchIntrestingEntities ();
const float radius = cr::square (500.0f); const float radius = cr::square (320.0f);
if (!game.isNullEntity (m_pickupItem)) { if (!game.isNullEntity (m_pickupItem)) {
bool itemExists = false; bool itemExists = false;
auto pickupItem = m_pickupItem; auto pickupItem = m_pickupItem;
for (auto &ent : intresting) { for (auto &ent : intresting) {
const Vector &origin = game.getAbsPos (ent); const Vector &origin = game.getEntityWorldOrigin (ent);
// too far from us ? // too far from us ?
if ((pev->origin - origin).lengthSq () > radius) { if ((pev->origin - origin).lengthSq () > radius) {
@ -505,7 +531,7 @@ void Bot::updatePickups () {
if (ent == m_itemIgnore) { if (ent == m_itemIgnore) {
continue; // someone owns this weapon or it hasn't respawned yet continue; // someone owns this weapon or it hasn't respawned yet
} }
const Vector &origin = game.getAbsPos (ent); const Vector &origin = game.getEntityWorldOrigin (ent);
// too far from us ? // too far from us ?
if ((pev->origin - origin).lengthSq () > radius) { if ((pev->origin - origin).lengthSq () > radius) {
@ -1784,12 +1810,12 @@ void Bot::setConditions () {
// check if our current enemy is still valid // check if our current enemy is still valid
if (!game.isNullEntity (m_lastEnemy)) { if (!game.isNullEntity (m_lastEnemy)) {
if (!util.isAlive (m_lastEnemy) && m_shootAtDeadTime < game.time ()) { if (!util.isAlive (m_lastEnemy) && m_shootAtDeadTime < game.time ()) {
m_lastEnemyOrigin= nullptr; m_lastEnemyOrigin = nullptr;
m_lastEnemy = nullptr; m_lastEnemy = nullptr;
} }
} }
else { else {
m_lastEnemyOrigin= nullptr; m_lastEnemyOrigin = nullptr;
m_lastEnemy = nullptr; m_lastEnemy = nullptr;
} }
@ -1857,7 +1883,7 @@ void Bot::filterTasks () {
filter[Task::PickupItem].desire = 50.0f; // always pickup button filter[Task::PickupItem].desire = 50.0f; // always pickup button
} }
else { else {
float distance = (500.0f - (game.getAbsPos (m_pickupItem) - pev->origin).length ()) * 0.2f; float distance = (500.0f - (game.getEntityWorldOrigin (m_pickupItem) - pev->origin).length ()) * 0.2f;
if (distance > 50.0f) { if (distance > 50.0f) {
distance = 50.0f; distance = 50.0f;
@ -2388,7 +2414,7 @@ void Bot::checkRadioQueue () {
clearSearchNodes (); clearSearchNodes ();
m_position = graph.getBombPos (); m_position = graph.getBombOrigin ();
startTask (Task::MoveToPosition, TaskPri::MoveToPosition, kInvalidNodeIndex, 0.0f, true); startTask (Task::MoveToPosition, TaskPri::MoveToPosition, kInvalidNodeIndex, 0.0f, true);
pushRadioMessage (Radio::RogerThat); pushRadioMessage (Radio::RogerThat);
@ -2837,7 +2863,7 @@ void Bot::frame () {
m_numEnemiesLeft = numEnemiesNear (pev->origin, kInfiniteDistance); m_numEnemiesLeft = numEnemiesNear (pev->origin, kInfiniteDistance);
if (bots.isBombPlanted () && m_team == Team::CT && m_notKilled) { if (bots.isBombPlanted () && m_team == Team::CT && m_notKilled) {
const Vector &bombPosition = graph.getBombPos (); const Vector &bombPosition = graph.getBombOrigin ();
if (!m_hasProgressBar && getCurrentTaskId () != Task::EscapeFromBomb && (pev->origin - bombPosition).lengthSq () < cr::square (1540.0f) && !isBombDefusing (bombPosition)) { if (!m_hasProgressBar && getCurrentTaskId () != Task::EscapeFromBomb && (pev->origin - bombPosition).lengthSq () < cr::square (1540.0f) && !isBombDefusing (bombPosition)) {
m_itemIgnore = nullptr; m_itemIgnore = nullptr;
@ -2846,8 +2872,10 @@ void Bot::frame () {
clearTask (getCurrentTaskId ()); clearTask (getCurrentTaskId ());
} }
} }
checkSpawnConditions (); checkSpawnConditions ();
checkForChat (); checkForChat ();
checkBreablesAround ();
if (game.is (GameFlags::HasBotVoice)) { if (game.is (GameFlags::HasBotVoice)) {
showChaterIcon (false); // end voice feedback showChaterIcon (false); // end voice feedback
@ -2867,7 +2895,7 @@ void Bot::update () {
m_moveSpeed = 0.0f; m_moveSpeed = 0.0f;
m_strafeSpeed = 0.0f; m_strafeSpeed = 0.0f;
m_moveAngles= nullptr; m_moveAngles = nullptr;
m_canChooseAimDirection = true; m_canChooseAimDirection = true;
m_notKilled = util.isAlive (ent ()); m_notKilled = util.isAlive (ent ());
@ -3234,7 +3262,7 @@ void Bot::huntEnemy_ () {
completeTask (); completeTask ();
m_prevGoalIndex = kInvalidNodeIndex; m_prevGoalIndex = kInvalidNodeIndex;
m_lastEnemyOrigin= nullptr; m_lastEnemyOrigin = nullptr;
} }
// do we need to calculate a new path? // do we need to calculate a new path?
@ -3455,7 +3483,7 @@ void Bot::camp_ () {
m_checkTerrain = false; m_checkTerrain = false;
m_moveToGoal = false; m_moveToGoal = false;
if (m_team == Team::CT && bots.isBombPlanted () && m_defendedBomb && !isBombDefusing (graph.getBombPos ()) && !isOutOfBombTimer ()) { if (m_team == Team::CT && bots.isBombPlanted () && m_defendedBomb && !isBombDefusing (graph.getBombOrigin ()) && !isOutOfBombTimer ()) {
m_defendedBomb = false; m_defendedBomb = false;
completeTask (); completeTask ();
} }
@ -3625,7 +3653,7 @@ void Bot::moveToPos_ () {
completeTask (); // we're done completeTask (); // we're done
m_prevGoalIndex = kInvalidNodeIndex; m_prevGoalIndex = kInvalidNodeIndex;
m_position= nullptr; m_position = nullptr;
} }
// didn't choose goal waypoint yet? // didn't choose goal waypoint yet?
@ -3720,17 +3748,12 @@ void Bot::bombDefuse_ () {
} }
bool pickupExists = !game.isNullEntity (m_pickupItem); bool pickupExists = !game.isNullEntity (m_pickupItem);
const Vector &bombPos = pickupExists ? m_pickupItem->v.origin : graph.getBombPos (); const Vector &bombPos = pickupExists ? game.getEntityWorldOrigin (m_pickupItem) : graph.getBombOrigin ();
if (pickupExists) {
if (graph.getBombPos () != bombPos) {
graph.setBombPos (bombPos);
}
}
bool defuseError = false; bool defuseError = false;
// exception: bomb has been defused // exception: bomb has been defused
if (bombPos.empty ()) { if (bombPos.empty () || game.isNullEntity (m_pickupItem)) {
defuseError = true; defuseError = true;
if (m_numFriendsLeft != 0 && rg.chance (50)) { if (m_numFriendsLeft != 0 && rg.chance (50)) {
@ -3771,8 +3794,8 @@ void Bot::bombDefuse_ () {
m_checkTerrain = true; m_checkTerrain = true;
m_moveToGoal = true; m_moveToGoal = true;
m_destOrigin= nullptr; m_destOrigin = nullptr;
m_entity= nullptr; m_entity = nullptr;
m_pickupItem = nullptr; m_pickupItem = nullptr;
m_pickupType = Pickup::None; m_pickupType = Pickup::None;
@ -4260,10 +4283,10 @@ void Bot::escapeFromBomb_ () {
clearSearchNodes (); clearSearchNodes ();
int lastSelectedGoal = kInvalidNodeIndex, minPathDistance = kInfiniteDistanceLong; int lastSelectedGoal = kInvalidNodeIndex, minPathDistance = kInfiniteDistanceLong;
float safeRadius = rg.float_ (1248.0f, 2048.0f); float safeRadius = rg.float_ (1513.0f, 2048.0f);
for (int i = 0; i < graph.length (); ++i) { for (int i = 0; i < graph.length (); ++i) {
if ((graph[i].origin - graph.getBombPos ()).length () < safeRadius || isOccupiedNode (i)) { if ((graph[i].origin - graph.getBombOrigin ()).length () < safeRadius || isOccupiedNode (i)) {
continue; continue;
} }
int pathDistance = graph.getPathDist (m_currentNodeIndex, i); int pathDistance = graph.getPathDist (m_currentNodeIndex, i);
@ -4296,8 +4319,8 @@ void Bot::escapeFromBomb_ () {
void Bot::shootBreakable_ () { void Bot::shootBreakable_ () {
m_aimFlags |= AimFlags::Override; m_aimFlags |= AimFlags::Override;
// Breakable destroyed? // breakable destroyed?
if (game.isNullEntity (lookupBreakable ())) { if (!game.isShootableBreakable (m_breakableEntity)) {
completeTask (); completeTask ();
return; return;
} }
@ -4306,12 +4329,10 @@ void Bot::shootBreakable_ () {
m_checkTerrain = false; m_checkTerrain = false;
m_moveToGoal = false; m_moveToGoal = false;
m_navTimeset = game.time (); m_navTimeset = game.time ();
m_camp = m_breakableOrigin;
const Vector &src = m_breakableOrigin;
m_camp = src;
// is bot facing the breakable? // is bot facing the breakable?
if (util.getShootingCone (ent (), src) >= 0.90f) { if (util.getShootingCone (ent (), m_breakableOrigin) >= 0.90f) {
m_moveSpeed = 0.0f; m_moveSpeed = 0.0f;
m_strafeSpeed = 0.0f; m_strafeSpeed = 0.0f;
@ -4324,6 +4345,8 @@ void Bot::shootBreakable_ () {
else { else {
m_checkTerrain = true; m_checkTerrain = true;
m_moveToGoal = true; m_moveToGoal = true;
completeTask ();
} }
} }
@ -4334,7 +4357,7 @@ void Bot::pickupItem_ () {
return; return;
} }
const Vector &dest = game.getAbsPos (m_pickupItem); const Vector &dest = game.getEntityWorldOrigin (m_pickupItem);
m_destOrigin = dest; m_destOrigin = dest;
m_entity = dest; m_entity = dest;
@ -5295,7 +5318,7 @@ void Bot::resetDoubleJump () {
m_doubleJumpEntity = nullptr; m_doubleJumpEntity = nullptr;
m_duckForJump = 0.0f; m_duckForJump = 0.0f;
m_doubleJumpOrigin= nullptr; m_doubleJumpOrigin = nullptr;
m_travelStartIndex = kInvalidNodeIndex; m_travelStartIndex = kInvalidNodeIndex;
m_jumpReady = false; m_jumpReady = false;
} }
@ -5459,9 +5482,9 @@ Vector Bot::isBombAudible () {
} }
if (m_difficulty > 2) { if (m_difficulty > 2) {
return graph.getBombPos (); return graph.getBombOrigin ();
} }
const Vector &bombOrigin = graph.getBombPos (); const Vector &bombOrigin = graph.getBombOrigin ();
float timeElapsed = ((game.time () - bots.getTimeBombPlanted ()) / mp_c4timer.float_ ()) * 100.0f; float timeElapsed = ((game.time () - bots.getTimeBombPlanted ()) / mp_c4timer.float_ ()) * 100.0f;
float desiredRadius = 768.0f; float desiredRadius = 768.0f;
@ -5592,7 +5615,7 @@ bool Bot::isOutOfBombTimer () {
if (timeLeft > 13.0f) { if (timeLeft > 13.0f) {
return false; return false;
} }
const Vector &bombOrigin = graph.getBombPos (); const Vector &bombOrigin = graph.getBombOrigin ();
// for terrorist, if timer is lower than 13 seconds, return true // for terrorist, if timer is lower than 13 seconds, return true
if (timeLeft < 13.0f && m_team == Team::Terrorist && (bombOrigin - pev->origin).lengthSq () < cr::square (964.0f)) { if (timeLeft < 13.0f && m_team == Team::Terrorist && (bombOrigin - pev->origin).lengthSq () < cr::square (964.0f)) {
@ -5634,16 +5657,16 @@ void Bot::updateHearing () {
for (int i = 0; i < game.maxClients (); ++i) { for (int i = 0; i < game.maxClients (); ++i) {
const Client &client = util.getClient (i); const Client &client = util.getClient (i);
if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive) || client.ent == ent () || client.team == m_team || client.timeSoundLasting < game.time ()) { if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive) || client.ent == ent () || client.team == m_team || client.noise.last < game.time ()) {
continue; continue;
} }
if (!game.checkVisibility (client.ent, set)) { if (!game.checkVisibility (client.ent, set)) {
continue; continue;
} }
float distance = (client.sound - pev->origin).length (); float distance = (client.noise.pos - pev->origin).length ();
if (distance > client.hearingDistance) { if (distance > client.noise.dist) {
continue; continue;
} }
@ -5727,17 +5750,6 @@ void Bot::updateHearing () {
} }
} }
bool Bot::isShootableBreakable (edict_t *ent) {
// this function is checking that pointed by ent pointer obstacle, can be destroyed.
auto classname = STRING (ent->v.classname);
if (strcmp (classname, "func_breakable") == 0 || (strcmp (classname, "func_pushable") == 0 && (ent->v.spawnflags & SF_PUSH_BREAKABLE))) {
return ent->v.takedamage != DAMAGE_NO && ent->v.impulse <= 0 && !(ent->v.flags & FL_WORLDBRUSH) && !(ent->v.spawnflags & SF_BREAK_TRIGGER_ONLY) && ent->v.health < 500.0f;
}
return false;
}
void Bot::enteredBuyZone (int buyState) { void Bot::enteredBuyZone (int buyState) {
// this function is gets called when bot enters a buyzone, to allow bot to buy some stuff // this function is gets called when bot enters a buyzone, to allow bot to buy some stuff

View file

@ -9,7 +9,7 @@
#include <yapb.h> #include <yapb.h>
ConVar yb_chat ("yb_chat", "1"); ConVar yb_chat ("yb_chat", "1", "Enables or disables bots chat functionality.");
void BotUtils::stripTags (String &line) { void BotUtils::stripTags (String &line) {
if (line.empty ()) { if (line.empty ()) {
@ -23,9 +23,9 @@ void BotUtils::stripTags (String &line) {
const size_t end = line.find (tag.second, start); const size_t end = line.find (tag.second, start);
const size_t diff = end - start; const size_t diff = end - start;
if (end != String::InvalidIndex && end > start && diff < 32 && diff > 2) { if (end != String::InvalidIndex && end > start && diff < 32 && diff > 1) {
line.erase (start, diff + tag.second.length ()); line.erase (start, diff + tag.second.length ());
break; continue;
} }
} }
} }

View file

@ -9,9 +9,9 @@
#include <yapb.h> #include <yapb.h>
ConVar yb_shoots_thru_walls ("yb_shoots_thru_walls", "2"); ConVar yb_shoots_thru_walls ("yb_shoots_thru_walls", "2", "Specifies whether bots able to fire at enemies behind the wall, if they hearing or suspecting them.", true, 0.0f, 3.0f);
ConVar yb_ignore_enemies ("yb_ignore_enemies", "0"); ConVar yb_ignore_enemies ("yb_ignore_enemies", "0", "Enables or disables searching world for enemies.");
ConVar yb_check_enemy_rendering ("yb_check_enemy_rendering", "0"); ConVar yb_check_enemy_rendering ("yb_check_enemy_rendering", "0", "Enables or disables checking enemy rendering flags. Useful for some mods.");
ConVar mp_friendlyfire ("mp_friendlyfire", nullptr, Var::NoRegister); ConVar mp_friendlyfire ("mp_friendlyfire", nullptr, Var::NoRegister);
@ -218,7 +218,7 @@ bool Bot::lookupEnemies () {
m_aimFlags |= AimFlags::LastEnemy; m_aimFlags |= AimFlags::LastEnemy;
} }
m_enemyParts = Visibility::None; m_enemyParts = Visibility::None;
m_enemyOrigin= nullptr; m_enemyOrigin = nullptr;
if (!game.isNullEntity (m_enemy)) { if (!game.isNullEntity (m_enemy)) {
player = m_enemy; player = m_enemy;
@ -447,7 +447,7 @@ const Vector &Bot::getEnemyBodyOffset () {
Vector aimPos = m_enemy->v.origin; Vector aimPos = m_enemy->v.origin;
if (m_difficulty > 2) { if (m_difficulty > 2) {
aimPos += (m_enemy->v.velocity - pev->velocity) * (getFrameInterval () * 1.75f); aimPos += (m_enemy->v.velocity - pev->velocity) * (getFrameInterval () * 1.25f);
} }
// if we only suspect an enemy behind a wall take the worst skill // if we only suspect an enemy behind a wall take the worst skill
@ -455,7 +455,7 @@ const Vector &Bot::getEnemyBodyOffset () {
aimPos += getBodyOffsetError (distance); aimPos += getBodyOffsetError (distance);
} }
else { else {
bool useBody = !usesPistol () && distance > kSprayDistance && distance < 2048.0f; bool useBody = !usesPistol () && distance >= kSprayDistance && distance < 3072.0f;
// now take in account different parts of enemy body // now take in account different parts of enemy body
if (m_enemyParts & (Visibility::Head | Visibility::Body)) { if (m_enemyParts & (Visibility::Head | Visibility::Body)) {
@ -506,10 +506,7 @@ float Bot::getEnemyBodyOffsetCorrection (float distance) {
float result = -2.0f; float result = -2.0f;
if (distance < kSprayDistance) { if (distance >= kDoubleSprayDistance) {
return -16.0f;
}
else if (distance >= kDoubleSprayDistance) {
if (sniper) { if (sniper) {
result = 0.18f; result = 0.18f;
} }
@ -523,7 +520,7 @@ float Bot::getEnemyBodyOffsetCorrection (float distance) {
result = 1.5f; result = 1.5f;
} }
else if (rifle) { else if (rifle) {
result = 1.0f; result = -1.0f;
} }
else if (m249) { else if (m249) {
result = -5.5f; result = -5.5f;
@ -844,7 +841,9 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) {
pev->button |= IN_ATTACK; // use primary attack pev->button |= IN_ATTACK; // use primary attack
} }
else { else {
pev->button |= IN_ATTACK; if ((m_oldButtons & IN_ATTACK) == 0) {
pev->button |= IN_ATTACK;
}
const float minDelay[] = { 0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.6f }; const float minDelay[] = { 0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.6f };
const float maxDelay[] = { 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.7f }; const float maxDelay[] = { 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.7f };
@ -916,6 +915,7 @@ void Bot::fireWeapons () {
const auto &prop = conf.getWeaponProp (id); const auto &prop = conf.getWeaponProp (id);
if (prop.ammo1 != -1 && prop.ammo1 < 32 && m_ammo[prop.ammo1] >= tab[selectIndex].minPrimaryAmmo) { if (prop.ammo1 != -1 && prop.ammo1 < 32 && m_ammo[prop.ammo1] >= tab[selectIndex].minPrimaryAmmo) {
// available ammo found, reload weapon // available ammo found, reload weapon
if (m_reloadState == Reload::None || m_reloadCheckTime > game.time ()) { if (m_reloadState == Reload::None || m_reloadCheckTime > game.time ()) {
m_isReloading = true; m_isReloading = true;

View file

@ -9,9 +9,9 @@
#include <yapb.h> #include <yapb.h>
ConVar yb_display_menu_text ("yb_display_menu_text", "1"); ConVar yb_display_menu_text ("yb_display_menu_text", "1", "Enables or disables display menu text, when players asks for menu. Useful only for Android.");
ConVar yb_password ("yb_password", "", Var::Password); ConVar yb_password ("yb_password", "", "The value (password) for the setinfo key, if user set's correct password, he's gains access to bot commands and menus.", false, 0.0f, 0.0f, Var::Password);
ConVar yb_password_key ("yb_password_key", "_ybpw"); ConVar yb_password_key ("yb_password_key", "_ybpw", "The name of setinfo key used to store password to bot commands and menus", false);
int BotControl::cmdAddBot () { int BotControl::cmdAddBot () {
enum args { alias = 1, difficulty, personality, team, model, name, max }; enum args { alias = 1, difficulty, personality, team, model, name, max };
@ -161,9 +161,25 @@ int BotControl::cmdWeaponMode () {
} }
int BotControl::cmdVersion () { int BotControl::cmdVersion () {
auto hash = String (PRODUCT_GIT_HASH).substr (0, 8);
auto author = String (PRODUCT_GIT_COMMIT_AUTHOR);
// if no hash specified, set local one
if (hash.startsWith ("unspe")) {
hash = "local";
}
// if no commit author, set local one
if (author.startsWith ("unspe")) {
author = PRODUCT_EMAIL;
}
msg ("%s v%s (build %u)", PRODUCT_NAME, PRODUCT_VERSION, util.buildNumber ()); msg ("%s v%s (build %u)", PRODUCT_NAME, PRODUCT_VERSION, util.buildNumber ());
msg (" compiled: %s %s by %s", __DATE__, __TIME__, PRODUCT_GIT_COMMIT_AUTHOR); msg (" compiled: %s %s by %s", __DATE__, __TIME__, author.chars ());
msg (" commit: %scommit/%s", PRODUCT_COMMENTS, PRODUCT_GIT_HASH);
if (!hash.startsWith ("local")) {
msg (" commit: %scommit/%s", PRODUCT_COMMENTS, hash.chars ());
}
msg (" url: %s", PRODUCT_URL); msg (" url: %s", PRODUCT_URL);
return BotCommandResult::Handled; return BotCommandResult::Handled;
@ -207,6 +223,84 @@ int BotControl::cmdList () {
return BotCommandResult::Handled; return BotCommandResult::Handled;
} }
int BotControl::cmdCvars () {
enum args { alias = 1, pattern, max };
// adding more args to args array, if not enough passed
fixMissingArgs (max);
const auto &match = getStr (pattern);
const bool isSave = match == "save";
File cfg;
// if save requested, dump cvars to yapb.cfg
if (isSave) {
cfg.open (strings.format ("%s/addons/yapb/conf/yapb.cfg", game.getModName ()), "wt");
cfg.puts ("// Configuration file for %s\n\n", PRODUCT_SHORT_NAME);
}
for (const auto &cvar : game.getCvars ()) {
if (cvar.info.empty ()) {
continue;
}
if (!isSave && match != "empty" && !strstr (cvar.reg.name, match.chars ())) {
continue;
}
// float value ?
bool isFloat = !strings.isEmpty (cvar.self->str ()) && strstr (cvar.self->str (), ".");
if (isSave) {
cfg.puts ("//\n");
cfg.puts ("// %s\n", String::join (cvar.info.split ("\n"), "\n// ").chars ());
cfg.puts ("// ---\n");
if (cvar.bounded) {
if (isFloat) {
cfg.puts ("// Default: \"%.1f\", Min: \"%.1f\", Max: \"%.1f\"\n", cvar.initial, cvar.min, cvar.max);
}
else {
cfg.puts ("// Default: \"%i\", Min: \"%i\", Max: \"%i\"\n", static_cast <int> (cvar.initial), static_cast <int> (cvar.min), static_cast <int> (cvar.max));
}
}
else {
cfg.puts ("// Default: \"%s\"\n", cvar.self->str ());
}
cfg.puts ("// \n");
if (cvar.bounded) {
if (isFloat) {
cfg.puts ("%s \"%.1f\"\n", cvar.reg.name, cvar.self->float_ ());
}
else {
cfg.puts ("%s \"%i\"\n", cvar.reg.name, cvar.self->int_ ());
}
}
else {
cfg.puts ("%s \"%s\"\n", cvar.reg.name, cvar.self->str ());
}
cfg.puts ("\n");
}
else {
game.print ("cvar: %s", cvar.reg.name);
game.print ("info: %s", cvar.info.chars ());
game.print (" ");
}
}
if (isSave) {
ctrl.msg ("Bots cvars has been written to file.");
cfg.close ();
}
return BotCommandResult::Handled;
}
int BotControl::cmdNode () { int BotControl::cmdNode () {
enum args { root, alias, cmd, cmd2, max }; enum args { root, alias, cmd, cmd2, max };
@ -1698,7 +1792,8 @@ void BotControl::kickBotByMenu (int page) {
for (int i = menuKey; i < page * 8; ++i) { for (int i = menuKey; i < page * 8; ++i) {
auto bot = bots[i]; auto bot = bots[i];
if (bot != nullptr) { // check for fakeclient bit, since we're clear it upon kick, but actual bot struct destroyed after client disconnected
if (bot != nullptr && (bot->pev->flags & FL_FAKECLIENT)) {
menuKeys |= cr::bit (cr::abs (i - menuKey)); menuKeys |= cr::bit (cr::abs (i - menuKey));
menus.appendf ("%1.1d. %s%s\n", i - menuKey + 1, STRING (bot->pev->netname), bot->m_team == Team::CT ? " \\y(CT)\\w" : " \\r(T)\\w"); menus.appendf ("%1.1d. %s%s\n", i - menuKey + 1, STRING (bot->pev->netname), bot->m_team == Team::CT ? " \\y(CT)\\w" : " \\r(T)\\w");
} }
@ -1795,6 +1890,7 @@ BotControl::BotControl () {
m_cmds.emplace ("graphmenu/wpmenu/wptmenu", "graphmenu [noarguments]", "Opens and displays bots graph edtior.", &BotControl::cmdNodeMenu); m_cmds.emplace ("graphmenu/wpmenu/wptmenu", "graphmenu [noarguments]", "Opens and displays bots graph edtior.", &BotControl::cmdNodeMenu);
m_cmds.emplace ("list/listbots", "list [noarguments]", "Lists the bots currently playing on server.", &BotControl::cmdList); m_cmds.emplace ("list/listbots", "list [noarguments]", "Lists the bots currently playing on server.", &BotControl::cmdList);
m_cmds.emplace ("graph/g/wp/wpt/waypoint", "graph [help]", "Handles graph operations.", &BotControl::cmdNode); m_cmds.emplace ("graph/g/wp/wpt/waypoint", "graph [help]", "Handles graph operations.", &BotControl::cmdNode);
m_cmds.emplace ("cvars", "cvars [save|cvar]", "Display all the cvars with their descriptions.", &BotControl::cmdCvars);
// declare the menus // declare the menus
createMenus (); createMenus ();

View file

@ -61,6 +61,9 @@ void Game::levelInitialize (edict_t *entities, int max) {
m_spawnCount[Team::CT] = 0; m_spawnCount[Team::CT] = 0;
m_spawnCount[Team::Terrorist] = 0; m_spawnCount[Team::Terrorist] = 0;
// clear all breakables before initialization
m_breakables.clear ();
// go thru the all entities on map, and do whatever we're want // go thru the all entities on map, and do whatever we're want
for (int i = 0; i < max; ++i) { for (int i = 0; i < max; ++i) {
auto ent = entities + i; auto ent = entities + i;
@ -132,6 +135,9 @@ void Game::levelInitialize (edict_t *entities, int max) {
else if (strncmp (classname, "func_button", 11) == 0) { else if (strncmp (classname, "func_button", 11) == 0) {
m_mapFlags |= MapFlags::HasButtons; m_mapFlags |= MapFlags::HasButtons;
} }
else if (isShootableBreakable (ent)) {
m_breakables.push (ent);
}
} }
// next maps doesn't have map-specific entities, so determine it by name // next maps doesn't have map-specific entities, so determine it by name
@ -301,7 +307,7 @@ const char *Game::getMapName () {
return strings.format ("%s", STRING (globals->mapname)); return strings.format ("%s", STRING (globals->mapname));
} }
Vector Game::getAbsPos (edict_t *ent) { Vector Game::getEntityWorldOrigin (edict_t *ent) {
// this expanded function returns the vector origin of a bounded entity, assuming that any // this expanded function returns the vector origin of a bounded entity, assuming that any
// entity that has a bounding box has its center at the center of the bounding box itself. // entity that has a bounding box has its center at the center of the bounding box itself.
@ -463,35 +469,61 @@ bool Game::isSoftwareRenderer () {
return false; return false;
} }
void Game::addNewCvar (const char *variable, const char *value, Var varType, bool regMissing, const char *regVal, ConVar *self) { void Game::addNewCvar (const char *name, const char *value, const char *info, bool bounded, float min, float max, Var varType, bool missingAction, const char *regval, ConVar *self) {
// this function adds globally defined variable to registration stack // this function adds globally defined variable to registration stack
VarPair pair {}; VarPair pair;
pair.reg.name = const_cast <char *> (variable); pair.reg.name = const_cast <char *> (name);
pair.reg.string = const_cast <char *> (value); pair.reg.string = const_cast <char *> (value);
pair.missing = regMissing; pair.missing = missingAction;
pair.regval = regVal; pair.regval = regval;
pair.info = info;
pair.bounded = bounded;
int engineFlags = FCVAR_EXTDLL; if (bounded) {
pair.min = min;
pair.max = max;
pair.initial = static_cast <float> (atof (value));
}
auto eflags = FCVAR_EXTDLL;
if (varType == Var::Normal) { if (varType == Var::Normal) {
engineFlags |= FCVAR_SERVER; eflags |= FCVAR_SERVER;
} }
else if (varType == Var::ReadOnly) { else if (varType == Var::ReadOnly) {
engineFlags |= FCVAR_SERVER | FCVAR_SPONLY | FCVAR_PRINTABLEONLY; eflags |= FCVAR_SERVER | FCVAR_SPONLY | FCVAR_PRINTABLEONLY;
} }
else if (varType == Var::Password) { else if (varType == Var::Password) {
engineFlags |= FCVAR_PROTECTED; eflags |= FCVAR_PROTECTED;
} }
pair.reg.flags = engineFlags; pair.reg.flags = eflags;
pair.self = self; pair.self = self;
pair.type = varType; pair.type = varType;
m_cvars.push (cr::move (pair)); m_cvars.push (cr::move (pair));
} }
void Game::checkCvarsBounds () {
for (const auto &var : m_cvars) {
if (!var.bounded || !var.self) {
continue;
}
auto value = var.self->float_ ();
auto str = var.self->str ();
// check the bounds and set default if out of bounds
if (value > var.max || value < var.min || (!strings.isEmpty (str) && isalpha (*str))) {
var.self->set (var.initial);
// notify about that
ctrl.msg ("Bogus value for cvar '%s', min is '%.1f' and max is '%.1f', and we're got '%s', value reverted to default '%.1f'.", var.reg.name, var.min, var.max, str, var.initial);
}
}
}
void Game::registerCvars (bool gameVars) { void Game::registerCvars (bool gameVars) {
// this function pushes all added global variables to engine registration // this function pushes all added global variables to engine registration
@ -500,9 +532,9 @@ void Game::registerCvars (bool gameVars) {
cvar_t &reg = var.reg; cvar_t &reg = var.reg;
if (var.type != Var::NoRegister) { if (var.type != Var::NoRegister) {
self.eptr = engfuncs.pfnCVarGetPointer (reg.name); self.ptr = engfuncs.pfnCVarGetPointer (reg.name);
if (!self.eptr) { if (!self.ptr) {
static cvar_t reg_; static cvar_t reg_;
// fix metamod' memlocs not found // fix metamod' memlocs not found
@ -513,22 +545,22 @@ void Game::registerCvars (bool gameVars) {
else { else {
engfuncs.pfnCVarRegister (&var.reg); engfuncs.pfnCVarRegister (&var.reg);
} }
self.eptr = engfuncs.pfnCVarGetPointer (reg.name); self.ptr = engfuncs.pfnCVarGetPointer (reg.name);
} }
} }
else if (gameVars) { else if (gameVars) {
self.eptr = engfuncs.pfnCVarGetPointer (reg.name); self.ptr = engfuncs.pfnCVarGetPointer (reg.name);
if (var.missing && !self.eptr) { if (var.missing && !self.ptr) {
if (reg.string == nullptr && var.regval != nullptr) { if (reg.string == nullptr && var.regval != nullptr) {
reg.string = const_cast <char *> (var.regval); reg.string = const_cast <char *> (var.regval);
reg.flags |= FCVAR_SERVER; reg.flags |= FCVAR_SERVER;
} }
engfuncs.pfnCVarRegister (&var.reg); engfuncs.pfnCVarRegister (&var.reg);
self.eptr = engfuncs.pfnCVarGetPointer (reg.name); self.ptr = engfuncs.pfnCVarGetPointer (reg.name);
} }
if (!self.eptr) { if (!self.ptr) {
print ("Got nullptr on cvar %s!", reg.name); print ("Got nullptr on cvar %s!", reg.name);
} }
} }
@ -644,7 +676,6 @@ bool Game::postload () {
// register bot cvars // register bot cvars
game.registerCvars (); game.registerCvars ();
// register server command(s) // register server command(s)
registerEngineCommand ("yapb", [] () { registerEngineCommand ("yapb", [] () {
ctrl.handleEngineCommands (); ctrl.handleEngineCommands ();
@ -718,7 +749,6 @@ bool Game::postload () {
logger.fatal ("Unable to load gamedll \"%s\". Exiting... (gamedir: %s)", gamedll, getModName ()); logger.fatal ("Unable to load gamedll \"%s\". Exiting... (gamedir: %s)", gamedll, getModName ());
} }
displayCSVersion (); displayCSVersion ();
} }
else { else {
bool binaryLoaded = loadCSBinary (); bool binaryLoaded = loadCSBinary ();
@ -782,6 +812,9 @@ void Game::slowFrame () {
// detect csdm // detect csdm
detectDeathmatch (); detectDeathmatch ();
// check the cvar bounds
checkCvarsBounds ();
// display welcome message // display welcome message
util.checkWelcome (); util.checkWelcome ();
m_slowFrame = time () + 1.0f; m_slowFrame = time () + 1.0f;
@ -816,6 +849,18 @@ void Game::searchEntities (const Vector &position, const float radius, EntitySea
} }
} }
bool Game::isShootableBreakable (edict_t *ent) {
if (isNullEntity (ent)) {
return false;
}
auto classname = STRING (ent->v.classname);
if (strcmp (classname, "func_breakable") == 0 || (strcmp (classname, "func_pushable") == 0 && (ent->v.spawnflags & SF_PUSH_BREAKABLE))) {
return ent->v.takedamage != DAMAGE_NO && ent->v.impulse <= 0 && !(ent->v.flags & FL_WORLDBRUSH) && !(ent->v.spawnflags & SF_BREAK_TRIGGER_ONLY) && ent->v.health < 500.0f;
}
return false;
}
void LightMeasure::initializeLightstyles () { void LightMeasure::initializeLightstyles () {
// this function initializes lighting information... // this function initializes lighting information...

View file

@ -9,9 +9,8 @@
#include <yapb.h> #include <yapb.h>
ConVar yb_graph_subfolder ("yb_graph_subfolder", ""); ConVar yb_graph_fixcamp ("yb_graph_fixcamp", "1", "Specifies whether bot should not 'fix' camp directions of camp waypoints when loading old PWF format.");
ConVar yb_graph_fixcamp ("yb_graph_fixcamp", "1"); ConVar yb_graph_url ("yb_graph_url", "http://graph.yapb.ru", "Specifies the URL from bots will be able to download graph in case of missing local one.", false);
ConVar yb_graph_url ("yb_graph_url", "http://graph.yapb.ru");
void BotGraph::initGraph () { void BotGraph::initGraph () {
// this function initialize the graph structures.. // this function initialize the graph structures..
@ -20,7 +19,7 @@ void BotGraph::initGraph () {
m_editFlags = 0; m_editFlags = 0;
m_learnVelocity= nullptr; m_learnVelocity= nullptr;
m_learnPosition= nullptr; m_learnPosition = nullptr;
m_lastNode= nullptr; m_lastNode= nullptr;
m_pathDisplayTime = 0.0f; m_pathDisplayTime = 0.0f;
@ -1679,7 +1678,7 @@ void BotGraph::saveOldFormat () {
const char *BotGraph::getOldFormatGraphName (bool isMemoryFile) { const char *BotGraph::getOldFormatGraphName (bool isMemoryFile) {
static String buffer; static String buffer;
buffer.assignf ("%s%s%s.pwf", getDataDirectory (isMemoryFile), strings.isEmpty (yb_graph_subfolder.str ()) ? "" : yb_graph_subfolder.str (), game.getMapName ()); buffer.assignf ("%s%s.pwf", getDataDirectory (isMemoryFile), game.getMapName ());
if (File::exists (buffer)) { if (File::exists (buffer)) {
return buffer.chars (); return buffer.chars ();
@ -2472,7 +2471,7 @@ void BotGraph::addBasic () {
Vector up, down, front, back; Vector up, down, front, back;
const Vector &diff = ((ladderLeft - ladderRight) ^ Vector (0.0f, 0.0f, 0.0f)).normalize () * 15.0f; const Vector &diff = ((ladderLeft - ladderRight) ^ Vector (0.0f, 0.0f, 0.0f)).normalize () * 15.0f;
front = back = game.getAbsPos (ent); front = back = game.getEntityWorldOrigin (ent);
front = front + diff; // front front = front + diff; // front
back = back - diff; // back back = back - diff; // back
@ -2512,7 +2511,7 @@ void BotGraph::addBasic () {
auto autoCreateForEntity = [] (int type, const char *entity) { auto autoCreateForEntity = [] (int type, const char *entity) {
game.searchEntities ("classname", entity, [&] (edict_t *ent) { game.searchEntities ("classname", entity, [&] (edict_t *ent) {
const Vector &pos = game.getAbsPos (ent); const Vector &pos = game.getEntityWorldOrigin (ent);
if (graph.getNearestNoBuckets (pos, 50.0f) == kInvalidNodeIndex) { if (graph.getNearestNoBuckets (pos, 50.0f) == kInvalidNodeIndex) {
graph.add (type, pos); graph.add (type, pos);
@ -2577,24 +2576,24 @@ const char *BotGraph::getDataDirectory (bool isMemoryFile) {
return buffer.chars (); return buffer.chars ();
} }
void BotGraph::setBombPos (bool reset, const Vector &pos) { void BotGraph::setBombOrigin (bool reset, const Vector &pos) {
// this function stores the bomb position as a vector // this function stores the bomb position as a vector
if (reset) { if (reset) {
m_bombPos= nullptr; m_bombOrigin = nullptr;
bots.setBombPlanted (false); bots.setBombPlanted (false);
return; return;
} }
if (!pos.empty ()) { if (!pos.empty ()) {
m_bombPos = pos; m_bombOrigin = pos;
return; return;
} }
game.searchEntities ("classname", "grenade", [&] (edict_t *ent) { game.searchEntities ("classname", "grenade", [&] (edict_t *ent) {
if (strcmp (STRING (ent->v.model) + 9, "c4.mdl") == 0) { if (strcmp (STRING (ent->v.model) + 9, "c4.mdl") == 0) {
m_bombPos = game.getAbsPos (ent); m_bombOrigin = game.getEntityWorldOrigin (ent);
return EntitySearchResult::Break; return EntitySearchResult::Break;
} }
return EntitySearchResult::Continue; return EntitySearchResult::Continue;

View file

@ -9,24 +9,25 @@
#include <yapb.h> #include <yapb.h>
ConVar yb_autovacate ("yb_autovacate", "1"); ConVar yb_autovacate ("yb_autovacate", "1", "Kick bots to automatically make room for human players.");
ConVar yb_quota ("yb_quota", "0", Var::Normal); ConVar yb_quota ("yb_quota", "0", "Specifies the number bots to be added to the game.", true, 0.0f, static_cast <float> (kGameMaxPlayers));
ConVar yb_quota_mode ("yb_quota_mode", "normal"); ConVar yb_quota_mode ("yb_quota_mode", "normal", "Specifies the type of quota.\nAllowed values: 'normal', 'fill', and 'match'.\nIf 'fill', the server will adjust bots to keep N players in the game, where N is yb_quota.\nIf 'match', the server will maintain a 1:N ratio of humans to bots, where N is yb_quota_match.", false);
ConVar yb_quota_match ("yb_quota_match", "0"); ConVar yb_quota_match ("yb_quota_match", "0", "Number of players to match if yb_quota_mode set to 'match'", true, 0.0f, static_cast <float> (kGameMaxPlayers));
ConVar yb_think_fps ("yb_think_fps", "30.0"); ConVar yb_think_fps ("yb_think_fps", "30.0", "Specifies hou many times per second bot code will run.", true, 30.0f, 90.0f);
ConVar yb_join_after_player ("yb_join_after_player", "0"); ConVar yb_join_after_player ("yb_join_after_player", "0", "Sepcifies whether bots should join server, only when at least one human player in game.");
ConVar yb_join_team ("yb_join_team", "any"); ConVar yb_join_team ("yb_join_team", "any", "Forces all bots to join team specified here.", false);
ConVar yb_join_delay ("yb_join_delay", "5.0"); ConVar yb_join_delay ("yb_join_delay", "5.0", "Specifies after how many seconds bots should start to join the game after the changelevel.", true, 0.0f, 30.0f);
ConVar yb_name_prefix ("yb_name_prefix", ""); ConVar yb_name_prefix ("yb_name_prefix", "", "All the bot names will be prefixed with string specified with this cvar.", false);
ConVar yb_difficulty ("yb_difficulty", "4"); ConVar yb_difficulty ("yb_difficulty", "4", "All bots difficulty level. Chaning at runtime will affect already created bots.", true, 0.0f, 4.0f);
ConVar yb_show_avatars ("yb_show_avatars", "1"); ConVar yb_show_avatars ("yb_show_avatars", "1", "Enables or disabels displaying bot avatars in front of their names in scoreboard. Note, that is currently you can see only avatars of your steam friends.");
ConVar yb_show_latency ("yb_show_latency", "2"); ConVar yb_show_latency ("yb_show_latency", "2", "Enables latency display in scoreboard.\nAllowed values: '0', '1', '2'.\nIf '0', there is nothing displayed.\nIf '1', there is a 'BOT' is displayed.\nIf '2' fake ping is displayed.", true, 0.0f, 2.0f);
ConVar yb_language ("yb_language", "en");
ConVar yb_ignore_cvars_on_changelevel ("yb_ignore_cvars_on_changelevel", "yb_quota,yb_autovacate"); ConVar yb_language ("yb_language", "en", "Specifies the language for bot messages and menus.", false);
ConVar yb_ignore_cvars_on_changelevel ("yb_ignore_cvars_on_changelevel", "yb_quota,yb_autovacate", "Specifies comma separated list of bot cvars, that will not be overriten by config on changelevel.", false);
ConVar mp_limitteams ("mp_limitteams", nullptr, Var::NoRegister); ConVar mp_limitteams ("mp_limitteams", nullptr, Var::NoRegister);
ConVar mp_autoteambalance ("mp_autoteambalance", nullptr, Var::NoRegister); ConVar mp_autoteambalance ("mp_autoteambalance", nullptr, Var::NoRegister);
@ -1024,8 +1025,9 @@ void Bot::newRound () {
clearSearchNodes (); clearSearchNodes ();
clearRoute (); clearRoute ();
m_pathOrigin= nullptr; m_pathOrigin = nullptr;
m_destOrigin= nullptr; m_destOrigin = nullptr;
m_path = nullptr; m_path = nullptr;
m_currentTravelFlags = 0; m_currentTravelFlags = 0;
m_desiredVelocity= nullptr; m_desiredVelocity= nullptr;
@ -1095,7 +1097,7 @@ void Bot::newRound () {
m_itemCheckTime = 0.0f; m_itemCheckTime = 0.0f;
m_breakableEntity = nullptr; m_breakableEntity = nullptr;
m_breakableOrigin= nullptr; m_breakableOrigin = nullptr;
m_timeDoorOpen = 0.0f; m_timeDoorOpen = 0.0f;
resetCollision (); resetCollision ();
@ -1104,7 +1106,7 @@ void Bot::newRound () {
m_enemy = nullptr; m_enemy = nullptr;
m_lastVictim = nullptr; m_lastVictim = nullptr;
m_lastEnemy = nullptr; m_lastEnemy = nullptr;
m_lastEnemyOrigin= nullptr; m_lastEnemyOrigin = nullptr;
m_trackingEdict = nullptr; m_trackingEdict = nullptr;
m_timeNextTracking = 0.0f; m_timeNextTracking = 0.0f;
@ -1129,8 +1131,8 @@ void Bot::newRound () {
m_liftState = 0; m_liftState = 0;
m_aimLastError= nullptr; m_aimLastError= nullptr;
m_position= nullptr; m_position = nullptr;
m_liftTravelPos= nullptr; m_liftTravelPos = nullptr;
setIdealReactionTimers (true); setIdealReactionTimers (true);
@ -1392,7 +1394,7 @@ void BotManager::notifyBombDefuse () {
if (bot->m_team == Team::Terrorist && bot->m_notKilled && bot->getCurrentTaskId () != Task::MoveToPosition) { if (bot->m_team == Team::Terrorist && bot->m_notKilled && bot->getCurrentTaskId () != Task::MoveToPosition) {
bot->clearSearchNodes (); bot->clearSearchNodes ();
bot->m_position = graph.getBombPos (); bot->m_position = graph.getBombOrigin ();
bot->startTask (Task::MoveToPosition, TaskPri::MoveToPosition, kInvalidNodeIndex, 0.0f, true); bot->startTask (Task::MoveToPosition, TaskPri::MoveToPosition, kInvalidNodeIndex, 0.0f, true);
} }
} }
@ -1576,7 +1578,7 @@ void BotManager::initRound () {
client.radio = 0; client.radio = 0;
} }
graph.setBombPos (true); graph.setBombOrigin (true);
graph.clearVisited (); graph.clearVisited ();
m_bombSayStatus = BombPlantedSay::ChatSay | BombPlantedSay::Chatter; m_bombSayStatus = BombPlantedSay::ChatSay | BombPlantedSay::Chatter;
@ -1687,8 +1689,8 @@ void BotConfig::loadMainConfig () {
firstLoad = false; firstLoad = false;
// android is abit hard to play, lower the difficulty by default // android is abit hard to play, lower the difficulty by default
if (plat.isAndroid && yb_difficulty.int_ () > 2) { if (plat.isAndroid && yb_difficulty.int_ () > 3) {
yb_difficulty.set (2); yb_difficulty.set (3);
} }
return; return;
} }
@ -1808,6 +1810,8 @@ void BotConfig::loadChatterConfig () {
// chatter initialization // chatter initialization
if (game.is (GameFlags::HasBotVoice) && yb_radio_mode.int_ () == 2 && util.openConfig ("chatter.cfg", "Couldn't open chatter system configuration", &file)) { if (game.is (GameFlags::HasBotVoice) && yb_radio_mode.int_ () == 2 && util.openConfig ("chatter.cfg", "Couldn't open chatter system configuration", &file)) {
m_chatter.clear ();
struct EventMap { struct EventMap {
String str; String str;
int code; int code;
@ -1827,10 +1831,10 @@ void BotConfig::loadChatterConfig () {
{ "Radio_ReportTeam", Radio::ReportInTeam, kMaxChatterRepeatInteval }, { "Radio_ReportTeam", Radio::ReportInTeam, kMaxChatterRepeatInteval },
{ "Radio_Affirmative", Radio::RogerThat, kMaxChatterRepeatInteval }, { "Radio_Affirmative", Radio::RogerThat, kMaxChatterRepeatInteval },
{ "Radio_EnemySpotted", Radio::EnemySpotted, 4.0f }, { "Radio_EnemySpotted", Radio::EnemySpotted, 4.0f },
{ "Radio_NeedBackup", Radio::NeedBackup, kMaxChatterRepeatInteval }, { "Radio_NeedBackup", Radio::NeedBackup, 5.0f },
{ "Radio_SectorClear", Radio::SectorClear, 10.0f }, { "Radio_SectorClear", Radio::SectorClear, 10.0f },
{ "Radio_InPosition", Radio::ImInPosition, 10.0f }, { "Radio_InPosition", Radio::ImInPosition, 10.0f },
{ "Radio_ReportingIn", Radio::ReportingIn, kMaxChatterRepeatInteval }, { "Radio_ReportingIn", Radio::ReportingIn, 3.0f },
{ "Radio_ShesGonnaBlow", Radio::ShesGonnaBlow, kMaxChatterRepeatInteval }, { "Radio_ShesGonnaBlow", Radio::ShesGonnaBlow, kMaxChatterRepeatInteval },
{ "Radio_Negative", Radio::Negative, kMaxChatterRepeatInteval }, { "Radio_Negative", Radio::Negative, kMaxChatterRepeatInteval },
{ "Radio_EnemyDown", Radio::EnemyDown, 10.0f }, { "Radio_EnemyDown", Radio::EnemyDown, 10.0f },
@ -1853,7 +1857,7 @@ void BotConfig::loadChatterConfig () {
{ "Chatter_WhereIsTheBomb", Chatter::WhereIsTheC4, kMaxChatterRepeatInteval }, { "Chatter_WhereIsTheBomb", Chatter::WhereIsTheC4, kMaxChatterRepeatInteval },
{ "Chatter_DefendingBombSite", Chatter::DefendingBombsite, kMaxChatterRepeatInteval }, { "Chatter_DefendingBombSite", Chatter::DefendingBombsite, kMaxChatterRepeatInteval },
{ "Chatter_BarelyDefused", Chatter::BarelyDefused, kMaxChatterRepeatInteval }, { "Chatter_BarelyDefused", Chatter::BarelyDefused, kMaxChatterRepeatInteval },
{ "Chatter_NiceshotCommander", Chatter::NiceShotCommander, kMaxChatterRepeatInteval }, { "Chatter_NiceshotCommander", Chatter::NiceShotCommander, 10.0f },
{ "Chatter_ReportingIn", Chatter::ReportingIn, 10.0f }, { "Chatter_ReportingIn", Chatter::ReportingIn, 10.0f },
{ "Chatter_SpotTheBomber", Chatter::SpotTheBomber, 4.3f }, { "Chatter_SpotTheBomber", Chatter::SpotTheBomber, 4.3f },
{ "Chatter_VIPSpotted", Chatter::VIPSpotted, 5.3f }, { "Chatter_VIPSpotted", Chatter::VIPSpotted, 5.3f },

View file

@ -37,10 +37,13 @@ void MessageDispatcher::netMsgTextMsg () {
return; return;
} }
// reset bomb position
if (game.mapIs (MapFlags::Demolition)) { // reset bomb position for all the bots
graph.setBombPos (true); const auto resetBombPosition = [] () -> void {
} if (game.mapIs (MapFlags::Demolition)) {
graph.setBombOrigin (true);
}
};
if (cached & TextMsgCache::Commencing) { if (cached & TextMsgCache::Commencing) {
util.setNeedForWelcome (true); util.setNeedForWelcome (true);
@ -48,6 +51,8 @@ void MessageDispatcher::netMsgTextMsg () {
else if (cached & TextMsgCache::CounterWin) { else if (cached & TextMsgCache::CounterWin) {
bots.setLastWinner (Team::CT); // update last winner for economics bots.setLastWinner (Team::CT); // update last winner for economics
dispatchChatterMessage (); dispatchChatterMessage ();
resetBombPosition ();
} }
else if (cached & TextMsgCache::RestartRound) { else if (cached & TextMsgCache::RestartRound) {
bots.updateTeamEconomics (Team::CT, true); bots.updateTeamEconomics (Team::CT, true);
@ -60,10 +65,14 @@ void MessageDispatcher::netMsgTextMsg () {
bot->m_moneyAmount = mp_startmoney.int_ (); bot->m_moneyAmount = mp_startmoney.int_ ();
return false; return false;
}); });
resetBombPosition ();
} }
else if (cached & TextMsgCache::TerroristWin) { else if (cached & TextMsgCache::TerroristWin) {
bots.setLastWinner (Team::Terrorist); // update last winner for economics bots.setLastWinner (Team::Terrorist); // update last winner for economics
dispatchChatterMessage (); dispatchChatterMessage ();
resetBombPosition ();
} }
else if ((cached & TextMsgCache::BombPlanted) && !bots.isBombPlanted ()) { else if ((cached & TextMsgCache::BombPlanted) && !bots.isBombPlanted ()) {
bots.setBombPlanted (true); bots.setBombPlanted (true);
@ -78,7 +87,7 @@ void MessageDispatcher::netMsgTextMsg () {
} }
} }
} }
graph.setBombPos (); graph.setBombOrigin ();
} }
// check for burst fire message // check for burst fire message

View file

@ -9,8 +9,8 @@
#include <yapb.h> #include <yapb.h>
ConVar yb_whose_your_daddy ("yb_whose_your_daddy", "0"); ConVar yb_whose_your_daddy ("yb_whose_your_daddy", "0", "Enables or disables extra hard difficulty for bots.");
ConVar yb_debug_heuristic_type ("yb_debug_heuristic_type", "4"); ConVar yb_debug_heuristic_type ("yb_debug_heuristic_type", "4", "Selects the heuristic function mode. For debug purposes only.", true, 0.0f, 4.0f);
int Bot::findBestGoal () { int Bot::findBestGoal () {
@ -20,7 +20,7 @@ int Bot::findBestGoal () {
game.searchEntities ("classname", "weaponbox", [&] (edict_t *ent) { game.searchEntities ("classname", "weaponbox", [&] (edict_t *ent) {
if (strcmp (STRING (ent->v.model), "models/w_backpack.mdl") == 0) { if (strcmp (STRING (ent->v.model), "models/w_backpack.mdl") == 0) {
result = graph.getNearest (game.getAbsPos (ent)); result = graph.getNearest (game.getEntityWorldOrigin (ent));
if (graph.exists (result)) { if (graph.exists (result)) {
return EntitySearchResult::Break; return EntitySearchResult::Break;
@ -100,7 +100,7 @@ int Bot::findBestGoal () {
} }
} }
else if (game.mapIs (MapFlags::Demolition) && m_team == Team::CT) { else if (game.mapIs (MapFlags::Demolition) && m_team == Team::CT) {
if (bots.isBombPlanted () && getCurrentTaskId () != Task::EscapeFromBomb && !graph.getBombPos ().empty ()) { if (bots.isBombPlanted () && getCurrentTaskId () != Task::EscapeFromBomb && !graph.getBombOrigin ().empty ()) {
if (bots.hasBombSay (BombPlantedSay::ChatSay)) { if (bots.hasBombSay (BombPlantedSay::ChatSay)) {
pushChatMessage (Chat::Plant); pushChatMessage (Chat::Plant);
@ -118,7 +118,7 @@ int Bot::findBestGoal () {
else if (game.mapIs (MapFlags::Demolition) && m_team == Team::Terrorist && bots.getRoundStartTime () + 10.0f < game.time ()) { else if (game.mapIs (MapFlags::Demolition) && m_team == Team::Terrorist && bots.getRoundStartTime () + 10.0f < game.time ()) {
// send some terrorists to guard planted bomb // send some terrorists to guard planted bomb
if (!m_defendedBomb && bots.isBombPlanted () && getCurrentTaskId () != Task::EscapeFromBomb && getBombTimeleft () >= 15.0) { if (!m_defendedBomb && bots.isBombPlanted () && getCurrentTaskId () != Task::EscapeFromBomb && getBombTimeleft () >= 15.0) {
return m_chosenGoalIndex = graph.getNearest (graph.getBombPos ()); return m_chosenGoalIndex = graph.getNearest (graph.getBombOrigin ());
} }
} }
@ -710,7 +710,7 @@ bool Bot::updateNavigation () {
if (!game.isNullEntity (tr.pHit) && game.isNullEntity (m_liftEntity) && strncmp (STRING (tr.pHit->v.classname), "func_door", 9) == 0) { if (!game.isNullEntity (tr.pHit) && game.isNullEntity (m_liftEntity) && strncmp (STRING (tr.pHit->v.classname), "func_door", 9) == 0) {
// if the door is near enough... // if the door is near enough...
if ((game.getAbsPos (tr.pHit) - pev->origin).lengthSq () < 2500.0f) { if ((game.getEntityWorldOrigin (tr.pHit) - pev->origin).lengthSq () < 2500.0f) {
ignoreCollision (); // don't consider being stuck ignoreCollision (); // don't consider being stuck
if (rg.chance (50)) { if (rg.chance (50)) {
@ -859,6 +859,8 @@ bool Bot::updateLiftHandling () {
m_navTimeset = game.time (); m_navTimeset = game.time ();
m_aimFlags |= AimFlags::Nav; m_aimFlags |= AimFlags::Nav;
pev->button &= ~(IN_FORWARD | IN_BACK | IN_MOVELEFT | IN_MOVERIGHT);
ignoreCollision (); ignoreCollision ();
}; };
@ -1783,7 +1785,7 @@ int Bot::findBombNode () {
auto &goals = graph.m_goalPoints; auto &goals = graph.m_goalPoints;
auto bomb = graph.getBombPos (); auto bomb = graph.getBombOrigin ();
auto audible = isBombAudible (); auto audible = isBombAudible ();
if (!audible.empty ()) { if (!audible.empty ()) {
@ -2326,8 +2328,8 @@ bool Bot::cantMoveForward (const Vector &normal, TraceResult *tr) {
} }
// trace from the left waist to the right forward waist pos // trace from the left waist to the right forward waist pos
src = pev->origin + Vector (0.0f, 0.0f, -17.0f) + right * 16.0f; src = pev->origin + Vector (0.0f, 0.0f, -24.0f) + right * 16.0f;
forward = pev->origin + Vector (0.0f, 0.0f, -17.0f) - right * -16.0f + normal * 24.0f; forward = pev->origin + Vector (0.0f, 0.0f, -24.0f) - right * -16.0f + normal * 24.0f;
game.testLine (src, forward, TraceIgnore::Monsters, ent (), tr); game.testLine (src, forward, TraceIgnore::Monsters, ent (), tr);
@ -2834,7 +2836,7 @@ void Bot::updateBodyAngles () {
void Bot::updateLookAngles () { void Bot::updateLookAngles () {
const float delta = cr::clamp (game.time () - m_lookUpdateTime, cr::kFloatEqualEpsilon, 0.03333f); const float delta = cr::clamp (game.time () - m_lookUpdateTime, cr::kFloatEqualEpsilon, 1.0f / 30.0f);
m_lookUpdateTime = game.time (); m_lookUpdateTime = game.time ();
// adjust all body and view angles to face an absolute vector // adjust all body and view angles to face an absolute vector
@ -2994,7 +2996,7 @@ int Bot::getNearestToPlantedBomb () {
// search the bomb on the map // search the bomb on the map
game.searchEntities ("classname", "grenade", [&result] (edict_t *ent) { game.searchEntities ("classname", "grenade", [&result] (edict_t *ent) {
if (strcmp (STRING (ent->v.model) + 9, "c4.mdl") == 0) { if (strcmp (STRING (ent->v.model) + 9, "c4.mdl") == 0) {
result = graph.getNearest (game.getAbsPos (ent)); result = graph.getNearest (game.getEntityWorldOrigin (ent));
if (graph.exists (result)) { if (graph.exists (result)) {
return EntitySearchResult::Break; return EntitySearchResult::Break;
@ -3053,7 +3055,7 @@ edict_t *Bot::lookupButton (const char *targetName) {
// find the nearest button which can open our target // find the nearest button which can open our target
game.searchEntities ("target", targetName, [&] (edict_t *ent) { game.searchEntities ("target", targetName, [&] (edict_t *ent) {
const Vector &pos = game.getAbsPos (ent); const Vector &pos = game.getEntityWorldOrigin (ent);
// check if this place safe // check if this place safe
if (!isDeadlyMove (pos)) { if (!isDeadlyMove (pos)) {

View file

@ -8,8 +8,8 @@
#include <yapb.h> #include <yapb.h>
ConVar yb_display_welcome_text ("yb_display_welcome_text", "1"); ConVar yb_display_welcome_text ("yb_display_welcome_text", "1", "Enables or disables showing welcome message to host entity on game start.");
ConVar yb_enable_query_hook ("yb_enable_query_hook", "1"); ConVar yb_enable_query_hook ("yb_enable_query_hook", "1", "Enables or disabled fake server queries response, that shows bots as real players in server browser.");
BotUtils::BotUtils () { BotUtils::BotUtils () {
m_needToSendWelcome = false; m_needToSendWelcome = false;
@ -329,7 +329,7 @@ void BotUtils::listenNoise (edict_t *ent, const String &sample, float volume) {
if (game.isNullEntity (ent) || sample.empty ()) { if (game.isNullEntity (ent) || sample.empty ()) {
return; return;
} }
const Vector &origin = game.getAbsPos (ent); const Vector &origin = game.getEntityWorldOrigin (ent);
// something wrong with sound... // something wrong with sound...
if (origin.empty ()) { if (origin.empty ()) {
@ -366,9 +366,9 @@ void BotUtils::listenNoise (edict_t *ent, const String &sample, float volume) {
// update noise stats // update noise stats
auto registerNoise = [&origin, &client, &volume] (float distance, float lasting) { auto registerNoise = [&origin, &client, &volume] (float distance, float lasting) {
client->hearingDistance = distance * volume; client->noise.dist = distance * volume;
client->timeSoundLasting = game.time () + lasting; client->noise.last = game.time () + lasting;
client->sound = origin; client->noise.pos = origin;
}; };
// client wasn't found // client wasn't found
@ -398,7 +398,7 @@ void BotUtils::listenNoise (edict_t *ent, const String &sample, float volume) {
// ct used hostage? // ct used hostage?
else if (noise & Noise::Hostage) { else if (noise & Noise::Hostage) {
registerNoise (1024.0f, 5.00f); registerNoise (1024.0f, 5.00);
} }
// broke something? // broke something?
@ -420,31 +420,33 @@ void BotUtils::simulateNoise (int playerIndex) {
return; // reliability check return; // reliability check
} }
Client &client = m_clients[playerIndex]; Client &client = m_clients[playerIndex];
ClientNoise noise {};
float hearDistance = 0.0f;
float timeSound = 0.0f;
auto buttons = client.ent->v.button | client.ent->v.oldbuttons; auto buttons = client.ent->v.button | client.ent->v.oldbuttons;
if (buttons & IN_ATTACK) // pressed attack button? // pressed attack button?
{ if (buttons & IN_ATTACK) {
hearDistance = 2048.0f; noise.dist = 2048.0f;
timeSound = game.time () + 0.3f; noise.last = game.time () + 0.3f;
} }
else if (buttons & IN_USE) // pressed used button?
{ // pressed used button?
hearDistance = 512.0f; else if (buttons & IN_USE) {
timeSound = game.time () + 0.5f; noise.dist = 512.0f;
noise.last = game.time () + 0.5f;
} }
else if (buttons & IN_RELOAD) // pressed reload button?
{ // pressed reload button?
hearDistance = 512.0f; else if (buttons & IN_RELOAD) {
timeSound = game.time () + 0.5f; noise.dist = 512.0f;
noise.last = game.time () + 0.5f;
} }
else if (client.ent->v.movetype == MOVETYPE_FLY) // uses ladder?
{ // uses ladder?
else if (client.ent->v.movetype == MOVETYPE_FLY) {
if (cr::abs (client.ent->v.velocity.z) > 50.0f) { if (cr::abs (client.ent->v.velocity.z) > 50.0f) {
hearDistance = 1024.0f; noise.dist = 1024.0f;
timeSound = game.time () + 0.3f; noise.last = game.time () + 0.3f;
} }
} }
else { else {
@ -452,29 +454,29 @@ void BotUtils::simulateNoise (int playerIndex) {
if (mp_footsteps.bool_ ()) { if (mp_footsteps.bool_ ()) {
// moves fast enough? // moves fast enough?
hearDistance = 1280.0f * (client.ent->v.velocity.length2d () / 260.0f); noise.dist = 1280.0f * (client.ent->v.velocity.length2d () / 260.0f);
timeSound = game.time () + 0.3f; noise.last = game.time () + 0.3f;
} }
} }
if (hearDistance <= 0.0) { if (noise.dist <= 0.0) {
return; // didn't issue sound? return; // didn't issue sound?
} }
// some sound already associated // some sound already associated
if (client.timeSoundLasting > game.time ()) { if (client.noise.last > game.time ()) {
if (client.hearingDistance <= hearDistance) { if (client.noise.dist <= noise.dist) {
// override it with new // override it with new
client.hearingDistance = hearDistance; client.noise.dist = noise.dist;
client.timeSoundLasting = timeSound; client.noise.last = noise.last;
client.sound = client.ent->v.origin; client.noise.pos = client.ent->v.origin;
} }
} }
else { else if (!cr::fzero (noise.last)) {
// just remember it // just remember it
client.hearingDistance = hearDistance; client.noise.dist = noise.dist;
client.timeSoundLasting = timeSound; client.noise.last = noise.last;
client.sound = client.ent->v.origin; client.noise.pos = client.ent->v.origin;
} }
} }
@ -615,7 +617,7 @@ void BotUtils::installSendTo () {
} }
// enable only on modern games // enable only on modern games
if (game.is (GameFlags::Modern) && (plat.isLinux || plat.isWindows) && !plat.isAndroid && !m_sendToHook.enabled ()) { if (game.is (GameFlags::Modern) && (plat.isLinux || plat.isWindows) && !plat.isArm && !m_sendToHook.enabled ()) {
m_sendToHook.patch (reinterpret_cast <void *> (&sendto), reinterpret_cast <void *> (&BotUtils::sendTo)); m_sendToHook.patch (reinterpret_cast <void *> (&sendto), reinterpret_cast <void *> (&BotUtils::sendTo));
} }
} }