diff --git a/include/compress.h b/include/compress.h index 6cca319..c4ee2a8 100644 --- a/include/compress.h +++ b/include/compress.h @@ -9,327 +9,295 @@ #pragma once -static constexpr int MAXBUF = 4096, PADDING = 18, THRESHOLD = 2, NIL = MAXBUF; +// see https://github.com/encode84/ulz/ +class FastLZ final : NonCopyable { +public: + static constexpr int EXCESS = 16; + static constexpr int WINDOW_BITS = 17; + static constexpr int WINDOW_SIZE = cr::bit (WINDOW_BITS); + static constexpr int WINDOW_MASK = WINDOW_SIZE - 1; -class Compress { -protected: - unsigned long int m_csize; + static constexpr int MIN_MATCH = 4; + static constexpr int MAX_CHAIN = cr::bit (5); - uint8 m_buffer[MAXBUF + PADDING - 1]; - int m_matchPos; - int m_matchLen; - - int m_left[MAXBUF + 1]; - int m_right[MAXBUF + 257]; - int m_parent[MAXBUF + 1]; + static constexpr int HASH_BITS = 19; + static constexpr int HASH_SIZE = cr::bit (HASH_BITS); + static constexpr int NIL = -1; + static constexpr int UNCOMPRESS_RESULT_FAILED = -1; private: - void initTrees (void) { - for (int i = MAXBUF + 1; i <= MAXBUF + 256; i++) { - m_right[i] = NIL; - } + int *m_hashTable; + int *m_prevTable; - for (int j = 0; j < MAXBUF; j++) { - m_parent[j] = NIL; - } +public: + FastLZ (void) { + m_hashTable = new int[HASH_SIZE]; + m_prevTable = new int[WINDOW_SIZE]; } - void insert (int node) { - int i; - - int compare = 1; - - uint8 *key = &m_buffer[node]; - int temp = MAXBUF + 1 + key[0]; - - m_right[node] = m_left[node] = NIL; - m_matchLen = 0; - - for (;;) { - if (compare >= 0) { - if (m_right[temp] != NIL) { - temp = m_right[temp]; - } - else { - m_right[temp] = node; - m_parent[node] = temp; - return; - } - } - else { - if (m_left[temp] != NIL) { - temp = m_left[temp]; - } - else { - m_left[temp] = node; - m_parent[node] = temp; - - return; - } - } - - for (i = 1; i < PADDING; i++) { - if ((compare = key[i] - m_buffer[temp + i]) != 0) { - break; - } - } - - if (i > m_matchLen) { - m_matchPos = temp; - - if ((m_matchLen = i) >= PADDING) { - break; - } - } - } - - m_parent[node] = m_parent[temp]; - m_left[node] = m_left[temp]; - m_right[node] = m_right[temp]; - m_parent[m_left[temp]] = node; - m_parent[m_right[temp]] = node; - - if (m_right[m_parent[temp]] == temp) { - m_right[m_parent[temp]] = node; - } - else { - m_left[m_parent[temp]] = node; - } - m_parent[temp] = NIL; - } - - void erase (int node) { - int temp; - - if (m_parent[node] == NIL) { - return; // not in tree - } - - if (m_right[node] == NIL) { - temp = m_left[node]; - } - else if (m_left[node] == NIL) { - temp = m_right[node]; - } - else { - temp = m_left[node]; - - if (m_right[temp] != NIL) { - do { - temp = m_right[temp]; - } while (m_right[temp] != NIL); - - m_right[m_parent[temp]] = m_left[temp]; - m_parent[m_left[temp]] = m_parent[temp]; - m_left[temp] = m_left[node]; - m_parent[m_left[node]] = temp; - } - - m_right[temp] = m_right[node]; - m_parent[m_right[node]] = temp; - } - m_parent[temp] = m_parent[node]; - - if (m_right[m_parent[node]] == node) { - m_right[m_parent[node]] = temp; - } - else { - m_left[m_parent[node]] = temp; - } - m_parent[node] = NIL; + ~FastLZ (void) { + delete [] m_hashTable; + delete [] m_prevTable; } public: - Compress (void) : m_csize (0), m_matchPos (0), m_matchLen (0) { - memset (m_right, 0, sizeof (m_right)); - memset (m_left, 0, sizeof (m_left)); - memset (m_parent, 0, sizeof (m_parent)); - memset (m_buffer, 0, sizeof (m_buffer)); - } - - ~Compress (void) = default; - - int encode_ (const char *fileName, uint8 *header, int headerSize, uint8 *buffer, int bufferSize) { - File fp (fileName, "wb"); - - if (!fp.isValid ()) { - return -1; + int compress (uint8 *in, int inLength, uint8 *out) { + for (int i = 0; i < HASH_SIZE; i++) { + m_hashTable[i] = NIL; } - int i, length, node, ptr, last, cbp, bp = 0; - uint8 cb[17] = {0, }, mask, bit; + uint8 *op = out; - fp.write (header, headerSize, 1); - initTrees (); + int anchor = 0; + int cur = 0; - cb[0] = 0; - cbp = mask = 1; - ptr = 0; - node = MAXBUF - PADDING; + while (cur < inLength) { + const int maxMatch = inLength - cur; - for (i = ptr; i < node; i++) - m_buffer[i] = ' '; + int bestLength = 0; + int dist = 0; - for (length = 0; (length < PADDING) && (bp < bufferSize); length++) { - bit = buffer[bp++]; - m_buffer[node + length] = bit; - } + if (maxMatch >= MIN_MATCH) { + const int limit = cr::max (cur - WINDOW_SIZE, NIL); - if (length == 0) { - return -1; - } + int chainLength = MAX_CHAIN; + int lookup = m_hashTable[hash32 (&in[cur])]; - for (i = 1; i <= PADDING; i++) { - insert (node - i); - } - insert (node); + while (lookup > limit) { + if (in[lookup + bestLength] == in[cur + bestLength] && load32 (&in[lookup]) == load32 (&in[cur])) { + int length = MIN_MATCH; - do { - if (m_matchLen > length) { - m_matchLen = length; - } - if (m_matchLen <= THRESHOLD) { - m_matchLen = 1; + while (length < maxMatch && in[lookup + length] == in[cur + length]) { + length++; + } - cb[0] |= mask; - cb[cbp++] = m_buffer[node]; - } - else { - cb[cbp++] = (uint8) (m_matchPos & 0xff); - cb[cbp++] = (uint8) (((m_matchPos >> 4) & 0xf0) | (m_matchLen - (THRESHOLD + 1))); - } + if (length > bestLength) { + bestLength = length; + dist = cur - lookup; - if ((mask <<= 1) == 0) { - for (i = 0; i < cbp; i++) { - fp.putch (cb[i]); - } - m_csize += cbp; - cb[0] = 0; - cbp = mask = 1; - } - last = m_matchLen; - - for (i = 0; (i < last) && (bp < bufferSize); i++) { - bit = buffer[bp++]; - erase (ptr); - - m_buffer[ptr] = bit; - - if (ptr < PADDING - 1) { - m_buffer[ptr + MAXBUF] = bit; - } - ptr = (ptr + 1) & (MAXBUF - 1); - node = (node + 1) & (MAXBUF - 1); - insert (node); - } - - while (i++ < last) { - erase (ptr); - - ptr = (ptr + 1) & (MAXBUF - 1); - node = (node + 1) & (MAXBUF - 1); - - if (length--) { - insert (node); - } - } - } while (length > 0); - - if (cbp > 1) { - for (i = 0; i < cbp; i++) { - fp.putch (cb[i]); - } - m_csize += cbp; - } - fp.close (); - - return m_csize; - } - - int decode_ (const char *fileName, int headerSize, uint8 *buffer, int bufferSize) { - int i, j, k, node; - unsigned int flags; - int bp = 0; - - uint8 bit; - - File fp (fileName, "rb"); - - if (!fp.isValid ()) { - return -1; - } - fp.seek (headerSize, SEEK_SET); - - node = MAXBUF - PADDING; - - for (i = 0; i < node; i++) { - m_buffer[i] = ' '; - } - flags = 0; - - for (;;) { - if (((flags >>= 1) & 256) == 0) { - int read = fp.getch (); - - if (read == EOF) { - break; - } - bit = static_cast (read); - flags = bit | 0xff00; - } - - if (flags & 1) { - int read = fp.getch (); - - if (read == EOF) { - break; - } - bit = static_cast (read); - buffer[bp++] = bit; - - if (bp > bufferSize) { - return -1; - } - m_buffer[node++] = bit; - node &= (MAXBUF - 1); - } - else { - if ((i = fp.getch ()) == EOF) { - break; - } - - if ((j = fp.getch ()) == EOF) { - break; - } - - i |= ((j & 0xf0) << 4); - j = (j & 0x0f) + THRESHOLD; - - for (k = 0; k <= j; k++) { - bit = m_buffer[(i + k) & (MAXBUF - 1)]; - buffer[bp++] = bit; - - if (bp > bufferSize) { - return -1; + if (length == maxMatch) { + break; + } + } } - m_buffer[node++] = bit; - node &= (MAXBUF - 1); + + if (--chainLength == 0) { + break; + } + lookup = m_prevTable[lookup & WINDOW_MASK]; + } + } + + if (bestLength == MIN_MATCH && (cur - anchor) >= (7 + 128)) { + bestLength = 0; + } + + if (bestLength >= MIN_MATCH && bestLength < maxMatch && (cur - anchor) != 6) { + const int next = cur + 1; + const int targetLength = bestLength + 1; + const int limit = cr::max (next - WINDOW_SIZE, NIL); + + int chainLength = MAX_CHAIN; + int lookup = m_hashTable[hash32 (&in[next])]; + + while (lookup > limit) { + if (in[lookup + bestLength] == in[next + bestLength] && load32 (&in[lookup]) == load32 (&in[next])) { + int length = MIN_MATCH; + + while (length < targetLength && in[lookup + length] == in[next + length]) { + length++; + } + + if (length == targetLength) { + bestLength = 0; + break; + } + } + + if (--chainLength == 0) { + break; + } + lookup = m_prevTable[lookup & WINDOW_MASK]; + } + } + + if (bestLength >= MIN_MATCH) { + const int length = bestLength - MIN_MATCH; + const int token = ((dist >> 12) & 16) + cr::min (length, 15); + + if (anchor != cur) { + const int run = cur - anchor; + + if (run >= 7) { + add (op, (7 << 5) + token); + encode (op, run - 7); + } + else { + add (op, (run << 5) + token); + } + copy (op, &in[anchor], run); + op += run; + } + else { + add (op, token); + } + + if (length >= 15) { + encode (op, length - 15); + } + store16 (op, dist); + op += 2; + + while (bestLength-- != 0) { + const uint32 hash = hash32 (&in[cur]); + + m_prevTable[cur & WINDOW_MASK] = m_hashTable[hash]; + m_hashTable[hash] = cur++; + } + anchor = cur; + } + else { + const uint32 hash = hash32 (&in[cur]); + + m_prevTable[cur & WINDOW_MASK] = m_hashTable[hash]; + m_hashTable[hash] = cur++; + } + } + + if (anchor != cur) { + const int run = cur - anchor; + + if (run >= 7) { + add (op, 7 << 5); + encode (op, run - 7); + } + else { + add (op, run << 5); + } + copy (op, &in[anchor], run); + op += run; + } + return op - out; + } + + int uncompress (uint8 *in, int inLength, uint8 *out, int outLength) { + uint8 *op = out; + uint8 *ip = in; + + const uint8 *opEnd = op + outLength; + const uint8 *ipEnd = ip + inLength; + + while (ip < ipEnd) { + const int token = *ip++; + + if (token >= 32) { + int run = token >> 5; + + if (run == 7) { + run += decode (ip); + } + + if ((opEnd - op) < run || (ipEnd - ip) < run) { + return UNCOMPRESS_RESULT_FAILED; + } + copy (op, ip, run); + + op += run; + ip += run; + + if (ip >= ipEnd) { + break; + } + } + int length = (token & 15) + MIN_MATCH; + + if (length == (15 + MIN_MATCH)) { + length += decode (ip); + } + + if ((opEnd - op) < length) { + return UNCOMPRESS_RESULT_FAILED; + } + const int dist = ((token & 16) << 12) + load16 (ip); + ip += 2; + + uint8 *cp = op - dist; + + if ((op - out) < dist) { + return UNCOMPRESS_RESULT_FAILED; + } + + if (dist >= 8) { + copy (op, cp, length); + op += length; + } + else + { + for (int i = 0; i < 4; i++) { + *op++ = *cp++; + } + + while (length-- != 4) { + *op++ = *cp++; } } } - fp.close (); - - return bp; + return (ip == ipEnd) ? op - out : UNCOMPRESS_RESULT_FAILED; } - // external decoder - static int decode (const char *fileName, int headerSize, uint8 *buffer, int bufferSize) { - static Compress compressor; - return compressor.decode_ (fileName, headerSize, buffer, bufferSize); +private: + inline uint16 load16 (void *ptr) { + return *reinterpret_cast (ptr); } - // external encoder - static int encode (const char *fileName, uint8 *header, int headerSize, uint8 *buffer, int bufferSize) { - static Compress compressor; - return compressor.encode_ (fileName, header, headerSize, buffer, bufferSize); + inline uint32 load32 (void *ptr) { + return *reinterpret_cast (ptr); } -}; + + inline void store16 (void *ptr, int val) { + *reinterpret_cast (ptr) = static_cast (val); + } + + inline void copy64 (void *dst, void *src) { + *reinterpret_cast (dst) = *reinterpret_cast (src); + } + + inline uint32 hash32 (void *ptr) { + return (load32 (ptr) * 0x9E3779B9) >> (32 - HASH_BITS); + } + + inline void copy (uint8 *dst, uint8 *src, int count) { + copy64 (dst, src); + + for (int i = 8; i < count; i += 8) { + copy64 (dst + i, src + i); + } + } + + inline void add (uint8 *&dst, int val) { + *dst++ = static_cast (val); + } + + inline void encode (uint8 *&ptr, uint32 val) { + while (val >= 128) { + val -= 128; + + *ptr++ = 128 + (val & 127); + val >>= 7; + } + *ptr++ = static_cast (val); + } + + inline uint32 decode (uint8 *&ptr) { + uint32 val = 0; + + for (int i = 0; i <= 21; i += 7) { + const uint32 cur = *ptr++; + val += cur << i; + + if (cur < 128) { + break; + } + } + return val; + } +}; \ No newline at end of file diff --git a/include/corelib.h b/include/corelib.h index 99527c4..1ef7b27 100644 --- a/include/corelib.h +++ b/include/corelib.h @@ -54,8 +54,9 @@ using int32 = signed long; using uint8 = unsigned char; using uint16 = unsigned short; using uint32 = unsigned long; +using uint64 = unsigned long long; -} +}; using namespace cr::types; @@ -71,13 +72,14 @@ constexpr float D2R = PI / 180.0f; constexpr float R2D = 180.0f / PI; // from metamod-p -static inline bool checkptr (const void *ptr) { +static inline bool checkptr (void *ptr) { #ifdef PLATFORM_WIN32 - if (IsBadCodePtr (reinterpret_cast (ptr))) + if (IsBadCodePtr (reinterpret_cast (ptr))) { return false; + } +#else + (void) (ptr); #endif - - (void)(ptr); return true; } @@ -109,6 +111,10 @@ template constexpr T abs (const T a) { return a > 0 ? a : -a; } +template constexpr T bit (const T a) { + return 1 << a; +} + static inline float powf (const float x, const float y) { union { float d; @@ -124,7 +130,7 @@ static inline float sqrtf (const float value) { } static inline float sinf (const float value) { - const signed long sign = static_cast (value * PI_RECIPROCAL); + const auto sign = static_cast (value * PI_RECIPROCAL); const float calc = (value - static_cast (sign) * PI); const float sqr = square (calc); @@ -135,7 +141,7 @@ static inline float sinf (const float value) { } static inline float cosf (const float value) { - const signed long sign = static_cast (value * PI_RECIPROCAL); + const auto sign = static_cast (value * PI_RECIPROCAL); const float calc = (value - static_cast (sign) * PI); const float sqr = square (calc); @@ -303,7 +309,7 @@ protected: NonCopyable (void) = default; ~NonCopyable (void) = default; -private: +public: NonCopyable (const NonCopyable &) = delete; NonCopyable &operator = (const NonCopyable &) = delete; }; @@ -323,33 +329,33 @@ public: // see: https://github.com/preshing/RandomSequence/ class RandomSequence : public Singleton { private: - unsigned int m_index; - unsigned int m_intermediateOffset; - unsigned long long m_divider; + uint32 m_index; + uint32 m_intermediateOffset; + uint64 m_divider; private: - unsigned int premute (unsigned int x) { - static constexpr unsigned int prime = 4294967291u; + uint32 premute (uint32 x) { + static constexpr uint32 prime = 4294967291u; if (x >= prime) { return x; } - const unsigned int residue = (static_cast (x) * x) % prime; + const uint32 residue = (static_cast (x) * x) % prime; return (x <= prime / 2) ? residue : prime - residue; } - unsigned int random (void) { + uint32 random (void) { return premute ((premute (m_index++) + m_intermediateOffset) ^ 0x5bf03635); } public: RandomSequence (void) { - const unsigned int seedBase = static_cast (time (nullptr)); - const unsigned int seedOffset = seedBase + 1; + const auto seedBase = static_cast (time (nullptr)); + const auto seedOffset = seedBase + 1; m_index = premute (premute (seedBase) + 0x682f0161); m_intermediateOffset = premute (premute (seedOffset) + 0x46790905); - m_divider = (static_cast (1)) << 32; + m_divider = (static_cast (1)) << 32; } template inline U getInt (U low, U high) { @@ -367,7 +373,7 @@ public: class SimpleColor final : private NonCopyable { public: - int red, green, blue; + int red = 0, green = 0, blue = 0; inline void reset (void) { red = green = blue = 0; @@ -390,7 +396,7 @@ public: inline Vector (float scaler = 0.0f) : x (scaler), y (scaler), z (scaler) {} inline Vector (float inputX, float inputY, float inputZ) : x (inputX), y (inputY), z (inputZ) {} inline Vector (float *other) : x (other[0]), y (other[1]), z (other[2]) {} - inline Vector (const Vector &right) : x (right.x), y (right.y), z (right.z) {} + inline Vector (const Vector &right) = default; public: inline operator float * (void) { @@ -401,33 +407,33 @@ public: return &x; } - inline const Vector operator + (const Vector &right) const { + inline Vector operator + (const Vector &right) const { return Vector (x + right.x, y + right.y, z + right.z); } - inline const Vector operator - (const Vector &right) const { + inline Vector operator - (const Vector &right) const { return Vector (x - right.x, y - right.y, z - right.z); } - inline const Vector operator - (void) const { + inline Vector operator - (void) const { return Vector (-x, -y, -z); } - friend inline const Vector operator* (const float vec, const Vector &right) { + friend inline Vector operator * (const float vec, const Vector &right) { return Vector (right.x * vec, right.y * vec, right.z * vec); } - inline const Vector operator * (float vec) const { + inline Vector operator * (float vec) const { return Vector (vec * x, vec * y, vec * z); } - inline const Vector operator / (float vec) const { + inline Vector operator / (float vec) const { const float inv = 1 / vec; return Vector (inv * x, inv * y, inv * z); } // cross product - inline const Vector operator ^ (const Vector &right) const { + inline Vector operator ^ (const Vector &right) const { return Vector (y * right.z - z * right.y, z * right.x - x * right.z, x * right.y - y * right.x); } @@ -478,13 +484,7 @@ public: return !fequal (x, right.x) && !fequal (y, right.y) && !fequal (z, right.z); } - inline const Vector &operator = (const Vector &right) { - x = right.x; - y = right.y; - z = right.z; - - return *this; - } + inline Vector &operator = (const Vector &right) = default; public: inline float length (void) const { @@ -572,7 +572,7 @@ public: float cosines[max] = { 0.0f, 0.0f, 0.0f, 0.0f }; // compute the sine and cosine compontents - sincosf (deg2rad (x), deg2rad (y), deg2rad (y), sines, cosines); + sincosf (deg2rad (x), deg2rad (y), deg2rad (z), sines, cosines); if (forward) { forward->x = cosines[pitch] * cosines[yaw]; @@ -596,12 +596,12 @@ public: class Library final : private NonCopyable { private: - void *m_ptr; + void *m_ptr = nullptr; public: - explicit Library (void) : m_ptr (nullptr) { } + explicit Library (void) = default; - Library (const char *filename) : m_ptr (nullptr) { + Library (const char *filename) { if (!filename) { return; } @@ -637,12 +637,13 @@ public: if (!isValid ()) { return nullptr; } - return (R) + return reinterpret_cast ( #ifdef PLATFORM_WIN32 - GetProcAddress (static_cast (m_ptr), function); + GetProcAddress (static_cast (m_ptr), function) #else - dlsym (m_ptr, function); + dlsym (m_ptr, function) #endif + ); } template R handle (void) { @@ -656,11 +657,11 @@ public: template class Pair final { public: - A first; - B second; + A first = move (A ()); + B second = move (B ()); public: - Pair (const A &a, const B &b) : first (cr::move (a)), second (cr::move (b)) { + Pair (const A &a, const B &b) : first (move (a)), second (move (b)) { } public: @@ -673,13 +674,12 @@ public: static constexpr size_t INVALID_INDEX = static_cast (-1); protected: - T *m_data; - size_t m_capacity; - size_t m_length; + T *m_data = nullptr; + size_t m_capacity = 0; + size_t m_length = 0; public: - Array (void) : m_data (nullptr), m_capacity (0), m_length (0) { - } + Array (void) = default; Array (Array &&other) noexcept { m_data = other.m_data; @@ -689,7 +689,7 @@ public: other.reset (); } - virtual ~Array (void) { + ~Array (void) { destroy (); } @@ -709,7 +709,7 @@ public: if (m_length + growSize < m_capacity) { return true; } - size_t maxSize = max (m_capacity + sizeof (T), static_cast (16)); + auto maxSize = max (m_capacity + sizeof (T), static_cast (16)); while (m_length + growSize > maxSize) { maxSize *= 2; @@ -738,16 +738,16 @@ public: bool res = reserve (newSize); while (m_length < newSize) { - push (T ()); + push (move (T ())); } return res; } - inline size_t length (void) const { + size_t length (void) const { return m_length; } - inline size_t capacity (void) const { + size_t capacity (void) const { return m_capacity; } @@ -765,7 +765,11 @@ public: return true; } - inline T &at (size_t index) { + T &at (size_t index) { + return m_data[index]; + } + + const T &at (size_t index) const { return m_data[index]; } @@ -861,7 +865,7 @@ public: m_length = 0; } - inline bool empty (void) const { + bool empty (void) const { return m_length == 0; } @@ -922,6 +926,12 @@ public: return true; } + void shuffle (void) { + for (size_t i = m_length; i >= 1; i--) { + swap (m_data[i - 1], m_data[RandomSequence::ref ().getInt (i, m_length - 2)]); + } + } + void reverse (void) { for (size_t i = 0; i < m_length / 2; i++) { swap (m_data[i], m_data[m_length - 1 - i]); @@ -1010,15 +1020,15 @@ public: return value.first; } - inline bool empty (void) const { + bool empty (void) const { return !length (); } - inline size_t length (void) const { + size_t length (void) const { return m_length; } - inline void clear (void) { + void clear (void) { base::clear (); } @@ -1081,7 +1091,12 @@ private: } }; + +// some fast string class class String final : private Array { +public: + static constexpr size_t INVALID_INDEX = Array ::INVALID_INDEX; + private: using base = Array ; @@ -1097,9 +1112,9 @@ private: } public: + String (String &&other) noexcept : base (move (other)) { } String (void) = default; - String (String &&other) noexcept : base (move (other)) { } ~String (void) = default; public: @@ -1119,12 +1134,12 @@ public: String &assign (const char *str, size_t length = 0) { length = length > 0 ? length : strlen (str); - clear (); - resize (length); - - memcpy (m_data, str, length); - terminate (); + base::clear (); + base::reserve (length + 1); + if (base::push (const_cast (str), length)) { + terminate (); + } return *this; } @@ -1133,7 +1148,7 @@ public: } String &assign (const char chr) { - resize (2); + resize (1); m_data[0] = chr; return *this; @@ -1143,11 +1158,9 @@ public: if (empty ()) { return assign (str); } - const size_t maxLength = strlen (str) + 1; - - resize (length () + maxLength); - strncat (m_data, str, maxLength); - + if (push (const_cast (str), strlen (str))) { + terminate (); + }; return *this; } @@ -1168,6 +1181,10 @@ public: return base::length (); } + size_t capacity (void) const { + return base::capacity (); + } + bool empty (void) const { return !length (); } @@ -1198,11 +1215,19 @@ public: return atoi (chars ()); } - inline void terminate (void) { + float toFloat (void) const { + return static_cast (atof (chars ())); + } + + void terminate (void) { m_data[m_length] = '\0'; } - inline char &at (size_t index) { + char &at (size_t index) { + return m_data[index]; + } + + const char &at (size_t index) const { return m_data[index]; } @@ -1211,33 +1236,30 @@ public: } int32 compare (const char *what) const { - return strcmp (begin (), what); + return strcmp (m_data, what); } bool contains (const String &what) const { return strstr (m_data, what.begin ()) != nullptr; } - template void format (const char *fmt, ...) { - va_list ap; - char buffer[BufferSize]; + template void assign (const char *fmt, Args ...args) { + const size_t size = snprintf (nullptr, 0, fmt, args...); - va_start (ap, fmt); - vsnprintf (buffer, bufsize (buffer), fmt, ap); - va_end (ap); + reserve (size + 1); + resize (size); - assign (buffer); + snprintf (&m_data[0], size + 1, fmt, args...); } - template void formatAppend (const char *fmt, ...) { - va_list ap; - char buffer[BufferSize]; + template void append (const char *fmt, Args ...args) { + const size_t size = snprintf (nullptr, 0, fmt, args...) + m_length; + const size_t len = m_length; - va_start (ap, fmt); - vsnprintf (buffer, bufsize (buffer), fmt, ap); - va_end (ap); + reserve (size + 1); + resize (size); - append (buffer); + snprintf (&m_data[len], size + 1, fmt, args...); } size_t insert (size_t at, const String &str) { @@ -1248,14 +1270,32 @@ public: return 0; } - size_t find (const String &search, size_t pos) const { - if (pos > length ()) { - return INVALID_INDEX; + size_t find (char val, size_t pos) const { + for (size_t i = pos; i < m_length; i++) { + if (m_data[i] == val) { + return i; + } } + return INVALID_INDEX; + } + + size_t find (const String &search, size_t pos) const { size_t len = search.length (); - for (size_t i = pos; len + i <= length (); i++) { - if (strncmp (m_data + i, search.chars (), len) == 0) { + if (len > m_length || pos > m_length) { + return INVALID_INDEX; + } + + for (size_t i = pos; i <= m_length - len; i++) { + size_t at = 0; + + for (; search.m_data[at]; at++) { + if (m_data[i + at] != search.m_data[at]) { + break; + } + } + + if (!search.m_data[at]) { return i; } } @@ -1268,7 +1308,7 @@ public: } size_t numReplaced = 0, posIndex = 0; - while (posIndex < length ()) { + while (posIndex < m_length) { posIndex = find (what, posIndex); if (posIndex == INVALID_INDEX) { @@ -1283,28 +1323,96 @@ public: return numReplaced; } - String substr (size_t start, size_t count = INVALID_INDEX) { + String substr (size_t start, size_t count = INVALID_INDEX) const { String result; - if (start >= length () || empty ()) { - return result; + if (start >= m_length || empty ()) { + return move (result); } if (count == INVALID_INDEX) { - count = length () - start; + count = m_length - start; } - else if (start + count >= length ()) { - count = length () - start; + else if (start + count >= m_length) { + count = m_length - start; } result.resize (count); - transfer (&result[0], &m_data[start], count); + // copy, not move + for (size_t i = 0; i < count; i++) { + result[i] = m_data[start + i]; + } result[count] = '\0'; - - return result; + return move (result); } - char &operator[] (size_t index) { - return at (index); + String &rtrim (const char *chars = "\r\n\t ") { + if (empty ()) { + return *this; + } + char *str = end () - 1; + + while (*str != 0) { + if (isTrimChar (*str, chars)) { + erase (str - begin ()); + str--; + } + else { + break; + } + } + return *this; + } + + String <rim (const char *chars = "\r\n\t ") { + if (empty ()) { + return *this; + } + char *str = begin (); + + while (isTrimChar (*str, chars)) { + str++; + } + + if (begin () != str) { + erase (0, str - begin ()); + } + return *this; + } + + String &trim (const char *chars = "\r\n\t ") { + return rtrim (chars).ltrim (chars); + } + + Array split (const String &delim) const { + Array tokens; + size_t prev = 0, pos = 0; + + do { + pos = find (delim, prev); + + if (pos == INVALID_INDEX) { + pos = m_length; + } + tokens.push (move (substr (prev, pos - prev))); + + prev = pos + delim.length (); + } while (pos < m_length && prev < m_length); + + return move (tokens); + } + + String &lowercase (void) { + for (auto &ch : *this) { + ch = static_cast (tolower (ch)); + } + return *this; + } + + String &uppercase (void) { + for (auto &ch : *this) { + ch = static_cast (toupper (ch)); + } + return *this; } friend String operator + (const String &lhs, const String &rhs) { @@ -1384,79 +1492,51 @@ public: return append (rhs); } - String &trimRight (const char *chars = "\r\n\t ") { - if (empty ()) { - return *this; - } - char *str = end () - 1; - - while (*str != 0) { - if (isTrimChar (*str, chars)) { - erase (str - begin ()); - str--; - } - else { - break; - } - } - return *this; + char &operator [] (size_t index) { + return at (index); } - String &trimLeft (const char *chars = "\r\n\t ") { - if (empty ()) { - return *this; - } - char *str = begin (); - - while (isTrimChar (*str, chars)) { - str++; - } - - if (begin () != str) { - erase (0, str - begin ()); - } - return *this; - } - - String &trim (const char *chars = "\r\n\t ") { - return trimLeft (chars).trimRight (chars); - } - - Array split (const char *delimiter) { - Array tokens; - size_t len, index = 0; - - do { - index += strspn (&m_data[index], delimiter); - len = strcspn (&m_data[index], delimiter); - - if (len > 0) { - tokens.push (move (substr (index, len))); - } - index += len; - } while (len > 0); - - return tokens; + const char &operator [] (size_t index) const { + return at (index); } public: - static void trimChars (char *str) { - size_t pos = 0; - char *dest = str; - - while (str[pos] <= ' ' && str[pos] > 0) { - pos++; + char *begin (void) { + return base::begin (); } - while (str[pos]) { - *(dest++) = str[pos]; - pos++; + char *begin (void) const { + return base::begin (); } - *(dest--) = '\0'; - while (dest >= str && *dest <= ' ' && *dest > 0) { - *(dest--) = '\0'; + char *end (void) { + return base::end (); } + + char *end (void) const { + return base::end (); + } + +public: + static String join (const Array &sequence, const String &delim, size_t start = 0) { + if (sequence.empty ()) { + return ""; + } + + if (sequence.length () == 1) { + return sequence.at (0); + } + String result; + + for (size_t index = start; index < sequence.length (); index++) { + if (index != start) { + result += delim + sequence[index]; + } + else { + result += sequence[index]; + } + } + return move (result); } }; @@ -1553,17 +1633,16 @@ public: class File : private NonCopyable { private: - FILE *m_handle; - size_t m_size; + FILE *m_handle = nullptr; + size_t m_size = 0; public: - File (void) : m_handle (nullptr), m_size (0) {} - - File (const String &fileName, const String &mode = "rt") : m_handle (nullptr), m_size (0){ + File (void) = default; + File (const String &fileName, const String &mode = "rt") { open (fileName, mode); } - virtual ~File (void) { + ~File (void) { close (); } @@ -1594,14 +1673,24 @@ public: return fflush (m_handle) ? false : true; } - int getch (void) { - return fgetc (m_handle); + char getch (void) { + return static_cast (fgetc (m_handle)); } char *gets (char *buffer, int count) { return fgets (buffer, count, m_handle); } + bool getLine (String &line) { + line.clear (); + char ch; + + while ((ch = getch ()) != '\n' && ch != EOF && !eof ()) { + line += ch; + } + return !eof (); + } + int writeFormat (const char *format, ...) { assert (m_handle != nullptr); @@ -1714,14 +1803,13 @@ public: class MemFile : private NonCopyable { private: - size_t m_size; - size_t m_pos; - uint8 *m_buffer; + size_t m_size = 0; + size_t m_pos = 0; + uint8 *m_buffer = nullptr; public: - MemFile (void) : m_size (0) , m_pos (0) , m_buffer (nullptr) {} - - MemFile (const String &filename) : m_size (0) , m_pos (0) , m_buffer (nullptr) { + MemFile (void) = default; + MemFile (const String &filename) { open (filename); } @@ -1748,14 +1836,14 @@ public: m_buffer = nullptr; } - int getch (void) { + char getch (void) { if (!m_buffer || m_pos >= m_size) { return -1; } int readCh = m_buffer[m_pos]; m_pos++; - return readCh; + return static_cast (readCh); } char *gets (char *buffer, size_t count) { @@ -1781,6 +1869,16 @@ public: return index ? buffer : nullptr; } + bool getLine (String &line) { + line.clear (); + char ch; + + while ((ch = getch ()) != '\n' && ch != EOF && m_pos < m_size) { + line += ch; + } + return m_pos < m_size; + } + size_t read (void *buffer, size_t size, size_t count = 1) { if (!m_buffer || m_size <= m_pos || !buffer || !size || !count) { return 0; @@ -1818,7 +1916,7 @@ public: return true; } - inline size_t getSize (void) const { + size_t getSize (void) const { return m_size; } @@ -1827,8 +1925,7 @@ public: } }; -}} - +}}; namespace cr { namespace types { @@ -1836,4 +1933,4 @@ namespace types { using StringArray = cr::classes::Array ; using IntArray = cr::classes::Array ; -}} +}}; diff --git a/include/engine.h b/include/engine.h index dbc1577..c293ad8 100644 --- a/include/engine.h +++ b/include/engine.h @@ -10,22 +10,22 @@ #pragma once // line draw -enum DrawLineType { +enum DrawLineType : int { DRAW_SIMPLE, DRAW_ARROW, DRAW_NUM }; // trace ignore -enum TraceIgnore { +enum TraceIgnore : int { TRACE_IGNORE_NONE = 0, - TRACE_IGNORE_GLASS = (1 << 0), - TRACE_IGNORE_MONSTERS = (1 << 1), + TRACE_IGNORE_GLASS = cr::bit (0), + TRACE_IGNORE_MONSTERS = cr::bit (1), TRACE_IGNORE_EVERYTHING = TRACE_IGNORE_GLASS | TRACE_IGNORE_MONSTERS }; // variable type -enum VarType { +enum VarType : int { VT_NORMAL = 0, VT_READONLY, VT_PASSWORD, @@ -34,7 +34,7 @@ enum VarType { }; // netmessage functions -enum NetMsgId { +enum NetMsgId : int { NETMSG_UNDEFINED = -1, NETMSG_VGUI = 1, NETMSG_SHOWMENU = 2, @@ -61,6 +61,36 @@ enum NetMsgId { NETMSG_NUM = 25 }; +// supported cs's +enum GameFlags : int { + GAME_CSTRIKE16 = cr::bit (0), // counter-strike 1.6 and above + GAME_XASH_ENGINE = cr::bit (1), // counter-strike 1.6 under the xash engine (additional flag) + GAME_CZERO = cr::bit (2), // counter-strike: condition zero + GAME_LEGACY = cr::bit (3), // counter-strike 1.3-1.5 with/without steam + GAME_MOBILITY = cr::bit (4), // additional flag that bot is running on android (additional flag) + GAME_OFFICIAL_CSBOT = cr::bit (5), // additional flag that indicates official cs bots are in game + GAME_METAMOD = cr::bit (6), // game running under meta\mod + GAME_CSDM = cr::bit (7), // csdm mod currently in use + GAME_CSDM_FFA = cr::bit (8), // csdm mod with ffa mode + GAME_REGAMEDLL = cr::bit (9), // server dll is a regamedll + GAME_SUPPORT_SVC_PINGS = cr::bit (10), // on that game version we can fake bots pings + GAME_SUPPORT_BOT_VOICE = cr::bit (11) // on that game version we can use chatter +}; + + +// defines map type +enum MapFlags : int { + MAP_AS = cr::bit (0), + MAP_CS = cr::bit (1), + MAP_DE = cr::bit (2), + MAP_ES = cr::bit (3), + MAP_KA = cr::bit (4), + MAP_FY = cr::bit (5), + + // additional flags + MAP_HAS_DOORS = cr::bit (6) +}; + // variable reg pair struct VarPair { VarType type; @@ -79,6 +109,14 @@ struct MessageBlock { int regMsgs[NETMSG_NUM]; }; +// referentia vector info +struct RefVector { + Vector forward, right, up; +}; + +// entity prototype +using EntityFunction = void (*) (entvars_t *); + // compare language struct LangComprarer { size_t operator () (const String &key) const { @@ -96,15 +134,14 @@ struct LangComprarer { }; // provides utility functions to not call original engine (less call-cost) -class Engine : public Singleton { +class Game final : public Singleton { private: int m_drawModels[DRAW_NUM]; int m_spawnCount[TEAM_UNASSIGNED]; // bot client command - char m_arguments[256]; bool m_isBotCommand; - int m_argumentCount; + StringArray m_botArgs; edict_t *m_startEntity; edict_t *m_localEntity; @@ -112,19 +149,27 @@ private: Array m_cvars; HashMap m_language; + Library m_gameLib; MessageBlock m_msgBlock; bool m_precached; + int m_gameFlags; + int m_mapFlags; + + float m_slowFrame; // per second updated frame public: - Engine (void); - ~Engine (void); + RefVector vec; + +public: + Game (void); + ~Game (void); public: // precaches internal stuff void precache (void); // initialize levels - void levelInitialize (void); + void levelInitialize (edict_t *ents, int max); // prints data to servers console void print (const char *fmt, ...); @@ -135,6 +180,9 @@ public: // prints center message to all players void centerPrint (const char *fmt, ...); + // prints center message to specified player + void centerPrint (edict_t *ent, const char *fmt, ...); + // prints message to client console void clientPrint (edict_t *ent, const char *fmt, ...); @@ -189,122 +237,170 @@ public: // checks whether softwared rendering is enabled bool isSoftwareRenderer (void); + // load the cs binary in non metamod mode + bool loadCSBinary (void); + + // do post-load stuff + bool postload (void); + + // detects if csdm mod is in use + void detectDeathmatch (void); + + // executes stuff every 1 second + void slowFrame (void); + + // begin message handler + void beginMessage (edict_t *ent, int dest, int type); + // public inlines public: // get the current time on server - inline float timebase (void) { - return g_pGlobals->time; + float timebase (void) const { + return globals->time; } // get "maxplayers" limit on server - inline int maxClients (void) { - return g_pGlobals->maxClients; + int maxClients (void) const { + return globals->maxClients; } // get the fakeclient command interface - inline bool isBotCmd (void) { + bool isBotCmd (void) const { return m_isBotCommand; } // gets custom engine args for client command - inline const char *botArgs (void) { - if (strncmp ("say ", m_arguments, 4) == 0) { - return &m_arguments[4]; - } - else if (strncmp ("say_team ", m_arguments, 9) == 0) { - return &m_arguments[9]; - } - return m_arguments; + const char *botArgs (void) const { + static String args; + args = String::join (m_botArgs, " ", m_botArgs[0] == "say" || m_botArgs[0] == "say_team" ? 1 : 0); + + return args.chars (); } // gets custom engine argv for client command - inline const char *botArgv (int num) { - return getField (m_arguments, static_cast (num)); + const char *botArgv (size_t index) const { + if (index >= m_botArgs.length ()) { + return ""; + } + return m_botArgs[index].chars (); } // gets custom engine argc for client command - inline int botArgc (void) { - return m_argumentCount; + int botArgc (void) const { + return m_botArgs.length (); } // gets edict pointer out of entity index - inline edict_t *entityOfIndex (const int index) { + edict_t *entityOfIndex (const int index) { return static_cast (m_startEntity + index); }; // gets edict index out of it's pointer - inline int indexOfEntity (const edict_t *ent) { + int indexOfEntity (const edict_t *ent) { return static_cast (ent - m_startEntity); }; // verify entity isn't null - inline bool isNullEntity (const edict_t *ent) { + bool isNullEntity (const edict_t *ent) { return !ent || !indexOfEntity (ent) || ent->free; } // get the wroldspawn entity - inline edict_t *getStartEntity (void) { + edict_t *getStartEntity (void) { return m_startEntity; } // get spawn count for team - inline int getSpawnCount (int team) { + int getSpawnCount (int team) const { return m_spawnCount[team]; } // gets the player team - inline int getTeam (edict_t *ent) { - extern Client g_clients[MAX_ENGINE_PLAYERS]; - return g_clients[indexOfEntity (ent) - 1].team; - } + int getTeam (edict_t *ent); // adds translation pair from config - inline void addTranslation (const String &original, const String &translated) { + void addTranslation (const String &original, const String &translated) { m_language.put (original, translated); } // resets the message capture mechanism - inline void resetMessages (void) { + void resetMessages (void) { m_msgBlock.msg = NETMSG_UNDEFINED; m_msgBlock.state = 0; m_msgBlock.bot = 0; }; // sets the currently executed message - inline void setCurrentMessageId (int message) { + void setCurrentMessageId (int message) { m_msgBlock.msg = message; } // set the bot entity that receive this message - inline void setCurrentMessageOwner (int id) { + void setCurrentMessageOwner (int id) { m_msgBlock.bot = id; } // find registered message id - inline int getMessageId (int type) { + int getMessageId (int type) { return m_msgBlock.regMsgs[type]; } // assigns message id for message type - inline void setMessageId (int type, int id) { + void setMessageId (int type, int id) { m_msgBlock.regMsgs[type] = id; } // tries to set needed message id - inline void captureMessage (int type, int msgId) { + void captureMessage (int type, int msgId) { if (type == m_msgBlock.regMsgs[msgId]) { setCurrentMessageId (msgId); } } // sets the precache to uninitialize - inline void setUnprecached (void) { + void setUnprecached (void) { m_precached = false; } - // static utility functions -private: - const char *getField (const char *string, size_t id); + // gets the local entity (host edict) + edict_t *getLocalEntity (void) { + return m_localEntity; + } + + // sets the local entity (host edict) + void setLocalEntity (edict_t *ent) { + m_localEntity = ent; + } + + // builds referential vector + void makeVectors (const Vector &in) { + in.makeVectors (&vec.forward, &vec.right, &vec.up); + } + + // what kind of map we're running ? + bool isMap (const int map) const { + return (m_mapFlags & map) == map; + } + + // what kind of game engine / game dll / mod / tool we're running ? + bool is (const int type) const { + return (m_gameFlags & type) == type; + } + + // adds game flag + void addGameFlag (const int type) { + m_gameFlags |= type; + } + + // gets the map type + bool mapIs (const int type) const { + return (m_mapFlags & type) == type; + } + + // get loaded gamelib + Library &getLib (void) { + return m_gameLib; + } }; // simplify access for console variables @@ -314,35 +410,35 @@ public: public: ConVar (const char *name, const char *initval, VarType type = VT_NOSERVER, bool regMissing = false, const char *regVal = nullptr) : m_eptr (nullptr) { - Engine::ref ().pushVarToRegStack (name, initval, type, regMissing, regVal, this); + Game::ref ().pushVarToRegStack (name, initval, type, regMissing, regVal, this); } - inline bool boolean (void) const { + bool boolean (void) const { return m_eptr->value > 0.0f; } - inline int integer (void) const { + int integer (void) const { return static_cast (m_eptr->value); } - inline float flt (void) const { + float flt (void) const { return m_eptr->value; } - inline const char *str (void) const { + const char *str (void) const { return m_eptr->string; } - inline void set (float val) const { - g_engfuncs.pfnCVarSetFloat (m_eptr->name, val); + void set (float val) { + engfuncs.pfnCVarSetFloat (m_eptr->name, val); } - inline void set (int val) const { + void set (int val) { set (static_cast (val)); } - inline void set (const char *val) const { - g_engfuncs.pfnCvar_DirectSet (m_eptr, const_cast (val)); + void set (const char *val) { + engfuncs.pfnCvar_DirectSet (m_eptr, const_cast (val)); } }; @@ -366,36 +462,36 @@ public: public: MessageWriter &start (int dest, int type, const Vector &pos = Vector::null (), edict_t *to = nullptr) { - g_engfuncs.pfnMessageBegin (dest, type, pos, to); + engfuncs.pfnMessageBegin (dest, type, pos, to); return *this; } void end (void) { - g_engfuncs.pfnMessageEnd (); + engfuncs.pfnMessageEnd (); } MessageWriter &writeByte (int val) { - g_engfuncs.pfnWriteByte (val); + engfuncs.pfnWriteByte (val); return *this; } MessageWriter &writeChar (int val) { - g_engfuncs.pfnWriteChar (val); + engfuncs.pfnWriteChar (val); return *this; } MessageWriter &writeShort (int val) { - g_engfuncs.pfnWriteShort (val); + engfuncs.pfnWriteShort (val); return *this; } MessageWriter &writeCoord (float val) { - g_engfuncs.pfnWriteCoord (val); + engfuncs.pfnWriteCoord (val); return *this; } MessageWriter &writeString (const char *val) { - g_engfuncs.pfnWriteString (val); + engfuncs.pfnWriteString (val); return *this; } @@ -409,26 +505,25 @@ public: } }; - class LightMeasure final : public Singleton { private: lightstyle_t m_lightstyle[MAX_LIGHTSTYLES]; int m_lightstyleValue[MAX_LIGHTSTYLEVALUE]; - bool m_doAnimation; + bool m_doAnimation = false; SimpleColor m_point; - model_t *m_worldModel; + model_t *m_worldModel = nullptr; public: - LightMeasure (void) : m_doAnimation (false), m_worldModel (nullptr) { + LightMeasure (void) { initializeLightstyles (); - m_point.reset (); } public: void initializeLightstyles (void); void animateLight (void); + void updateLight (int style, char *value); float getLightLevel (const Vector &point); float getSkyColor (void); @@ -437,18 +532,18 @@ private: template bool recursiveLightPoint (const M *node, const Vector &start, const Vector &end); public: - inline void resetWorldModel (void) { + void resetWorldModel (void) { m_worldModel = nullptr; } - inline void setWorldModel (model_t *model) { + void setWorldModel (model_t *model) { if (m_worldModel) { return; } m_worldModel = model; } - inline void enableAnimation (bool enable) { + void enableAnimation (bool enable) { m_doAnimation = enable; } }; \ No newline at end of file diff --git a/include/engine/meta_api.h b/include/engine/meta_api.h index 5bf7dc2..e8ba52c 100644 --- a/include/engine/meta_api.h +++ b/include/engine/meta_api.h @@ -112,7 +112,6 @@ typedef struct { extern gamedll_funcs_t *gpGamedllFuncs; extern mutil_funcs_t *gpMetaUtilFuncs; -extern meta_globals_t *gpMetaGlobals; extern metamod_funcs_t gMetaFunctionTable; #define MDLL_FUNC gpGamedllFuncs->dllapi_table diff --git a/include/engine/util.h b/include/engine/util.h index cc405f1..ee5b56a 100644 --- a/include/engine/util.h +++ b/include/engine/util.h @@ -16,23 +16,24 @@ #ifndef SDKUTIL_H #define SDKUTIL_H -extern globalvars_t *g_pGlobals; -extern enginefuncs_t g_engfuncs; +extern globalvars_t *globals; +extern enginefuncs_t engfuncs; +extern gamefuncs_t dllapi; // Use this instead of ALLOC_STRING on constant strings -#define STRING(offset) (const char *)(g_pGlobals->pStringBase + (int)offset) +#define STRING(offset) (const char *)(globals->pStringBase + (int)offset) // form fwgs-hlsdk static inline int MAKE_STRING (const char *val) { long long ptrdiff = val - STRING (0); if (ptrdiff > INT_MAX || ptrdiff < INT_MIN) { - return g_engfuncs.pfnAllocString (val); + return engfuncs.pfnAllocString (val); } return static_cast (ptrdiff); } -#define ENGINE_STR(str) (const_cast (STRING (g_engfuncs.pfnAllocString (str)))) +#define ENGINE_STR(str) (const_cast (STRING (engfuncs.pfnAllocString (str)))) // Dot products for view cone checking #define VIEW_FIELD_FULL (float)-1.0 // +-180 degrees diff --git a/include/globals.h b/include/globals.h deleted file mode 100644 index c65899e..0000000 --- a/include/globals.h +++ /dev/null @@ -1,73 +0,0 @@ -// -// Yet Another POD-Bot, based on PODBot by Markus Klinge ("CountFloyd"). -// Copyright (c) YaPB Development Team. -// -// This software is licensed under the BSD-style license. -// Additional exceptions apply. For full license details, see LICENSE.txt or visit: -// https://yapb.ru/license -// - -#pragma once - -extern bool g_canSayBombPlanted; -extern bool g_bombPlanted; -extern bool g_bombSayString; -extern bool g_roundEnded; -extern bool g_waypointOn; -extern bool g_autoWaypoint; -extern bool g_botsCanPause; -extern bool g_editNoclip; -extern bool g_gameWelcomeSent; - -extern float g_autoPathDistance; -extern float g_timeBombPlanted; -extern float g_timeNextBombUpdate; -extern float g_lastChatTime; -extern float g_timeRoundEnd; -extern float g_timeRoundMid; -extern float g_timeRoundStart; -extern float g_timePerSecondUpdate; -extern float g_lastRadioTime[MAX_TEAM_COUNT]; - -extern int g_mapFlags; -extern int g_gameFlags; - -extern int g_highestDamageCT; -extern int g_highestDamageT; -extern int g_highestKills; - -extern int g_normalWeaponPrefs[NUM_WEAPONS]; -extern int g_rusherWeaponPrefs[NUM_WEAPONS]; -extern int g_carefulWeaponPrefs[NUM_WEAPONS]; -extern int g_grenadeBuyPrecent[NUM_WEAPONS - 23]; -extern int g_botBuyEconomyTable[NUM_WEAPONS - 15]; -extern int g_radioSelect[MAX_ENGINE_PLAYERS]; -extern int g_lastRadio[MAX_TEAM_COUNT]; -extern int g_storeAddbotVars[4]; -extern int *g_weaponPrefs[]; - -extern Array g_chatFactory; -extern Array > g_chatterFactory; -extern Array g_botNames; -extern Array g_replyFactory; - -extern WeaponSelect g_weaponSelect[NUM_WEAPONS + 1]; -extern WeaponProperty g_weaponDefs[MAX_WEAPONS + 1]; - -extern Client g_clients[MAX_ENGINE_PLAYERS]; -extern MenuText g_menus[BOT_MENU_TOTAL_MENUS]; -extern Task g_taskFilters[TASK_MAX]; - -extern Experience *g_experienceData; - -extern edict_t *g_hostEntity; -extern Library g_gameLib; - -extern gamefuncs_t g_functionTable; - -static inline bool isEmptyStr (const char *input) { - if (input == nullptr) { - return true; - } - return *input == '\0'; -} diff --git a/include/platform.h b/include/platform.h index 9aa128f..6f971bb 100644 --- a/include/platform.h +++ b/include/platform.h @@ -87,7 +87,10 @@ // @todo: sse should be working ok on x86 android? #if defined(__ANDROID__) #define PLATFORM_ANDROID - #undef PLATFORM_HAS_SSE2 + + #if defined (__arm__) || defined (__aarch64__ ) + #undef PLATFORM_HAS_SSE2 + #endif #endif #else #error "Platform unrecognized." diff --git a/include/resource.h b/include/resource.h index 0803482..6d17956 100644 --- a/include/resource.h +++ b/include/resource.h @@ -12,7 +12,7 @@ // general product information #define PRODUCT_NAME "Yet Another POD-Bot" #define PRODUCT_SHORT_NAME "YaPB" -#define PRODUCT_VERSION "2.10" +#define PRODUCT_VERSION "2.91" #define PRODUCT_AUTHOR "YaPB Dev Team" #define PRODUCT_URL "https://yapb.ru/" #define PRODUCT_EMAIL "d@entix.io" @@ -26,7 +26,7 @@ #define PRODUCT_GIT_HASH "unspecified_hash" #define PRODUCT_GIT_COMMIT_AUTHOR "unspecified_author" #define PRODUCT_GIT_COMMIT_ID 0000 -#define PRODUCT_VERSION_DWORD_INTERNAL 2, 10 +#define PRODUCT_VERSION_DWORD_INTERNAL 2, 91 #define PRODUCT_VERSION_DWORD PRODUCT_VERSION_DWORD_INTERNAL, PRODUCT_GIT_COMMIT_ID #define PRODUCT_SUPPORT_VERSION "Beta 6.6 - Condition Zero" #define PRODUCT_COMMENTS "http://github.com/jeefo/yapb/" diff --git a/include/yapb.h b/include/yapb.h index 6674f82..d63b199 100644 --- a/include/yapb.h +++ b/include/yapb.h @@ -22,8 +22,11 @@ using namespace cr::classes; #include #include +#include +#include + // defines bots tasks -enum TaskID { +enum TaskID : int { TASK_NORMAL, TASK_PAUSE, TASK_MOVETOPOSITION, @@ -47,24 +50,8 @@ enum TaskID { TASK_MAX }; -// supported cs's -enum GameFlags { - GAME_CSTRIKE16 = (1 << 0), // counter-strike 1.6 and above - GAME_XASH_ENGINE = (1 << 1), // counter-strike 1.6 under the xash engine (additional flag) - GAME_CZERO = (1 << 2), // counter-strike: condition zero - GAME_LEGACY = (1 << 3), // counter-strike 1.3-1.5 with/without steam - GAME_MOBILITY = (1 << 4), // additional flag that bot is running on android (additional flag) - GAME_OFFICIAL_CSBOT = (1 << 5), // additional flag that indicates official cs bots are in game - GAME_METAMOD = (1 << 6), // game running under meta\mod - GAME_CSDM = (1 << 7), // csdm mod currently in use - GAME_CSDM_FFA = (1 << 8), // csdm mod with ffa mode - GAME_REGAMEDLL = (1 << 9), // server dll is a regamedll - GAME_SUPPORT_SVC_PINGS = (1 << 10), // on that game version we can fake bots pings - GAME_SUPPORT_BOT_VOICE = (1 << 11) // on that game version we can use chatter -}; - // bot menu ids -enum MenuId { +enum MenuId : int { BOT_MENU_INVALID = 0, BOT_MENU_MAIN, BOT_MENU_FEATURES, @@ -90,17 +77,23 @@ enum MenuId { BOT_MENU_TOTAL_MENUS }; +// bomb say string +enum BombSayStr : int { + BSS_NEED_TO_FIND_CHAT = cr::bit (1), + BSS_NEED_TO_FIND_CHATTER = cr::bit (2) +}; + // log levels -enum LogLevel { - LL_DEFAULT = 1, // default log message - LL_WARNING = 2, // warning log message - LL_ERROR = 3, // error log message - LL_IGNORE = 4, // additional flag - LL_FATAL = 5 // fatal error log message (terminate the game!) +enum LogLevel : int { + LL_DEFAULT = cr::bit (0), // default log message + LL_WARNING = cr::bit (1), // warning log message + LL_ERROR = cr::bit (2), // error log message + LL_IGNORE = cr::bit (3), // additional flag + LL_FATAL = cr::bit (4) // fatal error log message (terminate the game!) }; // chat types id's -enum ChatType { +enum ChatType : int { CHAT_KILLING = 0, // id to kill chat array CHAT_DEAD, // id to dead chat array CHAT_BOMBPLANT, // id to bomb chat array @@ -112,7 +105,7 @@ enum ChatType { }; // personalities defines -enum Personality { +enum Personality : int { PERSONALITY_NORMAL = 0, PERSONALITY_RUSHER, PERSONALITY_CAREFUL @@ -128,7 +121,7 @@ enum Difficulty : int { }; // collision states -enum CollisionState { +enum CollisionState : int { COLLISION_NOTDECICED, COLLISION_PROBING, COLLISION_NOMOVE, @@ -139,7 +132,7 @@ enum CollisionState { }; // counter-strike team id's -enum Team { +enum Team : int { TEAM_TERRORIST = 0, TEAM_COUNTER, TEAM_SPECTATOR, @@ -147,15 +140,15 @@ enum Team { }; // client flags -enum ClientFlags { - CF_USED = (1 << 0), - CF_ALIVE = (1 << 1), - CF_ADMIN = (1 << 2), - CF_ICON = (1 << 3) +enum ClientFlags : int { + CF_USED = cr::bit (0), + CF_ALIVE = cr::bit (1), + CF_ADMIN = cr::bit (2), + CF_ICON = cr::bit (3) }; // bot create status -enum BotCreationResult { +enum BotCreationResult : int { BOT_RESULT_CREATED, BOT_RESULT_MAX_PLAYERS_REACHED, BOT_RESULT_NAV_ERROR, @@ -163,7 +156,7 @@ enum BotCreationResult { }; // radio messages -enum RadioMessage { +enum RadioMessage : int { RADIO_COVER_ME = 1, RADIO_YOU_TAKE_THE_POINT = 2, RADIO_HOLD_THIS_POSITION = 3, @@ -188,7 +181,7 @@ enum RadioMessage { }; // chatter system (extending enum above, messages 30-39 is reserved) -enum ChatterMessage { +enum ChatterMessage : int { CHATTER_SPOT_THE_BOMBER = 40, CHATTER_FRIENDLY_FIRE, CHATTER_PAIN_DIED, @@ -244,7 +237,7 @@ enum ChatterMessage { }; // counter-strike weapon id's -enum Weapon { +enum Weapon : int { WEAPON_P228 = 1, WEAPON_SHIELD = 2, WEAPON_SCOUT = 3, @@ -281,7 +274,7 @@ enum Weapon { }; // buy counts -enum BuyState { +enum BuyState : int { BUYSTATE_PRIMARY_WEAPON = 0, BUYSTATE_ARMOR_VESTHELM, BUYSTATE_SECONDARY_WEAPON, @@ -293,7 +286,7 @@ enum BuyState { }; // economics limits -enum EconomyLimit { +enum EconomyLimit : int { ECO_PRIMARY_GT = 0, ECO_SMG_GT_CT, ECO_SMG_GT_TE, @@ -308,7 +301,7 @@ enum EconomyLimit { }; // defines for pickup items -enum PickupType { +enum PickupType : int { PICKUP_NONE, PICKUP_WEAPON, PICKUP_DROPPED_C4, @@ -320,42 +313,42 @@ enum PickupType { }; // fight style type -enum FightStyle { +enum FightStyle : int { FIGHT_NONE, FIGHT_STRAFE, FIGHT_STAY }; // dodge type -enum StrafeDir { +enum StrafeDir : int { STRAFE_DIR_NONE, STRAFE_DIR_LEFT, STRAFE_DIR_RIGHT }; // reload state -enum ReloadState { +enum ReloadState : int { RELOAD_NONE = 0, // no reload state currently RELOAD_PRIMARY = 1, // primary weapon reload state RELOAD_SECONDARY = 2 // secondary weapon reload state }; // collision probes -enum CollisionProbe { - PROBE_JUMP = (1 << 0), // probe jump when colliding - PROBE_DUCK = (1 << 1), // probe duck when colliding - PROBE_STRAFE = (1 << 2) // probe strafing when colliding +enum CollisionProbe : int { + PROBE_JUMP = cr::bit (0), // probe jump when colliding + PROBE_DUCK = cr::bit (1), // probe duck when colliding + PROBE_STRAFE = cr::bit (2) // probe strafing when colliding }; // vgui menus (since latest steam updates is obsolete, but left for old cs) -enum VGuiMenu { +enum VGuiMenu : int { VMS_TEAM = 2, // menu select team VMS_TF = 26, // terrorist select menu VMS_CT = 27 // ct select menu }; // lift usage states -enum LiftState { +enum LiftState : int { LIFT_NO_NEARBY = 0, LIFT_LOOKING_BUTTON_OUTSIDE, LIFT_WAITING_FOR, @@ -367,7 +360,7 @@ enum LiftState { }; // wayponit auto-downloader -enum WaypointDownloadError { +enum WaypointDownloadError : int { WDE_SOCKET_ERROR, WDE_CONNECT_ERROR, WDE_NOTFOUND_ERROR, @@ -375,7 +368,7 @@ enum WaypointDownloadError { }; // game start messages for counter-strike... -enum GameMessage { +enum GameMessage : int { GAME_MSG_NONE = 1, GAME_MSG_TEAM_SELECT = 2, GAME_MSG_CLASS_SELECT = 3, @@ -386,107 +379,107 @@ enum GameMessage { }; // sensing states -enum SensingState { - STATE_SEEING_ENEMY = (1 << 0), // seeing an enemy - STATE_HEARING_ENEMY = (1 << 1), // hearing an enemy - STATE_SUSPECT_ENEMY = (1 << 2), // suspect enemy behind obstacle - STATE_PICKUP_ITEM = (1 << 3), // pickup item nearby - STATE_THROW_HE = (1 << 4), // could throw he grenade - STATE_THROW_FB = (1 << 5), // could throw flashbang - STATE_THROW_SG = (1 << 6) // could throw smokegrenade +enum SensingState : int { + STATE_SEEING_ENEMY = cr::bit (0), // seeing an enemy + STATE_HEARING_ENEMY = cr::bit (1), // hearing an enemy + STATE_SUSPECT_ENEMY = cr::bit (2), // suspect enemy behind obstacle + STATE_PICKUP_ITEM = cr::bit (3), // pickup item nearby + STATE_THROW_HE = cr::bit (4), // could throw he grenade + STATE_THROW_FB = cr::bit (5), // could throw flashbang + STATE_THROW_SG = cr::bit (6) // could throw smokegrenade }; // positions to aim at -enum AimPosition { - AIM_NAVPOINT = (1 << 0), // aim at nav point - AIM_CAMP = (1 << 1), // aim at camp vector - AIM_PREDICT_PATH = (1 << 2), // aim at predicted path - AIM_LAST_ENEMY = (1 << 3), // aim at last enemy - AIM_ENTITY = (1 << 4), // aim at entity like buttons, hostages - AIM_ENEMY = (1 << 5), // aim at enemy - AIM_GRENADE = (1 << 6), // aim for grenade throw - AIM_OVERRIDE = (1 << 7) // overrides all others (blinded) +enum AimPosition : int { + AIM_NAVPOINT = cr::bit (0), // aim at nav point + AIM_CAMP = cr::bit (1), // aim at camp vector + AIM_PREDICT_PATH = cr::bit (2), // aim at predicted path + AIM_LAST_ENEMY = cr::bit (3), // aim at last enemy + AIM_ENTITY = cr::bit (4), // aim at entity like buttons, hostages + AIM_ENEMY = cr::bit (5), // aim at enemy + AIM_GRENADE = cr::bit (6), // aim for grenade throw + AIM_OVERRIDE = cr::bit (7) // overrides all others (blinded) }; // famas/glock burst mode status + m4a1/usp silencer -enum BurstMode { - BM_ON = 1, - BM_OFF = 2 +enum BurstMode : int { + BURST_ON = cr::bit (0), + BURST_OFF = cr::bit (1) }; // visibility flags -enum Visibility { - VISIBLE_HEAD = (1 << 1), - VISIBLE_BODY = (1 << 2), - VISIBLE_OTHER = (1 << 3) +enum Visibility : int { + VISIBLE_HEAD = cr::bit (1), + VISIBLE_BODY = cr::bit (2), + VISIBLE_OTHER = cr::bit (3) }; -// defines map type -enum MapFlags { - MAP_AS = (1 << 0), - MAP_CS = (1 << 1), - MAP_DE = (1 << 2), - MAP_ES = (1 << 3), - MAP_KA = (1 << 4), - MAP_FY = (1 << 5), - - // additional flags - MAP_HAS_DOORS = (1 << 6) +// command handler status +enum BotCommandStatus : int { + CMD_STATUS_HANDLED = 0, // command successfully handled + CMD_STATUS_LISTENSERV, // command is only avaialble on listen server + CMD_STATUS_BADFORMAT // wrong params }; // defines for waypoint flags field (32 bits are available) -enum WaypointFlag { - FLAG_LIFT = (1 << 1), // wait for lift to be down before approaching this waypoint - FLAG_CROUCH = (1 << 2), // must crouch to reach this waypoint - FLAG_CROSSING = (1 << 3), // a target waypoint - FLAG_GOAL = (1 << 4), // mission goal point (bomb, hostage etc.) - FLAG_LADDER = (1 << 5), // waypoint is on ladder - FLAG_RESCUE = (1 << 6), // waypoint is a hostage rescue point - FLAG_CAMP = (1 << 7), // waypoint is a camping point - FLAG_NOHOSTAGE = (1 << 8), // only use this waypoint if no hostage - FLAG_DOUBLEJUMP = (1 << 9), // bot help's another bot (requster) to get somewhere (using djump) - FLAG_SNIPER = (1 << 28), // it's a specific sniper point - FLAG_TF_ONLY = (1 << 29), // it's a specific terrorist point - FLAG_CF_ONLY = (1 << 30), // it's a specific ct point - FLAG_LOW_LIGHT = (1 << 31) // specifies that waypoints that have low amount of light (for flashlights) +enum WaypointFlag : int32 { + FLAG_LIFT = cr::bit (1), // wait for lift to be down before approaching this waypoint + FLAG_CROUCH = cr::bit (2), // must crouch to reach this waypoint + FLAG_CROSSING = cr::bit (3), // a target waypoint + FLAG_GOAL = cr::bit (4), // mission goal point (bomb, hostage etc.) + FLAG_LADDER = cr::bit (5), // waypoint is on ladder + FLAG_RESCUE = cr::bit (6), // waypoint is a hostage rescue point + FLAG_CAMP = cr::bit (7), // waypoint is a camping point + FLAG_NOHOSTAGE = cr::bit (8), // only use this waypoint if no hostage + FLAG_DOUBLEJUMP = cr::bit (9), // bot help's another bot (requster) to get somewhere (using djump) + FLAG_SNIPER = cr::bit (28), // it's a specific sniper point + FLAG_TF_ONLY = cr::bit (29), // it's a specific terrorist point + FLAG_CF_ONLY = cr::bit (30), // it's a specific ct point }; // defines for waypoint connection flags field (16 bits are available) -enum PathFlag { - PATHFLAG_JUMP = (1 << 0) // must jump for this connection +enum PathFlag : int { + PATHFLAG_JUMP = cr::bit (0) // must jump for this connection }; // enum pathfind search type -enum SearchPathType { +enum SearchPathType : int { SEARCH_PATH_FASTEST = 0, SEARCH_PATH_SAFEST_FASTER, SEARCH_PATH_SAFEST }; // defines waypoint connection types -enum PathConnection { +enum PathConnection : int { CONNECTION_OUTGOING = 0, CONNECTION_INCOMING, CONNECTION_BOTHWAYS }; // a* route state -enum RouteState { - ROUTE_OPEN, +enum RouteState : int { + ROUTE_OPEN = 0, ROUTE_CLOSED, ROUTE_NEW }; +// waypoint edit states +enum WaypointEditState : int { + WS_EDIT_ENABLED = cr::bit (1), + WS_EDIT_NOCLIP = cr::bit (2), + WS_EDIT_AUTO = cr::bit (3) +}; + // bot known file headers -const char FH_WAYPOINT[] = "PODWAY!"; -const char FH_EXPERIENCE[] = "PODEXP!"; -const char FH_VISTABLE[] = "PODVIS!"; -const char FH_MATRIX[] = "PODMAT!"; +constexpr char FH_WAYPOINT[8] = "PODWAY!"; +constexpr char FH_EXPERIENCE[8] = "SKYEXP!"; +constexpr char FH_VISTABLE[8] = "SKYVIS!"; +constexpr char FH_MATRIX[8] = "SKYPMX!"; constexpr int FV_WAYPOINT = 7; -constexpr int FV_EXPERIENCE = 4; -constexpr int FV_VISTABLE = 3; -constexpr int FV_MATRIX = 3; +constexpr int FV_EXPERIENCE = 5; +constexpr int FV_VISTABLE = 4; +constexpr int FV_MATRIX = 4; // some hardcoded desire defines used to override calculated ones constexpr float TASKPRI_NORMAL = 35.0f; @@ -514,7 +507,6 @@ constexpr float MAX_CHATTER_REPEAT = 99.0f; constexpr int MAX_PATH_INDEX = 8; constexpr int MAX_DAMAGE_VALUE = 2040; constexpr int MAX_GOAL_VALUE = 2040; -constexpr int MAX_KILL_HISTORY = 16; constexpr int MAX_WAYPOINTS = 2048; constexpr int MAX_ROUTE_LENGTH = MAX_WAYPOINTS / 2; constexpr int MAX_WEAPONS = 32; @@ -541,7 +533,7 @@ struct Route { }; // links keywords and replies together -struct KeywordFactory { +struct Keywords { StringArray keywords; StringArray replies; StringArray usedReplies; @@ -570,10 +562,22 @@ struct ChatterItem { float duration; }; -struct WeaponSelect { +// weapon properties structure +struct WeaponProp { + char classname[64]; + int ammo1; // ammo index for primary ammo + int ammo1Max; // max primary ammo + int slot; // HUD slot (0 based) + int pos; // slot position + int id; // weapon ID + int flags; // flags??? +}; + +// weapon info structure +struct WeaponInfo { int id; // the weapon id value - const char *weaponName; // name of the weapon when selecting it - const char *modelName; // model name to separate cs weapons + const char *name; // name of the weapon when selecting it + const char *model; // model name to separate cs weapons int price; // price when buying int minPrimaryAmmo; // minimum primary ammo int teamStandard; // used by team (number) (standard map) @@ -583,50 +587,31 @@ struct WeaponSelect { int newBuySelectT; // for counter-strike v1.6 int newBuySelectCT; // for counter-strike v1.6 int penetratePower; // penetrate power + int maxClip; // max ammo in clip bool primaryFireHold; // hold down primary fire button to use? }; -// struct for menus -struct MenuText { - MenuId id; // actual menu id - int slots; // together bits for valid keys - String text; // ptr to actual string -}; - // array of clients struct struct Client { - MenuId menu; // id to opened bot menu edict_t *ent; // pointer to actual edict Vector origin; // position in the world - Vector soundPos; // position sound was played - + Vector sound; // position sound was played int team; // bot team int team2; // real bot team in free for all mode (csdm) int flags; // client flags - + int radio; // radio orders + int menu; // identifier to openen menu float hearingDistance; // distance this sound is heared float timeSoundLasting; // time sound is played/heared - int iconFlags[MAX_ENGINE_PLAYERS]; // flag holding chatter icons float iconTimestamp[MAX_ENGINE_PLAYERS]; // timers for chatter icons }; // experience data hold in memory while playing struct Experience { - uint16 team0Damage; - uint16 team1Damage; - int16 team0DangerIndex; - int16 team1DangerIndex; - int16 team0Value; - int16 team1Value; -}; - -// experience data when saving/loading -struct ExperienceSave { - uint8 team0Damage; - uint8 team1Damage; - int8 team0Value; - int8 team1Value; + int damage[MAX_TEAM_COUNT]; + int index[MAX_TEAM_COUNT]; + int value[MAX_TEAM_COUNT]; }; // bot creation tab @@ -639,17 +624,6 @@ struct CreateQueue { String name; }; -// weapon properties structure -struct WeaponProperty { - char className[64]; - int ammo1; // ammo index for primary ammo - int ammo1Max; // max primary ammo - int slotID; // HUD slot (0 based) - int position; // slot position - int id; // weapon ID - int flags; // flags??? -}; - // define chatting collection structure struct ChatCollection { int chatProbability; @@ -670,10 +644,18 @@ struct WaypointHeader { }; // general experience & vistable header information structure -struct ExtensionHeader { +struct ExtHeader { char header[8]; int32 fileVersion; int32 pointNumber; + int32 compressed; + int32 uncompressed; +}; + +// floyd-warshall matrices +struct FloydMatrix { + int dist; + int index; }; // define general waypoint structure @@ -725,263 +707,268 @@ public: }; // main bot class -class Bot { +class Bot final { friend class BotManager; private: - unsigned int m_states; // sensing bitstates - - float m_moveSpeed; // current speed forward/backward - float m_strafeSpeed; // current speed sideways - float m_minSpeed; // minimum speed in normal mode - float m_oldCombatDesire; // holds old desire for filtering - - bool m_isLeader; // bot is leader of his team - bool m_checkTerrain; // check for terrain - bool m_moveToC4; // ct is moving to bomb - bool m_grenadeRequested; // bot requested change to grenade - - float m_prevTime; // time previously checked movement speed - float m_prevSpeed; // speed some frames before - Vector m_prevOrigin; // origin some frames before + uint32 m_states; // sensing bitstates + uint32 m_collideMoves[MAX_COLLIDE_MOVES]; // sorted array of movements + uint32 m_collisionProbeBits; // bits of possible collision moves + uint32 m_collStateIndex; // index into collide moves + uint32 m_aimFlags; // aiming conditions + uint32 m_currentTravelFlags; // connection flags like jumping int m_messageQueue[32]; // stack for messages - String m_tempStrings; // space for strings (say text...) - int m_radioSelect; // radio entry - float m_headedTime; - - edict_t *m_avoid; // avoid players on our way - float m_avoidTime; // time to avoid players around - - float m_itemCheckTime; // time next search for items needs to be done - PickupType m_pickupType; // type of entity which needs to be used/picked up - Vector m_breakableOrigin; // origin of breakable - - edict_t *m_pickupItem; // pointer to entity of item to use/pickup - edict_t *m_itemIgnore; // pointer to entity to ignore for pickup - edict_t *m_liftEntity; // pointer to lift entity - edict_t *m_breakableEntity; // pointer to breakable entity - - float m_timeDoorOpen; // time to next door open check - float m_lastChatTime; // time bot last chatted - float m_timeLogoSpray; // time bot last spray logo - float m_knifeAttackTime; // time to rush with knife (at the beginning of the round) - bool m_defendedBomb; // defend action issued - bool m_defendHostage; - - float m_askCheckTime; // time to ask team - float m_collideTime; // time last collision - float m_firstCollideTime; // time of first collision - float m_probeTime; // time of probing different moves - float m_lastCollTime; // time until next collision check - float m_jumpStateTimer; // timer for jumping collision check - - unsigned int m_collisionProbeBits; // bits of possible collision moves - unsigned int m_collideMoves[MAX_COLLIDE_MOVES]; // sorted array of movements - unsigned int m_collStateIndex; // index into collide moves - CollisionState m_collisionState; // collision State - - BinaryHeap m_routeQue; - Array m_routes; // pointer - PathWalk m_path; // pointer to current node from path - Path *m_currentPath; // pointer to the current path waypoint - - SearchPathType m_pathType; // which pathfinder to use - uint8 m_visibility; // visibility flags - - int m_currentWaypointIndex; // current waypoint index - int m_travelStartIndex; // travel start index to double jump action - int m_prevWptIndex[5]; // previous waypoint indices from waypoint find - int m_waypointFlags; // current waypoint flags - - int m_loosedBombWptIndex; // nearest to loosed bomb waypoint - int m_plantedBombWptIndex; // nearest to planted bomb waypoint - - uint16 m_currentTravelFlags; // connection flags like jumping - bool m_jumpFinished; // has bot finished jumping - Vector m_desiredVelocity; // desired velocity for jump waypoints - float m_navTimeset; // time waypoint chosen by Bot - - unsigned int m_aimFlags; // aiming conditions - Vector m_lookAt; // vector bot should look at - Vector m_throw; // origin of waypoint to throw grenades - - Vector m_enemyOrigin; // target origin chosen for shooting - Vector m_grenade; // calculated vector for grenades - Vector m_entity; // origin of entities like buttons etc. - Vector m_camp; // aiming vector when camping. - - float m_timeWaypointMove; // last time bot followed waypoints - bool m_wantsToFire; // bot needs consider firing - float m_shootAtDeadTime; // time to shoot at dying players - edict_t *m_avoidGrenade; // pointer to grenade entity to avoid - int m_needAvoidGrenade; // which direction to strafe away - - float m_followWaitTime; // wait to follow time - edict_t *m_targetEntity; // the entity that the bot is trying to reach - Array m_hostages; // pointer to used hostage entities - - bool m_moveToGoal; // bot currently moving to goal?? - bool m_isStuck; // bot is stuck - bool m_isReloading; // bot is reloading a gun - bool m_forceRadio; // should bot use radio anyway? - int m_oldButtons; // our old buttons int m_reloadState; // current reload state int m_voicePitch; // bot voice pitch int m_rechoiceGoalCount; // multiple failed goals? + int m_loosedBombWptIndex; // nearest to loosed bomb waypoint + int m_plantedBombWptIndex; // nearest to planted bomb waypoint + int m_currentWaypointIndex; // current waypoint index + int m_travelStartIndex; // travel start index to double jump action + int m_prevWptIndex[5]; // previous waypoint indices from waypoint find + int m_waypointFlags; // current waypoint flags + int m_needAvoidGrenade; // which direction to strafe away + int m_campDirection; // camp Facing direction + int m_campButtons; // buttons to press while camping + int m_tryOpenDoor; // attempt's to open the door + int m_liftState; // state of lift handling + int m_radioSelect; // radio entry + int m_pingOffset[2]; + int m_ping[3]; // bots pings in scoreboard - bool m_duckDefuse; // should or not bot duck to defuse bomb + float m_headedTime; + float m_prevTime; // time previously checked movement speed + float m_prevSpeed; // speed some frames before + float m_timeDoorOpen; // time to next door open check + float m_lastChatTime; // time bot last chatted + float m_timeLogoSpray; // time bot last spray logo + float m_knifeAttackTime; // time to rush with knife (at the beginning of the round) float m_duckDefuseCheckTime; // time to check for ducking for defuse - float m_frameInterval; // bot's frame interval float m_lastCommandTime; // time bot last thinked - float m_reloadCheckTime; // time to check reloading float m_zoomCheckTime; // time to check zoom again float m_shieldCheckTime; // time to check shiled drawing again float m_grenadeCheckTime; // time to check grenade usage float m_sniperStopTime; // bot switched to other weapon? float m_lastEquipTime; // last time we equipped in buyzone - - bool m_checkKnifeSwitch; // is time to check switch to knife action - bool m_checkWeaponSwitch; // is time to check weapon switch - bool m_isUsingGrenade; // bot currently using grenade?? - bool m_bombSearchOverridden; // use normal waypoint if applicable when near the bomb - - StrafeDir m_combatStrafeDir; // direction to strafe - FightStyle m_fightStyle; // combat style to use - float m_lastFightStyleCheck; // time checked style - float m_strafeSetTime; // time strafe direction was set - - float m_timeCamping; // time to camp - int m_campDirection; // camp Facing direction - float m_nextCampDirTime; // time next camp direction change - int m_campButtons; // buttons to press while camping - int m_tryOpenDoor; // attempt's to open the door - int m_liftState; // state of lift handling - float m_duckTime; // time to duck float m_jumpTime; // time last jump happened - float m_chatterTimes[CHATTER_MAX]; // voice command timers float m_soundUpdateTime; // time to update the sound float m_heardSoundTime; // last time noise is heard float m_buttonPushTime; // time to push the button float m_liftUsageTime; // time to use lift - - int m_pingOffset[2]; - int m_ping[3]; // bots pings in scoreboard - - Vector m_liftTravelPos; // lift travel position - Vector m_moveAngles; // bot move angles - Vector m_idealAngles; // angle wanted - + float m_askCheckTime; // time to ask team + float m_collideTime; // time last collision + float m_firstCollideTime; // time of first collision + float m_probeTime; // time of probing different moves + float m_lastCollTime; // time until next collision check + float m_jumpStateTimer; // timer for jumping collision check float m_lookYawVel; // look yaw velocity float m_lookPitchVel; // look pitch velocity float m_lookUpdateTime; // lookangles update time float m_aimErrorTime; // time to update error vector + float m_nextCampDirTime; // time next camp direction change + float m_lastFightStyleCheck; // time checked style + float m_strafeSetTime; // time strafe direction was set + float m_randomizeAnglesTime; // time last randomized location + float m_playerTargetTime; // time last targeting + float m_timeCamping; // time to camp + float m_timeWaypointMove; // last time bot followed waypoints + float m_shootAtDeadTime; // time to shoot at dying players + float m_followWaitTime; // wait to follow time + float m_chatterTimes[CHATTER_MAX]; // chatter command timers + float m_navTimeset; // time waypoint chosen by Bot + float m_moveSpeed; // current speed forward/backward + float m_strafeSpeed; // current speed sideways + float m_minSpeed; // minimum speed in normal mode + float m_oldCombatDesire; // holds old desire for filtering + float m_avoidTime; // time to avoid players around + float m_itemCheckTime; // time next search for items needs to be done + bool m_moveToGoal; // bot currently moving to goal?? + bool m_isStuck; // bot is stuck + bool m_isReloading; // bot is reloading a gun + bool m_forceRadio; // should bot use radio anyway? + bool m_defendedBomb; // defend action issued + bool m_defendHostage; // defend action issued + bool m_duckDefuse; // should or not bot duck to defuse bomb + bool m_checkKnifeSwitch; // is time to check switch to knife action + bool m_checkWeaponSwitch; // is time to check weapon switch + bool m_isUsingGrenade; // bot currently using grenade?? + bool m_bombSearchOverridden; // use normal waypoint if applicable when near the bomb + bool m_wantsToFire; // bot needs consider firing + bool m_jumpFinished; // has bot finished jumping + bool m_isLeader; // bot is leader of his team + bool m_checkTerrain; // check for terrain + bool m_moveToC4; // ct is moving to bomb + bool m_grenadeRequested; // bot requested change to grenade + + PickupType m_pickupType; // type of entity which needs to be used/picked up + PathWalk m_path; // pointer to current node from path + StrafeDir m_combatStrafeDir; // direction to strafe + FightStyle m_fightStyle; // combat style to use + CollisionState m_collisionState; // collision State + SearchPathType m_pathType; // which pathfinder to use + uint8 m_visibility; // visibility flags + + edict_t *m_pickupItem; // pointer to entity of item to use/pickup + edict_t *m_itemIgnore; // pointer to entity to ignore for pickup + edict_t *m_liftEntity; // pointer to lift entity + edict_t *m_breakableEntity; // pointer to breakable entity + 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 + Vector m_idealAngles; // angle wanted Vector m_randomizedIdealAngles; // angle wanted with noise Vector m_angularDeviation; // angular deviation from current to ideal angles Vector m_aimSpeed; // aim speed calculated Vector m_aimLastError; // last calculated aim error + Vector m_prevOrigin; // origin some frames before + Vector m_lookAt; // vector bot should look at + Vector m_throw; // origin of waypoint to throw grenades + Vector m_enemyOrigin; // target origin chosen for shooting + Vector m_grenade; // calculated vector for grenades + Vector m_entity; // origin of entities like buttons etc. + Vector m_camp; // aiming vector when camping. + Vector m_desiredVelocity; // desired velocity for jump waypoints + Vector m_breakableOrigin; // origin of breakable - float m_randomizeAnglesTime; // time last randomized location - float m_playerTargetTime; // time last targeting + Array m_hostages; // pointer to used hostage entities + Array m_routes; // pointer - void instantChatter (int type); - void ai (void); - void checkSpawnConditions (void); - void buyStuff (void); + BinaryHeap m_routeQue; + Path *m_currentPath; // pointer to the current path waypoint + String m_chatBuffer; // space for strings (say text...) + +private: + int pickBestWeapon (int *vec, int count, int moneySave); + int getBombPoint (void); + int getCoverPoint (float maxDistance); + int getDefendPoint (const Vector &origin); + int searchGoal (void); + int getGoalProcess (int tactic, IntArray *defensive, IntArray *offsensive); + int getMsgQueue (void); + int bestPrimaryCarried (void); + int bestSecondaryCarried (void); + int bestGrenadeCarried (void); + int bestWeaponCarried (void); + int changePointIndex (int index); + int getNearestPoint (void); + int numEnemiesNear (const Vector &origin, float radius); + int numFriendsNear (const Vector &origin, float radius); + int searchCampDir (void); + int searchAimingPoint (const Vector &to); + + float getBombTimeleft (void); + float getReachTime (void); + float isInFOV (const Vector &dest); + float getShiftSpeed (void); + float getEnemyBodyOffsetCorrection (float distance); bool canReplaceWeapon (void); - int pickBestWeapon (int *vec, int count, int moneySave); - bool canDuckUnder (const Vector &normal); bool canJumpUp (const Vector &normal); bool doneCanJumpUp (const Vector &normal); bool cantMoveForward (const Vector &normal, TraceResult *tr); - -#ifdef DEAD_CODE bool canStrafeLeft (TraceResult *tr); bool canStrafeRight (TraceResult *tr); - bool isBlockedLeft (void); bool isBlockedRight (void); + bool checkWallOnLeft (void); + bool checkWallOnRight (void); + bool updateNavigation (void); + bool isEnemyThreat (void); + bool isWeaponRestricted (int weaponIndex); + bool isWeaponRestrictedAMX (int weaponIndex); + bool isInViewCone (const Vector &origin); + bool checkBodyParts (edict_t *target, Vector *origin, uint8 *bodyPart); + bool seesEnemy (edict_t *player, bool ignoreFOV = false); + bool doPlayerAvoidance (const Vector &normal); + bool hasActiveGoal (void); + bool advanceMovement (void); + bool isBombDefusing (const Vector &bombOrigin); + bool isOccupiedPoint (int index); + bool seesItem (const Vector &dest, const char *itemName); + bool lastEnemyShootable (void); + bool isShootableBreakable (edict_t *ent); + bool rateGroundWeapon (edict_t *ent); + bool reactOnEnemy (void); + bool getNextBestPoint (void); + bool hasAnyWeapons (void); + bool isDeadlyMove (const Vector &to); + bool isOutOfBombTimer (void); + bool isWeaponBadAtDistance (int weaponIndex, float distance); + bool needToPauseFiring (float distance); + bool lookupEnemies (void); + bool isEnemyHidden (edict_t *enemy); + bool isFriendInLineOfFire (float distance); + bool isGroupOfEnemies (const Vector &location, int numEnemies = 1, float radius = 256.0f); + bool isPenetrableObstacle (const Vector &dest); + bool isPenetrableObstacle2 (const Vector &dest); + bool isEnemyBehindShield (edict_t *enemy); + bool checkChatKeywords (String &reply); + bool isReplyingToChat (void); + void instantChatter (int type); + void runAI (void); + void runMovement (void); + void checkSpawnConditions (void); + void buyStuff (void); void changePitch (float speed); void changeYaw (float speed); -#endif - void checkMsgQueue (void); void checkRadioQueue (void); void checkReload (void); - int getMaxClip (int id); void avoidGrenades (void); void checkGrenadesThrow (void); void checkBurstMode (float distance); void checkSilencer (void); void updateAimDir (void); - bool checkWallOnLeft (void); - bool checkWallOnRight (void); - int getBombPoint (void); - - bool processNavigation (void); - bool isEnemyThreat (void); - void processLookAngles (void); - void processBodyAngles (void); - void updateLookAnglesNewbie (const Vector &direction, const float delta); + void updateLookAngles (void); + void updateBodyAngles (void); + void updateLookAnglesNewbie (const Vector &direction, float delta); void setIdealReactionTimers (bool actual = false); - bool isWeaponRestricted (int weaponIndex); - bool isWeaponRestrictedAMX (int weaponIndex); - - bool isInViewCone (const Vector &origin); - void processHearing (void); - bool checkBodyParts (edict_t *target, Vector *origin, uint8 *bodyPart); - bool seesEnemy (edict_t *player, bool ignoreFOV = false); - - edict_t *getNearestButton (const char *className); - edict_t *lookupBreakable (void); - int getCoverPoint (float maxDistance); - int getDefendPoint (const Vector &origin); - int searchGoal (void); - int getGoalProcess (int tactic, IntArray *defensive, IntArray *offsensive); - void filterGoals (const IntArray &goals, int *result); + void updateHearing (void); + void postprocessGoals (const IntArray &goals, int *result); void processPickups (void); void checkTerrain (float movedDistance, const Vector &dirNormal); void checkDarkness (void); void checkParachute (void); - bool doPlayerAvoidance (const Vector &normal); - - void getCampDir (Vector *dest); - void collectGoalExperience (int damage, int team); + void getCampDirection (Vector *dest); + void collectGoalExperience (int damage); void collectDataExperience (edict_t *attacker, int damage); - int getMsgQueue (void); - bool hasActiveGoal (void); - bool advanceMovement (void); - float isInFOV (const Vector &dest); - - bool isBombDefusing (const Vector &bombOrigin); - bool isOccupiedPoint (int index); - - inline bool isOnLadder (void) { - return pev->movetype == MOVETYPE_FLY; - } - - inline bool isOnFloor (void) { - return (pev->flags & (FL_ONGROUND | FL_PARTIALGROUND)) != 0; - } - - inline bool isInWater (void) { - return pev->waterlevel >= 2; - } - float getShiftSpeed (void); - - bool seesItem (const Vector &dest, const char *itemName); - bool lastEnemyShootable (void); + void searchShortestPath (int srcIndex, int destIndex); + void searchPath (int srcIndex, int destIndex, SearchPathType pathType = SEARCH_PATH_FASTEST); + void clearRoute (void); + void sayDebug (const char *format, ...); + void frame (void); + void resetCollision (void); + void ignoreCollision (void); + void setConditions (void); + void overrideConditions (void); + void updateEmotions (void); + void setStrafeSpeed (const Vector &moveDir, float strafeSpeed); + void updateTeamJoin (void); + void updateTeamCommands (void); + void decideFollowUser (void); + void attackMovement (void); + void getValidPoint (void); + void fireWeapons (void); + void selectWeapons (float distance, int index, int id, int choosen); + void focusEnemy (void); + void selectBestWeapon (void); + void selectSecondary (void); + void selectWeaponByName (const char *name); + void selectWeaponById (int num); + void completeTask (void); void processTasks (void); - // split big task list into separate functions void normal_ (void); void spraypaint_ (void); void huntEnemy_ (void); @@ -1003,120 +990,101 @@ private: void pickupItem_ (void); void shootBreakable_ (void); - bool isShootableBreakable (edict_t *ent); - bool rateGroundWeapon (edict_t *ent); - bool reactOnEnemy (void); - void resetCollision (void); - void ignoreCollision (void); - void setConditions (void); - void overrideConditions (void); - void updateEmotions (void); - void setStrafeSpeed (const Vector &moveDir, float strafeSpeed); - void processTeamJoin (void); - void completeTask (void); - bool getNextBestPoint (void); - - int bestPrimaryCarried (void); - int bestSecondaryCarried (void); - int bestGrenadeCarried (void); - int bestWeaponCarried (void); - bool hasAnyWeapons (void); - - void runMovement (void); - uint8 computeMsec (void); - void getValidPoint (void); - - int changePointIndex (int index); - int getNearestPoint (void); - bool isDeadlyMove (const Vector &to); - bool isOutOfBombTimer (void); - + edict_t *lookupButton (const char *targetName); + edict_t *lookupBreakable (void); edict_t *correctGrenadeVelocity (const char *model); - Vector calcThrow (const Vector &spot1, const Vector &spot2); - Vector calcToss (const Vector &spot1, const Vector &spot2); - Vector isBombAudible (void); const Vector &getEnemyBodyOffset (void); + Vector calcThrow (const Vector &start, const Vector &stop); + Vector calcToss (const Vector &start, const Vector &stop); + Vector isBombAudible (void); Vector getBodyOffsetError (float distance); - float getEnemyBodyOffsetCorrection (float distance); - void processTeamCommands (void); - void decideFollowUser (void); - void attackMovement (void); - bool isWeaponBadAtDistance (int weaponIndex, float distance); - bool throttleFiring (float distance); - bool lookupEnemies (void); - bool isEnemyHidden (edict_t *enemy); - void fireWeapons (void); - void selectWeapons (float distance, int index, int id, int choosen); - void focusEnemy (void); + uint8 computeMsec (void); - void selectBestWeapon (void); - void selectSecondary (void); - bool isFriendInLineOfFire (float distance); - bool isGroupOfEnemies (const Vector &location, int numEnemies = 1, float radius = 256.0f); +private: + bool isOnLadder (void) const { + return pev->movetype == MOVETYPE_FLY; + } - bool isPenetrableObstacle (const Vector &dest); - bool isPenetrableObstacle2 (const Vector &dest); + bool isOnFloor (void) const { + return (pev->flags & (FL_ONGROUND | FL_PARTIALGROUND)) != 0; + } - int numEnemiesNear (const Vector &origin, float radius); - int numFriendsNear (const Vector &origin, float radius); - - void selectWeaponByName (const char *name); - void selectWeaponById (int num); - - bool isEnemyBehindShield (edict_t *enemy); - bool processChatKeywords (char *reply); - bool isReplyingToChat (void); - float getBombTimeleft (void); - float getReachTime (void); - - int searchCampDir (void); - int searchAimingPoint (const Vector &to); - - void searchShortestPath (int srcIndex, int destIndex); - void searchPath (int srcIndex, int destIndex, SearchPathType pathType = SEARCH_PATH_FASTEST); - void clearRoute (void); - void sayDebug (const char *format, ...); - void frame (void); + bool isInWater (void) const { + return pev->waterlevel >= 2; + } public: entvars_t *pev; int m_wantedTeam; // player team bot wants select int m_wantedClass; // player model bot wants to select - - int m_difficulty; + int m_difficulty; // bots hard level int m_moneyAmount; // amount of money in bot's bank - Personality m_personality; float m_spawnTime; // time this bot spawned float m_timeTeamOrder; // time of last radio command - float m_timePeriodicUpdate; // time to per-second think + float m_slowFrameTimestamp; // time to per-second think float m_timeRepotingInDelay; // time to delay report-in - - bool m_isVIP; // bot is vip? + float m_nextBuyTime; // next buy time + float m_checkDarkTime; // check for darkness time + float m_preventFlashing; // bot turned away from flashbang + float m_flashLevel; // flashlight level + float m_blindTime; // time when bot is blinded + float m_blindMoveSpeed; // mad speeds when bot is blind + float m_blindSidemoveSpeed; // mad side move speeds when bot is blind + float m_fallDownTime; // time bot started to fall + float m_duckForJump; // is bot needed to duck for double jump + float m_baseAgressionLevel; // base aggression level (on initializing) + float m_baseFearLevel; // base fear level (on initializing) + float m_agressionLevel; // dynamic aggression level (in game) + float m_fearLevel; // dynamic fear level (in game) + float m_nextEmotionUpdate; // next time to sanitize emotions + float m_thinkFps; // skip some frames in bot thinking + float m_thinkInterval; // interval between frames + float m_goalValue; // ranking value for this waypoint + float m_viewDistance; // current view distance + float m_maxViewDistance; // maximum view distance + float m_retreatTime; // time to retreat? + float m_enemyUpdateTime; // time to check for new enemies + float m_enemyReachableTimer; // time to recheck if enemy reachable + float m_enemyIgnoreTimer; // ignore enemy for some time + float m_seeEnemyTime; // time bot sees enemy + float m_enemySurpriseTime; // time of surprise + float m_idealReactionTime; // time of base reaction + float m_actualReactionTime; // time of current reaction time + float m_timeNextTracking; // time waypoint index for tracking player is recalculated + float m_firePause; // time to pause firing + float m_shootTime; // time to shoot + float m_timeLastFired; // time to last firing int m_numEnemiesLeft; // number of enemies alive left on map int m_numFriendsLeft; // number of friend alive left on map - int m_retryJoin; // retry count for chosing team/class int m_startAction; // team/class selection state - - bool m_notKilled; // has the player been killed or has he just respawned - bool m_notStarted; // team/class not chosen yet - int m_voteKickIndex; // index of player to vote against int m_lastVoteKick; // last index int m_voteMap; // number of map to vote for int m_logotypeIndex; // index for logotype + int m_buyState; // current count in buying + int m_blindButton; // buttons bot press, when blind + int m_radioOrder; // actual command + int m_actMessageIndex; // current processed message + int m_pushMessageIndex; // offset for next pushed message + int m_prevGoalIndex; // holds destination goal waypoint + int m_chosenGoalIndex; // used for experience, same as above + int m_lastDamageType; // stores last damage + int m_team; // bot team + int m_currentWeapon; // one current weapon for each bot + int m_ammoInClip[MAX_WEAPONS]; // ammo in clip for each weapons + int m_ammo[MAX_AMMO_SLOTS]; // total ammo amounts + bool m_isVIP; // bot is vip? + bool m_notKilled; // has the player been killed or has he just respawned + bool m_notStarted; // team/class not chosen yet bool m_ignoreBuyDelay; // when reaching buyzone in the middle of the round don't do pauses bool m_inBombZone; // bot in the bomb zone or not - int m_buyState; // current count in buying - float m_nextBuyTime; // next buy time - float m_checkDarkTime; // check for darkness time - bool m_inBuyZone; // bot currently in buy zone bool m_inVIPZone; // bot in the vip satefy zone bool m_buyingFinished; // done with buying @@ -1128,149 +1096,62 @@ public: bool m_hasProgressBar; // has progress bar on a HUD bool m_jumpReady; // is double jump ready bool m_canChooseAimDirection; // can choose aiming direction - float m_turnAwayFromFlashbang; // bot turned away from flashbang - - float m_flashLevel; // flashlight level - float m_blindTime; // time when bot is blinded - float m_blindMoveSpeed; // mad speeds when bot is blind - float m_blindSidemoveSpeed; // mad side move speeds when bot is blind - int m_blindButton; // buttons bot press, when blind + bool m_isEnemyReachable; // direct line to enemy edict_t *m_doubleJumpEntity; // pointer to entity that request double jump edict_t *m_radioEntity; // pointer to entity issuing a radio command - int m_radioOrder; // actual command - - float m_fallDownTime; // time bot started to fall - float m_duckForJump; // is bot needed to duck for double jump - float m_baseAgressionLevel; // base aggression level (on initializing) - float m_baseFearLevel; // base fear level (on initializing) - float m_agressionLevel; // dynamic aggression level (in game) - float m_fearLevel; // dynamic fear level (in game) - float m_nextEmotionUpdate; // next time to sanitize emotions - float m_thinkFps; // skip some frames in bot thinking - float m_thinkInterval; // interval between frames - - int m_actMessageIndex; // current processed message - int m_pushMessageIndex; // offset for next pushed message - - int m_prevGoalIndex; // holds destination goal waypoint - int m_chosenGoalIndex; // used for experience, same as above - float m_goalValue; // ranking value for this waypoint + edict_t *m_enemy; // pointer to enemy entity + edict_t *m_lastEnemy; // pointer to last enemy entity + edict_t *m_lastVictim; // pointer to killed entity + edict_t *m_trackingEdict; // pointer to last tracked player when camping/hiding Vector m_waypointOrigin; // origin of waypoint Vector m_destOrigin; // origin of move destination Vector m_position; // position to move to in move to position task Vector m_doubleJumpOrigin; // origin of double jump - - float m_viewDistance; // current view distance - float m_maxViewDistance; // maximum view distance Vector m_lastEnemyOrigin; // vector to last enemy origin + ChatCollection m_sayTextBuffer; // holds the index & the actual message of the last unprocessed text message of a player BurstMode m_weaponBurstMode; // bot using burst mode? (famas/glock18, but also silencer mode) - - edict_t *m_enemy; // pointer to enemy entity - float m_retreatTime; // time to retreat? - float m_enemyUpdateTime; // time to check for new enemies - float m_enemyReachableTimer; // time to recheck if enemy reachable - float m_enemyIgnoreTimer; // ignore enemy for some time - bool m_isEnemyReachable; // direct line to enemy - - float m_seeEnemyTime; // time bot sees enemy - float m_enemySurpriseTime; // time of surprise - float m_idealReactionTime; // time of base reaction - float m_actualReactionTime; // time of current reaction time - - edict_t *m_lastEnemy; // pointer to last enemy entity - edict_t *m_lastVictim; // pointer to killed entity - edict_t *m_trackingEdict; // pointer to last tracked player when camping/hiding - float m_timeNextTracking; // time waypoint index for tracking player is recalculated - - float m_firePause; // time to pause firing - float m_shootTime; // time to shoot - float m_timeLastFired; // time to last firing - int m_lastDamageType; // stores last damage - - int m_currentWeapon; // one current weapon for each bot - int m_ammoInClip[MAX_WEAPONS]; // ammo in clip for each weapons - int m_ammo[MAX_AMMO_SLOTS]; // total ammo amounts - int m_team; // bot team + Personality m_personality; // bots type Array m_tasks; +public: Bot (edict_t *bot, int difficulty, int personality, int team, int member, const String &steamId); ~Bot (void); - int ammo (void); - inline int ammoClip (void) { - return m_ammoInClip[m_currentWeapon]; - } - - inline edict_t *ent (void) { - return pev->pContainingEntity; - }; - int index (void); - - inline Vector centerPos (void) { - return (pev->absmax + pev->absmin) * 0.5; - }; - inline Vector eyePos (void) { - return pev->origin + pev->view_ofs; - }; - - float calcThinkInterval (void); - - // the main function that decides intervals of running bot ai - void framePeriodic (void); - - /// the things that can be executed while skipping frames - void frameThink (void); - +public: + void slowFrame (void); // the main function that decides intervals of running bot ai + void fastFrame (void); /// 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 showDebugOverlay (void); void newRound (void); void processBuyzoneEntering (int buyState); void pushMsgQueue (int message); - void prepareChatMessage (char *text); - bool searchOptimalPoint (void); - bool seesEntity (const Vector &dest, bool fromBody = false); - + void prepareChatMessage (const String &message); + void checkForChat (void); void showChaterIcon (bool show); void clearSearchNodes (void); void processBreakables (edict_t *touch); void avoidIncomingPlayers (edict_t *touch); - - void startTask (TaskID id, float desire, int data, float time, bool canContinue); + void startTask (TaskID id, float desire, int data, float time, bool resume); void clearTask (TaskID id); void filterTasks (void); void clearTasks (void); - - Task *task (void); - - inline TaskID taskId (void) { - return task ()->id; - } - void dropWeaponForUser (edict_t *user, bool discardC4); - void clearUsedName (void); - void say (const char *text); void sayTeam (const char *text); - void pushChatMessage (int type, bool isTeamSay = false); void pushRadioMessage (int message); void pushChatterMessage (int message); - void processChatterMessage (const char *sz); + void processChatterMessage (const char *tempMessage); void tryHeadTowardRadioMessage (void); - void kill (void); void kick (void); - void resetDoubleJump (void); void startDoubleJump (edict_t *ent); - int locatePlantedC4 (void); - bool hasHostage (void); bool usesRifle (void); bool usesPistol (void); @@ -1283,29 +1164,71 @@ public: bool hasSecondaryWeapon (void); bool hasShield (void); bool isShieldDrawn (void); + bool searchOptimalPoint (void); + bool seesEntity (const Vector &dest, bool fromBody = false); + + int index (void); + int getAmmo (void); + int getNearestToPlantedBomb (void); + + float getFrameInterval (void); + Task *getTask (void); + +public: + inline int getAmmoInClip (void) const { + return m_ammoInClip[m_currentWeapon]; + } + + inline Vector getCenter (void) const { + return (pev->absmax + pev->absmin) * 0.5; + }; + + inline Vector getEyesPos (void) const { + return pev->origin + pev->view_ofs; + }; + + inline TaskID taskId (void) { + return getTask ()->id; + } + + inline edict_t *ent (void) { + return pev->pContainingEntity; + }; }; // manager class -class BotManager : public Singleton { +class BotManager final : public Singleton { private: - Array m_creationTab; // bot creation tab - - Bot *m_bots[MAX_ENGINE_PLAYERS]; // all available bots - + float m_timeRoundStart; + float m_timeRoundEnd; + float m_timeRoundMid; float m_maintainTime; // time to maintain bot creation float m_quotaMaintainTime; // time to maintain bot quota float m_grenadeUpdateTime; // time to update active grenades float m_entityUpdateTime; // time to update intresting entities + float m_plantSearchUpdateTime; // time to update for searching planted bomb + float m_lastChatTime; // global chat time timestamp + float m_timeBombPlanted; // time the bomb were planted + float m_lastRadioTime[MAX_TEAM_COUNT]; // global radio time int m_lastWinner; // the team who won previous round int m_lastDifficulty; // last bots difficulty + int m_bombSayStatus; // some bot is issued whine about bomb + int m_lastRadio[MAX_TEAM_COUNT]; bool m_leaderChoosen[MAX_TEAM_COUNT]; // is team leader choose theese round bool m_economicsGood[MAX_TEAM_COUNT]; // is team able to buy anything bool m_deathMsgSent; // for fake ping + bool m_bombPlanted; + bool m_botsCanPause; + bool m_roundEnded; Array m_activeGrenades; // holds currently active grenades on the map Array m_intrestingEntities; // holds currently intresting entities on the map + Array m_creationTab; // bot creation tab + Array m_filters; + + Bot *m_bots[MAX_ENGINE_PLAYERS]; // all available bots edict_t *m_killerEntity; // killer entity for bots protected: @@ -1315,101 +1238,183 @@ public: BotManager (void); ~BotManager (void); - inline bool checkTeamEco (int team) { - return m_economicsGood[team]; - } - - bool isTeamStacked (int team); - - inline int getLastWinner (void) const { - return m_lastWinner; - } - - inline void setLastWinner (int winner) { - m_lastWinner = winner; - } - void reset (void); - - int index (edict_t *ent); +public: Bot *getBot (int index); Bot *getBot (edict_t *ent); Bot *getAliveBot (void); Bot *getHighfragBot (int team); + int index (edict_t *ent); int getHumansCount (bool ignoreSpectators = false); int getAliveHumansCount (void); int getBotCount (void); + + void setBombPlanted (bool isPlanted); void countTeamPlayers (int &ts, int &cts); - - void framePeriodic (void); + void slowFrame (void); void frame (void); - void createKillerEntity (void); void destroyKillerEntity (void); void touchKillerEntity (Bot *bot); - void destroy (void); void destroy (int index); - - void createRandom (bool manual = false) { - addbot ("", -1, -1, -1, -1, manual); - } void addbot (const String &name, int difficulty, int personality, int team, int member, bool manual); 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 kickBotByMenu (edict_t *ent, int selection); void killAllBots (int team = -1); void maintainQuota (void); void initQuota (void); + void initRound (void); void decrementQuota (int by = 1); void selectLeaders (int team, bool reset); - void listBots (void); void setWeaponMode (int selection); void updateTeamEconomics (int team, bool setTrue = false); void updateBotDifficulties (void); - - static void execGameEntity (entvars_t *vars); - - // grenades + void reset (void); + void initFilters (void); + void resetFilters (void); void updateActiveGrenade (void); void updateIntrestingEntities (void); - - inline Array &searchActiveGrenades (void) { - return m_activeGrenades; - } - - inline Array &searchIntrestingEntities (void) { - return m_intrestingEntities; - } - - inline bool hasActiveGrenades (void) { - return !m_activeGrenades.empty (); - } - - inline bool hasIntrestingEntities (void) { - return !m_intrestingEntities.empty (); - } - -public: void calculatePingOffsets (void); void sendPingOffsets (edict_t *to); void sendDeathMsgFix (void); + void captureChatRadio (const char *cmd, const char *arg, edict_t *ent); + void notifyBombDefuse (void); + void execGameEntity (entvars_t *vars); - inline void updateDeathMsgState (bool sent) { + bool isTeamStacked (int team); + +public: + Array &searchActiveGrenades (void) { + return m_activeGrenades; + } + + Array &searchIntrestingEntities (void) { + return m_intrestingEntities; + } + + bool hasActiveGrenades (void) const { + return !m_activeGrenades.empty (); + } + + bool hasIntrestingEntities (void) const { + return !m_intrestingEntities.empty (); + } + + bool checkTeamEco (int team) const { + return m_economicsGood[team]; + } + + int getLastWinner (void) const { + return m_lastWinner; + } + + void setLastWinner (int winner) { + m_lastWinner = winner; + } + + // get the list of filters + Array &getFilters (void) { + return m_filters; + } + + void createRandom (bool manual = false) { + addbot ("", -1, -1, -1, -1, manual); + } + + void updateDeathMsgState (bool sent) { m_deathMsgSent = sent; } + + bool isBombPlanted (void) const { + return m_bombPlanted; + } + + float getTimeBombPlanted (void) const { + return m_timeBombPlanted; + } + + float getRoundStartTime (void) const { + return m_timeRoundStart; + } + + float getRoundMidTime (void) const { + return m_timeRoundMid; + } + + float getRoundEndTime (void) const { + return m_timeRoundEnd; + } + + bool isRoundOver (void) const { + return m_roundEnded; + } + + void setRoundOver (const bool over) { + m_roundEnded = over; + } + + bool canPause (void) const { + return m_botsCanPause; + } + + void setCanPause (const bool pause) { + m_botsCanPause = pause; + } + + bool hasBombSay (int type) { + return (m_bombSayStatus & type) == type; + } + + void clearBombSay (int type) { + m_bombSayStatus &= ~type; + } + + void setPlantedBombSearchTimestamp (const float timestamp) { + m_plantSearchUpdateTime = timestamp; + } + + float getPlantedBombSearchTimestamp (void) const { + return m_plantSearchUpdateTime; + } + + void setLastRadioTimestamp (const int team, const float timestamp) { + m_lastRadioTime[team] = timestamp; + } + + float getLastRadioTimestamp (const int team) const { + return m_lastRadioTime[team]; + } + + void setLastRadio (const int team, const int radio) { + m_lastRadio[team] = radio; + } + + int getLastRadio (const int team) const { + return m_lastRadio[team]; + } + + void setLastChatTimestamp (const float timestamp) { + m_lastChatTime = timestamp; + } + + float getLastChatTimestamp (void) const { + return m_lastChatTime; + } + + // some bots are online ? + bool hasBotsOnline (void) { + return getBotCount () > 0; + } }; // waypoint operation class -class Waypoint : public Singleton { - +class Waypoint final : public Singleton { public: friend class Bot; @@ -1418,37 +1423,34 @@ private: int x, y, z; }; - Path *m_paths[MAX_WAYPOINTS]; + int m_editFlags; + int m_numWaypoints; + int m_loadTries; + int m_cacheWaypointIndex; + int m_lastJumpWaypoint; + int m_visibilityIndex; + int m_findWPIndex; + int m_facingAtIndex; + int m_highestDamage[MAX_TEAM_COUNT]; + + float m_timeJumpStarted; + float m_autoPathDistance; + float m_pathDisplayTime; + float m_arrowDisplayTime; + float m_waypointDisplayTime[MAX_WAYPOINTS]; + float m_waypointLightLevel[MAX_WAYPOINTS]; bool m_waypointPaths; bool m_isOnLadder; bool m_endJumpPoint; bool m_learnJumpWaypoint; bool m_waypointsChanged; - float m_timeJumpStarted; + bool m_needsVisRebuild; Vector m_learnVelocity; Vector m_learnPosition; Vector m_bombPos; - - int m_numWaypoints; - int m_loadTries; - int m_cacheWaypointIndex; - int m_lastJumpWaypoint; - int m_visibilityIndex; Vector m_lastWaypoint; - uint8 m_visLUT[MAX_WAYPOINTS][MAX_WAYPOINTS / 4]; - - float m_pathDisplayTime; - float m_arrowDisplayTime; - float m_waypointDisplayTime[MAX_WAYPOINTS]; - float m_waypointLightLevel[MAX_WAYPOINTS]; - int m_findWPIndex; - int m_facingAtIndex; - char m_infoBuffer[MAX_PRINT_BUFFER]; - - int *m_distMatrix; - int *m_pathMatrix; IntArray m_terrorPoints; IntArray m_ctPoints; @@ -1457,125 +1459,542 @@ private: IntArray m_sniperPoints; IntArray m_rescuePoints; IntArray m_visitedGoals; - IntArray m_buckets[MAX_WAYPOINT_BUCKET_MAX][MAX_WAYPOINT_BUCKET_MAX][MAX_WAYPOINT_BUCKET_MAX]; + String m_tempInfo; + + FloydMatrix *m_matrix; + Experience *m_experience; + edict_t *m_editor; + + Path *m_paths[MAX_WAYPOINTS]; + uint8 m_visLUT[MAX_WAYPOINTS][MAX_WAYPOINTS / 4]; public: - bool m_redoneVisibility; - Waypoint (void); ~Waypoint (void); - void init (void); - void initExperience (void); - void initVisibility (void); - void initTypes (void); - void initLightLevels (void); - - void addPath (int addIndex, int pathIndex, float distance); +public: int getFacingIndex (void); int getFarest (const Vector &origin, float maxDistance = 32.0); int getNearest (const Vector &origin, float minDistance = 9999.0f, int flags = -1); int getNearestNoBuckets (const Vector &origin, float minDistance = 9999.0f, int flags = -1); int getEditorNeareset (void); - IntArray searchRadius (float radius, const Vector &origin, int maxCount = -1); - - void push (int flags, const Vector &waypointOrigin = Vector::null ()); - void erase (int target); - void toggleFlags (int toggleFlag); - void setRadius (int radius); - bool isConnected (int pointA, int pointB); - bool isConnected (int num); - void rebuildVisibility (void); - void pathCreate (char dir); - void erasePath (void); - void cachePoint (void); + int getDangerIndex (int team, int start, int goal); + int getDangerValue (int team, int start, int goal); + int getDangerDamage (int team, int start, int goal); + int getPathDist (int srcIndex, int destIndex); + int clearConnections (int index); float calculateTravelTime (float maxSpeed, const Vector &src, const Vector &origin); + + bool load (void); bool isVisible (int srcIndex, int destIndex); bool isStandVisible (int srcIndex, int destIndex); bool isDuckVisible (int srcIndex, int destIndex); - void calculatePathRadius (int index); - - bool load (void); - void save (void); - void cleanupPathMemory (void); - int removeUselessConnections (int index, bool outputToConsole = true); - + bool isConnected (int pointA, int pointB); + bool isConnected (int index); bool isReachable (Bot *bot, int index); bool isNodeReacheable (const Vector &src, const Vector &destination); - void frame (void); bool checkNodes (void); + bool loadPathMatrix (void); + bool saveExtFile (const char *ext, const char *type, const char *magic, int version, uint8 *data, int32 size); + bool loadExtFile (const char *ext, const char *type, const char *magic, int version, uint8 *data); + bool isVisited (int index); + + void save (void); + void init (void); + void frame (void); + void loadExperience (void); + void loadVisibility (void); + void initTypes (void); + void initLightLevels (void); + void addPath (int addIndex, int pathIndex, float distance); + void push (int flags, const Vector &waypointOrigin = Vector::null ()); + void erase (int target); + void toggleFlags (int toggleFlag); + void setRadius (int index, float radius); + void rebuildVisibility (void); + void pathCreate (char dir); + void erasePath (void); + void cachePoint (int index); + void calculatePathRadius (int index); + void cleanupPathMemory (void); void saveExperience (void); void saveVisibility (void); void addBasic (void); void eraseFromDisk (void); - void initPathMatrix (void); void savePathMatrix (void); - bool loadPathMatrix (void); - - int getPathDist (int srcIndex, int destIndex); - const char *getInformation (int id); - - char *getAuthor (void) { - return m_infoBuffer; - } - bool hasChanged (void) { - return m_waypointsChanged; - } - void setSearchIndex (int index); void startLearnJump (void); - - bool isVisited (int index); void setVisited (int index); void clearVisited (void); + void initBuckets (void); + void addToBucket (const Vector &pos, int index); + void eraseFromBucket (const Vector &pos, int index); + void setBombPos (bool reset = false, const Vector &pos = Vector::null ()); + void closeSocket (int sock); + void updateGlobalExperience (void); const char *getDataDirectory (bool isMemoryFile = false); const char *getWaypointFilename (bool isMemoryFile = false); - void setBombPos (bool reset = false, const Vector &pos = Vector::null ()); + WaypointDownloadError downloadWaypoint (void); + Bucket locateBucket (const Vector &pos); - inline const Vector &getBombPos (void) { + IntArray searchRadius (float radius, const Vector &origin, int maxCount = -1); + IntArray &getWaypointsInBucket (const Vector &pos); + +public: + Experience *getRawExperience (void) { + return m_experience; + } + + int getHighestDamageForTeam (int team) const { + return m_highestDamage[team]; + } + + void setHighestDamageForTeam (int team, int value) { + m_highestDamage[team] = value; + } + + const char *getAuthor (void) const { + return m_tempInfo.chars (); + } + + bool hasChanged (void) const { + return m_waypointsChanged; + } + + bool hasEditFlag (int flag) const { + return !!(m_editFlags & flag); + } + + void setEditFlag (int flag) { + m_editFlags |= flag; + } + + void clearEditFlag (int flag) { + m_editFlags &= ~flag; + } + + void setAutoPathDistance (const float distance) { + m_autoPathDistance = distance; + } + + const Vector &getBombPos (void) const { return m_bombPos; } // access paths - inline Path &operator [] (int index) { + Path &operator [] (int index) { return *m_paths[index]; } // check waypoints range - inline bool exists (int index) const { + bool exists (int index) const { return index >= 0 && index < m_numWaypoints; } // get real waypoint num - inline int length (void) const { + int length (void) const { return m_numWaypoints; } // get the light level of waypoint - inline float getLightLevel (int id) const { + float getLightLevel (int id) const { return m_waypointLightLevel[id]; } - // free's socket handle - void closeSocket (int sock); + // check if has editor + bool hasEditor (void) const { + return !!m_editor; + } - // do actually downloading of waypoint file - WaypointDownloadError downloadWaypoint (void); + // set's the waypoint editor + void setEditor (edict_t *ent) { + m_editor = ent; + } - // initalize waypoint buckets - void initBuckets (void); + // get the current waypoint editor + edict_t *getEditor (void) { + return m_editor; + } +}; - void addToBucket (const Vector &pos, int index); - void eraseFromBucket (const Vector &pos, int index); +// mostly config stuff, and some stuff dealing with menus +class Config final : public Singleton { +private: + Array m_chat; + Array > m_chatter; + Array m_botNames; + Array m_replies; + Array m_weapons; - Bucket locateBucket (const Vector &pos); - IntArray &getWaypointsInBucket (const Vector &pos); + // weapon info gathered through engine messages + WeaponProp m_weaponProps[MAX_WEAPONS + 1]; + + // default tables for personality weapon preferences, overridden by general.cfg + int m_normalWeaponPrefs[NUM_WEAPONS] = { 0, 2, 1, 4, 5, 6, 3, 12, 10, 24, 25, 13, 11, 8, 7, 22, 23, 18, 21, 17, 19, 15, 17, 9, 14, 16 }; + int m_rusherWeaponPrefs[NUM_WEAPONS] = { 0, 2, 1, 4, 5, 6, 3, 24, 19, 22, 23, 20, 21, 10, 12, 13, 7, 8, 11, 9, 18, 17, 19, 25, 15, 16 }; + int m_carefulWeaponPrefs[NUM_WEAPONS] = { 0, 2, 1, 4, 25, 6, 3, 7, 8, 12, 10, 13, 11, 9, 24, 18, 14, 17, 16, 15, 19, 20, 21, 22, 23, 5 }; + int m_grenadeBuyPrecent[NUM_WEAPONS - 23] = { 95, 85, 60 }; + int m_botBuyEconomyTable[NUM_WEAPONS - 15] = { 1900, 2100, 2100, 4000, 6000, 7000, 16000, 1200, 800, 1000, 3000 }; + int *m_weaponPrefs[3] = { m_normalWeaponPrefs, m_rusherWeaponPrefs, m_carefulWeaponPrefs }; + +public: + Config (void) = default; + ~Config (void) = default; + +public: + + // load the configuration files + void load (bool onlyMain); + + // picks random bot name + BotName *pickBotName (void); + + // remove bot name from used list + void clearUsedName (Bot *bot); + + // initialize weapon info + void initWeapons (void); + + // fix weapon prices (ie for elite) + void adjustWeaponPrices (void); + + WeaponInfo &findWeaponById (int id); + +public: + + // get the chat array + Array &getChat (void) { + return m_chat; + } + + // get's the chatter array + Array > &getChatter (void) { + return m_chatter; + } + + // get's the replies array + Array &getReplies (void) { + return m_replies; + } + + // get's the weapon info data + Array &getWeapons (void) { + return m_weapons; + } + + // get's raw weapon info + WeaponInfo *getRawWeapons (void) { + return m_weapons.begin (); + } + + // set's the weapon properties + void setWeaponProp (const WeaponProp &prop) { + m_weaponProps[prop.id] = prop; + } + + // get's the weapons prop + const WeaponProp &getWeaponProp (int id) const { + return m_weaponProps[id]; + } + + // get's weapon preferences for personality + int *getWeaponPrefs (int personality) const { + return m_weaponPrefs[personality]; + } + + // get economics value + int *getEconLimit (void) { + return m_botBuyEconomyTable; + } + + // get's grenade buy percents + bool chanceToBuyGrenade (const int grenadeType) const { + return RandomSequence::ref ().chance (m_grenadeBuyPrecent[grenadeType]); + } +}; + +class BotUtils final : public Singleton { +private: + bool m_needToSendWelcome; + float m_welcomeReceiveTime; + StringArray m_sentences; + Array m_clients; + Array > m_tags; + +public: + BotUtils (void); + ~BotUtils (void) = default; + +public: + // need to send welcome message ? + void checkWelcome (void); + + // gets the weapon alias as hlsting, maybe move to config... + int getWeaponAlias (bool needString, const char *weaponAlias, int weaponIndex = -1); + + // gets the build number of bot + int buildNumber (void); + + // gets the shooting cone deviation + float getShootingCone (edict_t *ent, const Vector &position); + + // check if origin is visible from the entity side + bool isVisible (const Vector &origin, edict_t *ent); + + // check if entity is alive + bool isAlive (edict_t *ent); + + // check if origin is inside view cone of entity + bool isInViewCone (const Vector &origin, edict_t *ent); + + // checks if entitiy is fakeclient + bool isFakeClient (edict_t *ent); + + // check if entitiy is a player + bool isPlayer (edict_t *ent); + + // check if entity is a vip + bool isPlayerVIP (edict_t *ent); + + // opens config helper + bool openConfig (const char *fileName, const char *errorIfNotExists, MemFile *outFile, bool languageDependant = false); + + // nearest player search helper + bool findNearestPlayer (void **holder, edict_t *to, float searchDistance = 4096.0, bool sameTeam = false, bool needBot = false, bool needAlive = false, bool needDrawn = false, bool needBotWithC4 = false); + + // simple loggerfor bots + void logEntry (bool outputToConsole, int logLevel, const char *format, ...); + + // tracing decals for bots spraying logos + void traceDecals (entvars_t *pev, TraceResult *trace, int logotypeIndex); + + // attaches sound to client struct + void attachSoundsToClients (edict_t *ent, const char *sample, float volume); + + // simulate sound for players + void simulateSoundUpdates (int playerIndex); + + // simple format utility + const char *format (const char *format, ...); + + // update stats on clients + void updateClients (void); + + // chat helper to strip the clantags out of the string + void stripTags (String &line); + + // chat helper to make player name more human-like + void humanizePlayerName (String &playerName); + + // chat helper to add errors to the bot chat string + void addChatErrors (String &line); + + // chat helper to find keywords for given string + bool checkKeywords (const String &line, String &reply); + +public: + + // re-show welcome after changelevel ? + void setNeedForWelcome (bool need) { + m_needToSendWelcome = need; + } + + // get array of clients + Array &getClients (void) { + return m_clients; + } + + // get clients as const-reference + const Array &getClients (void) const { + return m_clients; + } + + // get single client as ref + Client &getClient (const int index) { + return m_clients[index]; + } + + // checks if string is not empty + bool isEmptyStr (const char *input) const { + if (input == nullptr) { + return true; + } + return *input == '\0'; + } +}; + +// bot command manager +class BotControl final : public Singleton { +public: + using Handler = int (BotControl::*) (void); + using MenuHandler = int (BotControl::*) (int); + +public: + // generic bot command + struct BotCmd { + String name, format, help; + Handler handler; + }; + + // single bot menu + struct Menu { + int ident, slots; + String text; + MenuHandler handler; + }; + +private: + StringArray m_args; + Array m_cmds; + Array m_menus; + + edict_t *m_ent; + + bool m_isFromConsole; + bool m_isMenuFillCommand; + int m_menuServerFillTeam; + int m_interMenuData[4] = { 0, }; + +public: + BotControl (void); + ~BotControl (void) = default; + +private: + int cmdAddBot (void); + int cmdKickBot (void); + int cmdKickBots (void); + int cmdKillBots (void); + int cmdFill (void); + int cmdVote (void); + int cmdWeaponMode (void); + int cmdVersion (void); + int cmdWaypointMenu (void); + int cmdMenu (void); + int cmdList (void); + int cmdWaypoint (void); + int cmdWaypointOn (void); + int cmdWaypointOff (void); + int cmdWaypointAdd (void); + int cmdWaypointAddBasic (void); + int cmdWaypointSave (void); + int cmdWaypointLoad (void); + int cmdWaypointErase (void); + int cmdWaypointDelete (void); + int cmdWaypointCheck (void); + int cmdWaypointCache (void); + int cmdWaypointClean (void); + int cmdWaypointSetRadius (void); + int cmdWaypointSetFlags (void); + int cmdWaypointTeleport (void); + int cmdWaypointPathCreate (void); + int cmdWaypointPathDelete (void); + int cmdWaypointPathSetAutoDistance (void); + int cmdWaypointAcquireEditor (void); + int cmdWaypointReleaseEditor (void); + +private: + int menuMain (int item); + int menuFeatures (int item); + int menuControl (int item); + int menuWeaponMode (int item); + int menuPersonality (int item); + int menuDifficulty (int item); + int menuTeamSelect (int item); + int menuClassSelect (int item); + int menuCommands (int item); + int menuWaypointPage1 (int item); + int menuWaypointPage2 (int item); + int menuWaypointRadius (int item); + int menuWaypointType (int item); + int menuWaypointFlag (int item); + int menuWaypointPath (int item); + int menuAutoPathDistance (int item); + int menuKickPage1 (int item); + int menuKickPage2 (int item); + int menuKickPage3 (int item); + int menuKickPage4 (int item); + +private: + void enableDrawModels (bool enable); + void createMenus (void); + +public: + bool executeCommands (void); + bool executeMenus (void); + void showMenu (int id); + void kickBotByMenu (int page); + void msg (const char *fmt, ...); + void assignAdminRights (edict_t *ent, char *infobuffer); + void maintainAdminRights (void); + +public: + void setFromConsole (const bool console) { + m_isFromConsole = console; + } + + void setArgs (const StringArray &args) { + m_args.assign (args); + } + + void setIssuer (edict_t *ent) { + m_ent = ent; + } + + void fixMissingArgs (size_t num) { + if (num < m_args.length ()) { + return; + } + + do { + m_args.push (""); + } while (num--); + } + + int getInt (size_t arg) const { + if (!hasArg (arg)) { + return 0; + } + return m_args[arg].toInt32 (); + } + + const String &getStr (size_t arg) { + static String empty ("empty"); + + if (!hasArg (arg) || m_args[arg].empty ()) { + return empty; + } + return m_args[arg]; + } + + bool hasArg (size_t arg) const { + return arg < m_args.length (); + } + + StringArray collectArgs (void) { + StringArray args; + + for (int i = 0; i < engfuncs.pfnCmd_Argc (); i++) { + args.push (engfuncs.pfnCmd_Argv (i)); + } + return args; + } + +public: + + // for the server commands + static void handleEngineCommands (void); + + // for the client commands + bool handleClientCommands (edict_t *ent); + + // for the client menu commands + bool handleMenuCommands (edict_t *ent); }; #include @@ -1583,49 +2002,27 @@ public: // expose bot super-globals static auto &waypoints = Waypoint::ref (); static auto &bots = BotManager::ref (); -static auto &engine = Engine::ref (); static auto &rng = RandomSequence::ref (); +static auto &conf = Config::ref (); +static auto &util = BotUtils::ref (); +static auto &ctrl = BotControl::ref (); +static auto &game = Game::ref (); static auto &illum = LightMeasure::ref (); -// prototypes of bot functions... -extern int getWeaponData (bool isString, const char *weaponAlias, int weaponIndex = -1); -extern int getWeaponPenetrationPower (int id); -extern int buildNumber (void); -extern float getShootingConeDeviation (edict_t *ent, const Vector &position); - -extern bool isVisible (const Vector &origin, edict_t *ent); -extern bool isAlive (edict_t *ent); -extern bool isInViewCone (const Vector &origin, edict_t *ent); -extern bool isFakeClient (edict_t *ent); -extern bool isPlayer (edict_t *ent); -extern bool isPlayerVIP (edict_t *ent); -extern bool openConfig (const char *fileName, const char *errorIfNotExists, MemFile *outFile, bool languageDependant = false); -extern bool findNearestPlayer (void **holder, edict_t *to, float searchDistance = 4096.0, bool sameTeam = false, bool needBot = false, bool needAlive = false, bool needDrawn = false, bool needBotWithC4 = false); - -extern void cleanupGarbage (void); -extern void initRound (void); -extern void checkWelcome (void); -extern void logEntry (bool outputToConsole, int logLevel, const char *format, ...); -extern void showMenu (edict_t *ent, MenuId menu); -extern void traceDecals (entvars_t *pev, TraceResult *trace, int logotypeIndex); -extern void attachSoundsToClients (edict_t *ent, const char *sample, float volume); -extern void simulateSoundUpdates (int playerIndex); - -extern const char *format (const char *format, ...); - // very global convars extern ConVar yb_jasonmode; extern ConVar yb_communication_type; extern ConVar yb_ignore_enemies; - -#include -#include -#include +extern ConVar yb_chat; +extern ConVar yb_language; inline int Bot::index (void) { - return engine.indexOfEntity (ent ()); + return game.indexOfEntity (ent ()); } -static inline void makeVectors (const Vector &in) { - in.makeVectors (&g_pGlobals->v_forward, &g_pGlobals->v_right, &g_pGlobals->v_up); +inline int Game::getTeam (edict_t *ent) { + if (game.isNullEntity (ent)) { + return TEAM_UNASSIGNED; + } + return util.getClient (indexOfEntity (ent) - 1).team; } \ No newline at end of file diff --git a/project/yapb.rc b/project/yapb.rc index 3b25b68..31e07db 100644 --- a/project/yapb.rc +++ b/project/yapb.rc @@ -4,7 +4,7 @@ // // This software is licensed under the BSD-style license. // Additional exceptions apply. For full license details, see LICENSE.txt or visit: -// https://yapb.jeefo.net/license +// https://yapb.ru/license // #include diff --git a/project/yapb.vcxproj b/project/yapb.vcxproj index 2758a68..6c99c4a 100644 --- a/project/yapb.vcxproj +++ b/project/yapb.vcxproj @@ -22,7 +22,6 @@ - @@ -32,7 +31,7 @@ - + @@ -193,7 +192,7 @@ .\release\inf\ Level4 true - ProgramDatabase + None CompileAsCpp SingleFile true @@ -209,6 +208,8 @@ MultiThreaded true true + false + true NDEBUG;%(PreprocessorDefinitions) @@ -228,8 +229,8 @@ true false Windows - false - false + true + true false false @@ -241,8 +242,9 @@ false - UseLinkTimeCodeGeneration + UseFastLinkTimeCodeGeneration true + NotSet diff --git a/project/yapb.vcxproj.filters b/project/yapb.vcxproj.filters index 85fb1d0..13d1ad6 100644 --- a/project/yapb.vcxproj.filters +++ b/project/yapb.vcxproj.filters @@ -21,9 +21,6 @@ include - - include - include @@ -62,9 +59,6 @@ source - - source - source @@ -89,6 +83,9 @@ source + + source + diff --git a/source/Android.mk b/source/Android.mk index 6b65145..520953c 100644 --- a/source/Android.mk +++ b/source/Android.mk @@ -20,7 +20,7 @@ LOCAL_SRC_FILES := \ manager.cpp \ chatlib.cpp \ combat.cpp \ - globals.cpp \ + control.cpp \ engine.cpp \ interface.cpp \ navigate.cpp \ diff --git a/source/basecode.cpp b/source/basecode.cpp index 717695c..71c9756 100644 --- a/source/basecode.cpp +++ b/source/basecode.cpp @@ -33,6 +33,7 @@ ConVar yb_best_weapon_picker_type ("yb_best_weapon_picker_type", "1"); ConVar mp_c4timer ("mp_c4timer", nullptr, VT_NOREGISTER); ConVar mp_flashlight ("mp_flashlight", nullptr, VT_NOREGISTER); ConVar mp_buytime ("mp_buytime", nullptr, VT_NOREGISTER, true, "1"); +ConVar mp_startmoney ("mp_startmoney", nullptr, VT_NOREGISTER, true, "800"); ConVar mp_footsteps ("mp_footsteps", nullptr, VT_NOREGISTER); ConVar sv_gravity ("sv_gravity", nullptr, VT_NOREGISTER); @@ -53,15 +54,15 @@ void Bot::pushMsgQueue (int message) { // notify other bots of the spoken text otherwise, bots won't respond to other bots (network messages aren't sent from bots) int entityIndex = index (); - for (int i = 0; i < engine.maxClients (); i++) { - Bot *otherBot = bots.getBot (i); + for (int i = 0; i < game.maxClients (); i++) { + auto other = bots.getBot (i); - if (otherBot != nullptr && otherBot->pev != pev) { - if (m_notKilled == otherBot->m_notKilled) { - otherBot->m_sayTextBuffer.entityIndex = entityIndex; - otherBot->m_sayTextBuffer.sayText = m_tempStrings; + if (other != nullptr && other->pev != pev) { + if (m_notKilled == other->m_notKilled) { + other->m_sayTextBuffer.entityIndex = entityIndex; + other->m_sayTextBuffer.sayText = m_chatBuffer; } - otherBot->m_sayTextBuffer.timeNextChat = engine.timebase () + otherBot->m_sayTextBuffer.chatDelay; + other->m_sayTextBuffer.timeNextChat = game.timebase () + other->m_sayTextBuffer.chatDelay; } } } @@ -89,14 +90,14 @@ bool Bot::isInViewCone (const Vector &origin) { // the field of view cone of the bot entity, false otherwise. It is assumed that entities // have a human-like field of view, that is, about 90 degrees. - return ::isInViewCone (origin, ent ()); + return util.isInViewCone (origin, ent ()); } bool Bot::seesItem (const Vector &destination, const char *itemName) { TraceResult tr; // trace a line from bot's eyes to destination.. - engine.testLine (eyePos (), destination, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (getEyesPos (), destination, TRACE_IGNORE_MONSTERS, ent (), &tr); // check if line of sight to object is not blocked (i.e. visible) if (tr.flFraction != 1.0f) { @@ -109,7 +110,7 @@ bool Bot::seesEntity (const Vector &dest, bool fromBody) { TraceResult tr; // trace a line from bot's eyes to destination... - engine.testLine (fromBody ? pev->origin : eyePos (), dest, TRACE_IGNORE_EVERYTHING, ent (), &tr); + game.testLine (fromBody ? pev->origin : getEyesPos (), dest, TRACE_IGNORE_EVERYTHING, ent (), &tr); // check if line of sight to object is not blocked (i.e. visible) return tr.flFraction >= 1.0f; @@ -120,20 +121,20 @@ void Bot::checkGrenadesThrow (void) { // do not check cancel if we have grenade in out hands bool checkTasks = taskId () == TASK_PLANTBOMB || taskId () == TASK_DEFUSEBOMB; - auto clearThrowStates = [] (unsigned int &states) { + auto clearThrowStates = [] (uint32 &states) { states &= ~(STATE_THROW_HE | STATE_THROW_FB | STATE_THROW_SG); }; // check if throwing a grenade is a good thing to do... - if (checkTasks || yb_ignore_enemies.boolean () || m_isUsingGrenade || m_grenadeRequested || m_isReloading || yb_jasonmode.boolean () || m_grenadeCheckTime >= engine.timebase ()) { + if (checkTasks || yb_ignore_enemies.boolean () || m_isUsingGrenade || m_grenadeRequested || m_isReloading || yb_jasonmode.boolean () || m_grenadeCheckTime >= game.timebase ()) { clearThrowStates (m_states); return; } // check again in some seconds - m_grenadeCheckTime = engine.timebase () + 0.5f; + m_grenadeCheckTime = game.timebase () + 0.5f; - if (!isAlive (m_lastEnemy) || !(m_states & (STATE_SUSPECT_ENEMY | STATE_HEARING_ENEMY))) { + if (!util.isAlive (m_lastEnemy) || !(m_states & (STATE_SUSPECT_ENEMY | STATE_HEARING_ENEMY))) { clearThrowStates (m_states); return; } @@ -143,7 +144,7 @@ void Bot::checkGrenadesThrow (void) { // if we don't have grenades no need to check it this round again if (grenadeToThrow == -1) { - m_grenadeCheckTime = engine.timebase () + 15.0f; // changed since, conzero can drop grens from dead players + m_grenadeCheckTime = game.timebase () + 15.0f; // changed since, conzero can drop grens from dead players clearThrowStates (m_states); return; @@ -208,10 +209,10 @@ void Bot::checkGrenadesThrow (void) { m_throw = waypoints[predict].origin; - auto throwPos = calcThrow (eyePos (), m_throw); + auto throwPos = calcThrow (getEyesPos (), m_throw); if (throwPos.lengthSq () < 100.0f) { - throwPos = calcToss (eyePos (), m_throw); + throwPos = calcToss (getEyesPos (), m_throw); } if (throwPos.empty ()) { @@ -247,10 +248,10 @@ void Bot::checkGrenadesThrow (void) { } if (allowThrowing) { - auto throwPos = calcThrow (eyePos (), m_throw); + auto throwPos = calcThrow (getEyesPos (), m_throw); if (throwPos.lengthSq () < 100.0f) { - throwPos = calcToss (eyePos (), m_throw); + throwPos = calcToss (getEyesPos (), m_throw); } if (throwPos.empty ()) { @@ -271,8 +272,8 @@ void Bot::checkGrenadesThrow (void) { } case WEAPON_SMOKE: - if (allowThrowing && !engine.isNullEntity (m_lastEnemy)) { - if (getShootingConeDeviation (m_lastEnemy, pev->origin) >= 0.9f) { + if (allowThrowing && !game.isNullEntity (m_lastEnemy)) { + if (util.getShootingCone (m_lastEnemy, pev->origin) >= 0.9f) { allowThrowing = false; } } @@ -285,7 +286,7 @@ void Bot::checkGrenadesThrow (void) { } break; } - const float MaxThrowTime = engine.timebase () + 0.3f; + const float MaxThrowTime = game.timebase () + 0.3f; if (m_states & STATE_THROW_HE) { startTask (TASK_THROWHEGRENADE, TASKPRI_THROWGRENADE, INVALID_WAYPOINT_INDEX, MaxThrowTime, false); @@ -310,7 +311,7 @@ void Bot::avoidGrenades (void) { } // check if old pointers to grenade is invalid - if (engine.isNullEntity (m_avoidGrenade)) { + if (game.isNullEntity (m_avoidGrenade)) { m_avoidGrenade = nullptr; m_needAvoidGrenade = 0; } @@ -327,38 +328,38 @@ void Bot::avoidGrenades (void) { } // check if visible to the bot - if (!seesEntity (pent->v.origin) && isInFOV (pent->v.origin - eyePos ()) > pev->fov * 0.5f) { + if (!seesEntity (pent->v.origin) && isInFOV (pent->v.origin - getEyesPos ()) > pev->fov * 0.5f) { continue; } auto model = STRING (pent->v.model) + 9; - if (m_turnAwayFromFlashbang < engine.timebase () && m_personality == PERSONALITY_RUSHER && m_difficulty == 4 && strcmp (model, "flashbang.mdl") == 0) { + if (m_preventFlashing < game.timebase () && m_personality == PERSONALITY_RUSHER && m_difficulty == 4 && strcmp (model, "flashbang.mdl") == 0) { // don't look at flash bang if (!(m_states & STATE_SEEING_ENEMY)) { - pev->v_angle.y = cr::angleNorm ((engine.getAbsPos (pent) - eyePos ()).toAngles ().y + 180.0f); + pev->v_angle.y = cr::angleNorm ((game.getAbsPos (pent) - getEyesPos ()).toAngles ().y + 180.0f); m_canChooseAimDirection = false; - m_turnAwayFromFlashbang = engine.timebase () + rng.getFloat (1.0f, 2.0f); + m_preventFlashing = game.timebase () + rng.getFloat (1.0f, 2.0f); } } else if (strcmp (model, "hegrenade.mdl") == 0) { - if (!engine.isNullEntity (m_avoidGrenade)) { + if (!game.isNullEntity (m_avoidGrenade)) { return; } - if (engine.getTeam (pent->v.owner) == m_team || pent->v.owner == ent ()) { + if (game.getTeam (pent->v.owner) == m_team || pent->v.owner == ent ()) { return; } if (!(pent->v.flags & FL_ONGROUND)) { float distance = (pent->v.origin - pev->origin).length (); - float distanceMoved = ((pent->v.origin + pent->v.velocity * calcThinkInterval ()) - pev->origin).length (); + float distanceMoved = ((pent->v.origin + pent->v.velocity * getFrameInterval ()) - pev->origin).length (); if (distanceMoved < distance && distance < 500.0f) { - makeVectors (pev->v_angle); + game.makeVectors (pev->v_angle); const Vector &dirToPoint = (pev->origin - pent->v.origin).normalize2D (); - const Vector &rightSide = g_pGlobals->v_right.normalize2D (); + const Vector &rightSide = game.vec.right.normalize2D (); if ((dirToPoint | rightSide) > 0.0f) { m_needAvoidGrenade = -1; @@ -385,80 +386,6 @@ void Bot::avoidGrenades (void) { } } -int Bot::bestPrimaryCarried (void) { - // this function returns the best weapon of this bot (based on personality prefs) - - int *ptr = g_weaponPrefs[m_personality]; - int weaponIndex = 0; - int weapons = pev->weapons; - - WeaponSelect *weaponTab = &g_weaponSelect[0]; - - // take the shield in account - if (hasShield ()) { - weapons |= (1 << WEAPON_SHIELD); - } - - for (int i = 0; i < NUM_WEAPONS; i++) { - if (weapons & (1 << weaponTab[*ptr].id)) { - weaponIndex = i; - } - ptr++; - } - return weaponIndex; -} - -int Bot::bestSecondaryCarried (void) { - // this function returns the best secondary weapon of this bot (based on personality prefs) - - int *ptr = g_weaponPrefs[m_personality]; - int weaponIndex = 0; - int weapons = pev->weapons; - - // take the shield in account - if (hasShield ()) { - weapons |= (1 << WEAPON_SHIELD); - } - WeaponSelect *weaponTab = &g_weaponSelect[0]; - - for (int i = 0; i < NUM_WEAPONS; i++) { - int id = weaponTab[*ptr].id; - - if ((weapons & (1 << weaponTab[*ptr].id)) && (id == WEAPON_USP || id == WEAPON_GLOCK || id == WEAPON_DEAGLE || id == WEAPON_P228 || id == WEAPON_ELITE || id == WEAPON_FIVESEVEN)) { - weaponIndex = i; - break; - } - ptr++; - } - return weaponIndex; -} - -bool Bot::rateGroundWeapon (edict_t *ent) { - // this function compares weapons on the ground to the one the bot is using - - int hasWeapon = 0; - int groundIndex = 0; - int *ptr = g_weaponPrefs[m_personality]; - - WeaponSelect *weaponTab = &g_weaponSelect[0]; - - for (int i = 0; i < NUM_WEAPONS; i++) { - if (strcmp (weaponTab[*ptr].modelName, STRING (ent->v.model) + 9) == 0) { - groundIndex = i; - break; - } - ptr++; - } - - if (groundIndex < 7) { - hasWeapon = bestSecondaryCarried (); - } - else { - hasWeapon = bestPrimaryCarried (); - } - return groundIndex > hasWeapon; -} - void Bot::processBreakables (edict_t *touch) { if (!isShootableBreakable (touch)) { @@ -466,7 +393,7 @@ void Bot::processBreakables (edict_t *touch) { } m_breakableEntity = lookupBreakable (); - if (engine.isNullEntity (m_breakableEntity)) { + if (game.isNullEntity (m_breakableEntity)) { return; } m_campButtons = pev->button & IN_DUCK; @@ -478,24 +405,24 @@ edict_t *Bot::lookupBreakable (void) { // this function checks if bot is blocked by a shoot able breakable in his moving direction TraceResult tr; - engine.testLine (pev->origin, pev->origin + (m_destOrigin - pev->origin).normalize () * 72.0f, TRACE_IGNORE_NONE, ent (), &tr); + game.testLine (pev->origin, pev->origin + (m_destOrigin - pev->origin).normalize () * 72.0f, TRACE_IGNORE_NONE, ent (), &tr); if (tr.flFraction != 1.0f) { edict_t *ent = tr.pHit; // check if this isn't a triggered (bomb) breakable and if it takes damage. if true, shoot the crap! if (isShootableBreakable (ent)) { - m_breakableOrigin = engine.getAbsPos (ent); + m_breakableOrigin = game.getAbsPos (ent); return ent; } } - engine.testLine (eyePos (), eyePos () + (m_destOrigin - eyePos ()).normalize () * 72.0f, TRACE_IGNORE_NONE, ent (), &tr); + game.testLine (getEyesPos (), getEyesPos () + (m_destOrigin - getEyesPos ()).normalize () * 72.0f, TRACE_IGNORE_NONE, ent (), &tr); if (tr.flFraction != 1.0f) { edict_t *ent = tr.pHit; if (isShootableBreakable (ent)) { - m_breakableOrigin = engine.getAbsPos (ent); + m_breakableOrigin = game.getAbsPos (ent); return ent; } } @@ -537,15 +464,15 @@ void Bot::processPickups (void) { Bot *bot = nullptr; constexpr float radius = cr::square (320.0f); - if (!engine.isNullEntity (m_pickupItem)) { + if (!game.isNullEntity (m_pickupItem)) { bool itemExists = false; auto pickupItem = m_pickupItem; for (auto ent : intresting) { - if (isPlayer (ent->v.owner)) { + if (util.isPlayer (ent->v.owner)) { continue; // someone owns this weapon or it hasn't re spawned yet } - const Vector &origin = engine.getAbsPos (ent); + const Vector &origin = game.getAbsPos (ent); // too far from us ? if ((pev->origin - origin).lengthSq () > radius) { @@ -582,7 +509,7 @@ void Bot::processPickups (void) { if (ent == m_itemIgnore) { continue; // someone owns this weapon or it hasn't respawned yet } - const Vector &origin = engine.getAbsPos (ent); + const Vector &origin = game.getAbsPos (ent); // too far from us ? if ((pev->origin - origin).lengthSq () > radius) { @@ -622,19 +549,27 @@ void Bot::processPickups (void) { // if the bot found something it can pickup... if (allowPickup) { - if (pickupType == PICKUP_WEAPON) // found weapon on ground? - { - int weaponCarried = bestPrimaryCarried (); + + // found weapon on ground? + if (pickupType == PICKUP_WEAPON) { + int primaryWeaponCarried = bestPrimaryCarried (); int secondaryWeaponCarried = bestSecondaryCarried (); - if (secondaryWeaponCarried < 7 && (m_ammo[g_weaponSelect[secondaryWeaponCarried].id] > 0.3 * g_weaponDefs[g_weaponSelect[secondaryWeaponCarried].id].ammo1Max) && strcmp (model, "w_357ammobox.mdl") == 0) { + const auto &config = conf.getWeapons (); + const auto &primary = config[primaryWeaponCarried]; + const auto &secondary = config[secondaryWeaponCarried]; + const auto &primaryProp = conf.getWeaponProp (primary.id); + const auto &secondaryProp = conf.getWeaponProp (secondary.id); + + if (secondaryWeaponCarried < 7 && (m_ammo[secondary.id] > 0.3 * secondaryProp.ammo1Max) && strcmp (model, "w_357ammobox.mdl") == 0) { allowPickup = false; } - else if (!m_isVIP && weaponCarried >= 7 && (m_ammo[g_weaponSelect[weaponCarried].id] > 0.3 * g_weaponDefs[g_weaponSelect[weaponCarried].id].ammo1Max) && strncmp (model, "w_", 2) == 0) { - bool isSniperRifle = weaponCarried == WEAPON_AWP || weaponCarried == WEAPON_G3SG1 || weaponCarried == WEAPON_SG550; - bool isSubmachine = weaponCarried == WEAPON_MP5 || weaponCarried == WEAPON_TMP || weaponCarried == WEAPON_P90 || weaponCarried == WEAPON_MAC10 || weaponCarried == WEAPON_UMP45; - bool isShotgun = weaponCarried == WEAPON_M3; - bool isRifle = weaponCarried == WEAPON_FAMAS || weaponCarried == WEAPON_AK47 || weaponCarried == WEAPON_M4A1 || weaponCarried == WEAPON_GALIL || weaponCarried == WEAPON_AUG || weaponCarried == WEAPON_SG552; + else if (!m_isVIP && primaryWeaponCarried >= 7 && (m_ammo[primary.id] > 0.3 * primaryProp.ammo1Max) && strncmp (model, "w_", 2) == 0) { + + const bool isSniperRifle = primaryWeaponCarried == WEAPON_AWP || primaryWeaponCarried == WEAPON_G3SG1 || primaryWeaponCarried == WEAPON_SG550; + const bool isSubmachine = primaryWeaponCarried == WEAPON_MP5 || primaryWeaponCarried == WEAPON_TMP || primaryWeaponCarried == WEAPON_P90 || primaryWeaponCarried == WEAPON_MAC10 || primaryWeaponCarried == WEAPON_UMP45; + const bool isShotgun = primaryWeaponCarried == WEAPON_M3; + const bool isRifle = primaryWeaponCarried == WEAPON_FAMAS || primaryWeaponCarried == WEAPON_AK47 || primaryWeaponCarried == WEAPON_M4A1 || primaryWeaponCarried == WEAPON_GALIL || primaryWeaponCarried == WEAPON_AUG || primaryWeaponCarried == WEAPON_SG552; if (strcmp (model, "w_9mmarclip.mdl") == 0 && !isRifle) { allowPickup = false; @@ -648,7 +583,7 @@ void Bot::processPickups (void) { else if (strcmp (model, "w_crossbow_clip.mdl") == 0 && !isSniperRifle) { allowPickup = false; } - else if (strcmp (model, "w_chainammo.mdl") == 0 && weaponCarried != WEAPON_M249) { + else if (strcmp (model, "w_chainammo.mdl") == 0 && primaryWeaponCarried != WEAPON_M249) { allowPickup = false; } } @@ -690,11 +625,11 @@ void Bot::processPickups (void) { m_itemIgnore = ent; allowPickup = false; - if (!m_defendHostage && m_difficulty > 2 && rng.chance (30) && m_timeCamping + 15.0f < engine.timebase ()) { + if (!m_defendHostage && m_difficulty > 2 && rng.chance (30) && m_timeCamping + 15.0f < game.timebase ()) { int index = getDefendPoint (origin); - startTask (TASK_CAMP, TASKPRI_CAMP, INVALID_WAYPOINT_INDEX, engine.timebase () + rng.getFloat (30.0f, 60.0f), true); // push camp task on to stack - startTask (TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, index, engine.timebase () + rng.getFloat (3.0f, 6.0f), true); // push move command + startTask (TASK_CAMP, TASKPRI_CAMP, INVALID_WAYPOINT_INDEX, game.timebase () + rng.getFloat (30.0f, 60.0f), true); // push camp task on to stack + startTask (TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, index, game.timebase () + rng.getFloat (3.0f, 6.0f), true); // push move command if (waypoints[index].vis.crouch <= waypoints[index].vis.stand) { m_campButtons |= IN_DUCK; @@ -718,9 +653,9 @@ void Bot::processPickups (void) { Path &path = waypoints[index]; float bombTimer = mp_c4timer.flt (); - float timeMidBlowup = g_timeBombPlanted + (bombTimer * 0.5f + bombTimer * 0.25f) - waypoints.calculateTravelTime (pev->maxspeed, pev->origin, path.origin); + float timeMidBlowup = bots.getTimeBombPlanted () + (bombTimer * 0.5f + bombTimer * 0.25f) - waypoints.calculateTravelTime (pev->maxspeed, pev->origin, path.origin); - if (timeMidBlowup > engine.timebase ()) { + if (timeMidBlowup > game.timebase ()) { clearTask (TASK_MOVETOPOSITION); // remove any move tasks startTask (TASK_CAMP, TASKPRI_CAMP, INVALID_WAYPOINT_INDEX, timeMidBlowup, true); // push camp task on to stack @@ -744,11 +679,11 @@ void Bot::processPickups (void) { } else if (m_team == TEAM_COUNTER) { if (pickupType == PICKUP_HOSTAGE) { - if (engine.isNullEntity (ent) || ent->v.health <= 0) { + if (game.isNullEntity (ent) || ent->v.health <= 0) { allowPickup = false; // never pickup dead hostage } - else - for (int i = 0; i < engine.maxClients (); i++) { + else { + for (int i = 0; i < game.maxClients (); i++) { if ((bot = bots.getBot (i)) != nullptr && bot->m_notKilled) { for (auto hostage : bot->m_hostages) { if (hostage == ent) { @@ -758,14 +693,21 @@ void Bot::processPickups (void) { } } } + } } else if (pickupType == PICKUP_PLANTED_C4) { - if (isPlayer (m_enemy)) { + if (util.isPlayer (m_enemy)) { allowPickup = false; return; } if (isOutOfBombTimer ()) { + completeTask (); + + // then start escape from bomb immediate + startTask (TASK_ESCAPEFROMBOMB, TASKPRI_ESCAPEFROMBOMB, INVALID_WAYPOINT_INDEX, 0.0f, true); + + // and no pickup allowPickup = false; return; } @@ -783,7 +725,7 @@ void Bot::processPickups (void) { int index = getDefendPoint (origin); Path &path = waypoints[index]; - float timeToExplode = g_timeBombPlanted + mp_c4timer.flt () - waypoints.calculateTravelTime (pev->maxspeed, pev->origin, path.origin); + float timeToExplode = bots.getTimeBombPlanted () + mp_c4timer.flt () - waypoints.calculateTravelTime (pev->maxspeed, pev->origin, path.origin); clearTask (TASK_MOVETOPOSITION); // remove any move tasks @@ -809,8 +751,8 @@ void Bot::processPickups (void) { if (!m_defendedBomb && m_difficulty > 2 && rng.chance (75) && pev->health < 80) { int index = getDefendPoint (origin); - startTask (TASK_CAMP, TASKPRI_CAMP, INVALID_WAYPOINT_INDEX, engine.timebase () + rng.getFloat (30.0f, 70.0f), true); // push camp task on to stack - startTask (TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, index, engine.timebase () + rng.getFloat (10.0f, 30.0f), true); // push move command + startTask (TASK_CAMP, TASKPRI_CAMP, INVALID_WAYPOINT_INDEX, game.timebase () + rng.getFloat (30.0f, 70.0f), true); // push camp task on to stack + startTask (TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, index, game.timebase () + rng.getFloat (10.0f, 30.0f), true); // push move command if (waypoints[index].vis.crouch <= waypoints[index].vis.stand) { m_campButtons |= IN_DUCK; @@ -840,8 +782,8 @@ void Bot::processPickups (void) { } } // end of the while loop - if (!engine.isNullEntity (pickupItem)) { - for (int i = 0; i < engine.maxClients (); i++) { + if (!game.isNullEntity (pickupItem)) { + for (int i = 0; i < game.maxClients (); i++) { if ((bot = bots.getBot (i)) != nullptr && bot->m_notKilled && bot->m_pickupItem == pickupItem) { m_pickupItem = nullptr; m_pickupType = PICKUP_NONE; @@ -851,7 +793,7 @@ void Bot::processPickups (void) { } // check if item is too high to reach, check if getting the item would hurt bot - if (pickupPos.z > eyePos ().z + (m_pickupType == PICKUP_HOSTAGE ? 50.0f : 20.0f) || isDeadlyMove (pickupPos)) { + if (pickupPos.z > getEyesPos ().z + (m_pickupType == PICKUP_HOSTAGE ? 50.0f : 20.0f) || isDeadlyMove (pickupPos)) { m_itemIgnore = m_pickupItem; m_pickupItem = nullptr; m_pickupType = PICKUP_NONE; @@ -862,14 +804,14 @@ void Bot::processPickups (void) { } } -void Bot::getCampDir (Vector *dest) { +void Bot::getCampDirection (Vector *dest) { // this function check if view on last enemy position is blocked - replace with better vector then // mostly used for getting a good camping direction vector if not camping on a camp waypoint TraceResult tr; - const Vector &src = eyePos (); + const Vector &src = getEyesPos (); - engine.testLine (src, *dest, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (src, *dest, TRACE_IGNORE_MONSTERS, ent (), &tr); // check if the trace hit something... if (tr.flFraction < 1.0f) { @@ -890,15 +832,15 @@ void Bot::getCampDir (Vector *dest) { int lookAtWaypoint = INVALID_WAYPOINT_INDEX; Path &path = waypoints[tempIndex]; - for (int i = 0; i < MAX_PATH_INDEX; i++) { - if (path.index[i] == INVALID_WAYPOINT_INDEX) { + for (auto &index : path.index) { + if (index == INVALID_WAYPOINT_INDEX) { continue; } - float distance = static_cast (waypoints.getPathDist (path.index[i], enemyIndex)); + auto distance = static_cast (waypoints.getPathDist (index, enemyIndex)); if (distance < minDistance) { minDistance = distance; - lookAtWaypoint = path.index[i]; + lookAtWaypoint = index; } } @@ -911,26 +853,24 @@ void Bot::getCampDir (Vector *dest) { void Bot::showChaterIcon (bool show) { // this function depending on show boolen, shows/remove chatter, icon, on the head of bot. - if (!(g_gameFlags & GAME_SUPPORT_BOT_VOICE) || yb_communication_type.integer () != 2) { + if (!game.is (GAME_SUPPORT_BOT_VOICE) || yb_communication_type.integer () != 2) { return; } auto sendBotVoice = [](bool show, edict_t *ent, int ownId) { - MessageWriter (MSG_ONE, engine.getMessageId (NETMSG_BOTVOICE), Vector::null (), ent) // begin message + MessageWriter (MSG_ONE, game.getMessageId (NETMSG_BOTVOICE), Vector::null (), ent) // begin message .writeByte (show) // switch on/off .writeByte (ownId); }; int ownId = index (); - for (int i = 0; i < engine.maxClients (); i++) { - Client &client = g_clients[i]; - + for (auto &client : util.getClients ()) { if (!(client.flags & CF_USED) || (client.ent->v.flags & FL_FAKECLIENT) || client.team != m_team) { continue; } - if (!show && (client.iconFlags[ownId] & CF_ICON) && client.iconTimestamp[ownId] < engine.timebase ()) { + if (!show && (client.iconFlags[ownId] & CF_ICON) && client.iconTimestamp[ownId] < game.timebase ()) { sendBotVoice (false, client.ent, ownId); client.iconTimestamp[ownId] = 0.0f; @@ -944,42 +884,41 @@ void Bot::showChaterIcon (bool show) { void Bot::instantChatter (int type) { // this function sends instant chatter messages. + auto &chatter = conf.getChatter (); - if (!(g_gameFlags & GAME_SUPPORT_BOT_VOICE) || yb_communication_type.integer () != 2 || g_chatterFactory[type].empty ()) { + if (!game.is (GAME_SUPPORT_BOT_VOICE) || yb_communication_type.integer () != 2 || chatter[type].empty ()) { return; } // delay only report team if (type == RADIO_REPORT_TEAM) { - if (m_timeRepotingInDelay < engine.timebase ()) { + if (m_timeRepotingInDelay < game.timebase ()) { return; } - m_timeRepotingInDelay = engine.timebase () + rng.getFloat (30.0f, 60.0f); + m_timeRepotingInDelay = game.timebase () + rng.getFloat (30.0f, 60.0f); } - auto playbackSound = g_chatterFactory[type].random (); - auto painSound = g_chatterFactory[CHATTER_PAIN_DIED].random (); + auto playbackSound = chatter[type].random (); + auto painSound = chatter[CHATTER_PAIN_DIED].random (); if (m_notKilled) { showChaterIcon (true); } MessageWriter msg; - for (int i = 0; i < engine.maxClients (); i++) { - Client &client = g_clients[i]; - + for (auto &client : util.getClients ()) { if (!(client.flags & CF_USED) || (client.ent->v.flags & FL_FAKECLIENT) || client.team != m_team) { continue; } - msg.start (MSG_ONE, engine.getMessageId (NETMSG_SENDAUDIO), Vector::null (), client.ent) // begin message - .writeByte (index ()); + msg.start (MSG_ONE, game.getMessageId (NETMSG_SENDAUDIO), Vector::null (), client.ent); // begin message + msg.writeByte (index ()); if (pev->deadflag & DEAD_DYING) { - client.iconTimestamp[index ()] = engine.timebase () + painSound.duration; - msg.writeString (format ("%s/%s.wav", yb_chatter_path.str (), painSound.name.chars ())); + client.iconTimestamp[index ()] = game.timebase () + painSound.duration; + msg.writeString (util.format ("%s/%s.wav", yb_chatter_path.str (), painSound.name.chars ())); } else if (!(pev->deadflag & DEAD_DEAD)) { - client.iconTimestamp[index ()] = engine.timebase () + playbackSound.duration; - msg.writeString (format ("%s/%s.wav", yb_chatter_path.str (), playbackSound.name.chars ())); + client.iconTimestamp[index ()] = game.timebase () + playbackSound.duration; + msg.writeString (util.format ("%s/%s.wav", yb_chatter_path.str (), playbackSound.name.chars ())); } msg.writeShort (m_voicePitch).end (); client.iconFlags[index ()] |= CF_ICON; @@ -992,13 +931,8 @@ void Bot::pushRadioMessage (int message) { if (yb_communication_type.integer () == 0 || m_numFriendsLeft == 0) { return; } - - if (!(g_gameFlags & GAME_SUPPORT_BOT_VOICE) || g_chatterFactory[message].empty () || yb_communication_type.integer () != 2) { - m_forceRadio = true; // use radio instead voice - } - else { - m_forceRadio = false; - } + auto &chatter = conf.getChatter (); + m_forceRadio = !game.is (GAME_SUPPORT_BOT_VOICE) || chatter[message].empty () || yb_communication_type.integer () != 2; // use radio instead voice m_radioSelect = message; pushMsgQueue (GAME_MSG_RADIO); @@ -1006,18 +940,19 @@ void Bot::pushRadioMessage (int message) { void Bot::pushChatterMessage (int message) { // this function inserts the voice message into the message queue (mostly same as above) + auto &chatter = conf.getChatter (); - if (!(g_gameFlags & GAME_SUPPORT_BOT_VOICE) || yb_communication_type.integer () != 2 || g_chatterFactory[message].empty () || m_numFriendsLeft == 0) { + if (!game.is (GAME_SUPPORT_BOT_VOICE) || yb_communication_type.integer () != 2 || chatter[message].empty () || m_numFriendsLeft == 0) { return; } bool sendMessage = false; float &messageTimer = m_chatterTimes[message]; - float &messageRepeat = g_chatterFactory[message][0].repeat; + float &messageRepeat = chatter[message][0].repeat; - if (messageTimer < engine.timebase () || cr::fequal (messageTimer, MAX_CHATTER_REPEAT)) { + if (messageTimer < game.timebase () || cr::fequal (messageTimer, MAX_CHATTER_REPEAT)) { if (!cr::fequal (messageTimer, MAX_CHATTER_REPEAT) && !cr::fequal (messageRepeat, MAX_CHATTER_REPEAT)) { - messageTimer = engine.timebase () + messageRepeat; + messageTimer = game.timebase () + messageRepeat; } sendMessage = true; } @@ -1032,6 +967,8 @@ void Bot::pushChatterMessage (int message) { void Bot::checkMsgQueue (void) { // this function checks and executes pending messages + extern ConVar mp_freezetime; + // no new message? if (m_actMessageIndex == m_pushMessageIndex) { return; @@ -1040,7 +977,7 @@ void Bot::checkMsgQueue (void) { int state = getMsgQueue (); // nothing to do? - if (state == GAME_MSG_NONE || (state == GAME_MSG_RADIO && (g_gameFlags & GAME_CSDM_FFA))) { + if (state == GAME_MSG_NONE || (state == GAME_MSG_RADIO && game.is (GAME_CSDM_FFA))) { return; } @@ -1048,13 +985,13 @@ void Bot::checkMsgQueue (void) { case GAME_MSG_PURCHASE: // general buy message // buy weapon - if (m_nextBuyTime > engine.timebase ()) { + if (m_nextBuyTime > game.timebase ()) { // keep sending message pushMsgQueue (GAME_MSG_PURCHASE); return; } - if (!m_inBuyZone || (g_gameFlags & GAME_CSDM)) { + if (!m_inBuyZone || game.is (GAME_CSDM)) { m_buyPending = true; m_buyingFinished = true; @@ -1062,7 +999,13 @@ void Bot::checkMsgQueue (void) { } m_buyPending = false; - m_nextBuyTime = engine.timebase () + rng.getFloat (0.5f, 1.3f); + m_nextBuyTime = game.timebase () + rng.getFloat (0.5f, 1.3f); + + // if freezetime is very low do not delay the buy process + if (mp_freezetime.flt () <= 1.0f) { + m_nextBuyTime = game.timebase (); + m_ignoreBuyDelay = true; + } // if bot buying is off then no need to buy if (!yb_botbuy.boolean ()) { @@ -1082,15 +1025,15 @@ void Bot::checkMsgQueue (void) { } // prevent terrorists from buying on es maps - if ((g_mapFlags & MAP_ES) && m_team == TEAM_TERRORIST) { + if (game.mapIs (MAP_ES) && m_team == TEAM_TERRORIST) { m_buyState = 6; } // prevent teams from buying on fun maps - if (g_mapFlags & (MAP_KA | MAP_FY)) { + if (game.mapIs (MAP_KA | MAP_FY)) { m_buyState = BUYSTATE_FINISHED; - if (g_mapFlags & MAP_KA) { + if (game.mapIs (MAP_KA)) { yb_jasonmode.set (1); } } @@ -1107,20 +1050,21 @@ void Bot::checkMsgQueue (void) { case GAME_MSG_RADIO: // if last bot radio command (global) happened just a 3 seconds ago, delay response - if (g_lastRadioTime[m_team] + 3.0f < engine.timebase ()) { + if (bots.getLastRadioTimestamp (m_team) + 3.0f < game.timebase ()) { // if same message like previous just do a yes/no if (m_radioSelect != RADIO_AFFIRMATIVE && m_radioSelect != RADIO_NEGATIVE) { - if (m_radioSelect == g_lastRadio[m_team] && g_lastRadioTime[m_team] + 1.5f > engine.timebase ()) + if (m_radioSelect == bots.getLastRadio (m_team) && bots.getLastRadioTimestamp (m_team) + 1.5f > game.timebase ()) { m_radioSelect = -1; + } else { if (m_radioSelect != RADIO_REPORTING_IN) { - g_lastRadio[m_team] = m_radioSelect; + bots.setLastRadio (m_team, m_radioSelect); } else { - g_lastRadio[m_team] = -1; + bots.setLastRadio (m_team, -1); } - for (int i = 0; i < engine.maxClients (); i++) { + for (int i = 0; i < game.maxClients (); i++) { Bot *bot = bots.getBot (i); if (bot != nullptr) { @@ -1136,11 +1080,11 @@ void Bot::checkMsgQueue (void) { if (m_radioSelect == RADIO_REPORTING_IN) { switch (taskId ()) { case TASK_NORMAL: - if (task ()->data != INVALID_WAYPOINT_INDEX && rng.chance (70)) { - Path &path = waypoints[task ()->data]; + if (getTask ()->data != INVALID_WAYPOINT_INDEX && rng.chance (70)) { + Path &path = waypoints[getTask ()->data]; if (path.flags & FLAG_GOAL) { - if ((g_mapFlags & MAP_DE) && m_team == TEAM_TERRORIST && m_hasC4) { + if (game.mapIs (MAP_DE) && m_team == TEAM_TERRORIST && m_hasC4) { instantChatter (CHATTER_GOING_TO_PLANT_BOMB); } else { @@ -1170,7 +1114,7 @@ void Bot::checkMsgQueue (void) { case TASK_CAMP: if (rng.chance (40)) { - if (g_bombPlanted && m_team == TEAM_TERRORIST) { + if (bots.isBombPlanted () && m_team == TEAM_TERRORIST) { instantChatter (CHATTER_GUARDING_DROPPED_BOMB); } else if (m_inVIPZone && m_team == TEAM_TERRORIST) { @@ -1206,30 +1150,31 @@ void Bot::checkMsgQueue (void) { break; } } + auto &chatter = conf.getChatter (); if (m_radioSelect != -1) { - if ((m_radioSelect != RADIO_REPORTING_IN && m_forceRadio) || yb_communication_type.integer () != 2 || g_chatterFactory[m_radioSelect].empty () || !(g_gameFlags & GAME_SUPPORT_BOT_VOICE)) { + if ((m_radioSelect != RADIO_REPORTING_IN && m_forceRadio) || yb_communication_type.integer () != 2 || chatter[m_radioSelect].empty () || !game.is (GAME_SUPPORT_BOT_VOICE)) { if (m_radioSelect < RADIO_GO_GO_GO) { - engine.execBotCmd (ent (), "radio1"); + game.execBotCmd (ent (), "radio1"); } else if (m_radioSelect < RADIO_AFFIRMATIVE) { m_radioSelect -= RADIO_GO_GO_GO - 1; - engine.execBotCmd (ent (), "radio2"); + game.execBotCmd (ent (), "radio2"); } else { m_radioSelect -= RADIO_AFFIRMATIVE - 1; - engine.execBotCmd (ent (), "radio3"); + game.execBotCmd (ent (), "radio3"); } // select correct menu item for this radio message - engine.execBotCmd (ent (), "menuselect %d", m_radioSelect); + game.execBotCmd (ent (), "menuselect %d", m_radioSelect); } else if (m_radioSelect != RADIO_REPORTING_IN) { instantChatter (m_radioSelect); } } m_forceRadio = false; // reset radio to voice - g_lastRadioTime[m_team] = engine.timebase (); // store last radio usage + bots.setLastRadioTimestamp (m_team, game.timebase ()); // store last radio usage } else { pushMsgQueue (GAME_MSG_RADIO); @@ -1238,12 +1183,12 @@ void Bot::checkMsgQueue (void) { // team independent saytext case GAME_MSG_SAY_CMD: - say (m_tempStrings.chars ()); + say (m_chatBuffer.chars ()); break; // team dependent saytext case GAME_MSG_SAY_TEAM_MSG: - sayTeam (m_tempStrings.chars ()); + sayTeam (m_chatBuffer.chars ()); break; default: @@ -1254,13 +1199,13 @@ void Bot::checkMsgQueue (void) { bool Bot::isWeaponRestricted (int weaponIndex) { // this function checks for weapon restrictions. - if (isEmptyStr (yb_restricted_weapons.str ())) { + if (util.isEmptyStr (yb_restricted_weapons.str ())) { return isWeaponRestrictedAMX (weaponIndex); // no banned weapons } auto bannedWeapons = String (yb_restricted_weapons.str ()).split (";"); for (auto &ban : bannedWeapons) { - const char *banned = STRING (getWeaponData (true, nullptr, weaponIndex)); + const char *banned = STRING (util.getWeaponAlias (true, nullptr, weaponIndex)); // check is this weapon is banned if (strncmp (ban.chars (), banned, ban.length ()) == 0) { @@ -1275,9 +1220,9 @@ bool Bot::isWeaponRestrictedAMX (int weaponIndex) { // check for weapon restrictions if ((1 << weaponIndex) & (WEAPON_PRIMARY | WEAPON_SECONDARY | WEAPON_SHIELD)) { - const char *restrictedWeapons = g_engfuncs.pfnCVarGetString ("amx_restrweapons"); + const char *restrictedWeapons = engfuncs.pfnCVarGetString ("amx_restrweapons"); - if (isEmptyStr (restrictedWeapons)) { + if (util.isEmptyStr (restrictedWeapons)) { return false; } int indices[] = {4, 25, 20, -1, 8, -1, 12, 19, -1, 5, 6, 13, 23, 17, 18, 1, 2, 21, 9, 24, 7, 16, 10, 22, -1, 3, 15, 14, 0, 11}; @@ -1294,9 +1239,9 @@ bool Bot::isWeaponRestrictedAMX (int weaponIndex) { // check for equipment restrictions else { - const char *restrictedEquipment = g_engfuncs.pfnCVarGetString ("amx_restrequipammo"); + const char *restrictedEquipment = engfuncs.pfnCVarGetString ("amx_restrequipammo"); - if (isEmptyStr (restrictedEquipment)) { + if (util.isEmptyStr (restrictedEquipment)) { return false; } int indices[] = {-1, -1, -1, 3, -1, -1, -1, -1, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, -1, -1, -1, -1, -1, 0, 1, 5}; @@ -1316,17 +1261,19 @@ bool Bot::canReplaceWeapon (void) { // this function determines currently owned primary weapon, and checks if bot has // enough money to buy more powerful weapon. + auto tab = conf.getRawWeapons (); + // if bot is not rich enough or non-standard weapon mode enabled return false - if (g_weaponSelect[25].teamStandard != 1 || m_moneyAmount < 4000) { + if (tab[25].teamStandard != 1 || m_moneyAmount < 4000) { return false; } - if (!isEmptyStr (yb_restricted_weapons.str ())) { + if (!util.isEmptyStr (yb_restricted_weapons.str ())) { auto bannedWeapons = String (yb_restricted_weapons.str ()).split (";"); // check if its banned for (auto &ban : bannedWeapons) { - if (m_currentWeapon == getWeaponData (false, ban.chars ())) { + if (m_currentWeapon == util.getWeaponAlias (false, ban.chars ())) { return true; } } @@ -1383,12 +1330,13 @@ int Bot::pickBestWeapon (int *vec, int count, int moneySave) { chance = 75; } } + auto &info = conf.getWeapons (); for (int i = 0; i < count; i++) { - auto weapon = &g_weaponSelect[vec[i]]; + auto &weapon = info[vec[i]]; // if wea have enough money for weapon buy it - if (weapon->price + moneySave < m_moneyAmount + rng.getInt (50, 200) && rng.chance (chance)) { + if (weapon.price + moneySave < m_moneyAmount + rng.getInt (50, 200) && rng.chance (chance)) { return vec[i]; } } @@ -1398,8 +1346,8 @@ int Bot::pickBestWeapon (int *vec, int count, int moneySave) { void Bot::buyStuff (void) { // this function does all the work in selecting correct buy menus for most weapons/items - WeaponSelect *selectedWeapon = nullptr; - m_nextBuyTime = engine.timebase (); + WeaponInfo *selectedWeapon = nullptr; + m_nextBuyTime = game.timebase (); if (!m_ignoreBuyDelay) { m_nextBuyTime += rng.getFloat (0.3f, 0.5f); @@ -1409,13 +1357,14 @@ void Bot::buyStuff (void) { int choices[NUM_WEAPONS]; // select the priority tab for this personality - int *ptr = g_weaponPrefs[m_personality] + NUM_WEAPONS; + const int *pref = conf.getWeaponPrefs (m_personality) + NUM_WEAPONS; + auto tab = conf.getRawWeapons (); - bool isPistolMode = g_weaponSelect[25].teamStandard == -1 && g_weaponSelect[3].teamStandard == 2; + bool isPistolMode = tab[25].teamStandard == -1 && tab[3].teamStandard == 2; bool teamEcoValid = bots.checkTeamEco (m_team); // do this, because xash engine is not capable to run all the features goldsrc, but we have cs 1.6 on it, so buy table must be the same - bool isOldGame = (g_gameFlags & GAME_LEGACY) && !(g_gameFlags & GAME_XASH_ENGINE); + bool isOldGame = game.is (GAME_LEGACY) && !game.is (GAME_XASH_ENGINE); switch (m_buyState) { case BUYSTATE_PRIMARY_WEAPON: // if no primary weapon and bot has some money, buy a primary weapon @@ -1425,12 +1374,12 @@ void Bot::buyStuff (void) { do { bool ignoreWeapon = false; - ptr--; + pref--; - assert (*ptr > -1); - assert (*ptr < NUM_WEAPONS); + assert (*pref > -1); + assert (*pref < NUM_WEAPONS); - selectedWeapon = &g_weaponSelect[*ptr]; + selectedWeapon = &tab[*pref]; count++; if (selectedWeapon->buyGroup == 1) { @@ -1438,7 +1387,7 @@ void Bot::buyStuff (void) { } // weapon available for every team? - if ((g_mapFlags & MAP_AS) && selectedWeapon->teamAS != 2 && selectedWeapon->teamAS != m_team) { + if (game.mapIs (MAP_AS) && selectedWeapon->teamAS != 2 && selectedWeapon->teamAS != m_team) { continue; } @@ -1457,7 +1406,7 @@ void Bot::buyStuff (void) { continue; } - int *limit = g_botBuyEconomyTable; + const int *limit = conf.getEconLimit (); int prostock = 0; // filter out weapons with bot economics @@ -1535,7 +1484,7 @@ void Bot::buyStuff (void) { break; } - if (ignoreWeapon && g_weaponSelect[25].teamStandard == 1 && yb_economics_rounds.boolean ()) { + if (ignoreWeapon && tab[25].teamStandard == 1 && yb_economics_rounds.boolean ()) { continue; } @@ -1547,7 +1496,7 @@ void Bot::buyStuff (void) { } if (selectedWeapon->price <= (m_moneyAmount - moneySave)) { - choices[weaponCount++] = *ptr; + choices[weaponCount++] = *pref; } } while (count < NUM_WEAPONS && weaponCount < 4); @@ -1563,24 +1512,24 @@ void Bot::buyStuff (void) { else { chosenWeapon = choices[weaponCount - 1]; } - selectedWeapon = &g_weaponSelect[chosenWeapon]; + selectedWeapon = &tab[chosenWeapon]; } else { selectedWeapon = nullptr; } if (selectedWeapon != nullptr) { - engine.execBotCmd (ent (), "buy;menuselect %d", selectedWeapon->buyGroup); + game.execBotCmd (ent (), "buy;menuselect %d", selectedWeapon->buyGroup); if (isOldGame) { - engine.execBotCmd (ent (), "menuselect %d", selectedWeapon->buySelect); + game.execBotCmd (ent (), "menuselect %d", selectedWeapon->buySelect); } else { if (m_team == TEAM_TERRORIST) { - engine.execBotCmd (ent (), "menuselect %d", selectedWeapon->newBuySelectT); + game.execBotCmd (ent (), "menuselect %d", selectedWeapon->newBuySelectT); } else { - engine.execBotCmd (ent (), "menuselect %d", selectedWeapon->newBuySelectCT); + game.execBotCmd (ent (), "menuselect %d", selectedWeapon->newBuySelectCT); } } } @@ -1599,10 +1548,10 @@ void Bot::buyStuff (void) { if (pev->armorvalue < rng.getInt (50, 80) && (isPistolMode || (teamEcoValid && hasPrimaryWeapon ()))) { // if bot is rich, buy kevlar + helmet, else buy a single kevlar if (m_moneyAmount > 1500 && !isWeaponRestricted (WEAPON_ARMORHELM)) { - engine.execBotCmd (ent (), "buyequip;menuselect 2"); + game.execBotCmd (ent (), "buyequip;menuselect 2"); } else if (!isWeaponRestricted (WEAPON_ARMOR)) { - engine.execBotCmd (ent (), "buyequip;menuselect 1"); + game.execBotCmd (ent (), "buyequip;menuselect 1"); } } break; @@ -1610,12 +1559,12 @@ void Bot::buyStuff (void) { case BUYSTATE_SECONDARY_WEAPON: // if bot has still some money, buy a better secondary weapon if (isPistolMode || (hasPrimaryWeapon () && (pev->weapons & ((1 << WEAPON_USP) | (1 << WEAPON_GLOCK))) && m_moneyAmount > rng.getInt (7500, 9000))) { do { - ptr--; + pref--; - assert (*ptr > -1); - assert (*ptr < NUM_WEAPONS); + assert (*pref > -1); + assert (*pref < NUM_WEAPONS); - selectedWeapon = &g_weaponSelect[*ptr]; + selectedWeapon = &tab[*pref]; count++; if (selectedWeapon->buyGroup != 1) { @@ -1628,7 +1577,7 @@ void Bot::buyStuff (void) { } // weapon available for every team? - if ((g_mapFlags & MAP_AS) && selectedWeapon->teamAS != 2 && selectedWeapon->teamAS != m_team) { + if (game.mapIs (MAP_AS) && selectedWeapon->teamAS != 2 && selectedWeapon->teamAS != m_team) { continue; } @@ -1641,7 +1590,7 @@ void Bot::buyStuff (void) { } if (selectedWeapon->price <= (m_moneyAmount - rng.getInt (100, 200))) { - choices[weaponCount++] = *ptr; + choices[weaponCount++] = *pref; } } while (count < NUM_WEAPONS && weaponCount < 4); @@ -1657,24 +1606,24 @@ void Bot::buyStuff (void) { else { chosenWeapon = choices[weaponCount - 1]; } - selectedWeapon = &g_weaponSelect[chosenWeapon]; + selectedWeapon = &tab[chosenWeapon]; } else { selectedWeapon = nullptr; } if (selectedWeapon != nullptr) { - engine.execBotCmd (ent (), "buy;menuselect %d", selectedWeapon->buyGroup); + game.execBotCmd (ent (), "buy;menuselect %d", selectedWeapon->buyGroup); if (isOldGame) { - engine.execBotCmd (ent (), "menuselect %d", selectedWeapon->buySelect); + game.execBotCmd (ent (), "menuselect %d", selectedWeapon->buySelect); } else { if (m_team == TEAM_TERRORIST) { - engine.execBotCmd (ent (), "menuselect %d", selectedWeapon->newBuySelectT); + game.execBotCmd (ent (), "menuselect %d", selectedWeapon->newBuySelectT); } else { - engine.execBotCmd (ent (), "menuselect %d", selectedWeapon->newBuySelectCT); + game.execBotCmd (ent (), "menuselect %d", selectedWeapon->newBuySelectCT); } } } @@ -1682,32 +1631,33 @@ void Bot::buyStuff (void) { break; case BUYSTATE_GRENADES: // if bot has still some money, choose if bot should buy a grenade or not - if (rng.chance (g_grenadeBuyPrecent[0]) && m_moneyAmount >= 400 && !isWeaponRestricted (WEAPON_EXPLOSIVE)) { - // buy a he grenade - engine.execBotCmd (ent (), "buyequip"); - engine.execBotCmd (ent (), "menuselect 4"); + + // buy a he grenade + if (conf.chanceToBuyGrenade (0) && m_moneyAmount >= 400 && !isWeaponRestricted (WEAPON_EXPLOSIVE)) { + game.execBotCmd (ent (), "buyequip"); + game.execBotCmd (ent (), "menuselect 4"); } - if (rng.chance (g_grenadeBuyPrecent[1]) && m_moneyAmount >= 300 && teamEcoValid && !isWeaponRestricted (WEAPON_FLASHBANG)) { - // buy a concussion grenade, i.e., 'flashbang' - engine.execBotCmd (ent (), "buyequip"); - engine.execBotCmd (ent (), "menuselect 3"); + // buy a concussion grenade, i.e., 'flashbang' + if (conf.chanceToBuyGrenade (1) && m_moneyAmount >= 300 && teamEcoValid && !isWeaponRestricted (WEAPON_FLASHBANG)) { + game.execBotCmd (ent (), "buyequip"); + game.execBotCmd (ent (), "menuselect 3"); } - if (rng.chance (g_grenadeBuyPrecent[2]) && m_moneyAmount >= 400 && teamEcoValid && !isWeaponRestricted (WEAPON_SMOKE)) { - // buy a smoke grenade - engine.execBotCmd (ent (), "buyequip"); - engine.execBotCmd (ent (), "menuselect 5"); + // buy a smoke grenade + if (conf.chanceToBuyGrenade (2) && m_moneyAmount >= 400 && teamEcoValid && !isWeaponRestricted (WEAPON_SMOKE)) { + game.execBotCmd (ent (), "buyequip"); + game.execBotCmd (ent (), "menuselect 5"); } break; case BUYSTATE_DEFUSER: // if bot is CT and we're on a bomb map, randomly buy the defuse kit - if ((g_mapFlags & MAP_DE) && m_team == TEAM_COUNTER && rng.chance (80) && m_moneyAmount > 200 && !isWeaponRestricted (WEAPON_DEFUSER)) { + if (game.mapIs (MAP_DE) && m_team == TEAM_COUNTER && rng.chance (80) && m_moneyAmount > 200 && !isWeaponRestricted (WEAPON_DEFUSER)) { if (isOldGame) { - engine.execBotCmd (ent (), "buyequip;menuselect 6"); + game.execBotCmd (ent (), "buyequip;menuselect 6"); } else { - engine.execBotCmd (ent (), "defuser"); // use alias in steamcs + game.execBotCmd (ent (), "defuser"); // use alias in steamcs } } break; @@ -1720,10 +1670,10 @@ void Bot::buyStuff (void) { // if it's somewhat darkm do buy nightvision goggles if ((skyColor >= 50.0f && lightLevel <= 15.0f) || (skyColor < 50.0f && lightLevel < 40.0f)) { if (isOldGame) { - engine.execBotCmd (ent (), "buyequip;menuselect 7"); + game.execBotCmd (ent (), "buyequip;menuselect 7"); } else { - engine.execBotCmd (ent (), "nvgs"); // use alias in steamcs + game.execBotCmd (ent (), "nvgs"); // use alias in steamcs } } } @@ -1731,16 +1681,16 @@ void Bot::buyStuff (void) { case BUYSTATE_AMMO: // buy enough primary & secondary ammo (do not check for money here) for (int i = 0; i <= 5; i++) { - engine.execBotCmd (ent (), "buyammo%d", rng.getInt (1, 2)); // simulate human + game.execBotCmd (ent (), "buyammo%d", rng.getInt (1, 2)); // simulate human } // buy enough secondary ammo if (hasPrimaryWeapon ()) { - engine.execBotCmd (ent (), "buy;menuselect 7"); + game.execBotCmd (ent (), "buy;menuselect 7"); } // buy enough primary ammo - engine.execBotCmd (ent (), "buy;menuselect 6"); + game.execBotCmd (ent (), "buy;menuselect 6"); // try to reload secondary weapon if (m_reloadState != RELOAD_PRIMARY) { @@ -1756,7 +1706,7 @@ void Bot::buyStuff (void) { void Bot::updateEmotions (void) { // slowly increase/decrease dynamic emotions back to their base level - if (m_nextEmotionUpdate > engine.timebase ()) { + if (m_nextEmotionUpdate > game.timebase ()) { return; } @@ -1781,22 +1731,22 @@ void Bot::updateEmotions (void) { if (m_fearLevel < 0.0f) { m_fearLevel = 0.0f; } - m_nextEmotionUpdate = engine.timebase () + 1.0f; + m_nextEmotionUpdate = game.timebase () + 1.0f; } void Bot::overrideConditions (void) { if (m_currentWeapon != WEAPON_KNIFE && m_difficulty > 2 && ((m_aimFlags & AIM_ENEMY) || (m_states & STATE_SEEING_ENEMY)) && !yb_jasonmode.boolean () && taskId () != TASK_CAMP && taskId () != TASK_SEEKCOVER && !isOnLadder ()) { m_moveToGoal = false; // don't move to goal - m_navTimeset = engine.timebase (); + m_navTimeset = game.timebase (); - if (isPlayer (m_enemy)) { + if (util.isPlayer (m_enemy)) { attackMovement (); } } // check if we need to escape from bomb - if ((g_mapFlags & MAP_DE) && g_bombPlanted && m_notKilled && taskId () != TASK_ESCAPEFROMBOMB && taskId () != TASK_CAMP && isOutOfBombTimer ()) { + if (game.mapIs (MAP_DE) && bots.isBombPlanted () && m_notKilled && taskId () != TASK_ESCAPEFROMBOMB && taskId () != TASK_CAMP && isOutOfBombTimer ()) { completeTask (); // complete current task // then start escape from bomb immediate @@ -1804,7 +1754,7 @@ void Bot::overrideConditions (void) { } // special handling, if we have a knife in our hands - if ((g_timeRoundStart + 6.0f > engine.timebase () || !hasAnyWeapons ()) && m_currentWeapon == WEAPON_KNIFE && isPlayer (m_enemy)) { + if ((bots.getRoundStartTime () + 6.0f > game.timebase () || !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 @@ -1812,9 +1762,9 @@ void Bot::overrideConditions (void) { int nearestToEnemyPoint = waypoints.getNearest (m_enemy->v.origin); if (nearestToEnemyPoint != INVALID_WAYPOINT_INDEX && nearestToEnemyPoint != m_currentWaypointIndex && cr::abs (waypoints[nearestToEnemyPoint].origin.z - m_enemy->v.origin.z) < 16.0f) { - float taskTime = engine.timebase () + length / pev->maxspeed * 0.5f; + float taskTime = game.timebase () + length / pev->maxspeed * 0.5f; - if (taskId () != TASK_MOVETOPOSITION && task ()->desire != TASKPRI_HIDE) { + if (taskId () != TASK_MOVETOPOSITION && getTask ()->desire != TASKPRI_HIDE) { startTask (TASK_MOVETOPOSITION, TASKPRI_HIDE, nearestToEnemyPoint, taskTime, true); } m_isEnemyReachable = false; @@ -1826,10 +1776,10 @@ void Bot::overrideConditions (void) { } // special handling for sniping - if (usesSniper () && (m_states & (STATE_SEEING_ENEMY | STATE_SUSPECT_ENEMY)) && m_sniperStopTime > engine.timebase () && taskId () != TASK_SEEKCOVER) { + if (usesSniper () && (m_states & (STATE_SEEING_ENEMY | STATE_SUSPECT_ENEMY)) && m_sniperStopTime > game.timebase () && taskId () != TASK_SEEKCOVER) { m_moveSpeed = 0.0f; m_strafeSpeed = 0.0f; - m_navTimeset = engine.timebase (); + m_navTimeset = game.timebase (); } } @@ -1851,8 +1801,8 @@ void Bot::setConditions (void) { } // did bot just kill an enemy? - if (!engine.isNullEntity (m_lastVictim)) { - if (engine.getTeam (m_lastVictim) != m_team) { + if (!game.isNullEntity (m_lastVictim)) { + if (game.getTeam (m_lastVictim) != m_team) { // add some aggression because we just killed somebody m_agressionLevel += 0.1f; @@ -1901,9 +1851,9 @@ void Bot::setConditions (void) { } // if no more enemies found AND bomb planted, switch to knife to get to bombplace faster - if (m_team == TEAM_COUNTER && m_currentWeapon != WEAPON_KNIFE && m_numEnemiesLeft == 0 && g_bombPlanted) { + if (m_team == TEAM_COUNTER && m_currentWeapon != WEAPON_KNIFE && m_numEnemiesLeft == 0 && bots.isBombPlanted ()) { selectWeaponByName ("weapon_knife"); - m_plantedBombWptIndex = locatePlantedC4 (); + m_plantedBombWptIndex = getNearestToPlantedBomb (); if (isOccupiedPoint (m_plantedBombWptIndex)) { instantChatter (CHATTER_BOMB_SITE_SECURED); @@ -1918,8 +1868,8 @@ void Bot::setConditions (void) { } // check if our current enemy is still valid - if (!engine.isNullEntity (m_lastEnemy)) { - if (!isAlive (m_lastEnemy) && m_shootAtDeadTime < engine.timebase ()) { + if (!game.isNullEntity (m_lastEnemy)) { + if (!util.isAlive (m_lastEnemy) && m_shootAtDeadTime < game.timebase ()) { m_lastEnemyOrigin.nullify (); m_lastEnemy = nullptr; } @@ -1930,15 +1880,15 @@ void Bot::setConditions (void) { } // don't listen if seeing enemy, just checked for sounds or being blinded (because its inhuman) - if (!yb_ignore_enemies.boolean () && m_soundUpdateTime < engine.timebase () && m_blindTime < engine.timebase () && m_seeEnemyTime + 1.0f < engine.timebase ()) { - processHearing (); - m_soundUpdateTime = engine.timebase () + 0.25f; + if (!yb_ignore_enemies.boolean () && m_soundUpdateTime < game.timebase () && m_blindTime < game.timebase () && m_seeEnemyTime + 1.0f < game.timebase ()) { + updateHearing (); + m_soundUpdateTime = game.timebase () + 0.25f; } - else if (m_heardSoundTime < engine.timebase ()) { + else if (m_heardSoundTime < game.timebase ()) { m_states &= ~STATE_HEARING_ENEMY; } - if (engine.isNullEntity (m_enemy) && !engine.isNullEntity (m_lastEnemy) && !m_lastEnemyOrigin.empty ()) { + if (game.isNullEntity (m_enemy) && !game.isNullEntity (m_lastEnemy) && !m_lastEnemyOrigin.empty ()) { m_aimFlags |= AIM_PREDICT_PATH; if (seesEntity (m_lastEnemyOrigin, true)) { @@ -1952,8 +1902,8 @@ void Bot::setConditions (void) { } // check if there are items needing to be used/collected - if (m_itemCheckTime < engine.timebase () || !engine.isNullEntity (m_pickupItem)) { - m_itemCheckTime = engine.timebase () + 0.5f; + if (m_itemCheckTime < game.timebase () || !game.isNullEntity (m_pickupItem)) { + m_itemCheckTime = game.timebase () + 0.5f; processPickups (); } filterTasks (); @@ -1961,7 +1911,7 @@ void Bot::setConditions (void) { void Bot::filterTasks (void) { // initialize & calculate the desire for all actions based on distances, emotions and other stuff - task (); + getTask (); float tempFear = m_fearLevel; float tempAgression = m_agressionLevel; @@ -1982,50 +1932,51 @@ void Bot::filterTasks (void) { tempFear = tempFear * 1.2f; tempAgression = tempAgression * 0.6f; } + auto &filter = bots.getFilters (); // bot found some item to use? - if (!engine.isNullEntity (m_pickupItem) && taskId () != TASK_ESCAPEFROMBOMB) { + if (!game.isNullEntity (m_pickupItem) && taskId () != TASK_ESCAPEFROMBOMB) { m_states |= STATE_PICKUP_ITEM; if (m_pickupType == PICKUP_BUTTON) { - g_taskFilters[TASK_PICKUPITEM].desire = 50.0f; // always pickup button + filter[TASK_PICKUPITEM].desire = 50.0f; // always pickup button } else { - float distance = (500.0f - (engine.getAbsPos (m_pickupItem) - pev->origin).length ()) * 0.2f; + float distance = (500.0f - (game.getAbsPos (m_pickupItem) - pev->origin).length ()) * 0.2f; if (distance > 50.0f) { distance = 50.0f; } - g_taskFilters[TASK_PICKUPITEM].desire = distance; + filter[TASK_PICKUPITEM].desire = distance; } } else { m_states &= ~STATE_PICKUP_ITEM; - g_taskFilters[TASK_PICKUPITEM].desire = 0.0f; + filter[TASK_PICKUPITEM].desire = 0.0f; } // calculate desire to attack if ((m_states & STATE_SEEING_ENEMY) && reactOnEnemy ()) { - g_taskFilters[TASK_ATTACK].desire = TASKPRI_ATTACK; + filter[TASK_ATTACK].desire = TASKPRI_ATTACK; } else { - g_taskFilters[TASK_ATTACK].desire = 0.0f; + filter[TASK_ATTACK].desire = 0.0f; } - float &seekCoverDesire = g_taskFilters[TASK_SEEKCOVER].desire; - float &huntEnemyDesire = g_taskFilters[TASK_HUNTENEMY].desire; - float &blindedDesire = g_taskFilters[TASK_BLINDED].desire; + float &seekCoverDesire = filter[TASK_SEEKCOVER].desire; + float &huntEnemyDesire = filter[TASK_HUNTENEMY].desire; + float &blindedDesire = filter[TASK_BLINDED].desire; // calculate desires to seek cover or hunt - if (isPlayer (m_lastEnemy) && !m_lastEnemyOrigin.empty () && !m_hasC4) { + 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 < engine.timebase () && m_seeEnemyTime - rng.getFloat (2.0f, 4.0f) < engine.timebase ()) { + if (m_numEnemiesLeft > m_numFriendsLeft * 0.5f && m_retreatTime < game.timebase () && m_seeEnemyTime - rng.getFloat (2.0f, 4.0f) < game.timebase ()) { - float timeSeen = m_seeEnemyTime - engine.timebase (); - float timeHeard = m_heardSoundTime - engine.timebase (); + float timeSeen = m_seeEnemyTime - game.timebase (); + float timeHeard = m_heardSoundTime - game.timebase (); float ratio = 0.0f; - m_retreatTime = engine.timebase () + rng.getFloat (3.0f, 15.0f); + m_retreatTime = game.timebase () + rng.getFloat (3.0f, 15.0f); if (timeSeen > timeHeard) { timeSeen += 10.0f; @@ -2035,9 +1986,9 @@ void Bot::filterTasks (void) { timeHeard += 10.0f; ratio = timeHeard * 0.1f; } - bool lowAmmo = m_ammoInClip[m_currentWeapon] < getMaxClip (m_currentWeapon) * 0.18f; + bool lowAmmo = m_ammoInClip[m_currentWeapon] < conf.findWeaponById (m_currentWeapon).maxClip * 0.18f; - if (g_bombPlanted || m_isStuck || m_currentWeapon == WEAPON_KNIFE) { + if (bots.isBombPlanted () || m_isStuck || m_currentWeapon == WEAPON_KNIFE) { ratio /= 3.0f; // reduce the seek cover desire if bomb is planted } else if (m_isVIP || m_isReloading || (lowAmmo && usesSniper ())) { @@ -2053,7 +2004,7 @@ void Bot::filterTasks (void) { } // if half of the round is over, allow hunting - if (taskId () != TASK_ESCAPEFROMBOMB && engine.isNullEntity (m_enemy) && g_timeRoundMid < engine.timebase () && !m_isUsingGrenade && m_currentWaypointIndex != waypoints.getNearest (m_lastEnemyOrigin) && m_personality != PERSONALITY_CAREFUL && !yb_ignore_enemies.boolean ()) { + if (taskId () != TASK_ESCAPEFROMBOMB && game.isNullEntity (m_enemy) && bots.getRoundMidTime () < game.timebase () && !m_isUsingGrenade && m_currentWaypointIndex != waypoints.getNearest (m_lastEnemyOrigin) && m_personality != PERSONALITY_CAREFUL && !yb_ignore_enemies.boolean ()) { float desireLevel = 4096.0f - ((1.0f - tempAgression) * (m_lastEnemyOrigin - pev->origin).length ()); desireLevel = (100.0f * desireLevel) / 4096.0f; @@ -2074,8 +2025,7 @@ void Bot::filterTasks (void) { } // blinded behavior - blindedDesire = m_blindTime > engine.timebase () ? TASKPRI_BLINDED : 0.0f; - + blindedDesire = m_blindTime > game.timebase () ? TASKPRI_BLINDED : 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 @@ -2121,24 +2071,24 @@ void Bot::filterTasks (void) { return old; }; - m_oldCombatDesire = hysteresisDesire (g_taskFilters[TASK_ATTACK].desire, 40.0f, 90.0f, m_oldCombatDesire); - g_taskFilters[TASK_ATTACK].desire = m_oldCombatDesire; + m_oldCombatDesire = hysteresisDesire (filter[TASK_ATTACK].desire, 40.0f, 90.0f, m_oldCombatDesire); + filter[TASK_ATTACK].desire = m_oldCombatDesire; - auto offensive = &g_taskFilters[TASK_ATTACK]; - auto pickup = &g_taskFilters[TASK_PICKUPITEM]; + auto offensive = &filter[TASK_ATTACK]; + auto pickup = &filter[TASK_PICKUPITEM]; // calc survive (cover/hide) - auto survive = thresholdDesire (&g_taskFilters[TASK_SEEKCOVER], 40.0f, 0.0f); - survive = subsumeDesire (&g_taskFilters[TASK_HIDE], survive); + auto survive = thresholdDesire (&filter[TASK_SEEKCOVER], 40.0f, 0.0f); + survive = subsumeDesire (&filter[TASK_HIDE], survive); - auto def = thresholdDesire (&g_taskFilters[TASK_HUNTENEMY], 41.0f, 0.0f); // don't allow hunting if desires 60< + auto def = thresholdDesire (&filter[TASK_HUNTENEMY], 41.0f, 0.0f); // don't allow hunting if desires 60< offensive = subsumeDesire (offensive, pickup); // if offensive task, don't allow picking up stuff auto sub = maxDesire (offensive, def); // default normal & careful tasks against offensive actions - auto final = subsumeDesire (&g_taskFilters[TASK_BLINDED], maxDesire (survive, sub)); // reason about fleeing instead + auto final = subsumeDesire (&filter[TASK_BLINDED], maxDesire (survive, sub)); // reason about fleeing instead if (!m_tasks.empty ()) { - final = maxDesire (final, task ()); + final = maxDesire (final, getTask ()); startTask (final->id, final->desire, final->data, final->time, final->resume); // push the final behavior in our task stack to carry out } } @@ -2167,7 +2117,7 @@ void Bot::startTask (TaskID id, float desire, int data, float time, bool resume) // leader bot? if (m_isLeader && tid == TASK_SEEKCOVER) { - processTeamCommands (); // reorganize team if fleeing + updateTeamCommands (); // reorganize team if fleeing } if (tid == TASK_CAMP) { @@ -2185,7 +2135,7 @@ void Bot::startTask (TaskID id, float desire, int data, float time, bool resume) } if (rng.chance (80) && tid == TASK_CAMP) { - if ((g_mapFlags & MAP_DE) && g_bombPlanted) { + if (game.mapIs (MAP_DE) && bots.isBombPlanted ()) { pushChatterMessage (CHATTER_GUARDING_DROPPED_BOMB); } else { @@ -2197,7 +2147,7 @@ void Bot::startTask (TaskID id, float desire, int data, float time, bool resume) m_chosenGoalIndex = yb_debug_goal.integer (); } else { - m_chosenGoalIndex = task ()->data; + m_chosenGoalIndex = getTask ()->data; } if (rng.chance (75) && tid == TASK_CAMP && m_team == TEAM_TERRORIST && m_inVIPZone) { @@ -2205,7 +2155,7 @@ void Bot::startTask (TaskID id, float desire, int data, float time, bool resume) } } -Task *Bot::task (void) { +Task *Bot::getTask (void) { if (m_tasks.empty ()) { m_tasks.push ({ TASK_NORMAL, TASKPRI_NORMAL, INVALID_WAYPOINT_INDEX, 0.0f, true }); } @@ -2254,7 +2204,7 @@ void Bot::completeTask (void) { } bool Bot::isEnemyThreat (void) { - if (engine.isNullEntity (m_enemy) || taskId () == TASK_SEEKCOVER) { + if (game.isNullEntity (m_enemy) || taskId () == TASK_SEEKCOVER) { return false; } @@ -2277,7 +2227,7 @@ bool Bot::reactOnEnemy (void) { return false; } - if (m_enemyReachableTimer < engine.timebase ()) { + if (m_enemyReachableTimer < game.timebase ()) { int ownIndex = m_currentWaypointIndex; if (ownIndex == INVALID_WAYPOINT_INDEX) { @@ -2285,8 +2235,8 @@ bool Bot::reactOnEnemy (void) { } int enemyIndex = waypoints.getNearest (m_enemy->v.origin); - float lineDist = (m_enemy->v.origin - pev->origin).length (); - float pathDist = static_cast (waypoints.getPathDist (ownIndex, enemyIndex)); + auto lineDist = (m_enemy->v.origin - pev->origin).length (); + auto pathDist = static_cast (waypoints.getPathDist (ownIndex, enemyIndex)); if (pathDist - lineDist > 112.0f) { m_isEnemyReachable = false; @@ -2294,11 +2244,11 @@ bool Bot::reactOnEnemy (void) { else { m_isEnemyReachable = true; } - m_enemyReachableTimer = engine.timebase () + 1.0f; + m_enemyReachableTimer = game.timebase () + 1.0f; } if (m_isEnemyReachable) { - m_navTimeset = engine.timebase (); // override existing movement by attack movement + m_navTimeset = game.timebase (); // override existing movement by attack movement return true; } return false; @@ -2306,22 +2256,22 @@ bool Bot::reactOnEnemy (void) { bool Bot::lastEnemyShootable (void) { // don't allow shooting through walls - if (!(m_aimFlags & AIM_LAST_ENEMY) || m_lastEnemyOrigin.empty () || engine.isNullEntity (m_lastEnemy)) { + if (!(m_aimFlags & AIM_LAST_ENEMY) || m_lastEnemyOrigin.empty () || game.isNullEntity (m_lastEnemy)) { return false; } - return getShootingConeDeviation (ent (), m_lastEnemyOrigin) >= 0.90f && isPenetrableObstacle (m_lastEnemyOrigin); + return util.getShootingCone (ent (), m_lastEnemyOrigin) >= 0.90f && isPenetrableObstacle (m_lastEnemyOrigin); } void Bot::checkRadioQueue (void) { // this function handling radio and reacting to it - float distance = (m_radioEntity->v.origin - pev->origin).length (); // don't allow bot listen you if bot is busy if ((taskId () == TASK_DEFUSEBOMB || taskId () == TASK_PLANTBOMB || hasHostage () || m_hasC4) && m_radioOrder != RADIO_REPORT_TEAM) { m_radioOrder = 0; return; } + float distance = (m_radioEntity->v.origin - pev->origin).length (); switch (m_radioOrder) { case RADIO_COVER_ME: @@ -2330,12 +2280,12 @@ void Bot::checkRadioQueue (void) { case CHATTER_GOING_TO_PLANT_BOMB: case CHATTER_COVER_ME: // check if line of sight to object is not blocked (i.e. visible) - if ((seesEntity (m_radioEntity->v.origin)) || (m_radioOrder == RADIO_STICK_TOGETHER_TEAM)) { - if (engine.isNullEntity (m_targetEntity) && engine.isNullEntity (m_enemy) && rng.chance (m_personality == PERSONALITY_CAREFUL ? 80 : 20)) { + if (seesEntity (m_radioEntity->v.origin) || m_radioOrder == RADIO_STICK_TOGETHER_TEAM) { + if (game.isNullEntity (m_targetEntity) && game.isNullEntity (m_enemy) && rng.chance (m_personality == PERSONALITY_CAREFUL ? 80 : 20)) { int numFollowers = 0; // Check if no more followers are allowed - for (int i = 0; i < engine.maxClients (); i++) { + for (int i = 0; i < game.maxClients (); i++) { Bot *bot = bots.getBot (i); if (bot != nullptr) { @@ -2360,12 +2310,12 @@ void Bot::checkRadioQueue (void) { TaskID taskID = taskId (); if (taskID == TASK_PAUSE || taskID == TASK_CAMP) { - task ()->time = engine.timebase (); + getTask ()->time = game.timebase (); } startTask (TASK_FOLLOWUSER, TASKPRI_FOLLOWUSER, INVALID_WAYPOINT_INDEX, 0.0f, true); } else if (numFollowers > allowedFollowers) { - for (int i = 0; (i < engine.maxClients () && numFollowers > allowedFollowers); i++) { + for (int i = 0; (i < game.maxClients () && numFollowers > allowedFollowers); i++) { Bot *bot = bots.getBot (i); if (bot != nullptr) { @@ -2389,14 +2339,14 @@ void Bot::checkRadioQueue (void) { break; case RADIO_HOLD_THIS_POSITION: - if (!engine.isNullEntity (m_targetEntity)) { + if (!game.isNullEntity (m_targetEntity)) { if (m_targetEntity == m_radioEntity) { m_targetEntity = nullptr; pushRadioMessage (RADIO_AFFIRMATIVE); m_campButtons = 0; - startTask (TASK_PAUSE, TASKPRI_PAUSE, INVALID_WAYPOINT_INDEX, engine.timebase () + rng.getFloat (30.0f, 60.0f), false); + startTask (TASK_PAUSE, TASKPRI_PAUSE, INVALID_WAYPOINT_INDEX, game.timebase () + rng.getFloat (30.0f, 60.0f), false); } } break; @@ -2406,8 +2356,8 @@ void Bot::checkRadioQueue (void) { break; case RADIO_TAKING_FIRE: - if (engine.isNullEntity (m_targetEntity)) { - if (engine.isNullEntity (m_enemy) && m_seeEnemyTime + 4.0f < engine.timebase ()) { + if (game.isNullEntity (m_targetEntity)) { + if (game.isNullEntity (m_enemy) && m_seeEnemyTime + 4.0f < game.timebase ()) { // decrease fear levels to lower probability of bot seeking cover again m_fearLevel -= 0.2f; @@ -2439,7 +2389,7 @@ void Bot::checkRadioQueue (void) { case RADIO_NEED_BACKUP: case CHATTER_SCARED_EMOTE: case CHATTER_PINNED_DOWN: - if (((engine.isNullEntity (m_enemy) && seesEntity (m_radioEntity->v.origin)) || distance < 2048.0f || !m_moveToC4) && rng.chance (50) && m_seeEnemyTime + 4.0f < engine.timebase ()) { + if (((game.isNullEntity (m_enemy) && seesEntity (m_radioEntity->v.origin)) || distance < 2048.0f || !m_moveToC4) && rng.chance (50) && m_seeEnemyTime + 4.0f < game.timebase ()) { m_fearLevel -= 0.1f; if (m_fearLevel < 0.0f) { @@ -2475,7 +2425,7 @@ void Bot::checkRadioQueue (void) { m_fearLevel = 0.0f; } } - else if ((engine.isNullEntity (m_enemy) && seesEntity (m_radioEntity->v.origin)) || distance < 2048.0f) { + else if ((game.isNullEntity (m_enemy) && seesEntity (m_radioEntity->v.origin)) || distance < 2048.0f) { TaskID taskID = taskId (); if (taskID == TASK_PAUSE || taskID == TASK_CAMP) { @@ -2487,18 +2437,18 @@ void Bot::checkRadioQueue (void) { pushRadioMessage (RADIO_AFFIRMATIVE); // don't pause/camp anymore - task ()->time = engine.timebase (); + getTask ()->time = game.timebase (); m_targetEntity = nullptr; - makeVectors (m_radioEntity->v.v_angle); + game.makeVectors (m_radioEntity->v.v_angle); - m_position = m_radioEntity->v.origin + g_pGlobals->v_forward * rng.getFloat (1024.0f, 2048.0f); + m_position = m_radioEntity->v.origin + game.vec.forward * rng.getFloat (1024.0f, 2048.0f); clearSearchNodes (); startTask (TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, INVALID_WAYPOINT_INDEX, 0.0f, true); } } - else if (!engine.isNullEntity (m_doubleJumpEntity)) { + else if (!game.isNullEntity (m_doubleJumpEntity)) { pushRadioMessage (RADIO_AFFIRMATIVE); resetDoubleJump (); } @@ -2508,7 +2458,7 @@ void Bot::checkRadioQueue (void) { break; case RADIO_SHES_GONNA_BLOW: - if (engine.isNullEntity (m_enemy) && distance < 2048.0f && g_bombPlanted && m_team == TEAM_TERRORIST) { + if (game.isNullEntity (m_enemy) && distance < 2048.0f && bots.isBombPlanted () && m_team == TEAM_TERRORIST) { pushRadioMessage (RADIO_AFFIRMATIVE); if (taskId () == TASK_CAMP) { @@ -2524,7 +2474,7 @@ void Bot::checkRadioQueue (void) { case RADIO_REGROUP_TEAM: // if no more enemies found AND bomb planted, switch to knife to get to bombplace faster - if (m_team == TEAM_COUNTER && m_currentWeapon != WEAPON_KNIFE && m_numEnemiesLeft == 0 && g_bombPlanted && taskId () != TASK_DEFUSEBOMB) { + if (m_team == TEAM_COUNTER && m_currentWeapon != WEAPON_KNIFE && m_numEnemiesLeft == 0 && bots.isBombPlanted () && taskId () != TASK_DEFUSEBOMB) { selectWeaponByName ("weapon_knife"); clearSearchNodes (); @@ -2537,19 +2487,19 @@ void Bot::checkRadioQueue (void) { break; case RADIO_STORM_THE_FRONT: - if (((engine.isNullEntity (m_enemy) && seesEntity (m_radioEntity->v.origin)) || distance < 1024.0f) && rng.chance (50)) { + if (((game.isNullEntity (m_enemy) && seesEntity (m_radioEntity->v.origin)) || distance < 1024.0f) && rng.chance (50)) { pushRadioMessage (RADIO_AFFIRMATIVE); // don't pause/camp anymore TaskID taskID = taskId (); if (taskID == TASK_PAUSE || taskID == TASK_CAMP) { - task ()->time = engine.timebase (); + getTask ()->time = game.timebase (); } m_targetEntity = nullptr; - makeVectors (m_radioEntity->v.v_angle); - m_position = m_radioEntity->v.origin + g_pGlobals->v_forward * rng.getFloat (1024.0f, 2048.0f); + game.makeVectors (m_radioEntity->v.v_angle); + m_position = m_radioEntity->v.origin + game.vec.forward * rng.getFloat (1024.0f, 2048.0f); clearSearchNodes (); startTask (TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, INVALID_WAYPOINT_INDEX, 0.0f, true); @@ -2568,7 +2518,7 @@ void Bot::checkRadioQueue (void) { break; case RADIO_TEAM_FALLBACK: - if ((engine.isNullEntity (m_enemy) && seesEntity (m_radioEntity->v.origin)) || distance < 1024.0f) { + if ((game.isNullEntity (m_enemy) && seesEntity (m_radioEntity->v.origin)) || distance < 1024.0f) { m_fearLevel += 0.5f; if (m_fearLevel > 1.0f) { @@ -2580,30 +2530,29 @@ void Bot::checkRadioQueue (void) { m_agressionLevel = 0.0f; } if (taskId () == TASK_CAMP) { - task ()->time += rng.getFloat (10.0f, 15.0f); + getTask ()->time += rng.getFloat (10.0f, 15.0f); } else { // don't pause/camp anymore TaskID taskID = taskId (); if (taskID == TASK_PAUSE) { - task ()->time = engine.timebase (); + getTask ()->time = game.timebase (); } m_targetEntity = nullptr; - m_seeEnemyTime = engine.timebase (); + m_seeEnemyTime = game.timebase (); // if bot has no enemy if (m_lastEnemyOrigin.empty ()) { float nearestDistance = 99999.0f; // take nearest enemy to ordering player - for (int i = 0; i < engine.maxClients (); i++) { - const Client &client = g_clients[i]; - + for (const auto &client : util.getClients ()) { if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.team == m_team) { continue; } - edict_t *enemy = client.ent; + + auto enemy = client.ent; float curDist = (m_radioEntity->v.origin - enemy->v.origin).lengthSq (); if (curDist < nearestDistance) { @@ -2627,12 +2576,12 @@ void Bot::checkRadioQueue (void) { case RADIO_SECTOR_CLEAR: // is bomb planted and it's a ct - if (!g_bombPlanted) { + if (!bots.isBombPlanted ()) { break; } // check if it's a ct command - if (engine.getTeam (m_radioEntity) == TEAM_COUNTER && m_team == TEAM_COUNTER && isFakeClient (m_radioEntity) && g_timeNextBombUpdate < engine.timebase ()) { + if (game.getTeam (m_radioEntity) == TEAM_COUNTER && m_team == TEAM_COUNTER && util.isFakeClient (m_radioEntity) && bots.getPlantedBombSearchTimestamp () < game.timebase ()) { float minDistance = 99999.0f; int bombPoint = INVALID_WAYPOINT_INDEX; @@ -2651,47 +2600,46 @@ void Bot::checkRadioQueue (void) { // does this bot want to defuse? if (taskId () == TASK_NORMAL) { // is he approaching this goal? - if (task ()->data == bombPoint) { - task ()->data = INVALID_WAYPOINT_INDEX; + if (getTask ()->data == bombPoint) { + getTask ()->data = INVALID_WAYPOINT_INDEX; pushRadioMessage (RADIO_AFFIRMATIVE); } } waypoints.setVisited (bombPoint); } - g_timeNextBombUpdate = engine.timebase () + 0.5f; + bots.setPlantedBombSearchTimestamp (game.timebase () + 0.5f); } break; case RADIO_GET_IN_POSITION: - if ((engine.isNullEntity (m_enemy) && seesEntity (m_radioEntity->v.origin)) || distance < 1024.0f) { + if ((game.isNullEntity (m_enemy) && seesEntity (m_radioEntity->v.origin)) || distance < 1024.0f) { pushRadioMessage (RADIO_AFFIRMATIVE); if (taskId () == TASK_CAMP) { - task ()->time = engine.timebase () + rng.getFloat (30.0f, 60.0f); + getTask ()->time = game.timebase () + rng.getFloat (30.0f, 60.0f); } else { // don't pause anymore TaskID taskID = taskId (); if (taskID == TASK_PAUSE) { - task ()->time = engine.timebase (); + getTask ()->time = game.timebase (); } m_targetEntity = nullptr; - m_seeEnemyTime = engine.timebase (); + m_seeEnemyTime = game.timebase (); // if bot has no enemy if (m_lastEnemyOrigin.empty ()) { float nearestDistance = 99999.0f; // take nearest enemy to ordering player - for (int i = 0; i < engine.maxClients (); i++) { - const Client &client = g_clients[i]; - - if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.team == m_team) + for (const auto &client : util.getClients ()) { + if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.team == m_team) { continue; + } - edict_t *enemy = client.ent; + auto enemy = client.ent; float dist = (m_radioEntity->v.origin - enemy->v.origin).lengthSq (); if (dist < nearestDistance) { @@ -2706,9 +2654,9 @@ void Bot::checkRadioQueue (void) { int index = getDefendPoint (m_radioEntity->v.origin); // push camp task on to stack - startTask (TASK_CAMP, TASKPRI_CAMP, INVALID_WAYPOINT_INDEX, engine.timebase () + rng.getFloat (30.0f, 60.0f), true); + startTask (TASK_CAMP, TASKPRI_CAMP, INVALID_WAYPOINT_INDEX, game.timebase () + rng.getFloat (30.0f, 60.0f), true); // push move command - startTask (TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, index, engine.timebase () + rng.getFloat (30.0f, 60.0f), true); + startTask (TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, index, game.timebase () + rng.getFloat (30.0f, 60.0f), true); if (waypoints[index].vis.crouch <= waypoints[index].vis.stand) { m_campButtons |= IN_DUCK; @@ -2726,14 +2674,15 @@ void Bot::checkRadioQueue (void) { void Bot::tryHeadTowardRadioMessage (void) { TaskID taskID = taskId (); - if (taskID == TASK_MOVETOPOSITION || m_headedTime + 15.0f < engine.timebase () || !isAlive (m_radioEntity) || m_hasC4) + if (taskID == TASK_MOVETOPOSITION || m_headedTime + 15.0f < game.timebase () || !util.isAlive (m_radioEntity) || m_hasC4) { return; + } - if ((isFakeClient (m_radioEntity) && rng.chance (25) && m_personality == PERSONALITY_NORMAL) || !(m_radioEntity->v.flags & FL_FAKECLIENT)) { + if ((util.isFakeClient (m_radioEntity) && rng.chance (25) && m_personality == PERSONALITY_NORMAL) || !(m_radioEntity->v.flags & FL_FAKECLIENT)) { if (taskID == TASK_PAUSE || taskID == TASK_CAMP) { - task ()->time = engine.timebase (); + getTask ()->time = game.timebase (); } - m_headedTime = engine.timebase (); + m_headedTime = game.timebase (); m_position = m_radioEntity->v.origin; clearSearchNodes (); @@ -2742,7 +2691,7 @@ void Bot::tryHeadTowardRadioMessage (void) { } void Bot::updateAimDir (void) { - unsigned int flags = m_aimFlags; + uint32 flags = m_aimFlags; // don't allow bot to look at danger positions under certain circumstances if (!(flags & (AIM_GRENADE | AIM_ENEMY | AIM_ENTITY))) { @@ -2760,14 +2709,12 @@ void Bot::updateAimDir (void) { float throwDistance = (m_throw - pev->origin).length (); float coordCorrection = 0.0f; - float angleCorrection = 0.0f; if (throwDistance > 100.0f && throwDistance < 800.0f) { - angleCorrection = 0.0f; coordCorrection = 0.25f * (m_throw.z - pev->origin.z); } else if (throwDistance >= 800.0f) { - angleCorrection = 37.0f * (throwDistance - 800.0f) / 800.0f; + float angleCorrection = 37.0f * (throwDistance - 800.0f) / 800.0f; if (angleCorrection > 45.0f) { angleCorrection = 45.0f; @@ -2786,7 +2733,7 @@ void Bot::updateAimDir (void) { m_lookAt = m_lastEnemyOrigin; // did bot just see enemy and is quite aggressive? - if (m_seeEnemyTime + 1.0f - m_actualReactionTime + m_baseAgressionLevel > engine.timebase ()) { + if (m_seeEnemyTime + 1.0f - m_actualReactionTime + m_baseAgressionLevel > game.timebase ()) { // feel free to fire if shootable if (!usesSniper () && lastEnemyShootable ()) { @@ -2797,7 +2744,7 @@ void Bot::updateAimDir (void) { else if (flags & AIM_PREDICT_PATH) { bool changePredictedEnemy = true; - if (m_timeNextTracking > engine.timebase () && m_trackingEdict == m_lastEnemy && isAlive (m_lastEnemy)) { + if (m_timeNextTracking > game.timebase () && m_trackingEdict == m_lastEnemy && util.isAlive (m_lastEnemy)) { changePredictedEnemy = false; } @@ -2808,7 +2755,7 @@ void Bot::updateAimDir (void) { m_lookAt = waypoints[aimPoint].origin; m_camp = m_lookAt; - m_timeNextTracking = engine.timebase () + 0.5f; + m_timeNextTracking = game.timebase () + 0.5f; m_trackingEdict = m_lastEnemy; } else { @@ -2830,17 +2777,10 @@ void Bot::updateAimDir (void) { m_lookAt = m_destOrigin; if (m_canChooseAimDirection && m_currentWaypointIndex != INVALID_WAYPOINT_INDEX && !(m_currentPath->flags & FLAG_LADDER)) { - auto experience = (g_experienceData + (m_currentWaypointIndex * waypoints.length ()) + m_currentWaypointIndex); + int dangerIndex = waypoints.getDangerIndex (m_team, m_currentWaypointIndex, m_currentWaypointIndex); - if (m_team == TEAM_TERRORIST) { - if (experience->team0DangerIndex != INVALID_WAYPOINT_INDEX && waypoints.isVisible (m_currentWaypointIndex, experience->team0DangerIndex)) { - m_lookAt = waypoints[experience->team0DangerIndex].origin; - } - } - else { - if (experience->team1DangerIndex != INVALID_WAYPOINT_INDEX && waypoints.isVisible (m_currentWaypointIndex, experience->team1DangerIndex)) { - m_lookAt = waypoints[experience->team1DangerIndex].origin; - } + if (waypoints.exists (dangerIndex) && waypoints.isVisible (m_currentWaypointIndex, dangerIndex)) { + m_lookAt = waypoints[dangerIndex].origin; } } } @@ -2853,12 +2793,12 @@ void Bot::updateAimDir (void) { void Bot::checkDarkness (void) { // do not check for darkness at the start of the round - if (m_spawnTime + 5.0f > engine.timebase () || !waypoints.exists (m_currentWaypointIndex)) { + if (m_spawnTime + 5.0f > game.timebase () || !waypoints.exists (m_currentWaypointIndex)) { return; } // do not check every frame - if (m_checkDarkTime + 2.5f > engine.timebase ()) { + if (m_checkDarkTime + 2.5f > game.timebase ()) { return; } @@ -2868,10 +2808,10 @@ void Bot::checkDarkness (void) { if (mp_flashlight.boolean () && !m_hasNVG) { auto task = TaskID (); - if (!(pev->effects & EF_DIMLIGHT) && task != TASK_CAMP && task != TASK_ATTACK && m_heardSoundTime + 3.0f < engine.timebase () && m_flashLevel > 30.0f && ((skyColor > 50.0f && lightLevel < 10.0f) || (skyColor <= 50.0f && lightLevel < 40.0f))) { + if (!(pev->effects & EF_DIMLIGHT) && task != TASK_CAMP && task != TASK_ATTACK && m_heardSoundTime + 3.0f < game.timebase () && m_flashLevel > 30.0f && ((skyColor > 50.0f && lightLevel < 10.0f) || (skyColor <= 50.0f && lightLevel < 40.0f))) { pev->impulse = 100; } - else if ((pev->effects & EF_DIMLIGHT) && (((lightLevel > 15.0f && skyColor > 50.0f) || (lightLevel > 45.0f && skyColor <= 50.0f)) || task == TASK_CAMP || task == TASK_ATTACK || m_flashLevel <= 0 || m_heardSoundTime + 3.0f >= engine.timebase ())) + else if ((pev->effects & EF_DIMLIGHT) && (((lightLevel > 15.0f && skyColor > 50.0f) || (lightLevel > 45.0f && skyColor <= 50.0f)) || task == TASK_CAMP || task == TASK_ATTACK || m_flashLevel <= 0 || m_heardSoundTime + 3.0f >= game.timebase ())) { pev->impulse = 100; } @@ -2881,17 +2821,17 @@ void Bot::checkDarkness (void) { pev->impulse = 100; } else if (!m_usesNVG && ((skyColor > 50.0f && lightLevel < 15.0f) || (skyColor <= 50.0f && lightLevel < 40.0f))) { - engine.execBotCmd (ent (), "nightvision"); + game.execBotCmd (ent (), "nightvision"); } else if (m_usesNVG && ((lightLevel > 20.0f && skyColor > 50.0f) || (lightLevel > 45.0f && skyColor <= 50.0f))) { - engine.execBotCmd (ent (), "nightvision"); + game.execBotCmd (ent (), "nightvision"); } } - m_checkDarkTime = engine.timebase (); + m_checkDarkTime = game.timebase (); } void Bot::checkParachute (void) { - static auto parachute = g_engfuncs.pfnCVarGetPointer ("sv_parachute"); + static auto parachute = engfuncs.pfnCVarGetPointer ("sv_parachute"); // if no cvar or it's not enabled do not bother if (parachute && parachute->value > 0.0f) { @@ -2899,30 +2839,30 @@ void Bot::checkParachute (void) { m_fallDownTime = 0.0f; } else if (cr::fzero (m_fallDownTime)) { - m_fallDownTime = engine.timebase (); + m_fallDownTime = game.timebase (); } // press use anyway - if (!cr::fzero (m_fallDownTime) && m_fallDownTime + 0.35f < engine.timebase ()) { + if (!cr::fzero (m_fallDownTime) && m_fallDownTime + 0.35f < game.timebase ()) { pev->button |= IN_USE; } } } -void Bot::framePeriodic (void) { - if (m_thinkFps <= engine.timebase ()) { +void Bot::slowFrame (void) { + if (m_thinkFps <= game.timebase ()) { // execute delayed think - frameThink (); + fastFrame (); // skip some frames - m_thinkFps = engine.timebase () + m_thinkInterval; + m_thinkFps = game.timebase () + m_thinkInterval; } else if (m_notKilled) { - processLookAngles (); + updateLookAngles (); } } -void Bot::frameThink (void) { +void Bot::fastFrame (void) { pev->button = 0; pev->flags |= FL_FAKECLIENT; // restore fake client bit, if it were removed by some evil action =) @@ -2931,14 +2871,14 @@ void Bot::frameThink (void) { m_moveAngles.nullify (); m_canChooseAimDirection = true; - m_notKilled = isAlive (ent ()); - m_team = engine.getTeam (ent ()); + m_notKilled = util.isAlive (ent ()); + m_team = game.getTeam (ent ()); - if ((g_mapFlags & MAP_AS) && !m_isVIP) { - m_isVIP = isPlayerVIP (ent ()); + if (game.mapIs (MAP_AS) && !m_isVIP) { + m_isVIP = util.isPlayerVIP (ent ()); } - if (m_team == TEAM_TERRORIST && (g_mapFlags & MAP_DE)) { + if (m_team == TEAM_TERRORIST && game.mapIs (MAP_DE)) { m_hasC4 = !!(pev->weapons & (1 << WEAPON_C4)); } @@ -2947,20 +2887,20 @@ void Bot::frameThink (void) { // if the bot hasn't selected stuff to start the game yet, go do that... if (m_notStarted) { - processTeamJoin (); // select team & class + updateTeamJoin (); // select team & class } else if (!m_notKilled) { // we got a teamkiller? vote him away... if (m_voteKickIndex != m_lastVoteKick && yb_tkpunish.boolean ()) { - engine.execBotCmd (ent (), "vote %d", m_voteKickIndex); + game.execBotCmd (ent (), "vote %d", m_voteKickIndex); m_lastVoteKick = m_voteKickIndex; // if bot tk punishment is enabled slay the tk - if (yb_tkpunish.integer () != 2 || isFakeClient (engine.entityOfIndex (m_voteKickIndex))) { + if (yb_tkpunish.integer () != 2 || util.isFakeClient (game.entityOfIndex (m_voteKickIndex))) { return; } - edict_t *killer = engine.entityOfIndex (m_lastVoteKick); + edict_t *killer = game.entityOfIndex (m_lastVoteKick); killer->v.frags++; MDLL_ClientKill (killer); @@ -2968,7 +2908,7 @@ void Bot::frameThink (void) { // host wants us to kick someone else if (m_voteMap != 0) { - engine.execBotCmd (ent (), "votemap %d", m_voteMap); + game.execBotCmd (ent (), "votemap %d", m_voteMap); m_voteMap = 0; } } @@ -2978,20 +2918,20 @@ void Bot::frameThink (void) { checkMsgQueue (); // check for pending messages if (botMovement) { - ai (); // execute main code + runAI (); // execute main code } runMovement (); // run the player movement } void Bot::frame (void) { - if (m_timePeriodicUpdate > engine.timebase ()) { + if (m_slowFrameTimestamp > game.timebase ()) { return; } m_numFriendsLeft = numFriendsNear (pev->origin, 99999.0f); m_numEnemiesLeft = numEnemiesNear (pev->origin, 99999.0f); - if (g_bombPlanted && m_team == TEAM_COUNTER && m_notKilled) { + if (bots.isBombPlanted () && m_team == TEAM_COUNTER && m_notKilled) { const Vector &bombPosition = waypoints.getBombPos (); if (!m_hasProgressBar && taskId () != TASK_ESCAPEFROMBOMB && (pev->origin - bombPosition).lengthSq () < cr::square (1540.0f) && m_moveSpeed < pev->maxspeed && !isBombDefusing (bombPosition)) { @@ -2999,54 +2939,18 @@ void Bot::frame (void) { } } checkSpawnConditions (); + checkForChat (); - extern ConVar yb_chat; - - // bot chatting turned on? - if (!m_notKilled && yb_chat.boolean () && m_lastChatTime + 10.0 < engine.timebase () && g_lastChatTime + 5.0f < engine.timebase () && !isReplyingToChat ()) { - // say a text every now and then - if (rng.chance (50)) { - m_lastChatTime = engine.timebase (); - g_lastChatTime = engine.timebase (); - - if (!g_chatFactory[CHAT_DEAD].empty ()) { - const String &phrase = g_chatFactory[CHAT_DEAD].random (); - bool sayBufferExists = false; - - // search for last messages, sayed - for (auto &sentence : m_sayTextBuffer.lastUsedSentences) { - if (strncmp (sentence.chars (), phrase.chars (), sentence.length ()) == 0) { - sayBufferExists = true; - break; - } - } - - if (!sayBufferExists) { - prepareChatMessage (const_cast (phrase.chars ())); - pushMsgQueue (GAME_MSG_SAY_CMD); - - // add to ignore list - m_sayTextBuffer.lastUsedSentences.push (phrase); - } - } - - // clear the used line buffer every now and then - if (static_cast (m_sayTextBuffer.lastUsedSentences.length ()) > rng.getInt (4, 6)) { - m_sayTextBuffer.lastUsedSentences.clear (); - } - } - } - - if (g_gameFlags & GAME_SUPPORT_BOT_VOICE) { + if (game.is (GAME_SUPPORT_BOT_VOICE)) { showChaterIcon (false); // end voice feedback } // clear enemy far away - if (!m_lastEnemyOrigin.empty () && !engine.isNullEntity (m_lastEnemy) && (pev->origin - m_lastEnemyOrigin).lengthSq () >= cr::square (1600.0f)) { + if (!m_lastEnemyOrigin.empty () && !game.isNullEntity (m_lastEnemy) && (pev->origin - m_lastEnemyOrigin).lengthSq () >= cr::square (1600.0f)) { m_lastEnemy = nullptr; m_lastEnemyOrigin.nullify (); } - m_timePeriodicUpdate = engine.timebase () + 0.5f; + m_slowFrameTimestamp = game.timebase () + 0.5f; } void Bot::normal_ (void) { @@ -3055,9 +2959,9 @@ void Bot::normal_ (void) { int debugGoal = yb_debug_goal.integer (); // user forced a waypoint as a goal? - if (debugGoal != INVALID_WAYPOINT_INDEX && task ()->data != debugGoal) { + if (debugGoal != INVALID_WAYPOINT_INDEX && getTask ()->data != debugGoal) { clearSearchNodes (); - task ()->data = debugGoal; + getTask ()->data = debugGoal; } // stand still if reached debug goal @@ -3072,31 +2976,32 @@ void Bot::normal_ (void) { } // bots rushing with knife, when have no enemy (thanks for idea to nicebot project) - if (m_currentWeapon == WEAPON_KNIFE && (engine.isNullEntity (m_lastEnemy) || !isAlive (m_lastEnemy)) && engine.isNullEntity (m_enemy) && m_knifeAttackTime < engine.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.timebase () && !hasShield () && numFriendsNear (pev->origin, 96.0f) == 0) { if (rng.chance (40)) { pev->button |= IN_ATTACK; } else { pev->button |= IN_ATTACK2; } - m_knifeAttackTime = engine.timebase () + rng.getFloat (2.5f, 6.0f); + m_knifeAttackTime = game.timebase () + rng.getFloat (2.5f, 6.0f); } + const auto &prop = conf.getWeaponProp (m_currentWeapon); - if (m_reloadState == RELOAD_NONE && ammo () != 0 && ammoClip () < 5 && g_weaponDefs[m_currentWeapon].ammo1 != -1) { + if (m_reloadState == RELOAD_NONE && getAmmo () != 0 && getAmmoInClip () < 5 && prop.ammo1 != -1) { m_reloadState = RELOAD_PRIMARY; } // if bomb planted and it's a CT calculate new path to bomb point if he's not already heading for - if (!m_bombSearchOverridden && g_bombPlanted && m_team == TEAM_COUNTER && task ()->data != INVALID_WAYPOINT_INDEX && !(waypoints[task ()->data].flags & FLAG_GOAL) && taskId () != TASK_ESCAPEFROMBOMB) { + if (!m_bombSearchOverridden && bots.isBombPlanted () && m_team == TEAM_COUNTER && getTask ()->data != INVALID_WAYPOINT_INDEX && !(waypoints[getTask ()->data].flags & FLAG_GOAL) && taskId () != TASK_ESCAPEFROMBOMB) { clearSearchNodes (); - task ()->data = INVALID_WAYPOINT_INDEX; + getTask ()->data = INVALID_WAYPOINT_INDEX; } // reached the destination (goal) waypoint? - if (processNavigation ()) { + if (updateNavigation ()) { // if we're reached the goal, and there is not enemies, notify the team - if (!g_bombPlanted && m_currentWaypointIndex != INVALID_WAYPOINT_INDEX && (m_currentPath->flags & FLAG_GOAL) && rng.chance (15) && numEnemiesNear (pev->origin, 650.0f) == 0) { + if (!bots.isBombPlanted () && m_currentWaypointIndex != INVALID_WAYPOINT_INDEX && (m_currentPath->flags & FLAG_GOAL) && rng.chance (15) && numEnemiesNear (pev->origin, 650.0f) == 0) { pushRadioMessage (RADIO_SECTOR_CLEAR); } @@ -3104,17 +3009,17 @@ void Bot::normal_ (void) { m_prevGoalIndex = INVALID_WAYPOINT_INDEX; // spray logo sometimes if allowed to do so - if (m_timeLogoSpray < engine.timebase () && yb_spraypaints.boolean () && rng.chance (60) && m_moveSpeed > getShiftSpeed () && engine.isNullEntity (m_pickupItem)) { - if (!((g_mapFlags & MAP_DE) && g_bombPlanted && m_team == TEAM_COUNTER)) { - startTask (TASK_SPRAY, TASKPRI_SPRAYLOGO, INVALID_WAYPOINT_INDEX, engine.timebase () + 1.0f, false); + if (m_timeLogoSpray < game.timebase () && yb_spraypaints.boolean () && rng.chance (60) && m_moveSpeed > getShiftSpeed () && game.isNullEntity (m_pickupItem)) { + if (!(game.mapIs (MAP_DE) && bots.isBombPlanted () && m_team == TEAM_COUNTER)) { + startTask (TASK_SPRAY, TASKPRI_SPRAYLOGO, INVALID_WAYPOINT_INDEX, game.timebase () + 1.0f, false); } } // reached waypoint is a camp waypoint - if ((m_currentPath->flags & FLAG_CAMP) && !(g_gameFlags & GAME_CSDM) && yb_camping_allowed.boolean ()) { + if ((m_currentPath->flags & FLAG_CAMP) && !game.is (GAME_CSDM) && yb_camping_allowed.boolean ()) { // check if bot has got a primary weapon and hasn't camped before - if (hasPrimaryWeapon () && m_timeCamping + 10.0f < engine.timebase () && !hasHostage ()) { + if (hasPrimaryWeapon () && m_timeCamping + 10.0f < game.timebase () && !hasHostage ()) { bool campingAllowed = true; // Check if it's not allowed for this team to camp here @@ -3130,7 +3035,7 @@ void Bot::normal_ (void) { } // don't allow vip on as_ maps to camp + don't allow terrorist carrying c4 to camp - if (campingAllowed && (m_isVIP || ((g_mapFlags & MAP_DE) && m_team == TEAM_TERRORIST && !g_bombPlanted && m_hasC4))) { + if (campingAllowed && (m_isVIP || (game.mapIs (MAP_DE) && m_team == TEAM_TERRORIST && !bots.isBombPlanted () && m_hasC4))) { campingAllowed = false; } @@ -3152,9 +3057,9 @@ void Bot::normal_ (void) { if (!(m_states & (STATE_SEEING_ENEMY | STATE_HEARING_ENEMY)) && !m_reloadState) { m_reloadState = RELOAD_PRIMARY; } - makeVectors (pev->v_angle); + game.makeVectors (pev->v_angle); - m_timeCamping = engine.timebase () + rng.getFloat (10.0f, 25.0f); + m_timeCamping = game.timebase () + rng.getFloat (10.0f, 25.0f); startTask (TASK_CAMP, TASKPRI_CAMP, INVALID_WAYPOINT_INDEX, m_timeCamping, true); m_camp = Vector (m_currentPath->campStartX, m_currentPath->campStartY, 0.0f); @@ -3175,7 +3080,7 @@ void Bot::normal_ (void) { } else { // some goal waypoints are map dependant so check it out... - if (g_mapFlags & MAP_CS) { + if (game.mapIs (MAP_CS)) { // CT Bot has some hostages following? if (m_team == TEAM_COUNTER && hasHostage ()) { // and reached a Rescue Point? @@ -3186,8 +3091,8 @@ void Bot::normal_ (void) { else if (m_team == TEAM_TERRORIST && rng.chance (75)) { int index = getDefendPoint (m_currentPath->origin); - startTask (TASK_CAMP, TASKPRI_CAMP, INVALID_WAYPOINT_INDEX, engine.timebase () + rng.getFloat (60.0f, 120.0f), true); // push camp task on to stack - startTask (TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, index, engine.timebase () + rng.getFloat (5.0f, 10.0f), true); // push move command + startTask (TASK_CAMP, TASKPRI_CAMP, INVALID_WAYPOINT_INDEX, game.timebase () + rng.getFloat (60.0f, 120.0f), true); // push camp task on to stack + startTask (TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, index, game.timebase () + rng.getFloat (5.0f, 10.0f), true); // push move command auto &path = waypoints[index]; @@ -3201,7 +3106,7 @@ void Bot::normal_ (void) { pushChatterMessage (CHATTER_GOING_TO_GUARD_VIP_SAFETY); // play info about that } } - else if ((g_mapFlags & MAP_DE) && ((m_currentPath->flags & FLAG_GOAL) || m_inBombZone)) { + else if (game.mapIs (MAP_DE) && ((m_currentPath->flags & FLAG_GOAL) || m_inBombZone)) { // is it a terrorist carrying the bomb? if (m_hasC4) { if ((m_states & STATE_SEEING_ENEMY) && numFriendsNear (pev->origin, 768.0f) == 0) { @@ -3209,14 +3114,14 @@ void Bot::normal_ (void) { pushRadioMessage (RADIO_NEED_BACKUP); instantChatter (CHATTER_SCARED_EMOTE); - startTask (TASK_CAMP, TASKPRI_CAMP, INVALID_WAYPOINT_INDEX, engine.timebase () + rng.getFloat (4.0f, 8.0f), true); + startTask (TASK_CAMP, TASKPRI_CAMP, INVALID_WAYPOINT_INDEX, game.timebase () + rng.getFloat (4.0f, 8.0f), true); } else { startTask (TASK_PLANTBOMB, TASKPRI_PLANTBOMB, INVALID_WAYPOINT_INDEX, 0.0f, false); } } else if (m_team == TEAM_COUNTER) { - if (!g_bombPlanted && numFriendsNear (pev->origin, 210.0f) < 4) { + if (!bots.isBombPlanted () && numFriendsNear (pev->origin, 210.0f) < 4) { int index = getDefendPoint (m_currentPath->origin); float campTime = rng.getFloat (25.0f, 40.f); @@ -3225,8 +3130,8 @@ void Bot::normal_ (void) { if (m_personality == PERSONALITY_RUSHER) { campTime *= 0.5f; } - startTask (TASK_CAMP, TASKPRI_CAMP, INVALID_WAYPOINT_INDEX, engine.timebase () + campTime, true); // push camp task on to stack - startTask (TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, index, engine.timebase () + rng.getFloat (5.0f, 11.0f), true); // push move command + startTask (TASK_CAMP, TASKPRI_CAMP, INVALID_WAYPOINT_INDEX, game.timebase () + campTime, true); // push camp task on to stack + startTask (TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, index, game.timebase () + rng.getFloat (5.0f, 11.0f), true); // push move command auto &path = waypoints[index]; @@ -3251,12 +3156,12 @@ void Bot::normal_ (void) { ignoreCollision (); // did we already decide about a goal before? - int destIndex = task ()->data != INVALID_WAYPOINT_INDEX ? task ()->data : searchGoal (); + int destIndex = getTask ()->data != INVALID_WAYPOINT_INDEX ? getTask ()->data : searchGoal (); m_prevGoalIndex = destIndex; // remember index - task ()->data = destIndex; + getTask ()->data = destIndex; // do pathfinding if it's not the current waypoint if (destIndex != m_currentWaypointIndex) { @@ -3270,22 +3175,22 @@ void Bot::normal_ (void) { } float shiftSpeed = getShiftSpeed (); - if ((!cr::fzero (m_moveSpeed) && m_moveSpeed > shiftSpeed) && (yb_walking_allowed.boolean () && mp_footsteps.boolean ()) && m_difficulty > 2 && !(m_aimFlags & AIM_ENEMY) && (m_heardSoundTime + 6.0f >= engine.timebase () || (m_states & STATE_SUSPECT_ENEMY)) && numEnemiesNear (pev->origin, 768.0f) >= 1 && !yb_jasonmode.boolean () && !g_bombPlanted) { + if ((!cr::fzero (m_moveSpeed) && m_moveSpeed > shiftSpeed) && (yb_walking_allowed.boolean () && mp_footsteps.boolean ()) && m_difficulty > 2 && !(m_aimFlags & AIM_ENEMY) && (m_heardSoundTime + 6.0f >= game.timebase () || (m_states & STATE_SUSPECT_ENEMY)) && numEnemiesNear (pev->origin, 768.0f) >= 1 && !yb_jasonmode.boolean () && !bots.isBombPlanted ()) { m_moveSpeed = shiftSpeed; } // bot hasn't seen anything in a long time and is asking his teammates to report in - if (yb_communication_type.integer () > 1 && m_seeEnemyTime + rng.getFloat (45.0f, 80.0f) < engine.timebase () && g_lastRadio[m_team] != RADIO_REPORT_TEAM && rng.chance (30) && g_timeRoundStart + 20.0f < engine.timebase () && m_askCheckTime < engine.timebase () && numFriendsNear (pev->origin, 1024.0f) == 0) { + if (yb_communication_type.integer () > 1 && m_seeEnemyTime + rng.getFloat (45.0f, 80.0f) < game.timebase () && bots.getLastRadio (m_team) != RADIO_REPORT_TEAM && rng.chance (30) && bots.getRoundStartTime () + 20.0f < game.timebase () && m_askCheckTime < game.timebase () && numFriendsNear (pev->origin, 1024.0f) == 0) { pushRadioMessage (RADIO_REPORT_TEAM); - m_askCheckTime = engine.timebase () + rng.getFloat (45.0f, 80.0f); + m_askCheckTime = game.timebase () + rng.getFloat (45.0f, 80.0f); // make sure everyone else will not ask next few moments - for (int i = 0; i < engine.maxClients (); i++) { + for (int i = 0; i < game.maxClients (); i++) { auto bot = bots.getBot (i); if (bot && bot->m_notKilled) { - bot->m_askCheckTime = engine.timebase () + rng.getFloat (5.0f, 10.0f); + bot->m_askCheckTime = game.timebase () + rng.getFloat (5.0f, 10.0f); } } } @@ -3295,27 +3200,27 @@ void Bot::spraypaint_ (void) { m_aimFlags |= AIM_ENTITY; // bot didn't spray this round? - if (m_timeLogoSpray < engine.timebase () && task ()->time > engine.timebase ()) { - makeVectors (pev->v_angle); - Vector sprayOrigin = eyePos () + g_pGlobals->v_forward * 128.0f; + if (m_timeLogoSpray < game.timebase () && getTask ()->time > game.timebase ()) { + game.makeVectors (pev->v_angle); + Vector sprayOrigin = getEyesPos () + game.vec.forward * 128.0f; TraceResult tr; - engine.testLine (eyePos (), sprayOrigin, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (getEyesPos (), sprayOrigin, TRACE_IGNORE_MONSTERS, ent (), &tr); // no wall in front? - if (tr.flFraction >= 1.0f) + if (tr.flFraction >= 1.0f) { sprayOrigin.z -= 128.0f; - + } m_entity = sprayOrigin; - if (task ()->time - 0.5f < engine.timebase ()) { + if (getTask ()->time - 0.5f < game.timebase ()) { // emit spraycan sound - g_engfuncs.pfnEmitSound (ent (), CHAN_VOICE, "player/sprayer.wav", 1.0f, ATTN_NORM, 0, 100); - engine.testLine (eyePos (), eyePos () + g_pGlobals->v_forward * 128.0f, TRACE_IGNORE_MONSTERS, ent (), &tr); + engfuncs.pfnEmitSound (ent (), CHAN_VOICE, "player/sprayer.wav", 1.0f, ATTN_NORM, 0, 100); + game.testLine (getEyesPos (), getEyesPos () + game.vec.forward * 128.0f, TRACE_IGNORE_MONSTERS, ent (), &tr); // paint the actual logo decal - traceDecals (pev, &tr, m_logotypeIndex); - m_timeLogoSpray = engine.timebase () + rng.getFloat (60.0f, 90.0f); + util.traceDecals (pev, &tr, m_logotypeIndex); + m_timeLogoSpray = game.timebase () + rng.getFloat (60.0f, 90.0f); } } else { @@ -3324,7 +3229,7 @@ void Bot::spraypaint_ (void) { m_moveToGoal = false; m_checkTerrain = false; - m_navTimeset = engine.timebase (); + m_navTimeset = game.timebase (); m_moveSpeed = 0.0f; m_strafeSpeed = 0.0f; @@ -3335,20 +3240,20 @@ void Bot::huntEnemy_ (void) { m_aimFlags |= AIM_NAVPOINT; // if we've got new enemy... - if (!engine.isNullEntity (m_enemy) || engine.isNullEntity (m_lastEnemy)) { + if (!game.isNullEntity (m_enemy) || game.isNullEntity (m_lastEnemy)) { // forget about it... clearTask (TASK_HUNTENEMY); m_prevGoalIndex = INVALID_WAYPOINT_INDEX; } - else if (engine.getTeam (m_lastEnemy) == m_team) { + else if (game.getTeam (m_lastEnemy) == m_team) { // don't hunt down our teammate... clearTask (TASK_HUNTENEMY); m_prevGoalIndex = INVALID_WAYPOINT_INDEX; m_lastEnemy = nullptr; } - else if (processNavigation ()) // reached last enemy pos? + else if (updateNavigation ()) // reached last enemy pos? { // forget about it... completeTask (); @@ -3361,7 +3266,7 @@ void Bot::huntEnemy_ (void) { clearSearchNodes (); int destIndex = INVALID_WAYPOINT_INDEX; - int goal = task ()->data; + int goal = getTask ()->data; // is there a remembered index? if (waypoints.exists (goal)) { @@ -3375,7 +3280,7 @@ void Bot::huntEnemy_ (void) { // remember index m_prevGoalIndex = destIndex; - task ()->data = destIndex; + getTask ()->data = destIndex; if (destIndex != m_currentWaypointIndex) { searchPath (m_currentWaypointIndex, destIndex, SEARCH_PATH_FASTEST); @@ -3387,7 +3292,7 @@ void Bot::huntEnemy_ (void) { // then make him move slow if near enemy if (!(m_currentTravelFlags & PATHFLAG_JUMP)) { if (m_currentWaypointIndex != INVALID_WAYPOINT_INDEX) { - if (m_currentPath->radius < 32.0f && !isOnLadder () && !isInWater () && m_seeEnemyTime + 4.0f > engine.timebase () && m_difficulty < 3) { + if (m_currentPath->radius < 32.0f && !isOnLadder () && !isInWater () && m_seeEnemyTime + 4.0f > game.timebase () && m_difficulty < 3) { pev->button |= IN_DUCK; } } @@ -3402,23 +3307,23 @@ void Bot::huntEnemy_ (void) { void Bot::seekCover_ (void) { m_aimFlags |= AIM_NAVPOINT; - if (!isAlive (m_lastEnemy)) { + if (!util.isAlive (m_lastEnemy)) { completeTask (); m_prevGoalIndex = INVALID_WAYPOINT_INDEX; } // reached final waypoint? - else if (processNavigation ()) { + else if (updateNavigation ()) { // yep. activate hide behaviour completeTask (); m_prevGoalIndex = INVALID_WAYPOINT_INDEX; // start hide task - startTask (TASK_HIDE, TASKPRI_HIDE, INVALID_WAYPOINT_INDEX, engine.timebase () + rng.getFloat (3.0f, 12.0f), false); + startTask (TASK_HIDE, TASKPRI_HIDE, INVALID_WAYPOINT_INDEX, game.timebase () + rng.getFloat (3.0f, 12.0f), false); Vector dest = m_lastEnemyOrigin; // get a valid look direction - getCampDir (&dest); + getCampDirection (&dest); m_aimFlags |= AIM_CAMP; m_camp = dest; @@ -3451,7 +3356,7 @@ void Bot::seekCover_ (void) { m_currentPath->campEndY = dest.y; } - if (m_reloadState == RELOAD_NONE && ammoClip () < 5 && ammo () != 0) { + if (m_reloadState == RELOAD_NONE && getAmmoInClip () < 5 && getAmmo () != 0) { m_reloadState = RELOAD_PRIMARY; } m_moveSpeed = 0.0f; @@ -3465,14 +3370,14 @@ void Bot::seekCover_ (void) { clearSearchNodes (); int destIndex = INVALID_WAYPOINT_INDEX; - if (task ()->data != INVALID_WAYPOINT_INDEX) { - destIndex = task ()->data; + if (getTask ()->data != INVALID_WAYPOINT_INDEX) { + destIndex = getTask ()->data; } else { destIndex = getCoverPoint (usesSniper () ? 256.0f : 512.0f); if (destIndex == INVALID_WAYPOINT_INDEX) { - m_retreatTime = engine.timebase () + rng.getFloat (5.0f, 10.0f); + m_retreatTime = game.timebase () + rng.getFloat (5.0f, 10.0f); m_prevGoalIndex = INVALID_WAYPOINT_INDEX; completeTask (); @@ -3482,7 +3387,7 @@ void Bot::seekCover_ (void) { m_campDirection = 0; m_prevGoalIndex = destIndex; - task ()->data = destIndex; + getTask ()->data = destIndex; if (destIndex != m_currentWaypointIndex) { searchPath (m_currentWaypointIndex, destIndex, SEARCH_PATH_FASTEST); @@ -3494,7 +3399,7 @@ void Bot::attackEnemy_ (void) { m_moveToGoal = false; m_checkTerrain = false; - if (!engine.isNullEntity (m_enemy)) { + if (!game.isNullEntity (m_enemy)) { ignoreCollision (); if (isOnLadder ()) { @@ -3511,14 +3416,14 @@ void Bot::attackEnemy_ (void) { completeTask (); m_destOrigin = m_lastEnemyOrigin; } - m_navTimeset = engine.timebase (); + m_navTimeset = game.timebase (); } void Bot::pause_ (void) { m_moveToGoal = false; m_checkTerrain = false; - m_navTimeset = engine.timebase (); + m_navTimeset = game.timebase (); m_moveSpeed = 0.0f; m_strafeSpeed = 0.0f; @@ -3532,8 +3437,8 @@ void Bot::pause_ (void) { if (m_moveSpeed < -pev->maxspeed) { m_moveSpeed = -pev->maxspeed; } - makeVectors (pev->v_angle); - m_camp = eyePos () + g_pGlobals->v_forward * 500.0f; + game.makeVectors (pev->v_angle); + m_camp = getEyesPos () + game.vec.forward * 500.0f; m_aimFlags |= AIM_OVERRIDE; m_wantsToFire = true; @@ -3543,7 +3448,7 @@ void Bot::pause_ (void) { } // stop camping if time over or gets hurt by something else than bullets - if (task ()->time < engine.timebase () || m_lastDamageType > 0) { + if (getTask ()->time < game.timebase () || m_lastDamageType > 0) { completeTask (); } } @@ -3551,10 +3456,10 @@ void Bot::pause_ (void) { void Bot::blind_ (void) { m_moveToGoal = false; m_checkTerrain = false; - m_navTimeset = engine.timebase (); + m_navTimeset = game.timebase (); // if bot remembers last enemy position - if (m_difficulty >= 2 && !m_lastEnemyOrigin.empty () && isPlayer (m_lastEnemy) && !usesSniper ()) { + if (m_difficulty >= 2 && !m_lastEnemyOrigin.empty () && util.isPlayer (m_lastEnemy) && !usesSniper ()) { m_lookAt = m_lastEnemyOrigin; // face last enemy m_wantsToFire = true; // and shoot it } @@ -3563,7 +3468,7 @@ void Bot::blind_ (void) { m_strafeSpeed = m_blindSidemoveSpeed; pev->button |= m_blindButton; - if (m_blindTime < engine.timebase ()) { + if (m_blindTime < game.timebase ()) { completeTask (); } } @@ -3578,7 +3483,7 @@ void Bot::camp_ (void) { m_checkTerrain = false; m_moveToGoal = false; - if (m_team == TEAM_COUNTER && g_bombPlanted && m_defendedBomb && !isBombDefusing (waypoints.getBombPos ()) && !isOutOfBombTimer ()) { + if (m_team == TEAM_COUNTER && bots.isBombPlanted () && m_defendedBomb && !isBombDefusing (waypoints.getBombPos ()) && !isOutOfBombTimer ()) { m_defendedBomb = false; completeTask (); } @@ -3588,16 +3493,16 @@ void Bot::camp_ (void) { setIdealReactionTimers (); m_idealReactionTime *= 0.5f; - m_navTimeset = engine.timebase (); - m_timeCamping = engine.timebase (); + m_navTimeset = game.timebase (); + m_timeCamping = game.timebase (); m_moveSpeed = 0.0f; m_strafeSpeed = 0.0f; getValidPoint (); - if (m_nextCampDirTime < engine.timebase ()) { - m_nextCampDirTime = engine.timebase () + rng.getFloat (2.0f, 5.0f); + if (m_nextCampDirTime < game.timebase ()) { + m_nextCampDirTime = game.timebase () + rng.getFloat (2.0f, 5.0f); if (m_currentPath->flags & FLAG_CAMP) { Vector dest; @@ -3661,14 +3566,15 @@ void Bot::camp_ (void) { m_camp = waypoints[searchCampDir ()].origin; } } - else + else { m_camp = waypoints[searchCampDir ()].origin; + } } // press remembered crouch button pev->button |= m_campButtons; // stop camping if time over or gets hurt by something else than bullets - if (task ()->time < engine.timebase () || m_lastDamageType > 0) { + if (getTask ()->time < game.timebase () || m_lastDamageType > 0) { completeTask (); } } @@ -3682,7 +3588,7 @@ void Bot::hide_ (void) { setIdealReactionTimers (); m_idealReactionTime *= 0.5f; - m_navTimeset = engine.timebase (); + m_navTimeset = game.timebase (); m_moveSpeed = 0.0f; m_strafeSpeed = 0.0f; @@ -3705,7 +3611,7 @@ void Bot::hide_ (void) { m_campButtons = 0; m_prevGoalIndex = INVALID_WAYPOINT_INDEX; - if (!engine.isNullEntity (m_enemy)) { + if (!game.isNullEntity (m_enemy)) { attackMovement (); } return; @@ -3726,14 +3632,14 @@ void Bot::hide_ (void) { } pev->button |= m_campButtons; - m_navTimeset = engine.timebase (); + m_navTimeset = game.timebase (); if (!m_isReloading) { checkReload (); } // stop camping if time over or gets hurt by something else than bullets - if (task ()->time < engine.timebase () || m_lastDamageType > 0) { + if (getTask ()->time < game.timebase () || m_lastDamageType > 0) { completeTask (); } } @@ -3746,7 +3652,7 @@ void Bot::moveToPos_ (void) { } // reached destination? - if (processNavigation ()) { + if (updateNavigation ()) { completeTask (); // we're done m_prevGoalIndex = INVALID_WAYPOINT_INDEX; @@ -3758,7 +3664,7 @@ void Bot::moveToPos_ (void) { clearSearchNodes (); int destIndex = INVALID_WAYPOINT_INDEX; - int goal = task ()->data; + int goal = getTask ()->data; if (waypoints.exists (goal)) { destIndex = goal; @@ -3768,7 +3674,7 @@ void Bot::moveToPos_ (void) { } if (waypoints.exists (destIndex)) { m_prevGoalIndex = destIndex; - task ()->data = destIndex; + getTask ()->data = destIndex; searchPath (m_currentWaypointIndex, destIndex, m_pathType); } @@ -3783,15 +3689,18 @@ void Bot::plantBomb_ (void) { // we're still got the C4? if (m_hasC4) { - selectWeaponByName ("weapon_c4"); - if (isAlive (m_enemy) || !m_inBombZone) { + if (m_currentWeapon != WEAPON_C4) { + selectWeaponByName ("weapon_c4"); + } + + if (util.isAlive (m_enemy) || !m_inBombZone) { completeTask (); } else { m_moveToGoal = false; m_checkTerrain = false; - m_navTimeset = engine.timebase (); + m_navTimeset = game.timebase (); if (m_currentPath->flags & FLAG_CROUCH) { pev->button |= (IN_ATTACK | IN_DUCK); @@ -3818,10 +3727,10 @@ void Bot::plantBomb_ (void) { float guardTime = mp_c4timer.flt () * 0.5f + mp_c4timer.flt () * 0.25f; // push camp task on to stack - startTask (TASK_CAMP, TASKPRI_CAMP, INVALID_WAYPOINT_INDEX, engine.timebase () + guardTime, true); + startTask (TASK_CAMP, TASKPRI_CAMP, INVALID_WAYPOINT_INDEX, game.timebase () + guardTime, true); // push move command - startTask (TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, index, engine.timebase () + guardTime, true); + startTask (TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, index, game.timebase () + guardTime, true); if (waypoints[index].vis.crouch <= waypoints[index].vis.stand) { m_campButtons |= IN_DUCK; @@ -3838,10 +3747,10 @@ void Bot::bombDefuse_ (void) { float defuseRemainingTime = fullDefuseTime; if (m_hasProgressBar /*&& isOnFloor ()*/) { - defuseRemainingTime = fullDefuseTime - engine.timebase (); + defuseRemainingTime = fullDefuseTime - game.timebase (); } - bool pickupExists = !engine.isNullEntity (m_pickupItem); + bool pickupExists = !game.isNullEntity (m_pickupItem); const Vector &bombPos = pickupExists ? m_pickupItem->v.origin : waypoints.getBombPos (); if (pickupExists) { @@ -3854,7 +3763,6 @@ void Bot::bombDefuse_ (void) { // exception: bomb has been defused if (bombPos.empty ()) { defuseError = true; - g_bombPlanted = false; if (m_numFriendsLeft != 0 && rng.chance (50)) { if (timeToBlowUp <= 3.0) { @@ -3913,7 +3821,7 @@ void Bot::bombDefuse_ (void) { // bot is reloading and we close enough to start defusing if (m_isReloading && (bombPos - pev->origin).length2D () < 80.0f) { - if (m_numEnemiesLeft == 0 || timeToBlowUp < fullDefuseTime + 7.0f || ((ammoClip () > 8 && m_reloadState == RELOAD_PRIMARY) || (ammoClip () > 5 && m_reloadState == RELOAD_SECONDARY))) { + if (m_numEnemiesLeft == 0 || timeToBlowUp < fullDefuseTime + 7.0f || ((getAmmoInClip () > 8 && m_reloadState == RELOAD_PRIMARY) || (getAmmoInClip () > 5 && m_reloadState == RELOAD_SECONDARY))) { int weaponIndex = bestWeaponCarried (); // just select knife and then select weapon @@ -3942,7 +3850,7 @@ void Bot::bombDefuse_ (void) { pev->button |= IN_USE; // if defusing is not already started, maybe crouch before - if (!m_hasProgressBar && m_duckDefuseCheckTime < engine.timebase ()) { + if (!m_hasProgressBar && m_duckDefuseCheckTime < game.timebase ()) { if (m_difficulty >= 2 && m_numEnemiesLeft != 0) { m_duckDefuse = true; } @@ -3968,7 +3876,7 @@ void Bot::bombDefuse_ (void) { m_duckDefuse = true; // duck } } - m_duckDefuseCheckTime = engine.timebase () + 1.5f; + m_duckDefuseCheckTime = game.timebase () + 1.5f; } // press duck button @@ -3984,7 +3892,7 @@ void Bot::bombDefuse_ (void) { pev->button |= IN_USE; m_reloadState = RELOAD_NONE; - m_navTimeset = engine.timebase (); + m_navTimeset = game.timebase (); // don't move when defusing m_moveToGoal = false; @@ -4002,12 +3910,13 @@ void Bot::bombDefuse_ (void) { } } } - else + else { completeTask (); + } } void Bot::followUser_ (void) { - if (engine.isNullEntity (m_targetEntity) || !isAlive (m_targetEntity)) { + if (game.isNullEntity (m_targetEntity) || !util.isAlive (m_targetEntity)) { m_targetEntity = nullptr; completeTask (); @@ -4015,12 +3924,12 @@ void Bot::followUser_ (void) { } if (m_targetEntity->v.button & IN_ATTACK) { - makeVectors (m_targetEntity->v.v_angle); + game.makeVectors (m_targetEntity->v.v_angle); TraceResult tr; - engine.testLine (m_targetEntity->v.origin + m_targetEntity->v.view_ofs, g_pGlobals->v_forward * 500.0f, TRACE_IGNORE_EVERYTHING, ent (), &tr); + game.testLine (m_targetEntity->v.origin + m_targetEntity->v.view_ofs, game.vec.forward * 500.0f, TRACE_IGNORE_EVERYTHING, ent (), &tr); - if (!engine.isNullEntity (tr.pHit) && isPlayer (tr.pHit) && engine.getTeam (tr.pHit) != m_team) { + if (!game.isNullEntity (tr.pHit) && util.isPlayer (tr.pHit) && game.getTeam (tr.pHit) != m_team) { m_targetEntity = nullptr; m_lastEnemy = tr.pHit; m_lastEnemyOrigin = tr.pHit->v.origin; @@ -4034,7 +3943,7 @@ void Bot::followUser_ (void) { m_moveSpeed = m_targetEntity->v.maxspeed; } - if (m_reloadState == RELOAD_NONE && ammo () != 0) { + if (m_reloadState == RELOAD_NONE && getAmmo () != 0) { m_reloadState = RELOAD_PRIMARY; } @@ -4045,10 +3954,10 @@ void Bot::followUser_ (void) { m_moveSpeed = 0.0f; if (m_followWaitTime == 0.0f) { - m_followWaitTime = engine.timebase (); + m_followWaitTime = game.timebase (); } else { - if (m_followWaitTime + 3.0f < engine.timebase ()) { + if (m_followWaitTime + 3.0f < game.timebase ()) { // stop following if we have been waiting too long m_targetEntity = nullptr; @@ -4070,8 +3979,8 @@ void Bot::followUser_ (void) { } // reached destination? - if (processNavigation ()) { - task ()->data = INVALID_WAYPOINT_INDEX; + if (updateNavigation ()) { + getTask ()->data = INVALID_WAYPOINT_INDEX; } // didn't choose goal waypoint yet? @@ -4090,7 +3999,7 @@ void Bot::followUser_ (void) { if (waypoints.exists (destIndex) && waypoints.exists (m_currentWaypointIndex)) { m_prevGoalIndex = destIndex; - task ()->data = destIndex; + getTask ()->data = destIndex; // always take the shortest path searchPath (m_currentWaypointIndex, destIndex, SEARCH_PATH_FASTEST); @@ -4111,7 +4020,7 @@ void Bot::throwExplosive_ (void) { m_moveSpeed = 0.0f; m_moveToGoal = false; } - else if (!(m_states & STATE_SUSPECT_ENEMY) && !engine.isNullEntity (m_enemy)) { + else if (!(m_states & STATE_SUSPECT_ENEMY) && !game.isNullEntity (m_enemy)) { dest = m_enemy->v.origin + m_enemy->v.velocity.make2D () * 0.55f; } m_isUsingGrenade = true; @@ -4121,21 +4030,21 @@ void Bot::throwExplosive_ (void) { if ((pev->origin - dest).lengthSq () < cr::square (400.0f)) { // heck, I don't wanna blow up myself - m_grenadeCheckTime = engine.timebase () + MAX_GRENADE_TIMER; + m_grenadeCheckTime = game.timebase () + MAX_GRENADE_TIMER; selectBestWeapon (); completeTask (); return; } - m_grenade = calcThrow (eyePos (), dest); + m_grenade = calcThrow (getEyesPos (), dest); if (m_grenade.lengthSq () < 100.0f) { m_grenade = calcToss (pev->origin, dest); } if (m_grenade.lengthSq () <= 100.0f) { - m_grenadeCheckTime = engine.timebase () + MAX_GRENADE_TIMER; + m_grenadeCheckTime = game.timebase () + MAX_GRENADE_TIMER; selectBestWeapon (); completeTask (); @@ -4143,7 +4052,7 @@ void Bot::throwExplosive_ (void) { else { auto grenade = correctGrenadeVelocity ("hegrenade.mdl"); - if (engine.isNullEntity (grenade)) { + if (game.isNullEntity (grenade)) { if (m_currentWeapon != WEAPON_EXPLOSIVE && !m_grenadeRequested) { if (pev->weapons & (1 << WEAPON_EXPLOSIVE)) { m_grenadeRequested = true; @@ -4176,7 +4085,7 @@ void Bot::throwFlashbang_ (void) { m_moveSpeed = 0.0f; m_moveToGoal = false; } - else if (!(m_states & STATE_SUSPECT_ENEMY) && !engine.isNullEntity (m_enemy)) { + else if (!(m_states & STATE_SUSPECT_ENEMY) && !game.isNullEntity (m_enemy)) { dest = m_enemy->v.origin + m_enemy->v.velocity.make2D () * 0.55f; } @@ -4187,21 +4096,21 @@ void Bot::throwFlashbang_ (void) { if ((pev->origin - dest).lengthSq () < cr::square (400.0f)) { // heck, I don't wanna blow up myself - m_grenadeCheckTime = engine.timebase () + MAX_GRENADE_TIMER; + m_grenadeCheckTime = game.timebase () + MAX_GRENADE_TIMER; selectBestWeapon (); completeTask (); return; } - m_grenade = calcThrow (eyePos (), dest); + m_grenade = calcThrow (getEyesPos (), dest); if (m_grenade.lengthSq () < 100.0f) { m_grenade = calcToss (pev->origin, dest); } if (m_grenade.lengthSq () <= 100.0f) { - m_grenadeCheckTime = engine.timebase () + MAX_GRENADE_TIMER; + m_grenadeCheckTime = game.timebase () + MAX_GRENADE_TIMER; selectBestWeapon (); completeTask (); @@ -4209,7 +4118,7 @@ void Bot::throwFlashbang_ (void) { else { auto grenade = correctGrenadeVelocity ("flashbang.mdl"); - if (engine.isNullEntity (grenade)) { + if (game.isNullEntity (grenade)) { if (m_currentWeapon != WEAPON_FLASHBANG && !m_grenadeRequested) { if (pev->weapons & (1 << WEAPON_FLASHBANG)) { m_grenadeRequested = true; @@ -4250,12 +4159,13 @@ void Bot::throwSmoke_ (void) { Vector src = m_lastEnemyOrigin - pev->velocity; // predict where the enemy is in 0.5 secs - if (!engine.isNullEntity (m_enemy)) + if (!game.isNullEntity (m_enemy)) { src = src + m_enemy->v.velocity * 0.5f; + } - m_grenade = (src - eyePos ()).normalize (); + m_grenade = (src - getEyesPos ()).normalize (); - if (task ()->time < engine.timebase ()) { + if (getTask ()->time < game.timebase ()) { completeTask (); return; } @@ -4265,7 +4175,7 @@ void Bot::throwSmoke_ (void) { m_grenadeRequested = true; selectWeaponByName ("weapon_smokegrenade"); - task ()->time = engine.timebase () + 1.2f; + getTask ()->time = game.timebase () + 1.2f; } else { m_grenadeRequested = false; @@ -4284,7 +4194,7 @@ void Bot::throwSmoke_ (void) { } void Bot::doublejump_ (void) { - if (!isAlive (m_doubleJumpEntity) || (m_aimFlags & AIM_ENEMY) || (m_travelStartIndex != INVALID_WAYPOINT_INDEX && task ()->time + (waypoints.calculateTravelTime (pev->maxspeed, waypoints[m_travelStartIndex].origin, m_doubleJumpOrigin) + 11.0f) < engine.timebase ())) { + if (!util.isAlive (m_doubleJumpEntity) || (m_aimFlags & AIM_ENEMY) || (m_travelStartIndex != INVALID_WAYPOINT_INDEX && getTask ()->time + (waypoints.calculateTravelTime (pev->maxspeed, waypoints[m_travelStartIndex].origin, m_doubleJumpOrigin) + 11.0f) < game.timebase ())) { resetDoubleJump (); return; } @@ -4294,29 +4204,29 @@ void Bot::doublejump_ (void) { m_moveToGoal = false; m_checkTerrain = false; - m_navTimeset = engine.timebase (); + m_navTimeset = game.timebase (); 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 < engine.timebase ()) { + if (m_duckForJump < game.timebase ()) { pev->button |= IN_DUCK; } else if (inJump && !(m_oldButtons & IN_JUMP)) { pev->button |= IN_JUMP; } - makeVectors (Vector (0.0f, pev->angles.y, 0.0f)); + game.makeVectors (Vector (0.0f, pev->angles.y, 0.0f)); Vector src = pev->origin + Vector (0.0f, 0.0f, 45.0f); - Vector dest = src + g_pGlobals->v_up * 256.0f; + Vector dest = src + game.vec.up * 256.0f; TraceResult tr; - engine.testLine (src, dest, TRACE_IGNORE_NONE, ent (), &tr); + game.testLine (src, dest, TRACE_IGNORE_NONE, ent (), &tr); if (tr.flFraction < 1.0f && tr.pHit == m_doubleJumpEntity && inJump) { - m_duckForJump = engine.timebase () + rng.getFloat (3.0f, 5.0f); - task ()->time = engine.timebase (); + m_duckForJump = game.timebase () + rng.getFloat (3.0f, 5.0f); + getTask ()->time = game.timebase (); } return; } @@ -4326,8 +4236,8 @@ void Bot::doublejump_ (void) { m_destOrigin = m_doubleJumpOrigin; } - if (processNavigation ()) { - task ()->data = INVALID_WAYPOINT_INDEX; + if (updateNavigation ()) { + getTask ()->data = INVALID_WAYPOINT_INDEX; } // didn't choose goal waypoint yet? @@ -4338,7 +4248,7 @@ void Bot::doublejump_ (void) { if (waypoints.exists (destIndex)) { m_prevGoalIndex = destIndex; - task ()->data = destIndex; + getTask ()->data = destIndex; m_travelStartIndex = m_currentWaypointIndex; // always take the shortest path @@ -4357,7 +4267,7 @@ void Bot::doublejump_ (void) { void Bot::escapeFromBomb_ (void) { m_aimFlags |= AIM_NAVPOINT; - if (!g_bombPlanted) { + if (!bots.isBombPlanted ()) { completeTask (); } @@ -4370,7 +4280,7 @@ void Bot::escapeFromBomb_ (void) { } // reached destination? - if (processNavigation ()) { + if (updateNavigation ()) { completeTask (); // we're done // press duck button if we still have some enemies @@ -4379,7 +4289,7 @@ void Bot::escapeFromBomb_ (void) { } // we're reached destination point so just sit down and camp - startTask (TASK_CAMP, TASKPRI_CAMP, INVALID_WAYPOINT_INDEX, engine.timebase () + 10.0f, true); + startTask (TASK_CAMP, TASKPRI_CAMP, INVALID_WAYPOINT_INDEX, game.timebase () + 10.0f, true); } // didn't choose goal waypoint yet? @@ -4410,11 +4320,11 @@ void Bot::escapeFromBomb_ (void) { completeTask (); // we're done // we have no destination point, so just sit down and camp - startTask (TASK_CAMP, TASKPRI_CAMP, INVALID_WAYPOINT_INDEX, engine.timebase () + 10.0f, true); + startTask (TASK_CAMP, TASKPRI_CAMP, INVALID_WAYPOINT_INDEX, game.timebase () + 10.0f, true); return; } m_prevGoalIndex = lastSelectedGoal; - task ()->data = lastSelectedGoal; + getTask ()->data = lastSelectedGoal; searchPath (m_currentWaypointIndex, lastSelectedGoal, SEARCH_PATH_FASTEST); } @@ -4424,7 +4334,7 @@ void Bot::shootBreakable_ (void) { m_aimFlags |= AIM_OVERRIDE; // Breakable destroyed? - if (engine.isNullEntity (lookupBreakable ())) { + if (game.isNullEntity (lookupBreakable ())) { completeTask (); return; } @@ -4432,13 +4342,13 @@ void Bot::shootBreakable_ (void) { m_checkTerrain = false; m_moveToGoal = false; - m_navTimeset = engine.timebase (); + m_navTimeset = game.timebase (); Vector src = m_breakableOrigin; m_camp = src; // is bot facing the breakable? - if (getShootingConeDeviation (ent (), src) >= 0.90f) { + if (util.getShootingCone (ent (), src) >= 0.90f) { m_moveSpeed = 0.0f; m_strafeSpeed = 0.0f; @@ -4454,13 +4364,13 @@ void Bot::shootBreakable_ (void) { } void Bot::pickupItem_ () { - if (engine.isNullEntity (m_pickupItem)) { + if (game.isNullEntity (m_pickupItem)) { m_pickupItem = nullptr; completeTask (); return; } - Vector dest = engine.getAbsPos (m_pickupItem); + Vector dest = game.getAbsPos (m_pickupItem); m_destOrigin = dest; m_entity = dest; @@ -4478,30 +4388,31 @@ void Bot::pickupItem_ () { // near to weapon? if (itemDistance < 50.0f) { - int id = 0; + int index = 0; + auto &info = conf.getWeapons (); - for (id = 0; id < 7; id++) { - if (strcmp (g_weaponSelect[id].modelName, STRING (m_pickupItem->v.model) + 9) == 0) { + for (index = 0; index < 7; index++) { + if (strcmp (info[index].model, STRING (m_pickupItem->v.model) + 9) == 0) { break; } } - if (id < 7) { + if (index < 7) { // secondary weapon. i.e., pistol int wid = 0; - for (id = 0; id < 7; id++) { - if (pev->weapons & (1 << g_weaponSelect[id].id)) { - wid = id; + for (index = 0; index < 7; index++) { + if (pev->weapons & (1 << info[index].id)) { + wid = index; } } if (wid > 0) { selectWeaponById (wid); - engine.execBotCmd (ent (), "drop"); + game.execBotCmd (ent (), "drop"); if (hasShield ()) { - engine.execBotCmd (ent (), "drop"); // discard both shield and pistol + game.execBotCmd (ent (), "drop"); // discard both shield and pistol } } processBuyzoneEntering (BUYSTATE_PRIMARY_WEAPON); @@ -4512,7 +4423,7 @@ void Bot::pickupItem_ () { if (wid == WEAPON_SHIELD || wid > 6 || hasShield ()) { selectWeaponById (wid); - engine.execBotCmd (ent (), "drop"); + game.execBotCmd (ent (), "drop"); } if (!wid) { @@ -4543,7 +4454,7 @@ void Bot::pickupItem_ () { if (wid > 6) { selectWeaponById (wid); - engine.execBotCmd (ent (), "drop"); + game.execBotCmd (ent (), "drop"); } } break; @@ -4571,7 +4482,7 @@ void Bot::pickupItem_ () { case PICKUP_HOSTAGE: m_aimFlags |= AIM_ENTITY; - if (!isAlive (m_pickupItem)) { + if (!util.isAlive (m_pickupItem)) { // don't pickup dead hostages m_pickupItem = nullptr; completeTask (); @@ -4580,7 +4491,7 @@ void Bot::pickupItem_ () { } if (itemDistance < 50.0f) { - float angleToEntity = isInFOV (dest - eyePos ()); + float angleToEntity = isInFOV (dest - getEyesPos ()); // bot faces hostage? if (angleToEntity <= 10.0f) { @@ -4609,7 +4520,7 @@ void Bot::pickupItem_ () { case PICKUP_BUTTON: m_aimFlags |= AIM_ENTITY; - if (engine.isNullEntity (m_pickupItem) || m_buttonPushTime < engine.timebase ()) { + if (game.isNullEntity (m_pickupItem) || m_buttonPushTime < game.timebase ()) { completeTask (); m_pickupType = PICKUP_NONE; @@ -4617,7 +4528,7 @@ void Bot::pickupItem_ () { } // find angles from bot origin to entity... - float angleToEntity = isInFOV (dest - eyePos ()); + float angleToEntity = isInFOV (dest - getEyesPos ()); // near to the button? if (itemDistance < 90.0f) { @@ -4632,7 +4543,7 @@ void Bot::pickupItem_ () { m_pickupItem = nullptr; m_pickupType = PICKUP_NONE; - m_buttonPushTime = engine.timebase () + 3.0f; + m_buttonPushTime = game.timebase () + 3.0f; completeTask (); } @@ -4753,15 +4664,15 @@ void Bot::checkSpawnConditions (void) { // 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_checkWeaponSwitch && m_buyingFinished && m_spawnTime + rng.getFloat (5.0f, 7.5f) < engine.timebase ()) { + if (m_checkKnifeSwitch && !m_checkWeaponSwitch && m_buyingFinished && m_spawnTime + rng.getFloat (5.0f, 7.5f) < game.timebase ()) { if (rng.getInt (1, 100) < 2 && yb_spraypaints.boolean ()) { - startTask (TASK_SPRAY, TASKPRI_SPRAYLOGO, INVALID_WAYPOINT_INDEX, engine.timebase () + 1.0f, false); + startTask (TASK_SPRAY, TASKPRI_SPRAYLOGO, INVALID_WAYPOINT_INDEX, game.timebase () + 1.0f, false); } - if (m_difficulty >= 2 && rng.chance (m_personality == PERSONALITY_RUSHER ? 99 : 50) && !m_isReloading && (g_mapFlags & (MAP_CS | MAP_DE | MAP_ES | MAP_AS))) { + if (m_difficulty >= 2 && rng.chance (m_personality == PERSONALITY_RUSHER ? 99 : 50) && !m_isReloading && game.mapIs (MAP_CS | MAP_DE | MAP_ES | MAP_AS)) { if (yb_jasonmode.boolean ()) { selectSecondary (); - engine.execBotCmd (ent (), "drop"); + game.execBotCmd (ent (), "drop"); } else { selectWeaponByName ("weapon_knife"); @@ -4769,13 +4680,13 @@ void Bot::checkSpawnConditions (void) { } m_checkKnifeSwitch = false; - if (rng.chance (yb_user_follow_percent.integer ()) && engine.isNullEntity (m_targetEntity) && !m_isLeader && !m_hasC4 && rng.chance (50)) { + if (rng.chance (yb_user_follow_percent.integer ()) && game.isNullEntity (m_targetEntity) && !m_isLeader && !m_hasC4 && rng.chance (50)) { decideFollowUser (); } } // check if we already switched weapon mode - if (m_checkWeaponSwitch && m_buyingFinished && m_spawnTime + rng.getFloat (3.0f, 4.5f) < engine.timebase ()) { + if (m_checkWeaponSwitch && m_buyingFinished && m_spawnTime + rng.getFloat (3.0f, 4.5f) < game.timebase ()) { if (hasShield () && isShieldDrawn ()) { pev->button |= IN_ATTACK2; } @@ -4803,7 +4714,7 @@ void Bot::checkSpawnConditions (void) { } } -void Bot::ai (void) { +void Bot::runAI (void) { // 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) @@ -4822,18 +4733,18 @@ void Bot::ai (void) { m_viewDistance = m_maxViewDistance; } - if (m_blindTime > engine.timebase ()) { + if (m_blindTime > game.timebase ()) { m_maxViewDistance = 4096.0f; } m_moveSpeed = pev->maxspeed; - if (m_prevTime <= engine.timebase ()) { + if (m_prevTime <= game.timebase ()) { // 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 = engine.timebase () + 0.2f; + m_prevTime = game.timebase () + 0.2f; } // if there's some radio message to respond, check it @@ -4846,16 +4757,16 @@ void Bot::ai (void) { // some stuff required by by chatter engine if (yb_communication_type.integer () == 2) { - if ((m_states & STATE_SEEING_ENEMY) && !engine.isNullEntity (m_enemy)) { + if ((m_states & STATE_SEEING_ENEMY) && !game.isNullEntity (m_enemy)) { int hasFriendNearby = numFriendsNear (pev->origin, 512.0f); if (!hasFriendNearby && rng.chance (45) && (m_enemy->v.weapons & (1 << WEAPON_C4))) { pushChatterMessage (CHATTER_SPOT_THE_BOMBER); } - else if (!hasFriendNearby && rng.chance (45) && m_team == TEAM_TERRORIST && isPlayerVIP (m_enemy)) { + else if (!hasFriendNearby && rng.chance (45) && m_team == TEAM_TERRORIST && util.isPlayerVIP (m_enemy)) { pushChatterMessage (CHATTER_VIP_SPOTTED); } - else if (!hasFriendNearby && rng.chance (50) && engine.getTeam (m_enemy) != m_team && isGroupOfEnemies (m_enemy->v.origin, 2, 384)) { + else if (!hasFriendNearby && rng.chance (50) && game.getTeam (m_enemy) != m_team && isGroupOfEnemies (m_enemy->v.origin, 2, 384.0f)) { pushChatterMessage (CHATTER_SCARED_EMOTE); } else if (!hasFriendNearby && rng.chance (40) && ((m_enemy->v.weapons & (1 << WEAPON_AWP)) || (m_enemy->v.weapons & (1 << WEAPON_SCOUT)) || (m_enemy->v.weapons & (1 << WEAPON_G3SG1)) || (m_enemy->v.weapons & (1 << WEAPON_SG550)))) { @@ -4869,9 +4780,9 @@ void Bot::ai (void) { } // if bomb planted warn teammates ! - if (g_canSayBombPlanted && g_bombPlanted && m_team == TEAM_COUNTER) { - g_canSayBombPlanted = false; + if (bots.hasBombSay (BSS_NEED_TO_FIND_CHATTER) && bots.isBombPlanted () && m_team == TEAM_COUNTER) { pushChatterMessage (CHATTER_GOTTA_FIND_BOMB); + bots.clearBombSay (BSS_NEED_TO_FIND_CHATTER); } } Vector src, destination; @@ -4885,15 +4796,15 @@ void Bot::ai (void) { processTasks (); // execute current task updateAimDir (); // choose aim direction - processLookAngles (); // and turn to chosen aim direction + updateLookAngles (); // and turn to chosen aim direction // the bots wants to fire at something? - if (m_wantsToFire && !m_isUsingGrenade && m_shootTime <= engine.timebase ()) { + if (m_wantsToFire && !m_isUsingGrenade && m_shootTime <= game.timebase ()) { fireWeapons (); // if bot didn't fire a bullet try again next frame } // check for reloading - if (m_reloadCheckTime <= engine.timebase ()) { + if (m_reloadCheckTime <= game.timebase ()) { checkReload (); } @@ -4901,7 +4812,7 @@ void Bot::ai (void) { setIdealReactionTimers (); // calculate 2 direction vectors, 1 without the up/down component - const Vector &dirOld = m_destOrigin - (pev->origin + pev->velocity * calcThinkInterval ()); + const Vector &dirOld = m_destOrigin - (pev->origin + pev->velocity * getFrameInterval ()); const Vector &dirNormal = dirOld.normalize2D (); m_moveAngles = dirOld.toAngles (); @@ -4919,12 +4830,12 @@ void Bot::ai (void) { if ((m_currentPath->flags & FLAG_CROUCH) && !(m_currentPath->flags & (FLAG_CAMP | FLAG_GOAL))) { pev->button |= IN_DUCK; } - m_timeWaypointMove = engine.timebase (); + m_timeWaypointMove = game.timebase (); // special movement for swimming here if (isInWater ()) { // check if we need to go forward or back press the correct buttons - if (isInFOV (m_destOrigin - eyePos ()) > 90.0f) { + if (isInFOV (m_destOrigin - getEyesPos ()) > 90.0f) { pev->button |= IN_BACK; } else { @@ -4958,29 +4869,29 @@ void Bot::ai (void) { } // time to reach waypoint - if (m_navTimeset + getReachTime () < engine.timebase () && engine.isNullEntity (m_enemy)) { + if (m_navTimeset + getReachTime () < game.timebase () && game.isNullEntity (m_enemy)) { getValidPoint (); // clear these pointers, bot mingh be stuck getting to them - if (!engine.isNullEntity (m_pickupItem) && !m_hasProgressBar) { + if (!game.isNullEntity (m_pickupItem) && !m_hasProgressBar) { m_itemIgnore = m_pickupItem; } m_pickupItem = nullptr; m_breakableEntity = nullptr; - m_itemCheckTime = engine.timebase () + 5.0f; + m_itemCheckTime = game.timebase () + 5.0f; m_pickupType = PICKUP_NONE; } - if (m_duckTime >= engine.timebase ()) { + if (m_duckTime >= game.timebase ()) { pev->button |= IN_DUCK; } if (pev->button & IN_JUMP) { - m_jumpTime = engine.timebase (); + m_jumpTime = game.timebase (); } - if (m_jumpTime + 0.85f > engine.timebase ()) { + if (m_jumpTime + 0.85f > game.timebase ()) { if (!isOnFloor () && !isInWater ()) { pev->button |= IN_DUCK; } @@ -5008,7 +4919,7 @@ void Bot::ai (void) { checkParachute (); // display some debugging thingy to host entity - if (!engine.isNullEntity (g_hostEntity) && yb_debug.integer () >= 1) { + if (!game.isDedicated () && yb_debug.integer () >= 1) { showDebugOverlay (); } @@ -5020,14 +4931,14 @@ void Bot::ai (void) { void Bot::showDebugOverlay (void) { bool displayDebugOverlay = false; - if (g_hostEntity->v.iuser2 == index ()) { + if (game.getLocalEntity ()->v.iuser2 == index ()) { displayDebugOverlay = true; } if (!displayDebugOverlay && yb_debug.integer () >= 2) { Bot *nearest = nullptr; - if (findNearestPlayer (reinterpret_cast (&nearest), g_hostEntity, 128.0f, false, true, true, true) && nearest == this) { + if (util.findNearestPlayer (reinterpret_cast (&nearest), game.getLocalEntity (), 128.0f, false, true, true, true) && nearest == this) { displayDebugOverlay = true; } } @@ -5081,22 +4992,22 @@ void Bot::showDebugOverlay (void) { } if (!m_tasks.empty ()) { - if (taskID != taskId () || index != m_currentWaypointIndex || goal != task ()->data || timeDebugUpdate < engine.timebase ()) { + if (taskID != taskId () || index != m_currentWaypointIndex || goal != getTask ()->data || timeDebugUpdate < game.timebase ()) { taskID = taskId (); index = m_currentWaypointIndex; - goal = task ()->data; + goal = getTask ()->data; String enemy = "(none)"; - if (!engine.isNullEntity (m_enemy)) { + if (!game.isNullEntity (m_enemy)) { enemy = STRING (m_enemy->v.netname); } - else if (!engine.isNullEntity (m_lastEnemy)) { - enemy.format ("%s (L)", STRING (m_lastEnemy->v.netname)); + else if (!game.isNullEntity (m_lastEnemy)) { + enemy.assign ("%s (L)", STRING (m_lastEnemy->v.netname)); } String pickup = "(none)"; - if (!engine.isNullEntity (m_pickupItem)) { + if (!game.isNullEntity (m_pickupItem)) { pickup = STRING (m_pickupItem->v.netname); } String aimFlags; @@ -5105,15 +5016,15 @@ void Bot::showDebugOverlay (void) { bool hasFlag = m_aimFlags & (1 << i); if (hasFlag) { - aimFlags.formatAppend (" %s", flags[1 << i].chars ()); + aimFlags.append (" %s", flags[1 << i].chars ()); } } - String weapon = STRING (getWeaponData (true, nullptr, m_currentWeapon)); + String weapon = STRING (util.getWeaponAlias (true, nullptr, m_currentWeapon)); String debugData; - debugData.format ("\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 (), task ()->desire, weapon.chars (), ammoClip (), ammo (), m_isReloading ? " (R)" : "", m_moneyAmount, aimFlags.trim ().chars (), m_moveSpeed, m_strafeSpeed, index, m_prevGoalIndex, goal, m_navTimeset - engine.timebase (), pev->movetype, enemy.chars (), pickup.chars (), personalities[m_personality].chars ()); + debugData.assign ("\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 ()); - MessageWriter (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, Vector::null (), g_hostEntity) + MessageWriter (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, Vector::null (), game.getLocalEntity ()) .writeByte (TE_TEXTMESSAGE) .writeByte (1) .writeShort (MessageWriter::fs16 (-1, 1 << 13)) @@ -5132,25 +5043,25 @@ void Bot::showDebugOverlay (void) { .writeShort (MessageWriter::fu16 (1.0, 1 << 8)) .writeString (debugData.chars ()); - timeDebugUpdate = engine.timebase () + 1.0f; + timeDebugUpdate = game.timebase () + 1.0f; } // green = destination origin // blue = ideal angles // red = view angles - engine.drawLine (g_hostEntity, eyePos (), m_destOrigin, 10, 0, 0, 255, 0, 250, 5, 1, DRAW_ARROW); + game.drawLine (game.getLocalEntity (), getEyesPos (), m_destOrigin, 10, 0, 0, 255, 0, 250, 5, 1, DRAW_ARROW); - makeVectors (m_idealAngles); - engine.drawLine (g_hostEntity, eyePos () - Vector (0.0f, 0.0f, 16.0f), eyePos () + g_pGlobals->v_forward * 300.0f, 10, 0, 0, 0, 255, 250, 5, 1, DRAW_ARROW); + game.makeVectors (m_idealAngles); + game.drawLine (game.getLocalEntity (), getEyesPos () - Vector (0.0f, 0.0f, 16.0f), getEyesPos () + game.vec.forward * 300.0f, 10, 0, 0, 0, 255, 250, 5, 1, DRAW_ARROW); - makeVectors (pev->v_angle); - engine.drawLine (g_hostEntity, eyePos () - Vector (0.0f, 0.0f, 32.0f), eyePos () + g_pGlobals->v_forward * 300.0f, 10, 0, 255, 0, 0, 250, 5, 1, DRAW_ARROW); + game.makeVectors (pev->v_angle); + game.drawLine (game.getLocalEntity (), getEyesPos () - Vector (0.0f, 0.0f, 32.0f), getEyesPos () + game.vec.forward * 300.0f, 10, 0, 255, 0, 0, 250, 5, 1, DRAW_ARROW); // now draw line from source to destination for (size_t i = 0; i < m_path.length () && i + 1 < m_path.length (); i++) { - engine.drawLine (g_hostEntity, waypoints[m_path[i]].origin, waypoints[m_path[i + 1]].origin, 15, 0, 255, 100, 55, 200, 5, 1, DRAW_ARROW); + game.drawLine (game.getLocalEntity (), waypoints[m_path[i]].origin, waypoints[m_path[i + 1]].origin, 15, 0, 255, 100, 55, 200, 5, 1, DRAW_ARROW); } } } @@ -5158,7 +5069,7 @@ void Bot::showDebugOverlay (void) { bool Bot::hasHostage (void) { for (auto hostage : m_hostages) { - if (!engine.isNullEntity (hostage)) { + if (!game.isNullEntity (hostage)) { // don't care about dead hostages if (hostage->v.health <= 0.0f || (pev->origin - hostage->v.origin).lengthSq () > cr::square (600.0f)) { @@ -5171,11 +5082,13 @@ bool Bot::hasHostage (void) { return false; } -int Bot::ammo (void) { - if (g_weaponDefs[m_currentWeapon].ammo1 == -1 || g_weaponDefs[m_currentWeapon].ammo1 > MAX_WEAPONS - 1) { +int Bot::getAmmo (void) { + const auto &prop = conf.getWeaponProp (m_currentWeapon); + + if (prop.ammo1 == -1 || prop.ammo1 > MAX_WEAPONS - 1) { return 0; } - return m_ammo[g_weaponDefs[m_currentWeapon].ammo1]; + return m_ammo[prop.ammo1]; } void Bot::processDamage (edict_t *inflictor, int damage, int armor, int bits) { @@ -5183,13 +5096,13 @@ void Bot::processDamage (edict_t *inflictor, int damage, int armor, int bits) { // other player. m_lastDamageType = bits; - collectGoalExperience (damage, m_team); + collectGoalExperience (damage); - if (isPlayer (inflictor)) { - if (yb_tkpunish.boolean () && engine.getTeam (inflictor) == m_team && !isFakeClient (inflictor)) { + if (util.isPlayer (inflictor)) { + if (yb_tkpunish.boolean () && game.getTeam (inflictor) == m_team && !util.isFakeClient (inflictor)) { // alright, die you teamkiller!!! m_actualReactionTime = 0.0f; - m_seeEnemyTime = engine.timebase (); + m_seeEnemyTime = game.timebase (); m_enemy = inflictor; m_lastEnemy = m_enemy; @@ -5218,15 +5131,15 @@ void Bot::processDamage (edict_t *inflictor, int damage, int armor, int bits) { } clearTask (TASK_CAMP); - if (engine.isNullEntity (m_enemy) && m_team != engine.getTeam (inflictor)) { + if (game.isNullEntity (m_enemy) && m_team != game.getTeam (inflictor)) { m_lastEnemy = inflictor; m_lastEnemyOrigin = inflictor->v.origin; // FIXME - Bot doesn't necessary sees this enemy - m_seeEnemyTime = engine.timebase (); + m_seeEnemyTime = game.timebase (); } - if (!(g_gameFlags & GAME_CSDM)) { + if (!game.is (GAME_CSDM)) { collectDataExperience (inflictor, armor + damage); } } @@ -5246,9 +5159,9 @@ void Bot::processBlind (int alpha) { // it's used to make bot blind from the grenade. m_maxViewDistance = rng.getFloat (10.0f, 20.0f); - m_blindTime = engine.timebase () + static_cast (alpha - 200) / 16.0f; + m_blindTime = game.timebase () + static_cast (alpha - 200) / 16.0f; - if (m_blindTime < engine.timebase ()) { + if (m_blindTime < game.timebase ()) { return; } m_enemy = nullptr; @@ -5283,52 +5196,30 @@ void Bot::processBlind (int alpha) { } } -void Bot::collectGoalExperience (int damage, int team) { +void Bot::collectGoalExperience (int damage) { // gets called each time a bot gets damaged by some enemy. tries to achieve a statistic about most/less dangerous // waypoints for a destination goal used for pathfinding if (waypoints.length () < 1 || waypoints.hasChanged () || m_chosenGoalIndex < 0 || m_prevGoalIndex < 0) { return; } + auto experience = waypoints.getRawExperience (); // only rate goal waypoint if bot died because of the damage // FIXME: could be done a lot better, however this cares most about damage done by sniping or really deadly weapons - if (pev->health - damage <= 0) { - if (team == TEAM_TERRORIST) { - int value = (g_experienceData + (m_chosenGoalIndex * waypoints.length ()) + m_prevGoalIndex)->team0Value; - value -= static_cast (pev->health / 20); - - if (value < -MAX_GOAL_VALUE) { - value = -MAX_GOAL_VALUE; - } - else if (value > MAX_GOAL_VALUE) { - value = MAX_GOAL_VALUE; - } - (g_experienceData + (m_chosenGoalIndex * waypoints.length ()) + m_prevGoalIndex)->team0Value = static_cast (value); - } - else { - int value = (g_experienceData + (m_chosenGoalIndex * waypoints.length ()) + m_prevGoalIndex)->team1Value; - value -= static_cast (pev->health / 20); - - if (value < -MAX_GOAL_VALUE) { - value = -MAX_GOAL_VALUE; - } - else if (value > MAX_GOAL_VALUE) { - value = MAX_GOAL_VALUE; - } - (g_experienceData + (m_chosenGoalIndex * waypoints.length ()) + m_prevGoalIndex)->team1Value = static_cast (value); - } + if (pev->health - damage <= 0 && experience) { + (experience + (m_chosenGoalIndex * waypoints.length ()) + m_prevGoalIndex)->value[m_team] = cr::clamp (waypoints.getDangerValue (m_team, m_chosenGoalIndex, m_prevGoalIndex) - static_cast (pev->health / 20), -MAX_GOAL_VALUE, MAX_GOAL_VALUE); } } void Bot::collectDataExperience (edict_t *attacker, int damage) { // this function gets called each time a bot gets damaged by some enemy. sotores the damage (teamspecific) done by victim. - if (!isPlayer (attacker)) { + if (!util.isPlayer (attacker)) { return; } - int attackerTeam = engine.getTeam (attacker); + int attackerTeam = game.getTeam (attacker); int victimTeam = m_team; if (attackerTeam == victimTeam) { @@ -5341,6 +5232,7 @@ void Bot::collectDataExperience (edict_t *attacker, int damage) { if (bots.getBot (attacker) != nullptr) { bots.getBot (attacker)->m_goalValue += static_cast (damage); } + auto experience = waypoints.getRawExperience (); if (damage < 20) { return; // do not collect damage less than 20 @@ -5354,57 +5246,28 @@ void Bot::collectDataExperience (edict_t *attacker, int damage) { } if (pev->health > 20.0f) { - if (victimTeam == TEAM_TERRORIST) { - (g_experienceData + (victimIndex * waypoints.length ()) + victimIndex)->team0Damage++; - } - else { - (g_experienceData + (victimIndex * waypoints.length ()) + victimIndex)->team1Damage++; - } + auto damageData = (experience + (victimIndex * waypoints.length ()) + victimIndex); - if ((g_experienceData + (victimIndex * waypoints.length ()) + victimIndex)->team0Damage > MAX_DAMAGE_VALUE) { - (g_experienceData + (victimIndex * waypoints.length ()) + victimIndex)->team0Damage = MAX_DAMAGE_VALUE; - } - - if ((g_experienceData + (victimIndex * waypoints.length ()) + victimIndex)->team1Damage > MAX_DAMAGE_VALUE) { - (g_experienceData + (victimIndex * waypoints.length ()) + victimIndex)->team1Damage = MAX_DAMAGE_VALUE; + if (victimTeam == TEAM_TERRORIST || victimTeam == TEAM_COUNTER) { + damageData->damage[victimTeam] = cr::clamp (++damageData->damage[victimTeam], 0, MAX_DAMAGE_VALUE); } } - float updateDamage = isFakeClient (attacker) ? 10.0f : 7.0f; + float updateDamage = util.isFakeClient (attacker) ? 10.0f : 7.0f; // store away the damage done - if (victimTeam == TEAM_TERRORIST) { - int value = (g_experienceData + (victimIndex * waypoints.length ()) + attackerIndex)->team0Damage; - value += static_cast (damage / updateDamage); + int damageValue = cr::clamp (waypoints.getDangerDamage (m_team, victimIndex, attackerIndex) + static_cast (damage / updateDamage), 0, MAX_DAMAGE_VALUE); - if (value > MAX_DAMAGE_VALUE) { - value = MAX_DAMAGE_VALUE; - } - - if (value > g_highestDamageT) { - g_highestDamageT = value; - } - (g_experienceData + (victimIndex * waypoints.length ()) + attackerIndex)->team0Damage = static_cast (value); - } - else { - int value = (g_experienceData + (victimIndex * waypoints.length ()) + attackerIndex)->team1Damage; - value += static_cast (damage / updateDamage); - - if (value > MAX_DAMAGE_VALUE) { - value = MAX_DAMAGE_VALUE; - } - - if (value > g_highestDamageCT) { - g_highestDamageCT = value; - } - (g_experienceData + (victimIndex * waypoints.length ()) + attackerIndex)->team1Damage = static_cast (value); + if (damageValue > waypoints.getHighestDamageForTeam (m_team)) { + waypoints.setHighestDamageForTeam (m_team, damageValue); } + (experience + (victimIndex * waypoints.length ()) + attackerIndex)->damage[m_team] = damageValue; } void Bot::processChatterMessage (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_COUNTER && strcmp (tempMessage, "#CTs_Win") == 0) || (m_team == TEAM_TERRORIST && strcmp (tempMessage, "#Terrorists_Win") == 0)) { - if (g_timeRoundMid > engine.timebase ()) { + if (bots.getRoundMidTime () > game.timebase ()) { pushChatterMessage (CHATTER_QUICK_WON_ROUND); } else { @@ -5424,14 +5287,14 @@ void Bot::processChatterMessage (const char *tempMessage) { } void Bot::pushChatMessage (int type, bool isTeamSay) { - extern ConVar yb_chat; + auto &chat = conf.getChat (); - if (g_chatFactory[type].empty () || !yb_chat.boolean ()) { + if (chat[type].empty () || !yb_chat.boolean ()) { return; } - const char *pickedPhrase = g_chatFactory[type].random ().chars (); + auto pickedPhrase = chat[type].random ().chars (); - if (isEmptyStr (pickedPhrase)) { + if (util.isEmptyStr (pickedPhrase)) { return; } @@ -5443,22 +5306,22 @@ void Bot::dropWeaponForUser (edict_t *user, bool discardC4) { // this function, asks bot to discard his current primary weapon (or c4) to the user that requsted it with /drop* // command, very useful, when i'm don't have money to buy anything... ) - if (isAlive (user) && m_moneyAmount >= 2000 && hasPrimaryWeapon () && (user->v.origin - pev->origin).length () <= 450.0f) { + if (util.isAlive (user) && m_moneyAmount >= 2000 && hasPrimaryWeapon () && (user->v.origin - pev->origin).length () <= 450.0f) { m_aimFlags |= AIM_ENTITY; m_lookAt = user->v.origin; if (discardC4) { selectWeaponByName ("weapon_c4"); - engine.execBotCmd (ent (), "drop"); + game.execBotCmd (ent (), "drop"); } else { selectBestWeapon (); - engine.execBotCmd (ent (), "drop"); + game.execBotCmd (ent (), "drop"); } m_pickupItem = nullptr; m_pickupType = PICKUP_NONE; - m_itemCheckTime = engine.timebase () + 5.0f; + m_itemCheckTime = game.timebase () + 5.0f; if (m_inBuyZone) { m_ignoreBuyDelay = true; @@ -5466,7 +5329,7 @@ void Bot::dropWeaponForUser (edict_t *user, bool discardC4) { m_buyState = BUYSTATE_PRIMARY_WEAPON; pushMsgQueue (GAME_MSG_PURCHASE); - m_nextBuyTime = engine.timebase (); + m_nextBuyTime = game.timebase (); } } } @@ -5477,8 +5340,8 @@ void Bot::startDoubleJump (edict_t *ent) { m_doubleJumpOrigin = ent->v.origin; m_doubleJumpEntity = ent; - startTask (TASK_DOUBLEJUMP, TASKPRI_DOUBLEJUMP, INVALID_WAYPOINT_INDEX, engine.timebase (), true); - sayTeam (format ("Ok %s, i will help you!", STRING (ent->v.netname))); + startTask (TASK_DOUBLEJUMP, TASKPRI_DOUBLEJUMP, INVALID_WAYPOINT_INDEX, game.timebase (), true); + sayTeam (util.format ("Ok %s, i will help you!", STRING (ent->v.netname))); } void Bot::resetDoubleJump (void) { @@ -5492,6 +5355,9 @@ void Bot::resetDoubleJump (void) { } void Bot::sayDebug (const char *format, ...) { + if (game.isDedicated ()) { + return; + } int level = yb_debug.integer (); if (level <= 2) { @@ -5505,21 +5371,21 @@ void Bot::sayDebug (const char *format, ...) { va_end (ap); String printBuf; - printBuf.format ("%s: %s", STRING (pev->netname), buffer); + printBuf.assign ("%s: %s", STRING (pev->netname), buffer); bool playMessage = false; - if (level == 3 && !engine.isNullEntity (g_hostEntity) && g_hostEntity->v.iuser2 == index ()) { + if (level == 3 && !game.isNullEntity (game.getLocalEntity ()) && game.getLocalEntity ()->v.iuser2 == index ()) { playMessage = true; } else if (level != 3) { playMessage = true; } if (playMessage && level > 3) { - logEntry (false, LL_DEFAULT, printBuf.chars ()); + util.logEntry (false, LL_DEFAULT, printBuf.chars ()); } if (playMessage) { - engine.print (printBuf.chars ()); + game.print (printBuf.chars ()); say (printBuf.chars ()); } } @@ -5538,7 +5404,7 @@ Vector Bot::calcToss (const Vector &start, const Vector &stop) { return Vector::null (); } Vector midPoint = start + (end - start) * 0.5f; - engine.testHull (midPoint, midPoint + Vector (0.0f, 0.0f, 500.0f), TRACE_IGNORE_MONSTERS, head_hull, ent (), &tr); + game.testHull (midPoint, midPoint + Vector (0.0f, 0.0f, 500.0f), TRACE_IGNORE_MONSTERS, head_hull, ent (), &tr); if (tr.flFraction < 1.0f) { midPoint = tr.vecEndPos; @@ -5560,12 +5426,12 @@ Vector Bot::calcToss (const Vector &start, const Vector &stop) { Vector apex = start + velocity * timeOne; apex.z = midPoint.z; - engine.testHull (start, apex, TRACE_IGNORE_NONE, head_hull, ent (), &tr); + game.testHull (start, apex, TRACE_IGNORE_NONE, head_hull, ent (), &tr); if (tr.flFraction < 1.0f || tr.fAllSolid) { return Vector::null (); } - engine.testHull (end, apex, TRACE_IGNORE_MONSTERS, head_hull, ent (), &tr); + game.testHull (end, apex, TRACE_IGNORE_MONSTERS, head_hull, ent (), &tr); if (tr.flFraction != 1.0f) { float dot = -(tr.vecPlaneNormal | (apex - end).normalize ()); @@ -5599,12 +5465,12 @@ Vector Bot::calcThrow (const Vector &start, const Vector &stop) { Vector apex = start + (stop - start) * 0.5f; apex.z += 0.5f * gravity * (time * 0.5f) * (time * 0.5f); - engine.testHull (start, apex, TRACE_IGNORE_NONE, head_hull, ent (), &tr); + game.testHull (start, apex, TRACE_IGNORE_NONE, head_hull, ent (), &tr); if (tr.flFraction != 1.0f) { return Vector::null (); } - engine.testHull (stop, apex, TRACE_IGNORE_MONSTERS, head_hull, ent (), &tr); + game.testHull (stop, apex, TRACE_IGNORE_MONSTERS, head_hull, ent (), &tr); if (tr.flFraction != 1.0 || tr.fAllSolid) { float dot = -(tr.vecPlaneNormal | (apex - stop).normalize ()); @@ -5619,13 +5485,13 @@ Vector Bot::calcThrow (const Vector &start, const Vector &stop) { edict_t *Bot::correctGrenadeVelocity (const char *model) { edict_t *pent = nullptr; - while (!engine.isNullEntity (pent = g_engfuncs.pfnFindEntityByString (pent, "classname", "grenade"))) { + 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; } - m_grenadeCheckTime = engine.timebase () + MAX_GRENADE_TIMER; + m_grenadeCheckTime = game.timebase () + MAX_GRENADE_TIMER; selectBestWeapon (); completeTask (); @@ -5639,7 +5505,7 @@ edict_t *Bot::correctGrenadeVelocity (const char *model) { Vector Bot::isBombAudible (void) { // this function checks if bomb is can be heard by the bot, calculations done by manual testing. - if (!g_bombPlanted || taskId () == TASK_ESCAPEFROMBOMB) { + if (!bots.isBombPlanted () || taskId () == TASK_ESCAPEFROMBOMB) { return Vector::null (); // reliability check } @@ -5648,7 +5514,7 @@ Vector Bot::isBombAudible (void) { } const Vector &bombOrigin = waypoints.getBombPos (); - float timeElapsed = ((engine.timebase () - g_timeBombPlanted) / mp_c4timer.flt ()) * 100.0f; + float timeElapsed = ((game.timebase () - bots.getTimeBombPlanted ()) / mp_c4timer.flt ()) * 100.0f; float desiredRadius = 768.0f; // start the manual calculations @@ -5675,7 +5541,7 @@ Vector Bot::isBombAudible (void) { uint8 Bot::computeMsec (void) { // estimate msec to use for this command based on time passed from the previous command - return static_cast ((engine.timebase () - m_lastCommandTime) * 1000.0f); + return static_cast ((game.timebase () - m_lastCommandTime) * 1000.0f); } void Bot::runMovement (void) { @@ -5693,12 +5559,12 @@ void Bot::runMovement (void) { // 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 = engine.timebase () - m_lastCommandTime; + m_frameInterval = game.timebase () - m_lastCommandTime; uint8 msecVal = computeMsec (); - m_lastCommandTime = engine.timebase (); + m_lastCommandTime = game.timebase (); - g_engfuncs.pfnRunPlayerMove (pev->pContainingEntity, m_moveAngles, m_moveSpeed, m_strafeSpeed, 0.0f, static_cast (pev->button), static_cast (pev->impulse), msecVal); + engfuncs.pfnRunPlayerMove (pev->pContainingEntity, m_moveAngles, m_moveSpeed, m_strafeSpeed, 0.0f, static_cast (pev->button), static_cast (pev->impulse), msecVal); // save our own copy of old buttons, since bot ai code is not running every frame now m_oldButtons = pev->button; @@ -5712,18 +5578,18 @@ void Bot::checkBurstMode (float distance) { } // if current weapon is glock, disable burstmode on long distances, enable it else - if (m_currentWeapon == WEAPON_GLOCK && distance < 300.0f && m_weaponBurstMode == BM_OFF) { + if (m_currentWeapon == WEAPON_GLOCK && distance < 300.0f && m_weaponBurstMode == BURST_OFF) { pev->button |= IN_ATTACK2; } - else if (m_currentWeapon == WEAPON_GLOCK && distance >= 300.0f && m_weaponBurstMode == BM_ON) { + else if (m_currentWeapon == WEAPON_GLOCK && distance >= 300.0f && m_weaponBurstMode == BURST_ON) { pev->button |= IN_ATTACK2; } // if current weapon is famas, disable burstmode on short distances, enable it else - if (m_currentWeapon == WEAPON_FAMAS && distance > 400.0f && m_weaponBurstMode == BM_OFF) { + if (m_currentWeapon == WEAPON_FAMAS && distance > 400.0f && m_weaponBurstMode == BURST_OFF) { pev->button |= IN_ATTACK2; } - else if (m_currentWeapon == WEAPON_FAMAS && distance <= 400.0f && m_weaponBurstMode == BM_ON) { + else if (m_currentWeapon == WEAPON_FAMAS && distance <= 400.0f && m_weaponBurstMode == BURST_ON) { pev->button |= IN_ATTACK2; } } @@ -5750,10 +5616,10 @@ void Bot::checkSilencer (void) { } float Bot::getBombTimeleft (void) { - if (!g_bombPlanted) { + if (!bots.isBombPlanted ()) { return 0.0f; } - float timeLeft = ((g_timeBombPlanted + mp_c4timer.flt ()) - engine.timebase ()); + float timeLeft = ((bots.getTimeBombPlanted () + mp_c4timer.flt ()) - game.timebase ()); if (timeLeft < 0.0f) { return 0.0f; @@ -5762,7 +5628,7 @@ float Bot::getBombTimeleft (void) { } bool Bot::isOutOfBombTimer (void) { - if (!(g_mapFlags & MAP_DE)) { + if (!game.mapIs (MAP_DE)) { return false; } @@ -5786,7 +5652,7 @@ bool Bot::isOutOfBombTimer (void) { bool hasTeammatesWithDefuserKit = false; // check if our teammates has defusal kit - for (int i = 0; i < engine.maxClients (); i++) { + for (int i = 0; i < game.maxClients (); i++) { auto *bot = bots.getBot (i); // search players with defuse kit @@ -5810,18 +5676,18 @@ bool Bot::isOutOfBombTimer (void) { return false; // return false otherwise } -void Bot::processHearing (void) { +void Bot::updateHearing (void) { int hearEnemyIndex = INVALID_WAYPOINT_INDEX; float minDistance = 99999.0f; // loop through all enemy clients to check for hearable stuff - for (int i = 0; i < engine.maxClients (); i++) { - const Client &client = g_clients[i]; + for (int i = 0; i < game.maxClients (); i++) { + const Client &client = util.getClient (i); - if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.ent == ent () || client.team == m_team || client.timeSoundLasting < engine.timebase ()) { + if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.ent == ent () || client.team == m_team || client.timeSoundLasting < game.timebase ()) { continue; } - float distance = (client.soundPos - pev->origin).length (); + float distance = (client.sound - pev->origin).length (); if (distance > client.hearingDistance) { continue; @@ -5834,21 +5700,21 @@ void Bot::processHearing (void) { } edict_t *player = nullptr; - if (hearEnemyIndex >= 0 && g_clients[hearEnemyIndex].team != m_team && !(g_gameFlags & GAME_CSDM_FFA)) { - player = g_clients[hearEnemyIndex].ent; + if (hearEnemyIndex >= 0 && util.getClient (hearEnemyIndex).team != m_team && !game.is (GAME_CSDM_FFA)) { + player = util.getClient (hearEnemyIndex).ent; } // did the bot hear someone ? - if (player != nullptr && isPlayer (player)) { + if (player != nullptr && util.isPlayer (player)) { // change to best weapon if heard something - if (m_shootTime < engine.timebase () - 5.0f && isOnFloor () && m_currentWeapon != WEAPON_C4 && m_currentWeapon != WEAPON_EXPLOSIVE && m_currentWeapon != WEAPON_SMOKE && m_currentWeapon != WEAPON_FLASHBANG && !yb_jasonmode.boolean ()) { + 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.boolean ()) { selectBestWeapon (); } - m_heardSoundTime = engine.timebase (); + m_heardSoundTime = game.timebase (); m_states |= STATE_HEARING_ENEMY; - if (rng.chance (15) && engine.isNullEntity (m_enemy) && engine.isNullEntity (m_lastEnemy) && m_seeEnemyTime + 7.0f < engine.timebase ()) { + if (rng.chance (15) && game.isNullEntity (m_enemy) && game.isNullEntity (m_lastEnemy) && m_seeEnemyTime + 7.0f < game.timebase ()) { pushChatterMessage (CHATTER_HEARD_ENEMY); } @@ -5871,7 +5737,7 @@ void Bot::processHearing (void) { // 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 < engine.timebase ()) { + if (distance > (player->v.origin - pev->origin).lengthSq () && m_seeEnemyTime + 2.0f < game.timebase ()) { m_lastEnemy = player; m_lastEnemyOrigin = player->v.origin; } @@ -5889,19 +5755,19 @@ void Bot::processHearing (void) { m_lastEnemyOrigin = m_enemyOrigin; m_states |= STATE_SEEING_ENEMY; - m_seeEnemyTime = engine.timebase (); + m_seeEnemyTime = game.timebase (); } // check if heard enemy can be shoot through some obstacle else { - if (m_difficulty > 2 && m_lastEnemy == player && m_seeEnemyTime + 3.0f > engine.timebase () && yb_shoots_thru_walls.boolean () && isPenetrableObstacle (player->v.origin)) { + if (m_difficulty > 2 && m_lastEnemy == player && m_seeEnemyTime + 3.0f > game.timebase () && yb_shoots_thru_walls.boolean () && isPenetrableObstacle (player->v.origin)) { m_enemy = player; m_lastEnemy = player; m_enemyOrigin = player->v.origin; m_lastEnemyOrigin = player->v.origin; m_states |= (STATE_SEEING_ENEMY | STATE_SUSPECT_ENEMY); - m_seeEnemyTime = engine.timebase (); + m_seeEnemyTime = game.timebase (); } } } @@ -5921,8 +5787,10 @@ bool Bot::isShootableBreakable (edict_t *ent) { void Bot::processBuyzoneEntering (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 < engine.timebase () && m_lastEquipTime + 15.0f < engine.timebase () && m_inBuyZone && (g_timeRoundStart + rng.getFloat (10.0f, 20.0f) + mp_buytime.flt () < engine.timebase ()) && !g_bombPlanted && m_moneyAmount > g_botBuyEconomyTable[0]) { + if (m_seeEnemyTime + 12.0f < game.timebase () && m_lastEquipTime + 15.0f < game.timebase () && m_inBuyZone && (bots.getRoundStartTime () + rng.getFloat (10.0f, 20.0f) + mp_buytime.flt () < game.timebase ()) && !bots.isBombPlanted () && m_moneyAmount > econLimit[ECO_PRIMARY_GT]) { m_ignoreBuyDelay = true; m_buyingFinished = false; m_buyState = buyState; @@ -5930,21 +5798,21 @@ void Bot::processBuyzoneEntering (int buyState) { // push buy message pushMsgQueue (GAME_MSG_PURCHASE); - m_nextBuyTime = engine.timebase (); - m_lastEquipTime = engine.timebase (); + m_nextBuyTime = game.timebase (); + m_lastEquipTime = game.timebase (); } } bool Bot::isBombDefusing (const Vector &bombOrigin) { // this function finds if somebody currently defusing the bomb. - if (!g_bombPlanted) { + if (!bots.isBombPlanted ()) { return false; } bool defusingInProgress = false; - for (int i = 0; i < engine.maxClients (); i++) { - Bot *bot = bots.getBot (i); + for (const auto &client : util.getClients ()) { + auto bot = bots.getBot (client.ent); if (bot == nullptr || bot == this) { continue; // skip invalid bots @@ -5958,10 +5826,9 @@ bool Bot::isBombDefusing (const Vector &bombOrigin) { defusingInProgress = true; break; } - const Client &client = g_clients[i]; // take in account peoples too - if (defusingInProgress || !(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.team != m_team || isFakeClient (client.ent)) { + if (defusingInProgress || !(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.team != m_team || util.isFakeClient (client.ent)) { continue; } diff --git a/source/chatlib.cpp b/source/chatlib.cpp index b9a9bff..48b611d 100644 --- a/source/chatlib.cpp +++ b/source/chatlib.cpp @@ -11,337 +11,86 @@ ConVar yb_chat ("yb_chat", "1"); -void stripClanTags (char *buffer) { - // this function strips 'clan' tags specified below in given string buffer - - if (isEmptyStr (buffer)) { - return; - } - size_t nameLength = strlen (buffer); - - if (nameLength < 4) { +void BotUtils::stripTags (String &line) { + if (line.empty ()) { return; } - constexpr const char *open[] = { "-=", "-[", "-]", "-}", "-{", "<[", "<]", "[-", "]-", "{-", "}-", "[[", "[", "{", "]", "}", "<", ">", "-", "|", "=", "+", "(", ")" }; - constexpr const char *close[] = { "=-", "]-", "[-", "{-", "}-", "]>", "[>", "-]", "-[", "-}", "-{", "]]", "]", "}", "[", "{", ">", "<", "-", "|", "=", "+", ")", "(" }; + for (const auto &tag : m_tags) { + const size_t start = line.find (tag.first, 0); - // for each known tag... - for (size_t i = 0; i < cr::arrsize (open); i++) { - size_t start = strstr (buffer, open[i]) - buffer; // look for a tag start + if (start != String::INVALID_INDEX) { + const size_t end = line.find (tag.second, start); + const size_t diff = end - start; - // have we found a tag start? - if (start < 32) { - size_t stop = strstr (buffer, close[i]) - buffer; // look for a tag stop - - // have we found a tag stop? - if (stop > start && stop < 32) { - size_t tag = strlen (close[i]); - size_t j = start; - - for (; j < nameLength - (stop + tag - start); j++) { - buffer[j] = buffer[j + (stop + tag - start)]; // overwrite the buffer with the stripped string - } - buffer[j] = '\0'; // terminate the string + if (end != String::INVALID_INDEX && end > start && diff < 32 && diff > 4) { + line.erase (start, diff + tag.second.length ()); + break; } } } - - // have we stripped too much (all the stuff)? - if (isEmptyStr (buffer)) { - String::trimChars (buffer); - return; - } - String::trimChars (buffer); // if so, string is just a tag - - // strip just the tag part... - for (size_t i = 0; i < cr::arrsize (open); i++) { - size_t start = strstr (buffer, open[i]) - buffer; // look for a tag start - - // have we found a tag start? - if (start < 32) { - size_t tag = strlen (open[i]); - size_t j = start; - - for (; j < nameLength - tag; j++) { - buffer[j] = buffer[j + tag]; // overwrite the buffer with the stripped string - } - buffer[j] = '\0'; // terminate the string - start = strstr (buffer, close[i]) - buffer; // look for a tag stop - - // have we found a tag stop ? - if (start < 32) { - tag = strlen (close[i]); - j = start; - - for (; j < nameLength - tag; j++) { - buffer[j] = buffer[j + tag]; // overwrite the buffer with the stripped string - } - buffer[j] = 0; // terminate the string - } - } - } - String::trimChars (buffer); // to finish, strip eventual blanks after and before the tag marks } -char *humanizeName (char *name) { - // this function humanize player name (i.e. trim clan and switch to lower case (sometimes)) - - static char outputName[64]; // create return name buffer - strncpy (outputName, name, cr::bufsize (outputName)); // copy name to new buffer +void BotUtils::humanizePlayerName (String &playerName) { + if (playerName.empty ()) { + return; + } // drop tag marks, 80 percent of time if (rng.chance (80)) { - stripClanTags (outputName); + stripTags (playerName); } else { - String::trimChars (outputName); + playerName.trim (); } - // sometimes switch name to lower characters - // note: since we're using russian names written in english, we reduce this shit to 6 percent - if (rng.chance (7)) { - for (size_t i = 0; i < strlen (outputName); i++) { - outputName[i] = static_cast (tolower (static_cast (outputName[i]))); // to lower case - } + // sometimes switch name to lower characters, only valid for the english languge + if (rng.chance (15) && strcmp (yb_language.str (), "en") == 0) { + playerName.lowercase (); } - return &outputName[0]; // return terminated string } -void addChatErrors (char *buffer) { - // this function humanize chat string to be more handwritten - - size_t length = strlen (buffer); // get length of string - size_t i = 0; - - // sometimes switch text to lowercase - // note: since we're using russian chat written in English, we reduce this shit to 4 percent - if (rng.chance (5)) { - for (i = 0; i < length; i++) { - buffer[i] = static_cast (tolower (static_cast (buffer[i]))); // switch to lowercase - } +void BotUtils::addChatErrors (String &line) { + // sometimes switch name to lower characters, only valid for the english languge + if (rng.chance (15) && strcmp (yb_language.str (), "en") == 0) { + line.lowercase (); } + auto length = line.length (); if (length > 15) { - size_t percentile = length / 2; + size_t percentile = line.length () / 2; // "length / 2" percent of time drop a character - if (rng.getInt (1u, 100u) < percentile) { - size_t pos = rng.getInt (length / 8, length - length / 8); // chose random position in string - - for (i = pos; i < length - 1; i++) { - buffer[i] = buffer[i + 1]; // overwrite the buffer with stripped string - } - buffer[i] = '\0'; // terminate string; - length--; // update new string length + if (rng.chance (percentile)) { + line.erase (rng.getInt (length / 8, length - length / 8)); } // "length" / 4 precent of time swap character - if (rng.getInt (1u, 100u) < percentile / 2) { + if (rng.chance (percentile / 2)) { size_t pos = rng.getInt (length / 8, 3 * length / 8); // choose random position in string - char ch = buffer[pos]; // swap characters - - buffer[pos] = buffer[pos + 1]; - buffer[pos + 1] = ch; + cr::swap (line[pos], line[pos + 1]); } } - buffer[length] = 0; // terminate string -} - -void Bot::prepareChatMessage (char *text) { - // this function parses messages from the botchat, replaces keywords and converts names into a more human style - - if (!yb_chat.boolean () || isEmptyStr (text)) { - return; - } - m_tempStrings.clear (); - - char *textStart = text; - char *pattern = text; - - edict_t *talkEntity = nullptr; - - auto getHumanizedName = [] (edict_t *ent) { - if (!engine.isNullEntity (ent)) { - return const_cast (humanizeName (const_cast (STRING (ent->v.netname)))); - } - return "unknown"; - }; - - while (pattern != nullptr) { - // all replacement placeholders start with a % - pattern = strchr (textStart, '%'); - - if (pattern != nullptr) { - size_t length = pattern - textStart; - - if (length > 0) { - m_tempStrings = String (textStart, length); - } - pattern++; - - // player with most frags? - if (*pattern == 'f') { - int highestFrags = -9000; // just pick some start value - int index = 0; - - for (int i = 0; i < engine.maxClients (); i++) { - const Client &client = g_clients[i]; - - if (!(client.flags & CF_USED) || client.ent == ent ()) { - continue; - } - int frags = static_cast (client.ent->v.frags); - - if (frags > highestFrags) { - highestFrags = frags; - index = i; - } - } - talkEntity = g_clients[index].ent; - m_tempStrings += getHumanizedName (talkEntity); - } - // mapname? - else if (*pattern == 'm') { - m_tempStrings += engine.getMapName (); - } - // roundtime? - else if (*pattern == 'r') { - int time = static_cast (g_timeRoundEnd - engine.timebase ()); - m_tempStrings.formatAppend ("%02d:%02d", time / 60, time % 60); - } - // chat reply? - else if (*pattern == 's') { - talkEntity = engine.entityOfIndex (m_sayTextBuffer.entityIndex); - m_tempStrings += getHumanizedName (talkEntity); - } - // teammate alive? - else if (*pattern == 't') { - int i; - - for (i = 0; i < engine.maxClients (); i++) { - const Client &client = g_clients[i]; - - if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.team != m_team || client.ent == ent ()) { - continue; - } - break; - } - - if (i < engine.maxClients ()) { - if (isPlayer (pev->dmg_inflictor) && m_team == engine.getTeam (pev->dmg_inflictor)) { - talkEntity = pev->dmg_inflictor; - } - else { - talkEntity = g_clients[i].ent; - } - m_tempStrings += getHumanizedName (talkEntity); - } - else // no teammates alive... - { - for (i = 0; i < engine.maxClients (); i++) { - const Client &client = g_clients[i]; - - if (!(client.flags & CF_USED) || client.team != m_team || client.ent == ent ()) { - continue; - } - break; - } - - if (i < engine.maxClients ()) { - talkEntity = g_clients[i].ent; - m_tempStrings += getHumanizedName (talkEntity); - } - } - } - else if (*pattern == 'e') { - int i; - - for (i = 0; i < engine.maxClients (); i++) { - const Client &client = g_clients[i]; - - if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.team == m_team || client.ent == ent ()) { - continue; - } - break; - } - - if (i < engine.maxClients ()) { - talkEntity = g_clients[i].ent; - m_tempStrings += getHumanizedName (talkEntity); - } - - // no teammates alive ? - else { - for (i = 0; i < engine.maxClients (); i++) { - const Client &client = g_clients[i]; - - if (!(client.flags & CF_USED) || client.team == m_team || client.ent == ent ()) { - continue; - } - break; - } - if (i < engine.maxClients ()) { - talkEntity = g_clients[i].ent; - m_tempStrings += getHumanizedName (talkEntity); - } - } - } - else if (*pattern == 'd') { - if (g_gameFlags & GAME_CZERO) { - if (rng.chance (30)) { - m_tempStrings += "CZ"; - } - else { - m_tempStrings += "Condition Zero"; - } - } - else if ((g_gameFlags & GAME_CSTRIKE16) || (g_gameFlags & GAME_LEGACY)) { - if (rng.chance (30)) { - m_tempStrings += "CS"; - } - else { - m_tempStrings += "Counter-Strike"; - } - } - } - else if (*pattern == 'v') { - talkEntity = m_lastVictim; - m_tempStrings += getHumanizedName (talkEntity); - } - pattern++; - textStart = pattern; - } - } - - if (textStart != nullptr) { - // let the bots make some mistakes... - char tempString[160]; - strncpy (tempString, textStart, cr::bufsize (tempString)); - - addChatErrors (tempString); - m_tempStrings += tempString; - } } -bool checkForKeywords (char *message, char *reply) { +bool BotUtils::checkKeywords (const String &line, String &reply) { // this function checks is string contain keyword, and generates reply to it - if (!yb_chat.boolean () || isEmptyStr (message)) { + if (!yb_chat.boolean () || line.empty ()) { return false; } - for (auto &factory : g_replyFactory) { + for (auto &factory : conf.getReplies ()) { for (auto &keyword : factory.keywords) { // check is keyword has occurred in message - if (strstr (message, keyword.chars ()) != nullptr) { + if (line.find (keyword, 0) != String::INVALID_INDEX) { StringArray &usedReplies = factory.usedReplies; - + if (usedReplies.length () >= factory.replies.length () / 2) { usedReplies.clear (); } - + if (!factory.replies.empty ()) { bool replyUsed = false; const String &choosenReply = factory.replies.random (); @@ -356,7 +105,7 @@ bool checkForKeywords (char *message, char *reply) { // reply not used, so use it if (!replyUsed) { - strcpy (reply, choosenReply.chars ()); // update final buffer + reply.assign (choosenReply); // update final buffer usedReplies.push (choosenReply); // add to ignore list return true; @@ -365,45 +114,234 @@ bool checkForKeywords (char *message, char *reply) { } } } + auto &chat = conf.getChat (); // didn't find a keyword? 70% of the time use some universal reply - if (rng.chance (70) && !g_chatFactory[CHAT_NOKW].empty ()) { - strcpy (reply, g_chatFactory[CHAT_NOKW].random ().chars ()); + if (rng.chance (70) && !chat[CHAT_NOKW].empty ()) { + reply.assign (chat[CHAT_NOKW].random ()); return true; } return false; } -bool Bot::processChatKeywords (char *reply) { +void Bot::prepareChatMessage (const String &message) { + // this function parses messages from the botchat, replaces keywords and converts names into a more human style + + if (!yb_chat.boolean () || message.empty ()) { + return; + } + m_chatBuffer = message; + + // must be called before return or on the end + auto finishPreparation = [&] (void) { + if (!m_chatBuffer.empty ()) { + util.addChatErrors (m_chatBuffer); + } + }; + + // need to check if we're have special symbols + size_t pos = message.find ('%', 0); + + // nothing found, bail out + if (pos == String::INVALID_INDEX || pos >= message.length ()) { + finishPreparation (); + return; + } + + // get the humanized name out of client + auto humanizedName = [] (const Client &client) -> String { + if (!util.isPlayer (client.ent)) { + return cr::move (String ("unknown")); + } + String playerName = STRING (client.ent->v.netname); + util.humanizePlayerName (playerName); + + return cr::move (playerName); + }; + + // find highfrag player + auto getHighfragPlayer = [&] (void) -> String { + int highestFrags = -1; + int index = 0; + + for (int i = 0; i < game.maxClients (); i++) { + const Client &client = util.getClient (i); + + if (!(client.flags & CF_USED) || client.ent == ent ()) { + continue; + } + int frags = static_cast (client.ent->v.frags); + + if (frags > highestFrags) { + highestFrags = frags; + index = i; + } + } + return humanizedName (util.getClient (index)); + }; + + // get roundtime + auto getRoundTime = [] (void) -> String { + int roundTimeSecs = static_cast (bots.getRoundEndTime () - game.timebase ()); + + String roundTime; + roundTime.assign ("%02d:%02d", roundTimeSecs / 60, cr::abs (roundTimeSecs) % 60); + + return cr::move (roundTime); + }; + + // get bot's victim + auto getMyVictim = [&] (void) -> String { + for (const Client &client : util.getClients ()) { + if (client.ent == m_lastVictim) { + return humanizedName (client); + } + } + return cr::move (String ("unknown")); + }; + + // get the game name alias + auto getGameName = [] (void) -> String { + String gameName; + + if (game.is (GAME_CZERO)) { + if (rng.chance (30)) { + gameName = "CZ"; + } + else { + gameName = "Condition Zero"; + } + } + else if (game.is (GAME_CSTRIKE16) || game.is (GAME_LEGACY)) { + if (rng.chance (30)) { + gameName = "CS"; + } + else { + gameName = "Counter-Strike"; + } + } + return cr::move (gameName); + }; + + // get enemy or teammate alive + auto getPlayerAlive = [&] (bool needsEnemy) -> String { + int index; + + for (index = 0; index < game.maxClients (); index++) { + const Client &client = util.getClient (index); + + if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.ent == ent ()) { + continue; + } + + if ((needsEnemy && m_team == client.team) || (!needsEnemy && m_team != client.team)) { + continue; + } + break; + } + + if (index < game.maxClients ()) { + if (!needsEnemy && util.isPlayer (pev->dmg_inflictor) && m_team == game.getTeam (pev->dmg_inflictor)) { + return humanizedName (util.getClient (game.indexOfEntity (pev->dmg_inflictor) - 1)); + } + else { + return humanizedName (util.getClient (index)); + } + } + else { + for (index = 0; index < game.maxClients (); index++) { + const Client &client = util.getClient (index); + + if (!(client.flags & CF_USED) || client.team != m_team || client.ent == ent ()) { + continue; + } + + if ((needsEnemy && m_team != client.team) || (!needsEnemy && m_team == client.team)) { + continue; + } + break; + } + + if (index < game.maxClients ()) { + return humanizedName (util.getClient (index)); + } + } + return cr::move (String ("unknown")); + }; + + // found one, let's do replace + switch (message[pos + 1]) { + + // the highest frag player + case 'f': + m_chatBuffer.replace ("%f", getHighfragPlayer ()); + break; + + // current map name + case 'm': + m_chatBuffer.replace ("%m", game.getMapName ()); + break; + + // round time + case 'r': + m_chatBuffer.replace ("%r", getRoundTime ()); + break; + + // chat reply + case 's': + if (m_sayTextBuffer.entityIndex != -1) { + m_chatBuffer.replace ("%s", humanizedName (util.getClient (m_sayTextBuffer.entityIndex))); + } + else { + m_chatBuffer.replace ("%s", getHighfragPlayer ()); + } + break; + + // last bot victim + case 'v': + m_chatBuffer.replace ("%v", getMyVictim ()); + break; + + // game name + case 'd': + m_chatBuffer.replace ("%d", getGameName ()); + break; + + // teammate alive + case 't': + m_chatBuffer.replace ("%t", getPlayerAlive (false)); + break; + + // enemy alive + case 'e': + m_chatBuffer.replace ("%e", getPlayerAlive (true)); + break; + + }; + finishPreparation (); +} + +bool Bot::checkChatKeywords (String &reply) { // this function parse chat buffer, and prepare buffer to keyword searching - char tempMessage[512]; - size_t maxLength = cr::bufsize (tempMessage); - - strncpy (tempMessage, m_sayTextBuffer.sayText.chars (), maxLength); // copy to safe place - - // text to uppercase for keyword parsing - for (size_t i = 0; i < maxLength; i++) { - tempMessage[i] = static_cast (toupper (static_cast (tempMessage[i]))); - } - return checkForKeywords (tempMessage, reply); + String message = m_sayTextBuffer.sayText; + return util.checkKeywords (message.uppercase (), reply); } bool Bot::isReplyingToChat (void) { // this function sends reply to a player if (m_sayTextBuffer.entityIndex != -1 && !m_sayTextBuffer.sayText.empty ()) { - char text[256]; + String message; // check is time to chat is good - if (m_sayTextBuffer.timeNextChat < engine.timebase ()) { - if (rng.chance (m_sayTextBuffer.chatProbability + rng.getInt (15, 35)) && processChatKeywords (reinterpret_cast (&text))) { - prepareChatMessage (text); + if (m_sayTextBuffer.timeNextChat < game.timebase ()) { + if (rng.chance (m_sayTextBuffer.chatProbability + rng.getInt (25, 45)) && checkChatKeywords (message)) { + prepareChatMessage (message); pushMsgQueue (GAME_MSG_SAY_CMD); - m_sayTextBuffer.entityIndex = -1; - m_sayTextBuffer.timeNextChat = engine.timebase () + m_sayTextBuffer.chatDelay; + m_sayTextBuffer.timeNextChat = game.timebase () + m_sayTextBuffer.chatDelay; m_sayTextBuffer.sayText.clear (); return true; @@ -415,20 +353,61 @@ bool Bot::isReplyingToChat (void) { return false; } +void Bot::checkForChat (void) { + + + // bot chatting turned on? + if (!m_notKilled && yb_chat.boolean () && m_lastChatTime + 10.0 < game.timebase () && bots.getLastChatTimestamp () + 5.0f < game.timebase () && !isReplyingToChat ()) { + auto &chat = conf.getChat (); + + // say a text every now and then + if (rng.chance (50)) { + m_lastChatTime = game.timebase (); + bots.setLastChatTimestamp (game.timebase ()); + + if (!chat[CHAT_DEAD].empty ()) { + const String &phrase = chat[CHAT_DEAD].random (); + bool sayBufferExists = false; + + // search for last messages, sayed + for (auto &sentence : m_sayTextBuffer.lastUsedSentences) { + if (strncmp (sentence.chars (), phrase.chars (), sentence.length ()) == 0) { + sayBufferExists = true; + break; + } + } + + if (!sayBufferExists) { + prepareChatMessage (phrase); + pushMsgQueue (GAME_MSG_SAY_CMD); + + // add to ignore list + m_sayTextBuffer.lastUsedSentences.push (phrase); + } + } + + // clear the used line buffer every now and then + if (static_cast (m_sayTextBuffer.lastUsedSentences.length ()) > rng.getInt (4, 6)) { + m_sayTextBuffer.lastUsedSentences.clear (); + } + } + } +} + void Bot::say (const char *text) { // this function prints saytext message to all players - if (isEmptyStr (text) || !yb_chat.boolean ()) { + if (util.isEmptyStr (text) || !yb_chat.boolean ()) { return; } - engine.execBotCmd (ent (), "say \"%s\"", text); + game.execBotCmd (ent (), "say \"%s\"", text); } void Bot::sayTeam (const char *text) { // this function prints saytext message only for teammates - if (isEmptyStr (text) || !yb_chat.boolean ()) { + if (util.isEmptyStr (text) || !yb_chat.boolean ()) { return; } - engine.execBotCmd (ent (), "say_team \"%s\"", text); + game.execBotCmd (ent (), "say_team \"%s\"", text); } diff --git a/source/combat.cpp b/source/combat.cpp index 803514e..f50e465 100644 --- a/source/combat.cpp +++ b/source/combat.cpp @@ -18,9 +18,7 @@ ConVar mp_friendlyfire ("mp_friendlyfire", nullptr, VT_NOREGISTER); int Bot::numFriendsNear (const Vector &origin, float radius) { int count = 0; - for (int i = 0; i < engine.maxClients (); i++) { - const Client &client = g_clients[i]; - + for (const auto &client : util.getClients ()) { if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.team != m_team || client.ent == ent ()) { continue; } @@ -35,9 +33,7 @@ int Bot::numFriendsNear (const Vector &origin, float radius) { int Bot::numEnemiesNear (const Vector &origin, float radius) { int count = 0; - for (int i = 0; i < engine.maxClients (); i++) { - const Client &client = g_clients[i]; - + for (const auto &client : util.getClients ()) { if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.team == m_team) { continue; } @@ -50,7 +46,7 @@ int Bot::numEnemiesNear (const Vector &origin, float radius) { } bool Bot::isEnemyHidden (edict_t *enemy) { - if (!yb_check_enemy_rendering.boolean () || engine.isNullEntity (enemy)) { + if (!yb_check_enemy_rendering.boolean () || game.isNullEntity (enemy)) { return false; } entvars_t &v = enemy->v; @@ -102,12 +98,12 @@ bool Bot::checkBodyParts (edict_t *target, Vector *origin, uint8 *bodyPart) { } TraceResult result; - Vector eyes = eyePos (); + Vector eyes = getEyesPos (); Vector spot = target->v.origin; *bodyPart = 0; - engine.testLine (eyes, spot, TRACE_IGNORE_EVERYTHING, ent (), &result); + game.testLine (eyes, spot, TRACE_IGNORE_EVERYTHING, ent (), &result); if (result.flFraction >= 1.0f) { *bodyPart |= VISIBLE_BODY; @@ -116,7 +112,7 @@ bool Bot::checkBodyParts (edict_t *target, Vector *origin, uint8 *bodyPart) { // check top of head spot.z += 25.0f; - engine.testLine (eyes, spot, TRACE_IGNORE_EVERYTHING, ent (), &result); + game.testLine (eyes, spot, TRACE_IGNORE_EVERYTHING, ent (), &result); if (result.flFraction >= 1.0f) { *bodyPart |= VISIBLE_HEAD; @@ -136,7 +132,7 @@ bool Bot::checkBodyParts (edict_t *target, Vector *origin, uint8 *bodyPart) { else { spot.z = target->v.origin.z - standFeet; } - engine.testLine (eyes, spot, TRACE_IGNORE_EVERYTHING, ent (), &result); + game.testLine (eyes, spot, TRACE_IGNORE_EVERYTHING, ent (), &result); if (result.flFraction >= 1.0f) { *bodyPart |= VISIBLE_OTHER; @@ -151,7 +147,7 @@ bool Bot::checkBodyParts (edict_t *target, Vector *origin, uint8 *bodyPart) { Vector perp (-dir.y, dir.x, 0.0f); spot = target->v.origin + Vector (perp.x * edgeOffset, perp.y * edgeOffset, 0); - engine.testLine (eyes, spot, TRACE_IGNORE_EVERYTHING, ent (), &result); + game.testLine (eyes, spot, TRACE_IGNORE_EVERYTHING, ent (), &result); if (result.flFraction >= 1.0f) { *bodyPart |= VISIBLE_OTHER; @@ -161,7 +157,7 @@ bool Bot::checkBodyParts (edict_t *target, Vector *origin, uint8 *bodyPart) { } spot = target->v.origin - Vector (perp.x * edgeOffset, perp.y * edgeOffset, 0); - engine.testLine (eyes, spot, TRACE_IGNORE_EVERYTHING, ent (), &result); + game.testLine (eyes, spot, TRACE_IGNORE_EVERYTHING, ent (), &result); if (result.flFraction >= 1.0f) { *bodyPart |= VISIBLE_OTHER; @@ -173,16 +169,16 @@ bool Bot::checkBodyParts (edict_t *target, Vector *origin, uint8 *bodyPart) { } bool Bot::seesEnemy (edict_t *player, bool ignoreFOV) { - if (engine.isNullEntity (player)) { + if (game.isNullEntity (player)) { return false; } - if (isPlayer (pev->dmg_inflictor) && engine.getTeam (pev->dmg_inflictor) != m_team) { + if (util.isPlayer (pev->dmg_inflictor) && game.getTeam (pev->dmg_inflictor) != m_team) { ignoreFOV = true; } if ((ignoreFOV || isInViewCone (player->v.origin)) && checkBodyParts (player, &m_enemyOrigin, &m_visibility)) { - m_seeEnemyTime = engine.timebase (); + m_seeEnemyTime = game.timebase (); m_lastEnemy = player; m_lastEnemyOrigin = m_enemyOrigin; @@ -195,7 +191,7 @@ bool Bot::lookupEnemies (void) { // 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 > engine.timebase () || m_blindTime > engine.timebase () || yb_ignore_enemies.boolean ()) { + if (m_enemyIgnoreTimer > game.timebase () || m_blindTime > game.timebase () || yb_ignore_enemies.boolean ()) { return false; } edict_t *player, *newEnemy = nullptr; @@ -204,35 +200,33 @@ bool Bot::lookupEnemies (void) { extern ConVar yb_whose_your_daddy; // clear suspected flag - if (!engine.isNullEntity (m_enemy) && (m_states & STATE_SEEING_ENEMY)) { + if (!game.isNullEntity (m_enemy) && (m_states & STATE_SEEING_ENEMY)) { m_states &= ~STATE_SUSPECT_ENEMY; } - else if (engine.isNullEntity (m_enemy) && m_seeEnemyTime + 1.0f > engine.timebase () && isAlive (m_lastEnemy)) { + else if (game.isNullEntity (m_enemy) && m_seeEnemyTime + 1.0f > game.timebase () && util.isAlive (m_lastEnemy)) { m_states |= STATE_SUSPECT_ENEMY; m_aimFlags |= AIM_LAST_ENEMY; } m_visibility = 0; m_enemyOrigin.nullify (); - if (!engine.isNullEntity (m_enemy)) { + if (!game.isNullEntity (m_enemy)) { player = m_enemy; // is player is alive - if (m_enemyUpdateTime > engine.timebase () && (m_enemy->v.origin - pev->origin).lengthSq () < nearestDistance && isAlive (player) && seesEnemy (player)) { + if (m_enemyUpdateTime > game.timebase () && (m_enemy->v.origin - pev->origin).lengthSq () < nearestDistance && util.isAlive (player) && seesEnemy (player)) { newEnemy = player; } } // the old enemy is no longer visible or - if (engine.isNullEntity (newEnemy)) { + if (game.isNullEntity (newEnemy)) { // ignore shielded enemies, while we have real one edict_t *shieldEnemy = nullptr; // search the world for players... - for (int i = 0; i < engine.maxClients (); i++) { - const Client &client = g_clients[i]; - + for (const auto &client : util.getClients ()) { if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.team == m_team || client.ent == ent ()) { continue; } @@ -255,28 +249,28 @@ bool Bot::lookupEnemies (void) { newEnemy = player; // aim VIP first on AS maps... - if (isPlayerVIP (newEnemy)) { + if (util.isPlayerVIP (newEnemy)) { break; } } } } - m_enemyUpdateTime = engine.timebase () + calcThinkInterval () * 30.0f; + m_enemyUpdateTime = game.timebase () + getFrameInterval () * 30.0f; - if (engine.isNullEntity (newEnemy) && !engine.isNullEntity (shieldEnemy)) { + if (game.isNullEntity (newEnemy) && !game.isNullEntity (shieldEnemy)) { newEnemy = shieldEnemy; } } - if (isPlayer (newEnemy)) { - g_botsCanPause = true; + if (util.isPlayer (newEnemy)) { + bots.setCanPause (true); m_aimFlags |= AIM_ENEMY; m_states |= STATE_SEEING_ENEMY; // 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 = engine.timebase (); + m_seeEnemyTime = game.timebase (); // zero out reaction time m_actualReactionTime = 0.0f; @@ -286,7 +280,7 @@ bool Bot::lookupEnemies (void) { return true; } else { - if (m_seeEnemyTime + 3.0f < engine.timebase () && (m_hasC4 || hasHostage () || !engine.isNullEntity (m_targetEntity))) { + if (m_seeEnemyTime + 3.0f < game.timebase () && (m_hasC4 || hasHostage () || !game.isNullEntity (m_targetEntity))) { pushRadioMessage (RADIO_ENEMY_SPOTTED); } m_targetEntity = nullptr; // stop following when we see an enemy... @@ -301,7 +295,7 @@ bool Bot::lookupEnemies (void) { if (usesSniper ()) { m_enemySurpriseTime *= 0.5f; } - m_enemySurpriseTime += engine.timebase (); + m_enemySurpriseTime += game.timebase (); // zero out reaction time m_actualReactionTime = 0.0f; @@ -311,25 +305,23 @@ bool Bot::lookupEnemies (void) { m_enemyReachableTimer = 0.0f; // keep track of when we last saw an enemy - m_seeEnemyTime = engine.timebase (); + m_seeEnemyTime = game.timebase (); if (!(m_oldButtons & IN_ATTACK)) { return true; } // now alarm all teammates who see this bot & don't have an actual enemy of the bots enemy should simulate human players seeing a teammate firing - for (int j = 0; j < engine.maxClients (); j++) { - const Client &client = g_clients[j]; - + for (const auto &client : util.getClients ()) { if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.team != m_team || client.ent == ent ()) { continue; } Bot *other = bots.getBot (client.ent); - if (other != nullptr && other->m_seeEnemyTime + 2.0f < engine.timebase () && engine.isNullEntity (other->m_lastEnemy) && isVisible (pev->origin, other->ent ()) && other->isInViewCone (pev->origin)) { + if (other != nullptr && other->m_seeEnemyTime + 2.0f < game.timebase () && 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 = engine.timebase (); + other->m_seeEnemyTime = game.timebase (); other->m_states |= (STATE_SUSPECT_ENEMY | STATE_HEARING_ENEMY); other->m_aimFlags |= AIM_LAST_ENEMY; } @@ -337,17 +329,17 @@ bool Bot::lookupEnemies (void) { return true; } } - else if (!engine.isNullEntity (m_enemy)) { + else if (!game.isNullEntity (m_enemy)) { newEnemy = m_enemy; m_lastEnemy = newEnemy; - if (!isAlive (newEnemy)) { + if (!util.isAlive (newEnemy)) { m_enemy = nullptr; // shoot at dying players if no new enemy to give some more human-like illusion - if (m_seeEnemyTime + 0.3f > engine.timebase ()) { + if (m_seeEnemyTime + 0.3f > game.timebase ()) { if (!usesSniper ()) { - m_shootAtDeadTime = engine.timebase () + 0.4f; + m_shootAtDeadTime = game.timebase () + 0.4f; m_actualReactionTime = 0.0f; m_states |= STATE_SUSPECT_ENEMY; @@ -355,7 +347,7 @@ bool Bot::lookupEnemies (void) { } return false; } - else if (m_shootAtDeadTime > engine.timebase ()) { + else if (m_shootAtDeadTime > game.timebase ()) { m_actualReactionTime = 0.0f; m_states |= STATE_SUSPECT_ENEMY; @@ -366,7 +358,7 @@ bool Bot::lookupEnemies (void) { // if no enemy visible check if last one shoot able through wall if (yb_shoots_thru_walls.boolean () && m_difficulty >= 2 && isPenetrableObstacle (newEnemy->v.origin)) { - m_seeEnemyTime = engine.timebase (); + m_seeEnemyTime = game.timebase (); m_states |= STATE_SUSPECT_ENEMY; m_aimFlags |= AIM_LAST_ENEMY; @@ -380,14 +372,14 @@ bool Bot::lookupEnemies (void) { } // check if bots should reload... - if ((m_aimFlags <= AIM_PREDICT_PATH && m_seeEnemyTime + 3.0f < engine.timebase () && !(m_states & (STATE_SEEING_ENEMY | STATE_HEARING_ENEMY)) && engine.isNullEntity (m_lastEnemy) && engine.isNullEntity (m_enemy) && taskId () != TASK_SHOOTBREAKABLE && taskId () != TASK_PLANTBOMB && taskId () != TASK_DEFUSEBOMB) || g_roundEnded) { + if ((m_aimFlags <= AIM_PREDICT_PATH && m_seeEnemyTime + 3.0f < game.timebase () && !(m_states & (STATE_SEEING_ENEMY | STATE_HEARING_ENEMY)) && game.isNullEntity (m_lastEnemy) && game.isNullEntity (m_enemy) && taskId () != TASK_SHOOTBREAKABLE && taskId () != TASK_PLANTBOMB && taskId () != 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 < engine.timebase ()) { + if ((usesSniper () || usesZoomableRifle ()) && m_zoomCheckTime + 1.0f < game.timebase ()) { if (pev->fov < 90.0f) { pev->button |= IN_ATTACK2; } @@ -399,16 +391,16 @@ bool Bot::lookupEnemies (void) { } Vector Bot::getBodyOffsetError (float distance) { - if (engine.isNullEntity (m_enemy)) { + if (game.isNullEntity (m_enemy)) { return Vector::null (); } - if (m_aimErrorTime < engine.timebase ()) { + if (m_aimErrorTime < game.timebase ()) { 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 (rng.getFloat (mins.x * error, maxs.x * error), rng.getFloat (mins.y * error, maxs.y * error), rng.getFloat (mins.z * error, maxs.z * error)); - m_aimErrorTime = engine.timebase () + rng.getFloat (0.5f, 1.0f); + m_aimErrorTime = game.timebase () + rng.getFloat (0.5f, 1.0f); } return m_aimLastError; } @@ -444,7 +436,7 @@ const Vector &Bot::getEnemyBodyOffset (void) { Vector aimPos = m_enemy->v.origin; if (m_difficulty > 2 && !(m_visibility & VISIBLE_OTHER)) { - aimPos = (m_enemy->v.velocity - pev->velocity) * calcThinkInterval () + aimPos; + aimPos = (m_enemy->v.velocity - pev->velocity) * getFrameInterval () + aimPos; } // if we only suspect an enemy behind a wall take the worst skill @@ -528,28 +520,26 @@ float Bot::getEnemyBodyOffsetCorrection (float distance) { bool Bot::isFriendInLineOfFire (float distance) { // bot can't hurt teammates, if friendly fire is not enabled... - if (!mp_friendlyfire.boolean () || (g_gameFlags & GAME_CSDM)) { + if (!mp_friendlyfire.boolean () || game.is (GAME_CSDM)) { return false; } - makeVectors (pev->v_angle); + game.makeVectors (pev->v_angle); TraceResult tr; - engine.testLine (eyePos (), eyePos () + distance * pev->v_angle, TRACE_IGNORE_NONE, ent (), &tr); + game.testLine (getEyesPos (), getEyesPos () + distance * pev->v_angle, TRACE_IGNORE_NONE, ent (), &tr); // check if we hit something - if (isPlayer (tr.pHit) && tr.pHit != ent ()) { + if (util.isPlayer (tr.pHit) && tr.pHit != ent ()) { auto hit = tr.pHit; // check valid range - if (isAlive (hit) && engine.getTeam (hit) == m_team) { + if (util.isAlive (hit) && game.getTeam (hit) == m_team) { return true; } } // search the world for players - for (int i = 0; i < engine.maxClients (); i++) { - const Client &client = g_clients[i]; - + for (const auto &client : util.getClients ()) { if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.team != m_team || client.ent == ent ()) { continue; } @@ -558,7 +548,7 @@ bool Bot::isFriendInLineOfFire (float distance) { float friendDistance = (pent->v.origin - pev->origin).length (); float squareDistance = cr::sqrtf (1089.0f + cr::square (friendDistance)); - if (friendDistance <= distance && getShootingConeDeviation (ent (), pent->v.origin) > cr::square (friendDistance) / cr::square (squareDistance)) { + if (friendDistance <= distance && util.getShootingCone (ent (), pent->v.origin) > cr::square (friendDistance) / cr::square (squareDistance)) { return true; } } @@ -573,10 +563,10 @@ bool Bot::isPenetrableObstacle (const Vector &dest) { return isPenetrableObstacle2 (dest); } - if (m_difficulty < 2) { + if (m_isUsingGrenade || m_difficulty < 2) { return false; } - int penetratePower = getWeaponPenetrationPower (m_currentWeapon); + int penetratePower = conf.findWeaponById (m_currentWeapon).penetratePower; if (penetratePower == 0) { return false; @@ -584,11 +574,11 @@ bool Bot::isPenetrableObstacle (const Vector &dest) { TraceResult tr; float obstacleDistance = 0.0f; - engine.testLine (eyePos (), dest, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (getEyesPos (), dest, TRACE_IGNORE_MONSTERS, ent (), &tr); if (tr.fStartSolid) { const Vector &source = tr.vecEndPos; - engine.testLine (dest, source, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (dest, source, TRACE_IGNORE_MONSTERS, ent (), &tr); if (tr.flFraction != 1.0f) { if ((tr.vecEndPos - dest).lengthSq () > cr::square (800.0f)) { @@ -620,11 +610,11 @@ bool Bot::isPenetrableObstacle (const Vector &dest) { bool Bot::isPenetrableObstacle2 (const Vector &dest) { // this function returns if enemy can be shoot through some obstacle - if (m_difficulty < 2 || getWeaponPenetrationPower (m_currentWeapon) == 0) { + if (m_isUsingGrenade || m_difficulty < 2 || !conf.findWeaponById (m_currentWeapon).penetratePower) { return false; } - const Vector &source = eyePos (); + const Vector &source = getEyesPos (); const Vector &direction = (dest - source).normalize (); // 1 unit long int thikness = 0; @@ -633,7 +623,7 @@ bool Bot::isPenetrableObstacle2 (const Vector &dest) { Vector point; TraceResult tr; - engine.testLine (source, dest, TRACE_IGNORE_EVERYTHING, ent (), &tr); + game.testLine (source, dest, TRACE_IGNORE_EVERYTHING, ent (), &tr); while (tr.flFraction != 1.0f && numHits < 3) { numHits++; @@ -641,11 +631,11 @@ bool Bot::isPenetrableObstacle2 (const Vector &dest) { point = tr.vecEndPos + direction; - while (g_engfuncs.pfnPointContents (point) == CONTENTS_SOLID && thikness < 98) { + while (engfuncs.pfnPointContents (point) == CONTENTS_SOLID && thikness < 98) { point = point + direction; thikness++; } - engine.testLine (point, dest, TRACE_IGNORE_EVERYTHING, ent (), &tr); + game.testLine (point, dest, TRACE_IGNORE_EVERYTHING, ent (), &tr); } if (numHits < 3 && thikness < 98) { @@ -656,18 +646,19 @@ bool Bot::isPenetrableObstacle2 (const Vector &dest) { return false; } -bool Bot::throttleFiring (float distance) { +bool Bot::needToPauseFiring (float distance) { // returns true if bot needs to pause between firing to compensate for punchangle & weapon spread if (usesSniper ()) { return false; } - if (m_firePause > engine.timebase ()) + if (m_firePause > game.timebase ()) { return true; + } if ((m_aimFlags & AIM_ENEMY) && !m_enemyOrigin.empty ()) { - if (getShootingConeDeviation (ent (), m_enemyOrigin) > 0.92f && isEnemyBehindShield (m_enemy)) { + if (util.getShootingCone (ent (), m_enemyOrigin) > 0.92f && isEnemyBehindShield (m_enemy)) { return true; } } @@ -685,16 +676,16 @@ bool Bot::throttleFiring (float distance) { const float xPunch = cr::deg2rad (pev->punchangle.x); const float yPunch = cr::deg2rad (pev->punchangle.y); - float interval = calcThinkInterval (); + float interval = getFrameInterval (); 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 < engine.timebase ()) { + if (m_firePause < game.timebase ()) { m_firePause = rng.getFloat (0.5f, 0.5f + 0.3f * tolerance); } m_firePause -= interval; - m_firePause += engine.timebase (); + m_firePause += game.timebase (); return true; } @@ -702,17 +693,18 @@ bool Bot::throttleFiring (float distance) { } void Bot::selectWeapons (float distance, int index, int id, int choosen) { - WeaponSelect *tab = &g_weaponSelect[0]; + auto tab = conf.getRawWeapons (); // we want to fire weapon, don't reload now if (!m_isReloading) { m_reloadState = RELOAD_NONE; - m_reloadCheckTime = engine.timebase () + 3.0f; + m_reloadCheckTime = game.timebase () + 3.0f; } // select this weapon if it isn't already selected if (m_currentWeapon != id) { - selectWeaponByName (g_weaponDefs[id].className); + const auto &prop = conf.getWeaponProp (id); + selectWeaponByName (prop.classname); // reset burst fire variables m_firePause = 0.0f; @@ -736,19 +728,19 @@ 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 < engine.timebase () && taskId () != TASK_CAMP) // better shield gun usage + if (hasShield () && m_shieldCheckTime < game.timebase () && taskId () != TASK_CAMP) // better shield gun usage { if (distance >= 750.0f && !isShieldDrawn ()) { pev->button |= IN_ATTACK2; // draw the shield } - else if (isShieldDrawn () || (!engine.isNullEntity (m_enemy) && ((m_enemy->v.button & IN_RELOAD) || !seesEnemy (m_enemy)))) { + else if (isShieldDrawn () || (!game.isNullEntity (m_enemy) && ((m_enemy->v.button & IN_RELOAD) || !seesEnemy (m_enemy)))) { pev->button |= IN_ATTACK2; // draw out the shield } - m_shieldCheckTime = engine.timebase () + 1.0f; + m_shieldCheckTime = game.timebase () + 1.0f; } // is the bot holding a sniper rifle? - if (usesSniper () && m_zoomCheckTime < engine.timebase ()) { + if (usesSniper () && m_zoomCheckTime < game.timebase ()) { // should the bot switch to the long-range zoom? if (distance > 1500.0f && pev->fov >= 40.0f) { pev->button |= IN_ATTACK2; @@ -763,11 +755,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 = engine.timebase () + 0.25f; + m_zoomCheckTime = game.timebase () + 0.25f; } // else is the bot holding a zoomable rifle? - else if (m_difficulty < 3 && usesZoomableRifle () && m_zoomCheckTime < engine.timebase ()) { + else if (m_difficulty < 3 && usesZoomableRifle () && m_zoomCheckTime < game.timebase ()) { // should the bot switch to zoomed mode? if (distance > 800.0f && pev->fov >= 90.0f) { pev->button |= IN_ATTACK2; @@ -777,23 +769,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 = engine.timebase () + 0.5f; + m_zoomCheckTime = game.timebase () + 0.5f; } // we're should stand still before firing sniper weapons, else sniping is useless.. if (usesSniper () && (m_states & (STATE_SEEING_ENEMY | STATE_SUSPECT_ENEMY)) && !m_isReloading && pev->velocity.lengthSq () > 0.0f) { m_moveSpeed = 0.0f; m_strafeSpeed = 0.0f; - m_navTimeset = engine.timebase (); + m_navTimeset = game.timebase (); if (cr::abs (pev->velocity.x) > 5.0f || cr::abs (pev->velocity.y) > 5.0f || cr::abs (pev->velocity.z) > 5.0f) { - m_sniperStopTime = engine.timebase () + 2.5f; + m_sniperStopTime = game.timebase () + 2.5f; return; } } // need to care for burst fire? - if (distance < MAX_SPRAY_DISTANCE || m_blindTime > engine.timebase ()) { + if (distance < MAX_SPRAY_DISTANCE || m_blindTime > game.timebase ()) { if (id == WEAPON_KNIFE) { if (distance < 64.0f) { if (rng.chance (30) || hasShield ()) { @@ -805,9 +797,10 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) { } } else { + const auto &prop = conf.getWeaponProp (tab[index].id); // if automatic weapon press attack - if (tab[choosen].primaryFireHold && m_ammo[g_weaponDefs[tab[index].id].ammo1] > tab[index].minPrimaryAmmo) { + if (tab[choosen].primaryFireHold && m_ammo[prop.ammo1] > tab[index].minPrimaryAmmo) { pev->button |= IN_ATTACK; } @@ -818,22 +811,22 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) { } } } - m_shootTime = engine.timebase (); + m_shootTime = game.timebase (); } else { - if (throttleFiring (distance)) { + if (needToPauseFiring (distance)) { return; } // don't attack with knife over long distance if (id == WEAPON_KNIFE) { - m_shootTime = engine.timebase (); + m_shootTime = game.timebase (); return; } if (tab[choosen].primaryFireHold) { - m_shootTime = engine.timebase (); - m_zoomCheckTime = engine.timebase (); + m_shootTime = game.timebase (); + m_zoomCheckTime = game.timebase (); pev->button |= IN_ATTACK; // use primary attack } @@ -845,27 +838,26 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) { const int offset = cr::abs (m_difficulty * 25 / 20 - 5); - m_shootTime = engine.timebase () + 0.1f + rng.getFloat (minDelay[offset], maxDelay[offset]); - m_zoomCheckTime = engine.timebase (); + m_shootTime = game.timebase () + 0.1f + rng.getFloat (minDelay[offset], maxDelay[offset]); + m_zoomCheckTime = game.timebase (); } } } void Bot::fireWeapons (void) { // this function will return true if weapon was fired, false otherwise - float distance = (m_lookAt - eyePos ()).length (); // how far away is the enemy? + 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 (!engine.isNullEntity (m_enemy)) { + if (!game.isNullEntity (m_enemy)) { if (isFriendInLineOfFire (distance)) { m_fightStyle = FIGHT_STRAFE; - m_lastFightStyleCheck = engine.timebase (); + m_lastFightStyleCheck = game.timebase (); return; } } - WeaponSelect *tab = &g_weaponSelect[0]; - + auto tab = conf.getRawWeapons (); edict_t *enemy = m_enemy; int selectId = WEAPON_KNIFE, selectIndex = 0, choosenWeapon = 0; @@ -878,7 +870,7 @@ void Bot::fireWeapons (void) { } // use knife if near and good difficulty (l33t dude!) - if (m_difficulty >= 3 && pev->health > 80.0f && !engine.isNullEntity (enemy) && pev->health >= enemy->v.health && distance < 100.0f && !isOnLadder () && !isGroupOfEnemies (pev->origin)) { + if (m_difficulty >= 3 && pev->health > 80.0f && !game.isNullEntity (enemy) && pev->health >= enemy->v.health && distance < 100.0f && !isOnLadder () && !isGroupOfEnemies (pev->origin)) { selectWeapons (distance, selectIndex, selectId, choosenWeapon); return; } @@ -907,12 +899,14 @@ void Bot::fireWeapons (void) { // is the bot carrying this weapon? if (weapons & (1 << id)) { - if (g_weaponDefs[id].ammo1 != -1 && g_weaponDefs[id].ammo1 < 32 && m_ammo[g_weaponDefs[id].ammo1] >= tab[selectIndex].minPrimaryAmmo) { + const auto &prop = conf.getWeaponProp (id); + + if (prop.ammo1 != -1 && prop.ammo1 < 32 && m_ammo[prop.ammo1] >= tab[selectIndex].minPrimaryAmmo) { // available ammo found, reload weapon - if (m_reloadState == RELOAD_NONE || m_reloadCheckTime > engine.timebase ()) { + if (m_reloadState == RELOAD_NONE || m_reloadCheckTime > game.timebase ()) { m_isReloading = true; m_reloadState = RELOAD_PRIMARY; - m_reloadCheckTime = engine.timebase (); + m_reloadCheckTime = game.timebase (); if (rng.chance (cr::abs (m_difficulty * 25 - 100))) { pushRadioMessage (RADIO_NEED_BACKUP); @@ -932,17 +926,19 @@ bool Bot::isWeaponBadAtDistance (int weaponIndex, float distance) { // this function checks, is it better to use pistol instead of current primary weapon // to attack our enemy, since current weapon is not very good in this situation. + auto &info = conf.getWeapons (); + if (m_difficulty < 2) { return false; } - int wid = g_weaponSelect[weaponIndex].id; + int wid = info[weaponIndex].id; if (wid == WEAPON_KNIFE) { return false; } // check is ammo available for secondary weapon - if (m_ammoInClip[g_weaponSelect[bestSecondaryCarried ()].id] >= 1) { + if (m_ammoInClip[info[bestSecondaryCarried ()].id] >= 3) { return false; } @@ -962,10 +958,10 @@ void Bot::focusEnemy (void) { // aim for the head and/or body m_lookAt = getEnemyBodyOffset (); - if (m_enemySurpriseTime > engine.timebase () || engine.isNullEntity (m_enemy)) { + if (m_enemySurpriseTime > game.timebase () || game.isNullEntity (m_enemy)) { return; } - float distance = (m_lookAt - eyePos ()).length2D (); // how far away is the enemy scum? + float distance = (m_lookAt - getEyesPos ()).length2D (); // how far away is the enemy scum? if (distance < 128.0f && !usesSniper ()) { if (m_currentWeapon == WEAPON_KNIFE) { @@ -981,13 +977,13 @@ void Bot::focusEnemy (void) { } } else { - float dot = getShootingConeDeviation (ent (), m_enemyOrigin); + float dot = util.getShootingCone (ent (), m_enemyOrigin); if (dot < 0.90f) { m_wantsToFire = false; } else { - float enemyDot = getShootingConeDeviation (m_enemy, pev->origin); + float enemyDot = util.getShootingCone (m_enemy, pev->origin); // enemy faces bot? if (enemyDot >= 0.90f) { @@ -1007,12 +1003,12 @@ void Bot::focusEnemy (void) { void Bot::attackMovement (void) { // no enemy? no need to do strafing - if (engine.isNullEntity (m_enemy)) { + if (game.isNullEntity (m_enemy)) { return; } - float distance = (m_lookAt - eyePos ()).length2D (); // how far away is the enemy scum? + float distance = (m_lookAt - getEyesPos ()).length2D (); // how far away is the enemy scum? - if (m_timeWaypointMove + calcThinkInterval () + 0.5f < engine.timebase ()) { + if (m_timeWaypointMove + getFrameInterval () + 0.5f < game.timebase ()) { int approach; if (m_currentWeapon == WEAPON_KNIFE) { @@ -1033,7 +1029,7 @@ void Bot::attackMovement (void) { } // only take cover when bomb is not planted and enemy can see the bot or the bot is VIP - if ((m_states & STATE_SEEING_ENEMY) && approach < 30 && !g_bombPlanted && (isInViewCone (m_enemy->v.origin) || m_isVIP)) { + if ((m_states & STATE_SEEING_ENEMY) && approach < 30 && !bots.isBombPlanted () && (isInViewCone (m_enemy->v.origin) || m_isVIP)) { m_moveSpeed = -pev->maxspeed; startTask (TASK_SEEKCOVER, TASKPRI_SEEKCOVER, INVALID_WAYPOINT_INDEX, 0.0f, true); } @@ -1050,10 +1046,10 @@ void Bot::attackMovement (void) { if (usesSniper () || !(m_visibility & (VISIBLE_BODY | VISIBLE_HEAD))) { m_fightStyle = FIGHT_STAY; - m_lastFightStyleCheck = engine.timebase (); + m_lastFightStyleCheck = game.timebase (); } else if (usesRifle () || usesSubmachine ()) { - if (m_lastFightStyleCheck + 3.0f < engine.timebase ()) { + if (m_lastFightStyleCheck + 3.0f < game.timebase ()) { int rand = rng.getInt (1, 100); if (distance < 450.0f) { @@ -1075,28 +1071,29 @@ void Bot::attackMovement (void) { m_fightStyle = FIGHT_STRAFE; } } - m_lastFightStyleCheck = engine.timebase (); + m_lastFightStyleCheck = game.timebase (); } } else { - if (m_lastFightStyleCheck + 3.0f < engine.timebase ()) { + if (m_lastFightStyleCheck + 3.0f < game.timebase ()) { if (rng.chance (50)) { m_fightStyle = FIGHT_STRAFE; } else { m_fightStyle = FIGHT_STAY; } - m_lastFightStyleCheck = engine.timebase (); + m_lastFightStyleCheck = game.timebase (); } } if (m_fightStyle == FIGHT_STRAFE || ((pev->button & IN_RELOAD) || m_isReloading) || (usesPistol () && distance < 400.0f) || m_currentWeapon == WEAPON_KNIFE) { - if (m_strafeSetTime < engine.timebase ()) { + if (m_strafeSetTime < game.timebase ()) { + // to start strafing, we have to first figure out if the target is on the left side or right side - makeVectors (m_enemy->v.v_angle); + game.makeVectors (m_enemy->v.v_angle); const Vector &dirToPoint = (pev->origin - m_enemy->v.origin).normalize2D (); - const Vector &rightSide = g_pGlobals->v_right.normalize2D (); + const Vector &rightSide = game.vec.right.normalize2D (); if ((dirToPoint | rightSide) < 0) { m_combatStrafeDir = STRAFE_DIR_LEFT; @@ -1108,7 +1105,7 @@ void Bot::attackMovement (void) { if (rng.chance (30)) { m_combatStrafeDir = (m_combatStrafeDir == STRAFE_DIR_LEFT ? STRAFE_DIR_RIGHT : STRAFE_DIR_LEFT); } - m_strafeSetTime = engine.timebase () + rng.getFloat (0.5f, 3.0f); + m_strafeSetTime = game.timebase () + rng.getFloat (0.5f, 3.0f); } if (m_combatStrafeDir == STRAFE_DIR_RIGHT) { @@ -1117,7 +1114,7 @@ void Bot::attackMovement (void) { } else { m_combatStrafeDir = STRAFE_DIR_LEFT; - m_strafeSetTime = engine.timebase () + rng.getFloat (0.8f, 1.3f); + m_strafeSetTime = game.timebase () + rng.getFloat (0.8f, 1.3f); } } else { @@ -1126,11 +1123,11 @@ void Bot::attackMovement (void) { } else { m_combatStrafeDir = STRAFE_DIR_RIGHT; - m_strafeSetTime = engine.timebase () + rng.getFloat (0.8f, 1.3f); + m_strafeSetTime = game.timebase () + rng.getFloat (0.8f, 1.3f); } } - if (m_difficulty >= 3 && (m_jumpTime + 5.0f < engine.timebase () && isOnFloor () && rng.getInt (0, 1000) < (m_isReloading ? 8 : 2) && pev->velocity.length2D () > 120.0f) && !usesSniper ()) { + if (m_difficulty >= 3 && (m_jumpTime + 5.0f < game.timebase () && isOnFloor () && rng.getInt (0, 1000) < (m_isReloading ? 8 : 2) && pev->velocity.length2D () > 120.0f) && !usesSniper ()) { pev->button |= IN_JUMP; } @@ -1147,16 +1144,16 @@ void Bot::attackMovement (void) { int enemyNearestIndex = waypoints.getNearest (m_enemy->v.origin); if (waypoints.isDuckVisible (m_currentWaypointIndex, enemyNearestIndex) && waypoints.isDuckVisible (enemyNearestIndex, m_currentWaypointIndex)) { - m_duckTime = engine.timebase () + 1.0f; + m_duckTime = game.timebase () + 0.5f; } } m_moveSpeed = 0.0f; m_strafeSpeed = 0.0f; - m_navTimeset = engine.timebase (); + m_navTimeset = game.timebase (); } } - if (m_duckTime > engine.timebase ()) { + if (m_duckTime > game.timebase ()) { m_moveSpeed = 0.0f; m_strafeSpeed = 0.0f; } @@ -1167,13 +1164,13 @@ void Bot::attackMovement (void) { if (m_isReloading) { m_moveSpeed = -pev->maxspeed; - m_duckTime = engine.timebase () - 1.0f; + m_duckTime = game.timebase () - 1.0f; } if (!isInWater () && !isOnLadder () && (m_moveSpeed > 0.0f || m_strafeSpeed >= 0.0f)) { - makeVectors (pev->v_angle); + game.makeVectors (pev->v_angle); - if (isDeadlyMove (pev->origin + (g_pGlobals->v_forward * m_moveSpeed * 0.2f) + (g_pGlobals->v_right * m_strafeSpeed * 0.2f) + (pev->velocity * calcThinkInterval ()))) { + if (isDeadlyMove (pev->origin + (game.vec.forward * m_moveSpeed * 0.2f) + (game.vec.right * m_strafeSpeed * 0.2f) + (pev->velocity * getFrameInterval ()))) { m_strafeSpeed = -m_strafeSpeed; m_moveSpeed = -m_moveSpeed; @@ -1213,13 +1210,13 @@ bool Bot::isShieldDrawn (void) { bool Bot::isEnemyBehindShield (edict_t *enemy) { // this function returns true, if enemy protected by the shield - if (engine.isNullEntity (enemy) || isShieldDrawn ()) { + if (game.isNullEntity (enemy) || isShieldDrawn ()) { return false; } // check if enemy has shield and this shield is drawn if (strncmp (STRING (enemy->v.viewmodel), "models/shield/v_shield_", 23) == 0 && (enemy->v.weaponanim == 6 || enemy->v.weaponanim == 7)) { - if (::isInViewCone (pev->origin, enemy)) { + if (util.isInViewCone (pev->origin, enemy)) { return true; } } @@ -1233,7 +1230,7 @@ bool Bot::usesSniper (void) { } bool Bot::usesRifle (void) { - WeaponSelect *tab = &g_weaponSelect[0]; + auto tab = conf.getRawWeapons (); int count = 0; while (tab->id) { @@ -1251,7 +1248,7 @@ bool Bot::usesRifle (void) { } bool Bot::usesPistol (void) { - WeaponSelect *tab = &g_weaponSelect[0]; + auto tab = conf.getRawWeapons (); int count = 0; // loop through all the weapons until terminator is found @@ -1285,6 +1282,56 @@ bool Bot::usesBadWeapon (void) { return m_currentWeapon == WEAPON_XM1014 || m_currentWeapon == WEAPON_M3 || m_currentWeapon == WEAPON_UMP45 || m_currentWeapon == WEAPON_MAC10 || m_currentWeapon == WEAPON_TMP || m_currentWeapon == WEAPON_P90; } +int Bot::bestPrimaryCarried (void) { + // this function returns the best weapon of this bot (based on personality prefs) + + const int *pref = conf.getWeaponPrefs (m_personality); + + int weaponIndex = 0; + int weapons = pev->weapons; + + auto &weaponTab = conf.getWeapons (); + + // take the shield in account + if (hasShield ()) { + weapons |= (1 << WEAPON_SHIELD); + } + + for (int i = 0; i < NUM_WEAPONS; i++) { + if (weapons & (1 << weaponTab[*pref].id)) { + weaponIndex = i; + } + pref++; + } + return weaponIndex; +} + +int Bot::bestSecondaryCarried (void) { + // this function returns the best secondary weapon of this bot (based on personality prefs) + + const int *pref = conf.getWeaponPrefs (m_personality); + + int weaponIndex = 0; + int weapons = pev->weapons; + + // take the shield in account + if (hasShield ()) { + weapons |= (1 << WEAPON_SHIELD); + } + auto tab = conf.getRawWeapons (); + + for (int i = 0; i < NUM_WEAPONS; i++) { + int id = tab[*pref].id; + + if ((weapons & (1 << tab[*pref].id)) && (id == WEAPON_USP || id == WEAPON_GLOCK || id == WEAPON_DEAGLE || id == WEAPON_P228 || id == WEAPON_ELITE || id == WEAPON_FIVESEVEN)) { + weaponIndex = i; + break; + } + pref++; + } + return weaponIndex; +} + int Bot::bestGrenadeCarried (void) { if (pev->weapons & (1 << WEAPON_EXPLOSIVE)) { return WEAPON_EXPLOSIVE; @@ -1298,6 +1345,32 @@ int Bot::bestGrenadeCarried (void) { return -1; } +bool Bot::rateGroundWeapon (edict_t *ent) { + // this function compares weapons on the ground to the one the bot is using + + int groundIndex = 0; + + const int *pref = conf.getWeaponPrefs (m_personality); + auto tab = conf.getRawWeapons (); + + for (int i = 0; i < NUM_WEAPONS; i++) { + if (strcmp (tab[*pref].model, STRING (ent->v.model) + 9) == 0) { + groundIndex = i; + break; + } + pref++; + } + int hasWeapon = 0; + + if (groundIndex < 7) { + hasWeapon = bestSecondaryCarried (); + } + else { + hasWeapon = bestPrimaryCarried (); + } + return groundIndex > hasWeapon; +} + bool Bot::hasAnyWeapons (void) { return (pev->weapons & (WEAPON_PRIMARY | WEAPON_SECONDARY)); } @@ -1315,7 +1388,7 @@ void Bot::selectBestWeapon (void) { if (m_isReloading) { return; } - WeaponSelect *tab = &g_weaponSelect[0]; + auto tab = conf.getRawWeapons (); int selectIndex = 0; int chosenWeaponIndex = 0; @@ -1332,12 +1405,13 @@ void Bot::selectBestWeapon (void) { bool ammoLeft = false; // is the bot already holding this weapon and there is still ammo in clip? - if (tab[selectIndex].id == m_currentWeapon && (ammoClip () < 0 || ammoClip () >= tab[selectIndex].minPrimaryAmmo)) { + if (tab[selectIndex].id == m_currentWeapon && (getAmmoInClip () < 0 || getAmmoInClip () >= tab[selectIndex].minPrimaryAmmo)) { ammoLeft = true; } + const auto &prop = conf.getWeaponProp (id); // is no ammo required for this weapon OR enough ammo available to fire - if (g_weaponDefs[id].ammo1 < 0 || (g_weaponDefs[id].ammo1 < 32 && m_ammo[g_weaponDefs[id].ammo1] >= tab[selectIndex].minPrimaryAmmo)) { + if (prop.ammo1 < 0 || (prop.ammo1 < 32 && m_ammo[prop.ammo1] >= tab[selectIndex].minPrimaryAmmo)) { ammoLeft = true; } @@ -1354,7 +1428,7 @@ void Bot::selectBestWeapon (void) { // select this weapon if it isn't already selected if (m_currentWeapon != id) { - selectWeaponByName (tab[selectIndex].weaponName); + selectWeaponByName (tab[selectIndex].name); } m_isReloading = false; m_reloadState = RELOAD_NONE; @@ -1370,7 +1444,7 @@ void Bot::selectSecondary (void) { } int Bot::bestWeaponCarried (void) { - WeaponSelect *tab = &g_weaponSelect[0]; + auto tab = conf.getRawWeapons (); int weapons = pev->weapons; int num = 0; @@ -1389,11 +1463,13 @@ int Bot::bestWeaponCarried (void) { } void Bot::selectWeaponByName (const char *name) { - engine.execBotCmd (ent (), name); + game.execBotCmd (ent (), name); } void Bot::selectWeaponById (int num) { - engine.execBotCmd (ent (), g_weaponSelect[num].weaponName); + auto tab = conf.getRawWeapons (); + + game.execBotCmd (ent (), tab[num].name); } void Bot::decideFollowUser (void) { @@ -1401,14 +1477,12 @@ void Bot::decideFollowUser (void) { Array users; // search friends near us - for (int i = 0; i < engine.maxClients (); i++) { - const Client &client = g_clients[i]; - + for (const auto &client : util.getClients ()) { if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.team != m_team || client.ent == ent ()) { continue; } - if (seesEntity (client.origin) && !isFakeClient (client.ent)) { + if (seesEntity (client.origin) && !util.isFakeClient (client.ent)) { users.push (client.ent); } } @@ -1422,9 +1496,9 @@ void Bot::decideFollowUser (void) { startTask (TASK_FOLLOWUSER, TASKPRI_FOLLOWUSER, INVALID_WAYPOINT_INDEX, 0.0f, true); } -void Bot::processTeamCommands (void) { +void Bot::updateTeamCommands (void) { // prevent spamming - if (m_timeTeamOrder > engine.timebase () + 2.0f || (g_gameFlags & GAME_CSDM_FFA) || !yb_communication_type.integer ()) { + if (m_timeTeamOrder > game.timebase () + 2.0f || game.is (GAME_CSDM_FFA) || !yb_communication_type.integer ()) { return; } @@ -1432,9 +1506,7 @@ void Bot::processTeamCommands (void) { bool memberExists = false; // search teammates seen by this bot - for (int i = 0; i < engine.maxClients (); i++) { - const Client &client = g_clients[i]; - + for (const auto &client : util.getClients ()) { if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.team != m_team || client.ent == ent ()) { continue; } @@ -1461,16 +1533,14 @@ void Bot::processTeamCommands (void) { else if (memberExists && yb_communication_type.integer () == 2) { pushChatterMessage (CHATTER_SCARED_EMOTE); } - m_timeTeamOrder = engine.timebase () + rng.getFloat (15.0f, 30.0f); + m_timeTeamOrder = game.timebase () + rng.getFloat (15.0f, 30.0f); } bool Bot::isGroupOfEnemies (const Vector &location, int numEnemies, float radius) { int numPlayers = 0; // search the world for enemy players... - for (int i = 0; i < engine.maxClients (); i++) { - const Client &client = g_clients[i]; - + for (const auto &client : util.getClients ()) { if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.ent == ent ()) { continue; } @@ -1497,10 +1567,10 @@ void Bot::checkReload (void) { } m_isReloading = false; // update reloading status - m_reloadCheckTime = engine.timebase () + 3.0f; + m_reloadCheckTime = game.timebase () + 3.0f; if (m_reloadState != RELOAD_NONE) { - int weaponIndex = 0, maxClip = 0; + int weaponIndex = 0; int weapons = pev->weapons; if (m_reloadState == RELOAD_PRIMARY) { @@ -1525,11 +1595,11 @@ void Bot::checkReload (void) { break; } } - maxClip = getMaxClip (weaponIndex); + const auto &prop = conf.getWeaponProp (weaponIndex); - if (m_ammoInClip[weaponIndex] < maxClip * 0.8f && g_weaponDefs[weaponIndex].ammo1 != -1 && g_weaponDefs[weaponIndex].ammo1 < 32 && m_ammo[g_weaponDefs[weaponIndex].ammo1] > 0) { + if (m_ammoInClip[weaponIndex] < conf.findWeaponById (weaponIndex).maxClip * 0.8f && prop.ammo1 != -1 && prop.ammo1 < 32 && m_ammo[prop.ammo1] > 0) { if (m_currentWeapon != weaponIndex) { - selectWeaponByName (g_weaponDefs[weaponIndex].className); + selectWeaponByName (prop.classname); } pev->button &= ~IN_ATTACK; @@ -1540,7 +1610,7 @@ void Bot::checkReload (void) { } else { // if we have enemy don't reload next weapon - if ((m_states & (STATE_SEEING_ENEMY | STATE_HEARING_ENEMY)) || m_seeEnemyTime + 5.0f > engine.timebase ()) { + if ((m_states & (STATE_SEEING_ENEMY | STATE_HEARING_ENEMY)) || m_seeEnemyTime + 5.0f > game.timebase ()) { m_reloadState = RELOAD_NONE; return; } @@ -1553,67 +1623,3 @@ void Bot::checkReload (void) { } } } - -int Bot::getMaxClip (int id) { - int maxClip = 0; - - switch (id) { - case WEAPON_M249: - maxClip = 100; - break; - - case WEAPON_P90: - maxClip = 50; - break; - - case WEAPON_GALIL: - maxClip = 35; - break; - - case WEAPON_ELITE: - case WEAPON_MP5: - case WEAPON_TMP: - case WEAPON_MAC10: - case WEAPON_M4A1: - case WEAPON_AK47: - case WEAPON_SG552: - case WEAPON_AUG: - case WEAPON_SG550: - maxClip = 30; - break; - - case WEAPON_UMP45: - case WEAPON_FAMAS: - maxClip = 25; - break; - - case WEAPON_GLOCK: - case WEAPON_FIVESEVEN: - case WEAPON_G3SG1: - maxClip = 20; - break; - - case WEAPON_P228: - maxClip = 13; - break; - - case WEAPON_USP: - maxClip = 12; - break; - - case WEAPON_AWP: - case WEAPON_SCOUT: - maxClip = 10; - break; - - case WEAPON_M3: - maxClip = 8; - break; - - case WEAPON_DEAGLE: - case WEAPON_XM1014: - maxClip = 7; - break; - } - return maxClip; -} diff --git a/source/control.cpp b/source/control.cpp new file mode 100644 index 0000000..3e5da75 --- /dev/null +++ b/source/control.cpp @@ -0,0 +1,2031 @@ +// +// Yet Another POD-Bot, based on PODBot by Markus Klinge ("CountFloyd"). +// Copyright (c) YaPB Development Team. +// +// This software is licensed under the BSD-style license. +// Additional exceptions apply. For full license details, see LICENSE.txt or visit: +// https://yapb.ru/license +// + +#include + +ConVar yb_display_menu_text ("yb_display_menu_text", "1"); +ConVar yb_password ("yb_password", "", VT_PASSWORD); +ConVar yb_password_key ("yb_password_key", "_ybpw"); + +int BotControl::cmdAddBot (void) { + enum args { alias = 1, difficulty, personality, team, model, name, max }; + + // adding more args to args array, if not enough passed + fixMissingArgs (max); + + // if team is specified, modify args to set team + if (m_args[alias].find ("_ct", 0) != String::INVALID_INDEX) { + m_args.set (team, "2"); + } + else if (m_args[alias].find ("_t", 0) != String::INVALID_INDEX) { + 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::INVALID_INDEX) { + m_args.set (difficulty, "4"); + m_args.set (personality, "1"); + } + bots.addbot (m_args[name], m_args[difficulty], m_args[personality], m_args[team], m_args[model], true); + + return CMD_STATUS_HANDLED; +} + +int BotControl::cmdKickBot (void) { + enum args { alias = 1, team, max }; + + // adding more args to args array, if not enough passed + fixMissingArgs (max); + + // if team is specified, kick from specified tram + if (m_args[alias].find ("_ct", 0) != String::INVALID_INDEX || getInt (team) == 2 || getStr (team) == "ct") { + bots.kickFromTeam (TEAM_COUNTER); + } + else if (m_args[alias].find ("_t", 0) != String::INVALID_INDEX || getInt (team) == 1 || getStr (team) == "t") { + bots.kickFromTeam (TEAM_TERRORIST); + } + else { + bots.kickRandom (); + } + return CMD_STATUS_HANDLED; +} + +int BotControl::cmdKickBots (void) { + enum args { alias = 1, instant, max }; + + // adding more args to args array, if not enough passed + fixMissingArgs (max); + + // check if we're need to remove bots instantly + auto kickInstant = getStr (instant) == "instant"; + + // kick the bots + bots.kickEveryone (kickInstant); + + return CMD_STATUS_HANDLED; +} + +int BotControl::cmdKillBots (void) { + enum args { alias = 1, team, max }; + + // adding more args to args array, if not enough passed + fixMissingArgs (max); + + // if team is specified, kick from specified tram + if (m_args[alias].find ("_ct", 0) != String::INVALID_INDEX || getInt (team) == 2 || getStr (team) == "ct") { + bots.killAllBots (TEAM_COUNTER); + } + else if (m_args[alias].find ("_t", 0) != String::INVALID_INDEX || getInt (team) == 1 || getStr (team) == "t") { + bots.killAllBots (TEAM_TERRORIST); + } + else { + bots.killAllBots (); + } + return CMD_STATUS_HANDLED; +} + +int BotControl::cmdFill (void) { + enum args { alias = 1, team, count, difficulty, personality, max }; + + // adding more args to args array, if not enough passed + fixMissingArgs (max); + + if (!hasArg (team)) { + return CMD_STATUS_BADFORMAT; + } + bots.serverFill (getInt (team), hasArg (personality) ? getInt (personality) : -1, hasArg (difficulty) ? getInt (difficulty) : -1, hasArg (count) ? getInt (count) : -1); + + return CMD_STATUS_HANDLED; +} + +int BotControl::cmdVote (void) { + enum args { alias = 1, mapid, max }; + + // adding more args to args array, if not enough passed + fixMissingArgs (max); + + if (!hasArg (mapid)) { + return CMD_STATUS_BADFORMAT; + } + int mapID = getInt (mapid); + + // loop through all players + for (int i = 0; i < game.maxClients (); i++) { + auto bot = bots.getBot (i); + + if (bot != nullptr) { + bot->m_voteMap = mapID; + } + } + msg ("All dead bots will vote for map #%d", mapID); + + return CMD_STATUS_HANDLED; +} + +int BotControl::cmdWeaponMode (void) { + enum args { alias = 1, type, max }; + + // adding more args to args array, if not enough passed + fixMissingArgs (max); + + if (!hasArg (type)) { + return CMD_STATUS_BADFORMAT; + } + HashMap modes; + + modes.put ("kinfe", 1); + modes.put ("pistol", 2); + modes.put ("shotgun", 3); + modes.put ("smg", 4); + modes.put ("rifle", 5); + modes.put ("sniper", 6); + modes.put ("stanard", 7); + + auto mode = getStr (type); + + // check if selected mode exists + if (!modes.exists (mode)) { + return CMD_STATUS_BADFORMAT; + } + bots.setWeaponMode (modes[mode]); + + return CMD_STATUS_HANDLED; +} + +int BotControl::cmdVersion (void) { + msg ("%s v%s (build %u)", PRODUCT_NAME, PRODUCT_VERSION, util.buildNumber ()); + msg (" compiled: %s %s by %s", __DATE__, __TIME__, PRODUCT_GIT_COMMIT_AUTHOR); + msg (" commit: %scommit/%s", PRODUCT_COMMENTS, PRODUCT_GIT_HASH); + msg (" url: %s", PRODUCT_URL); + + return CMD_STATUS_HANDLED; +} + +int BotControl::cmdWaypointMenu (void) { + enum args { alias = 1, max }; + + // waypoints is not supported on DS yet + if (!waypoints.hasEditor ()) { + msg ("Unable to open waypoint editor without setting the editor player."); + return CMD_STATUS_HANDLED; + } + showMenu (BOT_MENU_WAYPOINT_MAIN_PAGE1); + + return CMD_STATUS_HANDLED; +} + +int BotControl::cmdMenu (void) { + enum args { alias = 1, cmd, max }; + + // adding more args to args array, if not enough passed + fixMissingArgs (max); + + // reset the current menu + showMenu (BOT_MENU_INVALID); + + if (getStr (cmd) == "cmd" && util.isAlive (m_ent)) { + showMenu (BOT_MENU_COMMANDS); + } + else { + showMenu (BOT_MENU_MAIN); + } + return CMD_STATUS_HANDLED; +} + +int BotControl::cmdList (void) { + enum args { alias = 1, max }; + + bots.listBots (); + return CMD_STATUS_HANDLED; +} + +int BotControl::cmdWaypoint (void) { + enum args { root, alias, cmd, cmd2, max }; + + // adding more args to args array, if not enough passed + fixMissingArgs (max); + + // waypoints is not supported on DS yet + if (game.isDedicated () && !waypoints.hasEditor () && getStr (cmd) != "acquire_editor") { + msg ("Unable to use waypoint edit commands without setting waypoint editor player. Please use \"waypoint acquire_editor\" to acquire rights for waypoint editing"); + return CMD_STATUS_HANDLED; + } + + // should be moved to class? + static HashMap commands; + static Array descriptions; + + // fill only once + if (descriptions.empty ()) { + + // separate function + auto addWaypointCommand = [&] (const String &cmd, const String &format, const String &help, Handler handler) -> void { + commands.put (cmd, { cmd, format, help, cr::forward (handler) }); + descriptions.push (cmd); + }; + + // add waypoint commands + addWaypointCommand ("on", "on [display|auto|noclip|models]", "Enables displaying of waypoints, autowaypoint, noclip cheat", &BotControl::cmdWaypointOn); + addWaypointCommand ("off", "off [display|auto|noclip|models]", "Disables displaying of waypoints, autowaypoint, noclip cheat", &BotControl::cmdWaypointOff); + addWaypointCommand ("menu", "menu [noarguments]", "Opens and displays bots waypoint edtior.", &BotControl::cmdWaypointMenu); + addWaypointCommand ("add", "add [noarguments]", "Opens and displays bots waypoint add menu.", &BotControl::cmdWaypointAdd); + addWaypointCommand ("addbasic", "menu [noarguments]", "Adds basic waypoints such as player spawn points, goals and ladders.", &BotControl::cmdWaypointAddBasic); + addWaypointCommand ("save", "save [noarguments]", "Save waypoint file to disk.", &BotControl::cmdWaypointSave); + addWaypointCommand ("load", "load [noarguments]", "Load waypoint file from disk.", &BotControl::cmdWaypointLoad); + addWaypointCommand ("erase", "erase [iamsure]", "Erases the waypoint file from disk.", &BotControl::cmdWaypointErase); + addWaypointCommand ("delete", "delete [nearest|index]", "Deletes single waypoint from map.", &BotControl::cmdWaypointDelete); + addWaypointCommand ("check", "check [noarguments]", "Check if waypoints working correctly.", &BotControl::cmdWaypointCheck); + addWaypointCommand ("cache", "cache [nearest|index]", "Caching waypoint for future use.", &BotControl::cmdWaypointCache); + addWaypointCommand ("clean", "clean [all|nearest|index]", "Clean useless path connections from all or single waypoint.", &BotControl::cmdWaypointClean); + addWaypointCommand ("setradius", "setradius [radius] [nearest|index]", "Sets the radius for waypoint.", &BotControl::cmdWaypointSetRadius); + addWaypointCommand ("flags", "flags [noarguments]", "Open and displays menu for modifying flags for nearest point.", &BotControl::cmdWaypointSetFlags); + addWaypointCommand ("teleport", "teleport [index]", "Teleports player to specified waypoint index.", &BotControl::cmdWaypointTeleport); + + // add path commands + addWaypointCommand ("path_create", "path_create [noarguments]", "Opens and displays path creation menu.", &BotControl::cmdWaypointPathCreate); + addWaypointCommand ("path_create_in", "path_create_in [noarguments]", "Opens and displays path creation menu.", &BotControl::cmdWaypointPathCreate); + addWaypointCommand ("path_create_out", "path_create_out [noarguments]", "Opens and displays path creation menu.", &BotControl::cmdWaypointPathCreate); + addWaypointCommand ("path_create_both", "path_create_both [noarguments]", "Opens and displays path creation menu.", &BotControl::cmdWaypointPathCreate); + addWaypointCommand ("path_delete", "path_create_both [noarguments]", "Opens and displays path creation menu.", &BotControl::cmdWaypointPathDelete); + addWaypointCommand ("path_set_autopath", "path_set_autoath [max_distance]", "Opens and displays path creation menu.", &BotControl::cmdWaypointPathSetAutoDistance); + + // remote waypoint editing stuff + if (game.isDedicated ()) { + addWaypointCommand ("acquire_editor", "acquire_editor [max_distance]", "Acquires rights to edit waypoints on dedicated server.", &BotControl::cmdWaypointAcquireEditor); + addWaypointCommand ("release_editor", "acquire_editor [max_distance]", "Releases waypoint editing rights.", &BotControl::cmdWaypointAcquireEditor); + } + } + if (commands.exists (getStr (cmd))) { + auto &item = commands[getStr (cmd)]; + + // waypoints have only bad format return status + int status = (this->*item.handler) (); + + if (status == CMD_STATUS_BADFORMAT) { + msg ("Incorrect usage of \"%s %s %s\" command. Correct usage is:\n\n\t%s\n\nPlease use correct format.", m_args[root].chars (), m_args[alias].chars (), item.name.chars (), item.format.chars ()); + } + } + else { + if (getStr (cmd) == "help" && hasArg (cmd2) && commands.exists (getStr (cmd2))) { + auto &item = commands[getStr (cmd2)]; + + msg ("Command: \"%s %s %s\"\nFormat: %s\nHelp: %s", m_args[root].chars (), m_args[alias].chars (), item.name.chars (), item.format.chars (), item.help.chars ()); + } + else { + for (auto &desc : descriptions) { + auto &item = commands[desc]; + msg (" %s - %s", item.name.chars (), item.help.chars ()); + } + msg ("Currently waypoints are %s", waypoints.hasEditFlag (WS_EDIT_ENABLED) ? "Enabled" : "Disabled"); + } + } + return CMD_STATUS_HANDLED; +} + +int BotControl::cmdWaypointOn (void) { + enum args { alias = 1, cmd, option, max }; + + // adding more args to args array, if not enough passed + fixMissingArgs (max); + + // enable various features of editor + if (getStr (option) == "empty" || getStr (option) == "display" || getStr (option) == "models") { + waypoints.setEditFlag (WS_EDIT_ENABLED); + enableDrawModels (true); + + msg ("Waypoint editor has been enabled."); + } + else if (getStr (option) == "noclip") { + m_ent->v.movetype = MOVETYPE_NOCLIP; + + waypoints.setEditFlag (WS_EDIT_ENABLED | WS_EDIT_NOCLIP); + enableDrawModels (true); + + msg ("Waypoint editor has been enabled with noclip mode."); + } + else if (getStr (option) == "auto") { + waypoints.setEditFlag (WS_EDIT_ENABLED | WS_EDIT_AUTO); + enableDrawModels (true); + + msg ("Waypoint editor has been enabled with autowaypoint mode."); + } + + if (waypoints.hasEditFlag (WS_EDIT_ENABLED)) { + extern ConVar mp_roundtime, mp_freezetime, mp_timelimit; + + mp_roundtime.set (9); + mp_freezetime.set (0); + mp_timelimit.set (0); + } + return CMD_STATUS_HANDLED; +} + +int BotControl::cmdWaypointOff (void) { + enum args { waypoint = 1, cmd, option, max }; + + // adding more args to args array, if not enough passed + fixMissingArgs (max); + + // enable various features of editor + if (getStr (option) == "empty" || getStr (option) == "display") { + waypoints.clearEditFlag (WS_EDIT_ENABLED | WS_EDIT_AUTO | WS_EDIT_NOCLIP); + enableDrawModels (false); + + msg ("Waypoint editor has been disabled."); + } + else if (getStr (option) == "models") { + enableDrawModels (false); + + msg ("Waypoint editor has disabled spawn points highlighting."); + } + else if (getStr (option) == "noclip") { + m_ent->v.movetype = MOVETYPE_WALK; + waypoints.clearEditFlag (WS_EDIT_NOCLIP); + + msg ("Waypoint editor has disabled noclip mode."); + } + else if (getStr (option) == "auto") { + waypoints.clearEditFlag (WS_EDIT_AUTO); + msg ("Waypoint editor has disabled autowaypoint mode."); + } + return CMD_STATUS_HANDLED; +} + +int BotControl::cmdWaypointAdd (void) { + enum args { waypoint = 1, cmd, max }; + + // adding more args to args array, if not enough passed + fixMissingArgs (max); + + // turn waypoints on + waypoints.setEditFlag (WS_EDIT_ENABLED); + + // show the menu + showMenu (BOT_MENU_WAYPOINT_TYPE); + return CMD_STATUS_HANDLED; +} + +int BotControl::cmdWaypointAddBasic (void) { + enum args { waypoint = 1, cmd, max }; + + // adding more args to args array, if not enough passed + fixMissingArgs (max); + + // turn waypoints on + waypoints.setEditFlag (WS_EDIT_ENABLED); + + waypoints.addBasic (); + msg ("Basic waypoints was added."); + + return CMD_STATUS_HANDLED; +} + +int BotControl::cmdWaypointSave (void) { + enum args { waypoint = 1, cmd, nocheck, max }; + + // adding more args to args array, if not enough passed + fixMissingArgs (max); + + // if no check is set save anyway + if (getStr (nocheck) == "nocheck") { + waypoints.save (); + + msg ("All waypoints has been saved and written to disk (IGNORING QUALITY CONTROL)."); + } + else { + if (waypoints.checkNodes ()) { + waypoints.save (); + msg ("All waypoints has been saved and written to disk."); + } + else { + msg ("Could not save save waypoints to disk. Waypoint check has failed."); + } + } + return CMD_STATUS_HANDLED; +} + +int BotControl::cmdWaypointLoad (void) { + enum args { waypoint = 1, cmd, max }; + + // adding more args to args array, if not enough passed + fixMissingArgs (max); + + // just save waypoints on request + if (waypoints.load ()) { + msg ("Waypoints successfully loaded."); + } + else { + msg ("Could not load waypoints. See console..."); + } + return CMD_STATUS_HANDLED; +} + +int BotControl::cmdWaypointErase (void) { + enum args { waypoint = 1, cmd, iamsure, max }; + + // adding more args to args array, if not enough passed + fixMissingArgs (max); + + // prevent accidents when waypoints are deleted unintentionally + if (getStr (iamsure) == "iamsure") { + waypoints.eraseFromDisk (); + } + else { + msg ("Please, append \"iamsure\" as parameter to get waypoints erased from the disk."); + } + return CMD_STATUS_HANDLED; +} + +int BotControl::cmdWaypointDelete (void) { + enum args { waypoint = 1, cmd, nearest, max }; + + // adding more args to args array, if not enough passed + fixMissingArgs (max); + + // turn waypoints on + waypoints.setEditFlag (WS_EDIT_ENABLED); + + // if "neareset" or nothing passed delete neareset, else delete by index + if (getStr (nearest) == "empty" || getStr (nearest) == "nearest") { + waypoints.erase (INVALID_WAYPOINT_INDEX); + } + else { + int index = getInt (nearest); + + // check for existence + if (waypoints.exists (index)) { + waypoints.erase (index); + msg ("Waypoint #%d has beed deleted.", index); + } + else { + msg ("Could not delete waypoints #%d.", index); + } + } + return CMD_STATUS_HANDLED; +} + +int BotControl::cmdWaypointCheck (void) { + enum args { waypoint = 1, cmd, max }; + + // adding more args to args array, if not enough passed + fixMissingArgs (max); + + // check if nodes are ok + if (waypoints.checkNodes ()) { + msg ("Waypoints seems to be OK."); + } + return CMD_STATUS_HANDLED; +} + +int BotControl::cmdWaypointCache (void) { + enum args { waypoint = 1, cmd, nearest, max }; + + // adding more args to args array, if not enough passed + fixMissingArgs (max); + + // turn waypoints on + waypoints.setEditFlag (WS_EDIT_ENABLED); + + // if "neareset" or nothing passed delete neareset, else delete by index + if (getStr (nearest) == "empty" || getStr (nearest) == "nearest") { + waypoints.cachePoint (INVALID_WAYPOINT_INDEX); + + msg ("Nearest waypoint has been put into the memory."); + } + else { + int index = getInt (nearest); + + // check for existence + if (waypoints.exists (index)) { + waypoints.cachePoint (index); + msg ("Waypoint #%d has been put into the memory.", index); + } + else { + msg ("Could not put waypoint #%d into the memory.", index); + } + } + return CMD_STATUS_HANDLED; +} + +int BotControl::cmdWaypointClean (void) { + enum args { waypoint = 1, cmd, option, max }; + + // adding more args to args array, if not enough passed + fixMissingArgs (max); + + // turn waypoints on + waypoints.setEditFlag (WS_EDIT_ENABLED); + + // if "all" passed clean up all the paths + if (getStr (option) == "all") { + int removed = 0; + + for (int i = 0; i < waypoints.length (); i++) { + removed += waypoints.clearConnections (i); + } + msg ("Done. Processed %d waypoints. %d useless paths was cleared.", waypoints.length (), removed); + } + else if (getStr (option) == "empty" || getStr (option) == "nearest") { + int removed = waypoints.clearConnections (waypoints.getEditorNeareset ()); + + msg ("Done. Processed waypoint #%d. %d useless paths was cleared.", waypoints.getEditorNeareset (), removed); + } + else { + int index = getInt (option); + + // check for existence + if (waypoints.exists (index)) { + int removed = waypoints.clearConnections (index); + + msg ("Done. Processed waypoint #%d. %d useless paths was cleared.", index, removed); + } + else { + msg ("Could not process waypoint #%d clearance.", index); + } + } + return CMD_STATUS_HANDLED; +} + +int BotControl::cmdWaypointSetRadius (void) { + enum args { waypoint = 1, cmd, radius, index, max }; + + // adding more args to args array, if not enough passed + fixMissingArgs (max); + + // radius is a must + if (!hasArg (radius)) { + return CMD_STATUS_BADFORMAT; + } + int radiusIndex = INVALID_WAYPOINT_INDEX; + + if (getStr (index) == "empty" || getStr (index) == "nearest") { + radiusIndex = waypoints.getEditorNeareset (); + } + else { + radiusIndex = getInt (index); + } + float value = getStr (radius).toFloat (); + + waypoints.setRadius (radiusIndex, value); + msg ("Waypoint #%d has been set to radius %.2f.", radiusIndex, value); + + return CMD_STATUS_HANDLED; +} + +int BotControl::cmdWaypointSetFlags (void) { + enum args { waypoint = 1, cmd, max }; + + // adding more args to args array, if not enough passed + fixMissingArgs (max); + + // turn waypoints on + waypoints.setEditFlag (WS_EDIT_ENABLED); + + //show the flag menu + showMenu (BOT_MENU_WAYPOINT_FLAG); + return CMD_STATUS_HANDLED; +} + +int BotControl::cmdWaypointTeleport (void) { + enum args { waypoint = 1, cmd, teleport_index, max }; + + // adding more args to args array, if not enough passed + fixMissingArgs (max); + + if (!hasArg (teleport_index)) { + return CMD_STATUS_BADFORMAT; + } + int index = getInt (teleport_index); + + // check for existence + if (waypoints.exists (index)) { + engfuncs.pfnSetOrigin (waypoints.getEditor (), waypoints[index].origin); + + msg ("You have been teleported to waypoint #%d.", index); + + // turn waypoints on + waypoints.setEditFlag (WS_EDIT_ENABLED | WS_EDIT_NOCLIP); + } + else { + msg ("Could not teleport to waypoint #%d.", index); + } + return CMD_STATUS_HANDLED; +} + +int BotControl::cmdWaypointPathCreate (void) { + enum args { waypoint = 1, cmd, max }; + + // adding more args to args array, if not enough passed + fixMissingArgs (max); + + // turn waypoints on + waypoints.setEditFlag (WS_EDIT_ENABLED); + + // choose the direction for path creation + if (m_args[cmd].find ("_both", 0) != String::INVALID_INDEX) { + waypoints.pathCreate (CONNECTION_BOTHWAYS); + } + else if (m_args[cmd].find ("_in", 0) != String::INVALID_INDEX) { + waypoints.pathCreate (CONNECTION_INCOMING); + } + else if (m_args[cmd].find ("_out", 0) != String::INVALID_INDEX) { + waypoints.pathCreate (CONNECTION_OUTGOING); + } + else { + showMenu (BOT_MENU_WAYPOINT_PATH); + } + return CMD_STATUS_HANDLED; +} + +int BotControl::cmdWaypointPathDelete (void) { + enum args { waypoint = 1, cmd, max }; + + // adding more args to args array, if not enough passed + fixMissingArgs (max); + + // turn waypoints on + waypoints.setEditFlag (WS_EDIT_ENABLED); + + // delete the patch + waypoints.erasePath (); + + return CMD_STATUS_HANDLED; +} + +int BotControl::cmdWaypointPathSetAutoDistance (void) { + enum args { waypoint = 1, cmd, max }; + + // adding more args to args array, if not enough passed + fixMissingArgs (max); + + // turn waypoints on + waypoints.setEditFlag (WS_EDIT_ENABLED); + showMenu (BOT_MENU_WAYPOINT_AUTOPATH); + + return CMD_STATUS_HANDLED; +} + +int BotControl::cmdWaypointAcquireEditor (void) { + enum args { waypoint = 1, max }; + + // adding more args to args array, if not enough passed + fixMissingArgs (max); + + if (game.isNullEntity (m_ent)) { + msg ("This command should not be executed from HLDS console."); + return CMD_STATUS_HANDLED; + } + + if (waypoints.hasEditor ()) { + msg ("Sorry, players \"%s\" already acquired rights to edit waypoints on this server.", STRING (waypoints.getEditor ()->v.netname)); + return CMD_STATUS_HANDLED; + } + waypoints.setEditor (m_ent); + msg ("You're acquired rights to edit waypoints on this server. You're now able to use waypoint commands."); + + return CMD_STATUS_HANDLED; +} + +int BotControl::cmdWaypointReleaseEditor (void) { + enum args { waypoint = 1, max }; + + // adding more args to args array, if not enough passed + fixMissingArgs (max); + + if (!waypoints.hasEditor ()) { + msg ("No one is currently has rights to edit. Nothing to release."); + return CMD_STATUS_HANDLED; + } + waypoints.setEditor (nullptr); + msg ("Waypoint editor rights freed. You're now not able to use waypoint commands."); + + return CMD_STATUS_HANDLED; +} + +int BotControl::menuMain (int item) { + showMenu (BOT_MENU_INVALID); // reset menu display + + switch (item) { + case 1: + m_isMenuFillCommand = false; + showMenu (BOT_MENU_CONTROL); + break; + + case 2: + showMenu (BOT_MENU_FEATURES); + break; + + case 3: + m_isMenuFillCommand = true; + showMenu (BOT_MENU_TEAM_SELECT); + break; + + case 4: + bots.killAllBots (); + break; + + case 10: + showMenu (BOT_MENU_INVALID); + break; + + default: + showMenu (BOT_MENU_MAIN); + break; + } + return CMD_STATUS_HANDLED; +} + +int BotControl::menuFeatures (int item) { + showMenu (BOT_MENU_INVALID); // reset menu display + + switch (item) { + case 1: + showMenu (BOT_MENU_WEAPON_MODE); + break; + + case 2: + showMenu (waypoints.hasEditor () ? BOT_MENU_WAYPOINT_MAIN_PAGE1 : BOT_MENU_FEATURES); + break; + + case 3: + showMenu (BOT_MENU_PERSONALITY); + break; + + case 4: + extern ConVar yb_debug; + yb_debug.set (yb_debug.integer () ^ 1); + + showMenu (BOT_MENU_FEATURES); + break; + + case 5: + if (util.isAlive (m_ent)) { + showMenu (BOT_MENU_COMMANDS); + } + else { + showMenu (BOT_MENU_INVALID); // reset menu display + msg ("You're dead, and have no access to this menu"); + } + break; + + case 10: + showMenu (BOT_MENU_INVALID); + break; + } + return CMD_STATUS_HANDLED; +} + +int BotControl::menuControl (int item) { + showMenu (BOT_MENU_INVALID); // reset menu display + + switch (item) { + case 1: + bots.createRandom (true); + showMenu (BOT_MENU_CONTROL); + break; + + case 2: + showMenu (BOT_MENU_DIFFICULTY); + break; + + case 3: + bots.kickRandom (); + showMenu (BOT_MENU_CONTROL); + break; + + case 4: + bots.kickEveryone (); + break; + + case 5: + kickBotByMenu (1); + break; + + case 10: + showMenu (BOT_MENU_INVALID); + break; + } + return CMD_STATUS_HANDLED; +} + +int BotControl::menuWeaponMode (int item) { + showMenu (BOT_MENU_INVALID); // reset menu display + + switch (item) { + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + bots.setWeaponMode (item); + showMenu (BOT_MENU_WEAPON_MODE); + break; + + case 10: + showMenu (BOT_MENU_INVALID); + break; + } + return CMD_STATUS_HANDLED; +} + +int BotControl::menuPersonality (int item) { + if (m_isMenuFillCommand) { + showMenu (BOT_MENU_INVALID); // reset menu display + + switch (item) { + case 1: + case 2: + case 3: + case 4: + bots.serverFill (m_menuServerFillTeam, item - 2, m_interMenuData[0]); + showMenu (BOT_MENU_INVALID); + break; + + case 10: + showMenu (BOT_MENU_INVALID); + break; + } + return CMD_STATUS_HANDLED; + } + showMenu (BOT_MENU_INVALID); // reset menu display + + switch (item) { + case 1: + case 2: + case 3: + case 4: + m_interMenuData[3] = item - 2; + showMenu (BOT_MENU_TEAM_SELECT); + break; + + case 10: + showMenu (BOT_MENU_INVALID); + break; + } + return CMD_STATUS_HANDLED; +} + +int BotControl::menuDifficulty (int item) { + showMenu (BOT_MENU_INVALID); // reset menu display + + switch (item) { + case 1: + m_interMenuData[0] = 0; + break; + + case 2: + m_interMenuData[0] = 1; + break; + + case 3: + m_interMenuData[0] = 2; + break; + + case 4: + m_interMenuData[0] = 3; + break; + + case 5: + m_interMenuData[0] = 4; + break; + + case 10: + showMenu (BOT_MENU_INVALID); + break; + } + showMenu (BOT_MENU_PERSONALITY); + + return CMD_STATUS_HANDLED; +} + +int BotControl::menuTeamSelect (int item) { + if (m_isMenuFillCommand) { + showMenu (BOT_MENU_INVALID); // reset menu display + + if (item < 3) { + extern ConVar mp_limitteams, mp_autoteambalance; + + // turn off cvars if specified team + mp_limitteams.set (0); + mp_autoteambalance.set (0); + } + + switch (item) { + case 1: + case 2: + case 5: + m_menuServerFillTeam = item; + showMenu (BOT_MENU_DIFFICULTY); + break; + + case 10: + showMenu (BOT_MENU_INVALID); + break; + } + return CMD_STATUS_HANDLED; + } + showMenu (BOT_MENU_INVALID); // reset menu display + + switch (item) { + case 1: + case 2: + case 5: + m_interMenuData[1] = item; + + if (item == 5) { + m_interMenuData[2] = item; + bots.addbot ("", m_interMenuData[0], m_interMenuData[3], m_interMenuData[1], m_interMenuData[2], true); + } + else { + showMenu (item == 1 ? BOT_MENU_TERRORIST_SELECT : BOT_MENU_CT_SELECT); + } + break; + + case 10: + showMenu (BOT_MENU_INVALID); + break; + } + return CMD_STATUS_HANDLED; +} + +int BotControl::menuClassSelect (int item) { + showMenu (BOT_MENU_INVALID); // reset menu display + + switch (item) { + case 1: + case 2: + case 3: + case 4: + case 5: + m_interMenuData[2] = item; + bots.addbot ("", m_interMenuData[0], m_interMenuData[3], m_interMenuData[1], m_interMenuData[2], true); + break; + + case 10: + showMenu (BOT_MENU_INVALID); + break; + } + return CMD_STATUS_HANDLED; +} + +int BotControl::menuCommands (int item) { + showMenu (BOT_MENU_INVALID); // reset menu display + Bot *bot = 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 (item == 1) { + bot->startDoubleJump (m_ent); + } + else { + bot->resetDoubleJump (); + } + } + showMenu (BOT_MENU_COMMANDS); + break; + + 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); + } + showMenu (BOT_MENU_COMMANDS); + break; + + case 10: + showMenu (BOT_MENU_INVALID); + break; + } + return CMD_STATUS_HANDLED; +} + +int BotControl::menuWaypointPage1 (int item) { + showMenu (BOT_MENU_INVALID); // reset menu display + + switch (item) { + case 1: + if (waypoints.hasEditFlag (WS_EDIT_ENABLED)) { + waypoints.clearEditFlag (WS_EDIT_ENABLED); + enableDrawModels (false); + + msg ("Waypoint editor has been disabled."); + } + else { + waypoints.setEditFlag (WS_EDIT_ENABLED); + enableDrawModels (true); + + msg ("Waypoint editor has been enabled."); + } + showMenu (BOT_MENU_WAYPOINT_MAIN_PAGE1); + break; + + case 2: + waypoints.setEditFlag (WS_EDIT_ENABLED); + waypoints.cachePoint (INVALID_WAYPOINT_INDEX); + + showMenu (BOT_MENU_WAYPOINT_MAIN_PAGE1); + break; + + case 3: + waypoints.setEditFlag (WS_EDIT_ENABLED); + showMenu (BOT_MENU_WAYPOINT_PATH); + break; + + case 4: + waypoints.setEditFlag (WS_EDIT_ENABLED); + waypoints.erasePath (); + + showMenu (BOT_MENU_WAYPOINT_MAIN_PAGE1); + break; + + case 5: + waypoints.setEditFlag (WS_EDIT_ENABLED); + showMenu (BOT_MENU_WAYPOINT_TYPE); + break; + + case 6: + waypoints.setEditFlag (WS_EDIT_ENABLED); + waypoints.erase (INVALID_WAYPOINT_INDEX); + + showMenu (BOT_MENU_WAYPOINT_MAIN_PAGE1); + break; + + case 7: + waypoints.setEditFlag (WS_EDIT_ENABLED); + showMenu (BOT_MENU_WAYPOINT_AUTOPATH); + break; + + case 8: + waypoints.setEditFlag (WS_EDIT_ENABLED); + showMenu (BOT_MENU_WAYPOINT_RADIUS); + break; + + case 9: + showMenu (BOT_MENU_WAYPOINT_MAIN_PAGE2); + break; + + case 10: + showMenu (BOT_MENU_INVALID); + break; + } + return CMD_STATUS_HANDLED; +} + +int BotControl::menuWaypointPage2 (int item) { + showMenu (BOT_MENU_INVALID); // reset menu display + + switch (item) { + case 1: { + int terrPoints = 0; + int ctPoints = 0; + int goalPoints = 0; + int rescuePoints = 0; + int campPoints = 0; + int sniperPoints = 0; + int noHostagePoints = 0; + + for (int i = 0; i < waypoints.length (); i++) { + Path &path = waypoints[i]; + + if (path.flags & FLAG_TF_ONLY) { + terrPoints++; + } + + if (path.flags & FLAG_CF_ONLY) { + ctPoints++; + } + + if (path.flags & FLAG_GOAL) { + goalPoints++; + } + + if (path.flags & FLAG_RESCUE) { + rescuePoints++; + } + + if (path.flags & FLAG_CAMP) { + campPoints++; + } + + if (path.flags & FLAG_SNIPER) { + sniperPoints++; + } + + if (path.flags & FLAG_NOHOSTAGE) { + noHostagePoints++; + } + } + msg ("Waypoints: %d - T Points: %d\n" + "CT Points: %d - Goal Points: %d\n" + "Rescue Points: %d - Camp Points: %d\n" + "Block Hostage Points: %d - Sniper Points: %d\n", + waypoints.length (), terrPoints, ctPoints, goalPoints, rescuePoints, campPoints, noHostagePoints, sniperPoints); + + showMenu (BOT_MENU_WAYPOINT_MAIN_PAGE2); + } break; + + case 2: + waypoints.setEditFlag (WS_EDIT_ENABLED); + + if (waypoints.hasEditFlag (WS_EDIT_AUTO)) { + waypoints.clearEditFlag (WS_EDIT_AUTO); + } + else { + waypoints.setEditFlag (WS_EDIT_AUTO); + } + msg ("Auto-Waypoint %s", waypoints.hasEditFlag (WS_EDIT_AUTO) ? "Enabled" : "Disabled"); + + showMenu (BOT_MENU_WAYPOINT_MAIN_PAGE2); + break; + + case 3: + waypoints.setEditFlag (WS_EDIT_ENABLED); + showMenu (BOT_MENU_WAYPOINT_FLAG); + break; + + case 4: + if (waypoints.checkNodes ()) { + waypoints.save (); + } + else { + msg ("Waypoint not saved\nThere are errors, see console"); + } + showMenu (BOT_MENU_WAYPOINT_MAIN_PAGE2); + break; + + case 5: + waypoints.save (); + showMenu (BOT_MENU_WAYPOINT_MAIN_PAGE2); + break; + + case 6: + waypoints.load (); + showMenu (BOT_MENU_WAYPOINT_MAIN_PAGE2); + break; + + case 7: + if (waypoints.checkNodes ()) { + msg ("Nodes works fine"); + } + else { + msg ("There are errors, see console"); + } + showMenu (BOT_MENU_WAYPOINT_MAIN_PAGE2); + break; + + case 8: + waypoints.setEditFlag (WS_EDIT_ENABLED | WS_EDIT_NOCLIP); + showMenu (BOT_MENU_WAYPOINT_MAIN_PAGE2); + break; + + case 9: + showMenu (BOT_MENU_WAYPOINT_MAIN_PAGE1); + break; + } + return CMD_STATUS_HANDLED; +} + +int BotControl::menuWaypointRadius (int item) { + showMenu (BOT_MENU_INVALID); // reset menu display + waypoints.setEditFlag (WS_EDIT_ENABLED); // turn waypoints on in case + + constexpr float radius[] = { 0.0f, 8.0f, 16.0f, 32.0f, 48.0f, 64.0f, 80.0f, 96.0f, 128.0f }; + + if (item >= 1 && item <= 9) { + waypoints.setRadius (INVALID_WAYPOINT_INDEX, radius[item - 1]); + showMenu (BOT_MENU_WAYPOINT_RADIUS); + } + return CMD_STATUS_HANDLED; +} + +int BotControl::menuWaypointType (int item) { + showMenu (BOT_MENU_INVALID); // reset menu display + + switch (item) { + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + waypoints.push (item - 1); + showMenu (BOT_MENU_WAYPOINT_TYPE); + break; + + case 8: + waypoints.push (100); + showMenu (BOT_MENU_WAYPOINT_TYPE); + break; + + case 9: + waypoints.startLearnJump (); + showMenu (BOT_MENU_WAYPOINT_TYPE); + break; + + case 10: + showMenu (BOT_MENU_INVALID); + break; + } + return CMD_STATUS_HANDLED; +} + +int BotControl::menuWaypointFlag (int item) { + showMenu (BOT_MENU_INVALID); // reset menu display + + switch (item) { + case 1: + waypoints.toggleFlags (FLAG_NOHOSTAGE); + showMenu (BOT_MENU_WAYPOINT_FLAG); + break; + + case 2: + waypoints.toggleFlags (FLAG_TF_ONLY); + showMenu (BOT_MENU_WAYPOINT_FLAG); + break; + + case 3: + waypoints.toggleFlags (FLAG_CF_ONLY); + showMenu (BOT_MENU_WAYPOINT_FLAG); + break; + + case 4: + waypoints.toggleFlags (FLAG_LIFT); + showMenu (BOT_MENU_WAYPOINT_FLAG); + break; + + case 5: + waypoints.toggleFlags (FLAG_SNIPER); + showMenu (BOT_MENU_WAYPOINT_FLAG); + break; + } + return CMD_STATUS_HANDLED; +} + +int BotControl::menuAutoPathDistance (int item) { + showMenu (BOT_MENU_INVALID); // reset menu display + + constexpr float distances[] = { 0.0f, 100.0f, 130.0f, 160.0f, 190.0f, 220.0f, 250.0f }; + float result = 0.0f; + + if (item >= 1 && item <= 7) { + result = distances[item - 1]; + waypoints.setAutoPathDistance (result); + } + + if (cr::fzero (result)) { + msg ("Autopathing is now disabled."); + } + else { + msg ("Autopath distance is set to %.2f.", result); + } + showMenu (BOT_MENU_WAYPOINT_AUTOPATH); + + return CMD_STATUS_HANDLED; +} + +int BotControl::menuKickPage1 (int item) { + showMenu (BOT_MENU_INVALID); // reset menu display + + switch (item) { + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + bots.kickBot (item - 1); + kickBotByMenu (1); + break; + + case 9: + kickBotByMenu (2); + break; + + case 10: + showMenu (BOT_MENU_CONTROL); + break; + } + return CMD_STATUS_HANDLED; +} + +int BotControl::menuKickPage2 (int item) { + showMenu (BOT_MENU_INVALID); // reset menu display + + switch (item) { + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + bots.kickBot (item + 8 - 1); + kickBotByMenu (2); + break; + + case 9: + kickBotByMenu (3); + break; + + case 10: + kickBotByMenu (1); + break; + } + return CMD_STATUS_HANDLED; +} + +int BotControl::menuKickPage3 (int item) { + showMenu (BOT_MENU_INVALID); // reset menu display + + switch (item) { + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + bots.kickBot (item + 16 - 1); + kickBotByMenu (3); + break; + + case 9: + kickBotByMenu (4); + break; + + case 10: + kickBotByMenu (2); + break; + } + return CMD_STATUS_HANDLED; +} + +int BotControl::menuKickPage4 (int item) { + showMenu (BOT_MENU_INVALID); // reset menu display + + switch (item) { + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + bots.kickBot (item + 24 - 1); + kickBotByMenu (4); + break; + + case 10: + kickBotByMenu (3); + break; + } + return CMD_STATUS_HANDLED; +} + +int BotControl::menuWaypointPath (int item) { + showMenu (BOT_MENU_INVALID); // reset menu display + + switch (item) { + case 1: + waypoints.pathCreate (CONNECTION_OUTGOING); + showMenu (BOT_MENU_WAYPOINT_PATH); + break; + + case 2: + waypoints.pathCreate (CONNECTION_INCOMING); + showMenu (BOT_MENU_WAYPOINT_PATH); + break; + + case 3: + waypoints.pathCreate (CONNECTION_BOTHWAYS); + showMenu (BOT_MENU_WAYPOINT_PATH); + break; + + case 10: + showMenu (BOT_MENU_INVALID); + break; + } + return CMD_STATUS_HANDLED; +} + +bool BotControl::executeCommands (void) { + if (m_args.empty ()) { + return false; + } + + // handle only "yb" and "yapb" commands + if (m_args[0] != "yb" && m_args[0] != "yapb") { + return false; + } + Client &client = util.getClient (game.indexOfEntity (m_ent) - 1); + + // do not allow to execute stuff for non admins + if (m_ent != game.getLocalEntity () && !(client.flags & CF_ADMIN)) { + msg ("Access to YaPB commands is restricted."); + return true; + } + + auto aliasMatch = [] (String &test, const String &cmd, String &aliasName) -> bool { + for (auto &alias : test.split ("|")) { + if (alias == cmd) { + aliasName = alias; + return true; + } + } + return false; + }; + String cmd; + + // give some help + if (m_args.length () > 1 && stricmp ("help", m_args[1].chars ()) == 0) { + for (auto &item : m_cmds) { + if (aliasMatch (item.name, m_args[2], cmd)) { + msg ("Command: \"%s %s\"\nFormat: %s\nHelp: %s", m_args[0].chars (), cmd.chars (), item.format.chars (), item.help.chars ()); + + String aliases; + + for (auto &alias : item.name.split ("|")) { + aliases.append ("%s, ", alias.chars ()); + } + aliases.rtrim (", "); + msg ("Aliases: %s", aliases.chars ()); + + return true; + } + } + + if (m_args[2].empty ()) { + return true; + } + else { + msg ("No help found for \"%s\"", m_args[2].chars ()); + } + return true; + } + cmd.clear (); + + // if no args passed just print all the commands + if (m_args.length () == 1) { + msg ("usage %s [arguments]", m_args[0].chars ()); + msg ("valid commands are: "); + + for (auto &item : m_cmds) { + msg (" %s - %s", item.name.split ("|")[0].chars (), item.help.chars ()); + } + return true; + } + + // first search for a actual cmd + for (auto &item : m_cmds) { + auto root = m_args[0].chars (); + + if (aliasMatch (item.name, m_args[1], cmd)) { + auto alias = cmd.chars (); + + switch ((this->*item.handler) ()) { + case CMD_STATUS_HANDLED: + default: + break; + + case CMD_STATUS_LISTENSERV: + msg ("Command \"%s %s\" is only available from the listenserver console.", root, alias); + break; + + case CMD_STATUS_BADFORMAT: + msg ("Incorrect usage of \"%s %s\" command. Correct usage is:\n\n\t%s\n\nPlease type \"%s help %s\" to get more information.", root, alias, item.format.chars (), root, alias); + break; + } + + m_isFromConsole = false; + return true; + } + } + msg ("Unrecognized command: %s", m_args[1].chars ()); + return true; +} + +bool BotControl::executeMenus (void) { + if (!util.isPlayer (m_ent) || game.isBotCmd ()) { + return false; + } + auto &issuer = util.getClient (game.indexOfEntity (m_ent) - 1); + + // check if it's menu select, and some key pressed + if (getStr (0) != "menuselect" || getStr (1).empty () || issuer.menu == BOT_MENU_INVALID) { + return false; + } + + // let's get handle + for (auto &menu : m_menus) { + if (menu.ident == issuer.menu) { + return (this->*menu.handler) (getStr (1).toInt32 ()); + } + } + return false; +} + +void BotControl::showMenu (int id) { + static bool s_menusParsed = false; + + // make menus looks like we need only once + if (!s_menusParsed) { + for (auto &parsed : m_menus) { + const String &translated = game.translate (parsed.text.chars ()); + + // translate all the things + parsed.text = translated; + + // make menu looks best + if (!(game.is (GAME_LEGACY))) { + for (int j = 0; j < 10; j++) { + parsed.text.replace (util.format ("%d.", j), util.format ("\\r%d.\\w", j)); + } + } + } + s_menusParsed = true; + } + + if (!util.isPlayer (m_ent)) { + return; + } + Client &client = util.getClient (game.indexOfEntity (m_ent) - 1); + + if (id == BOT_MENU_INVALID) { + MessageWriter (MSG_ONE_UNRELIABLE, game.getMessageId (NETMSG_SHOWMENU), Vector::null (), m_ent) + .writeShort (0) + .writeChar (0) + .writeByte (0) + .writeString (""); + + client.menu = id; + return; + } + + for (auto &display : m_menus) { + if (display.ident == id) { + const char *text = (game.is (GAME_XASH_ENGINE | GAME_MOBILITY) && !yb_display_menu_text.boolean ()) ? " " : display.text.chars (); + MessageWriter msg; + + while (strlen (text) >= 64) { + msg.start (MSG_ONE_UNRELIABLE, game.getMessageId (NETMSG_SHOWMENU), Vector::null (), m_ent) + .writeShort (display.slots) + .writeChar (-1) + .writeByte (1); + + for (int i = 0; i < 64; i++) { + msg.writeChar (text[i]); + } + msg.end (); + text += 64; + } + + MessageWriter (MSG_ONE_UNRELIABLE, game.getMessageId (NETMSG_SHOWMENU), Vector::null (), m_ent) + .writeShort (display.slots) + .writeChar (-1) + .writeByte (0) + .writeString (text); + + client.menu = id; + engfuncs.pfnClientCommand (m_ent, "speak \"player/geiger1\"\n"); // Stops others from hearing menu sounds.. + } + } +} + +void BotControl::kickBotByMenu (int page) { + if (page > 4 || page < 1) { + return; + } + + String menus; + menus.assign ("\\yBots Remove Menu (%d/4):\\w\n\n", page); + + int menuKeys = (page == 4) ? cr::bit (9) : (cr::bit (8) | cr::bit (9)); + int menuKey = (page - 1) * 8; + + for (int i = menuKey; i < page * 8; i++) { + auto bot = bots.getBot (i); + + if (bot != nullptr) { + menuKeys |= cr::bit (cr::abs (i - menuKey)); + menus.append ("%1.1d. %s%s\n", i - menuKey + 1, STRING (bot->pev->netname), bot->m_team == TEAM_COUNTER ? " \\y(CT)\\w" : " \\r(T)\\w"); + } + else { + menus.append ("\\d %1.1d. Not a Bot\\w\n", i - menuKey + 1); + } + } + menus.append ("\n%s 0. Back", (page == 4) ? "" : " 9. More...\n"); + + // force to clear current menu + showMenu (BOT_MENU_INVALID); + + auto id = BOT_MENU_KICK_PAGE_1 - 1 + page; + + for (auto &menu : m_menus) { + if (menu.ident == id) { + menu.slots = menuKeys & static_cast (-1); + menu.text = menus; + + break; + } + } + showMenu (id); +} + +void BotControl::msg (const char *fmt, ...) { + va_list ap; + char buffer[MAX_PRINT_BUFFER]; + + va_start (ap, fmt); + vsnprintf (buffer, cr::bufsize (buffer), fmt, ap); + va_end (ap); + + if (game.isNullEntity (m_ent)) { + game.print (buffer); + return; + } + + if (m_isFromConsole || strlen (buffer) > 48) { + game.clientPrint (m_ent, buffer); + } + else { + game.centerPrint (m_ent, buffer); + game.clientPrint (m_ent, buffer); + } +} + +void BotControl::assignAdminRights (edict_t *ent, char *infobuffer) { + if (!game.isDedicated () || util.isFakeClient (ent)) { + return; + } + const String &key = yb_password_key.str (); + const String &password = yb_password.str (); + + if (!key.empty () && !password.empty ()) { + auto &client = util.getClient (game.indexOfEntity (ent) - 1); + + if (password == engfuncs.pfnInfoKeyValue (infobuffer, key.chars ())) { + client.flags |= CF_ADMIN; + } + else { + client.flags &= ~CF_ADMIN; + } + } +} + +void BotControl::maintainAdminRights (void) { + if (!game.isDedicated ()) { + return; + } + + for (int i = 0; i < game.maxClients (); i++) { + edict_t *player = game.entityOfIndex (i + 1); + + // code below is executed only on dedicated server + if (util.isPlayer (player) && !util.isFakeClient (player)) { + Client &client = util.getClient (i); + + if (client.flags & CF_ADMIN) { + if (util.isEmptyStr (yb_password_key.str ()) && util.isEmptyStr (yb_password.str ())) { + client.flags &= ~CF_ADMIN; + } + else if (!!strcmp (yb_password.str (), engfuncs.pfnInfoKeyValue (engfuncs.pfnGetInfoKeyBuffer (client.ent), const_cast (yb_password_key.str ())))) { + client.flags &= ~CF_ADMIN; + game.print ("Player %s had lost remote access to %s.", STRING (player->v.netname), PRODUCT_SHORT_NAME); + } + } + else if (!(client.flags & CF_ADMIN) && !util.isEmptyStr (yb_password_key.str ()) && !util.isEmptyStr (yb_password.str ())) { + if (strcmp (yb_password.str (), engfuncs.pfnInfoKeyValue (engfuncs.pfnGetInfoKeyBuffer (client.ent), const_cast (yb_password_key.str ()))) == 0) { + client.flags |= CF_ADMIN; + game.print ("Player %s had gained full remote access to %s.", STRING (player->v.netname), PRODUCT_SHORT_NAME); + } + } + } + } +} + +BotControl::BotControl (void) { + m_ent = nullptr; + m_isFromConsole = false; + m_isMenuFillCommand = false; + m_menuServerFillTeam = 5; + + auto addCommand = [&] (const String &cmd, const String &format, const String &help, Handler handler) -> void { + m_cmds.push ({ cmd, format, help, cr::forward (handler) }); + }; + + addCommand ("add|addbot|add_ct|addbot_ct|add_t|addbot_t|addhs|addhs_t|addhs_ct", "add [difficulty[personality[team[model[name]]]]]", "Adding specific bot into the game.", &BotControl::cmdAddBot); + addCommand ("kick|kickone|kick_ct|kick_t|kickbot_ct|kickbot_t", "kick [team]", "Kicks off the random bot from the game.", &BotControl::cmdKickBot); + addCommand ("removebots|kickbots|kickall", "removebots [instant]", "Kicks all the bots from the game.", &BotControl::cmdKickBots); + addCommand ("kill|killbots|killall|kill_ct|kill_t", "kill [team]", "Kills the specified team / all the bots.", &BotControl::cmdKillBots); + addCommand ("fill|fillserver", "fill [team[count[difficulty[pesonality]]]]", "Fill the server (add bots) with specified parameters.", &BotControl::cmdFill); + addCommand ("vote|votemap", "vote [map_id]", "Forces all the bot to vote to specified map.", &BotControl::cmdVote); + addCommand ("weapons|weaponmode", "weapons [knife|pistol|shotgun|smg|rifle|sniper|standard]", "Sets the bots weapon mode to use", &BotControl::cmdWeaponMode); + addCommand ("menu|botmenu", "menu [cmd]", "Opens the main bot menu, or command menu if specified.", &BotControl::cmdMenu); + addCommand ("version|ver|about", "version [no arguments]", "Displays version information about bot build.", &BotControl::cmdVersion); + addCommand ("wpmenu|wptmenu", "wpmenu [noarguments]", "Opens and displays bots waypoint edtior.", &BotControl::cmdWaypointMenu); + addCommand ("list|listbots", "list [noarguments]", "Lists the bots currently playing on server.", &BotControl::cmdList); + addCommand ("waypoint|wp|wpt|waypoint", "waypoint [help]", "Handles waypoint operations.", &BotControl::cmdWaypoint); + + // declare the menus + createMenus (); +} + +void BotControl::handleEngineCommands (void) { + ctrl.setArgs (cr::move (ctrl.collectArgs ())); + ctrl.setIssuer (game.getLocalEntity ()); + + ctrl.setFromConsole (true); + ctrl.executeCommands (); +} + +bool BotControl::handleClientCommands (edict_t *ent) { + setArgs (cr::move (collectArgs ())); + setIssuer (ent); + + setFromConsole (true); + return executeCommands (); +} + +bool BotControl::handleMenuCommands (edict_t *ent) { + setArgs (cr::move (collectArgs ())); + setIssuer (ent); + + setFromConsole (false); + return ctrl.executeMenus (); +} + +void BotControl::enableDrawModels (bool enable) { + StringArray entities; + + entities.push ("info_player_start"); + entities.push ("info_player_deathmatch"); + entities.push ("info_vip_start"); + + for (auto &entity : entities) { + edict_t *ent = nullptr; + + while (!game.isNullEntity (ent = engfuncs.pfnFindEntityByString (ent, "classname", entity.chars ()))) { + if (enable) { + ent->v.effects &= ~EF_NODRAW; + } + else { + ent->v.effects |= EF_NODRAW; + } + } + } +} + +void BotControl::createMenus (void) { + auto keys = [] (int numKeys) -> int { + int result = 0; + + for (int i = 0; i < numKeys; i++) { + result |= cr::bit (i); + } + result |= cr::bit (9); + + return result; + }; + + // bots main menu + m_menus.push ({ + BOT_MENU_MAIN, keys (4), + "\\yMain Menu\\w\n\n" + "1. Control Bots\n" + "2. Features\n\n" + "3. Fill Server\n" + "4. End Round\n\n" + "0. Exit", + cr::forward (&BotControl::menuMain) }); + + + // bots features menu + m_menus.push ({ + BOT_MENU_FEATURES, keys (5), + "\\yBots Features\\w\n\n" + "1. Weapon Mode Menu\n" + "2. Waypoint Menu\n" + "3. Select Personality\n\n" + "4. Toggle Debug Mode\n" + "5. Command Menu\n\n" + "0. Exit", + cr::forward (&BotControl::menuFeatures) }); + + // bot control menu + m_menus.push ({ + BOT_MENU_CONTROL, keys (5), + "\\yBots Control Menu\\w\n\n" + "1. Add a Bot, Quick\n" + "2. Add a Bot, Specified\n\n" + "3. Remove Random Bot\n" + "4. Remove All Bots\n\n" + "5. Remove Bot Menu\n\n" + "0. Exit", + cr::forward (&BotControl::menuControl) }); + + // weapon mode select menu + m_menus.push ({ + BOT_MENU_WEAPON_MODE, keys (7), + "\\yBots Weapon Mode\\w\n\n" + "1. Knives only\n" + "2. Pistols only\n" + "3. Shotguns only\n" + "4. Machine Guns only\n" + "5. Rifles only\n" + "6. Sniper Weapons only\n" + "7. All Weapons\n\n" + "0. Exit", + cr::forward (&BotControl::menuWeaponMode) }); + + // personality select menu + m_menus.push ({ + BOT_MENU_PERSONALITY, keys (4), + "\\yBots Personality\\w\n\n" + "1. Random\n" + "2. Normal\n" + "3. Aggressive\n" + "4. Careful\n\n" + "0. Exit", + cr::forward (&BotControl::menuPersonality) }); + + // difficulty select menu + m_menus.push ({ + BOT_MENU_DIFFICULTY, keys (5), + "\\yBots Difficulty Level\\w\n\n" + "1. Newbie\n" + "2. Average\n" + "3. Normal\n" + "4. Professional\n" + "5. Godlike\n\n" + "0. Exit", + cr::forward (&BotControl::menuDifficulty) }); + + // team select menu + m_menus.push ({ + BOT_MENU_TEAM_SELECT, keys (5), + "\\ySelect a team\\w\n\n" + "1. Terrorist Force\n" + "2. Counter-Terrorist Force\n\n" + "5. Auto-select\n\n" + "0. Exit", + cr::forward (&BotControl::menuTeamSelect) }); + + // terrorist model select menu + m_menus.push ({ + BOT_MENU_TERRORIST_SELECT, keys (5), + "\\ySelect an appearance\\w\n\n" + "1. Phoenix Connexion\n" + "2. L337 Krew\n" + "3. Arctic Avengers\n" + "4. Guerilla Warfare\n\n" + "5. Auto-select\n\n" + "0. Exit", + cr::forward (&BotControl::menuClassSelect) }); + + // counter-terrorist model select menu + m_menus.push ({ + BOT_MENU_CT_SELECT, keys (5), + "\\ySelect an appearance\\w\n\n" + "1. Seal Team 6 (DEVGRU)\n" + "2. German GSG-9\n" + "3. UK SAS\n" + "4. French GIGN\n\n" + "5. Auto-select\n\n" + "0. Exit", + cr::forward (&BotControl::menuClassSelect) }); + + // command menu + m_menus.push ({ + BOT_MENU_COMMANDS, keys (4), + "\\yBot Command Menu\\w\n\n" + "1. Make Double Jump\n" + "2. Finish Double Jump\n\n" + "3. Drop the C4 Bomb\n" + "4. Drop the Weapon\n\n" + "0. Exit", + cr::forward (&BotControl::menuCommands) }); + + // main waypoint menu + m_menus.push ({ + BOT_MENU_WAYPOINT_MAIN_PAGE1, keys (9), + "\\yWaypoint Operations (Page 1)\\w\n\n" + "1. Show/Hide waypoints\n" + "2. Cache waypoint\n" + "3. Create path\n" + "4. Delete path\n" + "5. Add waypoint\n" + "6. Delete waypoint\n" + "7. Set Autopath Distance\n" + "8. Set Radius\n\n" + "9. Next...\n\n" + "0. Exit", + cr::forward (&BotControl::menuWaypointPage1) }); + + // main waypoint menu (page 2) + m_menus.push ({ + BOT_MENU_WAYPOINT_MAIN_PAGE2, keys (9), + "\\yWaypoint Operations (Page 2)\\w\n\n" + "1. Waypoint stats\n" + "2. Autowaypoint on/off\n" + "3. Set flags\n" + "4. Save waypoints\n" + "5. Save without checking\n" + "6. Load waypoints\n" + "7. Check waypoints\n" + "8. Noclip cheat on/off\n\n" + "9. Previous...\n\n" + "0. Exit", + cr::forward (&BotControl::menuWaypointPage2) }); + + // select waypoint radius menu + m_menus.push ({ + BOT_MENU_WAYPOINT_RADIUS, keys (9), + "\\yWaypoint Radius\\w\n\n" + "1. SetRadius 0\n" + "2. SetRadius 8\n" + "3. SetRadius 16\n" + "4. SetRadius 32\n" + "5. SetRadius 48\n" + "6. SetRadius 64\n" + "7. SetRadius 80\n" + "8. SetRadius 96\n" + "9. SetRadius 128\n\n" + "0. Exit", + cr::forward (&BotControl::menuWaypointRadius) }); + + // waypoint add menu + m_menus.push ({ + BOT_MENU_WAYPOINT_TYPE, keys (9), + "\\yWaypoint Type\\w\n\n" + "1. Normal\n" + "\\r2. Terrorist Important\n" + "3. Counter-Terrorist Important\n" + "\\w4. Block with hostage / Ladder\n" + "\\y5. Rescue Zone\n" + "\\w6. Camping\n" + "7. Camp End\n" + "\\r8. Map Goal\n" + "\\w9. Jump\n\n" + "0. Exit", + cr::forward (&BotControl::menuWaypointType) }); + + // set waypoint flag menu + m_menus.push ({ + BOT_MENU_WAYPOINT_FLAG, keys (5), + "\\yToggle Waypoint Flags\\w\n\n" + "1. Block with Hostage\n" + "2. Terrorists Specific\n" + "3. CTs Specific\n" + "4. Use Elevator\n" + "5. Sniper Point (\\yFor Camp Points Only!\\w)\n\n" + "0. Exit", + cr::forward (&BotControl::menuWaypointFlag) }); + + // auto-path max distance + m_menus.push ({ + BOT_MENU_WAYPOINT_AUTOPATH, keys (7), + "\\yAutoPath Distance\\w\n\n" + "1. Distance 0\n" + "2. Distance 100\n" + "3. Distance 130\n" + "4. Distance 160\n" + "5. Distance 190\n" + "6. Distance 220\n" + "7. Distance 250 (Default)\n\n" + "0. Exit", + cr::forward (&BotControl::menuAutoPathDistance) }); + + // path connections + m_menus.push ({ + BOT_MENU_WAYPOINT_PATH, keys (3), + "\\yCreate Path (Choose Direction)\\w\n\n" + "1. Outgoing Path\n" + "2. Incoming Path\n" + "3. Bidirectional (Both Ways)\n\n" + "0. Exit", + cr::forward (&BotControl::menuWaypointPath) }); + + const String &empty = ""; + + // kick menus + m_menus.push ({ BOT_MENU_KICK_PAGE_1, 0x0, empty, cr::forward (&BotControl::menuKickPage1) }); + m_menus.push ({ BOT_MENU_KICK_PAGE_2, 0x0, empty, cr::forward (&BotControl::menuKickPage2) }); + m_menus.push ({ BOT_MENU_KICK_PAGE_3, 0x0, empty, cr::forward (&BotControl::menuKickPage3) }); + m_menus.push ({ BOT_MENU_KICK_PAGE_4, 0x0, empty, cr::forward (&BotControl::menuKickPage4) }); +} diff --git a/source/engine.cpp b/source/engine.cpp index 48617f9..0ae5361 100644 --- a/source/engine.cpp +++ b/source/engine.cpp @@ -13,74 +13,70 @@ ConVar sv_skycolor_r ("sv_skycolor_r", nullptr, VT_NOREGISTER); ConVar sv_skycolor_g ("sv_skycolor_g", nullptr, VT_NOREGISTER); ConVar sv_skycolor_b ("sv_skycolor_b", nullptr, VT_NOREGISTER); -Engine::Engine (void) { +Game::Game (void) { m_startEntity = nullptr; m_localEntity = nullptr; resetMessages (); - for (int i = 0; i < NETMSG_NUM; i++) { - m_msgBlock.regMsgs[i] = NETMSG_UNDEFINED; + for (auto &msg : m_msgBlock.regMsgs) { + msg = NETMSG_UNDEFINED; } m_precached = false; m_isBotCommand = false; - m_argumentCount = 0; + m_botArgs.reserve (8); - memset (m_arguments, 0, sizeof (m_arguments)); memset (m_drawModels, 0, sizeof (m_drawModels)); memset (m_spawnCount, 0, sizeof (m_spawnCount)); + m_gameFlags = 0; + m_mapFlags = 0; + m_slowFrame = 0.0; + m_cvars.clear (); } -Engine::~Engine (void) { +Game::~Game (void) { resetMessages (); - - for (int i = 0; i < NETMSG_NUM; i++) { - m_msgBlock.regMsgs[i] = NETMSG_UNDEFINED; - } } -void Engine::precache (void) { +void Game::precache (void) { if (m_precached) { return; } m_precached = true; - m_drawModels[DRAW_SIMPLE] = g_engfuncs.pfnPrecacheModel (ENGINE_STR ("sprites/laserbeam.spr")); - m_drawModels[DRAW_ARROW] = g_engfuncs.pfnPrecacheModel (ENGINE_STR ("sprites/arrow1.spr")); + m_drawModels[DRAW_SIMPLE] = engfuncs.pfnPrecacheModel (ENGINE_STR ("sprites/laserbeam.spr")); + m_drawModels[DRAW_ARROW] = engfuncs.pfnPrecacheModel (ENGINE_STR ("sprites/arrow1.spr")); - g_engfuncs.pfnPrecacheSound (ENGINE_STR ("weapons/xbow_hit1.wav")); // waypoint add - g_engfuncs.pfnPrecacheSound (ENGINE_STR ("weapons/mine_activate.wav")); // waypoint delete - g_engfuncs.pfnPrecacheSound (ENGINE_STR ("common/wpn_hudoff.wav")); // path add/delete start - g_engfuncs.pfnPrecacheSound (ENGINE_STR ("common/wpn_hudon.wav")); // path add/delete done - g_engfuncs.pfnPrecacheSound (ENGINE_STR ("common/wpn_moveselect.wav")); // path add/delete cancel - g_engfuncs.pfnPrecacheSound (ENGINE_STR ("common/wpn_denyselect.wav")); // path add/delete error + engfuncs.pfnPrecacheSound (ENGINE_STR ("weapons/xbow_hit1.wav")); // waypoint add + engfuncs.pfnPrecacheSound (ENGINE_STR ("weapons/mine_activate.wav")); // waypoint delete + engfuncs.pfnPrecacheSound (ENGINE_STR ("common/wpn_hudoff.wav")); // path add/delete start + engfuncs.pfnPrecacheSound (ENGINE_STR ("common/wpn_hudon.wav")); // path add/delete done + engfuncs.pfnPrecacheSound (ENGINE_STR ("common/wpn_moveselect.wav")); // path add/delete cancel + engfuncs.pfnPrecacheSound (ENGINE_STR ("common/wpn_denyselect.wav")); // path add/delete error - g_mapFlags = 0; // reset map type as worldspawn is the first entity spawned + m_mapFlags = 0; // reset map type as worldspawn is the first entity spawned // detect official csbots here, as they causing crash in linkent code when active for some reason - if (!(g_gameFlags & GAME_LEGACY) && g_engfuncs.pfnCVarGetPointer ("bot_stop") != nullptr) { - g_gameFlags |= GAME_OFFICIAL_CSBOT; + if (!(game.is (GAME_LEGACY)) && engfuncs.pfnCVarGetPointer ("bot_stop") != nullptr) { + m_gameFlags |= GAME_OFFICIAL_CSBOT; } pushRegStackToEngine (true); } -void Engine::levelInitialize (void) { +void Game::levelInitialize (edict_t *ents, int max) { // this function precaches needed models and initialize class variables - m_localEntity = nullptr; - m_spawnCount[TEAM_COUNTER] = 0; m_spawnCount[TEAM_TERRORIST] = 0; // go thru the all entities on map, and do whatever we're want - for (int i = 0; i < g_pGlobals->maxEntities; i++) { - - auto ent = g_engfuncs.pfnPEntityOfEntIndex (i); + for (int i = 0; i < max; i++) { + auto ent = ents + i; // only valid entities - if (engine.isNullEntity (ent) || ent->free || ent->v.classname == 0) { + if (!ent || ent->free || ent->v.classname == 0) { continue; } auto classname = STRING (ent->v.classname); @@ -89,18 +85,18 @@ void Engine::levelInitialize (void) { m_startEntity = ent; // initialize some structures - initRound (); + bots.initRound (); } else if (strcmp (classname, "player_weaponstrip") == 0) { - if ((g_gameFlags & GAME_LEGACY) && (STRING (ent->v.target))[0] == '\0') { - ent->v.target = ent->v.targetname = g_engfuncs.pfnAllocString ("fake"); + if ((game.is (GAME_LEGACY)) && (STRING (ent->v.target))[0] == '\0') { + ent->v.target = ent->v.targetname = engfuncs.pfnAllocString ("fake"); } else { - g_engfuncs.pfnRemoveEntity (ent); + engfuncs.pfnRemoveEntity (ent); } } else if (strcmp (classname, "info_player_start") == 0) { - g_engfuncs.pfnSetModel (ent, ENGINE_STR ("models/player/urban/urban.mdl")); + engfuncs.pfnSetModel (ent, ENGINE_STR ("models/player/urban/urban.mdl")); ent->v.rendermode = kRenderTransAlpha; // set its render mode to transparency ent->v.renderamt = 127; // set its transparency amount @@ -109,7 +105,7 @@ void Engine::levelInitialize (void) { m_spawnCount[TEAM_COUNTER]++; } else if (strcmp (classname, "info_player_deathmatch") == 0) { - g_engfuncs.pfnSetModel (ent, ENGINE_STR ("models/player/terror/terror.mdl")); + engfuncs.pfnSetModel (ent, ENGINE_STR ("models/player/terror/terror.mdl")); ent->v.rendermode = kRenderTransAlpha; // set its render mode to transparency ent->v.renderamt = 127; // set its transparency amount @@ -119,39 +115,42 @@ void Engine::levelInitialize (void) { } else if (strcmp (classname, "info_vip_start") == 0) { - g_engfuncs.pfnSetModel (ent, ENGINE_STR ("models/player/vip/vip.mdl")); + engfuncs.pfnSetModel (ent, ENGINE_STR ("models/player/vip/vip.mdl")); ent->v.rendermode = kRenderTransAlpha; // set its render mode to transparency ent->v.renderamt = 127; // set its transparency amount ent->v.effects |= EF_NODRAW; } else if (strcmp (classname, "func_vip_safetyzone") == 0 || strcmp (classname, "info_vip_safetyzone") == 0) { - g_mapFlags |= MAP_AS; // assassination map + m_mapFlags |= MAP_AS; // assassination map } else if (strcmp (classname, "hostage_entity") == 0) { - g_mapFlags |= MAP_CS; // rescue map + m_mapFlags |= MAP_CS; // rescue map } else if (strcmp (classname, "func_bomb_target") == 0 || strcmp (classname, "info_bomb_target") == 0) { - g_mapFlags |= MAP_DE; // defusion map + m_mapFlags |= MAP_DE; // defusion map } else if (strcmp (classname, "func_escapezone") == 0) { - g_mapFlags |= MAP_ES; + m_mapFlags |= MAP_ES; } else if (strncmp (classname, "func_door", 9) == 0) { - g_mapFlags |= MAP_HAS_DOORS; + m_mapFlags |= MAP_HAS_DOORS; } } // next maps doesn't have map-specific entities, so determine it by name - if (strncmp (engine.getMapName (), "fy_", 3) == 0) { - g_mapFlags |= MAP_FY; + if (strncmp (game.getMapName (), "fy_", 3) == 0) { + m_mapFlags |= MAP_FY; } - else if (strncmp (engine.getMapName (), "ka_", 3) == 0) { - g_mapFlags |= MAP_KA; + else if (strncmp (game.getMapName (), "ka_", 3) == 0) { + m_mapFlags |= MAP_KA; } + + // reset some timers + m_slowFrame = 0.0f; } -void Engine::print (const char *fmt, ...) { +void Game::print (const char *fmt, ...) { // this function outputs string into server console va_list ap; @@ -162,10 +161,11 @@ void Engine::print (const char *fmt, ...) { va_end (ap); strcat (string, "\n"); - g_engfuncs.pfnServerPrint (string); + + engfuncs.pfnServerPrint (string); } -void Engine::chatPrint (const char *fmt, ...) { +void Game::chatPrint (const char *fmt, ...) { va_list ap; char string[MAX_PRINT_BUFFER]; @@ -184,7 +184,7 @@ void Engine::chatPrint (const char *fmt, ...) { .writeString (string); } -void Engine::centerPrint (const char *fmt, ...) { +void Game::centerPrint (const char *fmt, ...) { va_list ap; char string[MAX_PRINT_BUFFER]; @@ -203,7 +203,7 @@ void Engine::centerPrint (const char *fmt, ...) { .writeString (string); } -void Engine::clientPrint (edict_t *ent, const char *fmt, ...) { +void Game::clientPrint (edict_t *ent, const char *fmt, ...) { va_list ap; char string[MAX_PRINT_BUFFER]; @@ -216,15 +216,31 @@ void Engine::clientPrint (edict_t *ent, const char *fmt, ...) { return; } strcat (string, "\n"); - g_engfuncs.pfnClientPrintf (ent, print_console, string); + engfuncs.pfnClientPrintf (ent, print_console, string); } -void Engine::drawLine (edict_t *ent, const Vector &start, const Vector &end, int width, int noise, int red, int green, int blue, int brightness, int speed, int life, DrawLineType type) { +void Game::centerPrint (edict_t *ent, const char *fmt, ...) { + va_list ap; + char string[MAX_PRINT_BUFFER]; + + va_start (ap, fmt); + vsnprintf (string, cr::bufsize (string), translate (fmt), ap); + va_end (ap); + + if (isNullEntity (ent)) { + print (string); + return; + } + strcat (string, "\n"); + engfuncs.pfnClientPrintf (ent, print_center, string); +} + +void Game::drawLine (edict_t *ent, const Vector &start, const Vector &end, int width, int noise, int red, int green, int blue, int brightness, int speed, int life, DrawLineType type) { // this function draws a arrow visible from the client side of the player whose player entity // is pointed to by ent, from the vector location start to the vector location end, // which is supposed to last life tenths seconds, and having the color defined by RGB. - if (!isPlayer (ent)) { + if (!util.isPlayer (ent)) { return; // reliability check } @@ -249,7 +265,7 @@ void Engine::drawLine (edict_t *ent, const Vector &start, const Vector &end, int .writeByte (speed); // speed } -void Engine::testLine (const Vector &start, const Vector &end, int ignoreFlags, edict_t *ignoreEntity, TraceResult *ptr) { +void Game::testLine (const Vector &start, const Vector &end, int ignoreFlags, edict_t *ignoreEntity, TraceResult *ptr) { // this function traces a line dot by dot, starting from vecStart in the direction of vecEnd, // ignoring or not monsters (depending on the value of IGNORE_MONSTERS, true or false), and stops // at the first obstacle encountered, returning the results of the trace in the TraceResult structure @@ -267,10 +283,10 @@ void Engine::testLine (const Vector &start, const Vector &end, int ignoreFlags, if (ignoreFlags & TRACE_IGNORE_GLASS) { engineFlags |= 0x100; } - g_engfuncs.pfnTraceLine (start, end, engineFlags, ignoreEntity, ptr); + engfuncs.pfnTraceLine (start, end, engineFlags, ignoreEntity, ptr); } -void Engine::testHull (const Vector &start, const Vector &end, int ignoreFlags, int hullNumber, edict_t *ignoreEntity, TraceResult *ptr) { +void Game::testHull (const Vector &start, const Vector &end, int ignoreFlags, int hullNumber, edict_t *ignoreEntity, TraceResult *ptr) { // this function traces a hull dot by dot, starting from vecStart in the direction of vecEnd, // ignoring or not monsters (depending on the value of IGNORE_MONSTERS, true or // false), and stops at the first obstacle encountered, returning the results @@ -282,12 +298,12 @@ void Engine::testHull (const Vector &start, const Vector &end, int ignoreFlags, // function allows to specify whether the trace starts "inside" an entity's polygonal model, // and if so, to specify that entity in ignoreEntity in order to ignore it as an obstacle. - g_engfuncs.pfnTraceHull (start, end, !!(ignoreFlags & TRACE_IGNORE_MONSTERS), hullNumber, ignoreEntity, ptr); + engfuncs.pfnTraceHull (start, end, !!(ignoreFlags & TRACE_IGNORE_MONSTERS), hullNumber, ignoreEntity, ptr); } -float Engine::getWaveLen (const char *fileName) { +float Game::getWaveLen (const char *fileName) { extern ConVar yb_chatter_path; - const char *filePath = format ("%s/%s/%s.wav", getModName (), yb_chatter_path.str (), fileName); + const char *filePath = util.format ("%s/%s/%s.wav", getModName (), yb_chatter_path.str (), fileName); File fp (filePath, "rb"); @@ -296,9 +312,9 @@ float Engine::getWaveLen (const char *fileName) { return 0.0f; } // check if we have engine function for this - if (!(g_gameFlags & GAME_XASH_ENGINE) && g_engfuncs.pfnGetApproxWavePlayLen != nullptr) { + if (!game.is (GAME_XASH_ENGINE) && engfuncs.pfnGetApproxWavePlayLen != nullptr) { fp.close (); - return g_engfuncs.pfnGetApproxWavePlayLen (filePath) / 1000.0f; + return engfuncs.pfnGetApproxWavePlayLen (filePath) / 1000.0f; } // else fuck with manual search @@ -321,36 +337,36 @@ float Engine::getWaveLen (const char *fileName) { memset (&waveHdr, 0, sizeof (waveHdr)); if (fp.read (&waveHdr, sizeof (WavHeader)) == 0) { - logEntry (true, LL_ERROR, "Wave File %s - has wrong or unsupported format", filePath); + util.logEntry (true, LL_ERROR, "Wave File %s - has wrong or unsupported format", filePath); return 0.0f; } if (strncmp (waveHdr.chunkID, "WAVE", 4) != 0) { - logEntry (true, LL_ERROR, "Wave File %s - has wrong wave chunk id", filePath); + util.logEntry (true, LL_ERROR, "Wave File %s - has wrong wave chunk id", filePath); return 0.0f; } fp.close (); if (waveHdr.dataChunkLength == 0) { - logEntry (true, LL_ERROR, "Wave File %s - has zero length!", filePath); + util.logEntry (true, LL_ERROR, "Wave File %s - has zero length!", filePath); return 0.0f; } return static_cast (waveHdr.dataChunkLength) / static_cast (waveHdr.bytesPerSecond); } -bool Engine::isDedicated (void) { +bool Game::isDedicated (void) { // return true if server is dedicated server, false otherwise - static bool dedicated = g_engfuncs.pfnIsDedicatedServer () > 0; + static bool dedicated = engfuncs.pfnIsDedicatedServer () > 0; return dedicated; } -const char *Engine::getModName (void) { +const char *Game::getModName (void) { // this function returns mod name without path static char modname[256]; - g_engfuncs.pfnGetGameDir (modname); + engfuncs.pfnGetGameDir (modname); size_t length = strlen (modname); size_t stop = length - 1; @@ -374,16 +390,16 @@ const char *Engine::getModName (void) { return &modname[0]; } -const char *Engine::getMapName (void) { +const char *Game::getMapName (void) { // this function gets the map name and store it in the map_name global string variable. static char engineMap[256]; - strncpy (engineMap, STRING (g_pGlobals->mapname), cr::bufsize (engineMap)); + strncpy (engineMap, STRING (globals->mapname), cr::bufsize (engineMap)); return &engineMap[0]; } -Vector Engine::getAbsPos (edict_t *ent) { +Vector Game::getAbsPos (edict_t *ent) { // this expanded function returns the vector origin of a bounded entity, assuming that any // entity that has a bounding box has its center at the center of the bounding box itself. @@ -397,31 +413,31 @@ Vector Engine::getAbsPos (edict_t *ent) { return ent->v.origin; } -void Engine::registerCmd (const char *command, void func (void)) { +void Game::registerCmd (const char *command, void func (void)) { // this function tells the engine that a new server command is being declared, in addition // to the standard ones, whose name is command_name. The engine is thus supposed to be aware // that for every "command_name" server command it receives, it should call the function // pointed to by "function" in order to handle it. // check for hl pre 1.1.0.4, as it's doesn't have pfnAddServerCommand - if (!cr::checkptr (reinterpret_cast (g_engfuncs.pfnAddServerCommand))) { - logEntry (true, LL_FATAL, "YaPB's minimum HL engine version is 1.1.0.4 and minimum Counter-Strike is Beta 6.6. Please update your engine version."); + if (!cr::checkptr (reinterpret_cast (engfuncs.pfnAddServerCommand))) { + util.logEntry (true, LL_FATAL, "YaPB's minimum HL engine version is 1.1.0.4 and minimum Counter-Strike is Beta 6.6. Please update your engine version."); } - g_engfuncs.pfnAddServerCommand (const_cast (command), func); + engfuncs.pfnAddServerCommand (const_cast (command), func); } -void Engine::playSound (edict_t *ent, const char *sound) { - g_engfuncs.pfnEmitSound (ent, CHAN_WEAPON, sound, 1.0f, ATTN_NORM, 0, 100); +void Game::playSound (edict_t *ent, const char *sound) { + engfuncs.pfnEmitSound (ent, CHAN_WEAPON, sound, 1.0f, ATTN_NORM, 0, 100); } -void Engine::execBotCmd (edict_t *ent, const char *fmt, ...) { +void Game::execBotCmd (edict_t *ent, const char *fmt, ...) { // the purpose of this function is to provide fakeclients (bots) with the same client // command-scripting advantages (putting multiple commands in one line between semicolons) // as real players. It is an improved version of botman's FakeClientCommand, in which you // supply directly the whole string as if you were typing it in the bot's "console". It // is supposed to work exactly like the pfnClientCommand (server-sided client command). - if (!isFakeClient (ent)) { + if (!util.isFakeClient (ent)) { return; } va_list ap; @@ -431,72 +447,63 @@ void Engine::execBotCmd (edict_t *ent, const char *fmt, ...) { vsnprintf (string, cr::bufsize (string), fmt, ap); va_end (ap); - if (isEmptyStr (string)) { + String str (string); + + if (str.empty ()) { return; } - m_arguments[0] = '\0'; - m_argumentCount = 0; - m_isBotCommand = true; + m_botArgs.clear (); - size_t i, pos = 0; - size_t length = strlen (string); + // helper to parse single (not multi) command + auto parsePartArgs = [&] (String &args) { + args.trim ("\r\n\t\" "); // trim new lines - while (pos < length) { - size_t start = pos; - size_t stop = pos; - - while (pos < length && string[pos] != ';') { - pos++; + // we're have empty commands? + if (args.empty ()) { + return; } - if (pos > 1 && string[pos - 1] == '\n') { - stop = pos - 2; - } - else { - stop = pos - 1; - } + // find first space + const size_t space = args.find (' ', 0); - for (i = start; i <= stop; i++) { - m_arguments[i - start] = string[i]; - } - m_arguments[i - start] = 0; - pos++; + // if found space + if (space != String::INVALID_INDEX) { + const auto quote = space + 1; // check for quote next to space - size_t index = 0; - m_argumentCount = 0; - - while (index < i - start) { - while (index < i - start && m_arguments[index] == ' ') { - index++; - } - if (m_arguments[index] == '"') { - index++; - - while (index < i - start && m_arguments[index] != '"') { - index++; - } - index++; + // check if we're got a quoted string + if (quote < args.length () && args[quote] == '\"') { + m_botArgs.push (cr::move (args.substr (0, space))); // add command + m_botArgs.push (cr::move (args.substr (quote, args.length () - 1).trim ("\""))); // add string with trimmed quotes } else { - while (index < i - start && m_arguments[index] != ' ') { - index++; + for (auto &arg : args.split (" ")) { + m_botArgs.push (cr::move (arg)); } } - m_argumentCount++; + } + else { + m_botArgs.push (cr::move (args)); // move all the part to args } MDLL_ClientCommand (ent); + m_botArgs.clear (); // clear space for next cmd + }; + + if (str.find (';', 0) != String::INVALID_INDEX) { + for (auto &part : str.split (";")) { + parsePartArgs (part); + } + } + else { + parsePartArgs (str); } m_isBotCommand = false; - - m_arguments[0] = '\0'; - m_argumentCount = 0; } -bool Engine::isSoftwareRenderer (void) { +bool Game::isSoftwareRenderer (void) { // xash always use "hw" structures - if (g_gameFlags & GAME_XASH_ENGINE) { + if (is (GAME_XASH_ENGINE)) { return false; } @@ -505,7 +512,7 @@ bool Engine::isSoftwareRenderer (void) { return true; } - // and on only windows version you can use software-render engine. Linux, OSX always defaults to OpenGL + // and on only windows version you can use software-render game. Linux, OSX always defaults to OpenGL #if defined (PLATFORM_WIN32) static bool isSoftware = GetModuleHandleA ("sw"); #else @@ -514,64 +521,7 @@ bool Engine::isSoftwareRenderer (void) { return isSoftware; } -const char *Engine::getField (const char *string, size_t id) { - // this function gets and returns a particular field in a string where several strings are concatenated - - const int IterBufMax = 4; - - static char arg[IterBufMax][512]; - static int iter = -1; - - if (iter > IterBufMax - 1) { - iter = 0; - } - - char *ptr = arg[cr::clamp (iter++, 0, IterBufMax - 1)]; - ptr[0] = 0; - - size_t pos = 0, count = 0, start = 0, stop = 0; - size_t length = strlen (string); - - while (pos < length && count <= id) { - while (pos < length && (string[pos] == ' ' || string[pos] == '\t')) { - pos++; - } - if (string[pos] == '"') { - pos++; - start = pos; - - while (pos < length && string[pos] != '"') { - pos++; - } - stop = pos - 1; - pos++; - } - else { - start = pos; - - while (pos < length && string[pos] != ' ' && string[pos] != '\t') { - pos++; - } - stop = pos - 1; - } - - if (count == id) { - size_t i = start; - - for (; i <= stop; i++) { - ptr[i - start] = string[i]; - } - ptr[i - start] = 0; - break; - } - count++; // we have parsed one field more - } - String::trimChars (ptr); - - return ptr; -} - -void Engine::execCmd (const char *fmt, ...) { +void Game::execCmd (const char *fmt, ...) { // this function asks the engine to execute a server command va_list ap; @@ -583,10 +533,10 @@ void Engine::execCmd (const char *fmt, ...) { va_end (ap); strcat (string, "\n"); - g_engfuncs.pfnServerCommand (string); + engfuncs.pfnServerCommand (string); } -void Engine::pushVarToRegStack (const char *variable, const char *value, VarType varType, bool regMissing, const char *regVal, ConVar *self) { +void Game::pushVarToRegStack (const char *variable, const char *value, VarType varType, bool regMissing, const char *regVal, ConVar *self) { // this function adds globally defined variable to registration stack VarPair pair; @@ -616,7 +566,7 @@ void Engine::pushVarToRegStack (const char *variable, const char *value, VarType m_cvars.push (pair); } -void Engine::pushRegStackToEngine (bool gameVars) { +void Game::pushRegStackToEngine (bool gameVars) { // this function pushes all added global variables to engine registration for (auto &var : m_cvars) { @@ -624,23 +574,23 @@ void Engine::pushRegStackToEngine (bool gameVars) { cvar_t ® = var.reg; if (var.type != VT_NOREGISTER) { - self.m_eptr = g_engfuncs.pfnCVarGetPointer (reg.name); + self.m_eptr = engfuncs.pfnCVarGetPointer (reg.name); if (self.m_eptr == nullptr) { - g_engfuncs.pfnCVarRegister (&var.reg); - self.m_eptr = g_engfuncs.pfnCVarGetPointer (reg.name); + engfuncs.pfnCVarRegister (&var.reg); + self.m_eptr = engfuncs.pfnCVarGetPointer (reg.name); } } else if (gameVars) { - self.m_eptr = g_engfuncs.pfnCVarGetPointer (reg.name); + self.m_eptr = engfuncs.pfnCVarGetPointer (reg.name); if (var.regMissing && self.m_eptr == nullptr) { if (reg.string == nullptr && var.regVal != nullptr) { reg.string = const_cast (var.regVal); reg.flags |= FCVAR_SERVER; } - g_engfuncs.pfnCVarRegister (&var.reg); - self.m_eptr = g_engfuncs.pfnCVarGetPointer (reg.name); + engfuncs.pfnCVarRegister (&var.reg); + self.m_eptr = engfuncs.pfnCVarGetPointer (reg.name); } if (!self.m_eptr) { @@ -650,10 +600,10 @@ void Engine::pushRegStackToEngine (bool gameVars) { } } -const char *Engine::translate (const char *input) { +const char *Game::translate (const char *input) { // this function translate input string into needed language - if (isDedicated ()) { + if (isDedicated () || !m_language.exists (input)) { return input; } static String result; @@ -664,7 +614,7 @@ const char *Engine::translate (const char *input) { return input; // nothing found } -void Engine::processMessages (void *ptr) { +void Game::processMessages (void *ptr) { if (m_msgBlock.msg == NETMSG_UNDEFINED) { return; } @@ -679,7 +629,7 @@ void Engine::processMessages (void *ptr) { static int state, id, clip; static Vector damageOrigin; - static WeaponProperty weaponProp; + static WeaponProp weaponProp; // some widely used stuff Bot *bot = bots.getBot (m_msgBlock.bot); @@ -746,7 +696,7 @@ void Engine::processMessages (void *ptr) { switch (m_msgBlock.state) { case 0: - strncpy (weaponProp.className, strVal, cr::bufsize (weaponProp.className)); + strncpy (weaponProp.classname, strVal, cr::bufsize (weaponProp.classname)); break; case 1: @@ -758,11 +708,11 @@ void Engine::processMessages (void *ptr) { break; case 5: - weaponProp.slotID = intVal; // slot for this weapon + weaponProp.slot = intVal; // slot for this weapon break; case 6: - weaponProp.position = intVal; // position in slot + weaponProp.pos = intVal; // position in slot break; case 7: @@ -771,7 +721,7 @@ void Engine::processMessages (void *ptr) { case 8: weaponProp.flags = intVal; // flags for weapon (WTF???) - g_weaponDefs[weaponProp.id] = weaponProp; // store away this weapon with it's ammo information... + conf.setWeaponProp (weaponProp); // store away this weapon with it's ammo information... break; } break; @@ -922,7 +872,7 @@ void Engine::processMessages (void *ptr) { Bot *notify = bots.getBot (i); if (notify != nullptr && notify->m_notKilled && killer != notify->ent () && notify->seesEntity (victim->v.origin) && getTeam (killer) == notify->m_team && getTeam (killer) != getTeam (victim)) { - if (killer == g_hostEntity) { + if (!bots.getBot (killer)) { notify->processChatterMessage ("#Bot_NiceShotCommander"); } else { @@ -937,7 +887,7 @@ void Engine::processMessages (void *ptr) { for (int i = 0; i < maxClients (); i++) { Bot *notify = bots.getBot (i); - if (notify != nullptr && notify->m_seeEnemyTime + 2.0f < timebase () && notify->m_notKilled && notify->m_team == getTeam (victim) && isVisible (killer->v.origin, notify->ent ()) && isNullEntity (notify->m_enemy) && getTeam (killer) != getTeam (victim)) { + if (notify != nullptr && notify->m_seeEnemyTime + 2.0f < timebase () && notify->m_notKilled && notify->m_team == getTeam (victim) && util.isVisible (killer->v.origin, notify->ent ()) && isNullEntity (notify->m_enemy) && getTeam (killer) != getTeam (victim)) { notify->m_actualReactionTime = 0.0f; notify->m_seeEnemyTime = timebase (); notify->m_enemy = killer; @@ -998,7 +948,7 @@ void Engine::processMessages (void *ptr) { case 1: if (numPlayers == 0 && intVal == 0) { - initRound (); + bots.initRound (); } break; } @@ -1023,10 +973,10 @@ void Engine::processMessages (void *ptr) { strcmp (strVal, "#Target_Bombed") == 0 || strcmp (strVal, "#Game_Commencing") == 0 || strcmp (strVal, "#Game_will_restart_in") == 0) { - g_roundEnded = true; + bots.setRoundOver (true); if (strcmp (strVal, "#Game_Commencing") == 0) { - g_gameWelcomeSent = true; + util.setNeedForWelcome (true); } if (strcmp (strVal, "#CTs_Win") == 0) { @@ -1059,9 +1009,8 @@ void Engine::processMessages (void *ptr) { } waypoints.setBombPos (true); } - else if (!g_bombPlanted && strcmp (strVal, "#Bomb_Planted") == 0) { - g_bombPlanted = g_bombSayString = true; - g_timeBombPlanted = timebase (); + else if (!bots.isBombPlanted () && strcmp (strVal, "#Bomb_Planted") == 0) { + bots.setBombPlanted (true); for (int i = 0; i < maxClients (); i++) { Bot *notify = bots.getBot (i); @@ -1078,10 +1027,10 @@ void Engine::processMessages (void *ptr) { waypoints.setBombPos (); } else if (bot != nullptr && strcmp (strVal, "#Switch_To_BurstFire") == 0) { - bot->m_weaponBurstMode = BM_ON; + bot->m_weaponBurstMode = BURST_ON; } else if (bot != nullptr && strcmp (strVal, "#Switch_To_SemiAuto") == 0) { - bot->m_weaponBurstMode = BM_OFF; + bot->m_weaponBurstMode = BURST_OFF; } } break; @@ -1108,10 +1057,10 @@ void Engine::processMessages (void *ptr) { else if (strVal[0] == 'S' && strVal[1] == 'P') { team = TEAM_SPECTATOR; } - auto &client = g_clients[playerIndex - 1]; + auto &client = util.getClient (playerIndex - 1); client.team2 = team; - client.team = (g_gameFlags & GAME_CSDM_FFA) ? playerIndex : team; + client.team = game.is (GAME_CSDM_FFA) ? playerIndex : team; } break; } @@ -1121,6 +1070,10 @@ void Engine::processMessages (void *ptr) { if (bot != nullptr && m_msgBlock.state == 0) { if (intVal > 0) { bot->m_hasProgressBar = true; // the progress bar on a hud + + if (game.mapIs (MAP_DE) && bots.isBombPlanted () && bot->m_team == TEAM_COUNTER) { + bots.notifyBombDefuse (); + } } else if (intVal == 0) { bot->m_hasProgressBar = false; // no progress bar or disappeared @@ -1154,22 +1107,336 @@ void Engine::processMessages (void *ptr) { break; default: - logEntry (true, LL_FATAL, "Network message handler error. Call to unrecognized message id (%d).\n", m_msgBlock.msg); + util.logEntry (true, LL_FATAL, "Network message handler error. Call to unrecognized message id (%d).\n", m_msgBlock.msg); } m_msgBlock.state++; // and finally update network message state } +bool Game::loadCSBinary (void) { + const char *modname = game.getModName (); + + if (!modname) { + return false; + } + +#if defined(PLATFORM_WIN32) + const char *libs[] = { "mp.dll", "cs.dll" }; +#elif defined(PLATFORM_LINUX) + const char *libs[] = { "cs.so", "cs_i386.so" }; +#elif defined(PLATFORM_OSX) + const char *libs[] = { "cs.dylib" }; +#endif + + auto libCheck = [&] (const char *modname, const char *dll) { + // try to load gamedll + if (!m_gameLib.isValid ()) { + util.logEntry (true, LL_FATAL | LL_IGNORE, "Unable to load gamedll \"%s\". Exiting... (gamedir: %s)", dll, modname); + + return false; + } + auto ent = m_gameLib.resolve ("trigger_random_unique"); + + // detect regamedll by addon entity they provide + if (ent != nullptr) { + m_gameFlags |= GAME_REGAMEDLL; + } + return true; + }; + + // search the libraries inside game dlls directory + for (const auto lib : libs) { + auto *path = util.format ("%s/dlls/%s", modname, lib); + + // if we can't read file, skip it + if (!File::exists (path)) { + continue; + } + + // special case, czero is always detected first, as it's has custom directory + if (strcmp (modname, "czero") == 0) { + m_gameFlags |= (GAME_CZERO | GAME_SUPPORT_BOT_VOICE | GAME_SUPPORT_SVC_PINGS); + + if (is (GAME_METAMOD)) { + return false; + } + m_gameLib.load (path); + + // verify dll is OK + if (!libCheck (modname, lib)) { + return false; + } + return true; + } + else { + m_gameLib.load (path); + + // verify dll is OK + if (!libCheck (modname, lib)) { + return false; + } + + // detect if we're running modern game + auto entity = m_gameLib.resolve ("weapon_famas"); + + // detect xash engine + if (engfuncs.pfnCVarGetPointer ("build") != nullptr) { + m_gameFlags |= (GAME_LEGACY | GAME_XASH_ENGINE); + + if (entity != nullptr) { + m_gameFlags |= GAME_SUPPORT_BOT_VOICE; + } + + if (is (GAME_METAMOD)) { + return false; + } + return true; + } + + if (entity != nullptr) { + m_gameFlags |= (GAME_CSTRIKE16 | GAME_SUPPORT_BOT_VOICE | GAME_SUPPORT_SVC_PINGS); + } + else { + m_gameFlags |= GAME_LEGACY; + } + + if (is (GAME_METAMOD)) { + return false; + } + return true; + } + } + return false; +} + +bool Game::postload (void) { + // register our cvars + game.pushRegStackToEngine (); + + // ensure we're have all needed directories + const char *mod = game.getModName (); + + // create the needed paths + File::pathCreate (const_cast (util.format ("%s/addons/yapb/conf/lang", mod))); + File::pathCreate (const_cast (util.format ("%s/addons/yapb/data/learned", mod))); + + // print game detection info + auto printGame = [&] (void) { + String gameVersionStr; + + if (is (GAME_LEGACY)) { + gameVersionStr.assign ("Legacy"); + } + else if (is (GAME_CZERO)) { + gameVersionStr.assign ("Condition Zero"); + } + else if (is (GAME_CSTRIKE16)) { + gameVersionStr.assign ("v1.6"); + } + + if (is (GAME_XASH_ENGINE)) { + gameVersionStr.append (" @ Xash3D Engine"); + + if (is (GAME_MOBILITY)) { + gameVersionStr.append (" Mobile"); + } + gameVersionStr.replace ("Legacy", "1.6 Limited"); + } + + if (is (GAME_SUPPORT_BOT_VOICE)) { + gameVersionStr.append (" (BV)"); + } + + if (is (GAME_REGAMEDLL)) { + gameVersionStr.append (" (RE)"); + } + + if (is (GAME_SUPPORT_SVC_PINGS)) { + gameVersionStr.append (" (SVC)"); + } + print ("[YAPB] Bot v%s.0.%d Loaded. Game detected as Counter-Strike: %s", PRODUCT_VERSION, util.buildNumber (), gameVersionStr.chars ()); + }; + +#ifdef PLATFORM_ANDROID + m_gameFlags |= (GAME_XASH_ENGINE | GAME_MOBILITY | GAME_SUPPORT_BOT_VOICE | GAME_REGAMEDLL); + + if (is (GAME_METAMOD)) { + return true; // we should stop the attempt for loading the real gamedll, since metamod handle this for us + } + + extern ConVar yb_difficulty; + yb_difficulty.set (2); + +#ifdef LOAD_HARDFP + const char *serverDLL = "libserver_hardfp.so"; +#else + const char *serverDLL = "libserver.so"; +#endif + + char gameDLLName[256]; + snprintf (gameDLLName, cr::bufsize (gameDLLName), "%s/%s", getenv ("XASH3D_GAMELIBDIR"), serverDLL); + + m_gameLib.load (gameDLLName); + + if (!m_gameLib.isValid ()) { + util.logEntry (true, LL_FATAL | LL_IGNORE, "Unable to load gamedll \"%s\". Exiting... (gamedir: %s)", gameDLLName, game.getModName ()); + return true; + } + printGame (); + +#else + bool binaryLoaded = loadCSBinary (); + + if (!binaryLoaded && !is (GAME_METAMOD)) { + util.logEntry (true, LL_FATAL | LL_IGNORE, "Mod that you has started, not supported by this bot (gamedir: %s)", game.getModName ()); + return true; + } + printGame (); + + if (is (GAME_METAMOD)) { + return true; + } +#endif + return false; +} + +void Game::detectDeathmatch (void) { + if (!game.is (GAME_METAMOD | GAME_REGAMEDLL)) { + return; + } + static auto dmActive = engfuncs.pfnCVarGetPointer ("csdm_active"); + static auto freeForAll = engfuncs.pfnCVarGetPointer ("mp_freeforall"); + + // csdm is only with amxx and metamod + if (dmActive) { + if (dmActive->value > 0.0f) { + m_gameFlags |= GAME_CSDM; + } + else if (is (GAME_CSDM)) { + m_gameFlags &= ~GAME_CSDM; + } + } + + // but this can be provided by regamedll + if (freeForAll) { + if (freeForAll->value > 0.0f) { + m_gameFlags |= GAME_CSDM_FFA; + } + else if (is (GAME_CSDM_FFA)) { + m_gameFlags &= ~GAME_CSDM_FFA; + } + } +} + +void Game::slowFrame (void) { + if (m_slowFrame > game.timebase ()) { + return; + } + + ctrl.maintainAdminRights (); + bots.calculatePingOffsets (); + + // calculate light levels for all waypoints if needed + waypoints.initLightLevels (); + + // detect csdm + detectDeathmatch (); + + // display welcome message + util.checkWelcome (); + m_slowFrame = timebase () + 1.0f; +} + +void Game::beginMessage (edict_t *ent, int dest, int type) { + // store the message type in our own variables, since the GET_USER_MSG_ID () will just do a lot of strcmp()'s... + if (is (GAME_METAMOD) && getMessageId (NETMSG_MONEY) == -1) { + + auto setMsgId = [&] (const char *name, NetMsgId id) { + setMessageId (id, GET_USER_MSG_ID (PLID, name, nullptr)); + }; + setMsgId ("VGUIMenu", NETMSG_VGUI); + setMsgId ("ShowMenu", NETMSG_SHOWMENU); + setMsgId ("WeaponList", NETMSG_WEAPONLIST); + setMsgId ("CurWeapon", NETMSG_CURWEAPON); + setMsgId ("AmmoX", NETMSG_AMMOX); + setMsgId ("AmmoPickup", NETMSG_AMMOPICKUP); + setMsgId ("Damage", NETMSG_DAMAGE); + setMsgId ("Money", NETMSG_MONEY); + setMsgId ("StatusIcon", NETMSG_STATUSICON); + setMsgId ("DeathMsg", NETMSG_DEATH); + setMsgId ("ScreenFade", NETMSG_SCREENFADE); + setMsgId ("HLTV", NETMSG_HLTV); + setMsgId ("TextMsg", NETMSG_TEXTMSG); + setMsgId ("TeamInfo", NETMSG_TEAMINFO); + setMsgId ("BarTime", NETMSG_BARTIME); + setMsgId ("SendAudio", NETMSG_SENDAUDIO); + setMsgId ("SayText", NETMSG_SAYTEXT); + setMsgId ("FlashBat", NETMSG_FLASHBAT); + setMsgId ("Flashlight", NETMSG_FLASHLIGHT); + setMsgId ("NVGToggle", NETMSG_NVGTOGGLE); + setMsgId ("ItemStatus", NETMSG_ITEMSTATUS); + + if (is (GAME_SUPPORT_BOT_VOICE)) { + setMessageId (NETMSG_BOTVOICE, GET_USER_MSG_ID (PLID, "BotVoice", nullptr)); + } + } + + if ((!is (GAME_LEGACY) || is (GAME_XASH_ENGINE)) && dest == MSG_SPEC && type == getMessageId (NETMSG_HLTV)) { + setCurrentMessageId (NETMSG_HLTV); + } + captureMessage (type, NETMSG_WEAPONLIST); + + if (!isNullEntity (ent)) { + int index = bots.index (ent); + + // is this message for a bot? + if (index != -1 && !(ent->v.flags & FL_DORMANT)) { + setCurrentMessageOwner (index); + + // message handling is done in usermsg.cpp + captureMessage (type, NETMSG_VGUI); + captureMessage (type, NETMSG_CURWEAPON); + captureMessage (type, NETMSG_AMMOX); + captureMessage (type, NETMSG_AMMOPICKUP); + captureMessage (type, NETMSG_DAMAGE); + captureMessage (type, NETMSG_MONEY); + captureMessage (type, NETMSG_STATUSICON); + captureMessage (type, NETMSG_SCREENFADE); + captureMessage (type, NETMSG_BARTIME); + captureMessage (type, NETMSG_TEXTMSG); + captureMessage (type, NETMSG_SHOWMENU); + captureMessage (type, NETMSG_FLASHBAT); + captureMessage (type, NETMSG_NVGTOGGLE); + captureMessage (type, NETMSG_ITEMSTATUS); + } + } + else if (dest == MSG_ALL) { + captureMessage (type, NETMSG_TEAMINFO); + captureMessage (type, NETMSG_DEATH); + captureMessage (type, NETMSG_TEXTMSG); + + if (type == SVC_INTERMISSION) { + for (int i = 0; i < game.maxClients (); i++) { + auto bot = bots.getBot (i); + + if (bot != nullptr) { + bot->m_notKilled = false; + } + } + } + } +} + void LightMeasure::initializeLightstyles (void) { // this function initializes lighting information... // reset all light styles - for (int i = 0; i < MAX_LIGHTSTYLES; i++) { - m_lightstyle[i].length = 0; - m_lightstyle[i].map[0] = 0x0; + for (auto &ls : m_lightstyle) { + ls.length = 0; + ls.map[0] = 0; } - for (int i = 0; i < MAX_LIGHTSTYLEVALUE; i++) { - m_lightstyleValue[i] = 264; + for (auto &lsv : m_lightstyleValue) { + lsv = 264; } } @@ -1181,7 +1448,7 @@ void LightMeasure::animateLight (void) { } // 'm' is normal light, 'a' is no light, 'z' is double bright - const int index = static_cast (engine.timebase () * 10.0f); + const int index = static_cast (game.timebase () * 10.0f); for (int j = 0; j < MAX_LIGHTSTYLES; j++) { if (!m_lightstyle[j].length) { @@ -1193,6 +1460,28 @@ void LightMeasure::animateLight (void) { } } +void LightMeasure::updateLight (int style, char *value) { + if (!m_doAnimation) { + return; + } + + if (style >= MAX_LIGHTSTYLES) { + return; + } + + if (util.isEmptyStr (value)){ + m_lightstyle[style].length = 0u; + m_lightstyle[style].map[0] = '\0'; + + return; + } + const auto copyLimit = sizeof (m_lightstyle[style].map) - sizeof ('\0'); + strncpy (m_lightstyle[style].map, value, copyLimit); + + m_lightstyle[style].map[copyLimit] = '\0'; + m_lightstyle[style].length = strlen (m_lightstyle[style].map); +} + template bool LightMeasure::recursiveLightPoint (const M *node, const Vector &start, const Vector &end) { if (node->contents < 0) { return false; @@ -1300,7 +1589,7 @@ float LightMeasure::getLightLevel (const Vector &point) { // it's depends if we're are on dedicated or on listenserver auto recursiveCheck = [&] (void) -> bool { - if (!engine.isSoftwareRenderer ()) { + if (!game.isSoftwareRenderer ()) { return recursiveLightPoint (reinterpret_cast (m_worldModel->nodes), point, endPoint); } return recursiveLightPoint (m_worldModel->nodes, point, endPoint); @@ -1309,5 +1598,5 @@ float LightMeasure::getLightLevel (const Vector &point) { } float LightMeasure::getSkyColor (void) { - return sv_skycolor_r.flt () + sv_skycolor_g.flt () + sv_skycolor_b.flt (); + return sv_skycolor_r.flt () + sv_skycolor_g.flt () + sv_skycolor_b.flt () / 3; } diff --git a/source/globals.cpp b/source/globals.cpp deleted file mode 100644 index 874262d..0000000 --- a/source/globals.cpp +++ /dev/null @@ -1,379 +0,0 @@ -// -// Yet Another POD-Bot, based on PODBot by Markus Klinge ("CountFloyd"). -// Copyright (c) YaPB Development Team. -// -// This software is licensed under the BSD-style license. -// Additional exceptions apply. For full license details, see LICENSE.txt or visit: -// https://yapb.ru/license -// - -#include - -bool g_canSayBombPlanted = true; -bool g_roundEnded = true; -bool g_botsCanPause = false; -bool g_bombPlanted = false; -bool g_bombSayString = false; -bool g_gameWelcomeSent = false; - -bool g_editNoclip = false; -bool g_waypointOn = false; -bool g_autoWaypoint = false; - -float g_lastChatTime = 0.0f; -float g_timeRoundStart = 0.0f; -float g_timeRoundEnd = 0.0f; -float g_timeRoundMid = 0.0f; -float g_timeNextBombUpdate = 0.0f; -float g_timeBombPlanted = 0.0f; -float g_timePerSecondUpdate = 0.0f; -float g_lastRadioTime[MAX_TEAM_COUNT] = { 0.0f, }; -float g_autoPathDistance = 250.0f; - -int g_lastRadio[MAX_TEAM_COUNT]; -int g_storeAddbotVars[4]; -int g_radioSelect[MAX_ENGINE_PLAYERS]; -int g_gameFlags = 0; -int g_mapFlags = 0; - -int g_highestDamageCT = 1; -int g_highestDamageT = 1; -int g_highestKills = 1; - -Array g_chatFactory; -Array > g_chatterFactory; -Array g_botNames; -Array g_replyFactory; -Library g_gameLib; - -meta_globals_t *gpMetaGlobals = nullptr; -gamedll_funcs_t *gpGamedllFuncs = nullptr; -mutil_funcs_t *gpMetaUtilFuncs = nullptr; - -gamefuncs_t g_functionTable; - -enginefuncs_t g_engfuncs; -Client g_clients[MAX_ENGINE_PLAYERS]; -WeaponProperty g_weaponDefs[MAX_WEAPONS + 1]; - -edict_t *g_hostEntity = nullptr; -globalvars_t *g_pGlobals = nullptr; -Experience *g_experienceData = nullptr; - -// default tables for personality weapon preferences, overridden by weapons.cfg -int g_normalWeaponPrefs[NUM_WEAPONS] = {0, 2, 1, 4, 5, 6, 3, 12, 10, 24, 25, 13, 11, 8, 7, 22, 23, 18, 21, 17, 19, 15, 17, 9, 14, 16}; -int g_rusherWeaponPrefs[NUM_WEAPONS] = {0, 2, 1, 4, 5, 6, 3, 24, 19, 22, 23, 20, 21, 10, 12, 13, 7, 8, 11, 9, 18, 17, 19, 25, 15, 16}; -int g_carefulWeaponPrefs[NUM_WEAPONS] = {0, 2, 1, 4, 25, 6, 3, 7, 8, 12, 10, 13, 11, 9, 24, 18, 14, 17, 16, 15, 19, 20, 21, 22, 23, 5}; -int g_grenadeBuyPrecent[NUM_WEAPONS - 23] = {95, 85, 60}; -int g_botBuyEconomyTable[NUM_WEAPONS - 15] = {1900, 2100, 2100, 4000, 6000, 7000, 16000, 1200, 800, 1000, 3000}; -int *g_weaponPrefs[] = {g_normalWeaponPrefs, g_rusherWeaponPrefs, g_carefulWeaponPrefs}; - -// metamod plugin information -plugin_info_t Plugin_info = { - META_INTERFACE_VERSION, // interface version - PRODUCT_SHORT_NAME, // plugin name - PRODUCT_VERSION, // plugin version - PRODUCT_DATE, // date of creation - PRODUCT_AUTHOR, // plugin author - PRODUCT_URL, // plugin URL - PRODUCT_LOGTAG, // plugin logtag - PT_CHANGELEVEL, // when loadable - PT_ANYTIME, // when unloadable -}; - -// table with all available actions for the bots (filtered in & out in Bot::setConditions) some of them have subactions included -Task g_taskFilters[TASK_MAX] = { - { TASK_NORMAL, 0, INVALID_WAYPOINT_INDEX, 0.0f, true }, - { TASK_PAUSE, 0, INVALID_WAYPOINT_INDEX, 0.0f, false }, - { TASK_MOVETOPOSITION, 0, INVALID_WAYPOINT_INDEX, 0.0f, true }, - { TASK_FOLLOWUSER, 0, INVALID_WAYPOINT_INDEX, 0.0f, true }, - { TASK_PICKUPITEM, 0, INVALID_WAYPOINT_INDEX, 0.0f, true }, - { TASK_CAMP, 0, INVALID_WAYPOINT_INDEX, 0.0f, true }, - { TASK_PLANTBOMB, 0, INVALID_WAYPOINT_INDEX, 0.0f, false }, - { TASK_DEFUSEBOMB, 0, INVALID_WAYPOINT_INDEX, 0.0f, false }, - { TASK_ATTACK, 0, INVALID_WAYPOINT_INDEX, 0.0f, false }, - { TASK_HUNTENEMY, 0, INVALID_WAYPOINT_INDEX, 0.0f, false }, - { TASK_SEEKCOVER, 0, INVALID_WAYPOINT_INDEX, 0.0f, false }, - { TASK_THROWHEGRENADE, 0, INVALID_WAYPOINT_INDEX, 0.0f, false }, - { TASK_THROWFLASHBANG, 0, INVALID_WAYPOINT_INDEX, 0.0f, false }, - { TASK_THROWSMOKE, 0, INVALID_WAYPOINT_INDEX, 0.0f, false }, - { TASK_DOUBLEJUMP, 0, INVALID_WAYPOINT_INDEX, 0.0f, false }, - { TASK_ESCAPEFROMBOMB, 0, INVALID_WAYPOINT_INDEX, 0.0f, false }, - { TASK_SHOOTBREAKABLE, 0, INVALID_WAYPOINT_INDEX, 0.0f, false }, - { TASK_HIDE, 0, INVALID_WAYPOINT_INDEX, 0.0f, false }, - { TASK_BLINDED, 0, INVALID_WAYPOINT_INDEX, 0.0f, false }, - { TASK_SPRAY, 0, INVALID_WAYPOINT_INDEX, 0.0f, false } -}; - -// weapons and their specifications -WeaponSelect g_weaponSelect[NUM_WEAPONS + 1] = { - { WEAPON_KNIFE, "weapon_knife", "knife.mdl", 0, 0, -1, -1, 0, 0, 0, 0, 0, true }, - { WEAPON_USP, "weapon_usp", "usp.mdl", 500, 1, -1, -1, 1, 1, 2, 2, 0, false }, - { WEAPON_GLOCK, "weapon_glock18", "glock18.mdl", 400, 1, -1, -1, 1, 2, 1, 1, 0, false }, - { WEAPON_DEAGLE, "weapon_deagle", "deagle.mdl", 650, 1, 2, 2, 1, 3, 4, 4, 2, false }, - { WEAPON_P228, "weapon_p228", "p228.mdl", 600, 1, 2, 2, 1, 4, 3, 3, 0 , false }, - { WEAPON_ELITE, "weapon_elite", "elite.mdl", 800, 1, 0, 0, 1, 5, 5, 5, 0, false }, - { WEAPON_FIVESEVEN, "weapon_fiveseven", "fiveseven.mdl", 750, 1, 1, 1, 1, 6, 5, 5, 0, false }, - { WEAPON_M3, "weapon_m3", "m3.mdl", 1700, 1, 2, -1, 2, 1, 1, 1, 0, false }, - { WEAPON_XM1014, "weapon_xm1014", "xm1014.mdl", 3000, 1, 2, -1, 2, 2, 2, 2, 0, false }, - { WEAPON_MP5, "weapon_mp5navy", "mp5.mdl", 1500, 1, 2, 1, 3, 1, 2, 2, 0, true }, - { WEAPON_TMP, "weapon_tmp", "tmp.mdl", 1250, 1, 1, 1, 3, 2, 1, 1, 0, true }, - { WEAPON_P90, "weapon_p90", "p90.mdl", 2350, 1, 2, 1, 3, 3, 4, 4, 0, true }, - { WEAPON_MAC10, "weapon_mac10", "mac10.mdl", 1400, 1, 0, 0, 3, 4, 1, 1, 0, true }, - { WEAPON_UMP45, "weapon_ump45", "ump45.mdl", 1700, 1, 2, 2, 3, 5, 3, 3, 0, true }, - { WEAPON_AK47, "weapon_ak47", "ak47.mdl", 2500, 1, 0, 0, 4, 1, 2, 2, 2, true }, - { WEAPON_SG552, "weapon_sg552", "sg552.mdl", 3500, 1, 0, -1, 4, 2, 4, 4, 2, true }, - { WEAPON_M4A1, "weapon_m4a1", "m4a1.mdl", 3100, 1, 1, 1, 4, 3, 3, 3, 2, true }, - { WEAPON_GALIL, "weapon_galil", "galil.mdl", 2000, 1, 0, 0, 4, -1, 1, 1, 2, true }, - { WEAPON_FAMAS, "weapon_famas", "famas.mdl", 2250, 1, 1, 1, 4, -1, 1, 1, 2, true }, - { WEAPON_AUG, "weapon_aug", "aug.mdl", 3500, 1, 1, 1, 4, 4, 4, 4, 2, true }, - { WEAPON_SCOUT, "weapon_scout", "scout.mdl", 2750, 1, 2, 0, 4, 5, 3, 2, 3, false }, - { WEAPON_AWP, "weapon_awp", "awp.mdl", 4750, 1, 2, 0, 4, 6, 5, 6, 3, false }, - { WEAPON_G3SG1, "weapon_g3sg1", "g3sg1.mdl", 5000, 1, 0, 2, 4, 7, 6, 6, 3, false }, - { WEAPON_SG550, "weapon_sg550", "sg550.mdl", 4200, 1, 1, 1, 4, 8, 5, 5, 3, false }, - { WEAPON_M249, "weapon_m249", "m249.mdl", 5750, 1, 2, 1, 5, 1, 1, 1, 2, true }, - { WEAPON_SHIELD, "weapon_shield", "shield.mdl", 2200, 0, 1, 1, 8, -1, 8, 8, 0, false }, - { 0, "", "", 0, 0, 0, 0, 0, 0, 0, 0, 0, false } -}; - -void setupBotMenus (void) { - int counter = 0; - - auto buildKeys = [](int numKeys) { - int keys = 0; - - for (int i = 0; i < numKeys; i++) { - keys |= (1 << i); - } - keys |= (1 << 9); - - return keys; - }; - - // bots main menu - g_menus[counter] = { - BOT_MENU_MAIN, buildKeys (4), - "\\yMain Menu\\w\n\n" - "1. Control Bots\n" - "2. Features\n\n" - "3. Fill Server\n" - "4. End Round\n\n" - "0. Exit" - }; - - // bots features menu - g_menus[++counter] = { - BOT_MENU_FEATURES, buildKeys (5), - "\\yBots Features\\w\n\n" - "1. Weapon Mode Menu\n" - "2. Waypoint Menu\n" - "3. Select Personality\n\n" - "4. Toggle Debug Mode\n" - "5. Command Menu\n\n" - "0. Exit" - }; - - // bot control menu - g_menus[++counter] = { - BOT_MENU_CONTROL, buildKeys (5), - "\\yBots Control Menu\\w\n\n" - "1. Add a Bot, Quick\n" - "2. Add a Bot, Specified\n\n" - "3. Remove Random Bot\n" - "4. Remove All Bots\n\n" - "5. Remove Bot Menu\n\n" - "0. Exit" - }; - - // weapon mode select menu - g_menus[++counter] = { - BOT_MENU_WEAPON_MODE, buildKeys (7), - "\\yBots Weapon Mode\\w\n\n" - "1. Knives only\n" - "2. Pistols only\n" - "3. Shotguns only\n" - "4. Machine Guns only\n" - "5. Rifles only\n" - "6. Sniper Weapons only\n" - "7. All Weapons\n\n" - "0. Exit" - }; - - // personality select menu - g_menus[++counter] = { - BOT_MENU_PERSONALITY, buildKeys (4), - "\\yBots Personality\\w\n\n" - "1. Random\n" - "2. Normal\n" - "3. Aggressive\n" - "4. Careful\n\n" - "0. Exit" - }; - - // difficulty select menu - g_menus[++counter] = { - BOT_MENU_DIFFICULTY, buildKeys (5), - "\\yBots Difficulty Level\\w\n\n" - "1. Newbie\n" - "2. Average\n" - "3. Normal\n" - "4. Professional\n" - "5. Godlike\n\n" - "0. Exit" - }; - - // team select menu - g_menus[++counter] = { - BOT_MENU_TEAM_SELECT, buildKeys (5), - "\\ySelect a team\\w\n\n" - "1. Terrorist Force\n" - "2. Counter-Terrorist Force\n\n" - "5. Auto-select\n\n" - "0. Exit" - }; - - // terrorist model select menu - g_menus[++counter] = { - BOT_MENU_TERRORIST_SELECT, buildKeys (5), - "\\ySelect an appearance\\w\n\n" - "1. Phoenix Connexion\n" - "2. L337 Krew\n" - "3. Arctic Avengers\n" - "4. Guerilla Warfare\n\n" - "5. Auto-select\n\n" - "0. Exit" - }; - - // counter-terrorist model select menu - g_menus[++counter] = { - BOT_MENU_CT_SELECT, buildKeys (5), - "\\ySelect an appearance\\w\n\n" - "1. Seal Team 6 (DEVGRU)\n" - "2. German GSG-9\n" - "3. UK SAS\n" - "4. French GIGN\n\n" - "5. Auto-select\n\n" - "0. Exit" - }; - - // command menu - g_menus[++counter] = { - BOT_MENU_COMMANDS, buildKeys (4), - "\\yBot Command Menu\\w\n\n" - "1. Make Double Jump\n" - "2. Finish Double Jump\n\n" - "3. Drop the C4 Bomb\n" - "4. Drop the Weapon\n\n" - "0. Exit" - }; - - // main waypoint menu - g_menus[++counter] = { - BOT_MENU_WAYPOINT_MAIN_PAGE1, buildKeys (9), - "\\yWaypoint Operations (Page 1)\\w\n\n" - "1. Show/Hide waypoints\n" - "2. Cache waypoint\n" - "3. Create path\n" - "4. Delete path\n" - "5. Add waypoint\n" - "6. Delete waypoint\n" - "7. Set Autopath Distance\n" - "8. Set Radius\n\n" - "9. Next...\n\n" - "0. Exit" - }; - - // main waypoint menu (page 2) - g_menus[++counter] = { - BOT_MENU_WAYPOINT_MAIN_PAGE2, buildKeys (9), - "\\yWaypoint Operations (Page 2)\\w\n\n" - "1. Waypoint stats\n" - "2. Autowaypoint on/off\n" - "3. Set flags\n" - "4. Save waypoints\n" - "5. Save without checking\n" - "6. Load waypoints\n" - "7. Check waypoints\n" - "8. Noclip cheat on/off\n\n" - "9. Previous...\n\n" - "0. Exit" - }; - - // select waypoint radius menu - g_menus[++counter] = { - BOT_MENU_WAYPOINT_RADIUS, buildKeys (9), - "\\yWaypoint Radius\\w\n\n" - "1. SetRadius 0\n" - "2. SetRadius 8\n" - "3. SetRadius 16\n" - "4. SetRadius 32\n" - "5. SetRadius 48\n" - "6. SetRadius 64\n" - "7. SetRadius 80\n" - "8. SetRadius 96\n" - "9. SetRadius 128\n\n" - "0. Exit" - }; - - // waypoint add menu - g_menus[++counter] = { - BOT_MENU_WAYPOINT_TYPE, buildKeys (9), - "\\yWaypoint Type\\w\n\n" - "1. Normal\n" - "\\r2. Terrorist Important\n" - "3. Counter-Terrorist Important\n" - "\\w4. Block with hostage / Ladder\n" - "\\y5. Rescue Zone\n" - "\\w6. Camping\n" - "7. Camp End\n" - "\\r8. Map Goal\n" - "\\w9. Jump\n\n" - "0. Exit" - }; - - // set waypoint flag menu - g_menus[++counter] = { - BOT_MENU_WAYPOINT_FLAG, buildKeys (5), - "\\yToggle Waypoint Flags\\w\n\n" - "1. Block with Hostage\n" - "2. Terrorists Specific\n" - "3. CTs Specific\n" - "4. Use Elevator\n" - "5. Sniper Point (\\yFor Camp Points Only!\\w)\n\n" - "0. Exit" - }; - - // auto-path max distance - g_menus[++counter] = { - BOT_MENU_WAYPOINT_AUTOPATH, buildKeys (7), - "\\yAutoPath Distance\\w\n\n" - "1. Distance 0\n" - "2. Distance 100\n" - "3. Distance 130\n" - "4. Distance 160\n" - "5. Distance 190\n" - "6. Distance 220\n" - "7. Distance 250 (Default)\n\n" - "0. Exit" - }; - - // path connections - g_menus[++counter] = { - BOT_MENU_WAYPOINT_PATH, buildKeys (3), - "\\yCreate Path (Choose Direction)\\w\n\n" - "1. Outgoing Path\n" - "2. Incoming Path\n" - "3. Bidirectional (Both Ways)\n\n" - "0. Exit" - }; - const String &empty = ""; - - // kick menus - g_menus[++counter] = { BOT_MENU_KICK_PAGE_1, 0x0, empty, }; - g_menus[++counter] = { BOT_MENU_KICK_PAGE_2, 0x0, empty, }; - g_menus[++counter] = { BOT_MENU_KICK_PAGE_3, 0x0, empty, }; - g_menus[++counter] = { BOT_MENU_KICK_PAGE_4, 0x0, empty, }; -} - -// bot menus -MenuText g_menus[BOT_MENU_TOTAL_MENUS]; \ No newline at end of file diff --git a/source/interface.cpp b/source/interface.cpp index 3df78ed..c62eb30 100644 --- a/source/interface.cpp +++ b/source/interface.cpp @@ -1,4 +1,4 @@ -// +// // Yet Another POD-Bot, based on PODBot by Markus Klinge ("CountFloyd"). // Copyright (c) YaPB Development Team. // @@ -9,2828 +9,71 @@ #include -// console vars -ConVar yb_ignore_cvars_on_changelevel ("yb_ignore_cvars_on_changelevel", "yb_quota,yb_autovacate"); -ConVar yb_password ("yb_password", "", VT_PASSWORD); -ConVar yb_password_key ("yb_password_key", "_ybpw"); -ConVar yb_language ("yb_language", "en"); ConVar yb_version ("yb_version", PRODUCT_VERSION, VT_READONLY); -ConVar mp_startmoney ("mp_startmoney", nullptr, VT_NOREGISTER, true, "800"); - -int handleBotCommands (edict_t *ent, const char *arg0, const char *arg1, const char *arg2, const char *arg3, const char *arg4, const char *arg5, const char *self) { - - // adding one bot with random parameters to random team - if (stricmp (arg0, "addbot") == 0 || stricmp (arg0, "add") == 0) { - bots.addbot (arg4, arg1, arg2, arg3, arg5, true); - } - - // adding one bot with high difficulty parameters to random team - else if (stricmp (arg0, "addbot_hs") == 0 || stricmp (arg0, "addhs") == 0) { - bots.addbot (arg4, "4", "1", arg3, arg5, true); - } - - // adding one bot with random parameters to terrorist team - else if (stricmp (arg0, "addbot_t") == 0 || stricmp (arg0, "add_t") == 0) { - bots.addbot (arg4, arg1, arg2, "1", arg5, true); - } - - // adding one bot with random parameters to counter-terrorist team - else if (stricmp (arg0, "addbot_ct") == 0 || stricmp (arg0, "add_ct") == 0) { - bots.addbot (arg4, arg1, arg2, "2", arg5, true); - } - - // kicking off one bot from the terrorist team - else if (stricmp (arg0, "kickbot_t") == 0 || stricmp (arg0, "kick_t") == 0) { - bots.kickFromTeam (TEAM_TERRORIST); - } - - // kicking off one bot from the counter-terrorist team - else if (stricmp (arg0, "kickbot_ct") == 0 || stricmp (arg0, "kick_ct") == 0) { - bots.kickFromTeam (TEAM_COUNTER); - } - // kills all bots on the terrorist team - else if (stricmp (arg0, "killbots_t") == 0 || stricmp (arg0, "kill_t") == 0) { - bots.killAllBots (TEAM_TERRORIST); - } - - // kills all bots on the counter-terrorist team - else if (stricmp (arg0, "killbots_ct") == 0 || stricmp (arg0, "kill_ct") == 0) { - bots.killAllBots (TEAM_COUNTER); - } - - // list all bots playeing on the server - else if (stricmp (arg0, "listbots") == 0 || stricmp (arg0, "list") == 0) { - bots.listBots (); - } - - // kick off all bots from the played server - else if (stricmp (arg0, "kickbots") == 0 || stricmp (arg0, "kickall") == 0) { - bots.kickEveryone (); - } - - // kill all bots on the played server - else if (stricmp (arg0, "killbots") == 0 || stricmp (arg0, "killall") == 0) { - bots.killAllBots (); - } - - // kick off one random bot from the played server - else if (stricmp (arg0, "kickone") == 0 || stricmp (arg0, "kick") == 0) { - bots.kickRandom (); - } - - // fill played server with bots - else if (stricmp (arg0, "fillserver") == 0 || stricmp (arg0, "fill") == 0) { - bots.serverFill (atoi (arg1), isEmptyStr (arg2) ? -1 : atoi (arg2), isEmptyStr (arg3) ? -1 : atoi (arg3), isEmptyStr (arg4) ? -1 : atoi (arg4)); - } - - // select the weapon mode for bots - else if (stricmp (arg0, "weaponmode") == 0 || stricmp (arg0, "wmode") == 0) { - int selection = atoi (arg1); - - // check is selected range valid - if (selection >= 1 && selection <= 7) - bots.setWeaponMode (selection); - else - engine.clientPrint (ent, "Choose weapon from 1 to 7 range"); - } - - // force all bots to vote to specified map - else if (stricmp (arg0, "votemap") == 0) { - if (!isEmptyStr (arg1)) { - int nominatedMap = atoi (arg1); - - // loop through all players - for (int i = 0; i < engine.maxClients (); i++) { - Bot *bot = bots.getBot (i); - - if (bot != nullptr) - bot->m_voteMap = nominatedMap; - } - engine.clientPrint (ent, "All dead bots will vote for map #%d", nominatedMap); - } - } - - // displays version information - else if (stricmp (arg0, "version") == 0 || stricmp (arg0, "ver") == 0) { - char versionData[] = "------------------------------------------------\n" - "Name: %s\n" - "Version: %s (Build: %u)\n" - "Compiled: %s, %s\n" - "Git Hash: %s\n" - "Git Commit Author: %s\n" - "------------------------------------------------"; - - engine.clientPrint (ent, versionData, PRODUCT_NAME, PRODUCT_VERSION, buildNumber (), __DATE__, __TIME__, PRODUCT_GIT_HASH, PRODUCT_GIT_COMMIT_AUTHOR); - } - - // display some sort of help information - else if (strcmp (arg0, "?") == 0 || strcmp (arg0, "help") == 0) { - engine.clientPrint (ent, "Bot Commands:"); - engine.clientPrint (ent, "%s version\t - display version information.", self); - engine.clientPrint (ent, "%s add\t - create a bot in current game.", self); - engine.clientPrint (ent, "%s fill\t - fill the server with random bots.", self); - engine.clientPrint (ent, "%s kickall\t - disconnects all bots from current game.", self); - engine.clientPrint (ent, "%s killbots\t - kills all bots in current game.", self); - engine.clientPrint (ent, "%s kick\t - disconnect one random bot from game.", self); - engine.clientPrint (ent, "%s weaponmode\t - select bot weapon mode.", self); - engine.clientPrint (ent, "%s votemap\t - allows dead bots to vote for specific map.", self); - engine.clientPrint (ent, "%s cmenu\t - displaying bots command menu.", self); - - if (strcmp (arg1, "full") == 0 || strcmp (arg1, "?") == 0) { - engine.clientPrint (ent, "%s add_t\t - creates one random bot to terrorist team.", self); - engine.clientPrint (ent, "%s add_ct\t - creates one random bot to ct team.", self); - engine.clientPrint (ent, "%s kick_t\t - disconnect one random bot from terrorist team.", self); - engine.clientPrint (ent, "%s kick_ct\t - disconnect one random bot from ct team.", self); - engine.clientPrint (ent, "%s kill_t\t - kills all bots on terrorist team.", self); - engine.clientPrint (ent, "%s kill_ct\t - kills all bots on ct team.", self); - engine.clientPrint (ent, "%s list\t - display list of bots currently playing.", self); - engine.clientPrint (ent, "%s deletewp\t - erase waypoint file from hard disk (permanently).", self); - - if (!engine.isDedicated ()) { - engine.print ("%s autowp\t - toggle autowaypointing.", self); - engine.print ("%s wp\t - toggle waypoint showing.", self); - engine.print ("%s wp on noclip\t - enable noclip cheat", self); - engine.print ("%s wp save nocheck\t - save waypoints without checking.", self); - engine.print ("%s wp add\t - open menu for waypoint creation.", self); - engine.print ("%s wp delete\t - delete waypoint nearest to host edict.", self); - engine.print ("%s wp menu\t - open main waypoint menu.", self); - engine.print ("%s wp addbasic\t - creates basic waypoints on map.", self); - engine.print ("%s wp find\t - show direction to specified waypoint.", self); - engine.print ("%s wp load\t - load the waypoint file from hard disk.", self); - engine.print ("%s wp check\t - checks if all waypoints connections are valid.", self); - engine.print ("%s wp cache\t - cache nearest waypoint.", self); - engine.print ("%s wp teleport\t - teleport hostile to specified waypoint.", self); - engine.print ("%s wp setradius\t - manually sets the wayzone radius for this waypoint.", self); - engine.print ("%s wp clean \t - cleans useless paths for specified or all waypoints.", self); - engine.print ("%s path autodistance - opens menu for setting autopath maximum distance.", self); - engine.print ("%s path cache\t - remember the nearest to player waypoint.", self); - engine.print ("%s path create\t - opens menu for path creation.", self); - engine.print ("%s path delete\t - delete path from cached to nearest waypoint.", self); - engine.print ("%s path create_in\t - creating incoming path connection.", self); - engine.print ("%s path create_out\t - creating outgoing path connection.", self); - engine.print ("%s path create_both\t - creating both-ways path connection.", self); - engine.print ("%s exp save\t - save the experience data.", self); - } - } - } - else if (stricmp (arg0, "bot_takedamage") == 0 && !isEmptyStr (arg1)) { - bool isOn = !!(atoi (arg1) == 1); - - for (int i = 0; i < engine.maxClients (); i++) { - Bot *bot = bots.getBot (i); - - if (bot != nullptr) { - bot->pev->takedamage = isOn ? 0.0f : 1.0f; - } - } - } - - // displays main bot menu - else if (stricmp (arg0, "botmenu") == 0 || stricmp (arg0, "menu") == 0) { - showMenu (ent, BOT_MENU_MAIN); - } - - // display command menu - else if (stricmp (arg0, "cmdmenu") == 0 || stricmp (arg0, "cmenu") == 0) { - if (isAlive (ent)) { - showMenu (ent, BOT_MENU_COMMANDS); - } - else { - showMenu (ent, BOT_MENU_INVALID); // reset menu display - engine.centerPrint ("You're dead, and have no access to this menu"); - } - } - else if (stricmp (arg0, "glp") == 0) { - for (int i = 0; i < waypoints.length (); i++) { - engine.print ("%d - %f - %f", i, waypoints.getLightLevel (i), illum.getSkyColor ()); - } - } - - // waypoint manimupulation (really obsolete, can be edited through menu) (supported only on listen server) - else if (stricmp (arg0, "waypoint") == 0 || stricmp (arg0, "wp") == 0 || stricmp (arg0, "wpt") == 0) { - if (engine.isDedicated () || engine.isNullEntity (g_hostEntity)) { - return 2; - } - - // enables or disable waypoint displaying - if (stricmp (arg1, "on") == 0) { - g_waypointOn = true; - engine.print ("Waypoint Editing Enabled"); - - // enables noclip cheat - if (!isEmptyStr (arg2) && stricmp (arg2, "noclip") == 0) { - if (g_editNoclip) { - g_hostEntity->v.movetype = MOVETYPE_WALK; - engine.print ("Noclip Cheat Disabled"); - - g_editNoclip = false; - } - else { - g_hostEntity->v.movetype = MOVETYPE_NOCLIP; - engine.print ("Noclip Cheat Enabled"); - - g_editNoclip = true; - } - } - engine.execCmd ("yapb wp mdl on"); - } - - // switching waypoint editing off - else if (stricmp (arg1, "off") == 0) { - g_waypointOn = false; - g_editNoclip = false; - g_hostEntity->v.movetype = MOVETYPE_WALK; - - engine.print ("Waypoint Editing Disabled"); - engine.execCmd ("yapb wp mdl off"); - } - - // toggles displaying player models on spawn spots - else if (stricmp (arg1, "mdl") == 0 || stricmp (arg1, "models") == 0) { - - auto toggleDraw = [] (bool draw, const String &stuff) { - edict_t *ent = nullptr; - - while (!engine.isNullEntity (ent = g_engfuncs.pfnFindEntityByString (ent, "classname", stuff.chars ()))) { - if (draw) { - ent->v.effects &= ~EF_NODRAW; - } - else { - ent->v.effects |= EF_NODRAW; - } - } - }; - StringArray entities; - - entities.push ("info_player_start"); - entities.push ("info_player_deathmatch"); - entities.push ("info_vip_start"); - - if (stricmp (arg2, "on") == 0) { - for (auto &type : entities) { - toggleDraw (true, type); - } - - engine.execCmd ("mp_roundtime 9"); // reset round time to maximum - engine.execCmd ("mp_timelimit 0"); // disable the time limit - engine.execCmd ("mp_freezetime 0"); // disable freezetime - } - else if (stricmp (arg2, "off") == 0) { - for (auto &type : entities) { - toggleDraw (false, type); - } - } - } - - // show direction to specified waypoint - else if (stricmp (arg1, "find") == 0) { - waypoints.setSearchIndex (atoi (arg2)); - } - - // opens adding waypoint menu - else if (stricmp (arg1, "add") == 0) { - g_waypointOn = true; // turn waypoints on - showMenu (g_hostEntity, BOT_MENU_WAYPOINT_TYPE); - } - - // creates basic waypoints on the map (ladder/spawn points/goals) - else if (stricmp (arg1, "addbasic") == 0) { - waypoints.addBasic (); - engine.centerPrint ("Basic waypoints was Created"); - } - - // delete nearest to host edict waypoint - else if (stricmp (arg1, "delete") == 0) { - g_waypointOn = true; // turn waypoints on - - if (!isEmptyStr (arg2)) { - waypoints.erase (atoi (arg2)); - } - else { - waypoints.erase (INVALID_WAYPOINT_INDEX); - } - } - - // save waypoint data into file on hard disk - else if (stricmp (arg1, "save") == 0) { - const char *waypointSaveMessage = "Waypoints Saved"; - - if (strcmp (arg2, "nocheck") == 0) { - waypoints.save (); - engine.print (waypointSaveMessage); - } - else if (waypoints.checkNodes ()) { - waypoints.save (); - engine.print (waypointSaveMessage); - } - } - - // remove waypoint and all corresponding files from hard disk - else if (stricmp (arg1, "erase") == 0) { - waypoints.eraseFromDisk (); - } - - // load all waypoints again (overrides all changes, that wasn't saved) - else if (stricmp (arg1, "load") == 0) { - if (waypoints.load ()) - engine.print ("Waypoints loaded"); - } - - // check all nodes for validation - else if (stricmp (arg1, "check") == 0) { - if (waypoints.checkNodes ()) - engine.centerPrint ("Nodes work Fine"); - } - - // opens menu for setting (removing) waypoint flags - else if (stricmp (arg1, "flags") == 0) { - showMenu (g_hostEntity, BOT_MENU_WAYPOINT_FLAG); - } - - // setting waypoint radius - else if (stricmp (arg1, "setradius") == 0) { - waypoints.setRadius (atoi (arg2)); - } - - // remembers nearest waypoint - else if (stricmp (arg1, "cache") == 0) { - waypoints.cachePoint (); - } - - // do a cleanup - else if (stricmp (arg1, "clean") == 0) { - if (!isEmptyStr (arg2)) { - if (stricmp (arg2, "all") == 0) { - int totalCleared = 0; - - for (int i = 0; i < waypoints.length (); i++) { - totalCleared += waypoints.removeUselessConnections (i, true); - } - engine.print ("Done. Processed %d waypoints. %d useless paths cleared.", waypoints.length (), totalCleared); - } - else { - int clearIndex = atoi (arg2), totalCleared = 0; - - if (waypoints.exists (clearIndex)) { - totalCleared += waypoints.removeUselessConnections (clearIndex); - - engine.print ("Done. Waypoint %d had %d useless paths.", clearIndex, totalCleared); - } - } - } - else { - int nearest = waypoints.getEditorNeareset (), totalCleared = 0; - - if (waypoints.exists (nearest)) { - totalCleared += waypoints.removeUselessConnections (nearest); - - engine.print ("Done. Waypoint %d had %d useless paths.", nearest, totalCleared); - } - } - } - - // teleport player to specified waypoint - else if (stricmp (arg1, "teleport") == 0) { - int teleportPoint = atoi (arg2); - - if (waypoints.exists (teleportPoint)) { - Path &path = waypoints[teleportPoint]; - - g_engfuncs.pfnSetOrigin (g_hostEntity, path.origin); - g_waypointOn = true; - - engine.print ("Player '%s' teleported to waypoint #%d (x:%.1f, y:%.1f, z:%.1f)", STRING (g_hostEntity->v.netname), teleportPoint, path.origin.x, path.origin.y, path.origin.z); //-V807 - g_editNoclip = true; - } - } - - // displays waypoint menu - else if (stricmp (arg1, "menu") == 0) { - showMenu (g_hostEntity, BOT_MENU_WAYPOINT_MAIN_PAGE1); - } - - // otherwise display waypoint current status - else { - engine.print ("Waypoints are %s", g_waypointOn == true ? "Enabled" : "Disabled"); - } - } - - // path waypoint editing system (supported only on listen server) - else if (stricmp (arg0, "pathwaypoint") == 0 || stricmp (arg0, "path") == 0 || stricmp (arg0, "pwp") == 0) { - if (engine.isDedicated () || engine.isNullEntity (g_hostEntity)) { - return 2; - } - - // opens path creation menu - if (stricmp (arg1, "create") == 0) { - showMenu (g_hostEntity, BOT_MENU_WAYPOINT_PATH); - } - - // creates incoming path from the cached waypoint - else if (stricmp (arg1, "create_in") == 0) { - waypoints.pathCreate (CONNECTION_INCOMING); - } - - // creates outgoing path from current waypoint - else if (stricmp (arg1, "create_out") == 0) { - waypoints.pathCreate (CONNECTION_OUTGOING); - } - - // creates bidirectional path from cached to current waypoint - else if (stricmp (arg1, "create_both") == 0) { - waypoints.pathCreate (CONNECTION_BOTHWAYS); - } - - // delete special path - else if (stricmp (arg1, "delete") == 0) { - waypoints.erasePath (); - } - - // sets auto path maximum distance - else if (stricmp (arg1, "autodistance") == 0) { - showMenu (g_hostEntity, BOT_MENU_WAYPOINT_AUTOPATH); - } - } - - // automatic waypoint handling (supported only on listen server) - else if (stricmp (arg0, "autowaypoint") == 0 || stricmp (arg0, "autowp") == 0) { - if (engine.isDedicated () || engine.isNullEntity (g_hostEntity)) { - return 2; - } - - // enable autowaypointing - if (stricmp (arg1, "on") == 0) { - g_autoWaypoint = true; - g_waypointOn = true; // turn this on just in case - } - - // disable autowaypointing - else if (stricmp (arg1, "off") == 0) { - g_autoWaypoint = false; - } - - // display status - engine.print ("Auto-Waypoint %s", g_autoWaypoint ? "Enabled" : "Disabled"); - } - - // experience system handling (supported only on listen server) - else if (stricmp (arg0, "experience") == 0 || stricmp (arg0, "exp") == 0) { - if (engine.isDedicated () || engine.isNullEntity (g_hostEntity)) { - return 2; - } - - // write experience table (and visibility table) to hard disk - if (stricmp (arg1, "save") == 0) { - waypoints.saveExperience (); - waypoints.saveVisibility (); - - engine.print ("Experience tab saved"); - } - } - else { - return 0; // command is not handled by bot - } - return 1; // command was handled by bot -} - -void execBotConfigs (bool onlyMain) { - static bool setMemoryPointers = true; - - if (setMemoryPointers) { - MemoryLoader::ref ().setup (g_engfuncs.pfnLoadFileForMe, g_engfuncs.pfnFreeFile); - setMemoryPointers = true; - } - - auto isCommentLine = [] (const char *line) { - char ch = *line; - return ch == '#' || ch == '/' || ch == '\r' || ch == ';' || ch == 0 || ch == ' '; - }; - - MemFile fp; - char lineBuffer[512]; - - // this is does the same as exec of engine, but not overwriting values of cvars spcified in yb_ignore_cvars_on_changelevel - if (onlyMain) { - static bool firstLoad = true; - - auto needsToIgnoreVar = [](StringArray &list, const char *needle) { - for (auto &var : list) { - if (var == needle) { - return true; - } - } - return false; - }; - - if (openConfig ("yapb.cfg", "YaPB main config file is not found.", &fp, false)) { - while (fp.gets (lineBuffer, 255)) { - if (isCommentLine (lineBuffer)) { - continue; - } - if (firstLoad) { - engine.execCmd (lineBuffer); - continue; - } - auto keyval = String (lineBuffer).split (" "); - - if (keyval.length () > 1) { - auto ignore = String (yb_ignore_cvars_on_changelevel.str ()).split (","); - - auto key = keyval[0].trim ().chars (); - auto cvar = g_engfuncs.pfnCVarGetPointer (key); - - if (cvar != nullptr) { - auto value = const_cast (keyval[1].trim ().trim ("\"").trim ().chars ()); - - if (needsToIgnoreVar (ignore, key) && !!stricmp (value, cvar->string)) { - engine.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 - g_engfuncs.pfnCvar_DirectSet (cvar, cvar->string); - } - else { - g_engfuncs.pfnCvar_DirectSet (cvar, value); - } - } - else - engine.execCmd (lineBuffer); - } - } - fp.close (); - } - firstLoad = false; - return; - } - KeywordFactory replies; - - // reserve some space for chat - g_chatFactory.reserve (CHAT_TOTAL); - g_chatterFactory.reserve (CHATTER_MAX); - g_botNames.reserve (CHATTER_MAX); - - // NAMING SYSTEM INITIALIZATION - if (openConfig ("names.cfg", "Name configuration file not found.", &fp, true)) { - g_botNames.clear (); - - while (fp.gets (lineBuffer, 255)) { - if (isCommentLine (lineBuffer)) { - continue; - } - StringArray pair = String (lineBuffer).split ("\t\t"); - - if (pair.length () > 1) { - strncpy (lineBuffer, pair[0].trim ().chars (), cr::bufsize (lineBuffer)); - } - - String::trimChars (lineBuffer); - lineBuffer[32] = 0; - - BotName item; - item.name = lineBuffer; - item.usedBy = 0; - - if (pair.length () > 1) { - item.steamId = pair[1].trim (); - } - g_botNames.push (cr::move (item)); - } - fp.close (); - } - - // CHAT SYSTEM CONFIG INITIALIZATION - if (openConfig ("chat.cfg", "Chat file not found.", &fp, true)) { - - int chatType = -1; - - while (fp.gets (lineBuffer, 255)) { - if (isCommentLine (lineBuffer)) { - continue; - } - String section (lineBuffer, strlen (lineBuffer) - 1); - section.trim (); - - if (section == "[KILLED]") { - chatType = 0; - continue; - } - else if (section == "[BOMBPLANT]") { - chatType = 1; - continue; - } - else if (section == "[DEADCHAT]") { - chatType = 2; - continue; - } - else if (section == "[REPLIES]") { - chatType = 3; - continue; - } - else if (section == "[UNKNOWN]") { - chatType = 4; - continue; - } - else if (section == "[TEAMATTACK]") { - chatType = 5; - continue; - } - else if (section == "[WELCOME]") { - chatType = 6; - continue; - } - else if (section == "[TEAMKILL]") { - chatType = 7; - continue; - } - - if (chatType != 3) { - lineBuffer[79] = 0; - } - - switch (chatType) { - case 0: - g_chatFactory[CHAT_KILLING].push (lineBuffer); - break; - - case 1: - g_chatFactory[CHAT_BOMBPLANT].push (lineBuffer); - break; - - case 2: - g_chatFactory[CHAT_DEAD].push (lineBuffer); - break; - - case 3: - if (strstr (lineBuffer, "@KEY") != nullptr) { - if (!replies.keywords.empty () && !replies.replies.empty ()) { - g_replyFactory.push (cr::forward (replies)); - replies.replies.clear (); - } - - replies.keywords.clear (); - replies.keywords = String (&lineBuffer[4]).split (","); - - for (auto &keywords : replies.keywords) { - keywords.trim ().trim ("\""); - } - } - else if (!replies.keywords.empty ()) { - replies.replies.push (lineBuffer); - } - break; - - case 4: - g_chatFactory[CHAT_NOKW].push (lineBuffer); - break; - - case 5: - g_chatFactory[CHAT_TEAMATTACK].push (lineBuffer); - break; - - case 6: - g_chatFactory[CHAT_WELCOME].push (lineBuffer); - break; - - case 7: - g_chatFactory[CHAT_TEAMKILL].push (lineBuffer); - break; - } - } - fp.close (); - } - else { - extern ConVar yb_chat; - yb_chat.set (0); - } - - // GENERAL DATA INITIALIZATION - if (openConfig ("general.cfg", "General configuration file not found. Loading defaults", &fp)) { - while (fp.gets (lineBuffer, 255)) { - if (isCommentLine (lineBuffer)) { - continue; - } - auto pair = String (lineBuffer).split ("="); - - if (pair.length () != 2) { - continue; - } - - for (auto &trim : pair) { - trim.trim (); - } - auto splitted = pair[1].split (","); - - if (pair[0] == "MapStandard") { - if (splitted.length () != NUM_WEAPONS) { - logEntry (true, LL_FATAL, "%s entry in general config is not valid.", pair[0].chars ()); - } - - for (int i = 0; i < NUM_WEAPONS; i++) { - g_weaponSelect[i].teamStandard = splitted[i].toInt32 (); - } - } - else if (pair[0] == "MapAS") { - if (splitted.length () != NUM_WEAPONS) { - logEntry (true, LL_FATAL, "%s entry in general config is not valid.", pair[0].chars ()); - } - - for (int i = 0; i < NUM_WEAPONS; i++) { - g_weaponSelect[i].teamAS = splitted[i].toInt32 (); - } - } - else if (pair[0] == "GrenadePercent") { - if (splitted.length () != 3) { - logEntry (true, LL_FATAL, "%s entry in general config is not valid.", pair[0].chars ()); - } - - for (int i = 0; i < 3; i++) { - g_grenadeBuyPrecent[i] = splitted[i].toInt32 (); - } - } - else if (pair[0] == "Economics") { - if (splitted.length () != 11) { - logEntry (true, LL_FATAL, "%s entry in general config is not valid.", pair[0].chars ()); - } - - for (int i = 0; i < 11; i++) { - g_botBuyEconomyTable[i] = splitted[i].toInt32 (); - } - } - else if (pair[0] == "PersonalityNormal") { - if (splitted.length () != NUM_WEAPONS) { - logEntry (true, LL_FATAL, "%s entry in general config is not valid.", pair[0].chars ()); - } - - for (int i = 0; i < NUM_WEAPONS; i++) { - g_normalWeaponPrefs[i] = splitted[i].toInt32 (); - } - } - else if (pair[0] == "PersonalityRusher") { - if (splitted.length () != NUM_WEAPONS) { - logEntry (true, LL_FATAL, "%s entry in general config is not valid.", pair[0].chars ()); - } - - for (int i = 0; i < NUM_WEAPONS; i++) { - g_rusherWeaponPrefs[i] = splitted[i].toInt32 (); - } - } - else if (pair[0] == "PersonalityCareful") { - if (splitted.length () != NUM_WEAPONS) { - logEntry (true, LL_FATAL, "%s entry in general config is not valid.", pair[0].chars ()); - } - - for (int i = 0; i < NUM_WEAPONS; i++) { - g_carefulWeaponPrefs[i] = splitted[i].toInt32 (); - } - } - } - fp.close (); - } - - // CHATTER SYSTEM INITIALIZATION - if ((g_gameFlags & GAME_SUPPORT_BOT_VOICE) && yb_communication_type.integer () == 2 && openConfig ("chatter.cfg", "Couldn't open chatter system configuration", &fp)) { - struct EventMap { - const char *str; - int code; - float repeat; - } chatterEventMap[] = { - { "Radio_CoverMe", RADIO_COVER_ME, MAX_CHATTER_REPEAT }, - { "Radio_YouTakePoint", RADIO_YOU_TAKE_THE_POINT, MAX_CHATTER_REPEAT }, - { "Radio_HoldPosition", RADIO_HOLD_THIS_POSITION, 10.0f }, - { "Radio_RegroupTeam", RADIO_REGROUP_TEAM, 10.0f }, - { "Radio_FollowMe", RADIO_FOLLOW_ME, 15.0f }, - { "Radio_TakingFire", RADIO_TAKING_FIRE, 5.0f }, - { "Radio_GoGoGo", RADIO_GO_GO_GO, MAX_CHATTER_REPEAT }, - { "Radio_Fallback", RADIO_TEAM_FALLBACK, MAX_CHATTER_REPEAT }, - { "Radio_StickTogether", RADIO_STICK_TOGETHER_TEAM, MAX_CHATTER_REPEAT }, - { "Radio_GetInPosition", RADIO_GET_IN_POSITION, MAX_CHATTER_REPEAT }, - { "Radio_StormTheFront", RADIO_STORM_THE_FRONT, MAX_CHATTER_REPEAT }, - { "Radio_ReportTeam", RADIO_REPORT_TEAM, MAX_CHATTER_REPEAT }, - { "Radio_Affirmative", RADIO_AFFIRMATIVE, MAX_CHATTER_REPEAT }, - { "Radio_EnemySpotted", RADIO_ENEMY_SPOTTED, 4.0f }, - { "Radio_NeedBackup", RADIO_NEED_BACKUP, MAX_CHATTER_REPEAT }, - { "Radio_SectorClear", RADIO_SECTOR_CLEAR, 10.0f }, - { "Radio_InPosition", RADIO_IN_POSITION, 10.0f }, - { "Radio_ReportingIn", RADIO_REPORTING_IN, MAX_CHATTER_REPEAT }, - { "Radio_ShesGonnaBlow", RADIO_SHES_GONNA_BLOW, MAX_CHATTER_REPEAT }, - { "Radio_Negative", RADIO_NEGATIVE, MAX_CHATTER_REPEAT }, - { "Radio_EnemyDown", RADIO_ENEMY_DOWN, 10.0f }, - { "Chatter_DiePain", CHATTER_PAIN_DIED, MAX_CHATTER_REPEAT }, - { "Chatter_GoingToPlantBomb", CHATTER_GOING_TO_PLANT_BOMB, 5.0f }, - { "Chatter_GoingToGuardVIPSafety", CHATTER_GOING_TO_GUARD_VIP_SAFETY, MAX_CHATTER_REPEAT }, - { "Chatter_RescuingHostages", CHATTER_RESCUING_HOSTAGES, MAX_CHATTER_REPEAT }, - { "Chatter_TeamKill", CHATTER_TEAM_ATTACK, MAX_CHATTER_REPEAT }, - { "Chatter_GuardingVipSafety", CHATTER_GUARDING_VIP_SAFETY, MAX_CHATTER_REPEAT }, - { "Chatter_PlantingC4", CHATTER_PLANTING_BOMB, 10.0f }, - { "Chatter_InCombat", CHATTER_IN_COMBAT, MAX_CHATTER_REPEAT }, - { "Chatter_SeeksEnemy", CHATTER_SEEK_ENEMY, MAX_CHATTER_REPEAT }, - { "Chatter_Nothing", CHATTER_NOTHING, MAX_CHATTER_REPEAT }, - { "Chatter_EnemyDown", CHATTER_ENEMY_DOWN, 10.0f }, - { "Chatter_UseHostage", CHATTER_USING_HOSTAGES, MAX_CHATTER_REPEAT }, - { "Chatter_WonTheRound", CHATTER_WON_THE_ROUND, MAX_CHATTER_REPEAT }, - { "Chatter_QuicklyWonTheRound", CHATTER_QUICK_WON_ROUND, MAX_CHATTER_REPEAT }, - { "Chatter_NoEnemiesLeft", CHATTER_NO_ENEMIES_LEFT, MAX_CHATTER_REPEAT }, - { "Chatter_FoundBombPlace", CHATTER_FOUND_BOMB_PLACE, 15.0f }, - { "Chatter_WhereIsTheBomb", CHATTER_WHERE_IS_THE_BOMB, MAX_CHATTER_REPEAT }, - { "Chatter_DefendingBombSite", CHATTER_DEFENDING_BOMBSITE, MAX_CHATTER_REPEAT }, - { "Chatter_BarelyDefused", CHATTER_BARELY_DEFUSED, MAX_CHATTER_REPEAT }, - { "Chatter_NiceshotCommander", CHATTER_NICESHOT_COMMANDER, MAX_CHATTER_REPEAT }, - { "Chatter_ReportingIn", CHATTER_REPORTING_IN, 10.0f }, - { "Chatter_SpotTheBomber", CHATTER_SPOT_THE_BOMBER, 4.3f }, - { "Chatter_VIPSpotted", CHATTER_VIP_SPOTTED, 5.3f }, - { "Chatter_FriendlyFire", CHATTER_FRIENDLY_FIRE, 2.1f }, - { "Chatter_GotBlinded", CHATTER_BLINDED, 5.0f }, - { "Chatter_GuardDroppedC4", CHATTER_GUARDING_DROPPED_BOMB, 3.0f }, - { "Chatter_DefusingC4", CHATTER_DEFUSING_BOMB, 3.0f }, - { "Chatter_FoundC4", CHATTER_FOUND_BOMB, 5.5f }, - { "Chatter_ScaredEmotion", CHATTER_SCARED_EMOTE, 6.1f }, - { "Chatter_HeardEnemy", CHATTER_SCARED_EMOTE, 12.8f }, - { "Chatter_SniperWarning", CHATTER_SNIPER_WARNING, 14.3f }, - { "Chatter_SniperKilled", CHATTER_SNIPER_KILLED, 12.1f }, - { "Chatter_OneEnemyLeft", CHATTER_ONE_ENEMY_LEFT, 12.5f }, - { "Chatter_TwoEnemiesLeft", CHATTER_TWO_ENEMIES_LEFT, 12.5f }, - { "Chatter_ThreeEnemiesLeft", CHATTER_THREE_ENEMIES_LEFT, 12.5f }, - { "Chatter_NiceshotPall", CHATTER_NICESHOT_PALL, 2.0f }, - { "Chatter_GoingToGuardHostages", CHATTER_GOING_TO_GUARD_HOSTAGES, 3.0f }, - { "Chatter_GoingToGuardDoppedBomb", CHATTER_GOING_TO_GUARD_DROPPED_BOMB, 6.0f }, - { "Chatter_OnMyWay", CHATTER_ON_MY_WAY, 1.5f }, - { "Chatter_LeadOnSir", CHATTER_LEAD_ON_SIR, 5.0f }, - { "Chatter_Pinned_Down", CHATTER_PINNED_DOWN, 5.0f }, - { "Chatter_GottaFindTheBomb", CHATTER_GOTTA_FIND_BOMB, 3.0f }, - { "Chatter_You_Heard_The_Man", CHATTER_YOU_HEARD_THE_MAN, 3.0f }, - { "Chatter_Lost_The_Commander", CHATTER_LOST_COMMANDER, 4.5f }, - { "Chatter_NewRound", CHATTER_NEW_ROUND, 3.5f }, - { "Chatter_CoverMe", CHATTER_COVER_ME, 3.5f }, - { "Chatter_BehindSmoke", CHATTER_BEHIND_SMOKE, 3.5f }, - { "Chatter_BombSiteSecured", CHATTER_BOMB_SITE_SECURED, 3.5f }, - { "Chatter_GoingToCamp", CHATTER_GOING_TO_CAMP, 25.0f }, - { "Chatter_Camp", CHATTER_CAMP, 25.0f }, - }; - - while (fp.gets (lineBuffer, 511)) { - if (isCommentLine (lineBuffer)) { - continue; - } - - if (strncmp (lineBuffer, "RewritePath", 11) == 0) { - extern ConVar yb_chatter_path; - yb_chatter_path.set (String (&lineBuffer[12]).trim ().chars ()); - } - else if (strncmp (lineBuffer, "Event", 5) == 0) { - auto items = String (&lineBuffer[6]).split ("="); - - if (items.length () != 2) { - logEntry (true, LL_ERROR, "Error in chatter config file syntax... Please correct all errors."); - continue; - } - - for (auto &item : items) { - item.trim (); - } - items[1].trim ("(;)"); - - for (size_t i = 0; i < cr::arrsize (chatterEventMap); i++) { - auto event = &chatterEventMap[i]; - - if (stricmp (event->str, items[0].chars ()) == 0) { - // this does common work of parsing comma-separated chatter line - auto sounds = items[1].split (","); - - for (auto &sound : sounds) { - sound.trim ().trim ("\""); - float duration = engine.getWaveLen (sound.chars ()); - - if (duration > 0.0f) { - g_chatterFactory[event->code].push ({ sound, event->repeat, duration }); - } - } - sounds.clear (); - } - } - } - } - fp.close (); - } - else { - yb_communication_type.set (1); - logEntry (true, LL_DEFAULT, "Chatter Communication disabled."); - } - - // LOCALIZER INITITALIZATION - if (!(g_gameFlags & GAME_LEGACY) && openConfig ("lang.cfg", "Specified language not found", &fp, true)) { - if (engine.isDedicated ()) { - return; // dedicated server will use only english translation - } - enum Lang { LANG_ORIGINAL, LANG_TRANSLATED, LANG_UNDEFINED } langState = static_cast (LANG_UNDEFINED); - - char buffer[MAX_PRINT_BUFFER] = { 0, }; - Pair lang; - - while (fp.gets (lineBuffer, 255)) { - if (isCommentLine (lineBuffer)) { - continue; - } - - if (strncmp (lineBuffer, "[ORIGINAL]", 10) == 0) { - langState = LANG_ORIGINAL; - - if (buffer[0] != 0) { - lang.second = buffer; - lang.second.trim (); - - buffer[0] = 0; - } - - if (!lang.second.empty () && !lang.first.empty ()) { - engine.addTranslation (lang.first, lang.second); - } - } - else if (strncmp (lineBuffer, "[TRANSLATED]", 12) == 0) { - - lang.first = buffer; - lang.first.trim (); - - langState = LANG_TRANSLATED; - buffer[0] = 0; - } - else { - switch (langState) { - case LANG_ORIGINAL: - strncat (buffer, lineBuffer, MAX_PRINT_BUFFER - 1 - strlen (buffer)); - break; - - case LANG_TRANSLATED: - strncat (buffer, lineBuffer, MAX_PRINT_BUFFER - 1 - strlen (buffer)); - break; - - case LANG_UNDEFINED: - break; - } - } - } - fp.close (); - } - else if (g_gameFlags & GAME_LEGACY) { - logEntry (true, LL_DEFAULT, "Multilingual system disabled, due to your Counter-Strike Version!"); - } - else if (strcmp (yb_language.str (), "en") != 0) { - logEntry (true, LL_ERROR, "Couldn't load language configuration"); - } - - // set personality weapon pointers here - g_weaponPrefs[PERSONALITY_NORMAL] = reinterpret_cast (&g_normalWeaponPrefs); - g_weaponPrefs[PERSONALITY_RUSHER] = reinterpret_cast (&g_rusherWeaponPrefs); - g_weaponPrefs[PERSONALITY_CAREFUL] = reinterpret_cast (&g_carefulWeaponPrefs); - - g_timePerSecondUpdate = 0.0f; -} - -void GameDLLInit (void) { - // this function is a one-time call, and appears to be the second function called in the - // DLL after GiveFntprsToDll() has been called. Its purpose is to tell the MOD DLL to - // initialize the game before the engine actually hooks into it with its video frames and - // clients connecting. Note that it is a different step than the *server* initialization. - // This one is called once, and only once, when the game process boots up before the first - // server is enabled. Here is a good place to do our own game session initialization, and - // to register by the engine side the server commands we need to administrate our bots. - - auto commandHandler = [](void) { - if (handleBotCommands (g_hostEntity, isEmptyStr (g_engfuncs.pfnCmd_Argv (1)) ? "help" : g_engfuncs.pfnCmd_Argv (1), g_engfuncs.pfnCmd_Argv (2), g_engfuncs.pfnCmd_Argv (3), g_engfuncs.pfnCmd_Argv (4), g_engfuncs.pfnCmd_Argv (5), g_engfuncs.pfnCmd_Argv (6), g_engfuncs.pfnCmd_Argv (0)) == 0) { - engine.print ("Unknown command: %s", g_engfuncs.pfnCmd_Argv (1)); - } - }; - - // register server command(s) - engine.registerCmd ("yapb", commandHandler); - engine.registerCmd ("yb", commandHandler); - - // set correct version string - yb_version.set (format ("%d.%d.%d", PRODUCT_VERSION_DWORD_INTERNAL, buildNumber ())); - - // execute main config - execBotConfigs (true); - - // register fake metamod command handler if we not! under mm - if (!(g_gameFlags & GAME_METAMOD)) { - engine.registerCmd ("meta", [](void) { engine.print ("You're launched standalone version of yapb. Metamod is not installed or not enabled!"); }); - } - - // elite price is 1000$ on older versions of cs... - if (g_gameFlags & GAME_LEGACY) { - for (int i = 0; i < NUM_WEAPONS; i++) { - auto &weapon = g_weaponSelect[i]; - - if (weapon.id == WEAPON_ELITE) { - weapon.price = 1000; - break; - } - } - } - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_IGNORED); - } - g_functionTable.pfnGameInit (); -} - -void Touch (edict_t *pentTouched, edict_t *pentOther) { - // this function is called when two entities' bounding boxes enter in collision. For example, - // when a player walks upon a gun, the player entity bounding box collides to the gun entity - // bounding box, and the result is that this function is called. It is used by the game for - // taking the appropriate action when such an event occurs (in our example, the player who - // is walking upon the gun will "pick it up"). Entities that "touch" others are usually - // entities having a velocity, as it is assumed that static entities (entities that don't - // move) will never touch anything. Hence, in our example, the pentTouched will be the gun - // (static entity), whereas the pentOther will be the player (as it is the one moving). When - // the two entities both have velocities, for example two players colliding, this function - // is called twice, once for each entity moving. - - if (!engine.isNullEntity (pentTouched) && (pentTouched->v.flags & FL_FAKECLIENT) && pentOther != engine.getStartEntity ()) { - Bot *bot = bots.getBot (pentTouched); - - if (bot != nullptr && pentOther != bot->ent ()) { - - if (isPlayer (pentOther) && isAlive (pentOther)) { - bot->avoidIncomingPlayers (pentOther); - } - else { - bot->processBreakables (pentOther); - } - } - } - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_IGNORED); - } - g_functionTable.pfnTouch (pentTouched, pentOther); -} - -int Spawn (edict_t *ent) { - // this function asks the game DLL to spawn (i.e, give a physical existence in the virtual - // world, in other words to 'display') the entity pointed to by ent in the game. The - // Spawn() function is one of the functions any entity is supposed to have in the game DLL, - // and any MOD is supposed to implement one for each of its entities. - - engine.precache (); - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META_VALUE (MRES_IGNORED, 0); - } - int result = g_functionTable.pfnSpawn (ent); // get result - - if (ent->v.rendermode == kRenderTransTexture) { - ent->v.flags &= ~FL_WORLDBRUSH; // clear the FL_WORLDBRUSH flag out of transparent ents - } - return result; -} - -void UpdateClientData (const struct edict_s *ent, int sendweapons, struct clientdata_s *cd) { - extern ConVar yb_latency_display; - - if ((g_gameFlags & GAME_SUPPORT_SVC_PINGS) && yb_latency_display.integer () == 2) { - bots.sendPingOffsets (const_cast (ent)); - } - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_IGNORED); - } - g_functionTable.pfnUpdateClientData (ent, sendweapons, cd); -} - -int ClientConnect (edict_t *ent, const char *name, const char *addr, char rejectReason[128]) { - // this function is called in order to tell the MOD DLL that a client attempts to connect the - // game. The entity pointer of this client is ent, the name under which he connects is - // pointed to by the pszName pointer, and its IP address string is pointed by the pszAddress - // one. Note that this does not mean this client will actually join the game ; he could as - // well be refused connection by the server later, because of latency timeout, unavailable - // game resources, or whatever reason. In which case the reason why the game DLL (read well, - // the game DLL, *NOT* the engine) refuses this player to connect will be printed in the - // rejectReason string in all letters. Understand that a client connecting process is done - // in three steps. First, the client requests a connection from the server. This is engine - // internals. When there are already too many players, the engine will refuse this client to - // connect, and the game DLL won't even notice. Second, if the engine sees no problem, the - // game DLL is asked. This is where we are. Once the game DLL acknowledges the connection, - // the client downloads the resources it needs and synchronizes its local engine with the one - // of the server. And then, the third step, which comes *AFTER* ClientConnect (), is when the - // client officially enters the game, through the ClientPutInServer () function, later below. - // Here we hook this function in order to keep track of the listen server client entity, - // because a listen server client always connects with a "loopback" address string. Also we - // tell the bot manager to check the bot population, in order to always have one free slot on - // the server for incoming clients. - - // check if this client is the listen server client - if (strcmp (addr, "loopback") == 0) { - g_hostEntity = ent; // save the edict of the listen server client... - } - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META_VALUE (MRES_IGNORED, 0); - } - return g_functionTable.pfnClientConnect (ent, name, addr, rejectReason); -} - -void ClientDisconnect (edict_t *ent) { - // this function is called whenever a client is VOLUNTARILY disconnected from the server, - // either because the client dropped the connection, or because the server dropped him from - // the game (latency timeout). The effect is the freeing of a client slot on the server. Note - // that clients and bots disconnected because of a level change NOT NECESSARILY call this - // function, because in case of a level change, it's a server shutdown, and not a normal - // disconnection. I find that completely stupid, but that's it. Anyway it's time to update - // the bots and players counts, and in case the client disconnecting is a bot, to back its - // brain(s) up to disk. We also try to notice when a listenserver client disconnects, so as - // to reset his entity pointer for safety. There are still a few server frames to go once a - // listen server client disconnects, and we don't want to send him any sort of message then. - - int index = engine.indexOfEntity (ent) - 1; - - if (index >= 0 && index < MAX_ENGINE_PLAYERS) { - auto bot = bots.getBot (index); - - // check if its a bot - if (bot != nullptr && bot->pev == &ent->v) { - bot->showChaterIcon (false); - bots.destroy (index); - } - } - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_IGNORED); - } - g_functionTable.pfnClientDisconnect (ent); -} - -void ClientUserInfoChanged (edict_t *ent, char *infobuffer) { - // this function is called when a player changes model, or changes team. Occasionally it - // enforces rules on these changes (for example, some MODs don't want to allow players to - // change their player model). But most commonly, this function is in charge of handling - // team changes, recounting the teams population, etc... - - if (engine.isDedicated () && !isFakeClient (ent)) { - const String &key = yb_password_key.str (); - const String &password = yb_password.str (); - - if (!key.empty () && !password.empty ()) { - int clientIndex = engine.indexOfEntity (ent) - 1; - - if (password == g_engfuncs.pfnInfoKeyValue (infobuffer, key.chars ())) { - g_clients[clientIndex].flags |= CF_ADMIN; - } - else { - g_clients[clientIndex].flags &= ~CF_ADMIN; - } - } - } - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_IGNORED); - } - g_functionTable.pfnClientUserInfoChanged (ent, infobuffer); -} - -void ClientCommand (edict_t *ent) { - // this function is called whenever the client whose player entity is ent issues a client - // command. How it works is that clients all have a global string in their client DLL that - // stores the command string; if ever that string is filled with characters, the client DLL - // sends it to the engine as a command to be executed. When the engine has executed that - // command, that string is reset to zero. By the server side, we can access this string - // by asking the engine with the CmdArgv(), CmdArgs() and CmdArgc() functions that work just - // like executable files argument processing work in C (argc gets the number of arguments, - // command included, args returns the whole string, and argv returns the wanted argument - // only). Here is a good place to set up either bot debug commands the listen server client - // could type in his game console, or real new client commands, but we wouldn't want to do - // so as this is just a bot DLL, not a MOD. The purpose is not to add functionality to - // clients. Hence it can lack of commenting a bit, since this code is very subject to change. - - const char *command = g_engfuncs.pfnCmd_Argv (0); - const char *arg1 = g_engfuncs.pfnCmd_Argv (1); - - static int fillServerTeam = 5; - static bool fillCommand = false; - - int issuerPlayerIndex = engine.indexOfEntity (ent) - 1; - - if (!engine.isBotCmd () && (ent == g_hostEntity || (g_clients[issuerPlayerIndex].flags & CF_ADMIN))) { - if (stricmp (command, "yapb") == 0 || stricmp (command, "yb") == 0) { - int state = handleBotCommands (ent, isEmptyStr (arg1) ? "help" : arg1, g_engfuncs.pfnCmd_Argv (2), g_engfuncs.pfnCmd_Argv (3), g_engfuncs.pfnCmd_Argv (4), g_engfuncs.pfnCmd_Argv (5), g_engfuncs.pfnCmd_Argv (6), g_engfuncs.pfnCmd_Argv (0)); - - switch (state) { - case 0: - engine.clientPrint (ent, "Unknown command: %s", arg1); - break; - - case 2: - engine.clientPrint (ent, "Command \"%s\", can only be executed from server console.", arg1); - break; - } - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_SUPERCEDE); - } - return; - } - else if (stricmp (command, "menuselect") == 0 && !isEmptyStr (arg1) && g_clients[issuerPlayerIndex].menu != BOT_MENU_INVALID) { - Client *client = &g_clients[issuerPlayerIndex]; - int selection = atoi (arg1); - - if (client->menu == BOT_MENU_WAYPOINT_TYPE) { - showMenu (ent, BOT_MENU_INVALID); // reset menu display - - switch (selection) { - case 1: - case 2: - case 3: - case 4: - case 5: - case 6: - case 7: - waypoints.push (selection - 1); - showMenu (ent, BOT_MENU_WAYPOINT_TYPE); - - break; - - case 8: - waypoints.push (100); - showMenu (ent, BOT_MENU_WAYPOINT_TYPE); - - break; - - case 9: - waypoints.startLearnJump (); - showMenu (ent, BOT_MENU_WAYPOINT_TYPE); - - break; - - case 10: - showMenu (ent, BOT_MENU_INVALID); - break; - } - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_SUPERCEDE); - } - return; - } - else if (client->menu == BOT_MENU_WAYPOINT_FLAG) { - showMenu (ent, BOT_MENU_INVALID); // reset menu display - - switch (selection) { - case 1: - waypoints.toggleFlags (FLAG_NOHOSTAGE); - showMenu (ent, BOT_MENU_WAYPOINT_FLAG); - break; - - case 2: - waypoints.toggleFlags (FLAG_TF_ONLY); - showMenu (ent, BOT_MENU_WAYPOINT_FLAG); - break; - - case 3: - waypoints.toggleFlags (FLAG_CF_ONLY); - showMenu (ent, BOT_MENU_WAYPOINT_FLAG); - break; - - case 4: - waypoints.toggleFlags (FLAG_LIFT); - showMenu (ent, BOT_MENU_WAYPOINT_FLAG); - break; - - case 5: - waypoints.toggleFlags (FLAG_SNIPER); - showMenu (ent, BOT_MENU_WAYPOINT_FLAG); - break; - } - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_SUPERCEDE); - } - return; - } - else if (client->menu == BOT_MENU_WAYPOINT_MAIN_PAGE1) { - showMenu (ent, BOT_MENU_INVALID); // reset menu display - - switch (selection) { - case 1: - if (g_waypointOn) - engine.execCmd ("yapb waypoint off"); - else - engine.execCmd ("yapb waypoint on"); - - showMenu (ent, BOT_MENU_WAYPOINT_MAIN_PAGE1); - break; - - case 2: - g_waypointOn = true; - waypoints.cachePoint (); - showMenu (ent, BOT_MENU_WAYPOINT_MAIN_PAGE1); - - break; - - case 3: - g_waypointOn = true; - showMenu (ent, BOT_MENU_WAYPOINT_PATH); - break; - - case 4: - g_waypointOn = true; - waypoints.erasePath (); - showMenu (ent, BOT_MENU_WAYPOINT_MAIN_PAGE1); - - break; - - case 5: - g_waypointOn = true; - showMenu (ent, BOT_MENU_WAYPOINT_TYPE); - break; - - case 6: - g_waypointOn = true; - waypoints.erase (INVALID_WAYPOINT_INDEX); - showMenu (ent, BOT_MENU_WAYPOINT_MAIN_PAGE1); - - break; - - case 7: - g_waypointOn = true; - showMenu (ent, BOT_MENU_WAYPOINT_AUTOPATH); - break; - - case 8: - g_waypointOn = true; - showMenu (ent, BOT_MENU_WAYPOINT_RADIUS); - break; - - case 9: - showMenu (ent, BOT_MENU_WAYPOINT_MAIN_PAGE2); - break; - - case 10: - showMenu (ent, BOT_MENU_INVALID); - break; - } - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_SUPERCEDE); - } - return; - } - else if (client->menu == BOT_MENU_WAYPOINT_MAIN_PAGE2) { - showMenu (ent, BOT_MENU_INVALID); // reset menu display - - switch (selection) { - case 1: { - int terrPoints = 0; - int ctPoints = 0; - int goalPoints = 0; - int rescuePoints = 0; - int campPoints = 0; - int sniperPoints = 0; - int noHostagePoints = 0; - - for (int i = 0; i < waypoints.length (); i++) { - Path &path = waypoints[i]; - - if (path.flags & FLAG_TF_ONLY) { - terrPoints++; - } - - if (path.flags & FLAG_CF_ONLY) { - ctPoints++; - } - - if (path.flags & FLAG_GOAL) { - goalPoints++; - } - - if (path.flags & FLAG_RESCUE) { - rescuePoints++; - } - - if (path.flags & FLAG_CAMP) { - campPoints++; - } - - if (path.flags & FLAG_SNIPER) { - sniperPoints++; - } - - if (path.flags & FLAG_NOHOSTAGE) { - noHostagePoints++; - } - } - engine.print ("Waypoints: %d - T Points: %d\n" - "CT Points: %d - Goal Points: %d\n" - "Rescue Points: %d - Camp Points: %d\n" - "Block Hostage Points: %d - Sniper Points: %d\n", - waypoints.length (), terrPoints, ctPoints, goalPoints, rescuePoints, campPoints, noHostagePoints, sniperPoints); - - showMenu (ent, BOT_MENU_WAYPOINT_MAIN_PAGE2); - } break; - - case 2: - g_waypointOn = true; - g_autoWaypoint &= 1; - g_autoWaypoint ^= 1; - - engine.centerPrint ("Auto-Waypoint %s", g_autoWaypoint ? "Enabled" : "Disabled"); - showMenu (ent, BOT_MENU_WAYPOINT_MAIN_PAGE2); - - break; - - case 3: - g_waypointOn = true; - showMenu (ent, BOT_MENU_WAYPOINT_FLAG); - break; - - case 4: - if (waypoints.checkNodes ()) { - waypoints.save (); - } - else { - engine.centerPrint ("Waypoint not saved\nThere are errors, see console"); - } - showMenu (ent, BOT_MENU_WAYPOINT_MAIN_PAGE2); - break; - - case 5: - waypoints.save (); - showMenu (ent, BOT_MENU_WAYPOINT_MAIN_PAGE2); - break; - - case 6: - waypoints.load (); - showMenu (ent, BOT_MENU_WAYPOINT_MAIN_PAGE2); - break; - - case 7: - if (waypoints.checkNodes ()) { - engine.centerPrint ("Nodes works fine"); - } - else { - engine.centerPrint ("There are errors, see console"); - } - showMenu (ent, BOT_MENU_WAYPOINT_MAIN_PAGE2); - break; - - case 8: - engine.execCmd ("yapb wp on noclip"); - showMenu (ent, BOT_MENU_WAYPOINT_MAIN_PAGE2); - break; - - case 9: - showMenu (ent, BOT_MENU_WAYPOINT_MAIN_PAGE1); - break; - } - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_SUPERCEDE); - } - return; - } - else if (client->menu == BOT_MENU_WAYPOINT_RADIUS) { - showMenu (ent, BOT_MENU_INVALID); // reset menu display - - g_waypointOn = true; // turn waypoints on in case - - const int radiusValue[] = {0, 8, 16, 32, 48, 64, 80, 96, 128}; - - if (selection >= 1 && selection <= 9) { - waypoints.setRadius (radiusValue[selection - 1]); - showMenu (ent, BOT_MENU_WAYPOINT_RADIUS); - } - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_SUPERCEDE); - } - return; - } - else if (client->menu == BOT_MENU_MAIN) { - showMenu (ent, BOT_MENU_INVALID); // reset menu display - - switch (selection) { - case 1: - fillCommand = false; - showMenu (ent, BOT_MENU_CONTROL); - break; - - case 2: - showMenu (ent, BOT_MENU_FEATURES); - break; - - case 3: - fillCommand = true; - showMenu (ent, BOT_MENU_TEAM_SELECT); - break; - - case 4: - bots.killAllBots (); - break; - - case 10: - showMenu (ent, BOT_MENU_INVALID); - break; - - default: - showMenu (ent, BOT_MENU_MAIN); - break; - } - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_SUPERCEDE); - } - return; - } - else if (client->menu == BOT_MENU_CONTROL) { - showMenu (ent, BOT_MENU_INVALID); // reset menu display - - switch (selection) { - case 1: - bots.createRandom (true); - showMenu (ent, BOT_MENU_CONTROL); - break; - - case 2: - showMenu (ent, BOT_MENU_DIFFICULTY); - break; - - case 3: - bots.kickRandom (); - showMenu (ent, BOT_MENU_CONTROL); - break; - - case 4: - bots.kickEveryone (); - break; - - case 5: - bots.kickBotByMenu (ent, 1); - break; - - case 10: - showMenu (ent, BOT_MENU_INVALID); - break; - } - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_SUPERCEDE); - } - return; - } - else if (client->menu == BOT_MENU_FEATURES) { - showMenu (ent, BOT_MENU_INVALID); // reset menu display - - switch (selection) { - case 1: - showMenu (ent, BOT_MENU_WEAPON_MODE); - break; - - case 2: - showMenu (ent, engine.isDedicated () ? BOT_MENU_FEATURES : BOT_MENU_WAYPOINT_MAIN_PAGE1); - break; - - case 3: - showMenu (ent, BOT_MENU_PERSONALITY); - break; - - case 4: - extern ConVar yb_debug; - yb_debug.set (yb_debug.integer () ^ 1); - - showMenu (ent, BOT_MENU_FEATURES); - break; - - case 5: - if (isAlive (ent)) { - showMenu (ent, BOT_MENU_COMMANDS); - } - else { - showMenu (ent, BOT_MENU_INVALID); // reset menu display - engine.centerPrint ("You're dead, and have no access to this menu"); - } - break; - - case 10: - showMenu (ent, BOT_MENU_INVALID); - break; - } - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_SUPERCEDE); - } - return; - } - else if (client->menu == BOT_MENU_COMMANDS) { - showMenu (ent, BOT_MENU_INVALID); // reset menu display - Bot *bot = nullptr; - - switch (selection) { - case 1: - case 2: - if (findNearestPlayer (reinterpret_cast (&bot), client->ent, 600.0f, true, true, true)) { - if (!bot->m_hasC4 && !bot->hasHostage ()) { - if (selection == 1) { - bot->startDoubleJump (client->ent); - } - else { - bot->resetDoubleJump (); - } - } - } - showMenu (ent, BOT_MENU_COMMANDS); - break; - - case 3: - case 4: - if (findNearestPlayer (reinterpret_cast (&bot), ent, 600.0f, true, true, true, true, selection == 4 ? false : true)) { - bot->dropWeaponForUser (ent, selection == 4 ? false : true); - } - showMenu (ent, BOT_MENU_COMMANDS); - break; - - case 10: - showMenu (ent, BOT_MENU_INVALID); - break; - } - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_SUPERCEDE); - } - return; - } - else if (client->menu == BOT_MENU_WAYPOINT_AUTOPATH) { - showMenu (ent, BOT_MENU_INVALID); // reset menu display - - const float autoDistanceValue[] = {0.0f, 100.0f, 130.0f, 160.0f, 190.0f, 220.0f, 250.0f}; - - if (selection >= 1 && selection <= 7) { - g_autoPathDistance = autoDistanceValue[selection - 1]; - } - - if (g_autoPathDistance == 0.0f) { - engine.centerPrint ("AutoPath disabled"); - } - else { - engine.centerPrint ("AutoPath maximum distance set to %.2f", g_autoPathDistance); - } - showMenu (ent, BOT_MENU_WAYPOINT_AUTOPATH); - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_SUPERCEDE); - } - return; - } - else if (client->menu == BOT_MENU_WAYPOINT_PATH) { - showMenu (ent, BOT_MENU_INVALID); // reset menu display - - switch (selection) { - case 1: - waypoints.pathCreate (CONNECTION_OUTGOING); - showMenu (ent, BOT_MENU_WAYPOINT_PATH); - break; - - case 2: - waypoints.pathCreate (CONNECTION_INCOMING); - showMenu (ent, BOT_MENU_WAYPOINT_PATH); - break; - - case 3: - waypoints.pathCreate (CONNECTION_BOTHWAYS); - showMenu (ent, BOT_MENU_WAYPOINT_PATH); - break; - - case 10: - showMenu (ent, BOT_MENU_INVALID); - break; - } - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_SUPERCEDE); - } - return; - } - else if (client->menu == BOT_MENU_DIFFICULTY) { - showMenu (ent, BOT_MENU_INVALID); // reset menu display - - client->menu = BOT_MENU_PERSONALITY; - - switch (selection) { - case 1: - g_storeAddbotVars[0] = 0; - break; - - case 2: - g_storeAddbotVars[0] = 1; - break; - - case 3: - g_storeAddbotVars[0] = 2; - break; - - case 4: - g_storeAddbotVars[0] = 3; - break; - - case 5: - g_storeAddbotVars[0] = 4; - break; - - case 10: - showMenu (ent, BOT_MENU_INVALID); - break; - } - showMenu (ent, BOT_MENU_PERSONALITY); - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_SUPERCEDE); - } - return; - } - else if (client->menu == BOT_MENU_TEAM_SELECT && fillCommand) { - showMenu (ent, BOT_MENU_INVALID); // reset menu display - - if (selection < 3) { - extern ConVar mp_limitteams; - extern ConVar mp_autoteambalance; - - // turn off cvars if specified team - mp_limitteams.set (0); - mp_autoteambalance.set (0); - } - - switch (selection) { - case 1: - case 2: - case 5: - fillServerTeam = selection; - showMenu (ent, BOT_MENU_DIFFICULTY); - break; - - case 10: - showMenu (ent, BOT_MENU_INVALID); - break; - } - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_SUPERCEDE); - } - return; - } - else if (client->menu == BOT_MENU_PERSONALITY && fillCommand) { - showMenu (ent, BOT_MENU_INVALID); // reset menu display - - switch (selection) { - case 1: - case 2: - case 3: - case 4: - bots.serverFill (fillServerTeam, selection - 2, g_storeAddbotVars[0]); - showMenu (ent, BOT_MENU_INVALID); - break; - - case 10: - showMenu (ent, BOT_MENU_INVALID); - break; - } - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_SUPERCEDE); - } - return; - } - else if (client->menu == BOT_MENU_TEAM_SELECT) { - showMenu (ent, BOT_MENU_INVALID); // reset menu display - - switch (selection) { - case 1: - case 2: - case 5: - g_storeAddbotVars[1] = selection; - - if (selection == 5) { - g_storeAddbotVars[2] = 5; - bots.addbot ("", g_storeAddbotVars[0], g_storeAddbotVars[3], g_storeAddbotVars[1], g_storeAddbotVars[2], true); - } - else { - if (selection == 1) { - showMenu (ent, BOT_MENU_TERRORIST_SELECT); - } - else { - showMenu (ent, BOT_MENU_CT_SELECT); - } - } - break; - - case 10: - showMenu (ent, BOT_MENU_INVALID); - break; - } - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_SUPERCEDE); - } - return; - } - else if (client->menu == BOT_MENU_PERSONALITY) { - showMenu (ent, BOT_MENU_INVALID); // reset menu display - - switch (selection) { - case 1: - case 2: - case 3: - case 4: - g_storeAddbotVars[3] = selection - 2; - showMenu (ent, BOT_MENU_TEAM_SELECT); - break; - - case 10: - showMenu (ent, BOT_MENU_INVALID); - break; - } - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_SUPERCEDE); - } - return; - } - else if (client->menu == BOT_MENU_TERRORIST_SELECT || client->menu == BOT_MENU_CT_SELECT) { - showMenu (ent, BOT_MENU_INVALID); // reset menu display - - switch (selection) { - case 1: - case 2: - case 3: - case 4: - case 5: - g_storeAddbotVars[2] = selection; - bots.addbot ("", g_storeAddbotVars[0], g_storeAddbotVars[3], g_storeAddbotVars[1], g_storeAddbotVars[2], true); - break; - - case 10: - showMenu (ent, BOT_MENU_INVALID); - break; - } - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_SUPERCEDE); - } - return; - } - else if (client->menu == BOT_MENU_WEAPON_MODE) { - showMenu (ent, BOT_MENU_INVALID); // reset menu display - - switch (selection) { - case 1: - case 2: - case 3: - case 4: - case 5: - case 6: - case 7: - bots.setWeaponMode (selection); - showMenu (ent, BOT_MENU_WEAPON_MODE); - break; - - case 10: - showMenu (ent, BOT_MENU_INVALID); - break; - } - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_SUPERCEDE); - } - return; - } - else if (client->menu == BOT_MENU_KICK_PAGE_1) { - showMenu (ent, BOT_MENU_INVALID); // reset menu display - - switch (selection) { - case 1: - case 2: - case 3: - case 4: - case 5: - case 6: - case 7: - case 8: - bots.kickBot (selection - 1); - bots.kickBotByMenu (ent, 1); - break; - - case 9: - bots.kickBotByMenu (ent, 2); - break; - - case 10: - showMenu (ent, BOT_MENU_CONTROL); - break; - } - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_SUPERCEDE); - } - return; - } - else if (client->menu == BOT_MENU_KICK_PAGE_2) { - showMenu (ent, BOT_MENU_INVALID); // reset menu display - - switch (selection) { - case 1: - case 2: - case 3: - case 4: - case 5: - case 6: - case 7: - case 8: - bots.kickBot (selection + 8 - 1); - bots.kickBotByMenu (ent, 2); - break; - - case 9: - bots.kickBotByMenu (ent, 3); - break; - - case 10: - bots.kickBotByMenu (ent, 1); - break; - } - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_SUPERCEDE); - } - return; - } - else if (client->menu == BOT_MENU_KICK_PAGE_3) { - showMenu (ent, BOT_MENU_INVALID); // reset menu display - - switch (selection) { - case 1: - case 2: - case 3: - case 4: - case 5: - case 6: - case 7: - case 8: - bots.kickBot (selection + 16 - 1); - bots.kickBotByMenu (ent, 3); - break; - - case 9: - bots.kickBotByMenu (ent, 4); - break; - - case 10: - bots.kickBotByMenu (ent, 2); - break; - } - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_SUPERCEDE); - } - return; - } - else if (client->menu == BOT_MENU_KICK_PAGE_4) { - showMenu (ent, BOT_MENU_INVALID); // reset menu display - - switch (selection) { - case 1: - case 2: - case 3: - case 4: - case 5: - case 6: - case 7: - case 8: - bots.kickBot (selection + 24 - 1); - bots.kickBotByMenu (ent, 4); - break; - - case 10: - bots.kickBotByMenu (ent, 3); - break; - } - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_SUPERCEDE); - } - return; - } - } - } - - if (!engine.isBotCmd () && (stricmp (command, "say") == 0 || stricmp (command, "say_team") == 0)) { - Bot *bot = nullptr; - - if (strcmp (arg1, "dropme") == 0 || strcmp (arg1, "dropc4") == 0) { - if (findNearestPlayer (reinterpret_cast (&bot), ent, 300.0f, true, true, true)) { - bot->dropWeaponForUser (ent, isEmptyStr (strstr (arg1, "c4")) ? false : true); +gamefuncs_t dllapi; +enginefuncs_t engfuncs; +gamedll_funcs_t dllfuncs; + +meta_globals_t *gpMetaGlobals = nullptr; +gamedll_funcs_t *gpGamedllFuncs = nullptr; +mutil_funcs_t *gpMetaUtilFuncs = nullptr; +globalvars_t *globals = nullptr; + +// metamod plugin information +plugin_info_t Plugin_info = { + META_INTERFACE_VERSION, // interface version + PRODUCT_SHORT_NAME, // plugin name + PRODUCT_VERSION, // plugin version + PRODUCT_DATE, // date of creation + PRODUCT_AUTHOR, // plugin author + PRODUCT_URL, // plugin URL + PRODUCT_LOGTAG, // plugin logtag + PT_CHANGELEVEL, // when loadable + PT_ANYTIME, // when unloadable +}; + +namespace VariadicCallbacks { + void clientCommand (edict_t *ent, char const *format, ...) { + // this function forces the client whose player entity is ent to issue a client command. + // How it works is that clients all have a argv global string in their client DLL that + // stores the command string; if ever that string is filled with characters, the client DLL + // sends it to the engine as a command to be executed. When the engine has executed that + // command, this argv string is reset to zero. Here is somehow a curious implementation of + // ClientCommand: the engine sets the command it wants the client to issue in his argv, then + // the client DLL sends it back to the engine, the engine receives it then executes the + // command therein. Don't ask me why we need all this complicated crap. Anyhow since bots have + // no client DLL, be certain never to call this function upon a bot entity, else it will just + // make the server crash. Since hordes of uncautious, not to say stupid, programmers don't + // even imagine some players on their servers could be bots, this check is performed less than + // sometimes actually by their side, that's why we strongly recommend to check it here too. In + // case it's a bot asking for a client command, we handle it like we do for bot commands + + va_list ap; + char buffer[MAX_PRINT_BUFFER]; + + va_start (ap, format); + _vsnprintf (buffer, cr::bufsize (buffer), format, ap); + va_end (ap); + + if (ent && (ent->v.flags & (FL_FAKECLIENT | FL_DORMANT))) { + if (bots.getBot (ent)) { + game.execBotCmd (ent, buffer); + } + + if (game.is (GAME_METAMOD)) { + RETURN_META (MRES_SUPERCEDE); // prevent bots to be forced to issue client commands } return; } - bool alive = isAlive (ent); - int team = -1; - - if (strcmp (command, "say_team") == 0) { - team = engine.getTeam (ent); - } - - for (int i = 0; i < engine.maxClients (); i++) { - const Client &client = g_clients[i]; - - if (!(client.flags & CF_USED) || (team != -1 && team != client.team) || alive != isAlive (client.ent)) { - continue; - } - Bot *target = bots.getBot (i); - - if (target != nullptr) { - target->m_sayTextBuffer.entityIndex = engine.indexOfEntity (ent); - - if (isEmptyStr (g_engfuncs.pfnCmd_Args ())) { - continue; - } - target->m_sayTextBuffer.sayText = g_engfuncs.pfnCmd_Args (); - target->m_sayTextBuffer.timeNextChat = engine.timebase () + target->m_sayTextBuffer.chatDelay; - } + if (game.is (GAME_METAMOD)) { + RETURN_META (MRES_IGNORED); } + engfuncs.pfnClientCommand (ent, buffer); } - - int clientIndex = engine.indexOfEntity (ent) - 1; - const Client &radioTarget = g_clients[clientIndex]; - - // check if this player alive, and issue something - if ((radioTarget.flags & CF_ALIVE) && g_radioSelect[clientIndex] != 0 && strncmp (command, "menuselect", 10) == 0) { - int radioCommand = atoi (arg1); - - if (radioCommand != 0) { - radioCommand += 10 * (g_radioSelect[clientIndex] - 1); - - if (radioCommand != RADIO_AFFIRMATIVE && radioCommand != RADIO_NEGATIVE && radioCommand != RADIO_REPORTING_IN) { - for (int i = 0; i < engine.maxClients (); i++) { - Bot *bot = bots.getBot (i); - - // validate bot - if (bot != nullptr && bot->m_team == radioTarget.team && ent != bot->ent () && bot->m_radioOrder == 0) { - bot->m_radioOrder = radioCommand; - bot->m_radioEntity = ent; - } - } - } - g_lastRadioTime[radioTarget.team] = engine.timebase (); - } - g_radioSelect[clientIndex] = 0; - } - else if (strncmp (command, "radio", 5) == 0) { - g_radioSelect[clientIndex] = atoi (&command[5]); - } - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_IGNORED); - } - g_functionTable.pfnClientCommand (ent); } -void ServerActivate (edict_t *pentEdictList, int edictCount, int clientMax) { - // this function is called when the server has fully loaded and is about to manifest itself - // on the network as such. Since a mapchange is actually a server shutdown followed by a - // restart, this function is also called when a new map is being loaded. Hence it's the - // perfect place for doing initialization stuff for our bots, such as reading the BSP data, - // loading the bot profiles, and drawing the world map (ie, filling the navigation hashtable). - // Once this function has been called, the server can be considered as "running". - - cleanupGarbage (); - execBotConfigs (false); // initialize all config files - - // do a level initialization - engine.levelInitialize (); - - // update worldmodel - illum.resetWorldModel (); - - // do level initialization stuff here... - waypoints.init (); - waypoints.load (); - - // execute main config - execBotConfigs (true); - - if (File::exists (format ("%s/maps/%s_yapb.cfg", engine.getModName (), engine.getMapName ()))) { - engine.execCmd ("exec maps/%s_yapb.cfg", engine.getMapName ()); - engine.print ("Executing Map-Specific config file"); - } - bots.initQuota (); - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_IGNORED); - } - g_functionTable.pfnServerActivate (pentEdictList, edictCount, clientMax); - - waypoints.rebuildVisibility (); -} - -void ServerDeactivate (void) { - // this function is called when the server is shutting down. A particular note about map - // changes: changing the map means shutting down the server and starting a new one. Of course - // this process is transparent to the user, but either in single player when the hero reaches - // a new level and in multiplayer when it's time for a map change, be aware that what happens - // is that the server actually shuts down and restarts with a new map. Hence we can use this - // function to free and deinit anything which is map-specific, for example we free the memory - // space we m'allocated for our BSP data, since a new map means new BSP data to interpret. In - // any case, when the new map will be booting, ServerActivate() will be called, so we'll do - // the loading of new bots and the new BSP data parsing there. - - // save collected experience on shutdown - waypoints.saveExperience (); - waypoints.saveVisibility (); - - // destroy global killer entity - bots.destroyKillerEntity (); - - // set state to unprecached - engine.setUnprecached (); - - // enable lightstyle animations on level change - illum.enableAnimation (true); - - // xash is not kicking fakeclients on changelevel - if (g_gameFlags & GAME_XASH_ENGINE) { - bots.kickEveryone (true, false); - bots.destroy (); - } - cleanupGarbage (); - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_IGNORED); - } - g_functionTable.pfnServerDeactivate (); -} - -void StartFrame (void) { - // this function starts a video frame. It is called once per video frame by the engine. If - // you run Half-Life at 90 fps, this function will then be called 90 times per second. By - // placing a hook on it, we have a good place to do things that should be done continuously - // during the game, for example making the bots think (yes, because no Think() function exists - // for the bots by the MOD side, remember). Also here we have control on the bot population, - // for example if a new player joins the server, we should disconnect a bot, and if the - // player population decreases, we should fill the server with other bots. - - // run periodic update of bot states - bots.frame (); - - // update lightstyle animations - illum.animateLight (); - - // record some stats of all players on the server - for (int i = 0; i < engine.maxClients (); i++) { - edict_t *player = engine.entityOfIndex (i + 1); - Client &client = g_clients[i]; - - if (!engine.isNullEntity (player) && (player->v.flags & FL_CLIENT)) { - client.ent = player; - client.flags |= CF_USED; - - if (isAlive (player)) { - client.flags |= CF_ALIVE; - } - else { - client.flags &= ~CF_ALIVE; - } - - if (client.flags & CF_ALIVE) { - // keep the clipping mode enabled, or it can be turned off after new round has started - if (g_hostEntity == player && g_editNoclip) { - g_hostEntity->v.movetype = MOVETYPE_NOCLIP; - } - client.origin = player->v.origin; - simulateSoundUpdates (i); - } - } - else { - client.flags &= ~(CF_USED | CF_ALIVE); - client.ent = nullptr; - } - } - - if (g_waypointOn && !engine.isDedicated () && !engine.isNullEntity (g_hostEntity)) { - waypoints.frame (); - } - bots.updateDeathMsgState (false); - - if (g_timePerSecondUpdate < engine.timebase ()) { - checkWelcome (); - - for (int i = 0; i < engine.maxClients (); i++) { - edict_t *player = engine.entityOfIndex (i + 1); - - // code below is executed only on dedicated server - if (engine.isDedicated () && !engine.isNullEntity (player) && (player->v.flags & FL_CLIENT) && !(player->v.flags & FL_FAKECLIENT)) { - Client &client = g_clients[i]; - - if (client.flags & CF_ADMIN) { - if (isEmptyStr (yb_password_key.str ()) && isEmptyStr (yb_password.str ())) { - client.flags &= ~CF_ADMIN; - } - else if (!!strcmp (yb_password.str (), g_engfuncs.pfnInfoKeyValue (g_engfuncs.pfnGetInfoKeyBuffer (client.ent), const_cast (yb_password_key.str ())))) { - client.flags &= ~CF_ADMIN; - engine.print ("Player %s had lost remote access to yapb.", STRING (player->v.netname)); - } - } - else if (!(client.flags & CF_ADMIN) && !isEmptyStr (yb_password_key.str ()) && !isEmptyStr (yb_password.str ())) { - if (strcmp (yb_password.str (), g_engfuncs.pfnInfoKeyValue (g_engfuncs.pfnGetInfoKeyBuffer (client.ent), const_cast (yb_password_key.str ()))) == 0) { - client.flags |= CF_ADMIN; - engine.print ("Player %s had gained full remote access to yapb.", STRING (player->v.netname)); - } - } - } - } - bots.calculatePingOffsets (); - - // calculate light levels for all waypoints if needed - waypoints.initLightLevels (); - - if (g_gameFlags & (GAME_METAMOD | GAME_REGAMEDLL)) { - static auto dmActive = g_engfuncs.pfnCVarGetPointer ("csdm_active"); - static auto freeForAll = g_engfuncs.pfnCVarGetPointer ("mp_freeforall"); - - // csdm is only with amxx and metamod - if (dmActive) { - if (dmActive->value > 0.0f) { - g_gameFlags |= GAME_CSDM; - } - else if (g_gameFlags & GAME_CSDM) { - g_gameFlags &= ~GAME_CSDM; - } - } - - // but this can be provided by regamedll - if (freeForAll) { - if (freeForAll->value > 0.0f) { - g_gameFlags |= GAME_CSDM_FFA; - } - else if (g_gameFlags & GAME_CSDM_FFA) { - g_gameFlags &= ~GAME_CSDM_FFA; - } - } - } - g_timePerSecondUpdate = engine.timebase () + 1.0f; - } - - if (bots.getBotCount () > 0) { - // keep track of grenades on map - bots.updateActiveGrenade (); - - // keep track of intresting entities - bots.updateIntrestingEntities (); - } - - // keep bot number up to date - bots.maintainQuota (); - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_IGNORED); - } - g_functionTable.pfnStartFrame (); - - // **** AI EXECUTION STARTS **** - bots.framePeriodic (); - // **** AI EXECUTION FINISH **** -} - -void StartFrame_Post (void) { - // this function starts a video frame. It is called once per video frame by the engine. If - // you run Half-Life at 90 fps, this function will then be called 90 times per second. By - // placing a hook on it, we have a good place to do things that should be done continuously - // during the game, for example making the bots think (yes, because no Think() function exists - // for the bots by the MOD side, remember). Post version called only by metamod. - - // **** AI EXECUTION STARTS **** - bots.framePeriodic (); - // **** AI EXECUTION FINISH **** - - RETURN_META (MRES_IGNORED); -} - -int Spawn_Post (edict_t *ent) { - // this function asks the game DLL to spawn (i.e, give a physical existence in the virtual - // world, in other words to 'display') the entity pointed to by ent in the game. The - // Spawn() function is one of the functions any entity is supposed to have in the game DLL, - // and any MOD is supposed to implement one for each of its entities. Post version called - // only by metamod. - - // solves the bots unable to see through certain types of glass bug. - if (ent->v.rendermode == kRenderTransTexture) { - ent->v.flags &= ~FL_WORLDBRUSH; // clear the FL_WORLDBRUSH flag out of transparent ents - } - RETURN_META_VALUE (MRES_IGNORED, 0); -} - -void ServerActivate_Post (edict_t *, int, int) { - // this function is called when the server has fully loaded and is about to manifest itself - // on the network as such. Since a mapchange is actually a server shutdown followed by a - // restart, this function is also called when a new map is being loaded. Hence it's the - // perfect place for doing initialization stuff for our bots, such as reading the BSP data, - // loading the bot profiles, and drawing the world map (ie, filling the navigation hashtable). - // Once this function has been called, the server can be considered as "running". Post version - // called only by metamod. - - waypoints.rebuildVisibility (); - - RETURN_META (MRES_IGNORED); -} - -void pfnChangeLevel (char *s1, char *s2) { - // the purpose of this function is to ask the engine to shutdown the server and restart a - // new one running the map whose name is s1. It is used ONLY IN SINGLE PLAYER MODE and is - // transparent to the user, because it saves the player state and equipment and restores it - // back in the new level. The "changelevel trigger point" in the old level is linked to the - // new level's spawn point using the s2 string, which is formatted as follows: "trigger_name - // to spawnpoint_name", without spaces (for example, "tr_1atotr_2lm" would tell the engine - // the player has reached the trigger point "tr_1a" and has to spawn in the next level on the - // spawn point named "tr_2lm". - - // save collected experience on map change - waypoints.saveExperience (); - waypoints.saveVisibility (); - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_IGNORED); - } - g_engfuncs.pfnChangeLevel (s1, s2); -} - -edict_t *pfnFindEntityByString (edict_t *edictStartSearchAfter, const char *field, const char *value) { - // round starts in counter-strike 1.5 - if ((g_gameFlags & GAME_LEGACY) && strcmp (value, "info_map_parameters") == 0) { - initRound (); - } - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META_VALUE (MRES_IGNORED, 0); - } - return g_engfuncs.pfnFindEntityByString (edictStartSearchAfter, field, value); -} - -void pfnEmitSound (edict_t *entity, int channel, const char *sample, float volume, float attenuation, int flags, int pitch) { - // this function tells the engine that the entity pointed to by "entity", is emitting a sound - // which fileName is "sample", at level "channel" (CHAN_VOICE, etc...), with "volume" as - // loudness multiplicator (normal volume VOL_NORM is 1.0), with a pitch of "pitch" (normal - // pitch PITCH_NORM is 100.0), and that this sound has to be attenuated by distance in air - // according to the value of "attenuation" (normal attenuation ATTN_NORM is 0.8 ; ATTN_NONE - // means no attenuation with distance). Optionally flags "fFlags" can be passed, which I don't - // know the heck of the purpose. After we tell the engine to emit the sound, we have to call - // SoundAttachToThreat() to bring the sound to the ears of the bots. Since bots have no client DLL - // to handle this for them, such a job has to be done manually. - - attachSoundsToClients (entity, sample, volume); - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_IGNORED); - } - g_engfuncs.pfnEmitSound (entity, channel, sample, volume, attenuation, flags, pitch); -} - -void pfnClientCommand (edict_t *ent, char const *format, ...) { - // this function forces the client whose player entity is ent to issue a client command. - // How it works is that clients all have a g_xgv global string in their client DLL that - // stores the command string; if ever that string is filled with characters, the client DLL - // sends it to the engine as a command to be executed. When the engine has executed that - // command, this g_xgv string is reset to zero. Here is somehow a curious implementation of - // ClientCommand: the engine sets the command it wants the client to issue in his g_xgv, then - // the client DLL sends it back to the engine, the engine receives it then executes the - // command therein. Don't ask me why we need all this complicated crap. Anyhow since bots have - // no client DLL, be certain never to call this function upon a bot entity, else it will just - // make the server crash. Since hordes of uncautious, not to say stupid, programmers don't - // even imagine some players on their servers could be bots, this check is performed less than - // sometimes actually by their side, that's why we strongly recommend to check it here too. In - // case it's a bot asking for a client command, we handle it like we do for bot commands - - va_list ap; - char buffer[MAX_PRINT_BUFFER]; - - va_start (ap, format); - _vsnprintf (buffer, cr::bufsize (buffer), format, ap); - va_end (ap); - - if (ent && (ent->v.flags & (FL_FAKECLIENT | FL_DORMANT))) { - if (bots.getBot (ent)) { - engine.execBotCmd (ent, buffer); - } - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_SUPERCEDE); // prevent bots to be forced to issue client commands - } - return; - } - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_IGNORED); - } - g_engfuncs.pfnClientCommand (ent, buffer); -} - -void pfnMessageBegin (int msgDest, int msgType, const float *origin, edict_t *ed) { - // this function called each time a message is about to sent. - - // store the message type in our own variables, since the GET_USER_MSG_ID () will just do a lot of strcmp()'s... - if ((g_gameFlags & GAME_METAMOD) && engine.getMessageId (NETMSG_MONEY) == -1) { - - auto setMsgId = [&] (const char *name, NetMsgId id) { - engine.setMessageId (id, GET_USER_MSG_ID (PLID, name, nullptr)); - }; - - setMsgId ("VGUIMenu", NETMSG_VGUI); - setMsgId ("ShowMenu", NETMSG_SHOWMENU); - setMsgId ("WeaponList", NETMSG_WEAPONLIST); - setMsgId ("CurWeapon", NETMSG_CURWEAPON); - setMsgId ("AmmoX", NETMSG_AMMOX); - setMsgId ("AmmoPickup", NETMSG_AMMOPICKUP); - setMsgId ("Damage", NETMSG_DAMAGE); - setMsgId ("Money", NETMSG_MONEY); - setMsgId ("StatusIcon", NETMSG_STATUSICON); - setMsgId ("DeathMsg", NETMSG_DEATH); - setMsgId ("ScreenFade", NETMSG_SCREENFADE); - setMsgId ("HLTV", NETMSG_HLTV); - setMsgId ("TextMsg", NETMSG_TEXTMSG); - setMsgId ("TeamInfo", NETMSG_TEAMINFO); - setMsgId ("BarTime", NETMSG_BARTIME); - setMsgId ("SendAudio", NETMSG_SENDAUDIO); - setMsgId ("SayText", NETMSG_SAYTEXT); - setMsgId ("FlashBat", NETMSG_FLASHBAT); - setMsgId ("Flashlight", NETMSG_FLASHLIGHT); - setMsgId ("NVGToggle", NETMSG_NVGTOGGLE); - setMsgId ("ItemStatus", NETMSG_ITEMSTATUS); - - if (g_gameFlags & GAME_SUPPORT_BOT_VOICE) { - engine.setMessageId (NETMSG_BOTVOICE, GET_USER_MSG_ID (PLID, "BotVoice", nullptr)); - } - } - engine.resetMessages (); - - if ((!(g_gameFlags & GAME_LEGACY) || (g_gameFlags & GAME_XASH_ENGINE)) && msgDest == MSG_SPEC && msgType == engine.getMessageId (NETMSG_HLTV)) { - engine.setCurrentMessageId (NETMSG_HLTV); - } - engine.captureMessage (msgType, NETMSG_WEAPONLIST); - - if (!engine.isNullEntity (ed)) { - int index = bots.index (ed); - - // is this message for a bot? - if (index != -1 && !(ed->v.flags & FL_DORMANT)) { - engine.setCurrentMessageOwner (index); - - // message handling is done in usermsg.cpp - engine.captureMessage (msgType, NETMSG_VGUI); - engine.captureMessage (msgType, NETMSG_CURWEAPON); - engine.captureMessage (msgType, NETMSG_AMMOX); - engine.captureMessage (msgType, NETMSG_AMMOPICKUP); - engine.captureMessage (msgType, NETMSG_DAMAGE); - engine.captureMessage (msgType, NETMSG_MONEY); - engine.captureMessage (msgType, NETMSG_STATUSICON); - engine.captureMessage (msgType, NETMSG_SCREENFADE); - engine.captureMessage (msgType, NETMSG_BARTIME); - engine.captureMessage (msgType, NETMSG_TEXTMSG); - engine.captureMessage (msgType, NETMSG_SHOWMENU); - engine.captureMessage (msgType, NETMSG_FLASHBAT); - engine.captureMessage (msgType, NETMSG_NVGTOGGLE); - engine.captureMessage (msgType, NETMSG_ITEMSTATUS); - } - } - else if (msgDest == MSG_ALL) { - engine.captureMessage (msgType, NETMSG_TEAMINFO); - engine.captureMessage (msgType, NETMSG_DEATH); - engine.captureMessage (msgType, NETMSG_TEXTMSG); - - if (msgType == SVC_INTERMISSION) { - for (int i = 0; i < engine.maxClients (); i++) { - Bot *bot = bots.getBot (i); - - if (bot != nullptr) { - bot->m_notKilled = false; - } - } - } - } - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_IGNORED); - } - g_engfuncs.pfnMessageBegin (msgDest, msgType, origin, ed); -} - -void pfnMessageEnd (void) { - engine.resetMessages (); - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_IGNORED); - } - g_engfuncs.pfnMessageEnd (); - - // send latency fix - bots.sendDeathMsgFix (); -} - -void pfnMessageEnd_Post (void) { - // send latency fix - bots.sendDeathMsgFix (); - - RETURN_META (MRES_IGNORED); -} - -void pfnWriteByte (int value) { - // if this message is for a bot, call the client message function... - engine.processMessages ((void *)&value); - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_IGNORED); - } - g_engfuncs.pfnWriteByte (value); -} - -void pfnWriteChar (int value) { - // if this message is for a bot, call the client message function... - engine.processMessages ((void *)&value); - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_IGNORED); - } - g_engfuncs.pfnWriteChar (value); -} - -void pfnWriteShort (int value) { - // if this message is for a bot, call the client message function... - engine.processMessages ((void *)&value); - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_IGNORED); - } - g_engfuncs.pfnWriteShort (value); -} - -void pfnWriteLong (int value) { - // if this message is for a bot, call the client message function... - engine.processMessages ((void *)&value); - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_IGNORED); - } - g_engfuncs.pfnWriteLong (value); -} - -void pfnWriteAngle (float value) { - // if this message is for a bot, call the client message function... - engine.processMessages ((void *)&value); - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_IGNORED); - } - g_engfuncs.pfnWriteAngle (value); -} - -void pfnWriteCoord (float value) { - // if this message is for a bot, call the client message function... - engine.processMessages ((void *)&value); - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_IGNORED); - } - g_engfuncs.pfnWriteCoord (value); -} - -void pfnWriteString (const char *sz) { - // if this message is for a bot, call the client message function... - engine.processMessages ((void *)sz); - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_IGNORED); - } - g_engfuncs.pfnWriteString (sz); -} - -void pfnWriteEntity (int value) { - // if this message is for a bot, call the client message function... - engine.processMessages ((void *)&value); - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_IGNORED); - } - g_engfuncs.pfnWriteEntity (value); -} - -int pfnCmd_Argc (void) { - // this function returns the number of arguments the current client command string has. Since - // bots have no client DLL and we may want a bot to execute a client command, we had to - // implement a g_xgv string in the bot DLL for holding the bots' commands, and also keep - // track of the argument count. Hence this hook not to let the engine ask an unexistent client - // DLL for a command we are holding here. Of course, real clients commands are still retrieved - // the normal way, by asking the engine. - - // is this a bot issuing that client command? - if (engine.isBotCmd ()) { - if (g_gameFlags & GAME_METAMOD) { - RETURN_META_VALUE (MRES_SUPERCEDE, engine.botArgc ()); - } - return engine.botArgc (); // if so, then return the argument count we know - } - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META_VALUE (MRES_IGNORED, 0); - } - return g_engfuncs.pfnCmd_Argc (); // ask the engine how many arguments there are -} - -const char *pfnCmd_Args (void) { - // this function returns a pointer to the whole current client command string. Since bots - // have no client DLL and we may want a bot to execute a client command, we had to implement - // a g_xgv string in the bot DLL for holding the bots' commands, and also keep track of the - // argument count. Hence this hook not to let the engine ask an unexistent client DLL for a - // command we are holding here. Of course, real clients commands are still retrieved the - // normal way, by asking the engine. - - // is this a bot issuing that client command? - if (engine.isBotCmd ()) { - if (g_gameFlags & GAME_METAMOD) { - RETURN_META_VALUE (MRES_SUPERCEDE, engine.botArgs ()); - } - return engine.botArgs (); // else return the whole bot client command string we know - } - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META_VALUE (MRES_IGNORED, nullptr); - } - return g_engfuncs.pfnCmd_Args (); // ask the client command string to the engine -} - -const char *pfnCmd_Argv (int argc) { - // this function returns a pointer to a certain argument of the current client command. Since - // bots have no client DLL and we may want a bot to execute a client command, we had to - // implement a g_xgv string in the bot DLL for holding the bots' commands, and also keep - // track of the argument count. Hence this hook not to let the engine ask an unexistent client - // DLL for a command we are holding here. Of course, real clients commands are still retrieved - // the normal way, by asking the engine. - - // is this a bot issuing that client command? - if (engine.isBotCmd ()) { - if (g_gameFlags & GAME_METAMOD) { - RETURN_META_VALUE (MRES_SUPERCEDE, engine.botArgv (argc)); - } - return engine.botArgv (argc); // if so, then return the wanted argument we know - } - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META_VALUE (MRES_IGNORED, nullptr); - } - return g_engfuncs.pfnCmd_Argv (argc); // ask the argument number "argc" to the engine -} - -void pfnClientPrintf (edict_t *ent, PRINT_TYPE printType, const char *message) { - // this function prints the text message string pointed to by message by the client side of - // the client entity pointed to by ent, in a manner depending of printType (print_console, - // print_center or print_chat). Be certain never to try to feed a bot with this function, - // as it will crash your server. Why would you, anyway ? bots have no client DLL as far as - // we know, right ? But since stupidity rules this world, we do a preventive check :) - - if (isFakeClient (ent)) { - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_SUPERCEDE); - } - return; - } - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_IGNORED); - } - g_engfuncs.pfnClientPrintf (ent, printType, message); -} - -void pfnSetClientMaxspeed (const edict_t *ent, float newMaxspeed) { - Bot *bot = bots.getBot (const_cast (ent)); - - // check wether it's not a bot - if (bot != nullptr) { - bot->pev->maxspeed = newMaxspeed; - } - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_IGNORED); - } - g_engfuncs.pfnSetClientMaxspeed (ent, newMaxspeed); -} - -int pfnRegUserMsg (const char *name, int size) { - // this function registers a "user message" by the engine side. User messages are network - // messages the game DLL asks the engine to send to clients. Since many MODs have completely - // different client features (Counter-Strike has a radar and a timer, for example), network - // messages just can't be the same for every MOD. Hence here the MOD DLL tells the engine, - // "Hey, you have to know that I use a network message whose name is pszName and it is size - // packets long". The engine books it, and returns the ID number under which he recorded that - // custom message. Thus every time the MOD DLL will be wanting to send a message named pszName - // using pfnMessageBegin (), it will know what message ID number to send, and the engine will - // know what to do, only for non-metamod version - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META_VALUE (MRES_IGNORED, 0); - } - int message = g_engfuncs.pfnRegUserMsg (name, size); - - if (strcmp (name, "VGUIMenu") == 0) { - engine.setMessageId (NETMSG_VGUI, message); - } - else if (strcmp (name, "ShowMenu") == 0) { - engine.setMessageId (NETMSG_SHOWMENU, message); - } - else if (strcmp (name, "WeaponList") == 0) { - engine.setMessageId (NETMSG_WEAPONLIST, message); - } - else if (strcmp (name, "CurWeapon") == 0) { - engine.setMessageId (NETMSG_CURWEAPON, message); - } - else if (strcmp (name, "AmmoX") == 0) { - engine.setMessageId (NETMSG_AMMOX, message); - } - else if (strcmp (name, "AmmoPickup") == 0) { - engine.setMessageId (NETMSG_AMMOPICKUP, message); - } - else if (strcmp (name, "Damage") == 0) { - engine.setMessageId (NETMSG_DAMAGE, message); - } - else if (strcmp (name, "Money") == 0) { - engine.setMessageId (NETMSG_MONEY, message); - } - else if (strcmp (name, "StatusIcon") == 0) { - engine.setMessageId (NETMSG_STATUSICON, message); - } - else if (strcmp (name, "DeathMsg") == 0) { - engine.setMessageId (NETMSG_DEATH, message); - } - else if (strcmp (name, "ScreenFade") == 0) { - engine.setMessageId (NETMSG_SCREENFADE, message); - } - else if (strcmp (name, "HLTV") == 0) { - engine.setMessageId (NETMSG_HLTV, message); - } - else if (strcmp (name, "TextMsg") == 0) { - engine.setMessageId (NETMSG_TEXTMSG, message); - } - else if (strcmp (name, "TeamInfo") == 0) { - engine.setMessageId (NETMSG_TEAMINFO, message); - } - else if (strcmp (name, "BarTime") == 0) { - engine.setMessageId (NETMSG_BARTIME, message); - } - else if (strcmp (name, "SendAudio") == 0) { - engine.setMessageId (NETMSG_SENDAUDIO, message); - } - else if (strcmp (name, "SayText") == 0) { - engine.setMessageId (NETMSG_SAYTEXT, message); - } - else if (strcmp (name, "BotVoice") == 0) { - engine.setMessageId (NETMSG_BOTVOICE, message); - } - else if (strcmp (name, "NVGToggle") == 0) { - engine.setMessageId (NETMSG_NVGTOGGLE, message); - } - else if (strcmp (name, "FlashBat") == 0) { - engine.setMessageId (NETMSG_FLASHBAT, message); - } - else if (strcmp (name, "Flashlight") == 0) { - engine.setMessageId (NETMSG_FLASHLIGHT, message); - } - else if (strcmp (name, "ItemStatus") == 0) { - engine.setMessageId (NETMSG_ITEMSTATUS, message); - } - return message; -} - -void pfnAlertMessage (ALERT_TYPE alertType, char *format, ...) { - va_list ap; - char buffer[MAX_PRINT_BUFFER]; - - va_start (ap, format); - vsnprintf (buffer, cr::bufsize (buffer), format, ap); - va_end (ap); - - if ((g_mapFlags & MAP_DE) && g_bombPlanted && strstr (buffer, "_Defuse_") != nullptr) { - // notify all terrorists that CT is starting bomb defusing - for (int i = 0; i < engine.maxClients (); i++) { - Bot *bot = bots.getBot (i); - - if (bot != nullptr && bot->m_team == TEAM_TERRORIST && bot->m_notKilled) { - bot->clearSearchNodes (); - - bot->m_position = waypoints.getBombPos (); - bot->startTask (TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, INVALID_WAYPOINT_INDEX, 0.0f, true); - } - } - } - - if (g_gameFlags & GAME_METAMOD) { - RETURN_META (MRES_IGNORED); - } - g_engfuncs.pfnAlertMessage (alertType, buffer); -} - -typedef void (*entity_func_t) (entvars_t *); -gamedll_funcs_t gameDLLFunc; - SHARED_LIBRARAY_EXPORT int GetEntityAPI2 (gamefuncs_t *functionTable, int *) { // this function is called right after GiveFnptrsToDll() by the engine in the game DLL (or // what it BELIEVES to be the game DLL), in order to copy the list of MOD functions that can @@ -2844,31 +87,359 @@ SHARED_LIBRARAY_EXPORT int GetEntityAPI2 (gamefuncs_t *functionTable, int *) { memset (functionTable, 0, sizeof (gamefuncs_t)); - if (!(g_gameFlags & GAME_METAMOD)) { - auto api_GetEntityAPI = g_gameLib.resolve ("GetEntityAPI"); + if (!(game.is (GAME_METAMOD))) { + auto api_GetEntityAPI = game.getLib ().resolve ("GetEntityAPI"); // pass other DLLs engine callbacks to function table... - if (api_GetEntityAPI (&g_functionTable, INTERFACE_VERSION) == 0) { - logEntry (true, LL_FATAL, "GetEntityAPI2: ERROR - Not Initialized."); + if (api_GetEntityAPI (&dllapi, INTERFACE_VERSION) == 0) { + util.logEntry (true, LL_FATAL, "GetEntityAPI2: ERROR - Not Initialized."); return FALSE; // error initializing function table!!! } - gameDLLFunc.dllapi_table = &g_functionTable; - gpGamedllFuncs = &gameDLLFunc; + dllfuncs.dllapi_table = &dllapi; + gpGamedllFuncs = &dllfuncs; - memcpy (functionTable, &g_functionTable, sizeof (gamefuncs_t)); + memcpy (functionTable, &dllapi, sizeof (gamefuncs_t)); } - functionTable->pfnGameInit = GameDLLInit; - functionTable->pfnSpawn = Spawn; - functionTable->pfnTouch = Touch; - functionTable->pfnClientConnect = ClientConnect; - functionTable->pfnClientDisconnect = ClientDisconnect; - functionTable->pfnClientUserInfoChanged = ClientUserInfoChanged; - functionTable->pfnClientCommand = ClientCommand; - functionTable->pfnServerActivate = ServerActivate; - functionTable->pfnServerDeactivate = ServerDeactivate; - functionTable->pfnStartFrame = StartFrame; - functionTable->pfnUpdateClientData = UpdateClientData; + functionTable->pfnGameInit = [] (void) { + // this function is a one-time call, and appears to be the second function called in the + // DLL after GiveFntprsToDll() has been called. Its purpose is to tell the MOD DLL to + // initialize the game before the engine actually hooks into it with its video frames and + // clients connecting. Note that it is a different step than the *server* initialization. + // This one is called once, and only once, when the game process boots up before the first + // server is enabled. Here is a good place to do our own game session initialization, and + // to register by the engine side the server commands we need to administrate our bots. + + conf.initWeapons (); + + // register server command(s) + game.registerCmd ("yapb", BotControl::handleEngineCommands); + game.registerCmd ("yb", BotControl::handleEngineCommands); + + // set correct version string + yb_version.set (util.format ("%d.%d.%d", PRODUCT_VERSION_DWORD_INTERNAL, util.buildNumber ())); + + // execute main config + conf.load (true); + + // register fake metamod command handler if we not! under mm + if (!(game.is (GAME_METAMOD))) { + game.registerCmd ("meta", [] (void) { + game.print ("You're launched standalone version of yapb. Metamod is not installed or not enabled!"); + }); + } + conf.adjustWeaponPrices (); + + if (game.is (GAME_METAMOD)) { + RETURN_META (MRES_IGNORED); + } + dllapi.pfnGameInit (); + }; + + functionTable->pfnSpawn = [] (edict_t *ent) { + // this function asks the game DLL to spawn (i.e, give a physical existence in the virtual + // world, in other words to 'display') the entity pointed to by ent in the game. The + // Spawn() function is one of the functions any entity is supposed to have in the game DLL, + // and any MOD is supposed to implement one for each of its entities. + + game.precache (); + + if (game.is (GAME_METAMOD)) { + RETURN_META_VALUE (MRES_IGNORED, 0); + } + int result = dllapi.pfnSpawn (ent); // get result + + if (ent->v.rendermode == kRenderTransTexture) { + ent->v.flags &= ~FL_WORLDBRUSH; // clear the FL_WORLDBRUSH flag out of transparent ents + } + return result; + }; + + functionTable->pfnTouch = [] (edict_t *pentTouched, edict_t *pentOther) { + // this function is called when two entities' bounding boxes enter in collision. For example, + // when a player walks upon a gun, the player entity bounding box collides to the gun entity + // bounding box, and the result is that this function is called. It is used by the game for + // taking the appropriate action when such an event occurs (in our example, the player who + // is walking upon the gun will "pick it up"). Entities that "touch" others are usually + // entities having a velocity, as it is assumed that static entities (entities that don't + // move) will never touch anything. Hence, in our example, the pentTouched will be the gun + // (static entity), whereas the pentOther will be the player (as it is the one moving). When + // the two entities both have velocities, for example two players colliding, this function + // is called twice, once for each entity moving. + + if (!game.isNullEntity (pentTouched) && pentOther != game.getStartEntity ()) { + Bot *bot = bots.getBot (pentTouched); + + if (bot != nullptr && pentOther != bot->ent ()) { + + if (util.isPlayer (pentOther)) { + bot->avoidIncomingPlayers (pentOther); + } + else { + bot->processBreakables (pentOther); + } + } + } + + if (game.is (GAME_METAMOD)) { + RETURN_META (MRES_IGNORED); + } + dllapi.pfnTouch (pentTouched, pentOther); + }; + + functionTable->pfnClientConnect = [] (edict_t *ent, const char *name, const char *addr, char rejectReason[128]) { + // this function is called in order to tell the MOD DLL that a client attempts to connect the + // game. The entity pointer of this client is ent, the name under which he connects is + // pointed to by the pszName pointer, and its IP address string is pointed by the pszAddress + // one. Note that this does not mean this client will actually join the game ; he could as + // well be refused connection by the server later, because of latency timeout, unavailable + // game resources, or whatever reason. In which case the reason why the game DLL (read well, + // the game DLL, *NOT* the engine) refuses this player to connect will be printed in the + // rejectReason string in all letters. Understand that a client connecting process is done + // in three steps. First, the client requests a connection from the server. This is engine + // internals. When there are already too many players, the engine will refuse this client to + // connect, and the game DLL won't even notice. Second, if the engine sees no problem, the + // game DLL is asked. This is where we are. Once the game DLL acknowledges the connection, + // the client downloads the resources it needs and synchronizes its local engine with the one + // of the server. And then, the third step, which comes *AFTER* ClientConnect (), is when the + // client officially enters the game, through the ClientPutInServer () function, later below. + // Here we hook this function in order to keep track of the listen server client entity, + // because a listen server client always connects with a "loopback" address string. Also we + // tell the bot manager to check the bot population, in order to always have one free slot on + // the server for incoming clients. + + // check if this client is the listen server client + if (strcmp (addr, "loopback") == 0) { + game.setLocalEntity (ent); // save the edict of the listen server client... + + // if not dedicated set the default editor for waypoints + if (!game.isDedicated ()) { + waypoints.setEditor (ent); + } + } + + if (game.is (GAME_METAMOD)) { + RETURN_META_VALUE (MRES_IGNORED, 0); + } + return dllapi.pfnClientConnect (ent, name, addr, rejectReason); + }; + + functionTable->pfnClientDisconnect = [] (edict_t *ent) { + // this function is called whenever a client is VOLUNTARILY disconnected from the server, + // either because the client dropped the connection, or because the server dropped him from + // the game (latency timeout). The effect is the freeing of a client slot on the server. Note + // that clients and bots disconnected because of a level change NOT NECESSARILY call this + // function, because in case of a level change, it's a server shutdown, and not a normal + // disconnection. I find that completely stupid, but that's it. Anyway it's time to update + // the bots and players counts, and in case the client disconnecting is a bot, to back its + // brain(s) up to disk. We also try to notice when a listenserver client disconnects, so as + // to reset his entity pointer for safety. There are still a few server frames to go once a + // listen server client disconnects, and we don't want to send him any sort of message then. + + int index = game.indexOfEntity (ent) - 1; + + if (index >= 0 && index < MAX_ENGINE_PLAYERS) { + auto bot = bots.getBot (index); + + // check if its a bot + if (bot != nullptr && bot->pev == &ent->v) { + bot->showChaterIcon (false); + bots.destroy (index); + } + } + if (game.is (GAME_METAMOD)) { + RETURN_META (MRES_IGNORED); + } + dllapi.pfnClientDisconnect (ent); + }; + + functionTable->pfnClientUserInfoChanged = [] (edict_t *ent, char *infobuffer) { + // this function is called when a player changes model, or changes team. Occasionally it + // enforces rules on these changes (for example, some MODs don't want to allow players to + // change their player model). But most commonly, this function is in charge of handling + // team changes, recounting the teams population, etc... + + ctrl.assignAdminRights (ent, infobuffer); + + if (game.is (GAME_METAMOD)) { + RETURN_META (MRES_IGNORED); + } + dllapi.pfnClientUserInfoChanged (ent, infobuffer); + }; + + functionTable->pfnClientCommand = [] (edict_t *ent) { + // this function is called whenever the client whose player entity is ent issues a client + // command. How it works is that clients all have a global string in their client DLL that + // stores the command string; if ever that string is filled with characters, the client DLL + // sends it to the engine as a command to be executed. When the engine has executed that + // command, that string is reset to zero. By the server side, we can access this string + // by asking the engine with the CmdArgv(), CmdArgs() and CmdArgc() functions that work just + // like executable files argument processing work in C (argc gets the number of arguments, + // command included, args returns the whole string, and argv returns the wanted argument + // only). Here is a good place to set up either bot debug commands the listen server client + // could type in his game console, or real new client commands, but we wouldn't want to do + // so as this is just a bot DLL, not a MOD. The purpose is not to add functionality to + // clients. Hence it can lack of commenting a bit, since this code is very subject to change. + + if (ctrl.handleClientCommands (ent)) { + if (game.is (GAME_METAMOD)) { + RETURN_META (MRES_SUPERCEDE); + } + return; + } + + else if (ctrl.handleMenuCommands (ent)) { + if (game.is (GAME_METAMOD)) { + RETURN_META (MRES_SUPERCEDE); + } + return; + } + + // record stuff about radio and chat + bots.captureChatRadio (engfuncs.pfnCmd_Argv (0), engfuncs.pfnCmd_Argv (1), ent); + + if (game.is (GAME_METAMOD)) { + RETURN_META (MRES_IGNORED); + } + dllapi.pfnClientCommand (ent); + }; + + functionTable->pfnServerActivate = [] (edict_t *pentEdictList, int edictCount, int clientMax) { + // this function is called when the server has fully loaded and is about to manifest itself + // on the network as such. Since a mapchange is actually a server shutdown followed by a + // restart, this function is also called when a new map is being loaded. Hence it's the + // perfect place for doing initialization stuff for our bots, such as reading the BSP data, + // loading the bot profiles, and drawing the world map (ie, filling the navigation hashtable). + // Once this function has been called, the server can be considered as "running". + + bots.destroy (); + conf.load (false); // initialize all config files + + // do a level initialization + game.levelInitialize (pentEdictList, edictCount); + + // update worldmodel + illum.resetWorldModel (); + + // do level initialization stuff here... + waypoints.init (); + waypoints.load (); + + // execute main config + conf.load (true); + + if (File::exists (util.format ("%s/maps/%s_yapb.cfg", game.getModName (), game.getMapName ()))) { + game.execCmd ("exec maps/%s_yapb.cfg", game.getMapName ()); + game.print ("Executing Map-Specific config file"); + } + bots.initQuota (); + + if (game.is (GAME_METAMOD)) { + RETURN_META (MRES_IGNORED); + } + dllapi.pfnServerActivate (pentEdictList, edictCount, clientMax); + + waypoints.rebuildVisibility (); + }; + + functionTable->pfnServerDeactivate = [] (void) { + // this function is called when the server is shutting down. A particular note about map + // changes: changing the map means shutting down the server and starting a new one. Of course + // this process is transparent to the user, but either in single player when the hero reaches + // a new level and in multiplayer when it's time for a map change, be aware that what happens + // is that the server actually shuts down and restarts with a new map. Hence we can use this + // function to free and deinit anything which is map-specific, for example we free the memory + // space we m'allocated for our BSP data, since a new map means new BSP data to interpret. In + // any case, when the new map will be booting, ServerActivate() will be called, so we'll do + // the loading of new bots and the new BSP data parsing there. + + // save collected experience on shutdown + waypoints.saveExperience (); + waypoints.saveVisibility (); + + // destroy global killer entity + bots.destroyKillerEntity (); + + // set state to unprecached + game.setUnprecached (); + + // enable lightstyle animations on level change + illum.enableAnimation (true); + + // send message on new map + util.setNeedForWelcome (false); + + // xash is not kicking fakeclients on changelevel + if (game.is (GAME_XASH_ENGINE)) { + bots.kickEveryone (true, false); + bots.destroy (); + } + waypoints.init (); + + if (game.is (GAME_METAMOD)) { + RETURN_META (MRES_IGNORED); + } + dllapi.pfnServerDeactivate (); + }; + + functionTable->pfnStartFrame = [] (void) { + // this function starts a video frame. It is called once per video frame by the game. If + // you run Half-Life at 90 fps, this function will then be called 90 times per second. By + // placing a hook on it, we have a good place to do things that should be done continuously + // during the game, for example making the bots think (yes, because no Think() function exists + // for the bots by the MOD side, remember). Also here we have control on the bot population, + // for example if a new player joins the server, we should disconnect a bot, and if the + // player population decreases, we should fill the server with other bots. + + // run periodic update of bot states + bots.frame (); + + // update lightstyle animations + illum.animateLight (); + + // update some stats for clients + util.updateClients (); + + if (waypoints.hasEditFlag (WS_EDIT_ENABLED) && waypoints.hasEditor ()) { + waypoints.frame (); + } + bots.updateDeathMsgState (false); + + // run stuff periodically + game.slowFrame (); + + if (bots.hasBotsOnline ()) { + // keep track of grenades on map + bots.updateActiveGrenade (); + + // keep track of intresting entities + bots.updateIntrestingEntities (); + } + + // keep bot number up to date + bots.maintainQuota (); + + if (game.is (GAME_METAMOD)) { + RETURN_META (MRES_IGNORED); + } + dllapi.pfnStartFrame (); + + // run the bot ai + bots.slowFrame (); + }; + + functionTable->pfnUpdateClientData = [] (const struct edict_s *ent, int sendweapons, struct clientdata_s *cd) { + extern ConVar yb_latency_display; + + if (game.is (GAME_SUPPORT_SVC_PINGS) && yb_latency_display.integer () == 2 && bots.hasBotsOnline ()) { + bots.sendPingOffsets (const_cast (ent)); + } + + if (game.is (GAME_METAMOD)) { + RETURN_META (MRES_IGNORED); + } + dllapi.pfnUpdateClientData (ent, sendweapons, cd); + }; functionTable->pfnPM_Move = [] (playermove_t *playerMove, int server) { // this is the player movement code clients run to predict things when the server can't update @@ -2876,12 +447,12 @@ SHARED_LIBRARAY_EXPORT int GetEntityAPI2 (gamefuncs_t *functionTable, int *) { // moving players. There is normally no distinction between them, else client-side prediction // wouldn't work properly (and it doesn't work that well, already...) - illum.setWorldModel (playerMove->physents[0u].model); + illum.setWorldModel (playerMove->physents[0].model); - if (g_gameFlags & GAME_METAMOD) { + if (game.is (GAME_METAMOD)) { RETURN_META (MRES_IGNORED); } - g_functionTable.pfnPM_Move (playerMove, server); + dllapi.pfnPM_Move (playerMove, server); }; return TRUE; } @@ -2899,9 +470,45 @@ SHARED_LIBRARAY_EXPORT int GetEntityAPI2_Post (gamefuncs_t *functionTable, int * memset (functionTable, 0, sizeof (gamefuncs_t)); - functionTable->pfnSpawn = Spawn_Post; - functionTable->pfnStartFrame = StartFrame_Post; - functionTable->pfnServerActivate = ServerActivate_Post; + functionTable->pfnSpawn = [] (edict_t *ent) { + // this function asks the game DLL to spawn (i.e, give a physical existence in the virtual + // world, in other words to 'display') the entity pointed to by ent in the game. The + // Spawn() function is one of the functions any entity is supposed to have in the game DLL, + // and any MOD is supposed to implement one for each of its entities. Post version called + // only by metamod. + + // solves the bots unable to see through certain types of glass bug. + if (ent->v.rendermode == kRenderTransTexture) { + ent->v.flags &= ~FL_WORLDBRUSH; // clear the FL_WORLDBRUSH flag out of transparent ents + } + RETURN_META_VALUE (MRES_IGNORED, 0); + }; + + functionTable->pfnStartFrame = [] (void) { + // this function starts a video frame. It is called once per video frame by the game. If + // you run Half-Life at 90 fps, this function will then be called 90 times per second. By + // placing a hook on it, we have a good place to do things that should be done continuously + // during the game, for example making the bots think (yes, because no Think() function exists + // for the bots by the MOD side, remember). Post version called only by metamod. + + // run the bot ai + bots.slowFrame (); + RETURN_META (MRES_IGNORED); + }; + + functionTable->pfnServerActivate = [] (edict_t *, int, int) { + // this function is called when the server has fully loaded and is about to manifest itself + // on the network as such. Since a mapchange is actually a server shutdown followed by a + // restart, this function is also called when a new map is being loaded. Hence it's the + // perfect place for doing initialization stuff for our bots, such as reading the BSP data, + // loading the bot profiles, and drawing the world map (ie, filling the navigation hashtable). + // Once this function has been called, the server can be considered as "running". Post version + // called only by metamod. + + waypoints.rebuildVisibility (); + + RETURN_META (MRES_IGNORED); + }; return TRUE; } @@ -2913,56 +520,388 @@ SHARED_LIBRARAY_EXPORT int GetNewDLLFunctions (newgamefuncs_t *functionTable, in // pass them too, else the DLL interfacing wouldn't be complete and the game possibly wouldn't // run properly. - auto api_GetNewDLLFunctions = g_gameLib.resolve ("GetNewDLLFunctions"); + auto api_GetNewDLLFunctions = game.getLib ().resolve ("GetNewDLLFunctions"); if (api_GetNewDLLFunctions == nullptr) { return FALSE; } if (!api_GetNewDLLFunctions (functionTable, interfaceVersion)) { - logEntry (true, LL_ERROR, "GetNewDLLFunctions: ERROR - Not Initialized."); + util.logEntry (true, LL_ERROR, "GetNewDLLFunctions: ERROR - Not Initialized."); return FALSE; } - gameDLLFunc.newapi_table = functionTable; + dllfuncs.newapi_table = functionTable; return TRUE; } SHARED_LIBRARAY_EXPORT int GetEngineFunctions (enginefuncs_t *functionTable, int *) { - if (g_gameFlags & GAME_METAMOD) { + if (game.is (GAME_METAMOD)) { memset (functionTable, 0, sizeof (enginefuncs_t)); } - functionTable->pfnChangeLevel = pfnChangeLevel; - functionTable->pfnFindEntityByString = pfnFindEntityByString; - functionTable->pfnEmitSound = pfnEmitSound; - functionTable->pfnClientCommand = pfnClientCommand; - functionTable->pfnMessageBegin = pfnMessageBegin; - functionTable->pfnMessageEnd = pfnMessageEnd; - functionTable->pfnWriteByte = pfnWriteByte; - functionTable->pfnWriteChar = pfnWriteChar; - functionTable->pfnWriteShort = pfnWriteShort; - functionTable->pfnWriteLong = pfnWriteLong; - functionTable->pfnWriteAngle = pfnWriteAngle; - functionTable->pfnWriteCoord = pfnWriteCoord; - functionTable->pfnWriteString = pfnWriteString; - functionTable->pfnWriteEntity = pfnWriteEntity; - functionTable->pfnRegUserMsg = pfnRegUserMsg; - functionTable->pfnClientPrintf = pfnClientPrintf; - functionTable->pfnCmd_Args = pfnCmd_Args; - functionTable->pfnCmd_Argv = pfnCmd_Argv; - functionTable->pfnCmd_Argc = pfnCmd_Argc; - functionTable->pfnSetClientMaxspeed = pfnSetClientMaxspeed; - functionTable->pfnAlertMessage = pfnAlertMessage; - + functionTable->pfnChangeLevel = [] (char *s1, char *s2) { + // the purpose of this function is to ask the engine to shutdown the server and restart a + // new one running the map whose name is s1. It is used ONLY IN SINGLE PLAYER MODE and is + // transparent to the user, because it saves the player state and equipment and restores it + // back in the new level. The "changelevel trigger point" in the old level is linked to the + // new level's spawn point using the s2 string, which is formatted as follows: "trigger_name + // to spawnpoint_name", without spaces (for example, "tr_1atotr_2lm" would tell the engine + // the player has reached the trigger point "tr_1a" and has to spawn in the next level on the + // spawn point named "tr_2lm". + + // save collected experience on map change + waypoints.saveExperience (); + waypoints.saveVisibility (); + + if (game.is (GAME_METAMOD)) { + RETURN_META (MRES_IGNORED); + } + engfuncs.pfnChangeLevel (s1, s2); + }; + + functionTable->pfnLightStyle = [] (int style, char *val) { + // ths function update lightstyle for the bots + + illum.updateLight (style, val); + + if (game.is (GAME_METAMOD)) { + RETURN_META (MRES_IGNORED); + } + 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 (GAME_LEGACY)) && strcmp (value, "info_map_parameters") == 0) { + bots.initRound (); + } + + if (game.is (GAME_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 + // which fileName is "sample", at level "channel" (CHAN_VOICE, etc...), with "volume" as + // loudness multiplicator (normal volume VOL_NORM is 1.0), with a pitch of "pitch" (normal + // pitch PITCH_NORM is 100.0), and that this sound has to be attenuated by distance in air + // according to the value of "attenuation" (normal attenuation ATTN_NORM is 0.8 ; ATTN_NONE + // means no attenuation with distance). Optionally flags "fFlags" can be passed, which I don't + // know the heck of the purpose. After we tell the engine to emit the sound, we have to call + // SoundAttachToThreat() to bring the sound to the ears of the bots. Since bots have no client DLL + // to handle this for them, such a job has to be done manually. + + util.attachSoundsToClients (entity, sample, volume); + + if (game.is (GAME_METAMOD)) { + RETURN_META (MRES_IGNORED); + } + engfuncs.pfnEmitSound (entity, channel, sample, volume, attenuation, flags, pitch); + }; + + functionTable->pfnMessageBegin = [] (int msgDest, int msgType, const float *origin, edict_t *ed) { + // this function called each time a message is about to sent. + + game.beginMessage (ed, msgDest, msgType); + + if (game.is (GAME_METAMOD)) { + RETURN_META (MRES_IGNORED); + } + engfuncs.pfnMessageBegin (msgDest, msgType, origin, ed); + }; + + functionTable->pfnMessageEnd = [] (void) { + game.resetMessages (); + + if (game.is (GAME_METAMOD)) { + RETURN_META (MRES_IGNORED); + } + engfuncs.pfnMessageEnd (); + + // send latency fix + bots.sendDeathMsgFix (); + }; + + functionTable->pfnWriteByte = [] (int value) { + // if this message is for a bot, call the client message function... + game.processMessages ((void *) &value); + + if (game.is (GAME_METAMOD)) { + RETURN_META (MRES_IGNORED); + } + engfuncs.pfnWriteByte (value); + }; + + functionTable->pfnWriteChar = [] (int value) { + // if this message is for a bot, call the client message function... + game.processMessages ((void *) &value); + + if (game.is (GAME_METAMOD)) { + RETURN_META (MRES_IGNORED); + } + engfuncs.pfnWriteChar (value); + }; + + functionTable->pfnWriteShort = [] (int value) { + // if this message is for a bot, call the client message function... + game.processMessages ((void *) &value); + + if (game.is (GAME_METAMOD)) { + RETURN_META (MRES_IGNORED); + } + engfuncs.pfnWriteShort (value); + }; + + functionTable->pfnWriteLong = [] (int value) { + // if this message is for a bot, call the client message function... + game.processMessages ((void *) &value); + + if (game.is (GAME_METAMOD)) { + RETURN_META (MRES_IGNORED); + } + engfuncs.pfnWriteLong (value); + }; + + functionTable->pfnWriteAngle = [] (float value) { + // if this message is for a bot, call the client message function... + game.processMessages ((void *) &value); + + if (game.is (GAME_METAMOD)) { + RETURN_META (MRES_IGNORED); + } + engfuncs.pfnWriteAngle (value); + }; + + functionTable->pfnWriteCoord = [] (float value) { + // if this message is for a bot, call the client message function... + game.processMessages ((void *) &value); + + if (game.is (GAME_METAMOD)) { + RETURN_META (MRES_IGNORED); + } + engfuncs.pfnWriteCoord (value); + }; + + functionTable->pfnWriteString = [] (const char *sz) { + // if this message is for a bot, call the client message function... + game.processMessages ((void *) sz); + + if (game.is (GAME_METAMOD)) { + RETURN_META (MRES_IGNORED); + } + engfuncs.pfnWriteString (sz); + }; + + functionTable->pfnWriteEntity = [] (int value) { + // if this message is for a bot, call the client message function... + game.processMessages ((void *) &value); + + if (game.is (GAME_METAMOD)) { + RETURN_META (MRES_IGNORED); + } + engfuncs.pfnWriteEntity (value); + }; + + functionTable->pfnRegUserMsg = [] (const char *name, int size) { + // this function registers a "user message" by the engine side. User messages are network + // messages the game DLL asks the engine to send to clients. Since many MODs have completely + // different client features (Counter-Strike has a radar and a timer, for example), network + // messages just can't be the same for every MOD. Hence here the MOD DLL tells the engine, + // "Hey, you have to know that I use a network message whose name is pszName and it is size + // packets long". The engine books it, and returns the ID number under which he recorded that + // custom message. Thus every time the MOD DLL will be wanting to send a message named pszName + // using pfnMessageBegin (), it will know what message ID number to send, and the engine will + // know what to do, only for non-metamod version + + if (game.is (GAME_METAMOD)) { + RETURN_META_VALUE (MRES_IGNORED, 0); + } + int message = engfuncs.pfnRegUserMsg (name, size); + + if (strcmp (name, "VGUIMenu") == 0) { + game.setMessageId (NETMSG_VGUI, message); + } + else if (strcmp (name, "ShowMenu") == 0) { + game.setMessageId (NETMSG_SHOWMENU, message); + } + else if (strcmp (name, "WeaponList") == 0) { + game.setMessageId (NETMSG_WEAPONLIST, message); + } + else if (strcmp (name, "CurWeapon") == 0) { + game.setMessageId (NETMSG_CURWEAPON, message); + } + else if (strcmp (name, "AmmoX") == 0) { + game.setMessageId (NETMSG_AMMOX, message); + } + else if (strcmp (name, "AmmoPickup") == 0) { + game.setMessageId (NETMSG_AMMOPICKUP, message); + } + else if (strcmp (name, "Damage") == 0) { + game.setMessageId (NETMSG_DAMAGE, message); + } + else if (strcmp (name, "Money") == 0) { + game.setMessageId (NETMSG_MONEY, message); + } + else if (strcmp (name, "StatusIcon") == 0) { + game.setMessageId (NETMSG_STATUSICON, message); + } + else if (strcmp (name, "DeathMsg") == 0) { + game.setMessageId (NETMSG_DEATH, message); + } + else if (strcmp (name, "ScreenFade") == 0) { + game.setMessageId (NETMSG_SCREENFADE, message); + } + else if (strcmp (name, "HLTV") == 0) { + game.setMessageId (NETMSG_HLTV, message); + } + else if (strcmp (name, "TextMsg") == 0) { + game.setMessageId (NETMSG_TEXTMSG, message); + } + else if (strcmp (name, "TeamInfo") == 0) { + game.setMessageId (NETMSG_TEAMINFO, message); + } + else if (strcmp (name, "BarTime") == 0) { + game.setMessageId (NETMSG_BARTIME, message); + } + else if (strcmp (name, "SendAudio") == 0) { + game.setMessageId (NETMSG_SENDAUDIO, message); + } + else if (strcmp (name, "SayText") == 0) { + game.setMessageId (NETMSG_SAYTEXT, message); + } + else if (strcmp (name, "BotVoice") == 0) { + game.setMessageId (NETMSG_BOTVOICE, message); + } + else if (strcmp (name, "NVGToggle") == 0) { + game.setMessageId (NETMSG_NVGTOGGLE, message); + } + else if (strcmp (name, "FlashBat") == 0) { + game.setMessageId (NETMSG_FLASHBAT, message); + } + else if (strcmp (name, "Flashlight") == 0) { + game.setMessageId (NETMSG_FLASHLIGHT, message); + } + else if (strcmp (name, "ItemStatus") == 0) { + game.setMessageId (NETMSG_ITEMSTATUS, message); + } + return message; + }; + + functionTable->pfnClientPrintf = [] (edict_t *ent, PRINT_TYPE printType, const char *message) { + // this function prints the text message string pointed to by message by the client side of + // the client entity pointed to by ent, in a manner depending of printType (print_console, + // print_center or print_chat). Be certain never to try to feed a bot with this function, + // as it will crash your server. Why would you, anyway ? bots have no client DLL as far as + // we know, right ? But since stupidity rules this world, we do a preventive check :) + + if (util.isFakeClient (ent)) { + if (game.is (GAME_METAMOD)) { + RETURN_META (MRES_SUPERCEDE); + } + return; + } + + if (game.is (GAME_METAMOD)) { + RETURN_META (MRES_IGNORED); + } + engfuncs.pfnClientPrintf (ent, printType, message); + }; + + functionTable->pfnCmd_Args = [] (void) { + // this function returns a pointer to the whole current client command string. Since bots + // have no client DLL and we may want a bot to execute a client command, we had to implement + // a argv string in the bot DLL for holding the bots' commands, and also keep track of the + // argument count. Hence this hook not to let the engine ask an unexistent client DLL for a + // command we are holding here. Of course, real clients commands are still retrieved the + // normal way, by asking the game. + + // is this a bot issuing that client command? + if (game.isBotCmd ()) { + if (game.is (GAME_METAMOD)) { + RETURN_META_VALUE (MRES_SUPERCEDE, game.botArgs ()); + } + return game.botArgs (); // else return the whole bot client command string we know + } + + if (game.is (GAME_METAMOD)) { + RETURN_META_VALUE (MRES_IGNORED, static_cast (nullptr)); + } + return engfuncs.pfnCmd_Args (); // ask the client command string to the engine + }; + + functionTable->pfnCmd_Argv = [] (int argc) { + // this function returns a pointer to a certain argument of the current client command. Since + // bots have no client DLL and we may want a bot to execute a client command, we had to + // implement a argv string in the bot DLL for holding the bots' commands, and also keep + // track of the argument count. Hence this hook not to let the engine ask an unexistent client + // DLL for a command we are holding here. Of course, real clients commands are still retrieved + // the normal way, by asking the game. + + // is this a bot issuing that client command? + if (game.isBotCmd ()) { + if (game.is (GAME_METAMOD)) { + RETURN_META_VALUE (MRES_SUPERCEDE, game.botArgv (argc)); + } + return game.botArgv (argc); // if so, then return the wanted argument we know + } + + if (game.is (GAME_METAMOD)) { + RETURN_META_VALUE (MRES_IGNORED, static_cast (nullptr)); + } + return engfuncs.pfnCmd_Argv (argc); // ask the argument number "argc" to the engine + }; + + functionTable->pfnCmd_Argc = [] (void) { + // this function returns the number of arguments the current client command string has. Since + // bots have no client DLL and we may want a bot to execute a client command, we had to + // implement a argv string in the bot DLL for holding the bots' commands, and also keep + // track of the argument count. Hence this hook not to let the engine ask an unexistent client + // DLL for a command we are holding here. Of course, real clients commands are still retrieved + // the normal way, by asking the game. + + // is this a bot issuing that client command? + if (game.isBotCmd ()) { + if (game.is (GAME_METAMOD)) { + RETURN_META_VALUE (MRES_SUPERCEDE, game.botArgc ()); + } + return game.botArgc (); // if so, then return the argument count we know + } + + if (game.is (GAME_METAMOD)) { + RETURN_META_VALUE (MRES_IGNORED, 0); + } + return engfuncs.pfnCmd_Argc (); // ask the engine how many arguments there are + }; + + functionTable->pfnSetClientMaxspeed = [] (const edict_t *ent, float newMaxspeed) { + Bot *bot = bots.getBot (const_cast (ent)); + + // check wether it's not a bot + if (bot != nullptr) { + bot->pev->maxspeed = newMaxspeed; + } + + if (game.is (GAME_METAMOD)) { + RETURN_META (MRES_IGNORED); + } + engfuncs.pfnSetClientMaxspeed (ent, newMaxspeed); + }; return TRUE; } SHARED_LIBRARAY_EXPORT int GetEngineFunctions_Post (enginefuncs_t *functionTable, int *) { memset (functionTable, 0, sizeof (enginefuncs_t)); - functionTable->pfnMessageEnd = pfnMessageEnd_Post; + functionTable->pfnMessageEnd = [] (void) { + // send latency fix + bots.sendDeathMsgFix (); + + RETURN_META (MRES_IGNORED); + }; return TRUE; } @@ -2971,7 +910,7 @@ SHARED_LIBRARAY_EXPORT int Server_GetBlendingInterface (int version, void **ppin // of the body move, which bones, which hitboxes and how) between the server and the game DLL. // some MODs can be using a different hitbox scheme than the standard one. - auto api_GetBlendingInterface = g_gameLib.resolve ("Server_GetBlendingInterface"); + auto api_GetBlendingInterface = game.getLib ().resolve ("Server_GetBlendingInterface"); if (api_GetBlendingInterface == nullptr) { return FALSE; @@ -3020,7 +959,7 @@ SHARED_LIBRARAY_EXPORT int Meta_Detach (PLUG_LOADTIME, PL_UNLOAD_REASON) { // to prevent unloading the plugin if its processing should not be interrupted. bots.kickEveryone (true); // kick all bots off this server - cleanupGarbage (); + waypoints.init (); return TRUE; } @@ -3029,107 +968,11 @@ SHARED_LIBRARAY_EXPORT void Meta_Init (void) { // this function is called by metamod, before any other interface functions. Purpose of this // function to give plugin a chance to determine is plugin running under metamod or not. - g_gameFlags |= GAME_METAMOD; -} - -bool loadCSBinary (void) { - const char *modname = engine.getModName (); - - if (!modname) { - return false; - } - -#if defined(PLATFORM_WIN32) - const char *libs[] = {"mp.dll", "cs.dll"}; -#elif defined(PLATFORM_LINUX) - const char *libs[] = {"cs.so", "cs_i386.so"}; -#elif defined(PLATFORM_OSX) - const char *libs[] = {"cs.dylib"}; -#endif - - auto libCheck = [] (const char *modname, const char *dll) { - // try to load gamedll - if (!g_gameLib.isValid ()) { - logEntry (true, LL_FATAL | LL_IGNORE, "Unable to load gamedll \"%s\". Exiting... (gamedir: %s)", dll, modname); - - return false; - } - auto ent = g_gameLib.resolve ("trigger_random_unique"); - - // detect regamedll by addon entity they provide - if (ent != nullptr) { - g_gameFlags |= GAME_REGAMEDLL; - } - return true; - }; - - // search the libraries inside game dlls directory - for (size_t i = 0; i < cr::arrsize (libs); i++) { - auto *path = format ("%s/dlls/%s", modname, libs[i]); - - // if we can't read file, skip it - if (!File::exists (path)) { - continue; - } - - // special case, czero is always detected first, as it's has custom directory - if (strcmp (modname, "czero") == 0) { - g_gameFlags |= (GAME_CZERO | GAME_SUPPORT_BOT_VOICE | GAME_SUPPORT_SVC_PINGS); - - if (g_gameFlags & GAME_METAMOD) { - return false; - } - g_gameLib.load (path); - - // verify dll is OK - if (!libCheck (modname, libs[i])) { - return false; - } - return true; - } - else { - g_gameLib.load (path); - - // verify dll is OK - if (!libCheck (modname, libs[i])) { - return false; - } - - // detect if we're running modern game - auto entity = g_gameLib.resolve ("weapon_famas"); - - // detect xash engine - if (g_engfuncs.pfnCVarGetPointer ("build") != nullptr) { - g_gameFlags |= (GAME_LEGACY | GAME_XASH_ENGINE); - - if (entity != nullptr) { - g_gameFlags |= GAME_SUPPORT_BOT_VOICE; - } - - if (g_gameFlags & GAME_METAMOD) { - return false; - } - return true; - } - - if (entity != nullptr) { - g_gameFlags |= (GAME_CSTRIKE16 | GAME_SUPPORT_BOT_VOICE | GAME_SUPPORT_SVC_PINGS); - } - else { - g_gameFlags |= GAME_LEGACY; - } - - if (g_gameFlags & GAME_METAMOD) { - return false; - } - return true; - } - } - return false; + game.addGameFlag (GAME_METAMOD); } DLL_GIVEFNPTRSTODLL GiveFnptrsToDll (enginefuncs_t *functionTable, globalvars_t *pGlobals) { - // this is the very first function that is called in the game DLL by the engine. Its purpose + // this is the very first function that is called in the game DLL by the game. Its purpose // is to set the functions interfacing up, by exchanging the functionTable function list // along with a pointer to the engine's global variables structure pGlobals, with the game // DLL. We can there decide if we want to load the normal game DLL just behind our bot DLL, @@ -3140,102 +983,14 @@ DLL_GIVEFNPTRSTODLL GiveFnptrsToDll (enginefuncs_t *functionTable, globalvars_t // such if necessary. Nothing really bot-related is done in this function. The actual bot // initialization stuff will be done later, when we'll be certain to have a multilayer game. - // get the engine functions from the engine... - memcpy (&g_engfuncs, functionTable, sizeof (enginefuncs_t)); - g_pGlobals = pGlobals; + // get the engine functions from the game... + memcpy (&engfuncs, functionTable, sizeof (enginefuncs_t)); + globals = pGlobals; - // register our cvars - engine.pushRegStackToEngine (); - - // ensure we're have all needed directories - { - const char *mod = engine.getModName (); - - // create the needed paths - File::pathCreate (const_cast (format ("%s/addons/yapb/conf/lang", mod))); - File::pathCreate (const_cast (format ("%s/addons/yapb/data/learned", mod))); - } - - // print game detection info - auto printDetectedGame = [] (void) { - String gameVersionStr; - - if (g_gameFlags & GAME_LEGACY) { - gameVersionStr.assign ("Legacy"); - } - else if (g_gameFlags & GAME_CZERO) { - gameVersionStr.assign ("Condition Zero"); - } - else if (g_gameFlags & GAME_CSTRIKE16) { - gameVersionStr.assign ("v1.6"); - } - - if (g_gameFlags & GAME_XASH_ENGINE) { - gameVersionStr.append (" @ Xash3D Engine"); - - if (g_gameFlags & GAME_MOBILITY) { - gameVersionStr.append (" Mobile"); - } - gameVersionStr.replace ("Legacy", "1.6 Limited"); - } - - if (g_gameFlags & GAME_SUPPORT_BOT_VOICE) { - gameVersionStr.append (" (BV)"); -} - - if (g_gameFlags & GAME_REGAMEDLL) { - gameVersionStr.append (" (RE)"); - } - - if (g_gameFlags & GAME_SUPPORT_SVC_PINGS) { - gameVersionStr.append (" (SVC)"); - } - engine.print ("[YAPB] Bot v%s.0.%d Loaded. Game detected as Counter-Strike: %s", PRODUCT_VERSION, buildNumber (), gameVersionStr.chars ()); - }; - -#ifdef PLATFORM_ANDROID - g_gameFlags |= (GAME_XASH_ENGINE | GAME_MOBILITY | GAME_SUPPORT_BOT_VOICE | GAME_REGAMEDLL); - - if (g_gameFlags & GAME_METAMOD) { - return; // we should stop the attempt for loading the real gamedll, since metamod handle this for us - } - - extern ConVar yb_difficulty; - yb_difficulty.set (2); - -#ifdef LOAD_HARDFP - const char *serverDLL = "libserver_hardfp.so"; -#else - const char *serverDLL = "libserver.so"; -#endif - - char gameDLLName[256]; - snprintf (gameDLLName, cr::bufsize (gameDLLName), "%s/%s", getenv ("XASH3D_GAMELIBDIR"), serverDLL); - - g_gameLib = new Library (gameDLLName); - - if (!g_gameLib->isValid ()) { - logEntry (true, LL_FATAL | LL_IGNORE, "Unable to load gamedll \"%s\". Exiting... (gamedir: %s)", gameDLLName, engine.getModName ()); - delete g_gameLib; - } - printDetectedGame (); - -#else - bool binaryLoaded = loadCSBinary (); - - if (!binaryLoaded && !(g_gameFlags & GAME_METAMOD)) { - logEntry (true, LL_FATAL | LL_IGNORE, "Mod that you has started, not supported by this bot (gamedir: %s)", engine.getModName ()); + if (game.postload ()) { return; } - printDetectedGame (); - - if (g_gameFlags & GAME_METAMOD) { - return; - } - -#endif - - auto api_GiveFnptrsToDll = g_gameLib.resolve ("GiveFnptrsToDll"); + auto api_GiveFnptrsToDll = game.getLib ().resolve ("GiveFnptrsToDll"); assert (api_GiveFnptrsToDll != nullptr); GetEngineFunctions (functionTable, nullptr); @@ -3251,15 +1006,14 @@ DLL_ENTRYPOINT { // dynamic library detaching ?? if (DLL_DETACHING) { - cleanupGarbage (); // free everything that's freeable - g_gameLib.unload (); // if dynamic link library of mod is load, free it + waypoints.init (); // free everything that's freeable } DLL_RETENTRY; // the return data type is OS specific too } -void helper_LinkEntity (entity_func_t &addr, const char *name, entvars_t *pev) { +void helper_LinkEntity (EntityFunction &addr, const char *name, entvars_t *pev) { if (addr == nullptr) { - addr = g_gameLib.resolve (name); + addr = game.getLib ().resolve (name); } if (addr == nullptr) { @@ -3270,7 +1024,7 @@ void helper_LinkEntity (entity_func_t &addr, const char *name, entvars_t *pev) { #define LINK_ENTITY(entityName) \ SHARED_LIBRARAY_EXPORT void entityName (entvars_t *pev) { \ - static entity_func_t addr; \ + static EntityFunction addr; \ helper_LinkEntity (addr, #entityName, pev); \ } diff --git a/source/manager.cpp b/source/manager.cpp index be703e5..810780f 100644 --- a/source/manager.cpp +++ b/source/manager.cpp @@ -18,6 +18,7 @@ ConVar yb_think_fps ("yb_think_fps", "30.0"); ConVar yb_join_after_player ("yb_join_after_player", "0"); ConVar yb_join_team ("yb_join_team", "any"); +ConVar yb_join_delay ("yb_join_delay", "5.0"); ConVar yb_name_prefix ("yb_name_prefix", ""); ConVar yb_difficulty ("yb_difficulty", "4"); @@ -25,8 +26,14 @@ ConVar yb_difficulty ("yb_difficulty", "4"); ConVar yb_latency_display ("yb_latency_display", "2"); ConVar yb_avatar_display ("yb_avatar_display", "1"); +ConVar yb_language ("yb_language", "en"); +ConVar yb_ignore_cvars_on_changelevel ("yb_ignore_cvars_on_changelevel", "yb_quota,yb_autovacate"); + ConVar mp_limitteams ("mp_limitteams", nullptr, VT_NOREGISTER); ConVar mp_autoteambalance ("mp_autoteambalance", nullptr, VT_NOREGISTER); +ConVar mp_roundtime ("mp_roundtime", nullptr, VT_NOREGISTER); +ConVar mp_timelimit ("mp_timelimit", nullptr, VT_NOREGISTER); +ConVar mp_freezetime ("mp_freezetime", nullptr, VT_NOREGISTER, true, "0"); BotManager::BotManager (void) { // this is a bot manager class constructor @@ -47,30 +54,33 @@ BotManager::BotManager (void) { m_activeGrenades.reserve (16); m_intrestingEntities.reserve (128); + m_filters.reserve (TASK_MAX); + + initFilters (); } BotManager::~BotManager (void) { - // this is a bot manager class destructor, do not use engine.MaxClients () here !! + // this is a bot manager class destructor, do not use game.MaxClients () here !! destroy (); } void BotManager::createKillerEntity (void) { // this function creates single trigger_hurt for using in Bot::Kill, to reduce lags, when killing all the bots - m_killerEntity = g_engfuncs.pfnCreateNamedEntity (MAKE_STRING ("trigger_hurt")); + m_killerEntity = engfuncs.pfnCreateNamedEntity (MAKE_STRING ("trigger_hurt")); m_killerEntity->v.dmg = 9999.0f; m_killerEntity->v.dmg_take = 1.0f; m_killerEntity->v.dmgtime = 2.0f; m_killerEntity->v.effects |= EF_NODRAW; - g_engfuncs.pfnSetOrigin (m_killerEntity, Vector (-99999.0f, -99999.0f, -99999.0f)); + engfuncs.pfnSetOrigin (m_killerEntity, Vector (-99999.0f, -99999.0f, -99999.0f)); MDLL_Spawn (m_killerEntity); } void BotManager::destroyKillerEntity (void) { - if (!engine.isNullEntity (m_killerEntity)) { - g_engfuncs.pfnRemoveEntity (m_killerEntity); + if (!game.isNullEntity (m_killerEntity)) { + engfuncs.pfnRemoveEntity (m_killerEntity); } } @@ -81,22 +91,23 @@ void BotManager::touchKillerEntity (Bot *bot) { return; } - if (engine.isNullEntity (m_killerEntity)) { + if (game.isNullEntity (m_killerEntity)) { createKillerEntity (); - if (engine.isNullEntity (m_killerEntity)) { + if (game.isNullEntity (m_killerEntity)) { MDLL_ClientKill (bot->ent ()); return; } } + const auto &prop = conf.getWeaponProp (bot->m_currentWeapon); - m_killerEntity->v.classname = MAKE_STRING (g_weaponDefs[bot->m_currentWeapon].className); + m_killerEntity->v.classname = MAKE_STRING (prop.classname); m_killerEntity->v.dmg_inflictor = bot->ent (); KeyValueData kv; - kv.szClassName = const_cast (g_weaponDefs[bot->m_currentWeapon].className); + kv.szClassName = const_cast (prop.classname); kv.szKeyName = "damagetype"; - kv.szValue = const_cast (format ("%d", (1 << 4))); + kv.szValue = const_cast (util.format ("%d", cr::bit (4))); kv.fHandled = FALSE; MDLL_KeyValue (m_killerEntity, &kv); @@ -109,7 +120,7 @@ extern "C" void player (entvars_t *pev); void BotManager::execGameEntity (entvars_t *vars) { // this function calls gamedll player() function, in case to create player entity in game - if (g_gameFlags & GAME_METAMOD) { + if (game.is (GAME_METAMOD)) { CALL_GAME_ENTITY (PLID, "player", vars); return; } @@ -125,17 +136,17 @@ BotCreationResult BotManager::create (const String &name, int difficulty, int pe // do not allow create bots when there is no waypoints if (!waypoints.length ()) { - engine.centerPrint ("Map is not waypointed. Cannot create bot"); + ctrl.msg ("Map is not waypointed. Cannot create bot"); return BOT_RESULT_NAV_ERROR; } // don't allow creating bots with changed waypoints (distance tables are messed up) else if (waypoints.hasChanged ()) { - engine.centerPrint ("Waypoints have been changed. Load waypoints again..."); + ctrl.msg ("Waypoints have been changed. Load waypoints again..."); return BOT_RESULT_NAV_ERROR; } else if (team != -1 && isTeamStacked (team - 1)) { - engine.centerPrint ("Desired team is stacked. Unable to proceed with bot creation"); + ctrl.msg ("Desired team is stacked. Unable to proceed with bot creation"); return BOT_RESULT_TEAM_STACKED; } if (difficulty < 0 || difficulty > 4) { @@ -148,11 +159,11 @@ BotCreationResult BotManager::create (const String &name, int difficulty, int pe } if (personality < PERSONALITY_NORMAL || personality > PERSONALITY_CAREFUL) { - if (rng.getInt (0, 100) < 50) { + if (rng.chance (5)) { personality = PERSONALITY_NORMAL; } else { - if (rng.getInt (0, 100) < 50) { + if (rng.chance (50)) { personality = PERSONALITY_RUSHER; } else { @@ -166,45 +177,34 @@ BotCreationResult BotManager::create (const String &name, int difficulty, int pe // setup name if (name.empty ()) { - if (!g_botNames.empty ()) { - bool nameFound = false; + botName = conf.pickBotName (); - for (int i = 0; i < MAX_ENGINE_PLAYERS * 4; i++) { - if (nameFound) { - break; - } - botName = &g_botNames.random (); - - if (botName->name.length () < 3 || botName->usedBy != 0) { - continue; - } - nameFound = true; - - resultName = botName->name; - steamId = botName->steamId; - } + if (botName) { + resultName = botName->name; + steamId = botName->steamId; + } + else { + resultName.assign ("yapb_%d.%d", rng.getInt (0, 10), rng.getInt (0, 10)); // just pick ugly random name } - else - resultName.format ("yapb_%d.%d", rng.getInt (0, 10), rng.getInt (0, 10)); // just pick ugly random name } else { resultName = name; } - if (!isEmptyStr (yb_name_prefix.str ())) { + if (!util.isEmptyStr (yb_name_prefix.str ())) { String prefixed; // temp buffer for storing modified name - prefixed.format ("%s %s", yb_name_prefix.str (), resultName.chars ()); + prefixed.assign ("%s %s", yb_name_prefix.str (), resultName.chars ()); // buffer has been modified, copy to real name resultName = cr::move (prefixed); } - bot = g_engfuncs.pfnCreateFakeClient (resultName.chars ()); + bot = engfuncs.pfnCreateFakeClient (resultName.chars ()); - if (engine.isNullEntity (bot)) { - engine.centerPrint ("Maximum players reached (%d/%d). Unable to create Bot.", engine.maxClients (), engine.maxClients ()); + if (game.isNullEntity (bot)) { + ctrl.msg ("Maximum players reached (%d/%d). Unable to create Bot.", game.maxClients (), game.maxClients ()); return BOT_RESULT_MAX_PLAYERS_REACHED; } - int index = engine.indexOfEntity (bot) - 1; + int index = game.indexOfEntity (bot) - 1; // ensure it free destroy (index); @@ -218,17 +218,17 @@ BotCreationResult BotManager::create (const String &name, int difficulty, int pe if (botName != nullptr) { botName->usedBy = m_bots[index]->index (); } - engine.print ("Connecting Bot..."); + ctrl.msg ("Connecting Bot..."); return BOT_RESULT_CREATED; } int BotManager::index (edict_t *ent) { // this function returns index of bot (using own bot array) - if (engine.isNullEntity (ent)) { + if (game.isNullEntity (ent)) { return -1; } - int index = engine.indexOfEntity (ent) - 1; + int index = game.indexOfEntity (ent) - 1; if (index < 0 || index >= MAX_ENGINE_PLAYERS) { return -1; @@ -264,11 +264,11 @@ Bot *BotManager::getAliveBot (void) { IntArray result; - for (int i = 0; i < engine.maxClients (); i++) { + for (int i = 0; i < game.maxClients (); i++) { if (result.length () > 4) { break; } - if (m_bots[i] != nullptr && isAlive (m_bots[i]->ent ())) { + if (m_bots[i] != nullptr && util.isAlive (m_bots[i]->ent ())) { result.push (i); } } @@ -279,14 +279,14 @@ Bot *BotManager::getAliveBot (void) { return nullptr; } -void BotManager::framePeriodic (void) { +void BotManager::slowFrame (void) { // this function calls think () function for all available at call moment bots - for (int i = 0; i < engine.maxClients (); i++) { + for (int i = 0; i < game.maxClients (); i++) { auto bot = m_bots[i]; if (bot != nullptr) { - bot->framePeriodic (); + bot->slowFrame (); } } } @@ -294,7 +294,7 @@ void BotManager::framePeriodic (void) { void BotManager::frame (void) { // this function calls periodic SecondThink () function for all available at call moment bots - for (int i = 0; i < engine.maxClients (); i++) { + for (int i = 0; i < game.maxClients (); i++) { auto bot = m_bots[i]; if (bot != nullptr) { @@ -303,7 +303,7 @@ void BotManager::frame (void) { } // select leader each team somewhere in round start - if (g_timeRoundStart + 5.0f > engine.timebase () && g_timeRoundStart + 10.0f < engine.timebase ()) { + if (m_timeRoundStart + 5.0f > game.timebase () && m_timeRoundStart + 10.0f < game.timebase ()) { for (int team = 0; team < MAX_TEAM_COUNT; team++) { selectLeaders (team, false); } @@ -349,14 +349,14 @@ void BotManager::maintainQuota (void) { if (waypoints.length () < 1 || waypoints.hasChanged ()) { if (yb_quota.integer () > 0) { - engine.centerPrint ("Map is not waypointed. Cannot create bot"); + ctrl.msg ("Map is not waypointed. Cannot create bot"); } yb_quota.set (0); return; } // bot's creation update - if (!m_creationTab.empty () && m_maintainTime < engine.timebase ()) { + if (!m_creationTab.empty () && m_maintainTime < game.timebase ()) { const CreateQueue &last = m_creationTab.pop (); const BotCreationResult callResult = create (last.name, last.difficulty, last.personality, last.team, last.member); @@ -374,28 +374,28 @@ void BotManager::maintainQuota (void) { yb_quota.set (getBotCount ()); } else if (callResult == BOT_RESULT_TEAM_STACKED) { - engine.print ("Could not add bot to the game: Team is stacked (to disable this check, set mp_limitteams and mp_autoteambalance to zero and restart the round)"); + ctrl.msg ("Could not add bot to the game: Team is stacked (to disable this check, set mp_limitteams and mp_autoteambalance to zero and restart the round)"); m_creationTab.clear (); yb_quota.set (getBotCount ()); } - m_maintainTime = engine.timebase () + 0.10f; + m_maintainTime = game.timebase () + 0.10f; } // now keep bot number up to date - if (m_quotaMaintainTime > engine.timebase ()) { + if (m_quotaMaintainTime > game.timebase ()) { return; } // not a best place for this, but whatever updateBotDifficulties (); - yb_quota.set (cr::clamp (yb_quota.integer (), 0, engine.maxClients ())); + yb_quota.set (cr::clamp (yb_quota.integer (), 0, game.maxClients ())); int totalHumansInGame = getHumansCount (); int humanPlayersInGame = getHumansCount (true); - if (!engine.isDedicated () && !totalHumansInGame) { + if (!game.isDedicated () && !totalHumansInGame) { return; } @@ -414,7 +414,7 @@ void BotManager::maintainQuota (void) { if (yb_join_after_player.boolean () && humanPlayersInGame == 0) { desiredBotCount = 0; } - int maxClients = engine.maxClients (); + int maxClients = game.maxClients (); if (yb_autovacate.boolean ()) { desiredBotCount = cr::min (desiredBotCount, maxClients - (humanPlayersInGame + 1)); @@ -422,7 +422,10 @@ void BotManager::maintainQuota (void) { else { desiredBotCount = cr::min (desiredBotCount, maxClients - humanPlayersInGame); } - int maxSpawnCount = engine.getSpawnCount (TEAM_TERRORIST) + engine.getSpawnCount (TEAM_COUNTER); + int maxSpawnCount = game.getSpawnCount (TEAM_TERRORIST) + game.getSpawnCount (TEAM_COUNTER); + + // sent message only to console from here + ctrl.setFromConsole (true); // add bots if necessary if (desiredBotCount > botsInGame && botsInGame < maxSpawnCount) { @@ -449,7 +452,7 @@ void BotManager::maintainQuota (void) { kickRandom (false, TEAM_UNASSIGNED); } } - m_quotaMaintainTime = engine.timebase () + 0.40f; + m_quotaMaintainTime = game.timebase () + 0.40f; } void BotManager::reset (void) { @@ -462,6 +465,37 @@ void BotManager::reset (void) { m_activeGrenades.clear (); } +void BotManager::initFilters (void) { + // table with all available actions for the bots (filtered in & out in bot::setconditions) some of them have subactions included + + m_filters.push ({ TASK_NORMAL, 0, INVALID_WAYPOINT_INDEX, 0.0f, true }); + m_filters.push ({ TASK_PAUSE, 0, INVALID_WAYPOINT_INDEX, 0.0f, false }); + m_filters.push ({ TASK_MOVETOPOSITION, 0, INVALID_WAYPOINT_INDEX, 0.0f, true }); + m_filters.push ({ TASK_FOLLOWUSER, 0, INVALID_WAYPOINT_INDEX, 0.0f, true }); + m_filters.push ({ TASK_PICKUPITEM, 0, INVALID_WAYPOINT_INDEX, 0.0f, true }); + m_filters.push ({ TASK_CAMP, 0, INVALID_WAYPOINT_INDEX, 0.0f, true }); + m_filters.push ({ TASK_PLANTBOMB, 0, INVALID_WAYPOINT_INDEX, 0.0f, false }); + m_filters.push ({ TASK_DEFUSEBOMB, 0, INVALID_WAYPOINT_INDEX, 0.0f, false }); + m_filters.push ({ TASK_ATTACK, 0, INVALID_WAYPOINT_INDEX, 0.0f, false }); + m_filters.push ({ TASK_HUNTENEMY, 0, INVALID_WAYPOINT_INDEX, 0.0f, false }); + m_filters.push ({ TASK_SEEKCOVER, 0, INVALID_WAYPOINT_INDEX, 0.0f, false }); + m_filters.push ({ TASK_THROWHEGRENADE, 0, INVALID_WAYPOINT_INDEX, 0.0f, false }); + m_filters.push ({ TASK_THROWFLASHBANG, 0, INVALID_WAYPOINT_INDEX, 0.0f, false }); + m_filters.push ({ TASK_THROWSMOKE, 0, INVALID_WAYPOINT_INDEX, 0.0f, false }); + m_filters.push ({ TASK_DOUBLEJUMP, 0, INVALID_WAYPOINT_INDEX, 0.0f, false }); + m_filters.push ({ TASK_ESCAPEFROMBOMB, 0, INVALID_WAYPOINT_INDEX, 0.0f, false }); + m_filters.push ({ TASK_SHOOTBREAKABLE, 0, INVALID_WAYPOINT_INDEX, 0.0f, false }); + m_filters.push ({ TASK_HIDE, 0, INVALID_WAYPOINT_INDEX, 0.0f, false }); + m_filters.push ({ TASK_BLINDED, 0, INVALID_WAYPOINT_INDEX, 0.0f, false }); + m_filters.push ({ TASK_SPRAY, 0, INVALID_WAYPOINT_INDEX, 0.0f, false }); +} + +void BotManager::resetFilters (void) { + for (auto &task : m_filters) { + task.time = 0.0f; + } +} + void BotManager::decrementQuota (int by) { if (by != 0) { yb_quota.set (cr::clamp (yb_quota.integer () - by, 0, yb_quota.integer ())); @@ -471,8 +505,8 @@ void BotManager::decrementQuota (int by) { } void BotManager::initQuota (void) { - m_maintainTime = engine.timebase () + 3.0f; - m_quotaMaintainTime = engine.timebase () + 3.0f; + m_maintainTime = game.timebase () + yb_join_delay.flt (); + m_quotaMaintainTime = game.timebase () + yb_join_delay.flt (); m_creationTab.clear (); } @@ -481,7 +515,7 @@ void BotManager::serverFill (int selection, int personality, int difficulty, int // this function fill server with bots, with specified team & personality // always keep one slot - int maxClients = yb_autovacate.boolean () ? engine.maxClients () - 1 - (engine.isDedicated () ? 0 : getHumansCount ()) : engine.maxClients (); + int maxClients = yb_autovacate.boolean () ? game.maxClients () - 1 - (game.isDedicated () ? 0 : getHumansCount ()) : game.maxClients (); if (getBotCount () >= maxClients - getHumansCount ()) { return; @@ -500,20 +534,20 @@ void BotManager::serverFill (int selection, int personality, int difficulty, int for (int i = 0; i <= toAdd; i++) { addbot ("", difficulty, personality, selection, -1, true); } - engine.centerPrint ("Fill Server with %s bots...", &teams[selection][0]); + ctrl.msg ("Fill Server with %s bots...", &teams[selection][0]); } void BotManager::kickEveryone (bool instant, bool zeroQuota) { // this function drops all bot clients from server (this function removes only yapb's)`q - engine.centerPrint ("Bots are removed from server."); + ctrl.msg ("Bots are removed from server."); if (zeroQuota) { decrementQuota (0); } if (instant) { - for (int i = 0; i < engine.maxClients (); i++) { + for (int i = 0; i < game.maxClients (); i++) { auto bot = m_bots[i]; if (bot != nullptr) { @@ -527,7 +561,7 @@ void BotManager::kickEveryone (bool instant, bool zeroQuota) { void BotManager::kickFromTeam (Team team, bool removeAll) { // this function remove random bot from specified team (if removeAll value = 1 then removes all players from team) - for (int i = 0; i < engine.maxClients (); i++) { + for (int i = 0; i < game.maxClients (); i++) { auto bot = m_bots[i]; if (bot != nullptr && team == bot->m_team) { @@ -541,60 +575,10 @@ void BotManager::kickFromTeam (Team team, bool removeAll) { } } -void BotManager::kickBotByMenu (edict_t *ent, int selection) { - // this function displays remove bot menu to specified entity (this function show's only yapb's). - - if (selection > 4 || selection < 1) - return; - - String menus; - menus.format ("\\yBots Remove Menu (%d/4):\\w\n\n", selection); - - int menuKeys = (selection == 4) ? (1 << 9) : ((1 << 8) | (1 << 9)); - int menuKey = (selection - 1) * 8; - - for (int i = menuKey; i < selection * 8; i++) { - auto bot = getBot (i); - - if (bot != nullptr && (bot->pev->flags & FL_FAKECLIENT)) { - menuKeys |= 1 << (cr::abs (i - menuKey)); - menus.formatAppend ("%1.1d. %s%s\n", i - menuKey + 1, STRING (bot->pev->netname), bot->m_team == TEAM_COUNTER ? " \\y(CT)\\w" : " \\r(T)\\w"); - } - else { - menus.formatAppend ("\\d %1.1d. Not a Bot\\w\n", i - menuKey + 1); - } - } - menus.formatAppend ("\n%s 0. Back", (selection == 4) ? "" : " 9. More...\n"); - - // force to clear current menu - showMenu (ent, BOT_MENU_INVALID); - - auto searchMenu = [](MenuId id) { - int menuIndex = 0; - - for (; menuIndex < BOT_MENU_TOTAL_MENUS; menuIndex++) { - if (g_menus[menuIndex].id == id) { - break; - } - } - return &g_menus[menuIndex]; - }; - - const unsigned int slots = menuKeys & static_cast (-1); - MenuId id = static_cast (BOT_MENU_KICK_PAGE_1 - 1 + selection); - - auto menu = searchMenu (id); - - menu->slots = slots; - menu->text = menus; - - showMenu (ent, id); -} - void BotManager::killAllBots (int team) { // this function kills all bots on server (only this dll controlled bots) - for (int i = 0; i < engine.maxClients (); i++) { + for (int i = 0; i < game.maxClients (); i++) { if (m_bots[i] != nullptr) { if (team != -1 && team != m_bots[i]->m_team) { continue; @@ -602,7 +586,7 @@ void BotManager::killAllBots (int team) { m_bots[i]->kill (); } } - engine.centerPrint ("All Bots died !"); + ctrl.msg ("All Bots died !"); } void BotManager::kickBot (int index) { @@ -634,7 +618,7 @@ bool BotManager::kickRandom (bool decQuota, Team fromTeam) { }; // first try to kick the bot that is currently dead - for (int i = 0; i < engine.maxClients (); i++) { + for (int i = 0; i < game.maxClients (); i++) { auto bot = m_bots[i]; if (bot && !bot->m_notKilled && belongsTeam (bot)) // is this slot used? @@ -656,7 +640,7 @@ bool BotManager::kickRandom (bool decQuota, Team fromTeam) { float score = 9999.0f; // search bots in this team - for (int i = 0; i < engine.maxClients (); i++) { + for (int i = 0; i < game.maxClients (); i++) { auto bot = m_bots [i]; if (bot && bot->pev->frags < score && belongsTeam (bot)) { @@ -674,7 +658,7 @@ bool BotManager::kickRandom (bool decQuota, Team fromTeam) { } // worst case, just kick some random bot - for (int i = 0; i < engine.maxClients (); i++) { + for (int i = 0; i < game.maxClients (); i++) { auto bot = m_bots[i]; if (bot && belongsTeam (bot)) // is this slot used? @@ -691,7 +675,9 @@ bool BotManager::kickRandom (bool decQuota, Team fromTeam) { void BotManager::setWeaponMode (int selection) { // this function sets bots weapon mode - int tabMapStandart[7][NUM_WEAPONS] = { + selection--; + + constexpr int std[7][NUM_WEAPONS] = { {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, // Knife only {-1, -1, -1, 2, 2, 0, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, // Pistols only {-1, -1, -1, -1, -1, -1, -1, 2, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, // Shotgun only @@ -701,7 +687,7 @@ void BotManager::setWeaponMode (int selection) { {-1, -1, -1, 2, 2, 0, 1, 2, 2, 2, 1, 2, 0, 2, 0, 0, 1, 0, 1, 1, 2, 2, 0, 1, 2, 1} // Standard }; - int tabMapAS[7][NUM_WEAPONS] = { + constexpr int as[7][NUM_WEAPONS] = { {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, // Knife only {-1, -1, -1, 2, 2, 0, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, // Pistols only {-1, -1, -1, -1, -1, -1, -1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, // Shotgun only @@ -710,35 +696,32 @@ void BotManager::setWeaponMode (int selection) { {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, -1, 1, -1, -1}, // Snipers only {-1, -1, -1, 2, 2, 0, 1, 1, 1, 1, 1, 1, 0, 2, 0, -1, 1, 0, 1, 1, 0, 0, -1, 1, 1, 1} // Standard }; + constexpr char modes[7][12] = {{"Knife"}, {"Pistol"}, {"Shotgun"}, {"Machine Gun"}, {"Rifle"}, {"Sniper"}, {"Standard"}}; + + // get the raw weapons array + auto tab = conf.getRawWeapons (); - char modeName[7][12] = {{"Knife"}, {"Pistol"}, {"Shotgun"}, {"Machine Gun"}, {"Rifle"}, {"Sniper"}, {"Standard"}}; - selection--; - + // set the correct weapon mode for (int i = 0; i < NUM_WEAPONS; i++) { - g_weaponSelect[i].teamStandard = tabMapStandart[selection][i]; - g_weaponSelect[i].teamAS = tabMapAS[selection][i]; + tab[i].teamStandard = std[selection][i]; + tab[i].teamAS = as[selection][i]; } + yb_jasonmode.set (selection == 0 ? 1 : 0); - if (selection == 0) { - yb_jasonmode.set (1); - } - else { - yb_jasonmode.set (0); - } - engine.centerPrint ("%s weapon mode selected", &modeName[selection][0]); + ctrl.msg ("%s weapon mode selected", &modes[selection][0]); } void BotManager::listBots (void) { // this function list's bots currently playing on the server - engine.print ("%-3.5s %-9.13s %-17.18s %-3.4s %-3.4s %-3.4s", "index", "name", "personality", "team", "difficulty", "frags"); + ctrl.msg ("%-3.5s %-9.13s %-17.18s %-3.4s %-3.4s %-3.4s", "index", "name", "personality", "team", "difficulty", "frags"); - for (int i = 0; i < engine.maxClients (); i++) { + for (int i = 0; i < game.maxClients (); i++) { Bot *bot = getBot (i); // is this player slot valid if (bot != nullptr) { - engine.print ("[%-3.1d] %-9.13s %-17.18s %-3.4s %-3.1d %-3.1d", i, STRING (bot->pev->netname), bot->m_personality == PERSONALITY_RUSHER ? "rusher" : bot->m_personality == PERSONALITY_NORMAL ? "normal" : "careful", bot->m_team == TEAM_COUNTER ? "CT" : "T", bot->m_difficulty, static_cast (bot->pev->frags)); + ctrl.msg ("[%-3.1d] %-9.13s %-17.18s %-3.4s %-3.1d %-3.1d", i, STRING (bot->pev->netname), bot->m_personality == PERSONALITY_RUSHER ? "rusher" : bot->m_personality == PERSONALITY_NORMAL ? "normal" : "careful", bot->m_team == TEAM_COUNTER ? "CT" : "T", bot->m_difficulty, static_cast (bot->pev->frags)); } } } @@ -748,7 +731,7 @@ int BotManager::getBotCount (void) { int count = 0; - for (int i = 0; i < engine.maxClients (); i++) { + for (int i = 0; i < game.maxClients (); i++) { if (m_bots[i] != nullptr) { count++; } @@ -757,9 +740,7 @@ int BotManager::getBotCount (void) { } void BotManager::countTeamPlayers (int &ts, int &cts) { - for (int i = 0; i < engine.maxClients (); i++) { - const Client &client = g_clients[i]; - + for (const auto &client : util.getClients ()) { if (client.flags & CF_USED) { if (client.team2 == TEAM_TERRORIST) { ts++; @@ -776,7 +757,7 @@ Bot *BotManager::getHighfragBot (int team) { float bestScore = -1; // search bots in this team - for (int i = 0; i < engine.maxClients (); i++) { + for (int i = 0; i < game.maxClients (); i++) { auto bot = bots.getBot (i); if (bot != nullptr && bot->m_notKilled && bot->m_team == team) { @@ -789,27 +770,28 @@ Bot *BotManager::getHighfragBot (int team) { return getBot (bestIndex); } -void BotManager::updateTeamEconomics (int team, bool forceGoodEconomics) { +void BotManager::updateTeamEconomics (int team, bool setTrue) { // this function decides is players on specified team is able to buy primary weapons by calculating players // that have not enough money to buy primary (with economics), and if this result higher 80%, player is can't // buy primary weapons. extern ConVar yb_economics_rounds; - if (forceGoodEconomics || !yb_economics_rounds.boolean ()) { + if (setTrue || !yb_economics_rounds.boolean ()) { m_economicsGood[team] = true; return; // don't check economics while economics disable } + const int *econLimit = conf.getEconLimit (); int numPoorPlayers = 0; int numTeamPlayers = 0; // start calculating - for (int i = 0; i < engine.maxClients (); i++) { + for (int i = 0; i < game.maxClients (); i++) { auto bot = m_bots[i]; if (bot != nullptr && bot->m_team == team) { - if (bot->m_moneyAmount <= g_botBuyEconomyTable[0]) { + if (bot->m_moneyAmount <= econLimit[ECO_PRIMARY_GT]) { numPoorPlayers++; } numTeamPlayers++; // update count of team @@ -837,7 +819,7 @@ void BotManager::updateBotDifficulties (void) { if (difficulty != m_lastDifficulty) { // sets new difficulty for all bots - for (int i = 0; i < engine.maxClients (); i++) { + for (int i = 0; i < game.maxClients (); i++) { auto bot = m_bots[i]; if (bot != nullptr) { @@ -867,35 +849,35 @@ Bot::Bot (edict_t *bot, int difficulty, int personality, int team, int member, c // this function does core operation of creating bot, it's called by CreateBot (), // when bot setup completed, (this is a bot class constructor) - int clientIndex = engine.indexOfEntity (bot); + int clientIndex = game.indexOfEntity (bot); memset (reinterpret_cast (this), 0, sizeof (*this)); pev = &bot->v; if (bot->pvPrivateData != nullptr) { - g_engfuncs.pfnFreeEntPrivateData (bot); + engfuncs.pfnFreeEntPrivateData (bot); } bot->pvPrivateData = nullptr; bot->v.frags = 0; // create the player entity by calling MOD's player function - BotManager::execGameEntity (&bot->v); + bots.execGameEntity (&bot->v); // set all info buffer keys for this bot - char *buffer = g_engfuncs.pfnGetInfoKeyBuffer (bot); - g_engfuncs.pfnSetClientKeyValue (clientIndex, buffer, "_vgui_menus", "0"); + char *buffer = engfuncs.pfnGetInfoKeyBuffer (bot); + engfuncs.pfnSetClientKeyValue (clientIndex, buffer, "_vgui_menus", "0"); - if (!(g_gameFlags & GAME_LEGACY) && yb_latency_display.integer () == 1) { - g_engfuncs.pfnSetClientKeyValue (clientIndex, buffer, "*bot", "1"); + if (!(game.is (GAME_LEGACY)) && yb_latency_display.integer () == 1) { + engfuncs.pfnSetClientKeyValue (clientIndex, buffer, "*bot", "1"); } char reject[256] = {0, }; - MDLL_ClientConnect (bot, STRING (bot->v.netname), format ("127.0.0.%d", engine.indexOfEntity (bot) + 100), reject); + MDLL_ClientConnect (bot, STRING (bot->v.netname), util.format ("127.0.0.%d", game.indexOfEntity (bot) + 100), reject); - if (!isEmptyStr (reject)) { - logEntry (true, LL_WARNING, "Server refused '%s' connection (%s)", STRING (bot->v.netname), reject); - engine.execCmd ("kick \"%s\"", STRING (bot->v.netname)); // kick the bot player if the server refused it + if (!util.isEmptyStr (reject)) { + util.logEntry (true, LL_WARNING, "Server refused '%s' connection (%s)", STRING (bot->v.netname), reject); + game.execCmd ("kick \"%s\"", STRING (bot->v.netname)); // kick the bot player if the server refused it bot->v.flags |= FL_KILLME; return; @@ -903,7 +885,7 @@ Bot::Bot (edict_t *bot, int difficulty, int personality, int team, int member, c // should be set after client connect if (yb_avatar_display.boolean () && !steamId.empty ()) { - g_engfuncs.pfnSetClientKeyValue (clientIndex, buffer, "*sid", steamId.chars ()); + engfuncs.pfnSetClientKeyValue (clientIndex, buffer, "*sid", steamId.chars ()); } memset (&m_pingOffset, 0, sizeof (m_pingOffset)); memset (&m_ping, 0, sizeof (m_ping)); @@ -926,7 +908,7 @@ Bot::Bot (edict_t *bot, int difficulty, int personality, int team, int member, c m_sayTextBuffer.chatProbability = rng.getInt (1, 100); m_notKilled = false; - m_weaponBurstMode = BM_OFF; + m_weaponBurstMode = BURST_OFF; m_difficulty = difficulty; if (difficulty < 0 || difficulty > 4) { @@ -934,9 +916,9 @@ Bot::Bot (edict_t *bot, int difficulty, int personality, int team, int member, c yb_difficulty.set (difficulty); } - m_lastCommandTime = engine.timebase () - 0.1f; - m_frameInterval = engine.timebase (); - m_timePeriodicUpdate = 0.0f; + m_lastCommandTime = game.timebase () - 0.1f; + m_frameInterval = game.timebase (); + m_slowFrameTimestamp = 0.0f; switch (personality) { case 1: @@ -967,7 +949,7 @@ Bot::Bot (edict_t *bot, int difficulty, int personality, int team, int member, c // copy them over to the temp level variables m_agressionLevel = m_baseAgressionLevel; m_fearLevel = m_baseFearLevel; - m_nextEmotionUpdate = engine.timebase () + 0.5f; + m_nextEmotionUpdate = game.timebase () + 0.5f; // just to be sure m_actMessageIndex = 0; @@ -980,26 +962,18 @@ Bot::Bot (edict_t *bot, int difficulty, int personality, int team, int member, c newRound (); } -void Bot::clearUsedName (void) { - for (auto &name : g_botNames) { - if (name.usedBy == index ()) { - name.usedBy = 0; - break; - } - } -} - -float Bot::calcThinkInterval (void) { +float Bot::getFrameInterval (void) { return cr::fzero (m_thinkInterval) ? m_frameInterval : m_thinkInterval; } Bot::~Bot (void) { // this is bot destructor - clearUsedName (); clearSearchNodes (); clearRoute (); clearTasks (); + + conf.clearUsedName (this); } int BotManager::getHumansCount (bool ignoreSpectators) { @@ -1007,10 +981,8 @@ int BotManager::getHumansCount (bool ignoreSpectators) { int count = 0; - for (int i = 0; i < engine.maxClients (); i++) { - const Client &client = g_clients[i]; - - if ((client.flags & CF_USED) && m_bots[i] == nullptr && !(client.ent->v.flags & FL_FAKECLIENT)) { + for (const auto &client : util.getClients ()) { + if ((client.flags & CF_USED) && getBot (client.ent) == nullptr && !(client.ent->v.flags & FL_FAKECLIENT)) { if (ignoreSpectators && client.team2 != TEAM_TERRORIST && client.team2 != TEAM_COUNTER) { continue; } @@ -1025,10 +997,8 @@ int BotManager::getAliveHumansCount (void) { int count = 0; - for (int i = 0; i < engine.maxClients (); i++) { - const Client &client = g_clients[i]; - - if ((client.flags & (CF_USED | CF_ALIVE)) && m_bots[i] == nullptr && !(client.ent->v.flags & FL_FAKECLIENT)) { + for (const auto &client : util.getClients ()) { + if ((client.flags & (CF_USED | CF_ALIVE)) && getBot (client.ent) == nullptr && !(client.ent->v.flags & FL_FAKECLIENT)) { count++; } } @@ -1046,9 +1016,7 @@ bool BotManager::isTeamStacked (int team) { } int teamCount[MAX_TEAM_COUNT] = { 0, }; - for (int i = 0; i < engine.maxClients (); i++) { - const Client &client = g_clients[i]; - + for (const auto &client : util.getClients ()) { if ((client.flags & CF_USED) && client.team2 != TEAM_UNASSIGNED && client.team2 != TEAM_SPECTATOR) { teamCount[client.team2]++; } @@ -1092,8 +1060,8 @@ void Bot::newRound (void) { for (i = 0; i < 5; i++) { m_prevWptIndex[i] = INVALID_WAYPOINT_INDEX; } - m_navTimeset = engine.timebase (); - m_team = engine.getTeam (ent ()); + m_navTimeset = game.timebase (); + m_team = game.getTeam (ent ()); m_isVIP = false; switch (m_personality) { @@ -1118,7 +1086,7 @@ void Bot::newRound (void) { m_isLeader = false; m_hasProgressBar = false; m_canChooseAimDirection = true; - m_turnAwayFromFlashbang = 0.0f; + m_preventFlashing = 0.0f; m_timeTeamOrder = 0.0f; m_timeRepotingInDelay = rng.getFloat (40.0f, 240.0f); @@ -1126,9 +1094,9 @@ void Bot::newRound (void) { m_minSpeed = 260.0f; m_prevSpeed = 0.0f; m_prevOrigin = Vector (9999.0f, 9999.0f, 9999.0f); - m_prevTime = engine.timebase (); - m_lookUpdateTime = engine.timebase (); - m_aimErrorTime = engine.timebase (); + m_prevTime = game.timebase (); + m_lookUpdateTime = game.timebase (); + m_aimErrorTime = game.timebase (); m_viewDistance = 4096.0f; m_maxViewDistance = 4096.0f; @@ -1191,8 +1159,8 @@ void Bot::newRound (void) { m_reloadState = RELOAD_NONE; m_reloadCheckTime = 0.0f; - m_shootTime = engine.timebase (); - m_playerTargetTime = engine.timebase (); + m_shootTime = game.timebase (); + m_playerTargetTime = game.timebase (); m_firePause = 0.0f; m_timeLastFired = 0.0f; @@ -1208,7 +1176,7 @@ void Bot::newRound (void) { m_jumpFinished = false; m_isStuck = false; - m_sayTextBuffer.timeNextChat = engine.timebase (); + m_sayTextBuffer.timeNextChat = game.timebase (); m_sayTextBuffer.entityIndex = -1; m_sayTextBuffer.sayText.clear (); @@ -1223,10 +1191,10 @@ void Bot::newRound (void) { m_currentWeapon = 0; } m_flashLevel = 100.0f; - m_checkDarkTime = engine.timebase (); + m_checkDarkTime = game.timebase (); - m_knifeAttackTime = engine.timebase () + rng.getFloat (1.3f, 2.6f); - m_nextBuyTime = engine.timebase () + rng.getFloat (0.6f, 2.0f); + m_knifeAttackTime = game.timebase () + rng.getFloat (1.3f, 2.6f); + m_nextBuyTime = game.timebase () + rng.getFloat (0.6f, 2.0f); m_buyPending = false; m_inBombZone = false; @@ -1251,9 +1219,9 @@ void Bot::newRound (void) { m_defendHostage = false; m_headedTime = 0.0f; - m_timeLogoSpray = engine.timebase () + rng.getFloat (5.0f, 30.0f); - m_spawnTime = engine.timebase (); - m_lastChatTime = engine.timebase (); + m_timeLogoSpray = game.timebase () + rng.getFloat (5.0f, 30.0f); + m_spawnTime = game.timebase (); + m_lastChatTime = game.timebase (); m_timeCamping = 0.0f; m_campDirection = 0; @@ -1261,7 +1229,7 @@ void Bot::newRound (void) { m_campButtons = 0; m_soundUpdateTime = 0.0f; - m_heardSoundTime = engine.timebase (); + m_heardSoundTime = game.timebase (); // clear its message queue for (i = 0; i < 32; i++) { @@ -1277,7 +1245,7 @@ void Bot::newRound (void) { if (rng.chance (50)) { pushChatterMessage (CHATTER_NEW_ROUND); } - m_thinkInterval = (g_gameFlags & (GAME_LEGACY | GAME_XASH_ENGINE)) ? 0.0f : (1.0f / cr::clamp (yb_think_fps.flt (), 30.0f, 90.0f)) * rng.getFloat (0.95f, 1.05f); + m_thinkInterval = game.is (GAME_LEGACY | GAME_XASH_ENGINE) ? 0.0f : (1.0f / cr::clamp (yb_think_fps.flt (), 30.0f, 90.0f)) * rng.getFloat (0.95f, 1.05f); } void Bot::kill (void) { @@ -1291,21 +1259,21 @@ void Bot::kick (void) { // this function kick off one bot from the server. auto username = STRING (pev->netname); - if (!(pev->flags & FL_FAKECLIENT) || isEmptyStr (username)) { + if (!(pev->flags & FL_FAKECLIENT) || util.isEmptyStr (username)) { return; } // clear fakeclient bit pev->flags &= ~FL_FAKECLIENT; - engine.execCmd ("kick \"%s\"", username); - engine.centerPrint ("Bot '%s' kicked", username); + game.execCmd ("kick \"%s\"", username); + ctrl.msg ("Bot '%s' kicked", username); } -void Bot::processTeamJoin (void) { +void Bot::updateTeamJoin (void) { // this function handles the selection of teams & class // cs prior beta 7.0 uses hud-based motd, so press fire once - if (g_gameFlags & GAME_LEGACY) { + if (game.is (GAME_LEGACY)) { pev->button |= IN_ATTACK; } @@ -1322,7 +1290,7 @@ void Bot::processTeamJoin (void) { if (bots.isTeamStacked (m_wantedTeam - 1)) { m_retryJoin = 0; - engine.print ("Could not add bot to the game: Team is stacked (to disable this check, set mp_limitteams and mp_autoteambalance to zero and restart the round)."); + ctrl.msg ("Could not add bot to the game: Team is stacked (to disable this check, set mp_limitteams and mp_autoteambalance to zero and restart the round)."); kick (); return; @@ -1347,20 +1315,20 @@ void Bot::processTeamJoin (void) { } // select the team the bot wishes to join... - engine.execBotCmd (ent (), "menuselect %d", m_wantedTeam); + game.execBotCmd (ent (), "menuselect %d", m_wantedTeam); } else if (m_startAction == GAME_MSG_CLASS_SELECT) { m_startAction = GAME_MSG_NONE; // switch back to idle // czero has additional models - int maxChoice = (g_gameFlags & GAME_CZERO) ? 5 : 4; + int maxChoice = game.is (GAME_CZERO) ? 5 : 4; if (m_wantedClass < 1 || m_wantedClass > maxChoice) { m_wantedClass = rng.getInt (1, maxChoice); // use random if invalid } // select the class the bot wishes to use... - engine.execBotCmd (ent (), "menuselect %d", m_wantedClass); + game.execBotCmd (ent (), "menuselect %d", m_wantedClass); // bot has now joined the game (doesn't need to be started) m_notStarted = false; @@ -1373,22 +1341,22 @@ void Bot::processTeamJoin (void) { } void BotManager::calculatePingOffsets (void) { - if (!(g_gameFlags & GAME_SUPPORT_SVC_PINGS) || yb_latency_display.integer () != 2) { + if (!game.is (GAME_SUPPORT_SVC_PINGS) || yb_latency_display.integer () != 2) { return; } int averagePing = 0; int numHumans = 0; - for (int i = 0; i < engine.maxClients (); i++) { - edict_t *ent = engine.entityOfIndex (i + 1); + for (int i = 0; i < game.maxClients (); i++) { + edict_t *ent = game.entityOfIndex (i + 1); - if (!isPlayer (ent)) { + if (!util.isPlayer (ent)) { continue; } numHumans++; int ping, loss; - g_engfuncs.pfnGetPlayerStats (ent, &ping, &loss); + engfuncs.pfnGetPlayerStats (ent, &ping, &loss); if (ping < 0 || ping > 100) { ping = rng.getInt (3, 15); @@ -1402,9 +1370,9 @@ void BotManager::calculatePingOffsets (void) { else { averagePing = rng.getInt (30, 40); } - - for (int i = 0; i < engine.maxClients (); i++) { - Bot *bot = getBot (i); + + for (int i = 0; i < game.maxClients (); i++) { + auto bot = getBot (i); if (bot == nullptr) { continue; @@ -1438,11 +1406,10 @@ void BotManager::calculatePingOffsets (void) { } void BotManager::sendPingOffsets (edict_t *to) { - if (!(g_gameFlags & GAME_SUPPORT_SVC_PINGS) || yb_latency_display.integer () != 2 || engine.isNullEntity (to) || (to->v.flags & FL_FAKECLIENT)) { + if (!game.is (GAME_SUPPORT_SVC_PINGS) || yb_latency_display.integer () != 2 || game.isNullEntity (to) || (to->v.flags & FL_FAKECLIENT)) { return; } - - if (!(to->v.flags & FL_CLIENT) && !(((to->v.button & IN_SCORE) || !(to->v.oldbuttons & IN_SCORE)))) { + if (!(to->v.flags & FL_CLIENT) || !(((to->v.button & IN_SCORE) || (to->v.oldbuttons & IN_SCORE)))) { return; } MessageWriter msg; @@ -1450,8 +1417,8 @@ void BotManager::sendPingOffsets (edict_t *to) { // missing from sdk constexpr int SVC_PINGS = 17; - for (int i = 0; i < engine.maxClients (); i++) { - Bot *bot = m_bots[i]; + for (int i = 0; i < game.maxClients (); i++) { + auto bot = m_bots[i]; if (bot == nullptr) { continue; @@ -1472,14 +1439,97 @@ void BotManager::sendDeathMsgFix (void) { if (yb_latency_display.integer () == 2 && m_deathMsgSent) { m_deathMsgSent = false; - for (int i = 0; i < engine.maxClients (); i++) { - sendPingOffsets (g_clients[i].ent); + for (const auto &client : util.getClients ()) { + sendPingOffsets (client.ent); + } + } +} + +void BotManager::captureChatRadio (const char *cmd, const char *arg, edict_t *ent) { + if (game.isBotCmd ()) { + return; + } + + if (stricmp (cmd, "say") == 0 || stricmp (cmd, "say_team") == 0) { + Bot *bot = nullptr; + + if (strcmp (arg, "dropme") == 0 || strcmp (arg, "dropc4") == 0) { + if (util.findNearestPlayer (reinterpret_cast (&bot), ent, 300.0f, true, true, true)) { + bot->dropWeaponForUser (ent, util.isEmptyStr (strstr (arg, "c4")) ? false : true); + } + return; + } + + bool alive = util.isAlive (ent); + int team = -1; + + if (strcmp (cmd, "say_team") == 0) { + team = game.getTeam (ent); + } + + for (const auto &client : util.getClients ()) { + if (!(client.flags & CF_USED) || (team != -1 && team != client.team) || alive != util.isAlive (client.ent)) { + continue; + } + auto target = bots.getBot (client.ent); + + if (target != nullptr) { + target->m_sayTextBuffer.entityIndex = game.indexOfEntity (ent); + + if (util.isEmptyStr (engfuncs.pfnCmd_Args ())) { + continue; + } + target->m_sayTextBuffer.sayText = engfuncs.pfnCmd_Args (); + target->m_sayTextBuffer.timeNextChat = game.timebase () + target->m_sayTextBuffer.chatDelay; + } + } + } + Client &radioTarget = util.getClient (game.indexOfEntity (ent) - 1); + + // check if this player alive, and issue something + if ((radioTarget.flags & CF_ALIVE) && radioTarget.radio != 0 && strncmp (cmd, "menuselect", 10) == 0) { + int radioCommand = atoi (arg); + + if (radioCommand != 0) { + radioCommand += 10 * (radioTarget.radio - 1); + + if (radioCommand != RADIO_AFFIRMATIVE && radioCommand != RADIO_NEGATIVE && radioCommand != RADIO_REPORTING_IN) { + for (int i = 0; i < game.maxClients (); i++) { + auto bot = bots.getBot (i); + + // validate bot + if (bot != nullptr && bot->m_team == radioTarget.team && ent != bot->ent () && bot->m_radioOrder == 0) { + bot->m_radioOrder = radioCommand; + bot->m_radioEntity = ent; + } + } + } + bots.setLastRadioTimestamp (radioTarget.team, game.timebase ()); + } + radioTarget.radio = 0; + } + else if (strncmp (cmd, "radio", 5) == 0) { + radioTarget.radio = atoi (&cmd[5]); + } +} + +void BotManager::notifyBombDefuse (void) { + // notify all terrorists that CT is starting bomb defusing + + for (int i = 0; i < game.maxClients (); i++) { + auto bot = bots.getBot (i); + + if (bot && bot->m_team == TEAM_TERRORIST && bot->m_notKilled && bot->taskId () != TASK_MOVETOPOSITION) { + bot->clearSearchNodes (); + + bot->m_position = waypoints.getBombPos (); + bot->startTask (TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, INVALID_WAYPOINT_INDEX, 0.0f, true); } } } void BotManager::updateActiveGrenade (void) { - if (m_grenadeUpdateTime > engine.timebase ()) { + if (m_grenadeUpdateTime > game.timebase ()) { return; } edict_t *grenade = nullptr; @@ -1488,18 +1538,18 @@ void BotManager::updateActiveGrenade (void) { m_activeGrenades.clear (); // search the map for any type of grenade - while (!engine.isNullEntity (grenade = g_engfuncs.pfnFindEntityByString (grenade, "classname", "grenade"))) { + while (!game.isNullEntity (grenade = engfuncs.pfnFindEntityByString (grenade, "classname", "grenade"))) { // do not count c4 as a grenade if (strcmp (STRING (grenade->v.model) + 9, "c4.mdl") == 0) { continue; } m_activeGrenades.push (grenade); } - m_grenadeUpdateTime = engine.timebase () + 0.213f; + m_grenadeUpdateTime = game.timebase () + 0.213f; } void BotManager::updateIntrestingEntities (void) { - if (m_entityUpdateTime > engine.timebase ()) { + if (m_entityUpdateTime > game.timebase ()) { return; } @@ -1507,11 +1557,11 @@ void BotManager::updateIntrestingEntities (void) { m_intrestingEntities.clear (); // search the map for entities - for (int i = MAX_ENGINE_PLAYERS - 1; i < g_pGlobals->maxEntities; i++) { - auto ent = engine.entityOfIndex (i); + for (int i = MAX_ENGINE_PLAYERS - 1; i < globals->maxEntities; i++) { + auto ent = game.entityOfIndex (i); // only valid drawn entities - if (engine.isNullEntity (ent) || ent->free || ent->v.classname == 0 || (ent->v.effects & EF_NODRAW)) { + if (game.isNullEntity (ent) || ent->free || ent->v.classname == 0 || (ent->v.effects & EF_NODRAW)) { continue; } auto classname = STRING (ent->v.classname); @@ -1522,16 +1572,16 @@ void BotManager::updateIntrestingEntities (void) { } // pickup some csdm stuff if we're running csdm - if ((g_mapFlags & MAP_CS) && strncmp ("hostage", classname, 7) == 0) { + if (game.mapIs (MAP_CS) && strncmp ("hostage", classname, 7) == 0) { m_intrestingEntities.push (ent); } // pickup some csdm stuff if we're running csdm - if ((g_gameFlags & GAME_CSDM) && strncmp ("csdm", classname, 4) == 0) { + if (game.is (GAME_CSDM) && strncmp ("csdm", classname, 4) == 0) { m_intrestingEntities.push (ent); } } - m_entityUpdateTime = engine.timebase () + 0.5f; + m_entityUpdateTime = game.timebase () + 0.5f; } void BotManager::selectLeaders (int team, bool reset) { @@ -1544,9 +1594,9 @@ void BotManager::selectLeaders (int team, bool reset) { return; } - if (g_mapFlags & MAP_AS) { + if (game.mapIs (MAP_AS)) { if (team == TEAM_COUNTER && !m_leaderChoosen[TEAM_COUNTER]) { - for (int i = 0; i < engine.maxClients (); i++) { + for (int i = 0; i < game.maxClients (); i++) { auto bot = m_bots[i]; if (bot != nullptr && bot->m_isVIP) { @@ -1574,9 +1624,9 @@ void BotManager::selectLeaders (int team, bool reset) { m_leaderChoosen[TEAM_TERRORIST] = true; } } - else if (g_mapFlags & MAP_DE) { + else if (game.mapIs (MAP_DE)) { if (team == TEAM_TERRORIST && !m_leaderChoosen[TEAM_TERRORIST]) { - for (int i = 0; i < engine.maxClients (); i++) { + for (int i = 0; i < game.maxClients (); i++) { auto bot = m_bots[i]; if (bot != nullptr && bot->m_hasC4) { @@ -1608,7 +1658,7 @@ void BotManager::selectLeaders (int team, bool reset) { m_leaderChoosen[TEAM_COUNTER] = true; } } - else if (g_mapFlags & (MAP_ES | MAP_KA | MAP_FY)) { + else if (game.mapIs (MAP_ES | MAP_KA | MAP_FY)) { auto bot = bots.getHighfragBot (team); if (!m_leaderChoosen[team] && bot) { @@ -1633,3 +1683,601 @@ void BotManager::selectLeaders (int team, bool reset) { } } } + +void BotManager::initRound (void) { + // this is called at the start of each round + + m_roundEnded = false; + + // check team economics + for (int team = 0; team < MAX_TEAM_COUNT; team++) { + updateTeamEconomics (team); + selectLeaders (team, true); + + m_lastRadioTime[team] = 0.0f; + } + reset (); + + for (int i = 0; i < game.maxClients (); i++) { + auto bot = getBot (i); + + if (bot != nullptr) { + bot->newRound (); + } + util.getClient (i).radio = 0; + } + waypoints.setBombPos (true); + waypoints.clearVisited (); + + m_bombSayStatus = 0; + m_timeBombPlanted = 0.0f; + m_plantSearchUpdateTime = 0.0f; + m_botsCanPause = false; + + resetFilters (); + waypoints.updateGlobalExperience (); // update experience data on round start + + // calculate the round mid/end in world time + m_timeRoundStart = game.timebase () + mp_freezetime.flt (); + m_timeRoundMid = m_timeRoundStart + mp_roundtime.flt () * 60.0f * 0.5f; + m_timeRoundEnd = m_timeRoundStart + mp_roundtime.flt () * 60.0f; +} + +void BotManager::setBombPlanted (bool isPlanted) { + if (isPlanted) { + m_timeBombPlanted = game.timebase (); + } + m_bombPlanted = isPlanted; +} + +void Config::load (bool onlyMain) { + static bool setMemoryPointers = true; + + if (setMemoryPointers) { + MemoryLoader::ref ().setup (engfuncs.pfnLoadFileForMe, engfuncs.pfnFreeFile); + setMemoryPointers = true; + } + + auto isCommentLine = [] (const String &line) { + char ch = line.at (0); + return ch == '#' || ch == '/' || ch == '\r' || ch == ';' || ch == 0 || ch == ' '; + }; + MemFile fp; + + String lineBuffer; + lineBuffer.reserve (512); + + // this is does the same as exec of engine, but not overwriting values of cvars spcified in yb_ignore_cvars_on_changelevel + if (onlyMain) { + static bool firstLoad = true; + + auto needsToIgnoreVar = [] (StringArray &list, const char *needle) { + for (auto &var : list) { + if (var == needle) { + return true; + } + } + return false; + }; + + if (util.openConfig ("yapb.cfg", "YaPB main config file is not found.", &fp, false)) { + while (fp.getLine (lineBuffer)) { + if (isCommentLine (lineBuffer)) { + continue; + } + if (firstLoad) { + game.execCmd (lineBuffer.chars ()); + continue; + } + auto keyval = lineBuffer.split (" "); + + if (keyval.length () > 1) { + auto ignore = String (yb_ignore_cvars_on_changelevel.str ()).split (","); + + auto key = keyval[0].trim ().chars (); + auto cvar = engfuncs.pfnCVarGetPointer (key); + + if (cvar != nullptr) { + auto value = const_cast (keyval[1].trim ().trim ("\"").trim ().chars ()); + + if (needsToIgnoreVar (ignore, key) && !!stricmp (value, cvar->string)) { + 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 + engfuncs.pfnCvar_DirectSet (cvar, cvar->string); + } + else { + engfuncs.pfnCvar_DirectSet (cvar, value); + } + } + else { + game.execCmd (lineBuffer.chars ()); + } + } + } + fp.close (); + } + firstLoad = false; + return; + } + + // reserve some space for chat + m_chat.reserve (CHAT_TOTAL); + m_chatter.reserve (CHATTER_MAX); + m_botNames.reserve (CHATTER_MAX); + + // naming initialization + if (util.openConfig ("names.cfg", "Name configuration file not found.", &fp, true)) { + m_botNames.clear (); + + while (fp.getLine (lineBuffer)) { + lineBuffer.trim (); + + if (isCommentLine (lineBuffer)) { + continue; + } + auto steamIdPair = lineBuffer.split ("\t\t"); + + if (steamIdPair.length () > 1) { + lineBuffer = steamIdPair[0].trim (); + } + + // max botname is 32 characters + if (lineBuffer.length () > 32) { + lineBuffer[32] = '\0'; + } + BotName item; + + item.name = cr::move (lineBuffer); + item.usedBy = 0; + + if (steamIdPair.length () > 1) { + item.steamId = steamIdPair[1].trim (); + } + m_botNames.push (cr::move (item)); + } + fp.close (); + } + + // chat config initialization + if (util.openConfig ("chat.cfg", "Chat file not found.", &fp, true)) { + StringArray *chat = nullptr; + Keywords replies; + + // clear all the stuff before loading new one + for (auto &item : m_chat) { + item.clear (); + } + m_replies.clear (); + + while (fp.getLine (lineBuffer)) { + lineBuffer.trim (); + + if (isCommentLine (lineBuffer)) { + continue; + } + + if (game.is (GAME_LEGACY)) { + lineBuffer[79] = '\0'; + } + else { + lineBuffer[255] = '\0'; + } + + if (lineBuffer == "[KILLED]") { + chat = &m_chat[CHAT_KILLING]; + continue; + } + else if (lineBuffer == "[BOMBPLANT]") { + chat = &m_chat[CHAT_KILLING]; + continue; + } + else if (lineBuffer == "[DEADCHAT]") { + chat = &m_chat[CHAT_DEAD]; + continue; + } + else if (lineBuffer == "[REPLIES]") { + chat = nullptr; + continue; + } + else if (lineBuffer == "[UNKNOWN]") { + chat = &m_chat[CHAT_NOKW]; + continue; + } + else if (lineBuffer == "[TEAMATTACK]") { + chat = &m_chat[CHAT_TEAMATTACK]; + continue; + } + else if (lineBuffer == "[WELCOME]") { + chat = &m_chat[CHAT_WELCOME]; + continue; + } + else if (lineBuffer == "[TEAMKILL]") { + chat = &m_chat[CHAT_TEAMKILL]; + continue; + } + + if (chat != nullptr) { + chat->push (lineBuffer); + } + else { + if (lineBuffer.contains ("@KEY")) { + if (!replies.keywords.empty () && !replies.replies.empty ()) { + m_replies.push (cr::forward (replies)); + replies.replies.clear (); + } + + replies.keywords.clear (); + replies.keywords = lineBuffer.substr (4).split (","); + + for (auto &keyword : replies.keywords) { + keyword.trim ().trim ("\""); + } + } + else if (!replies.keywords.empty () && !lineBuffer.empty ()) { + replies.replies.push (lineBuffer); + } + } + } + + // shuffle chat a bit + for (auto &item : m_chat) { + item.shuffle (); + item.shuffle (); + } + fp.close (); + } + else { + yb_chat.set (0); + } + + // weapon data initialization + if (util.openConfig ("general.cfg", "General configuration file not found. Loading defaults", &fp)) { + + auto addWeaponEntries = [] (Array &weapons, size_t max, bool as, const String &name, const StringArray &data) { + if (data.length () != max) { + util.logEntry (true, LL_ERROR, "%s entry in weapons config is not valid or malformed.", name.chars ()); + return; + } + + for (size_t i = 0; i < max; i++) { + if (as) { + weapons[i].teamAS = data[i].toInt32 (); + } + else { + weapons[i].teamStandard = data[i].toInt32 (); + } + } + }; + + auto addIntEntries = [] (int *to, size_t max, const String &name, const StringArray &data) { + if (data.length () != max) { + util.logEntry (true, LL_ERROR, "%s entry in weapons config is not valid or malformed.", name.chars ()); + return; + } + + for (size_t i = 0; i < max; i++) { + to[i] = data[i].toInt32 (); + } + }; + + while (fp.getLine (lineBuffer)) { + lineBuffer.trim (); + + if (isCommentLine (lineBuffer)) { + continue; + } + auto pair = lineBuffer.split ("="); + + if (pair.length () != 2) { + continue; + } + + for (auto &trim : pair) { + trim.trim (); + } + auto splitted = pair[1].split (","); + + if (pair[0] == "MapStandard") { + addWeaponEntries (m_weapons, NUM_WEAPONS, false, pair[0], splitted); + } + else if (pair[0] == "MapAS") { + addWeaponEntries (m_weapons, NUM_WEAPONS, true, pair[0], splitted); + } + + else if (pair[0] == "GrenadePercent") { + addIntEntries (m_grenadeBuyPrecent, 3, pair[0], splitted); + } + else if (pair[0] == "Economics") { + addIntEntries (m_botBuyEconomyTable, 11, pair[0], splitted); + } + else if (pair[0] == "PersonalityNormal") { + addIntEntries (m_normalWeaponPrefs, NUM_WEAPONS, pair[0], splitted); + } + else if (pair[0] == "PersonalityRusher") { + addIntEntries (m_rusherWeaponPrefs, NUM_WEAPONS, pair[0], splitted); + } + else if (pair[0] == "PersonalityCareful") { + addIntEntries (m_carefulWeaponPrefs, NUM_WEAPONS, pair[0], splitted); + } + } + fp.close (); + } + + // chatter initialization + if (game.is (GAME_SUPPORT_BOT_VOICE) && yb_communication_type.integer () == 2 && util.openConfig ("chatter.cfg", "Couldn't open chatter system configuration", &fp)) { + struct EventMap { + String str; + int code; + float repeat; + } chatterEventMap[] = { + { "Radio_CoverMe", RADIO_COVER_ME, MAX_CHATTER_REPEAT }, + { "Radio_YouTakePoint", RADIO_YOU_TAKE_THE_POINT, MAX_CHATTER_REPEAT }, + { "Radio_HoldPosition", RADIO_HOLD_THIS_POSITION, 10.0f }, + { "Radio_RegroupTeam", RADIO_REGROUP_TEAM, 10.0f }, + { "Radio_FollowMe", RADIO_FOLLOW_ME, 15.0f }, + { "Radio_TakingFire", RADIO_TAKING_FIRE, 5.0f }, + { "Radio_GoGoGo", RADIO_GO_GO_GO, MAX_CHATTER_REPEAT }, + { "Radio_Fallback", RADIO_TEAM_FALLBACK, MAX_CHATTER_REPEAT }, + { "Radio_StickTogether", RADIO_STICK_TOGETHER_TEAM, MAX_CHATTER_REPEAT }, + { "Radio_GetInPosition", RADIO_GET_IN_POSITION, MAX_CHATTER_REPEAT }, + { "Radio_StormTheFront", RADIO_STORM_THE_FRONT, MAX_CHATTER_REPEAT }, + { "Radio_ReportTeam", RADIO_REPORT_TEAM, MAX_CHATTER_REPEAT }, + { "Radio_Affirmative", RADIO_AFFIRMATIVE, MAX_CHATTER_REPEAT }, + { "Radio_EnemySpotted", RADIO_ENEMY_SPOTTED, 4.0f }, + { "Radio_NeedBackup", RADIO_NEED_BACKUP, MAX_CHATTER_REPEAT }, + { "Radio_SectorClear", RADIO_SECTOR_CLEAR, 10.0f }, + { "Radio_InPosition", RADIO_IN_POSITION, 10.0f }, + { "Radio_ReportingIn", RADIO_REPORTING_IN, MAX_CHATTER_REPEAT }, + { "Radio_ShesGonnaBlow", RADIO_SHES_GONNA_BLOW, MAX_CHATTER_REPEAT }, + { "Radio_Negative", RADIO_NEGATIVE, MAX_CHATTER_REPEAT }, + { "Radio_EnemyDown", RADIO_ENEMY_DOWN, 10.0f }, + { "Chatter_DiePain", CHATTER_PAIN_DIED, MAX_CHATTER_REPEAT }, + { "Chatter_GoingToPlantBomb", CHATTER_GOING_TO_PLANT_BOMB, 5.0f }, + { "Chatter_GoingToGuardVIPSafety", CHATTER_GOING_TO_GUARD_VIP_SAFETY, MAX_CHATTER_REPEAT }, + { "Chatter_RescuingHostages", CHATTER_RESCUING_HOSTAGES, MAX_CHATTER_REPEAT }, + { "Chatter_TeamKill", CHATTER_TEAM_ATTACK, MAX_CHATTER_REPEAT }, + { "Chatter_GuardingVipSafety", CHATTER_GUARDING_VIP_SAFETY, MAX_CHATTER_REPEAT }, + { "Chatter_PlantingC4", CHATTER_PLANTING_BOMB, 10.0f }, + { "Chatter_InCombat", CHATTER_IN_COMBAT, MAX_CHATTER_REPEAT }, + { "Chatter_SeeksEnemy", CHATTER_SEEK_ENEMY, MAX_CHATTER_REPEAT }, + { "Chatter_Nothing", CHATTER_NOTHING, MAX_CHATTER_REPEAT }, + { "Chatter_EnemyDown", CHATTER_ENEMY_DOWN, 10.0f }, + { "Chatter_UseHostage", CHATTER_USING_HOSTAGES, MAX_CHATTER_REPEAT }, + { "Chatter_WonTheRound", CHATTER_WON_THE_ROUND, MAX_CHATTER_REPEAT }, + { "Chatter_QuicklyWonTheRound", CHATTER_QUICK_WON_ROUND, MAX_CHATTER_REPEAT }, + { "Chatter_NoEnemiesLeft", CHATTER_NO_ENEMIES_LEFT, MAX_CHATTER_REPEAT }, + { "Chatter_FoundBombPlace", CHATTER_FOUND_BOMB_PLACE, 15.0f }, + { "Chatter_WhereIsTheBomb", CHATTER_WHERE_IS_THE_BOMB, MAX_CHATTER_REPEAT }, + { "Chatter_DefendingBombSite", CHATTER_DEFENDING_BOMBSITE, MAX_CHATTER_REPEAT }, + { "Chatter_BarelyDefused", CHATTER_BARELY_DEFUSED, MAX_CHATTER_REPEAT }, + { "Chatter_NiceshotCommander", CHATTER_NICESHOT_COMMANDER, MAX_CHATTER_REPEAT }, + { "Chatter_ReportingIn", CHATTER_REPORTING_IN, 10.0f }, + { "Chatter_SpotTheBomber", CHATTER_SPOT_THE_BOMBER, 4.3f }, + { "Chatter_VIPSpotted", CHATTER_VIP_SPOTTED, 5.3f }, + { "Chatter_FriendlyFire", CHATTER_FRIENDLY_FIRE, 2.1f }, + { "Chatter_GotBlinded", CHATTER_BLINDED, 5.0f }, + { "Chatter_GuardDroppedC4", CHATTER_GUARDING_DROPPED_BOMB, 3.0f }, + { "Chatter_DefusingC4", CHATTER_DEFUSING_BOMB, 3.0f }, + { "Chatter_FoundC4", CHATTER_FOUND_BOMB, 5.5f }, + { "Chatter_ScaredEmotion", CHATTER_SCARED_EMOTE, 6.1f }, + { "Chatter_HeardEnemy", CHATTER_SCARED_EMOTE, 12.8f }, + { "Chatter_SniperWarning", CHATTER_SNIPER_WARNING, 14.3f }, + { "Chatter_SniperKilled", CHATTER_SNIPER_KILLED, 12.1f }, + { "Chatter_OneEnemyLeft", CHATTER_ONE_ENEMY_LEFT, 12.5f }, + { "Chatter_TwoEnemiesLeft", CHATTER_TWO_ENEMIES_LEFT, 12.5f }, + { "Chatter_ThreeEnemiesLeft", CHATTER_THREE_ENEMIES_LEFT, 12.5f }, + { "Chatter_NiceshotPall", CHATTER_NICESHOT_PALL, 2.0f }, + { "Chatter_GoingToGuardHostages", CHATTER_GOING_TO_GUARD_HOSTAGES, 3.0f }, + { "Chatter_GoingToGuardDoppedBomb", CHATTER_GOING_TO_GUARD_DROPPED_BOMB, 6.0f }, + { "Chatter_OnMyWay", CHATTER_ON_MY_WAY, 1.5f }, + { "Chatter_LeadOnSir", CHATTER_LEAD_ON_SIR, 5.0f }, + { "Chatter_Pinned_Down", CHATTER_PINNED_DOWN, 5.0f }, + { "Chatter_GottaFindTheBomb", CHATTER_GOTTA_FIND_BOMB, 3.0f }, + { "Chatter_You_Heard_The_Man", CHATTER_YOU_HEARD_THE_MAN, 3.0f }, + { "Chatter_Lost_The_Commander", CHATTER_LOST_COMMANDER, 4.5f }, + { "Chatter_NewRound", CHATTER_NEW_ROUND, 3.5f }, + { "Chatter_CoverMe", CHATTER_COVER_ME, 3.5f }, + { "Chatter_BehindSmoke", CHATTER_BEHIND_SMOKE, 3.5f }, + { "Chatter_BombSiteSecured", CHATTER_BOMB_SITE_SECURED, 3.5f }, + { "Chatter_GoingToCamp", CHATTER_GOING_TO_CAMP, 25.0f }, + { "Chatter_Camp", CHATTER_CAMP, 25.0f }, + }; + + while (fp.getLine (lineBuffer)) { + lineBuffer.trim (); + + if (isCommentLine (lineBuffer)) { + continue; + } + extern ConVar yb_chatter_path; + + if (lineBuffer.substr (0, 11) == "RewritePath") { + yb_chatter_path.set (lineBuffer.substr (11).trim ().chars ()); + } + else if (lineBuffer.substr (0, 5) == "Event") { + auto items = lineBuffer.substr (5).split ("="); + + if (items.length () != 2) { + util.logEntry (true, LL_ERROR, "Error in chatter config file syntax... Please correct all errors."); + continue; + } + + for (auto &item : items) { + item.trim (); + } + items[1].trim ("(;)"); + + for (const auto &event : chatterEventMap) { + if (event.str == items[0]) { + // this does common work of parsing comma-separated chatter line + auto sounds = items[1].split (","); + + for (auto &sound : sounds) { + sound.trim ().trim ("\""); + float duration = game.getWaveLen (sound.chars ()); + + if (duration > 0.0f) { + m_chatter[event.code].push ({ sound, event.repeat, duration }); + } + } + sounds.clear (); + } + } + } + } + fp.close (); + } + else { + yb_communication_type.set (1); + util.logEntry (true, LL_DEFAULT, "Chatter Communication disabled."); + } + + // localizer inititalization + if (!(game.is (GAME_LEGACY)) && util.openConfig ("lang.cfg", "Specified language not found", &fp, true)) { + if (game.isDedicated ()) { + return; // dedicated server will use only english translation + } + enum Lang { LANG_ORIGINAL, LANG_TRANSLATED, LANG_UNDEFINED } langState = static_cast (LANG_UNDEFINED); + + String temp; + Pair lang; + + while (fp.getLine (lineBuffer)) { + lineBuffer.trim (); + + if (isCommentLine (lineBuffer)) { + continue; + } + + if (lineBuffer == "[ORIGINAL]") { + langState = LANG_ORIGINAL; + + if (!temp.empty ()) { + lang.second = cr::move (temp); + lang.second.trim (); + } + + if (!lang.second.empty () && !lang.first.empty ()) { + game.addTranslation (lang.first, lang.second); + } + } + else if (lineBuffer == "[TRANSLATED]") { + + lang.first = cr::move (temp); + lang.first.trim (); + + langState = LANG_TRANSLATED; + } + else { + switch (langState) { + case LANG_ORIGINAL: + temp += lineBuffer; + break; + + case LANG_TRANSLATED: + temp += lineBuffer; + break; + + case LANG_UNDEFINED: + break; + } + } + } + fp.close (); + } + else if (game.is (GAME_LEGACY)) { + util.logEntry (true, LL_DEFAULT, "Multilingual system disabled, due to your Counter-Strike Version!"); + } + else if (strcmp (yb_language.str (), "en") != 0) { + util.logEntry (true, LL_ERROR, "Couldn't load language configuration"); + } + + // set personality weapon pointers here + m_weaponPrefs[PERSONALITY_NORMAL] = reinterpret_cast (&m_normalWeaponPrefs); + m_weaponPrefs[PERSONALITY_RUSHER] = reinterpret_cast (&m_rusherWeaponPrefs); + m_weaponPrefs[PERSONALITY_CAREFUL] = reinterpret_cast (&m_carefulWeaponPrefs); +} + +BotName *Config::pickBotName (void) { + if (m_botNames.empty ()) { + return nullptr; + } + + for (int i = 0; i < MAX_ENGINE_PLAYERS * 4; i++) { + auto botName = &m_botNames.random (); + + if (botName->name.length () < 3 || botName->usedBy != 0) { + continue; + } + return botName; + } + return nullptr; +} + +void Config::clearUsedName (Bot *bot) { + for (auto &name : m_botNames) { + if (name.usedBy == bot->index ()) { + name.usedBy = 0; + break; + } + } +} + +void Config::initWeapons (void) { + m_weapons.reserve (NUM_WEAPONS + 1); + + // fill array with available weapons + m_weapons.push ({ WEAPON_KNIFE, "weapon_knife", "knife.mdl", 0, 0, -1, -1, 0, 0, 0, 0, 0, 0, true }); + m_weapons.push ({ WEAPON_USP, "weapon_usp", "usp.mdl", 500, 1, -1, -1, 1, 1, 2, 2, 0, 12, false }); + m_weapons.push ({ WEAPON_GLOCK, "weapon_glock18", "glock18.mdl", 400, 1, -1, -1, 1, 2, 1, 1, 0, 20, false }); + m_weapons.push ({ WEAPON_DEAGLE, "weapon_deagle", "deagle.mdl", 650, 1, 2, 2, 1, 3, 4, 4, 2, 7, false }); + m_weapons.push ({ WEAPON_P228, "weapon_p228", "p228.mdl", 600, 1, 2, 2, 1, 4, 3, 3, 0, 13, false }); + m_weapons.push ({ WEAPON_ELITE, "weapon_elite", "elite.mdl", 800, 1, 0, 0, 1, 5, 5, 5, 0, 30, false }); + m_weapons.push ({ WEAPON_FIVESEVEN, "weapon_fiveseven", "fiveseven.mdl", 750, 1, 1, 1, 1, 6, 5, 5, 0, 20, false }); + m_weapons.push ({ WEAPON_M3, "weapon_m3", "m3.mdl", 1700, 1, 2, -1, 2, 1, 1, 1, 0, 8, false }); + m_weapons.push ({ WEAPON_XM1014, "weapon_xm1014", "xm1014.mdl", 3000, 1, 2, -1, 2, 2, 2, 2, 0, 7, false }); + m_weapons.push ({ WEAPON_MP5, "weapon_mp5navy", "mp5.mdl", 1500, 1, 2, 1, 3, 1, 2, 2, 0, 30, true }); + m_weapons.push ({ WEAPON_TMP, "weapon_tmp", "tmp.mdl", 1250, 1, 1, 1, 3, 2, 1, 1, 0, 30, true }); + m_weapons.push ({ WEAPON_P90, "weapon_p90", "p90.mdl", 2350, 1, 2, 1, 3, 3, 4, 4, 0, 50, true }); + m_weapons.push ({ WEAPON_MAC10, "weapon_mac10", "mac10.mdl", 1400, 1, 0, 0, 3, 4, 1, 1, 0, 30, true }); + m_weapons.push ({ WEAPON_UMP45, "weapon_ump45", "ump45.mdl", 1700, 1, 2, 2, 3, 5, 3, 3, 0, 25, true }); + m_weapons.push ({ WEAPON_AK47, "weapon_ak47", "ak47.mdl", 2500, 1, 0, 0, 4, 1, 2, 2, 2, 30, true }); + m_weapons.push ({ WEAPON_SG552, "weapon_sg552", "sg552.mdl", 3500, 1, 0, -1, 4, 2, 4, 4, 2, 30, true }); + m_weapons.push ({ WEAPON_M4A1, "weapon_m4a1", "m4a1.mdl", 3100, 1, 1, 1, 4, 3, 3, 3, 2, 30, true }); + m_weapons.push ({ WEAPON_GALIL, "weapon_galil", "galil.mdl", 2000, 1, 0, 0, 4, -1, 1, 1, 2, 35, true }); + m_weapons.push ({ WEAPON_FAMAS, "weapon_famas", "famas.mdl", 2250, 1, 1, 1, 4, -1, 1, 1, 2, 25, true }); + m_weapons.push ({ WEAPON_AUG, "weapon_aug", "aug.mdl", 3500, 1, 1, 1, 4, 4, 4, 4, 2, 30, true }); + m_weapons.push ({ WEAPON_SCOUT, "weapon_scout", "scout.mdl", 2750, 1, 2, 0, 4, 5, 3, 2, 3, 10, false }); + m_weapons.push ({ WEAPON_AWP, "weapon_awp", "awp.mdl", 4750, 1, 2, 0, 4, 6, 5, 6, 3, 10, false }); + m_weapons.push ({ WEAPON_G3SG1, "weapon_g3sg1", "g3sg1.mdl", 5000, 1, 0, 2, 4, 7, 6, 6, 3, 20, false }); + m_weapons.push ({ WEAPON_SG550, "weapon_sg550", "sg550.mdl", 4200, 1, 1, 1, 4, 8, 5, 5, 3, 30, false }); + m_weapons.push ({ WEAPON_M249, "weapon_m249", "m249.mdl", 5750, 1, 2, 1, 5, 1, 1, 1, 2, 100, true }); + m_weapons.push ({ WEAPON_SHIELD, "weapon_shield", "shield.mdl", 2200, 0, 1, 1, 8, -1, 8, 8, 0, 0, false }); + + // not needed actually, but cause too much refactoring for now. todo + m_weapons.push ({ 0, "", "", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, false }); +} + +void Config::adjustWeaponPrices (void) { + // elite price is 1000$ on older versions of cs... + if (!(game.is (GAME_LEGACY))) { + return; + } + + for (auto &weapon : m_weapons) { + if (weapon.id == WEAPON_ELITE) { + weapon.price = 1000; + break; + } + } +} + +WeaponInfo &Config::findWeaponById (const int id) { + for (auto &weapon : m_weapons) { + if (weapon.id == id) { + return weapon; + } + } + return m_weapons.at (0); +} diff --git a/source/navigate.cpp b/source/navigate.cpp index 391f39b..1829e8b 100644 --- a/source/navigate.cpp +++ b/source/navigate.cpp @@ -1,4 +1,4 @@ -// +// // Yet Another POD-Bot, based on PODBot by Markus Klinge ("CountFloyd"). // Copyright (c) YaPB Development Team. // @@ -10,17 +10,17 @@ #include ConVar yb_whose_your_daddy ("yb_whose_your_daddy", "0"); -ConVar yb_debug_heuristic_type ("yb_debug_heuristic_type", "0"); +ConVar yb_debug_heuristic_type ("yb_debug_heuristic_type", "4"); int Bot::searchGoal (void) { // chooses a destination (goal) waypoint for a bot - if (!g_bombPlanted && m_team == TEAM_TERRORIST && (g_mapFlags & MAP_DE)) { + if (!bots.isBombPlanted () && m_team == TEAM_TERRORIST && game.mapIs (MAP_DE)) { edict_t *pent = nullptr; - while (!engine.isNullEntity (pent = g_engfuncs.pfnFindEntityByString (pent, "classname", "weaponbox"))) { + while (!game.isNullEntity (pent = engfuncs.pfnFindEntityByString (pent, "classname", "weaponbox"))) { if (strcmp (STRING (pent->v.model), "models/w_backpack.mdl") == 0) { - int index = waypoints.getNearest (engine.getAbsPos (pent)); + int index = waypoints.getNearest (game.getAbsPos (pent)); if (waypoints.exists (index)) { return m_loosedBombWptIndex = index; @@ -77,14 +77,14 @@ int Bot::searchGoal (void) { offensive = m_agressionLevel * 100.0f; defensive = m_fearLevel * 100.0f; - if (g_mapFlags & (MAP_AS | MAP_CS)) { + if (game.mapIs (MAP_AS | MAP_CS)) { if (m_team == TEAM_TERRORIST) { defensive += 25.0f; offensive -= 25.0f; } else if (m_team == TEAM_COUNTER) { // on hostage maps force more bots to save hostages - if (g_mapFlags & MAP_CS) { + if (game.mapIs (MAP_CS)) { defensive -= 25.0f - m_difficulty * 0.5f; offensive += 25.0f + m_difficulty * 5.0f; } @@ -94,11 +94,12 @@ int Bot::searchGoal (void) { } } } - else if ((g_mapFlags & MAP_DE) && m_team == TEAM_COUNTER) { - if (g_bombPlanted && taskId () != TASK_ESCAPEFROMBOMB && !waypoints.getBombPos ().empty ()) { - if (g_bombSayString) { + else if (game.mapIs (MAP_DE) && m_team == TEAM_COUNTER) { + if (bots.isBombPlanted () && taskId () != TASK_ESCAPEFROMBOMB && !waypoints.getBombPos ().empty ()) { + + if (bots.hasBombSay (BSS_NEED_TO_FIND_CHAT)) { pushChatMessage (CHAT_BOMBPLANT); - g_bombSayString = false; + bots.clearBombSay (BSS_NEED_TO_FIND_CHAT); } return m_chosenGoalIndex = getBombPoint (); } @@ -109,9 +110,9 @@ int Bot::searchGoal (void) { defensive += 10.0f; } } - else if ((g_mapFlags & MAP_DE) && m_team == TEAM_TERRORIST && g_timeRoundStart + 10.0f < engine.timebase ()) { + else if (game.mapIs (MAP_DE) && m_team == TEAM_TERRORIST && bots.getRoundStartTime () + 10.0f < game.timebase ()) { // send some terrorists to guard planted bomb - if (!m_defendedBomb && g_bombPlanted && taskId () != TASK_ESCAPEFROMBOMB && getBombTimeleft () >= 15.0) { + if (!m_defendedBomb && bots.isBombPlanted () && taskId () != TASK_ESCAPEFROMBOMB && getBombTimeleft () >= 15.0) { return m_chosenGoalIndex = getDefendPoint (waypoints.getBombPos ()); } } @@ -148,25 +149,25 @@ int Bot::getGoalProcess (int tactic, IntArray *defensive, IntArray *offsensive) int goalChoices[4] = { INVALID_WAYPOINT_INDEX, INVALID_WAYPOINT_INDEX, INVALID_WAYPOINT_INDEX, INVALID_WAYPOINT_INDEX }; if (tactic == 0 && !(*defensive).empty ()) { // careful goal - filterGoals (*defensive, goalChoices); + postprocessGoals (*defensive, goalChoices); } else if (tactic == 1 && !waypoints.m_campPoints.empty ()) // camp waypoint goal { // pickup sniper points if possible for sniping bots if (!waypoints.m_sniperPoints.empty () && usesSniper ()) { - filterGoals (waypoints.m_sniperPoints, goalChoices); + postprocessGoals (waypoints.m_sniperPoints, goalChoices); } else { - filterGoals (waypoints.m_campPoints, goalChoices); + postprocessGoals (waypoints.m_campPoints, goalChoices); } } else if (tactic == 2 && !(*offsensive).empty ()) { // offensive goal - filterGoals (*offsensive, goalChoices); + postprocessGoals (*offsensive, goalChoices); } else if (tactic == 3 && !waypoints.m_goalPoints.empty ()) // map goal waypoint { // force bomber to select closest goal, if round-start goal was reset by something - if (m_hasC4 && g_timeRoundStart + 20.0f < engine.timebase ()) { + if (m_hasC4 && bots.getRoundStartTime () + 20.0f < game.timebase ()) { float minDist = 9999999.0f; int count = 0; @@ -186,14 +187,14 @@ int Bot::getGoalProcess (int tactic, IntArray *defensive, IntArray *offsensive) } } - for (int i = 0; i < 4; i++) { - if (goalChoices[i] == INVALID_WAYPOINT_INDEX) { - goalChoices[i] = waypoints.m_goalPoints.random (); + for (auto &choice : goalChoices) { + if (choice == INVALID_WAYPOINT_INDEX) { + choice = waypoints.m_goalPoints.random (); } } } else { - filterGoals (waypoints.m_goalPoints, goalChoices); + postprocessGoals (waypoints.m_goalPoints, goalChoices); } } @@ -204,10 +205,10 @@ int Bot::getGoalProcess (int tactic, IntArray *defensive, IntArray *offsensive) if (goalChoices[0] == INVALID_WAYPOINT_INDEX) { return m_chosenGoalIndex = rng.getInt (0, waypoints.length () - 1); } - bool isSorting = false; + bool sorting = false; do { - isSorting = false; + sorting = false; for (int i = 0; i < 3; i++) { int testIndex = goalChoices[i + 1]; @@ -216,22 +217,19 @@ int Bot::getGoalProcess (int tactic, IntArray *defensive, IntArray *offsensive) break; } - int goal1 = m_team == TEAM_TERRORIST ? (g_experienceData + (m_currentWaypointIndex * waypoints.length ()) + goalChoices[i])->team0Value : (g_experienceData + (m_currentWaypointIndex * waypoints.length ()) + goalChoices[i])->team1Value; - int goal2 = m_team == TEAM_TERRORIST ? (g_experienceData + (m_currentWaypointIndex * waypoints.length ()) + goalChoices[i + 1])->team0Value : (g_experienceData + (m_currentWaypointIndex * waypoints.length ()) + goalChoices[i + 1])->team1Value; - - if (goal1 < goal2) { + if (waypoints.getDangerValue (m_team, m_currentWaypointIndex, goalChoices[i]) < waypoints.getDangerValue (m_team, m_currentWaypointIndex, goalChoices[i + 1])) { goalChoices[i + 1] = goalChoices[i]; goalChoices[i] = testIndex; - isSorting = true; + sorting = true; } } - } while (isSorting); + } while (sorting); return m_chosenGoalIndex = goalChoices[0]; // return and store goal } -void Bot::filterGoals (const IntArray &goals, int *result) { +void Bot::postprocessGoals (const IntArray &goals, int *result) { // this function filters the goals, so new goal is not bot's old goal, and array of goals doesn't contains duplicate goals int searchCount = 0; @@ -251,7 +249,7 @@ void Bot::filterGoals (const IntArray &goals, int *result) { } bool Bot::hasActiveGoal (void) { - int goal = task ()->data; + int goal = getTask ()->data; if (goal == INVALID_WAYPOINT_INDEX) { // not decided about a goal return false; @@ -273,16 +271,16 @@ void Bot::resetCollision (void) { m_collisionState = COLLISION_NOTDECICED; m_collStateIndex = 0; - for (int i = 0; i < MAX_COLLIDE_MOVES; i++) { - m_collideMoves[i] = 0; + for (auto &collideMove : m_collideMoves) { + collideMove = 0; } } void Bot::ignoreCollision (void) { resetCollision (); - m_prevTime = engine.timebase () + 1.2f; - m_lastCollTime = engine.timebase () + 1.5f; + m_prevTime = game.timebase () + 1.2f; + m_lastCollTime = game.timebase () + 1.5f; m_isStuck = false; m_checkTerrain = false; m_prevSpeed = m_moveSpeed; @@ -296,28 +294,28 @@ void Bot::avoidIncomingPlayers (edict_t *touch) { return; } - int ownId = engine.indexOfEntity (ent ()); - int otherId = engine.indexOfEntity (touch); + int ownId = game.indexOfEntity (ent ()); + int otherId = game.indexOfEntity (touch); if (ownId < otherId) { return; } if (m_avoid) { - int currentId = engine.indexOfEntity (m_avoid); + int currentId = game.indexOfEntity (m_avoid); if (currentId < otherId) { return; } } m_avoid = touch; - m_avoidTime = engine.timebase () + 0.33f + calcThinkInterval (); + m_avoidTime = game.timebase () + 0.33f + getFrameInterval (); } bool Bot::doPlayerAvoidance (const Vector &normal) { // avoid collision entity, got it form official csbot - if (m_avoidTime > engine.timebase () && isAlive (m_avoid)) { + 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); @@ -361,15 +359,15 @@ void Bot::checkTerrain (float movedDistance, const Vector &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 < engine.timebase () && m_seeEnemyTime + 0.8f < engine.timebase () && taskId () != TASK_ATTACK) { + if ((m_moveSpeed >= 10.0f || m_strafeSpeed >= 10.0f) && m_lastCollTime < game.timebase () && m_seeEnemyTime + 0.8f < game.timebase () && taskId () != TASK_ATTACK) { // didn't we move enough previously? if (movedDistance < 2.0f && m_prevSpeed >= 20.0f) { - m_prevTime = engine.timebase (); // then consider being stuck + m_prevTime = game.timebase (); // then consider being stuck m_isStuck = true; if (cr::fzero (m_firstCollideTime)) { - m_firstCollideTime = engine.timebase () + 0.2f; + m_firstCollideTime = game.timebase () + 0.2f; } } // not stuck yet @@ -377,9 +375,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 = engine.timebase () + 0.2f; + m_firstCollideTime = game.timebase () + 0.2f; } - else if (m_firstCollideTime <= engine.timebase ()) { + else if (m_firstCollideTime <= game.timebase ()) { m_isStuck = true; } } @@ -390,7 +388,7 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { // not stuck? if (!m_isStuck) { - if (m_probeTime + 0.5f < engine.timebase ()) { + if (m_probeTime + 0.5f < game.timebase ()) { resetCollision (); // reset collision memory if not being stuck for 0.5 secs } else { @@ -417,7 +415,7 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { bits |= (PROBE_JUMP | PROBE_STRAFE); } else { - bits |= (PROBE_STRAFE | (m_jumpStateTimer < engine.timebase () ? PROBE_JUMP : 0)); + bits |= (PROBE_STRAFE | (m_jumpStateTimer < game.timebase () ? PROBE_JUMP : 0)); } // collision check allowed if not flying through the air @@ -436,10 +434,10 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { state[i + 1] = 0; // to start strafing, we have to first figure out if the target is on the left side or right side - makeVectors (m_moveAngles); + game.makeVectors (m_moveAngles); Vector dirToPoint = (pev->origin - m_destOrigin).normalize2D (); - Vector rightSide = g_pGlobals->v_right.normalize2D (); + Vector rightSide = game.vec.right.normalize2D (); bool dirRight = false; bool dirLeft = false; @@ -452,21 +450,21 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { else { dirLeft = true; } - const Vector &testDir = m_moveSpeed > 0.0f ? g_pGlobals->v_forward : -g_pGlobals->v_forward; + const Vector &testDir = m_moveSpeed > 0.0f ? game.vec.forward : -game.vec.forward; // now check which side is blocked - src = pev->origin + g_pGlobals->v_right * 32.0f; + src = pev->origin + game.vec.right * 32.0f; dst = src + testDir * 32.0f; - engine.testHull (src, dst, TRACE_IGNORE_MONSTERS, head_hull, ent (), &tr); + game.testHull (src, dst, TRACE_IGNORE_MONSTERS, head_hull, ent (), &tr); - if (tr.flFraction != 1.0f) + if (tr.flFraction != 1.0f) { blockedRight = true; - - src = pev->origin - g_pGlobals->v_right * 32.0f; + } + src = pev->origin - game.vec.right * 32.0f; dst = src + testDir * 32.0f; - engine.testHull (src, dst, TRACE_IGNORE_MONSTERS, head_hull, ent (), &tr); + game.testHull (src, dst, TRACE_IGNORE_MONSTERS, head_hull, ent (), &tr); if (tr.flFraction != 1.0f) { blockedLeft = true; @@ -509,18 +507,18 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { } if (seesEntity (m_destOrigin)) { - makeVectors (m_moveAngles); + game.makeVectors (m_moveAngles); - src = eyePos (); - src = src + g_pGlobals->v_right * 15.0f; + src = getEyesPos (); + src = src + game.vec.right * 15.0f; - engine.testLine (src, m_destOrigin, TRACE_IGNORE_EVERYTHING, ent (), &tr); + game.testLine (src, m_destOrigin, TRACE_IGNORE_EVERYTHING, ent (), &tr); if (tr.flFraction >= 1.0f) { - src = eyePos (); - src = src - g_pGlobals->v_right * 15.0f; + src = getEyesPos (); + src = src - game.vec.right * 15.0f; - engine.testLine (src, m_destOrigin, TRACE_IGNORE_EVERYTHING, ent (), &tr); + game.testLine (src, m_destOrigin, TRACE_IGNORE_EVERYTHING, ent (), &tr); if (tr.flFraction >= 1.0f) { state[i] += 5; @@ -534,7 +532,7 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { src = pev->origin + Vector (0.0f, 0.0f, -17.0f); } dst = src + dirNormal * 30.0f; - engine.testLine (src, dst, TRACE_IGNORE_EVERYTHING, ent (), &tr); + game.testLine (src, dst, TRACE_IGNORE_EVERYTHING, ent (), &tr); if (tr.flFraction != 1.0f) { state[i] += 10; @@ -589,8 +587,8 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { m_collideMoves[i] = state[i]; } - m_collideTime = engine.timebase (); - m_probeTime = engine.timebase () + 0.5f; + m_collideTime = game.timebase (); + m_probeTime = game.timebase () + 0.5f; m_collisionProbeBits = bits; m_collisionState = COLLISION_PROBING; m_collStateIndex = 0; @@ -598,12 +596,12 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { } if (m_collisionState == COLLISION_PROBING) { - if (m_probeTime < engine.timebase ()) { + if (m_probeTime < game.timebase ()) { m_collStateIndex++; - m_probeTime = engine.timebase () + 0.5f; + m_probeTime = game.timebase () + 0.5f; if (m_collStateIndex > MAX_COLLIDE_MOVES) { - m_navTimeset = engine.timebase () - 5.0f; + m_navTimeset = game.timebase () - 5.0f; resetCollision (); } } @@ -613,7 +611,7 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { case COLLISION_JUMP: if (isOnFloor () || isInWater ()) { pev->button |= IN_JUMP; - m_jumpStateTimer = engine.timebase () + rng.getFloat (0.7f, 1.5f); + m_jumpStateTimer = game.timebase () + rng.getFloat (0.7f, 1.5f); } break; @@ -639,7 +637,7 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { doPlayerAvoidance (dirNormal); } -bool Bot::processNavigation (void) { +bool Bot::updateNavigation (void) { // this function is a main path navigation TraceResult tr, tr2; @@ -651,10 +649,10 @@ bool Bot::processNavigation (void) { // if wayzone radios non zero vary origin a bit depending on the body angles if (m_currentPath->radius > 0) { - makeVectors (Vector (pev->angles.x, cr::angleNorm (pev->angles.y + rng.getFloat (-90.0f, 90.0f)), 0.0f)); - m_waypointOrigin = m_waypointOrigin + g_pGlobals->v_forward * rng.getFloat (0, m_currentPath->radius); + game.makeVectors (Vector (pev->angles.x, cr::angleNorm (pev->angles.y + rng.getFloat (-90.0f, 90.0f)), 0.0f)); + m_waypointOrigin = m_waypointOrigin + game.vec.forward * rng.getFloat (0, m_currentPath->radius); } - m_navTimeset = engine.timebase (); + m_navTimeset = game.timebase (); } m_destOrigin = m_waypointOrigin + pev->view_ofs; @@ -703,35 +701,35 @@ bool Bot::processNavigation (void) { bool liftClosedDoorExists = false; // update waypoint time set - m_navTimeset = engine.timebase (); + m_navTimeset = game.timebase (); // trace line to door - engine.testLine (pev->origin, m_currentPath->origin, TRACE_IGNORE_EVERYTHING, ent (), &tr2); + game.testLine (pev->origin, m_currentPath->origin, TRACE_IGNORE_EVERYTHING, ent (), &tr2); if (tr2.flFraction < 1.0f && strcmp (STRING (tr2.pHit->v.classname), "func_door") == 0 && (m_liftState == LIFT_NO_NEARBY || m_liftState == LIFT_WAITING_FOR || m_liftState == LIFT_LOOKING_BUTTON_OUTSIDE) && pev->groundentity != tr2.pHit) { if (m_liftState == LIFT_NO_NEARBY) { m_liftState = LIFT_LOOKING_BUTTON_OUTSIDE; - m_liftUsageTime = engine.timebase () + 7.0f; + m_liftUsageTime = game.timebase () + 7.0f; } liftClosedDoorExists = true; } // trace line down - engine.testLine (m_currentPath->origin, m_currentPath->origin + Vector (0.0f, 0.0f, -50.0f), TRACE_IGNORE_EVERYTHING, ent (), &tr); + game.testLine (m_currentPath->origin, m_currentPath->origin + Vector (0.0f, 0.0f, -50.0f), TRACE_IGNORE_EVERYTHING, ent (), &tr); // if trace result shows us that it is a lift - if (!engine.isNullEntity (tr.pHit) && !m_path.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 (!game.isNullEntity (tr.pHit) && !m_path.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 == LIFT_NO_NEARBY || m_liftState == LIFT_WAITING_FOR || m_liftState == LIFT_LOOKING_BUTTON_OUTSIDE) && tr.pHit->v.velocity.z == 0.0f) { if (cr::abs (pev->origin.z - tr.vecEndPos.z) < 70.0f) { m_liftEntity = tr.pHit; m_liftState = LIFT_ENTERING_IN; m_liftTravelPos = m_currentPath->origin; - m_liftUsageTime = engine.timebase () + 5.0f; + m_liftUsageTime = game.timebase () + 5.0f; } } else if (m_liftState == LIFT_TRAVELING_BY) { m_liftState = LIFT_LEAVING; - m_liftUsageTime = engine.timebase () + 7.0f; + m_liftUsageTime = game.timebase () + 7.0f; } } else if (!m_path.empty ()) // no lift found at waypoint @@ -740,14 +738,14 @@ bool Bot::processNavigation (void) { int nextNode = m_path.next (); if (waypoints.exists (nextNode) && (waypoints[nextNode].flags & FLAG_LIFT)) { - engine.testLine (m_currentPath->origin, waypoints[nextNode].origin, TRACE_IGNORE_EVERYTHING, ent (), &tr); + game.testLine (m_currentPath->origin, waypoints[nextNode].origin, TRACE_IGNORE_EVERYTHING, ent (), &tr); - if (!engine.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)) { + 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 = LIFT_LOOKING_BUTTON_OUTSIDE; - m_liftUsageTime = engine.timebase () + 15.0f; + m_liftUsageTime = game.timebase () + 15.0f; } } @@ -760,7 +758,7 @@ bool Bot::processNavigation (void) { m_moveSpeed = 0.0f; m_strafeSpeed = 0.0f; - m_navTimeset = engine.timebase (); + m_navTimeset = game.timebase (); m_aimFlags |= AIM_NAVPOINT; resetCollision (); @@ -769,7 +767,7 @@ bool Bot::processNavigation (void) { bool needWaitForTeammate = false; // if some bot is following a bot going into lift - he should take the same lift to go - for (int i = 0; i < engine.maxClients (); i++) { + for (int i = 0; i < game.maxClients (); i++) { Bot *bot = bots.getBot (i); if (bot == nullptr || bot == this) { @@ -793,11 +791,11 @@ bool Bot::processNavigation (void) { if (needWaitForTeammate) { m_liftState = LIFT_WAIT_FOR_TEAMMATES; - m_liftUsageTime = engine.timebase () + 8.0f; + m_liftUsageTime = game.timebase () + 8.0f; } else { m_liftState = LIFT_LOOKING_BUTTON_INSIDE; - m_liftUsageTime = engine.timebase () + 10.0f; + m_liftUsageTime = game.timebase () + 10.0f; } } } @@ -807,7 +805,7 @@ bool Bot::processNavigation (void) { // need to wait our following teammate ? bool needWaitForTeammate = false; - for (int i = 0; i < engine.maxClients (); i++) { + for (int i = 0; i < game.maxClients (); i++) { Bot *bot = bots.getBot (i); if (bot == nullptr) { @@ -832,7 +830,7 @@ bool Bot::processNavigation (void) { m_moveSpeed = 0.0f; m_strafeSpeed = 0.0f; - m_navTimeset = engine.timebase (); + m_navTimeset = game.timebase (); m_aimFlags |= AIM_NAVPOINT; resetCollision (); @@ -840,36 +838,36 @@ bool Bot::processNavigation (void) { } // else we need to look for button - if (!needWaitForTeammate || m_liftUsageTime < engine.timebase ()) { + if (!needWaitForTeammate || m_liftUsageTime < game.timebase ()) { m_liftState = LIFT_LOOKING_BUTTON_INSIDE; - m_liftUsageTime = engine.timebase () + 10.0f; + m_liftUsageTime = game.timebase () + 10.0f; } } // bot is trying to find button inside a lift if (m_liftState == LIFT_LOOKING_BUTTON_INSIDE) { - edict_t *button = getNearestButton (STRING (m_liftEntity->v.targetname)); + edict_t *button = lookupButton (STRING (m_liftEntity->v.targetname)); // got a valid button entity ? - if (!engine.isNullEntity (button) && pev->groundentity == m_liftEntity && m_buttonPushTime + 1.0f < engine.timebase () && m_liftEntity->v.velocity.z == 0.0f && isOnFloor ()) { + 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 = engine.timebase (); + m_navTimeset = game.timebase (); } } // is lift activated and bot is standing on it and lift is moving ? if (m_liftState == LIFT_LOOKING_BUTTON_INSIDE || m_liftState == LIFT_ENTERING_IN || m_liftState == LIFT_WAIT_FOR_TEAMMATES || m_liftState == LIFT_WAITING_FOR) { - if (pev->groundentity == m_liftEntity && m_liftEntity->v.velocity.z != 0.0f && isOnFloor () && ((waypoints[m_prevWptIndex[0]].flags & FLAG_LIFT) || !engine.isNullEntity (m_targetEntity))) { + if (pev->groundentity == m_liftEntity && m_liftEntity->v.velocity.z != 0.0f && isOnFloor () && ((waypoints[m_prevWptIndex[0]].flags & FLAG_LIFT) || !game.isNullEntity (m_targetEntity))) { m_liftState = LIFT_TRAVELING_BY; - m_liftUsageTime = engine.timebase () + 14.0f; + m_liftUsageTime = game.timebase () + 14.0f; if ((pev->origin - m_destOrigin).lengthSq () < 225.0f) { m_moveSpeed = 0.0f; m_strafeSpeed = 0.0f; - m_navTimeset = engine.timebase (); + m_navTimeset = game.timebase (); m_aimFlags |= AIM_NAVPOINT; resetCollision (); @@ -885,7 +883,7 @@ bool Bot::processNavigation (void) { m_moveSpeed = 0.0f; m_strafeSpeed = 0.0f; - m_navTimeset = engine.timebase (); + m_navTimeset = game.timebase (); m_aimFlags |= AIM_NAVPOINT; resetCollision (); @@ -896,7 +894,7 @@ bool Bot::processNavigation (void) { if (m_liftState == LIFT_LOOKING_BUTTON_OUTSIDE) { // button has been pressed, lift should come - if (m_buttonPushTime + 8.0f >= engine.timebase ()) { + if (m_buttonPushTime + 8.0f >= game.timebase ()) { if (waypoints.exists (m_prevWptIndex[0])) { m_destOrigin = waypoints[m_prevWptIndex[0]].origin; } @@ -908,25 +906,23 @@ bool Bot::processNavigation (void) { m_moveSpeed = 0.0f; m_strafeSpeed = 0.0f; - m_navTimeset = engine.timebase (); + m_navTimeset = game.timebase (); m_aimFlags |= AIM_NAVPOINT; resetCollision (); } } - else if (!engine.isNullEntity (m_liftEntity)) { - edict_t *button = getNearestButton (STRING (m_liftEntity->v.targetname)); + else if (!game.isNullEntity (m_liftEntity)) { + edict_t *button = lookupButton (STRING (m_liftEntity->v.targetname)); // if we got a valid button entity - if (!engine.isNullEntity (button)) { + if (!game.isNullEntity (button)) { // lift is already used ? bool liftUsed = false; // iterate though clients, and find if lift already used - for (int i = 0; i < engine.maxClients (); i++) { - const Client &client = g_clients[i]; - - if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.team != m_team || client.ent == ent () || engine.isNullEntity (client.ent->v.groundentity)) { + for (const auto &client : util.getClients ()) { + if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.team != m_team || client.ent == ent () || game.isNullEntity (client.ent->v.groundentity)) { continue; } @@ -955,13 +951,13 @@ bool Bot::processNavigation (void) { m_pickupType = PICKUP_BUTTON; m_liftState = LIFT_WAITING_FOR; - m_navTimeset = engine.timebase (); - m_liftUsageTime = engine.timebase () + 20.0f; + m_navTimeset = game.timebase (); + m_liftUsageTime = game.timebase () + 20.0f; } } else { m_liftState = LIFT_WAITING_FOR; - m_liftUsageTime = engine.timebase () + 15.0f; + m_liftUsageTime = game.timebase () + 15.0f; } } } @@ -981,7 +977,7 @@ bool Bot::processNavigation (void) { m_moveSpeed = 0.0f; m_strafeSpeed = 0.0f; - m_navTimeset = engine.timebase (); + m_navTimeset = game.timebase (); m_aimFlags |= AIM_NAVPOINT; resetCollision (); @@ -1009,12 +1005,12 @@ bool Bot::processNavigation (void) { } } - if (!engine.isNullEntity (m_liftEntity) && !(m_currentPath->flags & FLAG_LIFT)) { + if (!game.isNullEntity (m_liftEntity) && !(m_currentPath->flags & FLAG_LIFT)) { if (m_liftState == LIFT_TRAVELING_BY) { m_liftState = LIFT_LEAVING; - m_liftUsageTime = engine.timebase () + 10.0f; + m_liftUsageTime = game.timebase () + 10.0f; } - if (m_liftState == LIFT_LEAVING && m_liftUsageTime < engine.timebase () && pev->groundentity != m_liftEntity) { + if (m_liftState == LIFT_LEAVING && m_liftUsageTime < game.timebase () && pev->groundentity != m_liftEntity) { m_liftState = LIFT_NO_NEARBY; m_liftUsageTime = 0.0f; @@ -1022,7 +1018,7 @@ bool Bot::processNavigation (void) { } } - if (m_liftUsageTime < engine.timebase () && m_liftUsageTime != 0.0f) { + if (m_liftUsageTime < game.timebase () && m_liftUsageTime != 0.0f) { m_liftEntity = nullptr; m_liftState = LIFT_NO_NEARBY; m_liftUsageTime = 0.0f; @@ -1044,17 +1040,17 @@ bool Bot::processNavigation (void) { } // check if we are going through a door... - if (g_mapFlags & MAP_HAS_DOORS) { - engine.testLine (pev->origin, m_waypointOrigin, TRACE_IGNORE_MONSTERS, ent (), &tr); + if (game.mapIs (MAP_HAS_DOORS)) { + game.testLine (pev->origin, m_waypointOrigin, TRACE_IGNORE_MONSTERS, ent (), &tr); - if (!engine.isNullEntity (tr.pHit) && engine.isNullEntity (m_liftEntity) && strncmp (STRING (tr.pHit->v.classname), "func_door", 9) == 0) { + if (!game.isNullEntity (tr.pHit) && game.isNullEntity (m_liftEntity) && strncmp (STRING (tr.pHit->v.classname), "func_door", 9) == 0) { // if the door is near enough... - if ((engine.getAbsPos (tr.pHit) - pev->origin).lengthSq () < 2500.0f) { + if ((game.getAbsPos (tr.pHit) - pev->origin).lengthSq () < 2500.0f) { ignoreCollision (); // don't consider being stuck if (rng.chance (50)) { // do not use door directrly under xash, or we will get failed assert in gamedll code - if (g_gameFlags & GAME_XASH_ENGINE) { + if (game.is (GAME_XASH_ENGINE)) { pev->button |= IN_USE; } else { @@ -1067,25 +1063,25 @@ bool Bot::processNavigation (void) { m_aimFlags &= ~(AIM_LAST_ENEMY | AIM_PREDICT_PATH); m_canChooseAimDirection = false; - edict_t *button = getNearestButton (STRING (tr.pHit->v.targetname)); + edict_t *button = lookupButton (STRING (tr.pHit->v.targetname)); // check if we got valid button - if (!engine.isNullEntity (button)) { + if (!game.isNullEntity (button)) { m_pickupItem = button; m_pickupType = PICKUP_BUTTON; - m_navTimeset = engine.timebase (); + m_navTimeset = game.timebase (); } // if bot hits the door, then it opens, so wait a bit to let it open safely - if (pev->velocity.length2D () < 2 && m_timeDoorOpen < engine.timebase ()) { - startTask (TASK_PAUSE, TASKPRI_PAUSE, INVALID_WAYPOINT_INDEX, engine.timebase () + 0.5f, false); - m_timeDoorOpen = engine.timebase () + 1.0f; // retry in 1 sec until door is open + if (pev->velocity.length2D () < 2 && m_timeDoorOpen < game.timebase ()) { + startTask (TASK_PAUSE, TASKPRI_PAUSE, INVALID_WAYPOINT_INDEX, game.timebase () + 0.5f, false); + m_timeDoorOpen = game.timebase () + 1.0f; // retry in 1 sec until door is open edict_t *pent = nullptr; - if (m_tryOpenDoor++ > 2 && findNearestPlayer (reinterpret_cast (&pent), ent (), 256.0f, false, false, true, true, false)) { - m_seeEnemyTime = engine.timebase () - 0.5f; + if (m_tryOpenDoor++ > 2 && util.findNearestPlayer (reinterpret_cast (&pent), ent (), 256.0f, false, false, true, true, false)) { + m_seeEnemyTime = game.timebase () - 0.5f; m_states |= STATE_SEEING_ENEMY; m_aimFlags |= AIM_ENEMY; @@ -1096,8 +1092,9 @@ bool Bot::processNavigation (void) { m_tryOpenDoor = 0; } - else + else { m_tryOpenDoor = 0; + } } } } @@ -1124,54 +1121,35 @@ bool Bot::processNavigation (void) { } // check if waypoint has a special travelflag, so they need to be reached more precisely - for (int i = 0; i < MAX_PATH_INDEX; i++) { - if (m_currentPath->connectionFlags[i] != 0) { + for (auto &flag : m_currentPath->connectionFlags) { + if (flag != 0) { desiredDistance = 0.0f; break; } } // needs precise placement - check if we get past the point - if (desiredDistance < 22.0f && waypointDistance < 30.0f && (pev->origin + (pev->velocity * calcThinkInterval ()) - m_waypointOrigin).lengthSq () > cr::square (waypointDistance)) { + if (desiredDistance < 22.0f && waypointDistance < 30.0f && (pev->origin + (pev->velocity * getFrameInterval ()) - m_waypointOrigin).lengthSq () > cr::square (waypointDistance)) { desiredDistance = waypointDistance + 1.0f; } if (waypointDistance < desiredDistance) { - + // did we reach a destination waypoint? - if (task ()->data == m_currentWaypointIndex) { - // add goal values + if (getTask ()->data == m_currentWaypointIndex) { if (m_chosenGoalIndex != INVALID_WAYPOINT_INDEX) { - int16 waypointValue; - int startIndex = m_chosenGoalIndex; - int goalIndex = m_currentWaypointIndex; + // add goal values + int goalValue = waypoints.getDangerValue (m_team, m_chosenGoalIndex, m_currentWaypointIndex); + int addedValue = static_cast (pev->health * 0.5f + m_goalValue * 0.5f); - if (m_team == TEAM_TERRORIST) { - waypointValue = (g_experienceData + (startIndex * waypoints.length ()) + goalIndex)->team0Value; - waypointValue += static_cast (pev->health * 0.5f); - waypointValue += static_cast (m_goalValue * 0.5f); + goalValue = cr::clamp (goalValue + addedValue, -MAX_GOAL_VALUE, MAX_GOAL_VALUE); - if (waypointValue < -MAX_GOAL_VALUE) { - waypointValue = -MAX_GOAL_VALUE; - } - else if (waypointValue > MAX_GOAL_VALUE) { - waypointValue = MAX_GOAL_VALUE; - } - (g_experienceData + (startIndex * waypoints.length ()) + goalIndex)->team0Value = waypointValue; - } - else { - waypointValue = (g_experienceData + (startIndex * waypoints.length ()) + goalIndex)->team1Value; - waypointValue += static_cast (pev->health * 0.5f); - waypointValue += static_cast (m_goalValue * 0.5f); + // update the experience for team + auto experience = waypoints.getRawExperience (); - if (waypointValue < -MAX_GOAL_VALUE) { - waypointValue = -MAX_GOAL_VALUE; - } - else if (waypointValue > MAX_GOAL_VALUE) { - waypointValue = MAX_GOAL_VALUE; - } - (g_experienceData + (startIndex * waypoints.length ()) + goalIndex)->team1Value = waypointValue; + if (experience) { + (experience + (m_chosenGoalIndex * waypoints.length ()) + m_currentWaypointIndex)->value[m_team] = goalValue; } } return true; @@ -1179,9 +1157,9 @@ bool Bot::processNavigation (void) { else if (m_path.empty ()) { return false; } - int taskTarget = task ()->data; + int taskTarget = getTask ()->data; - if ((g_mapFlags & MAP_DE) && g_bombPlanted && m_team == TEAM_COUNTER && taskId () != TASK_ESCAPEFROMBOMB && taskTarget != INVALID_WAYPOINT_INDEX) { + if (game.mapIs (MAP_DE) && bots.isBombPlanted () && m_team == TEAM_COUNTER && taskId () != TASK_ESCAPEFROMBOMB && taskTarget != INVALID_WAYPOINT_INDEX) { const Vector &bombOrigin = isBombAudible (); // bot within 'hearable' bomb tick noises? @@ -1211,11 +1189,11 @@ void Bot::searchShortestPath (int srcIndex, int destIndex) { // this function finds the shortest path from source index to destination index if (!waypoints.exists (srcIndex)){ - logEntry (false, LL_ERROR, "Pathfinder source path index not valid (%d)", srcIndex); + util.logEntry (false, LL_ERROR, "Pathfinder source path index not valid (%d)", srcIndex); return; } else if (!waypoints.exists (destIndex)) { - logEntry (false, LL_ERROR, "Pathfinder destination path index not valid (%d)", destIndex); + util.logEntry (false, LL_ERROR, "Pathfinder destination path index not valid (%d)", destIndex); return; } clearSearchNodes (); @@ -1226,11 +1204,11 @@ void Bot::searchShortestPath (int srcIndex, int destIndex) { m_path.push (srcIndex); while (srcIndex != destIndex) { - srcIndex = *(waypoints.m_pathMatrix + (srcIndex * waypoints.length ()) + destIndex); + srcIndex = (waypoints.m_matrix + (srcIndex * waypoints.length ()) + destIndex)->index; if (srcIndex < 0) { m_prevGoalIndex = INVALID_WAYPOINT_INDEX; - task ()->data = INVALID_WAYPOINT_INDEX; + getTask ()->data = INVALID_WAYPOINT_INDEX; return; } @@ -1238,217 +1216,215 @@ void Bot::searchShortestPath (int srcIndex, int destIndex) { } } -float gfunctionKillsDistT (int currentIndex, int parentIndex) { - // least kills and number of nodes to goal for a team - - if (parentIndex == INVALID_WAYPOINT_INDEX) { - return 0.0f; - } - float cost = static_cast ((g_experienceData + (currentIndex * waypoints.length ()) + currentIndex)->team0Damage + g_highestDamageT); - - Path ¤t = waypoints[currentIndex]; - - for (int i = 0; i < MAX_PATH_INDEX; i++) { - int neighbour = current.index[i]; - - if (neighbour != INVALID_WAYPOINT_INDEX) { - cost += (g_experienceData + (neighbour * waypoints.length ()) + neighbour)->team0Damage; - } - } - - if (current.flags & FLAG_CROUCH) { - cost *= 1.5f; - } - return cost; -} - -float gfunctionKillsDistCT (int currentIndex, int parentIndex) { - // least kills and number of nodes to goal for a team - - if (parentIndex == INVALID_WAYPOINT_INDEX) { - return 0.0f; - } - float cost = static_cast ((g_experienceData + (currentIndex * waypoints.length ()) + currentIndex)->team1Damage + g_highestDamageCT); - - Path ¤t = waypoints[currentIndex]; - - for (int i = 0; i < MAX_PATH_INDEX; i++) { - int neighbour = current.index[i]; - - if (neighbour != INVALID_WAYPOINT_INDEX) { - cost += static_cast ((g_experienceData + (neighbour * waypoints.length ()) + neighbour)->team1Damage); - } - } - - if (current.flags & FLAG_CROUCH) { - cost *= 1.5f; - - } - return cost; -} - -float gfunctionKillsDistCTWithHostage (int currentIndex, int parentIndex) { - // least kills and number of nodes to goal for a team - - Path ¤t = waypoints[currentIndex]; - - if (current.flags & FLAG_NOHOSTAGE) { - return 65355.0f; - } - else if (current.flags & (FLAG_CROUCH | FLAG_LADDER)) { - return gfunctionKillsDistCT (currentIndex, parentIndex) * 500.0f; - } - return gfunctionKillsDistCT (currentIndex, parentIndex); -} - -float gfunctionKillsT (int currentIndex, int) { - // least kills to goal for a team - - float cost = (g_experienceData + (currentIndex * waypoints.length ()) + currentIndex)->team0Damage; - - Path ¤t = waypoints[currentIndex]; - - for (int i = 0; i < MAX_PATH_INDEX; i++) { - int neighbour = current.index[i]; - - if (neighbour != INVALID_WAYPOINT_INDEX) { - cost += (g_experienceData + (neighbour * waypoints.length ()) + neighbour)->team0Damage; - } - } - - if (current.flags & FLAG_CROUCH) { - cost *= 1.5f; - } - return cost + 0.5f; -} - -float gfunctionKillsCT (int currentIndex, int parentIndex) { - // least kills to goal for a team - - if (parentIndex == INVALID_WAYPOINT_INDEX) { - return 0.0f; - } - float cost = (g_experienceData + (currentIndex * waypoints.length ()) + currentIndex)->team1Damage; - - Path ¤t = waypoints[currentIndex]; - - for (int i = 0; i < MAX_PATH_INDEX; i++) { - int neighbour = current.index[i]; - - if (neighbour != INVALID_WAYPOINT_INDEX) { - cost += (g_experienceData + (neighbour * waypoints.length ()) + neighbour)->team1Damage; - } - } - - if (current.flags & FLAG_CROUCH) { - cost *= 1.5f; - } - return cost + 0.5f; -} - -float gfunctionKillsCTWithHostage (int currentIndex, int parentIndex) { - // least kills to goal for a team - - if (parentIndex == INVALID_WAYPOINT_INDEX) { - return 0.0f; - } - Path ¤t = waypoints[currentIndex]; - - if (current.flags & FLAG_NOHOSTAGE) { - return 65355.0f; - } - else if (current.flags & (FLAG_CROUCH | FLAG_LADDER)) { - return gfunctionKillsDistCT (currentIndex, parentIndex) * 500.0f; - } - return gfunctionKillsCT (currentIndex, parentIndex); -} - -float gfunctionPathDist (int currentIndex, int parentIndex) { - if (parentIndex == INVALID_WAYPOINT_INDEX) { - return 0.0f; - } - Path &parent = waypoints[parentIndex]; - Path ¤t = waypoints[currentIndex]; - - for (int i = 0; i < MAX_PATH_INDEX; i++) { - if (parent.index[i] == currentIndex) { - // we don't like ladder or crouch point - if (current.flags & (FLAG_CROUCH | FLAG_LADDER)) { - return parent.distances[i] * 1.5f; - } - return static_cast (parent.distances[i]); - } - } - return 65355.0f; -} - -float gfunctionPathDistWithHostage (int currentIndex, int parentIndex) { - Path ¤t = waypoints[currentIndex]; - - if (current.flags & FLAG_NOHOSTAGE) { - return 65355.0f; - } - else if (current.flags & (FLAG_CROUCH | FLAG_LADDER)) { - return gfunctionPathDist (currentIndex, parentIndex) * 500.0f; - } - return gfunctionPathDist (currentIndex, parentIndex); -} - -float hfunctionPathDist (int index, int, int goalIndex) { - // square distance heuristic - - Path &start = waypoints[index]; - Path &goal = waypoints[goalIndex]; - - float x = cr::abs (start.origin.x - goal.origin.x); - float y = cr::abs (start.origin.y - goal.origin.y); - float z = cr::abs (start.origin.z - goal.origin.z); - - switch (yb_debug_heuristic_type.integer ()) { - case 0: - default: - return cr::max (cr::max (x, y), z); // chebyshev distance - - case 1: - return x + y + z; // manhattan distance - - case 2: - return 0.0f; // no heuristic - - case 3: - case 4: - // euclidean based distance - float euclidean = cr::powf (cr::powf (x, 2.0f) + cr::powf (y, 2.0f) + cr::powf (z, 2.0f), 0.5f); - - if (yb_debug_heuristic_type.integer () == 4) { - return 1000.0f * (cr::ceilf (euclidean) - euclidean); - } - return euclidean; - } -} - -float hfunctionPathDistWithHostage (int index, int startIndex, int goalIndex) { - // square distance heuristic with hostages - - if (waypoints[startIndex].flags & FLAG_NOHOSTAGE) { - return 65355.0f; - } - return hfunctionPathDist (index, startIndex, goalIndex); -} - -float hfunctionNone (int index, int startIndex, int goalIndex) { - return hfunctionPathDist (index, startIndex, goalIndex) / 128.0f * 10.0f; -} - void Bot::searchPath (int srcIndex, int destIndex, SearchPathType pathType /*= SEARCH_PATH_FASTEST */) { // this function finds a path from srcIndex to destIndex + // least kills and number of nodes to goal for a team + auto gfunctionKillsDist = [] (int team, int currentIndex, int parentIndex) -> float { + if (parentIndex == INVALID_WAYPOINT_INDEX) { + return 0.0f; + } + auto cost = static_cast (waypoints.getDangerDamage (team, currentIndex, currentIndex) + waypoints.getHighestDamageForTeam (team)); + Path ¤t = waypoints[currentIndex]; + + for (auto &neighbour : current.index) { + if (neighbour != INVALID_WAYPOINT_INDEX) { + cost += static_cast (waypoints.getDangerDamage (team, neighbour, neighbour)); + } + } + + if (current.flags & FLAG_CROUCH) { + cost *= 1.5f; + } + return cost; + }; + + // least kills and number of nodes to goal for a team + auto gfunctionKillsDistCTWithHostage = [&gfunctionKillsDist] (int team, int currentIndex, int parentIndex) -> float { + Path ¤t = waypoints[currentIndex]; + + if (current.flags & FLAG_NOHOSTAGE) { + return 65355.0f; + } + else if (current.flags & (FLAG_CROUCH | FLAG_LADDER)) { + return gfunctionKillsDist (team, currentIndex, parentIndex) * 500.0f; + } + return gfunctionKillsDist (team, currentIndex, parentIndex); + }; + + // least kills to goal for a team + auto gfunctionKills = [] (int team, int currentIndex, int) -> float { + auto cost = static_cast (waypoints.getDangerDamage (team, currentIndex, currentIndex)); + Path ¤t = waypoints[currentIndex]; + + for (auto &neighbour : current.index) { + if (neighbour != INVALID_WAYPOINT_INDEX) { + cost += static_cast (waypoints.getDangerDamage (team, neighbour, neighbour)); + } + } + + if (current.flags & FLAG_CROUCH) { + cost *= 1.5f; + } + return cost + 0.5f; + }; + + // least kills to goal for a team + auto gfunctionKillsCTWithHostage = [&gfunctionKills] (int team, int currentIndex, int parentIndex) -> float { + if (parentIndex == INVALID_WAYPOINT_INDEX) { + return 0.0f; + } + Path ¤t = waypoints[currentIndex]; + + if (current.flags & FLAG_NOHOSTAGE) { + return 65355.0f; + } + else if (current.flags & (FLAG_CROUCH | FLAG_LADDER)) { + return gfunctionKills (team, currentIndex, parentIndex) * 500.0f; + } + return gfunctionKills (team, currentIndex, parentIndex); + }; + + auto gfunctionPathDist = [] (int, int currentIndex, int parentIndex) -> float { + if (parentIndex == INVALID_WAYPOINT_INDEX) { + return 0.0f; + } + Path &parent = waypoints[parentIndex]; + Path ¤t = waypoints[currentIndex]; + + for (int i = 0; i < MAX_PATH_INDEX; i++) { + if (parent.index[i] == currentIndex) { + // we don't like ladder or crouch point + if (current.flags & (FLAG_CROUCH | FLAG_LADDER)) { + return parent.distances[i] * 1.5f; + } + return static_cast (parent.distances[i]); + } + } + return 65355.0f; + }; + + auto gfunctionPathDistWithHostage = [&gfunctionPathDist] (int, int currentIndex, int parentIndex) -> float { + Path ¤t = waypoints[currentIndex]; + + if (current.flags & FLAG_NOHOSTAGE) { + return 65355.0f; + } + else if (current.flags & (FLAG_CROUCH | FLAG_LADDER)) { + return gfunctionPathDist (TEAM_UNASSIGNED, currentIndex, parentIndex) * 500.0f; + } + return gfunctionPathDist (TEAM_UNASSIGNED, currentIndex, parentIndex); + }; + + // square distance heuristic + auto hfunctionPathDist = [] (int index, int, int goalIndex) -> float { + Path &start = waypoints[index]; + Path &goal = waypoints[goalIndex]; + + float x = cr::abs (start.origin.x - goal.origin.x); + float y = cr::abs (start.origin.y - goal.origin.y); + float z = cr::abs (start.origin.z - goal.origin.z); + + switch (yb_debug_heuristic_type.integer ()) { + case 0: + default: + return cr::max (cr::max (x, y), z); // chebyshev distance + + case 1: + return x + y + z; // manhattan distance + + case 2: + return 0.0f; // no heuristic + + case 3: + case 4: + // euclidean based distance + float euclidean = cr::powf (cr::powf (x, 2.0f) + cr::powf (y, 2.0f) + cr::powf (z, 2.0f), 0.5f); + + if (yb_debug_heuristic_type.integer () == 4) { + return 1000.0f *(cr::ceilf (euclidean) - euclidean); + } + return euclidean; + } + }; + + // square distance heuristic with hostages + auto hfunctionPathDistWithHostage = [&hfunctionPathDist] (int index, int startIndex, int goalIndex) -> float { + if (waypoints[startIndex].flags & FLAG_NOHOSTAGE) { + return 65355.0f; + } + return hfunctionPathDist (index, startIndex, goalIndex); + }; + + // none heuristic + auto hfunctionNone = [&hfunctionPathDist] (int index, int startIndex, int goalIndex) -> float { + return hfunctionPathDist (index, startIndex, goalIndex) / 128.0f * 10.0f; + }; + + // get correct calculation for heuristic + auto calculate = [&] (bool hfun, int a, int b, int c) -> float { + if (pathType == SEARCH_PATH_SAFEST_FASTER) { + if (game.mapIs (MAP_CS) && hasHostage ()) { + if (hfun) { + return hfunctionPathDistWithHostage (a, b, c); + } + else { + return gfunctionKillsDistCTWithHostage (a, b, c); + } + } + else { + if (hfun) { + return hfunctionPathDist (a, b, c); + } + else { + return gfunctionKillsDist (a, b, c); + } + } + } + else if (pathType == SEARCH_PATH_SAFEST) { + if (game.mapIs (MAP_CS) && hasHostage ()) { + if (hfun) { + return hfunctionNone (a, b, c); + } + else { + return gfunctionKillsCTWithHostage (a, b, c); + } + } + else { + if (hfun) { + return hfunctionNone (a, b, c); + } + else { + return gfunctionKills (a, b, c); + } + } + } + else { + if (game.mapIs (MAP_CS) && hasHostage ()) { + if (hfun) { + return hfunctionPathDistWithHostage (a, b, c); + } + else { + return gfunctionPathDistWithHostage (a, b, c); + } + } + else { + if (hfun) { + return hfunctionPathDist (a, b, c); + } + else { + return gfunctionPathDist (a, b, c); + } + } + } + }; + if (!waypoints.exists (srcIndex)) { - logEntry (false, LL_ERROR, "Pathfinder source path index not valid (%d)", srcIndex); + util.logEntry (false, LL_ERROR, "Pathfinder source path index not valid (%d)", srcIndex); return; } else if (!waypoints.exists (destIndex)) { - logEntry (false, LL_ERROR, "Pathfinder destination path index not valid (%d)", destIndex); + util.logEntry (false, LL_ERROR, "Pathfinder destination path index not valid (%d)", destIndex); return; } clearSearchNodes (); @@ -1457,58 +1433,11 @@ void Bot::searchPath (int srcIndex, int destIndex, SearchPathType pathType /*= S m_goalValue = 0.0f; clearRoute (); - - float (*gcalc) (int, int) = nullptr; - float (*hcalc) (int, int, int) = nullptr; - - switch (pathType) { - default: - case SEARCH_PATH_FASTEST: - if ((g_mapFlags & MAP_CS) && hasHostage ()) { - gcalc = gfunctionPathDistWithHostage; - hcalc = hfunctionPathDistWithHostage; - } - else { - gcalc = gfunctionPathDist; - hcalc = hfunctionPathDist; - } - break; - - case SEARCH_PATH_SAFEST_FASTER: - if (m_team == TEAM_TERRORIST) { - gcalc = gfunctionKillsDistT; - hcalc = hfunctionPathDist; - } - else if ((g_mapFlags & MAP_CS) && hasHostage ()) { - gcalc = gfunctionKillsDistCTWithHostage; - hcalc = hfunctionPathDistWithHostage; - } - else { - gcalc = gfunctionKillsDistCT; - hcalc = hfunctionPathDist; - } - break; - - case SEARCH_PATH_SAFEST: - if (m_team == TEAM_TERRORIST) { - gcalc = gfunctionKillsT; - hcalc = hfunctionNone; - } - else if ((g_mapFlags & MAP_CS) && hasHostage ()) { - gcalc = gfunctionKillsCTWithHostage; - hcalc = hfunctionNone; - } - else { - gcalc = gfunctionKillsCT; - hcalc = hfunctionNone; - } - break; - } auto srcRoute = &m_routes[srcIndex]; // put start node into open list - srcRoute->g = gcalc (srcIndex, INVALID_WAYPOINT_INDEX); - srcRoute->f = srcRoute->g + hcalc (srcIndex, srcIndex, destIndex); + srcRoute->g = calculate (false, m_team, srcIndex, INVALID_WAYPOINT_INDEX); + srcRoute->f = srcRoute->g + calculate (true, srcIndex, srcIndex, destIndex); srcRoute->state = ROUTE_OPEN; m_routeQue.clear (); @@ -1520,7 +1449,7 @@ void Bot::searchPath (int srcIndex, int destIndex, SearchPathType pathType /*= S // safes us from bad waypoints... if (m_routeQue.length () >= MAX_ROUTE_LENGTH - 1) { - logEntry (true, LL_ERROR, "A* Search for bots \"%s\" has tried to build path with at least %d nodes. Seems to be waypoints are broken.", STRING (pev->netname), m_routeQue.length ()); + util.logEntry (true, LL_ERROR, "A* Search for bots \"%s\" has tried to build path with at least %d nodes. Seems to be waypoints are broken.", STRING (pev->netname), m_routeQue.length ()); // bail out to shortest path searchShortestPath (srcIndex, destIndex); @@ -1550,17 +1479,15 @@ void Bot::searchPath (int srcIndex, int destIndex, SearchPathType pathType /*= S curRoute->state = ROUTE_CLOSED; // now expand the current node - for (int i = 0; i < MAX_PATH_INDEX; i++) { - int currentChild = waypoints[currentIndex].index[i]; - - if (currentChild == INVALID_WAYPOINT_INDEX) { + for (auto &child : waypoints[currentIndex].index) { + if (child == INVALID_WAYPOINT_INDEX) { continue; } - auto childRoute = &m_routes[currentChild]; + auto childRoute = &m_routes[child]; // calculate the F value as F = G + H - float g = curRoute->g + gcalc (currentChild, currentIndex); - float h = hcalc (currentChild, srcIndex, destIndex); + float g = curRoute->g + calculate (false, m_team, child, currentIndex); + float h = calculate (true, child, srcIndex, destIndex); float f = g + h; if (childRoute->state == ROUTE_NEW || childRoute->f > f) { @@ -1571,7 +1498,7 @@ void Bot::searchPath (int srcIndex, int destIndex, SearchPathType pathType /*= S childRoute->g = g; childRoute->f = f; - m_routeQue.push (currentChild, g); + m_routeQue.push (child, g); } } } @@ -1615,7 +1542,7 @@ int Bot::searchAimingPoint (const Vector &to) { } while (destIndex != m_currentWaypointIndex) { - destIndex = *(waypoints.m_pathMatrix + (destIndex * waypoints.length ()) + m_currentWaypointIndex); + destIndex = (waypoints.m_matrix + (destIndex * waypoints.length ()) + m_currentWaypointIndex)->index; if (destIndex < 0) { break; @@ -1667,7 +1594,7 @@ bool Bot::searchOptimalPoint (void) { } // cts with hostages should not pick waypoints with no hostage flag - if ((g_mapFlags & MAP_CS) && m_team == TEAM_COUNTER && (waypoints[at].flags & FLAG_NOHOSTAGE) && hasHostage ()) { + if (game.mapIs (MAP_CS) && m_team == TEAM_COUNTER && (waypoints[at].flags & FLAG_NOHOSTAGE) && hasHostage ()) { continue; } @@ -1677,7 +1604,7 @@ bool Bot::searchOptimalPoint (void) { } // check if waypoint is already used by another bot... - if (g_timeRoundStart + 5.0f > engine.timebase () && isOccupiedPoint (at)) { + if (bots.getRoundStartTime () + 5.0f > game.timebase () && isOccupiedPoint (at)) { busy = at; continue; } @@ -1785,7 +1712,7 @@ void Bot::getValidPoint (void) { m_prevGoalIndex = newGoal; m_chosenGoalIndex = newGoal; - task ()->data = newGoal; + getTask ()->data = newGoal; // do path finding if it's not the current waypoint if (newGoal != m_currentWaypointIndex) { @@ -1806,51 +1733,27 @@ void Bot::getValidPoint (void) { m_waypointOrigin = m_currentPath->origin; } - else if (m_navTimeset + getReachTime () < engine.timebase () && engine.isNullEntity (m_enemy)) { - if (m_team == TEAM_TERRORIST) { - int value = (g_experienceData + (m_currentWaypointIndex * waypoints.length ()) + m_currentWaypointIndex)->team0Damage; - value += 100; + else if (m_navTimeset + getReachTime () < game.timebase () && game.isNullEntity (m_enemy)) { + auto experience = waypoints.getRawExperience (); - if (value > MAX_DAMAGE_VALUE) { - value = MAX_DAMAGE_VALUE; - } - (g_experienceData + (m_currentWaypointIndex * waypoints.length ()) + m_currentWaypointIndex)->team0Damage = static_cast (value); + // increase danager for both teams + for (int team = TEAM_TERRORIST; team < MAX_TEAM_COUNT; team++) { + + int damageValue = waypoints.getDangerDamage (team, m_currentWaypointIndex, m_currentWaypointIndex); + damageValue = cr::clamp (damageValue + 100, 0, MAX_DAMAGE_VALUE); // affect nearby connected with victim waypoints - for (int i = 0; i < MAX_PATH_INDEX; i++) { - if (waypoints.exists (m_currentPath->index[i])) { - value = (g_experienceData + (m_currentPath->index[i] * waypoints.length ()) + m_currentPath->index[i])->team0Damage; - value += 2; + for (auto &neighbour : m_currentPath->index) { + if (waypoints.exists (neighbour)) { + int neighbourValue = waypoints.getDangerDamage (team, neighbour, neighbour); + neighbourValue = cr::clamp (neighbourValue + 100, 0, MAX_DAMAGE_VALUE); - if (value > MAX_DAMAGE_VALUE) { - value = MAX_DAMAGE_VALUE; - } - (g_experienceData + (m_currentPath->index[i] * waypoints.length ()) + m_currentPath->index[i])->team0Damage = static_cast (value); + (experience + (neighbour * waypoints.length ()) + neighbour)->damage[team] = neighbourValue; } } + (experience + (m_currentWaypointIndex * waypoints.length ()) + m_currentWaypointIndex)->damage[team] = damageValue; } - else { - int value = (g_experienceData + (m_currentWaypointIndex * waypoints.length ()) + m_currentWaypointIndex)->team1Damage; - value += 100; - if (value > MAX_DAMAGE_VALUE) { - value = MAX_DAMAGE_VALUE; - } - (g_experienceData + (m_currentWaypointIndex * waypoints.length ()) + m_currentWaypointIndex)->team1Damage = static_cast (value); - - // affect nearby connected with victim waypoints - for (int i = 0; i < MAX_PATH_INDEX; i++) { - if (waypoints.exists (m_currentPath->index[i])) { - value = (g_experienceData + (m_currentPath->index[i] * waypoints.length ()) + m_currentPath->index[i])->team1Damage; - value += 2; - - if (value > MAX_DAMAGE_VALUE) { - value = MAX_DAMAGE_VALUE; - } - (g_experienceData + (m_currentPath->index[i] * waypoints.length ()) + m_currentPath->index[i])->team1Damage = static_cast (value); - } - } - } clearSearchNodes (); rechoiceGoal (); @@ -1868,7 +1771,7 @@ int Bot::changePointIndex (int index) { m_prevWptIndex[0] = m_currentWaypointIndex; m_currentWaypointIndex = index; - m_navTimeset = engine.timebase (); + m_navTimeset = game.timebase (); m_currentPath = &waypoints[index]; m_waypointFlags = m_currentPath->flags; @@ -1996,7 +1899,7 @@ int Bot::getDefendPoint (const Vector &origin) { if (distance > 512) { continue; } - engine.testLine (waypoints[i].origin, waypoints[posIndex].origin, TRACE_IGNORE_EVERYTHING, ent (), &tr); + game.testLine (waypoints[i].origin, waypoints[posIndex].origin, TRACE_IGNORE_EVERYTHING, ent (), &tr); // check if line not hit anything if (tr.flFraction != 1.0f) { @@ -2014,26 +1917,18 @@ int Bot::getDefendPoint (const Vector &origin) { // use statistic if we have them for (int i = 0; i < MAX_PATH_INDEX; i++) { if (waypointIndex[i] != INVALID_WAYPOINT_INDEX) { - Experience *exp = (g_experienceData + (waypointIndex[i] * waypoints.length ()) + waypointIndex[i]); - int experience = INVALID_WAYPOINT_INDEX; + int experience = waypoints.getDangerDamage (m_team, waypointIndex[i], waypointIndex[i]); + experience = (experience * 100) / waypoints.getHighestDamageForTeam (m_team); - if (m_team == TEAM_TERRORIST) { - experience = exp->team0Damage; - } - else { - experience = exp->team1Damage; - } - - experience = (experience * 100) / (m_team == TEAM_TERRORIST ? g_highestDamageT : g_highestDamageCT); minDistance[i] = (experience * 100) / 8192; minDistance[i] += experience; } } - bool isOrderChanged = false; + bool sorting = false; // sort results waypoints for farest distance do { - isOrderChanged = false; + sorting = false; // completely sort the data for (int i = 0; i < 3 && waypointIndex[i] != INVALID_WAYPOINT_INDEX && waypointIndex[i + 1] != INVALID_WAYPOINT_INDEX && minDistance[i] > minDistance[i + 1]; i++) { @@ -2047,9 +1942,9 @@ int Bot::getDefendPoint (const Vector &origin) { minDistance[i] = minDistance[i + 1]; minDistance[i + 1] = index; - isOrderChanged = true; + sorting = true; } - } while (isOrderChanged); + } while (sorting); if (waypointIndex[0] == INVALID_WAYPOINT_INDEX) { IntArray found; @@ -2108,9 +2003,9 @@ int Bot::getCoverPoint (float maxDistance) { } // now get enemies neigbouring points - for (int i = 0; i < MAX_PATH_INDEX; i++) { - if (waypoints[enemyIndex].index[i] != INVALID_WAYPOINT_INDEX) { - enemies.push (waypoints[enemyIndex].index[i]); + for (auto &index : waypoints[enemyIndex].index) { + if (index != INVALID_WAYPOINT_INDEX) { + enemies.push (index); } } @@ -2158,26 +2053,18 @@ int Bot::getCoverPoint (float maxDistance) { // use statistic if we have them for (int i = 0; i < MAX_PATH_INDEX; i++) { if (waypointIndex[i] != INVALID_WAYPOINT_INDEX) { - Experience *exp = (g_experienceData + (waypointIndex[i] * waypoints.length ()) + waypointIndex[i]); - int experience = INVALID_WAYPOINT_INDEX; - - if (m_team == TEAM_TERRORIST) { - experience = exp->team0Damage; - } - else { - experience = exp->team1Damage; - } + int experience = waypoints.getDangerDamage (m_team, waypointIndex[i], waypointIndex[i]); experience = (experience * 100) / MAX_DAMAGE_VALUE; minDistance[i] = (experience * 100) / 8192; minDistance[i] += experience; } } - bool isOrderChanged; + bool sorting; // sort resulting waypoints for nearest distance do { - isOrderChanged = false; + sorting = false; for (int i = 0; i < 3 && waypointIndex[i] != INVALID_WAYPOINT_INDEX && waypointIndex[i + 1] != INVALID_WAYPOINT_INDEX && minDistance[i] > minDistance[i + 1]; i++) { int index = waypointIndex[i]; @@ -2189,19 +2076,19 @@ int Bot::getCoverPoint (float maxDistance) { minDistance[i] = minDistance[i + 1]; minDistance[i + 1] = index; - isOrderChanged = true; + sorting = true; } - } while (isOrderChanged); + } while (sorting); TraceResult tr; // take the first one which isn't spotted by the enemy - for (int i = 0; i < MAX_PATH_INDEX; i++) { - if (waypointIndex[i] != INVALID_WAYPOINT_INDEX) { - engine.testLine (m_lastEnemyOrigin + Vector (0.0f, 0.0f, 36.0f), waypoints[waypointIndex[i]].origin, TRACE_IGNORE_EVERYTHING, ent (), &tr); + for (auto &index : waypointIndex) { + if (index != INVALID_WAYPOINT_INDEX) { + game.testLine (m_lastEnemyOrigin + Vector (0.0f, 0.0f, 36.0f), waypoints[index].origin, TRACE_IGNORE_EVERYTHING, ent (), &tr); if (tr.flFraction < 1.0f) { - return waypointIndex[i]; + return index; } } } @@ -2224,18 +2111,16 @@ bool Bot::getNextBestPoint (void) { return false; } - for (int i = 0; i < MAX_PATH_INDEX; i++) { - int id = m_currentPath->index[i]; - - if (id != INVALID_WAYPOINT_INDEX && waypoints.isConnected (id, m_path.next ()) && waypoints.isConnected (m_currentWaypointIndex, id)) { + for (auto &index : m_currentPath->index) { + if (index != INVALID_WAYPOINT_INDEX && waypoints.isConnected (index, m_path.next ()) && waypoints.isConnected (m_currentWaypointIndex, index)) { // don't use ladder waypoints as alternative - if (waypoints[id].flags & FLAG_LADDER) { + if (waypoints[index].flags & FLAG_LADDER) { continue; } - if (!isOccupiedPoint (id)) { - m_path.first () = id; + if (!isOccupiedPoint (index)) { + m_path.first () = index; return true; } } @@ -2267,21 +2152,14 @@ bool Bot::advanceMovement (void) { TaskID taskID = taskId (); // only if we in normal task and bomb is not planted - if (taskID == TASK_NORMAL && g_timeRoundMid + 5.0f < engine.timebase () && m_timeCamping + 5.0f < engine.timebase () && !g_bombPlanted && m_personality != PERSONALITY_RUSHER && !m_hasC4 && !m_isVIP && m_loosedBombWptIndex == INVALID_WAYPOINT_INDEX && !hasHostage ()) { + 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_loosedBombWptIndex == INVALID_WAYPOINT_INDEX && !hasHostage ()) { m_campButtons = 0; const int nextIndex = m_path.next (); - float kills = 0; - - if (m_team == TEAM_TERRORIST) { - kills = (g_experienceData + (nextIndex * waypoints.length ()) + nextIndex)->team0Damage; - } - else { - kills = (g_experienceData + (nextIndex * waypoints.length ()) + nextIndex)->team1Damage; - } + auto kills = static_cast (waypoints.getDangerDamage (m_team, nextIndex, nextIndex)); // if damage done higher than one - if (kills > 1.0f && g_timeRoundMid > engine.timebase ()) { + if (kills > 1.0f && bots.getRoundMidTime () > game.timebase ()) { switch (m_personality) { case PERSONALITY_NORMAL: kills *= 0.33f; @@ -2293,11 +2171,11 @@ bool Bot::advanceMovement (void) { } if (m_baseAgressionLevel < kills && hasPrimaryWeapon ()) { - startTask (TASK_CAMP, TASKPRI_CAMP, INVALID_WAYPOINT_INDEX, engine.timebase () + rng.getFloat (m_difficulty * 0.5f, static_cast (m_difficulty)) * 5.0f, true); - startTask (TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, getDefendPoint (waypoints[nextIndex].origin), engine.timebase () + rng.getFloat (3.0f, 10.0f), true); + startTask (TASK_CAMP, TASKPRI_CAMP, INVALID_WAYPOINT_INDEX, game.timebase () + rng.getFloat (m_difficulty * 0.5f, static_cast (m_difficulty)) * 5.0f, true); + startTask (TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, getDefendPoint (waypoints[nextIndex].origin), game.timebase () + rng.getFloat (3.0f, 10.0f), true); } } - else if (g_botsCanPause && !isOnLadder () && !isInWater () && !m_currentTravelFlags && isOnFloor ()) { + else if (bots.canPause () && !isOnLadder () && !isInWater () && !m_currentTravelFlags && isOnFloor ()) { if (static_cast (kills) == m_baseAgressionLevel) { m_campButtons |= IN_DUCK; } @@ -2314,7 +2192,7 @@ bool Bot::advanceMovement (void) { m_chosenGoalIndex = newGoal; // remember index - task ()->data = newGoal; + getTask ()->data = newGoal; // do path finding if it's not the current waypoint if (newGoal != m_currentWaypointIndex) { @@ -2375,12 +2253,12 @@ bool Bot::advanceMovement (void) { // bot not already on ladder but will be soon? if ((waypoints[destIndex].flags & FLAG_LADDER) && !isOnLadder ()) { // get ladder waypoints used by other (first moving) bots - for (int c = 0; c < engine.maxClients (); c++) { + for (int c = 0; c < game.maxClients (); c++) { Bot *otherBot = bots.getBot (c); // if another bot uses this ladder, wait 3 secs if (otherBot != nullptr && otherBot != this && otherBot->m_notKilled && otherBot->m_currentWaypointIndex == destIndex) { - startTask (TASK_PAUSE, TASKPRI_PAUSE, INVALID_WAYPOINT_INDEX, engine.timebase () + 3.0f, false); + startTask (TASK_PAUSE, TASKPRI_PAUSE, INVALID_WAYPOINT_INDEX, game.timebase () + 3.0f, false); return true; } } @@ -2393,18 +2271,18 @@ bool Bot::advanceMovement (void) { // if wayzone radius non zero vary origin a bit depending on the body angles if (m_currentPath->radius > 0.0f) { - makeVectors (Vector (pev->angles.x, cr::angleNorm (pev->angles.y + rng.getFloat (-90.0f, 90.0f)), 0.0f)); - m_waypointOrigin = m_waypointOrigin + g_pGlobals->v_forward * rng.getFloat (0.0f, m_currentPath->radius); + game.makeVectors (Vector (pev->angles.x, cr::angleNorm (pev->angles.y + rng.getFloat (-90.0f, 90.0f)), 0.0f)); + m_waypointOrigin = m_waypointOrigin + game.vec.forward * rng.getFloat (0.0f, m_currentPath->radius); } if (isOnLadder ()) { - engine.testLine (Vector (pev->origin.x, pev->origin.y, pev->absmin.z), m_waypointOrigin, TRACE_IGNORE_EVERYTHING, ent (), &tr); + game.testLine (Vector (pev->origin.x, pev->origin.y, pev->absmin.z), m_waypointOrigin, TRACE_IGNORE_EVERYTHING, ent (), &tr); if (tr.flFraction < 1.0f) { m_waypointOrigin = m_waypointOrigin + (pev->origin - m_waypointOrigin) * 0.5f + Vector (0.0f, 0.0f, 32.0f); } } - m_navTimeset = engine.timebase (); + m_navTimeset = game.timebase (); return true; } @@ -2415,24 +2293,24 @@ bool Bot::cantMoveForward (const Vector &normal, TraceResult *tr) { // use some TraceLines to determine if anything is blocking the current path of the bot. // first do a trace from the bot's eyes forward... - Vector src = eyePos (); + Vector src = getEyesPos (); Vector forward = src + normal * 24.0f; - makeVectors (Vector (0.0f, pev->angles.y, 0.0f)); + game.makeVectors (Vector (0.0f, pev->angles.y, 0.0f)); auto checkDoor = [] (TraceResult *tr) { - if (!(g_mapFlags & MAP_HAS_DOORS)) { + if (!game.mapIs (MAP_HAS_DOORS)) { return false; } return tr->flFraction < 1.0f && strncmp ("func_door", STRING (tr->pHit->v.classname), 9) != 0; }; // trace from the bot's eyes straight forward... - engine.testLine (src, forward, TRACE_IGNORE_MONSTERS, ent (), tr); + game.testLine (src, forward, TRACE_IGNORE_MONSTERS, ent (), tr); // check if the trace hit something... if (tr->flFraction < 1.0f) { - if ((g_mapFlags & MAP_HAS_DOORS) && strncmp ("func_door", STRING (tr->pHit->v.classname), 9) == 0) { + if (game.mapIs (MAP_HAS_DOORS) && strncmp ("func_door", STRING (tr->pHit->v.classname), 9) == 0) { return false; } return true; // bot's head will hit something @@ -2440,10 +2318,10 @@ bool Bot::cantMoveForward (const Vector &normal, TraceResult *tr) { // bot's head is clear, check at shoulder level... // trace from the bot's shoulder left diagonal forward to the right shoulder... - src = eyePos () + Vector (0.0f, 0.0f, -16.0f) - g_pGlobals->v_right * -16.0f; - forward = eyePos () + Vector (0.0f, 0.0f, -16.0f) + g_pGlobals->v_right * 16.0f + normal * 24.0f; + src = getEyesPos () + Vector (0.0f, 0.0f, -16.0f) - game.vec.right * -16.0f; + forward = getEyesPos () + Vector (0.0f, 0.0f, -16.0f) + game.vec.right * 16.0f + normal * 24.0f; - engine.testLine (src, forward, TRACE_IGNORE_MONSTERS, ent (), tr); + game.testLine (src, forward, TRACE_IGNORE_MONSTERS, ent (), tr); // check if the trace hit something... if (checkDoor (tr)) { @@ -2452,10 +2330,10 @@ bool Bot::cantMoveForward (const Vector &normal, TraceResult *tr) { // bot's head is clear, check at shoulder level... // trace from the bot's shoulder right diagonal forward to the left shoulder... - src = eyePos () + Vector (0.0f, 0.0f, -16.0f) + g_pGlobals->v_right * 16.0f; - forward = eyePos () + Vector (0.0f, 0.0f, -16.0f) - g_pGlobals->v_right * -16.0f + normal * 24.0f; + src = getEyesPos () + Vector (0.0f, 0.0f, -16.0f) + game.vec.right * 16.0f; + forward = getEyesPos () + Vector (0.0f, 0.0f, -16.0f) - game.vec.right * -16.0f + normal * 24.0f; - engine.testLine (src, forward, TRACE_IGNORE_MONSTERS, ent (), tr); + game.testLine (src, forward, TRACE_IGNORE_MONSTERS, ent (), tr); // check if the trace hit something... if (checkDoor (tr)) { @@ -2467,7 +2345,7 @@ bool Bot::cantMoveForward (const Vector &normal, TraceResult *tr) { src = pev->origin + Vector (0.0f, 0.0f, -19.0f + 19.0f); forward = src + Vector (0.0f, 0.0f, 10.0f) + normal * 24.0f; - engine.testLine (src, forward, TRACE_IGNORE_MONSTERS, ent (), tr); + game.testLine (src, forward, TRACE_IGNORE_MONSTERS, ent (), tr); // check if the trace hit something... if (checkDoor (tr)) { @@ -2476,7 +2354,7 @@ bool Bot::cantMoveForward (const Vector &normal, TraceResult *tr) { src = pev->origin; forward = src + normal * 24.0f; - engine.testLine (src, forward, TRACE_IGNORE_MONSTERS, ent (), tr); + game.testLine (src, forward, TRACE_IGNORE_MONSTERS, ent (), tr); // check if the trace hit something... if (checkDoor (tr)) { @@ -2485,11 +2363,11 @@ bool Bot::cantMoveForward (const Vector &normal, TraceResult *tr) { } else { // trace from the left waist to the right forward waist pos - src = pev->origin + Vector (0.0f, 0.0f, -17.0f) - g_pGlobals->v_right * -16.0f; - forward = pev->origin + Vector (0.0f, 0.0f, -17.0f) + g_pGlobals->v_right * 16.0f + normal * 24.0f; + src = pev->origin + Vector (0.0f, 0.0f, -17.0f) - game.vec.right * -16.0f; + forward = pev->origin + Vector (0.0f, 0.0f, -17.0f) + game.vec.right * 16.0f + normal * 24.0f; // trace from the bot's waist straight forward... - engine.testLine (src, forward, TRACE_IGNORE_MONSTERS, ent (), tr); + game.testLine (src, forward, TRACE_IGNORE_MONSTERS, ent (), tr); // check if the trace hit something... if (checkDoor (tr)) { @@ -2497,10 +2375,10 @@ bool Bot::cantMoveForward (const Vector &normal, TraceResult *tr) { } // trace from the left waist to the right forward waist pos - src = pev->origin + Vector (0.0f, 0.0f, -17.0f) + g_pGlobals->v_right * 16.0f; - forward = pev->origin + Vector (0.0f, 0.0f, -17.0f) - g_pGlobals->v_right * -16.0f + normal * 24.0f; + src = pev->origin + Vector (0.0f, 0.0f, -17.0f) + game.vec.right * 16.0f; + forward = pev->origin + Vector (0.0f, 0.0f, -17.0f) - game.vec.right * -16.0f + normal * 24.0f; - engine.testLine (src, forward, TRACE_IGNORE_MONSTERS, ent (), tr); + game.testLine (src, forward, TRACE_IGNORE_MONSTERS, ent (), tr); // check if the trace hit something... if (checkDoor (tr)) { @@ -2517,7 +2395,7 @@ bool Bot::canStrafeLeft (TraceResult *tr) { makeVectors (pev->v_angle); Vector src = pev->origin; - Vector left = src - g_pGlobals->v_right * -40.0f; + Vector left = src - game.vec.right * -40.0f; // trace from the bot's waist straight left... TraceLine (src, left, true, ent (), tr); @@ -2528,7 +2406,7 @@ bool Bot::canStrafeLeft (TraceResult *tr) { } src = left; - left = left + g_pGlobals->v_forward * 40.0f; + left = left + game.vec.forward * 40.0f; // trace from the strafe pos straight forward... TraceLine (src, left, true, ent (), tr); @@ -2546,7 +2424,7 @@ bool Bot::canStrafeRight (TraceResult *tr) { makeVectors (pev->v_angle); Vector src = pev->origin; - Vector right = src + g_pGlobals->v_right * 40.0f; + Vector right = src + game.vec.right * 40.0f; // trace from the bot's waist straight right... TraceLine (src, right, true, ent (), tr); @@ -2556,7 +2434,7 @@ bool Bot::canStrafeRight (TraceResult *tr) { return false; // bot's body will hit something } src = right; - right = right + g_pGlobals->v_forward * 40.0f; + right = right + game.vec.forward * 40.0f; // trace from the strafe pos straight forward... TraceLine (src, right, true, ent (), tr); @@ -2581,14 +2459,14 @@ bool Bot::canJumpUp (const Vector &normal) { } // convert current view angle to vectors for traceline math... - makeVectors (Vector (0.0f, pev->angles.y, 0.0f)); + game.makeVectors (Vector (0.0f, pev->angles.y, 0.0f)); // check for normal jump height first... Vector src = pev->origin + Vector (0.0f, 0.0f, -36.0f + 45.0f); Vector dest = src + normal * 32.0f; // trace a line forward at maximum jump height... - engine.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); if (tr.flFraction < 1.0f) { return doneCanJumpUp (normal); @@ -2598,7 +2476,7 @@ bool Bot::canJumpUp (const Vector &normal) { src = dest; dest.z = dest.z + 37.0f; - engine.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); if (tr.flFraction < 1.0f) { return false; @@ -2606,11 +2484,11 @@ bool Bot::canJumpUp (const Vector &normal) { } // now check same height to one side of the bot... - src = pev->origin + g_pGlobals->v_right * 16.0f + Vector (0.0f, 0.0f, -36.0f + 45.0f); + src = pev->origin + game.vec.right * 16.0f + Vector (0.0f, 0.0f, -36.0f + 45.0f); dest = src + normal * 32.0f; // trace a line forward at maximum jump height... - engine.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); // if trace hit something, return false if (tr.flFraction < 1.0f) { @@ -2621,7 +2499,7 @@ bool Bot::canJumpUp (const Vector &normal) { src = dest; dest.z = dest.z + 37.0f; - engine.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); // if trace hit something, return false if (tr.flFraction < 1.0f) { @@ -2629,11 +2507,11 @@ bool Bot::canJumpUp (const Vector &normal) { } // now check same height on the other side of the bot... - src = pev->origin + (-g_pGlobals->v_right * 16.0f) + Vector (0.0f, 0.0f, -36.0f + 45.0f); + src = pev->origin + (-game.vec.right * 16.0f) + Vector (0.0f, 0.0f, -36.0f + 45.0f); dest = src + normal * 32.0f; // trace a line forward at maximum jump height... - engine.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); // if trace hit something, return false if (tr.flFraction < 1.0f) { @@ -2644,7 +2522,7 @@ bool Bot::canJumpUp (const Vector &normal) { src = dest; dest.z = dest.z + 37.0f; - engine.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); // if trace hit something, return false return tr.flFraction > 1.0f; @@ -2658,7 +2536,7 @@ bool Bot::doneCanJumpUp (const Vector &normal) { TraceResult tr; // trace a line forward at maximum jump height... - engine.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); if (tr.flFraction < 1.0f) { return false; @@ -2668,7 +2546,7 @@ bool Bot::doneCanJumpUp (const Vector &normal) { src = dest; dest.z = dest.z + 37.0f; - engine.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); // if trace hit something, check duckjump if (tr.flFraction < 1.0f) { @@ -2677,11 +2555,11 @@ bool Bot::doneCanJumpUp (const Vector &normal) { } // now check same height to one side of the bot... - src = pev->origin + g_pGlobals->v_right * 16.0f + Vector (0.0f, 0.0f, -36.0f + 63.0f); + src = pev->origin + game.vec.right * 16.0f + Vector (0.0f, 0.0f, -36.0f + 63.0f); dest = src + normal * 32.0f; // trace a line forward at maximum jump height... - engine.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); // if trace hit something, return false if (tr.flFraction < 1.0f) { @@ -2692,7 +2570,7 @@ bool Bot::doneCanJumpUp (const Vector &normal) { src = dest; dest.z = dest.z + 37.0f; - engine.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); // if trace hit something, return false if (tr.flFraction < 1.0f) { @@ -2700,11 +2578,11 @@ bool Bot::doneCanJumpUp (const Vector &normal) { } // now check same height on the other side of the bot... - src = pev->origin + (-g_pGlobals->v_right * 16.0f) + Vector (0.0f, 0.0f, -36.0f + 63.0f); + src = pev->origin + (-game.vec.right * 16.0f) + Vector (0.0f, 0.0f, -36.0f + 63.0f); dest = src + normal * 32.0f; // trace a line forward at maximum jump height... - engine.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); // if trace hit something, return false if (tr.flFraction < 1.0f) { @@ -2715,7 +2593,7 @@ bool Bot::doneCanJumpUp (const Vector &normal) { src = dest; dest.z = dest.z + 37.0f; - engine.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); // if trace hit something, return false return tr.flFraction > 1.0f; @@ -2728,7 +2606,7 @@ bool Bot::canDuckUnder (const Vector &normal) { Vector baseHeight; // convert current view angle to vectors for TraceLine math... - makeVectors (Vector (0.0f, pev->angles.y, 0.0f)); + game.makeVectors (Vector (0.0f, pev->angles.y, 0.0f)); // use center of the body first... if (pev->flags & FL_DUCKING) { @@ -2742,7 +2620,7 @@ bool Bot::canDuckUnder (const Vector &normal) { Vector dest = src + normal * 32.0f; // trace a line forward at duck height... - engine.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); // if trace hit something, return false if (tr.flFraction < 1.0f) { @@ -2750,11 +2628,11 @@ bool Bot::canDuckUnder (const Vector &normal) { } // now check same height to one side of the bot... - src = baseHeight + g_pGlobals->v_right * 16.0f; + src = baseHeight + game.vec.right * 16.0f; dest = src + normal * 32.0f; // trace a line forward at duck height... - engine.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); // if trace hit something, return false if (tr.flFraction < 1.0f) { @@ -2762,11 +2640,11 @@ bool Bot::canDuckUnder (const Vector &normal) { } // now check same height on the other side of the bot... - src = baseHeight + (-g_pGlobals->v_right * 16.0f); + src = baseHeight + (-game.vec.right * 16.0f); dest = src + normal * 32.0f; // trace a line forward at duck height... - engine.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); // if trace hit something, return false return tr.flFraction > 1.0f; @@ -2784,10 +2662,10 @@ bool Bot::isBlockedLeft (void) { makeVectors (pev->angles); // do a trace to the left... - engine.TestLine (pev->origin, g_pGlobals->v_forward * direction - g_pGlobals->v_right * 48.0f, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.TestLine (pev->origin, game.vec.forward * direction - game.vec.right * 48.0f, TRACE_IGNORE_MONSTERS, ent (), &tr); // check if the trace hit something... - if ((g_mapFlags & MAP_HAS_DOORS) && tr.flFraction < 1.0f && strncmp ("func_door", STRING (tr.pHit->v.classname), 9) != 0) { + if (game.mapIs (MAP_HAS_DOORS) && tr.flFraction < 1.0f && strncmp ("func_door", STRING (tr.pHit->v.classname), 9) != 0) { return true; // bot's body will hit something } return false; @@ -2803,10 +2681,10 @@ bool Bot::isBlockedRight (void) { makeVectors (pev->angles); // do a trace to the right... - engine.TestLine (pev->origin, pev->origin + g_pGlobals->v_forward * direction + g_pGlobals->v_right * 48.0f, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.TestLine (pev->origin, pev->origin + game.vec.forward * direction + game.vec.right * 48.0f, TRACE_IGNORE_MONSTERS, ent (), &tr); // check if the trace hit something... - if ((g_mapFlags & MAP_HAS_DOORS) && tr.flFraction < 1.0f && (strncmp ("func_door", STRING (tr.pHit->v.classname), 9) != 0)) { + if (game.mapIs (MAP_HAS_DOORS) && tr.flFraction < 1.0f && (strncmp ("func_door", STRING (tr.pHit->v.classname), 9) != 0)) { return true; // bot's body will hit something } return false; @@ -2816,9 +2694,9 @@ bool Bot::isBlockedRight (void) { bool Bot::checkWallOnLeft (void) { TraceResult tr; - makeVectors (pev->angles); + game.makeVectors (pev->angles); - engine.testLine (pev->origin, pev->origin - g_pGlobals->v_right * 40.0f, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (pev->origin, pev->origin - game.vec.right * 40.0f, TRACE_IGNORE_MONSTERS, ent (), &tr); // check if the trace hit something... if (tr.flFraction < 1.0f) { @@ -2829,10 +2707,10 @@ bool Bot::checkWallOnLeft (void) { bool Bot::checkWallOnRight (void) { TraceResult tr; - makeVectors (pev->angles); + game.makeVectors (pev->angles); // do a trace to the right... - engine.testLine (pev->origin, pev->origin + g_pGlobals->v_right * 40.0f, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (pev->origin, pev->origin + game.vec.right * 40.0f, TRACE_IGNORE_MONSTERS, ent (), &tr); // check if the trace hit something... if (tr.flFraction < 1.0f) { @@ -2848,7 +2726,7 @@ bool Bot::isDeadlyMove (const Vector &to) { TraceResult tr; Vector move ((to - botPos).toYaw (), 0.0f, 0.0f); - makeVectors (move); + game.makeVectors (move); Vector direction = (to - botPos).normalize (); // 1 unit long Vector check = botPos; @@ -2856,7 +2734,7 @@ bool Bot::isDeadlyMove (const Vector &to) { down.z = down.z - 1000.0f; // straight down 1000 units - engine.testHull (check, down, TRACE_IGNORE_MONSTERS, head_hull, ent (), &tr); + game.testHull (check, down, TRACE_IGNORE_MONSTERS, head_hull, ent (), &tr); if (tr.flFraction > 0.036f) { // we're not on ground anymore? tr.flFraction = 0.036f; @@ -2871,7 +2749,7 @@ bool Bot::isDeadlyMove (const Vector &to) { down = check; down.z = down.z - 1000.0f; // straight down 1000 units - engine.testHull (check, down, TRACE_IGNORE_MONSTERS, head_hull, ent (), &tr); + game.testHull (check, down, TRACE_IGNORE_MONSTERS, head_hull, ent (), &tr); if (tr.fStartSolid) { // Wall blocking? return false; @@ -3003,7 +2881,7 @@ int Bot::searchCampDir (void) { return rng.getInt (0, waypoints.length () - 1); } -void Bot::processBodyAngles (void) { +void Bot::updateBodyAngles (void) { // set the body angles to point the gun correctly pev->angles.x = -pev->v_angle.x * (1.0f / 3.0f); pev->angles.y = pev->v_angle.y; @@ -3011,12 +2889,12 @@ void Bot::processBodyAngles (void) { pev->angles.clampAngles (); } -void Bot::processLookAngles (void) { - const float delta = cr::clamp (engine.timebase () - m_lookUpdateTime, cr::EQEPSILON, 0.05f); - m_lookUpdateTime = engine.timebase (); +void Bot::updateLookAngles (void) { + const float delta = cr::clamp (game.timebase () - m_lookUpdateTime, cr::EQEPSILON, 0.05f); + m_lookUpdateTime = game.timebase (); // adjust all body and view angles to face an absolute vector - Vector direction = (m_lookAt - eyePos ()).toAngles (); + Vector direction = (m_lookAt - getEyesPos ()).toAngles (); direction.x *= -1.0f; // invert for engine direction.clampAngles (); @@ -3024,14 +2902,14 @@ void Bot::processLookAngles (void) { // lower skilled bot's have lower aiming if (m_difficulty < 2) { updateLookAnglesNewbie (direction, delta); - processBodyAngles (); + updateBodyAngles (); return; } if (m_difficulty > 3 && (m_aimFlags & AIM_ENEMY) && (m_wantsToFire || usesSniper ()) && yb_whose_your_daddy.boolean ()) { pev->v_angle = direction; - processBodyAngles (); + updateBodyAngles (); return; } @@ -3068,10 +2946,10 @@ void Bot::processLookAngles (void) { pev->v_angle = m_idealAngles; pev->v_angle.clampAngles (); - processBodyAngles (); + updateBodyAngles (); } -void Bot::updateLookAnglesNewbie (const Vector &direction, const float delta) { +void Bot::updateLookAnglesNewbie (const Vector &direction, float delta) { Vector spring (13.0f, 13.0f, 0.0f); Vector damperCoefficient (0.22f, 0.22f, 0.0f); @@ -3090,14 +2968,14 @@ void Bot::updateLookAnglesNewbie (const Vector &direction, const float delta) { m_idealAngles.clampAngles (); if (m_aimFlags & (AIM_ENEMY | AIM_ENTITY)) { - m_playerTargetTime = engine.timebase (); + m_playerTargetTime = game.timebase (); 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 < engine.timebase () && ((pev->velocity.length () > 1.0f && m_angularDeviation.length () < 5.0f) || m_angularDeviation.length () < 1.0f)) { + if (m_randomizeAnglesTime < game.timebase () && ((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 @@ -3109,13 +2987,13 @@ void Bot::updateLookAnglesNewbie (const Vector &direction, const float delta) { m_randomizedIdealAngles = m_idealAngles + Vector (rng.getFloat (-randomize.x * 0.5f, randomize.x * 1.5f), rng.getFloat (-randomize.y, randomize.y), 0.0f); // set next time to do this - m_randomizeAnglesTime = engine.timebase () + rng.getFloat (0.4f, offsetDelay); + m_randomizeAnglesTime = game.timebase () + rng.getFloat (0.4f, offsetDelay); } float stiffnessMultiplier = noTargetRatio; // take in account whether the bot was targeting someone in the last N seconds - if (engine.timebase () - (m_playerTargetTime + offsetDelay) < noTargetRatio * 10.0f) { - stiffnessMultiplier = 1.0f - (engine.timebase () - m_timeLastFired) * 0.1f; + if (game.timebase () - (m_playerTargetTime + offsetDelay) < noTargetRatio * 10.0f) { + stiffnessMultiplier = 1.0f - (game.timebase () - m_timeLastFired) * 0.1f; // don't allow that stiffness multiplier less than zero if (stiffnessMultiplier < 0.0f) { @@ -3151,10 +3029,10 @@ void Bot::updateLookAnglesNewbie (const Vector &direction, const float delta) { } void Bot::setStrafeSpeed (const Vector &moveDir, float strafeSpeed) { - makeVectors (pev->angles); + game.makeVectors (pev->angles); const Vector &los = (moveDir - pev->origin).normalize2D (); - float dot = los | g_pGlobals->v_forward.make2D (); + float dot = los | game.vec.forward.make2D (); if (dot > 0.0f && !checkWallOnRight ()) { m_strafeSpeed = strafeSpeed; @@ -3164,19 +3042,19 @@ void Bot::setStrafeSpeed (const Vector &moveDir, float strafeSpeed) { } } -int Bot::locatePlantedC4 (void) { +int Bot::getNearestToPlantedBomb (void) { // this function tries to find planted c4 on the defuse scenario map and returns nearest to it waypoint - if (m_team != TEAM_TERRORIST || !(g_mapFlags & MAP_DE)) { + if (m_team != TEAM_TERRORIST || !game.mapIs (MAP_DE)) { return INVALID_WAYPOINT_INDEX; // don't search for bomb if the player is CT, or it's not defusing bomb } edict_t *bombEntity = nullptr; // temporaly pointer to bomb // search the bomb on the map - while (!engine.isNullEntity (bombEntity = g_engfuncs.pfnFindEntityByString (bombEntity, "classname", "grenade"))) { + while (!game.isNullEntity (bombEntity = engfuncs.pfnFindEntityByString (bombEntity, "classname", "grenade"))) { if (strcmp (STRING (bombEntity->v.model) + 9, "c4.mdl") == 0) { - int nearestIndex = waypoints.getNearest (engine.getAbsPos (bombEntity)); + int nearestIndex = waypoints.getNearest (game.getAbsPos (bombEntity)); if (waypoints.exists (nearestIndex)) { return nearestIndex; @@ -3191,20 +3069,19 @@ bool Bot::isOccupiedPoint (int index) { if (!waypoints.exists (index)) { return true; } - for (int i = 0; i < engine.maxClients (); i++) { - const Client &client = g_clients[i]; + for (const auto &client : util.getClients ()) { if (!(client.flags & (CF_USED | CF_ALIVE)) || client.team != m_team || client.ent == ent ()) { continue; } - auto bot = bots.getBot (i); + auto bot = bots.getBot (client.ent); if (bot == this) { continue; } if (bot != nullptr) { - int occupyId = getShootingConeDeviation (bot->ent (), pev->origin) >= 0.7f ? bot->m_prevWptIndex[0] : bot->m_currentWaypointIndex; + int occupyId = util.getShootingCone (bot->ent (), pev->origin) >= 0.7f ? bot->m_prevWptIndex[0] : bot->m_currentWaypointIndex; if (bot != nullptr) { if (index == occupyId) { @@ -3221,19 +3098,19 @@ bool Bot::isOccupiedPoint (int index) { return false; } -edict_t *Bot::getNearestButton (const char *targetName) { +edict_t *Bot::lookupButton (const char *targetName) { // this function tries to find nearest to current bot button, and returns pointer to // it's entity, also here must be specified the target, that button must open. - if (isEmptyStr (targetName)) { + if (util.isEmptyStr (targetName)) { return nullptr; } float nearestDistance = 99999.0f; edict_t *searchEntity = nullptr, *foundEntity = nullptr; // find the nearest button which can open our target - while (!engine.isNullEntity (searchEntity = g_engfuncs.pfnFindEntityByString (searchEntity, "target", targetName))) { - const Vector &pos = engine.getAbsPos (searchEntity); + while (!game.isNullEntity (searchEntity = engfuncs.pfnFindEntityByString (searchEntity, "target", targetName))) { + const Vector &pos = game.getAbsPos (searchEntity); // check if this place safe if (!isDeadlyMove (pos)) { diff --git a/source/support.cpp b/source/support.cpp index 8d0a31b..a2e5b55 100644 --- a/source/support.cpp +++ b/source/support.cpp @@ -1,4 +1,3 @@ -// // Yet Another POD-Bot, based on PODBot by Markus Klinge ("CountFloyd"). // Copyright (c) YaPB Development Team. // @@ -9,13 +8,57 @@ #include -ConVar yb_display_menu_text ("yb_display_menu_text", "1"); ConVar yb_display_welcome_text ("yb_display_welcome_text", "1"); -ConVar mp_roundtime ("mp_roundtime", nullptr, VT_NOREGISTER); -ConVar mp_freezetime ("mp_freezetime", nullptr, VT_NOREGISTER, true, "0"); +BotUtils::BotUtils (void) { + m_needToSendWelcome = false; + m_welcomeReceiveTime = 0.0f; -const char *format (const char *format, ...) { + // add default messages + m_sentences.push ("hello user,communication is acquired"); + m_sentences.push ("your presence is acknowledged"); + m_sentences.push ("high man, your in command now"); + m_sentences.push ("blast your hostile for good"); + m_sentences.push ("high man, kill some idiot here"); + m_sentences.push ("is there a doctor in the area"); + m_sentences.push ("warning, experimental materials detected"); + m_sentences.push ("high amigo, shoot some but"); + m_sentences.push ("attention, hours of work software, detected"); + m_sentences.push ("time for some bad ass explosion"); + m_sentences.push ("bad ass son of a breach device activated"); + m_sentences.push ("high, do not question this great service"); + m_sentences.push ("engine is operative, hello and goodbye"); + m_sentences.push ("high amigo, your administration has been great last day"); + m_sentences.push ("attention, expect experimental armed hostile presence"); + m_sentences.push ("warning, medical attention required"); + + m_tags.push ({ "[[", "]]" }); + m_tags.push ({ "-=", "=-" }); + m_tags.push ({ "-[", "]-" }); + m_tags.push ({ "-]", "[-" }); + m_tags.push ({ "-}", "{-" }); + m_tags.push ({ "-{", "}-" }); + m_tags.push ({ "<[", "]>" }); + m_tags.push ({ "<]", "[>" }); + m_tags.push ({ "[-", "-]" }); + m_tags.push ({ "]-", "-[" }); + m_tags.push ({ "{-", "-}" }); + m_tags.push ({ "}-", "-{" }); + m_tags.push ({ "[", "]" }); + m_tags.push ({ "{", "}" }); + m_tags.push ({ "<", "[" }); + m_tags.push ({ ">", "<" }); + m_tags.push ({ "-", "-" }); + m_tags.push ({ "|", "|" }); + m_tags.push ({ "=", "=" }); + m_tags.push ({ "+", "+" }); + m_tags.push ({ "(", ")" }); + m_tags.push ({ ")", "(" }); + + m_clients.resize (MAX_ENGINE_PLAYERS + 1); +} + +const char *BotUtils::format (const char *format, ...) { static char strBuffer[2][MAX_PRINT_BUFFER]; static int rotator = 0; @@ -32,30 +75,30 @@ const char *format (const char *format, ...) { return ptr; } -bool isAlive (edict_t *ent) { - if (engine.isNullEntity (ent)) { +bool BotUtils::isAlive (edict_t *ent) { + if (game.isNullEntity (ent)) { return false; } return ent->v.deadflag == DEAD_NO && ent->v.health > 0 && ent->v.movetype != MOVETYPE_NOCLIP; } -float getShootingConeDeviation (edict_t *ent, const Vector &position) { - makeVectors (ent->v.v_angle); +float BotUtils::getShootingCone (edict_t *ent, const Vector &position) { + game.makeVectors (ent->v.v_angle); // he's facing it, he meant it - return g_pGlobals->v_forward | (position - (ent->v.origin + ent->v.view_ofs)).normalize (); + return game.vec.forward | (position - (ent->v.origin + ent->v.view_ofs)).normalize (); } -bool isInViewCone (const Vector &origin, edict_t *ent) { - return getShootingConeDeviation (ent, origin) >= cr::cosf (cr::deg2rad ((ent->v.fov > 0 ? ent->v.fov : 90.0f) * 0.5f)); +bool BotUtils::isInViewCone (const Vector &origin, edict_t *ent) { + return getShootingCone (ent, origin) >= cr::cosf (cr::deg2rad ((ent->v.fov > 0 ? ent->v.fov : 90.0f) * 0.5f)); } -bool isVisible (const Vector &origin, edict_t *ent) { - if (engine.isNullEntity (ent)) { +bool BotUtils::isVisible (const Vector &origin, edict_t *ent) { + if (game.isNullEntity (ent)) { return false; } TraceResult tr; - engine.testLine (ent->v.origin + ent->v.view_ofs, origin, TRACE_IGNORE_EVERYTHING, ent, &tr); + game.testLine (ent->v.origin + ent->v.view_ofs, origin, TRACE_IGNORE_EVERYTHING, ent, &tr); if (tr.flFraction != 1.0f) { return false; @@ -63,81 +106,7 @@ bool isVisible (const Vector &origin, edict_t *ent) { return true; } -void showMenu (edict_t *ent, MenuId menu) { - static bool s_menusParsed = false; - - // make menus looks like we need only once - if (!s_menusParsed) { - extern void setupBotMenus (void); - setupBotMenus (); - - for (int i = 0; i < BOT_MENU_TOTAL_MENUS; i++) { - auto parsed = &g_menus[i]; - const String &translated = engine.translate (parsed->text.chars ()); - - // translate all the things - parsed->text = translated; - - // make menu looks best - if (!(g_gameFlags & GAME_LEGACY)) { - for (int j = 0; j < 10; j++) { - parsed->text.replace (format ("%d.", j), format ("\\r%d.\\w", j)); - } - } - } - s_menusParsed = true; - } - - if (!isPlayer (ent)) { - return; - } - Client &client = g_clients[engine.indexOfEntity (ent) - 1]; - - if (menu == BOT_MENU_INVALID) { - MessageWriter (MSG_ONE_UNRELIABLE, engine.getMessageId (NETMSG_SHOWMENU), Vector::null (), ent) - .writeShort (0) - .writeChar (0) - .writeByte (0) - .writeString (""); - - client.menu = menu; - return; - } - int menuIndex = 0; - - for (; menuIndex < BOT_MENU_TOTAL_MENUS; menuIndex++) { - if (g_menus[menuIndex].id == menu) { - break; - } - } - const auto &menuText = g_menus[menuIndex]; - const char *text = ((g_gameFlags & (GAME_XASH_ENGINE | GAME_MOBILITY)) && !yb_display_menu_text.boolean ()) ? " " : menuText.text.chars (); - MessageWriter msg; - - while (strlen (text) >= 64) { - msg.start (MSG_ONE_UNRELIABLE, engine.getMessageId (NETMSG_SHOWMENU), Vector::null (), ent) - .writeShort (menuText.slots) - .writeChar (-1) - .writeByte (1); - - for (int i = 0; i < 64; i++) { - msg.writeChar (text[i]); - } - msg.end (); - text += 64; - } - - MessageWriter (MSG_ONE_UNRELIABLE, engine.getMessageId (NETMSG_SHOWMENU), Vector::null (), ent) - .writeShort (menuText.slots) - .writeChar (-1) - .writeByte (0) - .writeString (text); - - client.menu = menu; - g_engfuncs.pfnClientCommand (ent, "speak \"player/geiger1\"\n"); // Stops others from hearing menu sounds.. -} - -void traceDecals (entvars_t *pev, TraceResult *trace, int logotypeIndex) { +void BotUtils::traceDecals (entvars_t *pev, TraceResult *trace, int logotypeIndex) { // this function draw spraypaint depending on the tracing results. static StringArray logotypes; @@ -146,18 +115,18 @@ void traceDecals (entvars_t *pev, TraceResult *trace, int logotypeIndex) { logotypes = String ("{biohaz;{graf003;{graf004;{graf005;{lambda06;{target;{hand1;{spit2;{bloodhand6;{foot_l;{foot_r").split (";"); } int entityIndex = -1, message = TE_DECAL; - int decalIndex = g_engfuncs.pfnDecalIndex (logotypes[logotypeIndex].chars ()); + int decalIndex = engfuncs.pfnDecalIndex (logotypes[logotypeIndex].chars ()); if (decalIndex < 0) { - decalIndex = g_engfuncs.pfnDecalIndex ("{lambda06"); + decalIndex = engfuncs.pfnDecalIndex ("{lambda06"); } if (trace->flFraction == 1.0f) { return; } - if (!engine.isNullEntity (trace->pHit)) { + if (!game.isNullEntity (trace->pHit)) { if (trace->pHit->v.solid == SOLID_BSP || trace->pHit->v.movetype == MOVETYPE_PUSHSTEP) { - entityIndex = engine.indexOfEntity (trace->pHit); + entityIndex = game.indexOfEntity (trace->pHit); } else { return; @@ -185,11 +154,11 @@ void traceDecals (entvars_t *pev, TraceResult *trace, int logotypeIndex) { if (logotypes[logotypeIndex].contains ("{")) { MessageWriter (MSG_BROADCAST, SVC_TEMPENTITY) .writeByte (TE_PLAYERDECAL) - .writeByte (engine.indexOfEntity (pev->pContainingEntity)) + .writeByte (game.indexOfEntity (pev->pContainingEntity)) .writeCoord (trace->vecEndPos.x) .writeCoord (trace->vecEndPos.y) .writeCoord (trace->vecEndPos.z) - .writeShort (static_cast (engine.indexOfEntity (trace->pHit))) + .writeShort (static_cast (game.indexOfEntity (trace->pHit))) .writeByte (decalIndex); } else { @@ -209,185 +178,8 @@ void traceDecals (entvars_t *pev, TraceResult *trace, int logotypeIndex) { } } -void cleanupGarbage (void) { - // this function free's all allocated memory - waypoints.init (); // frees waypoint data - - delete[] g_experienceData; - g_experienceData = nullptr; -} - -void updateGlobalExperience (void) { - // this function called after each end of the round to update knowledge about most dangerous waypoints for each team. - - // no waypoints, no experience used or waypoints edited or being edited? - if (waypoints.length () < 1 || waypoints.hasChanged ()) { - return; // no action - } - - uint16 maxDamage; // maximum damage - uint16 actDamage; // actual damage - - int bestIndex; // best index to store - bool recalcKills = false; - - // get the most dangerous waypoint for this position for terrorist team - for (int i = 0; i < waypoints.length (); i++) { - maxDamage = 0; - bestIndex = INVALID_WAYPOINT_INDEX; - - for (int j = 0; j < waypoints.length (); j++) { - if (i == j) { - continue; - } - actDamage = (g_experienceData + (i * waypoints.length ()) + j)->team0Damage; - - if (actDamage > maxDamage) { - maxDamage = actDamage; - bestIndex = j; - } - } - - if (maxDamage > MAX_DAMAGE_VALUE) { - recalcKills = true; - } - (g_experienceData + (i * waypoints.length ()) + i)->team0DangerIndex = static_cast (bestIndex); - } - - // get the most dangerous waypoint for this position for counter-terrorist team - for (int i = 0; i < waypoints.length (); i++) { - maxDamage = 0; - bestIndex = INVALID_WAYPOINT_INDEX; - - for (int j = 0; j < waypoints.length (); j++) { - if (i == j) { - continue; - } - actDamage = (g_experienceData + (i * waypoints.length ()) + j)->team1Damage; - - if (actDamage > maxDamage) { - maxDamage = actDamage; - bestIndex = j; - } - } - - if (maxDamage > MAX_DAMAGE_VALUE) { - recalcKills = true; - } - (g_experienceData + (i * waypoints.length ()) + i)->team1DangerIndex = static_cast (bestIndex); - } - - // adjust values if overflow is about to happen - if (recalcKills) { - for (int i = 0; i < waypoints.length (); i++) { - for (int j = 0; j < waypoints.length (); j++) { - if (i == j) { - continue; - } - - int clip = (g_experienceData + (i * waypoints.length ()) + j)->team0Damage; - clip -= static_cast (MAX_DAMAGE_VALUE * 0.5); - - if (clip < 0) { - clip = 0; - } - (g_experienceData + (i * waypoints.length ()) + j)->team0Damage = static_cast (clip); - - clip = (g_experienceData + (i * waypoints.length ()) + j)->team1Damage; - clip -= static_cast (MAX_DAMAGE_VALUE * 0.5); - - if (clip < 0) { - clip = 0; - } - (g_experienceData + (i * waypoints.length ()) + j)->team1Damage = static_cast (clip); - } - } - } - g_highestKills++; - - int clip = g_highestDamageT - static_cast (MAX_DAMAGE_VALUE * 0.5); - - if (clip < 1) { - clip = 1; - } - g_highestDamageT = clip; - - clip = (int)g_highestDamageCT - static_cast (MAX_DAMAGE_VALUE * 0.5); - - if (clip < 1) { - clip = 1; - } - g_highestDamageCT = clip; - - if (g_highestKills == MAX_KILL_HISTORY) { - for (int i = 0; i < waypoints.length (); i++) { - (g_experienceData + (i * waypoints.length ()) + i)->team0Damage /= static_cast (engine.maxClients () * 0.5); - (g_experienceData + (i * waypoints.length ()) + i)->team1Damage /= static_cast (engine.maxClients () * 0.5); - } - g_highestKills = 1; - } -} - -void initRound (void) { - // this is called at the start of each round - - g_roundEnded = false; - g_canSayBombPlanted = true; - - // check team economics - for (int team = 0; team < MAX_TEAM_COUNT; team++) { - bots.updateTeamEconomics (team); - bots.selectLeaders (team, true); - } - bots.reset (); - - for (int i = 0; i < engine.maxClients (); i++) { - auto bot = bots.getBot (i); - - if (bot != nullptr) { - bot->newRound (); - } - g_radioSelect[i] = 0; - } - waypoints.setBombPos (true); - waypoints.clearVisited (); - - g_bombSayString = false; - g_timeBombPlanted = 0.0f; - g_timeNextBombUpdate = 0.0f; - - for (int i = 0; i < MAX_TEAM_COUNT; i++) { - g_lastRadioTime[i] = 0.0f; - } - g_botsCanPause = false; - - for (int i = 0; i < TASK_MAX; i++) { - g_taskFilters[i].time = 0.0f; - } - updateGlobalExperience (); // update experience data on round start - - // calculate the round mid/end in world time - g_timeRoundStart = engine.timebase () + mp_freezetime.flt (); - g_timeRoundMid = g_timeRoundStart + mp_roundtime.flt () * 60.0f * 0.5f; - g_timeRoundEnd = g_timeRoundStart + mp_roundtime.flt () * 60.0f; -} - -int getWeaponPenetrationPower (int id) { - // returns if weapon can pierce through a wall - - int i = 0; - - while (g_weaponSelect[i].id) { - if (g_weaponSelect[i].id == id) { - return g_weaponSelect[i].penetratePower; - } - i++; - } - return 0; -} - -bool isPlayer (edict_t *ent) { - if (engine.isNullEntity (ent)) { +bool BotUtils::isPlayer (edict_t *ent) { + if (game.isNullEntity (ent)) { return false; } @@ -401,25 +193,25 @@ bool isPlayer (edict_t *ent) { return false; } -bool isPlayerVIP (edict_t *ent) { - if (!(g_mapFlags & MAP_AS)) { +bool BotUtils::isPlayerVIP (edict_t *ent) { + if (!game.mapIs (MAP_AS)) { return false; } if (!isPlayer (ent)) { return false; } - return *(g_engfuncs.pfnInfoKeyValue (g_engfuncs.pfnGetInfoKeyBuffer (ent), "model")) == 'v'; + return *(engfuncs.pfnInfoKeyValue (engfuncs.pfnGetInfoKeyBuffer (ent), "model")) == 'v'; } -bool isFakeClient (edict_t *ent) { - if (bots.getBot (ent) != nullptr || (!engine.isNullEntity (ent) && (ent->v.flags & FL_FAKECLIENT))) { +bool BotUtils::isFakeClient (edict_t *ent) { + if (bots.getBot (ent) != nullptr || (!game.isNullEntity (ent) && (ent->v.flags & FL_FAKECLIENT))) { return true; } return false; } -bool openConfig (const char *fileName, const char *errorIfNotExists, MemFile *outFile, bool languageDependant /*= false*/) { +bool BotUtils::openConfig (const char *fileName, const char *errorIfNotExists, MemFile *outFile, bool languageDependant /*= false*/) { if (outFile->isValid ()) { outFile->close (); } @@ -446,11 +238,13 @@ bool openConfig (const char *fileName, const char *errorIfNotExists, MemFile *ou // unload and reopen file using MemoryFile outFile->open (langConfig); } - else + else { outFile->open (format ("%s/lang/en_%s", configDir, fileName)); + } } - else + else { outFile->open (format ("%s/%s", configDir, fileName)); + } if (!outFile->isValid ()) { logEntry (true, LL_ERROR, errorIfNotExists); @@ -459,57 +253,31 @@ bool openConfig (const char *fileName, const char *errorIfNotExists, MemFile *ou return true; } -void checkWelcome (void) { +void BotUtils::checkWelcome (void) { // the purpose of this function, is to send quick welcome message, to the listenserver entity. - if (engine.isDedicated ()) - return; - - static bool messageSent = !yb_display_welcome_text.boolean (); - static float receiveTime = 0.0f; - - if (messageSent) { + if (game.isDedicated () || !yb_display_welcome_text.boolean () || !m_needToSendWelcome) { return; } + m_welcomeReceiveTime = 0.0f; - if (g_gameFlags & GAME_LEGACY) { - g_gameWelcomeSent = true; + if (game.is (GAME_LEGACY)) { + m_needToSendWelcome = true; + return; + } + bool needToSendMsg = (waypoints.length () > 0 ? m_needToSendWelcome : true); + + if (isAlive (game.getLocalEntity ()) && m_welcomeReceiveTime < 1.0 && needToSendMsg) { + m_welcomeReceiveTime = game.timebase () + 4.0f; // receive welcome message in four seconds after game has commencing } - static StringArray sentences; - - if (!(g_gameFlags & (GAME_MOBILITY | GAME_XASH_ENGINE)) && sentences.empty ()) { - // add default messages - sentences.push ("hello user,communication is acquired"); - sentences.push ("your presence is acknowledged"); - sentences.push ("high man, your in command now"); - sentences.push ("blast your hostile for good"); - sentences.push ("high man, kill some idiot here"); - sentences.push ("is there a doctor in the area"); - sentences.push ("warning, experimental materials detected"); - sentences.push ("high amigo, shoot some but"); - sentences.push ("attention, hours of work software, detected"); - sentences.push ("time for some bad ass explosion"); - sentences.push ("bad ass son of a breach device activated"); - sentences.push ("high, do not question this great service"); - sentences.push ("engine is operative, hello and goodbye"); - sentences.push ("high amigo, your administration has been great last day"); - sentences.push ("attention, expect experimental armed hostile presence"); - sentences.push ("warning, medical attention required"); - } - bool needToSendMsg = (waypoints.length () > 0 ? g_gameWelcomeSent : true); - - if (isAlive (g_hostEntity) && receiveTime < 1.0 && needToSendMsg) { - receiveTime = engine.timebase () + 4.0f; // receive welcome message in four seconds after game has commencing - } - - if (receiveTime > 0.0f && receiveTime < engine.timebase () && needToSendMsg) { - if (!(g_gameFlags & (GAME_MOBILITY | GAME_XASH_ENGINE))) { - engine.execCmd ("speak \"%s\"", sentences.random ().chars ()); + if (m_welcomeReceiveTime > 0.0f && needToSendMsg) { + if (!game.is (GAME_MOBILITY | GAME_XASH_ENGINE)) { + game.execCmd ("speak \"%s\"", m_sentences.random ().chars ()); } - engine.chatPrint ("----- %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); + game.chatPrint ("----- %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, Vector::null (), g_hostEntity) + MessageWriter (MSG_ONE, SVC_TEMPENTITY, Vector::null (), game.getLocalEntity ()) .writeByte (TE_TEXTMESSAGE) .writeByte (1) .writeShort (MessageWriter::fs16 (-1, 1 << 13)) @@ -529,12 +297,12 @@ void checkWelcome (void) { .writeShort (MessageWriter::fu16 (0.1f, 1 << 8)) .writeString (format ("\nServer is running %s v%s (Build: %u)\nDeveloped by %s\n\n%s", PRODUCT_SHORT_NAME, PRODUCT_VERSION, buildNumber (), PRODUCT_AUTHOR, waypoints.getAuthor ())); - receiveTime = 0.0; - messageSent = true; + m_welcomeReceiveTime = 0.0f; + m_needToSendWelcome = false; } } -void logEntry (bool outputToConsole, int logLevel, const char *format, ...) { +void BotUtils::logEntry (bool outputToConsole, int logLevel, const char *format, ...) { // this function logs a message to the message log file root directory. va_list ap; @@ -563,7 +331,7 @@ void logEntry (bool outputToConsole, int logLevel, const char *format, ...) { } if (outputToConsole) { - engine.print ("%s%s", levelString, buffer); + game.print ("%s%s", levelString, buffer); } // now check if logging disabled @@ -599,7 +367,7 @@ void logEntry (bool outputToConsole, int logLevel, const char *format, ...) { if (logLevel == LL_FATAL) { bots.kickEveryone (true); - cleanupGarbage (); + waypoints.init (); #if defined(PLATFORM_WIN32) DestroyWindow (GetForegroundWindow ()); @@ -616,7 +384,7 @@ void logEntry (bool outputToConsole, int logLevel, const char *format, ...) { } } -bool findNearestPlayer (void **pvHolder, edict_t *to, float searchDistance, bool sameTeam, bool needBot, bool isAlive, bool needDrawn, bool needBotWithC4) { +bool BotUtils::findNearestPlayer (void **pvHolder, edict_t *to, float searchDistance, bool sameTeam, bool needBot, bool needAlive, bool needDrawn, bool needBotWithC4) { // this function finds nearest to to, player with set of parameters, like his // team, live status, search distance etc. if needBot is true, then pvHolder, will // be filled with bot pointer, else with edict pointer(!). @@ -624,16 +392,14 @@ bool findNearestPlayer (void **pvHolder, edict_t *to, float searchDistance, bool edict_t *survive = nullptr; // pointer to temporally & survive entity float nearestPlayer = 4096.0f; // nearest player - int toTeam = engine.getTeam (to); - - for (int i = 0; i < engine.maxClients (); i++) { - const Client &client = g_clients[i]; + int toTeam = game.getTeam (to); + for (const auto &client : m_clients) { if (!(client.flags & CF_USED) || client.ent == to) { continue; } - if ((sameTeam && client.team != toTeam) || (isAlive && !(client.flags & CF_ALIVE)) || (needBot && !isFakeClient (client.ent)) || (needDrawn && (client.ent->v.effects & EF_NODRAW)) || (needBotWithC4 && (client.ent->v.weapons & WEAPON_C4))) { + if ((sameTeam && client.team != toTeam) || (needAlive && !(client.flags & CF_ALIVE)) || (needBot && !isFakeClient (client.ent)) || (needDrawn && (client.ent->v.effects & EF_NODRAW)) || (needBotWithC4 && (client.ent->v.weapons & WEAPON_C4))) { continue; // filter players with parameters } float distance = (client.ent->v.origin - to->v.origin).length (); @@ -644,8 +410,9 @@ bool findNearestPlayer (void **pvHolder, edict_t *to, float searchDistance, bool } } - if (engine.isNullEntity (survive)) + if (game.isNullEntity (survive)) { return false; // nothing found + } // fill the holder if (needBot) { @@ -657,26 +424,26 @@ bool findNearestPlayer (void **pvHolder, edict_t *to, float searchDistance, bool return true; } -void attachSoundsToClients (edict_t *ent, const char *sample, float volume) { +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 - if (engine.isNullEntity (ent) || isEmptyStr (sample)) { + if (game.isNullEntity (ent) || isEmptyStr (sample)) { return; } - const Vector &origin = engine.getAbsPos (ent); + const Vector &origin = game.getAbsPos (ent); if (origin.empty ()) { return; } - int index = engine.indexOfEntity (ent) - 1; + int index = game.indexOfEntity (ent) - 1; - if (index < 0 || index >= engine.maxClients ()) { + if (index < 0 || index >= game.maxClients ()) { float nearestDistance = 99999.0f; // loop through all players - for (int i = 0; i < engine.maxClients (); i++) { - const Client &client = g_clients[i]; + for (int i = 0; i < game.maxClients (); i++) { + const Client &client = m_clients[i]; if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE)) { continue; @@ -692,63 +459,63 @@ void attachSoundsToClients (edict_t *ent, const char *sample, float volume) { } // in case of worst case - if (index < 0 || index >= engine.maxClients ()) { + if (index < 0 || index >= game.maxClients ()) { return; } - Client &client = g_clients[index]; + 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 = engine.timebase () + 0.5f; - client.soundPos = origin; + client.timeSoundLasting = game.timebase () + 0.5f; + client.sound = origin; } else if (strncmp ("items/gunpickup", sample, 15) == 0) { // weapon pickup? client.hearingDistance = 768.0f * volume; - client.timeSoundLasting = engine.timebase () + 0.5f; - client.soundPos = origin; + client.timeSoundLasting = game.timebase () + 0.5f; + client.sound = origin; } else if (strncmp ("weapons/zoom", sample, 12) == 0) { // sniper zooming? client.hearingDistance = 512.0f * volume; - client.timeSoundLasting = engine.timebase () + 0.1f; - client.soundPos = origin; + client.timeSoundLasting = game.timebase () + 0.1f; + client.sound = origin; } else if (strncmp ("items/9mmclip", sample, 13) == 0) { // ammo pickup? client.hearingDistance = 512.0f * volume; - client.timeSoundLasting = engine.timebase () + 0.1f; - client.soundPos = origin; + client.timeSoundLasting = game.timebase () + 0.1f; + client.sound = origin; } else if (strncmp ("hostage/hos", sample, 11) == 0) { // CT used hostage? client.hearingDistance = 1024.0f * volume; - client.timeSoundLasting = engine.timebase () + 5.0f; - client.soundPos = origin; + client.timeSoundLasting = game.timebase () + 5.0f; + client.sound = origin; } else if (strncmp ("debris/bustmetal", sample, 16) == 0 || strncmp ("debris/bustglass", sample, 16) == 0) { // broke something? client.hearingDistance = 1024.0f * volume; - client.timeSoundLasting = engine.timebase () + 2.0f; - client.soundPos = origin; + client.timeSoundLasting = game.timebase () + 2.0f; + client.sound = origin; } else if (strncmp ("doors/doormove", sample, 14) == 0) { // someone opened a door client.hearingDistance = 1024.0f * volume; - client.timeSoundLasting = engine.timebase () + 3.0f; - client.soundPos = origin; + client.timeSoundLasting = game.timebase () + 3.0f; + client.sound = origin; } } -void simulateSoundUpdates (int playerIndex) { +void BotUtils::simulateSoundUpdates (int playerIndex) { // this function tries to simulate playing of sounds to let the bots hear sounds which aren't // captured through server sound hooking - if (playerIndex < 0 || playerIndex >= engine.maxClients ()) { + if (playerIndex < 0 || playerIndex >= game.maxClients ()) { return; // reliability check } - Client &client = g_clients[playerIndex]; + Client &client = m_clients[playerIndex]; float hearDistance = 0.0f; float timeSound = 0.0f; @@ -756,23 +523,23 @@ void simulateSoundUpdates (int playerIndex) { if (client.ent->v.oldbuttons & IN_ATTACK) // pressed attack button? { hearDistance = 2048.0f; - timeSound = engine.timebase () + 0.3f; + timeSound = game.timebase () + 0.3f; } else if (client.ent->v.oldbuttons & IN_USE) // pressed used button? { hearDistance = 512.0f; - timeSound = engine.timebase () + 0.5f; + timeSound = game.timebase () + 0.5f; } else if (client.ent->v.oldbuttons & IN_RELOAD) // pressed reload button? { hearDistance = 512.0f; - timeSound = engine.timebase () + 0.5f; + timeSound = game.timebase () + 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 = engine.timebase () + 0.3f; + timeSound = game.timebase () + 0.3f; } } else { @@ -781,7 +548,7 @@ void simulateSoundUpdates (int playerIndex) { if (mp_footsteps.boolean ()) { // moves fast enough? hearDistance = 1280.0f * (client.ent->v.velocity.length2D () / 260.0f); - timeSound = engine.timebase () + 0.3f; + timeSound = game.timebase () + 0.3f; } } @@ -790,23 +557,52 @@ void simulateSoundUpdates (int playerIndex) { } // some sound already associated - if (client.timeSoundLasting > engine.timebase ()) { + if (client.timeSoundLasting > game.timebase ()) { if (client.hearingDistance <= hearDistance) { // override it with new client.hearingDistance = hearDistance; client.timeSoundLasting = timeSound; - client.soundPos = client.ent->v.origin; + client.sound = client.ent->v.origin; } } else { // just remember it client.hearingDistance = hearDistance; client.timeSoundLasting = timeSound; - client.soundPos = client.ent->v.origin; + client.sound = client.ent->v.origin; } } -int buildNumber (void) { +void BotUtils::updateClients (void) { + // record some stats of all players on the server + for (int i = 0; i < game.maxClients (); i++) { + edict_t *player = game.entityOfIndex (i + 1); + Client &client = m_clients[i]; + + if (!game.isNullEntity (player) && (player->v.flags & FL_CLIENT)) { + client.ent = player; + client.flags |= CF_USED; + + if (util.isAlive (player)) { + client.flags |= CF_ALIVE; + } + else { + client.flags &= ~CF_ALIVE; + } + + if (client.flags & CF_ALIVE) { + client.origin = player->v.origin; + simulateSoundUpdates (i); + } + } + else { + client.flags &= ~(CF_USED | CF_ALIVE); + client.ent = nullptr; + } + } +} + +int BotUtils::buildNumber (void) { // this function generates build number from the compiler date macros static int buildNumber = 0; @@ -848,7 +644,7 @@ int buildNumber (void) { return buildNumber; } -int getWeaponData (bool needString, const char *weaponAlias, int weaponIndex) { +int BotUtils::getWeaponAlias (bool needString, const char *weaponAlias, int weaponIndex) { // this function returning weapon id from the weapon alias and vice versa. // structure definition for weapon tab @@ -895,18 +691,18 @@ int getWeaponData (bool needString, const char *weaponAlias, int weaponIndex) { // if we need to return the string, find by weapon id if (needString && weaponIndex != -1) { - for (size_t i = 0; i < cr::arrsize (weaponTab); i++) { - if (weaponTab[i].weaponIndex == weaponIndex) { // is weapon id found? - return MAKE_STRING (weaponTab[i].alias); + for (auto &tab : weaponTab) { + if (tab.weaponIndex == weaponIndex) { // is weapon id found? + return MAKE_STRING (tab.alias); } } return MAKE_STRING ("(none)"); // return none } // else search weapon by name and return weapon id - for (size_t i = 0; i < cr::arrsize (weaponTab); i++) { - if (strncmp (weaponTab[i].alias, weaponAlias, strlen (weaponTab[i].alias)) == 0) { - return weaponTab[i].weaponIndex; + for (auto &tab : weaponTab) { + if (strncmp (tab.alias, weaponAlias, strlen (tab.alias)) == 0) { + return tab.weaponIndex; } } return -1; // no weapon was found return -1 diff --git a/source/waypoint.cpp b/source/waypoint.cpp index 437e033..8ff2224 100644 --- a/source/waypoint.cpp +++ b/source/waypoint.cpp @@ -1,4 +1,3 @@ -// // Yet Another POD-Bot, based on PODBot by Markus Klinge ("CountFloyd"). // Copyright (c) YaPB Development Team. // @@ -17,6 +16,7 @@ ConVar yb_waypoint_autodl_enable ("yb_waypoint_autodl_enable", "1"); void Waypoint::init (void) { // this function initialize the waypoint structures.. m_loadTries = 0; + m_editFlags = 0; m_learnVelocity.nullify (); m_learnPosition.nullify (); @@ -24,11 +24,22 @@ void Waypoint::init (void) { m_pathDisplayTime = 0.0f; m_arrowDisplayTime = 0.0f; + m_autoPathDistance = 250.0f; // have any waypoint path nodes been allocated yet? if (m_waypointPaths) { cleanupPathMemory (); } + + // reset highest recorded damage + for (int team = TEAM_TERRORIST; team < MAX_TEAM_COUNT; team++) { + m_highestDamage[team] = 1; + } + + // free experience stuff + delete[] m_experience; + m_experience = nullptr; + m_numWaypoints = 0; } @@ -39,7 +50,7 @@ void Waypoint::cleanupPathMemory (void) { } } -int Waypoint::removeUselessConnections (int index, bool outputToConsole) { +int Waypoint::clearConnections (int index) { // this function removes the useless paths connections from and to waypoint pointed by index. This is based on code from POD-bot MM from KWo if (!exists (index)) { @@ -52,20 +63,6 @@ int Waypoint::removeUselessConnections (int index, bool outputToConsole) { } const int INFINITE_DISTANCE = 99999; - auto printInfo = [&outputToConsole](const char *fmt, ...) { - if (!outputToConsole) { - return; - } - char buffer[MAX_PRINT_BUFFER]; - - va_list ap; - va_start (ap, fmt); - vsnprintf (buffer, MAX_PRINT_BUFFER - 1, fmt, ap); - va_end (ap); - - engine.print (buffer); - }; - struct Connection { int index; int number; @@ -108,7 +105,7 @@ int Waypoint::removeUselessConnections (int index, bool outputToConsole) { } if (top.number == INVALID_WAYPOINT_INDEX) { - printInfo ("Cannot find path to the closest connected waypoint to waypoint number %d!\n", index); + ctrl.msg ("Cannot find path to the closest connected waypoint to waypoint number %d!\n", index); return numConnectionsFixed; } bool sorting = false; @@ -126,9 +123,7 @@ int Waypoint::removeUselessConnections (int index, bool outputToConsole) { } while (sorting); // calculate angles related to the angle of the closeset connected waypoint - for (int i = 0; i < MAX_PATH_INDEX; i++) { - auto &cur = sorted[i]; - + for (auto &cur : sorted) { if (cur.index == INVALID_WAYPOINT_INDEX) { cur.distance = INFINITE_DISTANCE; cur.angles = 360.0f; @@ -166,7 +161,7 @@ int Waypoint::removeUselessConnections (int index, bool outputToConsole) { m_paths[id1]->connectionVelocity[id2].nullify (); m_waypointsChanged = true; - g_waypointOn = true; + setEditFlag (WS_EDIT_ENABLED); numConnectionsFixed++; }; @@ -196,14 +191,14 @@ int Waypoint::removeUselessConnections (int index, bool outputToConsole) { if ((cur.distance + prev2.distance) * 1.1f / 2.0f < static_cast (prev.distance)) { if (m_paths[index]->index[prev.number] == prev.index) { - printInfo ("Removing a useless (P.0.1) connection from index = %d to %d.", index, prev.index); + ctrl.msg ("Removing a useless (P.0.1) connection from index = %d to %d.", index, prev.index); // unassign this path unassignPath (index, prev.number); for (int j = 0; j < MAX_PATH_INDEX; j++) { if (m_paths[prev.index]->index[j] == index && !(m_paths[prev.index]->connectionFlags[j] & PATHFLAG_JUMP)) { - printInfo ("Removing a useless (P.0.2) connection from index = %d to %d.", prev.index, index); + ctrl.msg ("Removing a useless (P.0.2) connection from index = %d to %d.", prev.index, index); // unassign this path unassignPath (prev.index, j); @@ -220,7 +215,7 @@ int Waypoint::removeUselessConnections (int index, bool outputToConsole) { return true; } else { - printInfo ("Failed to remove a useless (P.0) connection from index = %d to %d.", index, prev.index); + ctrl.msg ("Failed to remove a useless (P.0) connection from index = %d to %d.", index, prev.index); return false; } } @@ -230,7 +225,7 @@ int Waypoint::removeUselessConnections (int index, bool outputToConsole) { for (int i = 2; i < MAX_PATH_INDEX; i++) { - while (inspect_p0 (i)); + while (inspect_p0 (i)) { } } // check pass 1 @@ -238,14 +233,14 @@ int Waypoint::removeUselessConnections (int index, bool outputToConsole) { if ((sorted[1].angles - top.angles < 80.0f || 360.0f - (sorted[1].angles - top.angles) < 80.0f) && (!(m_paths[sorted[0].index]->flags & FLAG_LADDER) || !(m_paths[index]->flags & FLAG_LADDER)) && !(m_paths[index]->connectionFlags[sorted[0].number] & PATHFLAG_JUMP)) { if ((sorted[1].distance + top.distance) * 1.1f / 2.0f < static_cast (sorted[0].distance)) { if (m_paths[index]->index[sorted[0].number] == sorted[0].index) { - printInfo ("Removing a useless (P.1.1) connection from index = %d to %d.", index, sorted[0].index); + ctrl.msg ("Removing a useless (P.1.1) connection from index = %d to %d.", index, sorted[0].index); // unassign this path unassignPath (index, sorted[0].number); for (int j = 0; j < MAX_PATH_INDEX; j++) { if (m_paths[sorted[0].index]->index[j] == index && !(m_paths[sorted[0].index]->connectionFlags[j] & PATHFLAG_JUMP)) { - printInfo ("Removing a useless (P.1.2) connection from index = %d to %d.", sorted[0].index, index); + ctrl.msg ("Removing a useless (P.1.2) connection from index = %d to %d.", sorted[0].index, index); // unassign this path unassignPath (sorted[0].index, j); @@ -259,7 +254,7 @@ int Waypoint::removeUselessConnections (int index, bool outputToConsole) { sorted[MAX_PATH_INDEX - 1].index = INVALID_WAYPOINT_INDEX; } else { - printInfo ("Failed to remove a useless (P.1) connection from index = %d to %d.", sorted[0].index, index); + ctrl.msg ("Failed to remove a useless (P.1) connection from index = %d to %d.", sorted[0].index, index); } } } @@ -286,14 +281,14 @@ int Waypoint::removeUselessConnections (int index, bool outputToConsole) { } if (m_paths[index]->index[cur.number] == cur.index) { - printInfo ("Removing a useless (P.2.1) connection from index = %d to %d.", index, cur.index); + ctrl.msg ("Removing a useless (P.2.1) connection from index = %d to %d.", index, cur.index); // unassign this path unassignPath (index, cur.number); for (int j = 0; j < MAX_PATH_INDEX; j++) { if (m_paths[cur.index]->index[j] == index && !(m_paths[cur.index]->connectionFlags[j] & PATHFLAG_JUMP)) { - printInfo ("Removing a useless (P.2.2) connection from index = %d to %d.", cur.index, index); + ctrl.msg ("Removing a useless (P.2.2) connection from index = %d to %d.", cur.index, index); // unassign this path unassignPath (cur.index, j); @@ -308,7 +303,7 @@ int Waypoint::removeUselessConnections (int index, bool outputToConsole) { return true; } else { - printInfo ("Failed to remove a useless (P.2) connection from index = %d to %d.", index, cur.index); + ctrl.msg ("Failed to remove a useless (P.2) connection from index = %d to %d.", index, cur.index); } } else if (cur.distance < static_cast (prev.distance * 1.1f)) { @@ -318,14 +313,14 @@ int Waypoint::removeUselessConnections (int index, bool outputToConsole) { } if (m_paths[index]->index[prev.number] == prev.index) { - printInfo ("Removing a useless (P.2.3) connection from index = %d to %d.", index, prev.index); + ctrl.msg ("Removing a useless (P.2.3) connection from index = %d to %d.", index, prev.index); // unassign this path unassignPath (index, prev.number); for (int j = 0; j < MAX_PATH_INDEX; j++) { if (m_paths[prev.index]->index[j] == index && !(m_paths[prev.index]->connectionFlags[j] & PATHFLAG_JUMP)) { - printInfo ("Removing a useless (P.2.4) connection from index = %d to %d.", prev.index, index); + ctrl.msg ("Removing a useless (P.2.4) connection from index = %d to %d.", prev.index, index); // unassign this path unassignPath (prev.index, j); @@ -342,7 +337,7 @@ int Waypoint::removeUselessConnections (int index, bool outputToConsole) { return true; } else { - printInfo ("Failed to remove a useless (P.2) connection from index = %d to %d.", index, prev.index); + ctrl.msg ("Failed to remove a useless (P.2) connection from index = %d to %d.", index, prev.index); } } } @@ -353,7 +348,7 @@ int Waypoint::removeUselessConnections (int index, bool outputToConsole) { }; for (int i = 1; i < MAX_PATH_INDEX; i++) { - while (inspect_p2 (i)); + while (inspect_p2 (i)) { } } // check pass 3 @@ -361,14 +356,14 @@ int Waypoint::removeUselessConnections (int index, bool outputToConsole) { if ((top.angles - sorted[0].angles < 40.0f || (360.0f - top.angles - sorted[0].angles) < 40.0f) && (!(m_paths[sorted[0].index]->flags & FLAG_LADDER) || !(m_paths[index]->flags & FLAG_LADDER)) && !(m_paths[index]->connectionFlags[sorted[0].number] & PATHFLAG_JUMP)) { if (top.distance * 1.1f < static_cast (sorted[0].distance)) { if (m_paths[index]->index[sorted[0].number] == sorted[0].index) { - printInfo ("Removing a useless (P.3.1) connection from index = %d to %d.", index, sorted[0].index); + ctrl.msg ("Removing a useless (P.3.1) connection from index = %d to %d.", index, sorted[0].index); // unassign this path unassignPath (index, sorted[0].number); for (int j = 0; j < MAX_PATH_INDEX; j++) { if (m_paths[sorted[0].index]->index[j] == index && !(m_paths[sorted[0].index]->connectionFlags[j] & PATHFLAG_JUMP)) { - printInfo ("Removing a useless (P.3.2) connection from index = %d to %d.", sorted[0].index, index); + ctrl.msg ("Removing a useless (P.3.2) connection from index = %d to %d.", sorted[0].index, index); // unassign this path unassignPath (sorted[0].index, j); @@ -382,19 +377,19 @@ int Waypoint::removeUselessConnections (int index, bool outputToConsole) { sorted[MAX_PATH_INDEX - 1].index = INVALID_WAYPOINT_INDEX; } else { - printInfo ("Failed to remove a useless (P.3) connection from index = %d to %d.", sorted[0].index, index); + ctrl.msg ("Failed to remove a useless (P.3) connection from index = %d to %d.", sorted[0].index, index); } } else if (sorted[0].distance * 1.1f < static_cast (top.distance) && !(m_paths[index]->connectionFlags[top.number] & PATHFLAG_JUMP)) { if (m_paths[index]->index[top.number] == top.index) { - printInfo ("Removing a useless (P.3.3) connection from index = %d to %d.", index, sorted[0].index); + ctrl.msg ("Removing a useless (P.3.3) connection from index = %d to %d.", index, sorted[0].index); // unassign this path unassignPath (index, top.number); for (int j = 0; j < MAX_PATH_INDEX; j++) { if (m_paths[top.index]->index[j] == index && !(m_paths[top.index]->connectionFlags[j] & PATHFLAG_JUMP)) { - printInfo ("Removing a useless (P.3.4) connection from index = %d to %d.", sorted[0].index, index); + ctrl.msg ("Removing a useless (P.3.4) connection from index = %d to %d.", sorted[0].index, index); // unassign this path unassignPath (top.index, j); @@ -403,7 +398,7 @@ int Waypoint::removeUselessConnections (int index, bool outputToConsole) { sorted[0].index = INVALID_WAYPOINT_INDEX; } else { - printInfo ("Failed to remove a useless (P.3) connection from index = %d to %d.", sorted[0].index, index); + ctrl.msg ("Failed to remove a useless (P.3) connection from index = %d to %d.", sorted[0].index, index); } } } @@ -418,20 +413,20 @@ void Waypoint::addPath (int addIndex, int pathIndex, float distance) { Path *path = m_paths[addIndex]; // don't allow paths get connected twice - for (int i = 0; i < MAX_PATH_INDEX; i++) { - if (path->index[i] == pathIndex) { - logEntry (true, LL_WARNING, "Denied path creation from %d to %d (path already exists)", addIndex, pathIndex); + for (auto &index : path->index) { + if (index == pathIndex) { + ctrl.msg ("Denied path creation from %d to %d (path already exists)", addIndex, pathIndex); return; } } // check for free space in the connection indices - for (int16 i = 0; i < MAX_PATH_INDEX; i++) { + for (int i = 0; i < MAX_PATH_INDEX; i++) { if (path->index[i] == INVALID_WAYPOINT_INDEX) { path->index[i] = static_cast (pathIndex); path->distances[i] = cr::abs (static_cast (distance)); - logEntry (true, LL_DEFAULT, "Path added from %d to %d", addIndex, pathIndex); + ctrl.msg ("Path added from %d to %d", addIndex, pathIndex); return; } } @@ -448,7 +443,7 @@ void Waypoint::addPath (int addIndex, int pathIndex, float distance) { } if (slotID != INVALID_WAYPOINT_INDEX) { - logEntry (true, LL_DEFAULT, "Path added from %d to %d", addIndex, pathIndex); + ctrl.msg ("Path added from %d to %d", addIndex, pathIndex); path->index[slotID] = static_cast (pathIndex); path->distances[slotID] = cr::abs (static_cast (distance)); @@ -494,10 +489,10 @@ int Waypoint::getNearestNoBuckets (const Vector &origin, float minDistance, int } int Waypoint::getEditorNeareset (void) { - if (!g_waypointOn) { + if (!hasEditFlag (WS_EDIT_ENABLED)) { return INVALID_WAYPOINT_INDEX; } - return getNearestNoBuckets (g_hostEntity->v.origin, 50.0f); + return getNearestNoBuckets (m_editor->v.origin, 50.0f); } int Waypoint::getNearest (const Vector &origin, float minDistance, int flags) { @@ -554,7 +549,7 @@ IntArray Waypoint::searchRadius (float radius, const Vector &origin, int maxCoun } void Waypoint::push (int flags, const Vector &waypointOrigin) { - if (engine.isNullEntity (g_hostEntity)) { + if (game.isNullEntity (m_editor)) { return; } @@ -568,7 +563,7 @@ void Waypoint::push (int flags, const Vector &waypointOrigin) { Vector newOrigin = waypointOrigin; if (waypointOrigin.empty ()) { - newOrigin = g_hostEntity->v.origin; + newOrigin = m_editor->v.origin; } if (bots.getBotCount () > 0) { @@ -584,18 +579,17 @@ void Waypoint::push (int flags, const Vector &waypointOrigin) { path = m_paths[index]; if (!(path->flags & FLAG_CAMP)) { - engine.centerPrint ("This is not Camping Waypoint"); + ctrl.msg ("This is not Camping Waypoint"); return; } - - makeVectors (g_hostEntity->v.v_angle); - forward = g_hostEntity->v.origin + g_hostEntity->v.view_ofs + g_pGlobals->v_forward * 640.0f; + game.makeVectors (m_editor->v.v_angle); + forward = m_editor->v.origin + m_editor->v.view_ofs + game.vec.forward * 640.0f; path->campEndX = forward.x; path->campEndY = forward.y; // play "done" sound... - engine.playSound (g_hostEntity, "common/wpn_hudon.wav"); + game.playSound (m_editor, "common/wpn_hudon.wav"); } return; @@ -603,7 +597,7 @@ void Waypoint::push (int flags, const Vector &waypointOrigin) { index = getEditorNeareset (); if (index != INVALID_WAYPOINT_INDEX && m_paths[index] != nullptr) { - distance = (m_paths[index]->origin - g_hostEntity->v.origin).length (); + distance = (m_paths[index]->origin - m_editor->v.origin).length (); if (distance < 50.0f) { placeNew = false; @@ -612,15 +606,16 @@ void Waypoint::push (int flags, const Vector &waypointOrigin) { path->origin = (path->origin + m_learnPosition) * 0.5f; } } - else + else { newOrigin = m_learnPosition; + } break; case 10: index = getEditorNeareset (); if (index != INVALID_WAYPOINT_INDEX && m_paths[index] != nullptr) { - distance = (m_paths[index]->origin - g_hostEntity->v.origin).length (); + distance = (m_paths[index]->origin - m_editor->v.origin).length (); if (distance < 50.0f) { placeNew = false; @@ -632,7 +627,7 @@ void Waypoint::push (int flags, const Vector &waypointOrigin) { connectionFlags += path->connectionFlags[i]; } if (connectionFlags == 0) { - path->origin = (path->origin + g_hostEntity->v.origin) * 0.5f; + path->origin = (path->origin + m_editor->v.origin) * 0.5f; } } } @@ -671,7 +666,7 @@ void Waypoint::push (int flags, const Vector &waypointOrigin) { } // store the last used waypoint for the auto waypoint code... - m_lastWaypoint = g_hostEntity->v.origin; + m_lastWaypoint = m_editor->v.origin; } // set the time that this waypoint was originally displayed... @@ -681,7 +676,7 @@ void Waypoint::push (int flags, const Vector &waypointOrigin) { m_lastJumpWaypoint = index; } else if (flags == 10) { - distance = (m_paths[m_lastJumpWaypoint]->origin - g_hostEntity->v.origin).length (); + distance = (m_paths[m_lastJumpWaypoint]->origin - m_editor->v.origin).length (); addPath (m_lastJumpWaypoint, index, distance); for (i = 0; i < MAX_PATH_INDEX; i++) { @@ -701,15 +696,15 @@ void Waypoint::push (int flags, const Vector &waypointOrigin) { return; } - if (g_hostEntity->v.flags & FL_DUCKING) { + if (m_editor->v.flags & FL_DUCKING) { path->flags |= FLAG_CROUCH; // set a crouch waypoint } - if (g_hostEntity->v.movetype == MOVETYPE_FLY) { + if (m_editor->v.movetype == MOVETYPE_FLY) { path->flags |= FLAG_LADDER; - makeVectors (g_hostEntity->v.v_angle); + game.makeVectors (m_editor->v.v_angle); - forward = g_hostEntity->v.origin + g_hostEntity->v.view_ofs + g_pGlobals->v_forward * 640.0f; + forward = m_editor->v.origin + m_editor->v.view_ofs + game.vec.forward * 640.0f; path->campStartY = forward.y; } else if (m_isOnLadder) { @@ -739,8 +734,8 @@ void Waypoint::push (int flags, const Vector &waypointOrigin) { path->flags |= FLAG_CROSSING; path->flags |= FLAG_CAMP; - makeVectors (g_hostEntity->v.v_angle); - forward = g_hostEntity->v.origin + g_hostEntity->v.view_ofs + g_pGlobals->v_forward * 640.0f; + game.makeVectors (m_editor->v.v_angle); + forward = m_editor->v.origin + m_editor->v.view_ofs + game.vec.forward * 640.0f; path->campStartX = forward.x; path->campStartY = forward.y; @@ -767,9 +762,9 @@ void Waypoint::push (int flags, const Vector &waypointOrigin) { // other ladder waypoints should connect to this if (m_paths[i]->flags & FLAG_LADDER) { // check if the waypoint is reachable from the new one - engine.testLine (newOrigin, m_paths[i]->origin, TRACE_IGNORE_MONSTERS, g_hostEntity, &tr); + game.testLine (newOrigin, m_paths[i]->origin, TRACE_IGNORE_MONSTERS, m_editor, &tr); - if (tr.flFraction == 1.0f && cr::abs (newOrigin.x - m_paths[i]->origin.x) < 64.0f && cr::abs (newOrigin.y - m_paths[i]->origin.y) < 64.0f && cr::abs (newOrigin.z - m_paths[i]->origin.z) < g_autoPathDistance) { + if (tr.flFraction == 1.0f && cr::abs (newOrigin.x - m_paths[i]->origin.x) < 64.0f && cr::abs (newOrigin.y - m_paths[i]->origin.y) < 64.0f && cr::abs (newOrigin.z - m_paths[i]->origin.z) < m_autoPathDistance) { distance = (m_paths[i]->origin - newOrigin).length (); addPath (index, i, distance); @@ -822,9 +817,9 @@ void Waypoint::push (int flags, const Vector &waypointOrigin) { addPath (i, index, distance); } } - removeUselessConnections (index, false); + clearConnections (index); } - engine.playSound (g_hostEntity, "weapons/xbow_hit1.wav"); + game.playSound (m_editor, "weapons/xbow_hit1.wav"); calculatePathRadius (index); // calculate the wayzone of this waypoint } @@ -889,7 +884,7 @@ void Waypoint::erase (int target) { m_numWaypoints--; m_waypointDisplayTime[index] = 0; - engine.playSound (g_hostEntity, "weapons/mine_activate.wav"); + game.playSound (m_editor, "weapons/mine_activate.wav"); } void Waypoint::toggleFlags (int toggleFlag) { @@ -903,35 +898,35 @@ void Waypoint::toggleFlags (int toggleFlag) { } else if (!(m_paths[index]->flags & toggleFlag)) { if (toggleFlag == FLAG_SNIPER && !(m_paths[index]->flags & FLAG_CAMP)) { - logEntry (true, LL_ERROR, "Cannot assign sniper flag to waypoint #%d. This is not camp waypoint", index); + ctrl.msg ("Cannot assign sniper flag to waypoint #%d. This is not camp waypoint", index); return; } m_paths[index]->flags |= toggleFlag; } // play "done" sound... - engine.playSound (g_hostEntity, "common/wpn_hudon.wav"); + game.playSound (m_editor, "common/wpn_hudon.wav"); } } -void Waypoint::setRadius (int radius) { +void Waypoint::setRadius (int index, float radius) { // this function allow manually setting the zone radius - int index = getEditorNeareset (); + int node = exists (index) ? index : getEditorNeareset (); - if (index != INVALID_WAYPOINT_INDEX) { - m_paths[index]->radius = static_cast (radius); + if (node != INVALID_WAYPOINT_INDEX) { + m_paths[node]->radius = static_cast (radius); // play "done" sound... - engine.playSound (g_hostEntity, "common/wpn_hudon.wav"); + game.playSound (m_editor, "common/wpn_hudon.wav"); } } bool Waypoint::isConnected (int pointA, int pointB) { // this function checks if waypoint A has a connection to waypoint B - for (int i = 0; i < MAX_PATH_INDEX; i++) { - if (m_paths[pointA]->index[i] == pointB) { + for (auto &index : m_paths[pointA]->index) { + if (index == pointB) { return true; } } @@ -950,17 +945,17 @@ int Waypoint::getFacingIndex (void) { for (int i = 0; i < m_numWaypoints; i++) { auto path = m_paths[i]; - if ((path->origin - g_hostEntity->v.origin).lengthSq () > cr::square (500.0f)) { + if ((path->origin - m_editor->v.origin).lengthSq () > cr::square (500.0f)) { continue; } cones.clear (); // get the current view cones - cones.push (getShootingConeDeviation (g_hostEntity, path->origin)); - cones.push (getShootingConeDeviation (g_hostEntity, path->origin - Vector (0.0f, 0.0f, (path->flags & FLAG_CROUCH) ? 6.0f : 12.0f))); - cones.push (getShootingConeDeviation (g_hostEntity, path->origin - Vector (0.0f, 0.0f, (path->flags & FLAG_CROUCH) ? 12.0f : 24.0f))); - cones.push (getShootingConeDeviation (g_hostEntity, path->origin + Vector (0.0f, 0.0f, (path->flags & FLAG_CROUCH) ? 6.0f : 12.0f))); - cones.push (getShootingConeDeviation (g_hostEntity, path->origin + Vector (0.0f, 0.0f, (path->flags & FLAG_CROUCH) ? 12.0f : 24.0f))); + cones.push (util.getShootingCone (m_editor, path->origin)); + cones.push (util.getShootingCone (m_editor, path->origin - Vector (0.0f, 0.0f, (path->flags & FLAG_CROUCH) ? 6.0f : 12.0f))); + cones.push (util.getShootingCone (m_editor, path->origin - Vector (0.0f, 0.0f, (path->flags & FLAG_CROUCH) ? 12.0f : 24.0f))); + cones.push (util.getShootingCone (m_editor, path->origin + Vector (0.0f, 0.0f, (path->flags & FLAG_CROUCH) ? 6.0f : 12.0f))); + cones.push (util.getShootingCone (m_editor, path->origin + Vector (0.0f, 0.0f, (path->flags & FLAG_CROUCH) ? 12.0f : 24.0f))); // check if we can see it for (auto &cone : cones) { @@ -979,7 +974,7 @@ void Waypoint::pathCreate (char dir) { int nodeFrom = getEditorNeareset (); if (nodeFrom == INVALID_WAYPOINT_INDEX) { - engine.centerPrint ("Unable to find nearest waypoint in 50 units"); + ctrl.msg ("Unable to find nearest waypoint in 50 units"); return; } int nodeTo = m_facingAtIndex; @@ -989,13 +984,13 @@ void Waypoint::pathCreate (char dir) { nodeTo = m_cacheWaypointIndex; } else { - engine.centerPrint ("Unable to find destination waypoint"); + ctrl.msg ("Unable to find destination waypoint"); return; } } if (nodeTo == nodeFrom) { - engine.centerPrint ("Unable to connect waypoint with itself"); + ctrl.msg ("Unable to connect waypoint with itself"); return; } @@ -1012,7 +1007,7 @@ void Waypoint::pathCreate (char dir) { addPath (nodeTo, nodeFrom, distance); } - engine.playSound (g_hostEntity, "common/wpn_hudon.wav"); + game.playSound (m_editor, "common/wpn_hudon.wav"); m_waypointsChanged = true; } @@ -1023,7 +1018,7 @@ void Waypoint::erasePath (void) { int index = 0; if (nodeFrom == INVALID_WAYPOINT_INDEX) { - engine.centerPrint ("Unable to find nearest waypoint in 50 units"); + ctrl.msg ("Unable to find nearest waypoint in 50 units"); return; } int nodeTo = m_facingAtIndex; @@ -1033,7 +1028,7 @@ void Waypoint::erasePath (void) { nodeTo = m_cacheWaypointIndex; } else { - engine.centerPrint ("Unable to find destination waypoint"); + ctrl.msg ("Unable to find destination waypoint"); return; } } @@ -1047,7 +1042,7 @@ void Waypoint::erasePath (void) { m_paths[nodeFrom]->connectionFlags[index] = 0; m_paths[nodeFrom]->connectionVelocity[index].nullify (); - engine.playSound (g_hostEntity, "weapons/mine_activate.wav"); + game.playSound (m_editor, "weapons/mine_activate.wav"); return; } } @@ -1067,24 +1062,24 @@ void Waypoint::erasePath (void) { m_paths[nodeFrom]->connectionFlags[index] = 0; m_paths[nodeFrom]->connectionVelocity[index].nullify (); - engine.playSound (g_hostEntity, "weapons/mine_activate.wav"); + game.playSound (m_editor, "weapons/mine_activate.wav"); return; } } - engine.centerPrint ("There is already no path on this waypoint"); + ctrl.msg ("There is already no path on this waypoint"); } -void Waypoint::cachePoint (void) { - int node = getEditorNeareset (); +void Waypoint::cachePoint (int index) { + int node = exists (index) ? index : getEditorNeareset (); if (node == INVALID_WAYPOINT_INDEX) { m_cacheWaypointIndex = INVALID_WAYPOINT_INDEX; - engine.centerPrint ("Cached waypoint cleared (nearby point not found in 50 units range)"); + ctrl.msg ("Cached waypoint cleared (nearby point not found in 50 units range)"); return; } m_cacheWaypointIndex = node; - engine.centerPrint ("Waypoint #%d has been put into memory", m_cacheWaypointIndex); + ctrl.msg ("Waypoint #%d has been put into memory", m_cacheWaypointIndex); } void Waypoint::calculatePathRadius (int index) { @@ -1098,8 +1093,8 @@ void Waypoint::calculatePathRadius (int index) { return; } - for (int i = 0; i < MAX_PATH_INDEX; i++) { - if (path->index[i] != INVALID_WAYPOINT_INDEX && (m_paths[path->index[i]]->flags & FLAG_LADDER)) { + for (auto &test : path->index) { + if (test != INVALID_WAYPOINT_INDEX && (m_paths[test]->flags & FLAG_LADDER)) { path->radius = 0.0f; return; } @@ -1109,23 +1104,23 @@ void Waypoint::calculatePathRadius (int index) { for (float scanDistance = 32.0f; scanDistance < 128.0f; scanDistance += 16.0f) { start = path->origin; - makeVectors (Vector::null ()); + game.makeVectors (Vector::null ()); - direction = g_pGlobals->v_forward * scanDistance; + direction = game.vec.forward * scanDistance; direction = direction.toAngles (); path->radius = scanDistance; for (float circleRadius = 0.0f; circleRadius < 360.0f; circleRadius += 20.0f) { - makeVectors (direction); + game.makeVectors (direction); - Vector radiusStart = start + g_pGlobals->v_forward * scanDistance; - Vector radiusEnd = start + g_pGlobals->v_forward * scanDistance; + Vector radiusStart = start + game.vec.forward * scanDistance; + Vector radiusEnd = start + game.vec.forward * scanDistance; - engine.testHull (radiusStart, radiusEnd, TRACE_IGNORE_MONSTERS, head_hull, nullptr, &tr); + game.testHull (radiusStart, radiusEnd, TRACE_IGNORE_MONSTERS, head_hull, nullptr, &tr); if (tr.flFraction < 1.0f) { - engine.testLine (radiusStart, radiusEnd, TRACE_IGNORE_MONSTERS, nullptr, &tr); + game.testLine (radiusStart, radiusEnd, TRACE_IGNORE_MONSTERS, nullptr, &tr); if (strncmp ("func_door", STRING (tr.pHit->v.classname), 9) == 0) { path->radius = 0.0f; @@ -1139,10 +1134,10 @@ void Waypoint::calculatePathRadius (int index) { break; } - Vector dropStart = start + g_pGlobals->v_forward * scanDistance; + Vector dropStart = start + game.vec.forward * scanDistance; Vector dropEnd = dropStart - Vector (0.0f, 0.0f, scanDistance + 60.0f); - engine.testHull (dropStart, dropEnd, TRACE_IGNORE_MONSTERS, head_hull, nullptr, &tr); + game.testHull (dropStart, dropEnd, TRACE_IGNORE_MONSTERS, head_hull, nullptr, &tr); if (tr.flFraction >= 1.0f) { wayBlocked = true; @@ -1150,10 +1145,10 @@ void Waypoint::calculatePathRadius (int index) { break; } - dropStart = start - g_pGlobals->v_forward * scanDistance; + dropStart = start - game.vec.forward * scanDistance; dropEnd = dropStart - Vector (0.0f, 0.0f, scanDistance + 60.0f); - engine.testHull (dropStart, dropEnd, TRACE_IGNORE_MONSTERS, head_hull, nullptr, &tr); + game.testHull (dropStart, dropEnd, TRACE_IGNORE_MONSTERS, head_hull, nullptr, &tr); if (tr.flFraction >= 1.0f) { wayBlocked = true; @@ -1162,7 +1157,7 @@ void Waypoint::calculatePathRadius (int index) { } radiusEnd.z += 34.0f; - engine.testHull (radiusStart, radiusEnd, TRACE_IGNORE_MONSTERS, head_hull, nullptr, &tr); + game.testHull (radiusStart, radiusEnd, TRACE_IGNORE_MONSTERS, head_hull, nullptr, &tr); if (tr.flFraction < 1.0f) { wayBlocked = true; @@ -1179,192 +1174,195 @@ void Waypoint::calculatePathRadius (int index) { } path->radius -= 16.0f; - if (path->radius < 0.0f) + if (path->radius < 0.0f) { path->radius = 0.0f; + } } void Waypoint::saveExperience (void) { - ExtensionHeader header; - if (m_numWaypoints < 1 || m_waypointsChanged) { return; } - - memset (header.header, 0, sizeof (header.header)); - strcpy (header.header, FH_EXPERIENCE); - - header.fileVersion = FV_EXPERIENCE; - header.pointNumber = m_numWaypoints; - - ExperienceSave *experienceSave = new ExperienceSave[m_numWaypoints * m_numWaypoints]; - - for (int i = 0; i < m_numWaypoints; i++) { - for (int j = 0; j < m_numWaypoints; j++) { - (experienceSave + (i * m_numWaypoints) + j)->team0Damage = static_cast ((g_experienceData + (i * m_numWaypoints) + j)->team0Damage >> 3); - (experienceSave + (i * m_numWaypoints) + j)->team1Damage = static_cast ((g_experienceData + (i * m_numWaypoints) + j)->team1Damage >> 3); - (experienceSave + (i * m_numWaypoints) + j)->team0Value = static_cast ((g_experienceData + (i * m_numWaypoints) + j)->team0Value / 8); - (experienceSave + (i * m_numWaypoints) + j)->team1Value = static_cast ((g_experienceData + (i * m_numWaypoints) + j)->team1Value / 8); - } - } - int result = Compress::encode (format ("%slearned/%s.exp", getDataDirectory (), engine.getMapName ()), (uint8 *)&header, sizeof (ExtensionHeader), (uint8 *) experienceSave, m_numWaypoints * m_numWaypoints * sizeof (ExperienceSave)); - - delete[] experienceSave; - - if (result == -1) { - logEntry (true, LL_ERROR, "Couldn't save experience data"); - return; - } + saveExtFile ("exp", "Experience", FH_EXPERIENCE, FV_EXPERIENCE, reinterpret_cast (m_experience), m_numWaypoints * m_numWaypoints * sizeof (Experience)); } -void Waypoint::initExperience (void) { - int i, j; - - delete[] g_experienceData; - g_experienceData = nullptr; +void Waypoint::loadExperience (void) { + delete[] m_experience; + m_experience = nullptr; if (m_numWaypoints < 1) { return; } - g_experienceData = new Experience[m_numWaypoints * m_numWaypoints]; + m_experience = new Experience[m_numWaypoints * m_numWaypoints + FastLZ::EXCESS]; - g_highestDamageCT = 1; - g_highestDamageT = 1; + // reset highest recorded damage + for (int team = TEAM_TERRORIST; team < MAX_TEAM_COUNT; team++) { + m_highestDamage[team] = 1; + } // initialize table by hand to correct values, and NOT zero it out - for (i = 0; i < m_numWaypoints; i++) { - for (j = 0; j < m_numWaypoints; j++) { - (g_experienceData + (i * m_numWaypoints) + j)->team0DangerIndex = INVALID_WAYPOINT_INDEX; - (g_experienceData + (i * m_numWaypoints) + j)->team1DangerIndex = INVALID_WAYPOINT_INDEX; - - (g_experienceData + (i * m_numWaypoints) + j)->team0Damage = 0; - (g_experienceData + (i * m_numWaypoints) + j)->team1Damage = 0; - - (g_experienceData + (i * m_numWaypoints) + j)->team0Value = 0; - (g_experienceData + (i * m_numWaypoints) + j)->team1Value = 0; + for (int team = TEAM_TERRORIST; team < MAX_TEAM_COUNT; team++) { + for (int i = 0; i < m_numWaypoints; i++) { + for (int j = 0; j < m_numWaypoints; j++) { + (m_experience + (i * m_numWaypoints) + j)->index[team] = INVALID_WAYPOINT_INDEX; + (m_experience + (i * m_numWaypoints) + j)->damage[team] = 0; + (m_experience + (i * m_numWaypoints) + j)->value[team] = 0; + } } } - File fp (format ("%slearned/%s.exp", getDataDirectory (), engine.getMapName ()), "rb"); + bool isLoaded = loadExtFile ("exp", "Experience", FH_EXPERIENCE, FV_EXPERIENCE, reinterpret_cast (m_experience)); - // if file exists, read the experience data from it - if (fp.isValid ()) { - ExtensionHeader header; - memset (&header, 0, sizeof (header)); + // set's the highest damage if loaded ok + if (!isLoaded) { + return; + } - if (fp.read (&header, sizeof (header)) == 0) { - logEntry (true, LL_ERROR, "Experience data damaged (unable to read header)"); - - fp.close (); - return; - } - fp.close (); - - if (strncmp (header.header, FH_EXPERIENCE, strlen (FH_EXPERIENCE)) == 0) { - if (header.fileVersion == FV_EXPERIENCE && header.pointNumber == m_numWaypoints) { - ExperienceSave *experienceLoad = new ExperienceSave[m_numWaypoints * m_numWaypoints * sizeof (ExperienceSave)]; - - Compress::decode (format ("%slearned/%s.exp", getDataDirectory (), engine.getMapName ()), sizeof (ExtensionHeader), (uint8 *)experienceLoad, m_numWaypoints * m_numWaypoints * sizeof (ExperienceSave)); - - for (i = 0; i < m_numWaypoints; i++) { - for (j = 0; j < m_numWaypoints; j++) { - if (i == j) { - (g_experienceData + (i * m_numWaypoints) + j)->team0Damage = (uint16) ((experienceLoad + (i * m_numWaypoints) + j)->team0Damage); - (g_experienceData + (i * m_numWaypoints) + j)->team1Damage = (uint16) ((experienceLoad + (i * m_numWaypoints) + j)->team1Damage); - - if ((g_experienceData + (i * m_numWaypoints) + j)->team0Damage > g_highestDamageT) - g_highestDamageT = (g_experienceData + (i * m_numWaypoints) + j)->team0Damage; - - if ((g_experienceData + (i * m_numWaypoints) + j)->team1Damage > g_highestDamageCT) - g_highestDamageCT = (g_experienceData + (i * m_numWaypoints) + j)->team1Damage; - } - else { - (g_experienceData + (i * m_numWaypoints) + j)->team0Damage = (uint16) ((experienceLoad + (i * m_numWaypoints) + j)->team0Damage) << 3; - (g_experienceData + (i * m_numWaypoints) + j)->team1Damage = (uint16) ((experienceLoad + (i * m_numWaypoints) + j)->team1Damage) << 3; - } - - (g_experienceData + (i * m_numWaypoints) + j)->team0Value = (int16) ((experienceLoad + i * (m_numWaypoints) + j)->team0Value) * 8; - (g_experienceData + (i * m_numWaypoints) + j)->team1Value = (int16) ((experienceLoad + i * (m_numWaypoints) + j)->team1Value) * 8; + for (int team = TEAM_TERRORIST; team < MAX_TEAM_COUNT; team++) { + for (int i = 0; i < m_numWaypoints; i++) { + for (int j = 0; j < m_numWaypoints; j++) { + if (i == j) { + if ((m_experience + (i * m_numWaypoints) + j)->damage[team] > m_highestDamage[team]) { + m_highestDamage[team] = (m_experience + (i * m_numWaypoints) + j)->damage[team]; } } - delete[] experienceLoad; } - else - logEntry (true, LL_WARNING, "Experience data damaged (wrong version, or not for this map)"); } } } +void Waypoint::loadVisibility (void) { + m_visibilityIndex = 0; + m_needsVisRebuild = true; + + if (m_numWaypoints <= 0) { + return; + } + bool isLoaded = loadExtFile ("vis", "Visibility", FH_VISTABLE, FV_VISTABLE, reinterpret_cast (m_visLUT)); + + // if loaded, do not recalculate visibility + if (isLoaded) { + m_needsVisRebuild = false; + } +} + void Waypoint::saveVisibility (void) { - if (m_numWaypoints == 0) { + if (m_numWaypoints < 1 || m_waypointsChanged) { return; } - - ExtensionHeader header; - memset (&header, 0, sizeof (ExtensionHeader)); - - // parse header - memset (header.header, 0, sizeof (header.header)); - strcpy (header.header, FH_VISTABLE); - - header.fileVersion = FV_VISTABLE; - header.pointNumber = m_numWaypoints; - - File fp (format ("%slearned/%s.vis", getDataDirectory (), engine.getMapName ()), "wb"); - - if (!fp.isValid ()) { - logEntry (true, LL_ERROR, "Failed to open visibility table for writing"); - return; - } - fp.close (); - - Compress::encode (format ("%slearned/%s.vis", getDataDirectory (), engine.getMapName ()), (uint8 *)&header, sizeof (ExtensionHeader), (uint8 *)m_visLUT, MAX_WAYPOINTS * (MAX_WAYPOINTS / 4) * sizeof (uint8)); + saveExtFile ("vis", "Visibility", FH_VISTABLE, FV_VISTABLE, reinterpret_cast (m_visLUT), MAX_WAYPOINTS * (MAX_WAYPOINTS / 4) * sizeof (uint8)); } -void Waypoint::initVisibility (void) { - if (m_numWaypoints == 0) +void Waypoint::savePathMatrix (void) { + if (m_numWaypoints < 1) { return; + } + saveExtFile ("pmx", "Pathmatrix", FH_MATRIX, FV_MATRIX, reinterpret_cast (m_matrix), m_numWaypoints * m_numWaypoints * sizeof (FloydMatrix)); +} - ExtensionHeader header; +bool Waypoint::loadPathMatrix (void) { + if (m_numWaypoints <= 0) { + return false; + } + return loadExtFile ("pmx", "Pathmatrix", FH_MATRIX, FV_MATRIX, reinterpret_cast (m_matrix)); +} - File fp (format ("%slearned/%s.vis", getDataDirectory (), engine.getMapName ()), "rb"); - m_redoneVisibility = false; +bool Waypoint::saveExtFile (const char *ext, const char *type, const char *magic, int version, uint8 *data, int32 size) { + FastLZ lz; + bool dataSaved = false; + ExtHeader header; + header.pointNumber = m_numWaypoints; + header.fileVersion = version; + header.uncompressed = size; + + strncpy (header.header, magic, strlen (magic)); + + auto compressed = new uint8[header.uncompressed]; + int compressedLength = lz.compress (reinterpret_cast (data), header.uncompressed, compressed); + + if (compressedLength > 0) { + File fp (util.format ("%slearned/%s.%s", getDataDirectory (), game.getMapName (), ext), "wb"); + + if (fp.isValid ()) { + header.compressed = compressedLength; + + fp.write (&header, sizeof (ExtHeader)); + fp.write (compressed, compressedLength); + fp.close (); + + game.print ("Successfully saved Bots %s data.", type); + dataSaved = true; + } + else { + util.logEntry (true, LL_ERROR, "Couldn't save %s data (unable to write the file)", type); + dataSaved = false; + + } + } + else { + util.logEntry (true, LL_ERROR, "Couldn't save %s data (unable to compress data)", type); + dataSaved = false; + } + delete[] compressed; + + return dataSaved; +} + +bool Waypoint::loadExtFile (const char *ext, const char *type, const char *magic, int version, uint8 *data) { + File fp (util.format ("%slearned/%s.%s", getDataDirectory (), game.getMapName (), ext), "rb"); + + // if file exists, read the visibility data from it if (!fp.isValid ()) { - m_visibilityIndex = 0; - m_redoneVisibility = true; - - logEntry (true, LL_DEFAULT, "Vistable doesn't exists, vistable will be rebuilded"); - return; + return false; } + ExtHeader header; - // read the header of the file - if (fp.read (&header, sizeof (header)) == 0) { - logEntry (true, LL_ERROR, "Vistable damaged (unable to read header)"); - - fp.close (); - return; - } - - if (strncmp (header.header, FH_VISTABLE, strlen (FH_VISTABLE)) != 0 || header.fileVersion != FV_VISTABLE || header.pointNumber != m_numWaypoints) { - m_visibilityIndex = 0; - m_redoneVisibility = true; - - logEntry (true, LL_WARNING, "Visibility table damaged (wrong version, or not for this map), vistable will be rebuilded."); + if (fp.read (&header, sizeof (ExtHeader)) == 0) { + util.logEntry (true, LL_ERROR, "%s data damaged (unable to read header)", type); fp.close (); - return; + return false; } - int result = Compress::decode (format ("%slearned/%s.vis", getDataDirectory (), engine.getMapName ()), sizeof (ExtensionHeader), (uint8 *)m_visLUT, MAX_WAYPOINTS * (MAX_WAYPOINTS / 4) * sizeof (uint8)); - if (result == -1) { - m_visibilityIndex = 0; - m_redoneVisibility = true; - - logEntry (true, LL_ERROR, "Failed to decode vistable, vistable will be rebuilded."); + if (!!strncmp (header.header, magic, strlen (magic))) { + util.logEntry (true, LL_ERROR, "%s data damaged (bad header '%s')", type, header.header); fp.close (); - return; + return false; + } + + FastLZ lz; + bool dataLoaded = false; + + // check the header + if (header.fileVersion == version && header.pointNumber == m_numWaypoints && header.compressed > 1) { + auto compressed = new uint8[header.compressed]; + + if (fp.read (compressed, sizeof (uint8), header.compressed) == static_cast (header.compressed)) { + int status = lz.uncompress (compressed, header.compressed, data, header.uncompressed); + + if (status == FastLZ::UNCOMPRESS_RESULT_FAILED) { + util.logEntry (true, LL_ERROR, "%s data damaged (failed to decompress data)", type); + dataLoaded = false; + } + else { + game.print ("Successfully loaded the bots %s tables.", type); + dataLoaded = true; + } + } + else { + util.logEntry (true, LL_ERROR, "%s data damaged (unable to read compressed data)", type); + dataLoaded = false; + } + delete[] compressed; + } + else { + util.logEntry (true, LL_ERROR, "%s data damaged (wrong version, or not for this map)", type); + dataLoaded = false; } fp.close (); + + return dataLoaded; } void Waypoint::initLightLevels (void) { @@ -1420,8 +1418,8 @@ bool Waypoint::load (void) { if (m_loadTries++ > 3) { m_loadTries = 0; - sprintf (m_infoBuffer, "Giving up loading waypoint file (%s). Something went wrong.", engine.getMapName ()); - logEntry (true, LL_ERROR, m_infoBuffer); + m_tempInfo.assign ("Giving up loading waypoint file (%s). Something went wrong.", game.getMapName ()); + util.logEntry (true, LL_ERROR, m_tempInfo.chars ()); return false; } @@ -1431,16 +1429,21 @@ bool Waypoint::load (void) { memset (&header, 0, sizeof (header)); // save for faster access - const char *map = engine.getMapName (); + const char *map = game.getMapName (); // helper function auto throwError = [&] (const char *fmt, ...) -> bool { + char infobuffer[MAX_PRINT_BUFFER]; + va_list ap; va_start (ap, fmt); - vsnprintf (m_infoBuffer, MAX_PRINT_BUFFER - 1, fmt, ap); + vsnprintf (infobuffer, MAX_PRINT_BUFFER - 1, fmt, ap); va_end (ap); - logEntry (true, LL_ERROR, m_infoBuffer); + util.logEntry (true, LL_ERROR, infobuffer); + ctrl.msg (infobuffer); + + m_tempInfo = infobuffer; if (fp.isValid ()) { fp.close (); @@ -1494,7 +1497,7 @@ bool Waypoint::load (void) { } else { if (yb_waypoint_autodl_enable.boolean ()) { - logEntry (true, LL_DEFAULT, "%s.pwf does not exist, trying to download from waypoint database", map); + util.logEntry (true, LL_DEFAULT, "%s.pwf does not exist, trying to download from waypoint database", map); switch (downloadWaypoint ()) { case WDE_SOCKET_ERROR: @@ -1507,7 +1510,7 @@ bool Waypoint::load (void) { return throwError ("%s.pwf does not exist. Can't autodownload. Waypoint not available.", map); case WDE_NOERROR: - logEntry (true, LL_DEFAULT, "%s.pwf was downloaded from waypoint database. Trying to load...", map); + util.logEntry (true, LL_DEFAULT, "%s.pwf was downloaded from waypoint database. Trying to load...", map); return load (); } } @@ -1515,10 +1518,10 @@ bool Waypoint::load (void) { } if (strncmp (header.author, "official", 7) == 0) { - sprintf (m_infoBuffer, "Using Official Waypoint File"); + m_tempInfo.assign ("Using Official Waypoint File"); } else { - sprintf (m_infoBuffer, "Using waypoint file by: %s", header.author); + m_tempInfo.assign ("Using waypoint file by: %s", header.author); } for (int i = 0; i < m_numWaypoints; i++) { @@ -1530,13 +1533,11 @@ bool Waypoint::load (void) { initTypes (); m_waypointsChanged = false; - g_highestKills = 1; - m_pathDisplayTime = 0.0f; m_arrowDisplayTime = 0.0f; - initVisibility (); - initExperience (); + loadVisibility (); + loadExperience (); extern ConVar yb_debug_goal; yb_debug_goal.set (INVALID_WAYPOINT_INDEX); @@ -1552,8 +1553,8 @@ void Waypoint::save (void) { memset (header.header, 0, sizeof (header.header)); strcpy (header.header, FH_WAYPOINT); - strncpy (header.author, STRING (g_hostEntity->v.netname), cr::bufsize (header.author)); - strncpy (header.mapName, engine.getMapName (), cr::bufsize (header.mapName)); + strncpy (header.author, STRING (m_editor->v.netname), cr::bufsize (header.author)); + strncpy (header.mapName, game.getMapName (), cr::bufsize (header.mapName)); header.mapName[31] = 0; header.fileVersion = FV_WAYPOINT; @@ -1572,18 +1573,19 @@ void Waypoint::save (void) { } fp.close (); } - else - logEntry (true, LL_ERROR, "Error writing '%s.pwf' waypoint file", engine.getMapName ()); + else { + util.logEntry (true, LL_ERROR, "Error writing '%s.pwf' waypoint file", game.getMapName ()); + } } const char *Waypoint::getWaypointFilename (bool isMemoryFile) { static String buffer; - buffer.format ("%s%s%s.pwf", getDataDirectory (isMemoryFile), isEmptyStr (yb_wptsubfolder.str ()) ? "" : yb_wptsubfolder.str (), engine.getMapName ()); + buffer.assign ("%s%s%s.pwf", getDataDirectory (isMemoryFile), util.isEmptyStr (yb_wptsubfolder.str ()) ? "" : yb_wptsubfolder.str (), game.getMapName ()); if (File::exists (buffer)) { return buffer.chars (); } - return format ("%s%s.pwf", getDataDirectory (isMemoryFile), engine.getMapName ()); + return util.format ("%s%s.pwf", getDataDirectory (isMemoryFile), game.getMapName ()); } float Waypoint::calculateTravelTime (float maxSpeed, const Vector &src, const Vector &origin) { @@ -1609,7 +1611,7 @@ bool Waypoint::isReachable (Bot *bot, int index) { float ladderDist = (dst - src).length2D (); TraceResult tr; - engine.testLine (src, dst, TRACE_IGNORE_MONSTERS, bot->ent (), &tr); + game.testLine (src, dst, TRACE_IGNORE_MONSTERS, bot->ent (), &tr); // if waypoint is visible from current position (even behind head)... if (tr.flFraction >= 1.0f) { @@ -1642,32 +1644,33 @@ bool Waypoint::isNodeReacheable (const Vector &src, const Vector &destination) { float distance = (destination - src).length (); // is the destination not close enough? - if (distance > g_autoPathDistance) { + if (distance > m_autoPathDistance) { return false; } // check if we go through a func_illusionary, in which case return false - engine.testHull (src, destination, TRACE_IGNORE_MONSTERS, head_hull, g_hostEntity, &tr); + game.testHull (src, destination, TRACE_IGNORE_MONSTERS, head_hull, m_editor, &tr); - if (!engine.isNullEntity (tr.pHit) && strcmp ("func_illusionary", STRING (tr.pHit->v.classname)) == 0) { + if (!game.isNullEntity (tr.pHit) && strcmp ("func_illusionary", STRING (tr.pHit->v.classname)) == 0) { return false; // don't add pathwaypoints through func_illusionaries } // check if this waypoint is "visible"... - engine.testLine (src, destination, TRACE_IGNORE_MONSTERS, g_hostEntity, &tr); + game.testLine (src, destination, TRACE_IGNORE_MONSTERS, m_editor, &tr); // if waypoint is visible from current position (even behind head)... if (tr.flFraction >= 1.0f || strncmp ("func_door", STRING (tr.pHit->v.classname), 9) == 0) { // if it's a door check if nothing blocks behind if (strncmp ("func_door", STRING (tr.pHit->v.classname), 9) == 0) { - engine.testLine (tr.vecEndPos, destination, TRACE_IGNORE_MONSTERS, tr.pHit, &tr); + game.testLine (tr.vecEndPos, destination, TRACE_IGNORE_MONSTERS, tr.pHit, &tr); - if (tr.flFraction < 1.0f) + if (tr.flFraction < 1.0f) { return false; + } } // check for special case of both waypoints being in water... - if (g_engfuncs.pfnPointContents (src) == CONTENTS_WATER && g_engfuncs.pfnPointContents (destination) == CONTENTS_WATER) { + if (engfuncs.pfnPointContents (src) == CONTENTS_WATER && engfuncs.pfnPointContents (destination) == CONTENTS_WATER) { return true; // then they're reachable each other } @@ -1677,7 +1680,7 @@ bool Waypoint::isNodeReacheable (const Vector &src, const Vector &destination) { Vector destinationNew = destination; destinationNew.z = destinationNew.z - 50.0f; // straight down 50 units - engine.testLine (sourceNew, destinationNew, TRACE_IGNORE_MONSTERS, g_hostEntity, &tr); + game.testLine (sourceNew, destinationNew, TRACE_IGNORE_MONSTERS, m_editor, &tr); // check if we didn't hit anything, if not then it's in mid-air if (tr.flFraction >= 1.0) { @@ -1691,7 +1694,7 @@ bool Waypoint::isNodeReacheable (const Vector &src, const Vector &destination) { down.z = down.z - 1000.0f; // straight down 1000 units - engine.testLine (check, down, TRACE_IGNORE_MONSTERS, g_hostEntity, &tr); + game.testLine (check, down, TRACE_IGNORE_MONSTERS, m_editor, &tr); float lastHeight = tr.flFraction * 1000.0f; // height from ground distance = (destination - check).length (); // distance from goal @@ -1703,7 +1706,7 @@ bool Waypoint::isNodeReacheable (const Vector &src, const Vector &destination) { down = check; down.z = down.z - 1000.0f; // straight down 1000 units - engine.testLine (check, down, TRACE_IGNORE_MONSTERS, g_hostEntity, &tr); + game.testLine (check, down, TRACE_IGNORE_MONSTERS, m_editor, &tr); float height = tr.flFraction * 1000.0f; // height from ground @@ -1720,7 +1723,7 @@ bool Waypoint::isNodeReacheable (const Vector &src, const Vector &destination) { } void Waypoint::rebuildVisibility (void) { - if (!m_redoneVisibility) { + if (!m_needsVisRebuild) { return; } @@ -1745,7 +1748,7 @@ void Waypoint::rebuildVisibility (void) { // first check ducked visibility Vector dest = m_paths[i]->origin; - engine.testLine (sourceDuck, dest, TRACE_IGNORE_MONSTERS, nullptr, &tr); + game.testLine (sourceDuck, dest, TRACE_IGNORE_MONSTERS, nullptr, &tr); // check if line of sight to object is not blocked (i.e. visible) if (tr.flFraction != 1.0f || tr.fStartSolid) { @@ -1756,7 +1759,7 @@ void Waypoint::rebuildVisibility (void) { } res <<= 1; - engine.testLine (sourceStand, dest, TRACE_IGNORE_MONSTERS, nullptr, &tr); + game.testLine (sourceStand, dest, TRACE_IGNORE_MONSTERS, nullptr, &tr); // check if line of sight to object is not blocked (i.e. visible) if (tr.flFraction != 1.0f || tr.fStartSolid) { @@ -1773,7 +1776,7 @@ void Waypoint::rebuildVisibility (void) { else { dest.z += 28.0f; } - engine.testLine (sourceDuck, dest, TRACE_IGNORE_MONSTERS, nullptr, &tr); + game.testLine (sourceDuck, dest, TRACE_IGNORE_MONSTERS, nullptr, &tr); // check if line of sight to object is not blocked (i.e. visible) if (tr.flFraction != 1.0f || tr.fStartSolid) { @@ -1782,7 +1785,7 @@ void Waypoint::rebuildVisibility (void) { else { res &= 1; } - engine.testLine (sourceStand, dest, TRACE_IGNORE_MONSTERS, nullptr, &tr); + game.testLine (sourceStand, dest, TRACE_IGNORE_MONSTERS, nullptr, &tr); // check if line of sight to object is not blocked (i.e. visible) if (tr.flFraction != 1.0f || tr.fStartSolid) { @@ -1808,7 +1811,7 @@ void Waypoint::rebuildVisibility (void) { m_paths[m_visibilityIndex]->vis.crouch = crouchCount; m_paths[m_visibilityIndex]->vis.stand = standCount; } - m_redoneVisibility = false; + m_needsVisRebuild = false; } bool Waypoint::isVisible (int srcIndex, int destIndex) { @@ -1844,57 +1847,36 @@ bool Waypoint::isStandVisible (int srcIndex, int destIndex) { return !((res & 1) == 1); } -const char *Waypoint::getInformation (int id) { - // this function returns path information for waypoint pointed by id. - - Path *path = m_paths[id]; - - // if this path is null, return - if (path == nullptr) { - return "\0"; - } - bool jumpPoint = false; - - // iterate through connections and find, if it's a jump path - for (int i = 0; i < MAX_PATH_INDEX; i++) { - // check if we got a valid connection - if (path->index[i] != INVALID_WAYPOINT_INDEX && (path->connectionFlags[i] & PATHFLAG_JUMP)) { - jumpPoint = true; - } - } - - static char messageBuffer[MAX_PRINT_BUFFER]; - sprintf (messageBuffer, "%s%s%s%s%s%s%s%s%s%s%s%s%s%s", (path->flags == 0 && !jumpPoint) ? " (none)" : "", (path->flags & FLAG_LIFT) ? " LIFT" : "", (path->flags & FLAG_CROUCH) ? " CROUCH" : "", (path->flags & FLAG_CROSSING) ? " CROSSING" : "", (path->flags & FLAG_CAMP) ? " CAMP" : "", (path->flags & FLAG_TF_ONLY) ? " TERRORIST" : "", (path->flags & FLAG_CF_ONLY) ? " CT" : "", (path->flags & FLAG_SNIPER) ? " SNIPER" : "", (path->flags & FLAG_GOAL) ? " GOAL" : "", (path->flags & FLAG_LADDER) ? " LADDER" : "", (path->flags & FLAG_RESCUE) ? " RESCUE" : "", (path->flags & FLAG_DOUBLEJUMP) ? " JUMPHELP" : "", (path->flags & FLAG_NOHOSTAGE) ? " NOHOSTAGE" : "", jumpPoint ? " JUMP" : ""); - - // return the message buffer - return messageBuffer; -} - void Waypoint::frame (void) { // this function executes frame of waypoint operation code. - if (engine.isNullEntity (g_hostEntity)) { + if (game.isNullEntity (m_editor)) { return; // this function is only valid on listenserver, and in waypoint enabled mode. } + // keep the clipping mode enabled, or it can be turned off after new round has started + if (waypoints.hasEditFlag (WS_EDIT_NOCLIP) && util.isAlive (m_editor)) { + m_editor->v.movetype = MOVETYPE_NOCLIP; + } + float nearestDistance = 99999.0f; int nearestIndex = INVALID_WAYPOINT_INDEX; // check if it's time to add jump waypoint if (m_learnJumpWaypoint) { if (!m_endJumpPoint) { - if (g_hostEntity->v.button & IN_JUMP) { + if (m_editor->v.button & IN_JUMP) { push (9); - m_timeJumpStarted = engine.timebase (); + m_timeJumpStarted = game.timebase (); m_endJumpPoint = true; } else { - m_learnVelocity = g_hostEntity->v.velocity; - m_learnPosition = g_hostEntity->v.origin; + m_learnVelocity = m_editor->v.velocity; + m_learnPosition = m_editor->v.origin; } } - else if (((g_hostEntity->v.flags & FL_ONGROUND) || g_hostEntity->v.movetype == MOVETYPE_FLY) && m_timeJumpStarted + 0.1f < engine.timebase () && m_endJumpPoint) { + else if (((m_editor->v.flags & FL_ONGROUND) || m_editor->v.movetype == MOVETYPE_FLY) && m_timeJumpStarted + 0.1f < game.timebase () && m_endJumpPoint) { push (10); m_learnJumpWaypoint = false; @@ -1903,15 +1885,15 @@ void Waypoint::frame (void) { } // check if it's a autowaypoint mode enabled - if (g_autoWaypoint && (g_hostEntity->v.flags & (FL_ONGROUND | FL_PARTIALGROUND))) { + if (hasEditFlag (WS_EDIT_AUTO) && (m_editor->v.flags & (FL_ONGROUND | FL_PARTIALGROUND))) { // find the distance from the last used waypoint - float distance = (m_lastWaypoint - g_hostEntity->v.origin).lengthSq (); + float distance = (m_lastWaypoint - m_editor->v.origin).lengthSq (); if (distance > 16384.0f) { // check that no other reachable waypoints are nearby... for (int i = 0; i < m_numWaypoints; i++) { - if (isNodeReacheable (g_hostEntity->v.origin, m_paths[i]->origin)) { - distance = (m_paths[i]->origin - g_hostEntity->v.origin).lengthSq (); + if (isNodeReacheable (m_editor->v.origin, m_paths[i]->origin)) { + distance = (m_paths[i]->origin - m_editor->v.origin).lengthSq (); if (distance < nearestDistance) { nearestDistance = distance; @@ -1932,17 +1914,17 @@ void Waypoint::frame (void) { // now iterate through all waypoints in a map, and draw required ones for (int i = 0; i < m_numWaypoints; i++) { - float distance = (m_paths[i]->origin - g_hostEntity->v.origin).length (); + float distance = (m_paths[i]->origin - m_editor->v.origin).length (); // check if waypoint is whitin a distance, and is visible - if (distance < 512.0f && ((::isVisible (m_paths[i]->origin, g_hostEntity) && isInViewCone (m_paths[i]->origin, g_hostEntity)) || !isAlive (g_hostEntity) || distance < 128.0f)) { + if (distance < 512.0f && ((util.isVisible (m_paths[i]->origin, m_editor) && util.isInViewCone (m_paths[i]->origin, m_editor)) || !util.isAlive (m_editor) || distance < 128.0f)) { // check the distance if (distance < nearestDistance) { nearestIndex = i; nearestDistance = distance; } - if (m_waypointDisplayTime[i] + 0.8f < engine.timebase ()) { + if (m_waypointDisplayTime[i] + 0.8f < game.timebase ()) { float nodeHeight = 0.0f; // check the node height @@ -1998,15 +1980,15 @@ void Waypoint::frame (void) { // draw node without additional flags if (nodeFlagColor.x == -1) { - engine.drawLine (g_hostEntity, m_paths[i]->origin - Vector (0, 0, nodeHalfHeight), m_paths[i]->origin + Vector (0, 0, nodeHalfHeight), nodeWidth + 1, 0, static_cast (nodeColor.x), static_cast (nodeColor.y), static_cast (nodeColor.z), 250, 0, 10); + game.drawLine (m_editor, m_paths[i]->origin - Vector (0, 0, nodeHalfHeight), m_paths[i]->origin + Vector (0, 0, nodeHalfHeight), nodeWidth + 1, 0, static_cast (nodeColor.x), static_cast (nodeColor.y), static_cast (nodeColor.z), 250, 0, 10); } // draw node with flags else { - engine.drawLine (g_hostEntity, m_paths[i]->origin - Vector (0, 0, nodeHalfHeight), m_paths[i]->origin - Vector (0, 0, nodeHalfHeight - nodeHeight * 0.75f), nodeWidth, 0, static_cast (nodeColor.x), static_cast (nodeColor.y), static_cast (nodeColor.z), 250, 0, 10); // draw basic path - engine.drawLine (g_hostEntity, m_paths[i]->origin - Vector (0, 0, nodeHalfHeight - nodeHeight * 0.75f), m_paths[i]->origin + Vector (0, 0, nodeHalfHeight), nodeWidth, 0, static_cast (nodeFlagColor.x), static_cast (nodeFlagColor.y), static_cast (nodeFlagColor.z), 250, 0, 10); // draw additional path + game.drawLine (m_editor, m_paths[i]->origin - Vector (0, 0, nodeHalfHeight), m_paths[i]->origin - Vector (0, 0, nodeHalfHeight - nodeHeight * 0.75f), nodeWidth, 0, static_cast (nodeColor.x), static_cast (nodeColor.y), static_cast (nodeColor.z), 250, 0, 10); // draw basic path + game.drawLine (m_editor, m_paths[i]->origin - Vector (0, 0, nodeHalfHeight - nodeHeight * 0.75f), m_paths[i]->origin + Vector (0, 0, nodeHalfHeight), nodeWidth, 0, static_cast (nodeFlagColor.x), static_cast (nodeFlagColor.y), static_cast (nodeFlagColor.z), 250, 0, 10); // draw additional path } - m_waypointDisplayTime[i] = engine.timebase (); + m_waypointDisplayTime[i] = game.timebase (); } } } @@ -2018,23 +2000,23 @@ void Waypoint::frame (void) { // draw arrow to a some importaint waypoints if (exists (m_findWPIndex) || exists (m_cacheWaypointIndex) || exists (m_facingAtIndex)) { // check for drawing code - if (m_arrowDisplayTime + 0.5f < engine.timebase ()) { + if (m_arrowDisplayTime + 0.5f < game.timebase ()) { // finding waypoint - pink arrow if (m_findWPIndex != INVALID_WAYPOINT_INDEX) { - engine.drawLine (g_hostEntity, g_hostEntity->v.origin, m_paths[m_findWPIndex]->origin, 10, 0, 128, 0, 128, 200, 0, 5, DRAW_ARROW); + game.drawLine (m_editor, m_editor->v.origin, m_paths[m_findWPIndex]->origin, 10, 0, 128, 0, 128, 200, 0, 5, DRAW_ARROW); } // cached waypoint - yellow arrow if (m_cacheWaypointIndex != INVALID_WAYPOINT_INDEX) { - engine.drawLine (g_hostEntity, g_hostEntity->v.origin, m_paths[m_cacheWaypointIndex]->origin, 10, 0, 255, 255, 0, 200, 0, 5, DRAW_ARROW); + game.drawLine (m_editor, m_editor->v.origin, m_paths[m_cacheWaypointIndex]->origin, 10, 0, 255, 255, 0, 200, 0, 5, DRAW_ARROW); } // waypoint user facing at - white arrow if (m_facingAtIndex != INVALID_WAYPOINT_INDEX) { - engine.drawLine (g_hostEntity, g_hostEntity->v.origin, m_paths[m_facingAtIndex]->origin, 10, 0, 255, 255, 255, 200, 0, 5, DRAW_ARROW); + game.drawLine (m_editor, m_editor->v.origin, m_paths[m_facingAtIndex]->origin, 10, 0, 255, 255, 255, 200, 0, 5, DRAW_ARROW); } - m_arrowDisplayTime = engine.timebase (); + m_arrowDisplayTime = game.timebase (); } } @@ -2042,8 +2024,8 @@ void Waypoint::frame (void) { Path *path = m_paths[nearestIndex]; // draw a paths, camplines and danger directions for nearest waypoint - if (nearestDistance <= 56.0f && m_pathDisplayTime <= engine.timebase ()) { - m_pathDisplayTime = engine.timebase () + 1.0f; + if (nearestDistance <= 56.0f && m_pathDisplayTime <= game.timebase ()) { + m_pathDisplayTime = game.timebase () + 1.0f; // draw the camplines if (path->flags & FLAG_CAMP) { @@ -2057,8 +2039,8 @@ void Waypoint::frame (void) { Vector campEndOrigin = Vector (path->campEndX, path->campEndY, campSourceOrigin.z); // camp end // draw it now - engine.drawLine (g_hostEntity, campSourceOrigin, campStartOrigin, 10, 0, 255, 0, 0, 200, 0, 10); - engine.drawLine (g_hostEntity, campSourceOrigin, campEndOrigin, 10, 0, 255, 0, 0, 200, 0, 10); + game.drawLine (m_editor, campSourceOrigin, campStartOrigin, 10, 0, 255, 0, 0, 200, 0, 10); + game.drawLine (m_editor, campSourceOrigin, campEndOrigin, 10, 0, 255, 0, 0, 200, 0, 10); } // draw the connections @@ -2068,20 +2050,20 @@ void Waypoint::frame (void) { } // jump connection if (path->connectionFlags[i] & PATHFLAG_JUMP) { - engine.drawLine (g_hostEntity, path->origin, m_paths[path->index[i]]->origin, 5, 0, 255, 0, 128, 200, 0, 10); + game.drawLine (m_editor, path->origin, m_paths[path->index[i]]->origin, 5, 0, 255, 0, 128, 200, 0, 10); } else if (isConnected (path->index[i], nearestIndex)) { // twoway connection - engine.drawLine (g_hostEntity, path->origin, m_paths[path->index[i]]->origin, 5, 0, 255, 255, 0, 200, 0, 10); + game.drawLine (m_editor, path->origin, m_paths[path->index[i]]->origin, 5, 0, 255, 255, 0, 200, 0, 10); } else { // oneway connection - engine.drawLine (g_hostEntity, path->origin, m_paths[path->index[i]]->origin, 5, 0, 250, 250, 250, 200, 0, 10); + game.drawLine (m_editor, path->origin, m_paths[path->index[i]]->origin, 5, 0, 250, 250, 250, 200, 0, 10); } } // now look for oneway incoming connections for (int i = 0; i < m_numWaypoints; i++) { if (isConnected (m_paths[i]->pathNumber, path->pathNumber) && !isConnected (path->pathNumber, m_paths[i]->pathNumber)) { - engine.drawLine (g_hostEntity, path->origin, m_paths[i]->origin, 5, 0, 0, 192, 96, 200, 0, 10); + game.drawLine (m_editor, path->origin, m_paths[i]->origin, 5, 0, 0, 192, 96, 200, 0, 10); } } @@ -2092,77 +2074,92 @@ void Waypoint::frame (void) { if (path->radius > 0.0f) { float sqr = cr::sqrtf (path->radius * path->radius * 0.5f); - engine.drawLine (g_hostEntity, origin + Vector (path->radius, 0.0f, 0.0f), origin + Vector (sqr, -sqr, 0.0f), 5, 0, 0, 0, 255, 200, 0, 10); - engine.drawLine (g_hostEntity, origin + Vector (sqr, -sqr, 0.0f), origin + Vector (0.0f, -path->radius, 0.0f), 5, 0, 0, 0, 255, 200, 0, 10); + game.drawLine (m_editor, origin + Vector (path->radius, 0.0f, 0.0f), origin + Vector (sqr, -sqr, 0.0f), 5, 0, 0, 0, 255, 200, 0, 10); + game.drawLine (m_editor, origin + Vector (sqr, -sqr, 0.0f), origin + Vector (0.0f, -path->radius, 0.0f), 5, 0, 0, 0, 255, 200, 0, 10); - engine.drawLine (g_hostEntity, origin + Vector (0.0f, -path->radius, 0.0f), origin + Vector (-sqr, -sqr, 0.0f), 5, 0, 0, 0, 255, 200, 0, 10); - engine.drawLine (g_hostEntity, origin + Vector (-sqr, -sqr, 0.0f), origin + Vector (-path->radius, 0.0f, 0.0f), 5, 0, 0, 0, 255, 200, 0, 10); + game.drawLine (m_editor, origin + Vector (0.0f, -path->radius, 0.0f), origin + Vector (-sqr, -sqr, 0.0f), 5, 0, 0, 0, 255, 200, 0, 10); + game.drawLine (m_editor, origin + Vector (-sqr, -sqr, 0.0f), origin + Vector (-path->radius, 0.0f, 0.0f), 5, 0, 0, 0, 255, 200, 0, 10); - engine.drawLine (g_hostEntity, origin + Vector (-path->radius, 0.0f, 0.0f), origin + Vector (-sqr, sqr, 0.0f), 5, 0, 0, 0, 255, 200, 0, 10); - engine.drawLine (g_hostEntity, origin + Vector (-sqr, sqr, 0.0f), origin + Vector (0.0f, path->radius, 0.0f), 5, 0, 0, 0, 255, 200, 0, 10); + game.drawLine (m_editor, origin + Vector (-path->radius, 0.0f, 0.0f), origin + Vector (-sqr, sqr, 0.0f), 5, 0, 0, 0, 255, 200, 0, 10); + game.drawLine (m_editor, origin + Vector (-sqr, sqr, 0.0f), origin + Vector (0.0f, path->radius, 0.0f), 5, 0, 0, 0, 255, 200, 0, 10); - engine.drawLine (g_hostEntity, origin + Vector (0.0f, path->radius, 0.0f), origin + Vector (sqr, sqr, 0.0f), 5, 0, 0, 0, 255, 200, 0, 10); - engine.drawLine (g_hostEntity, origin + Vector (sqr, sqr, 0.0f), origin + Vector (path->radius, 0.0f, 0.0f), 5, 0, 0, 0, 255, 200, 0, 10); + game.drawLine (m_editor, origin + Vector (0.0f, path->radius, 0.0f), origin + Vector (sqr, sqr, 0.0f), 5, 0, 0, 0, 255, 200, 0, 10); + game.drawLine (m_editor, origin + Vector (sqr, sqr, 0.0f), origin + Vector (path->radius, 0.0f, 0.0f), 5, 0, 0, 0, 255, 200, 0, 10); } else { float sqr = cr::sqrtf (32.0f); - engine.drawLine (g_hostEntity, origin + Vector (sqr, -sqr, 0.0f), origin + Vector (-sqr, sqr, 0.0f), 5, 0, 255, 0, 0, 200, 0, 10); - engine.drawLine (g_hostEntity, origin + Vector (-sqr, -sqr, 0.0f), origin + Vector (sqr, sqr, 0.0f), 5, 0, 255, 0, 0, 200, 0, 10); + game.drawLine (m_editor, origin + Vector (sqr, -sqr, 0.0f), origin + Vector (-sqr, sqr, 0.0f), 5, 0, 255, 0, 0, 200, 0, 10); + game.drawLine (m_editor, origin + Vector (-sqr, -sqr, 0.0f), origin + Vector (sqr, sqr, 0.0f), 5, 0, 255, 0, 0, 200, 0, 10); } // draw the danger directions if (!m_waypointsChanged) { - if ((g_experienceData + (nearestIndex * m_numWaypoints) + nearestIndex)->team0DangerIndex != INVALID_WAYPOINT_INDEX && engine.getTeam (g_hostEntity) == TEAM_TERRORIST) { - engine.drawLine (g_hostEntity, path->origin, m_paths[(g_experienceData + (nearestIndex * m_numWaypoints) + nearestIndex)->team0DangerIndex]->origin, 15, 0, 255, 0, 0, 200, 0, 10, DRAW_ARROW); // draw a red arrow to this index's danger point - } + int dangerIndex = getDangerIndex (game.getTeam (m_editor), nearestIndex, nearestIndex); - if ((g_experienceData + (nearestIndex * m_numWaypoints) + nearestIndex)->team1DangerIndex != INVALID_WAYPOINT_INDEX && engine.getTeam (g_hostEntity) == TEAM_COUNTER) { - engine.drawLine (g_hostEntity, path->origin, m_paths[(g_experienceData + (nearestIndex * m_numWaypoints) + nearestIndex)->team1DangerIndex]->origin, 15, 0, 0, 0, 255, 200, 0, 10, DRAW_ARROW); // draw a blue arrow to this index's danger point + if (exists (dangerIndex)) { + game.drawLine (m_editor, path->origin, m_paths[dangerIndex]->origin, 15, 0, 255, 0, 0, 200, 0, 10, DRAW_ARROW); // draw a red arrow to this index's danger point } } + + auto getFlagsAsStr = [&] (int index) { + Path *path = m_paths[index]; + + // if this path is null, return + if (path == nullptr) { + return "\0"; + } + bool jumpPoint = false; + + // iterate through connections and find, if it's a jump path + for (int i = 0; i < MAX_PATH_INDEX; i++) { + // check if we got a valid connection + if (path->index[i] != INVALID_WAYPOINT_INDEX && (path->connectionFlags[i] & PATHFLAG_JUMP)) { + jumpPoint = true; + } + } + + static String buffer; + buffer.assign ("%s%s%s%s%s%s%s%s%s%s%s%s%s%s", (path->flags == 0 && !jumpPoint) ? " (none)" : "", (path->flags &FLAG_LIFT) ? " LIFT" : "", (path->flags &FLAG_CROUCH) ? " CROUCH" : "", (path->flags &FLAG_CROSSING) ? " CROSSING" : "", (path->flags &FLAG_CAMP) ? " CAMP" : "", (path->flags &FLAG_TF_ONLY) ? " TERRORIST" : "", (path->flags &FLAG_CF_ONLY) ? " CT" : "", (path->flags &FLAG_SNIPER) ? " SNIPER" : "", (path->flags &FLAG_GOAL) ? " GOAL" : "", (path->flags &FLAG_LADDER) ? " LADDER" : "", (path->flags &FLAG_RESCUE) ? " RESCUE" : "", (path->flags &FLAG_DOUBLEJUMP) ? " JUMPHELP" : "", (path->flags &FLAG_NOHOSTAGE) ? " NOHOSTAGE" : "", jumpPoint ? " JUMP" : ""); + + // return the message buffer + return buffer.chars (); + }; + // display some information - char tempMessage[4096]; + String waypointMessage; // show the information about that point - int length = sprintf (tempMessage, - "\n\n\n\n Waypoint Information:\n\n" - " Waypoint %d of %d, Radius: %.1f\n" - " Flags: %s\n\n", - nearestIndex, m_numWaypoints, path->radius, getInformation (nearestIndex)); + waypointMessage.assign ("\n\n\n\n Waypoint Information:\n\n" + " Waypoint %d of %d, Radius: %.1f\n" + " Flags: %s\n\n", nearestIndex, m_numWaypoints, path->radius, getFlagsAsStr (nearestIndex)); // if waypoint is not changed display experience also if (!m_waypointsChanged) { - int dangerIndexCT = (g_experienceData + nearestIndex * m_numWaypoints + nearestIndex)->team1DangerIndex; - int dangerIndexT = (g_experienceData + nearestIndex * m_numWaypoints + nearestIndex)->team0DangerIndex; + int dangerIndexCT = getDangerIndex (TEAM_COUNTER, nearestIndex, nearestIndex); + int dangerIndexT = getDangerIndex (TEAM_TERRORIST, nearestIndex, nearestIndex); - length += sprintf (&tempMessage[length], - " Experience Info:\n" - " CT: %d / %d\n" - " T: %d / %d\n", - dangerIndexCT, dangerIndexCT != INVALID_WAYPOINT_INDEX ? (g_experienceData + nearestIndex * m_numWaypoints + dangerIndexCT)->team1Damage : 0, dangerIndexT, dangerIndexT != INVALID_WAYPOINT_INDEX ? (g_experienceData + nearestIndex * m_numWaypoints + dangerIndexT)->team0Damage : 0); + waypointMessage.append (" Experience Info:\n" + " CT: %d / %d dmg\n" + " T: %d / %d dmg\n", dangerIndexCT, dangerIndexCT != INVALID_WAYPOINT_INDEX ? getDangerDamage (TEAM_COUNTER, nearestIndex, dangerIndexCT) : 0, dangerIndexT, dangerIndexT != INVALID_WAYPOINT_INDEX ? getDangerDamage (TEAM_TERRORIST, nearestIndex, dangerIndexT) : 0); } // check if we need to show the cached point index if (m_cacheWaypointIndex != INVALID_WAYPOINT_INDEX) { - length += sprintf (&tempMessage[length], - "\n Cached Waypoint Information:\n\n" - " Waypoint %d of %d, Radius: %.1f\n" - " Flags: %s\n", - m_cacheWaypointIndex, m_numWaypoints, m_paths[m_cacheWaypointIndex]->radius, getInformation (m_cacheWaypointIndex)); + waypointMessage.append ("\n Cached Waypoint Information:\n\n" + " Waypoint %d of %d, Radius: %.1f\n" + " Flags: %s\n", m_cacheWaypointIndex, m_numWaypoints, m_paths[m_cacheWaypointIndex]->radius, getFlagsAsStr (m_cacheWaypointIndex)); } // check if we need to show the facing point index if (m_facingAtIndex != INVALID_WAYPOINT_INDEX) { - length += sprintf (&tempMessage[length], - "\n Facing Waypoint Information:\n\n" - " Waypoint %d of %d, Radius: %.1f\n" - " Flags: %s\n", - m_facingAtIndex, m_numWaypoints, m_paths[m_facingAtIndex]->radius, getInformation (m_facingAtIndex)); + waypointMessage.append ("\n Facing Waypoint Information:\n\n" + " Waypoint %d of %d, Radius: %.1f\n" + " Flags: %s\n", m_facingAtIndex, m_numWaypoints, m_paths[m_facingAtIndex]->radius, getFlagsAsStr (m_facingAtIndex)); } // draw entire message - MessageWriter (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, Vector::null (), g_hostEntity) + MessageWriter (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, Vector::null (), m_editor) .writeByte (TE_TEXTMESSAGE) .writeByte (4) // channel .writeShort (MessageWriter::fs16 (0, 1 << 13)) // x @@ -2179,7 +2176,7 @@ void Waypoint::frame (void) { .writeShort (0) // fadeintime .writeShort (0) // fadeouttime .writeShort (MessageWriter::fu16 (1.1f, 1 << 8)) // holdtime - .writeString (tempMessage); + .writeString (waypointMessage.chars ()); } } @@ -2188,8 +2185,8 @@ bool Waypoint::isConnected (int index) { if (i == index) { continue; } - for (int j = 0; j < MAX_PATH_INDEX; j++) { - if (m_paths[i]->index[j] == index) { + for (auto &test : m_paths[i]->index) { + if (test == index) { return true; } } @@ -2210,7 +2207,7 @@ bool Waypoint::checkNodes (void) { for (j = 0; j < MAX_PATH_INDEX; j++) { if (m_paths[i]->index[j] != INVALID_WAYPOINT_INDEX) { if (m_paths[i]->index[j] > m_numWaypoints) { - logEntry (true, LL_WARNING, "Waypoint %d connected with invalid Waypoint #%d!", i, m_paths[i]->index[j]); + util.logEntry (true, LL_WARNING, "Waypoint %d connected with invalid Waypoint #%d!", i, m_paths[i]->index[j]); return false; } connections++; @@ -2220,19 +2217,19 @@ bool Waypoint::checkNodes (void) { if (connections == 0) { if (!isConnected (i)) { - logEntry (true, LL_WARNING, "Waypoint %d isn't connected with any other Waypoint!", i); + util.logEntry (true, LL_WARNING, "Waypoint %d isn't connected with any other Waypoint!", i); return false; } } if (m_paths[i]->pathNumber != i) { - logEntry (true, LL_WARNING, "Waypoint %d pathnumber differs from index!", i); + util.logEntry (true, LL_WARNING, "Waypoint %d pathnumber differs from index!", i); return false; } if (m_paths[i]->flags & FLAG_CAMP) { if (m_paths[i]->campEndX == 0.0f && m_paths[i]->campEndY == 0.0f) { - logEntry (true, LL_WARNING, "Waypoint %d Camp-Endposition not set!", i); + util.logEntry (true, LL_WARNING, "Waypoint %d Camp-Endposition not set!", i); return false; } } @@ -2252,45 +2249,40 @@ bool Waypoint::checkNodes (void) { for (int k = 0; k < MAX_PATH_INDEX; k++) { if (m_paths[i]->index[k] != INVALID_WAYPOINT_INDEX) { if (!exists (m_paths[i]->index[k])) { - logEntry (true, LL_WARNING, "Waypoint %d - Pathindex %d out of Range!", i, k); - g_engfuncs.pfnSetOrigin (g_hostEntity, m_paths[i]->origin); - - g_waypointOn = true; - g_editNoclip = true; + util.logEntry (true, LL_WARNING, "Waypoint %d - Pathindex %d out of Range!", i, k); + engfuncs.pfnSetOrigin (m_editor, m_paths[i]->origin); + setEditFlag (WS_EDIT_ENABLED | WS_EDIT_NOCLIP); return false; } else if (m_paths[i]->index[k] == i) { - logEntry (true, LL_WARNING, "Waypoint %d - Pathindex %d points to itself!", i, k); + util.logEntry (true, LL_WARNING, "Waypoint %d - Pathindex %d points to itself!", i, k); - if (g_waypointOn && !engine.isDedicated ()) { - g_engfuncs.pfnSetOrigin (g_hostEntity, m_paths[i]->origin); + engfuncs.pfnSetOrigin (m_editor, m_paths[i]->origin); + setEditFlag (WS_EDIT_ENABLED | WS_EDIT_NOCLIP); - g_waypointOn = true; - g_editNoclip = true; - } return false; } } } } - if (g_mapFlags & MAP_CS) { + if (game.mapIs (MAP_CS)) { if (rescuePoints == 0) { - logEntry (true, LL_WARNING, "You didn't set a Rescue Point!"); + util.logEntry (true, LL_WARNING, "You didn't set a Rescue Point!"); return false; } } if (terrPoints == 0) { - logEntry (true, LL_WARNING, "You didn't set any Terrorist Important Point!"); + util.logEntry (true, LL_WARNING, "You didn't set any Terrorist Important Point!"); return false; } else if (ctPoints == 0) { - logEntry (true, LL_WARNING, "You didn't set any CT Important Point!"); + util.logEntry (true, LL_WARNING, "You didn't set any CT Important Point!"); return false; } else if (goalPoints == 0) { - logEntry (true, LL_WARNING, "You didn't set any Goal Point!"); + util.logEntry (true, LL_WARNING, "You didn't set any Goal Point!"); return false; } @@ -2325,14 +2317,11 @@ bool Waypoint::checkNodes (void) { for (i = 0; i < m_numWaypoints; i++) { if (!visited[i]) { - logEntry (true, LL_WARNING, "Path broken from Waypoint #0 to Waypoint #%d!", i); + util.logEntry (true, LL_WARNING, "Path broken from Waypoint #0 to Waypoint #%d!", i); - if (g_waypointOn && !engine.isDedicated ()) { - g_engfuncs.pfnSetOrigin (g_hostEntity, m_paths[i]->origin); - - g_waypointOn = true; - g_editNoclip = true; - } + engfuncs.pfnSetOrigin (m_editor, m_paths[i]->origin); + setEditFlag (WS_EDIT_ENABLED | WS_EDIT_NOCLIP); + return false; } } @@ -2373,14 +2362,11 @@ bool Waypoint::checkNodes (void) { for (i = 0; i < m_numWaypoints; i++) { if (!visited[i]) { - logEntry (true, LL_WARNING, "Path broken from Waypoint #%d to Waypoint #0!", i); + util.logEntry (true, LL_WARNING, "Path broken from Waypoint #%d to Waypoint #0!", i); - if (g_waypointOn && !engine.isDedicated ()) { - g_engfuncs.pfnSetOrigin (g_hostEntity, m_paths[i]->origin); + engfuncs.pfnSetOrigin (m_editor, m_paths[i]->origin); + setEditFlag (WS_EDIT_ENABLED | WS_EDIT_NOCLIP); - g_waypointOn = true; - g_editNoclip = true; - } return false; } } @@ -2388,47 +2374,43 @@ bool Waypoint::checkNodes (void) { } void Waypoint::initPathMatrix (void) { - int i, j, k; + delete[] m_matrix; + m_matrix = nullptr; - delete[] m_distMatrix; - delete[] m_pathMatrix; - - m_distMatrix = nullptr; - m_pathMatrix = nullptr; - - m_distMatrix = new int[m_numWaypoints * m_numWaypoints]; - m_pathMatrix = new int[m_numWaypoints * m_numWaypoints]; + m_matrix = new FloydMatrix[m_numWaypoints * m_numWaypoints + FastLZ::EXCESS]; if (loadPathMatrix ()) { return; // matrix loaded from file } + const int points = m_numWaypoints; - for (i = 0; i < m_numWaypoints; i++) { - for (j = 0; j < m_numWaypoints; j++) { - *(m_distMatrix + i * m_numWaypoints + j) = 999999; - *(m_pathMatrix + i * m_numWaypoints + j) = INVALID_WAYPOINT_INDEX; + for (int i = 0; i < points; i++) { + for (int j = 0; j < points; j++) { + (m_matrix + i * points + j)->dist = 999999; + (m_matrix + i * points + j)->index = INVALID_WAYPOINT_INDEX; } } - for (i = 0; i < m_numWaypoints; i++) { - for (j = 0; j < MAX_PATH_INDEX; j++) { - if (m_paths[i]->index[j] >= 0 && m_paths[i]->index[j] < m_numWaypoints) { - *(m_distMatrix + (i * m_numWaypoints) + m_paths[i]->index[j]) = m_paths[i]->distances[j]; - *(m_pathMatrix + (i * m_numWaypoints) + m_paths[i]->index[j]) = m_paths[i]->index[j]; + for (int i = 0; i < points; i++) { + for (int j = 0; j < MAX_PATH_INDEX; j++) { + if (!exists (m_paths[i]->index[j])) { + continue; } + (m_matrix + (i * points) + m_paths[i]->index[j])->dist = m_paths[i]->distances[j]; + (m_matrix + (i * points) + m_paths[i]->index[j])->index = m_paths[i]->index[j]; } } - for (i = 0; i < m_numWaypoints; i++) { - *(m_distMatrix + (i * m_numWaypoints) + i) = 0; + for (int i = 0; i < points; i++) { + (m_matrix + (i * points) + i)->dist = 0; } - for (k = 0; k < m_numWaypoints; k++) { - for (i = 0; i < m_numWaypoints; i++) { - for (j = 0; j < m_numWaypoints; j++) { - if (*(m_distMatrix + (i * m_numWaypoints) + k) + *(m_distMatrix + (k * m_numWaypoints) + j) < (*(m_distMatrix + (i * m_numWaypoints) + j))) { - *(m_distMatrix + (i * m_numWaypoints) + j) = *(m_distMatrix + (i * m_numWaypoints) + k) + *(m_distMatrix + (k * m_numWaypoints) + j); - *(m_pathMatrix + (i * m_numWaypoints) + j) = *(m_pathMatrix + (i * m_numWaypoints) + k); + for (int k = 0; k < points; k++) { + for (int i = 0; i < points; i++) { + for (int j = 0; j < points; j++) { + if ((m_matrix + (i * points) + k)->dist + (m_matrix + (k * points) + j)->dist < (m_matrix + (i * points) + j)->dist) { + (m_matrix + (i * points) + j)->dist = (m_matrix + (i * points) + k)->dist + (m_matrix + (k * points) + j)->dist; + (m_matrix + (i * points) + j)->index = (m_matrix + (i * points) + k)->index; } } } @@ -2438,89 +2420,20 @@ void Waypoint::initPathMatrix (void) { savePathMatrix (); } -void Waypoint::savePathMatrix (void) { - if (m_numWaypoints < 1 || m_waypointsChanged) { - return; - } - - File fp (format ("%slearned/%s.pmt", getDataDirectory (), engine.getMapName ()), "wb"); - - // unable to open file - if (!fp.isValid ()) { - logEntry (false, LL_FATAL, "Failed to open file for writing"); - return; - } - ExtensionHeader header; - - memset (header.header, 0, sizeof (header.header)); - strcpy (header.header, FH_MATRIX); - - header.fileVersion = FV_MATRIX; - header.pointNumber = m_numWaypoints; - - // write header info - fp.write (&header, sizeof (ExtensionHeader)); - - // write path & distance matrix - fp.write (m_pathMatrix, sizeof (int), m_numWaypoints * m_numWaypoints); - fp.write (m_distMatrix, sizeof (int), m_numWaypoints * m_numWaypoints); - - // and close the file - fp.close (); -} - -bool Waypoint::loadPathMatrix (void) { - File fp (format ("%slearned/%s.pmt", getDataDirectory (), engine.getMapName ()), "rb"); - - // file doesn't exists return false - if (!fp.isValid ()) { - return false; - } - - ExtensionHeader header; - memset (&header, 0, sizeof (header)); - - // read number of waypoints - if (fp.read (&header, sizeof (ExtensionHeader)) == 0) { - fp.close (); - return false; - } - - if (header.pointNumber != m_numWaypoints || header.fileVersion != FV_MATRIX) { - logEntry (true, LL_WARNING, "Pathmatrix damaged (wrong version, or not for this map). Pathmatrix will be rebuilt."); - fp.close (); - - return false; - } - - // read path & distance matrixes - if (fp.read (m_pathMatrix, sizeof (int), m_numWaypoints * m_numWaypoints) == 0) { - fp.close (); - return false; - } - - if (fp.read (m_distMatrix, sizeof (int), m_numWaypoints * m_numWaypoints) == 0) { - fp.close (); - return false; - } - fp.close (); // and close the file - - return true; -} - int Waypoint::getPathDist (int srcIndex, int destIndex) { if (!exists (srcIndex) || !exists (destIndex)) { return 1; } - return *(m_distMatrix + (srcIndex * m_numWaypoints) + destIndex); + return (m_matrix + (srcIndex * m_numWaypoints) + destIndex)->dist; } void Waypoint::setVisited (int index) { if (!exists (index)) { return; } - if (!isVisited (index) && (m_paths[index]->flags & FLAG_GOAL)) + if (!isVisited (index) && (m_paths[index]->flags & FLAG_GOAL)) { m_visitedGoals.push (index); + } } void Waypoint::clearVisited (void) { @@ -2542,7 +2455,7 @@ void Waypoint::addBasic (void) { edict_t *ent = nullptr; // first of all, if map contains ladder points, create it - while (!engine.isNullEntity (ent = g_engfuncs.pfnFindEntityByString (ent, "classname", "func_ladder"))) { + while (!game.isNullEntity (ent = engfuncs.pfnFindEntityByString (ent, "classname", "func_ladder"))) { Vector ladderLeft = ent->v.absmin; Vector ladderRight = ent->v.absmax; ladderLeft.z = ladderRight.z; @@ -2551,7 +2464,7 @@ void Waypoint::addBasic (void) { Vector up, down, front, back; Vector diff = ((ladderLeft - ladderRight) ^ Vector (0.0f, 0.0f, 0.0f)).normalize () * 15.0f; - front = back = engine.getAbsPos (ent); + front = back = game.getAbsPos (ent); front = front + diff; // front back = back - diff; // back @@ -2559,14 +2472,14 @@ void Waypoint::addBasic (void) { up = down = front; down.z = ent->v.absmax.z; - engine.testHull (down, up, TRACE_IGNORE_MONSTERS, point_hull, nullptr, &tr); + game.testHull (down, up, TRACE_IGNORE_MONSTERS, point_hull, nullptr, &tr); - if (g_engfuncs.pfnPointContents (up) == CONTENTS_SOLID || tr.flFraction != 1.0f) { + if (engfuncs.pfnPointContents (up) == CONTENTS_SOLID || tr.flFraction != 1.0f) { up = down = back; down.z = ent->v.absmax.z; } - engine.testHull (down, up - Vector (0.0f, 0.0f, 1000.0f), TRACE_IGNORE_MONSTERS, point_hull, nullptr, &tr); + game.testHull (down, up - Vector (0.0f, 0.0f, 1000.0f), TRACE_IGNORE_MONSTERS, point_hull, nullptr, &tr); up = tr.vecEndPos; Vector point = up + Vector (0.0f, 0.0f, 39.0f); @@ -2590,8 +2503,8 @@ void Waypoint::addBasic (void) { auto autoCreateForEntity = [](int type, const char *entity) { edict_t *ent = nullptr; - while (!engine.isNullEntity (ent = g_engfuncs.pfnFindEntityByString (ent, "classname", entity))) { - const Vector &pos = engine.getAbsPos (ent); + while (!game.isNullEntity (ent = engfuncs.pfnFindEntityByString (ent, "classname", entity))) { + const Vector &pos = game.getAbsPos (ent); if (waypoints.getNearestNoBuckets (pos, 50.0f) == INVALID_WAYPOINT_INDEX) { waypoints.push (type, pos); @@ -2618,23 +2531,23 @@ void Waypoint::eraseFromDisk (void) { // this function removes waypoint file from the hard disk StringArray forErase; - const char *map = engine.getMapName (); + const char *map = game.getMapName (); bots.kickEveryone (true); // if we're delete waypoint, delete all corresponding to it files - forErase.push (format ("%s%s.pwf", getDataDirectory (), map)); // waypoint itself - forErase.push (format ("%slearned/%s.exp", getDataDirectory (), map)); // corresponding to waypoint experience - forErase.push (format ("%slearned/%s.vis", getDataDirectory (), map)); // corresponding to waypoint vistable - forErase.push (format ("%slearned/%s.pmt", getDataDirectory (), map)); // corresponding to waypoint path matrix + forErase.push (util.format ("%s%s.pwf", getDataDirectory (), map)); // waypoint itself + forErase.push (util.format ("%slearned/%s.exp", getDataDirectory (), map)); // corresponding to waypoint experience + forErase.push (util.format ("%slearned/%s.vis", getDataDirectory (), map)); // corresponding to waypoint vistable + forErase.push (util.format ("%slearned/%s.pmt", getDataDirectory (), map)); // corresponding to waypoint path matrix for (auto &item : forErase) { if (File::exists (const_cast (item.chars ()))) { _unlink (item.chars ()); - logEntry (true, LL_DEFAULT, "File %s, has been deleted from the hard disk", item.chars ()); + util.logEntry (true, LL_DEFAULT, "File %s, has been deleted from the hard disk", item.chars ()); } else { - logEntry (true, LL_ERROR, "Unable to open %s", item.chars ()); + util.logEntry (true, LL_ERROR, "Unable to open %s", item.chars ()); } } init (); // reintialize points @@ -2647,7 +2560,7 @@ const char *Waypoint::getDataDirectory (bool isMemoryFile) { buffer.assign ("addons/yapb/data/"); } else { - buffer.format ("%s/addons/yapb/data/", engine.getModName ()); + buffer.assign ("%s/addons/yapb/data/", game.getModName ()); } return buffer.chars (); } @@ -2657,7 +2570,7 @@ void Waypoint::setBombPos (bool reset, const Vector &pos) { if (reset) { m_bombPos.nullify (); - g_bombPlanted = false; + bots.setBombPlanted (false); return; } @@ -2668,9 +2581,9 @@ void Waypoint::setBombPos (bool reset, const Vector &pos) { } edict_t *ent = nullptr; - while (!engine.isNullEntity (ent = g_engfuncs.pfnFindEntityByString (ent, "classname", "grenade"))) { + while (!game.isNullEntity (ent = engfuncs.pfnFindEntityByString (ent, "classname", "grenade"))) { if (strcmp (STRING (ent->v.model) + 9, "c4.mdl") == 0) { - m_bombPos = engine.getAbsPos (ent); + m_bombPos = game.getAbsPos (ent); break; } } @@ -2684,7 +2597,7 @@ void Waypoint::setSearchIndex (int index) { m_findWPIndex = index; if (exists (m_findWPIndex)) { - engine.print ("Showing Direction to Waypoint #%d", m_findWPIndex); + ctrl.msg ("Showing Direction to Waypoint #%d", m_findWPIndex); } else { m_findWPIndex = INVALID_WAYPOINT_INDEX; @@ -2697,11 +2610,10 @@ Waypoint::Waypoint (void) { memset (m_visLUT, 0, sizeof (m_visLUT)); memset (m_waypointDisplayTime, 0, sizeof (m_waypointDisplayTime)); memset (m_waypointLightLevel, 0, sizeof (m_waypointLightLevel)); - memset (m_infoBuffer, 0, sizeof (m_infoBuffer)); m_waypointPaths = false; m_endJumpPoint = false; - m_redoneVisibility = false; + m_needsVisRebuild = false; m_learnJumpWaypoint = false; m_waypointsChanged = false; m_timeJumpStarted = 0.0f; @@ -2722,26 +2634,25 @@ Waypoint::Waypoint (void) { m_rescuePoints.clear (); m_sniperPoints.clear (); - m_distMatrix = nullptr; - m_pathMatrix = nullptr; + m_matrix = nullptr; + m_editor = nullptr; - for (int i = 0; i < MAX_WAYPOINTS; i++) { - m_paths[i] = nullptr; + for (auto &path : m_paths) { + path = nullptr; } } Waypoint::~Waypoint (void) { + + // free floyd warshall + delete[] m_matrix; + m_matrix = nullptr; + + // free experience stuff + delete[] m_experience; + m_experience = nullptr; + cleanupPathMemory (); - - delete[] m_distMatrix; - delete[] m_pathMatrix; - - m_distMatrix = nullptr; - m_pathMatrix = nullptr; - - for (int i = 0; i < MAX_WAYPOINTS; i++) { - m_paths[i] = nullptr; - } } void Waypoint::closeSocket (int sock) { @@ -2785,13 +2696,13 @@ WaypointDownloadError Waypoint::downloadWaypoint (void) { timeout.tv_sec = 5; timeout.tv_usec = 0; - int result = setsockopt (socketHandle, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof (timeout)); + int result = setsockopt (socketHandle, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast (&timeout), sizeof (timeout)); if (result < 0) { closeSocket (socketHandle); return WDE_SOCKET_ERROR; } - result = setsockopt (socketHandle, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, sizeof (timeout)); + result = setsockopt (socketHandle, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast (&timeout), sizeof (timeout)); if (result < 0) { closeSocket (socketHandle); @@ -2801,15 +2712,15 @@ WaypointDownloadError Waypoint::downloadWaypoint (void) { dest.sin_family = AF_INET; dest.sin_port = htons (80); - dest.sin_addr.s_addr = inet_addr (inet_ntoa (*((struct in_addr *)host->h_addr))); + dest.sin_addr.s_addr = inet_addr (inet_ntoa (*(reinterpret_cast (host->h_addr)))); - if (connect (socketHandle, (struct sockaddr *)&dest, (int)sizeof (dest)) == -1) { + if (connect (socketHandle, reinterpret_cast (&dest), static_cast (sizeof (dest))) == -1) { closeSocket (socketHandle); return WDE_CONNECT_ERROR; } String request; - request.format ("GET /wpdb/%s.pwf HTTP/1.0\r\nAccept: */*\r\nUser-Agent: %s/%s\r\nHost: %s\r\n\r\n", engine.getMapName (), PRODUCT_SHORT_NAME, PRODUCT_VERSION, yb_waypoint_autodl_host.str ()); + request.assign ("GET /wpdb/%s.pwf HTTP/1.0\r\nAccept: */*\r\nUser-Agent: %s/%s\r\nHost: %s\r\n\r\n", game.getMapName (), PRODUCT_SHORT_NAME, PRODUCT_VERSION, yb_waypoint_autodl_host.str ()); if (send (socketHandle, request.chars (), static_cast (request.length () + 1), 0) < 1) { closeSocket (socketHandle); @@ -2921,3 +2832,96 @@ IntArray &Waypoint::getWaypointsInBucket (const Vector &pos) { const Bucket &bucket = locateBucket (pos); return m_buckets[bucket.x][bucket.y][bucket.z]; } + +void Waypoint::updateGlobalExperience (void) { + // this function called after each end of the round to update knowledge about most dangerous waypoints for each team. + + // no waypoints, no experience used or waypoints edited or being edited? + if (m_numWaypoints < 1 || m_waypointsChanged) { + return; // no action + } + bool adjustValues = false; + + // get the most dangerous waypoint for this position for both teams + for (int team = TEAM_TERRORIST; team < MAX_TEAM_COUNT; team++) { + int bestIndex = INVALID_WAYPOINT_INDEX; // best index to store + int maxDamage = 0; + + for (int i = 0; i < waypoints.length (); i++) { + maxDamage = 0; + bestIndex = INVALID_WAYPOINT_INDEX; + + for (int j = 0; j < waypoints.length (); j++) { + if (i == j) { + continue; + } + int actDamage = getDangerDamage (team, i, j); + + if (actDamage > maxDamage) { + maxDamage = actDamage; + bestIndex = j; + } + } + + if (maxDamage > MAX_DAMAGE_VALUE) { + adjustValues = true; + } + (m_experience + (i * m_numWaypoints) + i)->index[team] = bestIndex; + } + } + constexpr int HALF_DAMAGE_VALUE = static_cast (MAX_DAMAGE_VALUE * 0.5); + + // adjust values if overflow is about to happen + if (adjustValues) { + for (int team = TEAM_TERRORIST; team < MAX_TEAM_COUNT; team++) { + for (int i = 0; i < m_numWaypoints; i++) { + for (int j = 0; j < m_numWaypoints; j++) { + if (i == j) { + continue; + } + (m_experience + (i * m_numWaypoints) + j)->damage[team] = cr::clamp (getDangerDamage (team, i, j) - HALF_DAMAGE_VALUE, 0, MAX_DAMAGE_VALUE); + } + } + } + } + + for (int team = TEAM_TERRORIST; team < MAX_TEAM_COUNT; team++) { + m_highestDamage[team] = cr::clamp (m_highestDamage [team] - HALF_DAMAGE_VALUE, 1, MAX_DAMAGE_VALUE); + } +} + +int Waypoint::getDangerIndex (int team, int start, int goal) { + if (team != TEAM_TERRORIST && team != TEAM_COUNTER) { + return INVALID_WAYPOINT_INDEX; + } + + // realiablity check + if (!exists (start) || !exists (goal)) { + return INVALID_WAYPOINT_INDEX; + } + return (m_experience + (start * m_numWaypoints) + goal)->index[team]; +} + +int Waypoint::getDangerValue (int team, int start, int goal) { + if (team != TEAM_TERRORIST && team != TEAM_COUNTER) { + return 0; + } + + // reliability check + if (!exists (start) || !exists (goal)) { + return 0; + } + return (m_experience + (start * m_numWaypoints) + goal)->value[team]; +} + +int Waypoint::getDangerDamage (int team, int start, int goal) { + if (team != TEAM_TERRORIST && team != TEAM_COUNTER) { + return 0; + } + + // reliability check + if (!exists (start) || !exists (goal)) { + return 0; + } + return (m_experience + (start * m_numWaypoints) + goal)->damage[team]; +} \ No newline at end of file