diff --git a/include/config.h b/include/config.h index 2faff1b..0379548 100644 --- a/include/config.h +++ b/include/config.h @@ -219,6 +219,5 @@ public: } }; - // explose global static auto &conf = BotConfig::get (); \ No newline at end of file diff --git a/include/control.h b/include/control.h index 18fcd98..47dc10d 100644 --- a/include/control.h +++ b/include/control.h @@ -49,6 +49,7 @@ private: Array m_menus; edict_t *m_ent; + Bot *m_djump; bool m_isFromConsole; bool m_rapidOutput; @@ -183,7 +184,7 @@ public: public: // for the server commands - static void handleEngineCommands (); + void handleEngineCommands (); // for the client commands bool handleClientCommands (edict_t *ent); diff --git a/include/crlib/cr-dict.h b/include/crlib/cr-dict.h index 7515d5b..3f9411f 100644 --- a/include/crlib/cr-dict.h +++ b/include/crlib/cr-dict.h @@ -67,7 +67,9 @@ namespace detail { // basic dictionary template , size_t HashSize = 36> class Dictionary final : public DenyCopying { public: - static constexpr size_t kInvalidIndex = static_cast (-1); + enum : size_t { + InvalidIndex = static_cast (-1) + }; private: Array m_table; @@ -105,7 +107,7 @@ private: return created; } - return kInvalidIndex; + return InvalidIndex; } size_t findIndex (const K &key) const { @@ -126,7 +128,7 @@ public: public: bool exists (const K &key) const { - return findIndex (key) != kInvalidIndex; + return findIndex (key) != InvalidIndex; } bool empty () const { @@ -140,7 +142,7 @@ public: bool find (const K &key, V &value) const { size_t index = findIndex (key); - if (index == kInvalidIndex) { + if (index == InvalidIndex) { return false; } value = m_buckets[index].value; diff --git a/include/crlib/cr-http.h b/include/crlib/cr-http.h index f7a3bff..bc3ea3c 100644 --- a/include/crlib/cr-http.h +++ b/include/crlib/cr-http.h @@ -178,12 +178,12 @@ namespace detail { } size_t protocol = uri.find ("://"); - if (protocol != String::kInvalidIndex) { + if (protocol != String::InvalidIndex) { result.protocol = uri.substr (0, protocol); size_t host = uri.find ("/", protocol + 3); - if (host != String::kInvalidIndex) { + if (host != String::InvalidIndex) { result.path = uri.substr (host + 1); result.host = uri.substr (protocol + 3, host - protocol - 3); @@ -244,7 +244,7 @@ private: String response (reinterpret_cast (buffer)); size_t responseCodeStart = response.find ("HTTP/1.1"); - if (responseCodeStart != String::kInvalidIndex) { + if (responseCodeStart != String::InvalidIndex) { String respCode = response.substr (responseCodeStart + 9, 3).trim (); if (respCode == "200") { @@ -369,7 +369,7 @@ public: String boundaryName = localPath; size_t boundarySlash = localPath.findLastOf ("\\/"); - if (boundarySlash != String::kInvalidIndex) { + if (boundarySlash != String::InvalidIndex) { boundaryName = localPath.substr (boundarySlash + 1); } const String &kBoundary = "---crlib_upload_boundary_1337"; diff --git a/include/crlib/cr-lambda.h b/include/crlib/cr-lambda.h index de9aa89..077dcad 100644 --- a/include/crlib/cr-lambda.h +++ b/include/crlib/cr-lambda.h @@ -14,10 +14,14 @@ CR_NAMESPACE_BEGIN -static constexpr uint32 kLambdaSmallBufferSize = sizeof (void *) * 16; - template class Lambda; template class Lambda { +private: + enum : uint32 { + LamdaSmallBufferLength = sizeof (void *) * 16 + }; + +private: class LambdaFunctorWrapper { public: LambdaFunctorWrapper () = default; @@ -69,7 +73,7 @@ template class Lambda { union { UniquePtr m_functor; - uint8 m_small[kLambdaSmallBufferSize]; + uint8 m_small[LamdaSmallBufferLength]; }; bool m_smallObject; @@ -118,7 +122,7 @@ public: } template Lambda (F function) { - if (cr::fix (sizeof (function) > kLambdaSmallBufferSize)) { + if (cr::fix (sizeof (function) > LamdaSmallBufferLength)) { m_smallObject = false; new (m_small) UniquePtr (createUniqueBase , LambdaFunctorWrapper> (cr::move (function))); } diff --git a/include/crlib/cr-string.h b/include/crlib/cr-string.h index c609b4d..3442c3f 100644 --- a/include/crlib/cr-string.h +++ b/include/crlib/cr-string.h @@ -23,10 +23,15 @@ CR_NAMESPACE_BEGIN // small-string optimized string class, sso stuff based on: https://github.com/elliotgoodrich/SSO-23/ class String final { public: - static constexpr size_t kInvalidIndex = static_cast (-1); + enum : size_t { + InvalidIndex = static_cast (-1) + }; private: - static constexpr size_t kExcessSpace = 32; + enum : size_t { + ExcessSpace = 32, + CharBit = CHAR_BIT + }; private: using Length = Twin ; @@ -34,7 +39,7 @@ private: private: union Data { struct Big { - char excess[kExcessSpace - sizeof (char *) - 2 * sizeof (size_t)]; + char excess[ExcessSpace - sizeof (char *) - 2 * sizeof (size_t)]; char *ptr; size_t length; size_t capacity; @@ -47,7 +52,9 @@ private: } m_data; private: - static size_t const kSmallCapacity = sizeof (typename Data::Big) / sizeof (char) - 1; + enum : size_t { + SmallCapacity = sizeof (typename Data::Big) / sizeof (char) - 1 + }; public: explicit String () { @@ -89,7 +96,7 @@ private: } template static bool getMostSignificantBit (uint8 byte) { - return byte & cr::bit (CHAR_BIT - N - 1); + return byte & cr::bit (CharBit - N - 1); } template static void setLeastSignificantBit (uint8 &byte, bool bit) { @@ -103,10 +110,10 @@ private: template static void setMostSignificantBit (uint8 &byte, bool bit) { if (bit) { - byte |= cr::bit (CHAR_BIT - N - 1); + byte |= cr::bit (CharBit - N - 1); } else { - byte &= ~cr::bit (CHAR_BIT - N - 1); + byte &= ~cr::bit (CharBit - N - 1); } } @@ -144,7 +151,7 @@ private: } void setLength (size_t amount, size_t capacity) { - if (amount <= kSmallCapacity) { + if (amount <= SmallCapacity) { endString (m_data.small.str, amount); setSmallLength (static_cast (amount)); } @@ -159,11 +166,11 @@ private: } void setSmallLength (uint8 length) { - m_data.small.length = static_cast (kSmallCapacity - length) << 2; + m_data.small.length = static_cast (SmallCapacity - length) << 2; } size_t getSmallLength () const { - return kSmallCapacity - ((m_data.small.length >> 2) & 63u); + return SmallCapacity - ((m_data.small.length >> 2) & 63u); } void setDataNonSmall (size_t length, size_t capacity) { @@ -208,14 +215,14 @@ public: String &assign (const char *str, size_t length = 0) { length = length > 0 ? length : strlen (str); - if (length <= kSmallCapacity) { + if (length <= SmallCapacity) { moveString (m_data.small.str, str, length); endString (m_data.small.str, length); setSmallLength (static_cast (length)); } else { - auto capacity = cr::max (kSmallCapacity * 2, length); + auto capacity = cr::max (SmallCapacity * 2, length); m_data.big.ptr = alloc.allocate (capacity + 1); if (m_data.big.ptr) { @@ -287,7 +294,7 @@ public: void resize (size_t amount) { size_t oldLength = length (); - if (amount <= kSmallCapacity) { + if (amount <= SmallCapacity) { if (!isSmall ()) { auto ptr = m_data.big.ptr; @@ -300,7 +307,7 @@ public: size_t newCapacity = 0; if (isSmall ()) { - newCapacity = cr::max (amount, kSmallCapacity * 2); + newCapacity = cr::max (amount, SmallCapacity * 2); auto ptr = alloc.allocate (newCapacity + 1); moveString (ptr, m_data.small.str, cr::min (oldLength, amount)); @@ -369,7 +376,7 @@ public: return i; } } - return kInvalidIndex; + return InvalidIndex; } size_t find (const String &pattern, size_t start = 0) const { @@ -377,7 +384,7 @@ public: const size_t dataLength = length (); if (patternLength > dataLength || start > dataLength) { - return kInvalidIndex; + return InvalidIndex; } for (size_t i = start; i <= dataLength - patternLength; ++i) { @@ -393,7 +400,7 @@ public: return i; } } - return kInvalidIndex; + return InvalidIndex; } size_t rfind (char pattern) const { @@ -402,7 +409,7 @@ public: return i; } } - return kInvalidIndex; + return InvalidIndex; } size_t rfind (const String &pattern) const { @@ -410,7 +417,7 @@ public: const size_t dataLength = length (); if (patternLength > dataLength) { - return kInvalidIndex; + return InvalidIndex; } bool match = true; @@ -428,7 +435,7 @@ public: return i; } } - return kInvalidIndex; + return InvalidIndex; } size_t findFirstOf (const String &pattern, size_t start = 0) const { @@ -442,7 +449,7 @@ public: } } } - return kInvalidIndex; + return InvalidIndex; } size_t findLastOf (const String &pattern) const { @@ -456,7 +463,7 @@ public: } } } - return kInvalidIndex; + return InvalidIndex; } size_t findFirstNotOf (const String &pattern, size_t start = 0) const { @@ -479,7 +486,7 @@ public: return i; } } - return kInvalidIndex; + return InvalidIndex; } size_t findLastNotOf (const String &pattern) const { @@ -501,10 +508,9 @@ public: return i; } } - return kInvalidIndex; + return InvalidIndex; } - size_t countChar (char ch) const { size_t count = 0; @@ -533,10 +539,10 @@ public: return count; } - String substr (size_t start, size_t count = kInvalidIndex) const { + String substr (size_t start, size_t count = InvalidIndex) const { start = cr::min (start, length ()); - if (count == kInvalidIndex) { + if (count == InvalidIndex) { count = length (); } return String (data () + start, cr::min (count, length () - start)); @@ -551,7 +557,7 @@ public: while (pos < length ()) { pos = find (needle, pos); - if (pos == kInvalidIndex) { + if (pos == InvalidIndex) { break; } erase (pos, needle.length ()); @@ -581,7 +587,7 @@ public: Array tokens; size_t prev = 0, pos = 0; - while ((pos = find (delim, pos)) != kInvalidIndex) { + while ((pos = find (delim, pos)) != InvalidIndex) { tokens.push (substr (prev, pos - prev)); prev = ++pos; } @@ -641,7 +647,7 @@ public: } bool contains (const String &rhs) const { - return find (rhs) != kInvalidIndex; + return find (rhs) != InvalidIndex; } String &lowercase () { @@ -670,7 +676,7 @@ public: size_t begin = length (); for (size_t i = 0; i < begin; ++i) { - if (characters.find (at (i)) == kInvalidIndex) { + if (characters.find (at (i)) == InvalidIndex) { begin = i; break; } @@ -682,7 +688,7 @@ public: size_t end = 0; for (size_t i = length (); i > 0; --i) { - if (characters.find (at (i - 1)) == kInvalidIndex) { + if (characters.find (at (i - 1)) == InvalidIndex) { end = i; break; } diff --git a/include/crlib/cr-vector.h b/include/crlib/cr-vector.h index 1636eb4..04024fb 100644 --- a/include/crlib/cr-vector.h +++ b/include/crlib/cr-vector.h @@ -14,67 +14,71 @@ CR_NAMESPACE_BEGIN // 3dmath vector -class Vector final { +template class Vec3D { public: - float x = 0.0f, y = 0.0f, z = 0.0f; + T x = 0.0f, y = 0.0f, z = 0.0f; public: - Vector (const float scaler = 0.0f) : x (scaler), y (scaler), z (scaler) + Vec3D (const T &scaler = 0.0f) : x (scaler), y (scaler), z (scaler) { } - explicit Vector (const float _x, const float _y, const float _z) : x (_x), y (_y), z (_z) + Vec3D (const T &x, const T &y, const T &z) : x (x), y (y), z (z) { } - Vector (float *rhs) : x (rhs[0]), y (rhs[1]), z (rhs[2]) + Vec3D (T *rhs) : x (rhs[0]), y (rhs[1]), z (rhs[2]) { } - Vector (const Vector &) = default; + Vec3D (const Vec3D &) = default; + + Vec3D (decltype (nullptr)) { + clear (); + } public: - operator float *() { + operator T * () { return &x; } - operator const float * () const { + operator const T * () const { return &x; } - Vector operator + (const Vector &rhs) const { - return Vector (x + rhs.x, y + rhs.y, z + rhs.z); + Vec3D operator + (const Vec3D &rhs) const { + return { x + rhs.x, y + rhs.y, z + rhs.z }; } - Vector operator - (const Vector &rhs) const { - return Vector (x - rhs.x, y - rhs.y, z - rhs.z); + Vec3D operator - (const Vec3D &rhs) const { + return { x - rhs.x, y - rhs.y, z - rhs.z }; } - Vector operator - () const { - return Vector (-x, -y, -z); + Vec3D operator - () const { + return { -x, -y, -z }; } - friend Vector operator * (const float scale, const Vector &rhs) { - return Vector (rhs.x * scale, rhs.y * scale, rhs.z * scale); + friend Vec3D operator * (const T &scale, const Vec3D &rhs) { + return { rhs.x * scale, rhs.y * scale, rhs.z * scale }; } - Vector operator * (const float scale) const { - return Vector (scale * x, scale * y, scale * z); + Vec3D operator * (const T &scale) const { + return { scale * x, scale * y, scale * z }; } - Vector operator / (const float div) const { - const float inv = 1 / div; - return Vector (inv * x, inv * y, inv * z); + Vec3D operator / (const T &rhs) const { + const auto inv = 1 / (rhs + kFloatEqualEpsilon); + return { inv * x, inv * y, inv * z }; } // cross product - Vector operator ^ (const Vector &rhs) const { - return Vector (y * rhs.z - z * rhs.y, z * rhs.x - x * rhs.z, x * rhs.y - y * rhs.x); + Vec3D operator ^ (const Vec3D &rhs) const { + return { y * rhs.z - z * rhs.y, z * rhs.x - x * rhs.z, x * rhs.y - y * rhs.x }; } // dot product - float operator | (const Vector &rhs) const { + T operator | (const Vec3D &rhs) const { return x * rhs.x + y * rhs.y + z * rhs.z; } - const Vector &operator += (const Vector &rhs) { + const Vec3D &operator += (const Vec3D &rhs) { x += rhs.x; y += rhs.y; z += rhs.z; @@ -82,24 +86,24 @@ public: return *this; } - const Vector &operator -= (const Vector &right) { - x -= right.x; - y -= right.y; - z -= right.z; + const Vec3D &operator -= (const Vec3D &rhs) { + x -= rhs.x; + y -= rhs.y; + z -= rhs.z; return *this; } - const Vector &operator *= (float scale) { - x *= scale; - y *= scale; - z *= scale; + const Vec3D &operator *= (const T &rhs) { + x *= rhs; + y *= rhs; + z *= rhs; return *this; } - const Vector &operator /= (float div) { - const float inv = 1 / div; + const Vec3D &operator /= (const T &rhs) { + const auto inv = 1 / (rhs + kFloatEqualEpsilon); x *= inv; y *= inv; @@ -108,67 +112,66 @@ public: return *this; } - bool operator == (const Vector &rhs) const { + bool operator == (const Vec3D &rhs) const { return cr::fequal (x, rhs.x) && cr::fequal (y, rhs.y) && cr::fequal (z, rhs.z); } - bool operator != (const Vector &rhs) const { - return !cr::fequal (x, rhs.x) && !cr::fequal (y, rhs.y) && !cr::fequal (z, rhs.z); + bool operator != (const Vec3D &rhs) const { + return !operator == (rhs); } - Vector &operator = (const Vector &) = default; + void operator = (decltype (nullptr)) { + clear (); + } + + Vec3D &operator = (const Vec3D &) = default; public: - float length () const { + T length () const { return cr::sqrtf (lengthSq ()); } - float length2d () const { + T length2d () const { return cr::sqrtf (x * x + y * y); } - float lengthSq () const { + T lengthSq () const { return x * x + y * y + z * z; } - Vector get2d () const { - return Vector (x, y, 0.0f); + Vec3D get2d () const { + return { x, y, 0.0f }; } - Vector normalize () const { - float len = length () + cr::kFloatCmpEpsilon; + Vec3D normalize () const { + auto len = length () + cr::kFloatCmpEpsilon; if (cr::fzero (len)) { - return Vector (0.0f, 0.0f, 1.0f); + return { 0.0f, 0.0f, 1.0f }; } len = 1.0f / len; - return Vector (x * len, y * len, z * len); + return { x * len, y * len, z * len }; } - Vector normalize2d () const { - float len = length2d () + cr::kFloatCmpEpsilon; + Vec3D normalize2d () const { + auto len = length2d () + cr::kFloatCmpEpsilon; if (cr::fzero (len)) { - return Vector (0.0f, 1.0f, 0.0f); + return { 0.0f, 1.0f, 0.0f }; } len = 1.0f / len; - return Vector (x * len, y * len, 0.0f); + return { x * len, y * len, 0.0f }; } bool empty () const { return cr::fzero (x) && cr::fzero (y) && cr::fzero (z); } - static const Vector &null () { - static const Vector &s_null {}; - return s_null; - } - void clear () { x = y = z = 0.0f; } - Vector clampAngles () { + Vec3D clampAngles () { x = cr::normalizeAngles (x); y = cr::normalizeAngles (y); z = 0.0f; @@ -176,78 +179,84 @@ public: return *this; } - float pitch () const { + T pitch () const { if (cr::fzero (x) && cr::fzero (y)) { return 0.0f; } return cr::degreesToRadians (cr::atan2f (z, length2d ())); } - float yaw () const { + T yaw () const { if (cr::fzero (x) && cr::fzero (y)) { return 0.0f; } return cr::radiansToDegrees (cr:: atan2f (y, x)); } - Vector angles () const { + Vec3D angles () const { if (cr::fzero (x) && cr::fzero (y)) { - return Vector (z > 0.0f ? 90.0f : 270.0f, 0.0, 0.0f); + return { z > 0.0f ? 90.0f : 270.0f, 0.0, 0.0f }; } - return Vector (cr::radiansToDegrees (cr::atan2f (z, length2d ())), cr::radiansToDegrees (cr::atan2f (y, x)), 0.0f); + return { cr::radiansToDegrees (cr::atan2f (z, length2d ())), cr::radiansToDegrees (cr::atan2f (y, x)), 0.0f }; } - void buildVectors (Vector *forward, Vector *right, Vector *upward) const { + void angleVectors (Vec3D *forward, Vec3D *right, Vec3D *upward) const { enum { pitch, yaw, roll, unused, max }; - float sines[max] = { 0.0f, 0.0f, 0.0f, 0.0f }; - float cosines[max] = { 0.0f, 0.0f, 0.0f, 0.0f }; + T sines[max] = { 0.0f, 0.0f, 0.0f, 0.0f }; + T cosines[max] = { 0.0f, 0.0f, 0.0f, 0.0f }; // compute the sine and cosine compontents cr::sincosf (cr::degreesToRadians (x), cr::degreesToRadians (y), cr::degreesToRadians (z), sines, cosines); if (forward) { - forward->x = cosines[pitch] * cosines[yaw]; - forward->y = cosines[pitch] * sines[yaw]; - forward->z = -sines[pitch]; + *forward = { + cosines[pitch] * cosines[yaw], + cosines[pitch] * sines[yaw], + -sines[pitch] + }; } if (right) { - right->x = -sines[roll] * sines[pitch] * cosines[yaw] + cosines[roll] * sines[yaw]; - right->y = -sines[roll] * sines[pitch] * sines[yaw] - cosines[roll] * cosines[yaw]; - right->z = -sines[roll] * cosines[pitch]; + *right = { + -sines[roll] * sines[pitch] * cosines[yaw] + cosines[roll] * sines[yaw], + -sines[roll] * sines[pitch] * sines[yaw] - cosines[roll] * cosines[yaw], + -sines[roll] * cosines[pitch] + }; } if (upward) { - upward->x = cosines[roll] * sines[pitch] * cosines[yaw] + sines[roll] * sines[yaw]; - upward->y = cosines[roll] * sines[pitch] * sines[yaw] - sines[roll] * cosines[yaw]; - upward->z = cosines[roll] * cosines[pitch]; + *upward = { + cosines[roll] * sines[pitch] * cosines[yaw] + sines[roll] * sines[yaw], + upward->y = cosines[roll] * sines[pitch] * sines[yaw] - sines[roll] * cosines[yaw], + upward->z = cosines[roll] * cosines[pitch] + }; } } - const Vector &forward () { - static Vector s_fwd {}; - buildVectors (&s_fwd, nullptr, nullptr); + const Vec3D &forward () { + static Vec3D s_fwd {}; + angleVectors (&s_fwd, nullptr, nullptr); return s_fwd; } - const Vector &upward () { - static Vector s_up {}; - buildVectors (nullptr, nullptr, &s_up); + const Vec3D &upward () { + static Vec3D s_up {}; + angleVectors (nullptr, nullptr, &s_up); return s_up; } - const Vector &right () { - static Vector s_right {}; - buildVectors (nullptr, &s_right, nullptr); + const Vec3D &right () { + static Vec3D s_right {}; + angleVectors (nullptr, &s_right, nullptr); return s_right; } }; -// expose global null vector -static auto &nullvec = Vector::null (); +// default is float +using Vector = Vec3D ; CR_NAMESPACE_END \ No newline at end of file diff --git a/include/engine.h b/include/engine.h index 6af020b..bcfc9ed 100644 --- a/include/engine.h +++ b/include/engine.h @@ -57,9 +57,16 @@ CR_DECLARE_SCOPED_ENUM (MapFlags, Escape = cr::bit (3), KnifeArena = cr::bit (4), Fun = cr::bit (5), - HasDoors = cr::bit (10) // additional flags + HasDoors = cr::bit (10), // additional flags + HasButtons = cr::bit (11) // map has buttons ) +// recursive entity search +CR_DECLARE_SCOPED_ENUM (EntitySearchResult, + Continue, + Break +); + // variable reg pair struct VarPair { Var type; @@ -74,6 +81,9 @@ using EntityFunction = void (*) (entvars_t *); // provides utility functions to not call original engine (less call-cost) class Game final : public Singleton { +public: + using EntitySearch = Lambda ; + private: int m_drawModels[DrawLine::Count]; int m_spawnCount[Team::Unassigned]; @@ -130,7 +140,7 @@ public: Vector getAbsPos (edict_t *ent); // registers a server command - void registerCmd (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); @@ -159,10 +169,16 @@ public: // executes stuff every 1 second void slowFrame (); + // search entities by variable field + void searchEntities (const String &field, const String &value, EntitySearch functor); + + // search entities in sphere + void searchEntities (const Vector &position, const float radius, EntitySearch functor); + // public inlines public: // get the current time on server - float timebase () const { + float time () const { return globals->time; } @@ -361,7 +377,7 @@ private: public: MessageWriter () = default; - MessageWriter (int dest, int type, const Vector &pos = nullvec, edict_t *to = nullptr) { + MessageWriter (int dest, int type, const Vector &pos = nullptr, edict_t *to = nullptr) { start (dest, type, pos, to); m_autoDestruct = true; } @@ -373,7 +389,7 @@ public: } public: - MessageWriter &start (int dest, int type, const Vector &pos = nullvec, edict_t *to = nullptr) { + MessageWriter &start (int dest, int type, const Vector &pos = nullptr, edict_t *to = nullptr) { engfuncs.pfnMessageBegin (dest, type, pos, to); return *this; } diff --git a/include/graph.h b/include/graph.h index 8085986..34191f0 100644 --- a/include/graph.h +++ b/include/graph.h @@ -323,7 +323,7 @@ public: void initNodesTypes (); void initLightLevels (); void addPath (int addIndex, int pathIndex, float distance); - void add (int type, const Vector &pos = nullvec); + void add (int type, const Vector &pos = nullptr); void erase (int target); void toggleFlags (int toggleFlag); void setRadius (int index, float radius); @@ -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 = nullvec); + void setBombPos (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); diff --git a/include/manager.h b/include/manager.h index 704b36d..5c5f0da 100644 --- a/include/manager.h +++ b/include/manager.h @@ -81,7 +81,6 @@ public: float getConnectionTime (int botId); void setBombPlanted (bool isPlanted); - void slowFrame (); void frame (); void createKillerEntity (); void destroyKillerEntity (); @@ -91,7 +90,6 @@ public: void addbot (const String &name, const String &difficulty, const String &personality, const String &team, const String &member, bool manual); void serverFill (int selection, int personality = Personality::Normal, int difficulty = -1, int numToAdd = -1); void kickEveryone (bool instant = false, bool zeroQuota = true); - bool kickRandom (bool decQuota = true, Team fromTeam = Team::Unassigned); void kickBot (int index); void kickFromTeam (Team team, bool removeAll = false); void killAllBots (int team = -1); @@ -117,6 +115,7 @@ public: void handleDeath (edict_t *killer, edict_t *victim); bool isTeamStacked (int team); + bool kickRandom (bool decQuota = true, Team fromTeam = Team::Unassigned); public: Array &searchActiveGrenades () { diff --git a/include/utils.h b/include/utils.h index ecead99..86406de 100644 --- a/include/utils.h +++ b/include/utils.h @@ -9,6 +9,18 @@ #pragma once +// noise types +CR_DECLARE_SCOPED_ENUM (Noise, + NeedHandle = cr::bit (0), + HitFall = cr::bit (1), + Pickup = cr::bit (2), + Zoom = cr::bit (3), + Ammo = cr::bit (4), + Hostage = cr::bit (5), + Broke = cr::bit (6), + Door = cr::bit (7) +) + class BotUtils final : public Singleton { private: bool m_needToSendWelcome; @@ -18,6 +30,7 @@ private: SmallArray m_clients; SmallArray > m_tags; + Dictionary m_noiseCache; SimpleHook m_sendToHook; public: @@ -65,10 +78,10 @@ public: void traceDecals (entvars_t *pev, TraceResult *trace, int logotypeIndex); // attaches sound to client struct - void attachSoundsToClients (edict_t *ent, const char *sample, float volume); + void listenNoise (edict_t *ent, const String &sample, float volume); // simulate sound for players - void simulateSoundUpdates (int playerIndex); + void simulateNoise (int playerIndex); // update stats on clients void updateClients (); diff --git a/include/yapb.h b/include/yapb.h index 60542c5..ab30f71 100644 --- a/include/yapb.h +++ b/include/yapb.h @@ -524,10 +524,10 @@ struct Client { int radio; // radio orders int menu; // identifier to openen menu int ping; // when bot latency is enabled, client ping stored here - float hearingDistance; // distance this sound is heared - float timeSoundLasting; // time sound is played/heared 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 ? }; @@ -622,7 +622,6 @@ private: float m_strafeSpeed; // current speed sideways float m_minSpeed; // minimum speed in normal mode float m_oldCombatDesire; // holds old desire for filtering - float m_avoidTime; // time to avoid players around float m_itemCheckTime; // time next search for items needs to be done float m_joinServerTime; // time when bot joined the game float m_playServerTime; // time bot spent in the game @@ -659,7 +658,6 @@ private: edict_t *m_breakableEntity; // pointer to breakable entity edict_t *m_targetEntity; // the entity that the bot is trying to reach edict_t *m_avoidGrenade; // pointer to grenade entity to avoid - edict_t *m_avoid; // avoid players on our way Vector m_liftTravelPos; // lift travel position Vector m_moveAngles; // bot move angles @@ -732,7 +730,7 @@ private: bool hasActiveGoal (); bool advanceMovement (); bool isBombDefusing (const Vector &bombOrigin); - bool isOccupiedPoint (int index); + bool isOccupiedNode (int index); bool seesItem (const Vector &dest, const char *itemName); bool lastEnemyShootable (); bool isShootableBreakable (edict_t *ent); @@ -754,9 +752,11 @@ private: bool checkChatKeywords (String &reply); bool isReplyingToChat (); bool isReachableNode (int index); + bool updateLiftHandling (); + bool updateLiftStates (); void instantChatter (int type); - void runAI (); + void update (); void runMovement (); void checkSpawnConditions (); void buyStuff (); @@ -808,6 +808,7 @@ private: void selectWeaponById (int num); void completeTask (); void executeTasks (); + void trackEnemies (); void normal_ (); void spraypaint_ (); @@ -881,8 +882,10 @@ public: float m_agressionLevel; // dynamic aggression level (in game) float m_fearLevel; // dynamic fear level (in game) float m_nextEmotionUpdate; // next time to sanitize emotions - float m_thinkFps; // skip some frames in bot thinking - float m_thinkInterval; // interval between frames + float m_updateTime; // skip some frames in bot thinking + float m_updateInterval; // interval between frames + float m_viewFps; // time to update bots vision + float m_viewUpdateInterval; // interval to update bot vision float m_goalValue; // ranking value for this node float m_viewDistance; // current view distance float m_maxViewDistance; // maximum view distance @@ -962,20 +965,18 @@ public: ~Bot () = default; public: - void slowFrame (); // the main Lambda that decides intervals of running bot ai - void fastFrame (); /// the things that can be executed while skipping frames - void processBlind (int alpha); - void processDamage (edict_t *inflictor, int damage, int armor, int bits); + void logic (); /// the things that can be executed while skipping frames + void takeBlind (int alpha); + void takeDamage (edict_t *inflictor, int damage, int armor, int bits); void showDebugOverlay (); void newRound (); - void processBuyzoneEntering (int buyState); + void enteredBuyZone (int buyState); void pushMsgQueue (int message); void prepareChatMessage (const String &message); void checkForChat (); void showChaterIcon (bool show); void clearSearchNodes (); - void processBreakables (edict_t *touch); - void avoidIncomingPlayers (edict_t *touch); + void checkBreakable (edict_t *touch); void startTask (Task id, float desire, int data, float time, bool resume); void clearTask (Task id); void filterTasks (); @@ -986,7 +987,7 @@ public: void pushChatMessage (int type, bool isTeamSay = false); void pushRadioMessage (int message); void pushChatterMessage (int message); - void processChatterMessage (const char *tempMessage); + void handleChatter (const char *tempMessage); void tryHeadTowardRadioMessage (); void kill (); void kick (); diff --git a/project/yapb.vcxproj b/project/yapb.vcxproj index f8dc622..d1802db 100644 --- a/project/yapb.vcxproj +++ b/project/yapb.vcxproj @@ -58,7 +58,7 @@ - + diff --git a/project/yapb.vcxproj.filters b/project/yapb.vcxproj.filters index 70957ba..444fa00 100644 --- a/project/yapb.vcxproj.filters +++ b/project/yapb.vcxproj.filters @@ -137,9 +137,6 @@ source - - source - source @@ -170,6 +167,9 @@ source + + source + diff --git a/source/Android.mk b/source/Android.mk index 92d3e68..b130ca9 100644 --- a/source/Android.mk +++ b/source/Android.mk @@ -22,7 +22,7 @@ LOCAL_SRC_FILES := \ combat.cpp \ control.cpp \ engine.cpp \ - interface.cpp \ + linkage.cpp \ navigate.cpp \ support.cpp \ graph.cpp \ diff --git a/source/basecode.cpp b/source/basecode.cpp index bf6b655..8aa14cd 100644 --- a/source/basecode.cpp +++ b/source/basecode.cpp @@ -27,7 +27,7 @@ ConVar yb_botbuy ("yb_botbuy", "1"); 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", "1"); +ConVar yb_best_weapon_picker_type ("yb_best_weapon_picker_type", "0"); // game console variables ConVar mp_c4timer ("mp_c4timer", nullptr, Var::NoRegister); @@ -60,7 +60,7 @@ void Bot::pushMsgQueue (int message) { other->m_sayTextBuffer.entityIndex = entityIndex; other->m_sayTextBuffer.sayText = m_chatBuffer; } - other->m_sayTextBuffer.timeNextChat = game.timebase () + other->m_sayTextBuffer.chatDelay; + other->m_sayTextBuffer.timeNextChat = game.time () + other->m_sayTextBuffer.chatDelay; } } } @@ -124,13 +124,13 @@ void Bot::checkGrenadesThrow () { }; // check if throwing a grenade is a good thing to do... - if (checkTasks || yb_ignore_enemies.bool_ () || m_isUsingGrenade || m_grenadeRequested || m_isReloading || yb_jasonmode.bool_ () || m_grenadeCheckTime >= game.timebase ()) { + if (checkTasks || yb_ignore_enemies.bool_ () || m_isUsingGrenade || m_grenadeRequested || m_isReloading || yb_jasonmode.bool_ () || m_grenadeCheckTime >= game.time ()) { clearThrowStates (m_states); return; } // check again in some seconds - m_grenadeCheckTime = game.timebase () + 0.5f; + m_grenadeCheckTime = game.time () + 0.5f; if (!util.isAlive (m_lastEnemy) || !(m_states & (Sense::SuspectEnemy | Sense::HearingEnemy))) { clearThrowStates (m_states); @@ -142,7 +142,7 @@ void Bot::checkGrenadesThrow () { // if we don't have grenades no need to check it this round again if (grenadeToThrow == -1) { - m_grenadeCheckTime = game.timebase () + 15.0f; // changed since, conzero can drop grens from dead players + m_grenadeCheckTime = game.time () + 15.0f; // changed since, conzero can drop grens from dead players clearThrowStates (m_states); return; @@ -151,10 +151,10 @@ void Bot::checkGrenadesThrow () { int cancelProb = 20; if (grenadeToThrow == Weapon::Flashbang) { - cancelProb = 10; + cancelProb = 25; } else if (grenadeToThrow == Weapon::Smoke) { - cancelProb = 5; + cancelProb = 35; } if (rg.chance (cancelProb)) { clearThrowStates (m_states); @@ -284,7 +284,7 @@ void Bot::checkGrenadesThrow () { } break; } - const float MaxThrowTime = game.timebase () + 0.3f; + const float MaxThrowTime = game.time () + 0.3f; if (m_states & Sense::ThrowExplosive) { startTask (Task::ThrowExplosive, TaskPri::Throw, kInvalidNodeIndex, MaxThrowTime, false); @@ -331,13 +331,13 @@ void Bot::avoidGrenades () { } auto model = STRING (pent->v.model) + 9; - if (m_preventFlashing < game.timebase () && 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 if (!(m_states & Sense::SeeingEnemy)) { pev->v_angle.y = cr::normalizeAngles ((game.getAbsPos (pent) - getEyesPos ()).angles ().y + 180.0f); m_canChooseAimDirection = false; - m_preventFlashing = game.timebase () + rg.float_ (1.0f, 2.0f); + m_preventFlashing = game.time () + rg.float_ (1.0f, 2.0f); } } else if (strcmp (model, "hegrenade.mdl") == 0) { @@ -350,10 +350,10 @@ void Bot::avoidGrenades () { } if (!(pent->v.flags & FL_ONGROUND)) { - float distance = (pent->v.origin - pev->origin).length (); - float distanceMoved = ((pent->v.origin + pent->v.velocity * getFrameInterval ()) - pev->origin).length (); + float distance = (pent->v.origin - pev->origin).lengthSq (); + float distanceMoved = ((pent->v.origin + pent->v.velocity * getFrameInterval ()) - pev->origin).lengthSq (); - if (distanceMoved < distance && distance < 500.0f) { + if (distanceMoved < distance && distance < cr::square (500.0f)) { const auto &dirToPoint = (pev->origin - pent->v.origin).normalize2d (); const auto &rightSide = pev->v_angle.right ().normalize2d (); @@ -367,22 +367,24 @@ void Bot::avoidGrenades () { } } } - else if ((pent->v.flags & FL_ONGROUND) == 0 && strcmp (model, "smokegrenade.mdl") == 0) { - float distance = (pent->v.origin - pev->origin).length (); + else if ((pent->v.flags & FL_ONGROUND) && strcmp (model, "smokegrenade.mdl") == 0) { + if (isInFOV (pent->v.origin - getEyesPos ()) < pev->fov - 7.0f) { + float distance = (pent->v.origin - pev->origin).length (); - // shrink bot's viewing distance to smoke grenade's distance - if (m_viewDistance > distance) { - m_viewDistance = distance; + // shrink bot's viewing distance to smoke grenade's distance + if (m_viewDistance > distance) { + m_viewDistance = distance; - if (rg.chance (45)) { - pushChatterMessage (Chatter::BehindSmoke); + if (rg.chance (45)) { + pushChatterMessage (Chatter::BehindSmoke); + } } } } } } -void Bot::processBreakables (edict_t *touch) { +void Bot::checkBreakable (edict_t *touch) { if (!isShootableBreakable (touch)) { return; @@ -423,7 +425,7 @@ edict_t *Bot::lookupBreakable () { } } m_breakableEntity = nullptr; - m_breakableOrigin= nullvec; + m_breakableOrigin= nullptr; return nullptr; } @@ -447,7 +449,7 @@ void Bot::setIdealReactionTimers (bool actual) { void Bot::updatePickups () { // this function finds Items to collect or use in the near of a bot - + // don't try to pickup anything while on ladder or trying to escape from bomb... if (isOnLadder () || getCurrentTaskId () == Task::EscapeFromBomb || yb_jasonmode.bool_ () || !bots.hasIntrestingEntities ()) { m_pickupItem = nullptr; @@ -481,10 +483,10 @@ void Bot::updatePickups () { } if (itemExists) { - return; } else { + m_pickupItem = nullptr; m_pickupType = Pickup::None; } @@ -492,7 +494,7 @@ void Bot::updatePickups () { edict_t *pickupItem = nullptr; Pickup pickupType = Pickup::None; - Vector pickupPos = nullvec; + Vector pickupPos = nullptr; m_pickupItem = nullptr; m_pickupType = Pickup::None; @@ -619,11 +621,11 @@ void Bot::updatePickups () { m_itemIgnore = ent; allowPickup = false; - if (!m_defendHostage && m_difficulty > 2 && rg.chance (30) && m_timeCamping + 15.0f < game.timebase ()) { + if (!m_defendHostage && m_difficulty > 2 && rg.chance (30) && m_timeCamping + 15.0f < game.time ()) { int index = findDefendNode (origin); - startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, game.timebase () + rg.float_ (30.0f, 60.0f), true); // push camp task on to stack - startTask (Task::MoveToPosition, TaskPri::MoveToPosition, index, game.timebase () + rg.float_ (3.0f, 6.0f), true); // push move command + startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, game.time () + rg.float_ (30.0f, 60.0f), true); // push camp task on to stack + startTask (Task::MoveToPosition, TaskPri::MoveToPosition, index, game.time () + rg.float_ (3.0f, 6.0f), true); // push move command if (graph[index].vis.crouch <= graph[index].vis.stand) { m_campButtons |= IN_DUCK; @@ -649,7 +651,7 @@ void Bot::updatePickups () { float bombTimer = mp_c4timer.float_ (); float timeMidBlowup = bots.getTimeBombPlanted () + (bombTimer * 0.5f + bombTimer * 0.25f) - graph.calculateTravelTime (pev->maxspeed, pev->origin, path.origin); - if (timeMidBlowup > game.timebase ()) { + if (timeMidBlowup > game.time ()) { clearTask (Task::MoveToPosition); // remove any move tasks startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, timeMidBlowup, true); // push camp task on to stack @@ -745,8 +747,8 @@ void Bot::updatePickups () { if (!m_defendedBomb && m_difficulty > 2 && rg.chance (75) && pev->health < 80) { int index = findDefendNode (origin); - startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, game.timebase () + rg.float_ (30.0f, 70.0f), true); // push camp task on to stack - startTask (Task::MoveToPosition, TaskPri::MoveToPosition, index, game.timebase () + rg.float_ (10.0f, 30.0f), true); // push move command + startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, game.time () + rg.float_ (30.0f, 70.0f), true); // push camp task on to stack + startTask (Task::MoveToPosition, TaskPri::MoveToPosition, index, game.time () + rg.float_ (10.0f, 30.0f), true); // push move command if (graph[index].vis.crouch <= graph[index].vis.stand) { m_campButtons |= IN_DUCK; @@ -852,7 +854,7 @@ void Bot::showChaterIcon (bool show) { } auto sendBotVoice = [](bool show, edict_t *ent, int ownId) { - MessageWriter (MSG_ONE, msgs.id (NetMsg::BotVoice), nullvec, ent) // begin message + MessageWriter (MSG_ONE, msgs.id (NetMsg::BotVoice), nullptr, ent) // begin message .writeByte (show) // switch on/off .writeByte (ownId); }; @@ -864,7 +866,7 @@ void Bot::showChaterIcon (bool show) { continue; } - if (!show && (client.iconFlags[ownIndex] & ClientFlags::Icon) && client.iconTimestamp[ownIndex] < game.timebase ()) { + if (!show && (client.iconFlags[ownIndex] & ClientFlags::Icon) && client.iconTimestamp[ownIndex] < game.time ()) { sendBotVoice (false, client.ent, entindex ()); client.iconTimestamp[ownIndex] = 0.0f; @@ -895,15 +897,15 @@ void Bot::instantChatter (int type) { if (!(client.flags & ClientFlags::Used) || (client.ent->v.flags & FL_FAKECLIENT) || client.team != m_team) { continue; } - msg.start (MSG_ONE, msgs.id (NetMsg::SendAudio), nullvec, client.ent); // begin message + msg.start (MSG_ONE, msgs.id (NetMsg::SendAudio), nullptr, client.ent); // begin message msg.writeByte (ownIndex); if (pev->deadflag & DEAD_DYING) { - client.iconTimestamp[ownIndex] = game.timebase () + painSound.duration; + client.iconTimestamp[ownIndex] = game.time () + painSound.duration; msg.writeString (strings.format ("%s/%s.wav", yb_chatter_path.str (), painSound.name.chars ())); } else if (!(pev->deadflag & DEAD_DEAD)) { - client.iconTimestamp[ownIndex] = game.timebase () + playbackSound.duration; + client.iconTimestamp[ownIndex] = game.time () + playbackSound.duration; msg.writeString (strings.format ("%s/%s.wav", yb_chatter_path.str (), playbackSound.name.chars ())); } msg.writeShort (m_voicePitch).end (); @@ -934,9 +936,9 @@ void Bot::pushChatterMessage (int message) { const float messageRepeat = conf.getChatterMessageRepeatInterval (message); float &messageTimer = m_chatterTimes[message]; - if (messageTimer < game.timebase () || cr::fequal (messageTimer, kMaxChatterRepeatInteval)) { + if (messageTimer < game.time () || cr::fequal (messageTimer, kMaxChatterRepeatInteval)) { if (!cr::fequal (messageTimer, kMaxChatterRepeatInteval) && !cr::fequal (messageRepeat, kMaxChatterRepeatInteval)) { - messageTimer = game.timebase () + messageRepeat; + messageTimer = game.time () + messageRepeat; } sendMessage = true; } @@ -970,7 +972,7 @@ void Bot::checkMsgQueue () { case BotMsg::Buy: // general buy message // buy weapon - if (m_nextBuyTime > game.timebase ()) { + if (m_nextBuyTime > game.time ()) { // keep sending message pushMsgQueue (BotMsg::Buy); return; @@ -984,11 +986,11 @@ void Bot::checkMsgQueue () { } m_buyPending = false; - m_nextBuyTime = game.timebase () + rg.float_ (0.5f, 1.3f); + m_nextBuyTime = game.time () + rg.float_ (0.5f, 1.3f); // if freezetime is very low do not delay the buy process if (mp_freezetime.float_ () <= 1.0f) { - m_nextBuyTime = game.timebase (); + m_nextBuyTime = game.time (); m_ignoreBuyDelay = true; } @@ -1035,10 +1037,10 @@ void Bot::checkMsgQueue () { case BotMsg::Radio: // if last bot radio command (global) happened just a 3 seconds ago, delay response - if (bots.getLastRadioTimestamp (m_team) + 3.0f < game.timebase ()) { + if (bots.getLastRadioTimestamp (m_team) + 3.0f < game.time ()) { // if same message like previous just do a yes/no if (m_radioSelect != Radio::RogerThat && m_radioSelect != Radio::Negative) { - if (m_radioSelect == bots.getLastRadio (m_team) && bots.getLastRadioTimestamp (m_team) + 1.5f > game.timebase ()) { + if (m_radioSelect == bots.getLastRadio (m_team) && bots.getLastRadioTimestamp (m_team) + 1.5f > game.time ()) { m_radioSelect = -1; } else { @@ -1080,7 +1082,7 @@ void Bot::checkMsgQueue () { } } m_forceRadio = false; // reset radio to voice - bots.setLastRadioTimestamp (m_team, game.timebase ()); // store last radio usage + bots.setLastRadioTimestamp (m_team, game.time ()); // store last radio usage } else { pushMsgQueue (BotMsg::Radio); @@ -1200,8 +1202,9 @@ bool Bot::canReplaceWeapon () { int Bot::pickBestWeapon (int *vec, int count, int moneySave) { // this function picks best available weapon from random choice with money save - if (yb_best_weapon_picker_type.int_ () == 1) { + bool needMoreRandomWeapon = (m_personality == Personality::Careful) || (rg.chance (25) && m_personality == Personality::Normal); + if (needMoreRandomWeapon) { auto pick = [] (const float factor) -> float { union { unsigned int u; @@ -1253,7 +1256,7 @@ void Bot::buyStuff () { // this function does all the work in selecting correct buy menus for most weapons/items WeaponInfo *selectedWeapon = nullptr; - m_nextBuyTime = game.timebase (); + m_nextBuyTime = game.time (); if (!m_ignoreBuyDelay) { m_nextBuyTime += rg.float_ (0.3f, 0.5f); @@ -1613,7 +1616,7 @@ void Bot::buyStuff () { void Bot::updateEmotions () { // slowly increase/decrease dynamic emotions back to their base level - if (m_nextEmotionUpdate > game.timebase ()) { + if (m_nextEmotionUpdate > game.time ()) { return; } @@ -1638,14 +1641,14 @@ void Bot::updateEmotions () { if (m_fearLevel < 0.0f) { m_fearLevel = 0.0f; } - m_nextEmotionUpdate = game.timebase () + 1.0f; + m_nextEmotionUpdate = game.time () + 1.0f; } void Bot::overrideConditions () { if (m_currentWeapon != Weapon::Knife && m_difficulty > 2 && ((m_aimFlags & AimFlags::Enemy) || (m_states & Sense::SeeingEnemy)) && !yb_jasonmode.bool_ () && getCurrentTaskId () != Task::Camp && getCurrentTaskId () != Task::SeekCover && !isOnLadder ()) { m_moveToGoal = false; // don't move to goal - m_navTimeset = game.timebase (); + m_navTimeset = game.time (); if (util.isPlayer (m_enemy)) { attackMovement (); @@ -1661,7 +1664,7 @@ void Bot::overrideConditions () { } // special handling, if we have a knife in our hands - if ((bots.getRoundStartTime () + 6.0f > game.timebase () || !hasAnyWeapons ()) && m_currentWeapon == Weapon::Knife && util.isPlayer (m_enemy)) { + if ((bots.getRoundStartTime () + 6.0f > game.time () || !hasAnyWeapons ()) && m_currentWeapon == Weapon::Knife && util.isPlayer (m_enemy)) { float length = (pev->origin - m_enemy->v.origin).length2d (); // do waypoint movement if enemy is not reachable with a knife @@ -1669,7 +1672,7 @@ void Bot::overrideConditions () { int nearestToEnemyPoint = graph.getNearest (m_enemy->v.origin); if (nearestToEnemyPoint != kInvalidNodeIndex && nearestToEnemyPoint != m_currentNodeIndex && cr::abs (graph[nearestToEnemyPoint].origin.z - m_enemy->v.origin.z) < 16.0f) { - float taskTime = game.timebase () + length / pev->maxspeed * 0.5f; + float taskTime = game.time () + length / pev->maxspeed * 0.5f; if (getCurrentTaskId () != Task::MoveToPosition && getTask ()->desire != TaskPri::Hide) { startTask (Task::MoveToPosition, TaskPri::Hide, nearestToEnemyPoint, taskTime, true); @@ -1683,10 +1686,21 @@ void Bot::overrideConditions () { } // special handling for sniping - if (usesSniper () && (m_states & (Sense::SeeingEnemy | Sense::SuspectEnemy)) && m_sniperStopTime > game.timebase () && getCurrentTaskId () != Task::SeekCover) { + if (usesSniper () && (m_states & (Sense::SeeingEnemy | Sense::SuspectEnemy)) && m_sniperStopTime > game.time () && getCurrentTaskId () != Task::SeekCover) { m_moveSpeed = 0.0f; m_strafeSpeed = 0.0f; - m_navTimeset = game.timebase (); + + m_navTimeset = game.time (); + } + + // special handling for reloading + if (m_reloadState != Reload::None && m_isReloading && ((pev->button | m_oldButtons) & IN_RELOAD)) { + if (m_seeEnemyTime + 4.0f < game.time () && (m_states & Sense::SuspectEnemy)) { + m_moveSpeed = 0.0f; + m_strafeSpeed = 0.0f; + + m_navTimeset = game.time (); + } } } @@ -1695,17 +1709,10 @@ void Bot::setConditions () { // action after applying all of the Filters m_aimFlags = 0; - updateEmotions (); // does bot see an enemy? - if (lookupEnemies ()) { - m_states |= Sense::SeeingEnemy; - } - else { - m_states &= ~Sense::SeeingEnemy; - m_enemy = nullptr; - } + trackEnemies (); // did bot just kill an enemy? if (!game.isNullEntity (m_lastVictim)) { @@ -1762,7 +1769,7 @@ void Bot::setConditions () { selectWeaponByName ("weapon_knife"); m_plantedBombNodeIndex = getNearestToPlantedBomb (); - if (isOccupiedPoint (m_plantedBombNodeIndex)) { + if (isOccupiedNode (m_plantedBombNodeIndex)) { pushChatterMessage (Chatter::BombsiteSecured); } } @@ -1776,22 +1783,22 @@ void Bot::setConditions () { // check if our current enemy is still valid if (!game.isNullEntity (m_lastEnemy)) { - if (!util.isAlive (m_lastEnemy) && m_shootAtDeadTime < game.timebase ()) { - m_lastEnemyOrigin= nullvec; + if (!util.isAlive (m_lastEnemy) && m_shootAtDeadTime < game.time ()) { + m_lastEnemyOrigin= nullptr; m_lastEnemy = nullptr; } } else { - m_lastEnemyOrigin= nullvec; + m_lastEnemyOrigin= nullptr; m_lastEnemy = nullptr; } // don't listen if seeing enemy, just checked for sounds or being blinded (because its inhuman) - if (!yb_ignore_enemies.bool_ () && m_soundUpdateTime < game.timebase () && m_blindTime < game.timebase () && m_seeEnemyTime + 1.0f < game.timebase ()) { + if (!yb_ignore_enemies.bool_ () && m_soundUpdateTime < game.time () && m_blindTime < game.time () && m_seeEnemyTime + 1.0f < game.time ()) { updateHearing (); - m_soundUpdateTime = game.timebase () + 0.25f; + m_soundUpdateTime = game.time () + 0.25f; } - else if (m_heardSoundTime < game.timebase ()) { + else if (m_heardSoundTime < game.time ()) { m_states &= ~Sense::HearingEnemy; } @@ -1809,10 +1816,10 @@ void Bot::setConditions () { } // check if there are items needing to be used/collected - if (m_itemCheckTime < game.timebase () || !game.isNullEntity (m_pickupItem)) { + if (m_itemCheckTime < game.time () || !game.isNullEntity (m_pickupItem)) { updatePickups (); - m_itemCheckTime = game.timebase () + 0.5f; + m_itemCheckTime = game.time () + 0.5f; } filterTasks (); } @@ -1878,13 +1885,13 @@ void Bot::filterTasks () { if (util.isPlayer (m_lastEnemy) && !m_lastEnemyOrigin.empty () && !m_hasC4) { float retreatLevel = (100.0f - (pev->health > 50.0f ? 100.0f : pev->health)) * tempFear; // retreat level depends on bot health - if (m_numEnemiesLeft > m_numFriendsLeft * 0.5f && m_retreatTime < game.timebase () && m_seeEnemyTime - rg.float_ (2.0f, 4.0f) < game.timebase ()) { + if (m_numEnemiesLeft > m_numFriendsLeft * 0.5f && m_retreatTime < game.time () && m_seeEnemyTime - rg.float_ (2.0f, 4.0f) < game.time ()) { - float timeSeen = m_seeEnemyTime - game.timebase (); - float timeHeard = m_heardSoundTime - game.timebase (); + float timeSeen = m_seeEnemyTime - game.time (); + float timeHeard = m_heardSoundTime - game.time (); float ratio = 0.0f; - m_retreatTime = game.timebase () + rg.float_ (3.0f, 15.0f); + m_retreatTime = game.time () + rg.float_ (3.0f, 15.0f); if (timeSeen > timeHeard) { timeSeen += 10.0f; @@ -1900,7 +1907,7 @@ void Bot::filterTasks () { ratio /= 3.0f; // reduce the seek cover desire if bomb is planted } else if (m_isVIP || m_isReloading || (lowAmmo && usesSniper ())) { - ratio *= 2.0f; // triple the seek cover desire if bot is VIP or reloading + ratio *= 3.0f; // triple the seek cover desire if bot is VIP or reloading } else { ratio /= 2.0f; // reduce seek cover otherwise @@ -1912,7 +1919,7 @@ void Bot::filterTasks () { } // if half of the round is over, allow hunting - if (getCurrentTaskId () != Task::EscapeFromBomb && game.isNullEntity (m_enemy) && bots.getRoundMidTime () < game.timebase () && !m_isUsingGrenade && m_currentNodeIndex != graph.getNearest (m_lastEnemyOrigin) && m_personality != Personality::Careful && !yb_ignore_enemies.bool_ ()) { + if (getCurrentTaskId () != Task::EscapeFromBomb && game.isNullEntity (m_enemy) && bots.getRoundMidTime () < game.time () && !m_isUsingGrenade && m_currentNodeIndex != graph.getNearest (m_lastEnemyOrigin) && m_personality != Personality::Careful && !yb_ignore_enemies.bool_ ()) { float desireLevel = 4096.0f - ((1.0f - tempAgression) * (m_lastEnemyOrigin - pev->origin).length ()); desireLevel = (100.0f * desireLevel) / 4096.0f; @@ -1933,7 +1940,7 @@ void Bot::filterTasks () { } // blinded behavior - blindedDesire = m_blindTime > game.timebase () ? TaskPri::Blind : 0.0f; + blindedDesire = m_blindTime > game.time () ? TaskPri::Blind : 0.0f; // now we've initialized all the desires go through the hard work // of filtering all actions against each other to pick the most @@ -2135,7 +2142,7 @@ bool Bot::reactOnEnemy () { return false; } - if (m_enemyReachableTimer < game.timebase ()) { + if (m_enemyReachableTimer < game.time ()) { int ownIndex = m_currentNodeIndex; if (ownIndex == kInvalidNodeIndex) { @@ -2152,11 +2159,11 @@ bool Bot::reactOnEnemy () { else { m_isEnemyReachable = true; } - m_enemyReachableTimer = game.timebase () + 1.0f; + m_enemyReachableTimer = game.time () + 1.0f; } if (m_isEnemyReachable) { - m_navTimeset = game.timebase (); // override existing movement by attack movement + m_navTimeset = game.time (); // override existing movement by attack movement return true; } return false; @@ -2214,7 +2221,7 @@ void Bot::checkRadioQueue () { Task taskID = getCurrentTaskId (); if (taskID == Task::Pause || taskID == Task::Camp) { - getTask ()->time = game.timebase (); + getTask ()->time = game.time (); } startTask (Task::FollowUser, TaskPri::FollowUser, kInvalidNodeIndex, 0.0f, true); } @@ -2250,7 +2257,7 @@ void Bot::checkRadioQueue () { m_campButtons = 0; - startTask (Task::Pause, TaskPri::Pause, kInvalidNodeIndex, game.timebase () + rg.float_ (30.0f, 60.0f), false); + startTask (Task::Pause, TaskPri::Pause, kInvalidNodeIndex, game.time () + rg.float_ (30.0f, 60.0f), false); } } break; @@ -2261,7 +2268,7 @@ void Bot::checkRadioQueue () { case Radio::TakingFireNeedAssistance: if (game.isNullEntity (m_targetEntity)) { - if (game.isNullEntity (m_enemy) && m_seeEnemyTime + 4.0f < game.timebase ()) { + if (game.isNullEntity (m_enemy) && m_seeEnemyTime + 4.0f < game.time ()) { // decrease fear levels to lower probability of bot seeking cover again m_fearLevel -= 0.2f; @@ -2293,7 +2300,7 @@ void Bot::checkRadioQueue () { case Radio::NeedBackup: case Chatter::ScaredEmotion: case Chatter::PinnedDown: - if (((game.isNullEntity (m_enemy) && seesEntity (m_radioEntity->v.origin)) || distance < 2048.0f || !m_moveToC4) && rg.chance (50) && m_seeEnemyTime + 4.0f < game.timebase ()) { + if (((game.isNullEntity (m_enemy) && seesEntity (m_radioEntity->v.origin)) || distance < 2048.0f || !m_moveToC4) && rg.chance (50) && m_seeEnemyTime + 4.0f < game.time ()) { m_fearLevel -= 0.1f; if (m_fearLevel < 0.0f) { @@ -2341,7 +2348,7 @@ void Bot::checkRadioQueue () { pushRadioMessage (Radio::RogerThat); // don't pause/camp anymore - getTask ()->time = game.timebase (); + getTask ()->time = game.time (); m_targetEntity = nullptr; m_position = m_radioEntity->v.origin + m_radioEntity->v.v_angle.forward () * rg.float_ (1024.0f, 2048.0f); @@ -2396,7 +2403,7 @@ void Bot::checkRadioQueue () { Task taskID = getCurrentTaskId (); if (taskID == Task::Pause || taskID == Task::Camp) { - getTask ()->time = game.timebase (); + getTask ()->time = game.time (); } m_targetEntity = nullptr; m_position = m_radioEntity->v.origin + m_radioEntity->v.v_angle.forward () * rg.float_ (1024.0f, 2048.0f); @@ -2437,10 +2444,10 @@ void Bot::checkRadioQueue () { Task taskID = getCurrentTaskId (); if (taskID == Task::Pause) { - getTask ()->time = game.timebase (); + getTask ()->time = game.time (); } m_targetEntity = nullptr; - m_seeEnemyTime = game.timebase (); + m_seeEnemyTime = game.time (); // if bot has no enemy if (m_lastEnemyOrigin.empty ()) { @@ -2549,7 +2556,7 @@ void Bot::checkRadioQueue () { } // check if it's a ct command - if (game.getTeam (m_radioEntity) == Team::CT && m_team == Team::CT && util.isFakeClient (m_radioEntity) && bots.getPlantedBombSearchTimestamp () < game.timebase ()) { + if (game.getTeam (m_radioEntity) == Team::CT && m_team == Team::CT && util.isFakeClient (m_radioEntity) && bots.getPlantedBombSearchTimestamp () < game.time ()) { float minDistance = kInfiniteDistance; int bombPoint = kInvalidNodeIndex; @@ -2575,7 +2582,7 @@ void Bot::checkRadioQueue () { } graph.setVisited (bombPoint); } - bots.setPlantedBombSearchTimestamp (game.timebase () + 0.5f); + bots.setPlantedBombSearchTimestamp (game.time () + 0.5f); } break; @@ -2584,18 +2591,18 @@ void Bot::checkRadioQueue () { pushRadioMessage (Radio::RogerThat); if (getCurrentTaskId () == Task::Camp) { - getTask ()->time = game.timebase () + rg.float_ (30.0f, 60.0f); + getTask ()->time = game.time () + rg.float_ (30.0f, 60.0f); } else { // don't pause anymore Task taskID = getCurrentTaskId (); if (taskID == Task::Pause) { - getTask ()->time = game.timebase (); + getTask ()->time = game.time (); } m_targetEntity = nullptr; - m_seeEnemyTime = game.timebase (); + m_seeEnemyTime = game.time (); // if bot has no enemy if (m_lastEnemyOrigin.empty ()) { @@ -2622,9 +2629,9 @@ void Bot::checkRadioQueue () { int index = findDefendNode (m_radioEntity->v.origin); // push camp task on to stack - startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, game.timebase () + rg.float_ (30.0f, 60.0f), true); + startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, game.time () + rg.float_ (30.0f, 60.0f), true); // push move command - startTask (Task::MoveToPosition, TaskPri::MoveToPosition, index, game.timebase () + rg.float_ (30.0f, 60.0f), true); + startTask (Task::MoveToPosition, TaskPri::MoveToPosition, index, game.time () + rg.float_ (30.0f, 60.0f), true); if (graph[index].vis.crouch <= graph[index].vis.stand) { m_campButtons |= IN_DUCK; @@ -2642,15 +2649,15 @@ void Bot::checkRadioQueue () { void Bot::tryHeadTowardRadioMessage () { Task taskID = getCurrentTaskId (); - if (taskID == Task::MoveToPosition || m_headedTime + 15.0f < game.timebase () || !util.isAlive (m_radioEntity) || m_hasC4) { + if (taskID == Task::MoveToPosition || m_headedTime + 15.0f < game.time () || !util.isAlive (m_radioEntity) || m_hasC4) { return; } if ((util.isFakeClient (m_radioEntity) && rg.chance (25) && m_personality == Personality::Normal) || !(m_radioEntity->v.flags & FL_FAKECLIENT)) { if (taskID == Task::Pause || taskID == Task::Camp) { - getTask ()->time = game.timebase (); + getTask ()->time = game.time (); } - m_headedTime = game.timebase (); + m_headedTime = game.time (); m_position = m_radioEntity->v.origin; clearSearchNodes (); @@ -2701,7 +2708,7 @@ void Bot::updateAimDir () { m_lookAt = m_lastEnemyOrigin; // did bot just see enemy and is quite aggressive? - if (m_seeEnemyTime + 1.0f - m_actualReactionTime + m_baseAgressionLevel > game.timebase ()) { + if (m_seeEnemyTime + 1.0f - m_actualReactionTime + m_baseAgressionLevel > game.time ()) { // feel free to fire if shootable if (!usesSniper () && lastEnemyShootable ()) { @@ -2712,7 +2719,7 @@ void Bot::updateAimDir () { else if (flags & AimFlags::PredictPath) { bool changePredictedEnemy = true; - if (m_timeNextTracking > game.timebase () && m_trackingEdict == m_lastEnemy && util.isAlive (m_lastEnemy)) { + if (m_timeNextTracking > game.time () && m_trackingEdict == m_lastEnemy && util.isAlive (m_lastEnemy)) { changePredictedEnemy = false; } @@ -2723,7 +2730,7 @@ void Bot::updateAimDir () { m_lookAt = graph[aimPoint].origin; m_camp = m_lookAt; - m_timeNextTracking = game.timebase () + 0.5f; + m_timeNextTracking = game.time () + 0.5f; m_trackingEdict = m_lastEnemy; } else { @@ -2761,12 +2768,12 @@ void Bot::updateAimDir () { void Bot::checkDarkness () { // do not check for darkness at the start of the round - if (m_spawnTime + 5.0f > game.timebase () || !graph.exists (m_currentNodeIndex) || cr::fzero (m_path->light)) { + if (m_spawnTime + 5.0f > game.time () || !graph.exists (m_currentNodeIndex) || cr::fzero (m_path->light)) { return; } // do not check every frame - if (m_checkDarkTime + 5.0f > game.timebase ()) { + if (m_checkDarkTime + 5.0f > game.time ()) { return; } auto skyColor = illum.getSkyColor (); @@ -2774,10 +2781,10 @@ void Bot::checkDarkness () { if (mp_flashlight.bool_ () && !m_hasNVG) { auto task = Task (); - if (!(pev->effects & EF_DIMLIGHT) && task != Task::Camp && task != Task::Attack && m_heardSoundTime + 3.0f < game.timebase () && m_flashLevel > 30.0f && ((skyColor > 50.0f && m_path->light < 10.0f) || (skyColor <= 50.0f && m_path->light < 40.0f))) { + if (!(pev->effects & EF_DIMLIGHT) && task != Task::Camp && task != Task::Attack && m_heardSoundTime + 3.0f < game.time () && m_flashLevel > 30.0f && ((skyColor > 50.0f && m_path->light < 10.0f) || (skyColor <= 50.0f && m_path->light < 40.0f))) { pev->impulse = 100; } - else if ((pev->effects & EF_DIMLIGHT) && (((m_path->light > 15.0f && skyColor > 50.0f) || (m_path->light > 45.0f && skyColor <= 50.0f)) || task == Task::Camp || task == Task::Attack || m_flashLevel <= 0 || m_heardSoundTime + 3.0f >= game.timebase ())) + else if ((pev->effects & EF_DIMLIGHT) && (((m_path->light > 15.0f && skyColor > 50.0f) || (m_path->light > 45.0f && skyColor <= 50.0f)) || task == Task::Camp || task == Task::Attack || m_flashLevel <= 0 || m_heardSoundTime + 3.0f >= game.time ())) { pev->impulse = 100; } @@ -2793,7 +2800,7 @@ void Bot::checkDarkness () { game.botCommand (ent (), "nightvision"); } } - m_checkDarkTime = game.timebase (); + m_checkDarkTime = game.time (); } void Bot::checkParachute () { @@ -2805,36 +2812,62 @@ void Bot::checkParachute () { m_fallDownTime = 0.0f; } else if (cr::fzero (m_fallDownTime)) { - m_fallDownTime = game.timebase (); + m_fallDownTime = game.time (); } // press use anyway - if (!cr::fzero (m_fallDownTime) && m_fallDownTime + 0.35f < game.timebase ()) { + if (!cr::fzero (m_fallDownTime) && m_fallDownTime + 0.35f < game.time ()) { pev->button |= IN_USE; } } } -void Bot::slowFrame () { - if (m_thinkFps <= game.timebase ()) { - // execute delayed think - fastFrame (); - - // skip some frames - m_thinkFps = game.timebase () + m_thinkInterval; +void Bot::frame () { + if (m_updateTime <= game.time ()) { + update (); } else if (m_notKilled) { updateLookAngles (); } + + if (m_slowFrameTimestamp > game.time ()) { + return; + } + m_numFriendsLeft = numFriendsNear (pev->origin, kInfiniteDistance); + m_numEnemiesLeft = numEnemiesNear (pev->origin, kInfiniteDistance); + + if (bots.isBombPlanted () && m_team == Team::CT && m_notKilled) { + const Vector &bombPosition = graph.getBombPos (); + + if (!m_hasProgressBar && getCurrentTaskId () != Task::EscapeFromBomb && (pev->origin - bombPosition).lengthSq () < cr::square (1540.0f) && !isBombDefusing (bombPosition)) { + m_itemIgnore = nullptr; + m_itemCheckTime = game.time (); + + clearTask (getCurrentTaskId ()); + } + } + checkSpawnConditions (); + checkForChat (); + + if (game.is (GameFlags::HasBotVoice)) { + showChaterIcon (false); // end voice feedback + } + + // clear enemy far away + if (!m_lastEnemyOrigin.empty () && !game.isNullEntity (m_lastEnemy) && (pev->origin - m_lastEnemyOrigin).lengthSq () >= cr::square (1600.0f)) { + m_lastEnemy = nullptr; + m_lastEnemyOrigin = nullptr; + } + m_slowFrameTimestamp = game.time () + 0.5f; } -void Bot::fastFrame () { +void Bot::update () { pev->button = 0; pev->flags |= FL_FAKECLIENT; // restore fake client bit, if it were removed by some evil action =) m_moveSpeed = 0.0f; m_strafeSpeed = 0.0f; - m_moveAngles= nullvec; + m_moveAngles= nullptr; m_canChooseAimDirection = true; m_notKilled = util.isAlive (ent ()); @@ -2883,41 +2916,12 @@ void Bot::fastFrame () { checkMsgQueue (); // check for pending messages if (botMovement) { - runAI (); // execute main code + logic (); // execute main code } - runMovement (); // run the player movement -} + runMovement (); -void Bot::frame () { - if (m_slowFrameTimestamp > game.timebase ()) { - return; - } - m_numFriendsLeft = numFriendsNear (pev->origin, kInfiniteDistance); - m_numEnemiesLeft = numEnemiesNear (pev->origin, kInfiniteDistance); - - if (bots.isBombPlanted () && m_team == Team::CT && m_notKilled) { - const Vector &bombPosition = graph.getBombPos (); - - if (!m_hasProgressBar && getCurrentTaskId () != Task::EscapeFromBomb && (pev->origin - bombPosition).lengthSq () < cr::square (1540.0f) && !isBombDefusing (bombPosition)) { - m_itemIgnore = nullptr; - m_itemCheckTime = game.timebase (); - - clearTask (getCurrentTaskId ()); - } - } - checkSpawnConditions (); - checkForChat (); - - if (game.is (GameFlags::HasBotVoice)) { - showChaterIcon (false); // end voice feedback - } - - // clear enemy far away - if (!m_lastEnemyOrigin.empty () && !game.isNullEntity (m_lastEnemy) && (pev->origin - m_lastEnemyOrigin).lengthSq () >= cr::square (1600.0f)) { - m_lastEnemy = nullptr; - m_lastEnemyOrigin= nullvec; - } - m_slowFrameTimestamp = game.timebase () + 0.5f; + // delay next execution + m_updateTime = game.time () + m_updateInterval; } void Bot::normal_ () { @@ -2943,14 +2947,14 @@ void Bot::normal_ () { } // bots rushing with knife, when have no enemy (thanks for idea to nicebot project) - if (m_currentWeapon == Weapon::Knife && (game.isNullEntity (m_lastEnemy) || !util.isAlive (m_lastEnemy)) && game.isNullEntity (m_enemy) && m_knifeAttackTime < game.timebase () && !hasShield () && numFriendsNear (pev->origin, 96.0f) == 0) { + if (m_currentWeapon == Weapon::Knife && (game.isNullEntity (m_lastEnemy) || !util.isAlive (m_lastEnemy)) && game.isNullEntity (m_enemy) && m_knifeAttackTime < game.time () && !hasShield () && numFriendsNear (pev->origin, 96.0f) == 0) { if (rg.chance (40)) { pev->button |= IN_ATTACK; } else { pev->button |= IN_ATTACK2; } - m_knifeAttackTime = game.timebase () + rg.float_ (2.5f, 6.0f); + m_knifeAttackTime = game.time () + rg.float_ (2.5f, 6.0f); } const auto &prop = conf.getWeaponProp (m_currentWeapon); @@ -2976,9 +2980,9 @@ void Bot::normal_ () { m_prevGoalIndex = kInvalidNodeIndex; // spray logo sometimes if allowed to do so - if (m_timeLogoSpray < game.timebase () && yb_spraypaints.bool_ () && rg.chance (60) && m_moveSpeed > getShiftSpeed () && game.isNullEntity (m_pickupItem)) { + if (m_timeLogoSpray < game.time () && yb_spraypaints.bool_ () && rg.chance (60) && m_moveSpeed > getShiftSpeed () && game.isNullEntity (m_pickupItem)) { if (!(game.mapIs (MapFlags::Demolition) && bots.isBombPlanted () && m_team == Team::CT)) { - startTask (Task::Spraypaint, TaskPri::Spraypaint, kInvalidNodeIndex, game.timebase () + 1.0f, false); + startTask (Task::Spraypaint, TaskPri::Spraypaint, kInvalidNodeIndex, game.time () + 1.0f, false); } } @@ -2986,7 +2990,7 @@ void Bot::normal_ () { if ((m_path->flags & NodeFlag::Camp) && !game.is (GameFlags::CSDM) && yb_camping_allowed.bool_ ()) { // check if bot has got a primary weapon and hasn't camped before - if (hasPrimaryWeapon () && m_timeCamping + 10.0f < game.timebase () && !hasHostage ()) { + if (hasPrimaryWeapon () && m_timeCamping + 10.0f < game.time () && !hasHostage ()) { bool campingAllowed = true; // Check if it's not allowed for this team to camp here @@ -3007,7 +3011,7 @@ void Bot::normal_ () { } // check if another bot is already camping here - if (campingAllowed && isOccupiedPoint (m_currentNodeIndex)) { + if (campingAllowed && isOccupiedNode (m_currentNodeIndex)) { campingAllowed = false; } @@ -3024,7 +3028,7 @@ void Bot::normal_ () { if (!(m_states & (Sense::SeeingEnemy | Sense::HearingEnemy)) && !m_reloadState) { m_reloadState = Reload::Primary; } - m_timeCamping = game.timebase () + rg.float_ (10.0f, 25.0f); + m_timeCamping = game.time () + rg.float_ (10.0f, 25.0f); startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, m_timeCamping, true); m_camp = m_path->origin + m_path->start.forward () * 500.0f;; @@ -3056,8 +3060,8 @@ void Bot::normal_ () { else if (m_team == Team::Terrorist && rg.chance (75)) { int index = findDefendNode (m_path->origin); - startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, game.timebase () + rg.float_ (60.0f, 120.0f), true); // push camp task on to stack - startTask (Task::MoveToPosition, TaskPri::MoveToPosition, index, game.timebase () + rg.float_ (5.0f, 10.0f), true); // push move command + startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, game.time () + rg.float_ (60.0f, 120.0f), true); // push camp task on to stack + startTask (Task::MoveToPosition, TaskPri::MoveToPosition, index, game.time () + rg.float_ (5.0f, 10.0f), true); // push move command auto &path = graph[index]; @@ -3079,7 +3083,7 @@ void Bot::normal_ () { pushRadioMessage (Radio::NeedBackup); pushChatterMessage (Chatter::ScaredEmotion); - startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, game.timebase () + rg.float_ (4.0f, 8.0f), true); + startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, game.time () + rg.float_ (4.0f, 8.0f), true); } else { startTask (Task::PlantBomb, TaskPri::PlantBomb, kInvalidNodeIndex, 0.0f, false); @@ -3095,8 +3099,8 @@ void Bot::normal_ () { if (m_personality == Personality::Rusher) { campTime *= 0.5f; } - startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, game.timebase () + campTime, true); // push camp task on to stack - startTask (Task::MoveToPosition, TaskPri::MoveToPosition, index, game.timebase () + rg.float_ (5.0f, 11.0f), true); // push move command + startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, game.time () + campTime, true); // push camp task on to stack + startTask (Task::MoveToPosition, TaskPri::MoveToPosition, index, game.time () + rg.float_ (5.0f, 11.0f), true); // push move command auto &path = graph[index]; @@ -3128,9 +3132,16 @@ void Bot::normal_ () { // remember index getTask ()->data = destIndex; + auto pathSearchType = m_pathType; + + // override with fast path + if (game.mapIs (MapFlags::Demolition) && bots.isBombPlanted ()) { + pathSearchType = FindPath::Fast; + } + // do pathfinding if it's not the current waypoint if (destIndex != m_currentNodeIndex) { - findPath (m_currentNodeIndex, destIndex, m_pathType); + findPath (m_currentNodeIndex, destIndex, pathSearchType); } } else { @@ -3140,20 +3151,20 @@ void Bot::normal_ () { } float shiftSpeed = getShiftSpeed (); - if ((!cr::fzero (m_moveSpeed) && m_moveSpeed > shiftSpeed) && (yb_walking_allowed.bool_ () && mp_footsteps.bool_ ()) && m_difficulty > 2 && !(m_aimFlags & AimFlags::Enemy) && (m_heardSoundTime + 6.0f >= game.timebase () || (m_states & Sense::SuspectEnemy)) && numEnemiesNear (pev->origin, 768.0f) >= 1 && !yb_jasonmode.bool_ () && !bots.isBombPlanted ()) { + if ((!cr::fzero (m_moveSpeed) && m_moveSpeed > shiftSpeed) && (yb_walking_allowed.bool_ () && mp_footsteps.bool_ ()) && m_difficulty > 2 && !(m_aimFlags & AimFlags::Enemy) && (m_heardSoundTime + 6.0f >= game.time () || (m_states & Sense::SuspectEnemy)) && numEnemiesNear (pev->origin, 768.0f) >= 1 && !yb_jasonmode.bool_ () && !bots.isBombPlanted ()) { m_moveSpeed = shiftSpeed; } // bot hasn't seen anything in a long time and is asking his teammates to report in - if (yb_radio_mode.int_ () > 1 && m_seeEnemyTime + rg.float_ (45.0f, 80.0f) < game.timebase () && bots.getLastRadio (m_team) != Radio::ReportInTeam && rg.chance (15) && bots.getRoundStartTime () + 20.0f < game.timebase () && m_askCheckTime < game.timebase () && numFriendsNear (pev->origin, 1024.0f) == 0) { + if (yb_radio_mode.int_ () > 1 && m_seeEnemyTime + rg.float_ (45.0f, 80.0f) < game.time () && bots.getLastRadio (m_team) != Radio::ReportInTeam && rg.chance (15) && bots.getRoundStartTime () + 20.0f < game.time () && m_askCheckTime < game.time () && numFriendsNear (pev->origin, 1024.0f) == 0) { pushRadioMessage (Radio::ReportInTeam); - m_askCheckTime = game.timebase () + rg.float_ (45.0f, 80.0f); + m_askCheckTime = game.time () + rg.float_ (45.0f, 80.0f); // make sure everyone else will not ask next few moments for (const auto &bot : bots) { if (bot->m_notKilled) { - bot->m_askCheckTime = game.timebase () + rg.float_ (5.0f, 30.0f); + bot->m_askCheckTime = game.time () + rg.float_ (5.0f, 30.0f); } } } @@ -3163,7 +3174,7 @@ void Bot::spraypaint_ () { m_aimFlags |= AimFlags::Entity; // bot didn't spray this round? - if (m_timeLogoSpray < game.timebase () && getTask ()->time > game.timebase ()) { + if (m_timeLogoSpray < game.time () && getTask ()->time > game.time ()) { const auto &forward = pev->v_angle.forward (); Vector sprayOrigin = getEyesPos () + forward * 128.0f; @@ -3176,14 +3187,14 @@ void Bot::spraypaint_ () { } m_entity = sprayOrigin; - if (getTask ()->time - 0.5f < game.timebase ()) { + if (getTask ()->time - 0.5f < game.time ()) { // emit spraycan sound engfuncs.pfnEmitSound (ent (), CHAN_VOICE, "player/sprayer.wav", 1.0f, ATTN_NORM, 0, 100); game.testLine (getEyesPos (), getEyesPos () + forward * 128.0f, TraceIgnore::Monsters, ent (), &tr); // paint the actual logo decal util.traceDecals (pev, &tr, m_logotypeIndex); - m_timeLogoSpray = game.timebase () + rg.float_ (60.0f, 90.0f); + m_timeLogoSpray = game.time () + rg.float_ (60.0f, 90.0f); } } else { @@ -3192,7 +3203,7 @@ void Bot::spraypaint_ () { m_moveToGoal = false; m_checkTerrain = false; - m_navTimeset = game.timebase (); + m_navTimeset = game.time (); m_moveSpeed = 0.0f; m_strafeSpeed = 0.0f; @@ -3213,6 +3224,7 @@ void Bot::huntEnemy_ () { // don't hunt down our teammate... clearTask (Task::Hunt); + m_prevGoalIndex = kInvalidNodeIndex; m_lastEnemy = nullptr; } @@ -3222,10 +3234,11 @@ void Bot::huntEnemy_ () { completeTask (); m_prevGoalIndex = kInvalidNodeIndex; - m_lastEnemyOrigin= nullvec; + m_lastEnemyOrigin= nullptr; } - else if (!hasActiveGoal ()) // do we need to calculate a new path? - { + + // do we need to calculate a new path? + else if (!hasActiveGoal ()) { clearSearchNodes (); int destIndex = kInvalidNodeIndex; @@ -3251,11 +3264,11 @@ void Bot::huntEnemy_ () { } // bots skill higher than 60? - if (yb_walking_allowed.bool_ () && mp_footsteps.bool_ () && m_difficulty > 1 && !yb_jasonmode.bool_ ()) { + if (yb_walking_allowed.bool_ () && mp_footsteps.bool_ () && m_difficulty > 2 && !yb_jasonmode.bool_ ()) { // then make him move slow if near enemy if (!(m_currentTravelFlags & PathFlag::Jump)) { if (m_currentNodeIndex != kInvalidNodeIndex) { - if (m_path->radius < 32.0f && !isOnLadder () && !isInWater () && m_seeEnemyTime + 4.0f > game.timebase () && m_difficulty < 3) { + if (m_path->radius < 32.0f && !isOnLadder () && !isInWater () && m_seeEnemyTime + 4.0f > game.time () && m_difficulty < 3) { pev->button |= IN_DUCK; } } @@ -3282,7 +3295,7 @@ void Bot::seekCover_ () { m_prevGoalIndex = kInvalidNodeIndex; // start hide task - startTask (Task::Hide, TaskPri::Hide, kInvalidNodeIndex, game.timebase () + rg.float_ (3.0f, 12.0f), false); + startTask (Task::Hide, TaskPri::Hide, kInvalidNodeIndex, game.time () + rg.float_ (3.0f, 12.0f), false); Vector dest = m_lastEnemyOrigin; // get a valid look direction @@ -3337,7 +3350,7 @@ void Bot::seekCover_ () { destIndex = findCoverNode (usesSniper () ? 256.0f : 512.0f); if (destIndex == kInvalidNodeIndex) { - m_retreatTime = game.timebase () + rg.float_ (5.0f, 10.0f); + m_retreatTime = game.time () + rg.float_ (5.0f, 10.0f); m_prevGoalIndex = kInvalidNodeIndex; completeTask (); @@ -3376,14 +3389,14 @@ void Bot::attackEnemy_ () { completeTask (); m_destOrigin = m_lastEnemyOrigin; } - m_navTimeset = game.timebase (); + m_navTimeset = game.time (); } void Bot::pause_ () { m_moveToGoal = false; m_checkTerrain = false; - m_navTimeset = game.timebase (); + m_navTimeset = game.time (); m_moveSpeed = 0.0f; m_strafeSpeed = 0.0f; @@ -3407,7 +3420,7 @@ void Bot::pause_ () { } // stop camping if time over or gets hurt by something else than bullets - if (getTask ()->time < game.timebase () || m_lastDamageType > 0) { + if (getTask ()->time < game.time () || m_lastDamageType > 0) { completeTask (); } } @@ -3415,7 +3428,7 @@ void Bot::pause_ () { void Bot::blind_ () { m_moveToGoal = false; m_checkTerrain = false; - m_navTimeset = game.timebase (); + m_navTimeset = game.time (); // if bot remembers last enemy position if (m_difficulty >= 2 && !m_lastEnemyOrigin.empty () && util.isPlayer (m_lastEnemy) && !usesSniper ()) { @@ -3427,7 +3440,7 @@ void Bot::blind_ () { m_strafeSpeed = m_blindSidemoveSpeed; pev->button |= m_blindButton; - if (m_blindTime < game.timebase ()) { + if (m_blindTime < game.time ()) { completeTask (); } } @@ -3452,16 +3465,16 @@ void Bot::camp_ () { setIdealReactionTimers (); m_idealReactionTime *= 0.5f; - m_navTimeset = game.timebase (); - m_timeCamping = game.timebase (); + m_navTimeset = game.time (); + m_timeCamping = game.time (); m_moveSpeed = 0.0f; m_strafeSpeed = 0.0f; findValidNode (); - if (m_nextCampDirTime < game.timebase ()) { - m_nextCampDirTime = game.timebase () + rg.float_ (2.0f, 5.0f); + if (m_nextCampDirTime < game.time ()) { + m_nextCampDirTime = game.time () + rg.float_ (2.0f, 5.0f); if (m_path->flags & NodeFlag::Camp) { Vector dest; @@ -3530,7 +3543,7 @@ void Bot::camp_ () { pev->button |= m_campButtons; // stop camping if time over or gets hurt by something else than bullets - if (getTask ()->time < game.timebase () || m_lastDamageType > 0) { + if (getTask ()->time < game.time () || m_lastDamageType > 0) { completeTask (); } } @@ -3544,7 +3557,7 @@ void Bot::hide_ () { setIdealReactionTimers (); m_idealReactionTime *= 0.5f; - m_navTimeset = game.timebase (); + m_navTimeset = game.time (); m_moveSpeed = 0.0f; m_strafeSpeed = 0.0f; @@ -3588,14 +3601,14 @@ void Bot::hide_ () { } pev->button |= m_campButtons; - m_navTimeset = game.timebase (); + m_navTimeset = game.time (); if (!m_isReloading) { checkReload (); } // stop camping if time over or gets hurt by something else than bullets - if (getTask ()->time < game.timebase () || m_lastDamageType > 0) { + if (getTask ()->time < game.time () || m_lastDamageType > 0) { completeTask (); } } @@ -3612,7 +3625,7 @@ void Bot::moveToPos_ () { completeTask (); // we're done m_prevGoalIndex = kInvalidNodeIndex; - m_position= nullvec; + m_position= nullptr; } // didn't choose goal waypoint yet? @@ -3656,7 +3669,7 @@ void Bot::plantBomb_ () { else { m_moveToGoal = false; m_checkTerrain = false; - m_navTimeset = game.timebase (); + m_navTimeset = game.time (); if (m_path->flags & NodeFlag::Crouch) { pev->button |= (IN_ATTACK | IN_DUCK); @@ -3683,10 +3696,10 @@ void Bot::plantBomb_ () { float guardTime = mp_c4timer.float_ () * 0.5f + mp_c4timer.float_ () * 0.25f; // push camp task on to stack - startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, game.timebase () + guardTime, true); + startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, game.time () + guardTime, true); // push move command - startTask (Task::MoveToPosition, TaskPri::MoveToPosition, index, game.timebase () + guardTime, true); + startTask (Task::MoveToPosition, TaskPri::MoveToPosition, index, game.time () + guardTime, true); if (graph[index].vis.crouch <= graph[index].vis.stand) { m_campButtons |= IN_DUCK; @@ -3703,7 +3716,7 @@ void Bot::bombDefuse_ () { float defuseRemainingTime = fullDefuseTime; if (m_hasProgressBar /*&& isOnFloor ()*/) { - defuseRemainingTime = fullDefuseTime - game.timebase (); + defuseRemainingTime = fullDefuseTime - game.time (); } bool pickupExists = !game.isNullEntity (m_pickupItem); @@ -3758,8 +3771,8 @@ void Bot::bombDefuse_ () { m_checkTerrain = true; m_moveToGoal = true; - m_destOrigin= nullvec; - m_entity= nullvec; + m_destOrigin= nullptr; + m_entity= nullptr; m_pickupItem = nullptr; m_pickupType = Pickup::None; @@ -3807,7 +3820,7 @@ void Bot::bombDefuse_ () { pev->button |= IN_USE; // if defusing is not already started, maybe crouch before - if (!m_hasProgressBar && m_duckDefuseCheckTime < game.timebase ()) { + if (!m_hasProgressBar && m_duckDefuseCheckTime < game.time ()) { Vector botDuckOrigin, botStandOrigin; if (pev->button & IN_DUCK) { @@ -3830,7 +3843,7 @@ void Bot::bombDefuse_ () { m_duckDefuse = m_difficulty >= 2 && m_numEnemiesLeft != 0; // duck } } - m_duckDefuseCheckTime = game.timebase () + 5.0f; + m_duckDefuseCheckTime = game.time () + 5.0f; } // press duck button @@ -3846,7 +3859,7 @@ void Bot::bombDefuse_ () { pev->button |= IN_USE; m_reloadState = Reload::None; - m_navTimeset = game.timebase (); + m_navTimeset = game.time (); // don't move when defusing m_moveToGoal = false; @@ -3906,10 +3919,10 @@ void Bot::followUser_ () { m_moveSpeed = 0.0f; if (m_followWaitTime == 0.0f) { - m_followWaitTime = game.timebase (); + m_followWaitTime = game.time (); } else { - if (m_followWaitTime + 3.0f < game.timebase ()) { + if (m_followWaitTime + 3.0f < game.time ()) { // stop following if we have been waiting too long m_targetEntity = nullptr; @@ -3944,7 +3957,7 @@ void Bot::followUser_ () { for (auto &newIndex : points) { // if waypoint not yet used, assign it as dest - if (newIndex != m_currentNodeIndex && !isOccupiedPoint (newIndex)) { + if (newIndex != m_currentNodeIndex && !isOccupiedNode (newIndex)) { destIndex = newIndex; } } @@ -3982,7 +3995,7 @@ void Bot::throwExplosive_ () { if ((pev->origin - dest).lengthSq () < cr::square (400.0f)) { // heck, I don't wanna blow up myself - m_grenadeCheckTime = game.timebase () + kGrenadeCheckTime; + m_grenadeCheckTime = game.time () + kGrenadeCheckTime; selectBestWeapon (); completeTask (); @@ -3996,7 +4009,7 @@ void Bot::throwExplosive_ () { } if (m_grenade.lengthSq () <= 100.0f) { - m_grenadeCheckTime = game.timebase () + kGrenadeCheckTime; + m_grenadeCheckTime = game.time () + kGrenadeCheckTime; selectBestWeapon (); completeTask (); @@ -4048,7 +4061,7 @@ void Bot::throwFlashbang_ () { if ((pev->origin - dest).lengthSq () < cr::square (400.0f)) { // heck, I don't wanna blow up myself - m_grenadeCheckTime = game.timebase () + kGrenadeCheckTime; + m_grenadeCheckTime = game.time () + kGrenadeCheckTime; selectBestWeapon (); completeTask (); @@ -4062,7 +4075,7 @@ void Bot::throwFlashbang_ () { } if (m_grenade.lengthSq () <= 100.0f) { - m_grenadeCheckTime = game.timebase () + kGrenadeCheckTime; + m_grenadeCheckTime = game.time () + kGrenadeCheckTime; selectBestWeapon (); completeTask (); @@ -4117,7 +4130,7 @@ void Bot::throwSmoke_ () { m_grenade = (src - getEyesPos ()).normalize (); - if (getTask ()->time < game.timebase ()) { + if (getTask ()->time < game.time ()) { completeTask (); return; } @@ -4127,7 +4140,7 @@ void Bot::throwSmoke_ () { m_grenadeRequested = true; selectWeaponByName ("weapon_smokegrenade"); - getTask ()->time = game.timebase () + 1.2f; + getTask ()->time = game.time () + 1.2f; } else { m_grenadeRequested = false; @@ -4146,7 +4159,7 @@ void Bot::throwSmoke_ () { } void Bot::doublejump_ () { - if (!util.isAlive (m_doubleJumpEntity) || (m_aimFlags & AimFlags::Enemy) || (m_travelStartIndex != kInvalidNodeIndex && getTask ()->time + (graph.calculateTravelTime (pev->maxspeed, graph[m_travelStartIndex].origin, m_doubleJumpOrigin) + 11.0f) < game.timebase ())) { + if (!util.isAlive (m_doubleJumpEntity) || (m_aimFlags & AimFlags::Enemy) || (m_travelStartIndex != kInvalidNodeIndex && getTask ()->time + (graph.calculateTravelTime (pev->maxspeed, graph[m_travelStartIndex].origin, m_doubleJumpOrigin) + 11.0f) < game.time ())) { resetDoubleJump (); return; } @@ -4156,13 +4169,13 @@ void Bot::doublejump_ () { m_moveToGoal = false; m_checkTerrain = false; - m_navTimeset = game.timebase (); + m_navTimeset = game.time (); m_moveSpeed = 0.0f; m_strafeSpeed = 0.0f; bool inJump = (m_doubleJumpEntity->v.button & IN_JUMP) || (m_doubleJumpEntity->v.oldbuttons & IN_JUMP); - if (m_duckForJump < game.timebase ()) { + if (m_duckForJump < game.time ()) { pev->button |= IN_DUCK; } else if (inJump && !(m_oldButtons & IN_JUMP)) { @@ -4175,8 +4188,8 @@ void Bot::doublejump_ () { game.testLine (src, dest, TraceIgnore::None, ent (), &tr); if (tr.flFraction < 1.0f && tr.pHit == m_doubleJumpEntity && inJump) { - m_duckForJump = game.timebase () + rg.float_ (3.0f, 5.0f); - getTask ()->time = game.timebase (); + m_duckForJump = game.time () + rg.float_ (3.0f, 5.0f); + getTask ()->time = game.time (); } return; } @@ -4239,7 +4252,7 @@ void Bot::escapeFromBomb_ () { } // we're reached destination point so just sit down and camp - startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, game.timebase () + 10.0f, true); + startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, game.time () + 10.0f, true); } // didn't choose goal waypoint yet? @@ -4250,7 +4263,7 @@ void Bot::escapeFromBomb_ () { float safeRadius = rg.float_ (1248.0f, 2048.0f); for (int i = 0; i < graph.length (); ++i) { - if ((graph[i].origin - graph.getBombPos ()).length () < safeRadius || isOccupiedPoint (i)) { + if ((graph[i].origin - graph.getBombPos ()).length () < safeRadius || isOccupiedNode (i)) { continue; } int pathDistance = graph.getPathDist (m_currentNodeIndex, i); @@ -4270,7 +4283,7 @@ void Bot::escapeFromBomb_ () { completeTask (); // we're done // we have no destination point, so just sit down and camp - startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, game.timebase () + 10.0f, true); + startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, game.time () + 10.0f, true); return; } m_prevGoalIndex = lastSelectedGoal; @@ -4292,9 +4305,9 @@ void Bot::shootBreakable_ () { m_checkTerrain = false; m_moveToGoal = false; - m_navTimeset = game.timebase (); + m_navTimeset = game.time (); - Vector src = m_breakableOrigin; + const Vector &src = m_breakableOrigin; m_camp = src; // is bot facing the breakable? @@ -4306,6 +4319,7 @@ void Bot::shootBreakable_ () { selectBestWeapon (); } m_wantsToFire = true; + m_shootTime = game.time (); } else { m_checkTerrain = true; @@ -4320,7 +4334,7 @@ void Bot::pickupItem_ () { return; } - Vector dest = game.getAbsPos (m_pickupItem); + const Vector &dest = game.getAbsPos (m_pickupItem); m_destOrigin = dest; m_entity = dest; @@ -4365,7 +4379,7 @@ void Bot::pickupItem_ () { game.botCommand (ent (), "drop"); // discard both shield and pistol } } - processBuyzoneEntering (BuyState::PrimaryWeapon); + enteredBuyZone (BuyState::PrimaryWeapon); } else { // primary weapon @@ -4383,7 +4397,7 @@ void Bot::pickupItem_ () { break; } - processBuyzoneEntering (BuyState::PrimaryWeapon); + enteredBuyZone (BuyState::PrimaryWeapon); } checkSilencer (); // check the silencer } @@ -4470,7 +4484,7 @@ void Bot::pickupItem_ () { case Pickup::Button: m_aimFlags |= AimFlags::Entity; - if (game.isNullEntity (m_pickupItem) || m_buttonPushTime < game.timebase ()) { + if (game.isNullEntity (m_pickupItem) || m_buttonPushTime < game.time ()) { completeTask (); m_pickupType = Pickup::None; @@ -4493,7 +4507,7 @@ void Bot::pickupItem_ () { m_pickupItem = nullptr; m_pickupType = Pickup::None; - m_buttonPushTime = game.timebase () + 3.0f; + m_buttonPushTime = game.time () + 3.0f; completeTask (); } @@ -4614,9 +4628,9 @@ void Bot::checkSpawnConditions () { // this function is called instead of ai when buying finished, but freezetime is not yet left. // switch to knife if time to do this - if (m_checkKnifeSwitch && m_buyingFinished && m_spawnTime + rg.float_ (5.0f, 7.5f) < game.timebase ()) { + if (m_checkKnifeSwitch && m_buyingFinished && m_spawnTime + rg.float_ (5.0f, 7.5f) < game.time ()) { if (rg.int_ (1, 100) < 2 && yb_spraypaints.bool_ ()) { - startTask (Task::Spraypaint, TaskPri::Spraypaint, kInvalidNodeIndex, game.timebase () + 1.0f, false); + startTask (Task::Spraypaint, TaskPri::Spraypaint, kInvalidNodeIndex, game.time () + 1.0f, false); } if (m_difficulty >= 2 && rg.chance (m_personality == Personality::Rusher ? 99 : 50) && !m_isReloading && game.mapIs (MapFlags::HostageRescue | MapFlags::Demolition | MapFlags::Escape | MapFlags::Assassination)) { @@ -4636,7 +4650,7 @@ void Bot::checkSpawnConditions () { } // check if we already switched weapon mode - if (m_checkWeaponSwitch && m_buyingFinished && m_spawnTime + rg.float_ (3.0f, 4.5f) < game.timebase ()) { + if (m_checkWeaponSwitch && m_buyingFinished && m_spawnTime + rg.float_ (3.0f, 4.5f) < game.time ()) { if (hasShield () && isShieldDrawn ()) { pev->button |= IN_ATTACK2; } @@ -4664,7 +4678,7 @@ void Bot::checkSpawnConditions () { } } -void Bot::runAI () { +void Bot::logic () { // this function gets called each frame and is the core of all bot ai. from here all other subroutines are called float movedDistance = 2.0f; // length of different vector (distance bot moved) @@ -4683,18 +4697,18 @@ void Bot::runAI () { m_viewDistance = m_maxViewDistance; } - if (m_blindTime > game.timebase ()) { + if (m_blindTime > game.time ()) { m_maxViewDistance = 4096.0f; } m_moveSpeed = pev->maxspeed; - if (m_prevTime <= game.timebase ()) { + if (m_prevTime <= game.time ()) { // see how far bot has moved since the previous position... movedDistance = (m_prevOrigin - pev->origin).length (); // save current position as previous m_prevOrigin = pev->origin; - m_prevTime = game.timebase () + 0.2f; + m_prevTime = game.time () + 0.2f; } // if there's some radio message to respond, check it @@ -4749,12 +4763,12 @@ void Bot::runAI () { updateLookAngles (); // and turn to chosen aim direction // the bots wants to fire at something? - if (m_wantsToFire && !m_isUsingGrenade && m_shootTime <= game.timebase ()) { + if (m_wantsToFire && !m_isUsingGrenade && m_shootTime <= game.time ()) { fireWeapons (); // if bot didn't fire a bullet try again next frame } // check for reloading - if (m_reloadCheckTime <= game.timebase ()) { + if (m_reloadCheckTime <= game.time ()) { checkReload (); } @@ -4780,7 +4794,7 @@ void Bot::runAI () { if ((m_path->flags & NodeFlag::Crouch) && !(m_path->flags & (NodeFlag::Camp | NodeFlag::Goal))) { pev->button |= IN_DUCK; } - m_lastUsedNodesTime = game.timebase (); + m_lastUsedNodesTime = game.time (); // special movement for swimming here if (isInWater ()) { @@ -4819,7 +4833,7 @@ void Bot::runAI () { } // time to reach waypoint - if (m_navTimeset + getReachTime () < game.timebase () && game.isNullEntity (m_enemy)) { + if (m_navTimeset + getReachTime () < game.time () && game.isNullEntity (m_enemy)) { findValidNode (); m_breakableEntity = nullptr; @@ -4830,21 +4844,21 @@ void Bot::runAI () { m_itemIgnore = m_pickupItem; } - m_itemCheckTime = game.timebase () + 2.0f; + m_itemCheckTime = game.time () + 2.0f; m_pickupType = Pickup::None; m_pickupItem = nullptr; } } - if (m_duckTime >= game.timebase ()) { + if (m_duckTime >= game.time ()) { pev->button |= IN_DUCK; } if (pev->button & IN_JUMP) { - m_jumpTime = game.timebase (); + m_jumpTime = game.time (); } - if (m_jumpTime + 0.85f > game.timebase ()) { + if (m_jumpTime + 0.85f > game.time ()) { if (!isOnFloor () && !isInWater ()) { pev->button |= IN_DUCK; } @@ -4941,7 +4955,7 @@ void Bot::showDebugOverlay () { } if (!m_tasks.empty ()) { - if (taskID != getCurrentTaskId () || index != m_currentNodeIndex || goal != getTask ()->data || timeDebugUpdate < game.timebase ()) { + if (taskID != getCurrentTaskId () || index != m_currentNodeIndex || goal != getTask ()->data || timeDebugUpdate < game.time ()) { taskID = getCurrentTaskId (); index = m_currentNodeIndex; goal = getTask ()->data; @@ -4971,9 +4985,9 @@ void Bot::showDebugOverlay () { String weapon = STRING (util.getWeaponAlias (true, nullptr, m_currentWeapon)); String debugData; - debugData.assignf ("\n\n\n\n\n%s (H:%.1f/A:%.1f)- Task: %d=%s Desire:%.02f\nItem: %s Clip: %d Ammo: %d%s Money: %d AimFlags: %s\nSP=%.02f SSP=%.02f I=%d PG=%d G=%d T: %.02f MT: %d\nEnemy=%s Pickup=%s Type=%s\n", STRING (pev->netname), pev->health, pev->armorvalue, taskID, tasks[taskID].chars (), getTask ()->desire, weapon.chars (), getAmmoInClip (), getAmmo (), m_isReloading ? " (R)" : "", m_moneyAmount, aimFlags.trim ().chars (), m_moveSpeed, m_strafeSpeed, index, m_prevGoalIndex, goal, m_navTimeset - game.timebase (), pev->movetype, enemy.chars (), pickup.chars (), personalities[m_personality].chars ()); + debugData.assignf ("\n\n\n\n\n%s (H:%.1f/A:%.1f)- Task: %d=%s Desire:%.02f\nItem: %s Clip: %d Ammo: %d%s Money: %d AimFlags: %s\nSP=%.02f SSP=%.02f I=%d PG=%d G=%d T: %.02f MT: %d\nEnemy=%s Pickup=%s Type=%s\n", STRING (pev->netname), pev->health, pev->armorvalue, taskID, tasks[taskID].chars (), getTask ()->desire, weapon.chars (), getAmmoInClip (), getAmmo (), m_isReloading ? " (R)" : "", m_moneyAmount, aimFlags.trim ().chars (), m_moveSpeed, m_strafeSpeed, index, m_prevGoalIndex, goal, m_navTimeset - game.time (), pev->movetype, enemy.chars (), pickup.chars (), personalities[m_personality].chars ()); - MessageWriter (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, nullvec, game.getLocalEntity ()) + MessageWriter (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, nullptr, game.getLocalEntity ()) .writeByte (TE_TEXTMESSAGE) .writeByte (1) .writeShort (MessageWriter::fs16 (-1.0f, 13.0f)) @@ -4992,7 +5006,7 @@ void Bot::showDebugOverlay () { .writeShort (MessageWriter::fu16 (1.0f, 8.0f)) .writeString (debugData.chars ()); - timeDebugUpdate = game.timebase () + 1.0f; + timeDebugUpdate = game.time () + 1.0f; } // green = destination origin @@ -5034,7 +5048,7 @@ int Bot::getAmmo () { return m_ammo[prop.ammo1]; } -void Bot::processDamage (edict_t *inflictor, int damage, int armor, int bits) { +void Bot::takeDamage (edict_t *inflictor, int damage, int armor, int bits) { // this function gets called from the network message handler, when bot's gets hurt from any // other player. @@ -5045,7 +5059,7 @@ void Bot::processDamage (edict_t *inflictor, int damage, int armor, int bits) { if (yb_tkpunish.bool_ () && game.getTeam (inflictor) == m_team && !util.isFakeClient (inflictor)) { // alright, die you teamkiller!!! m_actualReactionTime = 0.0f; - m_seeEnemyTime = game.timebase (); + m_seeEnemyTime = game.time (); m_enemy = inflictor; m_lastEnemy = m_enemy; @@ -5053,7 +5067,7 @@ void Bot::processDamage (edict_t *inflictor, int damage, int armor, int bits) { m_enemyOrigin = m_enemy->v.origin; pushChatMessage (Chat::TeamAttack); - processChatterMessage ("#Bot_TeamAttack"); + handleChatter ("#Bot_TeamAttack"); pushChatterMessage (Chatter::FriendlyFire); } else { @@ -5079,7 +5093,7 @@ void Bot::processDamage (edict_t *inflictor, int damage, int armor, int bits) { m_lastEnemyOrigin = inflictor->v.origin; // FIXME - Bot doesn't necessary sees this enemy - m_seeEnemyTime = game.timebase (); + m_seeEnemyTime = game.time (); } if (!game.is (GameFlags::CSDM)) { @@ -5097,14 +5111,14 @@ void Bot::processDamage (edict_t *inflictor, int damage, int armor, int bits) { } } -void Bot::processBlind (int alpha) { +void Bot::takeBlind (int alpha) { // this function gets called by network message handler, when screenfade message get's send // it's used to make bot blind from the grenade. m_maxViewDistance = rg.float_ (10.0f, 20.0f); - m_blindTime = game.timebase () + static_cast (alpha - 200) / 16.0f; + m_blindTime = game.time () + static_cast (alpha - 200) / 16.0f; - if (m_blindTime < game.timebase ()) { + if (m_blindTime < game.time ()) { return; } m_enemy = nullptr; @@ -5202,11 +5216,11 @@ void Bot::updatePracticeDamage (edict_t *attacker, int damage) { graph.setDangerDamage (m_team, victimIndex, attackerIndex, damageValue); } -void Bot::processChatterMessage (const char *tempMessage) { +void Bot::handleChatter (const char *tempMessage) { // this function is added to prevent engine crashes with: 'Message XX started, before message XX ended', or something. if ((m_team == Team::CT && strcmp (tempMessage, "#CTs_Win") == 0) || (m_team == Team::Terrorist && strcmp (tempMessage, "#Terrorists_Win") == 0)) { - if (bots.getRoundMidTime () > game.timebase ()) { + if (bots.getRoundMidTime () > game.time ()) { pushChatterMessage (Chatter::QuickWonRound); } else { @@ -5253,7 +5267,7 @@ void Bot::dropWeaponForUser (edict_t *user, bool discardC4) { m_pickupItem = nullptr; m_pickupType = Pickup::None; - m_itemCheckTime = game.timebase () + 5.0f; + m_itemCheckTime = game.time () + 5.0f; if (m_inBuyZone) { m_ignoreBuyDelay = true; @@ -5261,7 +5275,7 @@ void Bot::dropWeaponForUser (edict_t *user, bool discardC4) { m_buyState = BuyState::PrimaryWeapon; pushMsgQueue (BotMsg::Buy); - m_nextBuyTime = game.timebase (); + m_nextBuyTime = game.time (); } } } @@ -5272,7 +5286,7 @@ void Bot::startDoubleJump (edict_t *ent) { m_doubleJumpOrigin = ent->v.origin; m_doubleJumpEntity = ent; - startTask (Task::DoubleJump, TaskPri::DoubleJump, kInvalidNodeIndex, game.timebase (), true); + startTask (Task::DoubleJump, TaskPri::DoubleJump, kInvalidNodeIndex, game.time (), true); sayTeam (strings.format ("Ok %s, i will help you!", STRING (ent->v.netname))); } @@ -5281,7 +5295,7 @@ void Bot::resetDoubleJump () { m_doubleJumpEntity = nullptr; m_duckForJump = 0.0f; - m_doubleJumpOrigin= nullvec; + m_doubleJumpOrigin= nullptr; m_travelStartIndex = kInvalidNodeIndex; m_jumpReady = false; } @@ -5333,7 +5347,7 @@ Vector Bot::calcToss (const Vector &start, const Vector &stop) { end.z -= 15.0f; if (cr::abs (end.z - start.z) > 500.0f) { - return nullvec; + return nullptr; } Vector midPoint = start + (end - start) * 0.5f; game.testHull (midPoint, midPoint + Vector (0.0f, 0.0f, 500.0f), TraceIgnore::Monsters, head_hull, ent (), &tr); @@ -5344,13 +5358,13 @@ Vector Bot::calcToss (const Vector &start, const Vector &stop) { } if (midPoint.z < start.z || midPoint.z < end.z) { - return nullvec; + return nullptr; } float timeOne = cr::sqrtf ((midPoint.z - start.z) / (0.5f * gravity)); float timeTwo = cr::sqrtf ((midPoint.z - end.z) / (0.5f * gravity)); if (timeOne < 0.1f) { - return nullvec; + return nullptr; } Vector velocity = (end - start) / (timeOne + timeTwo); velocity.z = gravity * timeOne; @@ -5361,7 +5375,7 @@ Vector Bot::calcToss (const Vector &start, const Vector &stop) { game.testHull (start, apex, TraceIgnore::None, head_hull, ent (), &tr); if (tr.flFraction < 1.0f || tr.fAllSolid) { - return nullvec; + return nullptr; } game.testHull (end, apex, TraceIgnore::Monsters, head_hull, ent (), &tr); @@ -5369,7 +5383,7 @@ Vector Bot::calcToss (const Vector &start, const Vector &stop) { float dot = -(tr.vecPlaneNormal | (apex - end).normalize ()); if (dot > 0.7f || tr.flFraction < 0.8f) { - return nullvec; + return nullptr; } } return velocity * 0.777f; @@ -5386,7 +5400,7 @@ Vector Bot::calcThrow (const Vector &start, const Vector &stop) { float time = velocity.length () / 195.0f; if (time < 0.01f) { - return nullvec; + return nullptr; } else if (time > 2.0f) { time = 1.2f; @@ -5400,7 +5414,7 @@ Vector Bot::calcThrow (const Vector &start, const Vector &stop) { game.testHull (start, apex, TraceIgnore::None, head_hull, ent (), &tr); if (tr.flFraction != 1.0f) { - return nullvec; + return nullptr; } game.testHull (stop, apex, TraceIgnore::Monsters, head_hull, ent (), &tr); @@ -5408,37 +5422,40 @@ Vector Bot::calcThrow (const Vector &start, const Vector &stop) { float dot = -(tr.vecPlaneNormal | (apex - stop).normalize ()); if (dot > 0.7f || tr.flFraction < 0.8f) { - return nullvec; + return nullptr; } } return velocity * 0.7793f; } edict_t *Bot::correctGrenadeVelocity (const char *model) { - edict_t *pent = nullptr; + edict_t *result = nullptr; + + game.searchEntities ("classname", "grenade", [&] (edict_t *ent) { + if (ent->v.owner == this->ent () && strcmp (STRING (ent->v.model) + 9, model) == 0) { + result = ent; - while (!game.isNullEntity (pent = engfuncs.pfnFindEntityByString (pent, "classname", "grenade"))) { - if (pent->v.owner == ent () && strcmp (STRING (pent->v.model) + 9, model) == 0) { // set the correct velocity for the grenade if (m_grenade.lengthSq () > 100.0f) { - pent->v.velocity = m_grenade; + ent->v.velocity = m_grenade; } - m_grenadeCheckTime = game.timebase () + kGrenadeCheckTime; + m_grenadeCheckTime = game.time () + kGrenadeCheckTime; selectBestWeapon (); completeTask (); - break; + return EntitySearchResult::Break; } - } - return pent; + return EntitySearchResult::Continue; + }); + return result; } Vector Bot::isBombAudible () { // this function checks if bomb is can be heard by the bot, calculations done by manual testing. if (!bots.isBombPlanted () || getCurrentTaskId () == Task::EscapeFromBomb) { - return nullvec; // reliability check + return nullptr; // reliability check } if (m_difficulty > 2) { @@ -5446,7 +5463,7 @@ Vector Bot::isBombAudible () { } const Vector &bombOrigin = graph.getBombPos (); - float timeElapsed = ((game.timebase () - bots.getTimeBombPlanted ()) / mp_c4timer.float_ ()) * 100.0f; + float timeElapsed = ((game.time () - bots.getTimeBombPlanted ()) / mp_c4timer.float_ ()) * 100.0f; float desiredRadius = 768.0f; // start the manual calculations @@ -5467,13 +5484,13 @@ Vector Bot::isBombAudible () { if (desiredRadius < (pev->origin - bombOrigin).length2d ()) { return bombOrigin; } - return nullvec; + return nullptr; } uint8 Bot::computeMsec () { // estimate msec to use for this command based on time passed from the previous command - return static_cast ((game.timebase () - m_lastCommandTime) * 1000.0f); + return static_cast ((game.time () - m_lastCommandTime) * 1000.0f); } void Bot::runMovement () { @@ -5491,10 +5508,10 @@ void Bot::runMovement () { // elapses, that bot will behave like a ghost : no movement, but bullets and players can // pass through it. Then, when the next frame will begin, the stucking problem will arise ! - m_frameInterval = game.timebase () - m_lastCommandTime; + m_frameInterval = game.time () - m_lastCommandTime; uint8 msecVal = computeMsec (); - m_lastCommandTime = game.timebase (); + m_lastCommandTime = game.time (); engfuncs.pfnRunPlayerMove (pev->pContainingEntity, m_moveAngles, m_moveSpeed, m_strafeSpeed, 0.0f, static_cast (pev->button), static_cast (pev->impulse), msecVal); @@ -5551,7 +5568,7 @@ float Bot::getBombTimeleft () { if (!bots.isBombPlanted ()) { return 0.0f; } - float timeLeft = ((bots.getTimeBombPlanted () + mp_c4timer.float_ ()) - game.timebase ()); + float timeLeft = ((bots.getTimeBombPlanted () + mp_c4timer.float_ ()) - game.time ()); if (timeLeft < 0.0f) { return 0.0f; @@ -5617,7 +5634,7 @@ 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.timebase ()) { + if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive) || client.ent == ent () || client.team == m_team || client.timeSoundLasting < game.time ()) { continue; } @@ -5644,14 +5661,14 @@ void Bot::updateHearing () { // did the bot hear someone ? if (player != nullptr && util.isPlayer (player)) { // change to best weapon if heard something - if (m_shootTime < game.timebase () - 5.0f && isOnFloor () && m_currentWeapon != Weapon::C4 && m_currentWeapon != Weapon::Explosive && m_currentWeapon != Weapon::Smoke && m_currentWeapon != Weapon::Flashbang && !yb_jasonmode.bool_ ()) { + if (m_shootTime < game.time () - 5.0f && isOnFloor () && m_currentWeapon != Weapon::C4 && m_currentWeapon != Weapon::Explosive && m_currentWeapon != Weapon::Smoke && m_currentWeapon != Weapon::Flashbang && !yb_jasonmode.bool_ ()) { selectBestWeapon (); } - m_heardSoundTime = game.timebase (); + m_heardSoundTime = game.time (); m_states |= Sense::HearingEnemy; - if (rg.chance (15) && game.isNullEntity (m_enemy) && game.isNullEntity (m_lastEnemy) && m_seeEnemyTime + 7.0f < game.timebase ()) { + if (rg.chance (15) && game.isNullEntity (m_enemy) && game.isNullEntity (m_lastEnemy) && m_seeEnemyTime + 7.0f < game.time ()) { pushChatterMessage (Chatter::HeardTheEnemy); } @@ -5674,7 +5691,7 @@ void Bot::updateHearing () { // if bot had an enemy but the heard one is nearer, take it instead float distance = (m_lastEnemyOrigin - pev->origin).lengthSq (); - if (distance > (player->v.origin - pev->origin).lengthSq () && m_seeEnemyTime + 2.0f < game.timebase ()) { + if (distance > (player->v.origin - pev->origin).lengthSq () && m_seeEnemyTime + 2.0f < game.time ()) { m_lastEnemy = player; m_lastEnemyOrigin = player->v.origin; } @@ -5692,19 +5709,19 @@ void Bot::updateHearing () { m_lastEnemyOrigin = m_enemyOrigin; m_states |= Sense::SeeingEnemy; - m_seeEnemyTime = game.timebase (); + m_seeEnemyTime = game.time (); } // check if heard enemy can be shoot through some obstacle else { - if (m_difficulty > 2 && m_lastEnemy == player && m_seeEnemyTime + 3.0f > game.timebase () && yb_shoots_thru_walls.bool_ () && isPenetrableObstacle (player->v.origin)) { + if (m_difficulty > 2 && m_lastEnemy == player && m_seeEnemyTime + 3.0f > game.time () && yb_shoots_thru_walls.bool_ () && isPenetrableObstacle (player->v.origin)) { m_enemy = player; m_lastEnemy = player; m_enemyOrigin = player->v.origin; m_lastEnemyOrigin = player->v.origin; m_states |= (Sense::SeeingEnemy | Sense::SuspectEnemy); - m_seeEnemyTime = game.timebase (); + m_seeEnemyTime = game.time (); } } } @@ -5721,13 +5738,13 @@ bool Bot::isShootableBreakable (edict_t *ent) { return false; } -void Bot::processBuyzoneEntering (int buyState) { +void Bot::enteredBuyZone (int buyState) { // this function is gets called when bot enters a buyzone, to allow bot to buy some stuff const int *econLimit = conf.getEconLimit (); // if bot is in buy zone, try to buy ammo for this weapon... - if (m_seeEnemyTime + 12.0f < game.timebase () && m_lastEquipTime + 15.0f < game.timebase () && m_inBuyZone && (bots.getRoundStartTime () + rg.float_ (10.0f, 20.0f) + mp_buytime.float_ () < game.timebase ()) && !bots.isBombPlanted () && m_moneyAmount > econLimit[EcoLimit::PrimaryGreater]) { + if (m_seeEnemyTime + 12.0f < game.time () && m_lastEquipTime + 15.0f < game.time () && m_inBuyZone && (bots.getRoundStartTime () + rg.float_ (10.0f, 20.0f) + mp_buytime.float_ () < game.time ()) && !bots.isBombPlanted () && m_moneyAmount > econLimit[EcoLimit::PrimaryGreater]) { m_ignoreBuyDelay = true; m_buyingFinished = false; m_buyState = buyState; @@ -5735,8 +5752,8 @@ void Bot::processBuyzoneEntering (int buyState) { // push buy message pushMsgQueue (BotMsg::Buy); - m_nextBuyTime = game.timebase (); - m_lastEquipTime = game.timebase (); + m_nextBuyTime = game.time (); + m_lastEquipTime = game.time (); } } diff --git a/source/chatlib.cpp b/source/chatlib.cpp index c6ff8fd..e4b0596 100644 --- a/source/chatlib.cpp +++ b/source/chatlib.cpp @@ -19,11 +19,11 @@ void BotUtils::stripTags (String &line) { for (const auto &tag : m_tags) { const size_t start = line.find (tag.first, 0); - if (start != String::kInvalidIndex) { + if (start != String::InvalidIndex) { const size_t end = line.find (tag.second, start); const size_t diff = end - start; - if (end != String::kInvalidIndex && end > start && diff < 32 && diff > 4) { + if (end != String::InvalidIndex && end > start && diff < 32 && diff > 2) { line.erase (start, diff + tag.second.length ()); break; } @@ -84,7 +84,7 @@ bool BotUtils::checkKeywords (const String &line, String &reply) { for (const auto &keyword : factory.keywords) { // check is keyword has occurred in message - if (line.find (keyword) != String::kInvalidIndex) { + if (line.find (keyword) != String::InvalidIndex) { StringArray &usedReplies = factory.usedReplies; if (usedReplies.length () >= factory.replies.length () / 4) { @@ -140,7 +140,7 @@ void Bot::prepareChatMessage (const String &message) { size_t pos = message.find ('%'); // nothing found, bail out - if (pos == String::kInvalidIndex || pos >= message.length ()) { + if (pos == String::InvalidIndex || pos >= message.length ()) { finishPreparation (); return; } @@ -181,7 +181,7 @@ void Bot::prepareChatMessage (const String &message) { // get roundtime auto getRoundTime = [] () -> String { - auto roundTimeSecs = static_cast (bots.getRoundEndTime () - game.timebase ()); + auto roundTimeSecs = static_cast (bots.getRoundEndTime () - game.time ()); String roundTime; roundTime.assignf ("%02d:%02d", cr::clamp (roundTimeSecs / 60, 0, 59), cr::clamp (cr::abs (roundTimeSecs % 60), 0, 59)); @@ -235,7 +235,7 @@ void Bot::prepareChatMessage (const String &message) { }; size_t replaceCounter = 0; - while (replaceCounter < 6 && (pos = m_chatBuffer.find ('%')) != String::kInvalidIndex) { + while (replaceCounter < 6 && (pos = m_chatBuffer.find ('%')) != String::InvalidIndex) { // found one, let's do replace switch (m_chatBuffer[pos + 1]) { @@ -295,7 +295,7 @@ bool Bot::isReplyingToChat () { if (m_sayTextBuffer.entityIndex != -1 && !m_sayTextBuffer.sayText.empty ()) { // check is time to chat is good - if (m_sayTextBuffer.timeNextChat < game.timebase () + rg.float_ (m_sayTextBuffer.chatDelay / 2, m_sayTextBuffer.chatDelay)) { + if (m_sayTextBuffer.timeNextChat < game.time () + rg.float_ (m_sayTextBuffer.chatDelay / 2, m_sayTextBuffer.chatDelay)) { String replyText; if (rg.chance (m_sayTextBuffer.chatProbability + rg.int_ (20, 50)) && checkChatKeywords (replyText)) { @@ -303,7 +303,7 @@ bool Bot::isReplyingToChat () { pushMsgQueue (BotMsg::Say); m_sayTextBuffer.entityIndex = -1; - m_sayTextBuffer.timeNextChat = game.timebase () + m_sayTextBuffer.chatDelay; + m_sayTextBuffer.timeNextChat = game.time () + m_sayTextBuffer.chatDelay; m_sayTextBuffer.sayText.clear (); return true; @@ -323,7 +323,7 @@ void Bot::checkForChat () { } // bot chatting turned on? - if (m_lastChatTime + rg.float_ (6.0f, 10.0f) < game.timebase () && bots.getLastChatTimestamp () + rg.float_ (2.5f, 5.0f) < game.timebase () && !isReplyingToChat ()) { + if (m_lastChatTime + rg.float_ (6.0f, 10.0f) < game.time () && bots.getLastChatTimestamp () + rg.float_ (2.5f, 5.0f) < game.time () && !isReplyingToChat ()) { if (conf.hasChatBank (Chat::Dead)) { const auto &phrase = conf.pickRandomFromChatBank (Chat::Dead); bool sayBufferExists = false; @@ -340,8 +340,8 @@ void Bot::checkForChat () { prepareChatMessage (phrase); pushMsgQueue (BotMsg::Say); - m_lastChatTime = game.timebase (); - bots.setLastChatTimestamp (game.timebase ()); + m_lastChatTime = game.time (); + bots.setLastChatTimestamp (game.time ()); // add to ignore list m_sayTextBuffer.lastUsedSentences.push (phrase); diff --git a/source/combat.cpp b/source/combat.cpp index 8b6eccc..148efd2 100644 --- a/source/combat.cpp +++ b/source/combat.cpp @@ -92,7 +92,7 @@ bool Bot::checkBodyParts (edict_t *target) { if (isEnemyHidden (target)) { m_enemyParts = Visibility::None; - m_enemyOrigin = nullvec; + m_enemyOrigin = nullptr; return false; } @@ -178,7 +178,7 @@ bool Bot::seesEnemy (edict_t *player, bool ignoreFOV) { } if ((ignoreFOV || isInViewCone (player->v.origin)) && checkBodyParts (player)) { - m_seeEnemyTime = game.timebase (); + m_seeEnemyTime = game.time (); m_lastEnemy = player; m_lastEnemyOrigin = m_enemyOrigin; @@ -187,11 +187,21 @@ bool Bot::seesEnemy (edict_t *player, bool ignoreFOV) { return false; } +void Bot::trackEnemies () { + if (lookupEnemies ()) { + m_states |= Sense::SeeingEnemy; + } + else { + m_states &= ~Sense::SeeingEnemy; + m_enemy = nullptr; + } +} + bool Bot::lookupEnemies () { // this function tries to find the best suitable enemy for the bot // do not search for enemies while we're blinded, or shooting disabled by user - if (m_enemyIgnoreTimer > game.timebase () || m_blindTime > game.timebase () || yb_ignore_enemies.bool_ ()) { + if (m_enemyIgnoreTimer > game.time () || m_blindTime > game.time () || yb_ignore_enemies.bool_ ()) { return false; } edict_t *player, *newEnemy = nullptr; @@ -203,18 +213,18 @@ bool Bot::lookupEnemies () { if (!game.isNullEntity (m_enemy) && (m_states & Sense::SeeingEnemy)) { m_states &= ~Sense::SuspectEnemy; } - else if (game.isNullEntity (m_enemy) && m_seeEnemyTime + 1.0f > game.timebase () && util.isAlive (m_lastEnemy)) { + else if (game.isNullEntity (m_enemy) && m_seeEnemyTime + 1.0f > game.time () && util.isAlive (m_lastEnemy)) { m_states |= Sense::SuspectEnemy; m_aimFlags |= AimFlags::LastEnemy; } m_enemyParts = Visibility::None; - m_enemyOrigin= nullvec; + m_enemyOrigin= nullptr; if (!game.isNullEntity (m_enemy)) { player = m_enemy; // is player is alive - if (m_enemyUpdateTime > game.timebase () && (m_enemy->v.origin - pev->origin).lengthSq () < nearestDistance && util.isAlive (player) && seesEnemy (player)) { + if (m_enemyUpdateTime > game.time () && (m_enemy->v.origin - pev->origin).lengthSq () < nearestDistance && util.isAlive (player) && seesEnemy (player)) { newEnemy = player; } } @@ -262,7 +272,7 @@ bool Bot::lookupEnemies () { } } } - m_enemyUpdateTime = cr::clamp (game.timebase () + getFrameInterval () * 25.0f, 0.5f, 0.75f); + m_enemyUpdateTime = cr::clamp (game.time () + getFrameInterval () * 25.0f, 0.5f, 0.75f); if (game.isNullEntity (newEnemy) && !game.isNullEntity (shieldEnemy)) { newEnemy = shieldEnemy; @@ -277,7 +287,7 @@ bool Bot::lookupEnemies () { // if enemy is still visible and in field of view, keep it keep track of when we last saw an enemy if (newEnemy == m_enemy) { - m_seeEnemyTime = game.timebase (); + m_seeEnemyTime = game.time (); // zero out reaction time m_actualReactionTime = 0.0f; @@ -287,7 +297,7 @@ bool Bot::lookupEnemies () { return true; } else { - if (m_seeEnemyTime + 3.0f < game.timebase () && (m_hasC4 || hasHostage () || !game.isNullEntity (m_targetEntity))) { + if (m_seeEnemyTime + 3.0f < game.time () && (m_hasC4 || hasHostage () || !game.isNullEntity (m_targetEntity))) { pushRadioMessage (Radio::EnemySpotted); } m_targetEntity = nullptr; // stop following when we see an enemy... @@ -302,7 +312,7 @@ bool Bot::lookupEnemies () { if (usesSniper ()) { m_enemySurpriseTime *= 0.5f; } - m_enemySurpriseTime += game.timebase (); + m_enemySurpriseTime += game.time (); // zero out reaction time m_actualReactionTime = 0.0f; @@ -312,7 +322,7 @@ bool Bot::lookupEnemies () { m_enemyReachableTimer = 0.0f; // keep track of when we last saw an enemy - m_seeEnemyTime = game.timebase (); + m_seeEnemyTime = game.time (); if (!(m_oldButtons & IN_ATTACK)) { return true; @@ -324,10 +334,10 @@ bool Bot::lookupEnemies () { continue; } - if (other->m_seeEnemyTime + 2.0f < game.timebase () && game.isNullEntity (other->m_lastEnemy) && util.isVisible (pev->origin, other->ent ()) && other->isInViewCone (pev->origin)) { + if (other->m_seeEnemyTime + 2.0f < game.time () && game.isNullEntity (other->m_lastEnemy) && util.isVisible (pev->origin, other->ent ()) && other->isInViewCone (pev->origin)) { other->m_lastEnemy = newEnemy; other->m_lastEnemyOrigin = m_lastEnemyOrigin; - other->m_seeEnemyTime = game.timebase (); + other->m_seeEnemyTime = game.time (); other->m_states |= (Sense::SuspectEnemy | Sense::HearingEnemy); other->m_aimFlags |= AimFlags::LastEnemy; } @@ -343,9 +353,9 @@ bool Bot::lookupEnemies () { m_enemy = nullptr; // shoot at dying players if no new enemy to give some more human-like illusion - if (m_seeEnemyTime + 0.3f > game.timebase ()) { + if (m_seeEnemyTime + 0.3f > game.time ()) { if (!usesSniper ()) { - m_shootAtDeadTime = game.timebase () + 0.4f; + m_shootAtDeadTime = game.time () + 0.4f; m_actualReactionTime = 0.0f; m_states |= Sense::SuspectEnemy; @@ -353,7 +363,7 @@ bool Bot::lookupEnemies () { } return false; } - else if (m_shootAtDeadTime > game.timebase ()) { + else if (m_shootAtDeadTime > game.time ()) { m_actualReactionTime = 0.0f; m_states |= Sense::SuspectEnemy; @@ -364,7 +374,7 @@ bool Bot::lookupEnemies () { // if no enemy visible check if last one shoot able through wall if (yb_shoots_thru_walls.bool_ () && m_difficulty >= 2 && isPenetrableObstacle (newEnemy->v.origin)) { - m_seeEnemyTime = game.timebase (); + m_seeEnemyTime = game.time (); m_states |= Sense::SuspectEnemy; m_aimFlags |= AimFlags::LastEnemy; @@ -378,14 +388,14 @@ bool Bot::lookupEnemies () { } // check if bots should reload... - if ((m_aimFlags <= AimFlags::PredictPath && m_seeEnemyTime + 3.0f < game.timebase () && !(m_states & (Sense::SeeingEnemy | Sense::HearingEnemy)) && game.isNullEntity (m_lastEnemy) && game.isNullEntity (m_enemy) && getCurrentTaskId () != Task::ShootBreakable && getCurrentTaskId () != Task::PlantBomb && getCurrentTaskId () != Task::DefuseBomb) || bots.isRoundOver ()) { + if ((m_aimFlags <= AimFlags::PredictPath && m_seeEnemyTime + 3.0f < game.time () && !(m_states & (Sense::SeeingEnemy | Sense::HearingEnemy)) && game.isNullEntity (m_lastEnemy) && game.isNullEntity (m_enemy) && getCurrentTaskId () != Task::ShootBreakable && getCurrentTaskId () != Task::PlantBomb && getCurrentTaskId () != Task::DefuseBomb) || bots.isRoundOver ()) { if (!m_reloadState) { m_reloadState = Reload::Primary; } } // is the bot using a sniper rifle or a zoomable rifle? - if ((usesSniper () || usesZoomableRifle ()) && m_zoomCheckTime + 1.0f < game.timebase ()) { + if ((usesSniper () || usesZoomableRifle ()) && m_zoomCheckTime + 1.0f < game.time ()) { if (pev->fov < 90.0f) { pev->button |= IN_ATTACK2; } @@ -398,15 +408,15 @@ bool Bot::lookupEnemies () { Vector Bot::getBodyOffsetError (float distance) { if (game.isNullEntity (m_enemy)) { - return nullvec; + return nullptr; } - if (m_aimErrorTime < game.timebase ()) { + if (m_aimErrorTime < game.time ()) { const float error = distance / (cr::clamp (m_difficulty, 1, 4) * 1000.0f); Vector &maxs = m_enemy->v.maxs, &mins = m_enemy->v.mins; m_aimLastError = Vector (rg.float_ (mins.x * error, maxs.x * error), rg.float_ (mins.y * error, maxs.y * error), rg.float_ (mins.z * error, maxs.z * error)); - m_aimErrorTime = game.timebase () + rg.float_ (0.5f, 1.0f); + m_aimErrorTime = game.time () + rg.float_ (0.5f, 1.0f); } return m_aimLastError; } @@ -434,15 +444,10 @@ const Vector &Bot::getEnemyBodyOffset () { else if (distance < 800.0f && usesSniper ()) { m_enemyParts &= ~Visibility::Head; } - - // do not aim at head while enemy is soooo close enough to enemy when recoil aims at head automatically - else if (distance < kSprayDistance) { - m_enemyParts &= ~Visibility::Head; - } Vector aimPos = m_enemy->v.origin; - if (m_difficulty > 2 && !(m_enemyParts & Visibility::Other)) { - aimPos = (m_enemy->v.velocity - pev->velocity) * getFrameInterval () + aimPos; + if (m_difficulty > 2) { + aimPos += (m_enemy->v.velocity - pev->velocity) * (getFrameInterval () * 1.75f); } // if we only suspect an enemy behind a wall take the worst skill @@ -450,16 +455,22 @@ const Vector &Bot::getEnemyBodyOffset () { aimPos += getBodyOffsetError (distance); } else { + bool useBody = !usesPistol () && distance > kSprayDistance && distance < 2048.0f; + // now take in account different parts of enemy body if (m_enemyParts & (Visibility::Head | Visibility::Body)) { int headshotFreq[5] = { 20, 40, 60, 80, 100 }; // now check is our skill match to aim at head, else aim at enemy body - if (rg.chance (headshotFreq[m_difficulty]) || usesPistol ()) { + if (rg.chance (headshotFreq[m_difficulty]) && !useBody) { aimPos.z = headOffset (m_enemy) + getEnemyBodyOffsetCorrection (distance); } else { aimPos.z += getEnemyBodyOffsetCorrection (distance); + + if (useBody) { + aimPos.z += 4.5f; + } } } else if (m_enemyParts & Visibility::Body) { @@ -496,7 +507,7 @@ float Bot::getEnemyBodyOffsetCorrection (float distance) { float result = -2.0f; if (distance < kSprayDistance) { - return -9.0f; + return -16.0f; } else if (distance >= kDoubleSprayDistance) { if (sniper) { @@ -655,7 +666,7 @@ bool Bot::needToPauseFiring (float distance) { return false; } - if (m_firePause > game.timebase ()) { + if (m_firePause > game.time ()) { return true; } @@ -678,16 +689,16 @@ bool Bot::needToPauseFiring (float distance) { const float xPunch = cr::degreesToRadians (pev->punchangle.x); const float yPunch = cr::degreesToRadians (pev->punchangle.y); - float interval = getFrameInterval (); - float tolerance = (100.0f - m_difficulty * 25.0f) / 99.0f; + const float interval = getFrameInterval (); + const float tolerance = (100.0f - m_difficulty * 25.0f) / 99.0f; // check if we need to compensate recoil if (cr::tanf (cr::sqrtf (cr::abs (xPunch * xPunch) + cr::abs (yPunch * yPunch))) * distance > offset + 30.0f + tolerance) { - if (m_firePause < game.timebase ()) { - m_firePause = rg.float_ (0.5f, 0.5f + 0.3f * tolerance); + if (m_firePause < game.time ()) { + m_firePause = rg.float_ (0.65f, 0.65f + 0.3f * tolerance); } m_firePause -= interval; - m_firePause += game.timebase (); + m_firePause += game.time (); return true; } @@ -700,7 +711,7 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) { // we want to fire weapon, don't reload now if (!m_isReloading) { m_reloadState = Reload::None; - m_reloadCheckTime = game.timebase () + 3.0f; + m_reloadCheckTime = game.time () + 3.0f; } // select this weapon if it isn't already selected @@ -730,7 +741,7 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) { // if we're have a glock or famas vary burst fire mode checkBurstMode (distance); - if (hasShield () && m_shieldCheckTime < game.timebase () && getCurrentTaskId () != Task::Camp) // better shield gun usage + if (hasShield () && m_shieldCheckTime < game.time () && getCurrentTaskId () != Task::Camp) // better shield gun usage { if (distance >= 750.0f && !isShieldDrawn ()) { pev->button |= IN_ATTACK2; // draw the shield @@ -738,11 +749,11 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) { else if (isShieldDrawn () || (!game.isNullEntity (m_enemy) && ((m_enemy->v.button & IN_RELOAD) || !seesEntity (m_enemy->v.origin)))) { pev->button |= IN_ATTACK2; // draw out the shield } - m_shieldCheckTime = game.timebase () + 1.0f; + m_shieldCheckTime = game.time () + 1.0f; } // is the bot holding a sniper rifle? - if (usesSniper () && m_zoomCheckTime < game.timebase ()) { + if (usesSniper () && m_zoomCheckTime < game.time ()) { // should the bot switch to the long-range zoom? if (distance > 1500.0f && pev->fov >= 40.0f) { pev->button |= IN_ATTACK2; @@ -757,11 +768,11 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) { else if (distance <= 150.0f && pev->fov < 90.0f) { pev->button |= IN_ATTACK2; } - m_zoomCheckTime = game.timebase () + 0.25f; + m_zoomCheckTime = game.time () + 0.25f; } // else is the bot holding a zoomable rifle? - else if (m_difficulty < 3 && usesZoomableRifle () && m_zoomCheckTime < game.timebase ()) { + else if (m_difficulty < 3 && usesZoomableRifle () && m_zoomCheckTime < game.time ()) { // should the bot switch to zoomed mode? if (distance > 800.0f && pev->fov >= 90.0f) { pev->button |= IN_ATTACK2; @@ -771,23 +782,23 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) { else if (distance <= 800.0f && pev->fov < 90.0f) { pev->button |= IN_ATTACK2; } - m_zoomCheckTime = game.timebase () + 0.5f; + m_zoomCheckTime = game.time () + 0.5f; } // we're should stand still before firing sniper weapons, else sniping is useless.. if (usesSniper () && (m_states & (Sense::SeeingEnemy | Sense::SuspectEnemy)) && !m_isReloading && pev->velocity.lengthSq () > 0.0f) { m_moveSpeed = 0.0f; m_strafeSpeed = 0.0f; - m_navTimeset = game.timebase (); + m_navTimeset = game.time (); if (cr::abs (pev->velocity.x) > 5.0f || cr::abs (pev->velocity.y) > 5.0f || cr::abs (pev->velocity.z) > 5.0f) { - m_sniperStopTime = game.timebase () + 2.5f; + m_sniperStopTime = game.time () + 2.5f; return; } } // need to care for burst fire? - if (distance < kSprayDistance || m_blindTime > game.timebase ()) { + if (distance < kSprayDistance || m_blindTime > game.time ()) { if (id == Weapon::Knife) { if (distance < 64.0f) { if (rg.chance (30) || hasShield ()) { @@ -813,7 +824,7 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) { } } } - m_shootTime = game.timebase (); + m_shootTime = game.time (); } else { if (needToPauseFiring (distance)) { @@ -822,13 +833,13 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) { // don't attack with knife over long distance if (id == Weapon::Knife) { - m_shootTime = game.timebase (); + m_shootTime = game.time (); return; } if (tab[choosen].primaryFireHold) { - m_shootTime = game.timebase (); - m_zoomCheckTime = game.timebase (); + m_shootTime = game.time (); + m_zoomCheckTime = game.time (); pev->button |= IN_ATTACK; // use primary attack } @@ -840,21 +851,22 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) { const int offset = cr::abs (m_difficulty * 25 / 20 - 5); - m_shootTime = game.timebase () + 0.1f + rg.float_ (minDelay[offset], maxDelay[offset]); - m_zoomCheckTime = game.timebase (); + m_shootTime = game.time () + 0.1f + rg.float_ (minDelay[offset], maxDelay[offset]); + m_zoomCheckTime = game.time (); } } } void Bot::fireWeapons () { // this function will return true if weapon was fired, false otherwise + float distance = (m_lookAt - getEyesPos ()).length (); // how far away is the enemy? // or if friend in line of fire, stop this too but do not update shoot time if (!game.isNullEntity (m_enemy)) { if (isFriendInLineOfFire (distance)) { m_fightStyle = Fight::Strafe; - m_lastFightStyleCheck = game.timebase (); + m_lastFightStyleCheck = game.time (); return; } @@ -905,10 +917,10 @@ void Bot::fireWeapons () { 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.timebase ()) { + if (m_reloadState == Reload::None || m_reloadCheckTime > game.time ()) { m_isReloading = true; m_reloadState = Reload::Primary; - m_reloadCheckTime = game.timebase (); + m_reloadCheckTime = game.time (); if (rg.chance (cr::abs (m_difficulty * 25 - 100))) { pushRadioMessage (Radio::NeedBackup); @@ -957,10 +969,14 @@ bool Bot::isWeaponBadAtDistance (int weaponIndex, float distance) { } void Bot::focusEnemy () { + if (game.isNullEntity (m_enemy)) { + return; + } + // aim for the head and/or body m_lookAt = getEnemyBodyOffset (); - if (m_enemySurpriseTime > game.timebase () || game.isNullEntity (m_enemy)) { + if (m_enemySurpriseTime > game.time ()) { return; } float distance = (m_lookAt - getEyesPos ()).length2d (); // how far away is the enemy scum? @@ -1010,7 +1026,7 @@ void Bot::attackMovement () { } float distance = (m_lookAt - getEyesPos ()).length2d (); // how far away is the enemy scum? - if (m_lastUsedNodesTime + getFrameInterval () + 0.5f < game.timebase ()) { + if (m_lastUsedNodesTime + getFrameInterval () + 0.5f < game.time ()) { int approach; if (m_currentWeapon == Weapon::Knife) { @@ -1048,10 +1064,10 @@ void Bot::attackMovement () { if (usesSniper () || !(m_enemyParts & (Visibility::Body | Visibility::Head))) { m_fightStyle = Fight::Stay; - m_lastFightStyleCheck = game.timebase (); + m_lastFightStyleCheck = game.time (); } else if (usesRifle () || usesSubmachine ()) { - if (m_lastFightStyleCheck + 3.0f < game.timebase ()) { + if (m_lastFightStyleCheck + 3.0f < game.time ()) { int rand = rg.int_ (1, 100); if (distance < 450.0f) { @@ -1073,23 +1089,15 @@ void Bot::attackMovement () { m_fightStyle = Fight::Strafe; } } - m_lastFightStyleCheck = game.timebase (); + m_lastFightStyleCheck = game.time (); } } else { - if (m_lastFightStyleCheck + 3.0f < game.timebase ()) { - if (rg.chance (50)) { - m_fightStyle = Fight::Strafe; - } - else { - m_fightStyle = Fight::Stay; - } - m_lastFightStyleCheck = game.timebase (); - } + m_fightStyle = Fight::Strafe; } if (m_fightStyle == Fight::Strafe || ((pev->button & IN_RELOAD) || m_isReloading) || (usesPistol () && distance < 400.0f) || m_currentWeapon == Weapon::Knife) { - if (m_strafeSetTime < game.timebase ()) { + if (m_strafeSetTime < game.time ()) { // to start strafing, we have to first figure out if the target is on the left side or right side const auto &dirToPoint = (pev->origin - m_enemy->v.origin).normalize2d (); @@ -1105,7 +1113,7 @@ void Bot::attackMovement () { if (rg.chance (30)) { m_combatStrafeDir = (m_combatStrafeDir == Dodge::Left ? Dodge::Right : Dodge::Left); } - m_strafeSetTime = game.timebase () + rg.float_ (0.5f, 3.0f); + m_strafeSetTime = game.time () + rg.float_ (0.5f, 3.0f); } if (m_combatStrafeDir == Dodge::Right) { @@ -1114,7 +1122,7 @@ void Bot::attackMovement () { } else { m_combatStrafeDir = Dodge::Left; - m_strafeSetTime = game.timebase () + rg.float_ (0.8f, 1.3f); + m_strafeSetTime = game.time () + rg.float_ (0.8f, 1.1f); } } else { @@ -1123,11 +1131,11 @@ void Bot::attackMovement () { } else { m_combatStrafeDir = Dodge::Right; - m_strafeSetTime = game.timebase () + rg.float_ (0.8f, 1.3f); + m_strafeSetTime = game.time () + rg.float_ (0.8f, 1.1f); } } - if (m_difficulty >= 3 && (m_jumpTime + 5.0f < game.timebase () && isOnFloor () && rg.int_ (0, 1000) < (m_isReloading ? 8 : 2) && pev->velocity.length2d () > 120.0f) && !usesSniper ()) { + if (m_difficulty >= 3 && (m_jumpTime + 5.0f < game.time () && isOnFloor () && rg.int_ (0, 1000) < (m_isReloading ? 8 : 2) && pev->velocity.length2d () > 120.0f) && !usesSniper ()) { pev->button |= IN_JUMP; } @@ -1144,32 +1152,24 @@ void Bot::attackMovement () { int enemyNearestIndex = graph.getNearest (m_enemy->v.origin); if (graph.isDuckVisible (m_currentNodeIndex, enemyNearestIndex) && graph.isDuckVisible (enemyNearestIndex, m_currentNodeIndex)) { - m_duckTime = game.timebase () + 0.5f; + m_duckTime = game.time () + 0.64f; } } m_moveSpeed = 0.0f; m_strafeSpeed = 0.0f; - m_navTimeset = game.timebase (); + m_navTimeset = game.time (); } } - if (m_duckTime > game.timebase ()) { - m_moveSpeed = 0.0f; - m_strafeSpeed = 0.0f; - } - - if (m_moveSpeed > 0.0f && m_currentWeapon != Weapon::Knife) { - m_moveSpeed = getShiftSpeed (); - } - - if (m_isReloading) { - m_moveSpeed = -pev->maxspeed; - m_duckTime = game.timebase () - 1.0f; + if (m_fightStyle == Fight::Stay || (m_duckTime > game.time () || m_sniperStopTime > game.time ())) { + if (m_moveSpeed > 0.0f) { + m_moveSpeed = 0.0f; + } } if (!isInWater () && !isOnLadder () && (m_moveSpeed > 0.0f || m_strafeSpeed >= 0.0f)) { Vector right, forward; - pev->v_angle.buildVectors (&forward, &right, nullptr); + pev->v_angle.angleVectors (&forward, &right, nullptr); if (isDeadlyMove (pev->origin + (forward * m_moveSpeed * 0.2f) + (right * m_strafeSpeed * 0.2f) + (pev->velocity * getFrameInterval ()))) { m_strafeSpeed = -m_strafeSpeed; @@ -1499,7 +1499,7 @@ void Bot::decideFollowUser () { void Bot::updateTeamCommands () { // prevent spamming - if (m_timeTeamOrder > game.timebase () + 2.0f || game.is (GameFlags::FreeForAll) || !yb_radio_mode.int_ ()) { + if (m_timeTeamOrder > game.time () + 2.0f || game.is (GameFlags::FreeForAll) || !yb_radio_mode.int_ ()) { return; } @@ -1534,7 +1534,7 @@ void Bot::updateTeamCommands () { else if (memberExists && yb_radio_mode.int_ () == 2) { pushChatterMessage (Chatter::ScaredEmotion); } - m_timeTeamOrder = game.timebase () + rg.float_ (15.0f, 30.0f); + m_timeTeamOrder = game.time () + rg.float_ (15.0f, 30.0f); } bool Bot::isGroupOfEnemies (const Vector &location, int numEnemies, float radius) { @@ -1562,13 +1562,19 @@ bool Bot::isGroupOfEnemies (const Vector &location, int numEnemies, float radius void Bot::checkReload () { // check the reload state - if (getCurrentTaskId () == Task::PlantBomb || getCurrentTaskId () == Task::DefuseBomb || getCurrentTaskId () == Task::PickupItem || getCurrentTaskId () == Task::ThrowFlashbang || getCurrentTaskId () == Task::ThrowSmoke || m_isUsingGrenade) { + auto task = getCurrentTaskId (); + + // we're should not reload, while doing next tasks + bool uninterruptibleTask = (task == Task::PlantBomb || task == Task::DefuseBomb || task == Task::PickupItem || task == Task::ThrowExplosive || task == Task::ThrowFlashbang || task == Task::ThrowSmoke); + + // do not check for reload + if (uninterruptibleTask || m_isUsingGrenade) { m_reloadState = Reload::None; return; } m_isReloading = false; // update reloading status - m_reloadCheckTime = game.timebase () + 3.0f; + m_reloadCheckTime = game.time () + 3.0f; if (m_reloadState != Reload::None) { int weaponIndex = 0; @@ -1611,7 +1617,7 @@ void Bot::checkReload () { } else { // if we have enemy don't reload next weapon - if ((m_states & (Sense::SeeingEnemy | Sense::HearingEnemy)) || m_seeEnemyTime + 5.0f > game.timebase ()) { + if ((m_states & (Sense::SeeingEnemy | Sense::HearingEnemy)) || m_seeEnemyTime + 5.0f > game.time ()) { m_reloadState = Reload::None; return; } diff --git a/source/control.cpp b/source/control.cpp index d4fe7fb..0ba0b90 100644 --- a/source/control.cpp +++ b/source/control.cpp @@ -26,15 +26,15 @@ int BotControl::cmdAddBot () { } // if team is specified, modify args to set team - if (m_args[alias].find ("_ct", 0) != String::kInvalidIndex) { + if (m_args[alias].find ("_ct", 0) != String::InvalidIndex) { m_args.set (team, "2"); } - else if (m_args[alias].find ("_t", 0) != String::kInvalidIndex) { + else if (m_args[alias].find ("_t", 0) != String::InvalidIndex) { m_args.set (team, "1"); } // if highskilled bot is requsted set personality to rusher and maxout difficulty - if (m_args[alias].find ("hs", 0) != String::kInvalidIndex) { + if (m_args[alias].find ("hs", 0) != String::InvalidIndex) { m_args.set (difficulty, "4"); m_args.set (personality, "1"); } @@ -50,10 +50,10 @@ int BotControl::cmdKickBot () { fixMissingArgs (max); // if team is specified, kick from specified tram - if (m_args[alias].find ("_ct", 0) != String::kInvalidIndex || getInt (team) == 2 || getStr (team) == "ct") { + if (m_args[alias].find ("_ct", 0) != String::InvalidIndex || getInt (team) == 2 || getStr (team) == "ct") { bots.kickFromTeam (Team::CT); } - else if (m_args[alias].find ("_t", 0) != String::kInvalidIndex || getInt (team) == 1 || getStr (team) == "t") { + else if (m_args[alias].find ("_t", 0) != String::InvalidIndex || getInt (team) == 1 || getStr (team) == "t") { bots.kickFromTeam (Team::Terrorist); } else { @@ -84,10 +84,10 @@ int BotControl::cmdKillBots () { fixMissingArgs (max); // if team is specified, kick from specified tram - if (m_args[alias].find ("_ct", 0) != String::kInvalidIndex || getInt (team) == 2 || getStr (team) == "ct") { + if (m_args[alias].find ("_ct", 0) != String::InvalidIndex || getInt (team) == 2 || getStr (team) == "ct") { bots.killAllBots (Team::CT); } - else if (m_args[alias].find ("_t", 0) != String::kInvalidIndex || getInt (team) == 1 || getStr (team) == "t") { + else if (m_args[alias].find ("_t", 0) != String::InvalidIndex || getInt (team) == 1 || getStr (team) == "t") { bots.killAllBots (Team::Terrorist); } else { @@ -638,13 +638,13 @@ int BotControl::cmdNodePathCreate () { graph.setEditFlag (GraphEdit::On); // choose the direction for path creation - if (m_args[cmd].find ("_both", 0) != String::kInvalidIndex) { + if (m_args[cmd].find ("_both", 0) != String::InvalidIndex) { graph.pathCreate (PathConnection::Bidirectional); } - else if (m_args[cmd].find ("_in", 0) != String::kInvalidIndex) { + else if (m_args[cmd].find ("_in", 0) != String::InvalidIndex) { graph.pathCreate (PathConnection::Incoming); } - else if (m_args[cmd].find ("_out", 0) != String::kInvalidIndex) { + else if (m_args[cmd].find ("_out", 0) != String::InvalidIndex) { graph.pathCreate (PathConnection::Outgoing); } else { @@ -1052,17 +1052,20 @@ int BotControl::menuClassSelect (int item) { int BotControl::menuCommands (int item) { showMenu (Menu::None); // reset menu display - Bot *bot = nullptr; + Bot *nearest = nullptr; switch (item) { case 1: case 2: - if (util.findNearestPlayer (reinterpret_cast (&bot), m_ent, 600.0f, true, true, true) && bot->m_hasC4 && !bot->hasHostage ()) { + if (util.findNearestPlayer (reinterpret_cast (&m_djump), m_ent, 600.0f, true, true, true, true, false) && !m_djump->m_hasC4 && !m_djump->hasHostage ()) { if (item == 1) { - bot->startDoubleJump (m_ent); + m_djump->startDoubleJump (m_ent); } else { - bot->resetDoubleJump (); + if (m_djump) { + m_djump->resetDoubleJump (); + m_djump = nullptr; + } } } showMenu (Menu::Commands); @@ -1070,8 +1073,8 @@ int BotControl::menuCommands (int item) { case 3: case 4: - if (util.findNearestPlayer (reinterpret_cast (&bot), m_ent, 600.0f, true, true, true, true, item == 4 ? false : true)) { - bot->dropWeaponForUser (m_ent, item == 4 ? false : true); + if (util.findNearestPlayer (reinterpret_cast (&nearest), m_ent, 600.0f, true, true, true, true, item == 4 ? false : true)) { + nearest->dropWeaponForUser (m_ent, item == 4 ? false : true); } showMenu (Menu::Commands); break; @@ -1641,7 +1644,7 @@ void BotControl::showMenu (int id) { Client &client = util.getClient (game.indexOfPlayer (m_ent)); if (id == Menu::None) { - MessageWriter (MSG_ONE_UNRELIABLE, msgs.id (NetMsg::ShowMenu), nullvec, m_ent) + MessageWriter (MSG_ONE_UNRELIABLE, msgs.id (NetMsg::ShowMenu), nullptr, m_ent) .writeShort (0) .writeChar (0) .writeByte (0) @@ -1657,7 +1660,7 @@ void BotControl::showMenu (int id) { MessageWriter msg; while (strlen (text) >= 64) { - msg.start (MSG_ONE_UNRELIABLE, msgs.id (NetMsg::ShowMenu), nullvec, m_ent) + msg.start (MSG_ONE_UNRELIABLE, msgs.id (NetMsg::ShowMenu), nullptr, m_ent) .writeShort (display.slots) .writeChar (-1) .writeByte (1); @@ -1669,7 +1672,7 @@ void BotControl::showMenu (int id) { text += 64; } - MessageWriter (MSG_ONE_UNRELIABLE, msgs.id (NetMsg::ShowMenu), nullvec, m_ent) + MessageWriter (MSG_ONE_UNRELIABLE, msgs.id (NetMsg::ShowMenu), nullptr, m_ent) .writeShort (display.slots) .writeChar (-1) .writeByte (0) @@ -1773,6 +1776,8 @@ void BotControl::maintainAdminRights () { BotControl::BotControl () { m_ent = nullptr; + m_djump = nullptr; + m_isFromConsole = false; m_isMenuFillCommand = false; m_rapidOutput = false; @@ -1789,22 +1794,22 @@ BotControl::BotControl () { m_cmds.emplace ("version/ver/about", "version [no arguments]", "Displays version information about bot build.", &BotControl::cmdVersion); 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/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); // declare the menus createMenus (); } void BotControl::handleEngineCommands () { - ctrl.collectArgs (); - ctrl.setIssuer (game.getLocalEntity ()); + collectArgs (); + setIssuer (game.getLocalEntity ()); - ctrl.setFromConsole (true); - ctrl.executeCommands (); + setFromConsole (true); + executeCommands (); } bool BotControl::handleClientCommands (edict_t *ent) { - ctrl.collectArgs (); + collectArgs (); setIssuer (ent); setFromConsole (true); @@ -1812,7 +1817,7 @@ bool BotControl::handleClientCommands (edict_t *ent) { } bool BotControl::handleMenuCommands (edict_t *ent) { - ctrl.collectArgs (); + collectArgs (); setIssuer (ent); setFromConsole (false); @@ -1827,16 +1832,15 @@ void BotControl::enableDrawModels (bool enable) { entities.push ("info_vip_start"); for (auto &entity : entities) { - edict_t *ent = nullptr; - - while (!game.isNullEntity (ent = engfuncs.pfnFindEntityByString (ent, "classname", entity.chars ()))) { + game.searchEntities ("classname", entity, [&enable] (edict_t *ent) { if (enable) { ent->v.effects &= ~EF_NODRAW; } else { ent->v.effects |= EF_NODRAW; } - } + return EntitySearchResult::Continue; + }); } } diff --git a/source/engine.cpp b/source/engine.cpp index 17859ac..2c99698 100644 --- a/source/engine.cpp +++ b/source/engine.cpp @@ -129,6 +129,9 @@ void Game::levelInitialize (edict_t *entities, int max) { else if (strncmp (classname, "func_door", 9) == 0) { m_mapFlags |= MapFlags::HasDoors; } + else if (strncmp (classname, "func_button", 11) == 0) { + m_mapFlags |= MapFlags::HasButtons; + } } // next maps doesn't have map-specific entities, so determine it by name @@ -152,7 +155,7 @@ void Game::drawLine (edict_t *ent, const Vector &start, const Vector &end, int w return; // reliability check } - MessageWriter (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, nullvec, ent) + MessageWriter (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, nullptr, ent) .writeByte (TE_BEAMPOINTS) .writeCoord (end.x) .writeCoord (end.y) @@ -285,7 +288,7 @@ const char *Game::getModName () { name = engineModName; size_t slash = name.findLastOf ("\\/"); - if (slash != String::kInvalidIndex) { + if (slash != String::InvalidIndex) { name = name.substr (slash + 1); } name = name.trim (" \\/"); @@ -303,7 +306,7 @@ Vector Game::getAbsPos (edict_t *ent) { // entity that has a bounding box has its center at the center of the bounding box itself. if (isNullEntity (ent)) { - return nullvec; + return nullptr; } if (ent->v.origin.empty ()) { @@ -312,7 +315,7 @@ Vector Game::getAbsPos (edict_t *ent) { return ent->v.origin; } -void Game::registerCmd (const char *command, void func ()) { +void Game::registerEngineCommand (const char *command, void func ()) { // this function tells the engine that a new server command is being declared, in addition // to the standard ones, whose name is command_name. The engine is thus supposed to be aware // that for every "command_name" server command it receives, it should call the function @@ -378,7 +381,7 @@ uint8 *Game::getVisibilitySet (Bot *bot, bool pvs) { void Game::sendClientMessage (bool console, edict_t *ent, const char *message) { // helper to sending the client message - MessageWriter (MSG_ONE, msgs.id (NetMsg::TextMsg), nullvec, ent) + MessageWriter (MSG_ONE, msgs.id (NetMsg::TextMsg), nullptr, ent) .writeByte (console ? HUD_PRINTCONSOLE : HUD_PRINTCENTER) .writeString (message); } @@ -409,7 +412,7 @@ void Game::prepareBotArgs (edict_t *ent, String str) { const size_t space = args.find (' ', 0); // if found space - if (space != String::kInvalidIndex) { + if (space != String::InvalidIndex) { const auto quote = space + 1; // check for quote next to space // check if we're got a quoted string @@ -430,7 +433,7 @@ void Game::prepareBotArgs (edict_t *ent, String str) { m_botArgs.clear (); // clear space for next cmd }; - if (str.find (';', 0) != String::kInvalidIndex) { + if (str.find (';', 0) != String::InvalidIndex) { for (auto &part : str.split (";")) { parsePartArgs (part); } @@ -629,20 +632,40 @@ bool Game::loadCSBinary () { } bool Game::postload () { - // ensure we're have all needed directories - const char *mod = getModName (); - // create the needed paths - File::createPath (strings.format ("%s/addons/yapb/conf/lang", mod)); - File::createPath (strings.format ("%s/addons/yapb/data/learned", mod)); - File::createPath (strings.format ("%s/addons/yapb/data/graph", mod)); - File::createPath (strings.format ("%s/addons/yapb/data/logs", mod)); + // ensure we're have all needed directories + for (const auto &dir : StringArray { "conf/lang", "data/learned", "data/graph", "data/logs" }) { + File::createPath (strings.format ("%s/addons/yapb/%s", getModName (), dir.chars ())); + } // set out user agent for http stuff http.setUserAgent (strings.format ("%s/%s", PRODUCT_SHORT_NAME, PRODUCT_VERSION)); + // register bot cvars + game.registerCvars (); + + + // register server command(s) + registerEngineCommand ("yapb", [] () { + ctrl.handleEngineCommands (); + }); + + registerEngineCommand ("yb", [] () { + ctrl.handleEngineCommands (); + }); + + // register fake metamod command handler if we not! under mm + if (!(game.is (GameFlags::Metamod))) { + game.registerEngineCommand ("meta", [] () { + game.print ("You're launched standalone version of %s. Metamod is not installed or not enabled!", PRODUCT_SHORT_NAME); + }); + } + + // initialize weapons + conf.initWeapons (); + // print game detection info - auto printGame = [&] () { + auto displayCSVersion = [&] () { String gameVersionStr; StringArray gameVersionFlags; @@ -694,7 +717,7 @@ bool Game::postload () { if (!m_gameLib.load (gamedll)) { logger.fatal ("Unable to load gamedll \"%s\". Exiting... (gamedir: %s)", gamedll, getModName ()); } - printGame (); + displayCSVersion (); } else { @@ -703,7 +726,7 @@ bool Game::postload () { if (!binaryLoaded && !is (GameFlags::Metamod)) { logger.fatal ("Mod that you has started, not supported by this bot (gamedir: %s)", getModName ()); } - printGame (); + displayCSVersion (); if (is (GameFlags::Metamod)) { m_gameLib.unload (); @@ -742,7 +765,7 @@ void Game::detectDeathmatch () { } void Game::slowFrame () { - if (m_slowFrame > timebase ()) { + if (m_slowFrame > time ()) { return; } ctrl.maintainAdminRights (); @@ -761,7 +784,36 @@ void Game::slowFrame () { // display welcome message util.checkWelcome (); - m_slowFrame = timebase () + 1.0f; + m_slowFrame = time () + 1.0f; +} + +void Game::searchEntities (const String &field, const String &value, EntitySearch functor) { + edict_t *ent = nullptr; + + while (!game.isNullEntity (ent = engfuncs.pfnFindEntityByString (ent, field.chars (), value.chars ()))) { + if ((ent->v.flags & EF_NODRAW) || (ent->v.flags & FL_CLIENT)) { + continue; + } + + if (functor (ent) == EntitySearchResult::Break) { + break; + } + } +} + +void Game::searchEntities (const Vector &position, const float radius, EntitySearch functor) { + edict_t *ent = nullptr; + const Vector &pos = position.empty () ? m_startEntity->v.origin : position; + + while (!game.isNullEntity (ent = engfuncs.pfnFindEntityInSphere (ent, pos, radius))) { + if ((ent->v.flags & EF_NODRAW) || (ent->v.flags & FL_CLIENT)) { + continue; + } + + if (functor (ent) == EntitySearchResult::Break) { + break; + } + } } void LightMeasure::initializeLightstyles () { @@ -786,7 +838,7 @@ void LightMeasure::animateLight () { } // 'm' is normal light, 'a' is no light, 'z' is double bright - const int index = static_cast (game.timebase () * 10.0f); + const int index = static_cast (game.time () * 10.0f); for (int j = 0; j < MAX_LIGHTSTYLES; ++j) { if (!m_lightstyle[j].length) { diff --git a/source/graph.cpp b/source/graph.cpp index c09335b..006f81d 100644 --- a/source/graph.cpp +++ b/source/graph.cpp @@ -19,9 +19,9 @@ void BotGraph::initGraph () { m_loadAttempts = 0; m_editFlags = 0; - m_learnVelocity= nullvec; - m_learnPosition= nullvec; - m_lastNode= nullvec; + m_learnVelocity= nullptr; + m_learnPosition= nullptr; + m_lastNode= nullptr; m_pathDisplayTime = 0.0f; m_arrowDisplayTime = 0.0f; @@ -540,7 +540,7 @@ void BotGraph::add (int type, const Vector &pos) { Path *path = nullptr; bool addNewNode = true; - Vector newOrigin = pos == nullvec ? m_editor->v.origin : pos; + Vector newOrigin = pos.empty () ? m_editor->v.origin : pos; if (bots.hasBotsOnline ()) { bots.kickEveryone (true); @@ -624,8 +624,8 @@ void BotGraph::add (int type, const Vector &pos) { path->origin = newOrigin; addToBucket (newOrigin, index); - path->start = nullvec; - path->end = nullvec; + path->start = nullptr; + path->end = nullptr; path->display = 0.0f; path->light = 0.0f; @@ -634,7 +634,7 @@ void BotGraph::add (int type, const Vector &pos) { link.index = kInvalidNodeIndex; link.distance = 0; link.flags = 0; - link.velocity = nullvec; + link.velocity = nullptr; } @@ -804,7 +804,7 @@ void BotGraph::erase (int target) { link.index = kInvalidNodeIndex; link.flags = 0; link.distance = 0; - link.velocity = nullvec; + link.velocity = nullptr; } } } @@ -1037,9 +1037,8 @@ void BotGraph::calculatePathRadius (int index) { for (float scanDistance = 32.0f; scanDistance < 128.0f; scanDistance += 16.0f) { start = path.origin; - auto null = nullvec; - direction = null.forward () * scanDistance; + direction = Vector (0.0f, 0.0f, 0.0f).forward () * scanDistance; direction = direction.angles (); path.radius = scanDistance; @@ -1925,7 +1924,7 @@ void BotGraph::frame () { if (m_editor->v.button & IN_JUMP) { add (9); - m_timeJumpStarted = game.timebase (); + m_timeJumpStarted = game.time (); m_endJumpPoint = true; } else { @@ -1933,7 +1932,7 @@ void BotGraph::frame () { m_learnPosition = m_editor->v.origin; } } - else if (((m_editor->v.flags & FL_ONGROUND) || m_editor->v.movetype == MOVETYPE_FLY) && m_timeJumpStarted + 0.1f < game.timebase () && m_endJumpPoint) { + else if (((m_editor->v.flags & FL_ONGROUND) || m_editor->v.movetype == MOVETYPE_FLY) && m_timeJumpStarted + 0.1f < game.time () && m_endJumpPoint) { add (10); m_jumpLearnNode = false; @@ -1946,7 +1945,7 @@ void BotGraph::frame () { // find the distance from the last used node float distance = (m_lastNode - m_editor->v.origin).lengthSq (); - if (distance > 16384.0f) { + if (distance > cr::square (128.0f)) { // check that no other reachable nodes are nearby... for (const auto &path : m_paths) { if (isNodeReacheable (m_editor->v.origin, path.origin)) { @@ -1981,7 +1980,7 @@ void BotGraph::frame () { nearestDistance = distance; } - if (path.display + 0.8f < game.timebase ()) { + if (path.display + 0.8f < game.time ()) { float nodeHeight = 0.0f; // check the node height @@ -2045,7 +2044,7 @@ void BotGraph::frame () { game.drawLine (m_editor, path.origin - Vector (0, 0, nodeHalfHeight), path.origin - Vector (0, 0, nodeHalfHeight - nodeHeight * 0.75f), nodeWidth, 0, nodeColor, 250, 0, 10); // draw basic path game.drawLine (m_editor, path.origin - Vector (0, 0, nodeHalfHeight - nodeHeight * 0.75f), path.origin + Vector (0, 0, nodeHalfHeight), nodeWidth, 0, nodeFlagColor, 250, 0, 10); // draw additional path } - path.display = game.timebase (); + path.display = game.time (); } } } @@ -2057,7 +2056,7 @@ void BotGraph::frame () { // draw arrow to a some importaint nodes if (exists (m_findWPIndex) || exists (m_cacheNodeIndex) || exists (m_facingAtIndex)) { // check for drawing code - if (m_arrowDisplayTime + 0.5f < game.timebase ()) { + if (m_arrowDisplayTime + 0.5f < game.time ()) { // finding node - pink arrow if (m_findWPIndex != kInvalidNodeIndex) { @@ -2073,7 +2072,7 @@ void BotGraph::frame () { if (m_facingAtIndex != kInvalidNodeIndex) { game.drawLine (m_editor, m_editor->v.origin, m_paths[m_facingAtIndex].origin, 10, 0, Color (255, 255, 255), 200, 0, 5, DrawLine::Arrow); } - m_arrowDisplayTime = game.timebase (); + m_arrowDisplayTime = game.time (); } } @@ -2081,8 +2080,8 @@ void BotGraph::frame () { auto &path = m_paths[nearestIndex]; // draw a paths, camplines and danger directions for nearest node - if (nearestDistance <= 56.0f && m_pathDisplayTime <= game.timebase ()) { - m_pathDisplayTime = game.timebase () + 1.0f; + if (nearestDistance <= 56.0f && m_pathDisplayTime <= game.time ()) { + m_pathDisplayTime = game.time () + 1.0f; // draw the camplines if (path.flags & NodeFlag::Camp) { @@ -2214,7 +2213,7 @@ void BotGraph::frame () { } // draw entire message - MessageWriter (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, nullvec, m_editor) + MessageWriter (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, nullptr, m_editor) .writeByte (TE_TEXTMESSAGE) .writeByte (4) // channel .writeShort (MessageWriter::fs16 (0.0f, 13.0f)) // x @@ -2293,7 +2292,7 @@ bool BotGraph::checkNodes (bool teleportPlayer) { } if (path.flags & NodeFlag::Camp) { - if (path.end == nullvec) { + if (path.end.empty ()) { ctrl.msg ("Node %d Camp-Endposition not set!", path.number); return false; } @@ -2463,10 +2462,8 @@ bool BotGraph::isVisited (int index) { void BotGraph::addBasic () { // this function creates basic node types on map - edict_t *ent = nullptr; - // first of all, if map contains ladder points, create it - while (!game.isNullEntity (ent = engfuncs.pfnFindEntityByString (ent, "classname", "func_ladder"))) { + game.searchEntities ("classname", "func_ladder", [&] (edict_t *ent) { Vector ladderLeft = ent->v.absmin; Vector ladderRight = ent->v.absmax; ladderLeft.z = ladderRight.z; @@ -2474,7 +2471,7 @@ void BotGraph::addBasic () { TraceResult tr; Vector up, down, front, back; - 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 = front + diff; // front @@ -2509,18 +2506,19 @@ void BotGraph::addBasic () { add (3, point); } m_isOnLadder = false; - } - auto autoCreateForEntity = [](int type, const char *entity) { - edict_t *ent = nullptr; + return EntitySearchResult::Continue; + }); - while (!game.isNullEntity (ent = engfuncs.pfnFindEntityByString (ent, "classname", entity))) { + auto autoCreateForEntity = [] (int type, const char *entity) { + game.searchEntities ("classname", entity, [&] (edict_t *ent) { const Vector &pos = game.getAbsPos (ent); if (graph.getNearestNoBuckets (pos, 50.0f) == kInvalidNodeIndex) { graph.add (type, pos); } - } + return EntitySearchResult::Continue; + }); }; autoCreateForEntity (0, "info_player_deathmatch"); // then terrortist spawnpoints @@ -2583,7 +2581,7 @@ void BotGraph::setBombPos (bool reset, const Vector &pos) { // this function stores the bomb position as a vector if (reset) { - m_bombPos= nullvec; + m_bombPos= nullptr; bots.setBombPlanted (false); return; @@ -2593,14 +2591,14 @@ void BotGraph::setBombPos (bool reset, const Vector &pos) { m_bombPos = pos; return; } - edict_t *ent = nullptr; - - while (!game.isNullEntity (ent = engfuncs.pfnFindEntityByString (ent, "classname", "grenade"))) { + + game.searchEntities ("classname", "grenade", [&] (edict_t *ent) { if (strcmp (STRING (ent->v.model) + 9, "c4.mdl") == 0) { m_bombPos = game.getAbsPos (ent); - break; + return EntitySearchResult::Break; } - } + return EntitySearchResult::Continue; + }); } void BotGraph::startLearnJump () { @@ -2760,7 +2758,7 @@ void BotGraph::unassignPath (int from, int to) { link.index = kInvalidNodeIndex; link.distance = 0; link.flags = 0; - link.velocity = nullvec; + link.velocity = nullptr; setEditFlag (GraphEdit::On); m_hasChanged = true; diff --git a/source/interface.cpp b/source/linkage.cpp similarity index 96% rename from source/interface.cpp rename to source/linkage.cpp index c45f1bb..c869e46 100644 --- a/source/interface.cpp +++ b/source/linkage.cpp @@ -109,32 +109,16 @@ CR_EXPORT int GetEntityAPI2 (gamefuncs_t *functionTable, int *) { // server is enabled. Here is a good place to do our own game session initialization, and // to register by the engine side the server commands we need to administrate our bots. - // register bot cvars - game.registerCvars (); - // register logger logger.initialize (strings.format ("%slogs/yapb.log", graph.getDataDirectory (false)), [] (const char *msg) { game.print (msg); }); - conf.initWeapons (); - - // register server command(s) - game.registerCmd ("yapb", BotControl::handleEngineCommands); - game.registerCmd ("yb", BotControl::handleEngineCommands); - // set correct version string yb_version.set (strings.format ("%d.%d.%d", PRODUCT_VERSION_DWORD_INTERNAL, util.buildNumber ())); // execute main config conf.loadMainConfig (); - - // register fake metamod command handler if we not! under mm - if (!(game.is (GameFlags::Metamod))) { - game.registerCmd ("meta", [] () { - game.print ("You're launched standalone version of yapb. Metamod is not installed or not enabled!"); - }); - } conf.adjustWeaponPrices (); if (game.is (GameFlags::Metamod)) { @@ -177,14 +161,8 @@ CR_EXPORT int GetEntityAPI2 (gamefuncs_t *functionTable, int *) { if (!game.isNullEntity (pentTouched) && pentOther != game.getStartEntity ()) { auto bot = bots[pentTouched]; - if (bot != nullptr && pentOther != bot->ent ()) { - - if (util.isPlayer (pentOther)) { - bot->avoidIncomingPlayers (pentOther); - } - else { - bot->processBreakables (pentOther); - } + if (bot && pentOther != bot->ent () && !util.isPlayer (pentOther)) { + bot->checkBreakable (pentOther); } } @@ -397,9 +375,6 @@ CR_EXPORT int GetEntityAPI2 (gamefuncs_t *functionTable, int *) { // for example if a new player joins the server, we should disconnect a bot, and if the // player population decreases, we should fill the server with other bots. - // run periodic update of bot states - bots.frame (); - // update lightstyle animations illum.animateLight (); @@ -430,7 +405,7 @@ CR_EXPORT int GetEntityAPI2 (gamefuncs_t *functionTable, int *) { dllapi.pfnStartFrame (); // run the bot ai - bots.slowFrame (); + bots.frame (); }; functionTable->pfnCmdStart = [] (const edict_t *player, usercmd_t *cmd, unsigned int random_seed) { @@ -504,7 +479,8 @@ CR_EXPORT int GetEntityAPI2_Post (gamefuncs_t *table, int *) { // for the bots by the MOD side, remember). Post version called only by metamod. // run the bot ai - bots.slowFrame (); + bots.frame (); + RETURN_META (MRES_IGNORED); }; @@ -578,17 +554,19 @@ CR_EXPORT int GetEngineFunctions (enginefuncs_t *functionTable, int *) { engfuncs.pfnLightStyle (style, val); }; - functionTable->pfnFindEntityByString = [] (edict_t *edictStartSearchAfter, const char *field, const char *value) { - // round starts in counter-strike 1.5 - if ((game.is (GameFlags::Legacy)) && strcmp (value, "info_map_parameters") == 0) { - bots.initRound (); - } + if (game.is (GameFlags::Legacy)) { + functionTable->pfnFindEntityByString = [] (edict_t *edictStartSearchAfter, const char *field, const char *value) { + // round starts in counter-strike 1.5 + if (strcmp (value, "info_map_parameters") == 0) { + bots.initRound (); + } - if (game.is (GameFlags::Metamod)) { - RETURN_META_VALUE (MRES_IGNORED, static_cast (nullptr)); - } - return engfuncs.pfnFindEntityByString (edictStartSearchAfter, field, value); - }; + if (game.is (GameFlags::Metamod)) { + RETURN_META_VALUE (MRES_IGNORED, static_cast (nullptr)); + } + return engfuncs.pfnFindEntityByString (edictStartSearchAfter, field, value); + }; + } functionTable->pfnEmitSound = [] (edict_t *entity, int channel, const char *sample, float volume, float attenuation, int flags, int pitch) { // this function tells the engine that the entity pointed to by "entity", is emitting a sound @@ -601,7 +579,7 @@ CR_EXPORT int GetEngineFunctions (enginefuncs_t *functionTable, int *) { // SoundAttachToThreat() to bring the sound to the ears of the bots. Since bots have no client DLL // to handle this for them, such a job has to be done manually. - util.attachSoundsToClients (entity, sample, volume); + util.listenNoise (entity, sample, volume); if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); @@ -863,7 +841,7 @@ CR_EXPORT int Meta_Query (char *, plugin_info_t **pPlugInfo, mutil_funcs_t *pMet return TRUE; // tell metamod this plugin looks safe } -CR_EXPORT int Meta_Attach (PLUG_LOADTIME, metamod_funcs_t *functionTable, meta_globals_t *pMGlobals, gamedll_funcs_t *pGamedllFuncs) { +CR_EXPORT int Meta_Attach (PLUG_LOADTIME now, metamod_funcs_t *functionTable, meta_globals_t *pMGlobals, gamedll_funcs_t *pGamedllFuncs) { // this function is called when metamod attempts to load the plugin. Since it's the place // where we can tell if the plugin will be allowed to run or not, we wait until here to make // our initialization stuff, like registering CVARs and dedicated server commands. @@ -880,6 +858,11 @@ CR_EXPORT int Meta_Attach (PLUG_LOADTIME, metamod_funcs_t *functionTable, meta_g nullptr, // pfnGetEngineFunctions_Post () }; + if (now > Plugin_info.loadable) { + logger.error ("%s: plugin NOT attaching (can't load plugin right now)", Plugin_info.name); + return FALSE; // returning FALSE prevents metamod from attaching this plugin + } + // keep track of the pointers to engine function tables metamod gives us gpMetaGlobals = pMGlobals; memcpy (functionTable, &metamodFunctionTable, sizeof (metamod_funcs_t)); @@ -888,10 +871,14 @@ CR_EXPORT int Meta_Attach (PLUG_LOADTIME, metamod_funcs_t *functionTable, meta_g return TRUE; // returning true enables metamod to attach this plugin } -CR_EXPORT int Meta_Detach (PLUG_LOADTIME, PL_UNLOAD_REASON) { +CR_EXPORT int Meta_Detach (PLUG_LOADTIME now, PL_UNLOAD_REASON reason) { // this function is called when metamod unloads the plugin. A basic check is made in order // to prevent unloading the plugin if its processing should not be interrupted. + if (now > Plugin_info.unloadable && reason != PNL_CMD_FORCED) { + logger.error ("%s: plugin NOT detaching (can't unload plugin right now)", Plugin_info.name); + return FALSE; // returning FALSE prevents metamod from unloading this plugin + } bots.kickEveryone (true); // kick all bots off this server // save collected experience on shutdown diff --git a/source/manager.cpp b/source/manager.cpp index 6d3e9b4..7aebd40 100644 --- a/source/manager.cpp +++ b/source/manager.cpp @@ -250,23 +250,15 @@ Bot *BotManager::findAliveBot () { return nullptr; } -void BotManager::slowFrame () { - // this function calls showframe function for all available at call moment bots - - for (const auto &bot : m_bots) { - bot->slowFrame (); - } -} - void BotManager::frame () { - // this function calls periodic frame function for all available at call moment bots + // this function calls showframe function for all available at call moment bots for (const auto &bot : m_bots) { bot->frame (); } // select leader each team somewhere in round start - if (m_timeRoundStart + 5.0f > game.timebase () && m_timeRoundStart + 10.0f < game.timebase ()) { + if (m_timeRoundStart + 5.0f > game.time () && m_timeRoundStart + 10.0f < game.time ()) { for (int team = 0; team < kGameTeamNum; ++team) { selectLeaders (team, false); } @@ -319,7 +311,7 @@ void BotManager::maintainQuota () { } // bot's creation update - if (!m_creationTab.empty () && m_maintainTime < game.timebase ()) { + if (!m_creationTab.empty () && m_maintainTime < game.time ()) { const CreateQueue &last = m_creationTab.pop (); const BotCreateResult callResult = create (last.name, last.difficulty, last.personality, last.team, last.member); @@ -342,11 +334,11 @@ void BotManager::maintainQuota () { m_creationTab.clear (); yb_quota.set (getBotCount ()); } - m_maintainTime = game.timebase () + 0.10f; + m_maintainTime = game.time () + 0.10f; } // now keep bot number up to date - if (m_quotaMaintainTime > game.timebase ()) { + if (m_quotaMaintainTime > game.time ()) { return; } yb_quota.set (cr::clamp (yb_quota.int_ (), 0, game.maxClients ())); @@ -410,7 +402,7 @@ void BotManager::maintainQuota () { kickRandom (false, Team::Unassigned); } } - m_quotaMaintainTime = game.timebase () + 0.40f; + m_quotaMaintainTime = game.time () + 0.40f; } void BotManager::reset () { @@ -467,8 +459,8 @@ void BotManager::decrementQuota (int by) { } void BotManager::initQuota () { - m_maintainTime = game.timebase () + yb_join_delay.float_ (); - m_quotaMaintainTime = game.timebase () + yb_join_delay.float_ (); + m_maintainTime = game.time () + yb_join_delay.float_ (); + m_quotaMaintainTime = game.time () + yb_join_delay.float_ (); m_creationTab.clear (); } @@ -855,8 +847,8 @@ Bot::Bot (edict_t *bot, int difficulty, int personality, int team, int member) { m_difficulty = cr::clamp (difficulty, 0, 4); m_basePing = rg.int_ (7, 14); - m_lastCommandTime = game.timebase () - 0.1f; - m_frameInterval = game.timebase (); + m_lastCommandTime = game.time () - 0.1f; + m_frameInterval = game.time (); m_slowFrameTimestamp = 0.0f; // stuff from jk_botti @@ -892,7 +884,7 @@ Bot::Bot (edict_t *bot, int difficulty, int personality, int team, int member) { // copy them over to the temp level variables m_agressionLevel = m_baseAgressionLevel; m_fearLevel = m_baseFearLevel; - m_nextEmotionUpdate = game.timebase () + 0.5f; + m_nextEmotionUpdate = game.time () + 0.5f; // just to be sure m_actMessageIndex = 0; @@ -906,7 +898,7 @@ Bot::Bot (edict_t *bot, int difficulty, int personality, int team, int member) { } float Bot::getFrameInterval () { - return cr::fzero (m_thinkInterval) ? m_frameInterval : m_thinkInterval; + return m_frameInterval; } int BotManager::getHumansCount (bool ignoreSpectators) { @@ -977,10 +969,10 @@ void BotManager::handleDeath (edict_t *killer, edict_t *victim) { for (const auto ¬ify : bots) { if (notify->m_notKilled && killerTeam == notify->m_team && killerTeam != victimTeam && killer != notify->ent () && notify->seesEntity (victim->v.origin)) { if (!(killer->v.flags & FL_FAKECLIENT)) { - notify->processChatterMessage ("#Bot_NiceShotCommander"); + notify->handleChatter ("#Bot_NiceShotCommander"); } else { - notify->processChatterMessage ("#Bot_NiceShotPall"); + notify->handleChatter ("#Bot_NiceShotPall"); } break; } @@ -991,9 +983,9 @@ void BotManager::handleDeath (edict_t *killer, edict_t *victim) { // notice nearby to victim teammates, that attacker is near for (const auto ¬ify : bots) { - if (notify->m_seeEnemyTime + 2.0f < game.timebase () && notify->m_notKilled && notify->m_team == victimTeam && game.isNullEntity (notify->m_enemy) && killerTeam != victimTeam && util.isVisible (killer->v.origin, notify->ent ())) { + if (notify->m_seeEnemyTime + 2.0f < game.time () && notify->m_notKilled && notify->m_team == victimTeam && game.isNullEntity (notify->m_enemy) && killerTeam != victimTeam && util.isVisible (killer->v.origin, notify->ent ())) { notify->m_actualReactionTime = 0.0f; - notify->m_seeEnemyTime = game.timebase (); + notify->m_seeEnemyTime = game.time (); notify->m_enemy = killer; notify->m_lastEnemy = killer; notify->m_lastEnemyOrigin = killer->v.origin; @@ -1032,11 +1024,11 @@ void Bot::newRound () { clearSearchNodes (); clearRoute (); - m_pathOrigin= nullvec; - m_destOrigin= nullvec; + m_pathOrigin= nullptr; + m_destOrigin= nullptr; m_path = nullptr; m_currentTravelFlags = 0; - m_desiredVelocity= nullvec; + m_desiredVelocity= nullptr; m_currentNodeIndex = kInvalidNodeIndex; m_prevGoalIndex = kInvalidNodeIndex; m_chosenGoalIndex = kInvalidNodeIndex; @@ -1053,13 +1045,10 @@ void Bot::newRound () { m_oldButtons = pev->button; m_rechoiceGoalCount = 0; - m_avoid = nullptr; - m_avoidTime = 0.0f; - for (i = 0; i < 5; ++i) { m_previousNodes[i] = kInvalidNodeIndex; } - m_navTimeset = game.timebase (); + m_navTimeset = game.time (); m_team = game.getTeam (ent ()); m_isVIP = false; @@ -1093,9 +1082,9 @@ void Bot::newRound () { m_minSpeed = 260.0f; m_prevSpeed = 0.0f; m_prevOrigin = Vector (kInfiniteDistance, kInfiniteDistance, kInfiniteDistance); - m_prevTime = game.timebase (); - m_lookUpdateTime = game.timebase (); - m_aimErrorTime = game.timebase (); + m_prevTime = game.time (); + m_lookUpdateTime = game.time (); + m_aimErrorTime = game.time (); m_viewDistance = 4096.0f; m_maxViewDistance = 4096.0f; @@ -1106,7 +1095,7 @@ void Bot::newRound () { m_itemCheckTime = 0.0f; m_breakableEntity = nullptr; - m_breakableOrigin= nullvec; + m_breakableOrigin= nullptr; m_timeDoorOpen = 0.0f; resetCollision (); @@ -1115,7 +1104,7 @@ void Bot::newRound () { m_enemy = nullptr; m_lastVictim = nullptr; m_lastEnemy = nullptr; - m_lastEnemyOrigin= nullvec; + m_lastEnemyOrigin= nullptr; m_trackingEdict = nullptr; m_timeNextTracking = 0.0f; @@ -1139,9 +1128,9 @@ void Bot::newRound () { m_aimFlags = 0; m_liftState = 0; - m_aimLastError= nullvec; - m_position= nullvec; - m_liftTravelPos= nullvec; + m_aimLastError= nullptr; + m_position= nullptr; + m_liftTravelPos= nullptr; setIdealReactionTimers (true); @@ -1157,8 +1146,8 @@ void Bot::newRound () { m_reloadState = Reload::None; m_reloadCheckTime = 0.0f; - m_shootTime = game.timebase (); - m_playerTargetTime = game.timebase (); + m_shootTime = game.time (); + m_playerTargetTime = game.time (); m_firePause = 0.0f; m_timeLastFired = 0.0f; @@ -1174,7 +1163,7 @@ void Bot::newRound () { m_jumpFinished = false; m_isStuck = false; - m_sayTextBuffer.timeNextChat = game.timebase (); + m_sayTextBuffer.timeNextChat = game.time (); m_sayTextBuffer.entityIndex = -1; m_sayTextBuffer.sayText.clear (); @@ -1189,10 +1178,10 @@ void Bot::newRound () { m_currentWeapon = 0; } m_flashLevel = 100.0f; - m_checkDarkTime = game.timebase (); + m_checkDarkTime = game.time (); - m_knifeAttackTime = game.timebase () + rg.float_ (1.3f, 2.6f); - m_nextBuyTime = game.timebase () + rg.float_ (0.6f, 2.0f); + m_knifeAttackTime = game.time () + rg.float_ (1.3f, 2.6f); + m_nextBuyTime = game.time () + rg.float_ (0.6f, 2.0f); m_buyPending = false; m_inBombZone = false; @@ -1217,9 +1206,9 @@ void Bot::newRound () { m_defendHostage = false; m_headedTime = 0.0f; - m_timeLogoSpray = game.timebase () + rg.float_ (5.0f, 30.0f); - m_spawnTime = game.timebase (); - m_lastChatTime = game.timebase (); + m_timeLogoSpray = game.time () + rg.float_ (5.0f, 30.0f); + m_spawnTime = game.time (); + m_lastChatTime = game.time (); m_timeCamping = 0.0f; m_campDirection = 0; @@ -1227,7 +1216,7 @@ void Bot::newRound () { m_campButtons = 0; m_soundUpdateTime = 0.0f; - m_heardSoundTime = game.timebase (); + m_heardSoundTime = game.time (); // clear its message queue for (i = 0; i < 32; ++i) { @@ -1243,7 +1232,8 @@ void Bot::newRound () { if (rg.chance (50)) { pushChatterMessage (Chatter::NewRound); } - m_thinkInterval = game.is (GameFlags::Legacy | GameFlags::Xash3D) ? 0.0f : (1.0f / cr::clamp (yb_think_fps.float_ (), 30.0f, 90.0f)) * rg.float_ (0.95f, 1.05f); + m_updateInterval = game.is (GameFlags::Legacy | GameFlags::Xash3D) ? 0.0f : (1.0f / cr::clamp (yb_think_fps.float_ (), 30.0f, 60.0f)); + m_viewUpdateInterval = 1.0f / 30.0f; } void Bot::kill () { @@ -1344,15 +1334,6 @@ void BotManager::captureChatRadio (const char *cmd, const char *arg, edict_t *en } if (plat.caseStrMatch (cmd, "say") || plat.caseStrMatch (cmd, "say_team")) { - if (strcmp (arg, "dropme") == 0 || strcmp (arg, "dropc4") == 0) { - Bot *bot = nullptr; - - if (util.findNearestPlayer (reinterpret_cast (&bot), ent, 300.0f, true, true, true)) { - bot->dropWeaponForUser (ent, strings.isEmpty (strstr (arg, "c4")) ? false : true); - } - return; - } - bool alive = util.isAlive (ent); int team = -1; @@ -1373,7 +1354,7 @@ void BotManager::captureChatRadio (const char *cmd, const char *arg, edict_t *en continue; } target->m_sayTextBuffer.sayText = engfuncs.pfnCmd_Args (); - target->m_sayTextBuffer.timeNextChat = game.timebase () + target->m_sayTextBuffer.chatDelay; + target->m_sayTextBuffer.timeNextChat = game.time () + target->m_sayTextBuffer.chatDelay; } } } @@ -1396,7 +1377,7 @@ void BotManager::captureChatRadio (const char *cmd, const char *arg, edict_t *en } } } - bots.setLastRadioTimestamp (target.team, game.timebase ()); + bots.setLastRadioTimestamp (target.team, game.time ()); } target.radio = 0; } @@ -1419,59 +1400,61 @@ void BotManager::notifyBombDefuse () { } void BotManager::updateActiveGrenade () { - if (m_grenadeUpdateTime > game.timebase ()) { + if (m_grenadeUpdateTime > game.time ()) { return; } - edict_t *grenade = nullptr; - - // clear previously stored grenades - m_activeGrenades.clear (); + m_activeGrenades.clear (); // clear previously stored grenades // search the map for any type of grenade - while (!game.isNullEntity (grenade = engfuncs.pfnFindEntityByString (grenade, "classname", "grenade"))) { + game.searchEntities ("classname", "grenade", [&] (edict_t *e) { // do not count c4 as a grenade - if (strcmp (STRING (grenade->v.model) + 9, "c4.mdl") == 0) { - continue; + if (strcmp (STRING (e->v.model) + 9, "c4.mdl") == 0) { + return EntitySearchResult::Continue; } - m_activeGrenades.push (grenade); - } - m_grenadeUpdateTime = game.timebase () + 0.213f; + m_activeGrenades.push (e); + + // continue iteration + return EntitySearchResult::Continue; + }); + m_grenadeUpdateTime = game.time () + 0.25f; } void BotManager::updateIntrestingEntities () { - if (m_entityUpdateTime > game.timebase ()) { + if (m_entityUpdateTime > game.time ()) { return; } // clear previously stored entities m_intrestingEntities.clear (); - // search the map for entities - for (int i = kGameMaxPlayers - 1; i < globals->maxEntities; ++i) { - auto ent = game.entityOfIndex (i); + // search the map for any type of grenade + game.searchEntities (nullptr, kInfiniteDistance, [&] (edict_t *e) { + auto classname = STRING (e->v.classname); - // only valid drawn entities - if (game.isNullEntity (ent) || ent->free || ent->v.classname == 0 || (ent->v.effects & EF_NODRAW)) { - continue; - } - auto classname = STRING (ent->v.classname); - // search for grenades, weaponboxes, weapons, items and armoury entities if (strncmp ("weapon", classname, 6) == 0 || strncmp ("grenade", classname, 7) == 0 || strncmp ("item", classname, 4) == 0 || strncmp ("armoury", classname, 7) == 0) { - m_intrestingEntities.push (ent); + m_intrestingEntities.push (e); } // pickup some csdm stuff if we're running csdm if (game.mapIs (MapFlags::HostageRescue) && strncmp ("hostage", classname, 7) == 0) { - m_intrestingEntities.push (ent); + m_intrestingEntities.push (e); } - + + // add buttons + if (game.mapIs (MapFlags::HasButtons) && strncmp ("func_button", classname, 11) == 0) { + m_intrestingEntities.push (e); + } + // pickup some csdm stuff if we're running csdm if (game.is (GameFlags::CSDM) && strncmp ("csdm", classname, 4) == 0) { - m_intrestingEntities.push (ent); + m_intrestingEntities.push (e); } - } - m_entityUpdateTime = game.timebase () + 0.5f; + + // continue iteration + return EntitySearchResult::Continue; + }); + m_entityUpdateTime = game.time () + 0.5f; } void BotManager::selectLeaders (int team, bool reset) { @@ -1606,14 +1589,14 @@ void BotManager::initRound () { graph.updateGlobalPractice (); // update experience data on round start // calculate the round mid/end in world time - m_timeRoundStart = game.timebase () + mp_freezetime.float_ (); + m_timeRoundStart = game.time () + mp_freezetime.float_ (); m_timeRoundMid = m_timeRoundStart + mp_roundtime.float_ () * 60.0f * 0.5f; m_timeRoundEnd = m_timeRoundStart + mp_roundtime.float_ () * 60.0f; } void BotManager::setBombPlanted (bool isPlanted) { if (isPlanted) { - m_timeBombPlanted = game.timebase (); + m_timeBombPlanted = game.time (); } m_bombPlanted = isPlanted; } @@ -1680,6 +1663,12 @@ void BotConfig::loadMainConfig () { auto value = const_cast (keyval[1].trim ().trim ("\"").trim ().chars ()); if (needsToIgnoreVar (ignore, key) && !plat.caseStrMatch (value, cvar->string)) { + + // preserve quota number if it's zero + if (plat.caseStrMatch (cvar->name, "yb_quota") && yb_quota.int_ () <= 0) { + engfuncs.pfnCvar_DirectSet (cvar, value); + continue; + } game.print ("Bot CVAR '%s' differs from the stored in the config (%s/%s). Ignoring.", cvar->name, cvar->string, value); // ensure cvar will have old value @@ -2183,6 +2172,8 @@ void BotConfig::clearUsedName (Bot *bot) { } void BotConfig::initWeapons () { + m_weapons.clear (); + // fill array with available weapons m_weapons.emplace (Weapon::Knife, "weapon_knife", "knife.mdl", 0, 0, -1, -1, 0, 0, 0, 0, 0, 0, true ); m_weapons.emplace (Weapon::USP, "weapon_usp", "usp.mdl", 500, 1, -1, -1, 1, 1, 2, 2, 0, 12, false ); diff --git a/source/message.cpp b/source/message.cpp index 98c22ea..f1a3d3b 100644 --- a/source/message.cpp +++ b/source/message.cpp @@ -23,7 +23,7 @@ void MessageDispatcher::netMsgTextMsg () { auto notify = bots.findAliveBot (); if (notify && notify->m_notKilled) { - notify->processChatterMessage (m_args[msg].chars_); + notify->handleChatter (m_args[msg].chars_); } } @@ -52,6 +52,14 @@ void MessageDispatcher::netMsgTextMsg () { else if (cached & TextMsgCache::RestartRound) { bots.updateTeamEconomics (Team::CT, true); bots.updateTeamEconomics (Team::Terrorist, true); + + extern ConVar mp_startmoney; + + // set balance for all players + bots.forEach ([] (Bot *bot) { + bot->m_moneyAmount = mp_startmoney.int_ (); + return false; + }); } else if (cached & TextMsgCache::TerroristWin) { bots.setLastWinner (Team::Terrorist); // update last winner for economics @@ -157,7 +165,7 @@ void MessageDispatcher::netMsgCurWeapon () { // ammo amount decreased ? must have fired a bullet... if (m_args[id].long_ == m_bot->m_currentWeapon && m_bot->m_ammoInClip[m_args[id].long_] > m_args[clip].long_) { - m_bot->m_timeLastFired = game.timebase (); // remember the last bullet time + m_bot->m_timeLastFired = game.time (); // remember the last bullet time } m_bot->m_ammoInClip[m_args[id].long_] = m_args[clip].long_; } @@ -201,7 +209,7 @@ void MessageDispatcher::netMsgDamage () { // handle damage if any if (m_args[armor].long_ > 0 || m_args[health].long_) { - m_bot->processDamage (m_bot->pev->dmg_inflictor, m_args[health].long_, m_args[armor].long_, m_args[bits].long_); + m_bot->takeDamage (m_bot->pev->dmg_inflictor, m_args[health].long_, m_args[armor].long_, m_args[bits].long_); } } @@ -237,7 +245,7 @@ void MessageDispatcher::netMsgStatusIcon () { m_bot->m_inBuyZone = (m_args[enabled].long_ != 0); // try to equip in buyzone - m_bot->processBuyzoneEntering (BuyState::PrimaryWeapon); + m_bot->enteredBuyZone (BuyState::PrimaryWeapon); } else if (cached & StatusIconCache::VipSafety) { m_bot->m_inVIPZone = (m_args[enabled].long_ != 0); @@ -278,7 +286,7 @@ void MessageDispatcher::netMsgScreenFade () { // screen completely faded ? if (m_args[r].long_ >= 255 && m_args[g].long_ >= 255 && m_args[b].long_ >= 255 && m_args[alpha].long_ > 170) { - m_bot->processBlind (m_args[alpha].long_); + m_bot->takeBlind (m_args[alpha].long_); } } diff --git a/source/navigate.cpp b/source/navigate.cpp index caf9e40..c9387ea 100644 --- a/source/navigate.cpp +++ b/source/navigate.cpp @@ -16,17 +16,22 @@ int Bot::findBestGoal () { // chooses a destination (goal) node for a bot if (!bots.isBombPlanted () && m_team == Team::Terrorist && game.mapIs (MapFlags::Demolition)) { - edict_t *pent = nullptr; + int result = kInvalidNodeIndex; - while (!game.isNullEntity (pent = engfuncs.pfnFindEntityByString (pent, "classname", "weaponbox"))) { - if (strcmp (STRING (pent->v.model), "models/w_backpack.mdl") == 0) { - int index = graph.getNearest (game.getAbsPos (pent)); + game.searchEntities ("classname", "weaponbox", [&] (edict_t *ent) { + if (strcmp (STRING (ent->v.model), "models/w_backpack.mdl") == 0) { + result = graph.getNearest (game.getAbsPos (ent)); - if (graph.exists (index)) { - return m_loosedBombNodeIndex = index; + if (graph.exists (result)) { + return EntitySearchResult::Break; } - break; } + return EntitySearchResult::Continue; + }); + + // found one ? + if (graph.exists (result)) { + return m_loosedBombNodeIndex = result; } // forcing terrorist bot to not move to another bomb spot @@ -110,10 +115,10 @@ int Bot::findBestGoal () { defensive += 10.0f; } } - else if (game.mapIs (MapFlags::Demolition) && m_team == Team::Terrorist && bots.getRoundStartTime () + 10.0f < game.timebase ()) { + 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 = findDefendNode (graph.getBombPos ()); + return m_chosenGoalIndex = graph.getNearest (graph.getBombPos ()); } } @@ -167,7 +172,7 @@ int Bot::findGoalPost (int tactic, IntArray *defensive, IntArray *offsensive) { else if (tactic == 3 && !graph.m_goalPoints.empty ()) // map goal node { // force bomber to select closest goal, if round-start goal was reset by something - if (m_hasC4 && bots.getRoundStartTime () + 20.0f < game.timebase ()) { + if (m_hasC4 && bots.getRoundStartTime () + 20.0f < game.time ()) { float minDist = kInfiniteDistance; int count = 0; @@ -232,7 +237,7 @@ void Bot::postprocessGoals (const IntArray &goals, int *result) { for (int index = 0; index < 4; ++index) { int rand = goals.random (); - if (searchCount <= 8 && (m_prevGoalIndex == rand || ((result[0] == rand || result[1] == rand || result[2] == rand || result[3] == rand) && goals.length () > 4)) && !isOccupiedPoint (rand)) { + if (searchCount <= 8 && (m_prevGoalIndex == rand || ((result[0] == rand || result[1] == rand || result[2] == rand || result[3] == rand) && goals.length () > 4)) && !isOccupiedNode (rand)) { if (index > 0) { index--; } @@ -274,80 +279,80 @@ void Bot::resetCollision () { void Bot::ignoreCollision () { resetCollision (); - m_prevTime = game.timebase () + 1.2f; - m_lastCollTime = game.timebase () + 1.5f; + m_prevTime = game.time () + 1.2f; + m_lastCollTime = game.time () + 1.5f; m_isStuck = false; m_checkTerrain = false; m_prevSpeed = m_moveSpeed; m_prevOrigin = pev->origin; } -void Bot::avoidIncomingPlayers (edict_t *touch) { - auto task = getCurrentTaskId (); - - if (task == Task::PlantBomb || task == Task::DefuseBomb || task == Task::Camp || m_moveSpeed <= 100.0f || m_avoidTime > game.timebase ()) { - return; - } - - int ownId = entindex (); - int otherId = game.indexOfPlayer (touch); - - if (ownId < otherId) { - return; - } - - if (m_avoid) { - int currentId = game.indexOfPlayer (m_avoid); - - if (currentId < otherId) { - return; - } - } - m_avoid = touch; - m_avoidTime = game.timebase () + 0.33f + getFrameInterval (); -} - bool Bot::doPlayerAvoidance (const Vector &normal) { - // avoid collision entity, got it form official csbot + edict_t *hindrance = nullptr; + float distance = cr::square (300.0f); - if (m_avoidTime > game.timebase () && util.isAlive (m_avoid)) { - Vector dir (cr::cosf (pev->v_angle.y), cr::sinf (pev->v_angle.y), 0.0f); - Vector lat (-dir.y, dir.x, 0.0f); - Vector to = Vector (m_avoid->v.origin.x - pev->origin.x, m_avoid->v.origin.y - pev->origin.y, 0.0f).normalize (); + // find nearest player to bot + for (const auto &client : util.getClients ()) { - float toProj = to.x * dir.x + to.y * dir.y; - float latProj = to.x * lat.x + to.y * lat.y; - - constexpr float c = 0.5f; - - if (toProj > c) { - m_moveSpeed = -pev->maxspeed; - - if (m_avoid->v.button & IN_DUCK) { - m_moveSpeed = pev->maxspeed; - pev->button |= IN_JUMP; - } - return true; - } - else if (toProj < -c) { - m_moveSpeed = pev->maxspeed; - return true; + // need only good meat + if (!(client.flags & ClientFlags::Used)) { + continue; } - if (latProj >= c) { - pev->button |= IN_MOVELEFT; - setStrafeSpeed (normal, -pev->maxspeed); - return true; + // and still alive meet + if (!(client.flags & ClientFlags::Alive)) { + continue; } - else if (latProj <= -c) { - pev->button |= IN_MOVERIGHT; - setStrafeSpeed (normal, pev->maxspeed); - return true; + + // our team, alive and not myself? + if (client.team != m_team || client.ent == ent ()) { + continue; } + auto nearest = (client.ent->v.origin - pev->origin).lengthSq (); + + if (nearest < cr::square (pev->maxspeed) && nearest < distance) { + hindrance = client.ent; + distance = nearest; + } + } + + // found somebody? + if (!hindrance) { return false; } - else { - m_avoid = nullptr; + const float interval = getFrameInterval (); + + // use our movement angles, try to predict where we should be next frame + Vector right, forward; + m_moveAngles.angleVectors (&forward, &right, nullptr); + + Vector predict = pev->origin + forward * m_moveSpeed * interval; + + predict += right * m_strafeSpeed * interval; + predict += pev->velocity * interval; + + auto movedDistance = (hindrance->v.origin - predict).lengthSq (); + auto nextFrameDistance = ((hindrance->v.origin + hindrance->v.velocity * interval) - pev->origin).lengthSq (); + + // is player that near now or in future that we need to steer away? + if (movedDistance <= cr::square (48.0f) || (distance <= cr::square (56.0f) && nextFrameDistance < distance)) { + auto dir = (pev->origin - hindrance->v.origin).normalize2d (); + + // to start strafing, we have to first figure out if the target is on the left side or right side + if ((dir | right.get2d ()) > 0.0f) { + setStrafeSpeed (normal, pev->maxspeed); + } + else { + setStrafeSpeed (normal, -pev->maxspeed); + } + +#if 0 + if (distance < cr::square (56.0f)) { + if ((dir | forward.get2d ()) < 0.0f) + m_moveSpeed = -pev->maxspeed; + } +#endif + return true; } return false; } @@ -356,22 +361,20 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { m_isStuck = false; // if avoiding someone do not consider stuck - if (doPlayerAvoidance (dirNormal)) { - return; - } TraceResult tr; + doPlayerAvoidance (dirNormal); // Standing still, no need to check? // FIXME: doesn't care for ladder movement (handled separately) should be included in some way - if ((m_moveSpeed >= 10.0f || m_strafeSpeed >= 10.0f) && m_lastCollTime < game.timebase () && m_seeEnemyTime + 0.8f < game.timebase () && getCurrentTaskId () != Task::Attack) { + if ((m_moveSpeed >= 10.0f || m_strafeSpeed >= 10.0f) && m_lastCollTime < game.time () && m_seeEnemyTime + 0.8f < game.time () && getCurrentTaskId () != Task::Attack) { // didn't we move enough previously? if (movedDistance < 2.0f && m_prevSpeed >= 20.0f) { - m_prevTime = game.timebase (); // then consider being stuck + m_prevTime = game.time (); // then consider being stuck m_isStuck = true; if (cr::fzero (m_firstCollideTime)) { - m_firstCollideTime = game.timebase () + 0.2f; + m_firstCollideTime = game.time () + 0.2f; } } // not stuck yet @@ -379,9 +382,9 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { // test if there's something ahead blocking the way if (cantMoveForward (dirNormal, &tr) && !isOnLadder ()) { if (m_firstCollideTime == 0.0f) { - m_firstCollideTime = game.timebase () + 0.2f; + m_firstCollideTime = game.time () + 0.2f; } - else if (m_firstCollideTime <= game.timebase ()) { + else if (m_firstCollideTime <= game.time ()) { m_isStuck = true; } } @@ -392,7 +395,7 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { // not stuck? if (!m_isStuck) { - if (m_probeTime + 0.5f < game.timebase ()) { + if (m_probeTime + 0.5f < game.time ()) { resetCollision (); // reset collision memory if not being stuck for 0.5 secs } else { @@ -419,7 +422,7 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { bits |= (CollisionProbe::Jump | CollisionProbe::Strafe); } else { - bits |= (CollisionProbe::Strafe | (m_jumpStateTimer < game.timebase () ? CollisionProbe::Jump : 0)); + bits |= (CollisionProbe::Strafe | (m_jumpStateTimer < game.time () ? CollisionProbe::Jump : 0)); } // collision check allowed if not flying through the air @@ -439,10 +442,10 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { // to start strafing, we have to first figure out if the target is on the left side or right side Vector right, forward; - m_moveAngles.buildVectors (&forward, &right, nullptr); + m_moveAngles.angleVectors (&forward, &right, nullptr); - Vector dirToPoint = (pev->origin - m_destOrigin).normalize2d (); - Vector rightSide = right.normalize2d (); + const Vector &dirToPoint = (pev->origin - m_destOrigin).normalize2d (); + const Vector &rightSide = right.normalize2d (); bool dirRight = false; bool dirLeft = false; @@ -585,8 +588,8 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { m_collideMoves[i] = state[i]; } - m_collideTime = game.timebase (); - m_probeTime = game.timebase () + 0.5f; + m_collideTime = game.time (); + m_probeTime = game.time () + 0.5f; m_collisionProbeBits = bits; m_collisionState = CollisionState::Probing; m_collStateIndex = 0; @@ -594,12 +597,12 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { } if (m_collisionState == CollisionState::Probing) { - if (m_probeTime < game.timebase ()) { + if (m_probeTime < game.time ()) { m_collStateIndex++; - m_probeTime = game.timebase () + 0.5f; + m_probeTime = game.time () + 0.5f; if (m_collStateIndex > kMaxCollideMoves) { - m_navTimeset = game.timebase () - 5.0f; + m_navTimeset = game.time () - 5.0f; resetCollision (); } } @@ -609,7 +612,7 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { case CollisionState::Jump: if (isOnFloor () || isInWater ()) { pev->button |= IN_JUMP; - m_jumpStateTimer = game.timebase () + rg.float_ (0.7f, 1.5f); + m_jumpStateTimer = game.time () + rg.float_ (0.7f, 1.5f); } break; @@ -620,12 +623,10 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { break; case CollisionState::StrafeLeft: - pev->button |= IN_MOVELEFT; setStrafeSpeed (dirNormal, -pev->maxspeed); break; case CollisionState::StrafeRight: - pev->button |= IN_MOVERIGHT; setStrafeSpeed (dirNormal, pev->maxspeed); break; } @@ -637,8 +638,6 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { bool Bot::updateNavigation () { // this function is a main path navigation - TraceResult tr, tr2; - // check if we need to find a node... if (m_currentNodeIndex == kInvalidNodeIndex) { findValidNode (); @@ -648,7 +647,7 @@ bool Bot::updateNavigation () { if (m_path->radius > 0.0f) { m_pathOrigin = m_pathOrigin + Vector (pev->angles.x, cr::normalizeAngles (pev->angles.y + rg.float_ (-90.0f, 90.0f)), 0.0f).forward () * rg.float_ (0, m_path->radius); } - m_navTimeset = game.timebase (); + m_navTimeset = game.time (); } m_destOrigin = m_pathOrigin + pev->view_ofs; @@ -668,7 +667,7 @@ bool Bot::updateNavigation () { m_jumpFinished = true; m_checkTerrain = false; - m_desiredVelocity= nullvec; + m_desiredVelocity= nullptr; } } else if (!yb_jasonmode.bool_ () && m_currentWeapon == Weapon::Knife && isOnFloor ()) { @@ -694,334 +693,16 @@ bool Bot::updateNavigation () { // special lift handling (code merged from podbotmm) if (m_path->flags & NodeFlag::Lift) { - bool liftClosedDoorExists = false; - - // update node time set - m_navTimeset = game.timebase (); - - // trace line to door - game.testLine (pev->origin, m_path->origin, TraceIgnore::Everything, ent (), &tr2); - - if (tr2.flFraction < 1.0f && strcmp (STRING (tr2.pHit->v.classname), "func_door") == 0 && (m_liftState == LiftState::None || m_liftState == LiftState::WaitingFor || m_liftState == LiftState::LookingButtonOutside) && pev->groundentity != tr2.pHit) { - if (m_liftState == LiftState::None) { - m_liftState = LiftState::LookingButtonOutside; - m_liftUsageTime = game.timebase () + 7.0f; - } - liftClosedDoorExists = true; - } - - // trace line down - game.testLine (m_path->origin, m_path->origin + Vector (0.0f, 0.0f, -50.0f), TraceIgnore::Everything, ent (), &tr); - - // if trace result shows us that it is a lift - if (!game.isNullEntity (tr.pHit) && !m_pathWalk.empty () && (strcmp (STRING (tr.pHit->v.classname), "func_door") == 0 || strcmp (STRING (tr.pHit->v.classname), "func_plat") == 0 || strcmp (STRING (tr.pHit->v.classname), "func_train") == 0) && !liftClosedDoorExists) { - if ((m_liftState == LiftState::None || m_liftState == LiftState::WaitingFor || m_liftState == LiftState::LookingButtonOutside) && tr.pHit->v.velocity.z == 0.0f) { - if (cr::abs (pev->origin.z - tr.vecEndPos.z) < 70.0f) { - m_liftEntity = tr.pHit; - m_liftState = LiftState::EnteringIn; - m_liftTravelPos = m_path->origin; - m_liftUsageTime = game.timebase () + 5.0f; - } - } - else if (m_liftState == LiftState::TravelingBy) { - m_liftState = LiftState::Leaving; - m_liftUsageTime = game.timebase () + 7.0f; - } - } - else if (!m_pathWalk.empty ()) // no lift found at node - { - if ((m_liftState == LiftState::None || m_liftState == LiftState::WaitingFor) && m_pathWalk.hasNext ()) { - int nextNode = m_pathWalk.next (); - - if (graph.exists (nextNode) && (graph[nextNode].flags & NodeFlag::Lift)) { - game.testLine (m_path->origin, graph[nextNode].origin, TraceIgnore::Everything, ent (), &tr); - - if (!game.isNullEntity (tr.pHit) && (strcmp (STRING (tr.pHit->v.classname), "func_door") == 0 || strcmp (STRING (tr.pHit->v.classname), "func_plat") == 0 || strcmp (STRING (tr.pHit->v.classname), "func_train") == 0)) { - m_liftEntity = tr.pHit; - } - } - m_liftState = LiftState::LookingButtonOutside; - m_liftUsageTime = game.timebase () + 15.0f; - } - } - - // bot is going to enter the lift - if (m_liftState == LiftState::EnteringIn) { - m_destOrigin = m_liftTravelPos; - - // check if we enough to destination - if ((pev->origin - m_destOrigin).lengthSq () < 225.0f) { - m_moveSpeed = 0.0f; - m_strafeSpeed = 0.0f; - - m_navTimeset = game.timebase (); - m_aimFlags |= AimFlags::Nav; - - resetCollision (); - - // need to wait our following teammate ? - bool needWaitForTeammate = false; - - // if some bot is following a bot going into lift - he should take the same lift to go - for (const auto &bot : bots) { - if (!bot->m_notKilled || bot->m_team != m_team || bot->m_targetEntity != ent () || bot->getCurrentTaskId () != Task::FollowUser) { - continue; - } - - if (bot->pev->groundentity == m_liftEntity && bot->isOnFloor ()) { - break; - } - - bot->m_liftEntity = m_liftEntity; - bot->m_liftState = LiftState::EnteringIn; - bot->m_liftTravelPos = m_liftTravelPos; - - needWaitForTeammate = true; - } - - if (needWaitForTeammate) { - m_liftState = LiftState::WaitingForTeammates; - m_liftUsageTime = game.timebase () + 8.0f; - } - else { - m_liftState = LiftState::LookingButtonInside; - m_liftUsageTime = game.timebase () + 10.0f; - } - } - } - - // bot is waiting for his teammates - if (m_liftState == LiftState::WaitingForTeammates) { - // need to wait our following teammate ? - bool needWaitForTeammate = false; - - for (const auto &bot : bots) { - if (!bot->m_notKilled || bot->m_team != m_team || bot->m_targetEntity != ent () || bot->getCurrentTaskId () != Task::FollowUser || bot->m_liftEntity != m_liftEntity) { - continue; - } - - if (bot->pev->groundentity == m_liftEntity || !bot->isOnFloor ()) { - needWaitForTeammate = true; - break; - } - } - - // need to wait for teammate - if (needWaitForTeammate) { - m_destOrigin = m_liftTravelPos; - - if ((pev->origin - m_destOrigin).lengthSq () < 225.0f) { - m_moveSpeed = 0.0f; - m_strafeSpeed = 0.0f; - - m_navTimeset = game.timebase (); - m_aimFlags |= AimFlags::Nav; - - resetCollision (); - } - } - - // else we need to look for button - if (!needWaitForTeammate || m_liftUsageTime < game.timebase ()) { - m_liftState = LiftState::LookingButtonInside; - m_liftUsageTime = game.timebase () + 10.0f; - } - } - - // bot is trying to find button inside a lift - if (m_liftState == LiftState::LookingButtonInside) { - edict_t *button = lookupButton (STRING (m_liftEntity->v.targetname)); - - // got a valid button entity ? - if (!game.isNullEntity (button) && pev->groundentity == m_liftEntity && m_buttonPushTime + 1.0f < game.timebase () && m_liftEntity->v.velocity.z == 0.0f && isOnFloor ()) { - m_pickupItem = button; - m_pickupType = Pickup::Button; - - m_navTimeset = game.timebase (); - } - } - - // is lift activated and bot is standing on it and lift is moving ? - if (m_liftState == LiftState::LookingButtonInside || m_liftState == LiftState::EnteringIn || m_liftState == LiftState::WaitingForTeammates || m_liftState == LiftState::WaitingFor) { - if (pev->groundentity == m_liftEntity && m_liftEntity->v.velocity.z != 0.0f && isOnFloor () && ((graph[m_previousNodes[0]].flags & NodeFlag::Lift) || !game.isNullEntity (m_targetEntity))) { - m_liftState = LiftState::TravelingBy; - m_liftUsageTime = game.timebase () + 14.0f; - - if ((pev->origin - m_destOrigin).lengthSq () < 225.0f) { - m_moveSpeed = 0.0f; - m_strafeSpeed = 0.0f; - - m_navTimeset = game.timebase (); - m_aimFlags |= AimFlags::Nav; - - resetCollision (); - } - } - } - - // bots is currently moving on lift - if (m_liftState == LiftState::TravelingBy) { - m_destOrigin = Vector (m_liftTravelPos.x, m_liftTravelPos.y, pev->origin.z); - - if ((pev->origin - m_destOrigin).lengthSq () < 225.0f) { - m_moveSpeed = 0.0f; - m_strafeSpeed = 0.0f; - - m_navTimeset = game.timebase (); - m_aimFlags |= AimFlags::Nav; - - resetCollision (); - } - } - - // need to find a button outside the lift - if (m_liftState == LiftState::LookingButtonOutside) { - - // button has been pressed, lift should come - if (m_buttonPushTime + 8.0f >= game.timebase ()) { - if (graph.exists (m_previousNodes[0])) { - m_destOrigin = graph[m_previousNodes[0]].origin; - } - else { - m_destOrigin = pev->origin; - } - - if ((pev->origin - m_destOrigin).lengthSq () < 225.0f) { - m_moveSpeed = 0.0f; - m_strafeSpeed = 0.0f; - - m_navTimeset = game.timebase (); - m_aimFlags |= AimFlags::Nav; - - resetCollision (); - } - } - else if (!game.isNullEntity (m_liftEntity)) { - edict_t *button = lookupButton (STRING (m_liftEntity->v.targetname)); - - // if we got a valid button entity - if (!game.isNullEntity (button)) { - // lift is already used ? - bool liftUsed = false; - - // iterate though clients, and find if lift already used - for (const auto &client : util.getClients ()) { - if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive) || client.team != m_team || client.ent == ent () || game.isNullEntity (client.ent->v.groundentity)) { - continue; - } - - if (client.ent->v.groundentity == m_liftEntity) { - liftUsed = true; - break; - } - } - - // lift is currently used - if (liftUsed) { - if (graph.exists (m_previousNodes[0])) { - m_destOrigin = graph[m_previousNodes[0]].origin; - } - else { - m_destOrigin = button->v.origin; - } - - if ((pev->origin - m_destOrigin).lengthSq () < 225.0f) { - m_moveSpeed = 0.0f; - m_strafeSpeed = 0.0f; - } - } - else { - m_pickupItem = button; - m_pickupType = Pickup::Button; - m_liftState = LiftState::WaitingFor; - - m_navTimeset = game.timebase (); - m_liftUsageTime = game.timebase () + 20.0f; - } - } - else { - m_liftState = LiftState::WaitingFor; - m_liftUsageTime = game.timebase () + 15.0f; - } - } - } - - // bot is waiting for lift - if (m_liftState == LiftState::WaitingFor) { - if (graph.exists (m_previousNodes[0])) { - if (!(graph[m_previousNodes[0]].flags & NodeFlag::Lift)) { - m_destOrigin = graph[m_previousNodes[0]].origin; - } - else if (graph.exists (m_previousNodes[1])) { - m_destOrigin = graph[m_previousNodes[1]].origin; - } - } - - if ((pev->origin - m_destOrigin).lengthSq () < 100.0f) { - m_moveSpeed = 0.0f; - m_strafeSpeed = 0.0f; - - m_navTimeset = game.timebase (); - m_aimFlags |= AimFlags::Nav; - - resetCollision (); - } - } - - // if bot is waiting for lift, or going to it - if (m_liftState == LiftState::WaitingFor || m_liftState == LiftState::EnteringIn) { - // bot fall down somewhere inside the lift's groove :) - if (pev->groundentity != m_liftEntity && graph.exists (m_previousNodes[0])) { - if ((graph[m_previousNodes[0]].flags & NodeFlag::Lift) && (m_path->origin.z - pev->origin.z) > 50.0f && (graph[m_previousNodes[0]].origin.z - pev->origin.z) > 50.0f) { - m_liftState = LiftState::None; - m_liftEntity = nullptr; - m_liftUsageTime = 0.0f; - - clearSearchNodes (); - findBestNearestNode (); - - if (graph.exists (m_previousNodes[2])) { - findPath (m_currentNodeIndex, m_previousNodes[2], FindPath::Fast); - } - return false; - } - } - } - } - - if (!game.isNullEntity (m_liftEntity) && !(m_path->flags & NodeFlag::Lift)) { - if (m_liftState == LiftState::TravelingBy) { - m_liftState = LiftState::Leaving; - m_liftUsageTime = game.timebase () + 10.0f; - } - if (m_liftState == LiftState::Leaving && m_liftUsageTime < game.timebase () && pev->groundentity != m_liftEntity) { - m_liftState = LiftState::None; - m_liftUsageTime = 0.0f; - - m_liftEntity = nullptr; - } - } - - if (m_liftUsageTime < game.timebase () && m_liftUsageTime != 0.0f) { - m_liftEntity = nullptr; - m_liftState = LiftState::None; - m_liftUsageTime = 0.0f; - - clearSearchNodes (); - - if (graph.exists (m_previousNodes[0])) { - if (!(graph[m_previousNodes[0]].flags & NodeFlag::Lift)) { - changePointIndex (m_previousNodes[0]); - } - else { - findBestNearestNode (); + if (updateLiftHandling ()) { + if (!updateLiftStates ()) { + return false; } } else { - findBestNearestNode (); + return false; } - return false; } + TraceResult tr; // check if we are going through a door... if (game.mapIs (MapFlags::HasDoors)) { @@ -1047,25 +728,25 @@ bool Bot::updateNavigation () { m_aimFlags &= ~(AimFlags::LastEnemy | AimFlags::PredictPath); m_canChooseAimDirection = false; - edict_t *button = lookupButton (STRING (tr.pHit->v.targetname)); + auto button = lookupButton (STRING (tr.pHit->v.targetname)); // check if we got valid button if (!game.isNullEntity (button)) { m_pickupItem = button; m_pickupType = Pickup::Button; - m_navTimeset = game.timebase (); + m_navTimeset = game.time (); } // if bot hits the door, then it opens, so wait a bit to let it open safely - if (pev->velocity.length2d () < 2 && m_timeDoorOpen < game.timebase ()) { - startTask (Task::Pause, TaskPri::Pause, kInvalidNodeIndex, game.timebase () + 0.5f, false); - m_timeDoorOpen = game.timebase () + 1.0f; // retry in 1 sec until door is open + if (pev->velocity.length2d () < 2 && m_timeDoorOpen < game.time ()) { + startTask (Task::Pause, TaskPri::Pause, kInvalidNodeIndex, game.time () + 0.5f, false); + m_timeDoorOpen = game.time () + 1.0f; // retry in 1 sec until door is open edict_t *pent = nullptr; if (m_tryOpenDoor++ > 2 && util.findNearestPlayer (reinterpret_cast (&pent), ent (), 256.0f, false, false, true, true, false)) { - m_seeEnemyTime = game.timebase () - 0.5f; + m_seeEnemyTime = game.time () - 0.5f; m_states |= Sense::SeeingEnemy; m_aimFlags |= AimFlags::Enemy; @@ -1101,11 +782,6 @@ bool Bot::updateNavigation () { desiredDistance = m_path->radius; } - // if desired distance is big enough and used by someone, increase desired radius by twice, and mark it as reached... - if (desiredDistance >= m_path->radius && isOccupiedPoint (m_currentNodeIndex)) { - desiredDistance *= 2.0f; - } - // check if node has a special travelflag, so they need to be reached more precisely for (const auto &link : m_path->links) { if (link.flags != 0) { @@ -1115,7 +791,7 @@ bool Bot::updateNavigation () { } // needs precise placement - check if we get past the point - if (desiredDistance < 22.0f && nodeDistance < 30.0f && (pev->origin + (pev->velocity * getFrameInterval ()) - m_pathOrigin).lengthSq () > cr::square (nodeDistance)) { + if (desiredDistance < 22.0f && nodeDistance < 30.0f && (pev->origin + (pev->velocity * getFrameInterval ()) - m_pathOrigin).lengthSq () >= cr::square (nodeDistance)) { desiredDistance = nodeDistance + 1.0f; } @@ -1167,6 +843,316 @@ bool Bot::updateNavigation () { return false; } +bool Bot::updateLiftHandling () { + bool liftClosedDoorExists = false; + + // update node time set + m_navTimeset = game.time (); + + TraceResult tr, tr2; + + // wait for something about for lift + auto wait = [&] () { + m_moveSpeed = 0.0f; + m_strafeSpeed = 0.0f; + + m_navTimeset = game.time (); + m_aimFlags |= AimFlags::Nav; + + ignoreCollision (); + }; + + // trace line to door + game.testLine (pev->origin, m_path->origin, TraceIgnore::Everything, ent (), &tr2); + + if (tr2.flFraction < 1.0f && strcmp (STRING (tr2.pHit->v.classname), "func_door") == 0 && (m_liftState == LiftState::None || m_liftState == LiftState::WaitingFor || m_liftState == LiftState::LookingButtonOutside) && pev->groundentity != tr2.pHit) { + if (m_liftState == LiftState::None) { + m_liftState = LiftState::LookingButtonOutside; + m_liftUsageTime = game.time () + 7.0f; + } + liftClosedDoorExists = true; + } + + // trace line down + game.testLine (m_path->origin, m_path->origin + Vector (0.0f, 0.0f, -50.0f), TraceIgnore::Everything, ent (), &tr); + + // if trace result shows us that it is a lift + if (!game.isNullEntity (tr.pHit) && !m_pathWalk.empty () && (strcmp (STRING (tr.pHit->v.classname), "func_door") == 0 || strcmp (STRING (tr.pHit->v.classname), "func_plat") == 0 || strcmp (STRING (tr.pHit->v.classname), "func_train") == 0) && !liftClosedDoorExists) { + if ((m_liftState == LiftState::None || m_liftState == LiftState::WaitingFor || m_liftState == LiftState::LookingButtonOutside) && tr.pHit->v.velocity.z == 0.0f) { + if (cr::abs (pev->origin.z - tr.vecEndPos.z) < 70.0f) { + m_liftEntity = tr.pHit; + m_liftState = LiftState::EnteringIn; + m_liftTravelPos = m_path->origin; + m_liftUsageTime = game.time () + 5.0f; + } + } + else if (m_liftState == LiftState::TravelingBy) { + m_liftState = LiftState::Leaving; + m_liftUsageTime = game.time () + 7.0f; + } + } + else if (!m_pathWalk.empty ()) // no lift found at node + { + if ((m_liftState == LiftState::None || m_liftState == LiftState::WaitingFor) && m_pathWalk.hasNext ()) { + int nextNode = m_pathWalk.next (); + + if (graph.exists (nextNode) && (graph[nextNode].flags & NodeFlag::Lift)) { + game.testLine (m_path->origin, graph[nextNode].origin, TraceIgnore::Everything, ent (), &tr); + + if (!game.isNullEntity (tr.pHit) && (strcmp (STRING (tr.pHit->v.classname), "func_door") == 0 || strcmp (STRING (tr.pHit->v.classname), "func_plat") == 0 || strcmp (STRING (tr.pHit->v.classname), "func_train") == 0)) { + m_liftEntity = tr.pHit; + } + } + m_liftState = LiftState::LookingButtonOutside; + m_liftUsageTime = game.time () + 15.0f; + } + } + + // bot is going to enter the lift + if (m_liftState == LiftState::EnteringIn) { + m_destOrigin = m_liftTravelPos; + + // check if we enough to destination + if ((pev->origin - m_destOrigin).lengthSq () < cr::square (20.0f)) { + wait (); + + // need to wait our following teammate ? + bool needWaitForTeammate = false; + + // if some bot is following a bot going into lift - he should take the same lift to go + for (const auto &bot : bots) { + if (!bot->m_notKilled || bot->m_team != m_team || bot->m_targetEntity != ent () || bot->getCurrentTaskId () != Task::FollowUser) { + continue; + } + + if (bot->pev->groundentity == m_liftEntity && bot->isOnFloor ()) { + break; + } + + bot->m_liftEntity = m_liftEntity; + bot->m_liftState = LiftState::EnteringIn; + bot->m_liftTravelPos = m_liftTravelPos; + + needWaitForTeammate = true; + } + + if (needWaitForTeammate) { + m_liftState = LiftState::WaitingForTeammates; + m_liftUsageTime = game.time () + 8.0f; + } + else { + m_liftState = LiftState::LookingButtonInside; + m_liftUsageTime = game.time () + 10.0f; + } + } + } + + // bot is waiting for his teammates + if (m_liftState == LiftState::WaitingForTeammates) { + // need to wait our following teammate ? + bool needWaitForTeammate = false; + + for (const auto &bot : bots) { + if (!bot->m_notKilled || bot->m_team != m_team || bot->m_targetEntity != ent () || bot->getCurrentTaskId () != Task::FollowUser || bot->m_liftEntity != m_liftEntity) { + continue; + } + + if (bot->pev->groundentity == m_liftEntity || !bot->isOnFloor ()) { + needWaitForTeammate = true; + break; + } + } + + // need to wait for teammate + if (needWaitForTeammate) { + m_destOrigin = m_liftTravelPos; + + if ((pev->origin - m_destOrigin).lengthSq () < cr::square (20.0f)) { + wait (); + } + } + + // else we need to look for button + if (!needWaitForTeammate || m_liftUsageTime < game.time ()) { + m_liftState = LiftState::LookingButtonInside; + m_liftUsageTime = game.time () + 10.0f; + } + } + + // bot is trying to find button inside a lift + if (m_liftState == LiftState::LookingButtonInside) { + auto button = lookupButton (STRING (m_liftEntity->v.targetname)); + + // got a valid button entity ? + if (!game.isNullEntity (button) && pev->groundentity == m_liftEntity && m_buttonPushTime + 1.0f < game.time () && m_liftEntity->v.velocity.z == 0.0f && isOnFloor ()) { + m_pickupItem = button; + m_pickupType = Pickup::Button; + + m_navTimeset = game.time (); + } + } + + // is lift activated and bot is standing on it and lift is moving ? + if (m_liftState == LiftState::LookingButtonInside || m_liftState == LiftState::EnteringIn || m_liftState == LiftState::WaitingForTeammates || m_liftState == LiftState::WaitingFor) { + if (pev->groundentity == m_liftEntity && m_liftEntity->v.velocity.z != 0.0f && isOnFloor () && ((graph[m_previousNodes[0]].flags & NodeFlag::Lift) || !game.isNullEntity (m_targetEntity))) { + m_liftState = LiftState::TravelingBy; + m_liftUsageTime = game.time () + 14.0f; + + if ((pev->origin - m_destOrigin).lengthSq () < cr::square (20.0f)) { + wait (); + } + } + } + + // bots is currently moving on lift + if (m_liftState == LiftState::TravelingBy) { + m_destOrigin = Vector (m_liftTravelPos.x, m_liftTravelPos.y, pev->origin.z); + + if ((pev->origin - m_destOrigin).lengthSq () < cr::square (20.0f)) { + wait (); + } + } + + // need to find a button outside the lift + if (m_liftState == LiftState::LookingButtonOutside) { + + // button has been pressed, lift should come + if (m_buttonPushTime + 8.0f >= game.time ()) { + if (graph.exists (m_previousNodes[0])) { + m_destOrigin = graph[m_previousNodes[0]].origin; + } + else { + m_destOrigin = pev->origin; + } + + if ((pev->origin - m_destOrigin).lengthSq () < cr::square (20.0f)) { + wait (); + } + } + else if (!game.isNullEntity (m_liftEntity)) { + auto button = lookupButton (STRING (m_liftEntity->v.targetname)); + + // if we got a valid button entity + if (!game.isNullEntity (button)) { + // lift is already used ? + bool liftUsed = false; + + // iterate though clients, and find if lift already used + for (const auto &client : util.getClients ()) { + if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive) || client.team != m_team || client.ent == ent () || game.isNullEntity (client.ent->v.groundentity)) { + continue; + } + + if (client.ent->v.groundentity == m_liftEntity) { + liftUsed = true; + break; + } + } + + // lift is currently used + if (liftUsed) { + if (graph.exists (m_previousNodes[0])) { + m_destOrigin = graph[m_previousNodes[0]].origin; + } + else { + m_destOrigin = button->v.origin; + } + + if ((pev->origin - m_destOrigin).lengthSq () < cr::square (20.0f)) { + wait (); + } + } + else { + m_pickupItem = button; + m_pickupType = Pickup::Button; + m_liftState = LiftState::WaitingFor; + + m_navTimeset = game.time (); + m_liftUsageTime = game.time () + 20.0f; + } + } + else { + m_liftState = LiftState::WaitingFor; + m_liftUsageTime = game.time () + 15.0f; + } + } + } + + // bot is waiting for lift + if (m_liftState == LiftState::WaitingFor) { + if (graph.exists (m_previousNodes[0])) { + if (!(graph[m_previousNodes[0]].flags & NodeFlag::Lift)) { + m_destOrigin = graph[m_previousNodes[0]].origin; + } + else if (graph.exists (m_previousNodes[1])) { + m_destOrigin = graph[m_previousNodes[1]].origin; + } + } + + if ((pev->origin - m_destOrigin).lengthSq () < cr::square (20.0f)) { + wait (); + } + } + + // if bot is waiting for lift, or going to it + if (m_liftState == LiftState::WaitingFor || m_liftState == LiftState::EnteringIn) { + // bot fall down somewhere inside the lift's groove :) + if (pev->groundentity != m_liftEntity && graph.exists (m_previousNodes[0])) { + if ((graph[m_previousNodes[0]].flags & NodeFlag::Lift) && (m_path->origin.z - pev->origin.z) > 50.0f && (graph[m_previousNodes[0]].origin.z - pev->origin.z) > 50.0f) { + m_liftState = LiftState::None; + m_liftEntity = nullptr; + m_liftUsageTime = 0.0f; + + clearSearchNodes (); + findBestNearestNode (); + + if (graph.exists (m_previousNodes[2])) { + findPath (m_currentNodeIndex, m_previousNodes[2], FindPath::Fast); + } + return false; + } + } + } + return true; +} + +bool Bot::updateLiftStates () { + if (!game.isNullEntity (m_liftEntity) && !(m_path->flags & NodeFlag::Lift)) { + if (m_liftState == LiftState::TravelingBy) { + m_liftState = LiftState::Leaving; + m_liftUsageTime = game.time () + 10.0f; + } + if (m_liftState == LiftState::Leaving && m_liftUsageTime < game.time () && pev->groundentity != m_liftEntity) { + m_liftState = LiftState::None; + m_liftUsageTime = 0.0f; + + m_liftEntity = nullptr; + } + } + + if (m_liftUsageTime < game.time () && m_liftUsageTime != 0.0f) { + m_liftEntity = nullptr; + m_liftState = LiftState::None; + m_liftUsageTime = 0.0f; + + clearSearchNodes (); + + if (graph.exists (m_previousNodes[0])) { + if (!(graph[m_previousNodes[0]].flags & NodeFlag::Lift)) { + changePointIndex (m_previousNodes[0]); + } + else { + findBestNearestNode (); + } + } + else { + findBestNearestNode (); + } + return false; + } + return true; +} + void Bot::findShortestPath (int srcIndex, int destIndex) { // this function finds the shortest path from source index to destination index @@ -1589,7 +1575,7 @@ bool Bot::findBestNearestNode () { } // check if node is already used by another bot... - if (bots.getRoundStartTime () + 5.0f < game.timebase () && isOccupiedPoint (at)) { + if (bots.getRoundStartTime () + 5.0f < game.time () && isOccupiedNode (at)) { busy = at; continue; } @@ -1716,7 +1702,7 @@ void Bot::findValidNode () { m_pathOrigin = m_path->origin; } - else if (m_navTimeset + getReachTime () < game.timebase () && game.isNullEntity (m_enemy)) { + else if (m_navTimeset + getReachTime () < game.time () && game.isNullEntity (m_enemy)) { // increase danager for both teams for (int team = Team::Terrorist; team < kGameTeamNum; ++team) { @@ -1753,7 +1739,7 @@ int Bot::changePointIndex (int index) { m_previousNodes[0] = m_currentNodeIndex; m_currentNodeIndex = index; - m_navTimeset = game.timebase (); + m_navTimeset = game.time (); m_path = &graph[index]; m_pathFlags = m_path->flags; @@ -1870,7 +1856,7 @@ int Bot::findDefendNode (const Vector &origin) { // find the best node now for (int i = 0; i < graph.length (); ++i) { // exclude ladder & current nodes - if ((graph[i].flags & NodeFlag::Ladder) || i == srcIndex || !graph.isVisible (i, posIndex) || isOccupiedPoint (i)) { + if ((graph[i].flags & NodeFlag::Ladder) || i == srcIndex || !graph.isVisible (i, posIndex) || isOccupiedNode (i)) { continue; } @@ -1925,7 +1911,7 @@ int Bot::findDefendNode (const Vector &origin) { IntArray found; for (int i = 0; i < graph.length (); ++i) { - if ((graph[i].origin - origin).lengthSq () <= cr::square (1248.0f) && !graph.isVisible (i, posIndex) && !isOccupiedPoint (i)) { + if ((graph[i].origin - origin).lengthSq () <= cr::square (1248.0f) && !graph.isVisible (i, posIndex) && !isOccupiedNode (i)) { found.push (i); } } @@ -2051,7 +2037,7 @@ int Bot::findCoverNode (float maxDistance) { TraceResult tr; // take the first one which isn't spotted by the enemy - for (auto &index : nodeIndex) { + for (const auto &index : nodeIndex) { if (index != kInvalidNodeIndex) { game.testLine (m_lastEnemyOrigin + Vector (0.0f, 0.0f, 36.0f), graph[index].origin, TraceIgnore::Everything, ent (), &tr); @@ -2075,7 +2061,7 @@ bool Bot::selectBestNextNode () { assert (!m_pathWalk.empty ()); assert (m_pathWalk.hasNext ()); - if (!isOccupiedPoint (m_pathWalk.first ())) { + if (!isOccupiedNode (m_pathWalk.first ())) { return false; } @@ -2087,7 +2073,7 @@ bool Bot::selectBestNextNode () { continue; } - if (!isOccupiedPoint (link.index) && graph[link.index].origin.z <= graph[m_currentNodeIndex].origin.z + 10.0f) { + if (graph[link.index].origin.z <= graph[m_currentNodeIndex].origin.z + 10.0f && !isOccupiedNode (link.index)) { m_pathWalk.first () = link.index; return true; } @@ -2120,14 +2106,14 @@ bool Bot::advanceMovement () { Task taskID = getCurrentTaskId (); // only if we in normal task and bomb is not planted - if (taskID == Task::Normal && bots.getRoundMidTime () + 5.0f < game.timebase () && m_timeCamping + 5.0f < game.timebase () && !bots.isBombPlanted () && m_personality != Personality::Rusher && !m_hasC4 && !m_isVIP && m_loosedBombNodeIndex == kInvalidNodeIndex && !hasHostage ()) { + if (taskID == Task::Normal && bots.getRoundMidTime () + 5.0f < game.time () && m_timeCamping + 5.0f < game.time () && !bots.isBombPlanted () && m_personality != Personality::Rusher && !m_hasC4 && !m_isVIP && m_loosedBombNodeIndex == kInvalidNodeIndex && !hasHostage ()) { m_campButtons = 0; const int nextIndex = m_pathWalk.next (); auto kills = static_cast (graph.getDangerDamage (m_team, nextIndex, nextIndex)); // if damage done higher than one - if (kills > 1.0f && bots.getRoundMidTime () > game.timebase ()) { + if (kills > 1.0f && bots.getRoundMidTime () > game.time ()) { switch (m_personality) { case Personality::Normal: kills *= 0.33f; @@ -2139,8 +2125,8 @@ bool Bot::advanceMovement () { } if (m_baseAgressionLevel < kills && hasPrimaryWeapon ()) { - startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, game.timebase () + rg.float_ (m_difficulty * 0.5f, static_cast (m_difficulty)) * 5.0f, true); - startTask (Task::MoveToPosition, TaskPri::MoveToPosition, findDefendNode (graph[nextIndex].origin), game.timebase () + rg.float_ (3.0f, 10.0f), true); + startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, game.time () + rg.float_ (m_difficulty * 0.5f, static_cast (m_difficulty)) * 5.0f, true); + startTask (Task::MoveToPosition, TaskPri::MoveToPosition, findDefendNode (graph[nextIndex].origin), game.time () + rg.float_ (3.0f, 10.0f), true); } } else if (bots.canPause () && !isOnLadder () && !isInWater () && !m_currentTravelFlags && isOnFloor ()) { @@ -2224,7 +2210,7 @@ bool Bot::advanceMovement () { for (const auto &other : bots) { // if another bot uses this ladder, wait 3 secs if (other.get () != this && other->m_notKilled && other->m_currentNodeIndex == destIndex) { - startTask (Task::Pause, TaskPri::Pause, kInvalidNodeIndex, game.timebase () + 3.0f, false); + startTask (Task::Pause, TaskPri::Pause, kInvalidNodeIndex, game.time () + 3.0f, false); return true; } } @@ -2247,7 +2233,7 @@ bool Bot::advanceMovement () { m_pathOrigin = m_pathOrigin + (pev->origin - m_pathOrigin) * 0.5f + Vector (0.0f, 0.0f, 32.0f); } } - m_navTimeset = game.timebase (); + m_navTimeset = game.time (); return true; } @@ -2357,7 +2343,7 @@ bool Bot::canStrafeLeft (TraceResult *tr) { // this function checks if bot can move sideways Vector right, forward; - pev->v_angle.buildVectors (&forward, &right, nullptr); + pev->v_angle.angleVectors (&forward, &right, nullptr); Vector src = pev->origin; Vector dest = src - right * -40.0f; @@ -2387,7 +2373,7 @@ bool Bot::canStrafeRight (TraceResult *tr) { // this function checks if bot can move sideways Vector right, forward; - pev->v_angle.buildVectors (&forward, &right, nullptr); + pev->v_angle.angleVectors (&forward, &right, nullptr); Vector src = pev->origin; Vector dest = src + right * 40.0f; @@ -2620,7 +2606,7 @@ bool Bot::isBlockedLeft () { direction = -48.0f; } Vector right, forward; - pev->angles.buildVectors (&forward, &right, nullptr); + pev->angles.angleVectors (&forward, &right, nullptr); // do a trace to the left... game.testLine (pev->origin, forward * direction - right * 48.0f, TraceIgnore::Monsters, ent (), &tr); @@ -2640,7 +2626,7 @@ bool Bot::isBlockedRight () { direction = -48.0f; } Vector right, forward; - pev->angles.buildVectors (&forward, &right, nullptr); + pev->angles.angleVectors (&forward, &right, nullptr); // do a trace to the right... game.testLine (pev->origin, pev->origin + forward * direction + right * 48.0f, TraceIgnore::Monsters, ent (), &tr); @@ -2847,8 +2833,9 @@ void Bot::updateBodyAngles () { } void Bot::updateLookAngles () { - const float delta = cr::clamp (game.timebase () - m_lookUpdateTime, cr::kFloatEqualEpsilon, 0.05f); - m_lookUpdateTime = game.timebase (); + + const float delta = cr::clamp (game.time () - m_lookUpdateTime, cr::kFloatEqualEpsilon, 0.03333f); + m_lookUpdateTime = game.time (); // adjust all body and view angles to face an absolute vector Vector direction = (m_lookAt - getEyesPos ()).angles (); @@ -2881,8 +2868,8 @@ void Bot::updateLookAngles () { } m_idealAngles = pev->v_angle; - float angleDiffYaw = cr::anglesDifference (direction.y, m_idealAngles.y); float angleDiffPitch = cr::anglesDifference (direction.x, m_idealAngles.x); + float angleDiffYaw = cr::anglesDifference (direction.y, m_idealAngles.y); if (angleDiffYaw < 1.0f && angleDiffYaw > -1.0f) { m_lookYawVel = 0.0f; @@ -2924,14 +2911,14 @@ void Bot::updateLookAnglesNewbie (const Vector &direction, float delta) { m_idealAngles.clampAngles (); if (m_aimFlags & (AimFlags::Enemy | AimFlags::Entity)) { - m_playerTargetTime = game.timebase (); + m_playerTargetTime = game.time (); m_randomizedIdealAngles = m_idealAngles; stiffness = spring * (0.2f + offset / 125.0f); } else { // is it time for bot to randomize the aim direction again (more often where moving) ? - if (m_randomizeAnglesTime < game.timebase () && ((pev->velocity.length () > 1.0f && m_angularDeviation.length () < 5.0f) || m_angularDeviation.length () < 1.0f)) { + if (m_randomizeAnglesTime < game.time () && ((pev->velocity.length () > 1.0f && m_angularDeviation.length () < 5.0f) || m_angularDeviation.length () < 1.0f)) { // is the bot standing still ? if (pev->velocity.length () < 1.0f) { randomize = randomization * 0.2f; // randomize less @@ -2943,13 +2930,13 @@ void Bot::updateLookAnglesNewbie (const Vector &direction, float delta) { m_randomizedIdealAngles = m_idealAngles + Vector (rg.float_ (-randomize.x * 0.5f, randomize.x * 1.5f), rg.float_ (-randomize.y, randomize.y), 0.0f); // set next time to do this - m_randomizeAnglesTime = game.timebase () + rg.float_ (0.4f, offsetDelay); + m_randomizeAnglesTime = game.time () + rg.float_ (0.4f, offsetDelay); } float stiffnessMultiplier = noTargetRatio; // take in account whether the bot was targeting someone in the last N seconds - if (game.timebase () - (m_playerTargetTime + offsetDelay) < noTargetRatio * 10.0f) { - stiffnessMultiplier = 1.0f - (game.timebase () - m_timeLastFired) * 0.1f; + if (game.time () - (m_playerTargetTime + offsetDelay) < noTargetRatio * 10.0f) { + stiffnessMultiplier = 1.0f - (game.time () - m_timeLastFired) * 0.1f; // don't allow that stiffness multiplier less than zero if (stiffnessMultiplier < 0.0f) { @@ -3002,24 +2989,23 @@ int Bot::getNearestToPlantedBomb () { if (m_team != Team::Terrorist || !game.mapIs (MapFlags::Demolition)) { return kInvalidNodeIndex; // don't search for bomb if the player is CT, or it's not defusing bomb } - - edict_t *bombEntity = nullptr; // temporaly pointer to bomb + int result = kInvalidNodeIndex; // search the bomb on the map - while (!game.isNullEntity (bombEntity = engfuncs.pfnFindEntityByString (bombEntity, "classname", "grenade"))) { - if (strcmp (STRING (bombEntity->v.model) + 9, "c4.mdl") == 0) { - int nearestIndex = graph.getNearest (game.getAbsPos (bombEntity)); + game.searchEntities ("classname", "grenade", [&result] (edict_t *ent) { + if (strcmp (STRING (ent->v.model) + 9, "c4.mdl") == 0) { + result = graph.getNearest (game.getAbsPos (ent)); - if (graph.exists (nearestIndex)) { - return nearestIndex; + if (graph.exists (result)) { + return EntitySearchResult::Break; } - break; } - } - return kInvalidNodeIndex; + return EntitySearchResult::Continue; + }); + return result; } -bool Bot::isOccupiedPoint (int index) { +bool Bot::isOccupiedNode (int index) { if (!graph.exists (index)) { return true; } @@ -3062,25 +3048,26 @@ edict_t *Bot::lookupButton (const char *targetName) { if (strings.isEmpty (targetName)) { return nullptr; } - float nearestDistance = kInfiniteDistance; - edict_t *searchEntity = nullptr, *foundEntity = nullptr; + float nearest = kInfiniteDistance; + edict_t *result = nullptr; // find the nearest button which can open our target - while (!game.isNullEntity (searchEntity = engfuncs.pfnFindEntityByString (searchEntity, "target", targetName))) { - const Vector &pos = game.getAbsPos (searchEntity); + game.searchEntities ("target", targetName, [&] (edict_t *ent) { + const Vector &pos = game.getAbsPos (ent); // check if this place safe if (!isDeadlyMove (pos)) { float distance = (pev->origin - pos).lengthSq (); // check if we got more close button - if (distance <= nearestDistance) { - nearestDistance = distance; - foundEntity = searchEntity; + if (distance <= nearest) { + nearest = distance; + result = ent; } } - } - return foundEntity; + return EntitySearchResult::Continue; + }); + return result; } diff --git a/source/support.cpp b/source/support.cpp index aa2e9ad..3677645 100644 --- a/source/support.cpp +++ b/source/support.cpp @@ -56,6 +56,17 @@ BotUtils::BotUtils () { m_tags.emplace ("(", ")"); m_tags.emplace (")", "("); + // register noise cache + m_noiseCache["player/bhit"] = Noise::NeedHandle | Noise::HitFall; + m_noiseCache["player/head"] = Noise::NeedHandle | Noise::HitFall; + m_noiseCache["items/gunpi"] = Noise::NeedHandle | Noise::Pickup; + m_noiseCache["items/9mmcl"] = Noise::NeedHandle | Noise::Ammo; + m_noiseCache["weapons/zoo"] = Noise::NeedHandle | Noise::Zoom; + m_noiseCache["hostage/hos"] = Noise::NeedHandle | Noise::Hostage; + m_noiseCache["debris/bust"] = Noise::NeedHandle | Noise::Broke; + m_noiseCache["debris/bust"] = Noise::NeedHandle | Noise::Broke; + m_noiseCache["doors/doorm"] = Noise::NeedHandle | Noise::Door; + m_clients.resize (kGameMaxPlayers + 1); } @@ -234,7 +245,7 @@ void BotUtils::checkWelcome () { auto receiveEntity = game.getLocalEntity (); if (isAlive (receiveEntity) && m_welcomeReceiveTime < 1.0 && needToSendMsg) { - m_welcomeReceiveTime = game.timebase () + 4.0f; // receive welcome message in four seconds after game has commencing + m_welcomeReceiveTime = game.time () + 4.0f; // receive welcome message in four seconds after game has commencing } @@ -243,11 +254,11 @@ void BotUtils::checkWelcome () { game.serverCommand ("speak \"%s\"", m_sentences.random ().chars ()); } - MessageWriter (MSG_ONE, msgs.id (NetMsg::TextMsg), nullvec, receiveEntity) + MessageWriter (MSG_ONE, msgs.id (NetMsg::TextMsg), nullptr, receiveEntity) .writeByte (HUD_PRINTTALK) .writeString (strings.format ("----- %s v%s (Build: %u), {%s}, (c) %s, by %s (%s)-----", PRODUCT_SHORT_NAME, PRODUCT_VERSION, buildNumber (), PRODUCT_DATE, PRODUCT_END_YEAR, PRODUCT_AUTHOR, PRODUCT_URL)); - MessageWriter (MSG_ONE, SVC_TEMPENTITY, nullvec, receiveEntity) + MessageWriter (MSG_ONE, SVC_TEMPENTITY, nullptr, receiveEntity) .writeByte (TE_TEXTMESSAGE) .writeByte (1) .writeShort (MessageWriter::fs16 (-1.0f, 13.0f)) @@ -312,91 +323,96 @@ bool BotUtils::findNearestPlayer (void **pvHolder, edict_t *to, float searchDist return true; } -void BotUtils::attachSoundsToClients (edict_t *ent, const char *sample, float volume) { - // this function called by the sound hooking code (in emit_sound) enters the played sound into - // the array associated with the entity +void BotUtils::listenNoise (edict_t *ent, const String &sample, float volume) { + // this function called by the sound hooking code (in emit_sound) enters the played sound into the array associated with the entity - if (game.isNullEntity (ent) || strings.isEmpty (sample)) { + if (game.isNullEntity (ent) || sample.empty ()) { return; } const Vector &origin = game.getAbsPos (ent); + // something wrong with sound... if (origin.empty ()) { return; } - int index = game.indexOfPlayer (ent); + auto noise = m_noiseCache[sample.substr (0, 11)]; - if (index < 0 || index >= game.maxClients ()) { - float nearestDistance = kInfiniteDistance; + // we're not handling theese + if (!(noise & Noise::NeedHandle)) { + return; + } + + // find nearest player to sound origin + auto findNearbyClient = [&origin] () { + float nearest = kInfiniteDistance; + Client *result = nullptr; // loop through all players - for (int i = 0; i < game.maxClients (); ++i) { - const Client &client = m_clients[i]; - + for (auto &client : util.getClients ()) { if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive)) { continue; } - float distance = (client.origin - origin).length (); + auto distance = (client.origin - origin).lengthSq (); // now find nearest player - if (distance < nearestDistance) { - index = i; - nearestDistance = distance; + if (distance < nearest) { + result = &client; + nearest = distance; } } - } + return result; + }; + auto client = findNearbyClient (); - // in case of worst case - if (index < 0 || index >= game.maxClients ()) { + // 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 wasn't found + if (!client) { return; } - Client &client = m_clients[index]; - if (strncmp ("player/bhit_flesh", sample, 17) == 0 || strncmp ("player/headshot", sample, 15) == 0) { - // hit/fall sound? - client.hearingDistance = 768.0f * volume; - client.timeSoundLasting = game.timebase () + 0.5f; - client.sound = origin; + // hit/fall sound? + if (noise & Noise::HitFall) { + registerNoise (768.0f, 0.52f); } - else if (strncmp ("items/gunpickup", sample, 15) == 0) { - // weapon pickup? - client.hearingDistance = 768.0f * volume; - client.timeSoundLasting = game.timebase () + 0.5f; - client.sound = origin; + + // weapon pickup? + else if (noise & Noise::Pickup) { + registerNoise (768.0f, 0.45f); } - else if (strncmp ("weapons/zoom", sample, 12) == 0) { - // sniper zooming? - client.hearingDistance = 512.0f * volume; - client.timeSoundLasting = game.timebase () + 0.1f; - client.sound = origin; + + // sniper zooming? + else if (noise & Noise::Zoom) { + registerNoise (512.0f, 0.10f); } - else if (strncmp ("items/9mmclip", sample, 13) == 0) { - // ammo pickup? - client.hearingDistance = 512.0f * volume; - client.timeSoundLasting = game.timebase () + 0.1f; - client.sound = origin; + + // ammo pickup? + else if (noise & Noise::Ammo) { + registerNoise (512.0f, 0.25f); } - else if (strncmp ("hostage/hos", sample, 11) == 0) { - // CT used hostage? - client.hearingDistance = 1024.0f * volume; - client.timeSoundLasting = game.timebase () + 5.0f; - client.sound = origin; + + // ct used hostage? + else if (noise & Noise::Hostage) { + registerNoise (1024.0f, 5.00f); } - else if (strncmp ("debris/bustmetal", sample, 16) == 0 || strncmp ("debris/bustglass", sample, 16) == 0) { - // broke something? - client.hearingDistance = 1024.0f * volume; - client.timeSoundLasting = game.timebase () + 2.0f; - client.sound = origin; + + // broke something? + else if (noise & Noise::Broke) { + registerNoise (1024.0f, 2.00f); } - else if (strncmp ("doors/doormove", sample, 14) == 0) { - // someone opened a door - client.hearingDistance = 1024.0f * volume; - client.timeSoundLasting = game.timebase () + 3.0f; - client.sound = origin; + + // someone opened a door + else if (noise & Noise::Door) { + registerNoise (1024.0f, 3.00f); } } -void BotUtils::simulateSoundUpdates (int playerIndex) { +void BotUtils::simulateNoise (int playerIndex) { // this function tries to simulate playing of sounds to let the bots hear sounds which aren't // captured through server sound hooking @@ -407,27 +423,28 @@ void BotUtils::simulateSoundUpdates (int playerIndex) { float hearDistance = 0.0f; float timeSound = 0.0f; + auto buttons = client.ent->v.button | client.ent->v.oldbuttons; - if (client.ent->v.oldbuttons & IN_ATTACK) // pressed attack button? + if (buttons & IN_ATTACK) // pressed attack button? { hearDistance = 2048.0f; - timeSound = game.timebase () + 0.3f; + timeSound = game.time () + 0.3f; } - else if (client.ent->v.oldbuttons & IN_USE) // pressed used button? + else if (buttons & IN_USE) // pressed used button? { hearDistance = 512.0f; - timeSound = game.timebase () + 0.5f; + timeSound = game.time () + 0.5f; } - else if (client.ent->v.oldbuttons & IN_RELOAD) // pressed reload button? + else if (buttons & IN_RELOAD) // pressed reload button? { hearDistance = 512.0f; - timeSound = game.timebase () + 0.5f; + timeSound = game.time () + 0.5f; } else if (client.ent->v.movetype == MOVETYPE_FLY) // uses ladder? { if (cr::abs (client.ent->v.velocity.z) > 50.0f) { hearDistance = 1024.0f; - timeSound = game.timebase () + 0.3f; + timeSound = game.time () + 0.3f; } } else { @@ -436,7 +453,7 @@ void BotUtils::simulateSoundUpdates (int playerIndex) { if (mp_footsteps.bool_ ()) { // moves fast enough? hearDistance = 1280.0f * (client.ent->v.velocity.length2d () / 260.0f); - timeSound = game.timebase () + 0.3f; + timeSound = game.time () + 0.3f; } } @@ -445,7 +462,7 @@ void BotUtils::simulateSoundUpdates (int playerIndex) { } // some sound already associated - if (client.timeSoundLasting > game.timebase ()) { + if (client.timeSoundLasting > game.time ()) { if (client.hearingDistance <= hearDistance) { // override it with new client.hearingDistance = hearDistance; @@ -481,7 +498,7 @@ void BotUtils::updateClients () { if (client.flags & ClientFlags::Alive) { client.origin = player->v.origin; - simulateSoundUpdates (i); + simulateNoise (i); } } else { @@ -576,7 +593,7 @@ void BotUtils::sendPings (edict_t *to) { client.ping = getPingBitmask (client.ent, rg.int_ (5, 10), rg.int_ (15, 40)); } - msg.start (MSG_ONE_UNRELIABLE, kGamePingSVC, nullvec, to) + msg.start (MSG_ONE_UNRELIABLE, kGamePingSVC, nullptr, to) .writeLong (client.ping) .end (); }