diff --git a/include/control.h b/include/control.h index 47dc10d..34ffb5c 100644 --- a/include/control.h +++ b/include/control.h @@ -73,6 +73,7 @@ private: int cmdNodeMenu (); int cmdMenu (); int cmdList (); + int cmdCvars (); int cmdNode (); int cmdNodeOn (); int cmdNodeOff (); diff --git a/include/crlib/cr-dict.h b/include/crlib/cr-dict.h index 3f9411f..cf950fc 100644 --- a/include/crlib/cr-dict.h +++ b/include/crlib/cr-dict.h @@ -66,6 +66,9 @@ namespace detail { // basic dictionary template , size_t HashSize = 36> class Dictionary final : public DenyCopying { +private: + using DictBucket = detail::DictionaryBucket ; + public: enum : size_t { InvalidIndex = static_cast (-1) @@ -73,7 +76,7 @@ public: private: Array m_table; - Array > m_buckets; + Array m_buckets; H m_hasher; private: @@ -226,19 +229,19 @@ public: // for range-based loops public: - detail::DictionaryBucket *begin () { + DictBucket *begin () { return m_buckets.begin (); } - detail::DictionaryBucket *begin () const { + DictBucket *begin () const { return m_buckets.begin (); } - detail::DictionaryBucket *end () { + DictBucket *end () { return m_buckets.end (); } - detail::DictionaryBucket *end () const { + DictBucket *end () const { return m_buckets.end (); } }; diff --git a/include/crlib/cr-files.h b/include/crlib/cr-files.h index 9bf1d61..c373821 100644 --- a/include/crlib/cr-files.h +++ b/include/crlib/cr-files.h @@ -90,11 +90,11 @@ public: return fprintf (m_handle, fmt, cr::forward (args)...); } - bool puts (const String &buffer) { + bool puts (const char *buffer) { if (!*this) { return 0; } - if (fputs (buffer.chars (), m_handle) < 0) { + if (fputs (buffer, m_handle) < 0) { return false; } return true; diff --git a/include/crlib/cr-hook.h b/include/crlib/cr-hook.h index fd93da8..e078528 100644 --- a/include/crlib/cr-hook.h +++ b/include/crlib/cr-hook.h @@ -74,6 +74,9 @@ public: public: bool patch (void *address, void *replacement) { + if (plat.isArm) { + return false; + } uint8 *ptr = reinterpret_cast (address); while (*reinterpret_cast (ptr) == 0x25ff) { diff --git a/include/crlib/cr-platform.h b/include/crlib/cr-platform.h index 90c3066..d4a4be8 100644 --- a/include/crlib/cr-platform.h +++ b/include/crlib/cr-platform.h @@ -51,6 +51,13 @@ CR_NAMESPACE_BEGIN # define CR_ARCH_X86 #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) # define CR_HAS_SSE2 #endif @@ -85,6 +92,7 @@ struct Platform : public Singleton { bool isAndroid = false; bool isAndroidHardFP = false; bool isX64 = false; + bool isArm = false; Platform () { #if defined(CR_WINDOWS) @@ -94,22 +102,27 @@ struct Platform : public Singleton { #if defined(CR_ANDROID) isAndroid = true; -# if defined (CR_ANDROID_HARD_FP) +# if defined (CR_ANDROID_HARD_FP) isAndroidHardFP = true; -# endif +# endif #endif #if defined(CR_LINUX) isLinux = true; #endif -#if defined (CR_OSX) +#if defined(CR_OSX) isOSX = true; #endif -#if defined (CR_ARCH_X64) +#if defined(CR_ARCH_X64) || defined(CR_ARCH_ARM64) isX64 = true; #endif + +#if defined(CR_ARCH_ARM) + isArm = true; + isAndroid = true; +#endif } // helper platform-dependant functions diff --git a/include/crlib/cr-ulz.h b/include/crlib/cr-ulz.h index 5f3f007..94e1f5f 100644 --- a/include/crlib/cr-ulz.h +++ b/include/crlib/cr-ulz.h @@ -52,7 +52,7 @@ public: for (auto &htb : m_hashTable) { htb = EmptyHash; } - uint8 *op = out; + auto op = out; int32 anchor = 0; int32 cur = 0; @@ -99,9 +99,9 @@ public: } if (bestLength >= MinMatch && bestLength < maxMatch && (cur - anchor) != 6) { - const int32 next = cur + 1; - const int32 target = bestLength + 1; - const int32 limit = cr::max (next - WindowSize, EmptyHash); + const auto next = cur + 1; + const auto target = bestLength + 1; + const auto limit = cr::max (next - WindowSize, EmptyHash); int32 chainLength = MaxChain; int32 lookup = m_hashTable[hash32 (&in[next])]; @@ -128,11 +128,11 @@ public: } if (bestLength >= MinMatch) { - const int32 length = bestLength - MinMatch; - const int32 token = ((dist >> 12) & 16) + cr::min (length, 15); + const auto length = bestLength - MinMatch; + const auto token = ((dist >> 12) & 16) + cr::min (length, 15); if (anchor != cur) { - const int32 run = cur - anchor; + const auto run = cur - anchor; if (run >= 7) { add (op, (7 << 5) + token); @@ -155,7 +155,7 @@ public: op += 2; while (bestLength-- != 0) { - const uint32 hash = hash32 (&in[cur]); + const auto hash = hash32 (&in[cur]); m_prevTable[cur & WindowMask] = m_hashTable[hash]; m_hashTable[hash] = cur++; @@ -163,7 +163,7 @@ public: anchor = cur; } else { - const uint32 hash = hash32 (&in[cur]); + const auto hash = hash32 (&in[cur]); m_prevTable[cur & WindowMask] = m_hashTable[hash]; m_hashTable[hash] = cur++; @@ -171,7 +171,7 @@ public: } if (anchor != cur) { - const int32 run = cur - anchor; + const auto run = cur - anchor; if (run >= 7) { add (op, 7 << 5); @@ -187,17 +187,17 @@ public: } int32 uncompress (uint8 *in, int32 inputLength, uint8 *out, int32 outLength) { - uint8 *op = out; - uint8 *ip = in; + auto op = out; + auto ip = in; - const uint8 *opEnd = op + outLength; - const uint8 *ipEnd = ip + inputLength; + const auto opEnd = op + outLength; + const auto ipEnd = ip + inputLength; while (ip < ipEnd) { - const int32 token = *ip++; + const auto token = *ip++; if (token >= 32) { - int32 run = token >> 5; + auto run = token >> 5; if (run == 7) { run += decode (ip); @@ -215,7 +215,7 @@ public: break; } } - int32 length = (token & 15) + MinMatch; + auto length = (token & 15) + MinMatch; if (length == (15 + MinMatch)) { length += decode (ip); @@ -224,10 +224,10 @@ public: if ((opEnd - op) < length) { return UncompressFailure; } - const int32 dist = ((token & 16) << 12) + load16 (ip); + const auto dist = ((token & 16) << 12) + load16 (ip); ip += 2; - uint8 *cp = op - dist; + auto cp = op - dist; if ((op - out) < dist) { return UncompressFailure; @@ -237,8 +237,7 @@ public: copy (op, cp, length); op += length; } - else - { + else { for (int32 i = 0; i < 4; ++i) { *op++ = *cp++; } @@ -252,27 +251,33 @@ public: } private: - inline uint16 load16 (void *ptr) { - return *reinterpret_cast (ptr); + uint16 load16 (void *ptr) { + uint16 ret; + memcpy (&ret, ptr, sizeof (uint16)); + + return ret; } - inline uint32 load32 (void *ptr) { - return *reinterpret_cast (ptr); + uint32 load32 (void *ptr) { + uint32 ret; + memcpy (&ret, ptr, sizeof (uint32)); + + return ret; } - inline void store16 (void *ptr, int32 val) { - *reinterpret_cast (ptr) = static_cast (val); + void store16 (void *ptr, int32 val) { + memcpy (ptr, &val, sizeof (uint16)); } - inline void copy64 (void *dst, void *src) { - *reinterpret_cast (dst) = *reinterpret_cast (src); + void copy64 (void *dst, void *src) { + memcpy (dst, src, sizeof (uint64)); } - inline uint32 hash32 (void *ptr) { - return (load32 (ptr) * 0x9E3779B9) >> (32 - HashBits); + uint32 hash32 (void *ptr) { + 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); 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 (val); } - inline void encode (uint8 *&ptr, uint32 val) { + void encode (uint8 *&ptr, uint32 val) { while (val >= 128) { val -= 128; @@ -294,7 +299,7 @@ private: *ptr++ = static_cast (val); } - inline uint32 decode (uint8 *&ptr) { + uint32 decode (uint8 *&ptr) { uint32 val = 0; for (int32 i = 0; i <= 21; i += 7) { @@ -309,6 +314,4 @@ private: } }; - - CR_NAMESPACE_END \ No newline at end of file diff --git a/include/engine.h b/include/engine.h index bcfc9ed..364c020 100644 --- a/include/engine.h +++ b/include/engine.h @@ -74,6 +74,9 @@ struct VarPair { bool missing; const char *regval; class ConVar *self; + String info; + float initial, min, max; + bool bounded; }; // entity prototype @@ -95,6 +98,7 @@ private: edict_t *m_startEntity; edict_t *m_localEntity; + Array m_breakables; SmallArray m_cvars; SharedLibrary m_gameLib; @@ -137,10 +141,10 @@ public: const char *getMapName (); // get the "any" entity origin - Vector getAbsPos (edict_t *ent); + Vector getEntityWorldOrigin (edict_t *ent); // registers a server command - void registerEngineCommand (const char *command, void func_ ()); + void registerEngineCommand (const char *command, void func ()); // play's sound to client void playSound (edict_t *ent, const char *sound); @@ -149,7 +153,10 @@ public: void prepareBotArgs (edict_t *ent, String str); // 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 void registerCvars (bool gameVars = false); @@ -175,6 +182,9 @@ public: // search entities in sphere 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: // get the current time on server @@ -294,6 +304,21 @@ public: return m_gameLib; } + // get registered cvars list + const SmallArray &getCvars () { + return m_cvars; + } + + // check if map has breakables + const Array &getBreakables () { + return m_breakables; + } + + // map has breakables ? + bool hasBreakables () const { + return !m_breakables.empty (); + } + // helper to sending the client message void sendClientMessage (bool console, edict_t *ent, const char *message); @@ -332,33 +357,41 @@ public: }; // simplify access for console variables -class ConVar final { +class ConVar final : public DenyCopying { public: - cvar_t *eptr; + cvar_t *ptr; public: - ConVar (const char *name, const char *initval, Var type = Var::NoServer, bool regMissing = false, const char *regVal = nullptr) : eptr (nullptr) { - Game::get ().addNewCvar (name, initval, type, regMissing, regVal, this); + ConVar () = delete; + ~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 { - return eptr->value > 0.0f; + return ptr->value > 0.0f; } int int_ () const { - return static_cast (eptr->value); + return static_cast (ptr->value); } float float_ () const { - return eptr->value; + return ptr->value; } const char *str () const { - return eptr->string; + return ptr->string; } void set (float val) { - engfuncs.pfnCVarSetFloat (eptr->name, val); + engfuncs.pfnCVarSetFloat (ptr->name, val); } void set (int val) { @@ -366,7 +399,7 @@ public: } void set (const char *val) { - engfuncs.pfnCvar_DirectSet (eptr, const_cast (val)); + engfuncs.pfnCvar_DirectSet (ptr, const_cast (val)); } }; @@ -541,7 +574,7 @@ public: }; // for android -#if defined (CR_ANDROID) +#if defined (CR_ANDROID) && defined(CR_ARCH_ARM) extern "C" void player (entvars_t *pev); #endif @@ -588,7 +621,7 @@ public: public: void initialize () { - if (plat.isAndroid) { + if (plat.isArm) { return; } m_dlsym.patch (reinterpret_cast (&LookupSymbol), reinterpret_cast (&DynamicEntityLink::replacement)); @@ -596,7 +629,7 @@ public: } EntityFunction getPlayerFunction () { -#if defined (CR_ANDROID) +#if defined (CR_ANDROID) && defined(CR_ARCH_ARM) return player; #else return reinterpret_cast (search (Game::get ().lib ().handle (), "player")); diff --git a/include/graph.h b/include/graph.h index 34191f0..4e0a0c8 100644 --- a/include/graph.h +++ b/include/graph.h @@ -259,7 +259,7 @@ private: Vector m_learnVelocity; Vector m_learnPosition; - Vector m_bombPos; + Vector m_bombOrigin; Vector m_lastNode; IntArray m_terrorPoints; @@ -344,7 +344,7 @@ public: void initBuckets (); void addToBucket (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 unassignPath (int from, int to); void setDangerValue (int team, int start, int goal, int value); @@ -393,8 +393,8 @@ public: m_autoPathDistance = distance; } - const Vector &getBombPos () const { - return m_bombPos; + const Vector &getBombOrigin () const { + return m_bombOrigin; } // access paths diff --git a/include/manager.h b/include/manager.h index 5c5f0da..4a7ce00 100644 --- a/include/manager.h +++ b/include/manager.h @@ -118,11 +118,11 @@ public: bool kickRandom (bool decQuota = true, Team fromTeam = Team::Unassigned); public: - Array &searchActiveGrenades () { + const Array &searchActiveGrenades () { return m_activeGrenades; } - Array &searchIntrestingEntities () { + const Array &searchIntrestingEntities () { return m_intrestingEntities; } diff --git a/include/yapb.h b/include/yapb.h index e928b5b..97a35f8 100644 --- a/include/yapb.h +++ b/include/yapb.h @@ -268,13 +268,13 @@ CR_DECLARE_SCOPED_ENUM (Weapon, // buy counts CR_DECLARE_SCOPED_ENUM (BuyState, PrimaryWeapon = 0, - ArmorVestHelm , - SecondaryWeapon, - Grenades, - DefusalKit, - Ammo, - NightVision, - Done +ArmorVestHelm, +SecondaryWeapon, +Grenades, +DefusalKit, +Ammo, +NightVision, +Done ) // economics limits @@ -284,7 +284,7 @@ CR_DECLARE_SCOPED_ENUM (EcoLimit, SmgTEGreater, ShotgunGreater, ShotgunLess, - HeavyGreater , + HeavyGreater, HeavyLess, ProstockNormal, ProstockRusher, @@ -513,11 +513,17 @@ public: { } }; +// clients noise +struct ClientNoise { + Vector pos; + float dist; + float last; +}; + // array of clients struct struct Client { edict_t *ent; // pointer to actual edict Vector origin; // position in the world - Vector sound; // position sound was played int team; // bot team int team2; // real bot team in free for all mode (csdm) int flags; // client flags @@ -526,9 +532,8 @@ struct Client { int ping; // when bot latency is enabled, client ping stored here int iconFlags[kGameMaxPlayers]; // flag holding 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 ? + ClientNoise noise; }; // define chatting collection structure @@ -733,7 +738,6 @@ private: bool isOccupiedNode (int index); bool seesItem (const Vector &dest, const char *itemName); bool lastEnemyShootable (); - bool isShootableBreakable (edict_t *ent); bool rateGroundWeapon (edict_t *ent); bool reactOnEnemy (); bool selectBestNextNode (); @@ -975,6 +979,7 @@ public: void showChaterIcon (bool show); void clearSearchNodes (); void checkBreakable (edict_t *touch); + void checkBreablesAround (); void startTask (Task id, float desire, int data, float time, bool resume); void clearTask (Task id); void filterTasks (); diff --git a/source/android.cpp b/source/android.cpp index ff55027..7431e32 100644 --- a/source/android.cpp +++ b/source/android.cpp @@ -10,7 +10,7 @@ #include // 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) { if (!addr) { addr = game.lib ().resolve (name); diff --git a/source/basecode.cpp b/source/basecode.cpp index 8aa14cd..36de74f 100644 --- a/source/basecode.cpp +++ b/source/basecode.cpp @@ -9,25 +9,26 @@ #include -ConVar yb_debug ("yb_debug", "0"); -ConVar yb_debug_goal ("yb_debug_goal", "-1"); -ConVar yb_user_follow_percent ("yb_user_follow_percent", "20"); -ConVar yb_user_max_followers ("yb_user_max_followers", "1"); +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", "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", "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", "Specifies how many bots can follow a single user.", true, 0.0f, static_cast (kGameMaxPlayers / 2)); -ConVar yb_jasonmode ("yb_jasonmode", "0"); -ConVar yb_radio_mode ("yb_radio_mode", "2"); -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_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", "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_tkpunish ("yb_tkpunish", "1"); -ConVar yb_freeze_bots ("yb_freeze_bots", "0"); -ConVar yb_spraypaints ("yb_spraypaints", "1"); -ConVar yb_botbuy ("yb_botbuy", "1"); +ConVar yb_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_walking_allowed ("yb_walking_allowed", "1", "Sepcifies whether bots able to use 'shift' if they thinks that enemy is near."); +ConVar yb_camping_allowed ("yb_camping_allowed", "1", "Allows or disallows bots to camp. Doesn't affects bomb/hostage defending tasks"); -ConVar yb_chatter_path ("yb_chatter_path", "sound/radio/bot"); -ConVar yb_restricted_weapons ("yb_restricted_weapons", ""); -ConVar yb_best_weapon_picker_type ("yb_best_weapon_picker_type", "0"); +ConVar yb_tkpunish ("yb_tkpunish", "1", "Allows or disallows bots to take revenge of teamkillers / team attacks."); +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_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 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) { // don't look at flash bang 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_preventFlashing = game.time () + rg.float_ (1.0f, 2.0f); @@ -385,8 +386,7 @@ void Bot::avoidGrenades () { } void Bot::checkBreakable (edict_t *touch) { - - if (!isShootableBreakable (touch)) { + if (!game.isShootableBreakable (touch)) { return; } m_breakableEntity = lookupBreakable (); @@ -395,10 +395,36 @@ void Bot::checkBreakable (edict_t *touch) { return; } m_campButtons = pev->button & IN_DUCK; - 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 () { // 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); 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! - if (isShootableBreakable (ent)) { - m_breakableOrigin = game.getAbsPos (ent); + if (game.isShootableBreakable (ent)) { + m_breakableOrigin = game.getEntityWorldOrigin (ent); return ent; } } game.testLine (getEyesPos (), getEyesPos () + (m_destOrigin - getEyesPos ()).normalize () * 72.0f, TraceIgnore::None, ent (), &tr); if (tr.flFraction != 1.0f) { - edict_t *ent = tr.pHit; + auto ent = tr.pHit; - if (isShootableBreakable (ent)) { - m_breakableOrigin = game.getAbsPos (ent); + if (game.isShootableBreakable (ent)) { + m_breakableOrigin = game.getEntityWorldOrigin (ent); return ent; } } m_breakableEntity = nullptr; - m_breakableOrigin= nullptr; + m_breakableOrigin = nullptr; return nullptr; } @@ -459,14 +485,14 @@ void Bot::updatePickups () { } auto &intresting = bots.searchIntrestingEntities (); - const float radius = cr::square (500.0f); + const float radius = cr::square (320.0f); if (!game.isNullEntity (m_pickupItem)) { bool itemExists = false; auto pickupItem = m_pickupItem; for (auto &ent : intresting) { - const Vector &origin = game.getAbsPos (ent); + const Vector &origin = game.getEntityWorldOrigin (ent); // too far from us ? if ((pev->origin - origin).lengthSq () > radius) { @@ -505,7 +531,7 @@ void Bot::updatePickups () { if (ent == m_itemIgnore) { 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 ? if ((pev->origin - origin).lengthSq () > radius) { @@ -714,7 +740,7 @@ void Bot::updatePickups () { allowPickup = !isBombDefusing (origin) || m_hasProgressBar; pickupType = Pickup::PlantedC4; - + if (!m_defendedBomb && !allowPickup) { m_defendedBomb = true; @@ -1784,12 +1810,12 @@ void Bot::setConditions () { // check if our current enemy is still valid if (!game.isNullEntity (m_lastEnemy)) { if (!util.isAlive (m_lastEnemy) && m_shootAtDeadTime < game.time ()) { - m_lastEnemyOrigin= nullptr; + m_lastEnemyOrigin = nullptr; m_lastEnemy = nullptr; } } else { - m_lastEnemyOrigin= nullptr; + m_lastEnemyOrigin = nullptr; m_lastEnemy = nullptr; } @@ -1857,7 +1883,7 @@ void Bot::filterTasks () { filter[Task::PickupItem].desire = 50.0f; // always pickup button } 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) { distance = 50.0f; @@ -2388,7 +2414,7 @@ void Bot::checkRadioQueue () { clearSearchNodes (); - m_position = graph.getBombPos (); + m_position = graph.getBombOrigin (); startTask (Task::MoveToPosition, TaskPri::MoveToPosition, kInvalidNodeIndex, 0.0f, true); pushRadioMessage (Radio::RogerThat); @@ -2837,7 +2863,7 @@ void Bot::frame () { m_numEnemiesLeft = numEnemiesNear (pev->origin, kInfiniteDistance); 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)) { m_itemIgnore = nullptr; @@ -2846,8 +2872,10 @@ void Bot::frame () { clearTask (getCurrentTaskId ()); } } + checkSpawnConditions (); checkForChat (); + checkBreablesAround (); if (game.is (GameFlags::HasBotVoice)) { showChaterIcon (false); // end voice feedback @@ -2867,7 +2895,7 @@ void Bot::update () { m_moveSpeed = 0.0f; m_strafeSpeed = 0.0f; - m_moveAngles= nullptr; + m_moveAngles = nullptr; m_canChooseAimDirection = true; m_notKilled = util.isAlive (ent ()); @@ -3234,7 +3262,7 @@ void Bot::huntEnemy_ () { completeTask (); m_prevGoalIndex = kInvalidNodeIndex; - m_lastEnemyOrigin= nullptr; + m_lastEnemyOrigin = nullptr; } // do we need to calculate a new path? @@ -3455,7 +3483,7 @@ void Bot::camp_ () { m_checkTerrain = 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; completeTask (); } @@ -3625,7 +3653,7 @@ void Bot::moveToPos_ () { completeTask (); // we're done m_prevGoalIndex = kInvalidNodeIndex; - m_position= nullptr; + m_position = nullptr; } // didn't choose goal waypoint yet? @@ -3720,17 +3748,12 @@ void Bot::bombDefuse_ () { } 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; // exception: bomb has been defused - if (bombPos.empty ()) { + if (bombPos.empty () || game.isNullEntity (m_pickupItem)) { defuseError = true; if (m_numFriendsLeft != 0 && rg.chance (50)) { @@ -3771,8 +3794,8 @@ void Bot::bombDefuse_ () { m_checkTerrain = true; m_moveToGoal = true; - m_destOrigin= nullptr; - m_entity= nullptr; + m_destOrigin = nullptr; + m_entity = nullptr; m_pickupItem = nullptr; m_pickupType = Pickup::None; @@ -4260,10 +4283,10 @@ void Bot::escapeFromBomb_ () { clearSearchNodes (); 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) { - if ((graph[i].origin - graph.getBombPos ()).length () < safeRadius || isOccupiedNode (i)) { + if ((graph[i].origin - graph.getBombOrigin ()).length () < safeRadius || isOccupiedNode (i)) { continue; } int pathDistance = graph.getPathDist (m_currentNodeIndex, i); @@ -4296,8 +4319,8 @@ void Bot::escapeFromBomb_ () { void Bot::shootBreakable_ () { m_aimFlags |= AimFlags::Override; - // Breakable destroyed? - if (game.isNullEntity (lookupBreakable ())) { + // breakable destroyed? + if (!game.isShootableBreakable (m_breakableEntity)) { completeTask (); return; } @@ -4306,12 +4329,10 @@ void Bot::shootBreakable_ () { m_checkTerrain = false; m_moveToGoal = false; m_navTimeset = game.time (); - - const Vector &src = m_breakableOrigin; - m_camp = src; + m_camp = m_breakableOrigin; // 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_strafeSpeed = 0.0f; @@ -4324,6 +4345,8 @@ void Bot::shootBreakable_ () { else { m_checkTerrain = true; m_moveToGoal = true; + + completeTask (); } } @@ -4334,7 +4357,7 @@ void Bot::pickupItem_ () { return; } - const Vector &dest = game.getAbsPos (m_pickupItem); + const Vector &dest = game.getEntityWorldOrigin (m_pickupItem); m_destOrigin = dest; m_entity = dest; @@ -5295,7 +5318,7 @@ void Bot::resetDoubleJump () { m_doubleJumpEntity = nullptr; m_duckForJump = 0.0f; - m_doubleJumpOrigin= nullptr; + m_doubleJumpOrigin = nullptr; m_travelStartIndex = kInvalidNodeIndex; m_jumpReady = false; } @@ -5459,9 +5482,9 @@ Vector Bot::isBombAudible () { } 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 desiredRadius = 768.0f; @@ -5592,7 +5615,7 @@ bool Bot::isOutOfBombTimer () { if (timeLeft > 13.0f) { return false; } - const Vector &bombOrigin = graph.getBombPos (); + const Vector &bombOrigin = graph.getBombOrigin (); // 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)) { @@ -5634,16 +5657,16 @@ void Bot::updateHearing () { for (int i = 0; i < game.maxClients (); ++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; } if (!game.checkVisibility (client.ent, set)) { 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; } @@ -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) { // this function is gets called when bot enters a buyzone, to allow bot to buy some stuff diff --git a/source/chatlib.cpp b/source/chatlib.cpp index e4b0596..eb4bd6e 100644 --- a/source/chatlib.cpp +++ b/source/chatlib.cpp @@ -9,7 +9,7 @@ #include -ConVar yb_chat ("yb_chat", "1"); +ConVar yb_chat ("yb_chat", "1", "Enables or disables bots chat functionality."); void BotUtils::stripTags (String &line) { if (line.empty ()) { @@ -23,9 +23,9 @@ void BotUtils::stripTags (String &line) { const size_t end = line.find (tag.second, 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 ()); - break; + continue; } } } diff --git a/source/combat.cpp b/source/combat.cpp index 2dae748..6d7ed7b 100644 --- a/source/combat.cpp +++ b/source/combat.cpp @@ -9,9 +9,9 @@ #include -ConVar yb_shoots_thru_walls ("yb_shoots_thru_walls", "2"); -ConVar yb_ignore_enemies ("yb_ignore_enemies", "0"); -ConVar yb_check_enemy_rendering ("yb_check_enemy_rendering", "0"); +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", "Enables or disables searching world for enemies."); +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); @@ -218,7 +218,7 @@ bool Bot::lookupEnemies () { m_aimFlags |= AimFlags::LastEnemy; } m_enemyParts = Visibility::None; - m_enemyOrigin= nullptr; + m_enemyOrigin = nullptr; if (!game.isNullEntity (m_enemy)) { player = m_enemy; @@ -447,7 +447,7 @@ const Vector &Bot::getEnemyBodyOffset () { Vector aimPos = m_enemy->v.origin; 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 @@ -455,7 +455,7 @@ const Vector &Bot::getEnemyBodyOffset () { aimPos += getBodyOffsetError (distance); } 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 if (m_enemyParts & (Visibility::Head | Visibility::Body)) { @@ -506,10 +506,7 @@ float Bot::getEnemyBodyOffsetCorrection (float distance) { float result = -2.0f; - if (distance < kSprayDistance) { - return -16.0f; - } - else if (distance >= kDoubleSprayDistance) { + if (distance >= kDoubleSprayDistance) { if (sniper) { result = 0.18f; } @@ -523,7 +520,7 @@ float Bot::getEnemyBodyOffsetCorrection (float distance) { result = 1.5f; } else if (rifle) { - result = 1.0f; + result = -1.0f; } else if (m249) { 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 } 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 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); if (prop.ammo1 != -1 && prop.ammo1 < 32 && m_ammo[prop.ammo1] >= tab[selectIndex].minPrimaryAmmo) { + // available ammo found, reload weapon if (m_reloadState == Reload::None || m_reloadCheckTime > game.time ()) { m_isReloading = true; diff --git a/source/control.cpp b/source/control.cpp index 0ba0b90..9126df6 100644 --- a/source/control.cpp +++ b/source/control.cpp @@ -9,9 +9,9 @@ #include -ConVar yb_display_menu_text ("yb_display_menu_text", "1"); -ConVar yb_password ("yb_password", "", Var::Password); -ConVar yb_password_key ("yb_password_key", "_ybpw"); +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", "", "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", "The name of setinfo key used to store password to bot commands and menus", false); int BotControl::cmdAddBot () { enum args { alias = 1, difficulty, personality, team, model, name, max }; @@ -161,9 +161,25 @@ int BotControl::cmdWeaponMode () { } 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 (" compiled: %s %s by %s", __DATE__, __TIME__, PRODUCT_GIT_COMMIT_AUTHOR); - msg (" commit: %scommit/%s", PRODUCT_COMMENTS, PRODUCT_GIT_HASH); + msg (" compiled: %s %s by %s", __DATE__, __TIME__, author.chars ()); + + if (!hash.startsWith ("local")) { + msg (" commit: %scommit/%s", PRODUCT_COMMENTS, hash.chars ()); + } msg (" url: %s", PRODUCT_URL); return BotCommandResult::Handled; @@ -207,6 +223,84 @@ int BotControl::cmdList () { 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 (cvar.initial), static_cast (cvar.min), static_cast (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 () { enum args { root, alias, cmd, cmd2, max }; @@ -1698,7 +1792,8 @@ void BotControl::kickBotByMenu (int page) { for (int i = menuKey; i < page * 8; ++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)); 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 ("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 ("cvars", "cvars [save|cvar]", "Display all the cvars with their descriptions.", &BotControl::cmdCvars); // declare the menus createMenus (); diff --git a/source/engine.cpp b/source/engine.cpp index 2c99698..c03e64b 100644 --- a/source/engine.cpp +++ b/source/engine.cpp @@ -60,6 +60,9 @@ void Game::levelInitialize (edict_t *entities, int max) { m_spawnCount[Team::CT] = 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 for (int i = 0; i < max; ++i) { @@ -132,6 +135,9 @@ void Game::levelInitialize (edict_t *entities, int max) { else if (strncmp (classname, "func_button", 11) == 0) { 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 @@ -301,7 +307,7 @@ const char *Game::getMapName () { 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 // 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; } -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 - VarPair pair {}; + VarPair pair; - pair.reg.name = const_cast (variable); + pair.reg.name = const_cast (name); pair.reg.string = const_cast (value); - pair.missing = regMissing; - pair.regval = regVal; + pair.missing = missingAction; + 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 (atof (value)); + } + + auto eflags = FCVAR_EXTDLL; if (varType == Var::Normal) { - engineFlags |= FCVAR_SERVER; + eflags |= FCVAR_SERVER; } else if (varType == Var::ReadOnly) { - engineFlags |= FCVAR_SERVER | FCVAR_SPONLY | FCVAR_PRINTABLEONLY; + eflags |= FCVAR_SERVER | FCVAR_SPONLY | FCVAR_PRINTABLEONLY; } else if (varType == Var::Password) { - engineFlags |= FCVAR_PROTECTED; + eflags |= FCVAR_PROTECTED; } - pair.reg.flags = engineFlags; + pair.reg.flags = eflags; pair.self = self; pair.type = varType; 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) { // this function pushes all added global variables to engine registration @@ -500,9 +532,9 @@ void Game::registerCvars (bool gameVars) { cvar_t ® = var.reg; 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_; // fix metamod' memlocs not found @@ -513,22 +545,22 @@ void Game::registerCvars (bool gameVars) { else { engfuncs.pfnCVarRegister (&var.reg); } - self.eptr = engfuncs.pfnCVarGetPointer (reg.name); + self.ptr = engfuncs.pfnCVarGetPointer (reg.name); } } 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) { reg.string = const_cast (var.regval); reg.flags |= FCVAR_SERVER; } 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); } } @@ -644,7 +676,6 @@ bool Game::postload () { // register bot cvars game.registerCvars (); - // register server command(s) registerEngineCommand ("yapb", [] () { ctrl.handleEngineCommands (); @@ -718,7 +749,6 @@ bool Game::postload () { logger.fatal ("Unable to load gamedll \"%s\". Exiting... (gamedir: %s)", gamedll, getModName ()); } displayCSVersion (); - } else { bool binaryLoaded = loadCSBinary (); @@ -782,6 +812,9 @@ void Game::slowFrame () { // detect csdm detectDeathmatch (); + // check the cvar bounds + checkCvarsBounds (); + // display welcome message util.checkWelcome (); 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 () { // this function initializes lighting information... diff --git a/source/graph.cpp b/source/graph.cpp index 006f81d..d15a374 100644 --- a/source/graph.cpp +++ b/source/graph.cpp @@ -9,9 +9,8 @@ #include -ConVar yb_graph_subfolder ("yb_graph_subfolder", ""); -ConVar yb_graph_fixcamp ("yb_graph_fixcamp", "1"); -ConVar yb_graph_url ("yb_graph_url", "http://graph.yapb.ru"); +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_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); void BotGraph::initGraph () { // this function initialize the graph structures.. @@ -20,7 +19,7 @@ void BotGraph::initGraph () { m_editFlags = 0; m_learnVelocity= nullptr; - m_learnPosition= nullptr; + m_learnPosition = nullptr; m_lastNode= nullptr; m_pathDisplayTime = 0.0f; @@ -1679,7 +1678,7 @@ void BotGraph::saveOldFormat () { const char *BotGraph::getOldFormatGraphName (bool isMemoryFile) { 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)) { return buffer.chars (); @@ -2472,7 +2471,7 @@ void BotGraph::addBasic () { Vector up, down, front, back; 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 back = back - diff; // back @@ -2512,7 +2511,7 @@ void BotGraph::addBasic () { auto autoCreateForEntity = [] (int type, const char *entity) { 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) { graph.add (type, pos); @@ -2577,24 +2576,24 @@ const char *BotGraph::getDataDirectory (bool isMemoryFile) { 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 if (reset) { - m_bombPos= nullptr; + m_bombOrigin = nullptr; bots.setBombPlanted (false); return; } if (!pos.empty ()) { - m_bombPos = pos; + m_bombOrigin = pos; return; } game.searchEntities ("classname", "grenade", [&] (edict_t *ent) { 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::Continue; diff --git a/source/manager.cpp b/source/manager.cpp index bac85ad..954bd98 100644 --- a/source/manager.cpp +++ b/source/manager.cpp @@ -9,24 +9,25 @@ #include -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_mode ("yb_quota_mode", "normal"); -ConVar yb_quota_match ("yb_quota_match", "0"); -ConVar yb_think_fps ("yb_think_fps", "30.0"); +ConVar yb_quota ("yb_quota", "0", "Specifies the number bots to be added to the game.", true, 0.0f, static_cast (kGameMaxPlayers)); +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", "Number of players to match if yb_quota_mode set to 'match'", true, 0.0f, static_cast (kGameMaxPlayers)); +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_team ("yb_join_team", "any"); -ConVar yb_join_delay ("yb_join_delay", "5.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", "Forces all bots to join team specified here.", false); +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_difficulty ("yb_difficulty", "4"); +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", "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_latency ("yb_show_latency", "2"); -ConVar yb_language ("yb_language", "en"); -ConVar yb_ignore_cvars_on_changelevel ("yb_ignore_cvars_on_changelevel", "yb_quota,yb_autovacate"); +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", "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", "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_autoteambalance ("mp_autoteambalance", nullptr, Var::NoRegister); @@ -1024,8 +1025,9 @@ void Bot::newRound () { clearSearchNodes (); clearRoute (); - m_pathOrigin= nullptr; - m_destOrigin= nullptr; + m_pathOrigin = nullptr; + m_destOrigin = nullptr; + m_path = nullptr; m_currentTravelFlags = 0; m_desiredVelocity= nullptr; @@ -1095,7 +1097,7 @@ void Bot::newRound () { m_itemCheckTime = 0.0f; m_breakableEntity = nullptr; - m_breakableOrigin= nullptr; + m_breakableOrigin = nullptr; m_timeDoorOpen = 0.0f; resetCollision (); @@ -1104,7 +1106,7 @@ void Bot::newRound () { m_enemy = nullptr; m_lastVictim = nullptr; m_lastEnemy = nullptr; - m_lastEnemyOrigin= nullptr; + m_lastEnemyOrigin = nullptr; m_trackingEdict = nullptr; m_timeNextTracking = 0.0f; @@ -1129,8 +1131,8 @@ void Bot::newRound () { m_liftState = 0; m_aimLastError= nullptr; - m_position= nullptr; - m_liftTravelPos= nullptr; + m_position = nullptr; + m_liftTravelPos = nullptr; setIdealReactionTimers (true); @@ -1392,7 +1394,7 @@ void BotManager::notifyBombDefuse () { if (bot->m_team == Team::Terrorist && bot->m_notKilled && bot->getCurrentTaskId () != Task::MoveToPosition) { bot->clearSearchNodes (); - bot->m_position = graph.getBombPos (); + bot->m_position = graph.getBombOrigin (); bot->startTask (Task::MoveToPosition, TaskPri::MoveToPosition, kInvalidNodeIndex, 0.0f, true); } } @@ -1576,7 +1578,7 @@ void BotManager::initRound () { client.radio = 0; } - graph.setBombPos (true); + graph.setBombOrigin (true); graph.clearVisited (); m_bombSayStatus = BombPlantedSay::ChatSay | BombPlantedSay::Chatter; @@ -1687,8 +1689,8 @@ void BotConfig::loadMainConfig () { firstLoad = false; // android is abit hard to play, lower the difficulty by default - if (plat.isAndroid && yb_difficulty.int_ () > 2) { - yb_difficulty.set (2); + if (plat.isAndroid && yb_difficulty.int_ () > 3) { + yb_difficulty.set (3); } return; } @@ -1808,6 +1810,8 @@ void BotConfig::loadChatterConfig () { // chatter initialization 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 { String str; int code; @@ -1827,10 +1831,10 @@ void BotConfig::loadChatterConfig () { { "Radio_ReportTeam", Radio::ReportInTeam, kMaxChatterRepeatInteval }, { "Radio_Affirmative", Radio::RogerThat, kMaxChatterRepeatInteval }, { "Radio_EnemySpotted", Radio::EnemySpotted, 4.0f }, - { "Radio_NeedBackup", Radio::NeedBackup, kMaxChatterRepeatInteval }, + { "Radio_NeedBackup", Radio::NeedBackup, 5.0f }, { "Radio_SectorClear", Radio::SectorClear, 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_Negative", Radio::Negative, kMaxChatterRepeatInteval }, { "Radio_EnemyDown", Radio::EnemyDown, 10.0f }, @@ -1853,7 +1857,7 @@ void BotConfig::loadChatterConfig () { { "Chatter_WhereIsTheBomb", Chatter::WhereIsTheC4, kMaxChatterRepeatInteval }, { "Chatter_DefendingBombSite", Chatter::DefendingBombsite, kMaxChatterRepeatInteval }, { "Chatter_BarelyDefused", Chatter::BarelyDefused, kMaxChatterRepeatInteval }, - { "Chatter_NiceshotCommander", Chatter::NiceShotCommander, kMaxChatterRepeatInteval }, + { "Chatter_NiceshotCommander", Chatter::NiceShotCommander, 10.0f }, { "Chatter_ReportingIn", Chatter::ReportingIn, 10.0f }, { "Chatter_SpotTheBomber", Chatter::SpotTheBomber, 4.3f }, { "Chatter_VIPSpotted", Chatter::VIPSpotted, 5.3f }, diff --git a/source/message.cpp b/source/message.cpp index f1a3d3b..996031d 100644 --- a/source/message.cpp +++ b/source/message.cpp @@ -37,10 +37,13 @@ void MessageDispatcher::netMsgTextMsg () { return; } - // reset bomb position - if (game.mapIs (MapFlags::Demolition)) { - graph.setBombPos (true); - } + + // reset bomb position for all the bots + const auto resetBombPosition = [] () -> void { + if (game.mapIs (MapFlags::Demolition)) { + graph.setBombOrigin (true); + } + }; if (cached & TextMsgCache::Commencing) { util.setNeedForWelcome (true); @@ -48,6 +51,8 @@ void MessageDispatcher::netMsgTextMsg () { else if (cached & TextMsgCache::CounterWin) { bots.setLastWinner (Team::CT); // update last winner for economics dispatchChatterMessage (); + + resetBombPosition (); } else if (cached & TextMsgCache::RestartRound) { bots.updateTeamEconomics (Team::CT, true); @@ -60,10 +65,14 @@ void MessageDispatcher::netMsgTextMsg () { bot->m_moneyAmount = mp_startmoney.int_ (); return false; }); + + resetBombPosition (); } else if (cached & TextMsgCache::TerroristWin) { bots.setLastWinner (Team::Terrorist); // update last winner for economics dispatchChatterMessage (); + + resetBombPosition (); } else if ((cached & TextMsgCache::BombPlanted) && !bots.isBombPlanted ()) { bots.setBombPlanted (true); @@ -78,7 +87,7 @@ void MessageDispatcher::netMsgTextMsg () { } } } - graph.setBombPos (); + graph.setBombOrigin (); } // check for burst fire message diff --git a/source/navigate.cpp b/source/navigate.cpp index c9387ea..f063ef4 100644 --- a/source/navigate.cpp +++ b/source/navigate.cpp @@ -9,8 +9,8 @@ #include -ConVar yb_whose_your_daddy ("yb_whose_your_daddy", "0"); -ConVar yb_debug_heuristic_type ("yb_debug_heuristic_type", "4"); +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", "Selects the heuristic function mode. For debug purposes only.", true, 0.0f, 4.0f); int Bot::findBestGoal () { @@ -20,7 +20,7 @@ int Bot::findBestGoal () { game.searchEntities ("classname", "weaponbox", [&] (edict_t *ent) { 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)) { return EntitySearchResult::Break; @@ -100,7 +100,7 @@ int Bot::findBestGoal () { } } 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)) { 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 ()) { // send some terrorists to guard planted bomb 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 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 if (rg.chance (50)) { @@ -859,6 +859,8 @@ bool Bot::updateLiftHandling () { m_navTimeset = game.time (); m_aimFlags |= AimFlags::Nav; + pev->button &= ~(IN_FORWARD | IN_BACK | IN_MOVELEFT | IN_MOVERIGHT); + ignoreCollision (); }; @@ -1783,7 +1785,7 @@ int Bot::findBombNode () { auto &goals = graph.m_goalPoints; - auto bomb = graph.getBombPos (); + auto bomb = graph.getBombOrigin (); auto audible = isBombAudible (); 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 - src = pev->origin + Vector (0.0f, 0.0f, -17.0f) + right * 16.0f; - forward = pev->origin + Vector (0.0f, 0.0f, -17.0f) - right * -16.0f + normal * 24.0f; + src = pev->origin + Vector (0.0f, 0.0f, -24.0f) + right * 16.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); @@ -2834,7 +2836,7 @@ void Bot::updateBodyAngles () { 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 (); // adjust all body and view angles to face an absolute vector @@ -2994,7 +2996,7 @@ int Bot::getNearestToPlantedBomb () { // search the bomb on the map game.searchEntities ("classname", "grenade", [&result] (edict_t *ent) { 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)) { return EntitySearchResult::Break; @@ -3053,7 +3055,7 @@ edict_t *Bot::lookupButton (const char *targetName) { // find the nearest button which can open our target game.searchEntities ("target", targetName, [&] (edict_t *ent) { - const Vector &pos = game.getAbsPos (ent); + const Vector &pos = game.getEntityWorldOrigin (ent); // check if this place safe if (!isDeadlyMove (pos)) { diff --git a/source/support.cpp b/source/support.cpp index 3677645..783a833 100644 --- a/source/support.cpp +++ b/source/support.cpp @@ -8,8 +8,8 @@ #include -ConVar yb_display_welcome_text ("yb_display_welcome_text", "1"); -ConVar yb_enable_query_hook ("yb_enable_query_hook", "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", "Enables or disabled fake server queries response, that shows bots as real players in server browser."); BotUtils::BotUtils () { m_needToSendWelcome = false; @@ -329,7 +329,7 @@ void BotUtils::listenNoise (edict_t *ent, const String &sample, float volume) { if (game.isNullEntity (ent) || sample.empty ()) { return; } - const Vector &origin = game.getAbsPos (ent); + const Vector &origin = game.getEntityWorldOrigin (ent); // something wrong with sound... if (origin.empty ()) { @@ -366,9 +366,9 @@ void BotUtils::listenNoise (edict_t *ent, const String &sample, float volume) { // update noise stats auto registerNoise = [&origin, &client, &volume] (float distance, float lasting) { - client->hearingDistance = distance * volume; - client->timeSoundLasting = game.time () + lasting; - client->sound = origin; + client->noise.dist = distance * volume; + client->noise.last = game.time () + lasting; + client->noise.pos = origin; }; // client wasn't found @@ -398,7 +398,7 @@ void BotUtils::listenNoise (edict_t *ent, const String &sample, float volume) { // ct used hostage? else if (noise & Noise::Hostage) { - registerNoise (1024.0f, 5.00f); + registerNoise (1024.0f, 5.00); } // broke something? @@ -420,31 +420,33 @@ void BotUtils::simulateNoise (int playerIndex) { return; // reliability check } 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; - if (buttons & IN_ATTACK) // pressed attack button? - { - hearDistance = 2048.0f; - timeSound = game.time () + 0.3f; + // pressed attack button? + if (buttons & IN_ATTACK) { + noise.dist = 2048.0f; + noise.last = game.time () + 0.3f; } - else if (buttons & IN_USE) // pressed used button? - { - hearDistance = 512.0f; - timeSound = game.time () + 0.5f; + + // pressed used button? + else if (buttons & IN_USE) { + noise.dist = 512.0f; + noise.last = game.time () + 0.5f; } - else if (buttons & IN_RELOAD) // pressed reload button? - { - hearDistance = 512.0f; - timeSound = game.time () + 0.5f; + + // pressed reload button? + else if (buttons & IN_RELOAD) { + 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) { - hearDistance = 1024.0f; - timeSound = game.time () + 0.3f; + noise.dist = 1024.0f; + noise.last = game.time () + 0.3f; } } else { @@ -452,29 +454,29 @@ void BotUtils::simulateNoise (int playerIndex) { if (mp_footsteps.bool_ ()) { // moves fast enough? - hearDistance = 1280.0f * (client.ent->v.velocity.length2d () / 260.0f); - timeSound = game.time () + 0.3f; + noise.dist = 1280.0f * (client.ent->v.velocity.length2d () / 260.0f); + noise.last = game.time () + 0.3f; } } - if (hearDistance <= 0.0) { + if (noise.dist <= 0.0) { return; // didn't issue sound? } // some sound already associated - if (client.timeSoundLasting > game.time ()) { - if (client.hearingDistance <= hearDistance) { + if (client.noise.last > game.time ()) { + if (client.noise.dist <= noise.dist) { // override it with new - client.hearingDistance = hearDistance; - client.timeSoundLasting = timeSound; - client.sound = client.ent->v.origin; + client.noise.dist = noise.dist; + client.noise.last = noise.last; + client.noise.pos = client.ent->v.origin; } } - else { + else if (!cr::fzero (noise.last)) { // just remember it - client.hearingDistance = hearDistance; - client.timeSoundLasting = timeSound; - client.sound = client.ent->v.origin; + client.noise.dist = noise.dist; + client.noise.last = noise.last; + client.noise.pos = client.ent->v.origin; } } @@ -615,7 +617,7 @@ void BotUtils::installSendTo () { } // 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 (&sendto), reinterpret_cast (&BotUtils::sendTo)); } }