From 1bc1fd1913645bbafe6aae99788136a33703b43a Mon Sep 17 00:00:00 2001 From: Dmitry Date: Sat, 27 Jul 2019 17:36:24 +0300 Subject: [PATCH] savepoint, changelog later.. --- LICENSE.txt | 2 +- include/corelib.h | 1936 ---------------- include/crlib/cr-alloc.h | 78 + include/crlib/cr-array.h | 390 ++++ include/crlib/cr-basic.h | 128 ++ include/crlib/cr-binheap.h | 141 ++ include/crlib/cr-color.h | 41 + include/crlib/cr-complete.h | 40 + include/crlib/cr-dict.h | 237 ++ include/crlib/cr-files.h | 334 +++ include/crlib/cr-http.h | 425 ++++ include/crlib/cr-lambda.h | 173 ++ include/crlib/cr-library.h | 89 + include/crlib/cr-logger.h | 98 + include/crlib/cr-math.h | 179 ++ include/crlib/cr-movable.h | 58 + include/crlib/cr-platform.h | 172 ++ include/crlib/cr-random.h | 66 + include/crlib/cr-string.h | 870 +++++++ include/crlib/cr-twin.h | 75 + include/{compress.h => crlib/cr-ulz.h} | 161 +- include/crlib/cr-uniqueptr.h | 99 + include/crlib/cr-vector.h | 232 ++ include/engine.h | 364 +-- include/engine/eiface.h | 73 +- include/engine/extdll.h | 4 +- include/engine/util.h | 22 +- include/platform.h | 97 - include/resource.h | 4 +- include/yapb.h | 2012 ++++++++-------- project/makefile | 35 +- project/yapb.vcxproj | 31 +- project/yapb.vcxproj.filters | 86 +- source/Android.mk | 6 +- source/basecode.cpp | 2524 ++++++++++---------- source/chatlib.cpp | 253 +- source/combat.cpp | 453 ++-- source/control.cpp | 1205 +++++----- source/engine.cpp | 796 +++---- source/graph.cpp | 2940 ++++++++++++++++++++++++ source/interface.cpp | 388 ++-- source/manager.cpp | 1801 +++++++-------- source/navigate.cpp | 1362 ++++++----- source/support.cpp | 439 ++-- source/waypoint.cpp | 2928 ----------------------- 45 files changed, 12866 insertions(+), 10981 deletions(-) delete mode 100644 include/corelib.h create mode 100644 include/crlib/cr-alloc.h create mode 100644 include/crlib/cr-array.h create mode 100644 include/crlib/cr-basic.h create mode 100644 include/crlib/cr-binheap.h create mode 100644 include/crlib/cr-color.h create mode 100644 include/crlib/cr-complete.h create mode 100644 include/crlib/cr-dict.h create mode 100644 include/crlib/cr-files.h create mode 100644 include/crlib/cr-http.h create mode 100644 include/crlib/cr-lambda.h create mode 100644 include/crlib/cr-library.h create mode 100644 include/crlib/cr-logger.h create mode 100644 include/crlib/cr-math.h create mode 100644 include/crlib/cr-movable.h create mode 100644 include/crlib/cr-platform.h create mode 100644 include/crlib/cr-random.h create mode 100644 include/crlib/cr-string.h create mode 100644 include/crlib/cr-twin.h rename include/{compress.h => crlib/cr-ulz.h} (58%) create mode 100644 include/crlib/cr-uniqueptr.h create mode 100644 include/crlib/cr-vector.h delete mode 100644 include/platform.h create mode 100644 source/graph.cpp delete mode 100644 source/waypoint.cpp diff --git a/LICENSE.txt b/LICENSE.txt index d574d08..e0921fc 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (c) 2003-2018, YaPB Dev Team +Copyright (c) 2003-2019, YaPB Development Team Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/include/corelib.h b/include/corelib.h deleted file mode 100644 index a1a9a92..0000000 --- a/include/corelib.h +++ /dev/null @@ -1,1936 +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 - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#ifdef PLATFORM_WIN32 - #include -#else - #include -#endif - -#ifndef PLATFORM_WIN32 - #define _unlink unlink - #define _mkdir(p) mkdir (p, 0777) - #define stricmp strcasecmp -#else - #define stricmp _stricmp -#endif - -#ifdef PLATFORM_HAS_SSE2 - #include -#endif - -#undef min -#undef max - -// to remove ugly A_ prefixes -namespace cr { - -namespace types { - -using int8 = signed char; -using int16 = signed short; -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; - -constexpr float ONEPSILON = 0.01f; -constexpr float EQEPSILON = 0.001f; -constexpr float FLEPSILON = 1.192092896e-07f; - -constexpr float PI = 3.141592653589793115997963468544185161590576171875f; -constexpr float PI_RECIPROCAL = 1.0f / PI; -constexpr float PI_HALF = PI / 2; - -constexpr float D2R = PI / 180.0f; -constexpr float R2D = 180.0f / PI; - -// from metamod-p -static inline bool checkptr (void *ptr) { -#ifdef PLATFORM_WIN32 - if (IsBadCodePtr (reinterpret_cast (ptr))) { - return false; - } -#else - (void) (ptr); -#endif - return true; -} - -template constexpr size_t bufsize (const T (&)[N]) { - return N - 1; -} - -template constexpr size_t arrsize (const T (&)[N]) { - return N; -} - -constexpr float square (const float value) { - return value * value; -} - -template constexpr T min (const T a, const T b) { - return a < b ? a : b; -} - -template constexpr T max (const T a, const T b) { - return a > b ? a : b; -} - -template constexpr T clamp (const T x, const T a, const T b) { - return min (max (x, a), b); -} - -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; - int x; - } res { x }; - - res.x = static_cast (y * (res.x - 1064866805) + 1064866805); - return res.d; -} - -static inline float sqrtf (const float value) { - return powf (value, 0.5f); -} - -static inline float sinf (const float value) { - const auto sign = static_cast (value * PI_RECIPROCAL); - const float calc = (value - static_cast (sign) * PI); - - const float sqr = square (calc); - const float res = 1.00000000000000000000e+00f + sqr * (-1.66666671633720397949e-01f + sqr * (8.33333376795053482056e-03f + sqr * (-1.98412497411482036114e-04f + - sqr * (2.75565571428160183132e-06f + sqr * (-2.50368472620721149724e-08f + sqr * (1.58849267073435385100e-10f + sqr * -6.58925550841432672300e-13f)))))); - - return (sign & 1) ? -calc * res : value * res; -} - -static inline float cosf (const float value) { - const auto sign = static_cast (value * PI_RECIPROCAL); - const float calc = (value - static_cast (sign) * PI); - - const float sqr = square (calc); - const float res = sqr * (-5.00000000000000000000e-01f + sqr * (4.16666641831398010254e-02f + sqr * (-1.38888671062886714935e-03f + sqr * (2.48006890615215525031e-05f + - sqr * (-2.75369927749125054106e-07f + sqr * (2.06207229069832465029e-09f + sqr * -9.77507137733812925262e-12f)))))); - - const float f = -1.00000000000000000000e+00f; - - return (sign & 1) ? f - res : -f + res; -} - -static inline float tanf (const float value) { - return sinf (value) / cosf (value); -} - -static inline float atan2f (const float y, const float x) { - auto atanf = [](const float x) { - const float sqr = square (x); - return x * (48.70107004404898384f + sqr * (49.5326263772254345f + sqr * 9.40604244231624f)) / (48.70107004404996166f + sqr * (65.7663163908956299f + sqr * (21.587934067020262f + sqr))); - }; - - const float ax = abs (x); - const float ay = abs (y); - - if (ax < 1e-7f && ay < 1e-7f) { - return 0.0f; - } - - if (ax > ay) { - if (x < 0.0f) { - if (y >= 0.0f) { - return atanf (y / x) + PI; - } - return atanf (y / x) - PI; - } - return atanf (y / x); - } - - if (y < 0.0f) { - return atanf (-x / y) - PI_HALF; - } - return atanf (-x / y) + PI_HALF; -} - -static inline float ceilf (const float x) { - return static_cast (65536 - static_cast (65536.0f - x)); -} - -static inline void sincosf (const float x, const float y, const float z, float *sines, float *cosines) { - // this is the only place where sse2 stuff actually faster than chebyshev pads as we're can calculate 3 sines + 3 cosines - // using only two sse calls instead of 6 calls to sin/cos with standard functions - -#if defined (PLATFORM_HAS_SSE2) - auto inputSet = _mm_set_ps (x, y, z, 0.0f); - - auto _mm_sin = [] (__m128 rad) -> __m128 { - static auto pi2 = _mm_set_ps1 (PI * 2); - static auto rp1 = _mm_set_ps1 (4.0f / PI); - static auto rp2 = _mm_set_ps1 (-4.0f / (PI * PI)); - static auto val = _mm_cmpnlt_ps (rad, _mm_set_ps1 (PI)); - static auto csi = _mm_castsi128_ps (_mm_set1_epi32 (0x80000000)); - - val = _mm_and_ps (val, pi2); - rad = _mm_sub_ps (rad, val); - val = _mm_cmpngt_ps (rad, _mm_set_ps1 (-PI)); - val = _mm_and_ps (val, pi2); - rad = _mm_add_ps (rad, val); - val = _mm_mul_ps (_mm_andnot_ps (csi, rad), rp2); - val = _mm_add_ps (val, rp1); - - auto si = _mm_mul_ps (val, rad); - - val = _mm_mul_ps (_mm_andnot_ps (csi, si), si); - val = _mm_sub_ps (val, si); - val = _mm_mul_ps (val, _mm_set_ps1 (0.225f)); - - return _mm_add_ps (val, si); - }; - static auto hpi = _mm_set_ps1 (PI_HALF); - - auto s = _mm_sin (inputSet); - auto c = _mm_sin (_mm_add_ps (inputSet, hpi)); - - _mm_store_ps (sines, _mm_shuffle_ps (s, s, _MM_SHUFFLE (0, 1, 2, 3))); - _mm_store_ps (cosines, _mm_shuffle_ps (c, c, _MM_SHUFFLE (0, 1, 2, 3))); -#else - sines[0] = sinf (x); - sines[1] = sinf (y); - sines[2] = sinf (z); - - cosines[0] = cosf (x); - cosines[1] = cosf (y); - cosines[2] = cosf (z); -#endif -} - -constexpr bool fzero (const float entry) { - return abs (entry) < ONEPSILON; -} - -constexpr bool fequal (const float entry1, const float entry2) { - return abs (entry1 - entry2) < EQEPSILON; -} - -constexpr float rad2deg (const float radian) { - return radian * R2D; -} - -constexpr float deg2rad (const float degree) { - return degree * D2R; -} - -constexpr float angleMod (const float angle) { - return 360.0f / 65536.0f * (static_cast (angle * (65536.0f / 360.0f)) & 65535); -} - -constexpr float angleNorm (const float angle) { - return 360.0f / 65536.0f * (static_cast ((angle + 180.0f) * (65536.0f / 360.0f)) & 65535) - 180.0f; -} - -constexpr float angleDiff (const float dest, const float src) { - return angleNorm (dest - src); -} - -template struct ClearRef { - using Type = T; -}; - -template struct ClearRef { - using Type = T; -}; - -template struct ClearRef { - using Type = T; -}; - -template static inline typename ClearRef ::Type &&move (T &&type) { - return static_cast ::Type &&> (type); -} - -template static inline void swap (T &left, T &right) { - auto temp = move (left); - left = move (right); - right = move (temp); -} - -template static constexpr inline T &&forward (typename ClearRef ::Type &type) noexcept { - return static_cast (type); -} - -template static constexpr inline T &&forward (typename ClearRef ::Type &&type) noexcept { - return static_cast (type); -} - -template static inline void transfer (T *dest, T *src, size_t length) { - for (size_t i = 0; i < length; i++) { - dest[i] = move (src[i]); - } -} - -namespace classes { - -class NonCopyable { -protected: - NonCopyable (void) = default; - ~NonCopyable (void) = default; - -public: - NonCopyable (const NonCopyable &) = delete; - NonCopyable &operator = (const NonCopyable &) = delete; -}; - -template class Singleton : private NonCopyable { -public: - inline static T *ptr (void) { - return &ref (); - } - - inline static T &ref (void) { - static T ref; - return ref; - }; -}; - -// see: https://github.com/preshing/RandomSequence/ -class RandomSequence : public Singleton { -private: - uint32 m_index; - uint32 m_intermediateOffset; - uint64 m_divider; - -private: - uint32 premute (uint32 x) { - static constexpr uint32 prime = 4294967291u; - - if (x >= prime) { - return x; - } - const uint32 residue = (static_cast (x) * x) % prime; - return (x <= prime / 2) ? residue : prime - residue; - } - - uint32 random (void) { - return premute ((premute (m_index++) + m_intermediateOffset) ^ 0x5bf03635); - } - -public: - RandomSequence (void) { - 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; - } - - template inline U getInt (U low, U high) { - return static_cast (random () * (static_cast (high) - static_cast (low) + 1.0) / m_divider + static_cast (low)); - } - - inline float getFloat (float low, float high) { - return static_cast (random () * (static_cast (high) - static_cast (low)) / (m_divider - 1) + static_cast (low)); - } - - template inline bool chance (const U max, const U maxChance = 100) { - return getInt (0, maxChance) < max; - } -}; - -class SimpleColor final : private NonCopyable { -public: - int red = 0, green = 0, blue = 0; - - inline void reset (void) { - red = green = blue = 0; - } - - inline int avg (void) const { - return sum () / (sizeof (SimpleColor) / sizeof (int)); - } - - inline int sum (void) const { - return red + green + blue; - } -}; - -class Vector final { -public: - float x, y, z; - -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) = default; - -public: - inline operator float * (void) { - return &x; - } - - inline operator const float * (void) const { - return &x; - } - - inline Vector operator + (const Vector &right) const { - return Vector (x + right.x, y + right.y, z + right.z); - } - - inline Vector operator - (const Vector &right) const { - return Vector (x - right.x, y - right.y, z - right.z); - } - - inline Vector operator - (void) const { - return Vector (-x, -y, -z); - } - - friend inline Vector operator * (const float vec, const Vector &right) { - return Vector (right.x * vec, right.y * vec, right.z * vec); - } - - inline Vector operator * (float vec) const { - return Vector (vec * x, vec * y, vec * z); - } - - inline Vector operator / (float vec) const { - const float inv = 1 / vec; - return Vector (inv * x, inv * y, inv * z); - } - - // cross product - 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); - } - - // dot product - inline float operator | (const Vector &right) const { - return x * right.x + y * right.y + z * right.z; - } - - inline const Vector &operator += (const Vector &right) { - x += right.x; - y += right.y; - z += right.z; - - return *this; - } - - inline const Vector &operator -= (const Vector &right) { - x -= right.x; - y -= right.y; - z -= right.z; - - return *this; - } - - inline const Vector &operator *= (float vec) { - x *= vec; - y *= vec; - z *= vec; - - return *this; - } - - inline const Vector &operator /= (float vec) { - const float inv = 1 / vec; - - x *= inv; - y *= inv; - z *= inv; - - return *this; - } - - inline bool operator == (const Vector &right) const { - return fequal (x, right.x) && fequal (y, right.y) && fequal (z, right.z); - } - - inline bool operator != (const Vector &right) const { - return !fequal (x, right.x) && !fequal (y, right.y) && !fequal (z, right.z); - } - - inline Vector &operator = (const Vector &right) = default; - -public: - inline float length (void) const { - return sqrtf (x * x + y * y + z * z); - } - - inline float length2D (void) const { - return sqrtf (x * x + y * y); - } - - inline float lengthSq (void) const { - return x * x + y * y + z * z; - } - - inline Vector make2D (void) const { - return Vector (x, y, 0.0f); - } - - inline Vector normalize (void) const { - float len = length () + static_cast (FLEPSILON); - - if (fzero (len)) { - return Vector (0.0f, 0.0f, 1.0f); - } - len = 1.0f / len; - return Vector (x * len, y * len, z * len); - } - - inline Vector normalize2D (void) const { - float len = length2D () + static_cast (FLEPSILON); - - if (fzero (len)) { - return Vector (0.0f, 1.0f, 0.0f); - } - len = 1.0f / len; - return Vector (x * len, y * len, 0.0f); - } - - inline bool empty (void) const { - return fzero (x) && fzero (y) && fzero (z); - } - - inline static const Vector &null (void) { - static const Vector s_zero = Vector (0.0f, 0.0f, 0.0f); - return s_zero; - } - - inline void nullify (void) { - x = y = z = 0.0f; - } - - inline Vector clampAngles (void) { - x = angleNorm (x); - y = angleNorm (y); - z = 0.0f; - - return *this; - } - - inline float toPitch (void) const { - if (fzero (x) && fzero (y)) { - return 0.0f; - } - return rad2deg (atan2f (z, length2D ())); - } - - inline float toYaw (void) const { - if (fzero (x) && fzero (y)) { - return 0.0f; - } - return rad2deg (atan2f (y, x)); - } - - inline Vector toAngles (void) const { - if (fzero (x) && fzero (y)) { - return Vector (z > 0.0f ? 90.0f : 270.0f, 0.0, 0.0f); - } - return Vector (rad2deg (atan2f (z, length2D ())), rad2deg (atan2f (y, x)), 0.0f); - } - - inline void makeVectors (Vector *forward, Vector *right, Vector *upward) const { - enum { pitch, yaw, roll, unused, max }; - - float sines[max] = { 0.0f, 0.0f, 0.0f, 0.0f }; - float cosines[max] = { 0.0f, 0.0f, 0.0f, 0.0f }; - - // compute the sine and cosine compontents - sincosf (deg2rad (x), deg2rad (y), deg2rad (z), sines, cosines); - - if (forward) { - forward->x = cosines[pitch] * cosines[yaw]; - forward->y = cosines[pitch] * sines[yaw]; - forward->z = -sines[pitch]; - } - - if (right) { - right->x = -sines[roll] * sines[pitch] * cosines[yaw] + cosines[roll] * sines[yaw]; - right->y = -sines[roll] * sines[pitch] * sines[yaw] - cosines[roll] * cosines[yaw]; - right->z = -sines[roll] * cosines[pitch]; - } - - if (upward) { - upward->x = cosines[roll] * sines[pitch] * cosines[yaw] + sines[roll] * sines[yaw]; - upward->y = cosines[roll] * sines[pitch] * sines[yaw] - sines[roll] * cosines[yaw]; - upward->z = cosines[roll] * cosines[pitch]; - } - } -}; - -class Library final : private NonCopyable { -private: - void *m_ptr = nullptr; - -public: - explicit Library (void) = default; - - Library (const char *filename) { - if (!filename) { - return; - } - load (filename); - } - - virtual ~Library (void) { - unload (); - } - -public: - inline void *load (const char *filename) noexcept { -#ifdef PLATFORM_WIN32 - m_ptr = LoadLibrary (filename); -#else - m_ptr = dlopen (filename, RTLD_NOW); -#endif - return m_ptr; - } - - inline void unload (void) noexcept { - if (!isValid ()) { - return; - } -#ifdef PLATFORM_WIN32 - FreeLibrary (static_cast (m_ptr)); -#else - dlclose (m_ptr); -#endif - } - - template R resolve (const char *function) { - if (!isValid ()) { - return nullptr; - } - return reinterpret_cast ( -#ifdef PLATFORM_WIN32 - GetProcAddress (static_cast (m_ptr), function) -#else - dlsym (m_ptr, function) -#endif - ); - } - - template R handle (void) { - return static_cast (m_ptr); - } - - inline bool isValid (void) const { - return m_ptr != nullptr; - } -}; - -template class Pair final { -public: - A first = move (A ()); - B second = move (B ()); - -public: - Pair (const A &a, const B &b) : first (move (a)), second (move (b)) { - } - -public: - Pair (void) = default; - ~Pair (void) = default; -}; - -template class Array : private NonCopyable { -public: - static constexpr size_t INVALID_INDEX = static_cast (-1); - -protected: - T *m_data = nullptr; - size_t m_capacity = 0; - size_t m_length = 0; - -public: - Array (void) = default; - - Array (Array &&other) noexcept { - m_data = other.m_data; - m_length = other.m_length; - m_capacity = other.m_capacity; - - other.reset (); - } - - ~Array (void) { - destroy (); - } - -public: - void destroy (void) { - delete[] m_data; - reset (); - } - - void reset (void) { - m_data = nullptr; - m_capacity = 0; - m_length = 0; - } - - bool reserve (size_t growSize) { - if (m_length + growSize < m_capacity) { - return true; - } - auto maxSize = max (m_capacity + sizeof (T), static_cast (16)); - - while (m_length + growSize > maxSize) { - maxSize *= 2; - } - - if (maxSize >= INT_MAX / sizeof (T)) { - assert (!"Allocation Overflow!"); - return false; - } - auto buffer = new T[maxSize]; - - if (m_data != nullptr) { - if (maxSize < m_length) { - m_length = maxSize; - } - transfer (buffer, m_data, m_length); - delete[] m_data; - } - m_data = move (buffer); - m_capacity = move (maxSize); - - return true; - } - - bool resize (size_t newSize) { - bool res = reserve (newSize); - - while (m_length < newSize) { - push (move (T ())); - } - return res; - } - - size_t length (void) const { - return m_length; - } - - size_t capacity (void) const { - return m_capacity; - } - - bool set (size_t index, T object) { - if (index >= m_capacity) { - if (!reserve (index + 2)) { - return false; - } - } - m_data[index] = forward (object); - - if (index >= m_length) { - m_length = index + 1; - } - return true; - } - - T &at (size_t index) { - return m_data[index]; - } - - const T &at (size_t index) const { - return m_data[index]; - } - - bool at (size_t index, T &object) { - if (index >= m_length) { - return false; - } - object = m_data[index]; - return true; - } - - bool insert (size_t index, T object) { - return insert (index, &object, 1); - } - - bool insert (size_t index, T *objects, size_t count = 1) { - if (!objects || !count) { - return false; - } - size_t newSize = (m_length > index ? m_length : index) + count; - - if (newSize >= m_capacity && !reserve (newSize)) { - return false; - } - - if (index >= m_length) { - for (size_t i = 0; i < count; i++) { - m_data[i + index] = forward (objects[i]); - } - m_length = newSize; - } - else { - size_t i = 0; - - for (i = m_length; i > index; i--) { - m_data[i + count - 1] = move (m_data[i - 1]); - } - for (i = 0; i < count; i++) { - m_data[i + index] = forward (objects[i]); - } - m_length += count; - } - return true; - } - - bool insert (size_t at, Array &other) { - if (&other == this) { - return false; - } - return insert (at, other.m_data, other.m_length); - } - - bool erase (size_t index, size_t count) { - if (index + count > m_capacity) { - return false; - } - m_length -= count; - - for (size_t i = index; i < m_length; i++) { - m_data[i] = move (m_data[i + count]); - } - return true; - } - - bool shift (void) { - return erase (0, 1); - } - - bool unshift (T object) { - return insert (0, &object); - } - - bool erase (T &item) { - return erase (index (item), 1); - } - - bool push (T object) { - return insert (m_length, &object); - } - - bool push (T *objects, size_t count) { - return insert (m_length, objects, count); - } - - bool extend (Array &other) { - if (&other == this) { - return false; - } - return insert (m_length, other.m_data, other.m_length); - } - - void clear (void) { - m_length = 0; - } - - bool empty (void) const { - return m_length == 0; - } - - void shrink (bool destroyEmpty = true) { - if (!m_length) { - if (destroyEmpty) { - destroy (); - } - return; - } - T *buffer = new T[m_length]; - - if (m_data != nullptr) { - transfer (buffer, m_data); - delete[] m_data; - } - m_data = move (buffer); - m_capacity = move (m_length); - } - - size_t index (T &item) { - return &item - &m_data[0]; - } - - T pop (void) { - T item = move (m_data[m_length - 1]); - erase (m_length - 1, 1); - - return item; - } - - const T &back (void) const { - return m_data[m_length - 1]; - } - - T &back (void) { - return m_data[m_length - 1]; - } - - bool last (T &item) { - if (m_length == 0) { - return false; - } - item = m_data[m_length - 1]; - return true; - } - - bool assign (const Array &other) { - if (&other == this) { - return true; - } - if (!reserve (other.m_length)) { - return false; - } - transfer (m_data, other.m_data, other.m_length); - m_length = other.m_length; - - 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]); - } - } - - T &random (void) const { - return m_data[RandomSequence::ref ().getInt (0, static_cast (m_length - 1))]; - } - - Array &operator = (Array &&other) noexcept { - destroy (); - - m_data = other.m_data; - m_length = other.m_length; - m_capacity = other.m_capacity; - - other.reset (); - return *this; - } - - T &operator [] (size_t index) { - return at (index); - } - - const T &operator [] (size_t index) const { - return at (index); - } - - T *begin (void) { - return m_data; - } - - T *begin (void) const { - return m_data; - } - - T *end (void) { - return m_data + m_length; - } - - T *end (void) const { - return m_data + m_length; - } -}; - -template class BinaryHeap final : private Array > { -private: - using type = Pair ; - using base = Array ; - - using base::m_data; - using base::m_length; - -public: - BinaryHeap (void) { - base::reserve (Capacity); - } - BinaryHeap (BinaryHeap &&other) noexcept : base (move (other)) { } - -public: - void push (const A &first, const B &second) { - push ({ first, second }); - } - - void push (type &&pair) { - base::push (forward (pair)); - - if (length () > 1) { - siftUp (); - } - } - - A pop (void) { - assert (!empty ()); - - if (length () == 1) { - return base::pop ().first; - } - type value = move (m_data[0]); - - m_data[0] = move (base::back ()); - base::pop (); - - siftDown (); - return value.first; - } - - bool empty (void) const { - return !length (); - } - - size_t length (void) const { - return m_length; - } - - void clear (void) { - base::clear (); - } - - void siftUp (void) { - size_t child = m_length - 1; - - while (child != 0) { - size_t parentIndex = getParent (child); - - if (m_data[parentIndex].second <= m_data[child].second) { - break; - } - swap (m_data[child], m_data[parentIndex]); - child = parentIndex; - } - } - - void siftDown (void) { - size_t parent = 0; - size_t leftIndex = getLeft (parent); - - auto ref = move (m_data[parent]); - - while (leftIndex < m_length) { - size_t rightIndex = getRight (parent); - - if (rightIndex < m_length) { - if (m_data[rightIndex].second < m_data[leftIndex].second) { - leftIndex = rightIndex; - } - } - - if (ref.second <= m_data[leftIndex].second) { - break; - } - m_data[parent] = move (m_data[leftIndex]); - - parent = leftIndex; - leftIndex = getLeft (parent); - } - m_data[parent] = move (ref); - } - - BinaryHeap &operator = (BinaryHeap &&other) noexcept { - base::operator = (move (other)); - return *this; - } - -private: - static constexpr size_t getLeft (size_t index) { - return index << 1 | 1; - } - - static constexpr size_t getRight (size_t index) { - return ++index << 1; - } - - static constexpr size_t getParent (size_t index) { - return --index >> 1; - } -}; - - -// some fast string class -class String final : private Array { -public: - static constexpr size_t INVALID_INDEX = Array ::INVALID_INDEX; - -private: - using base = Array ; - -private: - bool isTrimChar (char chr, const char *chars) { - do { - if (*chars == chr) { - return true; - } - chars++; - } while (*chars); - return false; - } - -public: - String (String &&other) noexcept : base (move (other)) { } - - String (void) = default; - ~String (void) = default; - -public: - String (const char chr) { - assign (chr); - } - - String (const char *str, size_t length = 0) { - assign (str, length); - } - - String (const String &str, size_t length = 0) : base () { - assign (str.chars (), length); - } - -public: - String &assign (const char *str, size_t length = 0) { - length = length > 0 ? length : strlen (str); - - base::clear (); - base::reserve (length + 1); - - if (base::push (const_cast (str), length)) { - terminate (); - } - return *this; - } - - String &assign (const String &str, size_t length = 0) { - return assign (str.chars (), length); - } - - String &assign (const char chr) { - resize (1); - m_data[0] = chr; - - return *this; - } - - String &append (const char *str) { - if (empty ()) { - return assign (str); - } - if (push (const_cast (str), strlen (str))) { - terminate (); - }; - return *this; - } - - String &append (const String &str) { - return append (str.chars ()); - } - - String &append (const char chr) { - const char app[] = { chr, '\0' }; - return append (app); - } - - const char *chars (void) const { - return empty () ? "" : m_data; - } - - size_t length (void) const { - return base::length (); - } - - size_t capacity (void) const { - return base::capacity (); - } - - bool empty (void) const { - return !length (); - } - - void clear (void) { - base::clear (); - - if (m_data) { - m_data[0] = '\0'; - } - } - - void erase (size_t index, size_t count = 1) { - base::erase (index, count); - terminate (); - } - - void reserve (size_t count) { - base::reserve (count); - } - - void resize (size_t count) { - base::resize (count); - terminate (); - } - - int32 toInt32 (void) const { - return atoi (chars ()); - } - - float toFloat (void) const { - return static_cast (atof (chars ())); - } - - void terminate (void) { - m_data[m_length] = '\0'; - } - - char &at (size_t index) { - return m_data[index]; - } - - const char &at (size_t index) const { - return m_data[index]; - } - - int32 compare (const String &what) const { - return strcmp (m_data, what.begin ()); - } - - int32 compare (const char *what) const { - return strcmp (m_data, what); - } - - bool contains (const String &what) const { - return strstr (m_data, what.begin ()) != nullptr; - } - - template void assign (const char *fmt, Args ...args) { - const size_t size = snprintf (nullptr, 0, fmt, args...); - - reserve (size + 2); // for null-terminator - resize (size); - - snprintf (&m_data[0], size + 1, fmt, args...); - } - - 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; - - reserve (size + 2); // for null-terminator - resize (size); - - snprintf (&m_data[len], size + 1, fmt, args...); - } - - size_t insert (size_t at, const String &str) { - if (base::insert (at, str.begin (), str.length ())) { - terminate (); - return length (); - } - return 0; - } - - 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 (); - - 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; - } - } - return INVALID_INDEX; - } - - size_t replace (const String &what, const String &to) { - if (!what.length () || !to.length ()) { - return 0; - } - size_t numReplaced = 0, posIndex = 0; - - while (posIndex < m_length) { - posIndex = find (what, posIndex); - - if (posIndex == INVALID_INDEX) { - break; - } - erase (posIndex, what.length ()); - insert (posIndex, to); - - posIndex += to.length (); - numReplaced++; - } - return numReplaced; - } - - String substr (size_t start, size_t count = INVALID_INDEX) const { - String result; - - if (start >= m_length || empty ()) { - return move (result); - } - if (count == INVALID_INDEX) { - count = m_length - start; - } - else if (start + count >= m_length) { - count = m_length - start; - } - result.resize (count); - - // copy, not move - for (size_t i = 0; i < count; i++) { - result[i] = m_data[start + i]; - } - result[count] = '\0'; - return move (result); - } - - 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) { - return String (lhs).append (rhs); - } - - friend String operator + (const String &lhs, char rhs) { - return String (lhs).append (rhs); - } - - friend String operator + (char lhs, const String &rhs) { - return String (lhs).append (rhs); - } - - friend String operator + (const String &lhs, const char *rhs) { - return String (lhs).append (rhs); - } - - friend String operator + (const char *lhs, const String &rhs) { - return String (lhs).append (rhs); - } - - friend bool operator == (const String &lhs, const String &rhs) { - return lhs.compare (rhs) == 0; - } - - friend bool operator < (const String &lhs, const String &rhs) { - return lhs.compare (rhs) < 0; - } - - friend bool operator > (const String &lhs, const String &rhs) { - return lhs.compare (rhs) > 0; - } - - friend bool operator == (const char *lhs, const String &rhs) { - return rhs.compare (lhs) == 0; - } - - friend bool operator == (const String &lhs, const char *rhs) { - return lhs.compare (rhs) == 0; - } - - friend bool operator != (const String &lhs, const String &rhs) { - return lhs.compare (rhs) != 0; - } - - friend bool operator != (const char *lhs, const String &rhs) { - return rhs.compare (lhs) != 0; - } - - friend bool operator != (const String &lhs, const char *rhs) { - return lhs.compare (rhs) != 0; - } - - String &operator = (const String &rhs) { - return assign (rhs); - } - - String &operator = (const char *rhs) { - return assign (rhs); - } - - String &operator = (char rhs) { - return assign (rhs); - } - - String &operator = (String &&other) noexcept { - base::operator = (move (other)); - return *this; - } - - String &operator += (const String &rhs) { - return append (rhs); - } - - String &operator += (const char *rhs) { - return append (rhs); - } - - char &operator [] (size_t index) { - return at (index); - } - - const char &operator [] (size_t index) const { - return at (index); - } - -public: - char *begin (void) { - return base::begin (); - } - - char *begin (void) const { - return base::begin (); - } - - 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); - } -}; - -template struct StringHash { - unsigned long operator () (const K &key) const { - char *str = const_cast (key.chars ()); - size_t hash = key.length (); - - while (*str++) { - hash = ((hash << 5) + hash) + *str; - } - return hash; - } -}; - -template struct IntHash { - unsigned long operator () (K key) const { - key = ((key >> 16) ^ key) * 0x119de1f3; - key = ((key >> 16) ^ key) * 0x119de1f3; - key = (key >> 16) ^ key; - - return key; - } -}; - -template , size_t I = 256> class HashMap final : private NonCopyable { -public: - using Bucket = Pair ; - -private: - Array m_buckets[I]; - H m_hash; - - Array &getBucket (const K &key) { - return m_buckets[m_hash (key) % I]; - } - -public: - HashMap (void) = default; - ~HashMap (void) = default; - -public: - bool exists (const K &key) { - return get (key, nullptr); - } - - bool get (const K &key, V *val) { - for (auto &entry : getBucket (key)) { - if (entry.first == key) { - if (val != nullptr) { - *val = entry.second; - } - return true; - } - } - return false; - } - - bool get (const K &key, V &val) { - return get (key, &val); - } - - bool put (const K &key, const V &val) { - if (exists (key)) { - return false; - } - getBucket (key).push (move (Pair (key, val))); - return true; - } - - bool erase (const K &key) { - auto &bucket = getBucket (key); - - for (auto &entry : getBucket (key)) { - if (entry.first == key) { - bucket.erase (entry); - return true; - } - } - return false; - } - -public: - V &operator [] (const K &key) { - for (auto &entry : getBucket (key)) { - if (entry.first == key) { - return entry.second; - } - } - static V ret; - return ret; - } -}; - -class File : private NonCopyable { -private: - FILE *m_handle = nullptr; - size_t m_size = 0; - -public: - File (void) = default; - File (const String &fileName, const String &mode = "rt") { - open (fileName, mode); - } - - ~File (void) { - close (); - } - - bool open (const String &fileName, const String &mode) { - if ((m_handle = fopen (fileName.chars (), mode.chars ())) == nullptr) { - return false; - } - fseek (m_handle, 0L, SEEK_END); - m_size = ftell (m_handle); - fseek (m_handle, 0L, SEEK_SET); - - return true; - } - - void close (void) { - if (m_handle != nullptr) { - fclose (m_handle); - m_handle = nullptr; - } - m_size = 0; - } - - bool eof (void) { - return feof (m_handle) ? true : false; - } - - bool flush (void) { - return fflush (m_handle) ? false : true; - } - - 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); - - va_list ap; - va_start (ap, format); - int written = vfprintf (m_handle, format, ap); - va_end (ap); - - if (written < 0) { - return 0; - } - return written; - } - - char putch (char ch) { - return static_cast (fputc (ch, m_handle)); - } - - bool puts (const String &buffer) { - assert (m_handle != nullptr); - - if (fputs (buffer.chars (), m_handle) < 0) { - return false; - } - return true; - } - - size_t read (void *buffer, size_t size, size_t count = 1) { - return fread (buffer, size, count, m_handle); - } - - size_t write (void *buffer, size_t size, size_t count = 1) { - return fwrite (buffer, size, count, m_handle); - } - - bool seek (long offset, int origin) { - if (fseek (m_handle, offset, origin) != 0) { - return false; - } - return true; - } - - void rewind (void) { - ::rewind (m_handle); - } - - size_t getSize (void) const { - return m_size; - } - - bool isValid (void) const { - return m_handle != nullptr; - } - -public: - static inline bool exists (const String &filename) { - File fp; - - if (fp.open (filename, "rb")) { - fp.close (); - return true; - } - return false; - } - - static inline void pathCreate (char *path) { - for (char *str = path + 1; *str; str++) { - if (*str == '/') { - *str = 0; - _mkdir (path); - *str = '/'; - } - } - _mkdir (path); - } -}; - -class MemoryLoader : public Singleton { -private: - using Load = uint8 * (*) (const char *, int *); - using Unload = void (*) (void *); - -private: - Load m_load; - Unload m_unload; - -public: - MemoryLoader (void) = default; - ~MemoryLoader (void) = default; - -public: - void setup (Load load, Unload unload) { - m_load = load; - m_unload = unload; - } - - uint8 *load (const char *name, int *size) { - if (m_load) { - return m_load (name, size); - } - return nullptr; - } - - void unload (void *buffer) { - if (m_unload) { - m_unload (buffer); - } - } -}; - -class MemFile : private NonCopyable { -private: - size_t m_size = 0; - size_t m_pos = 0; - uint8 *m_buffer = nullptr; - -public: - MemFile (void) = default; - MemFile (const String &filename) { - open (filename); - } - - virtual ~MemFile (void) { - close (); - } - - bool open (const String &filename) { - m_size = 0; - m_pos = 0; - m_buffer = MemoryLoader::ref ().load (filename.chars (), reinterpret_cast (&m_size)); - - if (!m_buffer) { - return false; - } - return true; - } - - void close (void) { - MemoryLoader::ref ().unload (m_buffer); - - m_size = 0; - m_pos = 0; - m_buffer = nullptr; - } - - char getch (void) { - if (!m_buffer || m_pos >= m_size) { - return -1; - } - int readCh = m_buffer[m_pos]; - m_pos++; - - return static_cast (readCh); - } - - char *gets (char *buffer, size_t count) { - if (!m_buffer || m_pos >= m_size) { - return nullptr; - } - size_t index = 0; - buffer[0] = 0; - - for (; index < count - 1;) { - if (m_pos < m_size) { - buffer[index] = m_buffer[m_pos++]; - - if (buffer[index++] == '\n') { - break; - } - } - else { - break; - } - } - buffer[index] = 0; - 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; - } - size_t blocksRead = size * count <= m_size - m_pos ? size * count : m_size - m_pos; - - memcpy (buffer, &m_buffer[m_pos], blocksRead); - m_pos += blocksRead; - - return blocksRead / size; - } - - bool seek (size_t offset, int origin) { - if (!m_buffer || m_pos >= m_size) { - return false; - } - if (origin == SEEK_SET) { - if (offset >= m_size) { - return false; - } - m_pos = offset; - } - else if (origin == SEEK_END) { - if (offset >= m_size) { - return false; - } - m_pos = m_size - offset; - } - else { - if (m_pos + offset >= m_size) { - return false; - } - m_pos += offset; - } - return true; - } - - size_t getSize (void) const { - return m_size; - } - - bool isValid (void) const { - return m_buffer && m_size > 0; - } -}; - -}}; - -namespace cr { -namespace types { - -using StringArray = cr::classes::Array ; -using IntArray = cr::classes::Array ; - -}}; diff --git a/include/crlib/cr-alloc.h b/include/crlib/cr-alloc.h new file mode 100644 index 0000000..4347dd5 --- /dev/null +++ b/include/crlib/cr-alloc.h @@ -0,0 +1,78 @@ +// +// 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 + +#include + +#include +#include +#include + +CR_NAMESPACE_BEGIN + +// default allocator for cr-objects +class Allocator : public Singleton { +public: + Allocator () = default; + ~Allocator () = default; + +public: + template T *allocate (const size_t length = 1) { + auto ptr = reinterpret_cast (calloc (length, sizeof (T))); + + if (!ptr) { + plat.abort (); + } + + // calloc on linux with debug enabled doesn't always zero out memory +#if defined (CR_DEBUG) && !defined (CR_WINDOWS) + auto *zeroing = reinterpret_cast (ptr); + + for (size_t i = 0; i < length; ++i) { + zeroing[i] = 0; + } +#endif + return ptr; + } + + template void deallocate (T *ptr) { + if (ptr) { + free (reinterpret_cast (ptr)); + } + } + +public: + template void construct (T *d, Args &&...args) { + new (d) T (cr::forward (args)...); + } + + template void destruct (T *d) { + d->~T (); + } + +public: + template T *create (Args &&...args) { + auto d = allocate (); + new (d) T (cr::forward (args)...); + + return d; + } + + template void destroy (T *ptr) { + if (ptr) { + destruct (ptr); + deallocate (ptr); + } + } +}; + +static auto &alloc = Allocator::get (); + +CR_NAMESPACE_END \ No newline at end of file diff --git a/include/crlib/cr-array.h b/include/crlib/cr-array.h new file mode 100644 index 0000000..e522ae2 --- /dev/null +++ b/include/crlib/cr-array.h @@ -0,0 +1,390 @@ +// +// 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 + +#include +#include +#include +#include + +// policy to reserve memory +CR_DECLARE_SCOPED_ENUM (ReservePolicy, + MultipleByTwo, + PlusOne +); + +CR_NAMESPACE_BEGIN + +// simple array class like std::vector +template class Array : public DenyCopying { +public: + T *m_data = nullptr; + size_t m_capacity = 0; + size_t m_length = 0; + +public: + explicit Array () = default; + + Array (const size_t amount) { + reserve (amount); + } + + Array (Array &&rhs) noexcept { + m_data = rhs.m_data; + m_length = rhs.m_length; + m_capacity = rhs.m_capacity; + + rhs.reset (); + } + + ~Array () { + destroy (); + } + +private: + void destructElements () noexcept { + for (size_t i = 0; i < m_length; ++i) { + alloc.destruct (&m_data[i]); + } + } + + void transferElements (T *dest, T *src, size_t length) noexcept { + for (size_t i = 0; i < length; ++i) { + alloc.construct (&dest[i], cr::move (src[i])); + alloc.destruct (&src[i]); + } + } + + void destroy () { + destructElements (); + alloc.deallocate (m_data); + } + + void reset () { + m_data = nullptr; + m_capacity = 0; + m_length = 0; + } + + +public: + bool reserve (const size_t amount) { + if (m_length + amount < m_capacity) { + return true; + } + auto capacity = m_capacity ? m_capacity : 8; + + if (cr::fix (R == ReservePolicy::MultipleByTwo)) { + while (m_length + amount > capacity) { + capacity *= 2; + } + } + else { + capacity = amount + m_capacity + 1; + } + auto data = alloc.allocate (capacity); + + transferElements (data, m_data, m_length); + alloc.deallocate (m_data); + + m_data = data; + m_capacity = capacity; + + return true; + } + + bool resize (const size_t amount) { + if (amount < m_length) { + while (amount < m_length) { + discard (); + } + } + else if (amount > m_length) { + if (!ensure (amount)) { + return false; + } + size_t pushLength = amount - m_length; + + for (size_t i = 0; i < pushLength; ++i) { + push (T ()); + } + } + return true; + } + + bool ensure (const size_t amount) { + if (amount <= m_length) { + return true; + } + return reserve (amount - m_length); + } + + size_t length () const { + return m_length; + } + + size_t capacity () const { + return m_capacity; + } + + template bool set (size_t index, U &&object) { + if (index >= m_capacity) { + if (!reserve (index + 1)) { + return false; + } + } + alloc.construct (&m_data[index], cr::forward (object)); + + if (index >= m_length) { + m_length = index + 1; + } + return true; + } + + template bool insert (size_t index, U &&object) { + return insert (index, &object, 1); + } + + template bool insert (size_t index, U *objects, size_t count = 1) { + if (!objects || !count) { + return false; + } + const size_t capacity = (m_length > index ? m_length : index) + count; + + if (capacity >= m_capacity && !reserve (capacity)) { + return false; + } + + if (index >= m_length) { + for (size_t i = 0; i < count; ++i) { + alloc.construct (&m_data[i + index], cr::forward (objects[i])); + } + m_length = capacity; + } + else { + size_t i = 0; + + for (i = m_length; i > index; --i) { + m_data[i + count - 1] = cr::move (m_data[i - 1]); + } + for (i = 0; i < count; ++i) { + alloc.construct (&m_data[i + index], cr::forward (objects[i])); + } + m_length += count; + } + return true; + } + + bool insert (size_t at, const Array &rhs) { + if (&rhs == this) { + return false; + } + return insert (at, &rhs.m_data[0], rhs.m_length); + } + + bool erase (const size_t index, const size_t count) { + if (index + count > m_capacity) { + return false; + } + m_length -= count; + + for (size_t i = index; i < m_length; ++i) { + m_data[i] = cr::move (m_data[i + count]); + } + return true; + } + + bool shift () { + return erase (0, 1); + } + + template bool unshift (U &&object) { + return insert (0, &object); + } + + bool remove (const T &object) { + return erase (index (object), 1); + } + + template bool push (U &&object) { + if (!reserve (1)) { + return false; + } + alloc.construct (&m_data[m_length], cr::forward (object)); + m_length++; + + return true; + } + + template bool emplace (Args &&...args) { + if (!reserve (1)) { + return false; + } + alloc.construct (&m_data[m_length], cr::forward (args)...); + m_length++; + + return true; + } + + T pop () { + auto object = cr::move (m_data[m_length - 1]); + discard (); + + return object; + } + + void discard () { + erase (m_length - 1, 1); + } + + size_t index (const T &object) const { + return &object - &m_data[0]; + } + + void shuffle () { + for (size_t i = m_length; i >= 1; --i) { + cr::swap (m_data[i - 1], m_data[rg.int_ (i, m_length - 2)]); + } + } + + void reverse () { + for (size_t i = 0; i < m_length / 2; ++i) { + cr::swap (m_data[i], m_data[m_length - 1 - i]); + } + } + + template bool extend (U &&rhs) { + if (m_length == 0) { + *this = cr::move (rhs); + } + else { + for (size_t i = 0; i < rhs.length (); ++i) { + if (!push (cr::move (rhs[i]))) { + return false; + } + } + } + return true; + } + + template bool assign (U &&rhs) { + clear (); + return extend (cr::move (rhs)); + } + + void clear () { + destructElements (); + m_length = 0; + } + + bool empty () const { + return m_length == 0; + } + + bool shrink () { + if (m_length == m_capacity || !m_length) { + return false; + } + + auto data = alloc.allocate (m_length); + transferElements (data, m_data, m_length); + + alloc.deallocate (m_data); + + m_data = data; + m_capacity = m_length; + + return true; + } + + const T &at (size_t index) const { + return m_data[index]; + } + + T &at (size_t index) { + return m_data[index]; + } + + const T &first () const { + return m_data[0]; + } + + T &first () { + return m_data[0]; + } + + T &last () { + return m_data[m_length - 1]; + } + + const T &last () const { + return m_data[m_length - 1]; + } + + const T &random () const { + return m_data[rg.int_ (0, m_length - 1)]; + } + + T &random () { + return m_data[rg.int_ (0u, m_length - 1u)]; + } + + T *data () { + return m_data; + } + + T *data () const { + return m_data; + } + +public: + Array &operator = (Array &&rhs) noexcept { + if (this != &rhs) { + destroy (); + + m_data = rhs.m_data; + m_length = rhs.m_length; + m_capacity = rhs.m_capacity; + + rhs.reset (); + } + return *this; + } + +public: + const T &operator [] (size_t index) const { + return at (index); + } + + T &operator [] (size_t index) { + return at (index); + } + + // for range-based loops +public: + T *begin () { + return m_data; + } + + T *begin () const { + return m_data; + } + + T *end () { + return m_data + m_length; + } + + T *end () const { + return m_data + m_length; + } +}; + +CR_NAMESPACE_END + diff --git a/include/crlib/cr-basic.h b/include/crlib/cr-basic.h new file mode 100644 index 0000000..0310226 --- /dev/null +++ b/include/crlib/cr-basic.h @@ -0,0 +1,128 @@ +// +// 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 + +// our global namaespace +#define CR_NAMESPACE_BEGIN namespace cr { +#define CR_NAMESPACE_END } + +// undef base max +#if defined (max) +# undef max +#endif + +// undef base min +#if defined (min) +# undef min +#endif + +#include +#include + +#include +#include + +CR_NAMESPACE_BEGIN +// +// some useful type definitions +// +namespace types { +using int8 = signed char; +using int16 = signed short; +using int32 = signed int; +using uint8 = unsigned char; +using uint16 = unsigned short; +using uint32 = unsigned int; +using uint64 = unsigned long long; +}; + +// make types available for our own use +using namespace cr::types; + +// +// global helper stuff +// +template constexpr size_t bufsize (const T (&)[N]) { + return N - 1; +} + +template constexpr T abs (const T &a) { + return a > 0 ? a : -a; +} + +template constexpr T bit (const T &a) { + return static_cast (1ULL << a); +} + +template constexpr T min (const T &a, const T &b) { + return a < b ? a : b; +} + +template constexpr T max (const T &a, const T &b) { + return a > b ? a : b; +} + +template constexpr T square (const T &value) { + return value * value; +} + +template constexpr T clamp (const T &x, const T &a, const T &b) { + return min (max (x, a), b); +} + +template const T &fix (const T &type) { + return type; +} + +// +// base classes +// + +// simple non-copying base class +class DenyCopying { +protected: + explicit DenyCopying () = default; + ~DenyCopying () = default; + +public: + DenyCopying (const DenyCopying &) = delete; + DenyCopying &operator = (const DenyCopying &) = delete; +}; + +// singleton for objects +template struct Singleton : private DenyCopying { +public: + static inline T &get () { + static T ref_; + return ref_; + }; +}; + +// simple scoped enum, instaed of enum class +#define CR_DECLARE_SCOPED_ENUM_TYPE(enumName, enumType, ...) \ + CR_NAMESPACE_BEGIN \ + namespace enums { \ + struct _##enumName : protected DenyCopying { \ + enum Type : enumType { \ + __VA_ARGS__ \ + }; \ + }; \ + }; \ + CR_NAMESPACE_END \ + using enumName = ::cr::enums::_##enumName::Type; \ + +// same as above, but with int32 type +#define CR_DECLARE_SCOPED_ENUM(enumName, ...) \ + CR_DECLARE_SCOPED_ENUM_TYPE(enumName, int32, __VA_ARGS__); \ + +CR_NAMESPACE_END + +// platform-dependant-stuff +#include \ No newline at end of file diff --git a/include/crlib/cr-binheap.h b/include/crlib/cr-binheap.h new file mode 100644 index 0000000..d3c2767 --- /dev/null +++ b/include/crlib/cr-binheap.h @@ -0,0 +1,141 @@ +// +// 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 + +#include + +CR_NAMESPACE_BEGIN + +// simple priority queue +template class BinaryHeap final : public DenyCopying { +private: + Array m_data; + +public: + explicit BinaryHeap () = default; + + BinaryHeap (BinaryHeap &&rhs) noexcept : m_data (cr::move (rhs.m_data)) + { } + + ~BinaryHeap () = default; + +public: + template bool push (U &&item) { + if (!m_data.push (cr::move (item))) { + return false; + } + size_t length = m_data.length (); + + if (length > 1) { + percolateUp (length - 1); + } + return true; + } + + template bool emplace (Args &&...args) { + if (!m_data.emplace (cr::forward (args)...)) { + return false; + } + size_t length = m_data.length (); + + if (length > 1) { + percolateUp (length - 1); + } + return true; + } + + const T &top () const { + return m_data[0]; + } + + T pop () { + if (m_data.length () == 1) { + return m_data.pop (); + } + auto key (cr::move (m_data[0])); + + m_data[0] = cr::move (m_data.last ()); + m_data.discard (); + + percolateDown (0); + return key; + } + +public: + size_t length () const { + return m_data.length (); + } + + bool empty () const { + return !m_data.length (); + } + + void clear () { + m_data.clear (); + } + +private: + void percolateUp (size_t index) { + while (index != 0) { + size_t parent = this->parent (index); + + if (m_data[parent] <= m_data[index]) { + break; + } + cr::swap (m_data[index], m_data[parent]); + index = parent; + } + } + + void percolateDown (size_t index) { + while (this->left (index) < m_data.length ()) { + size_t best = this->left (index); + + if (this->right (index) < m_data.length ()) { + size_t right_index = this->right (index); + + if (m_data[right_index] < m_data[best]) { + best = right_index; + } + } + + if (m_data[index] <= m_data[best]) { + break; + } + cr::swap (m_data[index], m_data[best]); + + index = best; + best = this->left (index); + } + } + +private: + BinaryHeap &operator = (BinaryHeap &&rhs) noexcept { + if (this != &rhs) { + m_data = cr::move (rhs.m_data); + } + return *this; + } + +private: + static constexpr size_t left (size_t index) { + return index << 1 | 1; + } + + static constexpr size_t right (size_t index) { + return ++index << 1; + } + + static constexpr size_t parent (size_t index) { + return --index >> 1; + } +}; + +CR_NAMESPACE_END \ No newline at end of file diff --git a/include/crlib/cr-color.h b/include/crlib/cr-color.h new file mode 100644 index 0000000..9e75ad4 --- /dev/null +++ b/include/crlib/cr-color.h @@ -0,0 +1,41 @@ +// +// 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 + +#include + +CR_NAMESPACE_BEGIN + +// simple color holder +class Color final { +public: + int32 red = 0, green = 0, blue = 0; + +public: + explicit Color (int32 r, int32 g, int32 b) : red (r), green (g), blue (b) { } + + Color () = default; + ~Color () = default; + +public: + void reset () { + red = green = blue = 0; + } + + int32 avg () const { + return sum () / (sizeof (Color) / sizeof (int32)); + } + + int32 sum () const { + return red + green + blue; + } +}; + +CR_NAMESPACE_END \ No newline at end of file diff --git a/include/crlib/cr-complete.h b/include/crlib/cr-complete.h new file mode 100644 index 0000000..4bdd9d4 --- /dev/null +++ b/include/crlib/cr-complete.h @@ -0,0 +1,40 @@ +// +// 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 + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +CR_NAMESPACE_BEGIN + +namespace types { + using StringArray = Array ; + using IntArray = Array ; +}; + +using namespace cr::types; + +CR_NAMESPACE_END \ No newline at end of file diff --git a/include/crlib/cr-dict.h b/include/crlib/cr-dict.h new file mode 100644 index 0000000..28ade6c --- /dev/null +++ b/include/crlib/cr-dict.h @@ -0,0 +1,237 @@ +// +// 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 + +#include +#include +#include +#include + +CR_NAMESPACE_BEGIN + +// template for hashing our string +template struct StringHash { + uint32 operator () (const K &key) const { + char *str = const_cast (key.chars ()); + uint32 hash = 0; + + while (*str++) { + hash = ((hash << 5) + hash) + *str; + } + return hash; + } +}; + +// template for hashing integers +template struct IntHash { + uint32 operator () (K key) const { + key = ((key >> 16) ^ key) * 0x119de1f3; + key = ((key >> 16) ^ key) * 0x119de1f3; + key = (key >> 16) ^ key; + + return key; + } +}; + +namespace detail { + struct DictionaryList { + uint32 index; + DictionaryList *next; + }; + + template struct DictionaryBucket { + uint32 hash = static_cast (-1); + K key = K (); + V value = V (); + + public: + DictionaryBucket () = default; + ~DictionaryBucket () = default; + }; +} + +// basic dictionary +template , size_t HashSize = 36> class Dictionary final : public DenyCopying { +public: + static constexpr size_t kInvalidIndex = static_cast (-1); + +private: + Array m_table; + Array > m_buckets; + H m_hasher; + +private: + uint32 hash (const K &key) const { + return m_hasher (key); + } + + size_t findIndexOrAllocate (const K &key, bool allocate) { + auto hashed = hash (key); + auto pos = hashed % m_table.length (); + + for (auto bucket = m_table[pos]; bucket != nullptr; bucket = bucket->next) { + if (m_buckets[bucket->index].hash == hashed) { + return bucket->index; + } + } + + if (allocate) { + size_t created = m_buckets.length (); + m_buckets.resize (created + 1); + + auto allocated = alloc.allocate (); + + allocated->index = created; + allocated->next = m_table[pos]; + + m_table[pos] = allocated; + + m_buckets[created].key = key; + m_buckets[created].hash = hashed; + + return created; + } + return kInvalidIndex; + } + + size_t findIndex (const K &key) const { + return const_cast (this)->findIndexOrAllocate (key, false); + } + +public: + explicit Dictionary () { + reset (); + } + + Dictionary (Dictionary &&rhs) noexcept : m_table (cr::move (rhs.m_table)), m_buckets (cr::move (rhs.m_buckets)), m_hasher (cr::move (rhs.m_hasher)) + { } + + ~Dictionary () { + clear (); + } + +public: + bool exists (const K &key) const { + return findIndex (key) != kInvalidIndex; + } + + bool empty () const { + return m_buckets.empty (); + } + + size_t length () const { + return m_buckets.length (); + } + + bool find (const K &key, V &value) const { + size_t index = findIndex (key); + + if (index == kInvalidIndex) { + return false; + } + value = m_buckets[index].value; + return true; + } + + template bool push (const K &key, U &&value) { + operator [] (key) = cr::forward (value); + return true; + } + + bool remove (const K &key) { + auto hashed = hash (key); + + auto pos = hashed % m_table.length (); + auto *bucket = m_table[pos]; + + detail::DictionaryList *next = nullptr; + + while (bucket != nullptr) { + if (m_buckets[bucket->index].hash == hashed) { + if (!next) { + m_table[pos] = bucket->next; + } + else { + next->next = bucket->next; + } + m_buckets.erase (bucket->index, 1); + + alloc.deallocate (bucket); + bucket = nullptr; + + return true; + } + next = bucket; + bucket = bucket->next; + } + return false; + } + + void clear () { + for (auto object : m_table) { + while (object != nullptr) { + auto next = object->next; + + alloc.deallocate (object); + object = next; + } + } + m_table.clear (); + m_buckets.clear (); + + reset (); + } + + void reset () { + m_table.resize (HashSize); + + for (size_t i = 0; i < HashSize; ++i) { + m_table[i] = nullptr; + } + } + +public: + V &operator [] (const K &key) { + return m_buckets[findIndexOrAllocate (key, true)].value; + } + + const V &operator [] (const K &key) const { + return m_buckets[findIndex (key)].value; + } + + Dictionary &operator = (Dictionary &&rhs) noexcept { + if (this != &rhs) { + m_table = cr::move (rhs.m_table); + m_buckets = cr::move (rhs.m_buckets); + m_hasher = cr::move (rhs.m_hasher); + } + return *this; + } + + // for range-based loops +public: + detail::DictionaryBucket *begin () { + return m_buckets.begin (); + } + + detail::DictionaryBucket *begin () const { + return m_buckets.begin (); + } + + detail::DictionaryBucket *end () { + return m_buckets.end (); + } + + detail::DictionaryBucket *end () const { + return m_buckets.end (); + } +}; + +CR_NAMESPACE_END \ No newline at end of file diff --git a/include/crlib/cr-files.h b/include/crlib/cr-files.h new file mode 100644 index 0000000..e54b79c --- /dev/null +++ b/include/crlib/cr-files.h @@ -0,0 +1,334 @@ +// +// 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 + +#include + +#include +#include + +CR_NAMESPACE_BEGIN + +// simple stdio file wrapper +class File final : private DenyCopying { +private: + FILE *m_handle = nullptr; + size_t m_length = 0; + +public: + explicit File () = default; + + File (const String &file, const String &mode = "rt") { + open (file, mode); + } + + ~File () { + close (); + } +public: + + bool open (const String &file, const String &mode) { + if ((m_handle = fopen (file.chars (), mode.chars ())) == nullptr) { + return false; + } + fseek (m_handle, 0L, SEEK_END); + m_length = ftell (m_handle); + fseek (m_handle, 0L, SEEK_SET); + + return true; + } + + void close () { + if (m_handle != nullptr) { + fclose (m_handle); + m_handle = nullptr; + } + m_length = 0; + } + + bool eof () const { + return !!feof (m_handle); + } + + bool flush () const { + return !!fflush (m_handle); + } + + int getChar () const { + return fgetc (m_handle); + } + + char *getString (char *buffer, int count) const { + return fgets (buffer, count, m_handle); + } + + bool getLine (String &line) { + line.clear (); + int ch; + + while ((ch = getChar ()) != EOF && !eof ()) { + line += static_cast (ch); + + if (ch == '\n') { + break; + } + } + return !eof (); + } + + template size_t puts (const char *fmt, Args ...args) { + if (!*this) { + return false; + } + return fprintf (m_handle, fmt, cr::forward (args)...); + } + + bool puts (const String &buffer) { + if (!*this) { + return false; + } + if (fputs (buffer.chars (), m_handle) < 0) { + return false; + } + return true; + } + + int putChar (int ch) { + return fputc (ch, m_handle); + } + + size_t read (void *buffer, size_t size, size_t count = 1) { + return fread (buffer, size, count, m_handle); + } + + size_t write (void *buffer, size_t size, size_t count = 1) { + return fwrite (buffer, size, count, m_handle); + } + + bool seek (long offset, int origin) { + if (fseek (m_handle, offset, origin) != 0) { + return false; + } + return true; + } + + void rewind () { + ::rewind (m_handle); + } + + size_t length () const { + return m_length; + } + +public: + explicit operator bool () const { + return m_handle != nullptr; + } + +public: + static inline bool exists (const String &file) { + File fp; + + if (fp.open (file, "rb")) { + fp.close (); + return true; + } + return false; + } + + static inline void createPath (const char *path) { + for (auto str = const_cast (path) + 1; *str; ++str) { + if (*str == '/') { + *str = 0; + plat.createDirectory (path); + *str = '/'; + } + } + plat.createDirectory (path); + } +}; + +// wrapper for memory file for loading data into the memory +class MemFileStorage : public Singleton { +private: + using LoadFunction = Lambda ; + using FreeFunction = Lambda ; + +private: + LoadFunction m_load = nullptr; + FreeFunction m_free = nullptr; + +public: + inline MemFileStorage () = default; + inline ~MemFileStorage () = default; + +public: + void initizalize (LoadFunction loader, FreeFunction unloader) { + m_load = cr::move (loader); + m_free = cr::move (unloader); + } + +public: + uint8 *load (const String &file, int *size) { + if (m_load) { + return m_load (file.chars (), size); + } + return nullptr; + } + + void unload (void *buffer) { + if (m_free) { + m_free (buffer); + } + } +}; + +class MemFile final : public DenyCopying { +private: + static constexpr char kEOF = static_cast (-1); + +private: + uint8 *m_data = nullptr; + size_t m_length = 0, m_pos = 0; + +public: + explicit MemFile () = default; + + MemFile (const String &file) { + open (file); + } + + ~MemFile () { + close (); + } + +public: + bool open (const String &file) { + m_length = 0; + m_pos = 0; + m_data = MemFileStorage::get ().load (file.chars (), reinterpret_cast (&m_length)); + + if (!m_data) { + return false; + } + return true; + } + + void close () { + MemFileStorage::get ().unload (m_data); + + m_length = 0; + m_pos = 0; + m_data = nullptr; + } + + char getChar () { + if (!m_data || m_pos >= m_length) { + return kEOF; + } + auto ch = m_data[m_pos]; + m_pos++; + + return static_cast (ch); + } + + char *getString (char *buffer, size_t count) { + if (!m_data || m_pos >= m_length) { + return nullptr; + } + size_t index = 0; + buffer[0] = 0; + + for (; index < count - 1;) { + if (m_pos < m_length) { + buffer[index] = m_data[m_pos++]; + + if (buffer[index++] == '\n') { + break; + } + } + else { + break; + } + } + buffer[index] = 0; + return index ? buffer : nullptr; + } + + bool getLine (String &line) { + line.clear (); + char ch; + + while ((ch = getChar ()) != kEOF) { + line += ch; + + if (ch == '\n') { + break; + } + } + return !eof (); + } + + size_t read (void *buffer, size_t size, size_t count = 1) { + if (!m_data || m_length <= m_pos || !buffer || !size || !count) { + return 0; + } + size_t blocks_read = size * count <= m_length - m_pos ? size * count : m_length - m_pos; + + memcpy (buffer, &m_data[m_pos], blocks_read); + m_pos += blocks_read; + + return blocks_read / size; + } + + bool seek (size_t offset, int origin) { + if (!m_data || m_pos >= m_length) { + return false; + } + if (origin == SEEK_SET) { + if (offset >= m_length) { + return false; + } + m_pos = offset; + } + else if (origin == SEEK_END) { + if (offset >= m_length) { + return false; + } + m_pos = m_length - offset; + } + else { + if (m_pos + offset >= m_length) { + return false; + } + m_pos += offset; + } + return true; + } + + size_t length () const { + return m_length; + } + + bool eof () const { + return m_pos >= m_length; + } + + void rewind () { + m_pos = 0; + } + +public: + explicit operator bool () const { + return !!m_data && m_length > 0; + } +}; + +CR_NAMESPACE_END \ No newline at end of file diff --git a/include/crlib/cr-http.h b/include/crlib/cr-http.h new file mode 100644 index 0000000..a30382b --- /dev/null +++ b/include/crlib/cr-http.h @@ -0,0 +1,425 @@ +// +// 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 + +#include + +#include +#include +#include +#include + +#if defined (CR_LINUX) || defined (CR_OSX) +# include +# include +# include +# include +# include +# include +# include +# include +#elif defined (CR_WINDOWS) +# define WIN32_LEAN_AND_MEAN +# include +# include +#endif + +// status codes for http client +CR_DECLARE_SCOPED_ENUM (HttpClientResult, + OK = 0, + NotFound, + Forbidden, + SocketError, + ConnectError, + HttpOnly, + Undefined, + NoLocalFile = -1, + LocalFileExists = -2 +); + +CR_NAMESPACE_BEGIN + +class Socket final : public DenyCopying { +private: + int32 m_socket; + uint32 m_timeout; + +public: + Socket () : m_socket (-1), m_timeout (2) { +#if defined(CR_WINDOWS) + WSADATA wsa; + + if (WSAStartup (MAKEWORD (1, 1), &wsa) != 0) { + logger.error ("Unable to inialize sockets."); + } +#endif + } + + ~Socket () { + disconnect (); +#if defined (CR_WINDOWS) + WSACleanup (); +#endif + } + + +public: + bool connect (const String &hostname) { + auto host = gethostbyname (hostname.chars ()); + + if (!host) { + return false; + } + m_socket = static_cast (socket (AF_INET, SOCK_STREAM, 0)); + + if (m_socket < 0) { + return false; + } + + auto getTimeouts = [&] () -> Twin { +#if defined (CR_WINDOWS) + DWORD tv = m_timeout * 1000; +#else + timeval tv { static_cast (m_timeout), 0 }; +#endif + return { reinterpret_cast (&tv), sizeof (tv) }; + }; + auto timeouts = getTimeouts (); + + setsockopt (m_socket, SOL_SOCKET, SO_RCVTIMEO, timeouts.first, timeouts.second); + setsockopt (m_socket, SOL_SOCKET, SO_SNDTIMEO, timeouts.first, timeouts.second); + + sockaddr_in dest; + memset (&dest, 0, sizeof (dest)); + + dest.sin_family = AF_INET; + dest.sin_port = htons (80); + dest.sin_addr.s_addr = inet_addr (inet_ntoa (*(reinterpret_cast (host->h_addr)))); + + if (::connect (m_socket, reinterpret_cast (&dest), static_cast (sizeof (dest))) == -1) { + disconnect (); + return false; + } + return true; + } + + void setTimeout (uint32 timeout) { + m_timeout = timeout; + } + + void disconnect () { +#if defined(CR_WINDOWS) + if (m_socket != -1) { + closesocket (m_socket); + } +#else + if (m_socket != -1) + close (m_socket); +#endif + } + +public: + template int32 send (const U *buffer, int32 length) const { + return ::send (m_socket, reinterpret_cast (buffer), length, 0); + } + + template int32 recv (U *buffer, int32 length) { + return ::recv (m_socket, reinterpret_cast (buffer), length, 0); + } +}; + +namespace detail { + + // simple http uri omitting query-string and port + struct HttpUri { + String path, protocol, host; + + public: + static HttpUri parse (const String &uri) { + HttpUri result; + + if (uri.empty ()) { + return result; + } + size_t protocol = uri.find ("://"); + + if (protocol != String::kInvalidIndex) { + result.protocol = uri.substr (0, protocol); + + size_t host = uri.find ("/", protocol + 3); + + if (host != String::kInvalidIndex) { + result.path = uri.substr (host + 1); + result.host = uri.substr (protocol + 3, host - protocol - 3); + + return result; + } + } + return result; + } + }; +}; + +// simple http client for downloading/uploading files only +class HttpClient final : public Singleton { +private: + static constexpr int32 kMaxRecvErrors = 12; + +private: + Socket m_socket; + String m_userAgent = "crlib"; + HttpClientResult m_code = HttpClientResult::Undefined; + int32 m_chunkSize = 4096; + +public: + HttpClient () = default; + ~HttpClient () = default; + +private: + HttpClientResult parseResponseHeader (uint8 *buffer) { + bool isFinished = false; + int32 pos = 0, symbols = 0, errors = 0; + + // prase response header + while (!isFinished && pos < m_chunkSize) { + if (m_socket.recv (&buffer[pos], 1) < 1) { + if (++errors > kMaxRecvErrors) { + isFinished = true; + } + else { + continue; + } + } + + switch (buffer[pos]) { + case '\r': + break; + + case '\n': + isFinished = (symbols == 0); + symbols = 0; + break; + + default: + symbols++; + break; + } + pos++; + } + String response (reinterpret_cast (buffer)); + size_t responseCodeStart = response.find ("HTTP/1.1"); + + if (responseCodeStart != String::kInvalidIndex) { + String respCode = response.substr (responseCodeStart + 9, 3).trim (); + + if (respCode == "200") { + return HttpClientResult::OK; + } + else if (respCode == "403") { + return HttpClientResult::Forbidden; + } + else if (respCode == "404") { + return HttpClientResult::NotFound; + } + } + return HttpClientResult::NotFound; + } + +public: + + // simple blocked download + bool downloadFile (const String &url, const String &localPath) { + if (File::exists (localPath.chars ())) { + m_code = HttpClientResult::LocalFileExists; + return false; + } + auto uri = detail::HttpUri::parse (url); + + // no https... + if (uri.protocol == "https") { + m_code = HttpClientResult::HttpOnly; + return false; + } + + // unable to connect... + if (!m_socket.connect (uri.host)) { + m_code = HttpClientResult::ConnectError; + m_socket.disconnect (); + + return false; + } + + String request; + request.appendf ("GET /%s HTTP/1.1\r\n", uri.path.chars ()); + request.append ("Accept: */*\r\n"); + request.append ("Connection: close\r\n"); + request.append ("Keep-Alive: 115\r\n"); + request.appendf ("User-Agent: %s\r\n", m_userAgent.chars ()); + request.appendf ("Host: %s\r\n\r\n", uri.host.chars ()); + + if (m_socket.send (request.chars (), static_cast (request.length ())) < 1) { + m_code = HttpClientResult::SocketError; + m_socket.disconnect (); + + return false; + } + Array buffer (m_chunkSize); + m_code = parseResponseHeader (buffer.data ()); + + if (m_code != HttpClientResult::OK) { + m_socket.disconnect (); + return false; + } + + // receive the file + File file (localPath, "wb"); + + if (!file) { + m_code = HttpClientResult::Undefined; + m_socket.disconnect (); + + return false; + } + int32 length = 0; + int32 errors = 0; + + for (;;) { + length = m_socket.recv (buffer.data (), m_chunkSize); + + if (length > 0) { + file.write (buffer.data (), length); + } + else if (++errors > 12) { + break; + } + } + file.close (); + + m_socket.disconnect (); + m_code = HttpClientResult::OK; + + return true; + } + + bool uploadFile (const String &url, const String &localPath) { + if (!File::exists (localPath.chars ())) { + m_code = HttpClientResult::NoLocalFile; + return false; + } + auto uri = detail::HttpUri::parse (url); + + // no https... + if (uri.protocol == "https") { + m_code = HttpClientResult::HttpOnly; + return false; + } + + // unable to connect... + if (!m_socket.connect (uri.host)) { + m_code = HttpClientResult::ConnectError; + m_socket.disconnect (); + + return false; + } + + // receive the file + File file (localPath, "rb"); + + if (!file) { + m_code = HttpClientResult::Undefined; + m_socket.disconnect (); + + return false; + } + String boundaryName = localPath; + size_t boundarySlash = localPath.findLastOf ("\\/"); + + if (boundarySlash != String::kInvalidIndex) { + boundaryName = localPath.substr (boundarySlash + 1); + } + const String &kBoundary = "---crlib_upload_boundary_1337"; + + String request, start, end; + start.appendf ("--%s\r\n", kBoundary.chars ()); + start.appendf ("Content-Disposition: form-data; name='file'; filename='%s'\r\n", boundaryName.chars ()); + start.append ("Content-Type: application/octet-stream\r\n\r\n"); + + end.appendf ("\r\n--%s--\r\n\r\n", kBoundary.chars ()); + + request.appendf ("POST /%s HTTP/1.1\r\n", uri.path.chars ()); + request.appendf ("Host: %s\r\n", uri.host.chars ()); + request.appendf ("User-Agent: %s\r\n", m_userAgent.chars ()); + request.appendf ("Content-Type: multipart/form-data; boundary=%s\r\n", kBoundary.chars ()); + request.appendf ("Content-Length: %d\r\n\r\n", file.length () + start.length () + end.length ()); + + // send the main request + if (m_socket.send (request.chars (), static_cast (request.length ())) < 1) { + m_code = HttpClientResult::SocketError; + m_socket.disconnect (); + + return false; + } + + // send boundary start + if (m_socket.send (start.chars (), static_cast (start.length ())) < 1) { + m_code = HttpClientResult::SocketError; + m_socket.disconnect (); + + return false; + } + Array buffer (m_chunkSize); + int32 length = 0; + + for (;;) { + length = static_cast (file.read (buffer.data (), 1, m_chunkSize)); + + if (length > 0) { + m_socket.send (buffer.data (), length); + } + else { + break; + } + } + + // send boundary end + if (m_socket.send (end.chars (), static_cast (end.length ())) < 1) { + m_code = HttpClientResult::SocketError; + m_socket.disconnect (); + + return false; + } + m_code = parseResponseHeader (buffer.data ()); + m_socket.disconnect (); + + return m_code == HttpClientResult::OK; + } + +public: + void setUserAgent (const String &ua) { + m_userAgent = ua; + } + + HttpClientResult getLastStatusCode () { + return m_code; + } + + void setChunkSize (int32 chunkSize) { + m_chunkSize = chunkSize; + } + + void setTimeout (uint32 timeout) { + m_socket.setTimeout (timeout); + } +}; + +// expose global http client +static auto &http = HttpClient::get (); + +CR_NAMESPACE_END \ No newline at end of file diff --git a/include/crlib/cr-lambda.h b/include/crlib/cr-lambda.h new file mode 100644 index 0000000..f29efd0 --- /dev/null +++ b/include/crlib/cr-lambda.h @@ -0,0 +1,173 @@ +// +// 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 + +#include +#include + +CR_NAMESPACE_BEGIN + +static constexpr uint32 kLambdaSmallBufferSize = sizeof (void *) * 16; + +template class Lambda; +template class Lambda { + class LambdaFunctorWrapper { + public: + LambdaFunctorWrapper () = default; + virtual ~LambdaFunctorWrapper () = default; + + public: + virtual void move (uint8 *to) = 0; + virtual void small (uint8 *to) const = 0; + + virtual R invoke (Args &&...) = 0; + virtual UniquePtr clone () const = 0; + + public: + void operator delete (void *ptr) { + alloc.deallocate (ptr); + } + }; + + template class LambdaFunctor : public LambdaFunctorWrapper { + private: + T m_callable; + + public: + LambdaFunctor (const T &callable) : LambdaFunctorWrapper (), m_callable (callable) + { } + + LambdaFunctor (T &&callable) : LambdaFunctorWrapper (), m_callable (cr::move (callable)) + { } + + ~LambdaFunctor () override = default; + + public: + void move (uint8 *to) override { + new (to) LambdaFunctor (cr::move (m_callable)); + } + + void small (uint8 *to) const override { + new (to) LambdaFunctor (m_callable); + } + + R invoke (Args &&... args) override { + return m_callable (cr::forward (args)...); + } + + UniquePtr clone () const override { + return createUniqueBase , LambdaFunctorWrapper> (m_callable); + } + }; + + union { + UniquePtr m_functor; + uint8 m_small[kLambdaSmallBufferSize]; + }; + + bool m_smallObject; + +private: + void destroy () { + if (m_smallObject) { + reinterpret_cast (m_small)->~LambdaFunctorWrapper (); + } + else { + m_functor.reset (); + } + } + + void swap (Lambda &rhs) noexcept { + cr::swap (rhs, *this); + } + +public: + Lambda () noexcept : Lambda (nullptr) + { } + + Lambda (decltype (nullptr)) noexcept : m_functor (nullptr), m_smallObject (false) + { } + + Lambda (const Lambda &rhs) { + if (rhs.m_smallObject) { + reinterpret_cast (rhs.m_small)->small (m_small); + } + else { + new (m_small) UniquePtr (rhs.m_functor->clone ()); + } + m_smallObject = rhs.m_smallObject; + } + + Lambda (Lambda &&rhs) noexcept { + if (rhs.m_smallObject) { + reinterpret_cast (rhs.m_small)->move (m_small); + new (rhs.m_small) UniquePtr (nullptr); + } + else { + new (m_small) UniquePtr (cr::move (rhs.m_functor)); + } + m_smallObject = rhs.m_smallObject; + rhs.m_smallObject = false; + } + + template Lambda (F function) { + if (cr::fix (sizeof (function) > kLambdaSmallBufferSize)) { + m_smallObject = false; + new (m_small) UniquePtr (createUniqueBase , LambdaFunctorWrapper> (cr::move (function))); + } + else { + m_smallObject = true; + new (m_small) LambdaFunctor (cr::move (function)); + } + } + + ~Lambda () { + destroy (); + } + +public: + Lambda &operator = (const Lambda &rhs) { + destroy (); + + Lambda tmp (rhs); + swap (tmp); + + return *this; + } + + Lambda &operator = (Lambda &&rhs) noexcept { + destroy (); + + if (rhs.m_smallObject) { + reinterpret_cast (rhs.m_small)->move (m_small); + new (rhs.m_small) UniquePtr (nullptr); + } + else { + new (m_small) UniquePtr (cr::move (rhs.m_functor)); + } + + m_smallObject = rhs.m_smallObject; + rhs.m_smallObject = false; + + return *this; + } + + explicit operator bool () const noexcept { + return m_smallObject || !!m_functor; + } + +public: + R operator () (Args ...args) { + return m_smallObject ? reinterpret_cast (m_small)->invoke (cr::forward (args)...) : m_functor->invoke (cr::forward (args)...); + } +}; + + +CR_NAMESPACE_END \ No newline at end of file diff --git a/include/crlib/cr-library.h b/include/crlib/cr-library.h new file mode 100644 index 0000000..d008206 --- /dev/null +++ b/include/crlib/cr-library.h @@ -0,0 +1,89 @@ +// +// 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 + +#include +#include + +#if defined (CR_LINUX) || defined (CR_OSX) +# include +# include +# include +# include +# include +#elif defined (CR_WINDOWS) +# define WIN32_LEAN_AND_MEAN +# include +#endif + + +CR_NAMESPACE_BEGIN + +// handling dynamic library loading +class SharedLibrary final : public DenyCopying { +private: + void *m_handle = nullptr; + +public: + explicit SharedLibrary () = default; + + SharedLibrary (const String &file) { + if (file.empty ()) { + return; + } + load (file); + } + + ~SharedLibrary () { + unload (); + } + +public: + inline bool load (const String &file) noexcept { +#ifdef CR_WINDOWS + m_handle = LoadLibraryA (file.chars ()); +#else + m_handle = dlopen (file.chars (), RTLD_NOW); +#endif + return m_handle != nullptr; + } + + void unload () noexcept { + if (!*this) { + return; + } +#ifdef CR_WINDOWS + FreeLibrary (static_cast (m_handle)); +#else + dlclose (m_handle); +#endif + } + + template R resolve (const char *function) const { + if (!*this) { + return nullptr; + } + + return reinterpret_cast ( +#ifdef CR_WINDOWS + GetProcAddress (static_cast (m_handle), function) +#else + dlsym (m_handle, function) +#endif + ); + } + +public: + explicit operator bool () const { + return m_handle != nullptr; + } +}; + +CR_NAMESPACE_END \ No newline at end of file diff --git a/include/crlib/cr-logger.h b/include/crlib/cr-logger.h new file mode 100644 index 0000000..0bab998 --- /dev/null +++ b/include/crlib/cr-logger.h @@ -0,0 +1,98 @@ +// +// 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 + +#include + +#include +#include + +#if defined (CR_WINDOWS) +# define WIN32_LEAN_AND_MEAN +# include +#endif + +CR_NAMESPACE_BEGIN + +class SimpleLogger final : public Singleton { +public: + using PrintFunction = Lambda ; + +private: + File m_handle; + PrintFunction m_printer; + +public: + SimpleLogger () = default; + + ~SimpleLogger () { + m_handle.close (); + } + +private: + void logToFile (const char *level, const char *msg) { + if (!m_handle) { + return; + } + time_t ticks = time (&ticks); + auto tm = localtime (&ticks); + + auto timebuf = strings.chars (); + strftime (timebuf, StringBuffer::StaticBufferSize, "%Y-%m-%d %H:%M:%S", tm); + + m_handle.puts ("%s (%s): %s\n", timebuf, level, msg); + } + +public: + template void fatal (const char *fmt, Args ...args) { + auto msg = strings.format (fmt, cr::forward (args)...); + + logToFile ("FATAL", msg); + + if (m_printer) { + m_printer (msg); + } + plat.abort (msg); + } + + template void error (const char *fmt, Args ...args) { + auto msg = strings.format (fmt, cr::forward (args)...); + + logToFile ("ERROR", msg); + + if (m_printer) { + m_printer (msg); + } + } + + template void message (const char *fmt, Args ...args) { + auto msg = strings.format (fmt, cr::forward (args)...); + + logToFile ("INFO", msg); + + if (m_printer) { + m_printer (msg); + } + } + +public: + void initialize (const String &filename, PrintFunction printFunction) { + if (m_handle) { + m_handle.close (); + } + m_printer = cr::move (printFunction); + m_handle.open (filename, "at"); + } +}; + +// expose global instance +static auto &logger = SimpleLogger::get (); + +CR_NAMESPACE_END \ No newline at end of file diff --git a/include/crlib/cr-math.h b/include/crlib/cr-math.h new file mode 100644 index 0000000..eaf6f4c --- /dev/null +++ b/include/crlib/cr-math.h @@ -0,0 +1,179 @@ +// +// 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 + +#include + +#if defined (CR_HAS_SSE2) +# include +#endif + +CR_NAMESPACE_BEGIN + +constexpr float kFloatEpsilon = 0.01f; +constexpr float kFloatEqualEpsilon = 0.001f; +constexpr float kFloatCmpEpsilon = 1.192092896e-07f; + +constexpr float kMathPi = 3.141592653589793115997963468544185161590576171875f; +constexpr float kMathPiReciprocal = 1.0f / kMathPi; +constexpr float kMathPiHalf = kMathPi / 2; + +constexpr float kDegreeToRadians = kMathPi / 180.0f; +constexpr float kRadiansToDegree = 180.0f / kMathPi; + +constexpr bool fzero (const float e) { + return cr::abs (e) < kFloatEpsilon; +} + +constexpr bool fequal (const float a, const float b) { + return cr:: abs (a - b) < kFloatEqualEpsilon; +} + +constexpr float radiansToDegrees (const float r) { + return r * kRadiansToDegree; +} + +constexpr float degreesToRadians (const float d) { + return d * kDegreeToRadians; +} + +constexpr float modAngles (const float a) { + return 360.0f / 65536.0f * (static_cast (a * (65536.0f / 360.0f)) & 65535); +} + +constexpr float normalizeAngles (const float a) { + return 360.0f / 65536.0f * (static_cast ((a + 180.0f) * (65536.0f / 360.0f)) & 65535) - 180.0f; +} + +constexpr float anglesDifference (const float a, const float b) { + return normalizeAngles (a - b); +} + +inline float sinf (const float value) { + const auto sign = static_cast (value * kMathPiReciprocal); + const float calc = (value - static_cast (sign) * kMathPi); + + const float sqr = cr::square (calc); + const float res = 1.00000000000000000000e+00f + sqr * (-1.66666671633720397949e-01f + sqr * (8.33333376795053482056e-03f + sqr * (-1.98412497411482036114e-04f + + sqr * (2.75565571428160183132e-06f + sqr * (-2.50368472620721149724e-08f + sqr * (1.58849267073435385100e-10f + sqr * -6.58925550841432672300e-13f)))))); + + return (sign & 1) ? -calc * res : value * res; +} + +inline float cosf (const float value) { + const auto sign = static_cast (value * kMathPiReciprocal); + const float calc = (value - static_cast (sign) * kMathPi); + + const float sqr = cr::square (calc); + const float res = sqr * (-5.00000000000000000000e-01f + sqr * (4.16666641831398010254e-02f + sqr * (-1.38888671062886714935e-03f + sqr * (2.48006890615215525031e-05f + + sqr * (-2.75369927749125054106e-07f + sqr * (2.06207229069832465029e-09f + sqr * -9.77507137733812925262e-12f)))))); + + const float f = -1.00000000000000000000e+00f; + + return (sign & 1) ? f - res : -f + res; +} + +inline float atanf (const float x) { + const float sqr = cr::square (x); + return x * (48.70107004404898384f + sqr * (49.5326263772254345f + sqr * 9.40604244231624f)) / (48.70107004404996166f + sqr * (65.7663163908956299f + sqr * (21.587934067020262f + sqr))); +}; + +inline float atan2f (const float y, const float x) { + const float ax = cr::abs (x); + const float ay = cr::abs (y); + + if (ax < 1e-7f && ay < 1e-7f) { + return 0.0f; + } + + if (ax > ay) { + if (x < 0.0f) { + if (y >= 0.0f) { + return atanf (y / x) + kMathPi; + } + return atanf (y / x) - kMathPi; + } + return atanf (y / x); + } + + if (y < 0.0f) { + return atanf (-x / y) - kMathPiHalf; + } + return atanf (-x / y) + kMathPiHalf; +} + +inline float powf (const float x, const float y) { + union { + float d; + int x; + } res { x }; + + res.x = static_cast (y * (res.x - 1064866805) + 1064866805); + return res.d; +} + +inline float sqrtf (const float value) { + return powf (value, 0.5f); +} + +inline float tanf (const float value) { + return sinf (value) / cosf (value); +} + +constexpr float ceilf (const float x) { + return static_cast (65536 - static_cast (65536.0f - x)); +} + +inline void sincosf (const float x, const float y, const float z, float *sines, float *cosines) { +#if defined (CR_HAS_SSE2) + auto set = _mm_set_ps (x, y, z, 0.0f); + + auto _mm_sin = [] (__m128 rad) -> __m128 { + static auto pi2 = _mm_set_ps1 (kMathPi * 2); + static auto rp1 = _mm_set_ps1 (4.0f / kMathPi); + static auto rp2 = _mm_set_ps1 (-4.0f / (kMathPi * kMathPi)); + static auto val = _mm_cmpnlt_ps (rad, _mm_set_ps1 (kMathPi)); + static auto csi = _mm_castsi128_ps (_mm_set1_epi32 (0x80000000)); + + val = _mm_and_ps (val, pi2); + rad = _mm_sub_ps (rad, val); + val = _mm_cmpngt_ps (rad, _mm_set_ps1 (-kMathPi)); + val = _mm_and_ps (val, pi2); + rad = _mm_add_ps (rad, val); + val = _mm_mul_ps (_mm_andnot_ps (csi, rad), rp2); + val = _mm_add_ps (val, rp1); + + auto si = _mm_mul_ps (val, rad); + + val = _mm_mul_ps (_mm_andnot_ps (csi, si), si); + val = _mm_sub_ps (val, si); + val = _mm_mul_ps (val, _mm_set_ps1 (0.225f)); + + return _mm_add_ps (val, si); + }; + static auto hpi = _mm_set_ps1 (kMathPiHalf); + + auto s = _mm_sin (set); + auto c = _mm_sin (_mm_add_ps (set, hpi)); + + _mm_store_ps (sines, _mm_shuffle_ps (s, s, _MM_SHUFFLE (0, 1, 2, 3))); + _mm_store_ps (cosines, _mm_shuffle_ps (c, c, _MM_SHUFFLE (0, 1, 2, 3))); +#else + sines[0] = cr::sinf (x); + sines[1] = cr::sinf (y); + sines[2] = cr::sinf (z); + + cosines[0] = cr::cosf (x); + cosines[1] = cr::cosf (y); + cosines[2] = cr::cosf (z); +#endif +} + +CR_NAMESPACE_END \ No newline at end of file diff --git a/include/crlib/cr-movable.h b/include/crlib/cr-movable.h new file mode 100644 index 0000000..0bd02ab --- /dev/null +++ b/include/crlib/cr-movable.h @@ -0,0 +1,58 @@ +// +// 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 + +#include + +CR_NAMESPACE_BEGIN + +namespace detail { + template struct ClearRef { + using Type = T; + }; + template struct ClearRef { + using Type = T; + }; + template struct ClearRef { + using Type = T; + }; +}; + +template typename detail::ClearRef ::Type constexpr &&move (T &&type) noexcept { + return static_cast ::Type &&> (type); +} + +template constexpr T &&forward (typename detail::ClearRef ::Type &type) noexcept { + return static_cast (type); +} + +template constexpr T &&forward (typename detail::ClearRef ::Type &&type) noexcept { + return static_cast (type); +} + +template inline void swap (T &left, T &right) noexcept { + auto temp = cr::move (left); + left = cr::move (right); + right = cr::move (temp); +} + +template inline void swap (T (&left)[S], T (&right)[S]) noexcept { + if (&left == &right) { + return; + } + auto begin = left; + auto end = begin + S; + + for (auto temp = right; begin != end; ++begin, ++temp) { + cr::swap (*begin, *temp); + } +} + +CR_NAMESPACE_END \ No newline at end of file diff --git a/include/crlib/cr-platform.h b/include/crlib/cr-platform.h new file mode 100644 index 0000000..f402667 --- /dev/null +++ b/include/crlib/cr-platform.h @@ -0,0 +1,172 @@ +// +// 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 + +CR_NAMESPACE_BEGIN + +// detects the build platform +#if defined(__linux__) +# define CR_LINUX +#elif defined(__APPLE__) +# define CR_OSX +#elif defined(_WIN32) +# define CR_WINDOWS +#endif + +#if defined(__ANDROID__) +# define CR_ANDROID +# if defined(LOAD_HARDFP) +# define CR_ANDROID_HARD_FP +# endif +#endif + +// detects the compiler +#if defined(_MSC_VER) +# define CR_CXX_MSVC +#elif defined(__clang__) +# define CR_CXX_CLANG +#endif + +// configure export macros +#if defined(CR_WINDOWS) +# define CR_EXPORT extern "C" __declspec (dllexport) +#elif defined(CR_LINUX) || defined(CR_OSX) +# define CR_EXPORT extern "C" __attribute__((visibility("default"))) +#else +# error "Can't configure export macros. Compiler unrecognized." +#endif + +#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64__) || defined(__amd64) || (defined(_MSC_VER) && defined(_M_X64)) +# define CR_ARCH_X64 +#elif defined(__i686) || defined(__i686__) || defined(__i386) || defined(__i386__) || defined(i386) || (defined(_MSC_VER) && defined(_M_IX86)) +# define CR_ARCH_X86 +#endif + +#if (defined(CR_ARCH_X86) || defined(CR_ARCH_X64)) && !defined(CR_DEBUG) +# define CR_HAS_SSE2 +#endif + +CR_NAMESPACE_END + +#if defined(CR_WINDOWS) +# include +# define stricmp _stricmp +#else +# include +# include +# define stricmp strcasecmp +#endif + +#include + +#if defined (CR_ANDROID) +# include +#endif + +CR_NAMESPACE_BEGIN + +// helper struct for platform detection +struct Platform : public Singleton { + bool isWindows = false; + bool isLinux = false; + bool isOSX = false; + bool isAndroid = false; + bool isAndroidHardFP = false; + bool isX64 = false; + + Platform () { +#if defined(CR_WINDOWS) + isWindows = true; +#endif + +#if defined(CR_ANDROID) + isAndroid = true; + +# if defined (CR_ANDROID_HARD_FP) + isAndroidHardFP = true; +# endif +#endif + +#if defined(CR_LINUX) + isLinux = true; +#endif + +#if defined (CR_OSX) + isOSX = true; +#endif + +#if defined (CR_ARCH_X64) + isX64 = true; +#endif + } + + // helper platform-dependant functions + template bool checkPointer (U *ptr) { +#if defined(CR_WINDOWS) + if (IsBadCodePtr (reinterpret_cast (ptr))) { + return false; + } +#else + (void) (ptr); +#endif + return true; + } + + bool createDirectory (const char *dir) { + int result = 0; +#if defined(CR_WINDOWS) + result = _mkdir (dir); +#else + result = mkdir (dir, 0777); +#endif + return !!result; + } + + bool removeDirectory (const char *dir) { +#if defined(CR_WINDOWS) + _unlink (dir); +#else + unlink (dir); +#endif + return true; + } + + bool hasModule (const char *mod) { +#if defined(CR_WINDOWS) + return GetModuleHandleA (mod) != nullptr; +#else + (void) (mod); + return true; +#endif + } + + void abort (const char *msg = "OUT OF MEMORY!") noexcept { + fprintf (stderr, "%s\n", msg); + +#if defined (CR_ANDROID) + __android_log_write (ANDROID_LOG_ERROR, "crlib.fatal", msg); +#endif + +#if defined(CR_WINDOWS) + if (msg) { + DestroyWindow (GetForegroundWindow ()); + MessageBoxA (GetActiveWindow (), msg, "crlib.fatal", MB_ICONSTOP); + } +#endif + ::abort (); + } + +}; + +// expose platform singleton +static auto &plat = Platform::get (); + + +CR_NAMESPACE_END \ No newline at end of file diff --git a/include/crlib/cr-random.h b/include/crlib/cr-random.h new file mode 100644 index 0000000..c094544 --- /dev/null +++ b/include/crlib/cr-random.h @@ -0,0 +1,66 @@ +// +// 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 + +#include +#include + +CR_NAMESPACE_BEGIN + +// random number generator see: https://github.com/preshing/RandomSequence/ +class Random final : public Singleton { +private: + uint32 m_index, m_offset; + uint64 m_divider; + +public: + explicit Random () { + const auto base = static_cast (time (nullptr)); + const auto offset = base + 1; + + m_index = premute (premute (base) + 0x682f0161); + m_offset = premute (premute (offset) + 0x46790905); + m_divider = (static_cast (1)) << 32; + } + ~Random () = default; + +private: + uint32 premute (uint32 index) { + static constexpr auto prime = 4294967291u; + + if (index >= prime) { + return index; + } + const uint32 residue = (static_cast (index) * index) % prime; + return (index <= prime / 2) ? residue : prime - residue; + } + + uint32 generate () { + return premute ((premute (m_index++) + m_offset) ^ 0x5bf03635); + } + +public: + template U int_ (U low, U high) { + return static_cast (generate () * (static_cast (high) - static_cast (low) + 1.0) / m_divider + static_cast (low)); + } + + float float_ (float low, float high) { + return static_cast (generate () * (static_cast (high) - static_cast (low)) / (m_divider - 1) + static_cast (low)); + } + + template bool chance (const U max, const U maxChance = 100) { + return int_ (0, maxChance) < max; + } +}; + +// expose global random generator +static auto &rg = Random::get (); + +CR_NAMESPACE_END \ No newline at end of file diff --git a/include/crlib/cr-string.h b/include/crlib/cr-string.h new file mode 100644 index 0000000..2764d42 --- /dev/null +++ b/include/crlib/cr-string.h @@ -0,0 +1,870 @@ +// +// 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 + +#include +#include + +#include +#include +#include +#include +#include + +CR_NAMESPACE_BEGIN + +// small-string optimized string class, sso stuff based on: https://github.com/elliotgoodrich/SSO-23/ +class String final { +public: + static constexpr size_t kInvalidIndex = static_cast (-1); + +private: + static constexpr size_t kExcessSpace = 32; + +private: + using Length = Twin ; + +private: + union Data { + struct Big { + char excess[kExcessSpace - sizeof (char *) - 2 * sizeof (size_t)]; + char *ptr; + size_t length; + size_t capacity; + } big; + + struct Small { + char str[sizeof (Big) / sizeof (char) - 1]; + uint8 length; + } small; + } m_data; + +private: + static size_t const kSmallCapacity = sizeof (typename Data::Big) / sizeof (char) - 1; + +public: + explicit String () { + reset (); + assign ("", 0); + } + + String (const char *str, size_t length = 0) { + reset (); + assign (str, length); + } + + String (const String &str) { + reset (); + assign (str.data (), str.length ()); + } + + String (const char ch) { + reset (); + assign (ch); + } + + String (String &&rhs) noexcept { + m_data = rhs.m_data; + rhs.setMoved (); + } + + ~String () { + destroy (); + } + +private: + template static uint8 &getMostSignificantByte (T &object) { + return *(reinterpret_cast (&object) + sizeof (object) - 1); + } + + template static bool getLeastSignificantBit (uint8 byte) { + return byte & cr::bit (N); + } + + template static bool getMostSignificantBit (uint8 byte) { + return byte & cr::bit (CHAR_BIT - N - 1); + } + + template static void setLeastSignificantBit (uint8 &byte, bool bit) { + if (bit) { + byte |= cr::bit (N); + } + else { + byte &= ~cr::bit (N); + } + } + + template static void setMostSignificantBit (uint8 &byte, bool bit) { + if (bit) { + byte |= cr::bit (CHAR_BIT - N - 1); + } + else { + byte &= ~cr::bit (CHAR_BIT - N - 1); + } + } + + void destroy () { + if (!isSmall ()) { + alloc.deallocate (m_data.big.ptr); + } + } + + void reset () { + m_data.small.length = 0; + m_data.small.str[0] = '\0'; + + m_data.big.ptr = nullptr; + m_data.big.length = 0; + } + + void endString (const char *str, size_t at) { + const_cast (str)[at] = '\0'; + } + + void moveString (const char *dst, const char *src, size_t length) { + if (!dst) { + return; + } + memmove (const_cast (dst), src, length); + } + + const char *data () const { + return isSmall () ? m_data.small.str : m_data.big.ptr; + } + + void setMoved () { + setSmallLength (0); + } + + void setLength (size_t amount, size_t capacity) { + if (amount <= kSmallCapacity) { + endString (m_data.small.str, amount); + setSmallLength (static_cast (amount)); + } + else { + endString (m_data.big.ptr, amount); + setDataNonSmall (amount, capacity); + } + } + + bool isSmall () const { + return !getLeastSignificantBit <0> (m_data.small.length) && !getLeastSignificantBit <1> (m_data.small.length); + } + + void setSmallLength (uint8 length) { + m_data.small.length = static_cast (kSmallCapacity - length) << 2; + } + + size_t getSmallLength () const { + return kSmallCapacity - ((m_data.small.length >> 2) & 63u); + } + + void setDataNonSmall (size_t length, size_t capacity) { + uint8 &lengthHighByte = getMostSignificantByte (length); + uint8 &capacityHighByte = getMostSignificantByte (capacity); + + const bool lengthHasHighBit = getMostSignificantBit <0> (lengthHighByte); + const bool capacityHasHighBit = getMostSignificantBit <0> (capacityHighByte); + const bool capacityHasSecHighBit = getMostSignificantBit <1> (capacityHighByte); + + setMostSignificantBit <0> (lengthHighByte, capacityHasSecHighBit); + + capacityHighByte <<= 2; + setLeastSignificantBit <0> (capacityHighByte, capacityHasHighBit); + setLeastSignificantBit <1> (capacityHighByte, !lengthHasHighBit); + + m_data.big.length = length; + m_data.big.capacity = capacity; + } + + Length getDataNonSmall () const { + size_t length = m_data.big.length; + size_t capacity = m_data.big.capacity; + + uint8 &lengthHighByte = getMostSignificantByte (length); + uint8 &capacityHighByte = getMostSignificantByte (capacity); + + const bool capacityHasHighBit = getLeastSignificantBit <0> (capacityHighByte); + const bool lengthHasHighBit = !getLeastSignificantBit <1> (capacityHighByte); + const bool capacityHasSecHighBit = getMostSignificantBit <0> (lengthHighByte); + + setMostSignificantBit <0> (lengthHighByte, lengthHasHighBit); + + capacityHighByte >>= 2; + setMostSignificantBit <0> (capacityHighByte, capacityHasHighBit); + setMostSignificantBit <1> (capacityHighByte, capacityHasSecHighBit); + + return { length, capacity }; + } + +public: + String &assign (const char *str, size_t length = 0) { + length = length > 0 ? length : strlen (str); + + if (length <= kSmallCapacity) { + moveString (m_data.small.str, str, length); + + endString (m_data.small.str, length); + setSmallLength (static_cast (length)); + } + else { + auto capacity = cr::max (kSmallCapacity * 2, length); + m_data.big.ptr = alloc.allocate (capacity + 1); + + if (m_data.big.ptr) { + moveString (m_data.big.ptr, str, length); + + endString (m_data.big.ptr, length); + setDataNonSmall (length, capacity); + } + } + return *this; + } + + String &assign (const String &str, size_t length = 0) { + return assign (str.chars (), length); + } + + String &assign (const char ch) { + const char str[] { ch, '\0' }; + return assign (str, strlen (str)); + } + + String &append (const char *str, size_t length = 0) { + if (empty ()) { + return assign (str, length); + } + length = length > 0 ? length : strlen (str); + + size_t oldLength = this->length (); + size_t newLength = oldLength + length; + + resize (newLength); + + moveString (&data ()[oldLength], str, length); + endString (data (), newLength); + + return *this; + } + + String &append (const String &str, size_t length = 0) { + return append (str.chars (), length); + } + + String &append (const char ch) { + const char str[] { ch, '\0' }; + return append (str, strlen (str)); + } + + template String &assignf (const char *fmt, Args ...args) { + const size_t size = snprintf (nullptr, 0, fmt, args...); + + Array buffer (size + 1); + snprintf (buffer.data (), size + 1, fmt, cr::forward (args)...); + + return assign (buffer.data ()); + } + + template String &appendf (const char *fmt, Args ...args) { + if (empty ()) { + return assignf (fmt, cr::forward (args)...); + } + const size_t size = snprintf (nullptr, 0, fmt, args...) + length (); + + Array buffer (size + 1); + snprintf (buffer.data (), size + 1, fmt, cr::forward (args)...); + + return append (buffer.data ()); + } + + void resize (size_t amount) { + size_t oldLength = length (); + + if (amount <= kSmallCapacity) { + if (!isSmall ()) { + auto ptr = m_data.big.ptr; + + moveString (m_data.small.str, ptr, cr::min (oldLength, amount)); + alloc.deallocate (ptr); + } + setLength (amount, 0); + } + else { + size_t newCapacity = 0; + + if (isSmall ()) { + newCapacity = cr::max (amount, kSmallCapacity * 2); + auto ptr = alloc.allocate (newCapacity + 1); + + moveString (ptr, m_data.small.str, cr::min (oldLength, amount)); + m_data.big.ptr = ptr; + } + else if (amount < capacity ()) { + newCapacity = capacity (); + } + else { + newCapacity = cr::max (amount, capacity () * 3 / 2); + auto ptr = alloc.allocate (newCapacity + 1); + + moveString (ptr, m_data.big.ptr, cr::min (oldLength, amount)); + alloc.deallocate (m_data.big.ptr); + + m_data.big.ptr = ptr; + } + setLength (amount, newCapacity); + } + } + + bool insert (size_t index, const String &str) { + if (str.empty ()) { + return false; + } + + const size_t strLength = str.length (); + const size_t dataLength = length (); + + if (index >= dataLength) { + append (str.chars (), strLength); + } + else { + resize (dataLength + strLength); + + for (size_t i = dataLength; i > index; --i) { + at (i + strLength - 1) = at (i - 1); + } + + for (size_t i = 0; i < strLength; ++i) { + at (i + index) = str.at (i); + } + } + return true; + } + + bool erase (size_t index, size_t count = 1) { + const size_t dataLength = length (); + + if (index + count > dataLength) { + return false; + } + const size_t newLength = dataLength - count; + + for (size_t i = index; i < newLength; ++i) { + at (i) = at (i + count); + } + resize (newLength); + + return true; + } + + size_t find (char pattern, size_t start = 0) const { + for (size_t i = start; i < length (); ++i) { + if (at (i) == pattern) { + return i; + } + } + return kInvalidIndex; + } + + size_t find (const String &pattern, size_t start = 0) const { + const size_t patternLength = pattern.length (); + const size_t dataLength = length (); + + if (patternLength > dataLength || start > dataLength) { + return kInvalidIndex; + } + + for (size_t i = start; i <= dataLength - patternLength; ++i) { + size_t index = 0; + + for (; at (index) && index < patternLength; ++index) { + if (at (i + index) != pattern.at (index)) { + break; + } + } + + if (!pattern.at (index)) { + return i; + } + } + return kInvalidIndex; + } + + size_t rfind (char pattern) const { + for (size_t i = length (); i != 0; i--) { + if (at (i) == pattern) { + return i; + } + } + return kInvalidIndex; + } + + size_t rfind (const String &pattern) const { + const size_t patternLength = pattern.length (); + const size_t dataLength = length (); + + if (patternLength > dataLength) { + return kInvalidIndex; + } + bool match = true; + + for (size_t i = dataLength - 1; i >= patternLength; i--) { + match = true; + + for (size_t j = patternLength - 1; j > 0; j--) { + if (at (i + j) != pattern.at (j)) { + match = false; + break; + } + } + + if (match) { + return i; + } + } + return kInvalidIndex; + } + + size_t findFirstOf (const String &pattern, size_t start = 0) const { + const size_t patternLength = pattern.length (); + const size_t dataLength = length (); + + for (size_t i = start; i < dataLength; ++i) { + for (size_t j = 0; j < patternLength; ++j) { + if (at (i) == pattern.at (j)) { + return i; + } + } + } + return kInvalidIndex; + } + + size_t findLastOf (const String &pattern) const { + const size_t patternLength = pattern.length (); + const size_t dataLength = length (); + + for (size_t i = dataLength - 1; i > 0; i--) { + for (size_t j = 0; j < patternLength; ++j) { + if (at (i) == pattern.at (j)) { + return i; + } + } + } + return kInvalidIndex; + } + + size_t findFirstNotOf (const String &pattern, size_t start = 0) const { + const size_t patternLength = pattern.length (); + const size_t dataLength = length (); + + bool different = true; + + for (size_t i = start; i < dataLength; ++i) { + different = true; + + for (size_t j = 0; j < patternLength; ++j) { + if (at (i) == pattern.at (j)) { + different = false; + break; + } + } + + if (different) { + return i; + } + } + return kInvalidIndex; + } + + size_t findLastNotOf (const String &pattern) const { + const size_t patternLength = pattern.length (); + const size_t dataLength = length (); + + bool different = true; + + for (size_t i = dataLength - 1; i > 0; i--) { + different = true; + + for (size_t j = 0; j < patternLength; ++j) { + if (at (i) == pattern.at (j)) { + different = false; + break; + } + } + if (different) { + return i; + } + } + return kInvalidIndex; + } + + + size_t countChar (char ch) const { + size_t count = 0; + + for (size_t i = 0, e = length (); i != e; ++i) { + if (at (i) == ch) { + ++count; + } + } + return count; + } + + size_t countStr (const String &pattern) const { + const size_t patternLen = pattern.length (); + const size_t dataLength = length (); + + if (patternLen > dataLength) { + return 0; + } + size_t count = 0; + + for (size_t i = 0, e = dataLength - patternLen + 1; i != e; ++i) { + if (substr (i, patternLen).compare (pattern)) { + ++count; + } + } + return count; + } + + String substr (size_t start, size_t count = kInvalidIndex) const { + start = cr::min (start, length ()); + + if (count == kInvalidIndex) { + count = length (); + } + return String (data () + start, cr::min (count, length () - start)); + } + + size_t replace (const String &needle, const String &to) { + if (needle.empty () || to.empty ()) { + return 0; + } + size_t replaced = 0, pos = 0; + + while (pos < length ()) { + pos = find (needle, pos); + + if (pos == kInvalidIndex) { + break; + } + erase (pos, needle.length ()); + insert (pos, to); + + pos += to.length (); + replaced++; + } + return replaced; + } + + bool startsWith (const String &prefix) const { + const size_t prefixLength = prefix.length (); + const size_t dataLength = length (); + + return prefixLength <= dataLength && strncmp (data (), prefix.data (), prefixLength) == 0; + } + + bool endsWith (const String &suffix) const { + const size_t suffixLength = suffix.length (); + const size_t dataLength = length (); + + return suffixLength <= dataLength && strncmp (data () + dataLength - suffixLength, suffix.data (), suffixLength) == 0; + } + + Array split (const String &delim) const { + Array tokens; + size_t prev = 0, pos = 0; + + while ((pos = find (delim, pos)) != kInvalidIndex) { + tokens.push (substr (prev, pos - prev)); + prev = ++pos; + } + tokens.push (substr (prev, pos - prev)); + + return tokens; + } + + size_t length () const { + if (isSmall ()) { + return getSmallLength (); + } + else { + return getDataNonSmall ().first; + } + } + + size_t capacity () const { + if (isSmall ()) { + return sizeof (m_data) - 1; + } + else { + return getDataNonSmall ().second; + } + } + + bool small () const { + return isSmall (); + } + + bool empty () const { + return length () == 0; + } + + void clear () { + assign (""); + } + + const char *chars () const { + return data (); + } + + const char &at (size_t index) const { + return begin ()[index]; + } + + char &at (size_t index) { + return begin ()[index]; + } + + int32 compare (const String &rhs) const { + return strcmp (rhs.data (), data ()); + } + + int32 compare (const char *rhs) const { + return strcmp (rhs, data ()); + } + + bool contains (const String &rhs) const { + return find (rhs) != kInvalidIndex; + } + + String &lowercase () { + for (auto &ch : *this) { + ch = static_cast (::tolower (ch)); + } + return *this; + } + + String &uppercase () { + for (auto &ch : *this) { + ch = static_cast (::toupper (ch)); + } + return *this; + } + + int32 int_ () const { + return atoi (data ()); + } + + float float_ () const { + return static_cast (atof (data ())); + } + + String <rim (const String &characters = "\r\n\t ") { + size_t begin = length (); + + for (size_t i = 0; i < begin; ++i) { + if (characters.find (at (i)) == kInvalidIndex) { + begin = i; + break; + } + } + return *this = substr (begin, length () - begin); + } + + String &rtrim (const String &characters = "\r\n\t ") { + size_t end = 0; + + for (size_t i = length (); i > 0; --i) { + if (characters.find (at (i - 1)) == kInvalidIndex) { + end = i; + break; + } + } + return *this = substr (0, end); + } + + String &trim (const String &characters = "\r\n\t ") { + return ltrim (characters).rtrim (characters); + } + +public: + String &operator = (String &&rhs) noexcept { + destroy (); + + m_data = rhs.m_data; + rhs.setMoved (); + + return *this; + } + + String &operator = (const String &rhs) { + return assign (rhs); + } + + String &operator = (const char *rhs) { + return assign (rhs); + } + + String &operator = (char rhs) { + return assign (rhs); + } + + String &operator += (const String &rhs) { + return append (rhs); + } + + String &operator += (const char *rhs) { + return append (rhs); + } + + const char &operator [] (size_t index) const { + return at (index); + } + + char &operator [] (size_t index) { + return at (index); + } + + friend String operator + (const String &lhs, char rhs) { + return String (lhs).append (rhs); + } + + friend String operator + (char lhs, const String &rhs) { + return String (lhs).append (rhs); + } + + friend String operator + (const String &lhs, const char *rhs) { + return String (lhs).append (rhs); + } + + friend String operator + (const char *lhs, const String &rhs) { + return String (lhs).append (rhs); + } + + friend String operator + (const String &lhs, const String &rhs) { + return String (lhs).append (rhs); + } + + friend bool operator == (const String &lhs, const String &rhs) { + return lhs.compare (rhs) == 0; + } + + friend bool operator < (const String &lhs, const String &rhs) { + return lhs.compare (rhs) < 0; + } + + friend bool operator > (const String &lhs, const String &rhs) { + return lhs.compare (rhs) > 0; + } + + friend bool operator == (const char *lhs, const String &rhs) { + return rhs.compare (lhs) == 0; + } + + friend bool operator == (const String &lhs, const char *rhs) { + return lhs.compare (rhs) == 0; + } + + friend bool operator != (const String &lhs, const String &rhs) { + return lhs.compare (rhs) != 0; + } + + friend bool operator != (const char *lhs, const String &rhs) { + return rhs.compare (lhs) != 0; + } + + friend bool operator != (const String &lhs, const char *rhs) { + return lhs.compare (rhs) != 0; + } + +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 result; + } + + // for range-based loops +public: + char *begin () { + return const_cast (data ()); + } + + char *begin () const { + return const_cast (data ()); + } + + char *end () { + return begin () + length (); + } + + char *end () const { + return begin () + length (); + } +}; + +// simple rotation-string pool for holding temporary data passed to different modules and for formatting +class StringBuffer final : public Singleton { +public: + enum : size_t { + StaticBufferSize = static_cast (768), + RotationCount = static_cast (32) + }; + +private: + char m_data[RotationCount + 1][StaticBufferSize] {}; + size_t m_rotate = 0; + +public: + StringBuffer () = default; + ~StringBuffer () = default; + +public: + char *chars () { + if (++m_rotate >= RotationCount) { + m_rotate = 0; + } + return m_data[cr::clamp (m_rotate, 0, RotationCount)]; + } + + template U *format (const U *fmt, Args ...args) { + auto buffer = Singleton ::get ().chars (); + snprintf (buffer, StaticBufferSize, fmt, args...); + + return buffer; + } + + template U *format (const U *fmt) { + auto buffer = Singleton ::get ().chars (); + strncpy (buffer, fmt, StaticBufferSize); + + return buffer; + } +}; + +// expose global string pool +static auto &strings = StringBuffer::get (); + +CR_NAMESPACE_END \ No newline at end of file diff --git a/include/crlib/cr-twin.h b/include/crlib/cr-twin.h new file mode 100644 index 0000000..1b3f8ae --- /dev/null +++ b/include/crlib/cr-twin.h @@ -0,0 +1,75 @@ +// +// 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 + +#include +#include + +CR_NAMESPACE_BEGIN + +// simple pair (twin) +template class Twin { +public: + A first; + B second; + +public: + template Twin (T &&a, U &&b) : first (cr::forward (a)), second (cr::forward (b)) { } + template Twin (const Twin &rhs) : first (rhs.first), second (rhs.second) { } + template Twin (Twin &&rhs) noexcept : first (cr::move (rhs.first)), second (cr::move (rhs.second)) { } + +public: + explicit Twin () = default; + ~Twin () = default; + +public: + template Twin &operator = (const Twin &rhs) { + first = rhs.first; + second = rhs.second; + + return *this; + } + + template Twin &operator = (Twin &&rhs) { + first = cr::move (rhs.first); + second = cr::move (rhs.second); + + return *this; + } + + // specialized operators for binary heap, do not use as it's test only second element +public: + friend bool operator < (const Twin &a, const Twin &b) { + return a.second < b.second; + } + + friend bool operator <= (const Twin &a, const Twin &b) { + return a.second <= b.second; + } + + friend bool operator > (const Twin &a, const Twin &b) { + return b.second < a.second; + } + + friend bool operator >= (const Twin &a, const Twin &b) { + return b.second <= a.second; + } +}; + +// creating pairs +template constexpr Twin makeTwin (A &&a, B &&b) { + return Twin (cr::forward (a), cr::forward (b)); +} + +template constexpr Twin makeTwin (const A &a, const B &b) { + return Twin (a, b); +} + +CR_NAMESPACE_END \ No newline at end of file diff --git a/include/compress.h b/include/crlib/cr-ulz.h similarity index 58% rename from include/compress.h rename to include/crlib/cr-ulz.h index c4ee2a8..5b3cb80 100644 --- a/include/compress.h +++ b/include/crlib/cr-ulz.h @@ -9,62 +9,69 @@ #pragma once +#include + +CR_NAMESPACE_BEGIN + // see https://github.com/encode84/ulz/ -class FastLZ final : NonCopyable { +class ULZ final : DenyCopying { 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; - - static constexpr int MIN_MATCH = 4; - static constexpr int MAX_CHAIN = cr::bit (5); - - 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; + enum : int32 { + Excess = 16, + UncompressFailure = -1 + }; private: - int *m_hashTable; - int *m_prevTable; + enum : int32 { + WindowBits = 17, + WindowSize = cr::bit (WindowBits), + WindowMask = WindowSize - 1, + + MinMatch = 4, + MaxChain = cr::bit (5), + + HashBits = 19, + HashLength = cr::bit (HashBits), + EmptyHash = -1, + }; + + +private: + Array m_hashTable; + Array m_prevTable; public: - FastLZ (void) { - m_hashTable = new int[HASH_SIZE]; - m_prevTable = new int[WINDOW_SIZE]; - } - - ~FastLZ (void) { - delete [] m_hashTable; - delete [] m_prevTable; + ULZ () { + m_hashTable.resize (HashLength); + m_prevTable.resize (WindowSize); } + ~ULZ () = default; public: - int compress (uint8 *in, int inLength, uint8 *out) { - for (int i = 0; i < HASH_SIZE; i++) { - m_hashTable[i] = NIL; + int32 compress (uint8 *in, int32 inputLength, uint8 *out) { + for (auto &htb : m_hashTable) { + htb = EmptyHash; } uint8 *op = out; - int anchor = 0; - int cur = 0; + int32 anchor = 0; + int32 cur = 0; - while (cur < inLength) { - const int maxMatch = inLength - cur; + while (cur < inputLength) { + const int32 maxMatch = inputLength - cur; - int bestLength = 0; - int dist = 0; + int32 bestLength = 0; + int32 dist = 0; - if (maxMatch >= MIN_MATCH) { - const int limit = cr::max (cur - WINDOW_SIZE, NIL); + if (maxMatch >= MinMatch) { + const int32 limit = cr::max (cur - WindowSize, EmptyHash); - int chainLength = MAX_CHAIN; - int lookup = m_hashTable[hash32 (&in[cur])]; + int32 chainLength = MaxChain; + int32 lookup = m_hashTable[hash32 (&in[cur])]; while (lookup > limit) { if (in[lookup + bestLength] == in[cur + bestLength] && load32 (&in[lookup]) == load32 (&in[cur])) { - int length = MIN_MATCH; + int32 length = MinMatch; while (length < maxMatch && in[lookup + length] == in[cur + length]) { length++; @@ -83,31 +90,31 @@ public: if (--chainLength == 0) { break; } - lookup = m_prevTable[lookup & WINDOW_MASK]; + lookup = m_prevTable[lookup & WindowMask]; } } - if (bestLength == MIN_MATCH && (cur - anchor) >= (7 + 128)) { + if (bestLength == MinMatch && (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); + if (bestLength >= MinMatch && bestLength < maxMatch && (cur - anchor) != 6) { + const int32 next = cur + 1; + const int32 target = bestLength + 1; + const int32 limit = cr::max (next - WindowSize, EmptyHash); - int chainLength = MAX_CHAIN; - int lookup = m_hashTable[hash32 (&in[next])]; + int32 chainLength = MaxChain; + int32 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; + int32 length = MinMatch; - while (length < targetLength && in[lookup + length] == in[next + length]) { + while (length < target && in[lookup + length] == in[next + length]) { length++; } - if (length == targetLength) { + if (length == target) { bestLength = 0; break; } @@ -116,16 +123,16 @@ public: if (--chainLength == 0) { break; } - lookup = m_prevTable[lookup & WINDOW_MASK]; + lookup = m_prevTable[lookup & WindowMask]; } } - if (bestLength >= MIN_MATCH) { - const int length = bestLength - MIN_MATCH; - const int token = ((dist >> 12) & 16) + cr::min (length, 15); + if (bestLength >= MinMatch) { + const int32 length = bestLength - MinMatch; + const int32 token = ((dist >> 12) & 16) + cr::min (length, 15); if (anchor != cur) { - const int run = cur - anchor; + const int32 run = cur - anchor; if (run >= 7) { add (op, (7 << 5) + token); @@ -150,7 +157,7 @@ public: while (bestLength-- != 0) { const uint32 hash = hash32 (&in[cur]); - m_prevTable[cur & WINDOW_MASK] = m_hashTable[hash]; + m_prevTable[cur & WindowMask] = m_hashTable[hash]; m_hashTable[hash] = cur++; } anchor = cur; @@ -158,13 +165,13 @@ public: else { const uint32 hash = hash32 (&in[cur]); - m_prevTable[cur & WINDOW_MASK] = m_hashTable[hash]; + m_prevTable[cur & WindowMask] = m_hashTable[hash]; m_hashTable[hash] = cur++; } } if (anchor != cur) { - const int run = cur - anchor; + const int32 run = cur - anchor; if (run >= 7) { add (op, 7 << 5); @@ -179,25 +186,25 @@ public: return op - out; } - int uncompress (uint8 *in, int inLength, uint8 *out, int outLength) { + int32 uncompress (uint8 *in, int32 inputLength, uint8 *out, int32 outLength) { uint8 *op = out; uint8 *ip = in; const uint8 *opEnd = op + outLength; - const uint8 *ipEnd = ip + inLength; + const uint8 *ipEnd = ip + inputLength; while (ip < ipEnd) { - const int token = *ip++; + const int32 token = *ip++; if (token >= 32) { - int run = token >> 5; + int32 run = token >> 5; if (run == 7) { run += decode (ip); } if ((opEnd - op) < run || (ipEnd - ip) < run) { - return UNCOMPRESS_RESULT_FAILED; + return UncompressFailure; } copy (op, ip, run); @@ -208,22 +215,22 @@ public: break; } } - int length = (token & 15) + MIN_MATCH; + int32 length = (token & 15) + MinMatch; - if (length == (15 + MIN_MATCH)) { + if (length == (15 + MinMatch)) { length += decode (ip); } if ((opEnd - op) < length) { - return UNCOMPRESS_RESULT_FAILED; + return UncompressFailure; } - const int dist = ((token & 16) << 12) + load16 (ip); + const int32 dist = ((token & 16) << 12) + load16 (ip); ip += 2; uint8 *cp = op - dist; if ((op - out) < dist) { - return UNCOMPRESS_RESULT_FAILED; + return UncompressFailure; } if (dist >= 8) { @@ -232,7 +239,7 @@ public: } else { - for (int i = 0; i < 4; i++) { + for (int32 i = 0; i < 4; ++i) { *op++ = *cp++; } @@ -241,7 +248,7 @@ public: } } } - return (ip == ipEnd) ? op - out : UNCOMPRESS_RESULT_FAILED; + return static_cast (ip == ipEnd) ? static_cast (op - out) : UncompressFailure; } private: @@ -253,7 +260,7 @@ private: return *reinterpret_cast (ptr); } - inline void store16 (void *ptr, int val) { + inline void store16 (void *ptr, int32 val) { *reinterpret_cast (ptr) = static_cast (val); } @@ -262,18 +269,18 @@ private: } inline uint32 hash32 (void *ptr) { - return (load32 (ptr) * 0x9E3779B9) >> (32 - HASH_BITS); + return (load32 (ptr) * 0x9E3779B9) >> (32 - HashBits); } - inline void copy (uint8 *dst, uint8 *src, int count) { + inline void copy (uint8 *dst, uint8 *src, int32 count) { copy64 (dst, src); - for (int i = 8; i < count; i += 8) { + for (int32 i = 8; i < count; i += 8) { copy64 (dst + i, src + i); } } - inline void add (uint8 *&dst, int val) { + inline void add (uint8 *&dst, int32 val) { *dst++ = static_cast (val); } @@ -290,7 +297,7 @@ private: inline uint32 decode (uint8 *&ptr) { uint32 val = 0; - for (int i = 0; i <= 21; i += 7) { + for (int32 i = 0; i <= 21; i += 7) { const uint32 cur = *ptr++; val += cur << i; @@ -300,4 +307,8 @@ private: } return val; } -}; \ No newline at end of file +}; + + + +CR_NAMESPACE_END \ No newline at end of file diff --git a/include/crlib/cr-uniqueptr.h b/include/crlib/cr-uniqueptr.h new file mode 100644 index 0000000..3e6990b --- /dev/null +++ b/include/crlib/cr-uniqueptr.h @@ -0,0 +1,99 @@ +// +// 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 + +#include +#include + +CR_NAMESPACE_BEGIN + +// simple unique ptr +template class UniquePtr final : public DenyCopying { +private: + T *m_ptr = nullptr; + +public: + UniquePtr () = default; + explicit UniquePtr (T *ptr) : m_ptr (ptr) + { } + + UniquePtr (UniquePtr &&rhs) noexcept : m_ptr (rhs.m_ptr) { + rhs.m_ptr = nullptr; + } + + ~UniquePtr () { + destroy (); + } + +public: + T *get () const { + return m_ptr; + } + + T *release () { + auto ret = m_ptr; + m_ptr = nullptr; + + return ret; + } + + void reset (T *ptr = nullptr) { + destroy (); + m_ptr = ptr; + } + +private: + void destroy () { + if (m_ptr) { + alloc.destroy (m_ptr); + m_ptr = nullptr; + } + } + +public: + UniquePtr &operator = (UniquePtr &&rhs) noexcept { + if (this != &rhs) { + destroy (); + + m_ptr = rhs.m_ptr; + rhs.m_ptr = nullptr; + } + return *this; + } + + UniquePtr &operator = (decltype (nullptr)) { + destroy (); + return *this; + } + + T &operator * () const { + return *m_ptr; + } + + T *operator -> () const { + return m_ptr; + } + + explicit operator bool () const { + return m_ptr != nullptr; + } +}; + +// create unique +template UniquePtr createUnique (Args &&... args) { + return UniquePtr (alloc.create (cr::forward (args)...)); +} + +// create unique (base class) +template UniquePtr createUniqueBase (Args &&... args) { + return UniquePtr (alloc.create (cr::forward (args)...)); +} + +CR_NAMESPACE_END \ No newline at end of file diff --git a/include/crlib/cr-vector.h b/include/crlib/cr-vector.h new file mode 100644 index 0000000..e7d2161 --- /dev/null +++ b/include/crlib/cr-vector.h @@ -0,0 +1,232 @@ +// +// 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 + +#include + +CR_NAMESPACE_BEGIN + +// 3dmath vector +class Vector final { +public: + float x = 0.0f, y = 0.0f, z = 0.0f; + +public: + Vector (const float scaler = 0.0f) : x (scaler), y (scaler), z (scaler) + { } + + explicit Vector (const float _x, const float _y, const float _z) : x (_x), y (_y), z (_z) + { } + + Vector (float *rhs) : x (rhs[0]), y (rhs[1]), z (rhs[2]) + { } + + Vector (const Vector &) = default; + +public: + operator float *() { + return &x; + } + + operator const float * () const { + return &x; + } + + Vector operator + (const Vector &rhs) const { + return Vector (x + rhs.x, y + rhs.y, z + rhs.z); + } + + Vector operator - (const Vector &rhs) const { + return Vector (x - rhs.x, y - rhs.y, z - rhs.z); + } + + Vector operator - () const { + return Vector (-x, -y, -z); + } + + friend Vector operator * (const float scale, const Vector &rhs) { + return Vector (rhs.x * scale, rhs.y * scale, rhs.z * scale); + } + + Vector operator * (const float scale) const { + return Vector (scale * x, scale * y, scale * z); + } + + Vector operator / (const float div) const { + const float inv = 1 / div; + return Vector (inv * x, inv * y, inv * z); + } + + // cross product + Vector operator ^ (const Vector &rhs) const { + return Vector (y * rhs.z - z * rhs.y, z * rhs.x - x * rhs.z, x * rhs.y - y * rhs.x); + } + + // dot product + float operator | (const Vector &rhs) const { + return x * rhs.x + y * rhs.y + z * rhs.z; + } + + const Vector &operator += (const Vector &rhs) { + x += rhs.x; + y += rhs.y; + z += rhs.z; + + return *this; + } + + const Vector &operator -= (const Vector &right) { + x -= right.x; + y -= right.y; + z -= right.z; + + return *this; + } + + const Vector &operator *= (float scale) { + x *= scale; + y *= scale; + z *= scale; + + return *this; + } + + const Vector &operator /= (float div) { + const float inv = 1 / div; + + x *= inv; + y *= inv; + z *= inv; + + return *this; + } + + bool operator == (const Vector &rhs) const { + return cr::fequal (x, rhs.x) && cr::fequal (y, rhs.y) && cr::fequal (z, rhs.z); + } + + bool operator != (const Vector &rhs) const { + return !cr::fequal (x, rhs.x) && !cr::fequal (y, rhs.y) && !cr::fequal (z, rhs.z); + } + + Vector &operator = (const Vector &) = default; + +public: + float length () const { + return cr::sqrtf (lengthSq ()); + } + + float length2d () const { + return cr::sqrtf (x * x + y * y); + } + + float lengthSq () const { + return x * x + y * y + z * z; + } + + Vector get2d () const { + return Vector (x, y, 0.0f); + } + + Vector normalize () const { + float len = length () + cr::kFloatCmpEpsilon; + + if (cr::fzero (len)) { + return Vector (0.0f, 0.0f, 1.0f); + } + len = 1.0f / len; + return Vector (x * len, y * len, z * len); + } + + Vector normalize2d () const { + float len = length2d () + cr::kFloatCmpEpsilon; + + if (cr::fzero (len)) { + return Vector (0.0f, 1.0f, 0.0f); + } + len = 1.0f / len; + return Vector (x * len, y * len, 0.0f); + } + + bool empty () const { + return cr::fzero (x) && cr::fzero (y) && cr::fzero (z); + } + + static const Vector &null () { + static const auto s_zero = Vector (0.0f, 0.0f, 0.0f); + return s_zero; + } + + void clear () { + x = y = z = 0.0f; + } + + Vector clampAngles () { + x = cr::normalizeAngles (x); + y = cr::normalizeAngles (y); + z = 0.0f; + + return *this; + } + + float pitch () const { + if (cr::fzero (x) && cr::fzero (y)) { + return 0.0f; + } + return cr::degreesToRadians (cr::atan2f (z, length2d ())); + } + + float yaw () const { + if (cr::fzero (x) && cr::fzero (y)) { + return 0.0f; + } + return cr::radiansToDegrees (cr:: atan2f (y, x)); + } + + Vector angles () const { + if (cr::fzero (x) && cr::fzero (y)) { + return Vector (z > 0.0f ? 90.0f : 270.0f, 0.0, 0.0f); + } + return Vector (cr::radiansToDegrees (cr::atan2f (z, length2d ())), cr::radiansToDegrees (cr::atan2f (y, x)), 0.0f); + } + + void buildVectors (Vector *forward, Vector *right, Vector *upward) const { + enum { pitch, yaw, roll, unused, max }; + + float sines[max] = { 0.0f, 0.0f, 0.0f, 0.0f }; + float cosines[max] = { 0.0f, 0.0f, 0.0f, 0.0f }; + + // compute the sine and cosine compontents + cr::sincosf (cr::degreesToRadians (x), cr::degreesToRadians (y), cr::degreesToRadians (z), sines, cosines); + + if (forward) { + forward->x = cosines[pitch] * cosines[yaw]; + forward->y = cosines[pitch] * sines[yaw]; + forward->z = -sines[pitch]; + } + + if (right) { + right->x = -sines[roll] * sines[pitch] * cosines[yaw] + cosines[roll] * sines[yaw]; + right->y = -sines[roll] * sines[pitch] * sines[yaw] - cosines[roll] * cosines[yaw]; + right->z = -sines[roll] * cosines[pitch]; + } + + if (upward) { + upward->x = cosines[roll] * sines[pitch] * cosines[yaw] + sines[roll] * sines[yaw]; + upward->y = cosines[roll] * sines[pitch] * sines[yaw] - sines[roll] * cosines[yaw]; + upward->z = cosines[roll] * cosines[pitch]; + } + } +}; + +// expose global null vector +static auto &nullvec = Vector::null (); + +CR_NAMESPACE_END \ No newline at end of file diff --git a/include/engine.h b/include/engine.h index c293ad8..f011b8b 100644 --- a/include/engine.h +++ b/include/engine.h @@ -10,95 +10,91 @@ #pragma once // line draw -enum DrawLineType : int { - DRAW_SIMPLE, - DRAW_ARROW, - DRAW_NUM -}; +CR_DECLARE_SCOPED_ENUM (DrawLine, + Simple, + Arrow, + Count +); // trace ignore -enum TraceIgnore : int { - TRACE_IGNORE_NONE = 0, - TRACE_IGNORE_GLASS = cr::bit (0), - TRACE_IGNORE_MONSTERS = cr::bit (1), - TRACE_IGNORE_EVERYTHING = TRACE_IGNORE_GLASS | TRACE_IGNORE_MONSTERS -}; +CR_DECLARE_SCOPED_ENUM (TraceIgnore, + None = 0, + Glass = cr::bit (0), + Monsters = cr::bit (1), + Everything = Glass | Monsters +); // variable type -enum VarType : int { - VT_NORMAL = 0, - VT_READONLY, - VT_PASSWORD, - VT_NOSERVER, - VT_NOREGISTER -}; +CR_DECLARE_SCOPED_ENUM (Var, + Normal = 0, + ReadOnly, + Password, + NoServer, + NoRegister +); // netmessage functions -enum NetMsgId : int { - NETMSG_UNDEFINED = -1, - NETMSG_VGUI = 1, - NETMSG_SHOWMENU = 2, - NETMSG_WEAPONLIST = 3, - NETMSG_CURWEAPON = 4, - NETMSG_AMMOX = 5, - NETMSG_AMMOPICKUP = 6, - NETMSG_DAMAGE = 7, - NETMSG_MONEY = 8, - NETMSG_STATUSICON = 9, - NETMSG_DEATH = 10, - NETMSG_SCREENFADE = 11, - NETMSG_HLTV = 12, - NETMSG_TEXTMSG = 13, - NETMSG_TEAMINFO = 14, - NETMSG_BARTIME = 15, - NETMSG_SENDAUDIO = 17, - NETMSG_SAYTEXT = 18, - NETMSG_BOTVOICE = 19, - NETMSG_NVGTOGGLE = 20, - NETMSG_FLASHBAT = 21, - NETMSG_FLASHLIGHT = 22, - NETMSG_ITEMSTATUS = 23, - NETMSG_NUM = 25 -}; +CR_DECLARE_SCOPED_ENUM (NetMsg, + None = -1, + VGUI = 1, + ShowMenu = 2, + WeaponList = 3, + CurWeapon = 4, + AmmoX = 5, + AmmoPickup = 6, + Damage = 7, + Money = 8, + StatusIcon = 9, + DeathMsg = 10, + ScreenFade = 11, + HLTV = 12, + TextMsg = 13, + TeamInfo = 14, + BarTime = 15, + SendAudio = 17, + SayText = 18, + BotVoice = 19, + NVGToggle = 20, + FlashBat = 21, + Fashlight = 22, + ItemStatus = 23, + Count = 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 -}; - +CR_DECLARE_SCOPED_ENUM (GameFlags, + Modern = cr::bit (0), // counter-strike 1.6 and above + Xash3D = cr::bit (1), // counter-strike 1.6 under the xash engine (additional flag) + ConditionZero = cr::bit (2), // counter-strike: condition zero + Legacy = cr::bit (3), // counter-strike 1.3-1.5 with/without steam + Mobility = cr::bit (4), // additional flag that bot is running on android (additional flag) + CSBot = cr::bit (5), // additional flag that indicates official cs bots are in game + Metamod = cr::bit (6), // game running under meta\mod + CSDM = cr::bit (7), // csdm mod currently in use + FreeForAll = cr::bit (8), // csdm mod with ffa mode + ReGameDLL = cr::bit (9), // server dll is a regamedll + HasFakePings = cr::bit (10), // on that game version we can fake bots pings + HasBotVoice = 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) -}; +CR_DECLARE_SCOPED_ENUM (MapFlags, + Assassination = cr::bit (0), + HostageRescue = cr::bit (1), + Demolition = cr::bit (2), + Escape = cr::bit (3), + KnifeArena = cr::bit (4), + Fun = cr::bit (5), + HasDoors = cr::bit (10) // additional flags +); // variable reg pair struct VarPair { - VarType type; + Var type; cvar_t reg; + bool missing; + const char *regval; class ConVar *self; - - bool regMissing; - const char *regVal; }; // network message block @@ -106,7 +102,7 @@ struct MessageBlock { int bot; int state; int msg; - int regMsgs[NETMSG_NUM]; + int regMsgs[NetMsg::Count]; }; // referentia vector info @@ -117,14 +113,14 @@ struct RefVector { // entity prototype using EntityFunction = void (*) (entvars_t *); -// compare language -struct LangComprarer { - size_t operator () (const String &key) const { - char *str = const_cast (key.chars ()); - size_t hash = key.length (); +// language hasher +struct HashLangString { + uint32 operator () (const String &key) const { + auto str = reinterpret_cast (const_cast (key.chars ())); + uint32 hash = 0; while (*str++) { - if (!isalpha (*str)) { + if (!isalnum (*str)) { continue; } hash = ((*str << 5) + hash) + *str; @@ -136,8 +132,8 @@ struct LangComprarer { // provides utility functions to not call original engine (less call-cost) class Game final : public Singleton { private: - int m_drawModels[DRAW_NUM]; - int m_spawnCount[TEAM_UNASSIGNED]; + int m_drawModels[DrawLine::Count]; + int m_spawnCount[Team::Unassigned]; // bot client command bool m_isBotCommand; @@ -147,9 +143,9 @@ private: edict_t *m_localEntity; Array m_cvars; - HashMap m_language; + Dictionary m_language; - Library m_gameLib; + SharedLibrary m_gameLib; MessageBlock m_msgBlock; bool m_precached; @@ -161,33 +157,18 @@ public: RefVector vec; public: - Game (void); - ~Game (void); + Game (); + ~Game (); public: // precaches internal stuff - void precache (void); + void precache (); // initialize levels void levelInitialize (edict_t *ents, int max); - // prints data to servers console - void print (const char *fmt, ...); - - // prints chat message to all players - void chatPrint (const char *fmt, ...); - - // 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, ...); - // display world line - void 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 = DRAW_SIMPLE); + void drawLine (edict_t *ent, const Vector &start, const Vector &end, int width, int noise, const Color &color, int brightness, int speed, int life, DrawLine type = DrawLine::Simple); // test line void testLine (const Vector &start, const Vector &end, int ignoreFlags, edict_t *ignoreEntity, TraceResult *ptr); @@ -199,34 +180,31 @@ public: float getWaveLen (const char *fileName); // we are on dedicated server ? - bool isDedicated (void); + bool isDedicated (); // get stripped down mod name - const char *getModName (void); + const char *getModName (); // get the valid mapname - const char *getMapName (void); + const char *getMapName (); // get the "any" entity origin Vector getAbsPos (edict_t *ent); - // send server command - void execCmd (const char *fmt, ...); - // registers a server command - void registerCmd (const char *command, void func (void)); + void registerCmd (const char *command, void func_ ()); // play's sound to client void playSound (edict_t *ent, const char *sound); // sends bot command - void execBotCmd (edict_t *ent, const char *fmt, ...); + void prepareBotArgs (edict_t *ent, String str); // adds cvar to registration stack - void pushVarToRegStack (const char *variable, const char *value, VarType varType, bool regMissing, const char *regVal, ConVar *self); + void addNewCvar (const char *variable, const char *value, Var varType, bool regMissing, const char *regVal, class ConVar *self); // sends local registration stack for engine registration - void pushRegStackToEngine (bool gameVars = false); + void registerCvars (bool gameVars = false); // translates bot message into needed language const char *translate (const char *input); @@ -235,19 +213,19 @@ public: void processMessages (void *ptr); // checks whether softwared rendering is enabled - bool isSoftwareRenderer (void); + bool isSoftwareRenderer (); // load the cs binary in non metamod mode - bool loadCSBinary (void); + bool loadCSBinary (); // do post-load stuff - bool postload (void); + bool postload (); // detects if csdm mod is in use - void detectDeathmatch (void); + void detectDeathmatch (); // executes stuff every 1 second - void slowFrame (void); + void slowFrame (); // begin message handler void beginMessage (edict_t *ent, int dest, int type); @@ -255,26 +233,23 @@ public: // public inlines public: // get the current time on server - float timebase (void) const { + float timebase () const { return globals->time; } // get "maxplayers" limit on server - int maxClients (void) const { + int maxClients () const { return globals->maxClients; } // get the fakeclient command interface - bool isBotCmd (void) const { + bool isBotCmd () const { return m_isBotCommand; } // gets custom engine args for client command - 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 (); + const char *botArgs () const { + return strings.format (String::join (m_botArgs, " ", m_botArgs[0] == "say" || m_botArgs[0] == "say_team" ? 1 : 0).chars ()); } // gets custom engine argv for client command @@ -286,7 +261,7 @@ public: } // gets custom engine argc for client command - int botArgc (void) const { + int botArgc () const { return m_botArgs.length (); } @@ -295,18 +270,28 @@ public: return static_cast (m_startEntity + index); }; + // gets edict pointer out of entity index (player) + edict_t *playerOfIndex (const int index) { + return entityOfIndex (index) + 1; + }; + // gets edict index out of it's pointer int indexOfEntity (const edict_t *ent) { return static_cast (ent - m_startEntity); }; + // gets edict index of it's pointer (player) + int indexOfPlayer (const edict_t *ent) { + return indexOfEntity (ent) - 1; + } + // verify entity isn't null bool isNullEntity (const edict_t *ent) { return !ent || !indexOfEntity (ent) || ent->free; } // get the wroldspawn entity - edict_t *getStartEntity (void) { + edict_t *getStartEntity () { return m_startEntity; } @@ -320,12 +305,17 @@ public: // adds translation pair from config void addTranslation (const String &original, const String &translated) { - m_language.put (original, translated); + m_language.push (original, translated); + } + + // clear the translation table + void clearTranslation () { + m_language.clear (); } // resets the message capture mechanism - void resetMessages (void) { - m_msgBlock.msg = NETMSG_UNDEFINED; + void resetMessages () { + m_msgBlock.msg = NetMsg::None; m_msgBlock.state = 0; m_msgBlock.bot = 0; }; @@ -358,12 +348,12 @@ public: } // sets the precache to uninitialize - void setUnprecached (void) { + void setUnprecached () { m_precached = false; } // gets the local entity (host edict) - edict_t *getLocalEntity (void) { + edict_t *getLocalEntity () { return m_localEntity; } @@ -374,17 +364,12 @@ public: // 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; + in.buildVectors (&vec.forward, &vec.right, &vec.up); } // what kind of game engine / game dll / mod / tool we're running ? bool is (const int type) const { - return (m_gameFlags & type) == type; + return !!(m_gameFlags & type); } // adds game flag @@ -394,43 +379,79 @@ public: // gets the map type bool mapIs (const int type) const { - return (m_mapFlags & type) == type; + return !!(m_mapFlags & type); } // get loaded gamelib - Library &getLib (void) { + const SharedLibrary &lib () { return m_gameLib; } + + // helper to sending the client message + void sendClientMessage (bool console, edict_t *ent, const char *message); + + // send server command + template void serverCommand (const char *fmt, Args ...args) { + engfuncs.pfnServerCommand (strncat (strings.format (fmt, cr::forward (args)...), "\n", StringBuffer::StaticBufferSize)); + } + + // send a bot command + template void botCommand (edict_t *ent, const char *fmt, Args ...args) { + prepareBotArgs (ent, strings.format (fmt, cr::forward (args)...)); + } + + // prints data to servers console + template void print (const char *fmt, Args ...args) { + engfuncs.pfnServerPrint (strncat (strings.format (translate (fmt), cr::forward (args)...), "\n", StringBuffer::StaticBufferSize)); + } + + // prints center message to specified player + template void clientPrint (edict_t *ent, const char *fmt, Args ...args) { + if (isNullEntity (ent)) { + print (fmt, cr::forward (args)...); + return; + } + sendClientMessage (true, ent, strncat (strings.format (translate (fmt), cr::forward (args)...), "\n", StringBuffer::StaticBufferSize)); + } + + // prints message to client console + template void centerPrint (edict_t *ent, const char *fmt, Args ...args) { + if (isNullEntity (ent)) { + print (fmt, cr::forward (args)...); + return; + } + sendClientMessage (false, ent, strncat (strings.format (translate (fmt), cr::forward (args)...), "\n", StringBuffer::StaticBufferSize)); + } }; // simplify access for console variables class ConVar { public: - cvar_t *m_eptr; + cvar_t *eptr; public: - ConVar (const char *name, const char *initval, VarType type = VT_NOSERVER, bool regMissing = false, const char *regVal = nullptr) : m_eptr (nullptr) { - Game::ref ().pushVarToRegStack (name, initval, type, regMissing, regVal, this); + ConVar (const char *name, const char *initval, Var type = Var::NoServer, bool regMissing = false, const char *regVal = nullptr) : eptr (nullptr) { + Game::get ().addNewCvar (name, initval, type, regMissing, regVal, this); } - bool boolean (void) const { - return m_eptr->value > 0.0f; + bool bool_ () const { + return eptr->value > 0.0f; } - int integer (void) const { - return static_cast (m_eptr->value); + int int_ () const { + return static_cast (eptr->value); } - float flt (void) const { - return m_eptr->value; + float float_ () const { + return eptr->value; } - const char *str (void) const { - return m_eptr->string; + const char *str () const { + return eptr->string; } void set (float val) { - engfuncs.pfnCVarSetFloat (m_eptr->name, val); + engfuncs.pfnCVarSetFloat (eptr->name, val); } void set (int val) { @@ -438,7 +459,7 @@ public: } void set (const char *val) { - engfuncs.pfnCvar_DirectSet (m_eptr, const_cast (val)); + engfuncs.pfnCvar_DirectSet (eptr, const_cast (val)); } }; @@ -447,26 +468,26 @@ private: bool m_autoDestruct { false }; public: - MessageWriter (void) = default; + MessageWriter () = default; - MessageWriter (int dest, int type, const Vector &pos = Vector::null (), edict_t *to = nullptr) { + MessageWriter (int dest, int type, const Vector &pos = nullvec, edict_t *to = nullptr) { start (dest, type, pos, to); m_autoDestruct = true; } - virtual ~MessageWriter (void) { + ~MessageWriter () { if (m_autoDestruct) { end (); } } public: - MessageWriter &start (int dest, int type, const Vector &pos = Vector::null (), edict_t *to = nullptr) { + MessageWriter &start (int dest, int type, const Vector &pos = nullvec, edict_t *to = nullptr) { engfuncs.pfnMessageBegin (dest, type, pos, to); return *this; } - void end (void) { + void end () { engfuncs.pfnMessageEnd (); } @@ -475,6 +496,11 @@ public: return *this; } + MessageWriter &writeLong (int val) { + engfuncs.pfnWriteLong (val); + return *this; + } + MessageWriter &writeChar (int val) { engfuncs.pfnWriteChar (val); return *this; @@ -497,11 +523,11 @@ public: public: static inline uint16 fu16 (float value, float scale) { - return cr::clamp (static_cast (value * scale), 0, 0xffff); + return cr::clamp (static_cast (value * cr::bit (static_cast (scale))), 0, 0xffff); } static inline short fs16 (float value, float scale) { - return cr::clamp (static_cast (value * scale), -32767, 32767); + return cr::clamp (static_cast (value * cr::bit (static_cast (scale))), -32767, 32767); } }; @@ -511,28 +537,28 @@ private: int m_lightstyleValue[MAX_LIGHTSTYLEVALUE]; bool m_doAnimation = false; - SimpleColor m_point; + Color m_point; model_t *m_worldModel = nullptr; public: - LightMeasure (void) { + LightMeasure () { initializeLightstyles (); m_point.reset (); } public: - void initializeLightstyles (void); - void animateLight (void); + void initializeLightstyles (); + void animateLight (); void updateLight (int style, char *value); float getLightLevel (const Vector &point); - float getSkyColor (void); + float getSkyColor (); private: template bool recursiveLightPoint (const M *node, const Vector &start, const Vector &end); public: - void resetWorldModel (void) { + void resetWorldModel () { m_worldModel = nullptr; } diff --git a/include/engine/eiface.h b/include/engine/eiface.h index 586ba5a..8e0bd8a 100644 --- a/include/engine/eiface.h +++ b/include/engine/eiface.h @@ -85,6 +85,25 @@ typedef struct { int iHitgroup; // 0 == generic, non zero is specific body part } TraceResult; +typedef struct usercmd_s { + short lerp_msec; // Interpolation time on client + byte msec; // Duration in ms of command + vec3_t viewangles; // Command view angles. + +// intended velocities + float forwardmove; // Forward velocity. + float sidemove; // Sideways velocity. + float upmove; // Upward velocity. + byte lightlevel; // Light level at spot where we are standing. + unsigned short buttons; // Attack buttons + byte impulse; // Impulse command issued. + byte weaponselect; // Current weapon id + +// Experimental player impact stuff. + int impact_index; + vec3_t impact_position; +} usercmd_t; + typedef uint32 CRC32_t; // Engine hands this to DLLs for functionality callbacks @@ -111,7 +130,7 @@ typedef struct enginefuncs_s { edict_t *(*pfnEntitiesInPVS) (edict_t *pplayer); void (*pfnMakeVectors) (const float *rgflVector); void (*pfnAngleVectors) (const float *rgflVector, float *forward, float *right, float *up); - edict_t *(*pfnCreateEntity) (void); + edict_t *(*pfnCreateEntity) (); void (*pfnRemoveEntity) (edict_t *e); edict_t *(*pfnCreateNamedEntity) (int className); void (*pfnMakeStatic) (edict_t *ent); @@ -130,14 +149,14 @@ typedef struct enginefuncs_s { void (*pfnTraceSphere) (const float *v1, const float *v2, int fNoMonsters, float radius, edict_t *pentToSkip, TraceResult *ptr); void (*pfnGetAimVector) (edict_t *ent, float speed, float *rgflReturn); void (*pfnServerCommand) (char *str); - void (*pfnServerExecute) (void); + void (*pfnServerExecute) (); void (*pfnClientCommand) (edict_t *ent, char const *szFmt, ...); void (*pfnParticleEffect) (const float *org, const float *dir, float color, float count); void (*pfnLightStyle) (int style, char *val); int (*pfnDecalIndex) (const char *name); int (*pfnPointContents) (const float *rgflVector); void (*pfnMessageBegin) (int msg_dest, int msg_type, const float *pOrigin, edict_t *ed); - void (*pfnMessageEnd) (void); + void (*pfnMessageEnd) (); void (*pfnWriteByte) (int value); void (*pfnWriteChar) (int value); void (*pfnWriteShort) (int value); @@ -151,7 +170,7 @@ typedef struct enginefuncs_s { const char *(*pfnCVarGetString) (const char *szVarName); void (*pfnCVarSetFloat) (const char *szVarName, float flValue); void (*pfnCVarSetString) (const char *szVarName, const char *szValue); - void (*pfnAlertMessage) (ALERT_TYPE atype, char *szFmt, ...); + void (*pfnAlertMessage) (ALERT_TYPE atype, const char *szFmt, ...); void (*pfnEngineFprintf) (void *pfile, char *szFmt, ...); void *(*pfnPvAllocEntPrivateData) (edict_t *ent, int32 cb); void *(*pfnPvEntPrivateData) (edict_t *ent); @@ -172,9 +191,9 @@ typedef struct enginefuncs_s { const char *(*pfnNameForFunction) (uint32 function); void (*pfnClientPrintf) (edict_t *ent, PRINT_TYPE ptype, const char *szMsg); // JOHN: engine callbacks so game DLL can print messages to individual clients void (*pfnServerPrint) (const char *szMsg); - const char *(*pfnCmd_Args) (void); // these 3 added + const char *(*pfnCmd_Args) (); // these 3 added const char *(*pfnCmd_Argv) (int argc); // so game DLL can easily - int (*pfnCmd_Argc) (void); // access client 'cmd' strings + int (*pfnCmd_Argc) (); // access client 'cmd' strings void (*pfnGetAttachment) (const edict_t *ent, int iAttachment, float *rgflOrigin, float *rgflAngles); void (*pfnCRC32_Init) (CRC32_t *pulCRC); void (*pfnCRC32_ProcessBuffer) (CRC32_t *pulCRC, void *p, int len); @@ -183,7 +202,7 @@ typedef struct enginefuncs_s { int32 (*pfnRandomLong) (int32 lLow, int32 lHigh); float (*pfnRandomFloat) (float flLow, float flHigh); void (*pfnSetView) (const edict_t *client, const edict_t *pViewent); - float (*pfnTime) (void); + float (*pfnTime) (); void (*pfnCrosshairAngle) (const edict_t *client, float pitch, float yaw); uint8 *(*pfnLoadFileForMe) (char const *szFilename, int *pLength); void (*pfnFreeFile) (void *buffer); @@ -195,7 +214,7 @@ typedef struct enginefuncs_s { void (*pfnSetClientMaxspeed) (const edict_t *ent, float fNewMaxspeed); edict_t *(*pfnCreateFakeClient) (const char *netname); // returns nullptr if fake client can't be created void (*pfnRunPlayerMove) (edict_t *fakeclient, const float *viewangles, float forwardmove, float sidemove, float upmove, uint16 buttons, uint8 impulse, uint8 msec); - int (*pfnNumberOfEntities) (void); + int (*pfnNumberOfEntities) (); char *(*pfnGetInfoKeyBuffer) (edict_t *e); // passing in nullptr gets the serverinfo char *(*pfnInfoKeyValue) (char *infobuffer, char const *key); void (*pfnSetKeyValue) (char *infobuffer, char *key, char *value); @@ -205,7 +224,7 @@ typedef struct enginefuncs_s { int (*pfnPrecacheGeneric) (char *s); int (*pfnGetPlayerUserId) (edict_t *e); // returns the server assigned userid for this player. useful for logging frags, etc. returns -1 if the edict couldn't be found in the list of clients void (*pfnBuildSoundMsg) (edict_t *entity, int channel, const char *sample, float volume, float attenuation, int fFlags, int pitch, int msg_dest, int msg_type, const float *pOrigin, edict_t *ed); - int (*pfnIsDedicatedServer) (void); // is this a dedicated server? + int (*pfnIsDedicatedServer) (); // is this a dedicated server? cvar_t *(*pfnCVarGetPointer) (const char *szVarName); unsigned int (*pfnGetPlayerWONId) (edict_t *e); // returns the server assigned WONid for this player. useful for logging frags, etc. returns -1 if the edict couldn't be found in the list of clients @@ -221,7 +240,7 @@ typedef struct enginefuncs_s { void (*pfnDeltaSetField) (struct delta_s *pFields, const char *fieldname); void (*pfnDeltaUnsetField) (struct delta_s *pFields, const char *fieldname); void (*pfnDeltaAddEncoder) (char *name, void (*conditionalencode) (struct delta_s *pFields, const uint8 *from, const uint8 *to)); - int (*pfnGetCurrentPlayer) (void); + int (*pfnGetCurrentPlayer) (); int (*pfnCanSkipPlayer) (const edict_t *player); int (*pfnDeltaFindField) (struct delta_s *pFields, const char *fieldname); void (*pfnDeltaSetFieldByIndex) (struct delta_s *pFields, int fieldNumber); @@ -231,7 +250,7 @@ typedef struct enginefuncs_s { void (*pfnCvar_DirectSet) (struct cvar_t *var, char *value); void (*pfnForceUnmodified) (FORCE_TYPE type, float *mins, float *maxs, const char *szFilename); void (*pfnGetPlayerStats) (const edict_t *client, int *ping, int *packet_loss); - void (*pfnAddServerCommand) (char *cmd_name, void (*function) (void)); + void (*pfnAddServerCommand) (char *cmd_name, void (*function) ()); int (*pfnVoice_GetClientListening) (int iReceiver, int iSender); int (*pfnVoice_SetClientListening) (int iReceiver, int iSender, int bListen); @@ -244,18 +263,20 @@ typedef struct enginefuncs_s { int (*pfnGetFileSize) (char *szFilename); unsigned int (*pfnGetApproxWavePlayLen) (const char *filepath); - int (*pfnIsCareerMatch) (void); + int (*pfnIsCareerMatch) (); int (*pfnGetLocalizedStringLength) (const char *label); void (*pfnRegisterTutorMessageShown) (int mid); int (*pfnGetTimesTutorMessageShown) (int mid); void (*pfnProcessTutorMessageDecayBuffer) (int *buffer, int bufferLength); void (*pfnConstructTutorMessageDecayBuffer) (int *buffer, int bufferLength); - void (*pfnResetTutorMessageDecayData) (void); + void (*pfnResetTutorMessageDecayData) (); void (*pfnQueryClientCVarValue) (const edict_t *player, const char *cvarName); void (*pfnQueryClientCVarValue2) (const edict_t *player, const char *cvarName, int requestID); int (*pfnCheckParm) (const char *pchCmdLineToken, char **ppnext); - +#ifdef EIFACE_2019 + edict_t *(*pfnPEntityOfEntIndexAllEntities) (int iEntIndex); +#endif } enginefuncs_t; // Passed to pfnKeyValue @@ -270,7 +291,7 @@ typedef struct customization_s customization_t; typedef struct { // Initialize/shutdown the game (one-time call after loading of game .dll ) - void (*pfnGameInit) (void); + void (*pfnGameInit) (); int (*pfnSpawn) (edict_t *pent); void (*pfnThink) (edict_t *pent); void (*pfnUse) (edict_t *pentUsed, edict_t *pentOther); @@ -286,7 +307,7 @@ typedef struct { void (*pfnSaveGlobalState) (SAVERESTOREDATA *); void (*pfnRestoreGlobalState) (SAVERESTOREDATA *); - void (*pfnResetGlobalState) (void); + void (*pfnResetGlobalState) (); int (*pfnClientConnect) (edict_t *ent, const char *pszName, const char *pszAddress, char szRejectReason[128]); @@ -297,17 +318,17 @@ typedef struct { void (*pfnClientUserInfoChanged) (edict_t *ent, char *infobuffer); void (*pfnServerActivate) (edict_t *edictList, int edictCount, int clientMax); - void (*pfnServerDeactivate) (void); + void (*pfnServerDeactivate) (); void (*pfnPlayerPreThink) (edict_t *ent); void (*pfnPlayerPostThink) (edict_t *ent); - void (*pfnStartFrame) (void); - void (*pfnParmsNewLevel) (void); - void (*pfnParmsChangeLevel) (void); + void (*pfnStartFrame) (); + void (*pfnParmsNewLevel) (); + void (*pfnParmsChangeLevel) (); // Returns string describing current .dll. E.g., TeamFotrress 2, Half-Life - const char *(*pfnGetGameDescription) (void); + const char *(*pfnGetGameDescription) (); // Notify dll about a player customization. void (*pfnPlayerCustomization) (edict_t *ent, struct customization_s *pCustom); @@ -327,10 +348,10 @@ typedef struct { void (*pfnUpdateClientData) (const struct edict_s *ent, int sendweapons, struct clientdata_s *cd); int (*pfnAddToFullPack) (struct entity_state_s *state, int e, edict_t *ent, edict_t *host, int hostflags, int player, uint8 *pSet); void (*pfnCreateBaseline) (int player, int eindex, struct entity_state_s *baseline, struct edict_s *entity, int playermodelindex, float *player_mins, float *player_maxs); - void (*pfnRegisterEncoders) (void); + void (*pfnRegisterEncoders) (); int (*pfnGetWeaponData) (struct edict_s *player, struct weapon_data_s *info); - void (*pfnCmdStart) (const edict_t *player, const struct c *cmd, unsigned int random_seed); + void (*pfnCmdStart) (const edict_t *player, usercmd_t *cmd, unsigned int random_seed); void (*pfnCmdEnd) (const edict_t *player); // Return 1 if the packet is valid. Set response_buffer_size if you want to send a response packet. Incoming, it holds the max @@ -341,7 +362,7 @@ typedef struct { int (*pfnGetHullBounds) (int hullnumber, float *mins, float *maxs); // Create baselines for certain "unplaced" items. - void (*pfnCreateInstancedBaselines) (void); + void (*pfnCreateInstancedBaselines) (); // One of the pfnForceUnmodified files failed the consistency check for the specified player // Return 0 to allow the client to continue, 1 to force immediate disconnection ( with an optional disconnect message of up to 256 characters ) @@ -351,7 +372,7 @@ typedef struct { // the sv_unlag cvar. // Most games right now should return 0, until client-side weapon prediction code is written // and tested for them. - int (*pfnAllowLagCompensation) (void); + int (*pfnAllowLagCompensation) (); } gamefuncs_t; // Current version. @@ -361,7 +382,7 @@ typedef struct { // Called right before the object's memory is freed. // Calls its destructor. void (*pfnOnFreeEntPrivateData) (edict_t *pEnt); - void (*pfnGameShutdown) (void); + void (*pfnGameShutdown) (); int (*pfnShouldCollide) (edict_t *pentTouched, edict_t *pentOther); void (*pfnCvarValue) (const edict_t *pEnt, const char *value); diff --git a/include/engine/extdll.h b/include/engine/extdll.h index eb5a0e6..9a73784 100644 --- a/include/engine/extdll.h +++ b/include/engine/extdll.h @@ -54,9 +54,9 @@ typedef int func_t; // typedef int string_t; // from engine's pr_comp.h; typedef float vec_t; // needed before including progdefs.h -#include "corelib.h" +#include -typedef cr::classes::Vector vec3_t; +typedef cr::Vector vec3_t; using namespace cr::types; #include "const.h" diff --git a/include/engine/util.h b/include/engine/util.h index ee5b56a..26f160d 100644 --- a/include/engine/util.h +++ b/include/engine/util.h @@ -24,6 +24,7 @@ extern gamefuncs_t dllapi; #define STRING(offset) (const char *)(globals->pStringBase + (int)offset) // form fwgs-hlsdk +#if defined (CR_ARCH_X64) static inline int MAKE_STRING (const char *val) { long long ptrdiff = val - STRING (0); @@ -32,6 +33,9 @@ static inline int MAKE_STRING (const char *val) { } return static_cast (ptrdiff); } +#else +#define MAKE_STRING(str) ((uint64)(str) - (uint64)(STRING(0))) +#endif #define ENGINE_STR(str) (const_cast (STRING (engfuncs.pfnAllocString (str)))) @@ -93,17 +97,17 @@ typedef struct hudtextparms_s { #define PUSH_BLOCK_ONLY_X 1 #define PUSH_BLOCK_ONLY_Y 2 -#define VEC_HULL_MIN Vector (-16, -16, -36) -#define VEC_HULL_MAX Vector (16, 16, 36) -#define VEC_HUMAN_HULL_MIN Vector (-16, -16, 0) -#define VEC_HUMAN_HULL_MAX Vector (16, 16, 72) -#define VEC_HUMAN_HULL_DUCK Vector (16, 16, 36) +#define VEC_HULL_MIN Vector(-16, -16, -36) +#define VEC_HULL_MAX Vector(16, 16, 36) +#define VEC_HUMAN_HULL_MIN Vector(-16, -16, 0) +#define VEC_HUMAN_HULL_MAX Vector(16, 16, 72) +#define VEC_HUMAN_HULL_DUCK Vector(16, 16, 36) -#define VEC_VIEW Vector (0, 0, 28) +#define VEC_VIEW Vector(0, 0, 28) -#define VEC_DUCK_HULL_MIN Vector (-16, -16, -18) -#define VEC_DUCK_HULL_MAX Vector (16, 16, 18) -#define VEC_DUCK_VIEW Vector (0, 0, 12) +#define VEC_DUCK_HULL_MIN Vector(-16, -16, -18) +#define VEC_DUCK_HULL_MAX Vector(16, 16, 18) +#define VEC_DUCK_VIEW Vector(0, 0, 12) #define SVC_TEMPENTITY 23 #define SVC_CENTERPRINT 26 diff --git a/include/platform.h b/include/platform.h deleted file mode 100644 index 6f971bb..0000000 --- a/include/platform.h +++ /dev/null @@ -1,97 +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 - -// detects the build platform -#if defined(__linux__) - #define PLATFORM_LINUX -#elif defined(__APPLE__) - #define PLATFORM_OSX -#elif defined(_WIN32) - #define PLATFORM_WIN32 -#endif - -// by default sse has everyone -#define PLATFORM_HAS_SSE2 - -// detects the compiler -#if defined(_MSC_VER) - #define CXX_MSVC -#elif defined(__clang__) - #define CXX_CLANG -#endif - -// configure export macros -#if defined(PLATFORM_WIN32) - #define SHARED_LIBRARAY_EXPORT extern "C" __declspec (dllexport) -#elif defined(PLATFORM_LINUX) || defined(PLATFORM_OSX) - #define SHARED_LIBRARAY_EXPORT extern "C" __attribute__ ((visibility ("default"))) -#else - #error "Can't configure export macros. Compiler unrecognized." -#endif - -// operating system specific macros, functions and typedefs -#ifdef PLATFORM_WIN32 - - #include - #include - - #define STD_CALL __stdcall - - #define DLL_ENTRYPOINT int STD_CALL DllMain (HINSTANCE, DWORD dwReason, LPVOID) - #define DLL_DETACHING (dwReason == DLL_PROCESS_DETACH) - #define DLL_RETENTRY return TRUE - - #if defined(CXX_MSVC) && !defined (_M_X64) - #define DLL_GIVEFNPTRSTODLL extern "C" void STD_CALL - #elif defined(CXX_CLANG) || defined (_M_X64) - #define DLL_GIVEFNPTRSTODLL SHARED_LIBRARAY_EXPORT void STD_CALL - #endif - - // specify export parameter - #if defined(CXX_MSVC) || defined (CXX_CLANG) - #if !defined (_M_X64) - #pragma comment(linker, "/EXPORT:GiveFnptrsToDll=_GiveFnptrsToDll@8,@1") - #endif - #pragma comment(linker, "/SECTION:.data,RW") - #endif - -#elif defined(PLATFORM_LINUX) || defined(PLATFORM_OSX) - #include - #include - #include - #include - #include - - #include - #include - #include - #include - #include - - #define DLL_ENTRYPOINT __attribute__ ((destructor)) void _fini (void) - #define DLL_DETACHING TRUE - #define DLL_RETENTRY return - #define DLL_GIVEFNPTRSTODLL extern "C" void __attribute__ ((visibility ("default"))) - - #define STD_CALL /* */ - - // android is a linux with a special cases - // @todo: sse should be working ok on x86 android? - #if defined(__ANDROID__) - #define PLATFORM_ANDROID - - #if defined (__arm__) || defined (__aarch64__ ) - #undef PLATFORM_HAS_SSE2 - #endif - #endif -#else - #error "Platform unrecognized." -#endif diff --git a/include/resource.h b/include/resource.h index 6d17956..ebb4ac4 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.91" +#define PRODUCT_VERSION "2.92" #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, 91 +#define PRODUCT_VERSION_DWORD_INTERNAL 2, 92 #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 d63b199..e51cb15 100644 --- a/include/yapb.h +++ b/include/yapb.h @@ -9,521 +9,522 @@ #pragma once -#include -#include -#include -#include +#include +#include -using namespace cr::types; -using namespace cr::classes; +#include -#include -#include -#include -#include +// use all the cr-library +using namespace cr; -#include #include // defines bots tasks -enum TaskID : int { - TASK_NORMAL, - TASK_PAUSE, - TASK_MOVETOPOSITION, - TASK_FOLLOWUSER, - TASK_PICKUPITEM, - TASK_CAMP, - TASK_PLANTBOMB, - TASK_DEFUSEBOMB, - TASK_ATTACK, - TASK_HUNTENEMY, - TASK_SEEKCOVER, - TASK_THROWHEGRENADE, - TASK_THROWFLASHBANG, - TASK_THROWSMOKE, - TASK_DOUBLEJUMP, - TASK_ESCAPEFROMBOMB, - TASK_SHOOTBREAKABLE, - TASK_HIDE, - TASK_BLINDED, - TASK_SPRAY, - TASK_MAX -}; +CR_DECLARE_SCOPED_ENUM (Task, + Normal = 0, + Pause, + MoveToPosition, + FollowUser, + PickupItem, + Camp, + PlantBomb, + DefuseBomb, + Attack, + Hunt, + SeekCover, + ThrowExplosive, + ThrowFlashbang, + ThrowSmoke, + DoubleJump, + EscapeFromBomb, + ShootBreakable, + Hide, + Blind, + Spraypaint, +); // bot menu ids -enum MenuId : int { - BOT_MENU_INVALID = 0, - BOT_MENU_MAIN, - BOT_MENU_FEATURES, - BOT_MENU_CONTROL, - BOT_MENU_WEAPON_MODE, - BOT_MENU_PERSONALITY, - BOT_MENU_DIFFICULTY, - BOT_MENU_TEAM_SELECT, - BOT_MENU_TERRORIST_SELECT, - BOT_MENU_CT_SELECT, - BOT_MENU_COMMANDS, - BOT_MENU_WAYPOINT_MAIN_PAGE1, - BOT_MENU_WAYPOINT_MAIN_PAGE2, - BOT_MENU_WAYPOINT_RADIUS, - BOT_MENU_WAYPOINT_TYPE, - BOT_MENU_WAYPOINT_FLAG, - BOT_MENU_WAYPOINT_AUTOPATH, - BOT_MENU_WAYPOINT_PATH, - BOT_MENU_KICK_PAGE_1, - BOT_MENU_KICK_PAGE_2, - BOT_MENU_KICK_PAGE_3, - BOT_MENU_KICK_PAGE_4, - BOT_MENU_TOTAL_MENUS -}; +CR_DECLARE_SCOPED_ENUM (Menu, + None = 0, + Main, + Features, + Control, + WeaponMode, + Personality, + Difficulty, + TeamSelect, + TerroristSelect, + CTSelect, + Commands, + NodeMainPage1, + NodeMainPage2, + NodeRadius, + NodeType, + NodeFlag, + NodeAutoPath, + NodePath, + KickPage1, + KickPage2, + KickPage3, + KickPage4, +); // 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 : 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!) -}; +CR_DECLARE_SCOPED_ENUM (BombPlantedSay, + ChatSay = cr::bit (1), + Chatter = cr::bit (2) +); // chat types id's -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 - CHAT_TEAMATTACK, // id to team-attack chat array - CHAT_TEAMKILL, // id to team-kill chat array - CHAT_WELCOME, // id to welcome chat array - CHAT_NOKW, // id to no keyword chat array - CHAT_TOTAL // number for array -}; +CR_DECLARE_SCOPED_ENUM (Chat, + Kill = 0, // id to kill chat array + Dead, // id to dead chat array + Plant, // id to bomb chat array + TeamAttack, // id to team-attack chat array + TeamKill, // id to team-kill chat array + Hello, // id to welcome chat array + NoKeyword, // id to no keyword chat array + Count // number for array +); // personalities defines -enum Personality : int { - PERSONALITY_NORMAL = 0, - PERSONALITY_RUSHER, - PERSONALITY_CAREFUL -}; +CR_DECLARE_SCOPED_ENUM (Personality, + Normal = 0, + Rusher, + Careful, + Invalid = -1 +); // bot difficulties -enum Difficulty : int { - DIFFICULTY_VERY_EASY, - DIFFICULTY_EASY, - DIFFICULTY_NORMAL, - DIFFICULTY_HARD, - DIFFICULTY_VERY_HARD -}; +CR_DECLARE_SCOPED_ENUM (Difficulty, + VeryEasy, + Easy, + Normal, + Hard, + Extreme, + Invalid = -1 +); // collision states -enum CollisionState : int { - COLLISION_NOTDECICED, - COLLISION_PROBING, - COLLISION_NOMOVE, - COLLISION_JUMP, - COLLISION_DUCK, - COLLISION_STRAFELEFT, - COLLISION_STRAFERIGHT -}; +CR_DECLARE_SCOPED_ENUM (CollisionState, + Undecided, + Probing, + NoMove, + Jump, + Duck, + StrafeLeft, + StrafeRight +); // counter-strike team id's -enum Team : int { - TEAM_TERRORIST = 0, - TEAM_COUNTER, - TEAM_SPECTATOR, - TEAM_UNASSIGNED -}; +CR_DECLARE_SCOPED_ENUM (Team, + Terrorist = 0, + CT, + Spectator, + Unassigned, + Invalid = -1 +); + +// item status for StatusIcon message +CR_DECLARE_SCOPED_ENUM (ItemStatus, + Nightvision = cr::bit (0), + DefusalKit = cr::bit (1) +); // client flags -enum ClientFlags : int { - CF_USED = cr::bit (0), - CF_ALIVE = cr::bit (1), - CF_ADMIN = cr::bit (2), - CF_ICON = cr::bit (3) -}; +CR_DECLARE_SCOPED_ENUM (ClientFlags, + Used = cr::bit (0), + Alive = cr::bit (1), + Admin = cr::bit (2), + Icon = cr::bit (3) +); // bot create status -enum BotCreationResult : int { - BOT_RESULT_CREATED, - BOT_RESULT_MAX_PLAYERS_REACHED, - BOT_RESULT_NAV_ERROR, - BOT_RESULT_TEAM_STACKED -}; +CR_DECLARE_SCOPED_ENUM (BotCreateResult, + Success, + MaxPlayersReached, + GraphError, + TeamStacked +); // radio messages -enum RadioMessage : int { - RADIO_COVER_ME = 1, - RADIO_YOU_TAKE_THE_POINT = 2, - RADIO_HOLD_THIS_POSITION = 3, - RADIO_REGROUP_TEAM = 4, - RADIO_FOLLOW_ME = 5, - RADIO_TAKING_FIRE = 6, - RADIO_GO_GO_GO = 11, - RADIO_TEAM_FALLBACK = 12, - RADIO_STICK_TOGETHER_TEAM = 13, - RADIO_GET_IN_POSITION = 14, - RADIO_STORM_THE_FRONT = 15, - RADIO_REPORT_TEAM = 16, - RADIO_AFFIRMATIVE = 21, - RADIO_ENEMY_SPOTTED = 22, - RADIO_NEED_BACKUP = 23, - RADIO_SECTOR_CLEAR = 24, - RADIO_IN_POSITION = 25, - RADIO_REPORTING_IN = 26, - RADIO_SHES_GONNA_BLOW = 27, - RADIO_NEGATIVE = 28, - RADIO_ENEMY_DOWN = 29 -}; +CR_DECLARE_SCOPED_ENUM (Radio, + CoverMe = 1, + YouTakeThePoint = 2, + HoldThisPosition = 3, + RegroupTeam = 4, + FollowMe = 5, + TakingFireNeedAssistance = 6, + GoGoGo = 11, + TeamFallback = 12, + StickTogetherTeam = 13, + GetInPositionAndWaitForGo = 14, + StormTheFront = 15, + ReportInTeam = 16, + RogerThat = 21, + EnemySpotted = 22, + NeedBackup = 23, + SectorClear = 24, + ImInPosition = 25, + ReportingIn = 26, + ShesGonnaBlow = 27, + Negative = 28, + EnemyDown = 29 +); // chatter system (extending enum above, messages 30-39 is reserved) -enum ChatterMessage : int { - CHATTER_SPOT_THE_BOMBER = 40, - CHATTER_FRIENDLY_FIRE, - CHATTER_PAIN_DIED, - CHATTER_BLINDED, - CHATTER_GOING_TO_PLANT_BOMB, - CHATTER_RESCUING_HOSTAGES, - CHATTER_GOING_TO_CAMP, - CHATTER_HEARD_NOISE, - CHATTER_TEAM_ATTACK, - CHATTER_REPORTING_IN, - CHATTER_GUARDING_DROPPED_BOMB, - CHATTER_CAMP, - CHATTER_PLANTING_BOMB, - CHATTER_DEFUSING_BOMB, - CHATTER_IN_COMBAT, - CHATTER_SEEK_ENEMY, - CHATTER_NOTHING, - CHATTER_ENEMY_DOWN, - CHATTER_USING_HOSTAGES, - CHATTER_FOUND_BOMB, - CHATTER_WON_THE_ROUND, - CHATTER_SCARED_EMOTE, - CHATTER_HEARD_ENEMY, - CHATTER_SNIPER_WARNING, - CHATTER_SNIPER_KILLED, - CHATTER_VIP_SPOTTED, - CHATTER_GUARDING_VIP_SAFETY, - CHATTER_GOING_TO_GUARD_VIP_SAFETY, - CHATTER_QUICK_WON_ROUND, - CHATTER_ONE_ENEMY_LEFT, - CHATTER_TWO_ENEMIES_LEFT, - CHATTER_THREE_ENEMIES_LEFT, - CHATTER_NO_ENEMIES_LEFT, - CHATTER_FOUND_BOMB_PLACE, - CHATTER_WHERE_IS_THE_BOMB, - CHATTER_DEFENDING_BOMBSITE, - CHATTER_BARELY_DEFUSED, - CHATTER_NICESHOT_COMMANDER, - CHATTER_NICESHOT_PALL, - CHATTER_GOING_TO_GUARD_HOSTAGES, - CHATTER_GOING_TO_GUARD_DROPPED_BOMB, - CHATTER_ON_MY_WAY, - CHATTER_LEAD_ON_SIR, - CHATTER_PINNED_DOWN, - CHATTER_GOTTA_FIND_BOMB, - CHATTER_YOU_HEARD_THE_MAN, - CHATTER_LOST_COMMANDER, - CHATTER_NEW_ROUND, - CHATTER_COVER_ME, - CHATTER_BEHIND_SMOKE, - CHATTER_BOMB_SITE_SECURED, - CHATTER_MAX -}; +CR_DECLARE_SCOPED_ENUM (Chatter, + SpotTheBomber = 40, + FriendlyFire, + DiePain, + Blind, + GoingToPlantBomb, + RescuingHostages, + GoingToCamp, + HeardNoise, + TeamAttack, + ReportingIn, + GuardingDroppedC4, + Camping, + PlantingBomb, + DefusingBomb, + InCombat, + SeekingEnemies, + Nothing, + EnemyDown, + UsingHostages, + FoundC4, + WonTheRound, + ScaredEmotion, + HeardTheEnemy, + SniperWarning, + SniperKilled, + VIPSpotted, + GuardingVIPSafety, + GoingToGuardVIPSafety, + QuickWonRound, + OneEnemyLeft, + TwoEnemiesLeft, + ThreeEnemiesLeft, + NoEnemiesLeft, + FoundC4Plant, + WhereIsTheC4, + DefendingBombsite, + BarelyDefused, + NiceShotCommander, + NiceShotPall, + GoingToGuardHostages, + GoingToGuardDroppedC4, + OnMyWay, + LeadOnSir, + PinnedDown, + GottaFindC4, + YouHeardTheMan, + LostCommander, + NewRound, + CoverMe, + BehindSmoke, + BombsiteSecured, + Count +); // counter-strike weapon id's -enum Weapon : int { - WEAPON_P228 = 1, - WEAPON_SHIELD = 2, - WEAPON_SCOUT = 3, - WEAPON_EXPLOSIVE = 4, - WEAPON_XM1014 = 5, - WEAPON_C4 = 6, - WEAPON_MAC10 = 7, - WEAPON_AUG = 8, - WEAPON_SMOKE = 9, - WEAPON_ELITE = 10, - WEAPON_FIVESEVEN = 11, - WEAPON_UMP45 = 12, - WEAPON_SG550 = 13, - WEAPON_GALIL = 14, - WEAPON_FAMAS = 15, - WEAPON_USP = 16, - WEAPON_GLOCK = 17, - WEAPON_AWP = 18, - WEAPON_MP5 = 19, - WEAPON_M249 = 20, - WEAPON_M3 = 21, - WEAPON_M4A1 = 22, - WEAPON_TMP = 23, - WEAPON_G3SG1 = 24, - WEAPON_FLASHBANG = 25, - WEAPON_DEAGLE = 26, - WEAPON_SG552 = 27, - WEAPON_AK47 = 28, - WEAPON_KNIFE = 29, - WEAPON_P90 = 30, - WEAPON_ARMOR = 31, - WEAPON_ARMORHELM = 32, - WEAPON_DEFUSER = 33 -}; +CR_DECLARE_SCOPED_ENUM (Weapon, + P228 = 1, + Shield = 2, + Scout = 3, + Explosive = 4, + XM1014 = 5, + C4 = 6, + MAC10 = 7, + AUG = 8, + Smoke = 9, + Elite = 10, + FiveSeven = 11, + UMP45 = 12, + SG550 = 13, + Galil = 14, + Famas = 15, + USP = 16, + Glock18 = 17, + AWP = 18, + MP5 = 19, + M249 = 20, + M3 = 21, + M4A1 = 22, + TMP = 23, + G3SG1 = 24, + Flashbang = 25, + Deagle = 26, + SG552 = 27, + AK47 = 28, + Knife = 29, + P90 = 30, + Armor = 31, + ArmorHelm = 32, + Defuser = 33 +); // buy counts -enum BuyState : int { - BUYSTATE_PRIMARY_WEAPON = 0, - BUYSTATE_ARMOR_VESTHELM, - BUYSTATE_SECONDARY_WEAPON, - BUYSTATE_GRENADES, - BUYSTATE_DEFUSER, - BUYSTATE_AMMO, - BUYSTATE_NIGHTVISION, - BUYSTATE_FINISHED -}; +CR_DECLARE_SCOPED_ENUM (BuyState, + PrimaryWeapon = 0, + ArmorVestHelm , + SecondaryWeapon, + Grenades, + DefusalKit, + Ammo, + NightVision, + Done +); // economics limits -enum EconomyLimit : int { - ECO_PRIMARY_GT = 0, - ECO_SMG_GT_CT, - ECO_SMG_GT_TE, - ECO_SHOTGUN_GT, - ECO_SHOTGUN_LT, - ECO_HEAVY_GT, - ECO_HEAVY_LT, - ECO_PROSTOCK_NORMAL, - ECO_PROSTOCK_RUSHER, - ECO_PROSTOCK_CAREFUL, - ECO_SHIELDGUN_GT -}; +CR_DECLARE_SCOPED_ENUM (EcoLimit, + PrimaryGreater = 0, + SmgCTGreater, + SmgTEGreater, + ShotgunGreater, + ShotgunLess, + HeavyGreater , + HeavyLess, + ProstockNormal, + ProstockRusher, + ProstockCareful, + ShieldGreater +); // defines for pickup items -enum PickupType : int { - PICKUP_NONE, - PICKUP_WEAPON, - PICKUP_DROPPED_C4, - PICKUP_PLANTED_C4, - PICKUP_HOSTAGE, - PICKUP_BUTTON, - PICKUP_SHIELD, - PICKUP_DEFUSEKIT -}; +CR_DECLARE_SCOPED_ENUM (Pickup, + None = 0, + Weapon, + DroppedC4, + PlantedC4, + Hostage, + Button, + Shield, + DefusalKit +); // fight style type -enum FightStyle : int { - FIGHT_NONE, - FIGHT_STRAFE, - FIGHT_STAY -}; +CR_DECLARE_SCOPED_ENUM (Fight, + None = 0, + Strafe, + Stay +); // dodge type -enum StrafeDir : int { - STRAFE_DIR_NONE, - STRAFE_DIR_LEFT, - STRAFE_DIR_RIGHT -}; +CR_DECLARE_SCOPED_ENUM (Dodge, + None = 0, + Left, + Right +); // reload state -enum ReloadState : int { - RELOAD_NONE = 0, // no reload state currently - RELOAD_PRIMARY = 1, // primary weapon reload state - RELOAD_SECONDARY = 2 // secondary weapon reload state -}; +CR_DECLARE_SCOPED_ENUM (Reload, + None = 0, // no reload state currently + Primary, // primary weapon reload state + Secondary // secondary weapon reload state +); // collision probes -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 -}; +CR_DECLARE_SCOPED_ENUM (CollisionProbe, + Jump = cr::bit (0), // probe jump when colliding + Duck = cr::bit (1), // probe duck when colliding + Strafe = cr::bit (2) // probe strafing when colliding +); // vgui menus (since latest steam updates is obsolete, but left for old cs) -enum VGuiMenu : int { - VMS_TEAM = 2, // menu select team - VMS_TF = 26, // terrorist select menu - VMS_CT = 27 // ct select menu -}; +CR_DECLARE_SCOPED_ENUM (GuiMenu, + TeamSelect = 2, // menu select team + TerroristSelect = 26, // terrorist select menu + CTSelect = 27 // ct select menu +); // lift usage states -enum LiftState : int { - LIFT_NO_NEARBY = 0, - LIFT_LOOKING_BUTTON_OUTSIDE, - LIFT_WAITING_FOR, - LIFT_ENTERING_IN, - LIFT_WAIT_FOR_TEAMMATES, - LIFT_LOOKING_BUTTON_INSIDE, - LIFT_TRAVELING_BY, - LIFT_LEAVING -}; - -// wayponit auto-downloader -enum WaypointDownloadError : int { - WDE_SOCKET_ERROR, - WDE_CONNECT_ERROR, - WDE_NOTFOUND_ERROR, - WDE_NOERROR -}; +CR_DECLARE_SCOPED_ENUM (LiftState, + None = 0, + LookingButtonOutside, + WaitingFor, + EnteringIn, + WaitingForTeammates, + LookingButtonInside, + TravelingBy, + Leaving +); // game start messages for counter-strike... -enum GameMessage : int { - GAME_MSG_NONE = 1, - GAME_MSG_TEAM_SELECT = 2, - GAME_MSG_CLASS_SELECT = 3, - GAME_MSG_PURCHASE = 100, - GAME_MSG_RADIO = 200, - GAME_MSG_SAY_CMD = 10000, - GAME_MSG_SAY_TEAM_MSG = 10001 -}; +CR_DECLARE_SCOPED_ENUM (BotMsg, + None = 1, + TeamSelect = 2, + ClassSelect = 3, + Buy = 100, + Radio = 200, + Say = 10000, + SayTeam = 10001 +); // sensing states -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 -}; +CR_DECLARE_SCOPED_ENUM (Sense, + SeeingEnemy = cr::bit (0), // seeing an enemy + HearingEnemy = cr::bit (1), // hearing an enemy + SuspectEnemy = cr::bit (2), // suspect enemy behind obstacle + PickupItem = cr::bit (3), // pickup item nearby + ThrowExplosive = cr::bit (4), // could throw he grenade + ThrowFlashbang = cr::bit (5), // could throw flashbang + ThrowSmoke = cr::bit (6) // could throw smokegrenade +); // positions to aim at -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) -}; +CR_DECLARE_SCOPED_ENUM (AimFlags, + Nav = cr::bit (0), // aim at nav point + Camp = cr::bit (1), // aim at camp vector + PredictPath = cr::bit (2), // aim at predicted path + LastEnemy = cr::bit (3), // aim at last enemy + Entity = cr::bit (4), // aim at entity like buttons, hostages + Enemy = cr::bit (5), // aim at enemy + Grenade = cr::bit (6), // aim for grenade throw + Override = cr::bit (7) // overrides all others (blinded) +); // famas/glock burst mode status + m4a1/usp silencer -enum BurstMode : int { - BURST_ON = cr::bit (0), - BURST_OFF = cr::bit (1) -}; +CR_DECLARE_SCOPED_ENUM (BurstMode, + On = cr::bit (0), + Off = cr::bit (1) +); // visibility flags -enum Visibility : int { - VISIBLE_HEAD = cr::bit (1), - VISIBLE_BODY = cr::bit (2), - VISIBLE_OTHER = cr::bit (3) -}; +CR_DECLARE_SCOPED_ENUM (Visibility, + Head = cr::bit (1), + Body = cr::bit (2), + Other = cr::bit (3) +); // 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 -}; +CR_DECLARE_SCOPED_ENUM (BotCommandResult, + Handled = 0, // command successfully handled + ListenServer, // command is only avaialble on listen server + BadFormat // wrong params +); -// defines for waypoint flags field (32 bits are available) -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 nodes flags field (32 bits are available) +CR_DECLARE_SCOPED_ENUM (NodeFlag, + Lift = cr::bit (1), // wait for lift to be down before approaching this node + Crouch = cr::bit (2), // must crouch to reach this node + Crossing = cr::bit (3), // a target node + Goal = cr::bit (4), // mission goal point (bomb, hostage etc.) + Ladder = cr::bit (5), // node is on ladder + Rescue = cr::bit (6), // node is a hostage rescue point + Camp = cr::bit (7), // node is a camping point + NoHostage = cr::bit (8), // only use this node if no hostage + DoubleJump = cr::bit (9), // bot help's another bot (requster) to get somewhere (using djump) + Sniper = cr::bit (28), // it's a specific sniper point + TerroristOnly = cr::bit (29), // it's a specific terrorist point + CTOnly = cr::bit (30), // it's a specific ct point +); -// defines for waypoint connection flags field (16 bits are available) -enum PathFlag : int { - PATHFLAG_JUMP = cr::bit (0) // must jump for this connection -}; +// defines for node connection flags field (16 bits are available) +CR_DECLARE_SCOPED_ENUM_TYPE (PathFlag, uint16, + Jump = cr::bit (0) // must jump for this connection +); // enum pathfind search type -enum SearchPathType : int { - SEARCH_PATH_FASTEST = 0, - SEARCH_PATH_SAFEST_FASTER, - SEARCH_PATH_SAFEST -}; +CR_DECLARE_SCOPED_ENUM (FindPath, + Fast = 0, + Optimal, + Safe +); -// defines waypoint connection types -enum PathConnection : int { - CONNECTION_OUTGOING = 0, - CONNECTION_INCOMING, - CONNECTION_BOTHWAYS -}; +// defines node connection types +CR_DECLARE_SCOPED_ENUM (PathConnection, + Outgoing = 0, + Incoming, + Bidirectional +); + +// defines node add commands +CR_DECLARE_SCOPED_ENUM (GraphAdd, + Normal = 0, +); // a* route state -enum RouteState : int { - ROUTE_OPEN = 0, - ROUTE_CLOSED, - ROUTE_NEW -}; +CR_DECLARE_SCOPED_ENUM (RouteState, + Open = 0, + Closed, + 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) -}; +// node edit states +CR_DECLARE_SCOPED_ENUM (GraphEdit, + On = cr::bit (1), + Noclip = cr::bit (2), + Auto = cr::bit (3) +); -// bot known file headers -constexpr char FH_WAYPOINT[8] = "PODWAY!"; -constexpr char FH_EXPERIENCE[8] = "SKYEXP!"; -constexpr char FH_VISTABLE[8] = "SKYVIS!"; -constexpr char FH_MATRIX[8] = "SKYPMX!"; +CR_DECLARE_SCOPED_ENUM (StorageOption, + Practice = cr::bit (0), // this is practice (experience) file + Matrix = cr::bit (1), // this is floyd warshal path & distance matrix + Vistable = cr::bit (2), // this is vistable data + Graph = cr::bit (3), // this is a node graph data + Official = cr::bit (4), // this is additional flag for graph indicates graph are official + Recovered = cr::bit (5), // this is additional flag indicates graph converted from podbot and was bad + Author = cr::bit (6) // this is additional flag indicates that there's author info +); -constexpr int FV_WAYPOINT = 7; -constexpr int FV_EXPERIENCE = 5; -constexpr int FV_VISTABLE = 4; -constexpr int FV_MATRIX = 4; +CR_DECLARE_SCOPED_ENUM (StorageVersion, + Graph = 1, + Practice = 1, + Vistable = 1, + Matrix = 1, + Podbot = 7 +); // some hardcoded desire defines used to override calculated ones -constexpr float TASKPRI_NORMAL = 35.0f; -constexpr float TASKPRI_PAUSE = 36.0f; -constexpr float TASKPRI_CAMP = 37.0f; -constexpr float TASKPRI_SPRAYLOGO = 38.0f; -constexpr float TASKPRI_FOLLOWUSER = 39.0f; -constexpr float TASKPRI_MOVETOPOSITION = 50.0f; -constexpr float TASKPRI_DEFUSEBOMB = 89.0f; -constexpr float TASKPRI_PLANTBOMB = 89.0f; -constexpr float TASKPRI_ATTACK = 90.0f; -constexpr float TASKPRI_SEEKCOVER = 91.0f; -constexpr float TASKPRI_HIDE = 92.0f; -constexpr float TASKPRI_THROWGRENADE = 99.0f; -constexpr float TASKPRI_DOUBLEJUMP = 99.0f; -constexpr float TASKPRI_BLINDED = 100.0f; -constexpr float TASKPRI_SHOOTBREAKABLE = 100.0f; -constexpr float TASKPRI_ESCAPEFROMBOMB = 100.0f; +namespace TaskPri { + static constexpr float Normal { 35.0f }; + static constexpr float Pause { 36.0f }; + static constexpr float Camp { 37.0f }; + static constexpr float Spraypaint { 38.0f }; + static constexpr float FollowUser { 39.0f }; + static constexpr float MoveToPosition { 50.0f }; + static constexpr float DefuseBomb { 89.0f }; + static constexpr float PlantBomb { 89.0f }; + static constexpr float Attack { 90.0f }; + static constexpr float SeekCover { 91.0f }; + static constexpr float Hide { 92.0f }; + static constexpr float Throw { 99.0f }; + static constexpr float DoubleJump { 99.0f }; + static constexpr float Blind { 100.0f }; + static constexpr float ShootBreakable { 100.0f }; + static constexpr float EscapeFromBomb { 100.0f }; +} -constexpr float MAX_GRENADE_TIMER = 2.15f; -constexpr float MAX_SPRAY_DISTANCE = 260.0f; -constexpr float MAX_SPRAY_DISTANCE_X2 = MAX_SPRAY_DISTANCE * 2; -constexpr float MAX_CHATTER_REPEAT = 99.0f; +// storage file magic +constexpr char kPodbotMagic[8] = "PODWAY!"; +constexpr int32 kStorageMagic = 0x59415042; -constexpr int MAX_PATH_INDEX = 8; -constexpr int MAX_DAMAGE_VALUE = 2040; -constexpr int MAX_GOAL_VALUE = 2040; -constexpr int MAX_WAYPOINTS = 2048; -constexpr int MAX_ROUTE_LENGTH = MAX_WAYPOINTS / 2; -constexpr int MAX_WEAPONS = 32; -constexpr int NUM_WEAPONS = 26; -constexpr int MAX_COLLIDE_MOVES = 3; -constexpr int MAX_ENGINE_PLAYERS = 32; -constexpr int MAX_PRINT_BUFFER = 1024; -constexpr int MAX_TEAM_COUNT = 2; -constexpr int INVALID_WAYPOINT_INDEX = -1; +constexpr float kGrenadeCheckTime = 2.15f; +constexpr float kSprayDistance = 260.0f; +constexpr float kDoubleSprayDistance = kSprayDistance * 2; +constexpr float kMaxChatterRepeatInteval = 99.0f; -constexpr int MAX_WAYPOINT_BUCKET_SIZE = static_cast (MAX_WAYPOINTS * 0.65); -constexpr int MAX_WAYPOINT_BUCKET_MAX = MAX_WAYPOINTS * 8 / MAX_WAYPOINT_BUCKET_SIZE + 1; -constexpr int MAX_WAYPOINT_BUCKET_WPTS = MAX_WAYPOINT_BUCKET_SIZE / MAX_WAYPOINT_BUCKET_MAX; +constexpr int kMaxNodeLinks = 8; +constexpr int kMaxPracticeDamageValue = 2040; +constexpr int kMaxPracticeGoalValue = 2040; +constexpr int kMaxNodes = 2048; +constexpr int kMaxRouteLength = kMaxNodes / 2; +constexpr int kMaxWeapons = 32; +constexpr int kNumWeapons = 26; +constexpr int kMaxCollideMoves = 3; +constexpr int kGameMaxPlayers = 32; +constexpr int kGameTeamNum = 2; +constexpr int kInvalidNodeIndex = -1; + +constexpr int kMaxBucketSize = static_cast (kMaxNodes * 0.65); +constexpr int kMaxBucketsInsidePos = kMaxNodes * 8 / kMaxBucketSize + 1; +constexpr int kMaxNodesInsideBucket = kMaxBucketSize / kMaxBucketsInsidePos; // weapon masks -constexpr int WEAPON_PRIMARY = ((1 << WEAPON_XM1014) | (1 << WEAPON_M3) | (1 << WEAPON_MAC10) | (1 << WEAPON_UMP45) | (1 << WEAPON_MP5) | (1 << WEAPON_TMP) | (1 << WEAPON_P90) | (1 << WEAPON_AUG) | (1 << WEAPON_M4A1) | (1 << WEAPON_SG552) | (1 << WEAPON_AK47) | (1 << WEAPON_SCOUT) | (1 << WEAPON_SG550) | (1 << WEAPON_AWP) | (1 << WEAPON_G3SG1) | (1 << WEAPON_M249) | (1 << WEAPON_FAMAS) | (1 << WEAPON_GALIL)); -constexpr int WEAPON_SECONDARY = ((1 << WEAPON_P228) | (1 << WEAPON_ELITE) | (1 << WEAPON_USP) | (1 << WEAPON_GLOCK) | (1 << WEAPON_DEAGLE) | (1 << WEAPON_FIVESEVEN)); +constexpr auto kPrimaryWeaponMask = (cr::bit (Weapon::XM1014) | cr::bit (Weapon::M3) | cr::bit (Weapon::MAC10) | cr::bit (Weapon::UMP45) | cr::bit (Weapon::MP5) | cr::bit (Weapon::TMP) | cr::bit (Weapon::P90) | cr::bit (Weapon::AUG) | cr::bit (Weapon::M4A1) | cr::bit (Weapon::SG552) | cr::bit (Weapon::AK47) | cr::bit (Weapon::Scout) | cr::bit (Weapon::SG550) | cr::bit (Weapon::AWP) | cr::bit (Weapon::G3SG1) | cr::bit (Weapon::M249) | cr::bit (Weapon::Famas) | cr::bit (Weapon::Galil)); +constexpr auto kSecondaryWeaponMask = (cr::bit (Weapon::P228) | cr::bit (Weapon::Elite) | cr::bit (Weapon::USP) | cr::bit (Weapon::Glock18) | cr::bit (Weapon::Deagle) | cr::bit (Weapon::FiveSeven)); // a* route struct Route { @@ -532,27 +533,55 @@ struct Route { RouteState state; }; +// general stprage header information structure +struct StorageHeader { + int32 magic; + int32 version; + int32 options; + int32 length; + int32 compressed; + int32 uncompressed; +}; + // links keywords and replies together struct Keywords { StringArray keywords; StringArray replies; StringArray usedReplies; + +public: + Keywords () = default; + + Keywords (const StringArray &keywords, const StringArray &replies) { + this->keywords.clear (); + this->replies.clear (); + this->usedReplies.clear (); + + this->keywords.insert (0, keywords); + this->replies.insert (0, replies); + } }; // tasks definition -struct Task { - TaskID id; // major task/action carried out +struct BotTask { + Task id; // major task/action carried out float desire; // desire (filled in) for this task - int data; // additional data (waypoint index) + int data; // additional data (node index) float time; // time task expires bool resume; // if task can be continued if interrupted + +public: + BotTask (Task id, float desire, int data, float time, bool resume) : id (id), desire (desire), data (data), time (time), resume (resume) { } }; // botname structure definition struct BotName { - String steamId; - String name; - int usedBy; + String name = ""; + int usedBy = -1; + +public: + BotName () = default; + BotName (String &name, int usedBy) : name (cr::move (name)), usedBy (usedBy) { } }; // voice config structure definition @@ -560,6 +589,9 @@ struct ChatterItem { String name; float repeat; float duration; + +public: + ChatterItem (String name, float repeat, float duration) : name (cr::move (name)), repeat (repeat), duration (duration) { } }; // weapon properties structure @@ -589,6 +621,27 @@ struct WeaponInfo { int penetratePower; // penetrate power int maxClip; // max ammo in clip bool primaryFireHold; // hold down primary fire button to use? + +public: + WeaponInfo (int id, const char *name, const char *model, int price, int minPriAmmo, int teamStd, + int teamAs, int buyGroup, int buySelect, int newBuySelectT, int newBuySelectCT, int penetratePower, + int maxClip, bool fireHold) { + + this->id = id; + this->name = name; + this->model = model; + this->price = price; + this->minPrimaryAmmo = minPriAmmo; + this->teamStandard = teamStd; + this->teamAS = teamAs; + this->buyGroup = buyGroup; + this->buySelect = buySelect; + this->newBuySelectCT = newBuySelectCT; + this->newBuySelectT = newBuySelectT; + this->penetratePower = penetratePower; + this->maxClip = maxClip; + this->primaryFireHold = fireHold; + } }; // array of clients struct @@ -601,17 +654,12 @@ struct Client { int flags; // client flags int radio; // radio orders int menu; // identifier to openen menu + int ping; // when bot latency is enabled, client ping stored here float hearingDistance; // distance this sound is heared float timeSoundLasting; // time sound is played/heared - int iconFlags[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 { - int damage[MAX_TEAM_COUNT]; - int index[MAX_TEAM_COUNT]; - int value[MAX_TEAM_COUNT]; + int iconFlags[kGameMaxPlayers]; // flag holding chatter icons + float iconTimestamp[kGameMaxPlayers]; // timers for chatter icons + bool pingUpdate; // update ping ? }; // bot creation tab @@ -635,7 +683,7 @@ struct ChatCollection { }; // general waypoint header information structure -struct WaypointHeader { +struct PODGraphHeader { char header[8]; int32 fileVersion; int32 pointNumber; @@ -643,62 +691,71 @@ struct WaypointHeader { char author[32]; }; -// general experience & vistable header information structure -struct ExtHeader { - char header[8]; - int32 fileVersion; - int32 pointNumber; - int32 compressed; - int32 uncompressed; -}; - // floyd-warshall matrices -struct FloydMatrix { - int dist; - int index; +struct Matrix { + int16 dist; + int16 index; }; -// define general waypoint structure +// experience data hold in memory while playing +struct Practice { + int16 damage[kGameTeamNum]; + int16 index[kGameTeamNum]; + int16 value[kGameTeamNum]; +}; + +// defines linked waypoints +struct PathLink { + Vector velocity; + int32 distance; + uint16 flags; + int16 index; +}; + +// defines visibility count +struct PathVis { + uint16 stand, crouch; +}; + +// define graph path structure for yapb struct Path { - int32 pathNumber; - int32 flags; - Vector origin; - float radius; - - float campStartX; - float campStartY; - float campEndX; - float campEndY; - - int16 index[MAX_PATH_INDEX]; - uint16 connectionFlags[MAX_PATH_INDEX]; - Vector connectionVelocity[MAX_PATH_INDEX]; - int32 distances[MAX_PATH_INDEX]; - - struct Vis { - uint16 stand, crouch; - } vis; + int32 number, flags; + Vector origin, start, end; + float radius, light, display; + PathLink links[kMaxNodeLinks]; + PathVis vis; }; -// this structure links waypoints returned from pathfinder +// define waypoint structure for podbot (will convert on load) +struct PODPath { + int32 number, flags; + Vector origin; + float radius, csx, csy, cex, cey; + int16 index[kMaxNodeLinks]; + uint16 conflags[kMaxNodeLinks]; + Vector velocity[kMaxNodeLinks]; + int32 distance[kMaxNodeLinks]; + PathVis vis; +}; + +// this structure links nodes returned from pathfinder class PathWalk : public IntArray { public: - PathWalk (void) { - reserve (MAX_ROUTE_LENGTH); + explicit PathWalk () { clear (); } - ~PathWalk (void) = default; + ~PathWalk () = default; public: - inline int &next (void) { + int &next () { return at (1); } - inline int &first (void) { + int &first () { return at (0); } - inline bool hasNext (void) const { + bool hasNext () const { if (empty ()) { return false; } @@ -708,11 +765,15 @@ public: // main bot class class Bot final { +private: + using RouteTwin = Twin ; + +public: friend class BotManager; private: uint32 m_states; // sensing bitstates - uint32 m_collideMoves[MAX_COLLIDE_MOVES]; // sorted array of movements + uint32 m_collideMoves[kMaxCollideMoves]; // sorted array of movements uint32 m_collisionProbeBits; // bits of possible collision moves uint32 m_collStateIndex; // index into collide moves uint32 m_aimFlags; // aiming conditions @@ -723,20 +784,18 @@ private: 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_loosedBombWptIndex; // nearest to loosed bomb node + int m_plantedBombWptIndex; // nearest to planted bomb node + int m_currentNodeIndex; // current node 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_prevWptIndex[5]; // previous node indices from node find + int m_pathFlags; // current node 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 float m_headedTime; float m_prevTime; // time previously checked movement speed @@ -776,11 +835,11 @@ private: 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_lastUsedNodesTime; // last time bot followed nodes 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_chatterTimes[Chatter::Count]; // chatter command timers + float m_navTimeset; // time node chosen by Bot float m_moveSpeed; // current speed forward/backward float m_strafeSpeed; // current speed sideways float m_minSpeed; // minimum speed in normal mode @@ -798,7 +857,7 @@ private: 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_bombSearchOverridden; // use normal node 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 @@ -806,12 +865,12 @@ private: 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 + Pickup m_pickupType; // type of entity which needs to be used/picked up + PathWalk m_pathWalk; // pointer to current node from path + Dodge m_combatStrafeDir; // direction to strafe + Fight m_fightStyle; // combat style to use CollisionState m_collisionState; // collision State - SearchPathType m_pathType; // which pathfinder to use + FindPath m_pathType; // which pathfinder to use uint8 m_visibility; // visibility flags edict_t *m_pickupItem; // pointer to entity of item to use/pickup @@ -831,81 +890,81 @@ private: 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_throw; // origin of node 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_desiredVelocity; // desired velocity for jump nodes Vector m_breakableOrigin; // origin of breakable Array m_hostages; // pointer to used hostage entities Array m_routes; // pointer - BinaryHeap m_routeQue; - Path *m_currentPath; // pointer to the current path waypoint + BinaryHeap m_routeQue; + Path *m_path; // pointer to the current path node 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 findCampingDirection (); + int findAimingNode (const Vector &to); + int findNearestNode (); + int findBombNode (); + int findCoverNode (float maxDistance); + int findDefendNode (const Vector &origin); + int findBestGoal (); + int findGoalPost (int tactic, IntArray *defensive, IntArray *offsensive); + int getMsgQueue (); + int bestPrimaryCarried (); + int bestSecondaryCarried (); + int bestGrenadeCarried (); + int bestWeaponCarried (); 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 getBombTimeleft (); + float getReachTime (); float isInFOV (const Vector &dest); - float getShiftSpeed (void); + float getShiftSpeed (); float getEnemyBodyOffsetCorrection (float distance); - bool canReplaceWeapon (void); + bool canReplaceWeapon (); bool canDuckUnder (const Vector &normal); bool canJumpUp (const Vector &normal); bool doneCanJumpUp (const Vector &normal); bool cantMoveForward (const Vector &normal, TraceResult *tr); 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 isBlockedLeft (); + bool isBlockedRight (); + bool checkWallOnLeft (); + bool checkWallOnRight (); + bool updateNavigation (); + bool isEnemyThreat (); 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 hasActiveGoal (); + bool advanceMovement (); bool isBombDefusing (const Vector &bombOrigin); bool isOccupiedPoint (int index); bool seesItem (const Vector &dest, const char *itemName); - bool lastEnemyShootable (void); + bool lastEnemyShootable (); bool isShootableBreakable (edict_t *ent); bool rateGroundWeapon (edict_t *ent); - bool reactOnEnemy (void); - bool getNextBestPoint (void); - bool hasAnyWeapons (void); + bool reactOnEnemy (); + bool selectBestNextNode (); + bool hasAnyWeapons (); bool isDeadlyMove (const Vector &to); - bool isOutOfBombTimer (void); + bool isOutOfBombTimer (); bool isWeaponBadAtDistance (int weaponIndex, float distance); bool needToPauseFiring (float distance); - bool lookupEnemies (void); + bool lookupEnemies (); bool isEnemyHidden (edict_t *enemy); bool isFriendInLineOfFire (float distance); bool isGroupOfEnemies (const Vector &location, int numEnemies = 1, float radius = 256.0f); @@ -913,111 +972,112 @@ private: bool isPenetrableObstacle2 (const Vector &dest); bool isEnemyBehindShield (edict_t *enemy); bool checkChatKeywords (String &reply); - bool isReplyingToChat (void); + bool isReplyingToChat (); void instantChatter (int type); - void runAI (void); - void runMovement (void); - void checkSpawnConditions (void); - void buyStuff (void); + void runAI (); + void runMovement (); + void checkSpawnConditions (); + void buyStuff (); void changePitch (float speed); void changeYaw (float speed); - void checkMsgQueue (void); - void checkRadioQueue (void); - void checkReload (void); - void avoidGrenades (void); - void checkGrenadesThrow (void); + void checkMsgQueue (); + void checkRadioQueue (); + void checkReload (); + void avoidGrenades (); + void checkGrenadesThrow (); void checkBurstMode (float distance); - void checkSilencer (void); - void updateAimDir (void); - void updateLookAngles (void); - void updateBodyAngles (void); + void checkSilencer (); + void updateAimDir (); + void updateLookAngles (); + void updateBodyAngles (); void updateLookAnglesNewbie (const Vector &direction, float delta); void setIdealReactionTimers (bool actual = false); - void updateHearing (void); + void updateHearing (); void postprocessGoals (const IntArray &goals, int *result); - void processPickups (void); + void updatePickups (); void checkTerrain (float movedDistance, const Vector &dirNormal); - void checkDarkness (void); - void checkParachute (void); + void checkDarkness (); + void checkParachute (); void getCampDirection (Vector *dest); - void collectGoalExperience (int damage); - void collectDataExperience (edict_t *attacker, int damage); - void searchShortestPath (int srcIndex, int destIndex); - void searchPath (int srcIndex, int destIndex, SearchPathType pathType = SEARCH_PATH_FASTEST); - void clearRoute (void); + void updatePracticeValue (int damage); + void updatePracticeDamage (edict_t *attacker, int damage); + void findShortestPath (int srcIndex, int destIndex); + void findPath (int srcIndex, int destIndex, FindPath pathType = FindPath::Fast); + void clearRoute (); void sayDebug (const char *format, ...); - void frame (void); - void resetCollision (void); - void ignoreCollision (void); - void setConditions (void); - void overrideConditions (void); - void updateEmotions (void); + void frame (); + void resetCollision (); + void ignoreCollision (); + void setConditions (); + void overrideConditions (); + void updateEmotions (); 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 updateTeamJoin (); + void updateTeamCommands (); + void decideFollowUser (); + void attackMovement (); + void findValidNode (); + void fireWeapons (); void selectWeapons (float distance, int index, int id, int choosen); - void focusEnemy (void); - void selectBestWeapon (void); - void selectSecondary (void); + void focusEnemy (); + void selectBestWeapon (); + void selectSecondary (); void selectWeaponByName (const char *name); void selectWeaponById (int num); - void completeTask (void); - void processTasks (void); + void completeTask (); + void executeTasks (); - void normal_ (void); - void spraypaint_ (void); - void huntEnemy_ (void); - void seekCover_ (void); - void attackEnemy_ (void); - void pause_ (void); - void blind_ (void); - void camp_ (void); - void hide_ (void); - void moveToPos_ (void); - void plantBomb_ (void); - void bombDefuse_ (void); - void followUser_ (void); - void throwExplosive_ (void); - void throwFlashbang_ (void); - void throwSmoke_ (void); - void doublejump_ (void); - void escapeFromBomb_ (void); - void pickupItem_ (void); - void shootBreakable_ (void); + void normal_ (); + void spraypaint_ (); + void huntEnemy_ (); + void seekCover_ (); + void attackEnemy_ (); + void pause_ (); + void blind_ (); + void camp_ (); + void hide_ (); + void moveToPos_ (); + void plantBomb_ (); + void bombDefuse_ (); + void followUser_ (); + void throwExplosive_ (); + void throwFlashbang_ (); + void throwSmoke_ (); + void doublejump_ (); + void escapeFromBomb_ (); + void pickupItem_ (); + void shootBreakable_ (); edict_t *lookupButton (const char *targetName); - edict_t *lookupBreakable (void); + edict_t *lookupBreakable (); edict_t *correctGrenadeVelocity (const char *model); - const Vector &getEnemyBodyOffset (void); + const Vector &getEnemyBodyOffset (); Vector calcThrow (const Vector &start, const Vector &stop); Vector calcToss (const Vector &start, const Vector &stop); - Vector isBombAudible (void); + Vector isBombAudible (); Vector getBodyOffsetError (float distance); - uint8 computeMsec (void); + uint8 computeMsec (); private: - bool isOnLadder (void) const { + bool isOnLadder () const { return pev->movetype == MOVETYPE_FLY; } - bool isOnFloor (void) const { + bool isOnFloor () const { return (pev->flags & (FL_ONGROUND | FL_PARTIALGROUND)) != 0; } - bool isInWater (void) const { + bool isInWater () const { return pev->waterlevel >= 2; } public: entvars_t *pev; + int m_index; // saved bot index int m_wantedTeam; // player team bot wants select int m_wantedClass; // player model bot wants to select int m_difficulty; // bots hard level @@ -1026,7 +1086,6 @@ public: float m_spawnTime; // time this bot spawned float m_timeTeamOrder; // time of last radio command float m_slowFrameTimestamp; // time to per-second think - float m_timeRepotingInDelay; // time to delay report-in float m_nextBuyTime; // next buy time float m_checkDarkTime; // check for darkness time float m_preventFlashing; // bot turned away from flashbang @@ -1043,7 +1102,7 @@ public: 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_goalValue; // ranking value for this node float m_viewDistance; // current view distance float m_maxViewDistance; // maximum view distance float m_retreatTime; // time to retreat? @@ -1054,11 +1113,12 @@ public: 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_timeNextTracking; // time node 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_basePing; // base ping for bot 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 @@ -1072,12 +1132,12 @@ public: 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_prevGoalIndex; // holds destination goal node 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_ammoInClip[kMaxWeapons]; // ammo in clip for each weapons int m_ammo[MAX_AMMO_SLOTS]; // total ammo amounts bool m_isVIP; // bot is vip? @@ -1105,7 +1165,7 @@ public: 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_pathOrigin; // origin of node 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 @@ -1114,31 +1174,31 @@ public: 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) Personality m_personality; // bots type - Array m_tasks; + Array m_tasks; public: - Bot (edict_t *bot, int difficulty, int personality, int team, int member, const String &steamId); - ~Bot (void); + Bot (edict_t *bot, int difficulty, int personality, int team, int member); + ~Bot () = default; 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 slowFrame (); // the main Lambda that decides intervals of running bot ai + void fastFrame (); /// the things that can be executed while skipping frames void processBlind (int alpha); void processDamage (edict_t *inflictor, int damage, int armor, int bits); - void showDebugOverlay (void); - void newRound (void); + void showDebugOverlay (); + void newRound (); void processBuyzoneEntering (int buyState); void pushMsgQueue (int message); void prepareChatMessage (const String &message); - void checkForChat (void); + void checkForChat (); void showChaterIcon (bool show); - void clearSearchNodes (void); + void clearSearchNodes (); void processBreakables (edict_t *touch); void avoidIncomingPlayers (edict_t *touch); - void startTask (TaskID id, float desire, int data, float time, bool resume); - void clearTask (TaskID id); - void filterTasks (void); - void clearTasks (void); + void startTask (Task id, float desire, int data, float time, bool resume); + void clearTask (Task id); + void filterTasks (); + void clearTasks (); void dropWeaponForUser (edict_t *user, bool discardC4); void say (const char *text); void sayTeam (const char *text); @@ -1146,62 +1206,74 @@ public: void pushRadioMessage (int message); void pushChatterMessage (int message); void processChatterMessage (const char *tempMessage); - void tryHeadTowardRadioMessage (void); - void kill (void); - void kick (void); - void resetDoubleJump (void); + void tryHeadTowardRadioMessage (); + void kill (); + void kick (); + void resetDoubleJump (); void startDoubleJump (edict_t *ent); - bool hasHostage (void); - bool usesRifle (void); - bool usesPistol (void); - bool usesSniper (void); - bool usesSubmachine (void); - bool usesZoomableRifle (void); - bool usesBadWeapon (void); - bool usesCampGun (void); - bool hasPrimaryWeapon (void); - bool hasSecondaryWeapon (void); - bool hasShield (void); - bool isShieldDrawn (void); - bool searchOptimalPoint (void); + bool hasHostage (); + bool usesRifle (); + bool usesPistol (); + bool usesSniper (); + bool usesSubmachine (); + bool usesZoomableRifle (); + bool usesBadWeapon (); + bool usesCampGun (); + bool hasPrimaryWeapon (); + bool hasSecondaryWeapon (); + bool hasShield (); + bool isShieldDrawn (); + bool findBestNearestNode (); bool seesEntity (const Vector &dest, bool fromBody = false); - int index (void); - int getAmmo (void); - int getNearestToPlantedBomb (void); + int getAmmo (); + int getNearestToPlantedBomb (); - float getFrameInterval (void); - Task *getTask (void); + float getFrameInterval (); + BotTask *getTask (); public: - inline int getAmmoInClip (void) const { + int getAmmoInClip () const { return m_ammoInClip[m_currentWeapon]; } - inline Vector getCenter (void) const { + Vector getCenter () const { return (pev->absmax + pev->absmin) * 0.5; }; - inline Vector getEyesPos (void) const { + Vector getEyesPos () const { return pev->origin + pev->view_ofs; }; - inline TaskID taskId (void) { + Task getCurrentTaskId () { return getTask ()->id; } - inline edict_t *ent (void) { + edict_t *ent () { return pev->pContainingEntity; }; + + int index () const { + return m_index; + } + + int entindex () const { + return m_index + 1; + } }; // manager class class BotManager final : public Singleton { +public: + using ForEachBot = Lambda ; + using UniqueBot = UniquePtr ; + private: 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 @@ -1209,16 +1281,15 @@ private: 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 + float m_lastRadioTime[kGameTeamNum]; // 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]; + int m_lastRadio[kGameTeamNum]; // last radio message for team - 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_leaderChoosen[kGameTeamNum]; // is team leader choose theese round + bool m_economicsGood[kGameTeamNum]; // is team able to buy anything bool m_bombPlanted; bool m_botsCanPause; bool m_roundEnded; @@ -1226,83 +1297,82 @@ private: 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; + Array m_filters; // task filters - Bot *m_bots[MAX_ENGINE_PLAYERS]; // all available bots + Array m_bots; // all available bots edict_t *m_killerEntity; // killer entity for bots protected: - BotCreationResult create (const String &name, int difficulty, int personality, int team, int member); + BotCreateResult create (const String &name, int difficulty, int personality, int team, int member); public: - BotManager (void); - ~BotManager (void); + BotManager (); + ~BotManager () = default; public: - Bot *getBot (int index); - Bot *getBot (edict_t *ent); - Bot *getAliveBot (void); - Bot *getHighfragBot (int team); + Twin countTeamPlayers (); + + Bot *findBotByIndex (int index); + Bot *findBotByEntity (edict_t *ent); + + Bot *findAliveBot (); + Bot *findHighestFragBot (int team); - int index (edict_t *ent); int getHumansCount (bool ignoreSpectators = false); - int getAliveHumansCount (void); - int getBotCount (void); + int getAliveHumansCount (); + int getBotCount (); void setBombPlanted (bool isPlanted); - void countTeamPlayers (int &ts, int &cts); - void slowFrame (void); - void frame (void); - void createKillerEntity (void); - void destroyKillerEntity (void); + void slowFrame (); + void frame (); + void createKillerEntity (); + void destroyKillerEntity (); void touchKillerEntity (Bot *bot); - void destroy (void); - void destroy (int index); + void destroy (); 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 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); + bool kickRandom (bool decQuota = true, Team fromTeam = Team::Unassigned); void kickBot (int index); void kickFromTeam (Team team, bool removeAll = false); void killAllBots (int team = -1); - void maintainQuota (void); - void initQuota (void); - void initRound (void); + void maintainQuota (); + void initQuota (); + void initRound (); void decrementQuota (int by = 1); void selectLeaders (int team, bool reset); - void listBots (void); + void listBots (); void setWeaponMode (int selection); void updateTeamEconomics (int team, bool setTrue = false); - void updateBotDifficulties (void); - void reset (void); - void initFilters (void); - void resetFilters (void); - void updateActiveGrenade (void); - void updateIntrestingEntities (void); - void calculatePingOffsets (void); - void sendPingOffsets (edict_t *to); - void sendDeathMsgFix (void); + void updateBotDifficulties (); + void reset (); + void initFilters (); + void resetFilters (); + void updateActiveGrenade (); + void updateIntrestingEntities (); void captureChatRadio (const char *cmd, const char *arg, edict_t *ent); - void notifyBombDefuse (void); + void notifyBombDefuse (); void execGameEntity (entvars_t *vars); - + void forEach (ForEachBot handler); + void erase (Bot *bot); + bool isTeamStacked (int team); public: - Array &searchActiveGrenades (void) { + Array &searchActiveGrenades () { return m_activeGrenades; } - Array &searchIntrestingEntities (void) { + Array &searchIntrestingEntities () { return m_intrestingEntities; } - bool hasActiveGrenades (void) const { + bool hasActiveGrenades () const { return !m_activeGrenades.empty (); } - bool hasIntrestingEntities (void) const { + bool hasIntrestingEntities () const { return !m_intrestingEntities.empty (); } @@ -1310,7 +1380,7 @@ public: return m_economicsGood[team]; } - int getLastWinner (void) const { + int getLastWinner () const { return m_lastWinner; } @@ -1319,7 +1389,7 @@ public: } // get the list of filters - Array &getFilters (void) { + Array &getFilters () { return m_filters; } @@ -1327,31 +1397,27 @@ public: addbot ("", -1, -1, -1, -1, manual); } - void updateDeathMsgState (bool sent) { - m_deathMsgSent = sent; - } - - bool isBombPlanted (void) const { + bool isBombPlanted () const { return m_bombPlanted; } - float getTimeBombPlanted (void) const { + float getTimeBombPlanted () const { return m_timeBombPlanted; } - float getRoundStartTime (void) const { + float getRoundStartTime () const { return m_timeRoundStart; } - float getRoundMidTime (void) const { + float getRoundMidTime () const { return m_timeRoundMid; } - float getRoundEndTime (void) const { + float getRoundEndTime () const { return m_timeRoundEnd; } - bool isRoundOver (void) const { + bool isRoundOver () const { return m_roundEnded; } @@ -1359,7 +1425,7 @@ public: m_roundEnded = over; } - bool canPause (void) const { + bool canPause () const { return m_botsCanPause; } @@ -1379,7 +1445,7 @@ public: m_plantSearchUpdateTime = timestamp; } - float getPlantedBombSearchTimestamp (void) const { + float getPlantedBombSearchTimestamp () const { return m_plantSearchUpdateTime; } @@ -1403,18 +1469,44 @@ public: m_lastChatTime = timestamp; } - float getLastChatTimestamp (void) const { + float getLastChatTimestamp () const { return m_lastChatTime; } // some bots are online ? - bool hasBotsOnline (void) { + bool hasBotsOnline () { return getBotCount () > 0; } + +public: + Bot *operator [] (int index) { + return findBotByIndex (index); + } + + Bot *operator [] (edict_t *ent) { + return findBotByEntity (ent); + } + +public: + UniqueBot *begin () { + return m_bots.begin (); + } + + UniqueBot *begin () const { + return m_bots.begin (); + } + + UniqueBot *end () { + return m_bots.end (); + } + + UniqueBot *end () const { + return m_bots.end (); + } }; -// waypoint operation class -class Waypoint final : public Singleton { +// graph operation class +class BotGraph final : public Singleton { public: friend class Bot; @@ -1424,33 +1516,28 @@ private: }; int m_editFlags; - int m_numWaypoints; - int m_loadTries; - int m_cacheWaypointIndex; - int m_lastJumpWaypoint; - int m_visibilityIndex; + int m_loadAttempts; + int m_cacheNodeIndex; + int m_lastJumpNode; int m_findWPIndex; int m_facingAtIndex; - int m_highestDamage[MAX_TEAM_COUNT]; + int m_highestDamage[kGameTeamNum]; 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; + bool m_jumpLearnNode; + bool m_hasChanged; bool m_needsVisRebuild; Vector m_learnVelocity; Vector m_learnPosition; Vector m_bombPos; - Vector m_lastWaypoint; + Vector m_lastNode; IntArray m_terrorPoints; IntArray m_ctPoints; @@ -1459,27 +1546,27 @@ 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; + Array m_buckets[kMaxBucketsInsidePos][kMaxBucketsInsidePos][kMaxBucketsInsidePos]; + Array m_matrix; + Array m_practice; + Array m_paths; + Array m_vistable; + + String m_tempStrings; edict_t *m_editor; - Path *m_paths[MAX_WAYPOINTS]; - uint8 m_visLUT[MAX_WAYPOINTS][MAX_WAYPOINTS / 4]; - public: - Waypoint (void); - ~Waypoint (void); + BotGraph (); + ~BotGraph () = default; public: - int getFacingIndex (void); + int getFacingIndex (); 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); + int getEditorNeareset (); int getDangerIndex (int team, int start, int goal); int getDangerValue (int team, int start, int goal); int getDangerDamage (int team, int start, int goal); @@ -1488,69 +1575,70 @@ public: float calculateTravelTime (float maxSpeed, const Vector &src, const Vector &origin); - bool load (void); + bool convertOldFormat (); bool isVisible (int srcIndex, int destIndex); bool isStandVisible (int srcIndex, int destIndex); bool isDuckVisible (int srcIndex, int destIndex); - bool isConnected (int pointA, int pointB); + bool isConnected (int a, int b); bool isConnected (int index); bool isReachable (Bot *bot, int index); bool isNodeReacheable (const Vector &src, const Vector &destination); - 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 checkNodes (bool teleportPlayer); + bool loadPathMatrix (); 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); + bool saveGraphData (); + bool loadGraphData (); + + template bool saveStorage (const String &ext, const String &name, StorageOption options, StorageVersion version, const Array &data, uint8 *blob); + template bool loadStorage (const String &ext, const String &name, StorageOption options, StorageVersion version, Array &data, uint8 *blob, int32 *outOptions); + + void saveOldFormat (); + void initGraph (); + void frame (); + void loadPractice (); + void loadVisibility (); + void initNodesTypes (); + void initLightLevels (); void addPath (int addIndex, int pathIndex, float distance); - void push (int flags, const Vector &waypointOrigin = Vector::null ()); + void add (int type, const Vector &pos = nullvec); void erase (int target); void toggleFlags (int toggleFlag); void setRadius (int index, float radius); - void rebuildVisibility (void); + void rebuildVisibility (); void pathCreate (char dir); - void erasePath (void); + void erasePath (); 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); + void savePractice (); + void saveVisibility (); + void addBasic (); + void eraseFromDisk (); + void savePathMatrix (); void setSearchIndex (int index); - void startLearnJump (void); + void startLearnJump (); void setVisited (int index); - void clearVisited (void); - void initBuckets (void); + void clearVisited (); + void initBuckets (); 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); + void setBombPos (bool reset = false, const Vector &pos = nullvec); + void updateGlobalPractice (); + void unassignPath (int from, int to); + void setDangerValue (int team, int start, int goal, int value); + void setDangerDamage (int team, int start, int goal, int value); + void convertFromPOD (Path &path, const PODPath &pod); + void converToPOD (const Path &path, PODPath &pod); + void convertCampDirection (Path &path); const char *getDataDirectory (bool isMemoryFile = false); - const char *getWaypointFilename (bool isMemoryFile = false); + const char *getOldFormatGraphName (bool isMemoryFile = false); - WaypointDownloadError downloadWaypoint (void); Bucket locateBucket (const Vector &pos); - IntArray searchRadius (float radius, const Vector &origin, int maxCount = -1); - IntArray &getWaypointsInBucket (const Vector &pos); + const Array &getNodesInBucket (const Vector &pos); public: - Experience *getRawExperience (void) { - return m_experience; - } - int getHighestDamageForTeam (int team) const { return m_highestDamage[team]; } @@ -1559,12 +1647,12 @@ public: m_highestDamage[team] = value; } - const char *getAuthor (void) const { - return m_tempInfo.chars (); + const char *getAuthor () const { + return m_tempStrings.chars (); } - bool hasChanged (void) const { - return m_waypointsChanged; + bool hasChanged () const { + return m_hasChanged; } bool hasEditFlag (int flag) const { @@ -1583,113 +1671,158 @@ public: m_autoPathDistance = distance; } - const Vector &getBombPos (void) const { + const Vector &getBombPos () const { return m_bombPos; } // access paths Path &operator [] (int index) { - return *m_paths[index]; + return m_paths[index]; } - // check waypoints range + // check nodes range bool exists (int index) const { - return index >= 0 && index < m_numWaypoints; + return index >= 0 && index < static_cast (m_paths.length ()); } - // get real waypoint num - int length (void) const { - return m_numWaypoints; - } - - // get the light level of waypoint - float getLightLevel (int id) const { - return m_waypointLightLevel[id]; + // get real nodes num + int length () const { + return m_paths.length (); } // check if has editor - bool hasEditor (void) const { + bool hasEditor () const { return !!m_editor; } - // set's the waypoint editor + // set's the node editor void setEditor (edict_t *ent) { m_editor = ent; } - // get the current waypoint editor - edict_t *getEditor (void) { + // get the current node editor + edict_t *getEditor () { return m_editor; } }; // mostly config stuff, and some stuff dealing with menus -class Config final : public Singleton { +class BotConfig final : public Singleton { private: Array m_chat; Array > m_chatter; + Array m_botNames; Array m_replies; Array m_weapons; + Array m_weaponProps; - // weapon info gathered through engine messages - WeaponProp m_weaponProps[MAX_WEAPONS + 1]; + StringArray m_logos; + StringArray m_avatars; // 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_normalWeaponPrefs[kNumWeapons] = { 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[kNumWeapons] = { 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[kNumWeapons] = { 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[kNumWeapons - 23] = { 95, 85, 60 }; + int m_botBuyEconomyTable[kNumWeapons - 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; + BotConfig (); + ~BotConfig () = default; public: // load the configuration files - void load (bool onlyMain); + void loadConfigs (); + + // loads main config file + void loadMainConfig (); + + // loads bot names + void loadNamesConfig (); + + // loads weapons config + void loadWeaponsConfig (); + + // loads chatter config + void loadChatterConfig (); + + // loads chat config + void loadChatConfig (); + + // loads language config + void loadLanguageConfig (); + + // load bots logos config + void loadLogosConfig (); + + // load bots avatars config + void loadAvatarsConfig (); + + // sets memfile to use engine functions + void setupMemoryFiles (); // picks random bot name - BotName *pickBotName (void); + BotName *pickBotName (); // remove bot name from used list void clearUsedName (Bot *bot); // initialize weapon info - void initWeapons (void); + void initWeapons (); // fix weapon prices (ie for elite) - void adjustWeaponPrices (void); + void adjustWeaponPrices (); WeaponInfo &findWeaponById (int id); +private: + bool isCommentLine (const String &line) { + const char ch = line.at (0); + return ch == '#' || ch == '/' || ch == '\r' || ch == ';' || ch == 0 || ch == ' '; + }; + public: - // get the chat array - Array &getChat (void) { - return m_chat; + // checks whether chat banks contains messages + bool hasChatBank (int chatType) const { + return !m_chat[chatType].empty (); } - // get's the chatter array - Array > &getChatter (void) { - return m_chatter; + // checks whether chatter banks contains messages + bool hasChatterBank (int chatterType) const { + return !m_chatter[chatterType].empty (); + } + + // pick random phrase from chat bank + const String &pickRandomFromChatBank (int chatType) { + return m_chat[chatType].random (); + } + + // pick random phrase from chatter bank + const ChatterItem &pickRandomFromChatterBank (int chatterType) { + return m_chatter[chatterType].random (); + } + + // gets chatter repeat-interval + float getChatterMessageRepeatInterval (int chatterType) const { + return m_chatter[chatterType][0].repeat; } // get's the replies array - Array &getReplies (void) { + Array &getReplies () { return m_replies; } // get's the weapon info data - Array &getWeapons (void) { + Array &getWeapons () { return m_weapons; } // get's raw weapon info - WeaponInfo *getRawWeapons (void) { + WeaponInfo *getRawWeapons () { return m_weapons.begin (); } @@ -1709,13 +1842,31 @@ public: } // get economics value - int *getEconLimit (void) { + int *getEconLimit () { return m_botBuyEconomyTable; } // get's grenade buy percents - bool chanceToBuyGrenade (const int grenadeType) const { - return RandomSequence::ref ().chance (m_grenadeBuyPrecent[grenadeType]); + bool chanceToBuyGrenade (int grenadeType) const { + return rg.chance (m_grenadeBuyPrecent[grenadeType]); + } + + // get's random avatar for player (if any) + String getRandomAvatar () const { + if (!m_avatars.empty ()) { + return m_avatars.random (); + } + return ""; + } + + // get's random logo index + int getRandomLogoIndex () const { + return m_logos.index (m_logos.random ()); + } + + // get random name by index + const String &getRandomLogoName (int index) const { + return m_logos[index]; } }; @@ -1723,23 +1874,24 @@ class BotUtils final : public Singleton { private: bool m_needToSendWelcome; float m_welcomeReceiveTime; + StringArray m_sentences; Array m_clients; - Array > m_tags; + Array > m_tags; public: - BotUtils (void); - ~BotUtils (void) = default; + BotUtils (); + ~BotUtils () = default; public: // need to send welcome message ? - void checkWelcome (void); + void checkWelcome (); // 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); + int buildNumber (); // gets the shooting cone deviation float getShootingCone (edict_t *ent, const Vector &position); @@ -1768,9 +1920,6 @@ public: // 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); @@ -1780,11 +1929,8 @@ public: // simulate sound for players void simulateSoundUpdates (int playerIndex); - // simple format utility - const char *format (const char *format, ...); - // update stats on clients - void updateClients (void); + void updateClients (); // chat helper to strip the clantags out of the string void stripTags (String &line); @@ -1798,6 +1944,15 @@ public: // chat helper to find keywords for given string bool checkKeywords (const String &line, String &reply); + // generates ping bitmask for SVC_PINGS message + int getPingBitmask (edict_t *ent, int loss, int ping); + + // calculate our own pings for all the players + void calculatePings (); + + // send modified pings to all the clients + void sendPings (edict_t *to); + public: // re-show welcome after changelevel ? @@ -1806,12 +1961,12 @@ public: } // get array of clients - Array &getClients (void) { + Array &getClients () { return m_clients; } // get clients as const-reference - const Array &getClients (void) const { + const Array &getClients () const { return m_clients; } @@ -1832,71 +1987,80 @@ public: // bot command manager class BotControl final : public Singleton { public: - using Handler = int (BotControl::*) (void); + using Handler = int (BotControl::*) (); using MenuHandler = int (BotControl::*) (int); public: // generic bot command struct BotCmd { String name, format, help; - Handler handler; + Handler handler = nullptr; + + public: + BotCmd () = default; + BotCmd (String name, String format, String help, Handler handler) : name (cr::move (name)), format (cr::move (format)), help (cr::move (help)), handler (cr::move (handler)) { } }; // single bot menu - struct Menu { + struct BotMenu { int ident, slots; String text; MenuHandler handler; + + public: + BotMenu (int ident, int slots, String text, MenuHandler handler) : ident (ident), slots (slots), text (cr::move (text)), handler (cr::move (handler)) { } }; private: StringArray m_args; Array m_cmds; - Array m_menus; + Array m_menus; edict_t *m_ent; bool m_isFromConsole; + bool m_rapidOutput; bool m_isMenuFillCommand; int m_menuServerFillTeam; int m_interMenuData[4] = { 0, }; public: - BotControl (void); - ~BotControl (void) = default; + BotControl (); + ~BotControl () = 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); + int cmdAddBot (); + int cmdKickBot (); + int cmdKickBots (); + int cmdKillBots (); + int cmdFill (); + int cmdVote (); + int cmdWeaponMode (); + int cmdVersion (); + int cmdNodeMenu (); + int cmdMenu (); + int cmdList (); + int cmdNode (); + int cmdNodeOn (); + int cmdNodeOff (); + int cmdNodeAdd (); + int cmdNodeAddBasic (); + int cmdNodeSave (); + int cmdNodeLoad (); + int cmdNodeErase (); + int cmdNodeDelete (); + int cmdNodeCheck (); + int cmdNodeCache (); + int cmdNodeClean (); + int cmdNodeSetRadius (); + int cmdNodeSetFlags (); + int cmdNodeTeleport (); + int cmdNodePathCreate (); + int cmdNodePathDelete (); + int cmdNodePathSetAutoDistance (); + int cmdNodeAcquireEditor (); + int cmdNodeReleaseEditor (); + int cmdNodeUpload (); private: int menuMain (int item); @@ -1908,12 +2072,12 @@ private: 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 menuGraphPage1 (int item); + int menuGraphPage2 (int item); + int menuGraphRadius (int item); + int menuGraphType (int item); + int menuGraphFlag (int item); + int menuGraphPath (int item); int menuAutoPathDistance (int item); int menuKickPage1 (int item); int menuKickPage2 (int item); @@ -1922,24 +2086,24 @@ private: private: void enableDrawModels (bool enable); - void createMenus (void); + void createMenus (); public: - bool executeCommands (void); - bool executeMenus (void); + bool executeCommands (); + bool executeMenus (); + void showMenu (int id); void kickBotByMenu (int page); - void msg (const char *fmt, ...); void assignAdminRights (edict_t *ent, char *infobuffer); - void maintainAdminRights (void); + void maintainAdminRights (); public: - void setFromConsole (const bool console) { + void setFromConsole (bool console) { m_isFromConsole = console; } - void setArgs (const StringArray &args) { - m_args.assign (args); + void setRapidOutput (bool force) { + m_rapidOutput = force; } void setIssuer (edict_t *ent) { @@ -1950,17 +2114,14 @@ public: if (num < m_args.length ()) { return; } - - do { - m_args.push (""); - } while (num--); + m_args.resize (num); } int getInt (size_t arg) const { if (!hasArg (arg)) { return 0; } - return m_args[arg].toInt32 (); + return m_args[arg].int_ (); } const String &getStr (size_t arg) { @@ -1976,19 +2137,21 @@ public: return arg < m_args.length (); } - StringArray collectArgs (void) { - StringArray args; + void collectArgs () { + m_args.clear (); for (int i = 0; i < engfuncs.pfnCmd_Argc (); i++) { - args.push (engfuncs.pfnCmd_Argv (i)); + m_args.push (engfuncs.pfnCmd_Argv (i)); } - return args; } + // global heloer for sending message to correct channel + template void msg (const char *fmt, Args ...args); + public: // for the server commands - static void handleEngineCommands (void); + static void handleEngineCommands (); // for the client commands bool handleClientCommands (edict_t *ent); @@ -2000,29 +2163,44 @@ public: #include // expose bot super-globals -static auto &waypoints = Waypoint::ref (); -static auto &bots = BotManager::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 (); +static auto &graph = BotGraph::get (); +static auto &bots = BotManager::get (); +static auto &conf = BotConfig::get (); +static auto &util = BotUtils::get (); +static auto &ctrl = BotControl::get (); +static auto &game = Game::get (); +static auto &illum = LightMeasure::get (); // very global convars extern ConVar yb_jasonmode; -extern ConVar yb_communication_type; +extern ConVar yb_radio_mode; extern ConVar yb_ignore_enemies; extern ConVar yb_chat; extern ConVar yb_language; - -inline int Bot::index (void) { - return game.indexOfEntity (ent ()); -} +extern ConVar yb_show_latency; inline int Game::getTeam (edict_t *ent) { if (game.isNullEntity (ent)) { - return TEAM_UNASSIGNED; + return Team::Unassigned; } - return util.getClient (indexOfEntity (ent) - 1).team; -} \ No newline at end of file + return util.getClient (indexOfPlayer (ent)).team; +} + +// global heloer for sending message to correct channel +template inline void BotControl::msg (const char *fmt, Args ...args) { + auto result = strings.format (fmt, cr::forward (args)...); + + // if no receiver or many message have to appear, just print to server console + if (game.isNullEntity (m_ent) || m_rapidOutput) { + game.print (result); + return; + } + + if (m_isFromConsole || strlen (result) > 48) { + game.clientPrint (m_ent, result); + } + else { + game.centerPrint (m_ent, result); + game.clientPrint (m_ent, result); + } +} diff --git a/project/makefile b/project/makefile index c620d1c..de443db 100644 --- a/project/makefile +++ b/project/makefile @@ -11,18 +11,19 @@ PROJECT = yapb SOURCES = ../source OBJECTS = $(wildcard $(SOURCES)/*.cpp) -COMPILER_FLAGS = -mtune=generic -std=c++11 -m32 -Wall -Wextra -Werror -fno-exceptions -fno-rtti -DPOSIX -LINKER_FLAGS = -m32 +COMPILER_FLAGS = -std=c++11 -m32 -Wall -Wextra -Werror -fno-exceptions -fno-rtti +LINKER_FLAGS = -m32 -ldl ifeq "$(DEBUG)" "true" - COMPILER_FLAGS += -D_DEBUG -DDEBUG -g3 + COMPILER_FLAGS += -g3 -DCR_DEBUG BINARY_DIR = debug else - COMPILER_FLAGS += -DNDEBUG -pipe -O3 -msse2 -funroll-loops -fomit-frame-pointer -fno-stack-protector -fvisibility=hidden -fvisibility-inlines-hidden -nostdinc++ + COMPILER_FLAGS += -pipe -O3 -march=core2 -msse2 -mfpmath=sse -ffast-math -fno-builtin -fno-threadsafe-statics -funroll-loops -fomit-frame-pointer -fno-stack-protector -fvisibility=hidden -fvisibility-inlines-hidden BINARY_DIR = release + LINKER_FLAGS += -static-libgcc endif -INCLUDE = -I../include -I../include/engine +INCLUDE = -I../include COMPILER = $(CC) ifeq "$(shell uname -s)" "Darwin" @@ -31,11 +32,10 @@ endif ifeq "$(OSX)" "true" LIBRARY_EXT = dylib - COMPILER_FLAGS += -DOSX -D_OSX -mmacosx-version-min=10.9 + COMPILER_FLAGS += -mmacosx-version-min=10.9 LINKER_FLAGS += -dynamiclib -lstdc++ -mmacosx-version-min=10.9 -arch i386 else LIBRARY_EXT = so - COMPILER_FLAGS += -DLINUX -D_LINUX LINKER_FLAGS += -shared endif @@ -45,16 +45,26 @@ ifeq ($(findstring clang,$(COMPILER)),clang) ifeq "$(OSX)" "false" LINKER_FLAGS += -lgcc_eh else - LINKER_FLAGS += -nostdlib++ -Wunused-command-line-argument + ifeq "$(DEBUG)" "true" + LINKER_FLAGS += -lstdc++ + else + LINKER_FLAGS += -nostdlib++ -Wunused-command-line-argument -fuse-ld=lld -Wl,-z,notext --no-undefined + endif endif else ifeq ($(findstring gcc,$(COMPILER)),gcc) ifneq "$(OSX)" "false" - LINKER_FLAGS += -static-libgcc - COMPILER_FLAGS += -funroll-all-loops -Wno-implicit-fallthrough + ifneq "$(DEBUG)" "true" + LINKER_FLAGS += -Wl,--no-undefined -flto=thin + COMPILER_FLAGS += -funroll-all-loops -flto=thin + endif endif else ifeq ($(findstring icc,$(COMPILER)),icc) - COMPILER_FLAGS += -funroll-all-loops -no-prec-div -no-inline-min-size -no-inline-max-size -wd11076 -wd11074 - LINKER_FLAGS += -cxxlib-nostd -static-intel -no-intel-extensions + LINKER_FLAGS += -static-intel -no-intel-extensions + + ifneq "$(DEBUG)" "true" + COMPILER_FLAGS += -funroll-all-loops -ipo -wd11076 -wd11074 + LINKER_FLAGS += -cxxlib-nostd -Wl,--no-undefined,-z,notext,--gc-sections -ipo + endif endif OBJECTS_BIN := $(OBJECTS:%.cpp=$(BINARY_DIR)/%.o) @@ -78,6 +88,7 @@ debug: all: $(MAKE) compile DEBUG=true $(MAKE) compile DEBUG=false + clean: rm -rf release/*.o rm -rf release/$(BINARY_OUTPUT) diff --git a/project/yapb.vcxproj b/project/yapb.vcxproj index 6c99c4a..17edc5f 100644 --- a/project/yapb.vcxproj +++ b/project/yapb.vcxproj @@ -11,10 +11,29 @@ - + + + + + + + + + + + + + + + + + + + + + - @@ -22,7 +41,6 @@ - @@ -35,12 +53,13 @@ - + + @@ -108,7 +127,7 @@ Disabled - ..\include\engine;..\include;%(AdditionalIncludeDirectories) + ..\include;%(AdditionalIncludeDirectories) WIN32;_DEBUG;%(PreprocessorDefinitions) MultiThreadedDebug @@ -181,7 +200,7 @@ true Speed false - ..\mmgr;..\include\engine;..\include;%(AdditionalIncludeDirectories) + ..\include;%(AdditionalIncludeDirectories) NDEBUG;WIN32;%(PreprocessorDefinitions) false StreamingSIMDExtensions2 diff --git a/project/yapb.vcxproj.filters b/project/yapb.vcxproj.filters index 13d1ad6..123e8a9 100644 --- a/project/yapb.vcxproj.filters +++ b/project/yapb.vcxproj.filters @@ -13,14 +13,11 @@ {f98ff5ec-055a-46cd-b5b1-462ef4c1c73e} + + {76a583d1-8f55-451b-8516-2f7cce4d1875} + - - include - - - include - include @@ -42,9 +39,6 @@ include\engine - - include - include @@ -54,6 +48,69 @@ include\engine + + include\crlib + + + include\crlib + + + include\crlib + + + include\crlib + + + include\crlib + + + include\crlib + + + include\crlib + + + include\crlib + + + include\crlib + + + include\crlib + + + include\crlib + + + include\crlib + + + include\crlib + + + include\crlib + + + include\crlib + + + include\crlib + + + include\crlib + + + include\crlib + + + include\crlib + + + include\crlib + + + include\crlib + @@ -68,9 +125,6 @@ source - - source - source @@ -80,10 +134,13 @@ source + + source + source - + source @@ -96,5 +153,8 @@ project + + project + \ No newline at end of file diff --git a/source/Android.mk b/source/Android.mk index 520953c..37cd72a 100644 --- a/source/Android.mk +++ b/source/Android.mk @@ -1,7 +1,6 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) - include $(XASH3D_CONFIG) LOCAL_MODULE := yapb @@ -25,9 +24,10 @@ LOCAL_SRC_FILES := \ interface.cpp \ navigate.cpp \ support.cpp \ - waypoint.cpp \ + graph.cpp \ -LOCAL_CFLAGS += -O2 -std=c++11 -DLINUX -D_LINUX -DPOSIX -pipe -fno-strict-aliasing -Wall -Werror +LOCAL_CFLAGS += -O3 -std=c++11 -DLINUX -D_LINUX -DPOSIX -pipe -fno-strict-aliasing -Wall -Werror -Wno-array-bounds LOCAL_CPPFLAGS += -fno-exceptions -fno-rtti +LOCAL_LDLIBS := -llog include $(BUILD_SHARED_LIBRARY) diff --git a/source/basecode.cpp b/source/basecode.cpp index 71c9756..d873535 100644 --- a/source/basecode.cpp +++ b/source/basecode.cpp @@ -15,7 +15,7 @@ ConVar yb_user_follow_percent ("yb_user_follow_percent", "20"); ConVar yb_user_max_followers ("yb_user_max_followers", "1"); ConVar yb_jasonmode ("yb_jasonmode", "0"); -ConVar yb_communication_type ("yb_communication_type", "2"); +ConVar yb_radio_mode ("yb_radio_mode", "2"); ConVar yb_economics_rounds ("yb_economics_rounds", "1"); ConVar yb_walking_allowed ("yb_walking_allowed", "1"); ConVar yb_camping_allowed ("yb_camping_allowed", "1"); @@ -30,15 +30,15 @@ ConVar yb_restricted_weapons ("yb_restricted_weapons", ""); ConVar yb_best_weapon_picker_type ("yb_best_weapon_picker_type", "1"); // game console variables -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 mp_c4timer ("mp_c4timer", nullptr, Var::NoRegister); +ConVar mp_flashlight ("mp_flashlight", nullptr, Var::NoRegister); +ConVar mp_buytime ("mp_buytime", nullptr, Var::NoRegister, true, "1"); +ConVar mp_startmoney ("mp_startmoney", nullptr, Var::NoRegister, true, "800"); +ConVar mp_footsteps ("mp_footsteps", nullptr, Var::NoRegister); -ConVar sv_gravity ("sv_gravity", nullptr, VT_NOREGISTER); +ConVar sv_gravity ("sv_gravity", nullptr, Var::NoRegister); -int Bot::getMsgQueue (void) { +int Bot::getMsgQueue () { // this function get the current message from the bots message queue int message = m_messageQueue[m_actMessageIndex++]; @@ -50,14 +50,12 @@ int Bot::getMsgQueue (void) { void Bot::pushMsgQueue (int message) { // this function put a message into the bot message queue - if (message == GAME_MSG_SAY_CMD) { + if (message == BotMsg::Say) { // 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 < game.maxClients (); i++) { - auto other = bots.getBot (i); - - if (other != nullptr && other->pev != pev) { + for (const auto &other : bots) { + if (other->pev != pev) { if (m_notKilled == other->m_notKilled) { other->m_sayTextBuffer.entityIndex = entityIndex; other->m_sayTextBuffer.sayText = m_chatBuffer; @@ -71,8 +69,8 @@ void Bot::pushMsgQueue (int message) { } float Bot::isInFOV (const Vector &destination) { - float entityAngle = cr::angleMod (destination.toYaw ()); // find yaw angle from source to destination... - float viewAngle = cr::angleMod (pev->v_angle.y); // get bot's current view angle... + float entityAngle = cr::modAngles (destination.yaw ()); // find yaw angle from source to destination... + float viewAngle = cr::modAngles (pev->v_angle.y); // get bot's current view angle... // return the absolute value of angle to destination entity // zero degrees means straight ahead, 45 degrees to the left or @@ -97,7 +95,7 @@ bool Bot::seesItem (const Vector &destination, const char *itemName) { TraceResult tr; // trace a line from bot's eyes to destination.. - game.testLine (getEyesPos (), destination, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (getEyesPos (), destination, TraceIgnore::Monsters, ent (), &tr); // check if line of sight to object is not blocked (i.e. visible) if (tr.flFraction != 1.0f) { @@ -110,23 +108,23 @@ bool Bot::seesEntity (const Vector &dest, bool fromBody) { TraceResult tr; // trace a line from bot's eyes to destination... - game.testLine (fromBody ? pev->origin : getEyesPos (), dest, TRACE_IGNORE_EVERYTHING, ent (), &tr); + game.testLine (fromBody ? pev->origin : getEyesPos (), dest, TraceIgnore::Everything, ent (), &tr); // check if line of sight to object is not blocked (i.e. visible) return tr.flFraction >= 1.0f; } -void Bot::checkGrenadesThrow (void) { +void Bot::checkGrenadesThrow () { // do not check cancel if we have grenade in out hands - bool checkTasks = taskId () == TASK_PLANTBOMB || taskId () == TASK_DEFUSEBOMB; + bool checkTasks = getCurrentTaskId () == Task::PlantBomb || getCurrentTaskId () == Task::DefuseBomb; auto clearThrowStates = [] (uint32 &states) { - states &= ~(STATE_THROW_HE | STATE_THROW_FB | STATE_THROW_SG); + states &= ~(Sense::ThrowExplosive | Sense::ThrowFlashbang | Sense::ThrowSmoke); }; // 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 >= game.timebase ()) { + if (checkTasks || yb_ignore_enemies.bool_ () || m_isUsingGrenade || m_grenadeRequested || m_isReloading || yb_jasonmode.bool_ () || m_grenadeCheckTime >= game.timebase ()) { clearThrowStates (m_states); return; } @@ -134,7 +132,7 @@ void Bot::checkGrenadesThrow (void) { // check again in some seconds m_grenadeCheckTime = game.timebase () + 0.5f; - if (!util.isAlive (m_lastEnemy) || !(m_states & (STATE_SUSPECT_ENEMY | STATE_HEARING_ENEMY))) { + if (!util.isAlive (m_lastEnemy) || !(m_states & (Sense::SuspectEnemy | Sense::HearingEnemy))) { clearThrowStates (m_states); return; } @@ -152,18 +150,18 @@ void Bot::checkGrenadesThrow (void) { else { int cancelProb = 20; - if (grenadeToThrow == WEAPON_FLASHBANG) { + if (grenadeToThrow == Weapon::Flashbang) { cancelProb = 10; } - else if (grenadeToThrow == WEAPON_SMOKE) { + else if (grenadeToThrow == Weapon::Smoke) { cancelProb = 5; } - if (rng.chance (cancelProb)) { + if (rg.chance (cancelProb)) { clearThrowStates (m_states); return; } } - float distance = (m_lastEnemyOrigin - pev->origin).length2D (); + float distance = (m_lastEnemyOrigin - pev->origin).length2d (); // don't throw grenades at anything that isn't on the ground! if (!(m_lastEnemy->v.flags & FL_ONGROUND) && !m_lastEnemy->v.waterlevel && m_lastEnemyOrigin.z > pev->absmax.z) { @@ -176,38 +174,38 @@ void Bot::checkGrenadesThrow (void) { } // enemy within a good throw distance? - if (!m_lastEnemyOrigin.empty () && distance > (grenadeToThrow == WEAPON_SMOKE ? 200.0f : 400.0f) && distance < 1200.0f) { + if (!m_lastEnemyOrigin.empty () && distance > (grenadeToThrow == Weapon::Smoke ? 200.0f : 400.0f) && distance < 1200.0f) { bool allowThrowing = true; // care about different grenades switch (grenadeToThrow) { - case WEAPON_EXPLOSIVE: + case Weapon::Explosive: if (numFriendsNear (m_lastEnemy->v.origin, 256.0f) > 0) { allowThrowing = false; } else { - float radius = m_lastEnemy->v.velocity.length2D (); - const Vector &pos = (m_lastEnemy->v.velocity * 0.5f).make2D () + m_lastEnemy->v.origin; + float radius = m_lastEnemy->v.velocity.length2d (); + const Vector &pos = (m_lastEnemy->v.velocity * 0.5f).get2d () + m_lastEnemy->v.origin; if (radius < 164.0f) { radius = 164.0f; } - auto predicted = waypoints.searchRadius (radius, pos, 12); + auto predicted = graph.searchRadius (radius, pos, 12); if (predicted.empty ()) { - m_states &= ~STATE_THROW_HE; + m_states &= ~Sense::ThrowExplosive; break; } for (const auto predict : predicted) { allowThrowing = true; - if (!waypoints.exists (predict)) { + if (!graph.exists (predict)) { allowThrowing = false; continue; } - m_throw = waypoints[predict].origin; + m_throw = graph[predict].origin; auto throwPos = calcThrow (getEyesPos (), m_throw); @@ -226,18 +224,18 @@ void Bot::checkGrenadesThrow (void) { } if (allowThrowing) { - m_states |= STATE_THROW_HE; + m_states |= Sense::ThrowExplosive; } else { - m_states &= ~STATE_THROW_HE; + m_states &= ~Sense::ThrowExplosive; } break; - case WEAPON_FLASHBANG: { - int nearest = waypoints.getNearest ((m_lastEnemy->v.velocity * 0.5f).make2D () + m_lastEnemy->v.origin); + case Weapon::Flashbang: { + int nearest = graph.getNearest ((m_lastEnemy->v.velocity * 0.5f).get2d () + m_lastEnemy->v.origin); - if (nearest != INVALID_WAYPOINT_INDEX) { - m_throw = waypoints[nearest].origin; + if (nearest != kInvalidNodeIndex) { + m_throw = graph[nearest].origin; if (numFriendsNear (m_throw, 256.0f) > 0) { allowThrowing = false; @@ -263,15 +261,15 @@ void Bot::checkGrenadesThrow (void) { } if (allowThrowing) { - m_states |= STATE_THROW_FB; + m_states |= Sense::ThrowFlashbang; } else { - m_states &= ~STATE_THROW_FB; + m_states &= ~Sense::ThrowFlashbang; } break; } - case WEAPON_SMOKE: + case Weapon::Smoke: if (allowThrowing && !game.isNullEntity (m_lastEnemy)) { if (util.getShootingCone (m_lastEnemy, pev->origin) >= 0.9f) { allowThrowing = false; @@ -279,23 +277,23 @@ void Bot::checkGrenadesThrow (void) { } if (allowThrowing) { - m_states |= STATE_THROW_SG; + m_states |= Sense::ThrowSmoke; } else { - m_states &= ~STATE_THROW_SG; + m_states &= ~Sense::ThrowSmoke; } break; } const float MaxThrowTime = game.timebase () + 0.3f; - if (m_states & STATE_THROW_HE) { - startTask (TASK_THROWHEGRENADE, TASKPRI_THROWGRENADE, INVALID_WAYPOINT_INDEX, MaxThrowTime, false); + if (m_states & Sense::ThrowExplosive) { + startTask (Task::ThrowExplosive, TaskPri::Throw, kInvalidNodeIndex, MaxThrowTime, false); } - else if (m_states & STATE_THROW_FB) { - startTask (TASK_THROWFLASHBANG, TASKPRI_THROWGRENADE, INVALID_WAYPOINT_INDEX, MaxThrowTime, false); + else if (m_states & Sense::ThrowFlashbang) { + startTask (Task::ThrowFlashbang, TaskPri::Throw, kInvalidNodeIndex, MaxThrowTime, false); } - else if (m_states & STATE_THROW_SG) { - startTask (TASK_THROWSMOKE, TASKPRI_THROWGRENADE, INVALID_WAYPOINT_INDEX, MaxThrowTime, false); + else if (m_states & Sense::ThrowSmoke) { + startTask (Task::ThrowSmoke, TaskPri::Throw, kInvalidNodeIndex, MaxThrowTime, false); } } else { @@ -303,7 +301,7 @@ void Bot::checkGrenadesThrow (void) { } } -void Bot::avoidGrenades (void) { +void Bot::avoidGrenades () { // checks if bot 'sees' a grenade, and avoid it if (!bots.hasActiveGrenades ()) { @@ -333,13 +331,13 @@ void Bot::avoidGrenades (void) { } auto model = STRING (pent->v.model) + 9; - if (m_preventFlashing < game.timebase () && m_personality == PERSONALITY_RUSHER && m_difficulty == 4 && strcmp (model, "flashbang.mdl") == 0) { + if (m_preventFlashing < game.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 ((game.getAbsPos (pent) - getEyesPos ()).toAngles ().y + 180.0f); + if (!(m_states & Sense::SeeingEnemy)) { + pev->v_angle.y = cr::normalizeAngles ((game.getAbsPos (pent) - getEyesPos ()).angles ().y + 180.0f); m_canChooseAimDirection = false; - m_preventFlashing = game.timebase () + rng.getFloat (1.0f, 2.0f); + m_preventFlashing = game.timebase () + rg.float_ (1.0f, 2.0f); } } else if (strcmp (model, "hegrenade.mdl") == 0) { @@ -358,8 +356,8 @@ void Bot::avoidGrenades (void) { if (distanceMoved < distance && distance < 500.0f) { game.makeVectors (pev->v_angle); - const Vector &dirToPoint = (pev->origin - pent->v.origin).normalize2D (); - const Vector &rightSide = game.vec.right.normalize2D (); + const Vector &dirToPoint = (pev->origin - pent->v.origin).normalize2d (); + const Vector &rightSide = game.vec.right.normalize2d (); if ((dirToPoint | rightSide) > 0.0f) { m_needAvoidGrenade = -1; @@ -378,8 +376,8 @@ void Bot::avoidGrenades (void) { if (m_viewDistance > distance) { m_viewDistance = distance; - if (rng.chance (45)) { - pushChatterMessage (CHATTER_BEHIND_SMOKE); + if (rg.chance (45)) { + pushChatterMessage (Chatter::BehindSmoke); } } } @@ -398,14 +396,14 @@ void Bot::processBreakables (edict_t *touch) { } m_campButtons = pev->button & IN_DUCK; - startTask (TASK_SHOOTBREAKABLE, TASKPRI_SHOOTBREAKABLE, INVALID_WAYPOINT_INDEX, 0.0f, false); + startTask (Task::ShootBreakable, TaskPri::ShootBreakable, kInvalidNodeIndex, 0.0f, false); } -edict_t *Bot::lookupBreakable (void) { +edict_t *Bot::lookupBreakable () { // this function checks if bot is blocked by a shoot able breakable in his moving direction TraceResult tr; - game.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, TraceIgnore::None, ent (), &tr); if (tr.flFraction != 1.0f) { edict_t *ent = tr.pHit; @@ -416,7 +414,7 @@ edict_t *Bot::lookupBreakable (void) { return ent; } } - game.testLine (getEyesPos (), getEyesPos () + (m_destOrigin - getEyesPos ()).normalize () * 72.0f, TRACE_IGNORE_NONE, ent (), &tr); + game.testLine (getEyesPos (), getEyesPos () + (m_destOrigin - getEyesPos ()).normalize () * 72.0f, TraceIgnore::None, ent (), &tr); if (tr.flFraction != 1.0f) { edict_t *ent = tr.pHit; @@ -427,7 +425,7 @@ edict_t *Bot::lookupBreakable (void) { } } m_breakableEntity = nullptr; - m_breakableOrigin.nullify (); + m_breakableOrigin= nullvec; return nullptr; } @@ -446,66 +444,64 @@ void Bot::setIdealReactionTimers (bool actual) { return; } - m_idealReactionTime = rng.getFloat (reaction.min, reaction.max); + m_idealReactionTime = rg.float_ (reaction.min, reaction.max); } -void Bot::processPickups (void) { +void Bot::updatePickups () { // this function finds Items to collect or use in the near of a bot // don't try to pickup anything while on ladder or trying to escape from bomb... - if (isOnLadder () || taskId () == TASK_ESCAPEFROMBOMB || yb_jasonmode.boolean () || !bots.hasIntrestingEntities ()) { + if (isOnLadder () || getCurrentTaskId () == Task::EscapeFromBomb || yb_jasonmode.bool_ () || !bots.hasIntrestingEntities ()) { m_pickupItem = nullptr; - m_pickupType = PICKUP_NONE; + m_pickupType = Pickup::None; return; } + auto &intresting = bots.searchIntrestingEntities (); - - Bot *bot = nullptr; - constexpr float radius = cr::square (320.0f); - + const float radius = cr::square (500.0f); + if (!game.isNullEntity (m_pickupItem)) { bool itemExists = false; auto pickupItem = m_pickupItem; - - for (auto ent : intresting) { - if (util.isPlayer (ent->v.owner)) { - continue; // someone owns this weapon or it hasn't re spawned yet - } + + for (auto &ent : intresting) { const Vector &origin = game.getAbsPos (ent); // too far from us ? if ((pev->origin - origin).lengthSq () > radius) { continue; } - + if (ent == pickupItem) { if (seesItem (origin, STRING (ent->v.classname))) { itemExists = true; } + break; } } if (itemExists) { + return; } else { m_pickupItem = nullptr; - m_pickupType = PICKUP_NONE; + m_pickupType = Pickup::None; } } edict_t *pickupItem = nullptr; - PickupType pickupType = PICKUP_NONE; - Vector pickupPos = Vector::null (); + Pickup pickupType = Pickup::None; + Vector pickupPos = nullvec; m_pickupItem = nullptr; - m_pickupType = PICKUP_NONE; + m_pickupType = Pickup::None; - for (auto ent : intresting) { + for (const auto &ent : intresting) { bool allowPickup = false; // assume can't use it until known otherwise - + if (ent == m_itemIgnore) { continue; // someone owns this weapon or it hasn't respawned yet } @@ -523,35 +519,35 @@ void Bot::processPickups (void) { if (seesItem (origin, classname)) { if (strncmp ("hostage_entity", classname, 14) == 0) { allowPickup = true; - pickupType = PICKUP_HOSTAGE; + pickupType = Pickup::Hostage; } else if (strncmp ("weaponbox", classname, 9) == 0 && strcmp (model, "backpack.mdl") == 0) { allowPickup = true; - pickupType = PICKUP_DROPPED_C4; + pickupType = Pickup::DroppedC4; } else if ((strncmp ("weaponbox", classname, 9) == 0 || strncmp ("armoury_entity", classname, 14) == 0 || strncmp ("csdm", classname, 4) == 0) && !m_isUsingGrenade) { allowPickup = true; - pickupType = PICKUP_WEAPON; + pickupType = Pickup::Weapon; } else if (strncmp ("weapon_shield", classname, 13) == 0 && !m_isUsingGrenade) { allowPickup = true; - pickupType = PICKUP_SHIELD; + pickupType = Pickup::Shield; } - else if (strncmp ("item_thighpack", classname, 14) == 0 && m_team == TEAM_COUNTER && !m_hasDefuser) { + else if (strncmp ("item_thighpack", classname, 14) == 0 && m_team == Team::CT && !m_hasDefuser) { allowPickup = true; - pickupType = PICKUP_DEFUSEKIT; + pickupType = Pickup::DefusalKit; } else if (strncmp ("grenade", classname, 7) == 0 && strcmp (model, "c4.mdl") == 0) { allowPickup = true; - pickupType = PICKUP_PLANTED_C4; + pickupType = Pickup::PlantedC4; } } - + // if the bot found something it can pickup... if (allowPickup) { // found weapon on ground? - if (pickupType == PICKUP_WEAPON) { + if (pickupType == Pickup::Weapon) { int primaryWeaponCarried = bestPrimaryCarried (); int secondaryWeaponCarried = bestSecondaryCarried (); @@ -566,10 +562,10 @@ void Bot::processPickups (void) { } 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; + 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; @@ -583,7 +579,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 && primaryWeaponCarried != WEAPON_M249) { + else if (strcmp (model, "w_chainammo.mdl") == 0 && primaryWeaponCarried != Weapon::M249) { allowPickup = false; } } @@ -596,42 +592,42 @@ void Bot::processPickups (void) { else if ((strcmp (model, "kevlar.mdl") == 0 || strcmp (model, "battery.mdl") == 0) && pev->armorvalue >= 100.0f) { allowPickup = false; } - else if (strcmp (model, "flashbang.mdl") == 0 && (pev->weapons & (1 << WEAPON_FLASHBANG))) { + else if (strcmp (model, "flashbang.mdl") == 0 && (pev->weapons & cr::bit (Weapon::Flashbang))) { allowPickup = false; } - else if (strcmp (model, "hegrenade.mdl") == 0 && (pev->weapons & (1 << WEAPON_EXPLOSIVE))) { + else if (strcmp (model, "hegrenade.mdl") == 0 && (pev->weapons & cr::bit (Weapon::Explosive))) { allowPickup = false; } - else if (strcmp (model, "smokegrenade.mdl") == 0 && (pev->weapons & (1 << WEAPON_SMOKE))) { + else if (strcmp (model, "smokegrenade.mdl") == 0 && (pev->weapons & cr::bit (Weapon::Smoke))) { allowPickup = false; } } - else if (pickupType == PICKUP_SHIELD) // found a shield on ground? + else if (pickupType == Pickup::Shield) // found a shield on ground? { - if ((pev->weapons & (1 << WEAPON_ELITE)) || hasShield () || m_isVIP || (hasPrimaryWeapon () && !rateGroundWeapon (ent))) { + if ((pev->weapons & cr::bit (Weapon::Elite)) || hasShield () || m_isVIP || (hasPrimaryWeapon () && !rateGroundWeapon (ent))) { allowPickup = false; } } - else if (m_team == TEAM_TERRORIST) // terrorist team specific + else if (m_team == Team::Terrorist) // terrorist team specific { - if (pickupType == PICKUP_DROPPED_C4) { + if (pickupType == Pickup::DroppedC4) { allowPickup = true; m_destOrigin = origin; // ensure we reached dropped bomb - pushChatterMessage (CHATTER_FOUND_BOMB); // play info about that + pushChatterMessage (Chatter::FoundC4); // play info about that clearSearchNodes (); } - else if (pickupType == PICKUP_HOSTAGE) { + else if (pickupType == Pickup::Hostage) { m_itemIgnore = ent; allowPickup = false; - if (!m_defendHostage && m_difficulty > 2 && rng.chance (30) && m_timeCamping + 15.0f < game.timebase ()) { - int index = getDefendPoint (origin); + if (!m_defendHostage && m_difficulty > 2 && rg.chance (30) && m_timeCamping + 15.0f < game.timebase ()) { + int index = findDefendNode (origin); - 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 + startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, game.timebase () + rg.float_ (30.0f, 60.0f), true); // push camp task on to stack + startTask (Task::MoveToPosition, TaskPri::MoveToPosition, index, game.timebase () + rg.float_ (3.0f, 6.0f), true); // push move command - if (waypoints[index].vis.crouch <= waypoints[index].vis.stand) { + if (graph[index].vis.crouch <= graph[index].vis.stand) { m_campButtons |= IN_DUCK; } else { @@ -639,27 +635,27 @@ void Bot::processPickups (void) { } m_defendHostage = true; - pushChatterMessage (CHATTER_GOING_TO_GUARD_HOSTAGES); // play info about that + pushChatterMessage (Chatter::GoingToGuardHostages); // play info about that return; } } - else if (pickupType == PICKUP_PLANTED_C4) { + else if (pickupType == Pickup::PlantedC4) { allowPickup = false; if (!m_defendedBomb) { m_defendedBomb = true; - int index = getDefendPoint (origin); - Path &path = waypoints[index]; + int index = findDefendNode (origin); + Path &path = graph[index]; - float bombTimer = mp_c4timer.flt (); - float timeMidBlowup = bots.getTimeBombPlanted () + (bombTimer * 0.5f + bombTimer * 0.25f) - waypoints.calculateTravelTime (pev->maxspeed, pev->origin, path.origin); + float bombTimer = mp_c4timer.float_ (); + float timeMidBlowup = bots.getTimeBombPlanted () + (bombTimer * 0.5f + bombTimer * 0.25f) - graph.calculateTravelTime (pev->maxspeed, pev->origin, path.origin); if (timeMidBlowup > game.timebase ()) { - clearTask (TASK_MOVETOPOSITION); // remove any move tasks + clearTask (Task::MoveToPosition); // remove any move tasks - startTask (TASK_CAMP, TASKPRI_CAMP, INVALID_WAYPOINT_INDEX, timeMidBlowup, true); // push camp task on to stack - startTask (TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, index, timeMidBlowup, true); // push move command + startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, timeMidBlowup, true); // push camp task on to stack + startTask (Task::MoveToPosition, TaskPri::MoveToPosition, index, timeMidBlowup, true); // push move command if (path.vis.crouch <= path.vis.stand) { m_campButtons |= IN_DUCK; @@ -667,25 +663,25 @@ void Bot::processPickups (void) { else { m_campButtons &= ~IN_DUCK; } - if (rng.chance (90)) { - pushChatterMessage (CHATTER_DEFENDING_BOMBSITE); + if (rg.chance (90)) { + pushChatterMessage (Chatter::DefendingBombsite); } } else { - pushRadioMessage (RADIO_SHES_GONNA_BLOW); // issue an additional radio message + pushRadioMessage (Radio::ShesGonnaBlow); // issue an additional radio message } } } } - else if (m_team == TEAM_COUNTER) { - if (pickupType == PICKUP_HOSTAGE) { + else if (m_team == Team::CT) { + if (pickupType == Pickup::Hostage) { if (game.isNullEntity (ent) || ent->v.health <= 0) { allowPickup = false; // never pickup dead hostage } else { - for (int i = 0; i < game.maxClients (); i++) { - if ((bot = bots.getBot (i)) != nullptr && bot->m_notKilled) { - for (auto hostage : bot->m_hostages) { + for (const auto &other : bots) { + if (other->m_notKilled) { + for (const auto &hostage : other->m_hostages) { if (hostage == ent) { allowPickup = false; break; @@ -695,8 +691,8 @@ void Bot::processPickups (void) { } } } - else if (pickupType == PICKUP_PLANTED_C4) { - if (util.isPlayer (m_enemy)) { + else if (pickupType == Pickup::PlantedC4) { + if (util.isAlive (m_enemy)) { allowPickup = false; return; } @@ -705,32 +701,32 @@ void Bot::processPickups (void) { completeTask (); // then start escape from bomb immediate - startTask (TASK_ESCAPEFROMBOMB, TASKPRI_ESCAPEFROMBOMB, INVALID_WAYPOINT_INDEX, 0.0f, true); + startTask (Task::EscapeFromBomb, TaskPri::EscapeFromBomb, kInvalidNodeIndex, 0.0f, true); // and no pickup allowPickup = false; return; } - if (rng.chance (70)) { - pushChatterMessage (CHATTER_FOUND_BOMB_PLACE); + if (rg.chance (70)) { + pushChatterMessage (Chatter::FoundC4Plant); } - + allowPickup = !isBombDefusing (origin) || m_hasProgressBar; - pickupType = PICKUP_PLANTED_C4; - + pickupType = Pickup::PlantedC4; + if (!m_defendedBomb && !allowPickup) { m_defendedBomb = true; - int index = getDefendPoint (origin); - Path &path = waypoints[index]; + int index = findDefendNode (origin); + Path &path = graph[index]; - float timeToExplode = bots.getTimeBombPlanted () + mp_c4timer.flt () - waypoints.calculateTravelTime (pev->maxspeed, pev->origin, path.origin); + float timeToExplode = bots.getTimeBombPlanted () + mp_c4timer.float_ () - graph.calculateTravelTime (pev->maxspeed, pev->origin, path.origin); - clearTask (TASK_MOVETOPOSITION); // remove any move tasks + clearTask (Task::MoveToPosition); // remove any move tasks - startTask (TASK_CAMP, TASKPRI_CAMP, INVALID_WAYPOINT_INDEX, timeToExplode, true); // push camp task on to stack - startTask (TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, index, timeToExplode, true); // push move command + startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, timeToExplode, true); // push camp task on to stack + startTask (Task::MoveToPosition, TaskPri::MoveToPosition, index, timeToExplode, true); // push move command if (path.vis.crouch <= path.vis.stand) { m_campButtons |= IN_DUCK; @@ -739,22 +735,22 @@ void Bot::processPickups (void) { m_campButtons &= ~IN_DUCK; } - if (rng.chance (85)) { - pushChatterMessage (CHATTER_DEFENDING_BOMBSITE); + if (rg.chance (85)) { + pushChatterMessage (Chatter::DefendingBombsite); } } } - else if (pickupType == PICKUP_DROPPED_C4) { + else if (pickupType == Pickup::DroppedC4) { m_itemIgnore = ent; allowPickup = false; - if (!m_defendedBomb && m_difficulty > 2 && rng.chance (75) && pev->health < 80) { - int index = getDefendPoint (origin); + if (!m_defendedBomb && m_difficulty > 2 && rg.chance (75) && pev->health < 80) { + int index = findDefendNode (origin); - 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 + startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, game.timebase () + rg.float_ (30.0f, 70.0f), true); // push camp task on to stack + startTask (Task::MoveToPosition, TaskPri::MoveToPosition, index, game.timebase () + rg.float_ (10.0f, 30.0f), true); // push move command - if (waypoints[index].vis.crouch <= waypoints[index].vis.stand) { + if (graph[index].vis.crouch <= graph[index].vis.stand) { m_campButtons |= IN_DUCK; } else { @@ -762,7 +758,7 @@ void Bot::processPickups (void) { } m_defendedBomb = true; - pushChatterMessage (CHATTER_GOING_TO_GUARD_DROPPED_BOMB); // play info about that + pushChatterMessage (Chatter::GoingToGuardDroppedC4); // play info about that return; } } @@ -777,26 +773,26 @@ void Bot::processPickups (void) { break; } else { - pickupType = PICKUP_NONE; + pickupType = Pickup::None; } } } // end of the while loop 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) { + for (const auto &other : bots) { + if (other->m_notKilled && other->m_pickupItem == pickupItem) { m_pickupItem = nullptr; - m_pickupType = PICKUP_NONE; + m_pickupType = Pickup::None; return; } } // check if item is too high to reach, check if getting the item would hurt bot - if (pickupPos.z > getEyesPos ().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; + m_pickupType = Pickup::None; return; } @@ -811,7 +807,7 @@ void Bot::getCampDirection (Vector *dest) { TraceResult tr; const Vector &src = getEyesPos (); - game.testLine (src, *dest, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (src, *dest, TraceIgnore::Monsters, ent (), &tr); // check if the trace hit something... if (tr.flFraction < 1.0f) { @@ -821,31 +817,31 @@ void Bot::getCampDirection (Vector *dest) { return; } - int enemyIndex = waypoints.getNearest (*dest); - int tempIndex = waypoints.getNearest (pev->origin); + int enemyIndex = graph.getNearest (*dest); + int tempIndex = graph.getNearest (pev->origin); - if (tempIndex == INVALID_WAYPOINT_INDEX || enemyIndex == INVALID_WAYPOINT_INDEX) { + if (tempIndex == kInvalidNodeIndex || enemyIndex == kInvalidNodeIndex) { return; } float minDistance = 99999.0f; - int lookAtWaypoint = INVALID_WAYPOINT_INDEX; - Path &path = waypoints[tempIndex]; + int lookAtWaypoint = kInvalidNodeIndex; + Path &path = graph[tempIndex]; - for (auto &index : path.index) { - if (index == INVALID_WAYPOINT_INDEX) { + for (auto &link : path.links) { + if (link.index == kInvalidNodeIndex) { continue; } - auto distance = static_cast (waypoints.getPathDist (index, enemyIndex)); + auto distance = static_cast (graph.getPathDist (link.index, enemyIndex)); if (distance < minDistance) { minDistance = distance; - lookAtWaypoint = index; + lookAtWaypoint = link.index; } } - if (waypoints.exists (lookAtWaypoint)) { - *dest = waypoints[lookAtWaypoint].origin; + if (graph.exists (lookAtWaypoint)) { + *dest = graph[lookAtWaypoint].origin; } } } @@ -853,118 +849,109 @@ void Bot::getCampDirection (Vector *dest) { void Bot::showChaterIcon (bool show) { // this function depending on show boolen, shows/remove chatter, icon, on the head of bot. - if (!game.is (GAME_SUPPORT_BOT_VOICE) || yb_communication_type.integer () != 2) { + if (!game.is (GameFlags::HasBotVoice) || yb_radio_mode.int_ () != 2) { return; } auto sendBotVoice = [](bool show, edict_t *ent, int ownId) { - MessageWriter (MSG_ONE, game.getMessageId (NETMSG_BOTVOICE), Vector::null (), ent) // begin message + MessageWriter (MSG_ONE, game.getMessageId (NetMsg::BotVoice), nullvec, ent) // begin message .writeByte (show) // switch on/off .writeByte (ownId); }; - int ownId = index (); + int ownIndex = index (); for (auto &client : util.getClients ()) { - if (!(client.flags & CF_USED) || (client.ent->v.flags & FL_FAKECLIENT) || client.team != m_team) { + if (!(client.flags & ClientFlags::Used) || (client.ent->v.flags & FL_FAKECLIENT) || client.team != m_team) { continue; } - if (!show && (client.iconFlags[ownId] & CF_ICON) && client.iconTimestamp[ownId] < game.timebase ()) { - sendBotVoice (false, client.ent, ownId); + if (!show && (client.iconFlags[ownIndex] & ClientFlags::Icon) && client.iconTimestamp[ownIndex] < game.timebase ()) { + sendBotVoice (false, client.ent, entindex ()); - client.iconTimestamp[ownId] = 0.0f; - client.iconFlags[ownId] &= ~CF_ICON; + client.iconTimestamp[ownIndex] = 0.0f; + client.iconFlags[ownIndex] &= ~ClientFlags::Icon; } - else if (show && !(client.iconFlags[ownId] & CF_ICON)) { - sendBotVoice (true, client.ent, ownId); + else if (show && !(client.iconFlags[ownIndex] & ClientFlags::Icon)) { + sendBotVoice (true, client.ent, entindex ()); } } } void Bot::instantChatter (int type) { // this function sends instant chatter messages. - auto &chatter = conf.getChatter (); - - if (!game.is (GAME_SUPPORT_BOT_VOICE) || yb_communication_type.integer () != 2 || chatter[type].empty ()) { + if (!game.is (GameFlags::HasBotVoice) || yb_radio_mode.int_ () != 2 || !conf.hasChatterBank (type) || !conf.hasChatterBank (Chatter::DiePain)) { return; } - // delay only report team - if (type == RADIO_REPORT_TEAM) { - if (m_timeRepotingInDelay < game.timebase ()) { - return; - } - m_timeRepotingInDelay = game.timebase () + rng.getFloat (30.0f, 60.0f); - } - auto playbackSound = chatter[type].random (); - auto painSound = chatter[CHATTER_PAIN_DIED].random (); + auto playbackSound = conf.pickRandomFromChatterBank (type); + auto painSound = conf.pickRandomFromChatterBank (Chatter::DiePain); if (m_notKilled) { showChaterIcon (true); } MessageWriter msg; + int ownIndex = index (); for (auto &client : util.getClients ()) { - if (!(client.flags & CF_USED) || (client.ent->v.flags & FL_FAKECLIENT) || client.team != m_team) { + if (!(client.flags & ClientFlags::Used) || (client.ent->v.flags & FL_FAKECLIENT) || client.team != m_team) { continue; } - msg.start (MSG_ONE, game.getMessageId (NETMSG_SENDAUDIO), Vector::null (), client.ent); // begin message - msg.writeByte (index ()); + msg.start (MSG_ONE, game.getMessageId (NetMsg::SendAudio), nullvec, client.ent); // begin message + msg.writeByte (ownIndex); if (pev->deadflag & DEAD_DYING) { - client.iconTimestamp[index ()] = game.timebase () + painSound.duration; - msg.writeString (util.format ("%s/%s.wav", yb_chatter_path.str (), painSound.name.chars ())); + client.iconTimestamp[ownIndex] = game.timebase () + painSound.duration; + msg.writeString (strings.format ("%s/%s.wav", yb_chatter_path.str (), painSound.name.chars ())); } else if (!(pev->deadflag & DEAD_DEAD)) { - client.iconTimestamp[index ()] = game.timebase () + playbackSound.duration; - msg.writeString (util.format ("%s/%s.wav", yb_chatter_path.str (), playbackSound.name.chars ())); + client.iconTimestamp[ownIndex] = game.timebase () + playbackSound.duration; + msg.writeString (strings.format ("%s/%s.wav", yb_chatter_path.str (), playbackSound.name.chars ())); } msg.writeShort (m_voicePitch).end (); - client.iconFlags[index ()] |= CF_ICON; + client.iconFlags[ownIndex] |= ClientFlags::Icon; } } void Bot::pushRadioMessage (int message) { // this function inserts the radio message into the message queue - if (yb_communication_type.integer () == 0 || m_numFriendsLeft == 0) { + if (yb_radio_mode.int_ () == 0 || m_numFriendsLeft == 0) { return; } - 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_forceRadio = !game.is (GameFlags::HasBotVoice) || !conf.hasChatterBank (message) || yb_radio_mode.int_ () != 2; // use radio instead voice m_radioSelect = message; - pushMsgQueue (GAME_MSG_RADIO); + pushMsgQueue (BotMsg::Radio); } void Bot::pushChatterMessage (int message) { // this function inserts the voice message into the message queue (mostly same as above) - auto &chatter = conf.getChatter (); - if (!game.is (GAME_SUPPORT_BOT_VOICE) || yb_communication_type.integer () != 2 || chatter[message].empty () || m_numFriendsLeft == 0) { + if (!game.is (GameFlags::HasBotVoice) || yb_radio_mode.int_ () != 2 || !conf.hasChatterBank (message) || m_numFriendsLeft == 0) { return; } bool sendMessage = false; + const float messageRepeat = conf.getChatterMessageRepeatInterval (message); float &messageTimer = m_chatterTimes[message]; - float &messageRepeat = chatter[message][0].repeat; - if (messageTimer < game.timebase () || cr::fequal (messageTimer, MAX_CHATTER_REPEAT)) { - if (!cr::fequal (messageTimer, MAX_CHATTER_REPEAT) && !cr::fequal (messageRepeat, MAX_CHATTER_REPEAT)) { + if (messageTimer < game.timebase () || cr::fequal (messageTimer, kMaxChatterRepeatInteval)) { + if (!cr::fequal (messageTimer, kMaxChatterRepeatInteval) && !cr::fequal (messageRepeat, kMaxChatterRepeatInteval)) { messageTimer = game.timebase () + messageRepeat; } sendMessage = true; } if (!sendMessage) { + m_radioSelect = -1; return; } m_radioSelect = message; - pushMsgQueue (GAME_MSG_RADIO); + pushMsgQueue (BotMsg::Radio); } -void Bot::checkMsgQueue (void) { +void Bot::checkMsgQueue () { // this function checks and executes pending messages extern ConVar mp_freezetime; @@ -977,21 +964,21 @@ void Bot::checkMsgQueue (void) { int state = getMsgQueue (); // nothing to do? - if (state == GAME_MSG_NONE || (state == GAME_MSG_RADIO && game.is (GAME_CSDM_FFA))) { + if (state == BotMsg::None || (state == BotMsg::Radio && game.is (GameFlags::FreeForAll))) { return; } switch (state) { - case GAME_MSG_PURCHASE: // general buy message + case BotMsg::Buy: // general buy message // buy weapon if (m_nextBuyTime > game.timebase ()) { // keep sending message - pushMsgQueue (GAME_MSG_PURCHASE); + pushMsgQueue (BotMsg::Buy); return; } - if (!m_inBuyZone || game.is (GAME_CSDM)) { + if (!m_inBuyZone || game.is (GameFlags::CSDM)) { m_buyPending = true; m_buyingFinished = true; @@ -999,177 +986,98 @@ void Bot::checkMsgQueue (void) { } m_buyPending = false; - m_nextBuyTime = game.timebase () + rng.getFloat (0.5f, 1.3f); + m_nextBuyTime = game.timebase () + rg.float_ (0.5f, 1.3f); // if freezetime is very low do not delay the buy process - if (mp_freezetime.flt () <= 1.0f) { + if (mp_freezetime.float_ () <= 1.0f) { m_nextBuyTime = game.timebase (); m_ignoreBuyDelay = true; } // if bot buying is off then no need to buy - if (!yb_botbuy.boolean ()) { - m_buyState = BUYSTATE_FINISHED; + if (!yb_botbuy.bool_ ()) { + m_buyState = BuyState::Done; } // if fun-mode no need to buy - if (yb_jasonmode.boolean ()) { - m_buyState = BUYSTATE_FINISHED; + if (yb_jasonmode.bool_ ()) { + m_buyState = BuyState::Done; selectWeaponByName ("weapon_knife"); } // prevent vip from buying if (m_isVIP) { - m_buyState = BUYSTATE_FINISHED; - m_pathType = SEARCH_PATH_FASTEST; + m_buyState = BuyState::Done; + m_pathType = FindPath::Fast; } // prevent terrorists from buying on es maps - if (game.mapIs (MAP_ES) && m_team == TEAM_TERRORIST) { + if (game.mapIs (MapFlags::Escape) && m_team == Team::Terrorist) { m_buyState = 6; } // prevent teams from buying on fun maps - if (game.mapIs (MAP_KA | MAP_FY)) { - m_buyState = BUYSTATE_FINISHED; + if (game.mapIs (MapFlags::KnifeArena | MapFlags::Fun)) { + m_buyState = BuyState::Done; - if (game.mapIs (MAP_KA)) { + if (game.mapIs (MapFlags::KnifeArena)) { yb_jasonmode.set (1); } } - if (m_buyState > BUYSTATE_FINISHED - 1) { + if (m_buyState > BuyState::Done - 1) { m_buyingFinished = true; return; } - pushMsgQueue (GAME_MSG_NONE); + pushMsgQueue (BotMsg::None); buyStuff (); break; - case GAME_MSG_RADIO: + case BotMsg::Radio: // if last bot radio command (global) happened just a 3 seconds ago, delay response if (bots.getLastRadioTimestamp (m_team) + 3.0f < game.timebase ()) { // if same message like previous just do a yes/no - if (m_radioSelect != RADIO_AFFIRMATIVE && m_radioSelect != RADIO_NEGATIVE) { + if (m_radioSelect != Radio::RogerThat && m_radioSelect != Radio::Negative) { 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) { + if (m_radioSelect != Radio::ReportingIn) { bots.setLastRadio (m_team, m_radioSelect); } else { bots.setLastRadio (m_team, -1); } - for (int i = 0; i < game.maxClients (); i++) { - Bot *bot = bots.getBot (i); - - if (bot != nullptr) { - if (pev != bot->pev && bot->m_team == m_team) { - bot->m_radioOrder = m_radioSelect; - bot->m_radioEntity = ent (); - } + for (const auto &bot : bots) { + if (pev != bot->pev && bot->m_team == m_team) { + bot->m_radioOrder = m_radioSelect; + bot->m_radioEntity = ent (); } } } } - if (m_radioSelect == RADIO_REPORTING_IN) { - switch (taskId ()) { - case TASK_NORMAL: - if (getTask ()->data != INVALID_WAYPOINT_INDEX && rng.chance (70)) { - Path &path = waypoints[getTask ()->data]; - - if (path.flags & FLAG_GOAL) { - if (game.mapIs (MAP_DE) && m_team == TEAM_TERRORIST && m_hasC4) { - instantChatter (CHATTER_GOING_TO_PLANT_BOMB); - } - else { - instantChatter (CHATTER_NOTHING); - } - } - else if (path.flags & FLAG_RESCUE) { - instantChatter (CHATTER_RESCUING_HOSTAGES); - } - else if ((path.flags & FLAG_CAMP) && rng.chance (75)) { - instantChatter (CHATTER_GOING_TO_CAMP); - } - else { - instantChatter (CHATTER_HEARD_NOISE); - } - } - else if (rng.chance (30)) { - instantChatter (CHATTER_REPORTING_IN); - } - break; - - case TASK_MOVETOPOSITION: - if (rng.chance (2)) { - instantChatter (CHATTER_GOING_TO_CAMP); - } - break; - - case TASK_CAMP: - if (rng.chance (40)) { - if (bots.isBombPlanted () && m_team == TEAM_TERRORIST) { - instantChatter (CHATTER_GUARDING_DROPPED_BOMB); - } - else if (m_inVIPZone && m_team == TEAM_TERRORIST) { - instantChatter (CHATTER_GUARDING_VIP_SAFETY); - } - else { - instantChatter (CHATTER_CAMP); - } - } - break; - - case TASK_PLANTBOMB: - instantChatter (CHATTER_PLANTING_BOMB); - break; - - case TASK_DEFUSEBOMB: - instantChatter (CHATTER_DEFUSING_BOMB); - break; - - case TASK_ATTACK: - instantChatter (CHATTER_IN_COMBAT); - break; - - case TASK_HIDE: - case TASK_SEEKCOVER: - instantChatter (CHATTER_SEEK_ENEMY); - break; - - default: - if (rng.chance (50)) { - instantChatter (CHATTER_NOTHING); - } - break; - } - } - auto &chatter = conf.getChatter (); - if (m_radioSelect != -1) { - 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) { - game.execBotCmd (ent (), "radio1"); + if ((m_radioSelect != Radio::ReportingIn && m_forceRadio) || yb_radio_mode.int_ () != 2 || !conf.hasChatterBank (m_radioSelect) || !game.is (GameFlags::HasBotVoice)) { + if (m_radioSelect < Radio::GoGoGo) { + game.botCommand (ent (), "radio1"); } - else if (m_radioSelect < RADIO_AFFIRMATIVE) { - m_radioSelect -= RADIO_GO_GO_GO - 1; - game.execBotCmd (ent (), "radio2"); + else if (m_radioSelect < Radio::RogerThat) { + m_radioSelect -= Radio::GoGoGo - 1; + game.botCommand (ent (), "radio2"); } else { - m_radioSelect -= RADIO_AFFIRMATIVE - 1; - game.execBotCmd (ent (), "radio3"); + m_radioSelect -= Radio::RogerThat - 1; + game.botCommand (ent (), "radio3"); } // select correct menu item for this radio message - game.execBotCmd (ent (), "menuselect %d", m_radioSelect); + game.botCommand (ent (), "menuselect %d", m_radioSelect); } - else if (m_radioSelect != RADIO_REPORTING_IN) { + else if (m_radioSelect != Radio::ReportingIn) { instantChatter (m_radioSelect); } } @@ -1177,17 +1085,17 @@ void Bot::checkMsgQueue (void) { bots.setLastRadioTimestamp (m_team, game.timebase ()); // store last radio usage } else { - pushMsgQueue (GAME_MSG_RADIO); + pushMsgQueue (BotMsg::Radio); } break; // team independent saytext - case GAME_MSG_SAY_CMD: + case BotMsg::Say: say (m_chatBuffer.chars ()); break; // team dependent saytext - case GAME_MSG_SAY_TEAM_MSG: + case BotMsg::SayTeam: sayTeam (m_chatBuffer.chars ()); break; @@ -1219,7 +1127,7 @@ bool Bot::isWeaponRestrictedAMX (int weaponIndex) { // this function checks restriction set by AMX Mod, this function code is courtesy of KWo. // check for weapon restrictions - if ((1 << weaponIndex) & (WEAPON_PRIMARY | WEAPON_SECONDARY | WEAPON_SHIELD)) { + if (cr::bit (weaponIndex) & (kPrimaryWeaponMask | kSecondaryWeaponMask | Weapon::Shield)) { const char *restrictedWeapons = engfuncs.pfnCVarGetString ("amx_restrweapons"); if (util.isEmptyStr (restrictedWeapons)) { @@ -1257,7 +1165,7 @@ bool Bot::isWeaponRestrictedAMX (int weaponIndex) { } } -bool Bot::canReplaceWeapon (void) { +bool Bot::canReplaceWeapon () { // this function determines currently owned primary weapon, and checks if bot has // enough money to buy more powerful weapon. @@ -1279,13 +1187,13 @@ bool Bot::canReplaceWeapon (void) { } } - if (m_currentWeapon == WEAPON_SCOUT && m_moneyAmount > 5000) { + if (m_currentWeapon == Weapon::Scout && m_moneyAmount > 5000) { return true; } - else if (m_currentWeapon == WEAPON_MP5 && m_moneyAmount > 6000) { + else if (m_currentWeapon == Weapon::MP5 && m_moneyAmount > 6000) { return true; } - else if ((m_currentWeapon == WEAPON_M3 || m_currentWeapon == WEAPON_XM1014) && m_moneyAmount > 4000) { + else if ((m_currentWeapon == Weapon::M3 || m_currentWeapon == Weapon::XM1014) && m_moneyAmount > 4000) { return true; } return false; @@ -1294,7 +1202,7 @@ bool Bot::canReplaceWeapon (void) { int Bot::pickBestWeapon (int *vec, int count, int moneySave) { // this function picks best available weapon from random choice with money save - if (yb_best_weapon_picker_type.integer () == 1) { + if (yb_best_weapon_picker_type.int_ () == 1) { auto pick = [] (const float factor) -> float { union { @@ -1316,58 +1224,58 @@ int Bot::pickBestWeapon (int *vec, int count, int moneySave) { for (int *begin = vec, *end = vec + count - 1; begin < end; ++begin, --end) { cr::swap (*end, *begin); } - return vec[static_cast (static_cast (count - 1) * pick (rng.getFloat (1.0f, cr::powf (10.0f, buyFactor))) / buyFactor + 0.5f)]; + return vec[static_cast (static_cast (count - 1) * pick (rg.float_ (1.0f, cr::powf (10.0f, buyFactor))) / buyFactor + 0.5f)]; } int chance = 95; // high skilled bots almost always prefer best weapon if (m_difficulty < 4) { - if (m_personality == PERSONALITY_NORMAL) { + if (m_personality == Personality::Normal) { chance = 50; } - else if (m_personality == PERSONALITY_CAREFUL) { + else if (m_personality == Personality::Careful) { chance = 75; } } auto &info = conf.getWeapons (); - for (int i = 0; i < count; i++) { + for (int i = 0; i < count; ++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 + rg.int_ (50, 200) && rg.chance (chance)) { return vec[i]; } } - return vec[rng.getInt (0, count - 1)]; + return vec[rg.int_ (0, count - 1)]; } -void Bot::buyStuff (void) { +void Bot::buyStuff () { // this function does all the work in selecting correct buy menus for most weapons/items WeaponInfo *selectedWeapon = nullptr; m_nextBuyTime = game.timebase (); if (!m_ignoreBuyDelay) { - m_nextBuyTime += rng.getFloat (0.3f, 0.5f); + m_nextBuyTime += rg.float_ (0.3f, 0.5f); } int count = 0, weaponCount = 0; - int choices[NUM_WEAPONS]; + int choices[kNumWeapons]; // select the priority tab for this personality - const int *pref = conf.getWeaponPrefs (m_personality) + NUM_WEAPONS; + const int *pref = conf.getWeaponPrefs (m_personality) + kNumWeapons; auto tab = conf.getRawWeapons (); 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 = game.is (GAME_LEGACY) && !game.is (GAME_XASH_ENGINE); + bool isOldGame = game.is (GameFlags::Legacy) && !game.is (GameFlags::Xash3D); switch (m_buyState) { - case BUYSTATE_PRIMARY_WEAPON: // if no primary weapon and bot has some money, buy a primary weapon + case BuyState::PrimaryWeapon: // if no primary weapon and bot has some money, buy a primary weapon if ((!hasShield () && !hasPrimaryWeapon () && teamEcoValid) || (teamEcoValid && canReplaceWeapon ())) { int moneySave = 0; @@ -1377,7 +1285,7 @@ void Bot::buyStuff (void) { pref--; assert (*pref > -1); - assert (*pref < NUM_WEAPONS); + assert (*pref < kNumWeapons); selectedWeapon = &tab[*pref]; count++; @@ -1387,7 +1295,7 @@ void Bot::buyStuff (void) { } // weapon available for every team? - if (game.mapIs (MAP_AS) && selectedWeapon->teamAS != 2 && selectedWeapon->teamAS != m_team) { + if (game.mapIs (MapFlags::Assassination) && selectedWeapon->teamAS != 2 && selectedWeapon->teamAS != m_team) { continue; } @@ -1411,43 +1319,44 @@ void Bot::buyStuff (void) { // filter out weapons with bot economics switch (m_personality) { - case PERSONALITY_RUSHER: - prostock = limit[ECO_PROSTOCK_RUSHER]; + case Personality::Rusher: + prostock = limit[EcoLimit::ProstockRusher]; break; - case PERSONALITY_CAREFUL: - prostock = limit[ECO_PROSTOCK_CAREFUL]; + case Personality::Careful: + prostock = limit[EcoLimit::ProstockCareful]; break; - case PERSONALITY_NORMAL: - prostock = limit[ECO_PROSTOCK_NORMAL]; + case Personality::Normal: + default: + prostock = limit[EcoLimit::ProstockNormal]; break; } - if (m_team == TEAM_COUNTER) { + if (m_team == Team::CT) { switch (selectedWeapon->id) { - case WEAPON_TMP: - case WEAPON_UMP45: - case WEAPON_P90: - case WEAPON_MP5: - if (m_moneyAmount > limit[ECO_SMG_GT_CT] + prostock) { + case Weapon::TMP: + case Weapon::UMP45: + case Weapon::P90: + case Weapon::MP5: + if (m_moneyAmount > limit[EcoLimit::SmgCTGreater] + prostock) { ignoreWeapon = true; } break; } - if (selectedWeapon->id == WEAPON_SHIELD && m_moneyAmount > limit[ECO_SHIELDGUN_GT]) { + if (selectedWeapon->id == Weapon::Shield && m_moneyAmount > limit[EcoLimit::ShieldGreater]) { ignoreWeapon = true; } } - else if (m_team == TEAM_TERRORIST) { + else if (m_team == Team::Terrorist) { switch (selectedWeapon->id) { - case WEAPON_UMP45: - case WEAPON_MAC10: - case WEAPON_P90: - case WEAPON_MP5: - case WEAPON_SCOUT: - if (m_moneyAmount > limit[ECO_SMG_GT_TE] + prostock) { + case Weapon::UMP45: + case Weapon::MAC10: + case Weapon::P90: + case Weapon::MP5: + case Weapon::Scout: + if (m_moneyAmount > limit[EcoLimit::SmgTEGreater] + prostock) { ignoreWeapon = true; } break; @@ -1455,13 +1364,13 @@ void Bot::buyStuff (void) { } switch (selectedWeapon->id) { - case WEAPON_XM1014: - case WEAPON_M3: - if (m_moneyAmount < limit[ECO_SHOTGUN_LT]) { + case Weapon::XM1014: + case Weapon::M3: + if (m_moneyAmount < limit[EcoLimit::ShotgunLess]) { ignoreWeapon = true; } - if (m_moneyAmount >= limit[ECO_SHOTGUN_GT]) { + if (m_moneyAmount >= limit[EcoLimit::ShotgunGreater]) { ignoreWeapon = false; } @@ -1469,27 +1378,27 @@ void Bot::buyStuff (void) { } switch (selectedWeapon->id) { - case WEAPON_SG550: - case WEAPON_G3SG1: - case WEAPON_AWP: - case WEAPON_M249: - if (m_moneyAmount < limit[ECO_HEAVY_LT]) { + case Weapon::SG550: + case Weapon::G3SG1: + case Weapon::AWP: + case Weapon::M249: + if (m_moneyAmount < limit[EcoLimit::HeavyLess]) { ignoreWeapon = true; } - if (m_moneyAmount >= limit[ECO_HEAVY_GT]) { + if (m_moneyAmount >= limit[EcoLimit::HeavyGreater]) { ignoreWeapon = false; } break; } - if (ignoreWeapon && tab[25].teamStandard == 1 && yb_economics_rounds.boolean ()) { + if (ignoreWeapon && tab[25].teamStandard == 1 && yb_economics_rounds.bool_ ()) { continue; } // save money for grenade for example? - moneySave = rng.getInt (500, 1000); + moneySave = rg.int_ (500, 1000); if (bots.getLastWinner () == m_team) { moneySave = 0; @@ -1499,7 +1408,7 @@ void Bot::buyStuff (void) { choices[weaponCount++] = *pref; } - } while (count < NUM_WEAPONS && weaponCount < 4); + } while (count < kNumWeapons && weaponCount < 4); // found a desired weapon? if (weaponCount > 0) { @@ -1519,50 +1428,50 @@ void Bot::buyStuff (void) { } if (selectedWeapon != nullptr) { - game.execBotCmd (ent (), "buy;menuselect %d", selectedWeapon->buyGroup); + game.botCommand (ent (), "buy;menuselect %d", selectedWeapon->buyGroup); if (isOldGame) { - game.execBotCmd (ent (), "menuselect %d", selectedWeapon->buySelect); + game.botCommand (ent (), "menuselect %d", selectedWeapon->buySelect); } else { - if (m_team == TEAM_TERRORIST) { - game.execBotCmd (ent (), "menuselect %d", selectedWeapon->newBuySelectT); + if (m_team == Team::Terrorist) { + game.botCommand (ent (), "menuselect %d", selectedWeapon->newBuySelectT); } else { - game.execBotCmd (ent (), "menuselect %d", selectedWeapon->newBuySelectCT); + game.botCommand (ent (), "menuselect %d", selectedWeapon->newBuySelectCT); } } } } else if (hasPrimaryWeapon () && !hasShield ()) { - m_reloadState = RELOAD_PRIMARY; + m_reloadState = Reload::Primary; break; } else if ((hasSecondaryWeapon () && !hasShield ()) || hasShield ()) { - m_reloadState = RELOAD_SECONDARY; + m_reloadState = Reload::Secondary; break; } break; - case BUYSTATE_ARMOR_VESTHELM: // if armor is damaged and bot has some money, buy some armor - if (pev->armorvalue < rng.getInt (50, 80) && (isPistolMode || (teamEcoValid && hasPrimaryWeapon ()))) { + case BuyState::ArmorVestHelm: // if armor is damaged and bot has some money, buy some armor + if (pev->armorvalue < rg.int_ (50, 80) && (isPistolMode || (teamEcoValid && hasPrimaryWeapon ()))) { // if bot is rich, buy kevlar + helmet, else buy a single kevlar - if (m_moneyAmount > 1500 && !isWeaponRestricted (WEAPON_ARMORHELM)) { - game.execBotCmd (ent (), "buyequip;menuselect 2"); + if (m_moneyAmount > 1500 && !isWeaponRestricted (Weapon::ArmorHelm)) { + game.botCommand (ent (), "buyequip;menuselect 2"); } - else if (!isWeaponRestricted (WEAPON_ARMOR)) { - game.execBotCmd (ent (), "buyequip;menuselect 1"); + else if (!isWeaponRestricted (Weapon::Armor)) { + game.botCommand (ent (), "buyequip;menuselect 1"); } } break; - 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))) { + case BuyState::SecondaryWeapon: // if bot has still some money, buy a better secondary weapon + if (isPistolMode || (hasPrimaryWeapon () && (pev->weapons & (cr::bit (Weapon::USP) | cr::bit (Weapon::Glock18))) && m_moneyAmount > rg.int_ (7500, 9000))) { do { pref--; assert (*pref > -1); - assert (*pref < NUM_WEAPONS); + assert (*pref < kNumWeapons); selectedWeapon = &tab[*pref]; count++; @@ -1577,7 +1486,7 @@ void Bot::buyStuff (void) { } // weapon available for every team? - if (game.mapIs (MAP_AS) && selectedWeapon->teamAS != 2 && selectedWeapon->teamAS != m_team) { + if (game.mapIs (MapFlags::Assassination) && selectedWeapon->teamAS != 2 && selectedWeapon->teamAS != m_team) { continue; } @@ -1589,11 +1498,11 @@ void Bot::buyStuff (void) { continue; } - if (selectedWeapon->price <= (m_moneyAmount - rng.getInt (100, 200))) { + if (selectedWeapon->price <= (m_moneyAmount - rg.int_ (100, 200))) { choices[weaponCount++] = *pref; } - } while (count < NUM_WEAPONS && weaponCount < 4); + } while (count < kNumWeapons && weaponCount < 4); // found a desired weapon? if (weaponCount > 0) { @@ -1601,7 +1510,7 @@ void Bot::buyStuff (void) { // choose randomly from the best ones... if (weaponCount > 1) { - chosenWeapon = pickBestWeapon (choices, weaponCount, rng.getInt (100, 200)); + chosenWeapon = pickBestWeapon (choices, weaponCount, rg.int_ (100, 200)); } else { chosenWeapon = choices[weaponCount - 1]; @@ -1613,98 +1522,98 @@ void Bot::buyStuff (void) { } if (selectedWeapon != nullptr) { - game.execBotCmd (ent (), "buy;menuselect %d", selectedWeapon->buyGroup); + game.botCommand (ent (), "buy;menuselect %d", selectedWeapon->buyGroup); if (isOldGame) { - game.execBotCmd (ent (), "menuselect %d", selectedWeapon->buySelect); + game.botCommand (ent (), "menuselect %d", selectedWeapon->buySelect); } else { - if (m_team == TEAM_TERRORIST) { - game.execBotCmd (ent (), "menuselect %d", selectedWeapon->newBuySelectT); + if (m_team == Team::Terrorist) { + game.botCommand (ent (), "menuselect %d", selectedWeapon->newBuySelectT); } else { - game.execBotCmd (ent (), "menuselect %d", selectedWeapon->newBuySelectCT); + game.botCommand (ent (), "menuselect %d", selectedWeapon->newBuySelectCT); } } } } break; - case BUYSTATE_GRENADES: // if bot has still some money, choose if bot should buy a grenade or not + case BuyState::Grenades: // if bot has still some money, choose if bot should buy a grenade or not // buy a he grenade - if (conf.chanceToBuyGrenade (0) && m_moneyAmount >= 400 && !isWeaponRestricted (WEAPON_EXPLOSIVE)) { - game.execBotCmd (ent (), "buyequip"); - game.execBotCmd (ent (), "menuselect 4"); + if (conf.chanceToBuyGrenade (0) && m_moneyAmount >= 400 && !isWeaponRestricted (Weapon::Explosive)) { + game.botCommand (ent (), "buyequip"); + game.botCommand (ent (), "menuselect 4"); } // 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 (conf.chanceToBuyGrenade (1) && m_moneyAmount >= 300 && teamEcoValid && !isWeaponRestricted (Weapon::Flashbang)) { + game.botCommand (ent (), "buyequip"); + game.botCommand (ent (), "menuselect 3"); } // buy a smoke grenade - if (conf.chanceToBuyGrenade (2) && m_moneyAmount >= 400 && teamEcoValid && !isWeaponRestricted (WEAPON_SMOKE)) { - game.execBotCmd (ent (), "buyequip"); - game.execBotCmd (ent (), "menuselect 5"); + if (conf.chanceToBuyGrenade (2) && m_moneyAmount >= 400 && teamEcoValid && !isWeaponRestricted (Weapon::Smoke)) { + game.botCommand (ent (), "buyequip"); + game.botCommand (ent (), "menuselect 5"); } break; - case BUYSTATE_DEFUSER: // if bot is CT and we're on a bomb map, randomly buy the defuse kit - if (game.mapIs (MAP_DE) && m_team == TEAM_COUNTER && rng.chance (80) && m_moneyAmount > 200 && !isWeaponRestricted (WEAPON_DEFUSER)) { + case BuyState::DefusalKit: // if bot is CT and we're on a bomb map, randomly buy the defuse kit + if (game.mapIs (MapFlags::Demolition) && m_team == Team::CT && rg.chance (80) && m_moneyAmount > 200 && !isWeaponRestricted (Weapon::Defuser)) { if (isOldGame) { - game.execBotCmd (ent (), "buyequip;menuselect 6"); + game.botCommand (ent (), "buyequip;menuselect 6"); } else { - game.execBotCmd (ent (), "defuser"); // use alias in steamcs + game.botCommand (ent (), "defuser"); // use alias in steamcs } } break; - case BUYSTATE_NIGHTVISION: - if (m_moneyAmount > 2500 && !m_hasNVG && rng.chance (30)) { + case BuyState::NightVision: + if (m_moneyAmount > 2500 && !m_hasNVG && rg.chance (30) && m_path) { float skyColor = illum.getSkyColor (); - float lightLevel = waypoints.getLightLevel (m_currentWaypointIndex); + float lightLevel = m_path->light; // if it's somewhat darkm do buy nightvision goggles if ((skyColor >= 50.0f && lightLevel <= 15.0f) || (skyColor < 50.0f && lightLevel < 40.0f)) { if (isOldGame) { - game.execBotCmd (ent (), "buyequip;menuselect 7"); + game.botCommand (ent (), "buyequip;menuselect 7"); } else { - game.execBotCmd (ent (), "nvgs"); // use alias in steamcs + game.botCommand (ent (), "nvgs"); // use alias in steamcs } } } break; - case BUYSTATE_AMMO: // buy enough primary & secondary ammo (do not check for money here) - for (int i = 0; i <= 5; i++) { - game.execBotCmd (ent (), "buyammo%d", rng.getInt (1, 2)); // simulate human + case BuyState::Ammo: // buy enough primary & secondary ammo (do not check for money here) + for (int i = 0; i <= 5; ++i) { + game.botCommand (ent (), "buyammo%d", rg.int_ (1, 2)); // simulate human } // buy enough secondary ammo if (hasPrimaryWeapon ()) { - game.execBotCmd (ent (), "buy;menuselect 7"); + game.botCommand (ent (), "buy;menuselect 7"); } // buy enough primary ammo - game.execBotCmd (ent (), "buy;menuselect 6"); + game.botCommand (ent (), "buy;menuselect 6"); // try to reload secondary weapon - if (m_reloadState != RELOAD_PRIMARY) { - m_reloadState = RELOAD_SECONDARY; + if (m_reloadState != Reload::Primary) { + m_reloadState = Reload::Secondary; } m_ignoreBuyDelay = false; break; } m_buyState++; - pushMsgQueue (GAME_MSG_PURCHASE); + pushMsgQueue (BotMsg::Buy); } -void Bot::updateEmotions (void) { +void Bot::updateEmotions () { // slowly increase/decrease dynamic emotions back to their base level if (m_nextEmotionUpdate > game.timebase ()) { return; @@ -1734,9 +1643,9 @@ void Bot::updateEmotions (void) { m_nextEmotionUpdate = game.timebase () + 1.0f; } -void Bot::overrideConditions (void) { +void Bot::overrideConditions () { - 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 ()) { + if (m_currentWeapon != Weapon::Knife && m_difficulty > 2 && ((m_aimFlags & AimFlags::Enemy) || (m_states & Sense::SeeingEnemy)) && !yb_jasonmode.bool_ () && getCurrentTaskId () != Task::Camp && getCurrentTaskId () != Task::SeekCover && !isOnLadder ()) { m_moveToGoal = false; // don't move to goal m_navTimeset = game.timebase (); @@ -1746,26 +1655,26 @@ void Bot::overrideConditions (void) { } // check if we need to escape from bomb - if (game.mapIs (MAP_DE) && bots.isBombPlanted () && m_notKilled && taskId () != TASK_ESCAPEFROMBOMB && taskId () != TASK_CAMP && isOutOfBombTimer ()) { + if (game.mapIs (MapFlags::Demolition) && bots.isBombPlanted () && m_notKilled && getCurrentTaskId () != Task::EscapeFromBomb && getCurrentTaskId () != Task::Camp && isOutOfBombTimer ()) { completeTask (); // complete current task // then start escape from bomb immediate - startTask (TASK_ESCAPEFROMBOMB, TASKPRI_ESCAPEFROMBOMB, INVALID_WAYPOINT_INDEX, 0.0f, true); + startTask (Task::EscapeFromBomb, TaskPri::EscapeFromBomb, kInvalidNodeIndex, 0.0f, true); } // special handling, if we have a knife in our hands - if ((bots.getRoundStartTime () + 6.0f > game.timebase () || !hasAnyWeapons ()) && m_currentWeapon == WEAPON_KNIFE && util.isPlayer (m_enemy)) { - float length = (pev->origin - m_enemy->v.origin).length2D (); + 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 - if (length > 100.0f && (m_states & STATE_SEEING_ENEMY)) { - int nearestToEnemyPoint = waypoints.getNearest (m_enemy->v.origin); + if (length > 100.0f && (m_states & Sense::SeeingEnemy)) { + int nearestToEnemyPoint = graph.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) { + if (nearestToEnemyPoint != kInvalidNodeIndex && nearestToEnemyPoint != m_currentNodeIndex && cr::abs (graph[nearestToEnemyPoint].origin.z - m_enemy->v.origin.z) < 16.0f) { float taskTime = game.timebase () + length / pev->maxspeed * 0.5f; - if (taskId () != TASK_MOVETOPOSITION && getTask ()->desire != TASKPRI_HIDE) { - startTask (TASK_MOVETOPOSITION, TASKPRI_HIDE, nearestToEnemyPoint, taskTime, true); + if (getCurrentTaskId () != Task::MoveToPosition && getTask ()->desire != TaskPri::Hide) { + startTask (Task::MoveToPosition, TaskPri::Hide, nearestToEnemyPoint, taskTime, true); } m_isEnemyReachable = false; m_enemy = nullptr; @@ -1776,14 +1685,14 @@ void Bot::overrideConditions (void) { } // special handling for sniping - if (usesSniper () && (m_states & (STATE_SEEING_ENEMY | STATE_SUSPECT_ENEMY)) && m_sniperStopTime > game.timebase () && taskId () != TASK_SEEKCOVER) { + if (usesSniper () && (m_states & (Sense::SeeingEnemy | Sense::SuspectEnemy)) && m_sniperStopTime > game.timebase () && getCurrentTaskId () != Task::SeekCover) { m_moveSpeed = 0.0f; m_strafeSpeed = 0.0f; m_navTimeset = game.timebase (); } } -void Bot::setConditions (void) { +void Bot::setConditions () { // this function carried out each frame. does all of the sensing, calculates emotions and finally sets the desired // action after applying all of the Filters @@ -1793,10 +1702,10 @@ void Bot::setConditions (void) { // does bot see an enemy? if (lookupEnemies ()) { - m_states |= STATE_SEEING_ENEMY; + m_states |= Sense::SeeingEnemy; } else { - m_states &= ~STATE_SEEING_ENEMY; + m_states &= ~Sense::SeeingEnemy; m_enemy = nullptr; } @@ -1810,59 +1719,59 @@ void Bot::setConditions (void) { m_agressionLevel = 1.0f; } - if (rng.chance (10)) { - pushChatMessage (CHAT_KILLING); + if (rg.chance (10)) { + pushChatMessage (Chat::Kill); } - if (rng.chance (10)) { - pushRadioMessage (RADIO_ENEMY_DOWN); + if (rg.chance (10)) { + pushRadioMessage (Radio::EnemyDown); } - else if (rng.chance (60)) { - if ((m_lastVictim->v.weapons & (1 << WEAPON_AWP)) || (m_lastVictim->v.weapons & (1 << WEAPON_SCOUT)) || (m_lastVictim->v.weapons & (1 << WEAPON_G3SG1)) || (m_lastVictim->v.weapons & (1 << WEAPON_SG550))) { - pushChatterMessage (CHATTER_SNIPER_KILLED); + else if (rg.chance (60)) { + if ((m_lastVictim->v.weapons & cr::bit (Weapon::AWP)) || (m_lastVictim->v.weapons & cr::bit (Weapon::Scout)) || (m_lastVictim->v.weapons & cr::bit (Weapon::G3SG1)) || (m_lastVictim->v.weapons & cr::bit (Weapon::SG550))) { + pushChatterMessage (Chatter::SniperKilled); } else { switch (numEnemiesNear (pev->origin, 99999.0f)) { case 0: - if (rng.chance (50)) { - pushChatterMessage (CHATTER_NO_ENEMIES_LEFT); + if (rg.chance (50)) { + pushChatterMessage (Chatter::NoEnemiesLeft); } else { - pushChatterMessage (CHATTER_ENEMY_DOWN); + pushChatterMessage (Chatter::EnemyDown); } break; case 1: - pushChatterMessage (CHATTER_ONE_ENEMY_LEFT); + pushChatterMessage (Chatter::OneEnemyLeft); break; case 2: - pushChatterMessage (CHATTER_TWO_ENEMIES_LEFT); + pushChatterMessage (Chatter::TwoEnemiesLeft); break; case 3: - pushChatterMessage (CHATTER_THREE_ENEMIES_LEFT); + pushChatterMessage (Chatter::ThreeEnemiesLeft); break; default: - pushChatterMessage (CHATTER_ENEMY_DOWN); + pushChatterMessage (Chatter::EnemyDown); } } } // 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 && bots.isBombPlanted ()) { + if (m_team == Team::CT && m_currentWeapon != Weapon::Knife && m_numEnemiesLeft == 0 && bots.isBombPlanted ()) { selectWeaponByName ("weapon_knife"); m_plantedBombWptIndex = getNearestToPlantedBomb (); if (isOccupiedPoint (m_plantedBombWptIndex)) { - instantChatter (CHATTER_BOMB_SITE_SECURED); + pushChatterMessage (Chatter::BombsiteSecured); } } } else { - pushChatMessage (CHAT_TEAMKILL, true); - pushChatterMessage (CHATTER_TEAM_ATTACK); + pushChatMessage (Chat::TeamKill, true); + pushChatterMessage (Chatter::FriendlyFire); } m_lastVictim = nullptr; } @@ -1870,46 +1779,47 @@ void Bot::setConditions (void) { // check if our current enemy is still valid if (!game.isNullEntity (m_lastEnemy)) { if (!util.isAlive (m_lastEnemy) && m_shootAtDeadTime < game.timebase ()) { - m_lastEnemyOrigin.nullify (); + m_lastEnemyOrigin= nullvec; m_lastEnemy = nullptr; } } else { - m_lastEnemyOrigin.nullify (); + m_lastEnemyOrigin= nullvec; m_lastEnemy = nullptr; } // don't listen if seeing enemy, just checked for sounds or being blinded (because its inhuman) - if (!yb_ignore_enemies.boolean () && m_soundUpdateTime < game.timebase () && m_blindTime < game.timebase () && m_seeEnemyTime + 1.0f < game.timebase ()) { + if (!yb_ignore_enemies.bool_ () && 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 < game.timebase ()) { - m_states &= ~STATE_HEARING_ENEMY; + m_states &= ~Sense::HearingEnemy; } if (game.isNullEntity (m_enemy) && !game.isNullEntity (m_lastEnemy) && !m_lastEnemyOrigin.empty ()) { - m_aimFlags |= AIM_PREDICT_PATH; + m_aimFlags |= AimFlags::PredictPath; if (seesEntity (m_lastEnemyOrigin, true)) { - m_aimFlags |= AIM_LAST_ENEMY; + m_aimFlags |= AimFlags::LastEnemy; } } // check for grenades depending on difficulty - if (rng.chance (cr::max (25, m_difficulty * 25))) { + if (rg.chance (cr::max (25, m_difficulty * 25))) { checkGrenadesThrow (); } // check if there are items needing to be used/collected if (m_itemCheckTime < game.timebase () || !game.isNullEntity (m_pickupItem)) { + + updatePickups (); m_itemCheckTime = game.timebase () + 0.5f; - processPickups (); } filterTasks (); } -void Bot::filterTasks (void) { +void Bot::filterTasks () { // initialize & calculate the desire for all actions based on distances, emotions and other stuff getTask (); @@ -1935,11 +1845,11 @@ void Bot::filterTasks (void) { auto &filter = bots.getFilters (); // bot found some item to use? - if (!game.isNullEntity (m_pickupItem) && taskId () != TASK_ESCAPEFROMBOMB) { - m_states |= STATE_PICKUP_ITEM; + if (!game.isNullEntity (m_pickupItem) && getCurrentTaskId () != Task::EscapeFromBomb) { + m_states |= Sense::PickupItem; - if (m_pickupType == PICKUP_BUTTON) { - filter[TASK_PICKUPITEM].desire = 50.0f; // always pickup button + if (m_pickupType == Pickup::Button) { + filter[Task::PickupItem].desire = 50.0f; // always pickup button } else { float distance = (500.0f - (game.getAbsPos (m_pickupItem) - pev->origin).length ()) * 0.2f; @@ -1947,36 +1857,36 @@ void Bot::filterTasks (void) { if (distance > 50.0f) { distance = 50.0f; } - filter[TASK_PICKUPITEM].desire = distance; + filter[Task::PickupItem].desire = distance; } } else { - m_states &= ~STATE_PICKUP_ITEM; - filter[TASK_PICKUPITEM].desire = 0.0f; + m_states &= ~Sense::PickupItem; + filter[Task::PickupItem].desire = 0.0f; } // calculate desire to attack - if ((m_states & STATE_SEEING_ENEMY) && reactOnEnemy ()) { - filter[TASK_ATTACK].desire = TASKPRI_ATTACK; + if ((m_states & Sense::SeeingEnemy) && reactOnEnemy ()) { + filter[Task::Attack].desire = TaskPri::Attack; } else { - filter[TASK_ATTACK].desire = 0.0f; + filter[Task::Attack].desire = 0.0f; } - float &seekCoverDesire = filter[TASK_SEEKCOVER].desire; - float &huntEnemyDesire = filter[TASK_HUNTENEMY].desire; - float &blindedDesire = filter[TASK_BLINDED].desire; + float &seekCoverDesire = filter[Task::SeekCover].desire; + float &huntEnemyDesire = filter[Task::Hunt].desire; + float &blindedDesire = filter[Task::Blind].desire; // calculate desires to seek cover or hunt if (util.isPlayer (m_lastEnemy) && !m_lastEnemyOrigin.empty () && !m_hasC4) { float retreatLevel = (100.0f - (pev->health > 50.0f ? 100.0f : pev->health)) * tempFear; // retreat level depends on bot health - if (m_numEnemiesLeft > m_numFriendsLeft * 0.5f && m_retreatTime < game.timebase () && m_seeEnemyTime - rng.getFloat (2.0f, 4.0f) < game.timebase ()) { + if (m_numEnemiesLeft > m_numFriendsLeft * 0.5f && m_retreatTime < game.timebase () && m_seeEnemyTime - rg.float_ (2.0f, 4.0f) < game.timebase ()) { float timeSeen = m_seeEnemyTime - game.timebase (); float timeHeard = m_heardSoundTime - game.timebase (); float ratio = 0.0f; - m_retreatTime = game.timebase () + rng.getFloat (3.0f, 15.0f); + m_retreatTime = game.timebase () + rg.float_ (3.0f, 15.0f); if (timeSeen > timeHeard) { timeSeen += 10.0f; @@ -1988,7 +1898,7 @@ void Bot::filterTasks (void) { } bool lowAmmo = m_ammoInClip[m_currentWeapon] < conf.findWeaponById (m_currentWeapon).maxClip * 0.18f; - if (bots.isBombPlanted () || 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 ())) { @@ -2004,7 +1914,7 @@ void Bot::filterTasks (void) { } // if half of the round is over, allow hunting - 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 ()) { + if (getCurrentTaskId () != Task::EscapeFromBomb && game.isNullEntity (m_enemy) && bots.getRoundMidTime () < game.timebase () && !m_isUsingGrenade && m_currentNodeIndex != graph.getNearest (m_lastEnemyOrigin) && m_personality != Personality::Careful && !yb_ignore_enemies.bool_ ()) { float desireLevel = 4096.0f - ((1.0f - tempAgression) * (m_lastEnemyOrigin - pev->origin).length ()); desireLevel = (100.0f * desireLevel) / 4096.0f; @@ -2025,7 +1935,7 @@ void Bot::filterTasks (void) { } // blinded behavior - blindedDesire = m_blindTime > game.timebase () ? TASKPRI_BLINDED : 0.0f; + blindedDesire = m_blindTime > game.timebase () ? TaskPri::Blind : 0.0f; // now we've initialized all the desires go through the hard work // of filtering all actions against each other to pick the most @@ -2040,7 +1950,7 @@ void Bot::filterTasks (void) { // hard to check them all out. // this function returns the behavior having the higher activation level - auto maxDesire = [] (Task *first, Task *second) { + auto maxDesire = [] (BotTask *first, BotTask *second) { if (first->desire > second->desire) { return first; } @@ -2048,7 +1958,7 @@ void Bot::filterTasks (void) { }; // this function returns the first behavior if its activation level is anything higher than zero - auto subsumeDesire = [] (Task *first, Task *second) { + auto subsumeDesire = [] (BotTask *first, BotTask *second) { if (first->desire > 0) { return first; } @@ -2056,7 +1966,7 @@ void Bot::filterTasks (void) { }; // this function returns the input behavior if it's activation level exceeds the threshold, or some default behavior otherwise - auto thresholdDesire = [] (Task *first, float threshold, float desire) { + auto thresholdDesire = [] (BotTask *first, float threshold, float desire) { if (first->desire < threshold) { first->desire = desire; } @@ -2071,21 +1981,21 @@ void Bot::filterTasks (void) { return old; }; - m_oldCombatDesire = hysteresisDesire (filter[TASK_ATTACK].desire, 40.0f, 90.0f, m_oldCombatDesire); - filter[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 = &filter[TASK_ATTACK]; - auto pickup = &filter[TASK_PICKUPITEM]; + auto offensive = &filter[Task::Attack]; + auto pickup = &filter[Task::PickupItem]; // calc survive (cover/hide) - auto survive = thresholdDesire (&filter[TASK_SEEKCOVER], 40.0f, 0.0f); - survive = subsumeDesire (&filter[TASK_HIDE], survive); + auto survive = thresholdDesire (&filter[Task::SeekCover], 40.0f, 0.0f); + survive = subsumeDesire (&filter[Task::Hide], survive); - auto def = thresholdDesire (&filter[TASK_HUNTENEMY], 41.0f, 0.0f); // don't allow hunting if desires 60< + auto def = thresholdDesire (&filter[Task::Hunt], 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 (&filter[TASK_BLINDED], maxDesire (survive, sub)); // reason about fleeing instead + auto final = subsumeDesire (&filter[Task::Blind], maxDesire (survive, sub)); // reason about fleeing instead if (!m_tasks.empty ()) { final = maxDesire (final, getTask ()); @@ -2093,13 +2003,13 @@ void Bot::filterTasks (void) { } } -void Bot::clearTasks (void) { +void Bot::clearTasks () { // this function resets bot tasks stack, by removing all entries from the stack. m_tasks.clear (); } -void Bot::startTask (TaskID id, float desire, int data, float time, bool resume) { +void Bot::startTask (Task id, float desire, int data, float time, bool resume) { for (auto &task : m_tasks) { if (task.id == id) { if (!cr::fequal (task.desire, desire)) { @@ -2108,68 +2018,68 @@ void Bot::startTask (TaskID id, float desire, int data, float time, bool resume) return; } } - m_tasks.push ({ id, desire, data, time, resume }); + m_tasks.emplace (id, desire, data, time, resume); clearSearchNodes (); ignoreCollision (); - int tid = taskId (); + int tid = getCurrentTaskId (); // leader bot? - if (m_isLeader && tid == TASK_SEEKCOVER) { + if (m_isLeader && tid == Task::SeekCover) { updateTeamCommands (); // reorganize team if fleeing } - if (tid == TASK_CAMP) { + if (tid == Task::Camp) { selectBestWeapon (); } // this is best place to handle some voice commands report team some info - if (rng.chance (90)) { - if (tid == TASK_BLINDED) { - instantChatter (CHATTER_BLINDED); + if (rg.chance (90)) { + if (tid == Task::Blind) { + pushChatterMessage (Chatter::Blind); } - else if (tid == TASK_PLANTBOMB) { - instantChatter (CHATTER_PLANTING_BOMB); + else if (tid == Task::PlantBomb) { + pushChatterMessage (Chatter::PlantingBomb); } } - if (rng.chance (80) && tid == TASK_CAMP) { - if (game.mapIs (MAP_DE) && bots.isBombPlanted ()) { - pushChatterMessage (CHATTER_GUARDING_DROPPED_BOMB); + if (rg.chance (25) && tid == Task::Camp) { + if (game.mapIs (MapFlags::Demolition) && bots.isBombPlanted ()) { + pushChatterMessage (Chatter::GuardingDroppedC4); } else { - pushChatterMessage (CHATTER_GOING_TO_CAMP); + pushChatterMessage (Chatter::GoingToCamp); } } - if (yb_debug_goal.integer () != INVALID_WAYPOINT_INDEX) { - m_chosenGoalIndex = yb_debug_goal.integer (); + if (yb_debug_goal.int_ () != kInvalidNodeIndex) { + m_chosenGoalIndex = yb_debug_goal.int_ (); } else { m_chosenGoalIndex = getTask ()->data; } - if (rng.chance (75) && tid == TASK_CAMP && m_team == TEAM_TERRORIST && m_inVIPZone) { - pushChatterMessage (CHATTER_GOING_TO_GUARD_VIP_SAFETY); + if (rg.chance (75) && tid == Task::Camp && m_team == Team::Terrorist && m_inVIPZone) { + pushChatterMessage (Chatter::GoingToGuardVIPSafety); } } -Task *Bot::getTask (void) { +BotTask *Bot::getTask () { if (m_tasks.empty ()) { - m_tasks.push ({ TASK_NORMAL, TASKPRI_NORMAL, INVALID_WAYPOINT_INDEX, 0.0f, true }); + m_tasks.emplace (Task::Normal, TaskPri::Normal, kInvalidNodeIndex, 0.0f, true); } - return &m_tasks.back (); + return &m_tasks.last (); } -void Bot::clearTask (TaskID id) { +void Bot::clearTask (Task id) { // this function removes one task from the bot task stack. - if (m_tasks.empty () || taskId () == TASK_NORMAL) { + if (m_tasks.empty () || getCurrentTaskId () == Task::Normal) { return; // since normal task can be only once on the stack, don't remove it... } - if (taskId () == id) { + if (getCurrentTaskId () == id) { clearSearchNodes (); ignoreCollision (); @@ -2179,7 +2089,7 @@ void Bot::clearTask (TaskID id) { for (auto &task : m_tasks) { if (task.id == id) { - m_tasks.erase (task); + m_tasks.remove (task); } } @@ -2187,7 +2097,7 @@ void Bot::clearTask (TaskID id) { clearSearchNodes (); } -void Bot::completeTask (void) { +void Bot::completeTask () { // this function called whenever a task is completed. ignoreCollision (); @@ -2198,18 +2108,18 @@ void Bot::completeTask (void) { do { m_tasks.pop (); - } while (!m_tasks.empty () && !m_tasks.back ().resume); + } while (!m_tasks.empty () && !m_tasks.last ().resume); clearSearchNodes (); } -bool Bot::isEnemyThreat (void) { - if (game.isNullEntity (m_enemy) || taskId () == TASK_SEEKCOVER) { +bool Bot::isEnemyThreat () { + if (game.isNullEntity (m_enemy) || getCurrentTaskId () == Task::SeekCover) { return false; } // if bot is camping, he should be firing anyway and not leaving his position - if (taskId () == TASK_CAMP) { + if (getCurrentTaskId () == Task::Camp) { return false; } @@ -2220,7 +2130,7 @@ bool Bot::isEnemyThreat (void) { return false; } -bool Bot::reactOnEnemy (void) { +bool Bot::reactOnEnemy () { // the purpose of this function is check if task has to be interrupted because an enemy is near (run attack actions then) if (!isEnemyThreat ()) { @@ -2228,15 +2138,15 @@ bool Bot::reactOnEnemy (void) { } if (m_enemyReachableTimer < game.timebase ()) { - int ownIndex = m_currentWaypointIndex; + int ownIndex = m_currentNodeIndex; - if (ownIndex == INVALID_WAYPOINT_INDEX) { - ownIndex = getNearestPoint (); + if (ownIndex == kInvalidNodeIndex) { + ownIndex = findNearestNode (); } - int enemyIndex = waypoints.getNearest (m_enemy->v.origin); + int enemyIndex = graph.getNearest (m_enemy->v.origin); auto lineDist = (m_enemy->v.origin - pev->origin).length (); - auto pathDist = static_cast (waypoints.getPathDist (ownIndex, enemyIndex)); + auto pathDist = static_cast (graph.getPathDist (ownIndex, enemyIndex)); if (pathDist - lineDist > 112.0f) { m_isEnemyReachable = false; @@ -2254,69 +2164,65 @@ bool Bot::reactOnEnemy (void) { return false; } -bool Bot::lastEnemyShootable (void) { +bool Bot::lastEnemyShootable () { // don't allow shooting through walls - if (!(m_aimFlags & AIM_LAST_ENEMY) || m_lastEnemyOrigin.empty () || game.isNullEntity (m_lastEnemy)) { + if (!(m_aimFlags & AimFlags::LastEnemy) || m_lastEnemyOrigin.empty () || game.isNullEntity (m_lastEnemy)) { return false; } return util.getShootingCone (ent (), m_lastEnemyOrigin) >= 0.90f && isPenetrableObstacle (m_lastEnemyOrigin); } -void Bot::checkRadioQueue (void) { +void Bot::checkRadioQueue () { // this function handling radio and reacting to it // 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) { + if (getCurrentTaskId () == Task::DefuseBomb || getCurrentTaskId () == Task::PlantBomb || hasHostage () || m_hasC4) { m_radioOrder = 0; return; } float distance = (m_radioEntity->v.origin - pev->origin).length (); switch (m_radioOrder) { - case RADIO_COVER_ME: - case RADIO_FOLLOW_ME: - case RADIO_STICK_TOGETHER_TEAM: - case CHATTER_GOING_TO_PLANT_BOMB: - case CHATTER_COVER_ME: + case Radio::CoverMe: + case Radio::FollowMe: + case Radio::StickTogetherTeam: + case Chatter::GoingToPlantBomb: + case Chatter::CoverMe: // 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 (game.isNullEntity (m_targetEntity) && game.isNullEntity (m_enemy) && rng.chance (m_personality == PERSONALITY_CAREFUL ? 80 : 20)) { + if (seesEntity (m_radioEntity->v.origin) || m_radioOrder == Radio::StickTogetherTeam) { + if (game.isNullEntity (m_targetEntity) && game.isNullEntity (m_enemy) && rg.chance (m_personality == Personality::Careful ? 80 : 20)) { int numFollowers = 0; - // Check if no more followers are allowed - for (int i = 0; i < game.maxClients (); i++) { - Bot *bot = bots.getBot (i); - - if (bot != nullptr) { - if (bot->m_notKilled) { - if (bot->m_targetEntity == m_radioEntity) { - numFollowers++; - } + // check if no more followers are allowed + for (const auto &bot : bots) { + if (bot->m_notKilled) { + if (bot->m_targetEntity == m_radioEntity) { + numFollowers++; } } } - int allowedFollowers = yb_user_max_followers.integer (); + int allowedFollowers = yb_user_max_followers.int_ (); - if (m_radioEntity->v.weapons & (1 << WEAPON_C4)) { + if (m_radioEntity->v.weapons & cr::bit (Weapon::C4)) { allowedFollowers = 1; } if (numFollowers < allowedFollowers) { - pushRadioMessage (RADIO_AFFIRMATIVE); + pushRadioMessage (Radio::RogerThat); m_targetEntity = m_radioEntity; // don't pause/camp/follow anymore - TaskID taskID = taskId (); + Task taskID = getCurrentTaskId (); - if (taskID == TASK_PAUSE || taskID == TASK_CAMP) { + if (taskID == Task::Pause || taskID == Task::Camp) { getTask ()->time = game.timebase (); } - startTask (TASK_FOLLOWUSER, TASKPRI_FOLLOWUSER, INVALID_WAYPOINT_INDEX, 0.0f, true); + startTask (Task::FollowUser, TaskPri::FollowUser, kInvalidNodeIndex, 0.0f, true); } else if (numFollowers > allowedFollowers) { - for (int i = 0; (i < game.maxClients () && numFollowers > allowedFollowers); i++) { - Bot *bot = bots.getBot (i); + for (int i = 0; (i < game.maxClients () && numFollowers > allowedFollowers); ++i) { + auto bot = bots[i]; if (bot != nullptr) { if (bot->m_notKilled) { @@ -2328,34 +2234,34 @@ void Bot::checkRadioQueue (void) { } } } - else if (m_radioOrder != CHATTER_GOING_TO_PLANT_BOMB && rng.chance (15)) { - pushRadioMessage (RADIO_NEGATIVE); + else if (m_radioOrder != Chatter::GoingToPlantBomb && rg.chance (15)) { + pushRadioMessage (Radio::Negative); } } - else if (m_radioOrder != CHATTER_GOING_TO_PLANT_BOMB && rng.chance (25)) { - pushRadioMessage (RADIO_NEGATIVE); + else if (m_radioOrder != Chatter::GoingToPlantBomb && rg.chance (25)) { + pushRadioMessage (Radio::Negative); } } break; - case RADIO_HOLD_THIS_POSITION: + case Radio::HoldThisPosition: if (!game.isNullEntity (m_targetEntity)) { if (m_targetEntity == m_radioEntity) { m_targetEntity = nullptr; - pushRadioMessage (RADIO_AFFIRMATIVE); + pushRadioMessage (Radio::RogerThat); m_campButtons = 0; - startTask (TASK_PAUSE, TASKPRI_PAUSE, INVALID_WAYPOINT_INDEX, game.timebase () + rng.getFloat (30.0f, 60.0f), false); + startTask (Task::Pause, TaskPri::Pause, kInvalidNodeIndex, game.timebase () + rg.float_ (30.0f, 60.0f), false); } } break; - case CHATTER_NEW_ROUND: - pushChatterMessage (CHATTER_YOU_HEARD_THE_MAN); + case Chatter::NewRound: + pushChatterMessage (Chatter::YouHeardTheMan); break; - case RADIO_TAKING_FIRE: + case Radio::TakingFireNeedAssistance: 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 @@ -2365,57 +2271,57 @@ void Bot::checkRadioQueue (void) { m_fearLevel = 0.0f; } - if (rng.chance (45) && yb_communication_type.integer () == 2) { - pushChatterMessage (CHATTER_ON_MY_WAY); + if (rg.chance (45) && yb_radio_mode.int_ () == 2) { + pushChatterMessage (Chatter::OnMyWay); } - else if (m_radioOrder == RADIO_NEED_BACKUP && yb_communication_type.integer () != 2) { - pushRadioMessage (RADIO_AFFIRMATIVE); + else if (m_radioOrder == Radio::NeedBackup && yb_radio_mode.int_ () != 2) { + pushRadioMessage (Radio::RogerThat); } tryHeadTowardRadioMessage (); } - else if (rng.chance (25)) { - pushRadioMessage (RADIO_NEGATIVE); + else if (rg.chance (25)) { + pushRadioMessage (Radio::Negative); } } break; - case RADIO_YOU_TAKE_THE_POINT: + case Radio::YouTakeThePoint: if (seesEntity (m_radioEntity->v.origin) && m_isLeader) { - pushRadioMessage (RADIO_AFFIRMATIVE); + pushRadioMessage (Radio::RogerThat); } break; - case RADIO_ENEMY_SPOTTED: - case RADIO_NEED_BACKUP: - case CHATTER_SCARED_EMOTE: - case CHATTER_PINNED_DOWN: - if (((game.isNullEntity (m_enemy) && seesEntity (m_radioEntity->v.origin)) || distance < 2048.0f || !m_moveToC4) && rng.chance (50) && m_seeEnemyTime + 4.0f < game.timebase ()) { + case Radio::EnemySpotted: + case Radio::NeedBackup: + case Chatter::ScaredEmotion: + case Chatter::PinnedDown: + if (((game.isNullEntity (m_enemy) && seesEntity (m_radioEntity->v.origin)) || distance < 2048.0f || !m_moveToC4) && rg.chance (50) && m_seeEnemyTime + 4.0f < game.timebase ()) { m_fearLevel -= 0.1f; if (m_fearLevel < 0.0f) { m_fearLevel = 0.0f; } - if (rng.chance (45) && yb_communication_type.integer () == 2) { - pushChatterMessage (CHATTER_ON_MY_WAY); + if (rg.chance (45) && yb_radio_mode.int_ () == 2) { + pushChatterMessage (Chatter::OnMyWay); } - else if (m_radioOrder == RADIO_NEED_BACKUP && yb_communication_type.integer () != 2 && rng.chance (50)) { - pushRadioMessage (RADIO_AFFIRMATIVE); + else if (m_radioOrder == Radio::NeedBackup && yb_radio_mode.int_ () != 2 && rg.chance (50)) { + pushRadioMessage (Radio::RogerThat); } tryHeadTowardRadioMessage (); } - else if (rng.chance (30) && m_radioOrder == RADIO_NEED_BACKUP) { - pushRadioMessage (RADIO_NEGATIVE); + else if (rg.chance (30) && m_radioOrder == Radio::NeedBackup) { + pushRadioMessage (Radio::Negative); } break; - case RADIO_GO_GO_GO: + case Radio::GoGoGo: if (m_radioEntity == m_targetEntity) { - if (rng.chance (45) && yb_communication_type.integer () == 2) { - pushRadioMessage (RADIO_AFFIRMATIVE); + if (rg.chance (45) && yb_radio_mode.int_ () == 2) { + pushRadioMessage (Radio::RogerThat); } - else if (m_radioOrder == RADIO_NEED_BACKUP && yb_communication_type.integer () != 2) { - pushRadioMessage (RADIO_AFFIRMATIVE); + else if (m_radioOrder == Radio::NeedBackup && yb_radio_mode.int_ () != 2) { + pushRadioMessage (Radio::RogerThat); } m_targetEntity = nullptr; @@ -2426,83 +2332,83 @@ void Bot::checkRadioQueue (void) { } } else if ((game.isNullEntity (m_enemy) && seesEntity (m_radioEntity->v.origin)) || distance < 2048.0f) { - TaskID taskID = taskId (); + Task taskID = getCurrentTaskId (); - if (taskID == TASK_PAUSE || taskID == TASK_CAMP) { + if (taskID == Task::Pause || taskID == Task::Camp) { m_fearLevel -= 0.2f; if (m_fearLevel < 0.0f) { m_fearLevel = 0.0f; } - pushRadioMessage (RADIO_AFFIRMATIVE); + pushRadioMessage (Radio::RogerThat); // don't pause/camp anymore getTask ()->time = game.timebase (); m_targetEntity = nullptr; game.makeVectors (m_radioEntity->v.v_angle); - m_position = m_radioEntity->v.origin + game.vec.forward * rng.getFloat (1024.0f, 2048.0f); + m_position = m_radioEntity->v.origin + game.vec.forward * rg.float_ (1024.0f, 2048.0f); clearSearchNodes (); - startTask (TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, INVALID_WAYPOINT_INDEX, 0.0f, true); + startTask (Task::MoveToPosition, TaskPri::MoveToPosition, kInvalidNodeIndex, 0.0f, true); } } else if (!game.isNullEntity (m_doubleJumpEntity)) { - pushRadioMessage (RADIO_AFFIRMATIVE); + pushRadioMessage (Radio::RogerThat); resetDoubleJump (); } - else if (rng.chance (35)) { - pushRadioMessage (RADIO_NEGATIVE); + else if (rg.chance (35)) { + pushRadioMessage (Radio::Negative); } break; - case RADIO_SHES_GONNA_BLOW: - if (game.isNullEntity (m_enemy) && distance < 2048.0f && bots.isBombPlanted () && m_team == TEAM_TERRORIST) { - pushRadioMessage (RADIO_AFFIRMATIVE); + case Radio::ShesGonnaBlow: + if (game.isNullEntity (m_enemy) && distance < 2048.0f && bots.isBombPlanted () && m_team == Team::Terrorist) { + pushRadioMessage (Radio::RogerThat); - if (taskId () == TASK_CAMP) { - clearTask (TASK_CAMP); + if (getCurrentTaskId () == Task::Camp) { + clearTask (Task::Camp); } m_targetEntity = nullptr; - startTask (TASK_ESCAPEFROMBOMB, TASKPRI_ESCAPEFROMBOMB, INVALID_WAYPOINT_INDEX, 0.0f, true); + startTask (Task::EscapeFromBomb, TaskPri::EscapeFromBomb, kInvalidNodeIndex, 0.0f, true); } - else if (rng.chance (35)) { - pushRadioMessage (RADIO_NEGATIVE); + else if (rg.chance (35)) { + pushRadioMessage (Radio::Negative); } break; - case RADIO_REGROUP_TEAM: + case Radio::RegroupTeam: // 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 && bots.isBombPlanted () && taskId () != TASK_DEFUSEBOMB) { + if (m_team == Team::CT && m_currentWeapon != Weapon::Knife && m_numEnemiesLeft == 0 && bots.isBombPlanted () && getCurrentTaskId () != Task::DefuseBomb) { selectWeaponByName ("weapon_knife"); clearSearchNodes (); - m_position = waypoints.getBombPos (); - startTask (TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, INVALID_WAYPOINT_INDEX, 0.0f, true); + m_position = graph.getBombPos (); + startTask (Task::MoveToPosition, TaskPri::MoveToPosition, kInvalidNodeIndex, 0.0f, true); - pushRadioMessage (RADIO_AFFIRMATIVE); + pushRadioMessage (Radio::RogerThat); } break; - case RADIO_STORM_THE_FRONT: - if (((game.isNullEntity (m_enemy) && seesEntity (m_radioEntity->v.origin)) || distance < 1024.0f) && rng.chance (50)) { - pushRadioMessage (RADIO_AFFIRMATIVE); + case Radio::StormTheFront: + if (((game.isNullEntity (m_enemy) && seesEntity (m_radioEntity->v.origin)) || distance < 1024.0f) && rg.chance (50)) { + pushRadioMessage (Radio::RogerThat); // don't pause/camp anymore - TaskID taskID = taskId (); + Task taskID = getCurrentTaskId (); - if (taskID == TASK_PAUSE || taskID == TASK_CAMP) { + if (taskID == Task::Pause || taskID == Task::Camp) { getTask ()->time = game.timebase (); } m_targetEntity = nullptr; game.makeVectors (m_radioEntity->v.v_angle); - m_position = m_radioEntity->v.origin + game.vec.forward * rng.getFloat (1024.0f, 2048.0f); + m_position = m_radioEntity->v.origin + game.vec.forward * rg.float_ (1024.0f, 2048.0f); clearSearchNodes (); - startTask (TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, INVALID_WAYPOINT_INDEX, 0.0f, true); + startTask (Task::MoveToPosition, TaskPri::MoveToPosition, kInvalidNodeIndex, 0.0f, true); m_fearLevel -= 0.3f; @@ -2517,7 +2423,7 @@ void Bot::checkRadioQueue (void) { } break; - case RADIO_TEAM_FALLBACK: + case Radio::TeamFallback: if ((game.isNullEntity (m_enemy) && seesEntity (m_radioEntity->v.origin)) || distance < 1024.0f) { m_fearLevel += 0.5f; @@ -2529,14 +2435,14 @@ void Bot::checkRadioQueue (void) { if (m_agressionLevel < 0.0f) { m_agressionLevel = 0.0f; } - if (taskId () == TASK_CAMP) { - getTask ()->time += rng.getFloat (10.0f, 15.0f); + if (getCurrentTaskId () == Task::Camp) { + getTask ()->time += rg.float_ (10.0f, 15.0f); } else { // don't pause/camp anymore - TaskID taskID = taskId (); + Task taskID = getCurrentTaskId (); - if (taskID == TASK_PAUSE) { + if (taskID == Task::Pause) { getTask ()->time = game.timebase (); } m_targetEntity = nullptr; @@ -2548,7 +2454,7 @@ void Bot::checkRadioQueue (void) { // take nearest enemy to ordering player for (const auto &client : util.getClients ()) { - if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.team == m_team) { + if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive) || client.team == m_team) { continue; } @@ -2568,26 +2474,94 @@ void Bot::checkRadioQueue (void) { } break; - case RADIO_REPORT_TEAM: - if (rng.chance (30)) { - pushRadioMessage ((numEnemiesNear (pev->origin, 400.0f) == 0 && yb_communication_type.integer () != 2) ? RADIO_SECTOR_CLEAR : RADIO_REPORTING_IN); + case Radio::ReportInTeam: + switch (getCurrentTaskId ()) { + case Task::Normal: + if (getTask ()->data != kInvalidNodeIndex && rg.chance (70)) { + Path &path = graph[getTask ()->data]; + + if (path.flags & NodeFlag::Goal) { + if (game.mapIs (MapFlags::Demolition) && m_team == Team::Terrorist && m_hasC4) { + pushChatterMessage (Chatter::GoingToPlantBomb); + } + else { + pushChatterMessage (Chatter::Nothing); + } + } + else if (path.flags & NodeFlag::Rescue) { + pushChatterMessage (Chatter::RescuingHostages); + } + else if ((path.flags & NodeFlag::Camp) && rg.chance (75)) { + pushChatterMessage (Chatter::GoingToCamp); + } + else { + pushChatterMessage (Chatter::HeardNoise); + } + } + else if (rg.chance (30)) { + pushChatterMessage (Chatter::ReportingIn); + } + break; + + case Task::MoveToPosition: + if (rg.chance (2)) { + pushChatterMessage (Chatter::GoingToCamp); + } + break; + + case Task::Camp: + if (rg.chance (40)) { + if (bots.isBombPlanted () && m_team == Team::Terrorist) { + pushChatterMessage (Chatter::GuardingDroppedC4); + } + else if (m_inVIPZone && m_team == Team::Terrorist) { + pushChatterMessage (Chatter::GuardingVIPSafety); + } + else { + pushChatterMessage (Chatter::Camping); + } + } + break; + + case Task::PlantBomb: + pushChatterMessage (Chatter::PlantingBomb); + break; + + case Task::DefuseBomb: + pushChatterMessage (Chatter::DefusingBomb); + break; + + case Task::Attack: + pushChatterMessage (Chatter::InCombat); + break; + + case Task::Hide: + case Task::SeekCover: + pushChatterMessage (Chatter::SeekingEnemies); + break; + + default: + if (rg.chance (50)) { + pushChatterMessage (Chatter::Nothing); + } + break; } break; - case RADIO_SECTOR_CLEAR: + case Radio::SectorClear: // is bomb planted and it's a ct if (!bots.isBombPlanted ()) { break; } // check if it's a ct command - if (game.getTeam (m_radioEntity) == TEAM_COUNTER && m_team == TEAM_COUNTER && util.isFakeClient (m_radioEntity) && bots.getPlantedBombSearchTimestamp () < game.timebase ()) { + if (game.getTeam (m_radioEntity) == Team::CT && m_team == Team::CT && util.isFakeClient (m_radioEntity) && bots.getPlantedBombSearchTimestamp () < game.timebase ()) { float minDistance = 99999.0f; - int bombPoint = INVALID_WAYPOINT_INDEX; + int bombPoint = kInvalidNodeIndex; // find nearest bomb waypoint to player - for (auto &point : waypoints.m_goalPoints) { - distance = (waypoints[point].origin - m_radioEntity->v.origin).lengthSq (); + for (auto &point : graph.m_goalPoints) { + distance = (graph[point].origin - m_radioEntity->v.origin).lengthSq (); if (distance < minDistance) { minDistance = distance; @@ -2596,33 +2570,33 @@ void Bot::checkRadioQueue (void) { } // mark this waypoint as restricted point - if (bombPoint != INVALID_WAYPOINT_INDEX && !waypoints.isVisited (bombPoint)) { + if (bombPoint != kInvalidNodeIndex && !graph.isVisited (bombPoint)) { // does this bot want to defuse? - if (taskId () == TASK_NORMAL) { + if (getCurrentTaskId () == Task::Normal) { // is he approaching this goal? if (getTask ()->data == bombPoint) { - getTask ()->data = INVALID_WAYPOINT_INDEX; - pushRadioMessage (RADIO_AFFIRMATIVE); + getTask ()->data = kInvalidNodeIndex; + pushRadioMessage (Radio::RogerThat); } } - waypoints.setVisited (bombPoint); + graph.setVisited (bombPoint); } bots.setPlantedBombSearchTimestamp (game.timebase () + 0.5f); } break; - case RADIO_GET_IN_POSITION: + case Radio::GetInPositionAndWaitForGo: if ((game.isNullEntity (m_enemy) && seesEntity (m_radioEntity->v.origin)) || distance < 1024.0f) { - pushRadioMessage (RADIO_AFFIRMATIVE); + pushRadioMessage (Radio::RogerThat); - if (taskId () == TASK_CAMP) { - getTask ()->time = game.timebase () + rng.getFloat (30.0f, 60.0f); + if (getCurrentTaskId () == Task::Camp) { + getTask ()->time = game.timebase () + rg.float_ (30.0f, 60.0f); } else { // don't pause anymore - TaskID taskID = taskId (); + Task taskID = getCurrentTaskId (); - if (taskID == TASK_PAUSE) { + if (taskID == Task::Pause) { getTask ()->time = game.timebase (); } @@ -2635,7 +2609,7 @@ void Bot::checkRadioQueue (void) { // take nearest enemy to ordering player for (const auto &client : util.getClients ()) { - if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.team == m_team) { + if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive) || client.team == m_team) { continue; } @@ -2651,14 +2625,14 @@ void Bot::checkRadioQueue (void) { } clearSearchNodes (); - int index = getDefendPoint (m_radioEntity->v.origin); + int index = findDefendNode (m_radioEntity->v.origin); // push camp task on to stack - startTask (TASK_CAMP, TASKPRI_CAMP, INVALID_WAYPOINT_INDEX, game.timebase () + rng.getFloat (30.0f, 60.0f), true); + startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, game.timebase () + rg.float_ (30.0f, 60.0f), true); // push move command - startTask (TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, index, game.timebase () + rng.getFloat (30.0f, 60.0f), true); + startTask (Task::MoveToPosition, TaskPri::MoveToPosition, index, game.timebase () + rg.float_ (30.0f, 60.0f), true); - if (waypoints[index].vis.crouch <= waypoints[index].vis.stand) { + if (graph[index].vis.crouch <= graph[index].vis.stand) { m_campButtons |= IN_DUCK; } else { @@ -2671,40 +2645,40 @@ void Bot::checkRadioQueue (void) { m_radioOrder = 0; // radio command has been handled, reset } -void Bot::tryHeadTowardRadioMessage (void) { - TaskID taskID = taskId (); +void Bot::tryHeadTowardRadioMessage () { + Task taskID = getCurrentTaskId (); - if (taskID == TASK_MOVETOPOSITION || m_headedTime + 15.0f < game.timebase () || !util.isAlive (m_radioEntity) || m_hasC4) { + if (taskID == Task::MoveToPosition || m_headedTime + 15.0f < game.timebase () || !util.isAlive (m_radioEntity) || m_hasC4) { return; } - 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) { + if ((util.isFakeClient (m_radioEntity) && rg.chance (25) && m_personality == Personality::Normal) || !(m_radioEntity->v.flags & FL_FAKECLIENT)) { + if (taskID == Task::Pause || taskID == Task::Camp) { getTask ()->time = game.timebase (); } m_headedTime = game.timebase (); m_position = m_radioEntity->v.origin; clearSearchNodes (); - startTask (TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, INVALID_WAYPOINT_INDEX, 0.0f, true); + startTask (Task::MoveToPosition, TaskPri::MoveToPosition, kInvalidNodeIndex, 0.0f, true); } } -void Bot::updateAimDir (void) { +void Bot::updateAimDir () { uint32 flags = m_aimFlags; // don't allow bot to look at danger positions under certain circumstances - if (!(flags & (AIM_GRENADE | AIM_ENEMY | AIM_ENTITY))) { - if (isOnLadder () || isInWater () || (m_waypointFlags & FLAG_LADDER) || (m_currentTravelFlags & PATHFLAG_JUMP)) { - flags &= ~(AIM_LAST_ENEMY | AIM_PREDICT_PATH); + if (!(flags & (AimFlags::Grenade | AimFlags::Enemy | AimFlags::Entity))) { + if (isOnLadder () || isInWater () || (m_pathFlags & NodeFlag::Ladder) || (m_currentTravelFlags & PathFlag::Jump)) { + flags &= ~(AimFlags::LastEnemy | AimFlags::PredictPath); m_canChooseAimDirection = false; } } - if (flags & AIM_OVERRIDE) { + if (flags & AimFlags::Override) { m_lookAt = m_camp; } - else if (flags & AIM_GRENADE) { + else if (flags & AimFlags::Grenade) { m_lookAt = m_throw; float throwDistance = (m_throw - pev->origin).length (); @@ -2719,17 +2693,17 @@ void Bot::updateAimDir (void) { if (angleCorrection > 45.0f) { angleCorrection = 45.0f; } - coordCorrection = throwDistance * cr::tanf (cr::deg2rad (angleCorrection)) + 0.25f * (m_throw.z - pev->origin.z); + coordCorrection = throwDistance * cr::tanf (cr::degreesToRadians (angleCorrection)) + 0.25f * (m_throw.z - pev->origin.z); } m_lookAt.z += coordCorrection * 0.5f; } - else if (flags & AIM_ENEMY) { + else if (flags & AimFlags::Enemy) { focusEnemy (); } - else if (flags & AIM_ENTITY) { + else if (flags & AimFlags::Entity) { m_lookAt = m_entity; } - else if (flags & AIM_LAST_ENEMY) { + else if (flags & AimFlags::LastEnemy) { m_lookAt = m_lastEnemyOrigin; // did bot just see enemy and is quite aggressive? @@ -2741,7 +2715,7 @@ void Bot::updateAimDir (void) { } } } - else if (flags & AIM_PREDICT_PATH) { + else if (flags & AimFlags::PredictPath) { bool changePredictedEnemy = true; if (m_timeNextTracking > game.timebase () && m_trackingEdict == m_lastEnemy && util.isAlive (m_lastEnemy)) { @@ -2749,17 +2723,17 @@ void Bot::updateAimDir (void) { } if (changePredictedEnemy) { - int aimPoint = searchAimingPoint (m_lastEnemyOrigin); + int aimPoint = findAimingNode (m_lastEnemyOrigin); - if (aimPoint != INVALID_WAYPOINT_INDEX) { - m_lookAt = waypoints[aimPoint].origin; + if (aimPoint != kInvalidNodeIndex) { + m_lookAt = graph[aimPoint].origin; m_camp = m_lookAt; m_timeNextTracking = game.timebase () + 0.5f; m_trackingEdict = m_lastEnemy; } else { - m_aimFlags &= ~AIM_PREDICT_PATH; + m_aimFlags &= ~AimFlags::PredictPath; if (!m_camp.empty ()) { m_lookAt = m_camp; @@ -2770,17 +2744,17 @@ void Bot::updateAimDir (void) { m_lookAt = m_camp; } } - else if (flags & AIM_CAMP) { + else if (flags & AimFlags::Camp) { m_lookAt = m_camp; } - else if (flags & AIM_NAVPOINT) { + else if (flags & AimFlags::Nav) { m_lookAt = m_destOrigin; - if (m_canChooseAimDirection && m_currentWaypointIndex != INVALID_WAYPOINT_INDEX && !(m_currentPath->flags & FLAG_LADDER)) { - int dangerIndex = waypoints.getDangerIndex (m_team, m_currentWaypointIndex, m_currentWaypointIndex); + if (m_canChooseAimDirection && m_currentNodeIndex != kInvalidNodeIndex && !(m_path->flags & NodeFlag::Ladder)) { + int dangerIndex = graph.getDangerIndex (m_team, m_currentNodeIndex, m_currentNodeIndex); - if (waypoints.exists (dangerIndex) && waypoints.isVisible (m_currentWaypointIndex, dangerIndex)) { - m_lookAt = waypoints[dangerIndex].origin; + if (graph.exists (dangerIndex) && graph.isVisible (m_currentNodeIndex, dangerIndex)) { + m_lookAt = graph[dangerIndex].origin; } } } @@ -2790,10 +2764,10 @@ void Bot::updateAimDir (void) { } } -void Bot::checkDarkness (void) { +void Bot::checkDarkness () { // do not check for darkness at the start of the round - if (m_spawnTime + 5.0f > game.timebase () || !waypoints.exists (m_currentWaypointIndex)) { + if (m_spawnTime + 5.0f > game.timebase () || !graph.exists (m_currentNodeIndex)) { return; } @@ -2801,17 +2775,15 @@ void Bot::checkDarkness (void) { if (m_checkDarkTime + 2.5f > game.timebase ()) { return; } - - float lightLevel = waypoints.getLightLevel (m_currentWaypointIndex); float skyColor = illum.getSkyColor (); - if (mp_flashlight.boolean () && !m_hasNVG) { - auto task = TaskID (); + if (mp_flashlight.bool_ () && !m_hasNVG) { + auto task = Task (); - if (!(pev->effects & EF_DIMLIGHT) && task != TASK_CAMP && task != TASK_ATTACK && m_heardSoundTime + 3.0f < game.timebase () && m_flashLevel > 30.0f && ((skyColor > 50.0f && 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 && m_path->light < 10.0f) || (skyColor <= 50.0f && m_path->light < 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 >= game.timebase ())) + else if ((pev->effects & EF_DIMLIGHT) && (((m_path->light > 15.0f && skyColor > 50.0f) || (m_path->light > 45.0f && skyColor <= 50.0f)) || task == Task::Camp || task == Task::Attack || m_flashLevel <= 0 || m_heardSoundTime + 3.0f >= game.timebase ())) { pev->impulse = 100; } @@ -2820,17 +2792,17 @@ void Bot::checkDarkness (void) { if (pev->effects & EF_DIMLIGHT) { pev->impulse = 100; } - else if (!m_usesNVG && ((skyColor > 50.0f && lightLevel < 15.0f) || (skyColor <= 50.0f && lightLevel < 40.0f))) { - game.execBotCmd (ent (), "nightvision"); + else if (!m_usesNVG && ((skyColor > 50.0f && m_path->light < 15.0f) || (skyColor <= 50.0f && m_path->light < 40.0f))) { + game.botCommand (ent (), "nightvision"); } - else if (m_usesNVG && ((lightLevel > 20.0f && skyColor > 50.0f) || (lightLevel > 45.0f && skyColor <= 50.0f))) { - game.execBotCmd (ent (), "nightvision"); + else if (m_usesNVG && ((m_path->light > 20.0f && skyColor > 50.0f) || (m_path->light > 45.0f && skyColor <= 50.0f))) { + game.botCommand (ent (), "nightvision"); } } m_checkDarkTime = game.timebase (); } -void Bot::checkParachute (void) { +void Bot::checkParachute () { static auto parachute = engfuncs.pfnCVarGetPointer ("sv_parachute"); // if no cvar or it's not enabled do not bother @@ -2849,7 +2821,7 @@ void Bot::checkParachute (void) { } } -void Bot::slowFrame (void) { +void Bot::slowFrame () { if (m_thinkFps <= game.timebase ()) { // execute delayed think fastFrame (); @@ -2862,24 +2834,24 @@ void Bot::slowFrame (void) { } } -void Bot::fastFrame (void) { +void Bot::fastFrame () { pev->button = 0; pev->flags |= FL_FAKECLIENT; // restore fake client bit, if it were removed by some evil action =) m_moveSpeed = 0.0f; m_strafeSpeed = 0.0f; - m_moveAngles.nullify (); + m_moveAngles= nullvec; m_canChooseAimDirection = true; m_notKilled = util.isAlive (ent ()); m_team = game.getTeam (ent ()); - if (game.mapIs (MAP_AS) && !m_isVIP) { + if (game.mapIs (MapFlags::Assassination) && !m_isVIP) { m_isVIP = util.isPlayerVIP (ent ()); } - if (m_team == TEAM_TERRORIST && game.mapIs (MAP_DE)) { - m_hasC4 = !!(pev->weapons & (1 << WEAPON_C4)); + if (m_team == Team::Terrorist && game.mapIs (MapFlags::Demolition)) { + m_hasC4 = !!(pev->weapons & cr::bit (Weapon::C4)); } // is bot movement enabled @@ -2890,14 +2862,13 @@ void Bot::fastFrame (void) { updateTeamJoin (); // select team & class } else if (!m_notKilled) { - // we got a teamkiller? vote him away... - if (m_voteKickIndex != m_lastVoteKick && yb_tkpunish.boolean ()) { - game.execBotCmd (ent (), "vote %d", m_voteKickIndex); + if (m_voteKickIndex != m_lastVoteKick && yb_tkpunish.bool_ ()) { + game.botCommand (ent (), "vote %d", m_voteKickIndex); m_lastVoteKick = m_voteKickIndex; // if bot tk punishment is enabled slay the tk - if (yb_tkpunish.integer () != 2 || util.isFakeClient (game.entityOfIndex (m_voteKickIndex))) { + if (yb_tkpunish.int_ () != 2 || util.isFakeClient (game.entityOfIndex (m_voteKickIndex))) { return; } edict_t *killer = game.entityOfIndex (m_lastVoteKick); @@ -2908,11 +2879,11 @@ void Bot::fastFrame (void) { // host wants us to kick someone else if (m_voteMap != 0) { - game.execBotCmd (ent (), "votemap %d", m_voteMap); + game.botCommand (ent (), "votemap %d", m_voteMap); m_voteMap = 0; } } - else if (m_buyingFinished && !(pev->maxspeed < 10.0f && taskId () != TASK_PLANTBOMB && taskId () != TASK_DEFUSEBOMB) && !yb_freeze_bots.boolean () && !waypoints.hasChanged ()) { + else if (m_buyingFinished && !(pev->maxspeed < 10.0f && getCurrentTaskId () != Task::PlantBomb && getCurrentTaskId () != Task::DefuseBomb) && !yb_freeze_bots.bool_ () && !graph.hasChanged ()) { botMovement = true; } checkMsgQueue (); // check for pending messages @@ -2923,49 +2894,51 @@ void Bot::fastFrame (void) { runMovement (); // run the player movement } -void Bot::frame (void) { +void Bot::frame () { if (m_slowFrameTimestamp > game.timebase ()) { return; } - m_numFriendsLeft = numFriendsNear (pev->origin, 99999.0f); m_numEnemiesLeft = numEnemiesNear (pev->origin, 99999.0f); - if (bots.isBombPlanted () && m_team == TEAM_COUNTER && m_notKilled) { - const Vector &bombPosition = waypoints.getBombPos (); + if (bots.isBombPlanted () && m_team == Team::CT && m_notKilled) { + const Vector &bombPosition = graph.getBombPos (); - if (!m_hasProgressBar && taskId () != TASK_ESCAPEFROMBOMB && (pev->origin - bombPosition).lengthSq () < cr::square (1540.0f) && m_moveSpeed < pev->maxspeed && !isBombDefusing (bombPosition)) { - clearTasks (); + if (!m_hasProgressBar && getCurrentTaskId () != Task::EscapeFromBomb && (pev->origin - bombPosition).lengthSq () < cr::square (1540.0f) && !isBombDefusing (bombPosition)) { + m_itemIgnore = nullptr; + m_itemCheckTime = game.timebase (); + + clearTask (getCurrentTaskId ()); } } checkSpawnConditions (); checkForChat (); - if (game.is (GAME_SUPPORT_BOT_VOICE)) { + if (game.is (GameFlags::HasBotVoice)) { showChaterIcon (false); // end voice feedback } // clear enemy far away if (!m_lastEnemyOrigin.empty () && !game.isNullEntity (m_lastEnemy) && (pev->origin - m_lastEnemyOrigin).lengthSq () >= cr::square (1600.0f)) { m_lastEnemy = nullptr; - m_lastEnemyOrigin.nullify (); + m_lastEnemyOrigin= nullvec; } m_slowFrameTimestamp = game.timebase () + 0.5f; } -void Bot::normal_ (void) { - m_aimFlags |= AIM_NAVPOINT; +void Bot::normal_ () { + m_aimFlags |= AimFlags::Nav; - int debugGoal = yb_debug_goal.integer (); + int debugGoal = yb_debug_goal.int_ (); // user forced a waypoint as a goal? - if (debugGoal != INVALID_WAYPOINT_INDEX && getTask ()->data != debugGoal) { + if (debugGoal != kInvalidNodeIndex && getTask ()->data != debugGoal) { clearSearchNodes (); getTask ()->data = debugGoal; } // stand still if reached debug goal - else if (m_currentWaypointIndex == debugGoal) { + else if (m_currentNodeIndex == debugGoal) { pev->button = 0; ignoreCollision (); @@ -2976,77 +2949,77 @@ void Bot::normal_ (void) { } // bots rushing with knife, when have no enemy (thanks for idea to nicebot project) - if (m_currentWeapon == WEAPON_KNIFE && (game.isNullEntity (m_lastEnemy) || !util.isAlive (m_lastEnemy)) && game.isNullEntity (m_enemy) && m_knifeAttackTime < game.timebase () && !hasShield () && numFriendsNear (pev->origin, 96.0f) == 0) { - if (rng.chance (40)) { + 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 (rg.chance (40)) { pev->button |= IN_ATTACK; } else { pev->button |= IN_ATTACK2; } - m_knifeAttackTime = game.timebase () + rng.getFloat (2.5f, 6.0f); + m_knifeAttackTime = game.timebase () + rg.float_ (2.5f, 6.0f); } const auto &prop = conf.getWeaponProp (m_currentWeapon); - if (m_reloadState == RELOAD_NONE && getAmmo () != 0 && getAmmoInClip () < 5 && prop.ammo1 != -1) { - m_reloadState = RELOAD_PRIMARY; + 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 && bots.isBombPlanted () && m_team == TEAM_COUNTER && getTask ()->data != INVALID_WAYPOINT_INDEX && !(waypoints[getTask ()->data].flags & FLAG_GOAL) && taskId () != TASK_ESCAPEFROMBOMB) { + if (!m_bombSearchOverridden && bots.isBombPlanted () && m_team == Team::CT && getTask ()->data != kInvalidNodeIndex && !(graph[getTask ()->data].flags & NodeFlag::Goal) && getCurrentTaskId () != Task::EscapeFromBomb) { clearSearchNodes (); - getTask ()->data = INVALID_WAYPOINT_INDEX; + getTask ()->data = kInvalidNodeIndex; } // reached the destination (goal) waypoint? if (updateNavigation ()) { // if we're reached the goal, and there is not enemies, notify the team - 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); + if (!bots.isBombPlanted () && m_currentNodeIndex != kInvalidNodeIndex && (m_path->flags & NodeFlag::Goal) && rg.chance (15) && numEnemiesNear (pev->origin, 650.0f) == 0) { + pushRadioMessage (Radio::SectorClear); } completeTask (); - m_prevGoalIndex = INVALID_WAYPOINT_INDEX; + m_prevGoalIndex = kInvalidNodeIndex; // spray logo sometimes if allowed to do so - 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); + if (m_timeLogoSpray < game.timebase () && yb_spraypaints.bool_ () && rg.chance (60) && m_moveSpeed > getShiftSpeed () && game.isNullEntity (m_pickupItem)) { + if (!(game.mapIs (MapFlags::Demolition) && bots.isBombPlanted () && m_team == Team::CT)) { + startTask (Task::Spraypaint, TaskPri::Spraypaint, kInvalidNodeIndex, game.timebase () + 1.0f, false); } } // reached waypoint is a camp waypoint - if ((m_currentPath->flags & FLAG_CAMP) && !game.is (GAME_CSDM) && yb_camping_allowed.boolean ()) { + if ((m_path->flags & NodeFlag::Camp) && !game.is (GameFlags::CSDM) && yb_camping_allowed.bool_ ()) { // check if bot has got a primary weapon and hasn't camped before if (hasPrimaryWeapon () && m_timeCamping + 10.0f < game.timebase () && !hasHostage ()) { bool campingAllowed = true; // Check if it's not allowed for this team to camp here - if (m_team == TEAM_TERRORIST) { - if (m_currentPath->flags & FLAG_CF_ONLY) { + if (m_team == Team::Terrorist) { + if (m_path->flags & NodeFlag::CTOnly) { campingAllowed = false; } } else { - if (m_currentPath->flags & FLAG_TF_ONLY) { + if (m_path->flags & NodeFlag::TerroristOnly) { campingAllowed = false; } } // don't allow vip on as_ maps to camp + don't allow terrorist carrying c4 to camp - if (campingAllowed && (m_isVIP || (game.mapIs (MAP_DE) && m_team == TEAM_TERRORIST && !bots.isBombPlanted () && m_hasC4))) { + if (campingAllowed && (m_isVIP || (game.mapIs (MapFlags::Demolition) && m_team == Team::Terrorist && !bots.isBombPlanted () && m_hasC4))) { campingAllowed = false; } // check if another bot is already camping here - if (campingAllowed && isOccupiedPoint (m_currentWaypointIndex)) { + if (campingAllowed && isOccupiedPoint (m_currentNodeIndex)) { campingAllowed = false; } if (campingAllowed) { // crouched camping here? - if (m_currentPath->flags & FLAG_CROUCH) { + if (m_path->flags & NodeFlag::Crouch) { m_campButtons = IN_DUCK; } else { @@ -3054,21 +3027,23 @@ void Bot::normal_ (void) { } selectBestWeapon (); - if (!(m_states & (STATE_SEEING_ENEMY | STATE_HEARING_ENEMY)) && !m_reloadState) { - m_reloadState = RELOAD_PRIMARY; + if (!(m_states & (Sense::SeeingEnemy | Sense::HearingEnemy)) && !m_reloadState) { + m_reloadState = Reload::Primary; } game.makeVectors (pev->v_angle); - m_timeCamping = game.timebase () + rng.getFloat (10.0f, 25.0f); - startTask (TASK_CAMP, TASKPRI_CAMP, INVALID_WAYPOINT_INDEX, m_timeCamping, true); + m_timeCamping = game.timebase () + rg.float_ (10.0f, 25.0f); + startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, m_timeCamping, true); - m_camp = Vector (m_currentPath->campStartX, m_currentPath->campStartY, 0.0f); - m_aimFlags |= AIM_CAMP; + game.makeVectors (m_path->start); + + m_camp = m_path->origin + game.vec.forward * 500.0f;; + m_aimFlags |= AimFlags::Camp; m_campDirection = 0; // tell the world we're camping - if (rng.chance (40)) { - pushRadioMessage (RADIO_IN_POSITION); + if (rg.chance (40)) { + pushRadioMessage (Radio::ImInPosition); } m_moveToGoal = false; m_checkTerrain = false; @@ -3080,21 +3055,21 @@ void Bot::normal_ (void) { } else { // some goal waypoints are map dependant so check it out... - if (game.mapIs (MAP_CS)) { + if (game.mapIs (MapFlags::HostageRescue)) { // CT Bot has some hostages following? - if (m_team == TEAM_COUNTER && hasHostage ()) { + if (m_team == Team::CT && hasHostage ()) { // and reached a Rescue Point? - if (m_currentPath->flags & FLAG_RESCUE) { + if (m_path->flags & NodeFlag::Rescue) { m_hostages.clear (); } } - else if (m_team == TEAM_TERRORIST && rng.chance (75)) { - int index = getDefendPoint (m_currentPath->origin); + else if (m_team == Team::Terrorist && rg.chance (75)) { + int index = findDefendNode (m_path->origin); - 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 + startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, game.timebase () + rg.float_ (60.0f, 120.0f), true); // push camp task on to stack + startTask (Task::MoveToPosition, TaskPri::MoveToPosition, index, game.timebase () + rg.float_ (5.0f, 10.0f), true); // push move command - auto &path = waypoints[index]; + auto &path = graph[index]; // decide to duck or not to duck if (path.vis.crouch <= path.vis.stand) { @@ -3103,37 +3078,37 @@ void Bot::normal_ (void) { else { m_campButtons &= ~IN_DUCK; } - pushChatterMessage (CHATTER_GOING_TO_GUARD_VIP_SAFETY); // play info about that + pushChatterMessage (Chatter::GoingToGuardVIPSafety); // play info about that } } - else if (game.mapIs (MAP_DE) && ((m_currentPath->flags & FLAG_GOAL) || m_inBombZone)) { + else if (game.mapIs (MapFlags::Demolition) && ((m_path->flags & NodeFlag::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) { + if ((m_states & Sense::SeeingEnemy) && numFriendsNear (pev->origin, 768.0f) == 0) { // request an help also - pushRadioMessage (RADIO_NEED_BACKUP); - instantChatter (CHATTER_SCARED_EMOTE); + pushRadioMessage (Radio::NeedBackup); + pushChatterMessage (Chatter::ScaredEmotion); - startTask (TASK_CAMP, TASKPRI_CAMP, INVALID_WAYPOINT_INDEX, game.timebase () + rng.getFloat (4.0f, 8.0f), true); + startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, game.timebase () + rg.float_ (4.0f, 8.0f), true); } else { - startTask (TASK_PLANTBOMB, TASKPRI_PLANTBOMB, INVALID_WAYPOINT_INDEX, 0.0f, false); + startTask (Task::PlantBomb, TaskPri::PlantBomb, kInvalidNodeIndex, 0.0f, false); } } - else if (m_team == TEAM_COUNTER) { + else if (m_team == Team::CT) { if (!bots.isBombPlanted () && numFriendsNear (pev->origin, 210.0f) < 4) { - int index = getDefendPoint (m_currentPath->origin); + int index = findDefendNode (m_path->origin); - float campTime = rng.getFloat (25.0f, 40.f); + float campTime = rg.float_ (25.0f, 40.f); // rusher bots don't like to camp too much - if (m_personality == PERSONALITY_RUSHER) { + if (m_personality == Personality::Rusher) { campTime *= 0.5f; } - 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 + startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, game.timebase () + campTime, true); // push camp task on to stack + startTask (Task::MoveToPosition, TaskPri::MoveToPosition, index, game.timebase () + rg.float_ (5.0f, 11.0f), true); // push move command - auto &path = waypoints[index]; + auto &path = graph[index]; // decide to duck or not to duck if (path.vis.crouch <= path.vis.stand) { @@ -3142,7 +3117,7 @@ void Bot::normal_ (void) { else { m_campButtons &= ~IN_DUCK; } - pushChatterMessage (CHATTER_DEFENDING_BOMBSITE); // play info about that + pushChatterMessage (Chatter::DefendingBombsite); // play info about that } } } @@ -3156,7 +3131,7 @@ void Bot::normal_ (void) { ignoreCollision (); // did we already decide about a goal before? - int destIndex = getTask ()->data != INVALID_WAYPOINT_INDEX ? getTask ()->data : searchGoal (); + int destIndex = getTask ()->data != kInvalidNodeIndex ? getTask ()->data : findBestGoal (); m_prevGoalIndex = destIndex; @@ -3164,8 +3139,8 @@ void Bot::normal_ (void) { getTask ()->data = destIndex; // do pathfinding if it's not the current waypoint - if (destIndex != m_currentWaypointIndex) { - searchPath (m_currentWaypointIndex, destIndex, m_pathType); + if (destIndex != m_currentNodeIndex) { + findPath (m_currentNodeIndex, destIndex, m_pathType); } } else { @@ -3175,29 +3150,27 @@ 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 >= game.timebase () || (m_states & STATE_SUSPECT_ENEMY)) && numEnemiesNear (pev->origin, 768.0f) >= 1 && !yb_jasonmode.boolean () && !bots.isBombPlanted ()) { + if ((!cr::fzero (m_moveSpeed) && m_moveSpeed > shiftSpeed) && (yb_walking_allowed.bool_ () && mp_footsteps.bool_ ()) && m_difficulty > 2 && !(m_aimFlags & AimFlags::Enemy) && (m_heardSoundTime + 6.0f >= game.timebase () || (m_states & Sense::SuspectEnemy)) && numEnemiesNear (pev->origin, 768.0f) >= 1 && !yb_jasonmode.bool_ () && !bots.isBombPlanted ()) { m_moveSpeed = shiftSpeed; } // bot hasn't seen anything in a long time and is asking his teammates to report in - if (yb_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); + if (yb_radio_mode.int_ () > 1 && m_seeEnemyTime + rg.float_ (45.0f, 80.0f) < game.timebase () && bots.getLastRadio (m_team) != Radio::ReportInTeam && rg.chance (15) && bots.getRoundStartTime () + 20.0f < game.timebase () && m_askCheckTime < game.timebase () && numFriendsNear (pev->origin, 1024.0f) == 0) { + pushRadioMessage (Radio::ReportInTeam); - m_askCheckTime = game.timebase () + rng.getFloat (45.0f, 80.0f); + m_askCheckTime = game.timebase () + rg.float_ (45.0f, 80.0f); // make sure everyone else will not ask next few moments - for (int i = 0; i < game.maxClients (); i++) { - auto bot = bots.getBot (i); - - if (bot && bot->m_notKilled) { - bot->m_askCheckTime = game.timebase () + rng.getFloat (5.0f, 10.0f); + for (const auto &bot : bots) { + if (bot->m_notKilled) { + bot->m_askCheckTime = game.timebase () + rg.float_ (5.0f, 30.0f); } } } } -void Bot::spraypaint_ (void) { - m_aimFlags |= AIM_ENTITY; +void Bot::spraypaint_ () { + m_aimFlags |= AimFlags::Entity; // bot didn't spray this round? if (m_timeLogoSpray < game.timebase () && getTask ()->time > game.timebase ()) { @@ -3205,7 +3178,7 @@ void Bot::spraypaint_ (void) { Vector sprayOrigin = getEyesPos () + game.vec.forward * 128.0f; TraceResult tr; - game.testLine (getEyesPos (), sprayOrigin, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (getEyesPos (), sprayOrigin, TraceIgnore::Monsters, ent (), &tr); // no wall in front? if (tr.flFraction >= 1.0f) { @@ -3216,11 +3189,11 @@ void Bot::spraypaint_ (void) { if (getTask ()->time - 0.5f < game.timebase ()) { // emit spraycan sound 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); + game.testLine (getEyesPos (), getEyesPos () + game.vec.forward * 128.0f, TraceIgnore::Monsters, ent (), &tr); // paint the actual logo decal util.traceDecals (pev, &tr, m_logotypeIndex); - m_timeLogoSpray = game.timebase () + rng.getFloat (60.0f, 90.0f); + m_timeLogoSpray = game.timebase () + rg.float_ (60.0f, 90.0f); } } else { @@ -3236,21 +3209,21 @@ void Bot::spraypaint_ (void) { ignoreCollision (); } -void Bot::huntEnemy_ (void) { - m_aimFlags |= AIM_NAVPOINT; +void Bot::huntEnemy_ () { + m_aimFlags |= AimFlags::Nav; // if we've got new enemy... if (!game.isNullEntity (m_enemy) || game.isNullEntity (m_lastEnemy)) { // forget about it... - clearTask (TASK_HUNTENEMY); - m_prevGoalIndex = INVALID_WAYPOINT_INDEX; + clearTask (Task::Hunt); + m_prevGoalIndex = kInvalidNodeIndex; } else if (game.getTeam (m_lastEnemy) == m_team) { // don't hunt down our teammate... - clearTask (TASK_HUNTENEMY); - m_prevGoalIndex = INVALID_WAYPOINT_INDEX; + clearTask (Task::Hunt); + m_prevGoalIndex = kInvalidNodeIndex; m_lastEnemy = nullptr; } else if (updateNavigation ()) // reached last enemy pos? @@ -3258,41 +3231,41 @@ void Bot::huntEnemy_ (void) { // forget about it... completeTask (); - m_prevGoalIndex = INVALID_WAYPOINT_INDEX; - m_lastEnemyOrigin.nullify (); + m_prevGoalIndex = kInvalidNodeIndex; + m_lastEnemyOrigin= nullvec; } else if (!hasActiveGoal ()) // do we need to calculate a new path? { clearSearchNodes (); - int destIndex = INVALID_WAYPOINT_INDEX; + int destIndex = kInvalidNodeIndex; int goal = getTask ()->data; // is there a remembered index? - if (waypoints.exists (goal)) { + if (graph.exists (goal)) { destIndex = goal; } // find new one instead else { - destIndex = waypoints.getNearest (m_lastEnemyOrigin); + destIndex = graph.getNearest (m_lastEnemyOrigin); } // remember index m_prevGoalIndex = destIndex; getTask ()->data = destIndex; - if (destIndex != m_currentWaypointIndex) { - searchPath (m_currentWaypointIndex, destIndex, SEARCH_PATH_FASTEST); + if (destIndex != m_currentNodeIndex) { + findPath (m_currentNodeIndex, destIndex, FindPath::Fast); } } // bots skill higher than 60? - if (yb_walking_allowed.boolean () && mp_footsteps.boolean () && m_difficulty > 1 && !yb_jasonmode.boolean ()) { + if (yb_walking_allowed.bool_ () && mp_footsteps.bool_ () && m_difficulty > 1 && !yb_jasonmode.bool_ ()) { // 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 > game.timebase () && m_difficulty < 3) { + if (!(m_currentTravelFlags & PathFlag::Jump)) { + if (m_currentNodeIndex != kInvalidNodeIndex) { + if (m_path->radius < 32.0f && !isOnLadder () && !isInWater () && m_seeEnemyTime + 4.0f > game.timebase () && m_difficulty < 3) { pev->button |= IN_DUCK; } } @@ -3304,35 +3277,35 @@ void Bot::huntEnemy_ (void) { } } -void Bot::seekCover_ (void) { - m_aimFlags |= AIM_NAVPOINT; +void Bot::seekCover_ () { + m_aimFlags |= AimFlags::Nav; if (!util.isAlive (m_lastEnemy)) { completeTask (); - m_prevGoalIndex = INVALID_WAYPOINT_INDEX; + m_prevGoalIndex = kInvalidNodeIndex; } // reached final waypoint? else if (updateNavigation ()) { // yep. activate hide behaviour completeTask (); - m_prevGoalIndex = INVALID_WAYPOINT_INDEX; + m_prevGoalIndex = kInvalidNodeIndex; // start hide task - startTask (TASK_HIDE, TASKPRI_HIDE, INVALID_WAYPOINT_INDEX, game.timebase () + rng.getFloat (3.0f, 12.0f), false); + startTask (Task::Hide, TaskPri::Hide, kInvalidNodeIndex, game.timebase () + rg.float_ (3.0f, 12.0f), false); Vector dest = m_lastEnemyOrigin; // get a valid look direction getCampDirection (&dest); - m_aimFlags |= AIM_CAMP; + m_aimFlags |= AimFlags::Camp; m_camp = dest; m_campDirection = 0; // chosen waypoint is a camp waypoint? - if (m_currentPath->flags & FLAG_CAMP) { + if (m_path->flags & NodeFlag::Camp) { // use the existing camp wpt prefs - if (m_currentPath->flags & FLAG_CROUCH) { + if (m_path->flags & NodeFlag::Crouch) { m_campButtons = IN_DUCK; } else { @@ -3341,7 +3314,7 @@ void Bot::seekCover_ (void) { } else { // choose a crouch or stand pos - if (m_currentPath->vis.crouch <= m_currentPath->vis.stand) { + if (m_path->vis.crouch <= m_path->vis.stand) { m_campButtons = IN_DUCK; } else { @@ -3349,15 +3322,12 @@ void Bot::seekCover_ (void) { } // enter look direction from previously calculated positions - m_currentPath->campStartX = dest.x; - m_currentPath->campStartY = dest.y; - - m_currentPath->campEndX = dest.x; - m_currentPath->campEndY = dest.y; + m_path->start = dest; + m_path->end = dest; } - if (m_reloadState == RELOAD_NONE && getAmmoInClip () < 5 && getAmmo () != 0) { - m_reloadState = RELOAD_PRIMARY; + if (m_reloadState == Reload::None && getAmmoInClip () < 5 && getAmmo () != 0) { + m_reloadState = Reload::Primary; } m_moveSpeed = 0.0f; m_strafeSpeed = 0.0f; @@ -3368,17 +3338,17 @@ void Bot::seekCover_ (void) { else if (!hasActiveGoal ()) // we didn't choose a cover waypoint yet or lost it due to an attack? { clearSearchNodes (); - int destIndex = INVALID_WAYPOINT_INDEX; + int destIndex = kInvalidNodeIndex; - if (getTask ()->data != INVALID_WAYPOINT_INDEX) { + if (getTask ()->data != kInvalidNodeIndex) { destIndex = getTask ()->data; } else { - destIndex = getCoverPoint (usesSniper () ? 256.0f : 512.0f); + destIndex = findCoverNode (usesSniper () ? 256.0f : 512.0f); - if (destIndex == INVALID_WAYPOINT_INDEX) { - m_retreatTime = game.timebase () + rng.getFloat (5.0f, 10.0f); - m_prevGoalIndex = INVALID_WAYPOINT_INDEX; + if (destIndex == kInvalidNodeIndex) { + m_retreatTime = game.timebase () + rg.float_ (5.0f, 10.0f); + m_prevGoalIndex = kInvalidNodeIndex; completeTask (); return; @@ -3389,13 +3359,13 @@ void Bot::seekCover_ (void) { m_prevGoalIndex = destIndex; getTask ()->data = destIndex; - if (destIndex != m_currentWaypointIndex) { - searchPath (m_currentWaypointIndex, destIndex, SEARCH_PATH_FASTEST); + if (destIndex != m_currentNodeIndex) { + findPath (m_currentNodeIndex, destIndex, FindPath::Fast); } } } -void Bot::attackEnemy_ (void) { +void Bot::attackEnemy_ () { m_moveToGoal = false; m_checkTerrain = false; @@ -3408,7 +3378,7 @@ void Bot::attackEnemy_ (void) { } attackMovement (); - if (m_currentWeapon == WEAPON_KNIFE && !m_lastEnemyOrigin.empty ()) { + if (m_currentWeapon == Weapon::Knife && !m_lastEnemyOrigin.empty ()) { m_destOrigin = m_lastEnemyOrigin; } } @@ -3419,7 +3389,7 @@ void Bot::attackEnemy_ (void) { m_navTimeset = game.timebase (); } -void Bot::pause_ (void) { +void Bot::pause_ () { m_moveToGoal = false; m_checkTerrain = false; @@ -3427,7 +3397,7 @@ void Bot::pause_ (void) { m_moveSpeed = 0.0f; m_strafeSpeed = 0.0f; - m_aimFlags |= AIM_NAVPOINT; + m_aimFlags |= AimFlags::Nav; // is bot blinded and above average difficulty? if (m_viewDistance < 500.0f && m_difficulty >= 2) { @@ -3440,7 +3410,7 @@ void Bot::pause_ (void) { game.makeVectors (pev->v_angle); m_camp = getEyesPos () + game.vec.forward * 500.0f; - m_aimFlags |= AIM_OVERRIDE; + m_aimFlags |= AimFlags::Override; m_wantsToFire = true; } else { @@ -3453,7 +3423,7 @@ void Bot::pause_ (void) { } } -void Bot::blind_ (void) { +void Bot::blind_ () { m_moveToGoal = false; m_checkTerrain = false; m_navTimeset = game.timebase (); @@ -3473,17 +3443,17 @@ void Bot::blind_ (void) { } } -void Bot::camp_ (void) { - if (!yb_camping_allowed.boolean ()) { +void Bot::camp_ () { + if (!yb_camping_allowed.bool_ ()) { completeTask (); return; } - m_aimFlags |= AIM_CAMP; + m_aimFlags |= AimFlags::Camp; m_checkTerrain = false; m_moveToGoal = false; - if (m_team == TEAM_COUNTER && bots.isBombPlanted () && m_defendedBomb && !isBombDefusing (waypoints.getBombPos ()) && !isOutOfBombTimer ()) { + if (m_team == Team::CT && bots.isBombPlanted () && m_defendedBomb && !isBombDefusing (graph.getBombPos ()) && !isOutOfBombTimer ()) { m_defendedBomb = false; completeTask (); } @@ -3499,24 +3469,21 @@ void Bot::camp_ (void) { m_moveSpeed = 0.0f; m_strafeSpeed = 0.0f; - getValidPoint (); + findValidNode (); if (m_nextCampDirTime < game.timebase ()) { - m_nextCampDirTime = game.timebase () + rng.getFloat (2.0f, 5.0f); + m_nextCampDirTime = game.timebase () + rg.float_ (2.0f, 5.0f); - if (m_currentPath->flags & FLAG_CAMP) { + if (m_path->flags & NodeFlag::Camp) { Vector dest; // switch from 1 direction to the other if (m_campDirection < 1) { - dest.x = m_currentPath->campStartX; - dest.y = m_currentPath->campStartY; - + dest = m_path->start; m_campDirection ^= 1; } else { - dest.x = m_currentPath->campEndX; - dest.y = m_currentPath->campEndY; + dest = m_path->end; m_campDirection ^= 1; } dest.z = 0.0f; @@ -3528,20 +3495,20 @@ void Bot::camp_ (void) { int campPoints[3] = { 0, }; int distances[3] = { 0, }; - const Vector &dotA = (dest - pev->origin).normalize2D (); + const Vector &dotA = (dest - pev->origin).normalize2d (); - for (int i = 0; i < waypoints.length (); i++) { + for (int i = 0; i < graph.length (); ++i) { // skip invisible waypoints or current waypoint - if (!waypoints.isVisible (m_currentWaypointIndex, i) || (i == m_currentWaypointIndex)) { + if (!graph.isVisible (m_currentNodeIndex, i) || (i == m_currentNodeIndex)) { continue; } - const Vector &dotB = (waypoints[i].origin - pev->origin).normalize2D (); + const Vector &dotB = (graph[i].origin - pev->origin).normalize2d (); if ((dotA | dotB) > 0.9f) { - int distance = static_cast ((pev->origin - waypoints[i].origin).length ()); + int distance = static_cast ((pev->origin - graph[i].origin).length ()); if (numFoundPoints >= 3) { - for (int j = 0; j < 3; j++) { + for (int j = 0; j < 3; ++j) { if (distance > distances[j]) { distances[j] = distance; campPoints[j] = i; @@ -3560,14 +3527,14 @@ void Bot::camp_ (void) { } if (--numFoundPoints >= 0) { - m_camp = waypoints[campPoints[rng.getInt (0, numFoundPoints)]].origin; + m_camp = graph[campPoints[rg.int_ (0, numFoundPoints)]].origin; } else { - m_camp = waypoints[searchCampDir ()].origin; + m_camp = graph[findCampingDirection ()].origin; } } else { - m_camp = waypoints[searchCampDir ()].origin; + m_camp = graph[findCampingDirection ()].origin; } } // press remembered crouch button @@ -3579,8 +3546,8 @@ void Bot::camp_ (void) { } } -void Bot::hide_ (void) { - m_aimFlags |= AIM_CAMP; +void Bot::hide_ () { + m_aimFlags |= AimFlags::Camp; m_checkTerrain = false; m_moveToGoal = false; @@ -3592,7 +3559,7 @@ void Bot::hide_ (void) { m_moveSpeed = 0.0f; m_strafeSpeed = 0.0f; - getValidPoint (); + findValidNode (); if (hasShield () && !m_isReloading) { if (!isShieldDrawn ()) { @@ -3604,12 +3571,12 @@ void Bot::hide_ (void) { } // if we see an enemy and aren't at a good camping point leave the spot - if ((m_states & STATE_SEEING_ENEMY) || m_inBombZone) { - if (!(m_currentPath->flags & FLAG_CAMP)) { + if ((m_states & Sense::SeeingEnemy) || m_inBombZone) { + if (!(m_path->flags & NodeFlag::Camp)) { completeTask (); m_campButtons = 0; - m_prevGoalIndex = INVALID_WAYPOINT_INDEX; + m_prevGoalIndex = kInvalidNodeIndex; if (!game.isNullEntity (m_enemy)) { attackMovement (); @@ -3623,9 +3590,9 @@ void Bot::hide_ (void) { completeTask (); m_campButtons = 0; - m_prevGoalIndex = INVALID_WAYPOINT_INDEX; + m_prevGoalIndex = kInvalidNodeIndex; - if (taskId () == TASK_HIDE) { + if (getCurrentTaskId () == Task::Hide) { completeTask (); } return; @@ -3644,8 +3611,8 @@ void Bot::hide_ (void) { } } -void Bot::moveToPos_ (void) { - m_aimFlags |= AIM_NAVPOINT; +void Bot::moveToPos_ () { + m_aimFlags |= AimFlags::Nav; if (isShieldDrawn ()) { pev->button |= IN_ATTACK2; @@ -3655,28 +3622,28 @@ void Bot::moveToPos_ (void) { if (updateNavigation ()) { completeTask (); // we're done - m_prevGoalIndex = INVALID_WAYPOINT_INDEX; - m_position.nullify (); + m_prevGoalIndex = kInvalidNodeIndex; + m_position= nullvec; } // didn't choose goal waypoint yet? else if (!hasActiveGoal ()) { clearSearchNodes (); - int destIndex = INVALID_WAYPOINT_INDEX; + int destIndex = kInvalidNodeIndex; int goal = getTask ()->data; - if (waypoints.exists (goal)) { + if (graph.exists (goal)) { destIndex = goal; } else { - destIndex = waypoints.getNearest (m_position); + destIndex = graph.getNearest (m_position); } - if (waypoints.exists (destIndex)) { + if (graph.exists (destIndex)) { m_prevGoalIndex = destIndex; getTask ()->data = destIndex; - searchPath (m_currentWaypointIndex, destIndex, m_pathType); + findPath (m_currentNodeIndex, destIndex, m_pathType); } else { completeTask (); @@ -3684,13 +3651,13 @@ void Bot::moveToPos_ (void) { } } -void Bot::plantBomb_ (void) { - m_aimFlags |= AIM_CAMP; +void Bot::plantBomb_ () { + m_aimFlags |= AimFlags::Camp; // we're still got the C4? if (m_hasC4) { - if (m_currentWeapon != WEAPON_C4) { + if (m_currentWeapon != Weapon::C4) { selectWeaponByName ("weapon_c4"); } @@ -3702,7 +3669,7 @@ void Bot::plantBomb_ (void) { m_checkTerrain = false; m_navTimeset = game.timebase (); - if (m_currentPath->flags & FLAG_CROUCH) { + if (m_path->flags & NodeFlag::Crouch) { pev->button |= (IN_ATTACK | IN_DUCK); } else { @@ -3719,20 +3686,20 @@ void Bot::plantBomb_ (void) { // tell teammates to move over here... if (numFriendsNear (pev->origin, 1200.0f) != 0) { - pushRadioMessage (RADIO_NEED_BACKUP); + pushRadioMessage (Radio::NeedBackup); } clearSearchNodes (); - int index = getDefendPoint (pev->origin); + int index = findDefendNode (pev->origin); - float guardTime = mp_c4timer.flt () * 0.5f + mp_c4timer.flt () * 0.25f; + float guardTime = mp_c4timer.float_ () * 0.5f + mp_c4timer.float_ () * 0.25f; // push camp task on to stack - startTask (TASK_CAMP, TASKPRI_CAMP, INVALID_WAYPOINT_INDEX, game.timebase () + guardTime, true); + startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, game.timebase () + guardTime, true); // push move command - startTask (TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, index, game.timebase () + guardTime, true); + startTask (Task::MoveToPosition, TaskPri::MoveToPosition, index, game.timebase () + guardTime, true); - if (waypoints[index].vis.crouch <= waypoints[index].vis.stand) { + if (graph[index].vis.crouch <= graph[index].vis.stand) { m_campButtons |= IN_DUCK; } else { @@ -3741,7 +3708,7 @@ void Bot::plantBomb_ (void) { } } -void Bot::bombDefuse_ (void) { +void Bot::bombDefuse_ () { float fullDefuseTime = m_hasDefuser ? 7.0f : 12.0f; float timeToBlowUp = getBombTimeleft (); float defuseRemainingTime = fullDefuseTime; @@ -3751,11 +3718,11 @@ void Bot::bombDefuse_ (void) { } bool pickupExists = !game.isNullEntity (m_pickupItem); - const Vector &bombPos = pickupExists ? m_pickupItem->v.origin : waypoints.getBombPos (); + const Vector &bombPos = pickupExists ? m_pickupItem->v.origin : graph.getBombPos (); if (pickupExists) { - if (waypoints.getBombPos () != bombPos) { - waypoints.setBombPos (bombPos); + if (graph.getBombPos () != bombPos) { + graph.setBombPos (bombPos); } } bool defuseError = false; @@ -3764,24 +3731,24 @@ void Bot::bombDefuse_ (void) { if (bombPos.empty ()) { defuseError = true; - if (m_numFriendsLeft != 0 && rng.chance (50)) { + if (m_numFriendsLeft != 0 && rg.chance (50)) { if (timeToBlowUp <= 3.0) { - if (yb_communication_type.integer () == 2) { - instantChatter (CHATTER_BARELY_DEFUSED); + if (yb_radio_mode.int_ () == 2) { + pushChatterMessage (Chatter::BarelyDefused); } - else if (yb_communication_type.integer () == 1) { - pushRadioMessage (RADIO_SECTOR_CLEAR); + else if (yb_radio_mode.int_ () == 1) { + pushRadioMessage (Radio::SectorClear); } } else { - pushRadioMessage (RADIO_SECTOR_CLEAR); + pushRadioMessage (Radio::SectorClear); } } } else if (defuseRemainingTime > timeToBlowUp) { defuseError = true; } - else if (m_states & STATE_SEEING_ENEMY) { + else if (m_states & Sense::SeeingEnemy) { int friends = numFriendsNear (pev->origin, 768.0f); if (friends < 2 && defuseRemainingTime < timeToBlowUp) { @@ -3792,7 +3759,7 @@ void Bot::bombDefuse_ (void) { } if (m_numFriendsLeft > friends) { - pushRadioMessage (RADIO_NEED_BACKUP); + pushRadioMessage (Radio::NeedBackup); } } } @@ -3802,12 +3769,13 @@ void Bot::bombDefuse_ (void) { m_checkTerrain = true; m_moveToGoal = true; - m_destOrigin.nullify (); - m_entity.nullify (); + m_destOrigin= nullvec; + m_entity= nullvec; m_pickupItem = nullptr; - m_pickupType = PICKUP_NONE; + m_pickupType = Pickup::None; + selectBestWeapon (); completeTask (); return; } @@ -3820,14 +3788,14 @@ void Bot::bombDefuse_ (void) { m_strafeSpeed = 0.0f; // 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 || ((getAmmoInClip () > 8 && m_reloadState == RELOAD_PRIMARY) || (getAmmoInClip () > 5 && m_reloadState == RELOAD_SECONDARY))) { + if (m_isReloading && (bombPos - pev->origin).length2d () < 80.0f) { + 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 selectWeaponByName ("weapon_knife"); - if (weaponIndex > 0 && weaponIndex < NUM_WEAPONS) { + if (weaponIndex > 0 && weaponIndex < kNumWeapons) { selectWeaponById (weaponIndex); } m_isReloading = false; @@ -3842,7 +3810,7 @@ void Bot::bombDefuse_ (void) { } // head to bomb and press use button - m_aimFlags |= AIM_ENTITY; + m_aimFlags |= AimFlags::Entity; m_destOrigin = bombPos; m_entity = bombPos; @@ -3851,9 +3819,6 @@ void Bot::bombDefuse_ (void) { // if defusing is not already started, maybe crouch before if (!m_hasProgressBar && m_duckDefuseCheckTime < game.timebase ()) { - if (m_difficulty >= 2 && m_numEnemiesLeft != 0) { - m_duckDefuse = true; - } Vector botDuckOrigin, botStandOrigin; if (pev->button & IN_DUCK) { @@ -3873,10 +3838,10 @@ void Bot::bombDefuse_ (void) { m_duckDefuse = false; // stand } else { - m_duckDefuse = true; // duck + m_duckDefuse = m_difficulty >= 2 && m_numEnemiesLeft != 0; // duck } } - m_duckDefuseCheckTime = game.timebase () + 1.5f; + m_duckDefuseCheckTime = game.timebase () + 5.0f; } // press duck button @@ -3891,7 +3856,7 @@ void Bot::bombDefuse_ (void) { if (m_hasProgressBar || pickupExists || (m_oldButtons & IN_USE)) { pev->button |= IN_USE; - m_reloadState = RELOAD_NONE; + m_reloadState = Reload::None; m_navTimeset = game.timebase (); // don't move when defusing @@ -3903,10 +3868,10 @@ void Bot::bombDefuse_ (void) { // notify team if (m_numFriendsLeft != 0) { - pushChatterMessage (CHATTER_DEFUSING_BOMB); + pushChatterMessage (Chatter::DefusingBomb); if (numFriendsNear (pev->origin, 512.0f) < 2) { - pushRadioMessage (RADIO_NEED_BACKUP); + pushRadioMessage (Radio::NeedBackup); } } } @@ -3915,7 +3880,7 @@ void Bot::bombDefuse_ (void) { } } -void Bot::followUser_ (void) { +void Bot::followUser_ () { if (game.isNullEntity (m_targetEntity) || !util.isAlive (m_targetEntity)) { m_targetEntity = nullptr; completeTask (); @@ -3927,7 +3892,7 @@ void Bot::followUser_ (void) { game.makeVectors (m_targetEntity->v.v_angle); TraceResult tr; - game.testLine (m_targetEntity->v.origin + m_targetEntity->v.view_ofs, game.vec.forward * 500.0f, TRACE_IGNORE_EVERYTHING, ent (), &tr); + game.testLine (m_targetEntity->v.origin + m_targetEntity->v.view_ofs, game.vec.forward * 500.0f, TraceIgnore::Everything, ent (), &tr); if (!game.isNullEntity (tr.pHit) && util.isPlayer (tr.pHit) && game.getTeam (tr.pHit) != m_team) { m_targetEntity = nullptr; @@ -3943,8 +3908,8 @@ void Bot::followUser_ (void) { m_moveSpeed = m_targetEntity->v.maxspeed; } - if (m_reloadState == RELOAD_NONE && getAmmo () != 0) { - m_reloadState = RELOAD_PRIMARY; + if (m_reloadState == Reload::None && getAmmo () != 0) { + m_reloadState = Reload::Primary; } if ((m_targetEntity->v.origin - pev->origin).lengthSq () > cr::square (130.0f)) { @@ -3961,16 +3926,16 @@ void Bot::followUser_ (void) { // stop following if we have been waiting too long m_targetEntity = nullptr; - pushRadioMessage (RADIO_YOU_TAKE_THE_POINT); + pushRadioMessage (Radio::YouTakeThePoint); completeTask (); return; } } } - m_aimFlags |= AIM_NAVPOINT; + m_aimFlags |= AimFlags::Nav; - if (yb_walking_allowed.boolean () && m_targetEntity->v.maxspeed < m_moveSpeed && !yb_jasonmode.boolean ()) { + if (yb_walking_allowed.bool_ () && m_targetEntity->v.maxspeed < m_moveSpeed && !yb_jasonmode.bool_ ()) { m_moveSpeed = getShiftSpeed (); } @@ -3980,29 +3945,29 @@ void Bot::followUser_ (void) { // reached destination? if (updateNavigation ()) { - getTask ()->data = INVALID_WAYPOINT_INDEX; + getTask ()->data = kInvalidNodeIndex; } // didn't choose goal waypoint yet? if (!hasActiveGoal ()) { clearSearchNodes (); - int destIndex = waypoints.getNearest (m_targetEntity->v.origin); - IntArray points = waypoints.searchRadius (200.0f, m_targetEntity->v.origin); + int destIndex = graph.getNearest (m_targetEntity->v.origin); + IntArray points = graph.searchRadius (200.0f, m_targetEntity->v.origin); for (auto &newIndex : points) { // if waypoint not yet used, assign it as dest - if (newIndex != m_currentWaypointIndex && !isOccupiedPoint (newIndex)) { + if (newIndex != m_currentNodeIndex && !isOccupiedPoint (newIndex)) { destIndex = newIndex; } } - if (waypoints.exists (destIndex) && waypoints.exists (m_currentWaypointIndex)) { + if (graph.exists (destIndex) && graph.exists (m_currentNodeIndex)) { m_prevGoalIndex = destIndex; getTask ()->data = destIndex; // always take the shortest path - searchPath (m_currentWaypointIndex, destIndex, SEARCH_PATH_FASTEST); + findPath (m_currentNodeIndex, destIndex, FindPath::Fast); } else { m_targetEntity = nullptr; @@ -4011,17 +3976,17 @@ void Bot::followUser_ (void) { } } -void Bot::throwExplosive_ (void) { - m_aimFlags |= AIM_GRENADE; +void Bot::throwExplosive_ () { + m_aimFlags |= AimFlags::Grenade; Vector dest = m_throw; - if (!(m_states & STATE_SEEING_ENEMY)) { + if (!(m_states & Sense::SeeingEnemy)) { m_strafeSpeed = 0.0f; m_moveSpeed = 0.0f; m_moveToGoal = false; } - else if (!(m_states & STATE_SUSPECT_ENEMY) && !game.isNullEntity (m_enemy)) { - dest = m_enemy->v.origin + m_enemy->v.velocity.make2D () * 0.55f; + else if (!(m_states & Sense::SuspectEnemy) && !game.isNullEntity (m_enemy)) { + dest = m_enemy->v.origin + m_enemy->v.velocity.get2d () * 0.55f; } m_isUsingGrenade = true; m_checkTerrain = false; @@ -4030,7 +3995,7 @@ void Bot::throwExplosive_ (void) { if ((pev->origin - dest).lengthSq () < cr::square (400.0f)) { // heck, I don't wanna blow up myself - m_grenadeCheckTime = game.timebase () + MAX_GRENADE_TIMER; + m_grenadeCheckTime = game.timebase () + kGrenadeCheckTime; selectBestWeapon (); completeTask (); @@ -4044,7 +4009,7 @@ void Bot::throwExplosive_ (void) { } if (m_grenade.lengthSq () <= 100.0f) { - m_grenadeCheckTime = game.timebase () + MAX_GRENADE_TIMER; + m_grenadeCheckTime = game.timebase () + kGrenadeCheckTime; selectBestWeapon (); completeTask (); @@ -4053,8 +4018,8 @@ void Bot::throwExplosive_ (void) { auto grenade = correctGrenadeVelocity ("hegrenade.mdl"); if (game.isNullEntity (grenade)) { - if (m_currentWeapon != WEAPON_EXPLOSIVE && !m_grenadeRequested) { - if (pev->weapons & (1 << WEAPON_EXPLOSIVE)) { + if (m_currentWeapon != Weapon::Explosive && !m_grenadeRequested) { + if (pev->weapons & cr::bit (Weapon::Explosive)) { m_grenadeRequested = true; selectWeaponByName ("weapon_hegrenade"); } @@ -4076,17 +4041,17 @@ void Bot::throwExplosive_ (void) { pev->button |= m_campButtons; } -void Bot::throwFlashbang_ (void) { - m_aimFlags |= AIM_GRENADE; +void Bot::throwFlashbang_ () { + m_aimFlags |= AimFlags::Grenade; Vector dest = m_throw; - if (!(m_states & STATE_SEEING_ENEMY)) { + if (!(m_states & Sense::SeeingEnemy)) { m_strafeSpeed = 0.0f; m_moveSpeed = 0.0f; m_moveToGoal = false; } - else if (!(m_states & STATE_SUSPECT_ENEMY) && !game.isNullEntity (m_enemy)) { - dest = m_enemy->v.origin + m_enemy->v.velocity.make2D () * 0.55f; + else if (!(m_states & Sense::SuspectEnemy) && !game.isNullEntity (m_enemy)) { + dest = m_enemy->v.origin + m_enemy->v.velocity.get2d () * 0.55f; } m_isUsingGrenade = true; @@ -4096,7 +4061,7 @@ void Bot::throwFlashbang_ (void) { if ((pev->origin - dest).lengthSq () < cr::square (400.0f)) { // heck, I don't wanna blow up myself - m_grenadeCheckTime = game.timebase () + MAX_GRENADE_TIMER; + m_grenadeCheckTime = game.timebase () + kGrenadeCheckTime; selectBestWeapon (); completeTask (); @@ -4110,7 +4075,7 @@ void Bot::throwFlashbang_ (void) { } if (m_grenade.lengthSq () <= 100.0f) { - m_grenadeCheckTime = game.timebase () + MAX_GRENADE_TIMER; + m_grenadeCheckTime = game.timebase () + kGrenadeCheckTime; selectBestWeapon (); completeTask (); @@ -4119,8 +4084,8 @@ void Bot::throwFlashbang_ (void) { auto grenade = correctGrenadeVelocity ("flashbang.mdl"); if (game.isNullEntity (grenade)) { - if (m_currentWeapon != WEAPON_FLASHBANG && !m_grenadeRequested) { - if (pev->weapons & (1 << WEAPON_FLASHBANG)) { + if (m_currentWeapon != Weapon::Flashbang && !m_grenadeRequested) { + if (pev->weapons & cr::bit (Weapon::Flashbang)) { m_grenadeRequested = true; selectWeaponByName ("weapon_flashbang"); } @@ -4142,10 +4107,10 @@ void Bot::throwFlashbang_ (void) { pev->button |= m_campButtons; } -void Bot::throwSmoke_ (void) { - m_aimFlags |= AIM_GRENADE; +void Bot::throwSmoke_ () { + m_aimFlags |= AimFlags::Grenade; - if (!(m_states & STATE_SEEING_ENEMY)) { + if (!(m_states & Sense::SeeingEnemy)) { m_strafeSpeed = 0.0f; m_moveSpeed = 0.0f; m_moveToGoal = false; @@ -4170,8 +4135,8 @@ void Bot::throwSmoke_ (void) { return; } - if (m_currentWeapon != WEAPON_SMOKE && !m_grenadeRequested) { - if (pev->weapons & (1 << WEAPON_SMOKE)) { + if (m_currentWeapon != Weapon::Smoke && !m_grenadeRequested) { + if (pev->weapons & cr::bit (Weapon::Smoke)) { m_grenadeRequested = true; selectWeaponByName ("weapon_smokegrenade"); @@ -4193,12 +4158,12 @@ void Bot::throwSmoke_ (void) { pev->button |= m_campButtons; } -void Bot::doublejump_ (void) { - 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 ())) { +void Bot::doublejump_ () { + if (!util.isAlive (m_doubleJumpEntity) || (m_aimFlags & AimFlags::Enemy) || (m_travelStartIndex != kInvalidNodeIndex && getTask ()->time + (graph.calculateTravelTime (pev->maxspeed, graph[m_travelStartIndex].origin, m_doubleJumpOrigin) + 11.0f) < game.timebase ())) { resetDoubleJump (); return; } - m_aimFlags |= AIM_NAVPOINT; + m_aimFlags |= AimFlags::Nav; if (m_jumpReady) { m_moveToGoal = false; @@ -4222,39 +4187,39 @@ void Bot::doublejump_ (void) { Vector dest = src + game.vec.up * 256.0f; TraceResult tr; - game.testLine (src, dest, TRACE_IGNORE_NONE, ent (), &tr); + game.testLine (src, dest, TraceIgnore::None, ent (), &tr); if (tr.flFraction < 1.0f && tr.pHit == m_doubleJumpEntity && inJump) { - m_duckForJump = game.timebase () + rng.getFloat (3.0f, 5.0f); + m_duckForJump = game.timebase () + rg.float_ (3.0f, 5.0f); getTask ()->time = game.timebase (); } return; } - if (m_currentWaypointIndex == m_prevGoalIndex) { - m_waypointOrigin = m_doubleJumpOrigin; + if (m_currentNodeIndex == m_prevGoalIndex) { + m_pathOrigin = m_doubleJumpOrigin; m_destOrigin = m_doubleJumpOrigin; } if (updateNavigation ()) { - getTask ()->data = INVALID_WAYPOINT_INDEX; + getTask ()->data = kInvalidNodeIndex; } // didn't choose goal waypoint yet? if (!hasActiveGoal ()) { clearSearchNodes (); - int destIndex = waypoints.getNearest (m_doubleJumpOrigin); + int destIndex = graph.getNearest (m_doubleJumpOrigin); - if (waypoints.exists (destIndex)) { + if (graph.exists (destIndex)) { m_prevGoalIndex = destIndex; getTask ()->data = destIndex; - m_travelStartIndex = m_currentWaypointIndex; + m_travelStartIndex = m_currentNodeIndex; // always take the shortest path - searchPath (m_currentWaypointIndex, destIndex, SEARCH_PATH_FASTEST); + findPath (m_currentNodeIndex, destIndex, FindPath::Fast); - if (m_currentWaypointIndex == destIndex) { + if (m_currentNodeIndex == destIndex) { m_jumpReady = true; } } @@ -4264,8 +4229,8 @@ void Bot::doublejump_ (void) { } } -void Bot::escapeFromBomb_ (void) { - m_aimFlags |= AIM_NAVPOINT; +void Bot::escapeFromBomb_ () { + m_aimFlags |= AimFlags::Nav; if (!bots.isBombPlanted ()) { completeTask (); @@ -4275,7 +4240,7 @@ void Bot::escapeFromBomb_ (void) { pev->button |= IN_ATTACK2; } - if (m_currentWeapon != WEAPON_KNIFE && m_numEnemiesLeft == 0) { + if (m_currentWeapon != Weapon::Knife && m_numEnemiesLeft == 0) { selectWeaponByName ("weapon_knife"); } @@ -4289,21 +4254,21 @@ void Bot::escapeFromBomb_ (void) { } // we're reached destination point so just sit down and camp - startTask (TASK_CAMP, TASKPRI_CAMP, INVALID_WAYPOINT_INDEX, game.timebase () + 10.0f, true); + startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, game.timebase () + 10.0f, true); } // didn't choose goal waypoint yet? else if (!hasActiveGoal ()) { clearSearchNodes (); - int lastSelectedGoal = INVALID_WAYPOINT_INDEX, minPathDistance = 99999; - float safeRadius = rng.getFloat (1248.0f, 2048.0f); + int lastSelectedGoal = kInvalidNodeIndex, minPathDistance = 99999; + float safeRadius = rg.float_ (1248.0f, 2048.0f); - for (int i = 0; i < waypoints.length (); i++) { - if ((waypoints[i].origin - waypoints.getBombPos ()).length () < safeRadius || isOccupiedPoint (i)) { + for (int i = 0; i < graph.length (); ++i) { + if ((graph[i].origin - graph.getBombPos ()).length () < safeRadius || isOccupiedPoint (i)) { continue; } - int pathDistance = waypoints.getPathDist (m_currentWaypointIndex, i); + int pathDistance = graph.getPathDist (m_currentNodeIndex, i); if (minPathDistance > pathDistance) { minPathDistance = pathDistance; @@ -4312,7 +4277,7 @@ void Bot::escapeFromBomb_ (void) { } if (lastSelectedGoal < 0) { - lastSelectedGoal = waypoints.getFarest (pev->origin, safeRadius); + lastSelectedGoal = graph.getFarest (pev->origin, safeRadius); } // still no luck? @@ -4320,18 +4285,18 @@ 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, game.timebase () + 10.0f, true); + startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, game.timebase () + 10.0f, true); return; } m_prevGoalIndex = lastSelectedGoal; getTask ()->data = lastSelectedGoal; - searchPath (m_currentWaypointIndex, lastSelectedGoal, SEARCH_PATH_FASTEST); + findPath (m_currentNodeIndex, lastSelectedGoal, FindPath::Fast); } } -void Bot::shootBreakable_ (void) { - m_aimFlags |= AIM_OVERRIDE; +void Bot::shootBreakable_ () { + m_aimFlags |= AimFlags::Override; // Breakable destroyed? if (game.isNullEntity (lookupBreakable ())) { @@ -4352,7 +4317,7 @@ void Bot::shootBreakable_ (void) { m_moveSpeed = 0.0f; m_strafeSpeed = 0.0f; - if (m_currentWeapon == WEAPON_KNIFE) { + if (m_currentWeapon == Weapon::Knife) { selectBestWeapon (); } m_wantsToFire = true; @@ -4379,19 +4344,19 @@ void Bot::pickupItem_ () { float itemDistance = (dest - pev->origin).length (); switch (m_pickupType) { - case PICKUP_DROPPED_C4: - case PICKUP_NONE: + case Pickup::DroppedC4: + case Pickup::None: break; - case PICKUP_WEAPON: - m_aimFlags |= AIM_NAVPOINT; + case Pickup::Weapon: + m_aimFlags |= AimFlags::Nav; // near to weapon? if (itemDistance < 50.0f) { int index = 0; auto &info = conf.getWeapons (); - for (index = 0; index < 7; index++) { + for (index = 0; index < 7; ++index) { if (strcmp (info[index].model, STRING (m_pickupItem->v.model) + 9) == 0) { break; } @@ -4401,46 +4366,46 @@ void Bot::pickupItem_ () { // secondary weapon. i.e., pistol int wid = 0; - for (index = 0; index < 7; index++) { - if (pev->weapons & (1 << info[index].id)) { + for (index = 0; index < 7; ++index) { + if (pev->weapons & cr::bit (info[index].id)) { wid = index; } } if (wid > 0) { selectWeaponById (wid); - game.execBotCmd (ent (), "drop"); + game.botCommand (ent (), "drop"); if (hasShield ()) { - game.execBotCmd (ent (), "drop"); // discard both shield and pistol + game.botCommand (ent (), "drop"); // discard both shield and pistol } } - processBuyzoneEntering (BUYSTATE_PRIMARY_WEAPON); + processBuyzoneEntering (BuyState::PrimaryWeapon); } else { // primary weapon int wid = bestWeaponCarried (); - if (wid == WEAPON_SHIELD || wid > 6 || hasShield ()) { + if (wid == Weapon::Shield || wid > 6 || hasShield ()) { selectWeaponById (wid); - game.execBotCmd (ent (), "drop"); + game.botCommand (ent (), "drop"); } if (!wid) { m_itemIgnore = m_pickupItem; m_pickupItem = nullptr; - m_pickupType = PICKUP_NONE; + m_pickupType = Pickup::None; break; } - processBuyzoneEntering (BUYSTATE_PRIMARY_WEAPON); + processBuyzoneEntering (BuyState::PrimaryWeapon); } checkSilencer (); // check the silencer } break; - case PICKUP_SHIELD: - m_aimFlags |= AIM_NAVPOINT; + case Pickup::Shield: + m_aimFlags |= AimFlags::Nav; if (hasShield ()) { m_pickupItem = nullptr; @@ -4454,20 +4419,20 @@ void Bot::pickupItem_ () { if (wid > 6) { selectWeaponById (wid); - game.execBotCmd (ent (), "drop"); + game.botCommand (ent (), "drop"); } } break; - case PICKUP_PLANTED_C4: - m_aimFlags |= AIM_ENTITY; + case Pickup::PlantedC4: + m_aimFlags |= AimFlags::Entity; - if (m_team == TEAM_COUNTER && itemDistance < 80.0f) { - pushChatterMessage (CHATTER_DEFUSING_BOMB); + if (m_team == Team::CT && itemDistance < 80.0f) { + pushChatterMessage (Chatter::DefusingBomb); // notify team of defusing if (m_numFriendsLeft < 3) { - pushRadioMessage (RADIO_NEED_BACKUP); + pushRadioMessage (Radio::NeedBackup); } m_moveToGoal = false; m_checkTerrain = false; @@ -4475,12 +4440,12 @@ void Bot::pickupItem_ () { m_moveSpeed = 0.0f; m_strafeSpeed = 0.0f; - startTask (TASK_DEFUSEBOMB, TASKPRI_DEFUSEBOMB, INVALID_WAYPOINT_INDEX, 0.0f, false); + startTask (Task::DefuseBomb, TaskPri::DefuseBomb, kInvalidNodeIndex, 0.0f, false); } break; - case PICKUP_HOSTAGE: - m_aimFlags |= AIM_ENTITY; + case Pickup::Hostage: + m_aimFlags |= AimFlags::Entity; if (!util.isAlive (m_pickupItem)) { // don't pickup dead hostages @@ -4498,8 +4463,8 @@ void Bot::pickupItem_ () { // use game dll function to make sure the hostage is correctly 'used' MDLL_Use (m_pickupItem, ent ()); - if (rng.chance (80)) { - pushChatterMessage (CHATTER_USING_HOSTAGES); + if (rg.chance (80)) { + pushChatterMessage (Chatter::UsingHostages); } m_hostages.push (m_pickupItem); m_pickupItem = nullptr; @@ -4508,21 +4473,21 @@ void Bot::pickupItem_ () { } break; - case PICKUP_DEFUSEKIT: - m_aimFlags |= AIM_NAVPOINT; + case Pickup::DefusalKit: + m_aimFlags |= AimFlags::Nav; if (m_hasDefuser) { m_pickupItem = nullptr; - m_pickupType = PICKUP_NONE; + m_pickupType = Pickup::None; } break; - case PICKUP_BUTTON: - m_aimFlags |= AIM_ENTITY; + case Pickup::Button: + m_aimFlags |= AimFlags::Entity; if (game.isNullEntity (m_pickupItem) || m_buttonPushTime < game.timebase ()) { completeTask (); - m_pickupType = PICKUP_NONE; + m_pickupType = Pickup::None; break; } @@ -4542,7 +4507,7 @@ void Bot::pickupItem_ () { MDLL_Use (m_pickupItem, ent ()); m_pickupItem = nullptr; - m_pickupType = PICKUP_NONE; + m_pickupType = Pickup::None; m_buttonPushTime = game.timebase () + 3.0f; completeTask (); @@ -4552,127 +4517,127 @@ void Bot::pickupItem_ () { } } -void Bot::processTasks (void) { +void Bot::executeTasks () { // this is core function that handle task execution - switch (taskId ()) { + switch (getCurrentTaskId ()) { // normal task default: - case TASK_NORMAL: + case Task::Normal: normal_ (); break; // bot sprays messy logos all over the place... - case TASK_SPRAY: + case Task::Spraypaint: spraypaint_ (); break; // hunt down enemy - case TASK_HUNTENEMY: + case Task::Hunt: huntEnemy_ (); break; // bot seeks cover from enemy - case TASK_SEEKCOVER: + case Task::SeekCover: seekCover_ (); break; // plain attacking - case TASK_ATTACK: + case Task::Attack: attackEnemy_ (); break; // Bot is pausing - case TASK_PAUSE: + case Task::Pause: pause_ (); break; // blinded (flashbanged) behaviour - case TASK_BLINDED: + case Task::Blind: blind_ (); break; // camping behaviour - case TASK_CAMP: + case Task::Camp: camp_ (); break; // hiding behaviour - case TASK_HIDE: + case Task::Hide: hide_ (); break; // moves to a position specified in position has a higher priority than task_normal - case TASK_MOVETOPOSITION: + case Task::MoveToPosition: moveToPos_ (); break; // planting the bomb right now - case TASK_PLANTBOMB: + case Task::PlantBomb: plantBomb_ (); break; // bomb defusing behaviour - case TASK_DEFUSEBOMB: + case Task::DefuseBomb: bombDefuse_ (); break; // follow user behaviour - case TASK_FOLLOWUSER: + case Task::FollowUser: followUser_ (); break; // HE grenade throw behaviour - case TASK_THROWHEGRENADE: + case Task::ThrowExplosive: throwExplosive_ (); break; // flashbang throw behavior (basically the same code like for HE's) - case TASK_THROWFLASHBANG: + case Task::ThrowFlashbang: throwFlashbang_ (); break; // smoke grenade throw behavior // a bit different to the others because it mostly tries to throw the sg on the ground - case TASK_THROWSMOKE: + case Task::ThrowSmoke: throwSmoke_ (); break; // bot helps human player (or other bot) to get somewhere - case TASK_DOUBLEJUMP: + case Task::DoubleJump: doublejump_ (); break; // escape from bomb behaviour - case TASK_ESCAPEFROMBOMB: + case Task::EscapeFromBomb: escapeFromBomb_ (); break; // shooting breakables in the way action - case TASK_SHOOTBREAKABLE: + case Task::ShootBreakable: shootBreakable_ (); break; // picking up items and stuff behaviour - case TASK_PICKUPITEM: + case Task::PickupItem: pickupItem_ (); break; } } -void Bot::checkSpawnConditions (void) { +void Bot::checkSpawnConditions () { // this function is called instead of ai when buying finished, but freezetime is not yet left. // switch to knife if time to do this - if (m_checkKnifeSwitch && !m_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, game.timebase () + 1.0f, false); + if (m_checkKnifeSwitch && m_buyingFinished && m_spawnTime + rg.float_ (5.0f, 7.5f) < game.timebase ()) { + if (rg.int_ (1, 100) < 2 && yb_spraypaints.bool_ ()) { + startTask (Task::Spraypaint, TaskPri::Spraypaint, kInvalidNodeIndex, game.timebase () + 1.0f, false); } - 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 ()) { + if (m_difficulty >= 2 && rg.chance (m_personality == Personality::Rusher ? 99 : 50) && !m_isReloading && game.mapIs (MapFlags::HostageRescue | MapFlags::Demolition | MapFlags::Escape | MapFlags::Assassination)) { + if (yb_jasonmode.bool_ ()) { selectSecondary (); - game.execBotCmd (ent (), "drop"); + game.botCommand (ent (), "drop"); } else { selectWeaponByName ("weapon_knife"); @@ -4680,26 +4645,26 @@ void Bot::checkSpawnConditions (void) { } m_checkKnifeSwitch = false; - if (rng.chance (yb_user_follow_percent.integer ()) && game.isNullEntity (m_targetEntity) && !m_isLeader && !m_hasC4 && rng.chance (50)) { + if (rg.chance (yb_user_follow_percent.int_ ()) && game.isNullEntity (m_targetEntity) && !m_isLeader && !m_hasC4 && rg.chance (50)) { decideFollowUser (); } } // check if we already switched weapon mode - if (m_checkWeaponSwitch && m_buyingFinished && m_spawnTime + rng.getFloat (3.0f, 4.5f) < game.timebase ()) { + if (m_checkWeaponSwitch && m_buyingFinished && m_spawnTime + rg.float_ (3.0f, 4.5f) < game.timebase ()) { if (hasShield () && isShieldDrawn ()) { pev->button |= IN_ATTACK2; } else { switch (m_currentWeapon) { - case WEAPON_M4A1: - case WEAPON_USP: + case Weapon::M4A1: + case Weapon::USP: checkSilencer (); break; - case WEAPON_FAMAS: - case WEAPON_GLOCK: - if (rng.chance (50)) { + case Weapon::Famas: + case Weapon::Glock18: + if (rg.chance (50)) { pev->button |= IN_ATTACK2; } break; @@ -4714,7 +4679,7 @@ void Bot::checkSpawnConditions (void) { } } -void Bot::runAI (void) { +void Bot::runAI () { // 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) @@ -4756,33 +4721,33 @@ void Bot::runAI (void) { setConditions (); // some stuff required by by chatter engine - if (yb_communication_type.integer () == 2) { - if ((m_states & STATE_SEEING_ENEMY) && !game.isNullEntity (m_enemy)) { + if (yb_radio_mode.int_ () == 2) { + if ((m_states & Sense::SeeingEnemy) && !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); + if (!hasFriendNearby && rg.chance (45) && (m_enemy->v.weapons & cr::bit (Weapon::C4))) { + pushChatterMessage (Chatter::SpotTheBomber); } - else if (!hasFriendNearby && rng.chance (45) && m_team == TEAM_TERRORIST && util.isPlayerVIP (m_enemy)) { - pushChatterMessage (CHATTER_VIP_SPOTTED); + else if (!hasFriendNearby && rg.chance (45) && m_team == Team::Terrorist && util.isPlayerVIP (m_enemy)) { + pushChatterMessage (Chatter::VIPSpotted); } - 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 && rg.chance (50) && game.getTeam (m_enemy) != m_team && isGroupOfEnemies (m_enemy->v.origin, 2, 384.0f)) { + pushChatterMessage (Chatter::ScaredEmotion); } - 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)))) { - pushChatterMessage (CHATTER_SNIPER_WARNING); + else if (!hasFriendNearby && rg.chance (40) && ((m_enemy->v.weapons & cr::bit (Weapon::AWP)) || (m_enemy->v.weapons & cr::bit (Weapon::Scout)) || (m_enemy->v.weapons & cr::bit (Weapon::G3SG1)) || (m_enemy->v.weapons & cr::bit (Weapon::SG550)))) { + pushChatterMessage (Chatter::SniperWarning); } // if bot is trapped under shield yell for help ! - if (taskId () == TASK_CAMP && hasShield () && isShieldDrawn () && hasFriendNearby >= 2 && seesEnemy (m_enemy)) { - instantChatter (CHATTER_PINNED_DOWN); + if (getCurrentTaskId () == Task::Camp && hasShield () && isShieldDrawn () && hasFriendNearby >= 2 && seesEnemy (m_enemy)) { + pushChatterMessage (Chatter::PinnedDown); } } // if bomb planted warn teammates ! - 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); + if (bots.hasBombSay (BombPlantedSay::Chatter) && bots.isBombPlanted () && m_team == Team::CT) { + pushChatterMessage (Chatter::GottaFindC4); + bots.clearBombSay (BombPlantedSay::Chatter); } } Vector src, destination; @@ -4794,7 +4759,7 @@ void Bot::runAI (void) { avoidGrenades (); // avoid flyings grenades m_isUsingGrenade = false; - processTasks (); // execute current task + executeTasks (); // execute current task updateAimDir (); // choose aim direction updateLookAngles (); // and turn to chosen aim direction @@ -4813,9 +4778,9 @@ void Bot::runAI (void) { // calculate 2 direction vectors, 1 without the up/down component const Vector &dirOld = m_destOrigin - (pev->origin + pev->velocity * getFrameInterval ()); - const Vector &dirNormal = dirOld.normalize2D (); + const Vector &dirNormal = dirOld.normalize2d (); - m_moveAngles = dirOld.toAngles (); + m_moveAngles = dirOld.angles (); m_moveAngles.clampAngles (); m_moveAngles.x *= -1.0f; // invert for engine @@ -4824,13 +4789,13 @@ void Bot::runAI (void) { // allowed to move to a destination position? if (m_moveToGoal) { - getValidPoint (); + findValidNode (); // press duck button if we need to - if ((m_currentPath->flags & FLAG_CROUCH) && !(m_currentPath->flags & (FLAG_CAMP | FLAG_GOAL))) { + if ((m_path->flags & NodeFlag::Crouch) && !(m_path->flags & (NodeFlag::Camp | NodeFlag::Goal))) { pev->button |= IN_DUCK; } - m_timeWaypointMove = game.timebase (); + m_lastUsedNodesTime = game.timebase (); // special movement for swimming here if (isInWater ()) { @@ -4870,17 +4835,20 @@ void Bot::runAI (void) { // time to reach waypoint if (m_navTimeset + getReachTime () < game.timebase () && game.isNullEntity (m_enemy)) { - getValidPoint (); + findValidNode (); - // clear these pointers, bot mingh be stuck getting to them - if (!game.isNullEntity (m_pickupItem) && !m_hasProgressBar) { - m_itemIgnore = m_pickupItem; - } - - m_pickupItem = nullptr; m_breakableEntity = nullptr; - m_itemCheckTime = game.timebase () + 5.0f; - m_pickupType = PICKUP_NONE; + + if (getCurrentTaskId () == Task::PickupItem || (m_states & Sense::PickupItem)) { + // clear these pointers, bot mingh be stuck getting to them + if (!game.isNullEntity (m_pickupItem) && !m_hasProgressBar) { + m_itemIgnore = m_pickupItem; + } + + m_itemCheckTime = game.timebase () + 2.0f; + m_pickupType = Pickup::None; + m_pickupItem = nullptr; + } } if (m_duckTime >= game.timebase ()) { @@ -4919,7 +4887,7 @@ void Bot::runAI (void) { checkParachute (); // display some debugging thingy to host entity - if (!game.isDedicated () && yb_debug.integer () >= 1) { + if (!game.isDedicated () && yb_debug.int_ () >= 1) { showDebugOverlay (); } @@ -4928,14 +4896,14 @@ void Bot::runAI (void) { m_lastDamageType = -1; // reset damage } -void Bot::showDebugOverlay (void) { +void Bot::showDebugOverlay () { bool displayDebugOverlay = false; - if (game.getLocalEntity ()->v.iuser2 == index ()) { + if (game.getLocalEntity ()->v.iuser2 == entindex ()) { displayDebugOverlay = true; } - if (!displayDebugOverlay && yb_debug.integer () >= 2) { + if (!displayDebugOverlay && yb_debug.int_ () >= 2) { Bot *nearest = nullptr; if (util.findNearestPlayer (reinterpret_cast (&nearest), game.getLocalEntity (), 128.0f, false, true, true, true) && nearest == this) { @@ -4944,57 +4912,53 @@ void Bot::showDebugOverlay (void) { } if (displayDebugOverlay) { - static bool s_mapsFilled = false; - static float timeDebugUpdate = 0.0f; static int index, goal, taskID; - static HashMap > tasks; - static HashMap > personalities; - static HashMap > flags; + static Dictionary > tasks; + static Dictionary > personalities; + static Dictionary > flags; - if (!s_mapsFilled) { - tasks.put (TASK_NORMAL, "Normal"); - tasks.put (TASK_PAUSE, "Pause"); - tasks.put (TASK_MOVETOPOSITION, "Move"); - tasks.put (TASK_FOLLOWUSER, "Follow"); - tasks.put (TASK_PICKUPITEM, "Pickup"); - tasks.put (TASK_CAMP, "Camp"); - tasks.put (TASK_PLANTBOMB, "PlantBomb"); - tasks.put (TASK_DEFUSEBOMB, "DefuseBomb"); - tasks.put (TASK_ATTACK, "Attack"); - tasks.put (TASK_HUNTENEMY, "Hunt"); - tasks.put (TASK_SEEKCOVER, "SeekCover"); - tasks.put (TASK_THROWHEGRENADE, "ThrowHE"); - tasks.put (TASK_THROWFLASHBANG, "ThrowFL"); - tasks.put (TASK_THROWSMOKE, "ThrowSG"); - tasks.put (TASK_DOUBLEJUMP, "DoubleJump"); - tasks.put (TASK_ESCAPEFROMBOMB, "EscapeFromBomb"); - tasks.put (TASK_SHOOTBREAKABLE, "DestroyBreakable"); - tasks.put (TASK_HIDE, "Hide"); - tasks.put (TASK_BLINDED, "Blind"); - tasks.put (TASK_SPRAY, "Spray"); + if (tasks.empty ()) { + tasks.push (Task::Normal, "Normal"); + tasks.push (Task::Pause, "Pause"); + tasks.push (Task::MoveToPosition, "Move"); + tasks.push (Task::FollowUser, "Follow"); + tasks.push (Task::PickupItem, "Pickup"); + tasks.push (Task::Camp, "Camp"); + tasks.push (Task::PlantBomb, "PlantBomb"); + tasks.push (Task::DefuseBomb, "DefuseBomb"); + tasks.push (Task::Attack, "Attack"); + tasks.push (Task::Hunt, "Hunt"); + tasks.push (Task::SeekCover, "SeekCover"); + tasks.push (Task::ThrowExplosive, "ThrowHE"); + tasks.push (Task::ThrowFlashbang, "ThrowFL"); + tasks.push (Task::ThrowSmoke, "ThrowSG"); + tasks.push (Task::DoubleJump, "DoubleJump"); + tasks.push (Task::EscapeFromBomb, "EscapeFromBomb"); + tasks.push (Task::ShootBreakable, "DestroyBreakable"); + tasks.push (Task::Hide, "Hide"); + tasks.push (Task::Blind, "Blind"); + tasks.push (Task::Spraypaint, "Spray"); - personalities.put (PERSONALITY_RUSHER, "Rusher"); - personalities.put (PERSONALITY_NORMAL, "Normal"); - personalities.put (PERSONALITY_CAREFUL, "Careful"); + personalities.push (Personality::Rusher, "Rusher"); + personalities.push (Personality::Normal, "Normal"); + personalities.push (Personality::Careful, "Careful"); - flags.put (AIM_NAVPOINT, "Nav"); - flags.put (AIM_CAMP, "Camp"); - flags.put (AIM_PREDICT_PATH, "Predict"); - flags.put (AIM_LAST_ENEMY, "LastEnemy"); - flags.put (AIM_ENTITY, "Entity"); - flags.put (AIM_ENEMY, "Enemy"); - flags.put (AIM_GRENADE, "Grenade"); - flags.put (AIM_OVERRIDE, "Override"); - - s_mapsFilled = true; + flags.push (AimFlags::Nav, "Nav"); + flags.push (AimFlags::Camp, "Camp"); + flags.push (AimFlags::PredictPath, "Predict"); + flags.push (AimFlags::LastEnemy, "LastEnemy"); + flags.push (AimFlags::Entity, "Entity"); + flags.push (AimFlags::Enemy, "Enemy"); + flags.push (AimFlags::Grenade, "Grenade"); + flags.push (AimFlags::Override, "Override");; } if (!m_tasks.empty ()) { - if (taskID != taskId () || index != m_currentWaypointIndex || goal != getTask ()->data || timeDebugUpdate < game.timebase ()) { - taskID = taskId (); - index = m_currentWaypointIndex; + if (taskID != getCurrentTaskId () || index != m_currentNodeIndex || goal != getTask ()->data || timeDebugUpdate < game.timebase ()) { + taskID = getCurrentTaskId (); + index = m_currentNodeIndex; goal = getTask ()->data; String enemy = "(none)"; @@ -5003,7 +4967,7 @@ void Bot::showDebugOverlay (void) { enemy = STRING (m_enemy->v.netname); } else if (!game.isNullEntity (m_lastEnemy)) { - enemy.assign ("%s (L)", STRING (m_lastEnemy->v.netname)); + enemy.assignf ("%s (L)", STRING (m_lastEnemy->v.netname)); } String pickup = "(none)"; @@ -5012,35 +4976,35 @@ void Bot::showDebugOverlay (void) { } String aimFlags; - for (int i = 0; i < 8; i++) { - bool hasFlag = m_aimFlags & (1 << i); + for (int i = 0; i < 8; ++i) { + bool hasFlag = m_aimFlags & cr::bit (i); if (hasFlag) { - aimFlags.append (" %s", flags[1 << i].chars ()); + aimFlags.appendf (" %s", flags[cr::bit (i)].chars ()); } } String weapon = STRING (util.getWeaponAlias (true, nullptr, m_currentWeapon)); String debugData; - 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 ()); + debugData.assignf ("\n\n\n\n\n%s (H:%.1f/A:%.1f)- Task: %d=%s Desire:%.02f\nItem: %s Clip: %d Ammo: %d%s Money: %d AimFlags: %s\nSP=%.02f SSP=%.02f I=%d PG=%d G=%d T: %.02f MT: %d\nEnemy=%s Pickup=%s Type=%s\n", STRING (pev->netname), pev->health, pev->armorvalue, taskID, tasks[taskID].chars (), getTask ()->desire, weapon.chars (), getAmmoInClip (), getAmmo (), m_isReloading ? " (R)" : "", m_moneyAmount, aimFlags.trim ().chars (), m_moveSpeed, m_strafeSpeed, index, m_prevGoalIndex, goal, m_navTimeset - game.timebase (), pev->movetype, enemy.chars (), pickup.chars (), personalities[m_personality].chars ()); - MessageWriter (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, Vector::null (), game.getLocalEntity ()) + MessageWriter (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, nullvec, game.getLocalEntity ()) .writeByte (TE_TEXTMESSAGE) .writeByte (1) - .writeShort (MessageWriter::fs16 (-1, 1 << 13)) - .writeShort (MessageWriter::fs16 (0, 1 << 13)) + .writeShort (MessageWriter::fs16 (-1.0f, 13.0f)) + .writeShort (MessageWriter::fs16 (0.0f , 13.0f)) .writeByte (0) - .writeByte (m_team == TEAM_COUNTER ? 0 : 255) + .writeByte (m_team == Team::CT ? 0 : 255) .writeByte (100) - .writeByte (m_team != TEAM_COUNTER ? 0 : 255) + .writeByte (m_team != Team::CT ? 0 : 255) .writeByte (0) .writeByte (255) .writeByte (255) .writeByte (255) .writeByte (0) - .writeShort (MessageWriter::fu16 (0, 1 << 8)) - .writeShort (MessageWriter::fu16 (0, 1 << 8)) - .writeShort (MessageWriter::fu16 (1.0, 1 << 8)) + .writeShort (MessageWriter::fu16 (0.0f, 8.0f)) + .writeShort (MessageWriter::fu16 (0.0f, 8.0f)) + .writeShort (MessageWriter::fu16 (1.0f, 8.0f)) .writeString (debugData.chars ()); timeDebugUpdate = game.timebase () + 1.0f; @@ -5049,25 +5013,23 @@ void Bot::showDebugOverlay (void) { // green = destination origin // blue = ideal angles // red = view angles - - game.drawLine (game.getLocalEntity (), getEyesPos (), m_destOrigin, 10, 0, 0, 255, 0, 250, 5, 1, DRAW_ARROW); + game.drawLine (game.getLocalEntity (), getEyesPos (), m_destOrigin, 10, 0, Color (0, 255, 0), 250, 5, 1, DrawLine::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); + game.drawLine (game.getLocalEntity (), getEyesPos () - Vector (0.0f, 0.0f, 16.0f), getEyesPos () + game.vec.forward * 300.0f, 10, 0, Color (0, 0, 255), 250, 5, 1, DrawLine::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); + game.drawLine (game.getLocalEntity (), getEyesPos () - Vector (0.0f, 0.0f, 32.0f), getEyesPos () + game.vec.forward * 300.0f, 10, 0, Color (255, 0, 0), 250, 5, 1, DrawLine::Arrow); // now draw line from source to destination - - for (size_t i = 0; i < m_path.length () && i + 1 < m_path.length (); i++) { - 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); + for (size_t i = 0; i < m_pathWalk.length () && i + 1 < m_pathWalk.length (); ++i) { + game.drawLine (game.getLocalEntity (), graph[m_pathWalk[i]].origin, graph[m_pathWalk[i + 1]].origin, 15, 0, Color (255, 100, 55), 200, 5, 1, DrawLine::Arrow); } } } } -bool Bot::hasHostage (void) { +bool Bot::hasHostage () { for (auto hostage : m_hostages) { if (!game.isNullEntity (hostage)) { @@ -5082,10 +5044,10 @@ bool Bot::hasHostage (void) { return false; } -int Bot::getAmmo (void) { +int Bot::getAmmo () { const auto &prop = conf.getWeaponProp (m_currentWeapon); - if (prop.ammo1 == -1 || prop.ammo1 > MAX_WEAPONS - 1) { + if (prop.ammo1 == -1 || prop.ammo1 > kMaxWeapons - 1) { return 0; } return m_ammo[prop.ammo1]; @@ -5096,10 +5058,10 @@ void Bot::processDamage (edict_t *inflictor, int damage, int armor, int bits) { // other player. m_lastDamageType = bits; - collectGoalExperience (damage); + updatePracticeValue (damage); if (util.isPlayer (inflictor)) { - if (yb_tkpunish.boolean () && game.getTeam (inflictor) == m_team && !util.isFakeClient (inflictor)) { + if (yb_tkpunish.bool_ () && game.getTeam (inflictor) == m_team && !util.isFakeClient (inflictor)) { // alright, die you teamkiller!!! m_actualReactionTime = 0.0f; m_seeEnemyTime = game.timebase (); @@ -5109,9 +5071,9 @@ void Bot::processDamage (edict_t *inflictor, int damage, int armor, int bits) { m_lastEnemyOrigin = m_enemy->v.origin; m_enemyOrigin = m_enemy->v.origin; - pushChatMessage (CHAT_TEAMATTACK); + pushChatMessage (Chat::TeamAttack); processChatterMessage ("#Bot_TeamAttack"); - pushChatterMessage (CHATTER_FRIENDLY_FIRE); + pushChatterMessage (Chatter::FriendlyFire); } else { // attacked by an enemy @@ -5129,7 +5091,7 @@ void Bot::processDamage (edict_t *inflictor, int damage, int armor, int bits) { m_fearLevel += 1.0f; } } - clearTask (TASK_CAMP); + clearTask (Task::Camp); if (game.isNullEntity (m_enemy) && m_team != game.getTeam (inflictor)) { m_lastEnemy = inflictor; @@ -5139,17 +5101,17 @@ void Bot::processDamage (edict_t *inflictor, int damage, int armor, int bits) { m_seeEnemyTime = game.timebase (); } - if (!game.is (GAME_CSDM)) { - collectDataExperience (inflictor, armor + damage); + if (!game.is (GameFlags::CSDM)) { + updatePracticeDamage (inflictor, armor + damage); } } } // hurt by unusual damage like drowning or gas else { // leave the camping/hiding position - if (!waypoints.isReachable (this, waypoints.getNearest (m_destOrigin))) { + if (!graph.isReachable (this, graph.getNearest (m_destOrigin))) { clearSearchNodes (); - searchOptimalPoint (); + findBestNearestNode (); } } } @@ -5158,7 +5120,7 @@ void Bot::processBlind (int alpha) { // this function gets called by network message handler, when screenfade message get's send // it's used to make bot blind from the grenade. - m_maxViewDistance = rng.getFloat (10.0f, 20.0f); + m_maxViewDistance = rg.float_ (10.0f, 20.0f); m_blindTime = game.timebase () + static_cast (alpha - 200) / 16.0f; if (m_blindTime < game.timebase ()) { @@ -5177,7 +5139,7 @@ void Bot::processBlind (int alpha) { m_blindMoveSpeed = -pev->maxspeed; m_blindSidemoveSpeed = 0.0f; - if (rng.chance (50)) { + if (rg.chance (50)) { m_blindSidemoveSpeed = pev->maxspeed; } else { @@ -5187,7 +5149,7 @@ void Bot::processBlind (int alpha) { if (pev->health < 85.0f) { m_blindMoveSpeed = -pev->maxspeed; } - else if (m_personality == PERSONALITY_CAREFUL) { + else if (m_personality == Personality::Careful) { m_blindMoveSpeed = 0.0f; m_blindButton = IN_DUCK; } @@ -5196,23 +5158,22 @@ void Bot::processBlind (int alpha) { } } -void Bot::collectGoalExperience (int damage) { +void Bot::updatePracticeValue (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) { + if (graph.length () < 1 || graph.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 && 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); + if (pev->health - damage <= 0) { + graph.setDangerValue (m_team, m_chosenGoalIndex, m_prevGoalIndex, cr::clamp (graph.getDangerValue (m_team, m_chosenGoalIndex, m_prevGoalIndex) - static_cast (pev->health / 20), -kMaxPracticeGoalValue, kMaxPracticeGoalValue)); } } -void Bot::collectDataExperience (edict_t *attacker, int damage) { +void Bot::updatePracticeDamage (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 (!util.isPlayer (attacker)) { @@ -5229,77 +5190,67 @@ void Bot::collectDataExperience (edict_t *attacker, int damage) { // if these are bots also remember damage to rank destination of the bot m_goalValue -= static_cast (damage); - if (bots.getBot (attacker) != nullptr) { - bots.getBot (attacker)->m_goalValue += static_cast (damage); + if (bots[attacker] != nullptr) { + bots[attacker]->m_goalValue += static_cast (damage); } - auto experience = waypoints.getRawExperience (); if (damage < 20) { return; // do not collect damage less than 20 } - int attackerIndex = waypoints.getNearest (attacker->v.origin); - int victimIndex = m_currentWaypointIndex; + int attackerIndex = graph.getNearest (attacker->v.origin); + int victimIndex = m_currentNodeIndex; - if (victimIndex == INVALID_WAYPOINT_INDEX) { - victimIndex = getNearestPoint (); + if (victimIndex == kInvalidNodeIndex) { + victimIndex = findNearestNode (); } if (pev->health > 20.0f) { - auto damageData = (experience + (victimIndex * waypoints.length ()) + victimIndex); - - if (victimTeam == TEAM_TERRORIST || victimTeam == TEAM_COUNTER) { - damageData->damage[victimTeam] = cr::clamp (++damageData->damage[victimTeam], 0, MAX_DAMAGE_VALUE); + if (victimTeam == Team::Terrorist || victimTeam == Team::CT) { + graph.setDangerDamage (victimIndex, victimIndex, victimIndex, cr::clamp (graph.getDangerDamage (victimTeam, victimIndex, victimIndex), 0, kMaxPracticeDamageValue)); } } float updateDamage = util.isFakeClient (attacker) ? 10.0f : 7.0f; // store away the damage done - int damageValue = cr::clamp (waypoints.getDangerDamage (m_team, victimIndex, attackerIndex) + static_cast (damage / updateDamage), 0, MAX_DAMAGE_VALUE); + int damageValue = cr::clamp (graph.getDangerDamage (m_team, victimIndex, attackerIndex) + static_cast (damage / updateDamage), 0, kMaxPracticeDamageValue); - if (damageValue > waypoints.getHighestDamageForTeam (m_team)) { - waypoints.setHighestDamageForTeam (m_team, damageValue); + if (damageValue > graph.getHighestDamageForTeam (m_team)) { + graph.setHighestDamageForTeam (m_team, damageValue); } - (experience + (victimIndex * waypoints.length ()) + attackerIndex)->damage[m_team] = damageValue; + graph.setDangerDamage (m_team, victimIndex, attackerIndex, 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 ((m_team == Team::CT && strcmp (tempMessage, "#CTs_Win") == 0) || (m_team == Team::Terrorist && strcmp (tempMessage, "#Terrorists_Win") == 0)) { if (bots.getRoundMidTime () > game.timebase ()) { - pushChatterMessage (CHATTER_QUICK_WON_ROUND); + pushChatterMessage (Chatter::QuickWonRound); } else { - pushChatterMessage (CHATTER_WON_THE_ROUND); + pushChatterMessage (Chatter::WonTheRound); } } else if (strcmp (tempMessage, "#Bot_TeamAttack") == 0) { - pushChatterMessage (CHATTER_FRIENDLY_FIRE); + pushChatterMessage (Chatter::FriendlyFire); } else if (strcmp (tempMessage, "#Bot_NiceShotCommander") == 0) { - pushChatterMessage (CHATTER_NICESHOT_COMMANDER); + pushChatterMessage (Chatter::NiceShotCommander); } else if (strcmp (tempMessage, "#Bot_NiceShotPall") == 0) { - pushChatterMessage (CHATTER_NICESHOT_PALL); + pushChatterMessage (Chatter::NiceShotPall); } } void Bot::pushChatMessage (int type, bool isTeamSay) { - auto &chat = conf.getChat (); - - if (chat[type].empty () || !yb_chat.boolean ()) { - return; - } - auto pickedPhrase = chat[type].random ().chars (); - - if (util.isEmptyStr (pickedPhrase)) { + if (!conf.hasChatBank (type) || !yb_chat.bool_ ()) { return; } - prepareChatMessage (const_cast (pickedPhrase)); - pushMsgQueue (isTeamSay ? GAME_MSG_SAY_TEAM_MSG : GAME_MSG_SAY_CMD); + prepareChatMessage (conf.pickRandomFromChatBank (type)); + pushMsgQueue (isTeamSay ? BotMsg::SayTeam : BotMsg::Say); } void Bot::dropWeaponForUser (edict_t *user, bool discardC4) { @@ -5307,28 +5258,28 @@ void Bot::dropWeaponForUser (edict_t *user, bool discardC4) { // command, very useful, when i'm don't have money to buy anything... ) if (util.isAlive (user) && m_moneyAmount >= 2000 && hasPrimaryWeapon () && (user->v.origin - pev->origin).length () <= 450.0f) { - m_aimFlags |= AIM_ENTITY; + m_aimFlags |= AimFlags::Entity; m_lookAt = user->v.origin; if (discardC4) { selectWeaponByName ("weapon_c4"); - game.execBotCmd (ent (), "drop"); + game.botCommand (ent (), "drop"); } else { selectBestWeapon (); - game.execBotCmd (ent (), "drop"); + game.botCommand (ent (), "drop"); } m_pickupItem = nullptr; - m_pickupType = PICKUP_NONE; + m_pickupType = Pickup::None; m_itemCheckTime = game.timebase () + 5.0f; if (m_inBuyZone) { m_ignoreBuyDelay = true; m_buyingFinished = false; - m_buyState = BUYSTATE_PRIMARY_WEAPON; + m_buyState = BuyState::PrimaryWeapon; - pushMsgQueue (GAME_MSG_PURCHASE); + pushMsgQueue (BotMsg::Buy); m_nextBuyTime = game.timebase (); } } @@ -5340,17 +5291,17 @@ void Bot::startDoubleJump (edict_t *ent) { m_doubleJumpOrigin = ent->v.origin; m_doubleJumpEntity = ent; - startTask (TASK_DOUBLEJUMP, TASKPRI_DOUBLEJUMP, INVALID_WAYPOINT_INDEX, game.timebase (), true); - sayTeam (util.format ("Ok %s, i will help you!", STRING (ent->v.netname))); + startTask (Task::DoubleJump, TaskPri::DoubleJump, kInvalidNodeIndex, game.timebase (), true); + sayTeam (strings.format ("Ok %s, i will help you!", STRING (ent->v.netname))); } -void Bot::resetDoubleJump (void) { +void Bot::resetDoubleJump () { completeTask (); m_doubleJumpEntity = nullptr; m_duckForJump = 0.0f; - m_doubleJumpOrigin.nullify (); - m_travelStartIndex = INVALID_WAYPOINT_INDEX; + m_doubleJumpOrigin= nullvec; + m_travelStartIndex = kInvalidNodeIndex; m_jumpReady = false; } @@ -5358,31 +5309,31 @@ void Bot::sayDebug (const char *format, ...) { if (game.isDedicated ()) { return; } - int level = yb_debug.integer (); + int level = yb_debug.int_ (); if (level <= 2) { return; } va_list ap; - char buffer[MAX_PRINT_BUFFER]; + auto result = strings.chars (); va_start (ap, format); - vsnprintf (buffer, cr::bufsize (buffer), format, ap); + vsnprintf (result, StringBuffer::StaticBufferSize, format, ap); va_end (ap); String printBuf; - printBuf.assign ("%s: %s", STRING (pev->netname), buffer); + printBuf.assignf ("%s: %s", STRING (pev->netname), result); bool playMessage = false; - if (level == 3 && !game.isNullEntity (game.getLocalEntity ()) && game.getLocalEntity ()->v.iuser2 == index ()) { + if (level == 3 && !game.isNullEntity (game.getLocalEntity ()) && game.getLocalEntity ()->v.iuser2 == entindex ()) { playMessage = true; } else if (level != 3) { playMessage = true; } if (playMessage && level > 3) { - util.logEntry (false, LL_DEFAULT, printBuf.chars ()); + logger.message (printBuf.chars ()); } if (playMessage) { game.print (printBuf.chars ()); @@ -5395,16 +5346,16 @@ Vector Bot::calcToss (const Vector &start, const Vector &stop) { // returns null vector if toss is not feasible. TraceResult tr; - float gravity = sv_gravity.flt () * 0.55f; + float gravity = sv_gravity.float_ () * 0.55f; Vector end = stop - pev->velocity; end.z -= 15.0f; if (cr::abs (end.z - start.z) > 500.0f) { - return Vector::null (); + return nullvec; } Vector midPoint = start + (end - start) * 0.5f; - game.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), TraceIgnore::Monsters, head_hull, ent (), &tr); if (tr.flFraction < 1.0f) { midPoint = tr.vecEndPos; @@ -5412,13 +5363,13 @@ Vector Bot::calcToss (const Vector &start, const Vector &stop) { } if (midPoint.z < start.z || midPoint.z < end.z) { - return Vector::null (); + return nullvec; } float timeOne = cr::sqrtf ((midPoint.z - start.z) / (0.5f * gravity)); float timeTwo = cr::sqrtf ((midPoint.z - end.z) / (0.5f * gravity)); if (timeOne < 0.1f) { - return Vector::null (); + return nullvec; } Vector velocity = (end - start) / (timeOne + timeTwo); velocity.z = gravity * timeOne; @@ -5426,18 +5377,18 @@ Vector Bot::calcToss (const Vector &start, const Vector &stop) { Vector apex = start + velocity * timeOne; apex.z = midPoint.z; - game.testHull (start, apex, TRACE_IGNORE_NONE, head_hull, ent (), &tr); + game.testHull (start, apex, TraceIgnore::None, head_hull, ent (), &tr); if (tr.flFraction < 1.0f || tr.fAllSolid) { - return Vector::null (); + return nullvec; } - game.testHull (end, apex, TRACE_IGNORE_MONSTERS, head_hull, ent (), &tr); + game.testHull (end, apex, TraceIgnore::Monsters, head_hull, ent (), &tr); if (tr.flFraction != 1.0f) { float dot = -(tr.vecPlaneNormal | (apex - end).normalize ()); if (dot > 0.7f || tr.flFraction < 0.8f) { - return Vector::null (); + return nullvec; } } return velocity * 0.777f; @@ -5450,11 +5401,11 @@ Vector Bot::calcThrow (const Vector &start, const Vector &stop) { Vector velocity = stop - start; TraceResult tr; - float gravity = sv_gravity.flt () * 0.55f; + float gravity = sv_gravity.float_ () * 0.55f; float time = velocity.length () / 195.0f; if (time < 0.01f) { - return Vector::null (); + return nullvec; } else if (time > 2.0f) { time = 1.2f; @@ -5465,18 +5416,18 @@ 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); - game.testHull (start, apex, TRACE_IGNORE_NONE, head_hull, ent (), &tr); + game.testHull (start, apex, TraceIgnore::None, head_hull, ent (), &tr); if (tr.flFraction != 1.0f) { - return Vector::null (); + return nullvec; } - game.testHull (stop, apex, TRACE_IGNORE_MONSTERS, head_hull, ent (), &tr); + game.testHull (stop, apex, TraceIgnore::Monsters, head_hull, ent (), &tr); if (tr.flFraction != 1.0 || tr.fAllSolid) { float dot = -(tr.vecPlaneNormal | (apex - stop).normalize ()); if (dot > 0.7f || tr.flFraction < 0.8f) { - return Vector::null (); + return nullvec; } } return velocity * 0.7793f; @@ -5491,7 +5442,7 @@ edict_t *Bot::correctGrenadeVelocity (const char *model) { if (m_grenade.lengthSq () > 100.0f) { pent->v.velocity = m_grenade; } - m_grenadeCheckTime = game.timebase () + MAX_GRENADE_TIMER; + m_grenadeCheckTime = game.timebase () + kGrenadeCheckTime; selectBestWeapon (); completeTask (); @@ -5502,19 +5453,19 @@ edict_t *Bot::correctGrenadeVelocity (const char *model) { return pent; } -Vector Bot::isBombAudible (void) { +Vector Bot::isBombAudible () { // this function checks if bomb is can be heard by the bot, calculations done by manual testing. - if (!bots.isBombPlanted () || taskId () == TASK_ESCAPEFROMBOMB) { - return Vector::null (); // reliability check + if (!bots.isBombPlanted () || getCurrentTaskId () == Task::EscapeFromBomb) { + return nullvec; // reliability check } if (m_difficulty > 2) { - return waypoints.getBombPos (); + return graph.getBombPos (); } - const Vector &bombOrigin = waypoints.getBombPos (); + const Vector &bombOrigin = graph.getBombPos (); - float timeElapsed = ((game.timebase () - bots.getTimeBombPlanted ()) / mp_c4timer.flt ()) * 100.0f; + float timeElapsed = ((game.timebase () - bots.getTimeBombPlanted ()) / mp_c4timer.float_ ()) * 100.0f; float desiredRadius = 768.0f; // start the manual calculations @@ -5532,19 +5483,19 @@ Vector Bot::isBombAudible (void) { } // we hear bomb if length greater than radius - if (desiredRadius < (pev->origin - bombOrigin).length2D ()) { + if (desiredRadius < (pev->origin - bombOrigin).length2d ()) { return bombOrigin; } - return Vector::null (); + return nullvec; } -uint8 Bot::computeMsec (void) { +uint8 Bot::computeMsec () { // estimate msec to use for this command based on time passed from the previous command return static_cast ((game.timebase () - m_lastCommandTime) * 1000.0f); } -void Bot::runMovement (void) { +void Bot::runMovement () { // the purpose of this function is to compute, according to the specified computation // method, the msec value which will be passed as an argument of pfnRunPlayerMove. This // function is called every frame for every bot, since the RunPlayerMove is the function @@ -5578,28 +5529,28 @@ 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 == BURST_OFF) { + if (m_currentWeapon == Weapon::Glock18 && distance < 300.0f && m_weaponBurstMode == BurstMode::Off) { pev->button |= IN_ATTACK2; } - else if (m_currentWeapon == WEAPON_GLOCK && distance >= 300.0f && m_weaponBurstMode == BURST_ON) { + else if (m_currentWeapon == Weapon::Glock18 && distance >= 300.0f && m_weaponBurstMode == BurstMode::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 == BURST_OFF) { + if (m_currentWeapon == Weapon::Famas && distance > 400.0f && m_weaponBurstMode == BurstMode::Off) { pev->button |= IN_ATTACK2; } - else if (m_currentWeapon == WEAPON_FAMAS && distance <= 400.0f && m_weaponBurstMode == BURST_ON) { + else if (m_currentWeapon == Weapon::Famas && distance <= 400.0f && m_weaponBurstMode == BurstMode::On) { pev->button |= IN_ATTACK2; } } -void Bot::checkSilencer (void) { - if ((m_currentWeapon == WEAPON_USP || m_currentWeapon == WEAPON_M4A1) && !hasShield ()) { - int prob = (m_personality == PERSONALITY_RUSHER ? 35 : 65); +void Bot::checkSilencer () { + if ((m_currentWeapon == Weapon::USP || m_currentWeapon == Weapon::M4A1) && !hasShield ()) { + int prob = (m_personality == Personality::Rusher ? 35 : 65); // aggressive bots don't like the silencer - if (rng.chance (m_currentWeapon == WEAPON_USP ? prob / 2 : prob)) { + if (rg.chance (m_currentWeapon == Weapon::USP ? prob / 2 : prob)) { // is the silencer not attached... if (pev->weaponanim > 6) { pev->button |= IN_ATTACK2; // attach the silencer @@ -5615,11 +5566,11 @@ void Bot::checkSilencer (void) { } } -float Bot::getBombTimeleft (void) { +float Bot::getBombTimeleft () { if (!bots.isBombPlanted ()) { return 0.0f; } - float timeLeft = ((bots.getTimeBombPlanted () + mp_c4timer.flt ()) - game.timebase ()); + float timeLeft = ((bots.getTimeBombPlanted () + mp_c4timer.float_ ()) - game.timebase ()); if (timeLeft < 0.0f) { return 0.0f; @@ -5627,12 +5578,12 @@ float Bot::getBombTimeleft (void) { return timeLeft; } -bool Bot::isOutOfBombTimer (void) { - if (!game.mapIs (MAP_DE)) { +bool Bot::isOutOfBombTimer () { + if (!game.mapIs (MapFlags::Demolition)) { return false; } - if (m_currentWaypointIndex == INVALID_WAYPOINT_INDEX || (m_hasProgressBar || taskId () == TASK_ESCAPEFROMBOMB)) { + if (m_currentNodeIndex == kInvalidNodeIndex || (m_hasProgressBar || getCurrentTaskId () == Task::EscapeFromBomb)) { return false; // if CT bot already start defusing, or already escaping, return false } @@ -5643,27 +5594,25 @@ bool Bot::isOutOfBombTimer (void) { if (timeLeft > 13.0f) { return false; } - const Vector &bombOrigin = waypoints.getBombPos (); + const Vector &bombOrigin = graph.getBombPos (); // for terrorist, if timer is lower than 13 seconds, return true - if (timeLeft < 13.0f && m_team == TEAM_TERRORIST && (bombOrigin - pev->origin).lengthSq () < cr::square (964.0f)) { + if (timeLeft < 13.0f && m_team == Team::Terrorist && (bombOrigin - pev->origin).lengthSq () < cr::square (964.0f)) { return true; } bool hasTeammatesWithDefuserKit = false; // check if our teammates has defusal kit - for (int i = 0; i < game.maxClients (); i++) { - auto *bot = bots.getBot (i); - + for (const auto &bot : bots) { // search players with defuse kit - if (bot != nullptr && bot != this && bot->m_team == TEAM_COUNTER && bot->m_hasDefuser && (bombOrigin - bot->pev->origin).lengthSq () < cr::square (512.0f)) { + if (bot.get () != this && bot->m_team == Team::CT && bot->m_hasDefuser && (bombOrigin - bot->pev->origin).lengthSq () < cr::square (512.0f)) { hasTeammatesWithDefuserKit = true; break; } } // add reach time to left time - float reachTime = waypoints.calculateTravelTime (pev->maxspeed, m_currentPath->origin, bombOrigin); + float reachTime = graph.calculateTravelTime (pev->maxspeed, m_path->origin, bombOrigin); // for counter-terrorist check alos is we have time to reach position plus average defuse time if ((timeLeft < reachTime + 8.0f && !m_hasDefuser && !hasTeammatesWithDefuserKit) || (timeLeft < reachTime + 4.0f && m_hasDefuser)) { @@ -5676,15 +5625,15 @@ bool Bot::isOutOfBombTimer (void) { return false; // return false otherwise } -void Bot::updateHearing (void) { - int hearEnemyIndex = INVALID_WAYPOINT_INDEX; +void Bot::updateHearing () { + int hearEnemyIndex = kInvalidNodeIndex; float minDistance = 99999.0f; // loop through all enemy clients to check for hearable stuff - for (int i = 0; i < game.maxClients (); 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 < game.timebase ()) { + if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive) || client.ent == ent () || client.team == m_team || client.timeSoundLasting < game.timebase ()) { continue; } float distance = (client.sound - pev->origin).length (); @@ -5700,22 +5649,22 @@ void Bot::updateHearing (void) { } edict_t *player = nullptr; - if (hearEnemyIndex >= 0 && util.getClient (hearEnemyIndex).team != m_team && !game.is (GAME_CSDM_FFA)) { + if (hearEnemyIndex >= 0 && util.getClient (hearEnemyIndex).team != m_team && !game.is (GameFlags::FreeForAll)) { player = util.getClient (hearEnemyIndex).ent; } // did the bot hear someone ? if (player != nullptr && util.isPlayer (player)) { // change to best weapon if heard something - if (m_shootTime < game.timebase () - 5.0f && isOnFloor () && m_currentWeapon != WEAPON_C4 && m_currentWeapon != WEAPON_EXPLOSIVE && m_currentWeapon != WEAPON_SMOKE && m_currentWeapon != WEAPON_FLASHBANG && !yb_jasonmode.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.bool_ ()) { selectBestWeapon (); } m_heardSoundTime = game.timebase (); - m_states |= STATE_HEARING_ENEMY; + m_states |= Sense::HearingEnemy; - if (rng.chance (15) && game.isNullEntity (m_enemy) && game.isNullEntity (m_lastEnemy) && m_seeEnemyTime + 7.0f < game.timebase ()) { - pushChatterMessage (CHATTER_HEARD_ENEMY); + if (rg.chance (15) && game.isNullEntity (m_enemy) && game.isNullEntity (m_lastEnemy) && m_seeEnemyTime + 7.0f < game.timebase ()) { + pushChatterMessage (Chatter::HeardTheEnemy); } // didn't bot already have an enemy ? take this one... @@ -5728,7 +5677,7 @@ void Bot::updateHearing (void) { else { if (player == m_lastEnemy) { // bot sees enemy ? then bail out ! - if (m_states & STATE_SEEING_ENEMY) { + if (m_states & Sense::SeeingEnemy) { return; } m_lastEnemyOrigin = player->v.origin; @@ -5754,19 +5703,19 @@ void Bot::updateHearing (void) { m_lastEnemy = player; m_lastEnemyOrigin = m_enemyOrigin; - m_states |= STATE_SEEING_ENEMY; + m_states |= Sense::SeeingEnemy; 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 > game.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.bool_ () && 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_states |= (Sense::SeeingEnemy | Sense::SuspectEnemy); m_seeEnemyTime = game.timebase (); } } @@ -5790,13 +5739,13 @@ void Bot::processBuyzoneEntering (int buyState) { const int *econLimit = conf.getEconLimit (); // if bot is in buy zone, try to buy ammo for this weapon... - if (m_seeEnemyTime + 12.0f < game.timebase () && m_lastEquipTime + 15.0f < game.timebase () && m_inBuyZone && (bots.getRoundStartTime () + rng.getFloat (10.0f, 20.0f) + mp_buytime.flt () < game.timebase ()) && !bots.isBombPlanted () && m_moneyAmount > econLimit[ECO_PRIMARY_GT]) { + if (m_seeEnemyTime + 12.0f < game.timebase () && m_lastEquipTime + 15.0f < game.timebase () && m_inBuyZone && (bots.getRoundStartTime () + rg.float_ (10.0f, 20.0f) + mp_buytime.float_ () < game.timebase ()) && !bots.isBombPlanted () && m_moneyAmount > econLimit[EcoLimit::PrimaryGreater]) { m_ignoreBuyDelay = true; m_buyingFinished = false; m_buyState = buyState; // push buy message - pushMsgQueue (GAME_MSG_PURCHASE); + pushMsgQueue (BotMsg::Buy); m_nextBuyTime = game.timebase (); m_lastEquipTime = game.timebase (); @@ -5812,27 +5761,28 @@ bool Bot::isBombDefusing (const Vector &bombOrigin) { bool defusingInProgress = false; for (const auto &client : util.getClients ()) { - auto bot = bots.getBot (client.ent); + auto bot = bots[client.ent]; - if (bot == nullptr || bot == this) { + if (bot == nullptr || bot == this || !bot->m_notKilled) { continue; // skip invalid bots } - if (m_team != bot->m_team || bot->taskId () == TASK_ESCAPEFROMBOMB) { + if (m_team != bot->m_team || bot->getCurrentTaskId () == Task::EscapeFromBomb) { continue; // skip other mess } + float bombDistance = (client.ent->v.origin - bombOrigin).lengthSq (); - if ((bot->pev->origin - bombOrigin).length () < 140.0f && (bot->taskId () == TASK_DEFUSEBOMB || bot->m_hasProgressBar)) { + if (bombDistance < cr::square (140.0f) && (bot->getCurrentTaskId () == Task::DefuseBomb || bot->m_hasProgressBar)) { defusingInProgress = true; break; } // take in account peoples too - if (defusingInProgress || !(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.team != m_team || util.isFakeClient (client.ent)) { + if (defusingInProgress || !(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive) || client.team != m_team || util.isFakeClient (client.ent)) { continue; } - if ((client.ent->v.origin - bombOrigin).length () < 140.0f && ((client.ent->v.button | client.ent->v.oldbuttons) & IN_USE)) { + if (bombDistance < cr::square (140.0f) && ((client.ent->v.button | client.ent->v.oldbuttons) & IN_USE)) { defusingInProgress = true; break; } @@ -5840,8 +5790,8 @@ bool Bot::isBombDefusing (const Vector &bombOrigin) { return defusingInProgress; } -float Bot::getShiftSpeed (void) { - if (taskId () == TASK_SEEKCOVER || (pev->flags & FL_DUCKING) || (pev->button & IN_DUCK) || (m_oldButtons & IN_DUCK) || (m_currentTravelFlags & PATHFLAG_JUMP) || (m_currentPath != nullptr && m_currentPath->flags & FLAG_LADDER) || isOnLadder () || isInWater () || m_isStuck) { +float Bot::getShiftSpeed () { + if (getCurrentTaskId () == Task::SeekCover || (pev->flags & FL_DUCKING) || (pev->button & IN_DUCK) || (m_oldButtons & IN_DUCK) || (m_currentTravelFlags & PathFlag::Jump) || (m_path != nullptr && m_path->flags & NodeFlag::Ladder) || isOnLadder () || isInWater () || m_isStuck) { return pev->maxspeed; } return static_cast (pev->maxspeed * 0.4f); diff --git a/source/chatlib.cpp b/source/chatlib.cpp index 1a7e189..2d6c0e6 100644 --- a/source/chatlib.cpp +++ b/source/chatlib.cpp @@ -19,11 +19,11 @@ void BotUtils::stripTags (String &line) { for (const auto &tag : m_tags) { const size_t start = line.find (tag.first, 0); - if (start != String::INVALID_INDEX) { + if (start != String::kInvalidIndex) { const size_t end = line.find (tag.second, start); const size_t diff = end - start; - if (end != String::INVALID_INDEX && end > start && diff < 32 && diff > 4) { + if (end != String::kInvalidIndex && end > start && diff < 32 && diff > 4) { line.erase (start, diff + tag.second.length ()); break; } @@ -37,7 +37,7 @@ void BotUtils::humanizePlayerName (String &playerName) { } // drop tag marks, 80 percent of time - if (rng.chance (80)) { + if (rg.chance (80)) { stripTags (playerName); } else { @@ -45,14 +45,14 @@ void BotUtils::humanizePlayerName (String &playerName) { } // sometimes switch name to lower characters, only valid for the english languge - if (rng.chance (15) && strcmp (yb_language.str (), "en") == 0) { + if (rg.chance (8) && strcmp (yb_language.str (), "en") == 0) { playerName.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) { + if (rg.chance (8) && strcmp (yb_language.str (), "en") == 0) { line.lowercase (); } auto length = line.length (); @@ -61,13 +61,13 @@ void BotUtils::addChatErrors (String &line) { size_t percentile = line.length () / 2; // "length / 2" percent of time drop a character - if (rng.chance (percentile)) { - line.erase (rng.getInt (length / 8, length - length / 8)); + if (rg.chance (percentile)) { + line.erase (rg.int_ (length / 8, length - length / 8), 1); } // "length" / 4 precent of time swap character - if (rng.chance (percentile / 2)) { - size_t pos = rng.getInt (length / 8, 3 * length / 8); // choose random position in string + if (rg.chance (percentile / 2)) { + size_t pos = rg.int_ (length / 8, 3 * length / 8); // choose random position in string cr::swap (line[pos], line[pos + 1]); } } @@ -76,7 +76,7 @@ void BotUtils::addChatErrors (String &line) { bool BotUtils::checkKeywords (const String &line, String &reply) { // this function checks is string contain keyword, and generates reply to it - if (!yb_chat.boolean () || line.empty ()) { + if (!yb_chat.bool_ () || line.empty ()) { return false; } @@ -84,7 +84,7 @@ bool BotUtils::checkKeywords (const String &line, String &reply) { for (const auto &keyword : factory.keywords) { // check is keyword has occurred in message - if (line.find (keyword, 0) != String::INVALID_INDEX) { + if (line.find (keyword) != String::kInvalidIndex) { StringArray &usedReplies = factory.usedReplies; if (usedReplies.length () >= factory.replies.length () / 4) { @@ -113,11 +113,9 @@ bool BotUtils::checkKeywords (const String &line, String &reply) { } } } - auto &chat = conf.getChat (); - // didn't find a keyword? 70% of the time use some universal reply - if (rng.chance (70) && !chat[CHAT_NOKW].empty ()) { - reply.assign (chat[CHAT_NOKW].random ()); + if (rg.chance (70) && conf.hasChatBank (Chat::NoKeyword)) { + reply.assign (conf.pickRandomFromChatBank (Chat::NoKeyword)); return true; } return false; @@ -126,47 +124,49 @@ bool BotUtils::checkKeywords (const String &line, String &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 ()) { + if (!yb_chat.bool_ () || message.empty ()) { return; } - m_chatBuffer = message; + m_chatBuffer.assign (message.chars ()); // must be called before return or on the end - auto finishPreparation = [&] (void) { + auto finishPreparation = [&] () { if (!m_chatBuffer.empty ()) { util.addChatErrors (m_chatBuffer); } }; // need to check if we're have special symbols - size_t pos = message.find ('%', 0); + size_t pos = message.find ('%'); // nothing found, bail out - if (pos == String::INVALID_INDEX || pos >= message.length ()) { + if (pos == String::kInvalidIndex || 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")); + auto humanizedName = [] (int index) -> String { + auto ent = game.playerOfIndex (index); + + if (!util.isPlayer (ent)) { + return "unknown"; } - String playerName = STRING (client.ent->v.netname); + String playerName = STRING (ent->v.netname); util.humanizePlayerName (playerName); - return cr::move (playerName); + return playerName; }; // find highfrag player - auto getHighfragPlayer = [&] (void) -> String { + auto getHighfragPlayer = [&] () -> String { int highestFrags = -1; int index = 0; - for (int i = 0; i < game.maxClients (); i++) { + for (int i = 0; i < game.maxClients (); ++i) { const Client &client = util.getClient (i); - if (!(client.flags & CF_USED) || client.ent == ent ()) { + if (!(client.flags & ClientFlags::Used) || client.ent == ent ()) { continue; } int frags = static_cast (client.ent->v.frags); @@ -176,147 +176,116 @@ void Bot::prepareChatMessage (const String &message) { index = i; } } - return humanizedName (util.getClient (index)); + return humanizedName (index); }; // get roundtime - auto getRoundTime = [] (void) -> String { + auto getRoundTime = [] () -> String { auto roundTimeSecs = static_cast (bots.getRoundEndTime () - game.timebase ()); String roundTime; - roundTime.assign ("%02d:%02d", cr::clamp (roundTimeSecs / 60, 0, 59), cr::clamp (cr::abs (roundTimeSecs % 60), 0, 59)); + roundTime.assignf ("%02d:%02d", cr::clamp (roundTimeSecs / 60, 0, 59), cr::clamp (cr::abs (roundTimeSecs % 60), 0, 59)); - return cr::move (roundTime); + return 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")); + auto getMyVictim = [&] () -> String {; + return humanizedName (game.indexOfPlayer (m_lastVictim)); }; // get the game name alias - auto getGameName = [] (void) -> String { + auto getGameName = [] () -> String { String gameName; - if (game.is (GAME_CZERO)) { - if (rng.chance (30)) { + if (game.is (GameFlags::ConditionZero)) { + if (rg.chance (30)) { gameName = "CZ"; } else { gameName = "Condition Zero"; } } - else if (game.is (GAME_CSTRIKE16) || game.is (GAME_LEGACY)) { - if (rng.chance (30)) { + else if (game.is (GameFlags::Modern) || game.is (GameFlags::Legacy)) { + if (rg.chance (30)) { gameName = "CS"; } else { gameName = "Counter-Strike"; } } - return cr::move (gameName); + return 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 ()) { + for (const auto &client : util.getClients ()) { + if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive) || client.ent == ent ()) { continue; } - if ((needsEnemy && m_team == client.team) || (!needsEnemy && m_team != client.team)) { - continue; + if (needsEnemy && m_team != client.team) { + return humanizedName (game.indexOfPlayer (client.ent)); + } + else if (!needsEnemy && m_team == client.team) { + return humanizedName (game.indexOfPlayer (client.ent)); } - break; } + return "UnknowPA"; + }; + size_t replaceCounter = 0; - 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)); + while (replaceCounter < 6 && (pos = m_chatBuffer.find ('%')) != String::kInvalidIndex) { + // 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 (m_sayTextBuffer.entityIndex)); } else { - return humanizedName (util.getClient (index)); + m_chatBuffer.replace ("%s", getHighfragPlayer ()); } - } - else { - for (index = 0; index < game.maxClients (); index++) { - const Client &client = util.getClient (index); + break; - if (!(client.flags & CF_USED) || client.team != m_team || client.ent == ent ()) { - continue; - } + // last bot victim + case 'v': + m_chatBuffer.replace ("%v", getMyVictim ()); + break; - if ((needsEnemy && m_team != client.team) || (!needsEnemy && m_team == client.team)) { - continue; - } - break; - } + // game name + case 'd': + m_chatBuffer.replace ("%d", getGameName ()); + break; - if (index < game.maxClients ()) { - return humanizedName (util.getClient (index)); - } - } - return cr::move (String ("unknown")); - }; + // teammate alive + case 't': + m_chatBuffer.replace ("%t", getPlayerAlive (false)); + break; - // 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; - - }; + // enemy alive + case 'e': + m_chatBuffer.replace ("%e", getPlayerAlive (true)); + break; + }; + replaceCounter++; + } finishPreparation (); } @@ -327,18 +296,17 @@ bool Bot::checkChatKeywords (String &reply) { return util.checkKeywords (message.uppercase (), reply); } -bool Bot::isReplyingToChat (void) { +bool Bot::isReplyingToChat () { // this function sends reply to a player if (m_sayTextBuffer.entityIndex != -1 && !m_sayTextBuffer.sayText.empty ()) { - // check is time to chat is good - if (m_sayTextBuffer.timeNextChat < game.timebase ()) { + if (m_sayTextBuffer.timeNextChat < game.timebase () + rg.float_ (m_sayTextBuffer.chatDelay / 2, m_sayTextBuffer.chatDelay)) { String replyText; - if (rng.chance (m_sayTextBuffer.chatProbability + rng.getInt (25, 45)) && checkChatKeywords (replyText)) { + if (rg.chance (m_sayTextBuffer.chatProbability + rg.int_ (20, 50)) && checkChatKeywords (replyText)) { prepareChatMessage (replyText); - pushMsgQueue (GAME_MSG_SAY_CMD); + pushMsgQueue (BotMsg::Say); m_sayTextBuffer.entityIndex = -1; m_sayTextBuffer.timeNextChat = game.timebase () + m_sayTextBuffer.chatDelay; @@ -353,19 +321,17 @@ bool Bot::isReplyingToChat (void) { return false; } -void Bot::checkForChat (void) { +void Bot::checkForChat () { // say a text every now and then - if (rng.chance (35) || m_notKilled || !yb_chat.boolean ()) { + if (rg.chance (30) || m_notKilled || !yb_chat.bool_ ()) { return; } // bot chatting turned on? - if (m_lastChatTime + 10.0 < game.timebase () && bots.getLastChatTimestamp () + 5.0f < game.timebase () && !isReplyingToChat ()) { - auto &chat = conf.getChat (); - - if (!chat[CHAT_DEAD].empty ()) { - const String &phrase = chat[CHAT_DEAD].random (); + if (m_lastChatTime + rg.float_ (6.0f, 10.0f) < game.timebase () && bots.getLastChatTimestamp () + rg.float_ (2.5f, 5.0f) < game.timebase () && !isReplyingToChat ()) { + if (conf.hasChatBank (Chat::Dead)) { + const auto &phrase = conf.pickRandomFromChatBank (Chat::Dead); bool sayBufferExists = false; // search for last messages, sayed @@ -375,9 +341,10 @@ void Bot::checkForChat (void) { break; } } + if (!sayBufferExists) { prepareChatMessage (phrase); - pushMsgQueue (GAME_MSG_SAY_CMD); + pushMsgQueue (BotMsg::Say); m_lastChatTime = game.timebase (); bots.setLastChatTimestamp (game.timebase ()); @@ -388,7 +355,7 @@ void Bot::checkForChat (void) { } // clear the used line buffer every now and then - if (static_cast (m_sayTextBuffer.lastUsedSentences.length ()) > rng.getInt (4, 6)) { + if (static_cast (m_sayTextBuffer.lastUsedSentences.length ()) > rg.int_ (4, 6)) { m_sayTextBuffer.lastUsedSentences.clear (); } } @@ -397,17 +364,17 @@ void Bot::checkForChat (void) { void Bot::say (const char *text) { // this function prints saytext message to all players - if (util.isEmptyStr (text) || !yb_chat.boolean ()) { + if (util.isEmptyStr (text) || !yb_chat.bool_ ()) { return; } - game.execBotCmd (ent (), "say \"%s\"", text); + game.botCommand (ent (), "say \"%s\"", text); } void Bot::sayTeam (const char *text) { // this function prints saytext message only for teammates - if (util.isEmptyStr (text) || !yb_chat.boolean ()) { + if (util.isEmptyStr (text) || !yb_chat.bool_ ()) { return; } - game.execBotCmd (ent (), "say_team \"%s\"", text); + game.botCommand (ent (), "say_team \"%s\"", text); } diff --git a/source/combat.cpp b/source/combat.cpp index f50e465..ee0a04c 100644 --- a/source/combat.cpp +++ b/source/combat.cpp @@ -13,13 +13,13 @@ ConVar yb_shoots_thru_walls ("yb_shoots_thru_walls", "2"); ConVar yb_ignore_enemies ("yb_ignore_enemies", "0"); ConVar yb_check_enemy_rendering ("yb_check_enemy_rendering", "0"); -ConVar mp_friendlyfire ("mp_friendlyfire", nullptr, VT_NOREGISTER); +ConVar mp_friendlyfire ("mp_friendlyfire", nullptr, Var::NoRegister); int Bot::numFriendsNear (const Vector &origin, float radius) { int count = 0; for (const auto &client : util.getClients ()) { - if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.team != m_team || client.ent == ent ()) { + if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive) || client.team != m_team || client.ent == ent ()) { continue; } @@ -34,7 +34,7 @@ int Bot::numEnemiesNear (const Vector &origin, float radius) { int count = 0; for (const auto &client : util.getClients ()) { - if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.team == m_team) { + if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive) || client.team == m_team) { continue; } @@ -46,12 +46,12 @@ int Bot::numEnemiesNear (const Vector &origin, float radius) { } bool Bot::isEnemyHidden (edict_t *enemy) { - if (!yb_check_enemy_rendering.boolean () || game.isNullEntity (enemy)) { + if (!yb_check_enemy_rendering.bool_ () || game.isNullEntity (enemy)) { return false; } entvars_t &v = enemy->v; - bool enemyHasGun = (v.weapons & WEAPON_PRIMARY) || (v.weapons & WEAPON_SECONDARY); + bool enemyHasGun = (v.weapons & kPrimaryWeaponMask) || (v.weapons & kSecondaryWeaponMask); bool enemyGunfire = (v.button & IN_ATTACK) || (v.oldbuttons & IN_ATTACK); if ((v.renderfx == kRenderFxExplode || (v.effects & EF_NODRAW)) && (!enemyGunfire || !enemyHasGun)) { @@ -92,7 +92,7 @@ bool Bot::checkBodyParts (edict_t *target, Vector *origin, uint8 *bodyPart) { if (isEnemyHidden (target)) { *bodyPart = 0; - origin->nullify (); + origin->clear (); return false; } @@ -103,19 +103,19 @@ bool Bot::checkBodyParts (edict_t *target, Vector *origin, uint8 *bodyPart) { *bodyPart = 0; - game.testLine (eyes, spot, TRACE_IGNORE_EVERYTHING, ent (), &result); + game.testLine (eyes, spot, TraceIgnore::Everything, ent (), &result); if (result.flFraction >= 1.0f) { - *bodyPart |= VISIBLE_BODY; + *bodyPart |= Visibility::Body; *origin = result.vecEndPos; } // check top of head spot.z += 25.0f; - game.testLine (eyes, spot, TRACE_IGNORE_EVERYTHING, ent (), &result); + game.testLine (eyes, spot, TraceIgnore::Everything, ent (), &result); if (result.flFraction >= 1.0f) { - *bodyPart |= VISIBLE_HEAD; + *bodyPart |= Visibility::Head; *origin = result.vecEndPos; } @@ -132,35 +132,35 @@ bool Bot::checkBodyParts (edict_t *target, Vector *origin, uint8 *bodyPart) { else { spot.z = target->v.origin.z - standFeet; } - game.testLine (eyes, spot, TRACE_IGNORE_EVERYTHING, ent (), &result); + game.testLine (eyes, spot, TraceIgnore::Everything, ent (), &result); if (result.flFraction >= 1.0f) { - *bodyPart |= VISIBLE_OTHER; + *bodyPart |= Visibility::Other; *origin = result.vecEndPos; return true; } const float edgeOffset = 13.0f; - Vector dir = (target->v.origin - pev->origin).normalize2D (); + Vector dir = (target->v.origin - pev->origin).normalize2d (); Vector perp (-dir.y, dir.x, 0.0f); spot = target->v.origin + Vector (perp.x * edgeOffset, perp.y * edgeOffset, 0); - game.testLine (eyes, spot, TRACE_IGNORE_EVERYTHING, ent (), &result); + game.testLine (eyes, spot, TraceIgnore::Everything, ent (), &result); if (result.flFraction >= 1.0f) { - *bodyPart |= VISIBLE_OTHER; + *bodyPart |= Visibility::Other; *origin = result.vecEndPos; return true; } spot = target->v.origin - Vector (perp.x * edgeOffset, perp.y * edgeOffset, 0); - game.testLine (eyes, spot, TRACE_IGNORE_EVERYTHING, ent (), &result); + game.testLine (eyes, spot, TraceIgnore::Everything, ent (), &result); if (result.flFraction >= 1.0f) { - *bodyPart |= VISIBLE_OTHER; + *bodyPart |= Visibility::Other; *origin = result.vecEndPos; return true; @@ -187,11 +187,11 @@ bool Bot::seesEnemy (edict_t *player, bool ignoreFOV) { return false; } -bool Bot::lookupEnemies (void) { +bool Bot::lookupEnemies () { // this function tries to find the best suitable enemy for the bot // do not search for enemies while we're blinded, or shooting disabled by user - if (m_enemyIgnoreTimer > game.timebase () || m_blindTime > game.timebase () || yb_ignore_enemies.boolean ()) { + if (m_enemyIgnoreTimer > game.timebase () || m_blindTime > game.timebase () || yb_ignore_enemies.bool_ ()) { return false; } edict_t *player, *newEnemy = nullptr; @@ -200,15 +200,15 @@ bool Bot::lookupEnemies (void) { extern ConVar yb_whose_your_daddy; // clear suspected flag - if (!game.isNullEntity (m_enemy) && (m_states & STATE_SEEING_ENEMY)) { - m_states &= ~STATE_SUSPECT_ENEMY; + if (!game.isNullEntity (m_enemy) && (m_states & Sense::SeeingEnemy)) { + m_states &= ~Sense::SuspectEnemy; } else if (game.isNullEntity (m_enemy) && m_seeEnemyTime + 1.0f > game.timebase () && util.isAlive (m_lastEnemy)) { - m_states |= STATE_SUSPECT_ENEMY; - m_aimFlags |= AIM_LAST_ENEMY; + m_states |= Sense::SuspectEnemy; + m_aimFlags |= AimFlags::LastEnemy; } m_visibility = 0; - m_enemyOrigin.nullify (); + m_enemyOrigin= nullvec; if (!game.isNullEntity (m_enemy)) { player = m_enemy; @@ -227,7 +227,7 @@ bool Bot::lookupEnemies (void) { // search the world for players... for (const auto &client : util.getClients ()) { - if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.team == m_team || client.ent == ent ()) { + if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive) || client.team == m_team || client.ent == ent ()) { continue; } player = client.ent; @@ -249,7 +249,7 @@ bool Bot::lookupEnemies (void) { newEnemy = player; // aim VIP first on AS maps... - if (util.isPlayerVIP (newEnemy)) { + if (game.is (MapFlags::Assassination) && util.isPlayerVIP (newEnemy)) { break; } } @@ -265,8 +265,8 @@ bool Bot::lookupEnemies (void) { if (util.isPlayer (newEnemy)) { bots.setCanPause (true); - m_aimFlags |= AIM_ENEMY; - m_states |= STATE_SEEING_ENEMY; + m_aimFlags |= AimFlags::Enemy; + m_states |= Sense::SeeingEnemy; // 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) { @@ -281,11 +281,11 @@ bool Bot::lookupEnemies (void) { } else { if (m_seeEnemyTime + 3.0f < game.timebase () && (m_hasC4 || hasHostage () || !game.isNullEntity (m_targetEntity))) { - pushRadioMessage (RADIO_ENEMY_SPOTTED); + pushRadioMessage (Radio::EnemySpotted); } m_targetEntity = nullptr; // stop following when we see an enemy... - if (yb_whose_your_daddy.boolean ()) { + if (yb_whose_your_daddy.bool_ ()) { m_enemySurpriseTime = m_actualReactionTime * 0.5f; } else { @@ -312,18 +312,17 @@ bool Bot::lookupEnemies (void) { } // 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 (const auto &client : util.getClients ()) { - if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.team != m_team || client.ent == ent ()) { + for (const auto &other : bots) { + if (!other->m_notKilled || other->m_team != m_team || other.get () == this) { continue; } - Bot *other = bots.getBot (client.ent); - 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)) { + if (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 = game.timebase (); - other->m_states |= (STATE_SUSPECT_ENEMY | STATE_HEARING_ENEMY); - other->m_aimFlags |= AIM_LAST_ENEMY; + other->m_states |= (Sense::SuspectEnemy | Sense::HearingEnemy); + other->m_aimFlags |= AimFlags::LastEnemy; } } return true; @@ -341,7 +340,7 @@ bool Bot::lookupEnemies (void) { if (!usesSniper ()) { m_shootAtDeadTime = game.timebase () + 0.4f; m_actualReactionTime = 0.0f; - m_states |= STATE_SUSPECT_ENEMY; + m_states |= Sense::SuspectEnemy; return true; } @@ -349,7 +348,7 @@ bool Bot::lookupEnemies (void) { } else if (m_shootAtDeadTime > game.timebase ()) { m_actualReactionTime = 0.0f; - m_states |= STATE_SUSPECT_ENEMY; + m_states |= Sense::SuspectEnemy; return true; } @@ -357,11 +356,11 @@ 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)) { + if (yb_shoots_thru_walls.bool_ () && m_difficulty >= 2 && isPenetrableObstacle (newEnemy->v.origin)) { m_seeEnemyTime = game.timebase (); - m_states |= STATE_SUSPECT_ENEMY; - m_aimFlags |= AIM_LAST_ENEMY; + m_states |= Sense::SuspectEnemy; + m_aimFlags |= AimFlags::LastEnemy; m_enemy = newEnemy; m_lastEnemy = newEnemy; @@ -372,9 +371,9 @@ bool Bot::lookupEnemies (void) { } // check if bots should reload... - 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_aimFlags <= AimFlags::PredictPath && m_seeEnemyTime + 3.0f < game.timebase () && !(m_states & (Sense::SeeingEnemy | Sense::HearingEnemy)) && game.isNullEntity (m_lastEnemy) && game.isNullEntity (m_enemy) && getCurrentTaskId () != Task::ShootBreakable && getCurrentTaskId () != Task::PlantBomb && getCurrentTaskId () != Task::DefuseBomb) || bots.isRoundOver ()) { if (!m_reloadState) { - m_reloadState = RELOAD_PRIMARY; + m_reloadState = Reload::Primary; } } @@ -392,20 +391,20 @@ bool Bot::lookupEnemies (void) { Vector Bot::getBodyOffsetError (float distance) { if (game.isNullEntity (m_enemy)) { - return Vector::null (); + return nullvec; } 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 = game.timebase () + rng.getFloat (0.5f, 1.0f); + m_aimLastError = Vector (rg.float_ (mins.x * error, maxs.x * error), rg.float_ (mins.y * error, maxs.y * error), rg.float_ (mins.z * error, maxs.z * error)); + m_aimErrorTime = game.timebase () + rg.float_ (0.5f, 1.0f); } return m_aimLastError; } -const Vector &Bot::getEnemyBodyOffset (void) { +const Vector &Bot::getEnemyBodyOffset () { // the purpose of this function, is to make bot aiming not so ideal. it's mutate m_enemyOrigin enemy vector // returned from visibility check function. @@ -420,49 +419,49 @@ const Vector &Bot::getEnemyBodyOffset (void) { float distance = (m_enemy->v.origin - pev->origin).length (); // do not aim at head, at long distance (only if not using sniper weapon) - if ((m_visibility & VISIBLE_BODY) && !usesSniper () && distance > (m_difficulty > 2 ? 2000.0f : 1000.0f)) { - m_visibility &= ~VISIBLE_HEAD; + if ((m_visibility & Visibility::Body) && !usesSniper () && distance > (m_difficulty > 2 ? 2000.0f : 1000.0f)) { + m_visibility &= ~Visibility::Head; } // do not aim at head while close enough to enemy and having sniper else if (distance < 800.0f && usesSniper ()) { - m_visibility &= ~VISIBLE_HEAD; + m_visibility &= ~Visibility::Head; } // do not aim at head while enemy is soooo close enough to enemy when recoil aims at head automatically - else if (distance < MAX_SPRAY_DISTANCE) { - m_visibility &= ~VISIBLE_HEAD; + else if (distance < kSprayDistance) { + m_visibility &= ~Visibility::Head; } Vector aimPos = m_enemy->v.origin; - if (m_difficulty > 2 && !(m_visibility & VISIBLE_OTHER)) { + if (m_difficulty > 2 && !(m_visibility & Visibility::Other)) { aimPos = (m_enemy->v.velocity - pev->velocity) * getFrameInterval () + aimPos; } // if we only suspect an enemy behind a wall take the worst skill - if (!m_visibility && (m_states & STATE_SUSPECT_ENEMY)) { + if (!m_visibility && (m_states & Sense::SuspectEnemy)) { aimPos += getBodyOffsetError (distance); } else { // now take in account different parts of enemy body - if (m_visibility & (VISIBLE_HEAD | VISIBLE_BODY)) { + if (m_visibility & (Visibility::Head | Visibility::Body)) { int headshotFreq[5] = { 20, 40, 60, 80, 100 }; // now check is our skill match to aim at head, else aim at enemy body - if (rng.chance (headshotFreq[m_difficulty]) || usesPistol ()) { + if (rg.chance (headshotFreq[m_difficulty]) || usesPistol ()) { aimPos.z = headOffset (m_enemy) + getEnemyBodyOffsetCorrection (distance); } else { aimPos.z += getEnemyBodyOffsetCorrection (distance); } } - else if (m_visibility & VISIBLE_BODY) { + else if (m_visibility & Visibility::Body) { aimPos.z += getEnemyBodyOffsetCorrection (distance); } - else if (m_visibility & VISIBLE_OTHER) { + else if (m_visibility & Visibility::Other) { aimPos = m_enemyOrigin; } - else if (m_visibility & VISIBLE_HEAD) { + else if (m_visibility & Visibility::Head) { aimPos.z = headOffset (m_enemy) + getEnemyBodyOffsetCorrection (distance); } } @@ -484,15 +483,15 @@ float Bot::getEnemyBodyOffsetCorrection (float distance) { bool zoomableRifle = usesZoomableRifle (); bool submachine = usesSubmachine (); - bool shotgun = (m_currentWeapon == WEAPON_XM1014 || m_currentWeapon == WEAPON_M3); - bool m249 = m_currentWeapon == WEAPON_M249; + bool shotgun = (m_currentWeapon == Weapon::XM1014 || m_currentWeapon == Weapon::M3); + bool m249 = m_currentWeapon == Weapon::M249; float result = -2.0f; - if (distance < MAX_SPRAY_DISTANCE) { + if (distance < kSprayDistance) { return -9.0f; } - else if (distance >= MAX_SPRAY_DISTANCE_X2) { + else if (distance >= kDoubleSprayDistance) { if (sniper) { result = 0.18f; } @@ -520,13 +519,13 @@ 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 () || game.is (GAME_CSDM)) { + if (!mp_friendlyfire.bool_ () || game.is (GameFlags::CSDM)) { return false; } game.makeVectors (pev->v_angle); TraceResult tr; - game.testLine (getEyesPos (), getEyesPos () + distance * pev->v_angle, TRACE_IGNORE_NONE, ent (), &tr); + game.testLine (getEyesPos (), getEyesPos () + distance * pev->v_angle, TraceIgnore::None, ent (), &tr); // check if we hit something if (util.isPlayer (tr.pHit) && tr.pHit != ent ()) { @@ -540,7 +539,7 @@ bool Bot::isFriendInLineOfFire (float distance) { // search the world for players for (const auto &client : util.getClients ()) { - if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.team != m_team || client.ent == ent ()) { + if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive) || client.team != m_team || client.ent == ent ()) { continue; } edict_t *pent = client.ent; @@ -559,7 +558,7 @@ bool Bot::isPenetrableObstacle (const Vector &dest) { // this function returns true if enemy can be shoot through some obstacle, false otherwise. // credits goes to Immortal_BLG - if (yb_shoots_thru_walls.integer () == 2) { + if (yb_shoots_thru_walls.int_ () == 2) { return isPenetrableObstacle2 (dest); } @@ -574,11 +573,11 @@ bool Bot::isPenetrableObstacle (const Vector &dest) { TraceResult tr; float obstacleDistance = 0.0f; - game.testLine (getEyesPos (), dest, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (getEyesPos (), dest, TraceIgnore::Monsters, ent (), &tr); if (tr.fStartSolid) { const Vector &source = tr.vecEndPos; - game.testLine (dest, source, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (dest, source, TraceIgnore::Monsters, ent (), &tr); if (tr.flFraction != 1.0f) { if ((tr.vecEndPos - dest).lengthSq () > cr::square (800.0f)) { @@ -591,7 +590,7 @@ bool Bot::isPenetrableObstacle (const Vector &dest) { obstacleDistance = (tr.vecEndPos - source).lengthSq (); } } - constexpr float distance = cr::square (75.0f); + const float distance = cr::square (75.0f); if (obstacleDistance > 0.0f) { while (penetratePower > 0) { @@ -623,7 +622,7 @@ bool Bot::isPenetrableObstacle2 (const Vector &dest) { Vector point; TraceResult tr; - game.testLine (source, dest, TRACE_IGNORE_EVERYTHING, ent (), &tr); + game.testLine (source, dest, TraceIgnore::Everything, ent (), &tr); while (tr.flFraction != 1.0f && numHits < 3) { numHits++; @@ -635,7 +634,7 @@ bool Bot::isPenetrableObstacle2 (const Vector &dest) { point = point + direction; thikness++; } - game.testLine (point, dest, TRACE_IGNORE_EVERYTHING, ent (), &tr); + game.testLine (point, dest, TraceIgnore::Everything, ent (), &tr); } if (numHits < 3 && thikness < 98) { @@ -657,24 +656,24 @@ bool Bot::needToPauseFiring (float distance) { return true; } - if ((m_aimFlags & AIM_ENEMY) && !m_enemyOrigin.empty ()) { + if ((m_aimFlags & AimFlags::Enemy) && !m_enemyOrigin.empty ()) { if (util.getShootingCone (ent (), m_enemyOrigin) > 0.92f && isEnemyBehindShield (m_enemy)) { return true; } } float offset = 5.0f; - if (distance < MAX_SPRAY_DISTANCE * 0.5f) { + if (distance < kSprayDistance * 0.5f) { return false; } - else if (distance < MAX_SPRAY_DISTANCE) { + else if (distance < kSprayDistance) { offset = 12.0f; } - else if (distance < MAX_SPRAY_DISTANCE_X2) { + else if (distance < kDoubleSprayDistance) { offset = 10.0f; } - const float xPunch = cr::deg2rad (pev->punchangle.x); - const float yPunch = cr::deg2rad (pev->punchangle.y); + const float xPunch = cr::degreesToRadians (pev->punchangle.x); + const float yPunch = cr::degreesToRadians (pev->punchangle.y); float interval = getFrameInterval (); float tolerance = (100.0f - m_difficulty * 25.0f) / 99.0f; @@ -682,7 +681,7 @@ bool Bot::needToPauseFiring (float distance) { // check if we need to compensate recoil if (cr::tanf (cr::sqrtf (cr::abs (xPunch * xPunch) + cr::abs (yPunch * yPunch))) * distance > offset + 30.0f + tolerance) { if (m_firePause < game.timebase ()) { - m_firePause = rng.getFloat (0.5f, 0.5f + 0.3f * tolerance); + m_firePause = rg.float_ (0.5f, 0.5f + 0.3f * tolerance); } m_firePause -= interval; m_firePause += game.timebase (); @@ -697,7 +696,7 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) { // we want to fire weapon, don't reload now if (!m_isReloading) { - m_reloadState = RELOAD_NONE; + m_reloadState = Reload::None; m_reloadCheckTime = game.timebase () + 3.0f; } @@ -728,7 +727,7 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) { // if we're have a glock or famas vary burst fire mode checkBurstMode (distance); - if (hasShield () && m_shieldCheckTime < game.timebase () && taskId () != TASK_CAMP) // better shield gun usage + if (hasShield () && m_shieldCheckTime < game.timebase () && getCurrentTaskId () != Task::Camp) // better shield gun usage { if (distance >= 750.0f && !isShieldDrawn ()) { pev->button |= IN_ATTACK2; // draw the shield @@ -773,7 +772,7 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) { } // 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) { + if (usesSniper () && (m_states & (Sense::SeeingEnemy | Sense::SuspectEnemy)) && !m_isReloading && pev->velocity.lengthSq () > 0.0f) { m_moveSpeed = 0.0f; m_strafeSpeed = 0.0f; m_navTimeset = game.timebase (); @@ -785,10 +784,10 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) { } // need to care for burst fire? - if (distance < MAX_SPRAY_DISTANCE || m_blindTime > game.timebase ()) { - if (id == WEAPON_KNIFE) { + if (distance < kSprayDistance || m_blindTime > game.timebase ()) { + if (id == Weapon::Knife) { if (distance < 64.0f) { - if (rng.chance (30) || hasShield ()) { + if (rg.chance (30) || hasShield ()) { pev->button |= IN_ATTACK; // use primary attack } else { @@ -819,7 +818,7 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) { } // don't attack with knife over long distance - if (id == WEAPON_KNIFE) { + if (id == Weapon::Knife) { m_shootTime = game.timebase (); return; } @@ -838,20 +837,20 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) { const int offset = cr::abs (m_difficulty * 25 / 20 - 5); - m_shootTime = game.timebase () + 0.1f + rng.getFloat (minDelay[offset], maxDelay[offset]); + m_shootTime = game.timebase () + 0.1f + rg.float_ (minDelay[offset], maxDelay[offset]); m_zoomCheckTime = game.timebase (); } } } -void Bot::fireWeapons (void) { +void Bot::fireWeapons () { // this function will return true if weapon was fired, false otherwise float distance = (m_lookAt - getEyesPos ()).length (); // how far away is the enemy? // or if friend in line of fire, stop this too but do not update shoot time if (!game.isNullEntity (m_enemy)) { if (isFriendInLineOfFire (distance)) { - m_fightStyle = FIGHT_STRAFE; + m_fightStyle = Fight::Strafe; m_lastFightStyleCheck = game.timebase (); return; @@ -860,11 +859,11 @@ void Bot::fireWeapons (void) { auto tab = conf.getRawWeapons (); edict_t *enemy = m_enemy; - int selectId = WEAPON_KNIFE, selectIndex = 0, choosenWeapon = 0; + int selectId = Weapon::Knife, selectIndex = 0, choosenWeapon = 0; int weapons = pev->weapons; // if jason mode use knife only - if (yb_jasonmode.boolean ()) { + if (yb_jasonmode.bool_ ()) { selectWeapons (distance, selectIndex, selectId, choosenWeapon); return; } @@ -878,7 +877,7 @@ void Bot::fireWeapons (void) { // loop through all the weapons until terminator is found... while (tab[selectIndex].id) { // is the bot carrying this weapon? - if (weapons & (1 << tab[selectIndex].id)) { + if (weapons & cr::bit (tab[selectIndex].id)) { // is enough ammo available to fire AND check is better to use pistol in our current situation... if (m_ammoInClip[tab[selectIndex].id] > 0 && !isWeaponBadAtDistance (selectIndex, distance)) { @@ -898,18 +897,18 @@ void Bot::fireWeapons (void) { int id = tab[selectIndex].id; // is the bot carrying this weapon? - if (weapons & (1 << id)) { + if (weapons & cr::bit (id)) { const auto &prop = conf.getWeaponProp (id); if (prop.ammo1 != -1 && prop.ammo1 < 32 && m_ammo[prop.ammo1] >= tab[selectIndex].minPrimaryAmmo) { // available ammo found, reload weapon - if (m_reloadState == RELOAD_NONE || m_reloadCheckTime > game.timebase ()) { + if (m_reloadState == Reload::None || m_reloadCheckTime > game.timebase ()) { m_isReloading = true; - m_reloadState = RELOAD_PRIMARY; + m_reloadState = Reload::Primary; m_reloadCheckTime = game.timebase (); - if (rng.chance (cr::abs (m_difficulty * 25 - 100))) { - pushRadioMessage (RADIO_NEED_BACKUP); + if (rg.chance (cr::abs (m_difficulty * 25 - 100))) { + pushRadioMessage (Radio::NeedBackup); } } return; @@ -917,7 +916,7 @@ void Bot::fireWeapons (void) { } selectIndex++; } - selectId = WEAPON_KNIFE; // no available ammo, use knife! + selectId = Weapon::Knife; // no available ammo, use knife! } selectWeapons (distance, selectIndex, selectId, choosenWeapon); } @@ -933,7 +932,7 @@ bool Bot::isWeaponBadAtDistance (int weaponIndex, float distance) { } int wid = info[weaponIndex].id; - if (wid == WEAPON_KNIFE) { + if (wid == Weapon::Knife) { return false; } @@ -943,28 +942,28 @@ bool Bot::isWeaponBadAtDistance (int weaponIndex, float distance) { } // better use pistol in short range distances, when using sniper weapons - if ((wid == WEAPON_SCOUT || wid == WEAPON_AWP || wid == WEAPON_G3SG1 || wid == WEAPON_SG550) && distance < 450.0f) { + if ((wid == Weapon::Scout || wid == Weapon::AWP || wid == Weapon::G3SG1 || wid == Weapon::SG550) && distance < 450.0f) { return true; } // shotguns is too inaccurate at long distances, so weapon is bad - if ((wid == WEAPON_M3 || wid == WEAPON_XM1014) && distance > 750.0f) { + if ((wid == Weapon::M3 || wid == Weapon::XM1014) && distance > 750.0f) { return true; } return false; } -void Bot::focusEnemy (void) { +void Bot::focusEnemy () { // aim for the head and/or body m_lookAt = getEnemyBodyOffset (); if (m_enemySurpriseTime > game.timebase () || game.isNullEntity (m_enemy)) { return; } - float distance = (m_lookAt - getEyesPos ()).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) { + if (m_currentWeapon == Weapon::Knife) { if (distance < 80.0f) { m_wantsToFire = true; } @@ -1001,20 +1000,20 @@ void Bot::focusEnemy (void) { } } -void Bot::attackMovement (void) { +void Bot::attackMovement () { // no enemy? no need to do strafing if (game.isNullEntity (m_enemy)) { return; } - float distance = (m_lookAt - getEyesPos ()).length2D (); // how far away is the enemy scum? + float distance = (m_lookAt - getEyesPos ()).length2d (); // how far away is the enemy scum? - if (m_timeWaypointMove + getFrameInterval () + 0.5f < game.timebase ()) { + if (m_lastUsedNodesTime + getFrameInterval () + 0.5f < game.timebase ()) { int approach; - if (m_currentWeapon == WEAPON_KNIFE) { + if (m_currentWeapon == Weapon::Knife) { approach = 100; } - else if ((m_states & STATE_SUSPECT_ENEMY) && !(m_states & STATE_SEEING_ENEMY)) { + else if ((m_states & Sense::SuspectEnemy) && !(m_states & Sense::SeeingEnemy)) { approach = 49; } else if (m_isReloading || m_isVIP) { @@ -1029,9 +1028,9 @@ 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 && !bots.isBombPlanted () && (isInViewCone (m_enemy->v.origin) || m_isVIP)) { + if ((m_states & Sense::SeeingEnemy) && 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); + startTask (Task::SeekCover, TaskPri::SeekCover, kInvalidNodeIndex, 0.0f, true); } else if (approach < 50) { m_moveSpeed = 0.0f; @@ -1040,35 +1039,35 @@ void Bot::attackMovement (void) { m_moveSpeed = pev->maxspeed; } - if (distance < 96.0f && m_currentWeapon != WEAPON_KNIFE) { + if (distance < 96.0f && m_currentWeapon != Weapon::Knife) { m_moveSpeed = -pev->maxspeed; } - if (usesSniper () || !(m_visibility & (VISIBLE_BODY | VISIBLE_HEAD))) { - m_fightStyle = FIGHT_STAY; + if (usesSniper () || !(m_visibility & (Visibility::Body | Visibility::Head))) { + m_fightStyle = Fight::Stay; m_lastFightStyleCheck = game.timebase (); } else if (usesRifle () || usesSubmachine ()) { if (m_lastFightStyleCheck + 3.0f < game.timebase ()) { - int rand = rng.getInt (1, 100); + int rand = rg.int_ (1, 100); if (distance < 450.0f) { - m_fightStyle = FIGHT_STRAFE; + m_fightStyle = Fight::Strafe; } else if (distance < 1024.0f) { if (rand < (usesSubmachine () ? 50 : 30)) { - m_fightStyle = FIGHT_STRAFE; + m_fightStyle = Fight::Strafe; } else { - m_fightStyle = FIGHT_STAY; + m_fightStyle = Fight::Stay; } } else { if (rand < (usesSubmachine () ? 80 : 93)) { - m_fightStyle = FIGHT_STAY; + m_fightStyle = Fight::Stay; } else { - m_fightStyle = FIGHT_STRAFE; + m_fightStyle = Fight::Strafe; } } m_lastFightStyleCheck = game.timebase (); @@ -1076,45 +1075,45 @@ void Bot::attackMovement (void) { } else { if (m_lastFightStyleCheck + 3.0f < game.timebase ()) { - if (rng.chance (50)) { - m_fightStyle = FIGHT_STRAFE; + if (rg.chance (50)) { + m_fightStyle = Fight::Strafe; } else { - m_fightStyle = FIGHT_STAY; + m_fightStyle = Fight::Stay; } 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_fightStyle == Fight::Strafe || ((pev->button & IN_RELOAD) || m_isReloading) || (usesPistol () && distance < 400.0f) || m_currentWeapon == Weapon::Knife) { 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 game.makeVectors (m_enemy->v.v_angle); - const Vector &dirToPoint = (pev->origin - m_enemy->v.origin).normalize2D (); - const Vector &rightSide = game.vec.right.normalize2D (); + const Vector &dirToPoint = (pev->origin - m_enemy->v.origin).normalize2d (); + const Vector &rightSide = game.vec.right.normalize2d (); if ((dirToPoint | rightSide) < 0) { - m_combatStrafeDir = STRAFE_DIR_LEFT; + m_combatStrafeDir = Dodge::Left; } else { - m_combatStrafeDir = STRAFE_DIR_RIGHT; + m_combatStrafeDir = Dodge::Right; } - if (rng.chance (30)) { - m_combatStrafeDir = (m_combatStrafeDir == STRAFE_DIR_LEFT ? STRAFE_DIR_RIGHT : STRAFE_DIR_LEFT); + if (rg.chance (30)) { + m_combatStrafeDir = (m_combatStrafeDir == Dodge::Left ? Dodge::Right : Dodge::Left); } - m_strafeSetTime = game.timebase () + rng.getFloat (0.5f, 3.0f); + m_strafeSetTime = game.timebase () + rg.float_ (0.5f, 3.0f); } - if (m_combatStrafeDir == STRAFE_DIR_RIGHT) { + if (m_combatStrafeDir == Dodge::Right) { if (!checkWallOnLeft ()) { m_strafeSpeed = -pev->maxspeed; } else { - m_combatStrafeDir = STRAFE_DIR_LEFT; - m_strafeSetTime = game.timebase () + rng.getFloat (0.8f, 1.3f); + m_combatStrafeDir = Dodge::Left; + m_strafeSetTime = game.timebase () + rg.float_ (0.8f, 1.3f); } } else { @@ -1122,28 +1121,28 @@ void Bot::attackMovement (void) { m_strafeSpeed = pev->maxspeed; } else { - m_combatStrafeDir = STRAFE_DIR_RIGHT; - m_strafeSetTime = game.timebase () + rng.getFloat (0.8f, 1.3f); + m_combatStrafeDir = Dodge::Right; + m_strafeSetTime = game.timebase () + rg.float_ (0.8f, 1.3f); } } - 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 ()) { + if (m_difficulty >= 3 && (m_jumpTime + 5.0f < game.timebase () && isOnFloor () && rg.int_ (0, 1000) < (m_isReloading ? 8 : 2) && pev->velocity.length2d () > 120.0f) && !usesSniper ()) { pev->button |= IN_JUMP; } - if (m_moveSpeed > 0.0f && distance > 100.0f && m_currentWeapon != WEAPON_KNIFE) { + if (m_moveSpeed > 0.0f && distance > 100.0f && m_currentWeapon != Weapon::Knife) { m_moveSpeed = 0.0f; } - if (m_currentWeapon == WEAPON_KNIFE) { + if (m_currentWeapon == Weapon::Knife) { m_strafeSpeed = 0.0f; } } - else if (m_fightStyle == FIGHT_STAY) { - if ((m_visibility & (VISIBLE_HEAD | VISIBLE_BODY)) && !(m_visibility & VISIBLE_OTHER) && taskId () != TASK_SEEKCOVER && taskId () != TASK_HUNTENEMY) { - int enemyNearestIndex = waypoints.getNearest (m_enemy->v.origin); + else if (m_fightStyle == Fight::Stay) { + if ((m_visibility & (Visibility::Head | Visibility::Body)) && !(m_visibility & Visibility::Other) && getCurrentTaskId () != Task::SeekCover && getCurrentTaskId () != Task::Hunt) { + int enemyNearestIndex = graph.getNearest (m_enemy->v.origin); - if (waypoints.isDuckVisible (m_currentWaypointIndex, enemyNearestIndex) && waypoints.isDuckVisible (enemyNearestIndex, m_currentWaypointIndex)) { + if (graph.isDuckVisible (m_currentNodeIndex, enemyNearestIndex) && graph.isDuckVisible (enemyNearestIndex, m_currentNodeIndex)) { m_duckTime = game.timebase () + 0.5f; } } @@ -1158,7 +1157,7 @@ void Bot::attackMovement (void) { m_strafeSpeed = 0.0f; } - if (m_moveSpeed > 0.0f && m_currentWeapon != WEAPON_KNIFE) { + if (m_moveSpeed > 0.0f && m_currentWeapon != Weapon::Knife) { m_moveSpeed = getShiftSpeed (); } @@ -1180,25 +1179,25 @@ void Bot::attackMovement (void) { ignoreCollision (); } -bool Bot::hasPrimaryWeapon (void) { +bool Bot::hasPrimaryWeapon () { // this function returns returns true, if bot has a primary weapon - return (pev->weapons & WEAPON_PRIMARY) != 0; + return (pev->weapons & kPrimaryWeaponMask) != 0; } -bool Bot::hasSecondaryWeapon (void) { +bool Bot::hasSecondaryWeapon () { // this function returns returns true, if bot has a secondary weapon - return (pev->weapons & WEAPON_SECONDARY) != 0; + return (pev->weapons & kSecondaryWeaponMask) != 0; } -bool Bot::hasShield (void) { +bool Bot::hasShield () { // this function returns true, if bot has a tactical shield return strncmp (STRING (pev->viewmodel), "models/shield/v_shield_", 23) == 0; } -bool Bot::isShieldDrawn (void) { +bool Bot::isShieldDrawn () { // this function returns true, is the tactical shield is drawn if (!hasShield ()) { @@ -1215,7 +1214,7 @@ bool Bot::isEnemyBehindShield (edict_t *enemy) { } // 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 ((enemy->v.weaponanim == 6 || enemy->v.weaponanim == 7) && strncmp (STRING (enemy->v.viewmodel), "models/shield/v_shield_", 23) == 0) { if (util.isInViewCone (pev->origin, enemy)) { return true; } @@ -1223,13 +1222,13 @@ bool Bot::isEnemyBehindShield (edict_t *enemy) { return false; } -bool Bot::usesSniper (void) { +bool Bot::usesSniper () { // this function returns true, if returns if bot is using a sniper rifle - return m_currentWeapon == WEAPON_AWP || m_currentWeapon == WEAPON_G3SG1 || m_currentWeapon == WEAPON_SCOUT || m_currentWeapon == WEAPON_SG550; + return m_currentWeapon == Weapon::AWP || m_currentWeapon == Weapon::G3SG1 || m_currentWeapon == Weapon::Scout || m_currentWeapon == Weapon::SG550; } -bool Bot::usesRifle (void) { +bool Bot::usesRifle () { auto tab = conf.getRawWeapons (); int count = 0; @@ -1247,7 +1246,7 @@ bool Bot::usesRifle (void) { return false; } -bool Bot::usesPistol (void) { +bool Bot::usesPistol () { auto tab = conf.getRawWeapons (); int count = 0; @@ -1266,23 +1265,23 @@ bool Bot::usesPistol (void) { return false; } -bool Bot::usesCampGun (void) { +bool Bot::usesCampGun () { return usesSubmachine () || usesRifle () || usesSniper (); } -bool Bot::usesSubmachine (void) { - return m_currentWeapon == WEAPON_MP5 || m_currentWeapon == WEAPON_TMP || m_currentWeapon == WEAPON_P90 || m_currentWeapon == WEAPON_MAC10 || m_currentWeapon == WEAPON_UMP45; +bool Bot::usesSubmachine () { + return m_currentWeapon == Weapon::MP5 || m_currentWeapon == Weapon::TMP || m_currentWeapon == Weapon::P90 || m_currentWeapon == Weapon::MAC10 || m_currentWeapon == Weapon::UMP45; } -bool Bot::usesZoomableRifle (void) { - return m_currentWeapon == WEAPON_AUG || m_currentWeapon == WEAPON_SG552; +bool Bot::usesZoomableRifle () { + return m_currentWeapon == Weapon::AUG || m_currentWeapon == Weapon::SG552; } -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; +bool Bot::usesBadWeapon () { + 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) { +int Bot::bestPrimaryCarried () { // this function returns the best weapon of this bot (based on personality prefs) const int *pref = conf.getWeaponPrefs (m_personality); @@ -1294,11 +1293,11 @@ int Bot::bestPrimaryCarried (void) { // take the shield in account if (hasShield ()) { - weapons |= (1 << WEAPON_SHIELD); + weapons |= cr::bit (Weapon::Shield); } - for (int i = 0; i < NUM_WEAPONS; i++) { - if (weapons & (1 << weaponTab[*pref].id)) { + for (int i = 0; i < kNumWeapons; ++i) { + if (weapons & cr::bit (weaponTab[*pref].id)) { weaponIndex = i; } pref++; @@ -1306,7 +1305,7 @@ int Bot::bestPrimaryCarried (void) { return weaponIndex; } -int Bot::bestSecondaryCarried (void) { +int Bot::bestSecondaryCarried () { // this function returns the best secondary weapon of this bot (based on personality prefs) const int *pref = conf.getWeaponPrefs (m_personality); @@ -1316,14 +1315,14 @@ int Bot::bestSecondaryCarried (void) { // take the shield in account if (hasShield ()) { - weapons |= (1 << WEAPON_SHIELD); + weapons |= cr::bit (Weapon::Shield); } auto tab = conf.getRawWeapons (); - for (int i = 0; i < NUM_WEAPONS; i++) { + for (int i = 0; i < kNumWeapons; ++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)) { + if ((weapons & cr::bit (tab[*pref].id)) && (id == Weapon::USP || id == Weapon::Glock18 || id == Weapon::Deagle || id == Weapon::P228 || id == Weapon::Elite || id == Weapon::FiveSeven)) { weaponIndex = i; break; } @@ -1332,15 +1331,15 @@ int Bot::bestSecondaryCarried (void) { return weaponIndex; } -int Bot::bestGrenadeCarried (void) { - if (pev->weapons & (1 << WEAPON_EXPLOSIVE)) { - return WEAPON_EXPLOSIVE; +int Bot::bestGrenadeCarried () { + if (pev->weapons & cr::bit (Weapon::Explosive)) { + return Weapon::Explosive; } - else if (pev->weapons & (1 << WEAPON_SMOKE)) { - return WEAPON_SMOKE; + else if (pev->weapons & cr::bit (Weapon::Smoke)) { + return Weapon::Smoke; } - else if (pev->weapons & (1 << WEAPON_FLASHBANG)) { - return WEAPON_FLASHBANG; + else if (pev->weapons & cr::bit (Weapon::Flashbang)) { + return Weapon::Flashbang; } return -1; } @@ -1353,7 +1352,7 @@ bool Bot::rateGroundWeapon (edict_t *ent) { const int *pref = conf.getWeaponPrefs (m_personality); auto tab = conf.getRawWeapons (); - for (int i = 0; i < NUM_WEAPONS; i++) { + for (int i = 0; i < kNumWeapons; ++i) { if (strcmp (tab[*pref].model, STRING (ent->v.model) + 9) == 0) { groundIndex = i; break; @@ -1371,15 +1370,15 @@ bool Bot::rateGroundWeapon (edict_t *ent) { return groundIndex > hasWeapon; } -bool Bot::hasAnyWeapons (void) { - return (pev->weapons & (WEAPON_PRIMARY | WEAPON_SECONDARY)); +bool Bot::hasAnyWeapons () { + return (pev->weapons & (kPrimaryWeaponMask | kSecondaryWeaponMask)); } -void Bot::selectBestWeapon (void) { +void Bot::selectBestWeapon () { // this function chooses best weapon, from weapons that bot currently own, and change // current weapon to best one. - if (yb_jasonmode.boolean ()) { + if (yb_jasonmode.bool_ ()) { // if knife mode activated, force bot to use knife selectWeaponByName ("weapon_knife"); return; @@ -1396,7 +1395,7 @@ void Bot::selectBestWeapon (void) { // loop through all the weapons until terminator is found... while (tab[selectIndex].id) { // is the bot NOT carrying this weapon? - if (!(pev->weapons & (1 << tab[selectIndex].id))) { + if (!(pev->weapons & cr::bit (tab[selectIndex].id))) { selectIndex++; // skip to next weapon continue; } @@ -1421,7 +1420,7 @@ void Bot::selectBestWeapon (void) { selectIndex++; } - chosenWeaponIndex %= NUM_WEAPONS + 1; + chosenWeaponIndex %= kNumWeapons + 1; selectIndex = chosenWeaponIndex; int id = tab[selectIndex].id; @@ -1431,19 +1430,19 @@ void Bot::selectBestWeapon (void) { selectWeaponByName (tab[selectIndex].name); } m_isReloading = false; - m_reloadState = RELOAD_NONE; + m_reloadState = Reload::None; } -void Bot::selectSecondary (void) { +void Bot::selectSecondary () { int oldWeapons = pev->weapons; - pev->weapons &= ~WEAPON_PRIMARY; + pev->weapons &= ~kPrimaryWeaponMask; selectBestWeapon (); pev->weapons = oldWeapons; } -int Bot::bestWeaponCarried (void) { +int Bot::bestWeaponCarried () { auto tab = conf.getRawWeapons (); int weapons = pev->weapons; @@ -1453,32 +1452,32 @@ int Bot::bestWeaponCarried (void) { // loop through all the weapons until terminator is found... while (tab->id) { // is the bot carrying this weapon? - if (weapons & (1 << tab->id)) { + if (weapons & cr::bit (tab->id)) { num = i; } - i++; + ++i; tab++; } return num; } void Bot::selectWeaponByName (const char *name) { - game.execBotCmd (ent (), name); + game.botCommand (ent (), name); } void Bot::selectWeaponById (int num) { auto tab = conf.getRawWeapons (); - game.execBotCmd (ent (), tab[num].name); + game.botCommand (ent (), tab[num].name); } -void Bot::decideFollowUser (void) { +void Bot::decideFollowUser () { // this function forces bot to follow user - Array users; + Array users; // search friends near us for (const auto &client : util.getClients ()) { - if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.team != m_team || client.ent == ent ()) { + if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive) || client.team != m_team || client.ent == ent ()) { continue; } @@ -1492,13 +1491,13 @@ void Bot::decideFollowUser (void) { } m_targetEntity = users.random (); - pushChatterMessage (CHATTER_LEAD_ON_SIR); - startTask (TASK_FOLLOWUSER, TASKPRI_FOLLOWUSER, INVALID_WAYPOINT_INDEX, 0.0f, true); + pushChatterMessage (Chatter::LeadOnSir); + startTask (Task::FollowUser, TaskPri::FollowUser, kInvalidNodeIndex, 0.0f, true); } -void Bot::updateTeamCommands (void) { +void Bot::updateTeamCommands () { // prevent spamming - if (m_timeTeamOrder > game.timebase () + 2.0f || game.is (GAME_CSDM_FFA) || !yb_communication_type.integer ()) { + if (m_timeTeamOrder > game.timebase () + 2.0f || game.is (GameFlags::FreeForAll) || !yb_radio_mode.int_ ()) { return; } @@ -1507,7 +1506,7 @@ void Bot::updateTeamCommands (void) { // search teammates seen by this bot for (const auto &client : util.getClients ()) { - if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.team != m_team || client.ent == ent ()) { + if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive) || client.team != m_team || client.ent == ent ()) { continue; } memberExists = true; @@ -1520,20 +1519,20 @@ void Bot::updateTeamCommands (void) { // has teammates? if (memberNear) { - if (m_personality == PERSONALITY_RUSHER && yb_communication_type.integer () == 2) { - pushRadioMessage (RADIO_STORM_THE_FRONT); + if (m_personality == Personality::Rusher && yb_radio_mode.int_ () == 2) { + pushRadioMessage (Radio::StormTheFront); } - else if (m_personality != PERSONALITY_RUSHER && yb_communication_type.integer () == 2) { - pushRadioMessage (RADIO_TEAM_FALLBACK); + else if (m_personality != Personality::Rusher && yb_radio_mode.int_ () == 2) { + pushRadioMessage (Radio::TeamFallback); } } - else if (memberExists && yb_communication_type.integer () == 1) { - pushRadioMessage (RADIO_TAKING_FIRE); + else if (memberExists && yb_radio_mode.int_ () == 1) { + pushRadioMessage (Radio::TakingFireNeedAssistance); } - else if (memberExists && yb_communication_type.integer () == 2) { - pushChatterMessage (CHATTER_SCARED_EMOTE); + else if (memberExists && yb_radio_mode.int_ () == 2) { + pushChatterMessage (Chatter::ScaredEmotion); } - m_timeTeamOrder = game.timebase () + rng.getFloat (15.0f, 30.0f); + m_timeTeamOrder = game.timebase () + rg.float_ (15.0f, 30.0f); } bool Bot::isGroupOfEnemies (const Vector &location, int numEnemies, float radius) { @@ -1541,7 +1540,7 @@ bool Bot::isGroupOfEnemies (const Vector &location, int numEnemies, float radius // search the world for enemy players... for (const auto &client : util.getClients ()) { - if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE) || client.ent == ent ()) { + if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive) || client.ent == ent ()) { continue; } @@ -1559,38 +1558,38 @@ bool Bot::isGroupOfEnemies (const Vector &location, int numEnemies, float radius return false; } -void Bot::checkReload (void) { +void Bot::checkReload () { // check the reload state - if (taskId () == TASK_PLANTBOMB || taskId () == TASK_DEFUSEBOMB || taskId () == TASK_PICKUPITEM || taskId () == TASK_THROWFLASHBANG || taskId () == TASK_THROWSMOKE || m_isUsingGrenade) { - m_reloadState = RELOAD_NONE; + if (getCurrentTaskId () == Task::PlantBomb || getCurrentTaskId () == Task::DefuseBomb || getCurrentTaskId () == Task::PickupItem || getCurrentTaskId () == Task::ThrowFlashbang || getCurrentTaskId () == Task::ThrowSmoke || m_isUsingGrenade) { + m_reloadState = Reload::None; return; } m_isReloading = false; // update reloading status m_reloadCheckTime = game.timebase () + 3.0f; - if (m_reloadState != RELOAD_NONE) { + if (m_reloadState != Reload::None) { int weaponIndex = 0; int weapons = pev->weapons; - if (m_reloadState == RELOAD_PRIMARY) { - weapons &= WEAPON_PRIMARY; + if (m_reloadState == Reload::Primary) { + weapons &= kPrimaryWeaponMask; } - else if (m_reloadState == RELOAD_SECONDARY) { - weapons &= WEAPON_SECONDARY; + else if (m_reloadState == Reload::Secondary) { + weapons &= kSecondaryWeaponMask; } if (weapons == 0) { m_reloadState++; - if (m_reloadState > RELOAD_SECONDARY) { - m_reloadState = RELOAD_NONE; + if (m_reloadState > Reload::Secondary) { + m_reloadState = Reload::None; } return; } - for (int i = 1; i < MAX_WEAPONS; i++) { - if (weapons & (1 << i)) { + for (int i = 1; i < kMaxWeapons; ++i) { + if (weapons & cr::bit (i)) { weaponIndex = i; break; } @@ -1603,21 +1602,21 @@ void Bot::checkReload (void) { } pev->button &= ~IN_ATTACK; - if ((m_oldButtons & IN_RELOAD) == RELOAD_NONE) { + if ((m_oldButtons & IN_RELOAD) == Reload::None) { pev->button |= IN_RELOAD; // press reload button } m_isReloading = true; } else { // if we have enemy don't reload next weapon - if ((m_states & (STATE_SEEING_ENEMY | STATE_HEARING_ENEMY)) || m_seeEnemyTime + 5.0f > game.timebase ()) { - m_reloadState = RELOAD_NONE; + if ((m_states & (Sense::SeeingEnemy | Sense::HearingEnemy)) || m_seeEnemyTime + 5.0f > game.timebase ()) { + m_reloadState = Reload::None; return; } m_reloadState++; - if (m_reloadState > RELOAD_SECONDARY) { - m_reloadState = RELOAD_NONE; + if (m_reloadState > Reload::Secondary) { + m_reloadState = Reload::None; } return; } diff --git a/source/control.cpp b/source/control.cpp index 3e5da75..9c99fb9 100644 --- a/source/control.cpp +++ b/source/control.cpp @@ -10,53 +10,59 @@ #include ConVar yb_display_menu_text ("yb_display_menu_text", "1"); -ConVar yb_password ("yb_password", "", VT_PASSWORD); +ConVar yb_password ("yb_password", "", Var::Password); ConVar yb_password_key ("yb_password_key", "_ybpw"); -int BotControl::cmdAddBot (void) { +int BotControl::cmdAddBot () { enum args { alias = 1, difficulty, personality, team, model, name, max }; // adding more args to args array, if not enough passed fixMissingArgs (max); + // this is duplicate error as in main bot creation code, but not to be silent + if (!graph.length () || graph.hasChanged ()) { + ctrl.msg ("There is not graph found or graph is changed. Cannot create bot."); + return BotCommandResult::Handled; + } + // if team is specified, modify args to set team - if (m_args[alias].find ("_ct", 0) != String::INVALID_INDEX) { + if (m_args[alias].find ("_ct", 0) != String::kInvalidIndex) { m_args.set (team, "2"); } - else if (m_args[alias].find ("_t", 0) != String::INVALID_INDEX) { + else if (m_args[alias].find ("_t", 0) != String::kInvalidIndex) { 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) { + if (m_args[alias].find ("hs", 0) != String::kInvalidIndex) { 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; + return BotCommandResult::Handled; } -int BotControl::cmdKickBot (void) { +int BotControl::cmdKickBot () { 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); + if (m_args[alias].find ("_ct", 0) != String::kInvalidIndex || getInt (team) == 2 || getStr (team) == "ct") { + bots.kickFromTeam (Team::CT); } - else if (m_args[alias].find ("_t", 0) != String::INVALID_INDEX || getInt (team) == 1 || getStr (team) == "t") { - bots.kickFromTeam (TEAM_TERRORIST); + else if (m_args[alias].find ("_t", 0) != String::kInvalidIndex || getInt (team) == 1 || getStr (team) == "t") { + bots.kickFromTeam (Team::Terrorist); } else { bots.kickRandom (); } - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } -int BotControl::cmdKickBots (void) { +int BotControl::cmdKickBots () { enum args { alias = 1, instant, max }; // adding more args to args array, if not enough passed @@ -68,206 +74,205 @@ int BotControl::cmdKickBots (void) { // kick the bots bots.kickEveryone (kickInstant); - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } -int BotControl::cmdKillBots (void) { +int BotControl::cmdKillBots () { 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); + if (m_args[alias].find ("_ct", 0) != String::kInvalidIndex || getInt (team) == 2 || getStr (team) == "ct") { + bots.killAllBots (Team::CT); } - else if (m_args[alias].find ("_t", 0) != String::INVALID_INDEX || getInt (team) == 1 || getStr (team) == "t") { - bots.killAllBots (TEAM_TERRORIST); + else if (m_args[alias].find ("_t", 0) != String::kInvalidIndex || getInt (team) == 1 || getStr (team) == "t") { + bots.killAllBots (Team::Terrorist); } else { bots.killAllBots (); } - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } -int BotControl::cmdFill (void) { +int BotControl::cmdFill () { 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; + return BotCommandResult::BadFormat; } bots.serverFill (getInt (team), hasArg (personality) ? getInt (personality) : -1, hasArg (difficulty) ? getInt (difficulty) : -1, hasArg (count) ? getInt (count) : -1); - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } -int BotControl::cmdVote (void) { +int BotControl::cmdVote () { 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; + return BotCommandResult::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; - } + for (const auto &bot : bots) { + bot->m_voteMap = mapID; } msg ("All dead bots will vote for map #%d", mapID); - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } -int BotControl::cmdWeaponMode (void) { +int BotControl::cmdWeaponMode () { 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; + return BotCommandResult::BadFormat; } - HashMap modes; + Dictionary 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); + modes.push ("kinfe", 1); + modes.push ("pistol", 2); + modes.push ("shotgun", 3); + modes.push ("smg", 4); + modes.push ("rifle", 5); + modes.push ("sniper", 6); + modes.push ("stanard", 7); auto mode = getStr (type); // check if selected mode exists if (!modes.exists (mode)) { - return CMD_STATUS_BADFORMAT; + return BotCommandResult::BadFormat; } bots.setWeaponMode (modes[mode]); - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } -int BotControl::cmdVersion (void) { +int BotControl::cmdVersion () { 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; + return BotCommandResult::Handled; } -int BotControl::cmdWaypointMenu (void) { +int BotControl::cmdNodeMenu () { 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; + // graph editor is available only with editor + if (!graph.hasEditor ()) { + msg ("Unable to open graph editor without setting the editor player."); + return BotCommandResult::Handled; } - showMenu (BOT_MENU_WAYPOINT_MAIN_PAGE1); + showMenu (Menu::NodeMainPage1); - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } -int BotControl::cmdMenu (void) { +int BotControl::cmdMenu () { 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); + showMenu (Menu::None); if (getStr (cmd) == "cmd" && util.isAlive (m_ent)) { - showMenu (BOT_MENU_COMMANDS); + showMenu (Menu::Commands); } else { - showMenu (BOT_MENU_MAIN); + showMenu (Menu::Main); } - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } -int BotControl::cmdList (void) { +int BotControl::cmdList () { enum args { alias = 1, max }; bots.listBots (); - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } -int BotControl::cmdWaypoint (void) { +int BotControl::cmdNode () { 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; + // graph editor supported only with editor + if (game.isDedicated () && !graph.hasEditor () && getStr (cmd) != "acquire_editor") { + msg ("Unable to use graph edit commands without setting graph editor player. Please use \"graph acquire_editor\" to acquire rights for graph editing."); + return BotCommandResult::Handled; } // should be moved to class? - static HashMap commands; - static Array descriptions; + static Dictionary commands; + static StringArray 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) }); + auto pushGraphCmd = [&] (String cmd, String format, String help, Handler handler) -> void { + BotCmd botCmd (cr::move (cmd), cr::move (format), cr::move (help), cr::move (handler)); + + commands.push (cmd, cr::move (botCmd)); 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 graph commands + pushGraphCmd ("on", "on [display|auto|noclip|models]", "Enables displaying of graph, nodes, noclip cheat", &BotControl::cmdNodeOn); + pushGraphCmd ("off", "off [display|auto|noclip|models]", "Disables displaying of graph, auto adding nodes, noclip cheat", &BotControl::cmdNodeOff); + pushGraphCmd ("menu", "menu [noarguments]", "Opens and displays bots graph edtior.", &BotControl::cmdNodeMenu); + pushGraphCmd ("add", "add [noarguments]", "Opens and displays graph node add menu.", &BotControl::cmdNodeAdd); + pushGraphCmd ("addbasic", "menu [noarguments]", "Adds basic nodes such as player spawn points, goals and ladders.", &BotControl::cmdNodeAddBasic); + pushGraphCmd ("save", "save [noarguments]", "Save graph file to disk.", &BotControl::cmdNodeSave); + pushGraphCmd ("load", "load [noarguments]", "Load graph file from disk.", &BotControl::cmdNodeLoad); + pushGraphCmd ("erase", "erase [iamsure]", "Erases the graph file from disk.", &BotControl::cmdNodeErase); + pushGraphCmd ("delete", "delete [nearest|index]", "Deletes single graph node from map.", &BotControl::cmdNodeDelete); + pushGraphCmd ("check", "check [noarguments]", "Check if graph working correctly.", &BotControl::cmdNodeCheck); + pushGraphCmd ("cache", "cache [nearest|index]", "Caching node for future use.", &BotControl::cmdNodeCache); + pushGraphCmd ("clean", "clean [all|nearest|index]", "Clean useless path connections from all or single node.", &BotControl::cmdNodeClean); + pushGraphCmd ("setradius", "setradius [radius] [nearest|index]", "Sets the radius for node.", &BotControl::cmdNodeSetRadius); + pushGraphCmd ("flags", "flags [noarguments]", "Open and displays menu for modifying flags for nearest point.", &BotControl::cmdNodeSetFlags); + pushGraphCmd ("teleport", "teleport [index]", "Teleports player to specified node index.", &BotControl::cmdNodeTeleport); + pushGraphCmd ("upload", "upload [id]", "Uploads created graph to graph database.", &BotControl::cmdNodeUpload); // 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); + pushGraphCmd ("path_create", "path_create [noarguments]", "Opens and displays path creation menu.", &BotControl::cmdNodePathCreate); + pushGraphCmd ("path_create_in", "path_create_in [noarguments]", "Opens and displays path creation menu.", &BotControl::cmdNodePathCreate); + pushGraphCmd ("path_create_out", "path_create_out [noarguments]", "Opens and displays path creation menu.", &BotControl::cmdNodePathCreate); + pushGraphCmd ("path_create_both", "path_create_both [noarguments]", "Opens and displays path creation menu.", &BotControl::cmdNodePathCreate); + pushGraphCmd ("path_delete", "path_create_both [noarguments]", "Opens and displays path creation menu.", &BotControl::cmdNodePathDelete); + pushGraphCmd ("path_set_autopath", "path_set_autoath [max_distance]", "Opens and displays path creation menu.", &BotControl::cmdNodePathSetAutoDistance); - // remote waypoint editing stuff + // remote graph 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); + pushGraphCmd ("acquire_editor", "acquire_editor [max_distance]", "Acquires rights to edit graph on dedicated server.", &BotControl::cmdNodeAcquireEditor); + pushGraphCmd ("release_editor", "acquire_editor [max_distance]", "Releases graph editing rights.", &BotControl::cmdNodeAcquireEditor); } } if (commands.exists (getStr (cmd))) { - auto &item = commands[getStr (cmd)]; + auto item = commands[getStr (cmd)]; - // waypoints have only bad format return status + // graph have only bad format return status int status = (this->*item.handler) (); - if (status == CMD_STATUS_BADFORMAT) { + if (status == BotCommandResult::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 ()); } } @@ -282,13 +287,13 @@ int BotControl::cmdWaypoint (void) { 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"); + msg ("Currently Graph Status %s", graph.hasEditFlag (GraphEdit::On) ? "Enabled" : "Disabled"); } } - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } -int BotControl::cmdWaypointOn (void) { +int BotControl::cmdNodeOn () { enum args { alias = 1, cmd, option, max }; // adding more args to args array, if not enough passed @@ -296,434 +301,503 @@ int BotControl::cmdWaypointOn (void) { // enable various features of editor if (getStr (option) == "empty" || getStr (option) == "display" || getStr (option) == "models") { - waypoints.setEditFlag (WS_EDIT_ENABLED); + graph.setEditFlag (GraphEdit::On); enableDrawModels (true); - msg ("Waypoint editor has been enabled."); + msg ("Graph editor has been enabled."); } else if (getStr (option) == "noclip") { m_ent->v.movetype = MOVETYPE_NOCLIP; - waypoints.setEditFlag (WS_EDIT_ENABLED | WS_EDIT_NOCLIP); + graph.setEditFlag (GraphEdit::On | GraphEdit::Noclip); enableDrawModels (true); - msg ("Waypoint editor has been enabled with noclip mode."); + msg ("Graph editor has been enabled with noclip mode."); } else if (getStr (option) == "auto") { - waypoints.setEditFlag (WS_EDIT_ENABLED | WS_EDIT_AUTO); + graph.setEditFlag (GraphEdit::On | GraphEdit::Auto); enableDrawModels (true); - msg ("Waypoint editor has been enabled with autowaypoint mode."); + msg ("Graph editor has been enabled with auto add node mode."); } - if (waypoints.hasEditFlag (WS_EDIT_ENABLED)) { + if (graph.hasEditFlag (GraphEdit::On)) { extern ConVar mp_roundtime, mp_freezetime, mp_timelimit; mp_roundtime.set (9); mp_freezetime.set (0); mp_timelimit.set (0); } - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } -int BotControl::cmdWaypointOff (void) { - enum args { waypoint = 1, cmd, option, max }; +int BotControl::cmdNodeOff () { + enum args { graph_cmd = 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); + graph.clearEditFlag (GraphEdit::On | GraphEdit::Auto | GraphEdit::Noclip); enableDrawModels (false); - msg ("Waypoint editor has been disabled."); + msg ("Graph editor has been disabled."); } else if (getStr (option) == "models") { enableDrawModels (false); - msg ("Waypoint editor has disabled spawn points highlighting."); + msg ("Graph editor has disabled spawn points highlighting."); } else if (getStr (option) == "noclip") { m_ent->v.movetype = MOVETYPE_WALK; - waypoints.clearEditFlag (WS_EDIT_NOCLIP); + graph.clearEditFlag (GraphEdit::Noclip); - msg ("Waypoint editor has disabled noclip mode."); + msg ("Graph editor has disabled noclip mode."); } else if (getStr (option) == "auto") { - waypoints.clearEditFlag (WS_EDIT_AUTO); - msg ("Waypoint editor has disabled autowaypoint mode."); + graph.clearEditFlag (GraphEdit::Auto); + msg ("Graph editor has disabled auto add node mode."); } - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } -int BotControl::cmdWaypointAdd (void) { - enum args { waypoint = 1, cmd, max }; +int BotControl::cmdNodeAdd () { + enum args { graph_cmd = 1, cmd, max }; // adding more args to args array, if not enough passed fixMissingArgs (max); - // turn waypoints on - waypoints.setEditFlag (WS_EDIT_ENABLED); + // turn graph on + graph.setEditFlag (GraphEdit::On); // show the menu - showMenu (BOT_MENU_WAYPOINT_TYPE); - return CMD_STATUS_HANDLED; + showMenu (Menu::NodeType); + return BotCommandResult::Handled; } -int BotControl::cmdWaypointAddBasic (void) { - enum args { waypoint = 1, cmd, max }; +int BotControl::cmdNodeAddBasic () { + enum args { graph_cmd = 1, cmd, max }; // adding more args to args array, if not enough passed fixMissingArgs (max); - // turn waypoints on - waypoints.setEditFlag (WS_EDIT_ENABLED); + // turn graph on + graph.setEditFlag (GraphEdit::On); - waypoints.addBasic (); - msg ("Basic waypoints was added."); + graph.addBasic (); + msg ("Basic graph nodes was added."); - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } -int BotControl::cmdWaypointSave (void) { - enum args { waypoint = 1, cmd, nocheck, max }; +int BotControl::cmdNodeSave () { + enum args { graph_cmd = 1, cmd, option, 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 (); + if (getStr (option) == "nocheck") { + graph.saveGraphData (); - msg ("All waypoints has been saved and written to disk (IGNORING QUALITY CONTROL)."); + msg ("All nodes has been saved and written to disk (IGNORING QUALITY CONTROL)."); + } + else if (getStr (option) == "old") { + graph.saveOldFormat (); + + msg ("All nodes has been saved and written to disk (POD-Bot Format (.pwf))."); } else { - if (waypoints.checkNodes ()) { - waypoints.save (); - msg ("All waypoints has been saved and written to disk."); + if (graph.checkNodes (false)) { + graph.saveGraphData (); + msg ("All nodes has been saved and written to disk."); } else { - msg ("Could not save save waypoints to disk. Waypoint check has failed."); + msg ("Could not save save nodes to disk. Graph check has failed."); } } - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } -int BotControl::cmdWaypointLoad (void) { - enum args { waypoint = 1, cmd, max }; +int BotControl::cmdNodeLoad () { + enum args { graph_cmd = 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."); + // just save graph on request + if (graph.loadGraphData ()) { + msg ("Graph successfully loaded."); } else { - msg ("Could not load waypoints. See console..."); + msg ("Could not load Graph. See console..."); } - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } -int BotControl::cmdWaypointErase (void) { - enum args { waypoint = 1, cmd, iamsure, max }; +int BotControl::cmdNodeErase () { + enum args { graph_cmd = 1, cmd, iamsure, max }; // adding more args to args array, if not enough passed fixMissingArgs (max); - // prevent accidents when waypoints are deleted unintentionally + // prevent accidents when graph are deleted unintentionally if (getStr (iamsure) == "iamsure") { - waypoints.eraseFromDisk (); + graph.eraseFromDisk (); } else { - msg ("Please, append \"iamsure\" as parameter to get waypoints erased from the disk."); + msg ("Please, append \"iamsure\" as parameter to get graph erased from the disk."); } - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } -int BotControl::cmdWaypointDelete (void) { - enum args { waypoint = 1, cmd, nearest, max }; +int BotControl::cmdNodeDelete () { + enum args { graph_cmd = 1, cmd, nearest, max }; // adding more args to args array, if not enough passed fixMissingArgs (max); - // turn waypoints on - waypoints.setEditFlag (WS_EDIT_ENABLED); + // turn graph on + graph.setEditFlag (GraphEdit::On); // if "neareset" or nothing passed delete neareset, else delete by index if (getStr (nearest) == "empty" || getStr (nearest) == "nearest") { - waypoints.erase (INVALID_WAYPOINT_INDEX); + graph.erase (kInvalidNodeIndex); } else { int index = getInt (nearest); // check for existence - if (waypoints.exists (index)) { - waypoints.erase (index); - msg ("Waypoint #%d has beed deleted.", index); + if (graph.exists (index)) { + graph.erase (index); + msg ("Node #%d has beed deleted.", index); } else { - msg ("Could not delete waypoints #%d.", index); + msg ("Could not delete node #%d.", index); } } - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } -int BotControl::cmdWaypointCheck (void) { - enum args { waypoint = 1, cmd, max }; +int BotControl::cmdNodeCheck () { + enum args { graph_cmd = 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."); + if (graph.checkNodes (true)) { + msg ("Graph seems to be OK."); } - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } -int BotControl::cmdWaypointCache (void) { - enum args { waypoint = 1, cmd, nearest, max }; +int BotControl::cmdNodeCache () { + enum args { graph_cmd = 1, cmd, nearest, max }; // adding more args to args array, if not enough passed fixMissingArgs (max); - // turn waypoints on - waypoints.setEditFlag (WS_EDIT_ENABLED); + // turn graph on + graph.setEditFlag (GraphEdit::On); // if "neareset" or nothing passed delete neareset, else delete by index if (getStr (nearest) == "empty" || getStr (nearest) == "nearest") { - waypoints.cachePoint (INVALID_WAYPOINT_INDEX); + graph.cachePoint (kInvalidNodeIndex); - msg ("Nearest waypoint has been put into the memory."); + msg ("Nearest node 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); + if (graph.exists (index)) { + graph.cachePoint (index); + msg ("Node #%d has been put into the memory.", index); } else { - msg ("Could not put waypoint #%d into the memory.", index); + msg ("Could not put node #%d into the memory.", index); } } - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } -int BotControl::cmdWaypointClean (void) { - enum args { waypoint = 1, cmd, option, max }; +int BotControl::cmdNodeClean () { + enum args { graph_cmd = 1, cmd, option, max }; // adding more args to args array, if not enough passed fixMissingArgs (max); - // turn waypoints on - waypoints.setEditFlag (WS_EDIT_ENABLED); + // turn graph on + graph.setEditFlag (GraphEdit::On); // 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); + for (int i = 0; i < graph.length (); ++i) { + removed += graph.clearConnections (i); } - msg ("Done. Processed %d waypoints. %d useless paths was cleared.", waypoints.length (), removed); + msg ("Done. Processed %d nodes. %d useless paths was cleared.", graph.length (), removed); } else if (getStr (option) == "empty" || getStr (option) == "nearest") { - int removed = waypoints.clearConnections (waypoints.getEditorNeareset ()); + int removed = graph.clearConnections (graph.getEditorNeareset ()); - msg ("Done. Processed waypoint #%d. %d useless paths was cleared.", waypoints.getEditorNeareset (), removed); + msg ("Done. Processed node #%d. %d useless paths was cleared.", graph.getEditorNeareset (), removed); } else { int index = getInt (option); // check for existence - if (waypoints.exists (index)) { - int removed = waypoints.clearConnections (index); + if (graph.exists (index)) { + int removed = graph.clearConnections (index); - msg ("Done. Processed waypoint #%d. %d useless paths was cleared.", index, removed); + msg ("Done. Processed node #%d. %d useless paths was cleared.", index, removed); } else { - msg ("Could not process waypoint #%d clearance.", index); + msg ("Could not process node #%d clearance.", index); } } - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } -int BotControl::cmdWaypointSetRadius (void) { - enum args { waypoint = 1, cmd, radius, index, max }; +int BotControl::cmdNodeSetRadius () { + enum args { graph_cmd = 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; + return BotCommandResult::BadFormat; } - int radiusIndex = INVALID_WAYPOINT_INDEX; + int radiusIndex = kInvalidNodeIndex; if (getStr (index) == "empty" || getStr (index) == "nearest") { - radiusIndex = waypoints.getEditorNeareset (); + radiusIndex = graph.getEditorNeareset (); } else { radiusIndex = getInt (index); } - float value = getStr (radius).toFloat (); + float value = getStr (radius).float_ (); - waypoints.setRadius (radiusIndex, value); - msg ("Waypoint #%d has been set to radius %.2f.", radiusIndex, value); + graph.setRadius (radiusIndex, value); + msg ("Node #%d has been set to radius %.2f.", radiusIndex, value); - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } -int BotControl::cmdWaypointSetFlags (void) { - enum args { waypoint = 1, cmd, max }; +int BotControl::cmdNodeSetFlags () { + enum args { graph_cmd = 1, cmd, max }; // adding more args to args array, if not enough passed fixMissingArgs (max); - // turn waypoints on - waypoints.setEditFlag (WS_EDIT_ENABLED); + // turn graph on + graph.setEditFlag (GraphEdit::On); //show the flag menu - showMenu (BOT_MENU_WAYPOINT_FLAG); - return CMD_STATUS_HANDLED; + showMenu (Menu::NodeFlag); + return BotCommandResult::Handled; } -int BotControl::cmdWaypointTeleport (void) { - enum args { waypoint = 1, cmd, teleport_index, max }; +int BotControl::cmdNodeTeleport () { + enum args { graph_cmd = 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; + return BotCommandResult::BadFormat; } int index = getInt (teleport_index); // check for existence - if (waypoints.exists (index)) { - engfuncs.pfnSetOrigin (waypoints.getEditor (), waypoints[index].origin); + if (graph.exists (index)) { + engfuncs.pfnSetOrigin (graph.getEditor (), graph[index].origin); - msg ("You have been teleported to waypoint #%d.", index); + msg ("You have been teleported to node #%d.", index); - // turn waypoints on - waypoints.setEditFlag (WS_EDIT_ENABLED | WS_EDIT_NOCLIP); + // turn graph on + graph.setEditFlag (GraphEdit::On | GraphEdit::Noclip); } else { - msg ("Could not teleport to waypoint #%d.", index); + msg ("Could not teleport to node #%d.", index); } - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } -int BotControl::cmdWaypointPathCreate (void) { - enum args { waypoint = 1, cmd, max }; +int BotControl::cmdNodePathCreate () { + enum args { graph_cmd = 1, cmd, max }; // adding more args to args array, if not enough passed fixMissingArgs (max); - // turn waypoints on - waypoints.setEditFlag (WS_EDIT_ENABLED); + // turn graph on + graph.setEditFlag (GraphEdit::On); // choose the direction for path creation - if (m_args[cmd].find ("_both", 0) != String::INVALID_INDEX) { - waypoints.pathCreate (CONNECTION_BOTHWAYS); + if (m_args[cmd].find ("_both", 0) != String::kInvalidIndex) { + graph.pathCreate (PathConnection::Bidirectional); } - else if (m_args[cmd].find ("_in", 0) != String::INVALID_INDEX) { - waypoints.pathCreate (CONNECTION_INCOMING); + else if (m_args[cmd].find ("_in", 0) != String::kInvalidIndex) { + graph.pathCreate (PathConnection::Incoming); } - else if (m_args[cmd].find ("_out", 0) != String::INVALID_INDEX) { - waypoints.pathCreate (CONNECTION_OUTGOING); + else if (m_args[cmd].find ("_out", 0) != String::kInvalidIndex) { + graph.pathCreate (PathConnection::Outgoing); } else { - showMenu (BOT_MENU_WAYPOINT_PATH); + showMenu (Menu::NodePath); } - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } -int BotControl::cmdWaypointPathDelete (void) { - enum args { waypoint = 1, cmd, max }; +int BotControl::cmdNodePathDelete () { + enum args { graph_cmd = 1, cmd, max }; // adding more args to args array, if not enough passed fixMissingArgs (max); - // turn waypoints on - waypoints.setEditFlag (WS_EDIT_ENABLED); + // turn graph on + graph.setEditFlag (GraphEdit::On); // delete the patch - waypoints.erasePath (); + graph.erasePath (); - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } -int BotControl::cmdWaypointPathSetAutoDistance (void) { - enum args { waypoint = 1, cmd, max }; +int BotControl::cmdNodePathSetAutoDistance () { + enum args { graph_cmd = 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); + // turn graph on + graph.setEditFlag (GraphEdit::On); + showMenu (Menu::NodeAutoPath); - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } -int BotControl::cmdWaypointAcquireEditor (void) { - enum args { waypoint = 1, max }; +int BotControl::cmdNodeAcquireEditor () { + enum args { graph_cmd = 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; + return BotCommandResult::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; + if (graph.hasEditor ()) { + msg ("Sorry, players \"%s\" already acquired rights to edit graph on this server.", STRING (graph.getEditor ()->v.netname)); + return BotCommandResult::Handled; } - waypoints.setEditor (m_ent); - msg ("You're acquired rights to edit waypoints on this server. You're now able to use waypoint commands."); + graph.setEditor (m_ent); + msg ("You're acquired rights to edit graph on this server. You're now able to use graph commands."); - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } -int BotControl::cmdWaypointReleaseEditor (void) { - enum args { waypoint = 1, max }; +int BotControl::cmdNodeReleaseEditor () { + enum args { graph_cmd = 1, max }; // adding more args to args array, if not enough passed fixMissingArgs (max); - if (!waypoints.hasEditor ()) { + if (!graph.hasEditor ()) { msg ("No one is currently has rights to edit. Nothing to release."); - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } - waypoints.setEditor (nullptr); - msg ("Waypoint editor rights freed. You're now not able to use waypoint commands."); + graph.setEditor (nullptr); + msg ("Graph editor rights freed. You're now not able to use graph commands."); - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; +} + +int BotControl::cmdNodeUpload () { + enum args { graph_cmd = 1, cmd, id, max }; + + // adding more args to args array, if not enough passed + fixMissingArgs (max); + + if (!hasArg (id)) { + return BotCommandResult::BadFormat; + } + + // do not allow to upload bad graph + if (!graph.checkNodes (false)) { + msg ("Sorry, unable to upload graph file that contains errors. Please type \"wp check\" to verify graph consistency."); + return BotCommandResult::BadFormat; + } + msg ("\n"); + msg ("WARNING!"); + msg ("Graph uploaded to graph database in synchronous mode. That means if graph is big enough"); + msg ("you may notice the game freezes a bit during upload and issue request creation. Please, be patient."); + msg ("\n"); + + // six seconds is enough? + http.setTimeout (6); + + extern ConVar yb_graph_url; + + // try to upload the file + if (http.uploadFile (strings.format ("%s/", yb_graph_url.str ()), strings.format ("%sgraph/%s.graph", graph.getDataDirectory (false), game.getMapName ()))) { + msg ("Graph file was uploaded and Issue Request has been created for review."); + msg ("As soon as database administrator review your upload request, your graph will"); + msg ("be available to download for all YaPB users. You can check your issue request at:"); + msg ("URL: https://github.com/jeefo/yapb-graph/issues"); + msg ("\n"); + msg ("Thank you."); + msg ("\n"); + } + else { + String status; + auto code = http.getLastStatusCode (); + + if (code == HttpClientResult::Forbidden) { + status = "AlreadyExists"; + } + else if (code == HttpClientResult::NotFound) { + status = "AccessDenied"; + } + else { + status.assignf ("%d", code); + } + msg ("Something went wrong with uploading. Come back later. (%s)", status.chars ()); + msg ("\n"); + if (code == HttpClientResult::Forbidden) { + msg ("You should create issue-request manually for this graph"); + msg ("as it's already exists in database, can't overwrite. Sorry..."); + } + else { + msg ("There is an internal error, or somethingis totally wrong with"); + msg ("your files, and they are not passed sanity checks. Sorry..."); + } + msg ("\n"); + } + return BotCommandResult::Handled; } int BotControl::menuMain (int item) { - showMenu (BOT_MENU_INVALID); // reset menu display + showMenu (Menu::None); // reset menu display switch (item) { case 1: m_isMenuFillCommand = false; - showMenu (BOT_MENU_CONTROL); + showMenu (Menu::Control); break; case 2: - showMenu (BOT_MENU_FEATURES); + showMenu (Menu::Features); break; case 3: m_isMenuFillCommand = true; - showMenu (BOT_MENU_TEAM_SELECT); + showMenu (Menu::TeamSelect); break; case 4: @@ -731,72 +805,72 @@ int BotControl::menuMain (int item) { break; case 10: - showMenu (BOT_MENU_INVALID); + showMenu (Menu::None); break; default: - showMenu (BOT_MENU_MAIN); + showMenu (Menu::Main); break; } - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } int BotControl::menuFeatures (int item) { - showMenu (BOT_MENU_INVALID); // reset menu display + showMenu (Menu::None); // reset menu display switch (item) { case 1: - showMenu (BOT_MENU_WEAPON_MODE); + showMenu (Menu::WeaponMode); break; case 2: - showMenu (waypoints.hasEditor () ? BOT_MENU_WAYPOINT_MAIN_PAGE1 : BOT_MENU_FEATURES); + showMenu (graph.hasEditor () ? Menu::NodeMainPage1 : Menu::Features); break; case 3: - showMenu (BOT_MENU_PERSONALITY); + showMenu (Menu::Personality); break; case 4: extern ConVar yb_debug; - yb_debug.set (yb_debug.integer () ^ 1); + yb_debug.set (yb_debug.int_ () ^ 1); - showMenu (BOT_MENU_FEATURES); + showMenu (Menu::Features); break; case 5: if (util.isAlive (m_ent)) { - showMenu (BOT_MENU_COMMANDS); + showMenu (Menu::Commands); } else { - showMenu (BOT_MENU_INVALID); // reset menu display + showMenu (Menu::None); // reset menu display msg ("You're dead, and have no access to this menu"); } break; case 10: - showMenu (BOT_MENU_INVALID); + showMenu (Menu::None); break; } - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } int BotControl::menuControl (int item) { - showMenu (BOT_MENU_INVALID); // reset menu display + showMenu (Menu::None); // reset menu display switch (item) { case 1: bots.createRandom (true); - showMenu (BOT_MENU_CONTROL); + showMenu (Menu::Control); break; case 2: - showMenu (BOT_MENU_DIFFICULTY); + showMenu (Menu::Difficulty); break; case 3: bots.kickRandom (); - showMenu (BOT_MENU_CONTROL); + showMenu (Menu::Control); break; case 4: @@ -808,14 +882,14 @@ int BotControl::menuControl (int item) { break; case 10: - showMenu (BOT_MENU_INVALID); + showMenu (Menu::None); break; } - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } int BotControl::menuWeaponMode (int item) { - showMenu (BOT_MENU_INVALID); // reset menu display + showMenu (Menu::None); // reset menu display switch (item) { case 1: @@ -826,19 +900,19 @@ int BotControl::menuWeaponMode (int item) { case 6: case 7: bots.setWeaponMode (item); - showMenu (BOT_MENU_WEAPON_MODE); + showMenu (Menu::WeaponMode); break; case 10: - showMenu (BOT_MENU_INVALID); + showMenu (Menu::None); break; } - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } int BotControl::menuPersonality (int item) { if (m_isMenuFillCommand) { - showMenu (BOT_MENU_INVALID); // reset menu display + showMenu (Menu::None); // reset menu display switch (item) { case 1: @@ -846,16 +920,16 @@ int BotControl::menuPersonality (int item) { case 3: case 4: bots.serverFill (m_menuServerFillTeam, item - 2, m_interMenuData[0]); - showMenu (BOT_MENU_INVALID); + showMenu (Menu::None); break; case 10: - showMenu (BOT_MENU_INVALID); + showMenu (Menu::None); break; } - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } - showMenu (BOT_MENU_INVALID); // reset menu display + showMenu (Menu::None); // reset menu display switch (item) { case 1: @@ -863,18 +937,18 @@ int BotControl::menuPersonality (int item) { case 3: case 4: m_interMenuData[3] = item - 2; - showMenu (BOT_MENU_TEAM_SELECT); + showMenu (Menu::TeamSelect); break; case 10: - showMenu (BOT_MENU_INVALID); + showMenu (Menu::None); break; } - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } int BotControl::menuDifficulty (int item) { - showMenu (BOT_MENU_INVALID); // reset menu display + showMenu (Menu::None); // reset menu display switch (item) { case 1: @@ -898,17 +972,17 @@ int BotControl::menuDifficulty (int item) { break; case 10: - showMenu (BOT_MENU_INVALID); + showMenu (Menu::None); break; } - showMenu (BOT_MENU_PERSONALITY); + showMenu (Menu::Personality); - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } int BotControl::menuTeamSelect (int item) { if (m_isMenuFillCommand) { - showMenu (BOT_MENU_INVALID); // reset menu display + showMenu (Menu::None); // reset menu display if (item < 3) { extern ConVar mp_limitteams, mp_autoteambalance; @@ -923,16 +997,16 @@ int BotControl::menuTeamSelect (int item) { case 2: case 5: m_menuServerFillTeam = item; - showMenu (BOT_MENU_DIFFICULTY); + showMenu (Menu::Difficulty); break; case 10: - showMenu (BOT_MENU_INVALID); + showMenu (Menu::None); break; } - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } - showMenu (BOT_MENU_INVALID); // reset menu display + showMenu (Menu::None); // reset menu display switch (item) { case 1: @@ -945,19 +1019,19 @@ int BotControl::menuTeamSelect (int 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); + showMenu (item == 1 ? Menu::TerroristSelect : Menu::CTSelect); } break; case 10: - showMenu (BOT_MENU_INVALID); + showMenu (Menu::None); break; } - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } int BotControl::menuClassSelect (int item) { - showMenu (BOT_MENU_INVALID); // reset menu display + showMenu (Menu::None); // reset menu display switch (item) { case 1: @@ -970,14 +1044,14 @@ int BotControl::menuClassSelect (int item) { break; case 10: - showMenu (BOT_MENU_INVALID); + showMenu (Menu::None); break; } - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } int BotControl::menuCommands (int item) { - showMenu (BOT_MENU_INVALID); // reset menu display + showMenu (Menu::None); // reset menu display Bot *bot = nullptr; switch (item) { @@ -991,7 +1065,7 @@ int BotControl::menuCommands (int item) { bot->resetDoubleJump (); } } - showMenu (BOT_MENU_COMMANDS); + showMenu (Menu::Commands); break; case 3: @@ -999,90 +1073,90 @@ int BotControl::menuCommands (int item) { 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); + showMenu (Menu::Commands); break; case 10: - showMenu (BOT_MENU_INVALID); + showMenu (Menu::None); break; } - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } -int BotControl::menuWaypointPage1 (int item) { - showMenu (BOT_MENU_INVALID); // reset menu display +int BotControl::menuGraphPage1 (int item) { + showMenu (Menu::None); // reset menu display switch (item) { case 1: - if (waypoints.hasEditFlag (WS_EDIT_ENABLED)) { - waypoints.clearEditFlag (WS_EDIT_ENABLED); + if (graph.hasEditFlag (GraphEdit::On)) { + graph.clearEditFlag (GraphEdit::On); enableDrawModels (false); - msg ("Waypoint editor has been disabled."); + msg ("Graph editor has been disabled."); } else { - waypoints.setEditFlag (WS_EDIT_ENABLED); + graph.setEditFlag (GraphEdit::On); enableDrawModels (true); - msg ("Waypoint editor has been enabled."); + msg ("Graph editor has been enabled."); } - showMenu (BOT_MENU_WAYPOINT_MAIN_PAGE1); + showMenu (Menu::NodeMainPage1); break; case 2: - waypoints.setEditFlag (WS_EDIT_ENABLED); - waypoints.cachePoint (INVALID_WAYPOINT_INDEX); + graph.setEditFlag (GraphEdit::On); + graph.cachePoint (kInvalidNodeIndex); - showMenu (BOT_MENU_WAYPOINT_MAIN_PAGE1); + showMenu (Menu::NodeMainPage1); break; case 3: - waypoints.setEditFlag (WS_EDIT_ENABLED); - showMenu (BOT_MENU_WAYPOINT_PATH); + graph.setEditFlag (GraphEdit::On); + showMenu (Menu::NodePath); break; case 4: - waypoints.setEditFlag (WS_EDIT_ENABLED); - waypoints.erasePath (); + graph.setEditFlag (GraphEdit::On); + graph.erasePath (); - showMenu (BOT_MENU_WAYPOINT_MAIN_PAGE1); + showMenu (Menu::NodeMainPage1); break; case 5: - waypoints.setEditFlag (WS_EDIT_ENABLED); - showMenu (BOT_MENU_WAYPOINT_TYPE); + graph.setEditFlag (GraphEdit::On); + showMenu (Menu::NodeType); break; case 6: - waypoints.setEditFlag (WS_EDIT_ENABLED); - waypoints.erase (INVALID_WAYPOINT_INDEX); + graph.setEditFlag (GraphEdit::On); + graph.erase (kInvalidNodeIndex); - showMenu (BOT_MENU_WAYPOINT_MAIN_PAGE1); + showMenu (Menu::NodeMainPage1); break; case 7: - waypoints.setEditFlag (WS_EDIT_ENABLED); - showMenu (BOT_MENU_WAYPOINT_AUTOPATH); + graph.setEditFlag (GraphEdit::On); + showMenu (Menu::NodeAutoPath); break; case 8: - waypoints.setEditFlag (WS_EDIT_ENABLED); - showMenu (BOT_MENU_WAYPOINT_RADIUS); + graph.setEditFlag (GraphEdit::On); + showMenu (Menu::NodeRadius); break; case 9: - showMenu (BOT_MENU_WAYPOINT_MAIN_PAGE2); + showMenu (Menu::NodeMainPage2); break; case 10: - showMenu (BOT_MENU_INVALID); + showMenu (Menu::None); break; } - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } -int BotControl::menuWaypointPage2 (int item) { - showMenu (BOT_MENU_INVALID); // reset menu display +int BotControl::menuGraphPage2 (int item) { + showMenu (Menu::None); // reset menu display switch (item) { case 1: { @@ -1094,122 +1168,122 @@ int BotControl::menuWaypointPage2 (int item) { int sniperPoints = 0; int noHostagePoints = 0; - for (int i = 0; i < waypoints.length (); i++) { - Path &path = waypoints[i]; + for (int i = 0; i < graph.length (); ++i) { + Path &path = graph[i]; - if (path.flags & FLAG_TF_ONLY) { + if (path.flags & NodeFlag::TerroristOnly) { terrPoints++; } - if (path.flags & FLAG_CF_ONLY) { + if (path.flags & NodeFlag::CTOnly) { ctPoints++; } - if (path.flags & FLAG_GOAL) { + if (path.flags & NodeFlag::Goal) { goalPoints++; } - if (path.flags & FLAG_RESCUE) { + if (path.flags & NodeFlag::Rescue) { rescuePoints++; } - if (path.flags & FLAG_CAMP) { + if (path.flags & NodeFlag::Camp) { campPoints++; } - if (path.flags & FLAG_SNIPER) { + if (path.flags & NodeFlag::Sniper) { sniperPoints++; } - if (path.flags & FLAG_NOHOSTAGE) { + if (path.flags & NodeFlag::NoHostage) { noHostagePoints++; } } - msg ("Waypoints: %d - T Points: %d\n" + msg ("Nodes: %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); + graph.length (), terrPoints, ctPoints, goalPoints, rescuePoints, campPoints, noHostagePoints, sniperPoints); - showMenu (BOT_MENU_WAYPOINT_MAIN_PAGE2); + showMenu (Menu::NodeMainPage2); } break; case 2: - waypoints.setEditFlag (WS_EDIT_ENABLED); + graph.setEditFlag (GraphEdit::On); - if (waypoints.hasEditFlag (WS_EDIT_AUTO)) { - waypoints.clearEditFlag (WS_EDIT_AUTO); + if (graph.hasEditFlag (GraphEdit::Auto)) { + graph.clearEditFlag (GraphEdit::Auto); } else { - waypoints.setEditFlag (WS_EDIT_AUTO); + graph.setEditFlag (GraphEdit::Auto); } - msg ("Auto-Waypoint %s", waypoints.hasEditFlag (WS_EDIT_AUTO) ? "Enabled" : "Disabled"); + msg ("Auto-Add-Nodes %s", graph.hasEditFlag (GraphEdit::Auto) ? "Enabled" : "Disabled"); - showMenu (BOT_MENU_WAYPOINT_MAIN_PAGE2); + showMenu (Menu::NodeMainPage2); break; case 3: - waypoints.setEditFlag (WS_EDIT_ENABLED); - showMenu (BOT_MENU_WAYPOINT_FLAG); + graph.setEditFlag (GraphEdit::On); + showMenu (Menu::NodeFlag); break; case 4: - if (waypoints.checkNodes ()) { - waypoints.save (); + if (graph.checkNodes (true)) { + graph.saveGraphData (); } else { - msg ("Waypoint not saved\nThere are errors, see console"); + msg ("Graph not saved\nThere are errors. See console..."); } - showMenu (BOT_MENU_WAYPOINT_MAIN_PAGE2); + showMenu (Menu::NodeMainPage2); break; case 5: - waypoints.save (); - showMenu (BOT_MENU_WAYPOINT_MAIN_PAGE2); + graph.saveGraphData (); + showMenu (Menu::NodeMainPage2); break; case 6: - waypoints.load (); - showMenu (BOT_MENU_WAYPOINT_MAIN_PAGE2); + graph.loadGraphData (); + showMenu (Menu::NodeMainPage2); break; case 7: - if (waypoints.checkNodes ()) { + if (graph.checkNodes (true)) { msg ("Nodes works fine"); } else { msg ("There are errors, see console"); } - showMenu (BOT_MENU_WAYPOINT_MAIN_PAGE2); + showMenu (Menu::NodeMainPage2); break; case 8: - waypoints.setEditFlag (WS_EDIT_ENABLED | WS_EDIT_NOCLIP); - showMenu (BOT_MENU_WAYPOINT_MAIN_PAGE2); + graph.setEditFlag (GraphEdit::On | GraphEdit::Noclip); + showMenu (Menu::NodeMainPage2); break; case 9: - showMenu (BOT_MENU_WAYPOINT_MAIN_PAGE1); + showMenu (Menu::NodeMainPage1); break; } - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } -int BotControl::menuWaypointRadius (int item) { - showMenu (BOT_MENU_INVALID); // reset menu display - waypoints.setEditFlag (WS_EDIT_ENABLED); // turn waypoints on in case +int BotControl::menuGraphRadius (int item) { + showMenu (Menu::None); // reset menu display + graph.setEditFlag (GraphEdit::On); // turn graph 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); + graph.setRadius (kInvalidNodeIndex, radius[item - 1]); + showMenu (Menu::NodeRadius); } - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } -int BotControl::menuWaypointType (int item) { - showMenu (BOT_MENU_INVALID); // reset menu display +int BotControl::menuGraphType (int item) { + showMenu (Menu::None); // reset menu display switch (item) { case 1: @@ -1219,68 +1293,68 @@ int BotControl::menuWaypointType (int item) { case 5: case 6: case 7: - waypoints.push (item - 1); - showMenu (BOT_MENU_WAYPOINT_TYPE); + graph.add (item - 1); + showMenu (Menu::NodeType); break; case 8: - waypoints.push (100); - showMenu (BOT_MENU_WAYPOINT_TYPE); + graph.add (100); + showMenu (Menu::NodeType); break; case 9: - waypoints.startLearnJump (); - showMenu (BOT_MENU_WAYPOINT_TYPE); + graph.startLearnJump (); + showMenu (Menu::NodeType); break; case 10: - showMenu (BOT_MENU_INVALID); + showMenu (Menu::None); break; } - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } -int BotControl::menuWaypointFlag (int item) { - showMenu (BOT_MENU_INVALID); // reset menu display +int BotControl::menuGraphFlag (int item) { + showMenu (Menu::None); // reset menu display switch (item) { case 1: - waypoints.toggleFlags (FLAG_NOHOSTAGE); - showMenu (BOT_MENU_WAYPOINT_FLAG); + graph.toggleFlags (NodeFlag::NoHostage); + showMenu (Menu::NodeFlag); break; case 2: - waypoints.toggleFlags (FLAG_TF_ONLY); - showMenu (BOT_MENU_WAYPOINT_FLAG); + graph.toggleFlags (NodeFlag::TerroristOnly); + showMenu (Menu::NodeFlag); break; case 3: - waypoints.toggleFlags (FLAG_CF_ONLY); - showMenu (BOT_MENU_WAYPOINT_FLAG); + graph.toggleFlags (NodeFlag::CTOnly); + showMenu (Menu::NodeFlag); break; case 4: - waypoints.toggleFlags (FLAG_LIFT); - showMenu (BOT_MENU_WAYPOINT_FLAG); + graph.toggleFlags (NodeFlag::Lift); + showMenu (Menu::NodeFlag); break; case 5: - waypoints.toggleFlags (FLAG_SNIPER); - showMenu (BOT_MENU_WAYPOINT_FLAG); + graph.toggleFlags (NodeFlag::Sniper); + showMenu (Menu::NodeFlag); break; } - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } int BotControl::menuAutoPathDistance (int item) { - showMenu (BOT_MENU_INVALID); // reset menu display + showMenu (Menu::None); // 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); + graph.setAutoPathDistance (result); } if (cr::fzero (result)) { @@ -1289,13 +1363,13 @@ int BotControl::menuAutoPathDistance (int item) { else { msg ("Autopath distance is set to %.2f.", result); } - showMenu (BOT_MENU_WAYPOINT_AUTOPATH); + showMenu (Menu::NodeAutoPath); - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } int BotControl::menuKickPage1 (int item) { - showMenu (BOT_MENU_INVALID); // reset menu display + showMenu (Menu::None); // reset menu display switch (item) { case 1: @@ -1315,14 +1389,14 @@ int BotControl::menuKickPage1 (int item) { break; case 10: - showMenu (BOT_MENU_CONTROL); + showMenu (Menu::Control); break; } - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } int BotControl::menuKickPage2 (int item) { - showMenu (BOT_MENU_INVALID); // reset menu display + showMenu (Menu::None); // reset menu display switch (item) { case 1: @@ -1345,11 +1419,11 @@ int BotControl::menuKickPage2 (int item) { kickBotByMenu (1); break; } - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } int BotControl::menuKickPage3 (int item) { - showMenu (BOT_MENU_INVALID); // reset menu display + showMenu (Menu::None); // reset menu display switch (item) { case 1: @@ -1372,11 +1446,11 @@ int BotControl::menuKickPage3 (int item) { kickBotByMenu (2); break; } - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } int BotControl::menuKickPage4 (int item) { - showMenu (BOT_MENU_INVALID); // reset menu display + showMenu (Menu::None); // reset menu display switch (item) { case 1: @@ -1395,36 +1469,36 @@ int BotControl::menuKickPage4 (int item) { kickBotByMenu (3); break; } - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } -int BotControl::menuWaypointPath (int item) { - showMenu (BOT_MENU_INVALID); // reset menu display +int BotControl::menuGraphPath (int item) { + showMenu (Menu::None); // reset menu display switch (item) { case 1: - waypoints.pathCreate (CONNECTION_OUTGOING); - showMenu (BOT_MENU_WAYPOINT_PATH); + graph.pathCreate (PathConnection::Outgoing); + showMenu (Menu::NodePath); break; case 2: - waypoints.pathCreate (CONNECTION_INCOMING); - showMenu (BOT_MENU_WAYPOINT_PATH); + graph.pathCreate (PathConnection::Incoming); + showMenu (Menu::NodePath); break; case 3: - waypoints.pathCreate (CONNECTION_BOTHWAYS); - showMenu (BOT_MENU_WAYPOINT_PATH); + graph.pathCreate (PathConnection::Bidirectional); + showMenu (Menu::NodePath); break; case 10: - showMenu (BOT_MENU_INVALID); + showMenu (Menu::None); break; } - return CMD_STATUS_HANDLED; + return BotCommandResult::Handled; } -bool BotControl::executeCommands (void) { +bool BotControl::executeCommands () { if (m_args.empty ()) { return false; } @@ -1433,16 +1507,16 @@ bool BotControl::executeCommands (void) { if (m_args[0] != "yb" && m_args[0] != "yapb") { return false; } - Client &client = util.getClient (game.indexOfEntity (m_ent) - 1); + Client &client = util.getClient (game.indexOfPlayer (m_ent)); // 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."); + if (m_ent != game.getLocalEntity () && !(client.flags & ClientFlags::Admin)) { + msg ("Access to %s commands is restricted.", PRODUCT_SHORT_NAME); return true; } auto aliasMatch = [] (String &test, const String &cmd, String &aliasName) -> bool { - for (auto &alias : test.split ("|")) { + for (auto &alias : test.split ("/")) { if (alias == cmd) { aliasName = alias; return true; @@ -1453,15 +1527,15 @@ bool BotControl::executeCommands (void) { String cmd; // give some help - if (m_args.length () > 1 && stricmp ("help", m_args[1].chars ()) == 0) { + if (m_args.length () > 0 && m_args[1] == "help") { 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 ()); + for (auto &alias : item.name.split ("/")) { + aliases.appendf ("%s, ", alias.chars ()); } aliases.rtrim (", "); msg ("Aliases: %s", aliases.chars ()); @@ -1486,7 +1560,7 @@ bool BotControl::executeCommands (void) { msg ("valid commands are: "); for (auto &item : m_cmds) { - msg (" %s - %s", item.name.split ("|")[0].chars (), item.help.chars ()); + msg (" %s - %s", item.name.split ("/")[0].chars (), item.help.chars ()); } return true; } @@ -1499,15 +1573,15 @@ bool BotControl::executeCommands (void) { auto alias = cmd.chars (); switch ((this->*item.handler) ()) { - case CMD_STATUS_HANDLED: + case BotCommandResult::Handled: default: break; - case CMD_STATUS_LISTENSERV: + case BotCommandResult::ListenServer: msg ("Command \"%s %s\" is only available from the listenserver console.", root, alias); break; - case CMD_STATUS_BADFORMAT: + case BotCommandResult::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; } @@ -1520,21 +1594,21 @@ bool BotControl::executeCommands (void) { return true; } -bool BotControl::executeMenus (void) { +bool BotControl::executeMenus () { if (!util.isPlayer (m_ent) || game.isBotCmd ()) { return false; } - auto &issuer = util.getClient (game.indexOfEntity (m_ent) - 1); + auto &issuer = util.getClient (game.indexOfPlayer (m_ent)); // check if it's menu select, and some key pressed - if (getStr (0) != "menuselect" || getStr (1).empty () || issuer.menu == BOT_MENU_INVALID) { + if (getStr (0) != "menuselect" || getStr (1).empty () || issuer.menu == Menu::None) { return false; } // let's get handle for (auto &menu : m_menus) { if (menu.ident == issuer.menu) { - return (this->*menu.handler) (getStr (1).toInt32 ()); + return (this->*menu.handler) (getStr (1).int_ ()); } } return false; @@ -1551,10 +1625,10 @@ void BotControl::showMenu (int id) { // 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)); + // make menu looks best + if (!(game.is (GameFlags::Legacy))) { + for (int j = 0; j < 10; ++j) { + parsed.text.replace (strings.format ("%d.", j), strings.format ("\\r%d.\\w", j)); } } } @@ -1564,10 +1638,10 @@ void BotControl::showMenu (int id) { if (!util.isPlayer (m_ent)) { return; } - Client &client = util.getClient (game.indexOfEntity (m_ent) - 1); + Client &client = util.getClient (game.indexOfPlayer (m_ent)); - if (id == BOT_MENU_INVALID) { - MessageWriter (MSG_ONE_UNRELIABLE, game.getMessageId (NETMSG_SHOWMENU), Vector::null (), m_ent) + if (id == Menu::None) { + MessageWriter (MSG_ONE_UNRELIABLE, game.getMessageId (NetMsg::ShowMenu), nullvec, m_ent) .writeShort (0) .writeChar (0) .writeByte (0) @@ -1579,23 +1653,23 @@ void BotControl::showMenu (int id) { 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 (); + const char *text = (game.is (GameFlags::Xash3D | GameFlags::Mobility) && !yb_display_menu_text.bool_ ()) ? " " : display.text.chars (); MessageWriter msg; while (strlen (text) >= 64) { - msg.start (MSG_ONE_UNRELIABLE, game.getMessageId (NETMSG_SHOWMENU), Vector::null (), m_ent) + msg.start (MSG_ONE_UNRELIABLE, game.getMessageId (NetMsg::ShowMenu), nullvec, m_ent) .writeShort (display.slots) .writeChar (-1) .writeByte (1); - for (int i = 0; i < 64; i++) { + 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) + MessageWriter (MSG_ONE_UNRELIABLE, game.getMessageId (NetMsg::ShowMenu), nullvec, m_ent) .writeShort (display.slots) .writeChar (-1) .writeByte (0) @@ -1613,32 +1687,32 @@ void BotControl::kickBotByMenu (int page) { } String menus; - menus.assign ("\\yBots Remove Menu (%d/4):\\w\n\n", page); + menus.assignf ("\\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); + for (int i = menuKey; i < page * 8; ++i) { + auto bot = bots[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"); + menus.appendf ("%1.1d. %s%s\n", i - menuKey + 1, STRING (bot->pev->netname), bot->m_team == Team::CT ? " \\y(CT)\\w" : " \\r(T)\\w"); } else { - menus.append ("\\d %1.1d. Not a Bot\\w\n", i - menuKey + 1); + menus.appendf ("\\d %1.1d. Not a Bot\\w\n", i - menuKey + 1); } } - menus.append ("\n%s 0. Back", (page == 4) ? "" : " 9. More...\n"); + menus.appendf ("\n%s 0. Back", (page == 4) ? "" : " 9. More...\n"); // force to clear current menu - showMenu (BOT_MENU_INVALID); + showMenu (Menu::None); - auto id = BOT_MENU_KICK_PAGE_1 - 1 + page; + auto id = Menu::KickPage1 - 1 + page; for (auto &menu : m_menus) { if (menu.ident == id) { - menu.slots = menuKeys & static_cast (-1); + menu.slots = menuKeys & static_cast (-1); menu.text = menus; break; @@ -1647,28 +1721,6 @@ void BotControl::kickBotByMenu (int page) { 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; @@ -1677,41 +1729,41 @@ void BotControl::assignAdminRights (edict_t *ent, char *infobuffer) { const String &password = yb_password.str (); if (!key.empty () && !password.empty ()) { - auto &client = util.getClient (game.indexOfEntity (ent) - 1); + auto &client = util.getClient (game.indexOfPlayer (ent)); if (password == engfuncs.pfnInfoKeyValue (infobuffer, key.chars ())) { - client.flags |= CF_ADMIN; + client.flags |= ClientFlags::Admin; } else { - client.flags &= ~CF_ADMIN; + client.flags &= ~ClientFlags::Admin; } } } -void BotControl::maintainAdminRights (void) { +void BotControl::maintainAdminRights () { if (!game.isDedicated ()) { return; } - for (int i = 0; i < game.maxClients (); i++) { - edict_t *player = game.entityOfIndex (i + 1); + for (int i = 0; i < game.maxClients (); ++i) { + edict_t *player = game.playerOfIndex (i); // 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 (client.flags & ClientFlags::Admin) { if (util.isEmptyStr (yb_password_key.str ()) && util.isEmptyStr (yb_password.str ())) { - client.flags &= ~CF_ADMIN; + client.flags &= ~ClientFlags::Admin; } else if (!!strcmp (yb_password.str (), engfuncs.pfnInfoKeyValue (engfuncs.pfnGetInfoKeyBuffer (client.ent), const_cast (yb_password_key.str ())))) { - client.flags &= ~CF_ADMIN; + client.flags &= ~ClientFlags::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 ())) { + else if (!(client.flags & ClientFlags::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; + client.flags |= ClientFlags::Admin; game.print ("Player %s had gained full remote access to %s.", STRING (player->v.netname), PRODUCT_SHORT_NAME); } } @@ -1719,35 +1771,32 @@ void BotControl::maintainAdminRights (void) { } } -BotControl::BotControl (void) { +BotControl::BotControl () { m_ent = nullptr; m_isFromConsole = false; m_isMenuFillCommand = false; + m_rapidOutput = 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); + m_cmds.emplace ("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); + m_cmds.emplace ("kick/kickone/kick_ct/kick_t/kickbot_ct/kickbot_t", "kick [team]", "Kicks off the random bot from the game.", &BotControl::cmdKickBot); + m_cmds.emplace ("removebots/kickbots/kickall", "removebots [instant]", "Kicks all the bots from the game.", &BotControl::cmdKickBots); + m_cmds.emplace ("kill/killbots/killall/kill_ct/kill_t", "kill [team]", "Kills the specified team / all the bots.", &BotControl::cmdKillBots); + m_cmds.emplace ("fill/fillserver", "fill [team[count[difficulty[pesonality]]]]", "Fill the server (add bots) with specified parameters.", &BotControl::cmdFill); + m_cmds.emplace ("vote/votemap", "vote [map_id]", "Forces all the bot to vote to specified map.", &BotControl::cmdVote); + m_cmds.emplace ("weapons/weaponmode", "weapons [knife|pistol|shotgun|smg|rifle|sniper|standard]", "Sets the bots weapon mode to use", &BotControl::cmdWeaponMode); + m_cmds.emplace ("menu/botmenu", "menu [cmd]", "Opens the main bot menu, or command menu if specified.", &BotControl::cmdMenu); + m_cmds.emplace ("version/ver/about", "version [no arguments]", "Displays version information about bot build.", &BotControl::cmdVersion); + m_cmds.emplace ("graphmenu/wpmenu/wptmenu", "graphmenu [noarguments]", "Opens and displays bots graph edtior.", &BotControl::cmdNodeMenu); + m_cmds.emplace ("list/listbots", "list [noarguments]", "Lists the bots currently playing on server.", &BotControl::cmdList); + m_cmds.emplace ("graph/wp/wpt/waypoint", "graph [help]", "Handles graph operations.", &BotControl::cmdNode); // declare the menus createMenus (); } -void BotControl::handleEngineCommands (void) { - ctrl.setArgs (cr::move (ctrl.collectArgs ())); +void BotControl::handleEngineCommands () { + ctrl.collectArgs (); ctrl.setIssuer (game.getLocalEntity ()); ctrl.setFromConsole (true); @@ -1755,7 +1804,7 @@ void BotControl::handleEngineCommands (void) { } bool BotControl::handleClientCommands (edict_t *ent) { - setArgs (cr::move (collectArgs ())); + ctrl.collectArgs (); setIssuer (ent); setFromConsole (true); @@ -1763,7 +1812,7 @@ bool BotControl::handleClientCommands (edict_t *ent) { } bool BotControl::handleMenuCommands (edict_t *ent) { - setArgs (cr::move (collectArgs ())); + ctrl.collectArgs (); setIssuer (ent); setFromConsole (false); @@ -1791,11 +1840,11 @@ void BotControl::enableDrawModels (bool enable) { } } -void BotControl::createMenus (void) { +void BotControl::createMenus () { auto keys = [] (int numKeys) -> int { int result = 0; - for (int i = 0; i < numKeys; i++) { + for (int i = 0; i < numKeys; ++i) { result |= cr::bit (i); } result |= cr::bit (9); @@ -1804,20 +1853,20 @@ void BotControl::createMenus (void) { }; // bots main menu - m_menus.push ({ - BOT_MENU_MAIN, keys (4), + m_menus.emplace ( + 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) }); + &BotControl::menuMain); // bots features menu - m_menus.push ({ - BOT_MENU_FEATURES, keys (5), + m_menus.emplace ( + Menu::Features, keys (5), "\\yBots Features\\w\n\n" "1. Weapon Mode Menu\n" "2. Waypoint Menu\n" @@ -1825,11 +1874,11 @@ void BotControl::createMenus (void) { "4. Toggle Debug Mode\n" "5. Command Menu\n\n" "0. Exit", - cr::forward (&BotControl::menuFeatures) }); + &BotControl::menuFeatures); // bot control menu - m_menus.push ({ - BOT_MENU_CONTROL, keys (5), + m_menus.emplace ( + Menu::Control, keys (5), "\\yBots Control Menu\\w\n\n" "1. Add a Bot, Quick\n" "2. Add a Bot, Specified\n\n" @@ -1837,11 +1886,11 @@ void BotControl::createMenus (void) { "4. Remove All Bots\n\n" "5. Remove Bot Menu\n\n" "0. Exit", - cr::forward (&BotControl::menuControl) }); + &BotControl::menuControl); // weapon mode select menu - m_menus.push ({ - BOT_MENU_WEAPON_MODE, keys (7), + m_menus.emplace ( + Menu::WeaponMode, keys (7), "\\yBots Weapon Mode\\w\n\n" "1. Knives only\n" "2. Pistols only\n" @@ -1851,22 +1900,22 @@ void BotControl::createMenus (void) { "6. Sniper Weapons only\n" "7. All Weapons\n\n" "0. Exit", - cr::forward (&BotControl::menuWeaponMode) }); + &BotControl::menuWeaponMode); // personality select menu - m_menus.push ({ - BOT_MENU_PERSONALITY, keys (4), + m_menus.emplace ( + 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) }); + &BotControl::menuPersonality); // difficulty select menu - m_menus.push ({ - BOT_MENU_DIFFICULTY, keys (5), + m_menus.emplace ( + Menu::Difficulty, keys (5), "\\yBots Difficulty Level\\w\n\n" "1. Newbie\n" "2. Average\n" @@ -1874,21 +1923,21 @@ void BotControl::createMenus (void) { "4. Professional\n" "5. Godlike\n\n" "0. Exit", - cr::forward (&BotControl::menuDifficulty) }); + &BotControl::menuDifficulty); // team select menu - m_menus.push ({ - BOT_MENU_TEAM_SELECT, keys (5), + m_menus.emplace ( + Menu::TeamSelect, 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) }); + &BotControl::menuTeamSelect); // terrorist model select menu - m_menus.push ({ - BOT_MENU_TERRORIST_SELECT, keys (5), + m_menus.emplace ( + Menu::TerroristSelect, keys (5), "\\ySelect an appearance\\w\n\n" "1. Phoenix Connexion\n" "2. L337 Krew\n" @@ -1896,11 +1945,11 @@ void BotControl::createMenus (void) { "4. Guerilla Warfare\n\n" "5. Auto-select\n\n" "0. Exit", - cr::forward (&BotControl::menuClassSelect) }); + &BotControl::menuClassSelect); // counter-terrorist model select menu - m_menus.push ({ - BOT_MENU_CT_SELECT, keys (5), + m_menus.emplace ( + Menu::CTSelect, keys (5), "\\ySelect an appearance\\w\n\n" "1. Seal Team 6 (DEVGRU)\n" "2. German GSG-9\n" @@ -1908,22 +1957,22 @@ void BotControl::createMenus (void) { "4. French GIGN\n\n" "5. Auto-select\n\n" "0. Exit", - cr::forward (&BotControl::menuClassSelect) }); + &BotControl::menuClassSelect); // command menu - m_menus.push ({ - BOT_MENU_COMMANDS, keys (4), + m_menus.emplace ( + 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) }); + &BotControl::menuCommands); // main waypoint menu - m_menus.push ({ - BOT_MENU_WAYPOINT_MAIN_PAGE1, keys (9), + m_menus.emplace ( + Menu::NodeMainPage1, keys (9), "\\yWaypoint Operations (Page 1)\\w\n\n" "1. Show/Hide waypoints\n" "2. Cache waypoint\n" @@ -1935,11 +1984,11 @@ void BotControl::createMenus (void) { "8. Set Radius\n\n" "9. Next...\n\n" "0. Exit", - cr::forward (&BotControl::menuWaypointPage1) }); + &BotControl::menuGraphPage1); // main waypoint menu (page 2) - m_menus.push ({ - BOT_MENU_WAYPOINT_MAIN_PAGE2, keys (9), + m_menus.emplace ( + Menu::NodeMainPage2, keys (9), "\\yWaypoint Operations (Page 2)\\w\n\n" "1. Waypoint stats\n" "2. Autowaypoint on/off\n" @@ -1951,11 +2000,11 @@ void BotControl::createMenus (void) { "8. Noclip cheat on/off\n\n" "9. Previous...\n\n" "0. Exit", - cr::forward (&BotControl::menuWaypointPage2) }); + &BotControl::menuGraphPage2); // select waypoint radius menu - m_menus.push ({ - BOT_MENU_WAYPOINT_RADIUS, keys (9), + m_menus.emplace ( + Menu::NodeRadius, keys (9), "\\yWaypoint Radius\\w\n\n" "1. SetRadius 0\n" "2. SetRadius 8\n" @@ -1967,11 +2016,11 @@ void BotControl::createMenus (void) { "8. SetRadius 96\n" "9. SetRadius 128\n\n" "0. Exit", - cr::forward (&BotControl::menuWaypointRadius) }); + &BotControl::menuGraphRadius); // waypoint add menu - m_menus.push ({ - BOT_MENU_WAYPOINT_TYPE, keys (9), + m_menus.emplace ( + Menu::NodeType, keys (9), "\\yWaypoint Type\\w\n\n" "1. Normal\n" "\\r2. Terrorist Important\n" @@ -1983,11 +2032,11 @@ void BotControl::createMenus (void) { "\\r8. Map Goal\n" "\\w9. Jump\n\n" "0. Exit", - cr::forward (&BotControl::menuWaypointType) }); + &BotControl::menuGraphType); // set waypoint flag menu - m_menus.push ({ - BOT_MENU_WAYPOINT_FLAG, keys (5), + m_menus.emplace ( + Menu::NodeFlag, keys (5), "\\yToggle Waypoint Flags\\w\n\n" "1. Block with Hostage\n" "2. Terrorists Specific\n" @@ -1995,11 +2044,11 @@ void BotControl::createMenus (void) { "4. Use Elevator\n" "5. Sniper Point (\\yFor Camp Points Only!\\w)\n\n" "0. Exit", - cr::forward (&BotControl::menuWaypointFlag) }); + &BotControl::menuGraphFlag); // auto-path max distance - m_menus.push ({ - BOT_MENU_WAYPOINT_AUTOPATH, keys (7), + m_menus.emplace ( + Menu::NodeAutoPath, keys (7), "\\yAutoPath Distance\\w\n\n" "1. Distance 0\n" "2. Distance 100\n" @@ -2009,23 +2058,21 @@ void BotControl::createMenus (void) { "6. Distance 220\n" "7. Distance 250 (Default)\n\n" "0. Exit", - cr::forward (&BotControl::menuAutoPathDistance) }); + &BotControl::menuAutoPathDistance); // path connections - m_menus.push ({ - BOT_MENU_WAYPOINT_PATH, keys (3), + m_menus.emplace ( + Menu::NodePath, 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 = ""; + &BotControl::menuGraphPath); // 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) }); + m_menus.emplace (Menu::KickPage1, 0x0, "", &BotControl::menuKickPage1); + m_menus.emplace (Menu::KickPage2, 0x0, "", &BotControl::menuKickPage2); + m_menus.emplace (Menu::KickPage3, 0x0, "", &BotControl::menuKickPage3); + m_menus.emplace (Menu::KickPage4, 0x0, "", &BotControl::menuKickPage4); } diff --git a/source/engine.cpp b/source/engine.cpp index 0ae5361..95bd1a9 100644 --- a/source/engine.cpp +++ b/source/engine.cpp @@ -9,22 +9,21 @@ #include -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); +ConVar sv_skycolor_r ("sv_skycolor_r", nullptr, Var::NoRegister); +ConVar sv_skycolor_g ("sv_skycolor_g", nullptr, Var::NoRegister); +ConVar sv_skycolor_b ("sv_skycolor_b", nullptr, Var::NoRegister); -Game::Game (void) { +Game::Game () { m_startEntity = nullptr; m_localEntity = nullptr; resetMessages (); for (auto &msg : m_msgBlock.regMsgs) { - msg = NETMSG_UNDEFINED; + msg = NetMsg::None; } m_precached = false; m_isBotCommand = false; - m_botArgs.reserve (8); memset (m_drawModels, 0, sizeof (m_drawModels)); memset (m_spawnCount, 0, sizeof (m_spawnCount)); @@ -36,18 +35,18 @@ Game::Game (void) { m_cvars.clear (); } -Game::~Game (void) { +Game::~Game () { resetMessages (); } -void Game::precache (void) { +void Game::precache () { if (m_precached) { return; } m_precached = true; - m_drawModels[DRAW_SIMPLE] = engfuncs.pfnPrecacheModel (ENGINE_STR ("sprites/laserbeam.spr")); - m_drawModels[DRAW_ARROW] = engfuncs.pfnPrecacheModel (ENGINE_STR ("sprites/arrow1.spr")); + m_drawModels[DrawLine::Simple] = engfuncs.pfnPrecacheModel (ENGINE_STR ("sprites/laserbeam.spr")); + m_drawModels[DrawLine::Arrow] = engfuncs.pfnPrecacheModel (ENGINE_STR ("sprites/arrow1.spr")); engfuncs.pfnPrecacheSound (ENGINE_STR ("weapons/xbow_hit1.wav")); // waypoint add engfuncs.pfnPrecacheSound (ENGINE_STR ("weapons/mine_activate.wav")); // waypoint delete @@ -58,21 +57,21 @@ void Game::precache (void) { 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 (!(game.is (GAME_LEGACY)) && engfuncs.pfnCVarGetPointer ("bot_stop") != nullptr) { - m_gameFlags |= GAME_OFFICIAL_CSBOT; + // detect official csbots here + if (!(is (GameFlags::Legacy)) && engfuncs.pfnCVarGetPointer ("bot_stop") != nullptr) { + m_gameFlags |= GameFlags::CSBot; } - pushRegStackToEngine (true); + registerCvars (true); } void Game::levelInitialize (edict_t *ents, int max) { // this function precaches needed models and initialize class variables - m_spawnCount[TEAM_COUNTER] = 0; - m_spawnCount[TEAM_TERRORIST] = 0; + m_spawnCount[Team::CT] = 0; + m_spawnCount[Team::Terrorist] = 0; // go thru the all entities on map, and do whatever we're want - for (int i = 0; i < max; i++) { + for (int i = 0; i < max; ++i) { auto ent = ents + i; // only valid entities @@ -88,7 +87,7 @@ void Game::levelInitialize (edict_t *ents, int max) { bots.initRound (); } else if (strcmp (classname, "player_weaponstrip") == 0) { - if ((game.is (GAME_LEGACY)) && (STRING (ent->v.target))[0] == '\0') { + if ((is (GameFlags::Legacy)) && (STRING (ent->v.target))[0] == '\0') { ent->v.target = ent->v.targetname = engfuncs.pfnAllocString ("fake"); } else { @@ -102,7 +101,7 @@ void Game::levelInitialize (edict_t *ents, int max) { ent->v.renderamt = 127; // set its transparency amount ent->v.effects |= EF_NODRAW; - m_spawnCount[TEAM_COUNTER]++; + m_spawnCount[Team::CT]++; } else if (strcmp (classname, "info_player_deathmatch") == 0) { engfuncs.pfnSetModel (ent, ENGINE_STR ("models/player/terror/terror.mdl")); @@ -111,7 +110,7 @@ void Game::levelInitialize (edict_t *ents, int max) { ent->v.renderamt = 127; // set its transparency amount ent->v.effects |= EF_NODRAW; - m_spawnCount[TEAM_TERRORIST]++; + m_spawnCount[Team::Terrorist]++; } else if (strcmp (classname, "info_vip_start") == 0) { @@ -122,120 +121,35 @@ void Game::levelInitialize (edict_t *ents, int max) { ent->v.effects |= EF_NODRAW; } else if (strcmp (classname, "func_vip_safetyzone") == 0 || strcmp (classname, "info_vip_safetyzone") == 0) { - m_mapFlags |= MAP_AS; // assassination map + m_mapFlags |= MapFlags::Assassination; // assassination map } else if (strcmp (classname, "hostage_entity") == 0) { - m_mapFlags |= MAP_CS; // rescue map + m_mapFlags |= MapFlags::HostageRescue; // rescue map } else if (strcmp (classname, "func_bomb_target") == 0 || strcmp (classname, "info_bomb_target") == 0) { - m_mapFlags |= MAP_DE; // defusion map + m_mapFlags |= MapFlags::Demolition; // defusion map } else if (strcmp (classname, "func_escapezone") == 0) { - m_mapFlags |= MAP_ES; + m_mapFlags |= MapFlags::Escape; } else if (strncmp (classname, "func_door", 9) == 0) { - m_mapFlags |= MAP_HAS_DOORS; + m_mapFlags |= MapFlags::HasDoors; } } // next maps doesn't have map-specific entities, so determine it by name - if (strncmp (game.getMapName (), "fy_", 3) == 0) { - m_mapFlags |= MAP_FY; + if (strncmp (getMapName (), "fy_", 3) == 0) { + m_mapFlags |= MapFlags::Fun; } - else if (strncmp (game.getMapName (), "ka_", 3) == 0) { - m_mapFlags |= MAP_KA; + else if (strncmp (getMapName (), "ka_", 3) == 0) { + m_mapFlags |= MapFlags::KnifeArena; } // reset some timers m_slowFrame = 0.0f; } -void Game::print (const char *fmt, ...) { - // this function outputs string into server console - - va_list ap; - char string[MAX_PRINT_BUFFER]; - - va_start (ap, fmt); - vsnprintf (string, cr::bufsize (string), translate (fmt), ap); - va_end (ap); - - strcat (string, "\n"); - - engfuncs.pfnServerPrint (string); -} - -void Game::chatPrint (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 (isDedicated ()) { - print (string); - return; - } - strcat (string, "\n"); - - MessageWriter (MSG_BROADCAST, getMessageId (NETMSG_TEXTMSG)) - .writeByte (HUD_PRINTTALK) - .writeString (string); -} - -void Game::centerPrint (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 (isDedicated ()) { - print (string); - return; - } - strcat (string, "\n"); - - MessageWriter (MSG_BROADCAST, getMessageId (NETMSG_TEXTMSG)) - .writeByte (HUD_PRINTCENTER) - .writeString (string); -} - -void Game::clientPrint (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_console, string); -} - -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) { +void Game::drawLine (edict_t *ent, const Vector &start, const Vector &end, int width, int noise, const Color &color, int brightness, int speed, int life, DrawLine 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. @@ -244,7 +158,7 @@ void Game::drawLine (edict_t *ent, const Vector &start, const Vector &end, int w return; // reliability check } - MessageWriter (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, Vector::null (), ent) + MessageWriter (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, nullvec, ent) .writeByte (TE_BEAMPOINTS) .writeCoord (end.x) .writeCoord (end.y) @@ -258,9 +172,9 @@ void Game::drawLine (edict_t *ent, const Vector &start, const Vector &end, int w .writeByte (life) // life in 0.1's .writeByte (width) // width .writeByte (noise) // noise - .writeByte (red) // r, g, b - .writeByte (green) // r, g, b - .writeByte (blue) // r, g, b + .writeByte (color.red) // r, g, b + .writeByte (color.green) // r, g, b + .writeByte (color.blue) // r, g, b .writeByte (brightness) // brightness .writeByte (speed); // speed } @@ -276,11 +190,11 @@ void Game::testLine (const Vector &start, const Vector &end, int ignoreFlags, ed int engineFlags = 0; - if (ignoreFlags & TRACE_IGNORE_MONSTERS) { + if (ignoreFlags & TraceIgnore::Monsters) { engineFlags = 1; } - if (ignoreFlags & TRACE_IGNORE_GLASS) { + if (ignoreFlags & TraceIgnore::Glass) { engineFlags |= 0x100; } engfuncs.pfnTraceLine (start, end, engineFlags, ignoreEntity, ptr); @@ -298,21 +212,22 @@ void Game::testHull (const Vector &start, const Vector &end, int ignoreFlags, in // 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. - engfuncs.pfnTraceHull (start, end, !!(ignoreFlags & TRACE_IGNORE_MONSTERS), hullNumber, ignoreEntity, ptr); + engfuncs.pfnTraceHull (start, end, !!(ignoreFlags & TraceIgnore::Monsters), hullNumber, ignoreEntity, ptr); } float Game::getWaveLen (const char *fileName) { extern ConVar yb_chatter_path; - const char *filePath = util.format ("%s/%s/%s.wav", getModName (), yb_chatter_path.str (), fileName); + const char *filePath = strings.format ("%s/%s/%s.wav", getModName (), yb_chatter_path.str (), fileName); File fp (filePath, "rb"); // we're got valid handle? - if (!fp.isValid ()) { + if (!fp) { return 0.0f; } + // check if we have engine function for this - if (!game.is (GAME_XASH_ENGINE) && engfuncs.pfnGetApproxWavePlayLen != nullptr) { + if (!is (GameFlags::Xash3D) && plat.checkPointer (engfuncs.pfnGetApproxWavePlayLen)) { fp.close (); return engfuncs.pfnGetApproxWavePlayLen (filePath) / 1000.0f; } @@ -337,66 +252,56 @@ float Game::getWaveLen (const char *fileName) { memset (&waveHdr, 0, sizeof (waveHdr)); if (fp.read (&waveHdr, sizeof (WavHeader)) == 0) { - util.logEntry (true, LL_ERROR, "Wave File %s - has wrong or unsupported format", filePath); + logger.error ("Wave File %s - has wrong or unsupported format", filePath); return 0.0f; } if (strncmp (waveHdr.chunkID, "WAVE", 4) != 0) { - util.logEntry (true, LL_ERROR, "Wave File %s - has wrong wave chunk id", filePath); + logger.error ("Wave File %s - has wrong wave chunk id", filePath); return 0.0f; } fp.close (); if (waveHdr.dataChunkLength == 0) { - util.logEntry (true, LL_ERROR, "Wave File %s - has zero length!", filePath); + logger.error ("Wave File %s - has zero length!", filePath); return 0.0f; } return static_cast (waveHdr.dataChunkLength) / static_cast (waveHdr.bytesPerSecond); } -bool Game::isDedicated (void) { +bool Game::isDedicated () { // return true if server is dedicated server, false otherwise static bool dedicated = engfuncs.pfnIsDedicatedServer () > 0; return dedicated; } -const char *Game::getModName (void) { +const char *Game::getModName () { // this function returns mod name without path - static char modname[256]; + static String name; - engfuncs.pfnGetGameDir (modname); - size_t length = strlen (modname); - - size_t stop = length - 1; - while ((modname[stop] == '\\' || modname[stop] == '/') && stop > 0) { - stop--; + if (!name.empty ()) { + return name.chars (); } - size_t start = stop; - while (modname[start] != '\\' && modname[start] != '/' && start > 0) { - start--; - } + char engineModName[256]; + engfuncs.pfnGetGameDir (engineModName); - if (modname[start] == '\\' || modname[start] == '/') { - start++; - } + name = engineModName; + size_t slash = name.findLastOf ("\\/"); - for (length = start; length <= stop; length++) { - modname[length - start] = modname[length]; + if (slash != String::kInvalidIndex) { + name = name.substr (slash + 1); } - modname[length - start] = 0; // terminate the string - return &modname[0]; + name = name.trim (" \\/"); + return name.chars (); } -const char *Game::getMapName (void) { +const char *Game::getMapName () { // this function gets the map name and store it in the map_name global string variable. - static char engineMap[256]; - strncpy (engineMap, STRING (globals->mapname), cr::bufsize (engineMap)); - - return &engineMap[0]; + return strings.format ("%s", STRING (globals->mapname)); } Vector Game::getAbsPos (edict_t *ent) { @@ -404,51 +309,50 @@ Vector Game::getAbsPos (edict_t *ent) { // entity that has a bounding box has its center at the center of the bounding box itself. if (isNullEntity (ent)) { - return Vector::null (); + return nullvec; } if (ent->v.origin.empty ()) { - return ent->v.absmin + ent->v.size * 0.5f; + return (ent->v.absmin + ent->v.absmax) * 0.5f; } return ent->v.origin; } -void Game::registerCmd (const char *command, void func (void)) { +void Game::registerCmd (const char *command, void func ()) { // this function tells the engine that a new server command is being declared, in addition // to the standard ones, whose name is command_name. The engine is thus supposed to be aware // that for every "command_name" server command it receives, it should call the function // 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 (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."); + if (!plat.checkPointer (engfuncs.pfnAddServerCommand)) { + logger.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."); } engfuncs.pfnAddServerCommand (const_cast (command), func); } void Game::playSound (edict_t *ent, const char *sound) { + if (isNullEntity (ent)) { + return; + } engfuncs.pfnEmitSound (ent, CHAN_WEAPON, sound, 1.0f, ATTN_NORM, 0, 100); } -void Game::execBotCmd (edict_t *ent, const char *fmt, ...) { +void Game::sendClientMessage (bool console, edict_t *ent, const char *message) { + // helper to sending the client message + + MessageWriter (MSG_ONE, getMessageId (NetMsg::TextMsg), nullvec, ent) + .writeByte (console ? HUD_PRINTCONSOLE : HUD_PRINTCENTER) + .writeString (message); +} + +void Game::prepareBotArgs (edict_t *ent, String str) { // 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 (!util.isFakeClient (ent)) { - return; - } - va_list ap; - char string[256]; - - va_start (ap, fmt); - vsnprintf (string, cr::bufsize (string), fmt, ap); - va_end (ap); - - String str (string); - if (str.empty ()) { return; } @@ -468,7 +372,7 @@ void Game::execBotCmd (edict_t *ent, const char *fmt, ...) { const size_t space = args.find (' ', 0); // if found space - if (space != String::INVALID_INDEX) { + if (space != String::kInvalidIndex) { const auto quote = space + 1; // check for quote next to space // check if we're got a quoted string @@ -489,7 +393,7 @@ void Game::execBotCmd (edict_t *ent, const char *fmt, ...) { m_botArgs.clear (); // clear space for next cmd }; - if (str.find (';', 0) != String::INVALID_INDEX) { + if (str.find (';', 0) != String::kInvalidIndex) { for (auto &part : str.split (";")) { parsePartArgs (part); } @@ -500,10 +404,10 @@ void Game::execBotCmd (edict_t *ent, const char *fmt, ...) { m_isBotCommand = false; } -bool Game::isSoftwareRenderer (void) { +bool Game::isSoftwareRenderer () { // xash always use "hw" structures - if (is (GAME_XASH_ENGINE)) { + if (is (GameFlags::Xash3D)) { return false; } @@ -513,49 +417,31 @@ bool Game::isSoftwareRenderer (void) { } // 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 - static bool isSoftware = false; -#endif - return isSoftware; + if (plat.isWindows) { + return plat.hasModule ("sw"); + } + return true; } -void Game::execCmd (const char *fmt, ...) { - // this function asks the engine to execute a server command - - va_list ap; - char string[MAX_PRINT_BUFFER]; - - // concatenate all the arguments in one string - va_start (ap, fmt); - vsnprintf (string, cr::bufsize (string), fmt, ap); - va_end (ap); - - strcat (string, "\n"); - engfuncs.pfnServerCommand (string); -} - -void Game::pushVarToRegStack (const char *variable, const char *value, VarType varType, bool regMissing, const char *regVal, ConVar *self) { +void Game::addNewCvar (const char *variable, const char *value, Var varType, bool regMissing, const char *regVal, ConVar *self) { // this function adds globally defined variable to registration stack - VarPair pair; - memset (&pair, 0, sizeof (VarPair)); + VarPair pair = {}; pair.reg.name = const_cast (variable); pair.reg.string = const_cast (value); - pair.regMissing = regMissing; - pair.regVal = regVal; + pair.missing = regMissing; + pair.regval = regVal; int engineFlags = FCVAR_EXTDLL; - if (varType == VT_NORMAL) { + if (varType == Var::Normal) { engineFlags |= FCVAR_SERVER; } - else if (varType == VT_READONLY) { + else if (varType == Var::ReadOnly) { engineFlags |= FCVAR_SERVER | FCVAR_SPONLY | FCVAR_PRINTABLEONLY; } - else if (varType == VT_PASSWORD) { + else if (varType == Var::Password) { engineFlags |= FCVAR_PROTECTED; } @@ -563,37 +449,46 @@ void Game::pushVarToRegStack (const char *variable, const char *value, VarType v pair.self = self; pair.type = varType; - m_cvars.push (pair); + m_cvars.push (cr::move (pair)); } -void Game::pushRegStackToEngine (bool gameVars) { +void Game::registerCvars (bool gameVars) { // this function pushes all added global variables to engine registration for (auto &var : m_cvars) { ConVar &self = *var.self; cvar_t ® = var.reg; - if (var.type != VT_NOREGISTER) { - self.m_eptr = engfuncs.pfnCVarGetPointer (reg.name); + if (var.type != Var::NoRegister) { + self.eptr = engfuncs.pfnCVarGetPointer (reg.name); - if (self.m_eptr == nullptr) { - engfuncs.pfnCVarRegister (&var.reg); - self.m_eptr = engfuncs.pfnCVarGetPointer (reg.name); + if (!self.eptr) { + static cvar_t reg_; + + // fix metamod' memlocs not found + if (is (GameFlags::Metamod)) { + reg_ = var.reg; + engfuncs.pfnCVarRegister (®_); + } + else { + engfuncs.pfnCVarRegister (&var.reg); + } + self.eptr = engfuncs.pfnCVarGetPointer (reg.name); } } else if (gameVars) { - self.m_eptr = engfuncs.pfnCVarGetPointer (reg.name); + self.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); + if (var.missing && !self.eptr) { + if (reg.string == nullptr && var.regval != nullptr) { + reg.string = const_cast (var.regval); reg.flags |= FCVAR_SERVER; } engfuncs.pfnCVarRegister (&var.reg); - self.m_eptr = engfuncs.pfnCVarGetPointer (reg.name); + self.eptr = engfuncs.pfnCVarGetPointer (reg.name); } - if (!self.m_eptr) { + if (!self.eptr) { print ("Got nullptr on cvar %s!", reg.name); } } @@ -603,19 +498,19 @@ void Game::pushRegStackToEngine (bool gameVars) { const char *Game::translate (const char *input) { // this function translate input string into needed language - if (isDedicated () || !m_language.exists (input)) { + if (isDedicated ()) { return input; } static String result; - if (m_language.get (input, result)) { + if (m_language.find (input, result)) { return result.chars (); } return input; // nothing found } void Game::processMessages (void *ptr) { - if (m_msgBlock.msg == NETMSG_UNDEFINED) { + if (m_msgBlock.msg == NetMsg::None) { return; } @@ -632,32 +527,32 @@ void Game::processMessages (void *ptr) { static WeaponProp weaponProp; // some widely used stuff - Bot *bot = bots.getBot (m_msgBlock.bot); + auto bot = bots[m_msgBlock.bot]; - char *strVal = reinterpret_cast (ptr); - int intVal = *reinterpret_cast (ptr); - uint8 byteVal = *reinterpret_cast (ptr); + auto strVal = reinterpret_cast (ptr); + auto intVal = *reinterpret_cast (ptr); + auto byteVal = *reinterpret_cast (ptr); // now starts of network message execution switch (m_msgBlock.msg) { - case NETMSG_VGUI: + case NetMsg::VGUI: // this message is sent when a VGUI menu is displayed. if (bot != nullptr && m_msgBlock.state == 0) { switch (intVal) { - case VMS_TEAM: - bot->m_startAction = GAME_MSG_TEAM_SELECT; + case GuiMenu::TeamSelect: + bot->m_startAction = BotMsg::TeamSelect; break; - case VMS_TF: - case VMS_CT: - bot->m_startAction = GAME_MSG_CLASS_SELECT; + case GuiMenu::TerroristSelect: + case GuiMenu::CTSelect: + bot->m_startAction = BotMsg::ClassSelect; break; } } break; - case NETMSG_SHOWMENU: + case NetMsg::ShowMenu: // this message is sent when a text menu is displayed. // ignore first 3 fields of message @@ -666,32 +561,32 @@ void Game::processMessages (void *ptr) { } if (strcmp (strVal, "#Team_Select") == 0) { - bot->m_startAction = GAME_MSG_TEAM_SELECT; + bot->m_startAction = BotMsg::TeamSelect; } else if (strcmp (strVal, "#Team_Select_Spect") == 0) { - bot->m_startAction = GAME_MSG_TEAM_SELECT; + bot->m_startAction = BotMsg::TeamSelect; } else if (strcmp (strVal, "#IG_Team_Select_Spect") == 0) { - bot->m_startAction = GAME_MSG_TEAM_SELECT; + bot->m_startAction = BotMsg::TeamSelect; } else if (strcmp (strVal, "#IG_Team_Select") == 0) { - bot->m_startAction = GAME_MSG_TEAM_SELECT; + bot->m_startAction = BotMsg::TeamSelect; } else if (strcmp (strVal, "#IG_VIP_Team_Select") == 0) { - bot->m_startAction = GAME_MSG_TEAM_SELECT; + bot->m_startAction = BotMsg::TeamSelect; } else if (strcmp (strVal, "#IG_VIP_Team_Select_Spect") == 0) { - bot->m_startAction = GAME_MSG_TEAM_SELECT; + bot->m_startAction = BotMsg::TeamSelect; } else if (strcmp (strVal, "#Terrorist_Select") == 0) { - bot->m_startAction = GAME_MSG_CLASS_SELECT; + bot->m_startAction = BotMsg::ClassSelect; } else if (strcmp (strVal, "#CT_Select") == 0) { - bot->m_startAction = GAME_MSG_CLASS_SELECT; + bot->m_startAction = BotMsg::ClassSelect; } break; - case NETMSG_WEAPONLIST: + case NetMsg::WeaponList: // this message is sent when a client joins the game. All of the weapons are sent with the weapon ID and information about what ammo is used. switch (m_msgBlock.state) { @@ -726,7 +621,7 @@ void Game::processMessages (void *ptr) { } break; - case NETMSG_CURWEAPON: + case NetMsg::CurWeapon: // this message is sent when a weapon is selected (either by the bot chosing a weapon or by the server auto assigning the bot a weapon). In CS it's also called when Ammo is increased/decreased switch (m_msgBlock.state) { @@ -756,7 +651,7 @@ void Game::processMessages (void *ptr) { } break; - case NETMSG_AMMOX: + case NetMsg::AmmoX: // this message is sent whenever ammo amounts are adjusted (up or down). NOTE: Logging reveals that CS uses it very unreliable! switch (m_msgBlock.state) { @@ -772,7 +667,7 @@ void Game::processMessages (void *ptr) { } break; - case NETMSG_AMMOPICKUP: + case NetMsg::AmmoPickup: // this message is sent when the bot picks up some ammo (AmmoX messages are also sent so this message is probably // not really necessary except it allows the HUD to draw pictures of ammo that have been picked up. The bots // don't really need pictures since they don't have any eyes anyway. @@ -790,7 +685,7 @@ void Game::processMessages (void *ptr) { } break; - case NETMSG_DAMAGE: + case NetMsg::Damage: // this message gets sent when the bots are getting damaged. switch (m_msgBlock.state) { @@ -812,7 +707,7 @@ void Game::processMessages (void *ptr) { } break; - case NETMSG_MONEY: + case NetMsg::Money: // this message gets sent when the bots money amount changes if (bot != nullptr && m_msgBlock.state == 0) { @@ -820,7 +715,7 @@ void Game::processMessages (void *ptr) { } break; - case NETMSG_STATUSICON: + case NetMsg::StatusIcon: switch (m_msgBlock.state) { case 0: enabled = byteVal; @@ -832,7 +727,7 @@ void Game::processMessages (void *ptr) { bot->m_inBuyZone = (enabled != 0); // try to equip in buyzone - bot->processBuyzoneEntering (BUYSTATE_PRIMARY_WEAPON); + bot->processBuyzoneEntering (BuyState::PrimaryWeapon); } else if (strcmp (strVal, "vipsafety") == 0) { bot->m_inVIPZone = (enabled != 0); @@ -845,7 +740,7 @@ void Game::processMessages (void *ptr) { } break; - case NETMSG_DEATH: // this message sends on death + case NetMsg::DeathMsg: // this message sends on death switch (m_msgBlock.state) { case 0: killerIndex = intVal; @@ -856,8 +751,6 @@ void Game::processMessages (void *ptr) { break; case 2: - bots.updateDeathMsgState (true); - if (killerIndex != 0 && killerIndex != victimIndex) { edict_t *killer = entityOfIndex (killerIndex); edict_t *victim = entityOfIndex (victimIndex); @@ -866,13 +759,11 @@ void Game::processMessages (void *ptr) { break; } - if (yb_communication_type.integer () == 2) { + if (yb_radio_mode.int_ () == 2) { // need to send congrats on well placed shot - for (int i = 0; i < maxClients (); i++) { - 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 (!bots.getBot (killer)) { + for (const auto ¬ify : bots) { + if (notify->m_notKilled && killer != notify->ent () && notify->seesEntity (victim->v.origin) && getTeam (killer) == notify->m_team && getTeam (killer) != getTeam (victim)) { + if (!bots[killer]) { notify->processChatterMessage ("#Bot_NiceShotCommander"); } else { @@ -884,10 +775,8 @@ void Game::processMessages (void *ptr) { } // notice nearby to victim teammates, that attacker is near - 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) && util.isVisible (killer->v.origin, notify->ent ()) && isNullEntity (notify->m_enemy) && getTeam (killer) != getTeam (victim)) { + for (const auto ¬ify : bots) { + if (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; @@ -896,7 +785,7 @@ void Game::processMessages (void *ptr) { } } - Bot *notify = bots.getBot (killer); + auto notify = bots[killer]; // is this message about a bot who killed somebody? if (notify != nullptr) { @@ -904,10 +793,10 @@ void Game::processMessages (void *ptr) { } else // did a human kill a bot on his team? { - Bot *target = bots.getBot (victim); + auto target = bots[victim]; if (target != nullptr) { - if (getTeam (killer) == getTeam (victim)) { + if (getTeam (killer) == target->m_team) { target->m_voteKickIndex = killerIndex; } target->m_notKilled = false; @@ -918,7 +807,7 @@ void Game::processMessages (void *ptr) { } break; - case NETMSG_SCREENFADE: // this message gets sent when the screen fades (flashbang) + case NetMsg::ScreenFade: // this message gets sent when the screen fades (flashbang) switch (m_msgBlock.state) { case 3: r = byteVal; @@ -940,7 +829,7 @@ void Game::processMessages (void *ptr) { } break; - case NETMSG_HLTV: // round restart in steam cs + case NetMsg::HLTV: // round restart in steam cs switch (m_msgBlock.state) { case 0: numPlayers = intVal; @@ -954,7 +843,7 @@ void Game::processMessages (void *ptr) { } break; - case NETMSG_TEXTMSG: + case NetMsg::TextMsg: if (m_msgBlock.state == 1) { if (strcmp (strVal, "#CTs_Win") == 0 || strcmp (strVal, "#Bomb_Defused") == 0 || @@ -980,10 +869,10 @@ void Game::processMessages (void *ptr) { } if (strcmp (strVal, "#CTs_Win") == 0) { - bots.setLastWinner (TEAM_COUNTER); // update last winner for economics + bots.setLastWinner (Team::CT); // update last winner for economics - if (yb_communication_type.integer () == 2) { - Bot *notify = bots.getAliveBot (); + if (yb_radio_mode.int_ () == 2) { + Bot *notify = bots.findAliveBot (); if (notify != nullptr && notify->m_notKilled) { notify->processChatterMessage (strVal); @@ -992,50 +881,48 @@ void Game::processMessages (void *ptr) { } if (strcmp (strVal, "#Game_will_restart_in") == 0) { - bots.updateTeamEconomics (TEAM_COUNTER, true); - bots.updateTeamEconomics (TEAM_TERRORIST, true); + bots.updateTeamEconomics (Team::CT, true); + bots.updateTeamEconomics (Team::Terrorist, true); } if (strcmp (strVal, "#Terrorists_Win") == 0) { - bots.setLastWinner (TEAM_TERRORIST); // update last winner for economics + bots.setLastWinner (Team::Terrorist); // update last winner for economics - if (yb_communication_type.integer () == 2) { - Bot *notify = bots.getAliveBot (); + if (yb_radio_mode.int_ () == 2) { + Bot *notify = bots.findAliveBot (); if (notify != nullptr && notify->m_notKilled) { notify->processChatterMessage (strVal); } } } - waypoints.setBombPos (true); + graph.setBombPos (true); } else if (!bots.isBombPlanted () && strcmp (strVal, "#Bomb_Planted") == 0) { bots.setBombPlanted (true); - for (int i = 0; i < maxClients (); i++) { - Bot *notify = bots.getBot (i); - - if (notify != nullptr && notify->m_notKilled) { + for (const auto ¬ify : bots) { + if (notify->m_notKilled) { notify->clearSearchNodes (); notify->clearTasks (); - if (yb_communication_type.integer () == 2 && rng.chance (55) && notify->m_team == TEAM_COUNTER) { - notify->pushChatterMessage (CHATTER_WHERE_IS_THE_BOMB); + if (yb_radio_mode.int_ () == 2 && rg.chance (55) && notify->m_team == Team::CT) { + notify->pushChatterMessage (Chatter::WhereIsTheC4); } } } - waypoints.setBombPos (); + graph.setBombPos (); } else if (bot != nullptr && strcmp (strVal, "#Switch_To_BurstFire") == 0) { - bot->m_weaponBurstMode = BURST_ON; + bot->m_weaponBurstMode = BurstMode::On; } else if (bot != nullptr && strcmp (strVal, "#Switch_To_SemiAuto") == 0) { - bot->m_weaponBurstMode = BURST_OFF; + bot->m_weaponBurstMode = BurstMode::Off; } } break; - case NETMSG_TEAMINFO: + case NetMsg::TeamInfo: switch (m_msgBlock.state) { case 0: playerIndex = intVal; @@ -1043,35 +930,35 @@ void Game::processMessages (void *ptr) { case 1: if (playerIndex > 0 && playerIndex <= maxClients ()) { - int team = TEAM_UNASSIGNED; + int team = Team::Unassigned; if (strVal[0] == 'U' && strVal[1] == 'N') { - team = TEAM_UNASSIGNED; + team = Team::Unassigned; } else if (strVal[0] == 'T' && strVal[1] == 'E') { - team = TEAM_TERRORIST; + team = Team::Terrorist; } else if (strVal[0] == 'C' && strVal[1] == 'T') { - team = TEAM_COUNTER; + team = Team::CT; } else if (strVal[0] == 'S' && strVal[1] == 'P') { - team = TEAM_SPECTATOR; + team = Team::Spectator; } auto &client = util.getClient (playerIndex - 1); client.team2 = team; - client.team = game.is (GAME_CSDM_FFA) ? playerIndex : team; + client.team = is (GameFlags::FreeForAll) ? playerIndex : team; } break; } break; - case NETMSG_BARTIME: + case NetMsg::BarTime: 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) { + if (mapIs (MapFlags::Demolition) && bots.isBombPlanted () && bot->m_team == Team::CT) { bots.notifyBombDefuse (); } } @@ -1081,71 +968,68 @@ void Game::processMessages (void *ptr) { } break; - case NETMSG_ITEMSTATUS: + case NetMsg::ItemStatus: if (bot != nullptr && m_msgBlock.state == 0) { - - enum ItemStatus { - IS_NIGHTVISION = (1 << 0), - IS_DEFUSEKIT = (1 << 1) - }; - - bot->m_hasNVG = (intVal & IS_NIGHTVISION) ? true : false; - bot->m_hasDefuser = (intVal & IS_DEFUSEKIT) ? true : false; + bot->m_hasNVG = (intVal & ItemStatus::Nightvision) ? true : false; + bot->m_hasDefuser = (intVal & ItemStatus::DefusalKit) ? true : false; } break; - case NETMSG_FLASHBAT: + case NetMsg::FlashBat: if (bot != nullptr && m_msgBlock.state == 0) { bot->m_flashLevel = static_cast (intVal); } break; - case NETMSG_NVGTOGGLE: + case NetMsg::NVGToggle: if (bot != nullptr && m_msgBlock.state == 0) { bot->m_usesNVG = intVal > 0; } break; default: - util.logEntry (true, LL_FATAL, "Network message handler error. Call to unrecognized message id (%d).\n", m_msgBlock.msg); + logger.error ("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 (); +bool Game::loadCSBinary () { + auto modname = getModName (); if (!modname) { return false; } + StringArray libs; -#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 + if (plat.isWindows) { + libs.push ("mp.dll"); + libs.push ("cs.dll"); + } + else if (plat.isLinux) { + libs.push ("cs.so"); + libs.push ("cs_i386.so"); + } + else if (plat.isOSX) { + libs.push ("cs.dylib"); + } - auto libCheck = [&] (const char *modname, const char *dll) { + auto libCheck = [&] (const String &mod, const String &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; + if (!m_gameLib) { + logger.fatal ("Unable to load gamedll \"%s\". Exiting... (gamedir: %s)", dll.chars (), mod.chars ()); } auto ent = m_gameLib.resolve ("trigger_random_unique"); // detect regamedll by addon entity they provide if (ent != nullptr) { - m_gameFlags |= GAME_REGAMEDLL; + m_gameFlags |= GameFlags::ReGameDLL; } return true; }; // search the libraries inside game dlls directory - for (const auto lib : libs) { - auto *path = util.format ("%s/dlls/%s", modname, lib); + for (const auto &lib : libs) { + auto *path = strings.format ("%s/dlls/%s", modname, lib.chars ()); // if we can't read file, skip it if (!File::exists (path)) { @@ -1154,18 +1038,15 @@ bool Game::loadCSBinary (void) { // 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); + m_gameFlags |= (GameFlags::ConditionZero | GameFlags::HasBotVoice | GameFlags::HasFakePings); - if (is (GAME_METAMOD)) { + if (is (GameFlags::Metamod)) { return false; } m_gameLib.load (path); // verify dll is OK - if (!libCheck (modname, lib)) { - return false; - } - return true; + return libCheck (modname, lib); } else { m_gameLib.load (path); @@ -1180,26 +1061,26 @@ bool Game::loadCSBinary (void) { // detect xash engine if (engfuncs.pfnCVarGetPointer ("build") != nullptr) { - m_gameFlags |= (GAME_LEGACY | GAME_XASH_ENGINE); + m_gameFlags |= (GameFlags::Legacy | GameFlags::Xash3D); if (entity != nullptr) { - m_gameFlags |= GAME_SUPPORT_BOT_VOICE; + m_gameFlags |= GameFlags::HasBotVoice; } - if (is (GAME_METAMOD)) { + if (is (GameFlags::Metamod)) { return false; } return true; } if (entity != nullptr) { - m_gameFlags |= (GAME_CSTRIKE16 | GAME_SUPPORT_BOT_VOICE | GAME_SUPPORT_SVC_PINGS); + m_gameFlags |= (GameFlags::Modern | GameFlags::HasBotVoice | GameFlags::HasFakePings); } else { - m_gameFlags |= GAME_LEGACY; + m_gameFlags |= GameFlags::Legacy; } - if (is (GAME_METAMOD)) { + if (is (GameFlags::Metamod)) { return false; } return true; @@ -1208,99 +1089,88 @@ bool Game::loadCSBinary (void) { return false; } -bool Game::postload (void) { - // register our cvars - game.pushRegStackToEngine (); - +bool Game::postload () { // ensure we're have all needed directories - const char *mod = game.getModName (); + const char *mod = 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))); + File::createPath (strings.format ("%s/addons/yapb/conf/lang", mod)); + File::createPath (strings.format ("%s/addons/yapb/data/learned", mod)); + File::createPath (strings.format ("%s/addons/yapb/data/graph", mod)); + File::createPath (strings.format ("%s/addons/yapb/data/logs", mod)); + + // set out user agent for http stuff + http.setUserAgent (strings.format ("%s/%s", PRODUCT_SHORT_NAME, PRODUCT_VERSION)); // print game detection info - auto printGame = [&] (void) { + auto printGame = [&] () { String gameVersionStr; - if (is (GAME_LEGACY)) { + if (is (GameFlags::Legacy)) { gameVersionStr.assign ("Legacy"); } - else if (is (GAME_CZERO)) { + else if (is (GameFlags::ConditionZero)) { gameVersionStr.assign ("Condition Zero"); } - else if (is (GAME_CSTRIKE16)) { + else if (is (GameFlags::Modern)) { gameVersionStr.assign ("v1.6"); } - if (is (GAME_XASH_ENGINE)) { + if (is (GameFlags::Xash3D)) { gameVersionStr.append (" @ Xash3D Engine"); - if (is (GAME_MOBILITY)) { + if (is (GameFlags::Mobility)) { gameVersionStr.append (" Mobile"); } gameVersionStr.replace ("Legacy", "1.6 Limited"); } - if (is (GAME_SUPPORT_BOT_VOICE)) { + if (is (GameFlags::HasBotVoice)) { gameVersionStr.append (" (BV)"); } - if (is (GAME_REGAMEDLL)) { + if (is (GameFlags::ReGameDLL)) { gameVersionStr.append (" (RE)"); } - if (is (GAME_SUPPORT_SVC_PINGS)) { + if (is (GameFlags::HasFakePings)) { 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 (plat.isAndroid) { + m_gameFlags |= (GameFlags::Xash3D | GameFlags::Mobility | GameFlags::HasBotVoice | GameFlags::ReGameDLL); + + if (is (GameFlags::Metamod)) { + return true; // we should stop the attempt for loading the real gamedll, since metamod handle this for us + } + auto gamedll = strings.format ("%s/%s", getenv ("XASH3D_GAMELIBDIR"), plat.isAndroidHardFP ? "libserver_hardfp.so" : "libserver.so"); + + if (!m_gameLib.load (gamedll)) { + logger.fatal ("Unable to load gamedll \"%s\". Exiting... (gamedir: %s)", gamedll, getModName ()); + } + printGame (); - if (is (GAME_METAMOD)) { - return true; // we should stop the attempt for loading the real gamedll, since metamod handle this for us } + else { + bool binaryLoaded = loadCSBinary (); - extern ConVar yb_difficulty; - yb_difficulty.set (2); + if (!binaryLoaded && !is (GameFlags::Metamod)) { + logger.fatal ("Mod that you has started, not supported by this bot (gamedir: %s)", getModName ()); + } + printGame (); -#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; + if (is (GameFlags::Metamod)) { + m_gameLib.unload (); + 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)) { +void Game::detectDeathmatch () { + if (!is (GameFlags::Metamod | GameFlags::ReGameDLL)) { return; } static auto dmActive = engfuncs.pfnCVarGetPointer ("csdm_active"); @@ -1309,34 +1179,38 @@ void Game::detectDeathmatch (void) { // csdm is only with amxx and metamod if (dmActive) { if (dmActive->value > 0.0f) { - m_gameFlags |= GAME_CSDM; + m_gameFlags |= GameFlags::CSDM; } - else if (is (GAME_CSDM)) { - m_gameFlags &= ~GAME_CSDM; + else if (is (GameFlags::CSDM)) { + m_gameFlags &= ~GameFlags::CSDM; } } // but this can be provided by regamedll if (freeForAll) { if (freeForAll->value > 0.0f) { - m_gameFlags |= GAME_CSDM_FFA; + m_gameFlags |= GameFlags::FreeForAll; } - else if (is (GAME_CSDM_FFA)) { - m_gameFlags &= ~GAME_CSDM_FFA; + else if (is (GameFlags::FreeForAll)) { + m_gameFlags &= ~GameFlags::FreeForAll; } } } -void Game::slowFrame (void) { - if (m_slowFrame > game.timebase ()) { +void Game::slowFrame () { + if (m_slowFrame > timebase ()) { return; } - ctrl.maintainAdminRights (); - bots.calculatePingOffsets (); // calculate light levels for all waypoints if needed - waypoints.initLightLevels (); + graph.initLightLevels (); + + // update bot difficulties to newly selected from cvar + bots.updateBotDifficulties (); + + // update client pings + util.calculatePings (); // detect csdm detectDeathmatch (); @@ -1348,85 +1222,81 @@ void Game::slowFrame (void) { 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) { + if (is (GameFlags::Metamod) && getMessageId (NetMsg::Money) == -1) { - auto setMsgId = [&] (const char *name, NetMsgId id) { + auto setMsgId = [&] (const char *name, NetMsg 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); + 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::DeathMsg); + 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::Fashlight); + 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 (GameFlags::HasBotVoice)) { + 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); + if ((!is (GameFlags::Legacy) || is (GameFlags::Xash3D)) && dest == MSG_SPEC && type == getMessageId (NetMsg::HLTV)) { + setCurrentMessageId (NetMsg::HLTV); } - captureMessage (type, NETMSG_WEAPONLIST); + captureMessage (type, NetMsg::WeaponList); - if (!isNullEntity (ent)) { - int index = bots.index (ent); + if (!isNullEntity (ent) && !(ent->v.flags & FL_DORMANT)) { + auto bot = bots[ent]; // is this message for a bot? - if (index != -1 && !(ent->v.flags & FL_DORMANT)) { - setCurrentMessageOwner (index); + if (bot != nullptr) { + setCurrentMessageOwner (bot->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); + 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); + captureMessage (type, NetMsg::TeamInfo); + captureMessage (type, NetMsg::DeathMsg); + 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; - } + for (const auto &bot : bots) { + bot->m_notKilled = false; } } } } -void LightMeasure::initializeLightstyles (void) { +void LightMeasure::initializeLightstyles () { // this function initializes lighting information... // reset all light styles @@ -1440,7 +1310,7 @@ void LightMeasure::initializeLightstyles (void) { } } -void LightMeasure::animateLight (void) { +void LightMeasure::animateLight () { // this function performs light animations if (!m_doAnimation) { @@ -1450,7 +1320,7 @@ void LightMeasure::animateLight (void) { // 'm' is normal light, 'a' is no light, 'z' is double bright const int index = static_cast (game.timebase () * 10.0f); - for (int j = 0; j < MAX_LIGHTSTYLES; j++) { + for (int j = 0; j < MAX_LIGHTSTYLES; ++j) { if (!m_lightstyle[j].length) { m_lightstyleValue[j] = 256; continue; @@ -1483,7 +1353,7 @@ void LightMeasure::updateLight (int style, char *value) { } template bool LightMeasure::recursiveLightPoint (const M *node, const Vector &start, const Vector &end) { - if (node->contents < 0) { + if (!node || node->contents < 0) { return false; } @@ -1519,7 +1389,7 @@ template bool LightMeasure::recursiveLightPoint (const // lightplane = plane; auto surf = reinterpret_cast (m_worldModel->surfaces) + node->firstsurface; - for (int i = 0; i < node->numsurfaces; i++, surf++) { + for (int i = 0; i < node->numsurfaces; ++i, ++surf) { if (surf->flags & SURF_DRAWTILED) { continue; // no lightmaps } @@ -1576,6 +1446,10 @@ template bool LightMeasure::recursiveLightPoint (const } float LightMeasure::getLightLevel (const Vector &point) { + if (game.is (GameFlags::Legacy) && !game.is (GameFlags::Xash3D)) { + return 0.0f; + } + if (!m_worldModel) { return 0.0f; } @@ -1588,7 +1462,7 @@ float LightMeasure::getLightLevel (const Vector &point) { endPoint.z -= 2048.0f; // it's depends if we're are on dedicated or on listenserver - auto recursiveCheck = [&] (void) -> bool { + auto recursiveCheck = [&] () -> bool { if (!game.isSoftwareRenderer ()) { return recursiveLightPoint (reinterpret_cast (m_worldModel->nodes), point, endPoint); } @@ -1597,6 +1471,6 @@ float LightMeasure::getLightLevel (const Vector &point) { return !recursiveCheck () ? 0.0f : 100 * cr::sqrtf (cr::min (75.0f, static_cast (m_point.avg ())) / 75.0f); } -float LightMeasure::getSkyColor (void) { - return sv_skycolor_r.flt () + sv_skycolor_g.flt () + sv_skycolor_b.flt () / 3; +float LightMeasure::getSkyColor () { + return static_cast (Color (sv_skycolor_r.int_ (), sv_skycolor_g.int_ (), sv_skycolor_b.int_ ()).avg ()); } diff --git a/source/graph.cpp b/source/graph.cpp new file mode 100644 index 0000000..898172a --- /dev/null +++ b/source/graph.cpp @@ -0,0 +1,2940 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com +// +// 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_graph_subfolder ("yb_graph_subfolder", ""); +ConVar yb_graph_fixcamp ("yb_graph_fixcamp", "1"); +ConVar yb_graph_url ("yb_graph_url", "http://graph.yapb.ru"); + +void BotGraph::initGraph () { + // this function initialize the graph structures.. + + m_loadAttempts = 0; + m_editFlags = 0; + + m_learnVelocity= nullvec; + m_learnPosition= nullvec; + m_lastNode= nullvec; + + m_pathDisplayTime = 0.0f; + m_arrowDisplayTime = 0.0f; + m_autoPathDistance = 250.0f; + m_hasChanged = false; + + // reset highest recorded damage + for (int team = Team::Terrorist; team < kGameTeamNum; ++team) { + m_highestDamage[team] = 1; + } +} + +int BotGraph::clearConnections (int index) { + // this function removes the useless paths connections from and to node pointed by index. This is based on code from POD-bot MM from KWo + + if (!exists (index)) { + return 0; + } + int numFixedLinks = 0; + + // wrapper form unassiged paths + auto clearPath = [&] (int from, int to) { + unassignPath (from, to); + ++numFixedLinks; + }; + + if (bots.hasBotsOnline ()) { + bots.kickEveryone (true); + } + const int kInfiniteDistance = 99999; + + struct Connection { + int index; + int number; + int distance; + float angles; + + public: + Connection () { + reset (); + } + + public: + void reset () { + index = kInvalidNodeIndex; + number = kInvalidNodeIndex; + distance = kInfiniteDistance; + angles = 0.0f; + } + }; + auto &path = m_paths[index]; + + Connection sorted[kMaxNodeLinks]; + Connection top; + + for (int i = 0; i < kMaxNodeLinks; ++i) { + auto &cur = sorted[i]; + const auto &link = path.links[i]; + + cur.number = i; + cur.index = link.index; + cur.distance = link.distance; + + if (cur.index == kInvalidNodeIndex) { + cur.distance = kInfiniteDistance; + } + + if (cur.distance < top.distance) { + top.distance = link.distance; + top.number = i; + top.index = cur.index; + } + } + + if (top.number == kInvalidNodeIndex) { + ctrl.msg ("Cannot find path to the closest connected node to node number %d!\n", index); + return numFixedLinks; + } + bool sorting = false; + + // sort paths from the closest node to the farest away one... + do { + sorting = false; + + for (int i = 0; i < kMaxNodeLinks - 1; ++i) { + if (sorted[i].distance > sorted[i + 1].distance) { + cr::swap (sorted[i], sorted[i + 1]); + sorting = true; + } + } + } while (sorting); + + // calculate angles related to the angle of the closeset connected node + for (auto &cur : sorted) { + if (cur.index == kInvalidNodeIndex) { + cur.distance = kInfiniteDistance; + cur.angles = 360.0f; + } + else if (exists (cur.index)) { + cur.angles = ((m_paths[cur.index].origin - path.origin).angles () - (m_paths[sorted[0].index].origin - path.origin).angles ()).y; + + if (cur.angles < 0.0f) { + cur.angles += 360.0f; + } + } + } + + // sort the paths from the lowest to the highest angle (related to the vector closest node - checked index)... + do { + sorting = false; + + for (int i = 0; i < kMaxNodeLinks - 1; ++i) { + if (sorted[i].index != kInvalidNodeIndex && sorted[i].angles > sorted[i + 1].angles) { + cr::swap (sorted[i], sorted[i + 1]); + sorting = true; + } + } + } while (sorting); + + // reset top state + top.reset (); + + // printing all the stuff causes reliable message overflow + ctrl.setRapidOutput (true); + + // check pass 0 + auto inspect_p0 = [&] (const int id) -> bool { + if (id < 2) { + return false; + } + auto &cur = sorted[id], &prev = sorted[id - 1], &prev2 = sorted[id - 2]; + + if (cur.index == kInvalidNodeIndex || prev.index == kInvalidNodeIndex || prev2.index == kInvalidNodeIndex) { + return false; + } + + // store the highest index which should be tested later... + top.index = cur.index; + top.distance = cur.distance; + top.angles = cur.angles; + + if (cur.angles - prev2.angles < 80.0f) { + + // leave alone ladder connections and don't remove jump connections.. + if (((path.flags & NodeFlag::Ladder) && (m_paths[prev.index].flags & NodeFlag::Ladder)) || (path.links[prev.number].flags & PathFlag::Jump)) { + return false; + } + + if ((cur.distance + prev2.distance) * 1.1f / 2.0f < static_cast (prev.distance)) { + if (path.links[prev.number].index == prev.index) { + ctrl.msg ("Removing a useless (P.0.1) connection from index = %d to %d.", index, prev.index); + + // unassign this path + clearPath (index, prev.number); + + for (int j = 0; j < kMaxNodeLinks; ++j) { + if (m_paths[prev.index].links[j].index == index && !(m_paths[prev.index].links[j].flags & PathFlag::Jump)) { + ctrl.msg ("Removing a useless (P.0.2) connection from index = %d to %d.", prev.index, index); + + // unassign this path + clearPath (prev.index, j); + } + } + prev.index = kInvalidNodeIndex; + + for (int j = id - 1; j < kMaxNodeLinks - 1; ++j) { + sorted[j] = cr::move (sorted[j + 1]); + } + sorted[kMaxNodeLinks - 1].index = kInvalidNodeIndex; + + // do a second check + return true; + } + else { + ctrl.msg ("Failed to remove a useless (P.0) connection from index = %d to %d.", index, prev.index); + return false; + } + } + } + return false; + }; + + + for (int i = 2; i < kMaxNodeLinks; ++i) { + while (inspect_p0 (i)) { } + } + + // check pass 1 + if (exists (top.index) && exists (sorted[0].index) && exists (sorted[1].index)) { + if ((sorted[1].angles - top.angles < 80.0f || 360.0f - (sorted[1].angles - top.angles) < 80.0f) && (!(m_paths[sorted[0].index].flags & NodeFlag::Ladder) || !(path.flags & NodeFlag::Ladder)) && !(path.links[sorted[0].number].flags & PathFlag::Jump)) { + if ((sorted[1].distance + top.distance) * 1.1f / 2.0f < static_cast (sorted[0].distance)) { + if (path.links[sorted[0].number].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 + clearPath (index, sorted[0].number); + + for (int j = 0; j < kMaxNodeLinks; ++j) { + if (m_paths[sorted[0].index].links[j].index == index && !(m_paths[sorted[0].index].links[j].flags & PathFlag::Jump)) { + ctrl.msg ("Removing a useless (P.1.2) connection from index = %d to %d.", sorted[0].index, index); + + // unassign this path + clearPath (sorted[0].index, j); + } + } + sorted[0].index = kInvalidNodeIndex; + + for (int j = 0; j < kMaxNodeLinks - 1; ++j) { + sorted[j] = cr::move (sorted[j + 1]); + } + sorted[kMaxNodeLinks - 1].index = kInvalidNodeIndex; + } + else { + ctrl.msg ("Failed to remove a useless (P.1) connection from index = %d to %d.", sorted[0].index, index); + } + } + } + } + top.reset (); + + // check pass 2 + auto inspect_p2 = [&] (const int id) -> bool { + if (id < 1) { + return false; + } + auto &cur = sorted[id], &prev = sorted[id - 1]; + + if (cur.index == kInvalidNodeIndex || prev.index == kInvalidNodeIndex) { + return false; + } + + if (cur.angles - prev.angles < 40.0f) { + if (prev.distance < static_cast (cur.distance * 1.1f)) { + + // leave alone ladder connections and don't remove jump connections.. + if (((path.flags & NodeFlag::Ladder) && (m_paths[cur.index].flags & NodeFlag::Ladder)) || (path.links[cur.number].flags & PathFlag::Jump)) { + return false; + } + + if (path.links[cur.number].index == cur.index) { + ctrl.msg ("Removing a useless (P.2.1) connection from index = %d to %d.", index, cur.index); + + // unassign this path + clearPath (index, cur.number); + + for (int j = 0; j < kMaxNodeLinks; ++j) { + if (m_paths[cur.index].links[j].index == index && !(m_paths[cur.index].links[j].flags & PathFlag::Jump)) { + ctrl.msg ("Removing a useless (P.2.2) connection from index = %d to %d.", cur.index, index); + + // unassign this path + clearPath (cur.index, j); + } + } + cur.index = kInvalidNodeIndex; + + for (int j = id - 1; j < kMaxNodeLinks - 1; ++j) { + sorted[j] = cr::move (sorted[j + 1]); + } + sorted[kMaxNodeLinks - 1].index = kInvalidNodeIndex; + return true; + } + else { + 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)) { + // leave alone ladder connections and don't remove jump connections.. + if (((path.flags & NodeFlag::Ladder) && (m_paths[prev.index].flags & NodeFlag::Ladder)) || (path.links[prev.number].flags & PathFlag::Jump)) { + return false; + } + + if (path.links[prev.number].index == prev.index) { + ctrl.msg ("Removing a useless (P.2.3) connection from index = %d to %d.", index, prev.index); + + // unassign this path + clearPath (index, prev.number); + + for (int j = 0; j < kMaxNodeLinks; ++j) { + if (m_paths[prev.index].links[j].index == index && !(m_paths[prev.index].links[j].flags & PathFlag::Jump)) { + ctrl.msg ("Removing a useless (P.2.4) connection from index = %d to %d.", prev.index, index); + + // unassign this path + clearPath (prev.index, j); + } + } + prev.index = kInvalidNodeIndex; + + for (int j = id - 1; j < kMaxNodeLinks - 1; ++j) { + sorted[j] = cr::move (sorted[j + 1]); + } + sorted[kMaxNodeLinks - 1].index = kInvalidNodeIndex; + + // do a second check + return true; + } + else { + ctrl.msg ("Failed to remove a useless (P.2) connection from index = %d to %d.", index, prev.index); + } + } + } + else { + top = cur; + } + return false; + }; + + for (int i = 1; i < kMaxNodeLinks; ++i) { + while (inspect_p2 (i)) { } + } + + // check pass 3 + if (exists (top.index) && exists (sorted[0].index)) { + if ((top.angles - sorted[0].angles < 40.0f || (360.0f - top.angles - sorted[0].angles) < 40.0f) && (!(m_paths[sorted[0].index].flags & NodeFlag::Ladder) || !(path.flags & NodeFlag::Ladder)) && !(path.links[sorted[0].number].flags & PathFlag::Jump)) { + if (top.distance * 1.1f < static_cast (sorted[0].distance)) { + if (path.links[sorted[0].number].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 + clearPath (index, sorted[0].number); + + for (int j = 0; j < kMaxNodeLinks; ++j) { + if (m_paths[sorted[0].index].links[j].index == index && !(m_paths[sorted[0].index].links[j].flags & PathFlag::Jump)) { + ctrl.msg ("Removing a useless (P.3.2) connection from index = %d to %d.", sorted[0].index, index); + + // unassign this path + clearPath (sorted[0].index, j); + } + } + sorted[0].index = kInvalidNodeIndex; + + for (int j = 0; j < kMaxNodeLinks - 1; ++j) { + sorted[j] = cr::move (sorted[j + 1]); + } + sorted[kMaxNodeLinks - 1].index = kInvalidNodeIndex; + } + else { + 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) && !(path.links[top.number].flags & PathFlag::Jump)) { + if (path.links[top.number].index == top.index) { + ctrl.msg ("Removing a useless (P.3.3) connection from index = %d to %d.", index, sorted[0].index); + + // unassign this path + clearPath (index, top.number); + + for (int j = 0; j < kMaxNodeLinks; ++j) { + if (m_paths[top.index].links[j].index == index && !(m_paths[top.index].links[j].flags & PathFlag::Jump)) { + ctrl.msg ("Removing a useless (P.3.4) connection from index = %d to %d.", sorted[0].index, index); + + // unassign this path + clearPath (top.index, j); + } + } + sorted[0].index = kInvalidNodeIndex; + } + else { + ctrl.msg ("Failed to remove a useless (P.3) connection from index = %d to %d.", sorted[0].index, index); + } + } + } + } + ctrl.setRapidOutput (false); + + return numFixedLinks; +} + +void BotGraph::addPath (int addIndex, int pathIndex, float distance) { + if (!exists (addIndex) || !exists (pathIndex)) { + return; + } + auto &path = m_paths[addIndex]; + + // don't allow paths get connected twice + for (const auto &link : path.links) { + if (link.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 (auto &link : path.links) { + if (link.index == kInvalidNodeIndex) { + link.index = static_cast (pathIndex); + link.distance = cr::abs (static_cast (distance)); + + ctrl.msg ("Path added from %d to %d", addIndex, pathIndex); + return; + } + } + + // there wasn't any free space. try exchanging it with a long-distance path + int maxDistance = -9999; + int slot = kInvalidNodeIndex; + + for (int i = 0; i < kMaxNodeLinks; ++i) { + if (path.links[i].distance > maxDistance) { + maxDistance = path.links[i].distance; + slot = i; + } + } + + if (slot != kInvalidNodeIndex) { + ctrl.msg ("Path added from %d to %d", addIndex, pathIndex); + + path.links[slot].index = static_cast (pathIndex); + path.links[slot].distance = cr::abs (static_cast (distance)); + } +} + +int BotGraph::getFarest (const Vector &origin, float maxDistance) { + // find the farest node to that origin, and return the index to this node + + int index = kInvalidNodeIndex; + maxDistance = cr::square (maxDistance); + + for (const auto &path : m_paths) { + float distance = (path.origin - origin).lengthSq (); + + if (distance > maxDistance) { + index = path.number; + maxDistance = distance; + } + } + return index; +} + +int BotGraph::getNearestNoBuckets (const Vector &origin, float minDistance, int flags) { + // find the nearest node to that origin and return the index + + // fallback and go thru wall the nodes... + int index = kInvalidNodeIndex; + minDistance = cr::square (minDistance); + + for (const auto &path : m_paths) { + if (flags != -1 && !(path.flags & flags)) { + continue; // if flag not -1 and node has no this flag, skip node + } + float distance = (path.origin - origin).lengthSq (); + + if (distance < minDistance) { + index = path.number; + minDistance = distance; + } + } + return index; +} + +int BotGraph::getEditorNeareset () { + if (!hasEditFlag (GraphEdit::On)) { + return kInvalidNodeIndex; + } + return getNearestNoBuckets (m_editor->v.origin, 50.0f); +} + +int BotGraph::getNearest (const Vector &origin, float minDistance, int flags) { + // find the nearest node to that origin and return the index + + auto &bucket = getNodesInBucket (origin); + + if (bucket.empty ()) { + return getNearestNoBuckets (origin, minDistance, flags); + } + + int index = kInvalidNodeIndex; + auto minDistanceSq = cr::square (minDistance); + + for (const auto &at : bucket) { + if (flags != -1 && !(m_paths[at].flags & flags)) { + continue; // if flag not -1 and node has no this flag, skip node + } + float distance = (m_paths[at].origin - origin).lengthSq (); + + if (distance < minDistanceSq) { + index = at; + minDistanceSq = distance; + } + } + + // nothing found, try to find without buckets + if (index == kInvalidNodeIndex) { + return getNearestNoBuckets (origin, minDistance, flags); + } + return index; +} + +IntArray BotGraph::searchRadius (float radius, const Vector &origin, int maxCount) { + // returns all nodes within radius from position + + IntArray result; + auto &bucket = getNodesInBucket (origin); + + if (bucket.empty ()) { + result.push (getNearestNoBuckets (origin, radius)); + return cr::move (result); + } + radius = cr::square (radius); + + for (const auto &at : bucket) { + if (maxCount != -1 && static_cast (result.length ()) > maxCount) { + break; + } + + if ((m_paths[at].origin - origin).lengthSq () < radius) { + result.push (at); + } + } + return cr::move (result); +} + +void BotGraph::add (int type, const Vector &pos) { + if (game.isNullEntity (m_editor)) { + return; + } + int index = kInvalidNodeIndex; + Path *path = nullptr; + + bool addNewNode = true; + Vector newOrigin = pos == nullvec ? m_editor->v.origin : pos; + + if (bots.hasBotsOnline ()) { + bots.kickEveryone (true); + } + m_hasChanged = true; + + switch (type) { + case 6: + index = getEditorNeareset (); + + if (index != kInvalidNodeIndex) { + path = &m_paths[index]; + + if (!(path->flags & NodeFlag::Camp)) { + ctrl.msg ("This is not Camping Node"); + return; + } + path->end = m_editor->v.v_angle.get2d (); + + // play "done" sound... + game.playSound (m_editor, "common/wpn_hudon.wav"); + } + return; + + case 9: + index = getEditorNeareset (); + + if (index != kInvalidNodeIndex && m_paths[index].number >= 0) { + float distance = (m_paths[index].origin - m_editor->v.origin).length (); + + if (distance < 50.0f) { + addNewNode = false; + + path = &m_paths[index]; + path->origin = (path->origin + m_learnPosition) * 0.5f; + } + } + else { + newOrigin = m_learnPosition; + } + break; + + case 10: + index = getEditorNeareset (); + + if (index != kInvalidNodeIndex && m_paths[index].number >= 0) { + float distance = (m_paths[index].origin - m_editor->v.origin).length (); + + if (distance < 50.0f) { + addNewNode = false; + path = &m_paths[index]; + + int connectionFlags = 0; + + for (const auto &link : path->links) { + connectionFlags += link.flags; + } + + if (connectionFlags == 0) { + path->origin = (path->origin + m_editor->v.origin) * 0.5f; + } + } + } + break; + } + + if (addNewNode) { + // need to remove limit? + if (m_paths.length () >= kMaxNodes) { + return; + } + m_paths.push (Path ()); + + index = m_paths.length () - 1; + path = &m_paths[index]; + + path->number = index; + path->flags = 0; + + // store the origin (location) of this node + path->origin = newOrigin; + addToBucket (newOrigin, index); + + path->start = nullvec; + path->end = nullvec; + + path->display = 0.0f; + path->light = 0.0f; + + for (auto &link : path->links) { + link.index = kInvalidNodeIndex; + link.distance = 0; + link.flags = 0; + link.velocity = nullvec; + } + + + // store the last used node for the auto node code... + m_lastNode = m_editor->v.origin; + } + + if (type == 9) { + m_lastJumpNode = index; + } + else if (type == 10) { + float distance = (m_paths[m_lastJumpNode].origin - m_editor->v.origin).length (); + addPath (m_lastJumpNode, index, distance); + + for (auto &link : m_paths[m_lastJumpNode].links) { + if (link.index == index) { + link.flags |= PathFlag::Jump; + link.velocity = m_learnVelocity; + + break; + } + } + calculatePathRadius (index); + return; + } + + if (!path || path->number == kInvalidNodeIndex) { + return; + } + + if (m_editor->v.flags & FL_DUCKING) { + path->flags |= NodeFlag::Crouch; // set a crouch node + } + + if (m_editor->v.movetype == MOVETYPE_FLY) { + path->flags |= NodeFlag::Ladder; + } + else if (m_isOnLadder) { + path->flags |= NodeFlag::Ladder; + } + + switch (type) { + case 1: + path->flags |= NodeFlag::Crossing; + path->flags |= NodeFlag::TerroristOnly; + break; + + case 2: + path->flags |= NodeFlag::Crossing; + path->flags |= NodeFlag::CTOnly; + break; + + case 3: + path->flags |= NodeFlag::NoHostage; + break; + + case 4: + path->flags |= NodeFlag::Rescue; + break; + + case 5: + path->flags |= NodeFlag::Crossing; + path->flags |= NodeFlag::Camp; + + path->start = m_editor->v.v_angle; + break; + + case 100: + path->flags |= NodeFlag::Goal; + break; + } + + // Ladder nodes need careful connections + if (path->flags & NodeFlag::Ladder) { + float minDistance = 9999.0f; + int destIndex = kInvalidNodeIndex; + + TraceResult tr; + + // calculate all the paths to this new node + for (const auto &calc : m_paths) { + if (calc.number == index) { + continue; // skip the node that was just added + } + + // other ladder nodes should connect to this + if (calc.flags & NodeFlag::Ladder) { + // check if the node is reachable from the new one + game.testLine (newOrigin, calc.origin, TraceIgnore::Monsters, m_editor, &tr); + + if (cr::fequal (tr.flFraction, 1.0f) && cr::abs (newOrigin.x - calc.origin.x) < 64.0f && cr::abs (newOrigin.y - calc.origin.y) < 64.0f && cr::abs (newOrigin.z - calc.origin.z) < m_autoPathDistance) { + float distance = (calc.origin - newOrigin).length (); + + addPath (index, calc.number, distance); + addPath (calc.number, index, distance); + } + } + else { + // check if the node is reachable from the new one + if (isNodeReacheable (newOrigin, calc.origin) || isNodeReacheable (calc.origin, newOrigin)) { + float distance = (calc.origin - newOrigin).length (); + + if (distance < minDistance) { + destIndex = calc.number; + minDistance = distance; + } + } + } + } + + if (exists (destIndex)) { + // check if the node is reachable from the new one (one-way) + if (isNodeReacheable (newOrigin, m_paths[destIndex].origin)) { + addPath (index, destIndex, (m_paths[destIndex].origin - newOrigin).length ()); + } + + // check if the new one is reachable from the node (other way) + if (isNodeReacheable (m_paths[destIndex].origin, newOrigin)) { + addPath (destIndex, index, (m_paths[destIndex].origin - newOrigin).length ()); + } + } + } + else { + // calculate all the paths to this new node + for (const auto &calc : m_paths) { + if (calc.number == index) { + continue; // skip the node that was just added + } + + // check if the node is reachable from the new one (one-way) + if (isNodeReacheable (newOrigin, calc.origin)) { + addPath (index, calc.number, (calc.origin - newOrigin).length ()); + } + + // check if the new one is reachable from the node (other way) + if (isNodeReacheable (calc.origin, newOrigin)) { + addPath (calc.number, index, (calc.origin - newOrigin).length ()); + } + } + clearConnections (index); + } + game.playSound (m_editor, "weapons/xbow_hit1.wav"); + calculatePathRadius (index); // calculate the wayzone of this node +} + +void BotGraph::erase (int target) { + m_hasChanged = true; + + if (m_paths.empty ()) { + return; + } + + if (bots.hasBotsOnline ()) { + bots.kickEveryone (true); + } + const int index = (target == kInvalidNodeIndex) ? getEditorNeareset () : target; + + if (!exists (index)) { + return; + } + auto &path = m_paths[index]; + + // unassign paths that points to this nodes + for (auto &connected : m_paths) { + for (auto &link : connected.links) { + if (link.index == index) { + link.index = kInvalidNodeIndex; + link.flags = 0; + link.distance = 0; + link.velocity = nullvec; + } + } + } + + // relink nodes so the index will match path number + for (auto &relink : m_paths) { + + // if pathnumber bigger than deleted node... + if (relink.number > index) { + --relink.number; + } + + for (auto &neighbour : relink.links) { + if (neighbour.index > index) { + --neighbour.index; + } + } + } + eraseFromBucket (path.origin, index); + m_paths.remove (path); + + game.playSound (m_editor, "weapons/mine_activate.wav"); +} + +void BotGraph::toggleFlags (int toggleFlag) { + // this function allow manually changing flags + + int index = getEditorNeareset (); + + if (index != kInvalidNodeIndex) { + if (m_paths[index].flags & toggleFlag) { + m_paths[index].flags &= ~toggleFlag; + } + else if (!(m_paths[index].flags & toggleFlag)) { + if (toggleFlag == NodeFlag::Sniper && !(m_paths[index].flags & NodeFlag::Camp)) { + ctrl.msg ("Cannot assign sniper flag to node #%d. This is not camp node.", index); + return; + } + m_paths[index].flags |= toggleFlag; + } + + // play "done" sound... + game.playSound (m_editor, "common/wpn_hudon.wav"); + } +} + +void BotGraph::setRadius (int index, float radius) { + // this function allow manually setting the zone radius + + int node = exists (index) ? index : getEditorNeareset (); + + if (node != kInvalidNodeIndex) { + m_paths[node].radius = static_cast (radius); + + // play "done" sound... + game.playSound (m_editor, "common/wpn_hudon.wav"); + } +} + +bool BotGraph::isConnected (int a, int b) { + // this function checks if node A has a connection to node B + + for (const auto &link : m_paths[a].links) { + if (link.index == b) { + return true; + } + } + return false; +} + +int BotGraph::getFacingIndex () { + // this function finds node the user is pointing at. + + int indexToPoint = kInvalidNodeIndex; + + Array cones; + float maxCone = 0.0f; + + // find the node the user is pointing at + for (const auto &path : m_paths) { + if ((path.origin - m_editor->v.origin).lengthSq () > cr::square (500.0f)) { + continue; + } + cones.clear (); + + // get the current view cones + cones.push (util.getShootingCone (m_editor, path.origin)); + cones.push (util.getShootingCone (m_editor, path.origin - Vector (0.0f, 0.0f, (path.flags & NodeFlag::Crouch) ? 6.0f : 12.0f))); + cones.push (util.getShootingCone (m_editor, path.origin - Vector (0.0f, 0.0f, (path.flags & NodeFlag::Crouch) ? 12.0f : 24.0f))); + cones.push (util.getShootingCone (m_editor, path.origin + Vector (0.0f, 0.0f, (path.flags & NodeFlag::Crouch) ? 6.0f : 12.0f))); + cones.push (util.getShootingCone (m_editor, path.origin + Vector (0.0f, 0.0f, (path.flags & NodeFlag::Crouch) ? 12.0f : 24.0f))); + + // check if we can see it + for (auto &cone : cones) { + if (cone > 1.000f && cone > maxCone) { + maxCone = cone; + indexToPoint = path.number; + } + } + } + return indexToPoint; +} + +void BotGraph::pathCreate (char dir) { + // this function allow player to manually create a path from one node to another + + int nodeFrom = getEditorNeareset (); + + if (nodeFrom == kInvalidNodeIndex) { + ctrl.msg ("Unable to find nearest node in 50 units"); + return; + } + int nodeTo = m_facingAtIndex; + + if (!exists (nodeTo)) { + if (exists (m_cacheNodeIndex)) { + nodeTo = m_cacheNodeIndex; + } + else { + ctrl.msg ("Unable to find destination node"); + return; + } + } + + if (nodeTo == nodeFrom) { + ctrl.msg ("Unable to connect node with itself"); + return; + } + + float distance = (m_paths[nodeTo].origin - m_paths[nodeFrom].origin).length (); + + if (dir == PathConnection::Outgoing) { + addPath (nodeFrom, nodeTo, distance); + } + else if (dir == PathConnection::Incoming) { + addPath (nodeTo, nodeFrom, distance); + } + else { + addPath (nodeFrom, nodeTo, distance); + addPath (nodeTo, nodeFrom, distance); + } + + game.playSound (m_editor, "common/wpn_hudon.wav"); + m_hasChanged = true; +} + +void BotGraph::erasePath () { + // this function allow player to manually remove a path from one node to another + + int nodeFrom = getEditorNeareset (); + int index = 0; + + if (nodeFrom == kInvalidNodeIndex) { + ctrl.msg ("Unable to find nearest node in 50 units."); + return; + } + int nodeTo = m_facingAtIndex; + + if (!exists (nodeTo)) { + if (exists (m_cacheNodeIndex)) { + nodeTo = m_cacheNodeIndex; + } + else { + ctrl.msg ("Unable to find destination node"); + return; + } + } + + for (auto &link : m_paths[nodeFrom].links) { + if (link.index == nodeTo) { + unassignPath (nodeFrom, index); + game.playSound (m_editor, "weapons/mine_activate.wav"); + + return; + } + } + + // not found this way ? check for incoming connections then + index = nodeFrom; + nodeFrom = nodeTo; + nodeTo = index; + + for (auto &link : m_paths[nodeFrom].links) { + if (link.index == nodeTo) { + unassignPath (nodeFrom, index); + game.playSound (m_editor, "weapons/mine_activate.wav"); + + return; + } + } + ctrl.msg ("There is already no path on this node"); +} + +void BotGraph::cachePoint (int index) { + int node = exists (index) ? index : getEditorNeareset (); + + if (node == kInvalidNodeIndex) { + m_cacheNodeIndex = kInvalidNodeIndex; + ctrl.msg ("Cached node cleared (nearby point not found in 50 units range)."); + + return; + } + m_cacheNodeIndex = node; + ctrl.msg ("Node #%d has been put into memory.", m_cacheNodeIndex); +} + +void BotGraph::calculatePathRadius (int index) { + // calculate "wayzones" for the nearest node (meaning a dynamic distance area to vary node origin) + + auto &path = m_paths[index]; + Vector start, direction; + + if ((path.flags & (NodeFlag::Ladder | NodeFlag::Goal | NodeFlag::Camp | NodeFlag::Rescue | NodeFlag::Crouch)) || m_jumpLearnNode) { + path.radius = 0.0f; + return; + } + + for (const auto &test : path.links) { + if (test.index != kInvalidNodeIndex && (m_paths[test.index].flags & NodeFlag::Ladder)) { + path.radius = 0.0f; + return; + } + } + TraceResult tr; + bool wayBlocked = false; + + for (float scanDistance = 32.0f; scanDistance < 128.0f; scanDistance += 16.0f) { + start = path.origin; + game.makeVectors (nullvec); + + direction = game.vec.forward * scanDistance; + direction = direction.angles (); + + path.radius = scanDistance; + + for (float circleRadius = 0.0f; circleRadius < 360.0f; circleRadius += 20.0f) { + game.makeVectors (direction); + + Vector radiusStart = start + game.vec.forward * scanDistance; + Vector radiusEnd = start + game.vec.forward * scanDistance; + + game.testHull (radiusStart, radiusEnd, TraceIgnore::Monsters, head_hull, nullptr, &tr); + + if (tr.flFraction < 1.0f) { + game.testLine (radiusStart, radiusEnd, TraceIgnore::Monsters, nullptr, &tr); + + if (strncmp ("func_door", STRING (tr.pHit->v.classname), 9) == 0) { + path.radius = 0.0f; + wayBlocked = true; + + break; + } + wayBlocked = true; + path.radius -= 16.0f; + + break; + } + + Vector dropStart = start + game.vec.forward * scanDistance; + Vector dropEnd = dropStart - Vector (0.0f, 0.0f, scanDistance + 60.0f); + + game.testHull (dropStart, dropEnd, TraceIgnore::Monsters, head_hull, nullptr, &tr); + + if (tr.flFraction >= 1.0f) { + wayBlocked = true; + path.radius -= 16.0f; + + break; + } + dropStart = start - game.vec.forward * scanDistance; + dropEnd = dropStart - Vector (0.0f, 0.0f, scanDistance + 60.0f); + + game.testHull (dropStart, dropEnd, TraceIgnore::Monsters, head_hull, nullptr, &tr); + + if (tr.flFraction >= 1.0f) { + wayBlocked = true; + path.radius -= 16.0f; + break; + } + + radiusEnd.z += 34.0f; + game.testHull (radiusStart, radiusEnd, TraceIgnore::Monsters, head_hull, nullptr, &tr); + + if (tr.flFraction < 1.0f) { + wayBlocked = true; + path.radius -= 16.0f; + + break; + } + direction.y = cr::normalizeAngles (direction.y + circleRadius); + } + + if (wayBlocked) { + break; + } + } + path.radius -= 16.0f; + + if (path.radius < 0.0f) { + path.radius = 0.0f; + } +} + +void BotGraph::loadPractice () { + if (m_paths.empty ()) { + return; + } + + // reset highest recorded damage + for (int team = Team::Terrorist; team < kGameTeamNum; ++team) { + m_highestDamage[team] = 1; + } + + bool dataLoaded = loadStorage ("prc", "Practice", StorageOption::Practice, StorageVersion::Practice, m_practice, nullptr, nullptr); + int count = m_paths.length (); + + // set's the highest damage if loaded ok + if (dataLoaded) { + auto practice = m_practice.data (); + + for (int team = Team::Terrorist; team < kGameTeamNum; ++team) { + for (int i = 0; i < count; ++i) { + for (int j = 0; j < count; ++j) { + if (i == j) { + if ((practice + (i * count) + j)->damage[team] > m_highestDamage[team]) { + m_highestDamage[team] = (practice + (i * count) + j)->damage[team]; + } + } + } + } + } + return; + } + auto practice = m_practice.data (); + + // initialize table by hand to correct values, and NOT zero it out + for (int team = Team::Terrorist; team < kGameTeamNum; ++team) { + for (int i = 0; i < count; ++i) { + for (int j = 0; j < count; ++j) { + (practice + (i * count) + j)->index[team] = kInvalidNodeIndex; + (practice + (i * count) + j)->damage[team] = 0; + (practice + (i * count) + j)->value[team] = 0; + } + } + } +} + +void BotGraph::savePractice () { + if (m_paths.empty () || m_hasChanged) { + return; + } + saveStorage ("prc", "Practice", StorageOption::Practice, StorageVersion::Practice, m_practice, nullptr); +} + +void BotGraph::loadVisibility () { + m_needsVisRebuild = true; + + if (m_paths.empty ()) { + return; + } + bool dataLoaded = loadStorage ("vis", "Visibility", StorageOption::Vistable, StorageVersion::Vistable, m_vistable, nullptr, nullptr); + + // if loaded, do not recalculate visibility + if (dataLoaded) { + m_needsVisRebuild = false; + } +} + +void BotGraph::saveVisibility () { + if (m_paths.empty () || m_hasChanged || m_needsVisRebuild) { + return; + } + saveStorage ("vis", "Visibility", StorageOption::Vistable, StorageVersion::Vistable, m_vistable, nullptr); +} + +bool BotGraph::loadPathMatrix () { + if (m_paths.empty ()) { + return false; + } + bool dataLoaded = loadStorage ("pmx", "Pathmatrix", StorageOption::Matrix, StorageVersion::Matrix, m_matrix, nullptr, nullptr); + + // do not rebuild if loaded + if (dataLoaded) { + return true; + } + auto count = length (); + auto matrix = m_matrix.data (); + + for (int i = 0; i < count; ++i) { + for (int j = 0; j < count; ++j) { + (matrix + i * count + j)->dist = SHRT_MAX; + (matrix + i * count + j)->index = kInvalidNodeIndex; + } + } + + for (int i = 0; i < count; ++i) { + for (const auto &link : m_paths[i].links) { + if (!exists (link.index)) { + continue; + } + (matrix + (i * count) + link.index)->dist = static_cast (link.distance); + (matrix + (i * count) + link.index)->index = static_cast (link.index); + } + } + + for (int i = 0; i < count; ++i) { + (matrix + (i * count) + i)->dist = 0; + } + + for (int k = 0; k < count; ++k) { + for (int i = 0; i < count; ++i) { + for (int j = 0; j < count; ++j) { + int distance = (matrix + (i * count) + k)->dist + (matrix + (k * count) + j)->dist; + + if (distance < (matrix + (i * count) + j)->dist) { + (matrix + (i * count) + j)->dist = static_cast (distance); + (matrix + (i * count) + j)->index = static_cast ((matrix + (i * count) + k)->index); + } + } + } + } + savePathMatrix (); // save path matrix to file for faster access + + return true; +} + +void BotGraph::savePathMatrix () { + if (m_paths.empty ()) { + return; + } + saveStorage ("pmx", "Pathmatrix", StorageOption::Matrix, StorageVersion::Matrix, m_matrix, nullptr); +} + +void BotGraph::initLightLevels () { + // this function get's the light level for each waypoin on the map + + // no nodes ? no light levels, and only one-time init + if (m_paths.empty () || !cr::fzero (m_paths[0].light)) { + return; + } + + // update light levels for all nodes + for (auto &path : m_paths) { + path.light = illum.getLightLevel (path.origin); + } + // disable lightstyle animations on finish (will be auto-enabled on mapchange) + illum.enableAnimation (false); +} + +void BotGraph::initNodesTypes () { + m_terrorPoints.clear (); + m_ctPoints.clear (); + m_goalPoints.clear (); + m_campPoints.clear (); + m_rescuePoints.clear (); + m_sniperPoints.clear (); + m_visitedGoals.clear (); + + for (const auto &path : m_paths) { + if (path.flags & NodeFlag::TerroristOnly) { + m_terrorPoints.push (path.number); + } + else if (path.flags & NodeFlag::CTOnly) { + m_ctPoints.push (path.number); + } + else if (path.flags & NodeFlag::Goal) { + m_goalPoints.push (path.number); + } + else if (path.flags & NodeFlag::Camp) { + m_campPoints.push (path.number); + } + else if (path.flags & NodeFlag::Sniper) { + m_sniperPoints.push (path.number); + } + else if (path.flags & NodeFlag::Rescue) { + m_rescuePoints.push (path.number); + } + } +} + +bool BotGraph::convertOldFormat () { + MemFile fp (getOldFormatGraphName (true)); + + PODGraphHeader header; + memset (&header, 0, sizeof (header)); + + // save for faster access + const char *map = game.getMapName (); + if (fp) { + + if (fp.read (&header, sizeof (header)) == 0) { + return false; + } + + if (strncmp (header.header, kPodbotMagic, cr::bufsize (kPodbotMagic)) == 0) { + if (header.fileVersion != StorageVersion::Podbot) { + return false; + } + else if (!!stricmp (header.mapName, map)) { + return false; + } + else { + if (header.pointNumber == 0 || header.pointNumber > kMaxNodes) { + return false; + } + initGraph (); + m_paths.clear (); + + for (int i = 0; i < header.pointNumber; ++i) { + Path path {}; + PODPath podpath {}; + + if (fp.read (&podpath, sizeof (PODPath)) == 0) { + return false; + } + convertFromPOD (path, podpath); + + // more checks of node quality + if (path.number < 0 || path.number > header.pointNumber) { + return false; + } + // add to node array + m_paths.push (cr::move (path)); + } + } + } + else { + return false; + } + fp.close (); + } + else { + return false; + } + + // save new format in case loaded older one + if (!m_paths.empty ()) { + game.print ("Converting old PWF to new format Graph."); + + m_tempStrings = header.author; + return saveGraphData (); + } + return true; +} + +template bool BotGraph::saveStorage (const String &ext, const String &name, StorageOption options, StorageVersion version, const Array &data, uint8 *blob) { + bool isGraph = (ext == "graph"); + + String filename; + filename.assignf ("%s.%s", game.getMapName (), ext.chars ()); + + if (data.empty ()) { + logger.error ("Unable to save %s file. Empty data. (filename: '%s')", name.chars (), filename.chars ()); + return false; + } + else if (isGraph) { + for (auto &path : m_paths) { + path.display = 0.0f; + path.light = illum.getLightLevel (path.origin); + } + } + + // open the file + File file (strings.format ("%s%s/%s", getDataDirectory (false), isGraph ? "graph" : "learned", filename.chars ()), "wb"); + + // no open no fun + if (!file) { + logger.error ("Unable to open %s file for writing (filename: '%s')", name.chars (), filename.chars ()); + file.close (); + + return false; + } + ULZ lz; + + size_t rawLength = data.length () * sizeof (U); + Array compressed (rawLength + sizeof (uint8) * ULZ::Excess); + + // try to compress + auto compressedLength = lz.compress (reinterpret_cast (data.data ()), rawLength, reinterpret_cast (compressed.data ())); + + if (compressedLength > 0) { + StorageHeader hdr; + + hdr.magic = kStorageMagic; + hdr.version = version; + hdr.options = options; + hdr.length = m_paths.length (); + hdr.compressed = compressedLength; + hdr.uncompressed = rawLength; + + file.write (&hdr, sizeof (StorageHeader)); + file.write (compressed.data (), sizeof (uint8), compressedLength); + + // add creator + if ((options & StorageOption::Author) && blob != nullptr) { + file.write (blob, sizeof (uint8), 64); + } + game.print ("Successfully saved Bots %s data.", name.chars ()); + } + else { + logger.error ("Unable to compress %s data (filename: '%s')", name.chars (), filename.chars ()); + file.close (); + + return false; + } + file.close (); + return true; +} + +template bool BotGraph::loadStorage (const String &ext, const String &name, StorageOption options, StorageVersion version, Array &data, uint8 *blob, int32 *outOptions) { + String filename; + filename.assignf ("%s.%s", game.getMapName (), ext.chars ()).lowercase (); + + // graphs can be downloaded... + bool isGraph = (ext == "graph"); + MemFile file (strings.format ("%s%s/%s", getDataDirectory (true), isGraph ? "graph" : "learned", filename.chars ())); // open the file + + // resize data to fit the stuff + auto resizeData = [&] (const size_t length) { + data.resize (length); // for non-graph data the graph should be already loaded + data.shrink (); // free up memory to minimum + + // ensure we're have enough memory to decompress the data + data.ensure (length + ULZ::Excess); + }; + + // allocate non-graph data, so we're will be ok in case if loading will fail + if (!isGraph) { + resizeData (m_paths.length () * m_paths.length ()); + } + + // error has occured + auto bailout = [&] (const char *fmt, ...) -> bool { + va_list args; + auto result = strings.chars (); + + // concatenate string + va_start (args, fmt); + vsnprintf (result, StringBuffer::StaticBufferSize, fmt, args); + va_end (args); + + logger.error (result); + + // if graph reset paths + if (isGraph) { + bots.kickEveryone (true); + + m_tempStrings = result; + m_paths.clear (); + } + file.close (); + + return false; + }; + + // if graph & attempted to load multiple times, bail out, we're failed + if (isGraph && ++m_loadAttempts > 2) { + m_loadAttempts = 0; + return bailout ("Unable to load %s (filename: '%s'). Download process has failed as well. No nodes has been found.", name.chars (), filename.chars ()); + } + + // downloader for graph + auto download = [&] () -> bool { + auto toDownload = strings.format ("%sgraph/%s", getDataDirectory (false), filename.chars ()); + auto fromDownload = strings.format ("%s/graph/%s", yb_graph_url.str (), filename.chars ()); + + // try to download + if (http.downloadFile (fromDownload, toDownload)) { + game.print ("%s file '%s' successfully downloaded. Processing...", name.chars (), filename.chars ()); + return true; + } + else { + game.print ("Can't download '%s'. from '%s' to '%s'... (%d)", filename.chars (), fromDownload, toDownload, http.getLastStatusCode ()); + } + return false; + }; + + // tries to reload or open pwf file + auto tryReload = [&] () -> bool { + file.close (); + + if (!isGraph) { + return false; + } + + if (download ()) { + return loadStorage (ext, name, options, version, data, blob, outOptions); + } + + if (convertOldFormat ()) { + return loadStorage (ext, name, options, version, data, blob, outOptions); + } + return false; + }; + + // no open no fun + if (!file) { + if (tryReload ()) { + return true; + } + return bailout ("Unable to open %s file for reading (filename: '%s').", name.chars (), filename.chars ()); + } + + // read the header + StorageHeader hdr; + file.read (&hdr, sizeof (StorageHeader)); + + // check the magic + if (hdr.magic != kStorageMagic) { + if (tryReload ()) { + return true; + } + return bailout ("Unable to read magic of %s (filename: '%s').", name.chars (), filename.chars ()); + } + + // check the path-numbers + if (!isGraph && hdr.length != length ()) { + return bailout ("Damaged %s (filename: '%s'). Mismatch number of nodes (got: '%d', need: '%d').", name.chars (), filename.chars (), hdr.length, m_paths.length ()); + } + + // check the count + if (hdr.length == 0 || hdr.length > kMaxNodes) { + if (tryReload ()) { + return true; + } + return bailout ("Damaged %s (filename: '%s'). Paths length is overflowed (got: '%d').", name.chars (), filename.chars (), hdr.length); + } + + // check the version + if (hdr.version != version) { + if (tryReload ()) { + return true; + } + return bailout ("Damaged %s (filename: '%s'). Version number differs (got: '%d', need: '%d').", name.chars (), filename.chars (), hdr.length, hdr.version, version); + } + + // check the storage type + if ((hdr.options & options) != options) { + return bailout ("Incorrect storage format for %s (filename: '%s').", name.chars (), filename.chars ()); + } + Array compressed (hdr.compressed + sizeof (uint8) * ULZ::Excess); + + // graph is not resized upon load + if (isGraph) { + resizeData (hdr.length); + } + + // read compressed data + if (file.read (compressed.data (), sizeof (uint8), hdr.compressed) == static_cast (hdr.compressed)) { + ULZ lz; + + // try to uncompress + if (lz.uncompress (compressed.data (), hdr.compressed, reinterpret_cast (data.data ()), hdr.uncompressed) == ULZ::UncompressFailure) { + return bailout ("Unable to decompress ULZ data for %s (filename: '%s').", name.chars (), filename.chars ()); + } + else { + + if (outOptions) { + outOptions = &hdr.options; + } + + // author of graph.. save + if ((hdr.options & StorageOption::Author) && blob != nullptr) { + file.read (blob, sizeof (uint8), 64); + } + game.print ("Successfully loaded Bots %s data (%d/%.2fMB).", name.chars (), data.length (), static_cast (data.capacity () * sizeof (U)) / 1024.0f / 1024.0f); + file.close (); + + return true; + } + } + else { + return bailout ("Unable to read ULZ data for %s (filename: '%s').", name.chars (), filename.chars ()); + } + return false; +} + +bool BotGraph::loadGraphData () { + uint8 blob[64]; + int32 outOptions = 0; + + m_paths.clear (); + + // check if loaded + bool dataLoaded = loadStorage ("graph", "Graph", StorageOption::Graph, StorageVersion::Graph, m_paths, reinterpret_cast (blob), &outOptions); + + if (dataLoaded) { + initGraph (); + initBuckets (); + + // add data to buckets + for (const auto &path : m_paths) { + addToBucket (path.origin, path.number); + } + + if (outOptions & StorageOption::Official) { + m_tempStrings.assign ("Using Official Graph File"); + } + else { + m_tempStrings.assignf ("Using Graph File By: %s", blob); + } + initNodesTypes (); + loadPathMatrix (); + loadVisibility (); + loadPractice (); + + extern ConVar yb_debug_goal; + yb_debug_goal.set (kInvalidNodeIndex); + + return true; + } + return false; +} + +bool BotGraph::saveGraphData () { + auto options = StorageOption::Graph | StorageOption::Author; + String author; + + if (game.isNullEntity (m_editor) && !m_tempStrings.empty ()) { + author = m_tempStrings; + + options |= StorageOption::Recovered; + } + else if (!game.isNullEntity (m_editor)) { + author = STRING (m_editor->v.netname); + } + else { + author = "YAPB"; + } + author.resize (64); + + // mark as official + if (author.startsWith ("YAPB")) { + options |= StorageOption::Official; + } + return saveStorage ("graph", "Graph", static_cast (options), StorageVersion::Graph, m_paths, reinterpret_cast (author.begin ())); +} + +void BotGraph::saveOldFormat () { + PODGraphHeader header; + strcpy (header.header, kPodbotMagic); + 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 = StorageVersion::Podbot; + header.pointNumber = m_paths.length (); + + File fp; + + // file was opened + if (fp.open (getOldFormatGraphName (), "wb")) { + // write the node header to the file... + fp.write (&header, sizeof (header)); + + // save the node paths... + for (const auto &path : m_paths) { + PODPath pod {}; + converToPOD (path, pod); + + fp.write (&pod, sizeof (PODPath)); + } + fp.close (); + } + else { + logger.error ("Error writing '%s.pwf' node file.", game.getMapName ()); + } +} + +const char *BotGraph::getOldFormatGraphName (bool isMemoryFile) { + static String buffer; + buffer.assignf ("%s%s%s.pwf", getDataDirectory (isMemoryFile), util.isEmptyStr (yb_graph_subfolder.str ()) ? "" : yb_graph_subfolder.str (), game.getMapName ()); + + if (File::exists (buffer)) { + return buffer.chars (); + } + return strings.format ("%s%s.pwf", getDataDirectory (isMemoryFile), game.getMapName ()); +} + +float BotGraph::calculateTravelTime (float maxSpeed, const Vector &src, const Vector &origin) { + // this function returns 2D traveltime to a position + + return (origin - src).length2d () / maxSpeed; +} + +bool BotGraph::isReachable (Bot *bot, int index) { + // this function return whether bot able to reach index node or not, depending on several factors. + + if (!bot || !exists (index)) { + return false; + } + const Vector &src = bot->pev->origin; + const Vector &dst = m_paths[index].origin; + + // is the destination close enough? + if ((dst - src).lengthSq () >= cr::square (320.0f)) { + return false; + } + float ladderDist = (dst - src).length2d (); + + TraceResult tr; + game.testLine (src, dst, TraceIgnore::Monsters, bot->ent (), &tr); + + // if node is visible from current position (even behind head)... + if (tr.flFraction >= 1.0f) { + + // it's should be not a problem to reach node inside water... + if (bot->pev->waterlevel == 2 || bot->pev->waterlevel == 3) { + return true; + } + + // check for ladder + bool nonLadder = !(m_paths[index].flags & NodeFlag::Ladder) || ladderDist > 16.0f; + + // is dest node higher than src? (62 is max jump height) + if (nonLadder && dst.z > src.z + 62.0f) { + return false; // can't reach this one + } + + // is dest node lower than src? + if (nonLadder && dst.z < src.z - 100.0f) { + return false; // can't reach this one + } + return true; + } + return false; +} + +bool BotGraph::isNodeReacheable (const Vector &src, const Vector &destination) { + TraceResult tr; + + float distance = (destination - src).length (); + + // is the destination not close enough? + if (distance > m_autoPathDistance) { + return false; + } + + // check if we go through a func_illusionary, in which case return false + game.testHull (src, destination, TraceIgnore::Monsters, head_hull, m_editor, &tr); + + if (!game.isNullEntity (tr.pHit) && strcmp ("func_illusionary", STRING (tr.pHit->v.classname)) == 0) { + return false; // don't add pathnodes through func_illusionaries + } + + // check if this node is "visible"... + game.testLine (src, destination, TraceIgnore::Monsters, m_editor, &tr); + + // if node 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) { + game.testLine (tr.vecEndPos, destination, TraceIgnore::Monsters, tr.pHit, &tr); + + if (tr.flFraction < 1.0f) { + return false; + } + } + + // check for special case of both nodes being in water... + if (engfuncs.pfnPointContents (src) == CONTENTS_WATER && engfuncs.pfnPointContents (destination) == CONTENTS_WATER) { + return true; // then they're reachable each other + } + + // is dest node higher than src? (45 is max jump height) + if (destination.z > src.z + 45.0f) { + Vector sourceNew = destination; + Vector destinationNew = destination; + destinationNew.z = destinationNew.z - 50.0f; // straight down 50 units + + game.testLine (sourceNew, destinationNew, TraceIgnore::Monsters, m_editor, &tr); + + // check if we didn't hit anything, if not then it's in mid-air + if (tr.flFraction >= 1.0) { + return false; // can't reach this one + } + } + + // check if distance to ground drops more than step height at points between source and destination... + Vector direction = (destination - src).normalize (); // 1 unit long + Vector check = src, down = src; + + down.z = down.z - 1000.0f; // straight down 1000 units + + game.testLine (check, down, TraceIgnore::Monsters, m_editor, &tr); + + float lastHeight = tr.flFraction * 1000.0f; // height from ground + distance = (destination - check).length (); // distance from goal + + while (distance > 10.0f) { + // move 10 units closer to the goal... + check = check + (direction * 10.0f); + + down = check; + down.z = down.z - 1000.0f; // straight down 1000 units + + game.testLine (check, down, TraceIgnore::Monsters, m_editor, &tr); + + float height = tr.flFraction * 1000.0f; // height from ground + + // is the current height greater than the step height? + if (height < lastHeight - 18.0f) { + return false; // can't get there without jumping... + } + lastHeight = height; + distance = (destination - check).length (); // distance from goal + } + return true; + } + return false; +} + +void BotGraph::rebuildVisibility () { + if (!m_needsVisRebuild) { + return; + } + + TraceResult tr; + uint8 res, shift; + + for (const auto &vis : m_paths) { + Vector sourceDuck = vis.origin; + Vector sourceStand = vis.origin; + + if (vis.flags & NodeFlag::Crouch) { + sourceDuck.z += 12.0f; + sourceStand.z += 18.0f + 28.0f; + } + else { + sourceDuck.z += -18.0f + 12.0f; + sourceStand.z += 28.0f; + } + uint16 standCount = 0, crouchCount = 0; + + for (const auto &path : m_paths) { + // first check ducked visibility + Vector dest = path.origin; + + game.testLine (sourceDuck, dest, TraceIgnore::Monsters, nullptr, &tr); + + // check if line of sight to object is not blocked (i.e. visible) + if (!cr::fequal (tr.flFraction, 1.0f) || tr.fStartSolid) { + res = 1; + } + else { + res = 0; + } + res <<= 1; + + game.testLine (sourceStand, dest, TraceIgnore::Monsters, nullptr, &tr); + + // check if line of sight to object is not blocked (i.e. visible) + if (cr::fequal (tr.flFraction, 1.0f) || tr.fStartSolid) { + res |= 1; + } + + if (res != 0) { + dest = path.origin; + + // first check ducked visibility + if (path.flags & NodeFlag::Crouch) { + dest.z += 18.0f + 28.0f; + } + else { + dest.z += 28.0f; + } + game.testLine (sourceDuck, dest, TraceIgnore::Monsters, nullptr, &tr); + + // check if line of sight to object is not blocked (i.e. visible) + if (!cr::fequal (tr.flFraction, 1.0f) || tr.fStartSolid) { + res |= 2; + } + else { + res &= 1; + } + game.testLine (sourceStand, dest, TraceIgnore::Monsters, nullptr, &tr); + + // check if line of sight to object is not blocked (i.e. visible) + if (!cr::fequal (tr.flFraction, 1.0f) || tr.fStartSolid) { + res |= 1; + } + else { + res &= 2; + } + } + shift = (path.number % 4) << 1; + + m_vistable[vis.number * m_paths.length () + path.number] &= ~(3 << shift); + m_vistable[vis.number * m_paths.length () + path.number] |= res << shift; + + if (!(res & 2)) { + ++crouchCount; + } + + if (!(res & 1)) { + ++standCount; + } + } + m_paths[vis.number].vis.crouch = crouchCount; + m_paths[vis.number].vis.stand = standCount; + } + m_needsVisRebuild = false; + saveVisibility (); +} + +bool BotGraph::isVisible (int srcIndex, int destIndex) { + if (!exists (srcIndex) || !exists (destIndex)) { + return false; + } + + uint8 res = m_vistable[srcIndex * m_paths.length () + destIndex]; + res >>= (destIndex % 4) << 1; + + return !((res & 3) == 3); +} + +bool BotGraph::isDuckVisible (int srcIndex, int destIndex) { + if (!exists (srcIndex) || !exists (destIndex)) { + return false; + } + + uint8 res = m_vistable[srcIndex * m_paths.length () + destIndex]; + res >>= (destIndex % 4) << 1; + + return !((res & 2) == 2); +} + +bool BotGraph::isStandVisible (int srcIndex, int destIndex) { + if (!exists (srcIndex) || !exists (destIndex)) { + return false; + } + + uint8 res = m_vistable[srcIndex * m_paths.length () + destIndex]; + res >>= (destIndex % 4) << 1; + + return !((res & 1) == 1); +} + +void BotGraph::frame () { + // this function executes frame of graph operation code. + + if (game.isNullEntity (m_editor)) { + return; // this function is only valid with editor, and in graph enabled mode. + } + + // keep the clipping mode enabled, or it can be turned off after new round has started + if (graph.hasEditFlag (GraphEdit::Noclip) && util.isAlive (m_editor)) { + m_editor->v.movetype = MOVETYPE_NOCLIP; + } + + float nearestDistance = 99999.0f; + int nearestIndex = kInvalidNodeIndex; + + // check if it's time to add jump node + if (m_jumpLearnNode) { + if (!m_endJumpPoint) { + if (m_editor->v.button & IN_JUMP) { + add (9); + + m_timeJumpStarted = game.timebase (); + m_endJumpPoint = true; + } + else { + m_learnVelocity = m_editor->v.velocity; + m_learnPosition = m_editor->v.origin; + } + } + else if (((m_editor->v.flags & FL_ONGROUND) || m_editor->v.movetype == MOVETYPE_FLY) && m_timeJumpStarted + 0.1f < game.timebase () && m_endJumpPoint) { + add (10); + + m_jumpLearnNode = false; + m_endJumpPoint = false; + } + } + + // check if it's a auto-add-node mode enabled + if (hasEditFlag (GraphEdit::Auto) && (m_editor->v.flags & (FL_ONGROUND | FL_PARTIALGROUND))) { + // find the distance from the last used node + float distance = (m_lastNode - m_editor->v.origin).lengthSq (); + + if (distance > 16384.0f) { + // check that no other reachable nodes are nearby... + for (const auto &path : m_paths) { + if (isNodeReacheable (m_editor->v.origin, path.origin)) { + distance = (path.origin - m_editor->v.origin).lengthSq (); + + if (distance < nearestDistance) { + nearestDistance = distance; + } + } + } + + // make sure nearest node is far enough away... + if (nearestDistance >= cr::square (128.0f)) { + add (GraphAdd::Normal); // place a node here + } + } + } + m_facingAtIndex = getFacingIndex (); + + // reset the minimal distance changed before + nearestDistance = 999999.0f; + + // now iterate through all nodes in a map, and draw required ones + for (auto &path : m_paths) { + float distance = (path.origin - m_editor->v.origin).length (); + + // check if node is whitin a distance, and is visible + if (distance < 512.0f && ((util.isVisible (path.origin, m_editor) && util.isInViewCone (path.origin, m_editor)) || !util.isAlive (m_editor) || distance < 128.0f)) { + // check the distance + if (distance < nearestDistance) { + nearestIndex = path.number; + nearestDistance = distance; + } + + if (path.display + 0.8f < game.timebase ()) { + float nodeHeight = 0.0f; + + // check the node height + if (path.flags & NodeFlag::Crouch) { + nodeHeight = 36.0f; + } + else { + nodeHeight = 72.0f; + } + float nodeHalfHeight = nodeHeight * 0.5f; + + // all nodes are by default are green + Color nodeColor = Color (-1, -1, -1); + + // colorize all other nodes + if (path.flags & NodeFlag::Camp) { + nodeColor = Color (0, 255, 255); + } + else if (path.flags & NodeFlag::Goal) { + nodeColor = Color (128, 0, 255); + } + else if (path.flags & NodeFlag::Ladder) { + nodeColor = Color (128, 64, 0); + } + else if (path.flags & NodeFlag::Rescue) { + nodeColor = Color (255, 255, 255); + } + else { + nodeColor = Color (0, 255, 0); + } + + // colorize additional flags + Color nodeFlagColor = Color (-1, -1, -1); + + // check the colors + if (path.flags & NodeFlag::Sniper) { + nodeFlagColor = Color (130, 87, 0); + } + else if (path.flags & NodeFlag::NoHostage) { + nodeFlagColor = Color (255, 255, 255); + } + else if (path.flags & NodeFlag::TerroristOnly) { + nodeFlagColor = Color (255, 0, 0); + } + else if (path.flags & NodeFlag::CTOnly) { + nodeFlagColor = Color (0, 0, 255); + } + int nodeWidth = 14; + + if (exists (m_facingAtIndex) && path.number == m_facingAtIndex) { + nodeWidth *= 2; + } + + // draw node without additional flags + if (nodeFlagColor.red == -1) { + game.drawLine (m_editor, path.origin - Vector (0, 0, nodeHalfHeight), path.origin + Vector (0, 0, nodeHalfHeight), nodeWidth + 1, 0, nodeColor, 250, 0, 10); + } + + // draw node with flags + else { + game.drawLine (m_editor, path.origin - Vector (0, 0, nodeHalfHeight), path.origin - Vector (0, 0, nodeHalfHeight - nodeHeight * 0.75f), nodeWidth, 0, nodeColor, 250, 0, 10); // draw basic path + game.drawLine (m_editor, path.origin - Vector (0, 0, nodeHalfHeight - nodeHeight * 0.75f), path.origin + Vector (0, 0, nodeHalfHeight), nodeWidth, 0, nodeFlagColor, 250, 0, 10); // draw additional path + } + path.display = game.timebase (); + } + } + } + + if (nearestIndex == kInvalidNodeIndex) { + return; + } + + // draw arrow to a some importaint nodes + if (exists (m_findWPIndex) || exists (m_cacheNodeIndex) || exists (m_facingAtIndex)) { + // check for drawing code + if (m_arrowDisplayTime + 0.5f < game.timebase ()) { + + // finding node - pink arrow + if (m_findWPIndex != kInvalidNodeIndex) { + game.drawLine (m_editor, m_editor->v.origin, m_paths[m_findWPIndex].origin, 10, 0, Color (128, 0, 128), 200, 0, 5, DrawLine::Arrow); + } + + // cached node - yellow arrow + if (m_cacheNodeIndex != kInvalidNodeIndex) { + game.drawLine (m_editor, m_editor->v.origin, m_paths[m_cacheNodeIndex].origin, 10, 0, Color (255, 255, 0), 200, 0, 5, DrawLine::Arrow); + } + + // node user facing at - white arrow + if (m_facingAtIndex != kInvalidNodeIndex) { + game.drawLine (m_editor, m_editor->v.origin, m_paths[m_facingAtIndex].origin, 10, 0, Color (255, 255, 255), 200, 0, 5, DrawLine::Arrow); + } + m_arrowDisplayTime = game.timebase (); + } + } + + // create path pointer for faster access + auto &path = m_paths[nearestIndex]; + + // draw a paths, camplines and danger directions for nearest node + if (nearestDistance <= 56.0f && m_pathDisplayTime <= game.timebase ()) { + m_pathDisplayTime = game.timebase () + 1.0f; + + // draw the camplines + if (path.flags & NodeFlag::Camp) { + float height = 36.0f; + + // check if it's a source + if (path.flags & NodeFlag::Crouch) { + height = 18.0f; + } + const Vector &source = Vector (path.origin.x, path.origin.y, path.origin.z + height); // source + + game.makeVectors (Vector (path.start.x, path.start.y, 0)); + const Vector &start = path.origin + game.vec.forward * 500.0f; // camp start + + game.makeVectors (Vector (path.end.x, path.end.y, 0)); + const Vector &end = path.origin + game.vec.forward * 500.0f; // camp end + + // draw it now + game.drawLine (m_editor, source, start, 10, 0, Color (255, 0, 0), 200, 0, 10); + game.drawLine (m_editor, source, end, 10, 0, Color (255, 0, 0), 200, 0, 10); + } + + // draw the connections + for (const auto &link : path.links) { + if (link.index == kInvalidNodeIndex) { + continue; + } + // jump connection + if (link.flags & PathFlag::Jump) { + game.drawLine (m_editor, path.origin, m_paths[link.index].origin, 5, 0, Color (255, 0, 128), 200, 0, 10); + } + else if (isConnected (link.index, nearestIndex)) { // twoway connection + game.drawLine (m_editor, path.origin, m_paths[link.index].origin, 5, 0, Color (255, 255, 0), 200, 0, 10); + } + else { // oneway connection + game.drawLine (m_editor, path.origin, m_paths[link.index].origin, 5, 0, Color (250, 250, 250), 200, 0, 10); + } + } + + // now look for oneway incoming connections + for (const auto &connected : m_paths) { + if (isConnected (connected.number, path.number) && !isConnected (path.number, connected.number)) { + game.drawLine (m_editor, path.origin, connected.origin, 5, 0, Color (0, 192, 96), 200, 0, 10); + } + } + + // draw the radius circle + Vector origin = (path.flags & NodeFlag::Crouch) ? path.origin : path.origin - Vector (0.0f, 0.0f, 18.0f); + Color radiusColor (0, 0, 255); + + // if radius is nonzero, draw a full circle + if (path.radius > 0.0f) { + float sqr = cr::sqrtf (path.radius * path.radius * 0.5f); + + game.drawLine (m_editor, origin + Vector (path.radius, 0.0f, 0.0f), origin + Vector (sqr, -sqr, 0.0f), 5, 0, radiusColor, 200, 0, 10); + game.drawLine (m_editor, origin + Vector (sqr, -sqr, 0.0f), origin + Vector (0.0f, -path.radius, 0.0f), 5, 0, radiusColor, 200, 0, 10); + + game.drawLine (m_editor, origin + Vector (0.0f, -path.radius, 0.0f), origin + Vector (-sqr, -sqr, 0.0f), 5, 0, radiusColor, 200, 0, 10); + game.drawLine (m_editor, origin + Vector (-sqr, -sqr, 0.0f), origin + Vector (-path.radius, 0.0f, 0.0f), 5, 0, radiusColor, 200, 0, 10); + + game.drawLine (m_editor, origin + Vector (-path.radius, 0.0f, 0.0f), origin + Vector (-sqr, sqr, 0.0f), 5, 0, radiusColor, 200, 0, 10); + game.drawLine (m_editor, origin + Vector (-sqr, sqr, 0.0f), origin + Vector (0.0f, path.radius, 0.0f), 5, 0, radiusColor, 200, 0, 10); + + game.drawLine (m_editor, origin + Vector (0.0f, path.radius, 0.0f), origin + Vector (sqr, sqr, 0.0f), 5, 0, radiusColor, 200, 0, 10); + game.drawLine (m_editor, origin + Vector (sqr, sqr, 0.0f), origin + Vector (path.radius, 0.0f, 0.0f), 5, 0, radiusColor, 200, 0, 10); + } + else { + float sqr = cr::sqrtf (32.0f); + + game.drawLine (m_editor, origin + Vector (sqr, -sqr, 0.0f), origin + Vector (-sqr, sqr, 0.0f), 5, 0, radiusColor, 200, 0, 10); + game.drawLine (m_editor, origin + Vector (-sqr, -sqr, 0.0f), origin + Vector (sqr, sqr, 0.0f), 5, 0, radiusColor, 200, 0, 10); + } + + // draw the danger directions + if (!m_hasChanged) { + int dangerIndex = getDangerIndex (game.getTeam (m_editor), nearestIndex, nearestIndex); + + if (exists (dangerIndex)) { + game.drawLine (m_editor, path.origin, m_paths[dangerIndex].origin, 15, 0, Color (255, 0, 0), 200, 0, 10, DrawLine::Arrow); // draw a red arrow to this index's danger point + } + } + + auto getFlagsAsStr = [&] (int index) { + auto &path = m_paths[index]; + bool jumpPoint = false; + + // iterate through connections and find, if it's a jump path + for (const auto &link : path.links) { + + // check if we got a valid connection + if (link.index != kInvalidNodeIndex && (link.flags & PathFlag::Jump)) { + jumpPoint = true; + } + } + + static String buffer; + buffer.assignf ("%s%s%s%s%s%s%s%s%s%s%s%s%s%s", (path.flags == 0 && !jumpPoint) ? " (none)" : "", (path.flags & NodeFlag::Lift) ? " LIFT" : "", (path.flags & NodeFlag::Crouch) ? " CROUCH" : "", (path.flags & NodeFlag::Crossing) ? " CROSSING" : "", (path.flags & NodeFlag::Camp) ? " CAMP" : "", (path.flags & NodeFlag::TerroristOnly) ? " TERRORIST" : "", (path.flags & NodeFlag::CTOnly) ? " CT" : "", (path.flags & NodeFlag::Sniper) ? " SNIPER" : "", (path.flags & NodeFlag::Goal) ? " GOAL" : "", (path.flags & NodeFlag::Ladder) ? " LADDER" : "", (path.flags & NodeFlag::Rescue) ? " RESCUE" : "", (path.flags & NodeFlag::DoubleJump) ? " JUMPHELP" : "", (path.flags & NodeFlag::NoHostage) ? " NOHOSTAGE" : "", jumpPoint ? " JUMP" : ""); + + // return the message buffer + return buffer.chars (); + }; + + // display some information + String graphMessage; + + // show the information about that point + graphMessage.assignf ("\n\n\n\n Graph Information:\n\n" + " Node %d of %d, Radius: %.1f\n" + " Flags: %s\n\n", nearestIndex, m_paths.length () - 1, path.radius, getFlagsAsStr (nearestIndex)); + + // if node is not changed display experience also + if (!m_hasChanged) { + int dangerIndexCT = getDangerIndex (Team::CT, nearestIndex, nearestIndex); + int dangerIndexT = getDangerIndex (Team::Terrorist, nearestIndex, nearestIndex); + + graphMessage.appendf (" Experience Info:\n" + " CT: %d / %d dmg\n" + " T: %d / %d dmg\n", dangerIndexCT, dangerIndexCT != kInvalidNodeIndex ? getDangerDamage (Team::CT, nearestIndex, dangerIndexCT) : 0, dangerIndexT, dangerIndexT != kInvalidNodeIndex ? getDangerDamage (Team::Terrorist, nearestIndex, dangerIndexT) : 0); + } + + // check if we need to show the cached point index + if (m_cacheNodeIndex != kInvalidNodeIndex) { + graphMessage.appendf ("\n Cached Node Information:\n\n" + " Node %d of %d, Radius: %.1f\n" + " Flags: %s\n", m_cacheNodeIndex, m_paths.length (), m_paths[m_cacheNodeIndex].radius, getFlagsAsStr (m_cacheNodeIndex)); + } + + // check if we need to show the facing point index + if (m_facingAtIndex != kInvalidNodeIndex) { + graphMessage.appendf ("\n Facing Node Information:\n\n" + " Node %d of %d, Radius: %.1f\n" + " Flags: %s\n", m_facingAtIndex, m_paths.length (), m_paths[m_facingAtIndex].radius, getFlagsAsStr (m_facingAtIndex)); + } + + // draw entire message + MessageWriter (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, nullvec, m_editor) + .writeByte (TE_TEXTMESSAGE) + .writeByte (4) // channel + .writeShort (MessageWriter::fs16 (0.0f, 13.0f)) // x + .writeShort (MessageWriter::fs16 (0.0f, 13.0f)) // y + .writeByte (0) // effect + .writeByte (255) // r1 + .writeByte (255) // g1 + .writeByte (255) // b1 + .writeByte (1) // a1 + .writeByte (255) // r2 + .writeByte (255) // g2 + .writeByte (255) // b2 + .writeByte (255) // a2 + .writeShort (0) // fadeintime + .writeShort (0) // fadeouttime + .writeShort (MessageWriter::fu16 (1.1f, 8.0f)) // holdtime + .writeString (graphMessage.chars ()); + } +} + +bool BotGraph::isConnected (int index) { + for (const auto &path : m_paths) { + if (path.number == index) { + continue; + } + + for (const auto &test : path.links) { + if (test.index == index) { + return true; + } + } + } + return false; +} + +bool BotGraph::checkNodes (bool teleportPlayer) { + + auto teleport = [&] (const Path &path) -> void { + if (teleportPlayer) { + engfuncs.pfnSetOrigin (m_editor, path.origin); + setEditFlag (GraphEdit::On | GraphEdit::Noclip); + } + }; + + int terrPoints = 0; + int ctPoints = 0; + int goalPoints = 0; + int rescuePoints = 0; + + ctrl.setFromConsole (true); + + for (const auto &path : m_paths) { + int connections = 0; + + if (path.number != static_cast (m_paths.index (path))) { + ctrl.msg ("Node %d path differs from index %d!", path.number, m_paths.index (path)); + break; + } + + for (const auto &test : path.links) { + if (test.index != kInvalidNodeIndex) { + if (test.index > length ()) { + ctrl.msg ("Node %d connected with invalid Node #%d!", path.number, test.index); + return false; + } + ++connections; + break; + } + } + + if (connections == 0) { + if (!isConnected (path.number)) { + ctrl.msg ("Node %d isn't connected with any other Node!", path.number); + return false; + } + } + + if (path.flags & NodeFlag::Camp) { + if (path.end == nullvec) { + ctrl.msg ("Node %d Camp-Endposition not set!", path.number); + return false; + } + } + else if (path.flags & NodeFlag::TerroristOnly) { + ++terrPoints; + } + else if (path.flags & NodeFlag::CTOnly) { + ++ctPoints; + } + else if (path.flags & NodeFlag::Goal) { + ++goalPoints; + } + else if (path.flags & NodeFlag::Rescue) { + ++rescuePoints; + } + + for (const auto &test : path.links) { + if (test.index != kInvalidNodeIndex) { + if (!exists (test.index)) { + ctrl.msg ("Node %d - Pathindex %d out of Range!", path.number, test.index); + teleport (path); + + return false; + } + else if (test.index == path.number) { + ctrl.msg ("Node %d - Pathindex %d points to itself!", path.number, test.index); + teleport (path); + + return false; + } + } + } + } + + if (game.mapIs (MapFlags::HostageRescue)) { + if (rescuePoints == 0) { + ctrl.msg ("You didn't set a Rescue Point!"); + return false; + } + } + if (terrPoints == 0) { + ctrl.msg ("You didn't set any Terrorist Important Point!"); + return false; + } + else if (ctPoints == 0) { + ctrl.msg ("You didn't set any CT Important Point!"); + return false; + } + else if (goalPoints == 0) { + ctrl.msg ("You didn't set any Goal Point!"); + return false; + } + + // perform DFS instead of floyd-warshall, this shit speedup this process in a bit + PathWalk walk; + Array visited; + visited.resize (m_paths.length ()); + + // first check incoming connectivity, initialize the "visited" table + for (auto &visit : visited) { + visit = false; + } + walk.push (0); // always check from node number 0 + + while (!walk.empty ()) { + // pop a node from the stack + const int current = walk.first (); + walk.shift (); + + visited[current] = true; + + for (const auto &link : m_paths[current].links) { + int index = link.index; + + // skip this node as it's already visited + if (exists (index) && !visited[index]) { + visited[index] = true; + walk.push (index); + } + } + } + + for (const auto &path : m_paths) { + if (!visited[path.number]) { + ctrl.msg ("Path broken from Node #0 to Node #%d!", path.number); + teleport (path); + + return false; + } + } + + // then check outgoing connectivity + Array outgoingPaths; // store incoming paths for speedup + outgoingPaths.resize (m_paths.length ()); + + for (const auto &path: m_paths) { + outgoingPaths[path.number].resize (m_paths.length () + 1); + + for (const auto &link : path.links) { + if (exists (link.index)) { + outgoingPaths[link.index].push (path.number); + } + } + } + + // initialize the "visited" table + for (auto &visit : visited) { + visit = false; + } + walk.clear (); + walk.push (0); // always check from node number 0 + + while (!walk.empty ()) { + const int current = walk.first (); // pop a node from the stack + walk.shift (); + + for (auto &outgoing : outgoingPaths[current]) { + if (visited[outgoing]) { + continue; // skip this node as it's already visited + } + visited[outgoing] = true; + walk.push (outgoing); + } + } + + for (const auto &path : m_paths) { + if (!visited[path.number]) { + ctrl.msg ("Path broken from Node #%d to Node #0!", path.number); + teleport (path); + + return false; + } + } + return true; +} + +int BotGraph::getPathDist (int srcIndex, int destIndex) { + if (!exists (srcIndex) || !exists (destIndex)) { + return 1; + } + return (m_matrix.data () + (srcIndex * length ()) + destIndex)->dist; +} + +void BotGraph::setVisited (int index) { + if (!exists (index)) { + return; + } + if (!isVisited (index) && (m_paths[index].flags & NodeFlag::Goal)) { + m_visitedGoals.push (index); + } +} + +void BotGraph::clearVisited () { + m_visitedGoals.clear (); +} + +bool BotGraph::isVisited (int index) { + for (auto &visited : m_visitedGoals) { + if (visited == index) { + return true; + } + } + return false; +} + +void BotGraph::addBasic () { + // this function creates basic node types on map + + edict_t *ent = nullptr; + + // first of all, if map contains ladder points, create it + while (!game.isNullEntity (ent = engfuncs.pfnFindEntityByString (ent, "classname", "func_ladder"))) { + Vector ladderLeft = ent->v.absmin; + Vector ladderRight = ent->v.absmax; + ladderLeft.z = ladderRight.z; + + TraceResult tr; + Vector up, down, front, back; + + Vector diff = ((ladderLeft - ladderRight) ^ Vector (0.0f, 0.0f, 0.0f)).normalize () * 15.0f; + front = back = game.getAbsPos (ent); + + front = front + diff; // front + back = back - diff; // back + + up = down = front; + down.z = ent->v.absmax.z; + + game.testHull (down, up, TraceIgnore::Monsters, point_hull, nullptr, &tr); + + if (engfuncs.pfnPointContents (up) == CONTENTS_SOLID || !cr::fequal (tr.flFraction, 1.0f)) { + up = down = back; + down.z = ent->v.absmax.z; + } + + game.testHull (down, up - Vector (0.0f, 0.0f, 1000.0f), TraceIgnore::Monsters, point_hull, nullptr, &tr); + up = tr.vecEndPos; + + Vector point = up + Vector (0.0f, 0.0f, 39.0f); + m_isOnLadder = true; + + do { + if (getNearestNoBuckets (point, 50.0f) == kInvalidNodeIndex) { + add (3, point); + } + point.z += 160; + } while (point.z < down.z - 40.0f); + + point = down + Vector (0.0f, 0.0f, 38.0f); + + if (getNearestNoBuckets (point, 50.0f) == kInvalidNodeIndex) { + add (3, point); + } + m_isOnLadder = false; + } + + auto autoCreateForEntity = [](int type, const char *entity) { + edict_t *ent = nullptr; + + while (!game.isNullEntity (ent = engfuncs.pfnFindEntityByString (ent, "classname", entity))) { + const Vector &pos = game.getAbsPos (ent); + + if (graph.getNearestNoBuckets (pos, 50.0f) == kInvalidNodeIndex) { + graph.add (type, pos); + } + } + }; + + autoCreateForEntity (0, "info_player_deathmatch"); // then terrortist spawnpoints + autoCreateForEntity (0, "info_player_start"); // then add ct spawnpoints + autoCreateForEntity (0, "info_vip_start"); // then vip spawnpoint + autoCreateForEntity (0, "armoury_entity"); // weapons on the map ? + + autoCreateForEntity (4, "func_hostage_rescue"); // hostage rescue zone + autoCreateForEntity (4, "info_hostage_rescue"); // hostage rescue zone (same as above) + + autoCreateForEntity (100, "func_bomb_target"); // bombspot zone + autoCreateForEntity (100, "info_bomb_target"); // bombspot zone (same as above) + autoCreateForEntity (100, "hostage_entity"); // hostage entities + autoCreateForEntity (100, "func_vip_safetyzone"); // vip rescue (safety) zone + autoCreateForEntity (100, "func_escapezone"); // terrorist escape zone +} + +void BotGraph::eraseFromDisk () { + // this function removes graph file from the hard disk + + StringArray forErase; + const char *map = game.getMapName (); + + bots.kickEveryone (true); + + // if we're delete graph, delete all corresponding to it files + forErase.push (strings.format ("%s%s.pwf", getDataDirectory (), map)); // graph itself + forErase.push (strings.format ("%slearned/%s.exp", getDataDirectory (), map)); // corresponding to practice + forErase.push (strings.format ("%slearned/%s.vis", getDataDirectory (), map)); // corresponding to vistable + forErase.push (strings.format ("%slearned/%s.pmx", getDataDirectory (), map)); // corresponding to matrix + forErase.push (strings.format ("%sgraph/%s.graph", getDataDirectory (), map)); // new format graph + + for (const auto &item : forErase) { + if (File::exists (item)) { + plat.removeDirectory (item.chars ()); + game.print ("File %s, has been deleted from the hard disk", item.chars ()); + } + else { + logger.error ("Unable to open %s", item.chars ()); + } + } + initGraph (); // reintialize points + m_paths.clear (); +} + +const char *BotGraph::getDataDirectory (bool isMemoryFile) { + static String buffer; + buffer.clear (); + + if (isMemoryFile) { + buffer.assign ("addons/yapb/data/"); + } + else { + buffer.assignf ("%s/addons/yapb/data/", game.getModName ()); + } + return buffer.chars (); +} + +void BotGraph::setBombPos (bool reset, const Vector &pos) { + // this function stores the bomb position as a vector + + if (reset) { + m_bombPos= nullvec; + bots.setBombPlanted (false); + + return; + } + + if (!pos.empty ()) { + m_bombPos = pos; + return; + } + edict_t *ent = nullptr; + + while (!game.isNullEntity (ent = engfuncs.pfnFindEntityByString (ent, "classname", "grenade"))) { + if (strcmp (STRING (ent->v.model) + 9, "c4.mdl") == 0) { + m_bombPos = game.getAbsPos (ent); + break; + } + } +} + +void BotGraph::startLearnJump () { + m_jumpLearnNode = true; +} + +void BotGraph::setSearchIndex (int index) { + m_findWPIndex = index; + + if (exists (m_findWPIndex)) { + ctrl.msg ("Showing Direction to Node #%d", m_findWPIndex); + } + else { + m_findWPIndex = kInvalidNodeIndex; + } +} + +BotGraph::BotGraph () { + memset (m_highestDamage, 0, sizeof (m_highestDamage)); + + m_endJumpPoint = false; + m_needsVisRebuild = false; + m_jumpLearnNode = false; + m_hasChanged = false; + m_timeJumpStarted = 0.0f; + + m_lastJumpNode = kInvalidNodeIndex; + m_cacheNodeIndex = kInvalidNodeIndex; + m_findWPIndex = kInvalidNodeIndex; + m_facingAtIndex = kInvalidNodeIndex; + m_loadAttempts = 0; + m_isOnLadder = false; + + m_terrorPoints.clear (); + m_ctPoints.clear (); + m_goalPoints.clear (); + m_campPoints.clear (); + m_rescuePoints.clear (); + m_sniperPoints.clear (); + + m_loadAttempts = 0; + m_editFlags = 0; + m_pathDisplayTime = 0.0f; + m_arrowDisplayTime = 0.0f; + m_autoPathDistance = 250.0f; + + m_matrix.clear (); + m_practice.clear (); + + m_editor = nullptr; +} + +void BotGraph::initBuckets () { + for (int x = 0; x < kMaxBucketsInsidePos; ++x) { + for (int y = 0; y < kMaxBucketsInsidePos; ++y) { + for (int z = 0; z < kMaxBucketsInsidePos; ++z) { + m_buckets[x][y][z].reserve (kMaxNodesInsideBucket); + m_buckets[x][y][z].clear (); + } + } + } +} + +void BotGraph::addToBucket (const Vector &pos, int index) { + const auto &bucket = locateBucket (pos); + m_buckets[bucket.x][bucket.y][bucket.z].push (index); +} + +void BotGraph::eraseFromBucket (const Vector &pos, int index) { + const auto &bucket = locateBucket (pos); + auto &data = m_buckets[bucket.x][bucket.y][bucket.z]; + + for (size_t i = 0; i < data.length (); ++i) { + if (data[i] == index) { + data.erase (i, 1); + break; + } + } +} + +BotGraph::Bucket BotGraph::locateBucket (const Vector &pos) { + constexpr auto size = static_cast (kMaxNodes * 2); + + return { + cr::abs (static_cast ((pos.x + size) / kMaxBucketSize)), + cr::abs (static_cast ((pos.y + size) / kMaxBucketSize)), + cr::abs (static_cast ((pos.z + size) / kMaxBucketSize)) + }; +} + +const Array &BotGraph::getNodesInBucket (const Vector &pos) { + const auto &bucket = locateBucket (pos); + return m_buckets[bucket.x][bucket.y][bucket.z]; +} + +void BotGraph::updateGlobalPractice () { + // this function called after each end of the round to update knowledge about most dangerous nodes for each team. + + // no nodes, no experience used or nodes edited or being edited? + if (m_paths.empty () || m_hasChanged) { + return; // no action + } + bool adjustValues = false; + auto practice = m_practice.data (); + + // get the most dangerous node for this position for both teams + for (int team = Team::Terrorist; team < kGameTeamNum; ++team) { + int bestIndex = kInvalidNodeIndex; // best index to store + int maxDamage = 0; + + for (int i = 0; i < length (); ++i) { + maxDamage = 0; + bestIndex = kInvalidNodeIndex; + + for (int j = 0; j < length (); ++j) { + if (i == j) { + continue; + } + int actDamage = getDangerDamage (team, i, j); + + if (actDamage > maxDamage) { + maxDamage = actDamage; + bestIndex = j; + } + } + + if (maxDamage > kMaxPracticeDamageValue) { + adjustValues = true; + } + (practice + (i * length ()) + i)->index[team] = static_cast (bestIndex); + } + } + constexpr int HALF_DAMAGE_VALUE = static_cast (kMaxPracticeDamageValue * 0.5); + + // adjust values if overflow is about to happen + if (adjustValues) { + for (int team = Team::Terrorist; team < kGameTeamNum; ++team) { + for (int i = 0; i < length (); ++i) { + for (int j = 0; j < length (); ++j) { + if (i == j) { + continue; + } + (practice + (i * length ()) + j)->damage[team] = static_cast (cr::clamp (getDangerDamage (team, i, j) - HALF_DAMAGE_VALUE, 0, kMaxPracticeDamageValue)); + } + } + } + } + + for (int team = Team::Terrorist; team < kGameTeamNum; ++team) { + m_highestDamage[team] = cr::clamp (m_highestDamage [team] - HALF_DAMAGE_VALUE, 1, kMaxPracticeDamageValue); + } +} + +void BotGraph::unassignPath (int from, int to) { + auto &link = m_paths[from].links[to]; + + link.index = kInvalidNodeIndex; + link.distance = 0; + link.flags = 0; + link.velocity = nullvec; + + setEditFlag (GraphEdit::On); + m_hasChanged = true; +} + +void BotGraph::convertFromPOD (Path &path, const PODPath &pod) { + path = {}; + + path.number = pod.number; + path.flags = pod.flags; + path.origin = pod.origin; + path.start = Vector (pod.csx, pod.csy, 0.0f); + path.end = Vector (pod.cex, pod.cey, 0.0f); + + if (yb_graph_fixcamp.bool_ ()) { + convertCampDirection (path); + } + path.radius = pod.radius; + path.light = 0.0f; + path.display = 0.0f; + + for (int i = 0; i < kMaxNodeLinks; ++i) { + path.links[i].index = pod.index[i]; + path.links[i].distance = pod.distance[i]; + path.links[i].flags = pod.conflags[i]; + path.links[i].velocity = pod.velocity[i]; + } + path.vis.stand = pod.vis.stand; + path.vis.crouch = pod.vis.crouch; +} + +void BotGraph::converToPOD (const Path &path, PODPath &pod) { + pod = {}; + + pod.number = path.number; + pod.flags = path.flags; + pod.origin = path.origin; + pod.radius = path.radius; + pod.csx = path.start.x; + pod.csy = path.start.y; + pod.cex = path.end.x; + pod.cey = path.end.y; + + for (int i = 0; i < kMaxNodeLinks; ++i) { + pod.index[i] = path.links[i].index; + pod.distance[i] = path.links[i].distance; + pod.conflags[i] = path.links[i].flags; + pod.velocity[i] = path.links[i].velocity; + } + pod.vis.stand = path.vis.stand; + pod.vis.crouch = path.vis.crouch; +} + +void BotGraph::convertCampDirection (Path &path) { + // this function converts old vector based camp directions to angles, note that podbotmm graph + // are already saved with angles, and converting this stuff may result strange look directions. + + if (m_paths.empty ()) { + return; + } + const Vector &offset = path.origin + Vector (0.0f, 0.0f, (path.flags & NodeFlag::Crouch) ? 15.0f : 17.0f); + + path.start = (Vector (path.start.x, path.start.y, path.origin.z) - offset).angles (); + path.end = (Vector (path.end.x, path.end.y, path.origin.z) - offset).angles (); + + path.start.x = -path.start.x; + path.end.x = -path.end.x; + + path.start.clampAngles (); + path.end.clampAngles (); +} + +int BotGraph::getDangerIndex (int team, int start, int goal) { + if (team != Team::Terrorist && team != Team::CT) { + return kInvalidNodeIndex; + } + + // realiablity check + if (!exists (start) || !exists (goal)) { + return kInvalidNodeIndex; + } + return (m_practice.data () + (start * length ()) + goal)->index[team]; +} + +int BotGraph::getDangerValue (int team, int start, int goal) { + if (team != Team::Terrorist && team != Team::CT) { + return 0; + } + + // reliability check + if (!exists (start) || !exists (goal)) { + return 0; + } + return (m_practice.data () + (start * length ()) + goal)->value[team]; +} + +int BotGraph::getDangerDamage (int team, int start, int goal) { + if (team != Team::Terrorist && team != Team::CT) { + return 0; + } + + // reliability check + if (!exists (start) || !exists (goal)) { + return 0; + } + return (m_practice.data () + (start * length ()) + goal)->damage[team]; +} + +void BotGraph::setDangerValue (int team, int start, int goal, int value) { + if (team != Team::Terrorist && team != Team::CT) { + return; + } + + // reliability check + if (!exists (start) || !exists (goal)) { + return; + } + (m_practice.data () + (start * length ()) + goal)->value[team] = static_cast (value); +} + +void BotGraph::setDangerDamage (int team, int start, int goal, int value) { + if (team != Team::Terrorist && team != Team::CT) { + return; + } + + // reliability check + if (!exists (start) || !exists (goal)) { + return; + } + (m_practice.data () + (start * length ()) + goal)->damage[team] = static_cast (value); +} diff --git a/source/interface.cpp b/source/interface.cpp index c62eb30..4770ae6 100644 --- a/source/interface.cpp +++ b/source/interface.cpp @@ -9,7 +9,7 @@ #include -ConVar yb_version ("yb_version", PRODUCT_VERSION, VT_READONLY); +ConVar yb_version ("yb_version", PRODUCT_VERSION, Var::ReadOnly); gamefuncs_t dllapi; enginefuncs_t engfuncs; @@ -33,7 +33,7 @@ plugin_info_t Plugin_info = { PT_ANYTIME, // when unloadable }; -namespace VariadicCallbacks { +namespace variadic { 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 @@ -50,31 +50,31 @@ namespace VariadicCallbacks { // 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]; + auto buffer = strings.chars (); va_start (ap, format); - _vsnprintf (buffer, cr::bufsize (buffer), format, ap); + _vsnprintf (buffer, StringBuffer::StaticBufferSize, format, ap); va_end (ap); if (ent && (ent->v.flags & (FL_FAKECLIENT | FL_DORMANT))) { - if (bots.getBot (ent)) { - game.execBotCmd (ent, buffer); + if (bots[ent]) { + game.botCommand (ent, buffer); } - if (game.is (GAME_METAMOD)) { + if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_SUPERCEDE); // prevent bots to be forced to issue client commands } return; } - if (game.is (GAME_METAMOD)) { + if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } engfuncs.pfnClientCommand (ent, buffer); } } -SHARED_LIBRARAY_EXPORT int GetEntityAPI2 (gamefuncs_t *functionTable, int *) { +CR_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 // be called by the engine, into a memory block pointed to by the functionTable pointer @@ -87,13 +87,12 @@ SHARED_LIBRARAY_EXPORT int GetEntityAPI2 (gamefuncs_t *functionTable, int *) { memset (functionTable, 0, sizeof (gamefuncs_t)); - if (!(game.is (GAME_METAMOD))) { - auto api_GetEntityAPI = game.getLib ().resolve ("GetEntityAPI"); + if (!(game.is (GameFlags::Metamod))) { + auto api_GetEntityAPI = game.lib ().resolve ("GetEntityAPI"); // pass other DLLs engine callbacks to function table... - if (api_GetEntityAPI (&dllapi, INTERFACE_VERSION) == 0) { - util.logEntry (true, LL_FATAL, "GetEntityAPI2: ERROR - Not Initialized."); - return FALSE; // error initializing function table!!! + if (!api_GetEntityAPI || api_GetEntityAPI (&dllapi, INTERFACE_VERSION) == 0) { + logger.fatal ("Could not resolve symbol \"%s\" in the game dll.", "GetEntityAPI"); } dllfuncs.dllapi_table = &dllapi; gpGamedllFuncs = &dllfuncs; @@ -101,7 +100,7 @@ SHARED_LIBRARAY_EXPORT int GetEntityAPI2 (gamefuncs_t *functionTable, int *) { memcpy (functionTable, &dllapi, sizeof (gamefuncs_t)); } - functionTable->pfnGameInit = [] (void) { + functionTable->pfnGameInit = [] () { // 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 @@ -110,6 +109,14 @@ SHARED_LIBRARAY_EXPORT int GetEntityAPI2 (gamefuncs_t *functionTable, int *) { // server is enabled. Here is a good place to do our own game session initialization, and // to register by the engine side the server commands we need to administrate our bots. + // register bot cvars + game.registerCvars (); + + // register logger + logger.initialize (strings.format ("%slogs/yapb.log", graph.getDataDirectory (false)), [] (const char *msg) { + game.print (msg); + }); + conf.initWeapons (); // register server command(s) @@ -117,20 +124,20 @@ SHARED_LIBRARAY_EXPORT int GetEntityAPI2 (gamefuncs_t *functionTable, int *) { game.registerCmd ("yb", BotControl::handleEngineCommands); // set correct version string - yb_version.set (util.format ("%d.%d.%d", PRODUCT_VERSION_DWORD_INTERNAL, util.buildNumber ())); + yb_version.set (strings.format ("%d.%d.%d", PRODUCT_VERSION_DWORD_INTERNAL, util.buildNumber ())); // execute main config - conf.load (true); + conf.loadMainConfig (); // register fake metamod command handler if we not! under mm - if (!(game.is (GAME_METAMOD))) { - game.registerCmd ("meta", [] (void) { + if (!(game.is (GameFlags::Metamod))) { + game.registerCmd ("meta", [] () { game.print ("You're launched standalone version of yapb. Metamod is not installed or not enabled!"); }); } conf.adjustWeaponPrices (); - if (game.is (GAME_METAMOD)) { + if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } dllapi.pfnGameInit (); @@ -144,7 +151,7 @@ SHARED_LIBRARAY_EXPORT int GetEntityAPI2 (gamefuncs_t *functionTable, int *) { game.precache (); - if (game.is (GAME_METAMOD)) { + if (game.is (GameFlags::Metamod)) { RETURN_META_VALUE (MRES_IGNORED, 0); } int result = dllapi.pfnSpawn (ent); // get result @@ -168,7 +175,7 @@ SHARED_LIBRARAY_EXPORT int GetEntityAPI2 (gamefuncs_t *functionTable, int *) { // is called twice, once for each entity moving. if (!game.isNullEntity (pentTouched) && pentOther != game.getStartEntity ()) { - Bot *bot = bots.getBot (pentTouched); + auto bot = bots[pentTouched]; if (bot != nullptr && pentOther != bot->ent ()) { @@ -181,7 +188,7 @@ SHARED_LIBRARAY_EXPORT int GetEntityAPI2 (gamefuncs_t *functionTable, int *) { } } - if (game.is (GAME_METAMOD)) { + if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } dllapi.pfnTouch (pentTouched, pentOther); @@ -212,13 +219,13 @@ SHARED_LIBRARAY_EXPORT int GetEntityAPI2 (gamefuncs_t *functionTable, int *) { 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 not dedicated set the default editor for graph if (!game.isDedicated ()) { - waypoints.setEditor (ent); + graph.setEditor (ent); } } - if (game.is (GAME_METAMOD)) { + if (game.is (GameFlags::Metamod)) { RETURN_META_VALUE (MRES_IGNORED, 0); } return dllapi.pfnClientConnect (ent, name, addr, rejectReason); @@ -236,18 +243,18 @@ SHARED_LIBRARAY_EXPORT int GetEntityAPI2 (gamefuncs_t *functionTable, int *) { // 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) { + for (auto &bot : bots) { + if (bot->pev == &ent->v) { bot->showChaterIcon (false); - bots.destroy (index); + + conf.clearUsedName (bot.get ()); // clear the bot name + bots.erase (bot.get ()); // remove the bot from bots array + + break; } } - if (game.is (GAME_METAMOD)) { + + if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } dllapi.pfnClientDisconnect (ent); @@ -261,7 +268,7 @@ SHARED_LIBRARAY_EXPORT int GetEntityAPI2 (gamefuncs_t *functionTable, int *) { ctrl.assignAdminRights (ent, infobuffer); - if (game.is (GAME_METAMOD)) { + if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } dllapi.pfnClientUserInfoChanged (ent, infobuffer); @@ -282,14 +289,14 @@ SHARED_LIBRARAY_EXPORT int GetEntityAPI2 (gamefuncs_t *functionTable, int *) { // 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)) { + if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_SUPERCEDE); } return; } else if (ctrl.handleMenuCommands (ent)) { - if (game.is (GAME_METAMOD)) { + if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_SUPERCEDE); } return; @@ -298,7 +305,7 @@ SHARED_LIBRARAY_EXPORT int GetEntityAPI2 (gamefuncs_t *functionTable, int *) { // record stuff about radio and chat bots.captureChatRadio (engfuncs.pfnCmd_Argv (0), engfuncs.pfnCmd_Argv (1), ent); - if (game.is (GAME_METAMOD)) { + if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } dllapi.pfnClientCommand (ent); @@ -312,8 +319,7 @@ SHARED_LIBRARAY_EXPORT int GetEntityAPI2 (gamefuncs_t *functionTable, int *) { // 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 + conf.loadConfigs (); // initialize all config files // do a level initialization game.levelInitialize (pentEdictList, edictCount); @@ -322,27 +328,26 @@ SHARED_LIBRARAY_EXPORT int GetEntityAPI2 (gamefuncs_t *functionTable, int *) { illum.resetWorldModel (); // do level initialization stuff here... - waypoints.init (); - waypoints.load (); + graph.loadGraphData (); // execute main config - conf.load (true); + conf.loadMainConfig (); - if (File::exists (util.format ("%s/maps/%s_yapb.cfg", game.getModName (), game.getMapName ()))) { - game.execCmd ("exec maps/%s_yapb.cfg", game.getMapName ()); + if (File::exists (strings.format ("%s/maps/%s_yapb.cfg", game.getModName (), game.getMapName ()))) { + game.serverCommand ("exec maps/%s_yapb.cfg", game.getMapName ()); game.print ("Executing Map-Specific config file"); } bots.initQuota (); - if (game.is (GAME_METAMOD)) { + if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } dllapi.pfnServerActivate (pentEdictList, edictCount, clientMax); - waypoints.rebuildVisibility (); + graph.rebuildVisibility (); }; - functionTable->pfnServerDeactivate = [] (void) { + functionTable->pfnServerDeactivate = [] () { // 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 @@ -354,8 +359,7 @@ SHARED_LIBRARAY_EXPORT int GetEntityAPI2 (gamefuncs_t *functionTable, int *) { // the loading of new bots and the new BSP data parsing there. // save collected experience on shutdown - waypoints.saveExperience (); - waypoints.saveVisibility (); + graph.savePractice (); // destroy global killer entity bots.destroyKillerEntity (); @@ -370,19 +374,21 @@ SHARED_LIBRARAY_EXPORT int GetEntityAPI2 (gamefuncs_t *functionTable, int *) { util.setNeedForWelcome (false); // xash is not kicking fakeclients on changelevel - if (game.is (GAME_XASH_ENGINE)) { + if (game.is (GameFlags::Xash3D)) { bots.kickEveryone (true, false); - bots.destroy (); } - waypoints.init (); + graph.initGraph (); - if (game.is (GAME_METAMOD)) { + // clear all the bots + bots.destroy (); + + if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } dllapi.pfnServerDeactivate (); }; - functionTable->pfnStartFrame = [] (void) { + functionTable->pfnStartFrame = [] () { // 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 @@ -400,10 +406,9 @@ SHARED_LIBRARAY_EXPORT int GetEntityAPI2 (gamefuncs_t *functionTable, int *) { // update some stats for clients util.updateClients (); - if (waypoints.hasEditFlag (WS_EDIT_ENABLED) && waypoints.hasEditor ()) { - waypoints.frame (); + if (graph.hasEditFlag (GraphEdit::On) && graph.hasEditor ()) { + graph.frame (); } - bots.updateDeathMsgState (false); // run stuff periodically game.slowFrame (); @@ -419,7 +424,7 @@ SHARED_LIBRARAY_EXPORT int GetEntityAPI2 (gamefuncs_t *functionTable, int *) { // keep bot number up to date bots.maintainQuota (); - if (game.is (GAME_METAMOD)) { + if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } dllapi.pfnStartFrame (); @@ -428,17 +433,24 @@ SHARED_LIBRARAY_EXPORT int GetEntityAPI2 (gamefuncs_t *functionTable, int *) { bots.slowFrame (); }; - functionTable->pfnUpdateClientData = [] (const struct edict_s *ent, int sendweapons, struct clientdata_s *cd) { - extern ConVar yb_latency_display; + functionTable->pfnCmdStart = [] (const edict_t *player, usercmd_t *cmd, unsigned int random_seed) { + auto ent = const_cast (player); - if (game.is (GAME_SUPPORT_SVC_PINGS) && yb_latency_display.integer () == 2 && bots.hasBotsOnline ()) { - bots.sendPingOffsets (const_cast (ent)); + // if we're handle pings for bots and clients, clear IN_SCORE button so SV_ShouldUpdatePing engine function return false + // and SV_EmitPings will not overwrite our results + if (game.is (GameFlags::HasFakePings) && yb_show_latency.int_ () == 2) { + if ((cmd->buttons & IN_SCORE) || (ent->v.oldbuttons & IN_SCORE)) { + cmd->buttons &= ~IN_SCORE; + + // send our version of pings + util.sendPings (ent); + } } - - if (game.is (GAME_METAMOD)) { + + if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } - dllapi.pfnUpdateClientData (ent, sendweapons, cd); + dllapi.pfnCmdStart (player, cmd, random_seed); }; functionTable->pfnPM_Move = [] (playermove_t *playerMove, int server) { @@ -449,7 +461,7 @@ SHARED_LIBRARAY_EXPORT int GetEntityAPI2 (gamefuncs_t *functionTable, int *) { illum.setWorldModel (playerMove->physents[0].model); - if (game.is (GAME_METAMOD)) { + if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } dllapi.pfnPM_Move (playerMove, server); @@ -457,8 +469,8 @@ SHARED_LIBRARAY_EXPORT int GetEntityAPI2 (gamefuncs_t *functionTable, int *) { return TRUE; } -SHARED_LIBRARAY_EXPORT int GetEntityAPI2_Post (gamefuncs_t *functionTable, int *) { - // this function is called right after FuncPointers_t() by the engine in the game DLL (or +CR_EXPORT int GetEntityAPI2_Post (gamefuncs_t *table, 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 // be called by the engine, into a memory block pointed to by the functionTable pointer // that is passed into this function (explanation comes straight from botman). This allows @@ -468,9 +480,9 @@ SHARED_LIBRARAY_EXPORT int GetEntityAPI2_Post (gamefuncs_t *functionTable, int * // engine, and then calls the MOD DLL's version of GetEntityAPI to get the REAL gamedll // functions this time (to use in the bot code). Post version, called only by metamod. - memset (functionTable, 0, sizeof (gamefuncs_t)); + memset (table, 0, sizeof (gamefuncs_t)); - functionTable->pfnSpawn = [] (edict_t *ent) { + table->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, @@ -484,7 +496,7 @@ SHARED_LIBRARAY_EXPORT int GetEntityAPI2_Post (gamefuncs_t *functionTable, int * RETURN_META_VALUE (MRES_IGNORED, 0); }; - functionTable->pfnStartFrame = [] (void) { + table->pfnStartFrame = [] () { // 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 @@ -496,7 +508,7 @@ SHARED_LIBRARAY_EXPORT int GetEntityAPI2_Post (gamefuncs_t *functionTable, int * RETURN_META (MRES_IGNORED); }; - functionTable->pfnServerActivate = [] (edict_t *, int, int) { + table->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 @@ -505,7 +517,7 @@ SHARED_LIBRARAY_EXPORT int GetEntityAPI2_Post (gamefuncs_t *functionTable, int * // Once this function has been called, the server can be considered as "running". Post version // called only by metamod. - waypoints.rebuildVisibility (); + graph.rebuildVisibility (); RETURN_META (MRES_IGNORED); }; @@ -513,21 +525,21 @@ SHARED_LIBRARAY_EXPORT int GetEntityAPI2_Post (gamefuncs_t *functionTable, int * return TRUE; } -SHARED_LIBRARAY_EXPORT int GetNewDLLFunctions (newgamefuncs_t *functionTable, int *interfaceVersion) { +CR_EXPORT int GetNewDLLFunctions (newgamefuncs_t *functionTable, int *interfaceVersion) { // it appears that an extra function table has been added in the engine to gamedll interface // since the date where the first enginefuncs table standard was frozen. These ones are // facultative and we don't hook them, but since some MODs might be featuring it, we have to // pass them too, else the DLL interfacing wouldn't be complete and the game possibly wouldn't // run properly. - auto api_GetNewDLLFunctions = game.getLib ().resolve ("GetNewDLLFunctions"); + auto api_GetNewDLLFunctions = game.lib ().resolve (__FUNCTION__); if (api_GetNewDLLFunctions == nullptr) { return FALSE; } - if (!api_GetNewDLLFunctions (functionTable, interfaceVersion)) { - util.logEntry (true, LL_ERROR, "GetNewDLLFunctions: ERROR - Not Initialized."); + if (!api_GetNewDLLFunctions || !api_GetNewDLLFunctions (functionTable, interfaceVersion)) { + logger.error ("Could not resolve symbol \"%s\" in the game dll. Continuing...", __FUNCTION__); return FALSE; } @@ -535,8 +547,8 @@ SHARED_LIBRARAY_EXPORT int GetNewDLLFunctions (newgamefuncs_t *functionTable, in return TRUE; } -SHARED_LIBRARAY_EXPORT int GetEngineFunctions (enginefuncs_t *functionTable, int *) { - if (game.is (GAME_METAMOD)) { +CR_EXPORT int GetEngineFunctions (enginefuncs_t *functionTable, int *) { + if (game.is (GameFlags::Metamod)) { memset (functionTable, 0, sizeof (enginefuncs_t)); } @@ -551,10 +563,9 @@ SHARED_LIBRARAY_EXPORT int GetEngineFunctions (enginefuncs_t *functionTable, int // spawn point named "tr_2lm". // save collected experience on map change - waypoints.saveExperience (); - waypoints.saveVisibility (); + graph.savePractice (); - if (game.is (GAME_METAMOD)) { + if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } engfuncs.pfnChangeLevel (s1, s2); @@ -565,7 +576,7 @@ SHARED_LIBRARAY_EXPORT int GetEngineFunctions (enginefuncs_t *functionTable, int illum.updateLight (style, val); - if (game.is (GAME_METAMOD)) { + if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } engfuncs.pfnLightStyle (style, val); @@ -573,11 +584,11 @@ SHARED_LIBRARAY_EXPORT int GetEngineFunctions (enginefuncs_t *functionTable, int 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) { + if ((game.is (GameFlags::Legacy)) && strcmp (value, "info_map_parameters") == 0) { bots.initRound (); } - if (game.is (GAME_METAMOD)) { + if (game.is (GameFlags::Metamod)) { RETURN_META_VALUE (MRES_IGNORED, static_cast (nullptr)); } return engfuncs.pfnFindEntityByString (edictStartSearchAfter, field, value); @@ -596,7 +607,7 @@ SHARED_LIBRARAY_EXPORT int GetEngineFunctions (enginefuncs_t *functionTable, int util.attachSoundsToClients (entity, sample, volume); - if (game.is (GAME_METAMOD)) { + if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } engfuncs.pfnEmitSound (entity, channel, sample, volume, attenuation, flags, pitch); @@ -607,29 +618,26 @@ SHARED_LIBRARAY_EXPORT int GetEngineFunctions (enginefuncs_t *functionTable, int game.beginMessage (ed, msgDest, msgType); - if (game.is (GAME_METAMOD)) { + if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } engfuncs.pfnMessageBegin (msgDest, msgType, origin, ed); }; - functionTable->pfnMessageEnd = [] (void) { + functionTable->pfnMessageEnd = [] () { game.resetMessages (); - if (game.is (GAME_METAMOD)) { + if (game.is (GameFlags::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); + game.processMessages (reinterpret_cast (&value)); - if (game.is (GAME_METAMOD)) { + if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } engfuncs.pfnWriteByte (value); @@ -637,9 +645,9 @@ SHARED_LIBRARAY_EXPORT int GetEngineFunctions (enginefuncs_t *functionTable, int functionTable->pfnWriteChar = [] (int value) { // if this message is for a bot, call the client message function... - game.processMessages ((void *) &value); + game.processMessages (reinterpret_cast (&value)); - if (game.is (GAME_METAMOD)) { + if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } engfuncs.pfnWriteChar (value); @@ -647,9 +655,9 @@ SHARED_LIBRARAY_EXPORT int GetEngineFunctions (enginefuncs_t *functionTable, int functionTable->pfnWriteShort = [] (int value) { // if this message is for a bot, call the client message function... - game.processMessages ((void *) &value); + game.processMessages (reinterpret_cast (&value)); - if (game.is (GAME_METAMOD)) { + if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } engfuncs.pfnWriteShort (value); @@ -657,9 +665,9 @@ SHARED_LIBRARAY_EXPORT int GetEngineFunctions (enginefuncs_t *functionTable, int functionTable->pfnWriteLong = [] (int value) { // if this message is for a bot, call the client message function... - game.processMessages ((void *) &value); + game.processMessages (reinterpret_cast (&value)); - if (game.is (GAME_METAMOD)) { + if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } engfuncs.pfnWriteLong (value); @@ -667,9 +675,9 @@ SHARED_LIBRARAY_EXPORT int GetEngineFunctions (enginefuncs_t *functionTable, int functionTable->pfnWriteAngle = [] (float value) { // if this message is for a bot, call the client message function... - game.processMessages ((void *) &value); + game.processMessages (reinterpret_cast (&value)); - if (game.is (GAME_METAMOD)) { + if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } engfuncs.pfnWriteAngle (value); @@ -677,9 +685,9 @@ SHARED_LIBRARAY_EXPORT int GetEngineFunctions (enginefuncs_t *functionTable, int functionTable->pfnWriteCoord = [] (float value) { // if this message is for a bot, call the client message function... - game.processMessages ((void *) &value); + game.processMessages (reinterpret_cast (&value)); - if (game.is (GAME_METAMOD)) { + if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } engfuncs.pfnWriteCoord (value); @@ -687,9 +695,9 @@ SHARED_LIBRARAY_EXPORT int GetEngineFunctions (enginefuncs_t *functionTable, int functionTable->pfnWriteString = [] (const char *sz) { // if this message is for a bot, call the client message function... - game.processMessages ((void *) sz); + game.processMessages (reinterpret_cast (const_cast (sz))); - if (game.is (GAME_METAMOD)) { + if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } engfuncs.pfnWriteString (sz); @@ -697,9 +705,9 @@ SHARED_LIBRARAY_EXPORT int GetEngineFunctions (enginefuncs_t *functionTable, int functionTable->pfnWriteEntity = [] (int value) { // if this message is for a bot, call the client message function... - game.processMessages ((void *) &value); + game.processMessages (reinterpret_cast (&value)); - if (game.is (GAME_METAMOD)) { + if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } engfuncs.pfnWriteEntity (value); @@ -716,76 +724,76 @@ SHARED_LIBRARAY_EXPORT int GetEngineFunctions (enginefuncs_t *functionTable, int // 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)) { + if (game.is (GameFlags::Metamod)) { RETURN_META_VALUE (MRES_IGNORED, 0); } int message = engfuncs.pfnRegUserMsg (name, size); if (strcmp (name, "VGUIMenu") == 0) { - game.setMessageId (NETMSG_VGUI, message); + game.setMessageId (NetMsg::VGUI, message); } else if (strcmp (name, "ShowMenu") == 0) { - game.setMessageId (NETMSG_SHOWMENU, message); + game.setMessageId (NetMsg::ShowMenu, message); } else if (strcmp (name, "WeaponList") == 0) { - game.setMessageId (NETMSG_WEAPONLIST, message); + game.setMessageId (NetMsg::WeaponList, message); } else if (strcmp (name, "CurWeapon") == 0) { - game.setMessageId (NETMSG_CURWEAPON, message); + game.setMessageId (NetMsg::CurWeapon, message); } else if (strcmp (name, "AmmoX") == 0) { - game.setMessageId (NETMSG_AMMOX, message); + game.setMessageId (NetMsg::AmmoX, message); } else if (strcmp (name, "AmmoPickup") == 0) { - game.setMessageId (NETMSG_AMMOPICKUP, message); + game.setMessageId (NetMsg::AmmoPickup, message); } else if (strcmp (name, "Damage") == 0) { - game.setMessageId (NETMSG_DAMAGE, message); + game.setMessageId (NetMsg::Damage, message); } else if (strcmp (name, "Money") == 0) { - game.setMessageId (NETMSG_MONEY, message); + game.setMessageId (NetMsg::Money, message); } else if (strcmp (name, "StatusIcon") == 0) { - game.setMessageId (NETMSG_STATUSICON, message); + game.setMessageId (NetMsg::StatusIcon, message); } else if (strcmp (name, "DeathMsg") == 0) { - game.setMessageId (NETMSG_DEATH, message); + game.setMessageId (NetMsg::DeathMsg, message); } else if (strcmp (name, "ScreenFade") == 0) { - game.setMessageId (NETMSG_SCREENFADE, message); + game.setMessageId (NetMsg::ScreenFade, message); } else if (strcmp (name, "HLTV") == 0) { - game.setMessageId (NETMSG_HLTV, message); + game.setMessageId (NetMsg::HLTV, message); } else if (strcmp (name, "TextMsg") == 0) { - game.setMessageId (NETMSG_TEXTMSG, message); + game.setMessageId (NetMsg::TextMsg, message); } else if (strcmp (name, "TeamInfo") == 0) { - game.setMessageId (NETMSG_TEAMINFO, message); + game.setMessageId (NetMsg::TeamInfo, message); } else if (strcmp (name, "BarTime") == 0) { - game.setMessageId (NETMSG_BARTIME, message); + game.setMessageId (NetMsg::BarTime, message); } else if (strcmp (name, "SendAudio") == 0) { - game.setMessageId (NETMSG_SENDAUDIO, message); + game.setMessageId (NetMsg::SendAudio, message); } else if (strcmp (name, "SayText") == 0) { - game.setMessageId (NETMSG_SAYTEXT, message); + game.setMessageId (NetMsg::SayText, message); } else if (strcmp (name, "BotVoice") == 0) { - game.setMessageId (NETMSG_BOTVOICE, message); + game.setMessageId (NetMsg::BotVoice, message); } else if (strcmp (name, "NVGToggle") == 0) { - game.setMessageId (NETMSG_NVGTOGGLE, message); + game.setMessageId (NetMsg::NVGToggle, message); } else if (strcmp (name, "FlashBat") == 0) { - game.setMessageId (NETMSG_FLASHBAT, message); + game.setMessageId (NetMsg::FlashBat, message); } else if (strcmp (name, "Flashlight") == 0) { - game.setMessageId (NETMSG_FLASHLIGHT, message); + game.setMessageId (NetMsg::Fashlight, message); } else if (strcmp (name, "ItemStatus") == 0) { - game.setMessageId (NETMSG_ITEMSTATUS, message); + game.setMessageId (NetMsg::ItemStatus, message); } return message; }; @@ -798,19 +806,19 @@ SHARED_LIBRARAY_EXPORT int GetEngineFunctions (enginefuncs_t *functionTable, int // we know, right ? But since stupidity rules this world, we do a preventive check :) if (util.isFakeClient (ent)) { - if (game.is (GAME_METAMOD)) { + if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_SUPERCEDE); } return; } - if (game.is (GAME_METAMOD)) { + if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } engfuncs.pfnClientPrintf (ent, printType, message); }; - functionTable->pfnCmd_Args = [] (void) { + functionTable->pfnCmd_Args = [] () { // 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 @@ -820,13 +828,13 @@ SHARED_LIBRARAY_EXPORT int GetEngineFunctions (enginefuncs_t *functionTable, int // is this a bot issuing that client command? if (game.isBotCmd ()) { - if (game.is (GAME_METAMOD)) { + if (game.is (GameFlags::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)) { + if (game.is (GameFlags::Metamod)) { RETURN_META_VALUE (MRES_IGNORED, static_cast (nullptr)); } return engfuncs.pfnCmd_Args (); // ask the client command string to the engine @@ -842,19 +850,19 @@ SHARED_LIBRARAY_EXPORT int GetEngineFunctions (enginefuncs_t *functionTable, int // is this a bot issuing that client command? if (game.isBotCmd ()) { - if (game.is (GAME_METAMOD)) { + if (game.is (GameFlags::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)) { + if (game.is (GameFlags::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) { + functionTable->pfnCmd_Argc = [] () { // 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 @@ -864,61 +872,52 @@ SHARED_LIBRARAY_EXPORT int GetEngineFunctions (enginefuncs_t *functionTable, int // is this a bot issuing that client command? if (game.isBotCmd ()) { - if (game.is (GAME_METAMOD)) { + if (game.is (GameFlags::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)) { + if (game.is (GameFlags::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)); + auto bot = bots[const_cast (ent)]; // check wether it's not a bot if (bot != nullptr) { bot->pev->maxspeed = newMaxspeed; } - if (game.is (GAME_METAMOD)) { + if (game.is (GameFlags::Metamod)) { RETURN_META (MRES_IGNORED); } engfuncs.pfnSetClientMaxspeed (ent, newMaxspeed); }; + + functionTable->pfnClientCommand = variadic::clientCommand; + return TRUE; } -SHARED_LIBRARAY_EXPORT int GetEngineFunctions_Post (enginefuncs_t *functionTable, int *) { - - memset (functionTable, 0, sizeof (enginefuncs_t)); - - functionTable->pfnMessageEnd = [] (void) { - // send latency fix - bots.sendDeathMsgFix (); - - RETURN_META (MRES_IGNORED); - }; - return TRUE; -} - -SHARED_LIBRARAY_EXPORT int Server_GetBlendingInterface (int version, void **ppinterface, void *pstudio, float (*rotationmatrix)[3][4], float (*bonetransform)[128][3][4]) { +CR_EXPORT int Server_GetBlendingInterface (int version, void **ppinterface, void *pstudio, float (*rotationmatrix)[3][4], float (*bonetransform)[128][3][4]) { // this function synchronizes the studio model animation blending interface (i.e, what parts // 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 = game.getLib ().resolve ("Server_GetBlendingInterface"); + auto api_GetBlendingInterface = game.lib ().resolve (__FUNCTION__); - if (api_GetBlendingInterface == nullptr) { + if (!api_GetBlendingInterface) { + logger.error ("Could not resolve symbol \"%s\" in the game dll. Continuing...", __FUNCTION__); return FALSE; } return api_GetBlendingInterface (version, ppinterface, pstudio, rotationmatrix, bonetransform); } -SHARED_LIBRARAY_EXPORT int Meta_Query (char *, plugin_info_t **pPlugInfo, mutil_funcs_t *pMetaUtilFuncs) { +CR_EXPORT int Meta_Query (char *, plugin_info_t **pPlugInfo, mutil_funcs_t *pMetaUtilFuncs) { // this function is the first function ever called by metamod in the plugin DLL. Its purpose // is for metamod to retrieve basic information about the plugin, such as its meta-interface // version, for ensuring compatibility with the current version of the running metamod. @@ -929,7 +928,7 @@ SHARED_LIBRARAY_EXPORT int Meta_Query (char *, plugin_info_t **pPlugInfo, mutil_ return TRUE; // tell metamod this plugin looks safe } -SHARED_LIBRARAY_EXPORT int Meta_Attach (PLUG_LOADTIME, metamod_funcs_t *functionTable, meta_globals_t *pMGlobals, gamedll_funcs_t *pGamedllFuncs) { +CR_EXPORT int Meta_Attach (PLUG_LOADTIME, metamod_funcs_t *functionTable, meta_globals_t *pMGlobals, gamedll_funcs_t *pGamedllFuncs) { // this function is called when metamod attempts to load the plugin. Since it's the place // where we can tell if the plugin will be allowed to run or not, we wait until here to make // our initialization stuff, like registering CVARs and dedicated server commands. @@ -943,7 +942,7 @@ SHARED_LIBRARAY_EXPORT int Meta_Attach (PLUG_LOADTIME, metamod_funcs_t *function nullptr, // pfnGetNewDLLFunctions () nullptr, // pfnGetNewDLLFunctions_Post () GetEngineFunctions, // pfnGetEngineFunctions () - GetEngineFunctions_Post, // pfnGetEngineFunctions_Post () + nullptr, // pfnGetEngineFunctions_Post () }; // keep track of the pointers to engine function tables metamod gives us @@ -954,23 +953,44 @@ SHARED_LIBRARAY_EXPORT int Meta_Attach (PLUG_LOADTIME, metamod_funcs_t *function return TRUE; // returning true enables metamod to attach this plugin } -SHARED_LIBRARAY_EXPORT int Meta_Detach (PLUG_LOADTIME, PL_UNLOAD_REASON) { +CR_EXPORT int Meta_Detach (PLUG_LOADTIME, PL_UNLOAD_REASON) { // this function is called when metamod unloads the plugin. A basic check is made in order // to prevent unloading the plugin if its processing should not be interrupted. bots.kickEveryone (true); // kick all bots off this server - waypoints.init (); + + // save collected experience on shutdown + graph.savePractice (); return TRUE; } -SHARED_LIBRARAY_EXPORT void Meta_Init (void) { +CR_EXPORT void Meta_Init () { // 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. - game.addGameFlag (GAME_METAMOD); + game.addGameFlag (GameFlags::Metamod); } +// games GiveFnptrsToDll is a bit tricky +#if defined(CR_WINDOWS) +# if defined(CR_CXX_MSVC) || defined (CR_CXX_MSVC) +# if defined (CR_ARCH_X86) +# pragma comment(linker, "/EXPORT:GiveFnptrsToDll=_GiveFnptrsToDll@8,@1") +# endif +# pragma comment(linker, "/SECTION:.data,RW") +# endif +# define DLL_STDCALL __stdcall +# if defined(CR_CXX_MSVC) && !defined(CR_ARCH_X64) +# define DLL_GIVEFNPTRSTODLL extern "C" void DLL_STDCALL +# elif defined(CR_CXX_CLANG) || defined(CR_ARCH_X64) +# define DLL_GIVEFNPTRSTODLL CR_EXPORT void DLL_STDCALL +# endif +#elif defined(CR_LINUX) || defined (CR_OSX) || defined (CR_ANDROID) +# define DLL_GIVEFNPTRSTODLL CR_EXPORT void +# define DLL_STDCALL +#endif + DLL_GIVEFNPTRSTODLL GiveFnptrsToDll (enginefuncs_t *functionTable, globalvars_t *pGlobals) { // 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 @@ -990,30 +1010,22 @@ DLL_GIVEFNPTRSTODLL GiveFnptrsToDll (enginefuncs_t *functionTable, globalvars_t if (game.postload ()) { return; } - auto api_GiveFnptrsToDll = game.getLib ().resolve ("GiveFnptrsToDll"); - - assert (api_GiveFnptrsToDll != nullptr); + auto api_GiveFnptrsToDll = game.lib ().resolve (__FUNCTION__); + + if (!api_GiveFnptrsToDll) { + logger.fatal ("Could not resolve symbol \"%s\" in the game dll.", __FUNCTION__); + } GetEngineFunctions (functionTable, nullptr); // give the engine functions to the other DLL... - api_GiveFnptrsToDll (functionTable, pGlobals); -} - -DLL_ENTRYPOINT { - // dynamic library entry point, can be used for uninitialization stuff. NOT for initializing - // anything because if you ever attempt to wander outside the scope of this function on a - // DLL attach, LoadLibrary() will simply fail. And you can't do I/Os here either. - - // dynamic library detaching ?? - if (DLL_DETACHING) { - waypoints.init (); // free everything that's freeable + if (api_GiveFnptrsToDll) { + api_GiveFnptrsToDll (functionTable, pGlobals); } - DLL_RETENTRY; // the return data type is OS specific too } void helper_LinkEntity (EntityFunction &addr, const char *name, entvars_t *pev) { if (addr == nullptr) { - addr = game.getLib ().resolve (name); + addr = game.lib ().resolve (name); } if (addr == nullptr) { @@ -1022,10 +1034,10 @@ void helper_LinkEntity (EntityFunction &addr, const char *name, entvars_t *pev) addr (pev); } -#define LINK_ENTITY(entityName) \ - SHARED_LIBRARAY_EXPORT void entityName (entvars_t *pev) { \ - static EntityFunction addr; \ - helper_LinkEntity (addr, #entityName, pev); \ +#define LINK_ENTITY(entityName) \ + CR_EXPORT void entityName (entvars_t *pev) { \ + static EntityFunction addr; \ + helper_LinkEntity (addr, #entityName, pev); \ } // entities in counter-strike... diff --git a/source/manager.cpp b/source/manager.cpp index dfc3168..2569c59 100644 --- a/source/manager.cpp +++ b/source/manager.cpp @@ -11,7 +11,7 @@ ConVar yb_autovacate ("yb_autovacate", "1"); -ConVar yb_quota ("yb_quota", "0", VT_NORMAL); +ConVar yb_quota ("yb_quota", "0", Var::Normal); ConVar yb_quota_mode ("yb_quota_mode", "normal"); ConVar yb_quota_match ("yb_quota_match", "0"); ConVar yb_think_fps ("yb_think_fps", "30.0"); @@ -23,48 +23,36 @@ ConVar yb_join_delay ("yb_join_delay", "5.0"); ConVar yb_name_prefix ("yb_name_prefix", ""); ConVar yb_difficulty ("yb_difficulty", "4"); -ConVar yb_latency_display ("yb_latency_display", "2"); -ConVar yb_avatar_display ("yb_avatar_display", "1"); - +ConVar yb_show_avatars ("yb_show_avatars", "1"); +ConVar yb_show_latency ("yb_show_latency", "2"); ConVar yb_language ("yb_language", "en"); ConVar yb_ignore_cvars_on_changelevel ("yb_ignore_cvars_on_changelevel", "yb_quota,yb_autovacate"); -ConVar 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"); +ConVar mp_limitteams ("mp_limitteams", nullptr, Var::NoRegister); +ConVar mp_autoteambalance ("mp_autoteambalance", nullptr, Var::NoRegister); +ConVar mp_roundtime ("mp_roundtime", nullptr, Var::NoRegister); +ConVar mp_timelimit ("mp_timelimit", nullptr, Var::NoRegister); +ConVar mp_freezetime ("mp_freezetime", nullptr, Var::NoRegister, true, "0"); -BotManager::BotManager (void) { +BotManager::BotManager () { // this is a bot manager class constructor m_lastDifficulty = 0; m_lastWinner = -1; - m_deathMsgSent = false; - for (int i = 0; i < MAX_TEAM_COUNT; i++) { + for (int i = 0; i < kGameTeamNum; ++i) { m_leaderChoosen[i] = false; m_economicsGood[i] = true; } - memset (m_bots, 0, sizeof (m_bots)); reset (); m_creationTab.clear (); m_killerEntity = nullptr; - 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 game.MaxClients () here !! - destroy (); -} - -void BotManager::createKillerEntity (void) { +void BotManager::createKillerEntity () { // this function creates single trigger_hurt for using in Bot::Kill, to reduce lags, when killing all the bots m_killerEntity = engfuncs.pfnCreateNamedEntity (MAKE_STRING ("trigger_hurt")); @@ -78,7 +66,7 @@ void BotManager::createKillerEntity (void) { MDLL_Spawn (m_killerEntity); } -void BotManager::destroyKillerEntity (void) { +void BotManager::destroyKillerEntity () { if (!game.isNullEntity (m_killerEntity)) { engfuncs.pfnRemoveEntity (m_killerEntity); } @@ -107,7 +95,7 @@ void BotManager::touchKillerEntity (Bot *bot) { KeyValueData kv; kv.szClassName = const_cast (prop.classname); kv.szKeyName = "damagetype"; - kv.szValue = const_cast (util.format ("%d", cr::bit (4))); + kv.szValue = const_cast (strings.format ("%d", cr::bit (4))); kv.fHandled = FALSE; MDLL_KeyValue (m_killerEntity, &kv); @@ -120,59 +108,66 @@ 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 (game.is (GAME_METAMOD)) { + if (game.is (GameFlags::Metamod)) { CALL_GAME_ENTITY (PLID, "player", vars); return; } player (vars); } -BotCreationResult BotManager::create (const String &name, int difficulty, int personality, int team, int member) { +void BotManager::forEach (ForEachBot handler) { + for (const auto &bot : m_bots) { + if (handler (bot.get ())) { + return; + } + } +} + +BotCreateResult BotManager::create (const String &name, int difficulty, int personality, int team, int member) { // this function completely prepares bot entity (edict) for creation, creates team, difficulty, sets named etc, and // then sends result to bot constructor edict_t *bot = nullptr; String resultName; - // do not allow create bots when there is no waypoints - if (!waypoints.length ()) { - ctrl.msg ("Map is not waypointed. Cannot create bot"); - return BOT_RESULT_NAV_ERROR; + // do not allow create bots when there is no graph + if (!graph.length ()) { + ctrl.msg ("There is not graph found. Cannot create bot."); + return BotCreateResult::GraphError; } - // don't allow creating bots with changed waypoints (distance tables are messed up) - else if (waypoints.hasChanged ()) { - ctrl.msg ("Waypoints have been changed. Load waypoints again..."); - return BOT_RESULT_NAV_ERROR; + // don't allow creating bots with changed graph (distance tables are messed up) + else if (graph.hasChanged ()) { + ctrl.msg ("Graph has been changed. Load graph again..."); + return BotCreateResult::GraphError; } else if (team != -1 && isTeamStacked (team - 1)) { - ctrl.msg ("Desired team is stacked. Unable to proceed with bot creation"); - return BOT_RESULT_TEAM_STACKED; + ctrl.msg ("Desired team is stacked. Unable to proceed with bot creation."); + return BotCreateResult::TeamStacked; } if (difficulty < 0 || difficulty > 4) { - difficulty = yb_difficulty.integer (); + difficulty = yb_difficulty.int_ (); if (difficulty < 0 || difficulty > 4) { - difficulty = rng.getInt (3, 4); + difficulty = rg.int_ (3, 4); yb_difficulty.set (difficulty); } } - if (personality < PERSONALITY_NORMAL || personality > PERSONALITY_CAREFUL) { - if (rng.chance (5)) { - personality = PERSONALITY_NORMAL; + if (personality < Personality::Normal || personality > Personality::Careful) { + if (rg.chance (50)) { + personality = Personality::Normal; } else { - if (rng.chance (50)) { - personality = PERSONALITY_RUSHER; + if (rg.chance (50)) { + personality = Personality::Rusher; } else { - personality = PERSONALITY_CAREFUL; + personality = Personality::Careful; } } } - String steamId = ""; BotName *botName = nullptr; // setup name @@ -181,10 +176,9 @@ BotCreationResult BotManager::create (const String &name, int difficulty, int pe 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 + resultName.assignf ("yapb_%d.%d", rg.int_ (100, 10000), rg.int_ (100, 10000)); // just pick ugly random name } } else { @@ -193,7 +187,7 @@ BotCreationResult BotManager::create (const String &name, int difficulty, int pe if (!util.isEmptyStr (yb_name_prefix.str ())) { String prefixed; // temp buffer for storing modified name - prefixed.assign ("%s %s", yb_name_prefix.str (), resultName.chars ()); + prefixed.assignf ("%s %s", yb_name_prefix.str (), resultName.chars ()); // buffer has been modified, copy to real name resultName = cr::move (prefixed); @@ -202,109 +196,71 @@ BotCreationResult BotManager::create (const String &name, int difficulty, int pe 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; + return BotCreateResult::MaxPlayersReached; } - int index = game.indexOfEntity (bot) - 1; - - // ensure it free - destroy (index); - - assert (index >= 0 && index <= MAX_ENGINE_PLAYERS); // check index - assert (m_bots[index] == nullptr); // check bot slot - - m_bots[index] = new Bot (bot, difficulty, personality, team, member, steamId); + auto object = cr::createUnique (bot, difficulty, personality, team, member); + auto index = object->index (); // assign owner of bot name if (botName != nullptr) { - botName->usedBy = m_bots[index]->index (); + botName->usedBy = index; // save by who name is used } + m_bots.push (cr::move (object)); + ctrl.msg ("Connecting Bot..."); - return BOT_RESULT_CREATED; + return BotCreateResult::Success; } -int BotManager::index (edict_t *ent) { - // this function returns index of bot (using own bot array) - if (game.isNullEntity (ent)) { - return -1; - } - int index = game.indexOfEntity (ent) - 1; - - if (index < 0 || index >= MAX_ENGINE_PLAYERS) { - return -1; - } - - if (m_bots[index] != nullptr) { - return index; - } - return -1; // if no edict, return -1; -} - -Bot *BotManager::getBot (int index) { +Bot *BotManager::findBotByIndex (int index) { // this function finds a bot specified by index, and then returns pointer to it (using own bot array) - if (index < 0 || index >= MAX_ENGINE_PLAYERS) { + if (index < 0 || index >= kGameMaxPlayers) { return nullptr; } - - if (m_bots[index] != nullptr) { - return m_bots[index]; + for (const auto &bot : m_bots) { + if (bot->m_index == index) { + return bot.get (); + } } return nullptr; // no bot } -Bot *BotManager::getBot (edict_t *ent) { +Bot *BotManager::findBotByEntity (edict_t *ent) { // same as above, but using bot entity - return getBot (index (ent)); + return findBotByIndex (game.indexOfPlayer (ent)); } -Bot *BotManager::getAliveBot (void) { +Bot *BotManager::findAliveBot () { // this function finds one bot, alive bot :) - IntArray result; - - for (int i = 0; i < game.maxClients (); i++) { - if (result.length () > 4) { - break; + for (const auto &bot : m_bots) { + if (bot->m_notKilled) { + return bot.get (); } - if (m_bots[i] != nullptr && util.isAlive (m_bots[i]->ent ())) { - result.push (i); - } - } - - if (!result.empty ()) { - return m_bots[result.random ()]; } return nullptr; } -void BotManager::slowFrame (void) { - // this function calls think () function for all available at call moment bots +void BotManager::slowFrame () { + // this function calls showframe function for all available at call moment bots - for (int i = 0; i < game.maxClients (); i++) { - auto bot = m_bots[i]; - - if (bot != nullptr) { - bot->slowFrame (); - } + for (const auto &bot : m_bots) { + bot->slowFrame (); } } -void BotManager::frame (void) { - // this function calls periodic SecondThink () function for all available at call moment bots +void BotManager::frame () { + // this function calls periodic frame function for all available at call moment bots - for (int i = 0; i < game.maxClients (); i++) { - auto bot = m_bots[i]; - - if (bot != nullptr) { - bot->frame (); - } + for (const auto &bot : m_bots) { + bot->frame (); } // select leader each team somewhere in round start if (m_timeRoundStart + 5.0f > game.timebase () && m_timeRoundStart + 10.0f < game.timebase ()) { - for (int team = 0; team < MAX_TEAM_COUNT; team++) { + for (int team = 0; team < kGameTeamNum; ++team) { selectLeaders (team, false); } } @@ -334,22 +290,22 @@ void BotManager::addbot (const String &name, const String &difficulty, const Str const String &any = "*"; create.name = (name.empty () || name == any) ? String ("\0") : name; - create.difficulty = (difficulty.empty () || difficulty == any) ? -1 : difficulty.toInt32 (); - create.team = (team.empty () || team == any) ? -1 : team.toInt32 (); - create.member = (member.empty () || member == any) ? -1 : member.toInt32 (); - create.personality = (personality.empty () || personality == any) ? -1 : personality.toInt32 (); + create.difficulty = (difficulty.empty () || difficulty == any) ? -1 : difficulty.int_ (); + create.team = (team.empty () || team == any) ? -1 : team.int_ (); + create.member = (member.empty () || member == any) ? -1 : member.int_ (); + create.personality = (personality.empty () || personality == any) ? -1 : personality.int_ (); create.manual = manual; m_creationTab.push (cr::move (create)); } -void BotManager::maintainQuota (void) { +void BotManager::maintainQuota () { // this function keeps number of bots up to date, and don't allow to maintain bot creation // while creation process in process. - if (waypoints.length () < 1 || waypoints.hasChanged ()) { - if (yb_quota.integer () > 0) { - ctrl.msg ("Map is not waypointed. Cannot create bot"); + if (graph.length () < 1 || graph.hasChanged ()) { + if (yb_quota.int_ () > 0) { + ctrl.msg ("There is not graph found. Cannot create bot"); } yb_quota.set (0); return; @@ -358,22 +314,22 @@ void BotManager::maintainQuota (void) { // bot's creation update 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); + const BotCreateResult callResult = create (last.name, last.difficulty, last.personality, last.team, last.member); if (last.manual) { - yb_quota.set (yb_quota.integer () + 1); + yb_quota.set (yb_quota.int_ () + 1); } // check the result of creation - if (callResult == BOT_RESULT_NAV_ERROR) { - m_creationTab.clear (); // something wrong with waypoints, reset tab of creation + if (callResult == BotCreateResult::GraphError) { + m_creationTab.clear (); // something wrong with graph, reset tab of creation yb_quota.set (0); // reset quota } - else if (callResult == BOT_RESULT_MAX_PLAYERS_REACHED) { + else if (callResult == BotCreateResult::MaxPlayersReached) { m_creationTab.clear (); // maximum players reached, so set quota to maximum players yb_quota.set (getBotCount ()); } - else if (callResult == BOT_RESULT_TEAM_STACKED) { + else if (callResult == BotCreateResult::TeamStacked) { 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 (); @@ -386,11 +342,7 @@ void BotManager::maintainQuota (void) { if (m_quotaMaintainTime > game.timebase ()) { return; } - - // not a best place for this, but whatever - updateBotDifficulties (); - - yb_quota.set (cr::clamp (yb_quota.integer (), 0, game.maxClients ())); + yb_quota.set (cr::clamp (yb_quota.int_ (), 0, game.maxClients ())); int totalHumansInGame = getHumansCount (); int humanPlayersInGame = getHumansCount (true); @@ -399,30 +351,30 @@ void BotManager::maintainQuota (void) { return; } - int desiredBotCount = yb_quota.integer (); + int desiredBotCount = yb_quota.int_ (); int botsInGame = getBotCount (); if (stricmp (yb_quota_mode.str (), "fill") == 0) { botsInGame += humanPlayersInGame; } else if (stricmp (yb_quota_mode.str (), "match") == 0) { - int detectQuotaMatch = yb_quota_match.integer () == 0 ? yb_quota.integer () : yb_quota_match.integer (); + int detectQuotaMatch = yb_quota_match.int_ () == 0 ? yb_quota.int_ () : yb_quota_match.int_ (); desiredBotCount = cr::max (0, detectQuotaMatch * humanPlayersInGame); } - if (yb_join_after_player.boolean () && humanPlayersInGame == 0) { + if (yb_join_after_player.bool_ () && humanPlayersInGame == 0) { desiredBotCount = 0; } int maxClients = game.maxClients (); - if (yb_autovacate.boolean ()) { + if (yb_autovacate.bool_ ()) { desiredBotCount = cr::min (desiredBotCount, maxClients - (humanPlayersInGame + 1)); } else { desiredBotCount = cr::min (desiredBotCount, maxClients - humanPlayersInGame); } - int maxSpawnCount = game.getSpawnCount (TEAM_TERRORIST) + game.getSpawnCount (TEAM_COUNTER); + int maxSpawnCount = game.getSpawnCount (Team::Terrorist) + game.getSpawnCount (Team::CT) - humanPlayersInGame; // sent message only to console from here ctrl.setFromConsole (true); @@ -432,30 +384,29 @@ void BotManager::maintainQuota (void) { createRandom (); } else if (desiredBotCount < botsInGame) { - int ts = 0, cts = 0; - countTeamPlayers (ts, cts); + auto tp = countTeamPlayers (); bool isKicked = false; - if (ts > cts) { - isKicked = kickRandom (false, TEAM_TERRORIST); + if (tp.first > tp.second) { + isKicked = kickRandom (false, Team::Terrorist); } - else if (ts < cts) { - isKicked = kickRandom (false, TEAM_COUNTER); + else if (tp.first < tp.second) { + isKicked = kickRandom (false, Team::CT); } else { - isKicked = kickRandom (false, TEAM_UNASSIGNED); + isKicked = kickRandom (false, Team::Unassigned); } // if we can't kick player from correct team, just kick any random to keep quota control work if (!isKicked) { - kickRandom (false, TEAM_UNASSIGNED); + kickRandom (false, Team::Unassigned); } } m_quotaMaintainTime = game.timebase () + 0.40f; } -void BotManager::reset (void) { +void BotManager::reset () { m_maintainTime = 0.0f; m_quotaMaintainTime = 0.0f; m_grenadeUpdateTime = 0.0f; @@ -468,32 +419,32 @@ void BotManager::reset (void) { m_activeGrenades.clear (); } -void BotManager::initFilters (void) { +void BotManager::initFilters () { // 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 }); + m_filters.emplace (Task::Normal, 0.0f, kInvalidNodeIndex, 0.0f, true); + m_filters.emplace (Task::Pause, 0.0f, kInvalidNodeIndex, 0.0f, false); + m_filters.emplace (Task::MoveToPosition, 0.0f, kInvalidNodeIndex, 0.0f, true); + m_filters.emplace (Task::FollowUser, 0.0f, kInvalidNodeIndex, 0.0f, true); + m_filters.emplace (Task::PickupItem, 0.0f, kInvalidNodeIndex, 0.0f, true); + m_filters.emplace (Task::Camp, 0.0f, kInvalidNodeIndex, 0.0f, true); + m_filters.emplace (Task::PlantBomb, 0.0f, kInvalidNodeIndex, 0.0f, false); + m_filters.emplace (Task::DefuseBomb, 0.0f, kInvalidNodeIndex, 0.0f, false); + m_filters.emplace (Task::Attack, 0.0f, kInvalidNodeIndex, 0.0f, false); + m_filters.emplace (Task::Hunt, 0.0f, kInvalidNodeIndex, 0.0f, false); + m_filters.emplace (Task::SeekCover, 0.0f, kInvalidNodeIndex, 0.0f, false); + m_filters.emplace (Task::ThrowExplosive, 0.0f, kInvalidNodeIndex, 0.0f, false); + m_filters.emplace (Task::ThrowFlashbang, 0.0f, kInvalidNodeIndex, 0.0f, false); + m_filters.emplace (Task::ThrowSmoke, 0.0f, kInvalidNodeIndex, 0.0f, false); + m_filters.emplace (Task::DoubleJump, 0.0f, kInvalidNodeIndex, 0.0f, false); + m_filters.emplace (Task::EscapeFromBomb, 0.0f, kInvalidNodeIndex, 0.0f, false); + m_filters.emplace (Task::ShootBreakable, 0.0f, kInvalidNodeIndex, 0.0f, false); + m_filters.emplace (Task::Hide, 0.0f, kInvalidNodeIndex, 0.0f, false); + m_filters.emplace (Task::Blind, 0.0f, kInvalidNodeIndex, 0.0f, false); + m_filters.emplace (Task::Spraypaint, 0.0f, kInvalidNodeIndex, 0.0f, false); } -void BotManager::resetFilters (void) { +void BotManager::resetFilters () { for (auto &task : m_filters) { task.time = 0.0f; } @@ -501,15 +452,15 @@ void BotManager::resetFilters (void) { void BotManager::decrementQuota (int by) { if (by != 0) { - yb_quota.set (cr::clamp (yb_quota.integer () - by, 0, yb_quota.integer ())); + yb_quota.set (cr::clamp (yb_quota.int_ () - by, 0, yb_quota.int_ ())); return; } yb_quota.set (0); } -void BotManager::initQuota (void) { - m_maintainTime = game.timebase () + yb_join_delay.flt (); - m_quotaMaintainTime = game.timebase () + yb_join_delay.flt (); +void BotManager::initQuota () { + m_maintainTime = game.timebase () + yb_join_delay.float_ (); + m_quotaMaintainTime = game.timebase () + yb_join_delay.float_ (); m_creationTab.clear (); } @@ -518,7 +469,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 () ? game.maxClients () - 1 - (game.isDedicated () ? 0 : getHumansCount ()) : game.maxClients (); + int maxClients = yb_autovacate.bool_ () ? game.maxClients () - 1 - (game.isDedicated () ? 0 : getHumansCount ()) : game.maxClients (); if (getBotCount () >= maxClients - getHumansCount ()) { return; @@ -534,7 +485,7 @@ void BotManager::serverFill (int selection, int personality, int difficulty, int int toAdd = numToAdd == -1 ? maxClients - (getHumansCount () + getBotCount ()) : numToAdd; - for (int i = 0; i <= toAdd; i++) { + for (int i = 0; i <= toAdd; ++i) { addbot ("", difficulty, personality, selection, -1, true); } ctrl.msg ("Fill Server with %s bots...", &teams[selection][0]); @@ -543,6 +494,10 @@ void BotManager::serverFill (int selection, int personality, int difficulty, int void BotManager::kickEveryone (bool instant, bool zeroQuota) { // this function drops all bot clients from server (this function removes only yapb's)`q + if (!hasBotsOnline () || !yb_quota.bool_ ()) { + return; + } + ctrl.msg ("Bots are removed from server."); if (zeroQuota) { @@ -550,12 +505,8 @@ void BotManager::kickEveryone (bool instant, bool zeroQuota) { } if (instant) { - for (int i = 0; i < game.maxClients (); i++) { - auto bot = m_bots[i]; - - if (bot != nullptr) { - bot->kick (); - } + for (const auto &bot : m_bots) { + bot->kick (); } } m_creationTab.clear (); @@ -564,10 +515,8 @@ 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 < game.maxClients (); i++) { - auto bot = m_bots[i]; - - if (bot != nullptr && team == bot->m_team) { + for (const auto &bot : m_bots) { + if (team == bot->m_team) { decrementQuota (); bot->kick (); @@ -581,19 +530,17 @@ void BotManager::kickFromTeam (Team team, bool removeAll) { void BotManager::killAllBots (int team) { // this function kills all bots on server (only this dll controlled bots) - for (int i = 0; i < game.maxClients (); i++) { - if (m_bots[i] != nullptr) { - if (team != -1 && team != m_bots[i]->m_team) { - continue; - } - m_bots[i]->kill (); + for (const auto &bot : m_bots) { + if (team != -1 && team != bot->m_team) { + continue; } + bot->kill (); } ctrl.msg ("All Bots died !"); } void BotManager::kickBot (int index) { - auto bot = getBot (index); + auto bot = findBotByIndex (index); if (bot) { decrementQuota (); @@ -607,24 +554,22 @@ bool BotManager::kickRandom (bool decQuota, Team fromTeam) { // if forTeam is unassigned, that means random team bool deadBotFound = false; - auto updateQuota = [&] (void) { + auto updateQuota = [&] () { if (decQuota) { decrementQuota (); } }; auto belongsTeam = [&] (Bot *bot) { - if (fromTeam == TEAM_UNASSIGNED) { + if (fromTeam == Team::Unassigned) { return true; } return bot->m_team == fromTeam; }; // first try to kick the bot that is currently dead - for (int i = 0; i < game.maxClients (); i++) { - auto bot = m_bots[i]; - - if (bot && !bot->m_notKilled && belongsTeam (bot)) // is this slot used? + for (const auto &bot : m_bots) { + if (!bot->m_notKilled && belongsTeam (bot.get ())) // is this slot used? { updateQuota (); bot->kick (); @@ -639,32 +584,28 @@ bool BotManager::kickRandom (bool decQuota, Team fromTeam) { } // if no dead bots found try to find one with lowest amount of frags - int index = 0; + Bot *selected = nullptr; float score = 9999.0f; // search bots in this team - for (int i = 0; i < game.maxClients (); i++) { - auto bot = m_bots [i]; - - if (bot && bot->pev->frags < score && belongsTeam (bot)) { - index = i; + for (const auto &bot : m_bots) { + if (bot->pev->frags < score && belongsTeam (bot.get ())) { + selected = bot.get (); score = bot->pev->frags; } } // if found some bots - if (index != 0) { + if (selected != nullptr) { updateQuota (); - m_bots[index]->kick (); + selected->kick (); return true; } // worst case, just kick some random bot - for (int i = 0; i < game.maxClients (); i++) { - auto bot = m_bots[i]; - - if (bot && belongsTeam (bot)) // is this slot used? + for (const auto &bot : m_bots) { + if (belongsTeam (bot.get ())) // is this slot used? { updateQuota (); bot->kick (); @@ -680,7 +621,7 @@ void BotManager::setWeaponMode (int selection) { selection--; - constexpr int std[7][NUM_WEAPONS] = { + constexpr int std[7][kNumWeapons] = { {-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 @@ -690,7 +631,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 }; - constexpr int as[7][NUM_WEAPONS] = { + constexpr int as[7][kNumWeapons] = { {-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 @@ -705,7 +646,7 @@ void BotManager::setWeaponMode (int selection) { auto tab = conf.getRawWeapons (); // set the correct weapon mode - for (int i = 0; i < NUM_WEAPONS; i++) { + for (int i = 0; i < kNumWeapons; ++i) { tab[i].teamStandard = std[selection][i]; tab[i].teamAS = as[selection][i]; } @@ -714,63 +655,53 @@ void BotManager::setWeaponMode (int selection) { ctrl.msg ("%s weapon mode selected", &modes[selection][0]); } -void BotManager::listBots (void) { +void BotManager::listBots () { // this function list's bots currently playing on the server - ctrl.msg ("%-3.5s %-9.13s %-17.18s %-3.4s %-3.4s %-3.4s", "index", "name", "personality", "team", "difficulty", "frags"); + ctrl.msg ("%-3.5s\t%-19.16s\t%-10.12s\t%-3.4s\t%-3.4s\t%-3.4s", "index", "name", "personality", "team", "difficulty", "frags"); - for (int i = 0; i < game.maxClients (); i++) { - Bot *bot = getBot (i); - - // is this player slot valid - if (bot != nullptr) { - 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)); - } + for (const auto &bot : bots) {; + ctrl.msg ("[%-3.1d]\t%-19.16s\t%-10.12s\t%-3.4s\t%-3.1d\t%-3.1d", bot->index (), STRING (bot->pev->netname), bot->m_personality == Personality::Rusher ? "rusher" : bot->m_personality == Personality::Normal ? "normal" : "careful", bot->m_team == Team::CT ? "CT" : "T", bot->m_difficulty, static_cast (bot->pev->frags)); } + ctrl.msg ("%d bots", m_bots.length ()); } -int BotManager::getBotCount (void) { +int BotManager::getBotCount () { // this function returns number of yapb's playing on the server - int count = 0; - - for (int i = 0; i < game.maxClients (); i++) { - if (m_bots[i] != nullptr) { - count++; - } - } - return count; + return m_bots.length (); } -void BotManager::countTeamPlayers (int &ts, int &cts) { +Twin BotManager::countTeamPlayers () { + int ts = 0, cts = 0; + for (const auto &client : util.getClients ()) { - if (client.flags & CF_USED) { - if (client.team2 == TEAM_TERRORIST) { + if (client.flags & ClientFlags::Used) { + if (client.team2 == Team::Terrorist) { ts++; } - else if (client.team2 == TEAM_COUNTER) { + else if (client.team2 == Team::CT) { cts++; } } } + return { ts, cts }; } -Bot *BotManager::getHighfragBot (int team) { +Bot *BotManager::findHighestFragBot (int team) { int bestIndex = 0; float bestScore = -1; // search bots in this team - for (int i = 0; i < game.maxClients (); i++) { - auto bot = bots.getBot (i); - - if (bot != nullptr && bot->m_notKilled && bot->m_team == team) { + for (const auto &bot : bots) { + if (bot->m_notKilled && bot->m_team == team) { if (bot->pev->frags > bestScore) { - bestIndex = i; + bestIndex = bot->index (); bestScore = bot->pev->frags; } } } - return getBot (bestIndex); + return findBotByIndex (bestIndex); } void BotManager::updateTeamEconomics (int team, bool setTrue) { @@ -780,7 +711,7 @@ void BotManager::updateTeamEconomics (int team, bool setTrue) { extern ConVar yb_economics_rounds; - if (setTrue || !yb_economics_rounds.boolean ()) { + if (setTrue || !yb_economics_rounds.bool_ ()) { m_economicsGood[team] = true; return; // don't check economics while economics disable } @@ -790,11 +721,9 @@ void BotManager::updateTeamEconomics (int team, bool setTrue) { int numTeamPlayers = 0; // start calculating - for (int i = 0; i < game.maxClients (); i++) { - auto bot = m_bots[i]; - - if (bot != nullptr && bot->m_team == team) { - if (bot->m_moneyAmount <= econLimit[ECO_PRIMARY_GT]) { + for (const auto &bot : m_bots) { + if (bot->m_team == team) { + if (bot->m_moneyAmount <= econLimit[EcoLimit::PrimaryGreater]) { numPoorPlayers++; } numTeamPlayers++; // update count of team @@ -816,39 +745,26 @@ void BotManager::updateTeamEconomics (int team, bool setTrue) { } } -void BotManager::updateBotDifficulties (void) { - int difficulty = yb_difficulty.integer (); +void BotManager::updateBotDifficulties () { + int difficulty = yb_difficulty.int_ (); if (difficulty != m_lastDifficulty) { // sets new difficulty for all bots - for (int i = 0; i < game.maxClients (); i++) { - auto bot = m_bots[i]; - - if (bot != nullptr) { - bot->m_difficulty = difficulty; - } + for (const auto &bot : m_bots) { + bot->m_difficulty = difficulty; } m_lastDifficulty = difficulty; } } -void BotManager::destroy (void) { +void BotManager::destroy () { // this function free all bots slots (used on server shutdown) - for (int i = 0; i < MAX_ENGINE_PLAYERS; i++) { - destroy (i); - } + m_bots.clear (); } -void BotManager::destroy (int index) { - // this function frees one bot selected by index (used on bot disconnect) - - delete m_bots[index]; - m_bots[index] = nullptr; -} - -Bot::Bot (edict_t *bot, int difficulty, int personality, int team, int member, const String &steamId) { +Bot::Bot (edict_t *bot, int difficulty, int personality, int team, int member) { // this function does core operation of creating bot, it's called by CreateBot (), // when bot setup completed, (this is a bot class constructor) @@ -868,31 +784,32 @@ Bot::Bot (edict_t *bot, int difficulty, int personality, int team, int member, c bots.execGameEntity (&bot->v); // set all info buffer keys for this bot - char *buffer = engfuncs.pfnGetInfoKeyBuffer (bot); + auto buffer = engfuncs.pfnGetInfoKeyBuffer (bot); engfuncs.pfnSetClientKeyValue (clientIndex, buffer, "_vgui_menus", "0"); - if (!(game.is (GAME_LEGACY)) && yb_latency_display.integer () == 1) { - engfuncs.pfnSetClientKeyValue (clientIndex, buffer, "*bot", "1"); + if (!game.is (GameFlags::Legacy)) { + + if (yb_show_latency.int_ () == 1) { + engfuncs.pfnSetClientKeyValue (clientIndex, buffer, "*bot", "1"); + } + auto avatar = conf.getRandomAvatar (); + + if (yb_show_avatars.bool_ () && !avatar.empty ()) { + engfuncs.pfnSetClientKeyValue (clientIndex, buffer, "*sid", avatar.chars ()); + } } char reject[256] = {0, }; - MDLL_ClientConnect (bot, STRING (bot->v.netname), util.format ("127.0.0.%d", game.indexOfEntity (bot) + 100), reject); + MDLL_ClientConnect (bot, STRING (bot->v.netname), strings.format ("127.0.0.%d", clientIndex + 100), reject); 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 + logger.error ("Server refused '%s' connection (%s)", STRING (bot->v.netname), reject); + game.serverCommand ("kick \"%s\"", STRING (bot->v.netname)); // kick the bot player if the server refused it bot->v.flags |= FL_KILLME; return; } - // should be set after client connect - if (yb_avatar_display.boolean () && !steamId.empty ()) { - engfuncs.pfnSetClientKeyValue (clientIndex, buffer, "*sid", steamId.chars ()); - } - memset (&m_pingOffset, 0, sizeof (m_pingOffset)); - memset (&m_ping, 0, sizeof (m_ping)); - MDLL_ClientPutInServer (bot); bot->v.flags |= FL_FAKECLIENT; // set this player as fakeclient @@ -900,24 +817,20 @@ Bot::Bot (edict_t *bot, int difficulty, int personality, int team, int member, c m_notStarted = true; // hasn't joined game yet m_forceRadio = false; - m_startAction = GAME_MSG_NONE; + m_index = clientIndex - 1; + m_startAction = BotMsg::None; m_retryJoin = 0; m_moneyAmount = 0; - m_logotypeIndex = rng.getInt (0, 9); - m_tasks.reserve (TASK_MAX); + m_logotypeIndex = conf.getRandomLogoIndex (); // assign how talkative this bot will be - m_sayTextBuffer.chatDelay = rng.getFloat (3.8f, 10.0f); - m_sayTextBuffer.chatProbability = rng.getInt (10, 100); + m_sayTextBuffer.chatDelay = rg.float_ (3.8f, 10.0f); + m_sayTextBuffer.chatProbability = rg.int_ (10, 100); m_notKilled = false; - m_weaponBurstMode = BURST_OFF; - m_difficulty = difficulty; - - if (difficulty < 0 || difficulty > 4) { - difficulty = rng.getInt (3, 4); - yb_difficulty.set (difficulty); - } + m_weaponBurstMode = BurstMode::Off; + m_difficulty = cr::clamp (difficulty, 0, 4); + m_basePing = rg.int_ (7, 14); m_lastCommandTime = game.timebase () - 0.1f; m_frameInterval = game.timebase (); @@ -925,21 +838,21 @@ Bot::Bot (edict_t *bot, int difficulty, int personality, int team, int member, c switch (personality) { case 1: - m_personality = PERSONALITY_RUSHER; - m_baseAgressionLevel = rng.getFloat (0.7f, 1.0f); - m_baseFearLevel = rng.getFloat (0.0f, 0.4f); + m_personality = Personality::Rusher; + m_baseAgressionLevel = rg.float_ (0.7f, 1.0f); + m_baseFearLevel = rg.float_ (0.0f, 0.4f); break; case 2: - m_personality = PERSONALITY_CAREFUL; - m_baseAgressionLevel = rng.getFloat (0.2f, 0.5f); - m_baseFearLevel = rng.getFloat (0.7f, 1.0f); + m_personality = Personality::Careful; + m_baseAgressionLevel = rg.float_ (0.2f, 0.5f); + m_baseFearLevel = rg.float_ (0.7f, 1.0f); break; default: - m_personality = PERSONALITY_NORMAL; - m_baseAgressionLevel = rng.getFloat (0.4f, 0.7f); - m_baseFearLevel = rng.getFloat (0.4f, 0.7f); + m_personality = Personality::Normal; + m_baseAgressionLevel = rg.float_ (0.4f, 0.7f); + m_baseFearLevel = rg.float_ (0.4f, 0.7f); break; } @@ -947,7 +860,7 @@ Bot::Bot (edict_t *bot, int difficulty, int personality, int team, int member, c memset (&m_ammo, 0, sizeof (m_ammo)); m_currentWeapon = 0; // current weapon is not assigned at start - m_voicePitch = rng.getInt (80, 115); // assign voice pitch + m_voicePitch = rg.int_ (80, 115); // assign voice pitch // copy them over to the temp level variables m_agressionLevel = m_baseAgressionLevel; @@ -965,28 +878,18 @@ Bot::Bot (edict_t *bot, int difficulty, int personality, int team, int member, c newRound (); } -float Bot::getFrameInterval (void) { +float Bot::getFrameInterval () { return cr::fzero (m_thinkInterval) ? m_frameInterval : m_thinkInterval; } -Bot::~Bot (void) { - // this is bot destructor - - clearSearchNodes (); - clearRoute (); - clearTasks (); - - conf.clearUsedName (this); -} - int BotManager::getHumansCount (bool ignoreSpectators) { // this function returns number of humans playing on the server int count = 0; 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) { + if ((client.flags & ClientFlags::Used) && !bots[client.ent] && !(client.ent->v.flags & FL_FAKECLIENT)) { + if (ignoreSpectators && client.team2 != Team::Terrorist && client.team2 != Team::CT) { continue; } count++; @@ -995,13 +898,13 @@ int BotManager::getHumansCount (bool ignoreSpectators) { return count; } -int BotManager::getAliveHumansCount (void) { +int BotManager::getAliveHumansCount () { // this function returns number of humans playing on the server int count = 0; for (const auto &client : util.getClients ()) { - if ((client.flags & (CF_USED | CF_ALIVE)) && getBot (client.ent) == nullptr && !(client.ent->v.flags & FL_FAKECLIENT)) { + if ((client.flags & (ClientFlags::Used | ClientFlags::Alive)) && bots[client.ent] == nullptr && !(client.ent->v.flags & FL_FAKECLIENT)) { count++; } } @@ -1009,25 +912,34 @@ int BotManager::getAliveHumansCount (void) { } bool BotManager::isTeamStacked (int team) { - if (team != TEAM_COUNTER && team != TEAM_TERRORIST) { + if (team != Team::CT && team != Team::Terrorist) { return false; } - int limitTeams = mp_limitteams.integer (); + int limitTeams = mp_limitteams.int_ (); if (!limitTeams) { return false; } - int teamCount[MAX_TEAM_COUNT] = { 0, }; + int teamCount[kGameTeamNum] = { 0, }; for (const auto &client : util.getClients ()) { - if ((client.flags & CF_USED) && client.team2 != TEAM_UNASSIGNED && client.team2 != TEAM_SPECTATOR) { + if ((client.flags & ClientFlags::Used) && client.team2 != Team::Unassigned && client.team2 != Team::Spectator) { teamCount[client.team2]++; } } - return teamCount[team] + 1 > teamCount[team == TEAM_COUNTER ? TEAM_TERRORIST : TEAM_COUNTER] + limitTeams; + return teamCount[team] + 1 > teamCount[team == Team::CT ? Team::Terrorist : Team::CT] + limitTeams; } -void Bot::newRound (void) { +void BotManager::erase (Bot *bot) { + for (const auto &e : m_bots) { + if (e.get () == bot) { + m_bots.remove (e); // remove from bots array + break; + } + } +} + +void Bot::newRound () { // this function initializes a bot after creation & at the start of each round int i = 0; @@ -1036,16 +948,16 @@ void Bot::newRound (void) { clearSearchNodes (); clearRoute (); - m_waypointOrigin.nullify (); - m_destOrigin.nullify (); - m_currentPath = nullptr; + m_pathOrigin= nullvec; + m_destOrigin= nullvec; + m_path = nullptr; m_currentTravelFlags = 0; - m_desiredVelocity.nullify (); - m_currentWaypointIndex = INVALID_WAYPOINT_INDEX; - m_prevGoalIndex = INVALID_WAYPOINT_INDEX; - m_chosenGoalIndex = INVALID_WAYPOINT_INDEX; - m_loosedBombWptIndex = INVALID_WAYPOINT_INDEX; - m_plantedBombWptIndex = INVALID_WAYPOINT_INDEX; + m_desiredVelocity= nullvec; + m_currentNodeIndex = kInvalidNodeIndex; + m_prevGoalIndex = kInvalidNodeIndex; + m_chosenGoalIndex = kInvalidNodeIndex; + m_loosedBombWptIndex = kInvalidNodeIndex; + m_plantedBombWptIndex = kInvalidNodeIndex; m_grenadeRequested = false; m_moveToC4 = false; @@ -1060,24 +972,25 @@ void Bot::newRound (void) { m_avoid = nullptr; m_avoidTime = 0.0f; - for (i = 0; i < 5; i++) { - m_prevWptIndex[i] = INVALID_WAYPOINT_INDEX; + for (i = 0; i < 5; ++i) { + m_prevWptIndex[i] = kInvalidNodeIndex; } m_navTimeset = game.timebase (); m_team = game.getTeam (ent ()); m_isVIP = false; switch (m_personality) { - case PERSONALITY_NORMAL: - m_pathType = rng.chance (50) ? SEARCH_PATH_SAFEST_FASTER : SEARCH_PATH_SAFEST; + default: + case Personality::Normal: + m_pathType = rg.chance (50) ? FindPath::Optimal : FindPath::Safe; break; - case PERSONALITY_RUSHER: - m_pathType = SEARCH_PATH_FASTEST; + case Personality::Rusher: + m_pathType = FindPath::Fast; break; - case PERSONALITY_CAREFUL: - m_pathType = SEARCH_PATH_SAFEST; + case Personality::Careful: + m_pathType = FindPath::Safe; break; } @@ -1092,8 +1005,7 @@ void Bot::newRound (void) { m_preventFlashing = 0.0f; m_timeTeamOrder = 0.0f; - m_timeRepotingInDelay = rng.getFloat (40.0f, 240.0f); - m_askCheckTime = 0.0f; + m_askCheckTime = rg.float_ (30.0f, 90.0f); m_minSpeed = 260.0f; m_prevSpeed = 0.0f; m_prevOrigin = Vector (9999.0f, 9999.0f, 9999.0f); @@ -1110,7 +1022,7 @@ void Bot::newRound (void) { m_itemCheckTime = 0.0f; m_breakableEntity = nullptr; - m_breakableOrigin.nullify (); + m_breakableOrigin= nullvec; m_timeDoorOpen = 0.0f; resetCollision (); @@ -1119,7 +1031,7 @@ void Bot::newRound (void) { m_enemy = nullptr; m_lastVictim = nullptr; m_lastEnemy = nullptr; - m_lastEnemyOrigin.nullify (); + m_lastEnemyOrigin= nullvec; m_trackingEdict = nullptr; m_timeNextTracking = 0.0f; @@ -1143,23 +1055,22 @@ void Bot::newRound (void) { m_aimFlags = 0; m_liftState = 0; - m_aimLastError.nullify (); - m_position.nullify (); - m_liftTravelPos.nullify (); + m_aimLastError= nullvec; + m_position= nullvec; + m_liftTravelPos= nullvec; setIdealReactionTimers (true); m_targetEntity = nullptr; - m_tasks.reserve (TASK_MAX); m_followWaitTime = 0.0f; m_hostages.clear (); - for (i = 0; i < CHATTER_MAX; i++) { - m_chatterTimes[i] = -1.0f; + for (i = 0; i < Chatter::Count; ++i) { + m_chatterTimes[i] = kMaxChatterRepeatInteval; } m_isReloading = false; - m_reloadState = RELOAD_NONE; + m_reloadState = Reload::None; m_reloadCheckTime = 0.0f; m_shootTime = game.timebase (); @@ -1183,7 +1094,7 @@ void Bot::newRound (void) { m_sayTextBuffer.entityIndex = -1; m_sayTextBuffer.sayText.clear (); - m_buyState = BUYSTATE_PRIMARY_WEAPON; + m_buyState = BuyState::PrimaryWeapon; m_lastEquipTime = 0.0f; // if bot died, clear all weapon stuff and force buying again @@ -1196,8 +1107,8 @@ void Bot::newRound (void) { m_flashLevel = 100.0f; m_checkDarkTime = game.timebase (); - m_knifeAttackTime = game.timebase () + rng.getFloat (1.3f, 2.6f); - m_nextBuyTime = game.timebase () + rng.getFloat (0.6f, 2.0f); + m_knifeAttackTime = game.timebase () + rg.float_ (1.3f, 2.6f); + m_nextBuyTime = game.timebase () + rg.float_ (0.6f, 2.0f); m_buyPending = false; m_inBombZone = false; @@ -1208,8 +1119,8 @@ void Bot::newRound (void) { m_shieldCheckTime = 0.0f; m_zoomCheckTime = 0.0f; m_strafeSetTime = 0.0f; - m_combatStrafeDir = STRAFE_DIR_NONE; - m_fightStyle = FIGHT_NONE; + m_combatStrafeDir = Dodge::None; + m_fightStyle = Fight::None; m_lastFightStyleCheck = 0.0f; m_checkWeaponSwitch = true; @@ -1222,7 +1133,7 @@ void Bot::newRound (void) { m_defendHostage = false; m_headedTime = 0.0f; - m_timeLogoSpray = game.timebase () + rng.getFloat (5.0f, 30.0f); + m_timeLogoSpray = game.timebase () + rg.float_ (5.0f, 30.0f); m_spawnTime = game.timebase (); m_lastChatTime = game.timebase (); @@ -1235,30 +1146,30 @@ void Bot::newRound (void) { m_heardSoundTime = game.timebase (); // clear its message queue - for (i = 0; i < 32; i++) { - m_messageQueue[i] = GAME_MSG_NONE; + for (i = 0; i < 32; ++i) { + m_messageQueue[i] = BotMsg::None; } m_actMessageIndex = 0; m_pushMessageIndex = 0; // and put buying into its message queue - pushMsgQueue (GAME_MSG_PURCHASE); - startTask (TASK_NORMAL, TASKPRI_NORMAL, INVALID_WAYPOINT_INDEX, 0.0f, true); + pushMsgQueue (BotMsg::Buy); + startTask (Task::Normal, TaskPri::Normal, kInvalidNodeIndex, 0.0f, true); - if (rng.chance (50)) { - pushChatterMessage (CHATTER_NEW_ROUND); + if (rg.chance (50)) { + pushChatterMessage (Chatter::NewRound); } - 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); + m_thinkInterval = game.is (GameFlags::Legacy | GameFlags::Xash3D) ? 0.0f : (1.0f / cr::clamp (yb_think_fps.float_ (), 30.0f, 90.0f)) * rg.float_ (0.95f, 1.05f); } -void Bot::kill (void) { +void Bot::kill () { // this function kills a bot (not just using ClientKill, but like the CSBot does) // base code courtesy of Lazy (from bots-united forums!) bots.touchKillerEntity (this); } -void Bot::kick (void) { +void Bot::kick () { // this function kick off one bot from the server. auto username = STRING (pev->netname); @@ -1268,28 +1179,28 @@ void Bot::kick (void) { // clear fakeclient bit pev->flags &= ~FL_FAKECLIENT; - game.execCmd ("kick \"%s\"", username); + game.serverCommand ("kick \"%s\"", username); ctrl.msg ("Bot '%s' kicked", username); } -void Bot::updateTeamJoin (void) { +void Bot::updateTeamJoin () { // this function handles the selection of teams & class // cs prior beta 7.0 uses hud-based motd, so press fire once - if (game.is (GAME_LEGACY)) { + if (game.is (GameFlags::Legacy)) { pev->button |= IN_ATTACK; } // check if something has assigned team to us - else if (m_team == TEAM_TERRORIST || m_team == TEAM_COUNTER) { + else if (m_team == Team::Terrorist || m_team == Team::CT) { m_notStarted = false; } - else if (m_team == TEAM_UNASSIGNED && m_retryJoin > 2) { - m_startAction = GAME_MSG_TEAM_SELECT; + else if (m_team == Team::Unassigned && m_retryJoin > 2) { + m_startAction = BotMsg::TeamSelect; } // if bot was unable to join team, and no menus popups, check for stacked team - if (m_startAction == GAME_MSG_NONE && ++m_retryJoin > 3) { + if (m_startAction == BotMsg::None && ++m_retryJoin > 3) { if (bots.isTeamStacked (m_wantedTeam - 1)) { m_retryJoin = 0; @@ -1301,8 +1212,8 @@ void Bot::updateTeamJoin (void) { } // handle counter-strike stuff here... - if (m_startAction == GAME_MSG_TEAM_SELECT) { - m_startAction = GAME_MSG_NONE; // switch back to idle + if (m_startAction == BotMsg::TeamSelect) { + m_startAction = BotMsg::None; // switch back to idle char teamJoin = yb_join_team.str ()[0]; @@ -1318,132 +1229,27 @@ void Bot::updateTeamJoin (void) { } // select the team the bot wishes to join... - game.execBotCmd (ent (), "menuselect %d", m_wantedTeam); + game.botCommand (ent (), "menuselect %d", m_wantedTeam); } - else if (m_startAction == GAME_MSG_CLASS_SELECT) { - m_startAction = GAME_MSG_NONE; // switch back to idle + else if (m_startAction == BotMsg::ClassSelect) { + m_startAction = BotMsg::None; // switch back to idle // czero has additional models - int maxChoice = game.is (GAME_CZERO) ? 5 : 4; + int maxChoice = game.is (GameFlags::ConditionZero) ? 5 : 4; if (m_wantedClass < 1 || m_wantedClass > maxChoice) { - m_wantedClass = rng.getInt (1, maxChoice); // use random if invalid + m_wantedClass = rg.int_ (1, maxChoice); // use random if invalid } // select the class the bot wishes to use... - game.execBotCmd (ent (), "menuselect %d", m_wantedClass); + game.botCommand (ent (), "menuselect %d", m_wantedClass); // bot has now joined the game (doesn't need to be started) m_notStarted = false; // check for greeting other players, since we connected - if (rng.chance (20)) { - pushChatMessage (CHAT_WELCOME); - } - } -} - -void BotManager::calculatePingOffsets (void) { - if (!game.is (GAME_SUPPORT_SVC_PINGS) || yb_latency_display.integer () != 2) { - return; - } - int averagePing = 0; - int numHumans = 0; - - for (int i = 0; i < game.maxClients (); i++) { - edict_t *ent = game.entityOfIndex (i + 1); - - if (!util.isPlayer (ent)) { - continue; - } - numHumans++; - - int ping, loss; - engfuncs.pfnGetPlayerStats (ent, &ping, &loss); - - if (ping < 0 || ping > 100) { - ping = rng.getInt (3, 15); - } - averagePing += ping; - } - - if (numHumans > 0) { - averagePing /= numHumans; - } - else { - averagePing = rng.getInt (30, 40); - } - - for (int i = 0; i < game.maxClients (); i++) { - auto bot = getBot (i); - - if (bot == nullptr) { - continue; - } - - int part = static_cast (averagePing * 0.2f); - int botPing = rng.getInt (averagePing - part, averagePing + part) + rng.getInt (bot->m_difficulty + 3, bot->m_difficulty + 6) + 10; - - if (botPing <= 5) { - botPing = rng.getInt (10, 23); - } - else if (botPing > 100) { - botPing = rng.getInt (30, 40); - } - - for (bot->m_pingOffset[0] = 0; bot->m_pingOffset[0] < 4; bot->m_pingOffset[0]++) { - if ((botPing - bot->m_pingOffset[0]) % 4 == 0) { - bot->m_ping[0] = (botPing - bot->m_pingOffset[0]) / 4; - break; - } - } - - for (bot->m_pingOffset[1] = 0; bot->m_pingOffset[1] < 2; bot->m_pingOffset[1]++) { - if ((botPing - bot->m_pingOffset[1]) % 2 == 0) { - bot->m_ping[1] = (botPing - bot->m_pingOffset[1]) / 2; - break; - } - } - bot->m_ping[2] = botPing; - } -} - -void BotManager::sendPingOffsets (edict_t *to) { - 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)))) { - return; - } - MessageWriter msg; - - // missing from sdk - constexpr int SVC_PINGS = 17; - - for (int i = 0; i < game.maxClients (); i++) { - auto bot = m_bots[i]; - - if (bot == nullptr) { - continue; - } - msg.start (MSG_ONE_UNRELIABLE, SVC_PINGS, Vector::null (), to) - .writeByte (bot->m_pingOffset[0] * 64 + (1 + 2 * i)) - .writeShort (bot->m_ping[0]) - .writeByte (bot->m_pingOffset[1] * 128 + (2 + 4 * i)) - .writeShort (bot->m_ping[1]) - .writeByte (4 + 8 * i) - .writeShort (bot->m_ping[2]) - .writeByte (0) - .end (); - } -} - -void BotManager::sendDeathMsgFix (void) { - if (yb_latency_display.integer () == 2 && m_deathMsgSent) { - m_deathMsgSent = false; - - for (const auto &client : util.getClients ()) { - sendPingOffsets (client.ent); + if (rg.chance (20)) { + pushChatMessage (Chat::Hello); } } } @@ -1454,9 +1260,9 @@ void BotManager::captureChatRadio (const char *cmd, const char *arg, edict_t *en } if (stricmp (cmd, "say") == 0 || stricmp (cmd, "say_team") == 0) { - Bot *bot = nullptr; - if (strcmp (arg, "dropme") == 0 || strcmp (arg, "dropc4") == 0) { + Bot *bot = nullptr; + if (util.findNearestPlayer (reinterpret_cast (&bot), ent, 300.0f, true, true, true)) { bot->dropWeaponForUser (ent, util.isEmptyStr (strstr (arg, "c4")) ? false : true); } @@ -1471,13 +1277,13 @@ void BotManager::captureChatRadio (const char *cmd, const char *arg, edict_t *en } for (const auto &client : util.getClients ()) { - if (!(client.flags & CF_USED) || (team != -1 && team != client.team) || alive != util.isAlive (client.ent)) { + if (!(client.flags & ClientFlags::Used) || (team != -1 && team != client.team2) || alive != util.isAlive (client.ent)) { continue; } - auto target = bots.getBot (client.ent); + auto target = bots[client.ent]; if (target != nullptr) { - target->m_sayTextBuffer.entityIndex = game.indexOfEntity (ent); + target->m_sayTextBuffer.entityIndex = game.indexOfPlayer (ent); if (util.isEmptyStr (engfuncs.pfnCmd_Args ())) { continue; @@ -1487,51 +1293,48 @@ void BotManager::captureChatRadio (const char *cmd, const char *arg, edict_t *en } } } - Client &radioTarget = util.getClient (game.indexOfEntity (ent) - 1); + Client &target = util.getClient (game.indexOfPlayer (ent)); // check if this player alive, and issue something - if ((radioTarget.flags & CF_ALIVE) && radioTarget.radio != 0 && strncmp (cmd, "menuselect", 10) == 0) { + if ((target.flags & ClientFlags::Alive) && target.radio != 0 && strncmp (cmd, "menuselect", 10) == 0) { int radioCommand = atoi (arg); if (radioCommand != 0) { - radioCommand += 10 * (radioTarget.radio - 1); + radioCommand += 10 * (target.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); + if (radioCommand != Radio::RogerThat && radioCommand != Radio::Negative && radioCommand != Radio::ReportingIn) { + for (const auto &bot : bots) { // validate bot - if (bot != nullptr && bot->m_team == radioTarget.team && ent != bot->ent () && bot->m_radioOrder == 0) { + if (bot->m_team == target.team && ent != bot->ent () && bot->m_radioOrder == 0) { bot->m_radioOrder = radioCommand; bot->m_radioEntity = ent; } } } - bots.setLastRadioTimestamp (radioTarget.team, game.timebase ()); + bots.setLastRadioTimestamp (target.team, game.timebase ()); } - radioTarget.radio = 0; + target.radio = 0; } else if (strncmp (cmd, "radio", 5) == 0) { - radioTarget.radio = atoi (&cmd[5]); + target.radio = atoi (&cmd[5]); } } -void BotManager::notifyBombDefuse (void) { +void BotManager::notifyBombDefuse () { // 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) { + for (const auto &bot : bots) { + if (bot->m_team == Team::Terrorist && bot->m_notKilled && bot->getCurrentTaskId () != Task::MoveToPosition) { bot->clearSearchNodes (); - bot->m_position = waypoints.getBombPos (); - bot->startTask (TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, INVALID_WAYPOINT_INDEX, 0.0f, true); + bot->m_position = graph.getBombPos (); + bot->startTask (Task::MoveToPosition, TaskPri::MoveToPosition, kInvalidNodeIndex, 0.0f, true); } } } -void BotManager::updateActiveGrenade (void) { +void BotManager::updateActiveGrenade () { if (m_grenadeUpdateTime > game.timebase ()) { return; } @@ -1551,7 +1354,7 @@ void BotManager::updateActiveGrenade (void) { m_grenadeUpdateTime = game.timebase () + 0.213f; } -void BotManager::updateIntrestingEntities (void) { +void BotManager::updateIntrestingEntities () { if (m_entityUpdateTime > game.timebase ()) { return; } @@ -1560,7 +1363,7 @@ void BotManager::updateIntrestingEntities (void) { m_intrestingEntities.clear (); // search the map for entities - for (int i = MAX_ENGINE_PLAYERS - 1; i < globals->maxEntities; i++) { + for (int i = kGameMaxPlayers - 1; i < globals->maxEntities; ++i) { auto ent = game.entityOfIndex (i); // only valid drawn entities @@ -1568,19 +1371,19 @@ void BotManager::updateIntrestingEntities (void) { continue; } auto classname = STRING (ent->v.classname); - + // search for grenades, weaponboxes, weapons, items and armoury entities if (strncmp ("weapon", classname, 6) == 0 || strncmp ("grenade", classname, 7) == 0 || strncmp ("item", classname, 4) == 0 || strncmp ("armoury", classname, 7) == 0) { m_intrestingEntities.push (ent); } // pickup some csdm stuff if we're running csdm - if (game.mapIs (MAP_CS) && strncmp ("hostage", classname, 7) == 0) { + if (game.mapIs (MapFlags::HostageRescue) && strncmp ("hostage", classname, 7) == 0) { m_intrestingEntities.push (ent); } // pickup some csdm stuff if we're running csdm - if (game.is (GAME_CSDM) && strncmp ("csdm", classname, 4) == 0) { + if (game.is (GameFlags::CSDM) && strncmp ("csdm", classname, 4) == 0) { m_intrestingEntities.push (ent); } } @@ -1597,103 +1400,99 @@ void BotManager::selectLeaders (int team, bool reset) { return; } - if (game.mapIs (MAP_AS)) { - if (team == TEAM_COUNTER && !m_leaderChoosen[TEAM_COUNTER]) { - for (int i = 0; i < game.maxClients (); i++) { - auto bot = m_bots[i]; - - if (bot != nullptr && bot->m_isVIP) { + if (game.mapIs (MapFlags::Assassination)) { + if (team == Team::CT && !m_leaderChoosen[Team::CT]) { + for (const auto &bot : m_bots) { + if (bot->m_isVIP) { // vip bot is the leader bot->m_isLeader = true; - if (rng.chance (50)) { - bot->pushRadioMessage (RADIO_FOLLOW_ME); + if (rg.chance (50)) { + bot->pushRadioMessage (Radio::FollowMe); bot->m_campButtons = 0; } } } - m_leaderChoosen[TEAM_COUNTER] = true; + m_leaderChoosen[Team::CT] = true; } - else if (team == TEAM_TERRORIST && !m_leaderChoosen[TEAM_TERRORIST]) { - auto bot = bots.getHighfragBot (team); + else if (team == Team::Terrorist && !m_leaderChoosen[Team::Terrorist]) { + auto bot = bots.findHighestFragBot (team); if (bot != nullptr && bot->m_notKilled) { bot->m_isLeader = true; - if (rng.chance (45)) { - bot->pushRadioMessage (RADIO_FOLLOW_ME); + if (rg.chance (45)) { + bot->pushRadioMessage (Radio::FollowMe); } } - m_leaderChoosen[TEAM_TERRORIST] = true; + m_leaderChoosen[Team::Terrorist] = true; } } - else if (game.mapIs (MAP_DE)) { - if (team == TEAM_TERRORIST && !m_leaderChoosen[TEAM_TERRORIST]) { - for (int i = 0; i < game.maxClients (); i++) { - auto bot = m_bots[i]; - - if (bot != nullptr && bot->m_hasC4) { + else if (game.mapIs (MapFlags::Demolition)) { + if (team == Team::Terrorist && !m_leaderChoosen[Team::Terrorist]) { + for (const auto &bot : m_bots) { + if (bot->m_hasC4) { // bot carrying the bomb is the leader bot->m_isLeader = true; // terrorist carrying a bomb needs to have some company - if (rng.chance (75)) { - if (yb_communication_type.integer () == 2) { - bot->pushChatterMessage (CHATTER_GOING_TO_PLANT_BOMB); + if (rg.chance (75)) { + if (yb_radio_mode.int_ () == 2) { + bot->pushChatterMessage (Chatter::GoingToPlantBomb); } else { - bot->pushChatterMessage (RADIO_FOLLOW_ME); + bot->pushChatterMessage (Radio::FollowMe); } bot->m_campButtons = 0; } } } - m_leaderChoosen[TEAM_TERRORIST] = true; + m_leaderChoosen[Team::Terrorist] = true; } - else if (!m_leaderChoosen[TEAM_COUNTER]) { - if (auto bot = bots.getHighfragBot (team)) { + else if (!m_leaderChoosen[Team::CT]) { + if (auto bot = bots.findHighestFragBot (team)) { bot->m_isLeader = true; - if (rng.chance (30)) { - bot->pushRadioMessage (RADIO_FOLLOW_ME); + if (rg.chance (30)) { + bot->pushRadioMessage (Radio::FollowMe); } } - m_leaderChoosen[TEAM_COUNTER] = true; + m_leaderChoosen[Team::CT] = true; } } - else if (game.mapIs (MAP_ES | MAP_KA | MAP_FY)) { - auto bot = bots.getHighfragBot (team); + else if (game.mapIs (MapFlags::Escape | MapFlags::KnifeArena | MapFlags::Fun)) { + auto bot = bots.findHighestFragBot (team); if (!m_leaderChoosen[team] && bot) { bot->m_isLeader = true; - if (rng.chance (30)) { - bot->pushRadioMessage (RADIO_FOLLOW_ME); + if (rg.chance (30)) { + bot->pushRadioMessage (Radio::FollowMe); } m_leaderChoosen[team] = true; } } else { - auto bot = bots.getHighfragBot (team); + auto bot = bots.findHighestFragBot (team); if (!m_leaderChoosen[team] && bot) { bot->m_isLeader = true; - if (rng.chance (team == TEAM_TERRORIST ? 30 : 40)) { - bot->pushRadioMessage (RADIO_FOLLOW_ME); + if (rg.chance (team == Team::Terrorist ? 30 : 40)) { + bot->pushRadioMessage (Radio::FollowMe); } m_leaderChoosen[team] = true; } } } -void BotManager::initRound (void) { +void BotManager::initRound () { // this is called at the start of each round m_roundEnded = false; // check team economics - for (int team = 0; team < MAX_TEAM_COUNT; team++) { + for (int team = 0; team < kGameTeamNum; ++team) { updateTeamEconomics (team); selectLeaders (team, true); @@ -1701,29 +1500,31 @@ void BotManager::initRound (void) { } reset (); - for (int i = 0; i < game.maxClients (); i++) { - auto bot = getBot (i); - - if (bot != nullptr) { - bot->newRound (); - } - util.getClient (i).radio = 0; + // notify all bots about new round arrived + for (const auto &bot : bots) { + bot->newRound (); } - waypoints.setBombPos (true); - waypoints.clearVisited (); - m_bombSayStatus = 0; + // reset current radio message for all client + for (auto &client : util.getClients ()) { + client.radio = 0; + } + + graph.setBombPos (true); + graph.clearVisited (); + + m_bombSayStatus = BombPlantedSay::ChatSay | BombPlantedSay::Chatter; m_timeBombPlanted = 0.0f; m_plantSearchUpdateTime = 0.0f; m_botsCanPause = false; resetFilters (); - waypoints.updateGlobalExperience (); // update experience data on round start + graph.updateGlobalPractice (); // 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; + m_timeRoundStart = game.timebase () + mp_freezetime.float_ (); + m_timeRoundMid = m_timeRoundStart + mp_roundtime.float_ () * 60.0f * 0.5f; + m_timeRoundEnd = m_timeRoundStart + mp_roundtime.float_ () * 60.0f; } void BotManager::setBombPlanted (bool isPlanted) { @@ -1733,244 +1534,160 @@ void BotManager::setBombPlanted (bool isPlanted) { m_bombPlanted = isPlanted; } -void Config::load (bool onlyMain) { - static bool setMemoryPointers = true; +BotConfig::BotConfig () { + m_chat.ensure (Chat::Count); + m_chatter.ensure (Chatter::Count); + m_weaponProps.ensure (kMaxWeapons); +} - if (setMemoryPointers) { - MemoryLoader::ref ().setup (engfuncs.pfnLoadFileForMe, engfuncs.pfnFreeFile); - setMemoryPointers = true; +void BotConfig::loadConfigs () { + setupMemoryFiles (); + + loadNamesConfig (); + loadChatConfig (); + loadChatterConfig (); + loadWeaponsConfig (); + loadLanguageConfig (); + loadLogosConfig (); + loadAvatarsConfig (); +} + +void BotConfig::loadMainConfig () { + if (game.is (GameFlags::Legacy) && !game.is (GameFlags::Xash3D)) { + util.setNeedForWelcome (true); } + setupMemoryFiles (); - auto isCommentLine = [] (const String &line) { - char ch = line.at (0); - return ch == '#' || ch == '/' || ch == '\r' || ch == ';' || ch == 0 || ch == ' '; + static bool firstLoad = true; + + auto needsToIgnoreVar = [] (StringArray &list, const char *needle) { + for (const auto &var : list) { + if (var == needle) { + return true; + } + } + return false; }; - MemFile fp; - - String lineBuffer; - lineBuffer.reserve (512); + String line; + MemFile file; // 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; + if (util.openConfig ("yapb.cfg", "YaPB main config file is not found.", &file, false)) { + while (file.getLine (line)) { + line.trim (); - auto needsToIgnoreVar = [] (StringArray &list, const char *needle) { - for (auto &var : list) { - if (var == needle) { - return true; - } + if (isCommentLine (line)) { + continue; } - 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 (firstLoad) { + game.serverCommand (line.chars ()); + continue; + } + auto keyval = line.split (" "); - if (keyval.length () > 1) { - auto ignore = String (yb_ignore_cvars_on_changelevel.str ()).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); + 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 (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); + 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); - } + // ensure cvar will have old value + engfuncs.pfnCvar_DirectSet (cvar, cvar->string); } else { - game.execCmd (lineBuffer.chars ()); + engfuncs.pfnCvar_DirectSet (cvar, value); } } - } - 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 (); + game.serverCommand (line.chars ()); } } - }; + } + file.close (); + } + firstLoad = false; - 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; - } + // android is abit hard to play, lower the difficulty by default + if (plat.isAndroid && yb_difficulty.int_ () > 2) { + yb_difficulty.set (2); + } + return; +} - for (size_t i = 0; i < max; i++) { - to[i] = data[i].toInt32 (); - } - }; +void BotConfig::loadNamesConfig () { + setupMemoryFiles (); - while (fp.getLine (lineBuffer)) { - lineBuffer.trim (); + String line; + MemFile file; - if (isCommentLine (lineBuffer)) { + // naming initialization + if (util.openConfig ("names.cfg", "Name configuration file not found.", &file, true)) { + m_botNames.clear (); + + while (file.getLine (line)) { + line.trim (); + + if (isCommentLine (line)) { continue; } - auto pair = lineBuffer.split ("="); + // max botname is 32 characters + if (line.length () > 32) { + line[32] = '\0'; + } + m_botNames.emplace (line, -1); + } + file.close (); + } +} + +void BotConfig::loadWeaponsConfig () { + setupMemoryFiles (); + + auto addWeaponEntries = [] (Array &weapons, size_t max, bool as, const String &name, const StringArray &data) { + if (data.length () != max) { + logger.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].int_ (); + } + else { + weapons[i].teamStandard = data[i].int_ (); + } + } + }; + + auto addIntEntries = [] (int *to, size_t max, const String &name, const StringArray &data) { + if (data.length () != max) { + logger.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].int_ (); + } + }; + String line; + MemFile file; + + // weapon data initialization + if (util.openConfig ("general.cfg", "General configuration file not found. Loading defaults", &file)) { + while (file.getLine (line)) { + line.trim (); + + if (isCommentLine (line)) { + continue; + } + auto pair = line.split ("="); if (pair.length () != 2) { continue; @@ -1981,128 +1698,140 @@ void Config::load (bool onlyMain) { } auto splitted = pair[1].split (","); - if (pair[0] == "MapStandard") { - addWeaponEntries (m_weapons, NUM_WEAPONS, false, pair[0], splitted); + if (pair[0].startsWith ("MapStandard")) { + addWeaponEntries (m_weapons, kNumWeapons, false, pair[0], splitted); } - else if (pair[0] == "MapAS") { - addWeaponEntries (m_weapons, NUM_WEAPONS, true, pair[0], splitted); + else if (pair[0].startsWith ("MapAS")) { + addWeaponEntries (m_weapons, kNumWeapons, true, pair[0], splitted); } - else if (pair[0] == "GrenadePercent") { + else if (pair[0].startsWith ("GrenadePercent")) { addIntEntries (m_grenadeBuyPrecent, 3, pair[0], splitted); } - else if (pair[0] == "Economics") { + else if (pair[0].startsWith ("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].startsWith ("PersonalityNormal")) { + addIntEntries (m_normalWeaponPrefs, kNumWeapons, pair[0], splitted); } - else if (pair[0] == "PersonalityRusher") { - addIntEntries (m_rusherWeaponPrefs, NUM_WEAPONS, pair[0], splitted); + else if (pair[0].startsWith ("PersonalityRusher")) { + addIntEntries (m_rusherWeaponPrefs, kNumWeapons, pair[0], splitted); } - else if (pair[0] == "PersonalityCareful") { - addIntEntries (m_carefulWeaponPrefs, NUM_WEAPONS, pair[0], splitted); + else if (pair[0].startsWith ("PersonalityCareful")) { + addIntEntries (m_carefulWeaponPrefs, kNumWeapons, pair[0], splitted); } } - fp.close (); + file.close (); } + // 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); +} + +void BotConfig::loadChatterConfig () { + setupMemoryFiles (); + + String line; + MemFile file; + // 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)) { + if (game.is (GameFlags::HasBotVoice) && yb_radio_mode.int_ () == 2 && util.openConfig ("chatter.cfg", "Couldn't open chatter system configuration", &file)) { 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 }, + { "Radio_CoverMe", Radio::CoverMe, kMaxChatterRepeatInteval }, + { "Radio_YouTakePoint", Radio::YouTakeThePoint, kMaxChatterRepeatInteval }, + { "Radio_HoldPosition", Radio::HoldThisPosition, 10.0f }, + { "Radio_RegroupTeam", Radio::RegroupTeam, 10.0f }, + { "Radio_FollowMe", Radio::FollowMe, 15.0f }, + { "Radio_TakingFire", Radio::TakingFireNeedAssistance, 5.0f }, + { "Radio_GoGoGo", Radio::GoGoGo, kMaxChatterRepeatInteval }, + { "Radio_Fallback", Radio::TeamFallback, kMaxChatterRepeatInteval }, + { "Radio_StickTogether", Radio::StickTogetherTeam, kMaxChatterRepeatInteval }, + { "Radio_GetInPosition", Radio::GetInPositionAndWaitForGo, kMaxChatterRepeatInteval }, + { "Radio_StormTheFront", Radio::StormTheFront, kMaxChatterRepeatInteval }, + { "Radio_ReportTeam", Radio::ReportInTeam, kMaxChatterRepeatInteval }, + { "Radio_Affirmative", Radio::RogerThat, kMaxChatterRepeatInteval }, + { "Radio_EnemySpotted", Radio::EnemySpotted, 4.0f }, + { "Radio_NeedBackup", Radio::NeedBackup, kMaxChatterRepeatInteval }, + { "Radio_SectorClear", Radio::SectorClear, 10.0f }, + { "Radio_InPosition", Radio::ImInPosition, 10.0f }, + { "Radio_ReportingIn", Radio::ReportingIn, kMaxChatterRepeatInteval }, + { "Radio_ShesGonnaBlow", Radio::ShesGonnaBlow, kMaxChatterRepeatInteval }, + { "Radio_Negative", Radio::Negative, kMaxChatterRepeatInteval }, + { "Radio_EnemyDown", Radio::EnemyDown, 10.0f }, + { "Chatter_DiePain", Chatter::DiePain, kMaxChatterRepeatInteval }, + { "Chatter_GoingToPlantBomb", Chatter::GoingToPlantBomb, 5.0f }, + { "Chatter_GoingToGuardVIPSafety", Chatter::GoingToGuardVIPSafety, kMaxChatterRepeatInteval }, + { "Chatter_RescuingHostages", Chatter::RescuingHostages, kMaxChatterRepeatInteval }, + { "Chatter_TeamKill", Chatter::FriendlyFire, kMaxChatterRepeatInteval }, + { "Chatter_GuardingVipSafety", Chatter::GuardingVIPSafety, kMaxChatterRepeatInteval }, + { "Chatter_PlantingC4", Chatter::PlantingBomb, 10.0f }, + { "Chatter_InCombat", Chatter::InCombat, kMaxChatterRepeatInteval }, + { "Chatter_SeeksEnemy", Chatter::SeekingEnemies, kMaxChatterRepeatInteval }, + { "Chatter_Nothing", Chatter::Nothing, kMaxChatterRepeatInteval }, + { "Chatter_EnemyDown", Chatter::EnemyDown, 10.0f }, + { "Chatter_UseHostage", Chatter::UsingHostages, kMaxChatterRepeatInteval }, + { "Chatter_WonTheRound", Chatter::WonTheRound, kMaxChatterRepeatInteval }, + { "Chatter_QuicklyWonTheRound", Chatter::QuickWonRound, kMaxChatterRepeatInteval }, + { "Chatter_NoEnemiesLeft", Chatter::NoEnemiesLeft, kMaxChatterRepeatInteval }, + { "Chatter_FoundBombPlace", Chatter::FoundC4Plant, 15.0f }, + { "Chatter_WhereIsTheBomb", Chatter::WhereIsTheC4, kMaxChatterRepeatInteval }, + { "Chatter_DefendingBombSite", Chatter::DefendingBombsite, kMaxChatterRepeatInteval }, + { "Chatter_BarelyDefused", Chatter::BarelyDefused, kMaxChatterRepeatInteval }, + { "Chatter_NiceshotCommander", Chatter::NiceShotCommander, kMaxChatterRepeatInteval }, + { "Chatter_ReportingIn", Chatter::ReportingIn, 10.0f }, + { "Chatter_SpotTheBomber", Chatter::SpotTheBomber, 4.3f }, + { "Chatter_VIPSpotted", Chatter::VIPSpotted, 5.3f }, + { "Chatter_FriendlyFire", Chatter::FriendlyFire, 2.1f }, + { "Chatter_GotBlinded", Chatter::Blind, 12.0f }, + { "Chatter_GuardDroppedC4", Chatter::GuardingDroppedC4, 3.0f }, + { "Chatter_DefusingC4", Chatter::DefusingBomb, 3.0f }, + { "Chatter_FoundC4", Chatter::FoundC4, 5.5f }, + { "Chatter_ScaredEmotion", Chatter::ScaredEmotion, 6.1f }, + { "Chatter_HeardEnemy", Chatter::ScaredEmotion, 12.8f }, + { "Chatter_SniperWarning", Chatter::SniperWarning, 14.3f }, + { "Chatter_SniperKilled", Chatter::SniperKilled, 12.1f }, + { "Chatter_OneEnemyLeft", Chatter::OneEnemyLeft, 12.5f }, + { "Chatter_TwoEnemiesLeft", Chatter::TwoEnemiesLeft, 12.5f }, + { "Chatter_ThreeEnemiesLeft", Chatter::ThreeEnemiesLeft, 12.5f }, + { "Chatter_NiceshotPall", Chatter::NiceShotPall, 2.0f }, + { "Chatter_GoingToGuardHostages", Chatter::GoingToGuardHostages, 3.0f }, + { "Chatter_GoingToGuardDoppedBomb", Chatter::GoingToGuardDroppedC4, 6.0f }, + { "Chatter_OnMyWay", Chatter::OnMyWay, 1.5f }, + { "Chatter_LeadOnSir", Chatter::LeadOnSir, 5.0f }, + { "Chatter_Pinned_Down", Chatter::PinnedDown, 5.0f }, + { "Chatter_GottaFindTheBomb", Chatter::GottaFindC4, 3.0f }, + { "Chatter_You_Heard_The_Man", Chatter::YouHeardTheMan, 3.0f }, + { "Chatter_Lost_The_Commander", Chatter::LostCommander, 4.5f }, + { "Chatter_NewRound", Chatter::NewRound, 3.5f }, + { "Chatter_CoverMe", Chatter::CoverMe, 3.5f }, + { "Chatter_BehindSmoke", Chatter::BehindSmoke, 3.5f }, + { "Chatter_BombSiteSecured", Chatter::BombsiteSecured, 3.5f }, + { "Chatter_GoingToCamp", Chatter::GoingToCamp, 30.0f }, + { "Chatter_Camp", Chatter::Camping, 10.0f }, }; - while (fp.getLine (lineBuffer)) { - lineBuffer.trim (); + while (file.getLine (line)) { + line.trim (); - if (isCommentLine (lineBuffer)) { + if (isCommentLine (line)) { continue; } extern ConVar yb_chatter_path; - if (lineBuffer.substr (0, 11) == "RewritePath") { - yb_chatter_path.set (lineBuffer.substr (11).trim ().chars ()); + if (line.startsWith ("RewritePath")) { + yb_chatter_path.set (line.substr (11).trim ().chars ()); } - else if (lineBuffer.substr (0, 5) == "Event") { - auto items = lineBuffer.substr (5).split ("="); + else if (line.startsWith ("Event")) { + auto items = line.substr (5).split ("="); if (items.length () != 2) { - util.logEntry (true, LL_ERROR, "Error in chatter config file syntax... Please correct all errors."); + logger.error ("Error in chatter config file syntax... Please correct all errors."); continue; } @@ -2121,7 +1850,7 @@ void Config::load (bool onlyMain) { float duration = game.getWaveLen (sound.chars ()); if (duration > 0.0f) { - m_chatter[event.code].push ({ sound, event.repeat, duration }); + m_chatter[event.code].emplace (cr::move (sound), event.repeat, duration); } } sounds.clear (); @@ -2129,88 +1858,230 @@ void Config::load (bool onlyMain) { } } } - fp.close (); + file.close (); } else { - yb_communication_type.set (1); - util.logEntry (true, LL_DEFAULT, "Chatter Communication disabled."); + yb_radio_mode.set (1); + logger.message ("Bots 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 +void BotConfig::loadChatConfig () { + setupMemoryFiles (); + + String line; + MemFile file; + + // chat config initialization + if (util.openConfig ("chat.cfg", "Chat file not found.", &file, true)) { + StringArray *chat = nullptr; + + StringArray keywords {}; + StringArray replies {}; + + // clear all the stuff before loading new one + for (auto &item : m_chat) { + item.clear (); } - enum Lang { LANG_ORIGINAL, LANG_TRANSLATED, LANG_UNDEFINED } langState = static_cast (LANG_UNDEFINED); + m_replies.clear (); - String temp; - Pair lang; + while (file.getLine (line)) { + line.trim (); - while (fp.getLine (lineBuffer)) { - lineBuffer.trim (); - - if (isCommentLine (lineBuffer)) { + if (isCommentLine (line)) { 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); - } + if (line.startsWith ("[KILLED]")) { + chat = &m_chat[Chat::Kill]; + continue; + } + else if (line.startsWith ("[BOMBPLANT]")) { + chat = &m_chat[Chat::Kill]; + continue; + } + else if (line.startsWith ("[DEADCHAT]")) { + chat = &m_chat[Chat::Dead]; + continue; + } + else if (line.startsWith ("[REPLIES]")) { + chat = nullptr; + continue; + } + else if (line.startsWith ("[UNKNOWN]")) { + chat = &m_chat[Chat::NoKeyword]; + continue; + } + else if (line.startsWith ("[TEAMATTACK]")) { + chat = &m_chat[Chat::TeamAttack]; + continue; + } + else if (line.startsWith ("[WELCOME]")) { + chat = &m_chat[Chat::Hello]; + continue; + } + else if (line.startsWith ("[TEAMKILL]")) { + chat = &m_chat[Chat::TeamKill]; + continue; } - else if (lineBuffer == "[TRANSLATED]") { - lang.first = cr::move (temp); - lang.first.trim (); - - langState = LANG_TRANSLATED; + if (chat != nullptr) { + chat->push (line); } else { - switch (langState) { - case LANG_ORIGINAL: - temp += lineBuffer; - break; + if (line.startsWith ("@KEY")) { + if (!keywords.empty () && !replies.empty ()) { + m_replies.emplace (keywords, replies); - case LANG_TRANSLATED: - temp += lineBuffer; - break; + keywords.clear (); + replies.clear (); + } - case LANG_UNDEFINED: - break; + keywords.clear (); + keywords = cr::move (line.substr (4).split (",")); + + for (auto &keyword : keywords) { + keyword.trim ().trim ("\""); + } + } + else if (!keywords.empty () && !line.empty ()) { + replies.push (line); } } } - 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); + // shuffle chat a bit + for (auto &item : m_chat) { + item.shuffle (); + item.shuffle (); + } + file.close (); + } + else { + yb_chat.set (0); + } } -BotName *Config::pickBotName (void) { +void BotConfig::loadLanguageConfig () { + setupMemoryFiles (); + + if (game.isDedicated () || game.is (GameFlags::Legacy)) { + + if (game.is (GameFlags::Legacy)) { + logger.message ("Bots multilingual system disabled, due to your Counter-Strike Version!"); + } + return; // dedicated server will use only english translation + } + String line; + MemFile file; + + // localizer inititalization + if (util.openConfig ("lang.cfg", "Specified language not found.", &file, true)) { + String temp; + Twin lang; + + // clear all the translations before new load + game.clearTranslation (); + + while (file.getLine (line)) { + if (isCommentLine (line)) { + continue; + } + + if (line.startsWith ("[ORIGINAL]")) { + if (!temp.empty ()) { + lang.second = cr::move (temp); + } + + if (!lang.second.empty () && !lang.first.empty ()) { + game.addTranslation (lang.first.trim (), lang.second.trim ()); + } + } + else if (line.startsWith ("[TRANSLATED]") && !temp.empty ()) { + lang.first = cr::move (temp); + } + else { + temp += line; + } + } + file.close (); + } + else if (strcmp (yb_language.str (), "en") != 0) { + logger.error ("Couldn't load language configuration"); + } +} + +void BotConfig::loadAvatarsConfig () { + setupMemoryFiles (); + + if (game.is (GameFlags::Legacy)) { + return; + } + + String line; + MemFile file; + + // avatars inititalization + if (util.openConfig ("avatars.cfg", "Avatars config file not found. Avatars will not display.", &file)) { + m_avatars.clear (); + + while (file.getLine (line)) { + if (isCommentLine (line)) { + continue; + } + m_avatars.push (cr::move (line.trim ())); + } + } +} + +void BotConfig::loadLogosConfig () { + setupMemoryFiles (); + + String line; + MemFile file; + + // logos inititalization + if (util.openConfig ("logos.cfg", "Logos config file not found. Loading defaults.", &file)) { + m_logos.clear (); + + while (file.getLine (line)) { + if (isCommentLine (line)) { + continue; + } + m_logos.push (cr::move (line.trim ())); + } + } + else { + m_logos = cr::move (String ("{biohaz;{graf003;{graf004;{graf005;{lambda06;{target;{hand1;{spit2;{bloodhand6;{foot_l;{foot_r").split (";")); + } +} + +void BotConfig::setupMemoryFiles () { + static bool setMemoryPointers = true; + + auto wrapLoadFile= [] (const char *filename, int *length) { + return engfuncs.pfnLoadFileForMe (filename, length); + }; + + auto wrapFreeFile = [] (void *buffer) { + return engfuncs.pfnFreeFile (buffer); + }; + + if (setMemoryPointers) { + MemFileStorage::get ().initizalize (wrapLoadFile, wrapFreeFile); + setMemoryPointers = true; + } +} + +BotName *BotConfig::pickBotName () { if (m_botNames.empty ()) { return nullptr; } - for (int i = 0; i < MAX_ENGINE_PLAYERS * 4; i++) { + for (size_t i = 0; i < m_botNames.length () * 2; ++i) { auto botName = &m_botNames.random (); - if (botName->name.length () < 3 || botName->usedBy != 0) { + if (botName->name.length () < 3 || botName->usedBy != -1) { continue; } return botName; @@ -2218,65 +2089,63 @@ BotName *Config::pickBotName (void) { return nullptr; } -void Config::clearUsedName (Bot *bot) { +void BotConfig::clearUsedName (Bot *bot) { for (auto &name : m_botNames) { if (name.usedBy == bot->index ()) { - name.usedBy = 0; + name.usedBy = -1; break; } } } -void Config::initWeapons (void) { - m_weapons.reserve (NUM_WEAPONS + 1); - +void BotConfig::initWeapons () { // 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 }); + m_weapons.emplace (Weapon::Knife, "weapon_knife", "knife.mdl", 0, 0, -1, -1, 0, 0, 0, 0, 0, 0, true ); + m_weapons.emplace (Weapon::USP, "weapon_usp", "usp.mdl", 500, 1, -1, -1, 1, 1, 2, 2, 0, 12, false ); + m_weapons.emplace (Weapon::Glock18, "weapon_glock18", "glock18.mdl", 400, 1, -1, -1, 1, 2, 1, 1, 0, 20, false ); + m_weapons.emplace (Weapon::Deagle, "weapon_deagle", "deagle.mdl", 650, 1, 2, 2, 1, 3, 4, 4, 2, 7, false ); + m_weapons.emplace (Weapon::P228, "weapon_p228", "p228.mdl", 600, 1, 2, 2, 1, 4, 3, 3, 0, 13, false ); + m_weapons.emplace (Weapon::Elite, "weapon_elite", "elite.mdl", 800, 1, 0, 0, 1, 5, 5, 5, 0, 30, false ); + m_weapons.emplace (Weapon::FiveSeven, "weapon_fiveseven", "fiveseven.mdl", 750, 1, 1, 1, 1, 6, 5, 5, 0, 20, false ); + m_weapons.emplace (Weapon::M3, "weapon_m3", "m3.mdl", 1700, 1, 2, -1, 2, 1, 1, 1, 0, 8, false ); + m_weapons.emplace (Weapon::XM1014, "weapon_xm1014", "xm1014.mdl", 3000, 1, 2, -1, 2, 2, 2, 2, 0, 7, false ); + m_weapons.emplace (Weapon::MP5, "weapon_mp5navy", "mp5.mdl", 1500, 1, 2, 1, 3, 1, 2, 2, 0, 30, true ); + m_weapons.emplace (Weapon::TMP, "weapon_tmp", "tmp.mdl", 1250, 1, 1, 1, 3, 2, 1, 1, 0, 30, true ); + m_weapons.emplace (Weapon::P90, "weapon_p90", "p90.mdl", 2350, 1, 2, 1, 3, 3, 4, 4, 0, 50, true ); + m_weapons.emplace (Weapon::MAC10, "weapon_mac10", "mac10.mdl", 1400, 1, 0, 0, 3, 4, 1, 1, 0, 30, true ); + m_weapons.emplace (Weapon::UMP45, "weapon_ump45", "ump45.mdl", 1700, 1, 2, 2, 3, 5, 3, 3, 0, 25, true ); + m_weapons.emplace (Weapon::AK47, "weapon_ak47", "ak47.mdl", 2500, 1, 0, 0, 4, 1, 2, 2, 2, 30, true ); + m_weapons.emplace (Weapon::SG552, "weapon_sg552", "sg552.mdl", 3500, 1, 0, -1, 4, 2, 4, 4, 2, 30, true ); + m_weapons.emplace (Weapon::M4A1, "weapon_m4a1", "m4a1.mdl", 3100, 1, 1, 1, 4, 3, 3, 3, 2, 30, true ); + m_weapons.emplace (Weapon::Galil, "weapon_galil", "galil.mdl", 2000, 1, 0, 0, 4, -1, 1, 1, 2, 35, true ); + m_weapons.emplace (Weapon::Famas, "weapon_famas", "famas.mdl", 2250, 1, 1, 1, 4, -1, 1, 1, 2, 25, true ); + m_weapons.emplace (Weapon::AUG, "weapon_aug", "aug.mdl", 3500, 1, 1, 1, 4, 4, 4, 4, 2, 30, true ); + m_weapons.emplace (Weapon::Scout, "weapon_scout", "scout.mdl", 2750, 1, 2, 0, 4, 5, 3, 2, 3, 10, false ); + m_weapons.emplace (Weapon::AWP, "weapon_awp", "awp.mdl", 4750, 1, 2, 0, 4, 6, 5, 6, 3, 10, false ); + m_weapons.emplace (Weapon::G3SG1, "weapon_g3sg1", "g3sg1.mdl", 5000, 1, 0, 2, 4, 7, 6, 6, 3, 20, false ); + m_weapons.emplace (Weapon::SG550, "weapon_sg550", "sg550.mdl", 4200, 1, 1, 1, 4, 8, 5, 5, 3, 30, false ); + m_weapons.emplace (Weapon::M249, "weapon_m249", "m249.mdl", 5750, 1, 2, 1, 5, 1, 1, 1, 2, 100, true ); + m_weapons.emplace (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 }); + m_weapons.emplace (0, "", "", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, false ); } -void Config::adjustWeaponPrices (void) { +void BotConfig::adjustWeaponPrices () { // elite price is 1000$ on older versions of cs... - if (!(game.is (GAME_LEGACY))) { + if (!(game.is (GameFlags::Legacy))) { return; } for (auto &weapon : m_weapons) { - if (weapon.id == WEAPON_ELITE) { + if (weapon.id == Weapon::Elite) { weapon.price = 1000; break; } } } -WeaponInfo &Config::findWeaponById (const int id) { +WeaponInfo &BotConfig::findWeaponById (const int id) { for (auto &weapon : m_weapons) { if (weapon.id == id) { return weapon; diff --git a/source/navigate.cpp b/source/navigate.cpp index 1829e8b..26ba773 100644 --- a/source/navigate.cpp +++ b/source/navigate.cpp @@ -12,17 +12,17 @@ ConVar yb_whose_your_daddy ("yb_whose_your_daddy", "0"); ConVar yb_debug_heuristic_type ("yb_debug_heuristic_type", "4"); -int Bot::searchGoal (void) { +int Bot::findBestGoal () { - // chooses a destination (goal) waypoint for a bot - if (!bots.isBombPlanted () && m_team == TEAM_TERRORIST && game.mapIs (MAP_DE)) { + // chooses a destination (goal) node for a bot + if (!bots.isBombPlanted () && m_team == Team::Terrorist && game.mapIs (MapFlags::Demolition)) { edict_t *pent = nullptr; while (!game.isNullEntity (pent = engfuncs.pfnFindEntityByString (pent, "classname", "weaponbox"))) { if (strcmp (STRING (pent->v.model), "models/w_backpack.mdl") == 0) { - int index = waypoints.getNearest (game.getAbsPos (pent)); + int index = graph.getNearest (game.getAbsPos (pent)); - if (waypoints.exists (index)) { + if (graph.exists (index)) { return m_loosedBombWptIndex = index; } break; @@ -31,7 +31,7 @@ int Bot::searchGoal (void) { // forcing terrorist bot to not move to another bomb spot if (m_inBombZone && !m_hasProgressBar && m_hasC4) { - return waypoints.getNearest (pev->origin, 768.0f, FLAG_GOAL); + return graph.getNearest (pev->origin, 768.0f, NodeFlag::Goal); } } int tactic = 0; @@ -50,41 +50,41 @@ int Bot::searchGoal (void) { IntArray *defensiveWpts = nullptr; switch (m_team) { - case TEAM_TERRORIST: - offensiveWpts = &waypoints.m_ctPoints; - defensiveWpts = &waypoints.m_terrorPoints; + case Team::Terrorist: + offensiveWpts = &graph.m_ctPoints; + defensiveWpts = &graph.m_terrorPoints; break; - case TEAM_COUNTER: + case Team::CT: default: - offensiveWpts = &waypoints.m_terrorPoints; - defensiveWpts = &waypoints.m_ctPoints; + offensiveWpts = &graph.m_terrorPoints; + defensiveWpts = &graph.m_ctPoints; break; } // terrorist carrying the C4? if (m_hasC4 || m_isVIP) { tactic = 3; - return getGoalProcess (tactic, defensiveWpts, offensiveWpts); + return findGoalPost (tactic, defensiveWpts, offensiveWpts); } - else if (m_team == TEAM_COUNTER && hasHostage ()) { + else if (m_team == Team::CT && hasHostage ()) { tactic = 2; - offensiveWpts = &waypoints.m_rescuePoints; + offensiveWpts = &graph.m_rescuePoints; - return getGoalProcess (tactic, defensiveWpts, offensiveWpts); + return findGoalPost (tactic, defensiveWpts, offensiveWpts); } offensive = m_agressionLevel * 100.0f; defensive = m_fearLevel * 100.0f; - if (game.mapIs (MAP_AS | MAP_CS)) { - if (m_team == TEAM_TERRORIST) { + if (game.mapIs (MapFlags::Assassination | MapFlags::HostageRescue)) { + if (m_team == Team::Terrorist) { defensive += 25.0f; offensive -= 25.0f; } - else if (m_team == TEAM_COUNTER) { + else if (m_team == Team::CT) { // on hostage maps force more bots to save hostages - if (game.mapIs (MAP_CS)) { + if (game.mapIs (MapFlags::HostageRescue)) { defensive -= 25.0f - m_difficulty * 0.5f; offensive += 25.0f + m_difficulty * 5.0f; } @@ -94,33 +94,33 @@ int Bot::searchGoal (void) { } } } - else if (game.mapIs (MAP_DE) && m_team == TEAM_COUNTER) { - if (bots.isBombPlanted () && taskId () != TASK_ESCAPEFROMBOMB && !waypoints.getBombPos ().empty ()) { + else if (game.mapIs (MapFlags::Demolition) && m_team == Team::CT) { + if (bots.isBombPlanted () && getCurrentTaskId () != Task::EscapeFromBomb && !graph.getBombPos ().empty ()) { - if (bots.hasBombSay (BSS_NEED_TO_FIND_CHAT)) { - pushChatMessage (CHAT_BOMBPLANT); - bots.clearBombSay (BSS_NEED_TO_FIND_CHAT); + if (bots.hasBombSay (BombPlantedSay::ChatSay)) { + pushChatMessage (Chat::Plant); + bots.clearBombSay (BombPlantedSay::ChatSay); } - return m_chosenGoalIndex = getBombPoint (); + return m_chosenGoalIndex = findBombNode (); } defensive += 25.0f + m_difficulty * 4.0f; offensive -= 25.0f - m_difficulty * 0.5f; - if (m_personality != PERSONALITY_RUSHER) { + if (m_personality != Personality::Rusher) { defensive += 10.0f; } } - else if (game.mapIs (MAP_DE) && m_team == TEAM_TERRORIST && bots.getRoundStartTime () + 10.0f < game.timebase ()) { + else if (game.mapIs (MapFlags::Demolition) && m_team == Team::Terrorist && bots.getRoundStartTime () + 10.0f < game.timebase ()) { // send some terrorists to guard planted bomb - if (!m_defendedBomb && bots.isBombPlanted () && taskId () != TASK_ESCAPEFROMBOMB && getBombTimeleft () >= 15.0) { - return m_chosenGoalIndex = getDefendPoint (waypoints.getBombPos ()); + if (!m_defendedBomb && bots.isBombPlanted () && getCurrentTaskId () != Task::EscapeFromBomb && getBombTimeleft () >= 15.0) { + return m_chosenGoalIndex = findDefendNode (graph.getBombPos ()); } } - goalDesire = rng.getFloat (0.0f, 100.0f) + offensive; - forwardDesire = rng.getFloat (0.0f, 100.0f) + offensive; - campDesire = rng.getFloat (0.0f, 100.0f) + defensive; - backoffDesire = rng.getFloat (0.0f, 100.0f) + defensive; + goalDesire = rg.float_ (0.0f, 100.0f) + offensive; + forwardDesire = rg.float_ (0.0f, 100.0f) + offensive; + campDesire = rg.float_ (0.0f, 100.0f) + defensive; + backoffDesire = rg.float_ (0.0f, 100.0f) + defensive; if (!usesCampGun ()) { campDesire *= 0.5f; @@ -142,37 +142,37 @@ int Bot::searchGoal (void) { if (goalDesire > tacticChoice) { tactic = 3; } - return getGoalProcess (tactic, defensiveWpts, offensiveWpts); + return findGoalPost (tactic, defensiveWpts, offensiveWpts); } -int Bot::getGoalProcess (int tactic, IntArray *defensive, IntArray *offsensive) { - int goalChoices[4] = { INVALID_WAYPOINT_INDEX, INVALID_WAYPOINT_INDEX, INVALID_WAYPOINT_INDEX, INVALID_WAYPOINT_INDEX }; +int Bot::findGoalPost (int tactic, IntArray *defensive, IntArray *offsensive) { + int goalChoices[4] = { kInvalidNodeIndex, kInvalidNodeIndex, kInvalidNodeIndex, kInvalidNodeIndex }; if (tactic == 0 && !(*defensive).empty ()) { // careful goal postprocessGoals (*defensive, goalChoices); } - else if (tactic == 1 && !waypoints.m_campPoints.empty ()) // camp waypoint goal + else if (tactic == 1 && !graph.m_campPoints.empty ()) // camp node goal { // pickup sniper points if possible for sniping bots - if (!waypoints.m_sniperPoints.empty () && usesSniper ()) { - postprocessGoals (waypoints.m_sniperPoints, goalChoices); + if (!graph.m_sniperPoints.empty () && usesSniper ()) { + postprocessGoals (graph.m_sniperPoints, goalChoices); } else { - postprocessGoals (waypoints.m_campPoints, goalChoices); + postprocessGoals (graph.m_campPoints, goalChoices); } } else if (tactic == 2 && !(*offsensive).empty ()) { // offensive goal postprocessGoals (*offsensive, goalChoices); } - else if (tactic == 3 && !waypoints.m_goalPoints.empty ()) // map goal waypoint + else if (tactic == 3 && !graph.m_goalPoints.empty ()) // map goal node { // force bomber to select closest goal, if round-start goal was reset by something if (m_hasC4 && bots.getRoundStartTime () + 20.0f < game.timebase ()) { float minDist = 9999999.0f; int count = 0; - for (auto &point : waypoints.m_goalPoints) { - float distance = (waypoints[point].origin - pev->origin).lengthSq (); + for (auto &point : graph.m_goalPoints) { + float distance = (graph[point].origin - pev->origin).lengthSq (); if (distance > cr::square (1024.0f)) { continue; @@ -188,44 +188,39 @@ int Bot::getGoalProcess (int tactic, IntArray *defensive, IntArray *offsensive) } for (auto &choice : goalChoices) { - if (choice == INVALID_WAYPOINT_INDEX) { - choice = waypoints.m_goalPoints.random (); + if (choice == kInvalidNodeIndex) { + choice = graph.m_goalPoints.random (); } } } else { - postprocessGoals (waypoints.m_goalPoints, goalChoices); + postprocessGoals (graph.m_goalPoints, goalChoices); } } - if (!waypoints.exists (m_currentWaypointIndex)) { - m_currentWaypointIndex = changePointIndex (getNearestPoint ()); + if (!graph.exists (m_currentNodeIndex)) { + m_currentNodeIndex = changePointIndex (findNearestNode ()); } - if (goalChoices[0] == INVALID_WAYPOINT_INDEX) { - return m_chosenGoalIndex = rng.getInt (0, waypoints.length () - 1); + if (goalChoices[0] == kInvalidNodeIndex) { + return m_chosenGoalIndex = rg.int_ (0, graph.length () - 1); } bool sorting = false; do { sorting = false; - for (int i = 0; i < 3; i++) { - int testIndex = goalChoices[i + 1]; - - if (testIndex < 0) { + for (int i = 0; i < 3; ++i) { + if (goalChoices[i + 1] < 0) { break; } - 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; - + if (graph.getDangerValue (m_team, m_currentNodeIndex, goalChoices[i]) < graph.getDangerValue (m_team, m_currentNodeIndex, goalChoices[i + 1])) { + cr::swap (goalChoices[i + 1], goalChoices[i]); sorting = true; } } } while (sorting); - return m_chosenGoalIndex = goalChoices[0]; // return and store goal } @@ -234,7 +229,7 @@ void Bot::postprocessGoals (const IntArray &goals, int *result) { int searchCount = 0; - for (int index = 0; index < 4; index++) { + for (int index = 0; index < 4; ++index) { int rand = goals.random (); if (searchCount <= 8 && (m_prevGoalIndex == rand || ((result[0] == rand || result[1] == rand || result[2] == rand || result[3] == rand) && goals.length () > 4)) && !isOccupiedPoint (rand)) { @@ -248,27 +243,27 @@ void Bot::postprocessGoals (const IntArray &goals, int *result) { } } -bool Bot::hasActiveGoal (void) { +bool Bot::hasActiveGoal () { int goal = getTask ()->data; - if (goal == INVALID_WAYPOINT_INDEX) { // not decided about a goal + if (goal == kInvalidNodeIndex) { // not decided about a goal return false; } - else if (goal == m_currentWaypointIndex) { // no nodes needed + else if (goal == m_currentNodeIndex) { // no nodes needed return true; } - else if (m_path.empty ()) { // no path calculated + else if (m_pathWalk.empty ()) { // no path calculated return false; } - return goal == m_path.back (); // got path - check if still valid + return goal == m_pathWalk.last (); // got path - check if still valid } -void Bot::resetCollision (void) { +void Bot::resetCollision () { m_collideTime = 0.0f; m_probeTime = 0.0f; m_collisionProbeBits = 0; - m_collisionState = COLLISION_NOTDECICED; + m_collisionState = CollisionState::Undecided; m_collStateIndex = 0; for (auto &collideMove : m_collideMoves) { @@ -276,7 +271,7 @@ void Bot::resetCollision (void) { } } -void Bot::ignoreCollision (void) { +void Bot::ignoreCollision () { resetCollision (); m_prevTime = game.timebase () + 1.2f; @@ -288,21 +283,21 @@ void Bot::ignoreCollision (void) { } void Bot::avoidIncomingPlayers (edict_t *touch) { - auto task = taskId (); + auto task = getCurrentTaskId (); - if (task == TASK_PLANTBOMB || task == TASK_DEFUSEBOMB || task == TASK_CAMP || m_moveSpeed <= 100.0f) { + if (task == Task::PlantBomb || task == Task::DefuseBomb || task == Task::Camp || m_moveSpeed <= 100.0f) { return; } - int ownId = game.indexOfEntity (ent ()); - int otherId = game.indexOfEntity (touch); + int ownId = entindex (); + int otherId = game.indexOfPlayer (touch); if (ownId < otherId) { return; } if (m_avoid) { - int currentId = game.indexOfEntity (m_avoid); + int currentId = game.indexOfPlayer (m_avoid); if (currentId < otherId) { return; @@ -316,7 +311,6 @@ bool Bot::doPlayerAvoidance (const Vector &normal) { // avoid collision entity, got it form official csbot if (m_avoidTime > game.timebase () && util.isAlive (m_avoid)) { - Vector dir (cr::cosf (pev->v_angle.y), cr::sinf (pev->v_angle.y), 0.0f); Vector lat (-dir.y, dir.x, 0.0f); Vector to = Vector (m_avoid->v.origin.x - pev->origin.x, m_avoid->v.origin.y - pev->origin.y, 0.0f).normalize (); @@ -324,7 +318,7 @@ bool Bot::doPlayerAvoidance (const Vector &normal) { float toProj = to.x * dir.x + to.y * dir.y; float latProj = to.x * lat.x + to.y * lat.y; - const float c = 0.5f; + constexpr float c = 0.5f; if (toProj > c) { m_moveSpeed = -pev->maxspeed; @@ -355,11 +349,15 @@ bool Bot::doPlayerAvoidance (const Vector &normal) { void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { m_isStuck = false; + + if (doPlayerAvoidance (dirNormal) || m_avoidTime > game.timebase ()) { + return; + } TraceResult tr; // Standing still, no need to check? // FIXME: doesn't care for ladder movement (handled separately) should be included in some way - if ((m_moveSpeed >= 10.0f || m_strafeSpeed >= 10.0f) && m_lastCollTime < game.timebase () && m_seeEnemyTime + 0.8f < game.timebase () && taskId () != TASK_ATTACK) { + if ((m_moveSpeed >= 10.0f || m_strafeSpeed >= 10.0f) && m_lastCollTime < game.timebase () && m_seeEnemyTime + 0.8f < game.timebase () && getCurrentTaskId () != Task::Attack) { // didn't we move enough previously? if (movedDistance < 2.0f && m_prevSpeed >= 20.0f) { @@ -393,51 +391,51 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { } else { // remember to keep pressing duck if it was necessary ago - if (m_collideMoves[m_collStateIndex] == COLLISION_DUCK && (isOnFloor () || isInWater ())) { + if (m_collideMoves[m_collStateIndex] == CollisionState::Duck && (isOnFloor () || isInWater ())) { pev->button |= IN_DUCK; } } return; } - // bot is stuck! + // bot is stuck! Vector src; Vector dst; // not yet decided what to do? - if (m_collisionState == COLLISION_NOTDECICED) { + if (m_collisionState == CollisionState::Undecided) { int bits = 0; if (isOnLadder ()) { - bits |= PROBE_STRAFE; + bits |= CollisionProbe::Strafe; } else if (isInWater ()) { - bits |= (PROBE_JUMP | PROBE_STRAFE); + bits |= (CollisionProbe::Jump | CollisionProbe::Strafe); } else { - bits |= (PROBE_STRAFE | (m_jumpStateTimer < game.timebase () ? PROBE_JUMP : 0)); + bits |= (CollisionProbe::Strafe | (m_jumpStateTimer < game.timebase () ? CollisionProbe::Jump : 0)); } // collision check allowed if not flying through the air if (isOnFloor () || isOnLadder () || isInWater ()) { - int state[MAX_COLLIDE_MOVES * 2 + 1]; + int state[kMaxCollideMoves * 2 + 1]; int i = 0; // first 4 entries hold the possible collision states - state[i++] = COLLISION_STRAFELEFT; - state[i++] = COLLISION_STRAFERIGHT; - state[i++] = COLLISION_JUMP; - state[i++] = COLLISION_DUCK; + state[i++] = CollisionState::StrafeLeft; + state[i++] = CollisionState::StrafeRight; + state[i++] = CollisionState::Jump; + state[i++] = CollisionState::Duck; - if (bits & PROBE_STRAFE) { + if (bits & CollisionProbe::Strafe) { state[i] = 0; state[i + 1] = 0; // to start strafing, we have to first figure out if the target is on the left side or right side game.makeVectors (m_moveAngles); - Vector dirToPoint = (pev->origin - m_destOrigin).normalize2D (); - Vector rightSide = game.vec.right.normalize2D (); + Vector dirToPoint = (pev->origin - m_destOrigin).normalize2d (); + Vector rightSide = game.vec.right.normalize2d (); bool dirRight = false; bool dirLeft = false; @@ -456,7 +454,7 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { src = pev->origin + game.vec.right * 32.0f; dst = src + testDir * 32.0f; - game.testHull (src, dst, TRACE_IGNORE_MONSTERS, head_hull, ent (), &tr); + game.testHull (src, dst, TraceIgnore::Monsters, head_hull, ent (), &tr); if (tr.flFraction != 1.0f) { blockedRight = true; @@ -464,7 +462,7 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { src = pev->origin - game.vec.right * 32.0f; dst = src + testDir * 32.0f; - game.testHull (src, dst, TRACE_IGNORE_MONSTERS, head_hull, ent (), &tr); + game.testHull (src, dst, TraceIgnore::Monsters, head_hull, ent (), &tr); if (tr.flFraction != 1.0f) { blockedLeft = true; @@ -480,7 +478,7 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { if (blockedLeft) { state[i] -= 5; } - i++; + ++i; if (dirRight) { state[i] += 5; @@ -495,7 +493,7 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { } // now weight all possible states - if (bits & PROBE_JUMP) { + if (bits & CollisionProbe::Jump) { state[i] = 0; if (canJumpUp (dirNormal)) { @@ -512,13 +510,13 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { src = getEyesPos (); src = src + game.vec.right * 15.0f; - game.testLine (src, m_destOrigin, TRACE_IGNORE_EVERYTHING, ent (), &tr); + game.testLine (src, m_destOrigin, TraceIgnore::Everything, ent (), &tr); if (tr.flFraction >= 1.0f) { src = getEyesPos (); src = src - game.vec.right * 15.0f; - game.testLine (src, m_destOrigin, TRACE_IGNORE_EVERYTHING, ent (), &tr); + game.testLine (src, m_destOrigin, TraceIgnore::Everything, ent (), &tr); if (tr.flFraction >= 1.0f) { state[i] += 5; @@ -532,7 +530,7 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { src = pev->origin + Vector (0.0f, 0.0f, -17.0f); } dst = src + dirNormal * 30.0f; - game.testLine (src, dst, TRACE_IGNORE_EVERYTHING, ent (), &tr); + game.testLine (src, dst, TraceIgnore::Everything, ent (), &tr); if (tr.flFraction != 1.0f) { state[i] += 10; @@ -541,10 +539,10 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { else { state[i] = 0; } - i++; + ++i; #if 0 - if (bits & PROBE_DUCK) + if (bits & CollisionProbe::Duck) { state[i] = 0; @@ -559,74 +557,67 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { else #endif state[i] = 0; - i++; + ++i; // weighted all possible moves, now sort them to start with most probable - bool isSorting = false; + bool sorting = false; do { - isSorting = false; - for (i = 0; i < 3; i++) { - if (state[i + MAX_COLLIDE_MOVES] < state[i + MAX_COLLIDE_MOVES + 1]) { - int temp = state[i]; + sorting = false; + for (i = 0; i < 3; ++i) { + if (state[i + kMaxCollideMoves] < state[i + kMaxCollideMoves + 1]) { + cr::swap (state[i], state[i + 1]); + cr::swap (state[i + kMaxCollideMoves], state[i + kMaxCollideMoves + 1]); - state[i] = state[i + 1]; - state[i + 1] = temp; - - temp = state[i + MAX_COLLIDE_MOVES]; - - state[i + MAX_COLLIDE_MOVES] = state[i + MAX_COLLIDE_MOVES + 1]; - state[i + MAX_COLLIDE_MOVES + 1] = temp; - - isSorting = true; + sorting = true; } } - } while (isSorting); + } while (sorting); - for (i = 0; i < MAX_COLLIDE_MOVES; i++) { + for (i = 0; i < kMaxCollideMoves; ++i) { m_collideMoves[i] = state[i]; } m_collideTime = game.timebase (); m_probeTime = game.timebase () + 0.5f; m_collisionProbeBits = bits; - m_collisionState = COLLISION_PROBING; + m_collisionState = CollisionState::Probing; m_collStateIndex = 0; } } - if (m_collisionState == COLLISION_PROBING) { + if (m_collisionState == CollisionState::Probing) { if (m_probeTime < game.timebase ()) { m_collStateIndex++; m_probeTime = game.timebase () + 0.5f; - if (m_collStateIndex > MAX_COLLIDE_MOVES) { + if (m_collStateIndex > kMaxCollideMoves) { m_navTimeset = game.timebase () - 5.0f; resetCollision (); } } - if (m_collStateIndex < MAX_COLLIDE_MOVES) { + if (m_collStateIndex < kMaxCollideMoves) { switch (m_collideMoves[m_collStateIndex]) { - case COLLISION_JUMP: + case CollisionState::Jump: if (isOnFloor () || isInWater ()) { pev->button |= IN_JUMP; - m_jumpStateTimer = game.timebase () + rng.getFloat (0.7f, 1.5f); + m_jumpStateTimer = game.timebase () + rg.float_ (0.7f, 1.5f); } break; - case COLLISION_DUCK: + case CollisionState::Duck: if (isOnFloor () || isInWater ()) { pev->button |= IN_DUCK; } break; - case COLLISION_STRAFELEFT: + case CollisionState::StrafeLeft: pev->button |= IN_MOVELEFT; setStrafeSpeed (dirNormal, -pev->maxspeed); break; - case COLLISION_STRAFERIGHT: + case CollisionState::StrafeRight: pev->button |= IN_MOVERIGHT; setStrafeSpeed (dirNormal, pev->maxspeed); break; @@ -634,32 +625,31 @@ void Bot::checkTerrain (float movedDistance, const Vector &dirNormal) { } } } - doPlayerAvoidance (dirNormal); } -bool Bot::updateNavigation (void) { +bool Bot::updateNavigation () { // this function is a main path navigation TraceResult tr, tr2; - // check if we need to find a waypoint... - if (m_currentWaypointIndex == INVALID_WAYPOINT_INDEX) { - getValidPoint (); - m_waypointOrigin = m_currentPath->origin; + // check if we need to find a node... + if (m_currentNodeIndex == kInvalidNodeIndex) { + findValidNode (); + m_pathOrigin = m_path->origin; // if wayzone radios non zero vary origin a bit depending on the body angles - if (m_currentPath->radius > 0) { - 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); + if (m_path->radius > 0) { + game.makeVectors (Vector (pev->angles.x, cr::normalizeAngles (pev->angles.y + rg.float_ (-90.0f, 90.0f)), 0.0f)); + m_pathOrigin = m_pathOrigin + game.vec.forward * rg.float_ (0, m_path->radius); } m_navTimeset = game.timebase (); } - m_destOrigin = m_waypointOrigin + pev->view_ofs; + m_destOrigin = m_pathOrigin + pev->view_ofs; - float waypointDistance = (pev->origin - m_waypointOrigin).length (); + float nodeDistance = (pev->origin - m_pathOrigin).length (); - // this waypoint has additional travel flags - care about them - if (m_currentTravelFlags & PATHFLAG_JUMP) { + // this node has additional travel flags - care about them + if (m_currentTravelFlags & PathFlag::Jump) { // bot is not jumped yet? if (!m_jumpFinished) { @@ -672,20 +662,20 @@ bool Bot::updateNavigation (void) { m_jumpFinished = true; m_checkTerrain = false; - m_desiredVelocity.nullify (); + m_desiredVelocity= nullvec; } } - else if (!yb_jasonmode.boolean () && m_currentWeapon == WEAPON_KNIFE && isOnFloor ()) { + else if (!yb_jasonmode.bool_ () && m_currentWeapon == Weapon::Knife && isOnFloor ()) { selectBestWeapon (); } } - if (m_currentPath->flags & FLAG_LADDER) { - if (m_waypointOrigin.z >= (pev->origin.z + 16.0f)) { - m_waypointOrigin = m_currentPath->origin + Vector (0.0f, 0.0f, 16.0f); + if (m_path->flags & NodeFlag::Ladder) { + if (m_pathOrigin.z >= (pev->origin.z + 16.0f)) { + m_pathOrigin = m_path->origin + Vector (0.0f, 0.0f, 16.0f); } - else if (m_waypointOrigin.z < pev->origin.z + 16.0f && !isOnLadder () && isOnFloor () && !(pev->flags & FL_DUCKING)) { - m_moveSpeed = waypointDistance; + else if (m_pathOrigin.z < pev->origin.z + 16.0f && !isOnLadder () && isOnFloor () && !(pev->flags & FL_DUCKING)) { + m_moveSpeed = nodeDistance; if (m_moveSpeed < 150.0f) { m_moveSpeed = 150.0f; @@ -697,60 +687,60 @@ bool Bot::updateNavigation (void) { } // special lift handling (code merged from podbotmm) - if (m_currentPath->flags & FLAG_LIFT) { + if (m_path->flags & NodeFlag::Lift) { bool liftClosedDoorExists = false; - // update waypoint time set + // update node time set m_navTimeset = game.timebase (); // trace line to door - game.testLine (pev->origin, m_currentPath->origin, TRACE_IGNORE_EVERYTHING, ent (), &tr2); + game.testLine (pev->origin, m_path->origin, TraceIgnore::Everything, ent (), &tr2); - if (tr2.flFraction < 1.0f && strcmp (STRING (tr2.pHit->v.classname), "func_door") == 0 && (m_liftState == 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; + if (tr2.flFraction < 1.0f && strcmp (STRING (tr2.pHit->v.classname), "func_door") == 0 && (m_liftState == LiftState::None || m_liftState == LiftState::WaitingFor || m_liftState == LiftState::LookingButtonOutside) && pev->groundentity != tr2.pHit) { + if (m_liftState == LiftState::None) { + m_liftState = LiftState::LookingButtonOutside; m_liftUsageTime = game.timebase () + 7.0f; } liftClosedDoorExists = true; } // trace line down - game.testLine (m_currentPath->origin, m_currentPath->origin + Vector (0.0f, 0.0f, -50.0f), TRACE_IGNORE_EVERYTHING, ent (), &tr); + game.testLine (m_path->origin, m_path->origin + Vector (0.0f, 0.0f, -50.0f), TraceIgnore::Everything, ent (), &tr); // if trace result shows us that it is a lift - if (!game.isNullEntity (tr.pHit) && !m_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 (!game.isNullEntity (tr.pHit) && !m_pathWalk.empty () && (strcmp (STRING (tr.pHit->v.classname), "func_door") == 0 || strcmp (STRING (tr.pHit->v.classname), "func_plat") == 0 || strcmp (STRING (tr.pHit->v.classname), "func_train") == 0) && !liftClosedDoorExists) { + if ((m_liftState == LiftState::None || m_liftState == LiftState::WaitingFor || m_liftState == LiftState::LookingButtonOutside) && tr.pHit->v.velocity.z == 0.0f) { if (cr::abs (pev->origin.z - tr.vecEndPos.z) < 70.0f) { m_liftEntity = tr.pHit; - m_liftState = LIFT_ENTERING_IN; - m_liftTravelPos = m_currentPath->origin; + m_liftState = LiftState::EnteringIn; + m_liftTravelPos = m_path->origin; m_liftUsageTime = game.timebase () + 5.0f; } } - else if (m_liftState == LIFT_TRAVELING_BY) { - m_liftState = LIFT_LEAVING; + else if (m_liftState == LiftState::TravelingBy) { + m_liftState = LiftState::Leaving; m_liftUsageTime = game.timebase () + 7.0f; } } - else if (!m_path.empty ()) // no lift found at waypoint + else if (!m_pathWalk.empty ()) // no lift found at node { - if ((m_liftState == LIFT_NO_NEARBY || m_liftState == LIFT_WAITING_FOR) && m_path.hasNext ()) { - int nextNode = m_path.next (); + if ((m_liftState == LiftState::None || m_liftState == LiftState::WaitingFor) && m_pathWalk.hasNext ()) { + int nextNode = m_pathWalk.next (); - if (waypoints.exists (nextNode) && (waypoints[nextNode].flags & FLAG_LIFT)) { - game.testLine (m_currentPath->origin, waypoints[nextNode].origin, TRACE_IGNORE_EVERYTHING, ent (), &tr); + if (graph.exists (nextNode) && (graph[nextNode].flags & NodeFlag::Lift)) { + game.testLine (m_path->origin, graph[nextNode].origin, TraceIgnore::Everything, ent (), &tr); if (!game.isNullEntity (tr.pHit) && (strcmp (STRING (tr.pHit->v.classname), "func_door") == 0 || strcmp (STRING (tr.pHit->v.classname), "func_plat") == 0 || strcmp (STRING (tr.pHit->v.classname), "func_train") == 0)) { m_liftEntity = tr.pHit; } } - m_liftState = LIFT_LOOKING_BUTTON_OUTSIDE; + m_liftState = LiftState::LookingButtonOutside; m_liftUsageTime = game.timebase () + 15.0f; } } // bot is going to enter the lift - if (m_liftState == LIFT_ENTERING_IN) { + if (m_liftState == LiftState::EnteringIn) { m_destOrigin = m_liftTravelPos; // check if we enough to destination @@ -759,7 +749,7 @@ bool Bot::updateNavigation (void) { m_strafeSpeed = 0.0f; m_navTimeset = game.timebase (); - m_aimFlags |= AIM_NAVPOINT; + m_aimFlags |= AimFlags::Nav; resetCollision (); @@ -767,14 +757,8 @@ bool Bot::updateNavigation (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 < game.maxClients (); i++) { - Bot *bot = bots.getBot (i); - - if (bot == nullptr || bot == this) { - continue; - } - - if (!bot->m_notKilled || bot->m_team != m_team || bot->m_targetEntity != ent () || bot->taskId () != TASK_FOLLOWUSER) { + for (const auto &bot : bots) { + if (!bot->m_notKilled || bot->m_team != m_team || bot->m_targetEntity != ent () || bot->getCurrentTaskId () != Task::FollowUser) { continue; } @@ -783,36 +767,30 @@ bool Bot::updateNavigation (void) { } bot->m_liftEntity = m_liftEntity; - bot->m_liftState = LIFT_ENTERING_IN; + bot->m_liftState = LiftState::EnteringIn; bot->m_liftTravelPos = m_liftTravelPos; needWaitForTeammate = true; } if (needWaitForTeammate) { - m_liftState = LIFT_WAIT_FOR_TEAMMATES; + m_liftState = LiftState::WaitingForTeammates; m_liftUsageTime = game.timebase () + 8.0f; } else { - m_liftState = LIFT_LOOKING_BUTTON_INSIDE; + m_liftState = LiftState::LookingButtonInside; m_liftUsageTime = game.timebase () + 10.0f; } } } // bot is waiting for his teammates - if (m_liftState == LIFT_WAIT_FOR_TEAMMATES) { + if (m_liftState == LiftState::WaitingForTeammates) { // need to wait our following teammate ? bool needWaitForTeammate = false; - for (int i = 0; i < game.maxClients (); i++) { - Bot *bot = bots.getBot (i); - - if (bot == nullptr) { - continue; // skip invalid bots - } - - if (!bot->m_notKilled || bot->m_team != m_team || bot->m_targetEntity != ent () || bot->taskId () != TASK_FOLLOWUSER || bot->m_liftEntity != m_liftEntity) { + for (const auto &bot : bots) { + if (!bot->m_notKilled || bot->m_team != m_team || bot->m_targetEntity != ent () || bot->getCurrentTaskId () != Task::FollowUser || bot->m_liftEntity != m_liftEntity) { continue; } @@ -831,7 +809,7 @@ bool Bot::updateNavigation (void) { m_strafeSpeed = 0.0f; m_navTimeset = game.timebase (); - m_aimFlags |= AIM_NAVPOINT; + m_aimFlags |= AimFlags::Nav; resetCollision (); } @@ -839,28 +817,28 @@ bool Bot::updateNavigation (void) { // else we need to look for button if (!needWaitForTeammate || m_liftUsageTime < game.timebase ()) { - m_liftState = LIFT_LOOKING_BUTTON_INSIDE; + m_liftState = LiftState::LookingButtonInside; m_liftUsageTime = game.timebase () + 10.0f; } } // bot is trying to find button inside a lift - if (m_liftState == LIFT_LOOKING_BUTTON_INSIDE) { + if (m_liftState == LiftState::LookingButtonInside) { edict_t *button = lookupButton (STRING (m_liftEntity->v.targetname)); // got a valid button entity ? if (!game.isNullEntity (button) && pev->groundentity == m_liftEntity && m_buttonPushTime + 1.0f < game.timebase () && m_liftEntity->v.velocity.z == 0.0f && isOnFloor ()) { m_pickupItem = button; - m_pickupType = PICKUP_BUTTON; + m_pickupType = Pickup::Button; 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) || !game.isNullEntity (m_targetEntity))) { - m_liftState = LIFT_TRAVELING_BY; + if (m_liftState == LiftState::LookingButtonInside || m_liftState == LiftState::EnteringIn || m_liftState == LiftState::WaitingForTeammates || m_liftState == LiftState::WaitingFor) { + if (pev->groundentity == m_liftEntity && m_liftEntity->v.velocity.z != 0.0f && isOnFloor () && ((graph[m_prevWptIndex[0]].flags & NodeFlag::Lift) || !game.isNullEntity (m_targetEntity))) { + m_liftState = LiftState::TravelingBy; m_liftUsageTime = game.timebase () + 14.0f; if ((pev->origin - m_destOrigin).lengthSq () < 225.0f) { @@ -868,7 +846,7 @@ bool Bot::updateNavigation (void) { m_strafeSpeed = 0.0f; m_navTimeset = game.timebase (); - m_aimFlags |= AIM_NAVPOINT; + m_aimFlags |= AimFlags::Nav; resetCollision (); } @@ -876,7 +854,7 @@ bool Bot::updateNavigation (void) { } // bots is currently moving on lift - if (m_liftState == LIFT_TRAVELING_BY) { + if (m_liftState == LiftState::TravelingBy) { m_destOrigin = Vector (m_liftTravelPos.x, m_liftTravelPos.y, pev->origin.z); if ((pev->origin - m_destOrigin).lengthSq () < 225.0f) { @@ -884,19 +862,19 @@ bool Bot::updateNavigation (void) { m_strafeSpeed = 0.0f; m_navTimeset = game.timebase (); - m_aimFlags |= AIM_NAVPOINT; + m_aimFlags |= AimFlags::Nav; resetCollision (); } } // need to find a button outside the lift - if (m_liftState == LIFT_LOOKING_BUTTON_OUTSIDE) { + if (m_liftState == LiftState::LookingButtonOutside) { // button has been pressed, lift should come if (m_buttonPushTime + 8.0f >= game.timebase ()) { - if (waypoints.exists (m_prevWptIndex[0])) { - m_destOrigin = waypoints[m_prevWptIndex[0]].origin; + if (graph.exists (m_prevWptIndex[0])) { + m_destOrigin = graph[m_prevWptIndex[0]].origin; } else { m_destOrigin = pev->origin; @@ -907,7 +885,7 @@ bool Bot::updateNavigation (void) { m_strafeSpeed = 0.0f; m_navTimeset = game.timebase (); - m_aimFlags |= AIM_NAVPOINT; + m_aimFlags |= AimFlags::Nav; resetCollision (); } @@ -922,7 +900,7 @@ bool Bot::updateNavigation (void) { // iterate though clients, and find if lift already used 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)) { + if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive) || client.team != m_team || client.ent == ent () || game.isNullEntity (client.ent->v.groundentity)) { continue; } @@ -934,8 +912,8 @@ bool Bot::updateNavigation (void) { // lift is currently used if (liftUsed) { - if (waypoints.exists (m_prevWptIndex[0])) { - m_destOrigin = waypoints[m_prevWptIndex[0]].origin; + if (graph.exists (m_prevWptIndex[0])) { + m_destOrigin = graph[m_prevWptIndex[0]].origin; } else { m_destOrigin = button->v.origin; @@ -948,28 +926,28 @@ bool Bot::updateNavigation (void) { } else { m_pickupItem = button; - m_pickupType = PICKUP_BUTTON; - m_liftState = LIFT_WAITING_FOR; + m_pickupType = Pickup::Button; + m_liftState = LiftState::WaitingFor; m_navTimeset = game.timebase (); m_liftUsageTime = game.timebase () + 20.0f; } } else { - m_liftState = LIFT_WAITING_FOR; + m_liftState = LiftState::WaitingFor; m_liftUsageTime = game.timebase () + 15.0f; } } } // bot is waiting for lift - if (m_liftState == LIFT_WAITING_FOR) { - if (waypoints.exists (m_prevWptIndex[0])) { - if (!(waypoints[m_prevWptIndex[0]].flags & FLAG_LIFT)) { - m_destOrigin = waypoints[m_prevWptIndex[0]].origin; + if (m_liftState == LiftState::WaitingFor) { + if (graph.exists (m_prevWptIndex[0])) { + if (!(graph[m_prevWptIndex[0]].flags & NodeFlag::Lift)) { + m_destOrigin = graph[m_prevWptIndex[0]].origin; } - else if (waypoints.exists (m_prevWptIndex[1])) { - m_destOrigin = waypoints[m_prevWptIndex[1]].origin; + else if (graph.exists (m_prevWptIndex[1])) { + m_destOrigin = graph[m_prevWptIndex[1]].origin; } } @@ -978,26 +956,26 @@ bool Bot::updateNavigation (void) { m_strafeSpeed = 0.0f; m_navTimeset = game.timebase (); - m_aimFlags |= AIM_NAVPOINT; + m_aimFlags |= AimFlags::Nav; resetCollision (); } } // if bot is waiting for lift, or going to it - if (m_liftState == LIFT_WAITING_FOR || m_liftState == LIFT_ENTERING_IN) { + if (m_liftState == LiftState::WaitingFor || m_liftState == LiftState::EnteringIn) { // bot fall down somewhere inside the lift's groove :) - if (pev->groundentity != m_liftEntity && waypoints.exists (m_prevWptIndex[0])) { - if ((waypoints[m_prevWptIndex[0]].flags & FLAG_LIFT) && (m_currentPath->origin.z - pev->origin.z) > 50.0f && (waypoints[m_prevWptIndex[0]].origin.z - pev->origin.z) > 50.0f) { - m_liftState = LIFT_NO_NEARBY; + if (pev->groundentity != m_liftEntity && graph.exists (m_prevWptIndex[0])) { + if ((graph[m_prevWptIndex[0]].flags & NodeFlag::Lift) && (m_path->origin.z - pev->origin.z) > 50.0f && (graph[m_prevWptIndex[0]].origin.z - pev->origin.z) > 50.0f) { + m_liftState = LiftState::None; m_liftEntity = nullptr; m_liftUsageTime = 0.0f; clearSearchNodes (); - searchOptimalPoint (); + findBestNearestNode (); - if (waypoints.exists (m_prevWptIndex[2])) { - searchPath (m_currentWaypointIndex, m_prevWptIndex[2], SEARCH_PATH_FASTEST); + if (graph.exists (m_prevWptIndex[2])) { + findPath (m_currentNodeIndex, m_prevWptIndex[2], FindPath::Fast); } return false; } @@ -1005,13 +983,13 @@ bool Bot::updateNavigation (void) { } } - if (!game.isNullEntity (m_liftEntity) && !(m_currentPath->flags & FLAG_LIFT)) { - if (m_liftState == LIFT_TRAVELING_BY) { - m_liftState = LIFT_LEAVING; + if (!game.isNullEntity (m_liftEntity) && !(m_path->flags & NodeFlag::Lift)) { + if (m_liftState == LiftState::TravelingBy) { + m_liftState = LiftState::Leaving; m_liftUsageTime = game.timebase () + 10.0f; } - if (m_liftState == LIFT_LEAVING && m_liftUsageTime < game.timebase () && pev->groundentity != m_liftEntity) { - m_liftState = LIFT_NO_NEARBY; + if (m_liftState == LiftState::Leaving && m_liftUsageTime < game.timebase () && pev->groundentity != m_liftEntity) { + m_liftState = LiftState::None; m_liftUsageTime = 0.0f; m_liftEntity = nullptr; @@ -1020,37 +998,37 @@ bool Bot::updateNavigation (void) { if (m_liftUsageTime < game.timebase () && m_liftUsageTime != 0.0f) { m_liftEntity = nullptr; - m_liftState = LIFT_NO_NEARBY; + m_liftState = LiftState::None; m_liftUsageTime = 0.0f; clearSearchNodes (); - if (waypoints.exists (m_prevWptIndex[0])) { - if (!(waypoints[m_prevWptIndex[0]].flags & FLAG_LIFT)) { + if (graph.exists (m_prevWptIndex[0])) { + if (!(graph[m_prevWptIndex[0]].flags & NodeFlag::Lift)) { changePointIndex (m_prevWptIndex[0]); } else { - searchOptimalPoint (); + findBestNearestNode (); } } else { - searchOptimalPoint (); + findBestNearestNode (); } return false; } // check if we are going through a door... - if (game.mapIs (MAP_HAS_DOORS)) { - game.testLine (pev->origin, m_waypointOrigin, TRACE_IGNORE_MONSTERS, ent (), &tr); + if (game.mapIs (MapFlags::HasDoors)) { + game.testLine (pev->origin, m_pathOrigin, TraceIgnore::Monsters, ent (), &tr); if (!game.isNullEntity (tr.pHit) && game.isNullEntity (m_liftEntity) && strncmp (STRING (tr.pHit->v.classname), "func_door", 9) == 0) { // if the door is near enough... if ((game.getAbsPos (tr.pHit) - pev->origin).lengthSq () < 2500.0f) { ignoreCollision (); // don't consider being stuck - if (rng.chance (50)) { + if (rg.chance (50)) { // do not use door directrly under xash, or we will get failed assert in gamedll code - if (game.is (GAME_XASH_ENGINE)) { + if (game.is (GameFlags::Xash3D)) { pev->button |= IN_USE; } else { @@ -1060,7 +1038,7 @@ bool Bot::updateNavigation (void) { } // make sure we are always facing the door when going through it - m_aimFlags &= ~(AIM_LAST_ENEMY | AIM_PREDICT_PATH); + m_aimFlags &= ~(AimFlags::LastEnemy | AimFlags::PredictPath); m_canChooseAimDirection = false; edict_t *button = lookupButton (STRING (tr.pHit->v.targetname)); @@ -1068,14 +1046,14 @@ bool Bot::updateNavigation (void) { // check if we got valid button if (!game.isNullEntity (button)) { m_pickupItem = button; - m_pickupType = PICKUP_BUTTON; + m_pickupType = Pickup::Button; 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 < game.timebase ()) { - startTask (TASK_PAUSE, TASKPRI_PAUSE, INVALID_WAYPOINT_INDEX, game.timebase () + 0.5f, false); + if (pev->velocity.length2d () < 2 && m_timeDoorOpen < game.timebase ()) { + startTask (Task::Pause, TaskPri::Pause, kInvalidNodeIndex, game.timebase () + 0.5f, false); m_timeDoorOpen = game.timebase () + 1.0f; // retry in 1 sec until door is open edict_t *pent = nullptr; @@ -1083,8 +1061,8 @@ bool Bot::updateNavigation (void) { 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; + m_states |= Sense::SeeingEnemy; + m_aimFlags |= AimFlags::Enemy; m_lastEnemy = pent; m_enemy = pent; @@ -1100,84 +1078,80 @@ bool Bot::updateNavigation (void) { } float desiredDistance = 0.0f; - // initialize the radius for a special waypoint type, where the wpt is considered to be reached - if (m_currentPath->flags & FLAG_LIFT) { + // initialize the radius for a special node type, where the wpt is considered to be reached + if (m_path->flags & NodeFlag::Lift) { desiredDistance = 50.0f; } - else if ((pev->flags & FL_DUCKING) || (m_currentPath->flags & FLAG_GOAL)) { + else if ((pev->flags & FL_DUCKING) || (m_path->flags & NodeFlag::Goal)) { desiredDistance = 25.0f; } - else if (m_currentPath->flags & FLAG_LADDER) { + else if (m_path->flags & NodeFlag::Ladder) { desiredDistance = 15.0f; } - else if (m_currentTravelFlags & PATHFLAG_JUMP) { + else if (m_currentTravelFlags & PathFlag::Jump) { desiredDistance = 0.0f; } - else if (isOccupiedPoint (m_currentWaypointIndex)) { + else if (isOccupiedPoint (m_currentNodeIndex)) { desiredDistance = 120.0f; } else { - desiredDistance = m_currentPath->radius; + desiredDistance = m_path->radius; } - // check if waypoint has a special travelflag, so they need to be reached more precisely - for (auto &flag : m_currentPath->connectionFlags) { - if (flag != 0) { + // check if node has a special travelflag, so they need to be reached more precisely + for (const auto &link : m_path->links) { + if (link.flags != 0) { 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 * getFrameInterval ()) - m_waypointOrigin).lengthSq () > cr::square (waypointDistance)) { - desiredDistance = waypointDistance + 1.0f; + if (desiredDistance < 22.0f && nodeDistance < 30.0f && (pev->origin + (pev->velocity * getFrameInterval ()) - m_pathOrigin).lengthSq () > cr::square (nodeDistance)) { + desiredDistance = nodeDistance + 1.0f; } - if (waypointDistance < desiredDistance) { + if (nodeDistance < desiredDistance) { - // did we reach a destination waypoint? - if (getTask ()->data == m_currentWaypointIndex) { - if (m_chosenGoalIndex != INVALID_WAYPOINT_INDEX) { + // did we reach a destination node? + if (getTask ()->data == m_currentNodeIndex) { + if (m_chosenGoalIndex != kInvalidNodeIndex) { // add goal values - int goalValue = waypoints.getDangerValue (m_team, m_chosenGoalIndex, m_currentWaypointIndex); + int goalValue = graph.getDangerValue (m_team, m_chosenGoalIndex, m_currentNodeIndex); int addedValue = static_cast (pev->health * 0.5f + m_goalValue * 0.5f); - goalValue = cr::clamp (goalValue + addedValue, -MAX_GOAL_VALUE, MAX_GOAL_VALUE); + goalValue = cr::clamp (goalValue + addedValue, -kMaxPracticeGoalValue, kMaxPracticeGoalValue); - // update the experience for team - auto experience = waypoints.getRawExperience (); - - if (experience) { - (experience + (m_chosenGoalIndex * waypoints.length ()) + m_currentWaypointIndex)->value[m_team] = goalValue; - } + // update the practice for team + graph.setDangerValue (m_team, m_chosenGoalIndex, m_currentNodeIndex, goalValue); } return true; } - else if (m_path.empty ()) { + else if (m_pathWalk.empty ()) { return false; } int taskTarget = getTask ()->data; - if (game.mapIs (MAP_DE) && bots.isBombPlanted () && m_team == TEAM_COUNTER && taskId () != TASK_ESCAPEFROMBOMB && taskTarget != INVALID_WAYPOINT_INDEX) { + if (game.mapIs (MapFlags::Demolition) && bots.isBombPlanted () && m_team == Team::CT && getCurrentTaskId () != Task::EscapeFromBomb && taskTarget != kInvalidNodeIndex) { const Vector &bombOrigin = isBombAudible (); // bot within 'hearable' bomb tick noises? if (!bombOrigin.empty ()) { - float distance = (bombOrigin - waypoints[taskTarget].origin).length (); + float distance = (bombOrigin - graph[taskTarget].origin).length (); if (distance > 512.0f) { - if (rng.chance (50) && !waypoints.isVisited (taskTarget)) { - pushRadioMessage (RADIO_SECTOR_CLEAR); + if (rg.chance (50) && !graph.isVisited (taskTarget)) { + pushRadioMessage (Radio::SectorClear); } - waypoints.setVisited (taskTarget); // doesn't hear so not a good goal + graph.setVisited (taskTarget); // doesn't hear so not a good goal } } else { - if (rng.chance (50) && !waypoints.isVisited (taskTarget)) { - pushRadioMessage (RADIO_SECTOR_CLEAR); + if (rg.chance (50) && !graph.isVisited (taskTarget)) { + pushRadioMessage (Radio::SectorClear); } - waypoints.setVisited (taskTarget); // doesn't hear so not a good goal + graph.setVisited (taskTarget); // doesn't hear so not a good goal } } advanceMovement (); // do the actual movement checking @@ -1185,15 +1159,15 @@ bool Bot::updateNavigation (void) { return false; } -void Bot::searchShortestPath (int srcIndex, int destIndex) { +void Bot::findShortestPath (int srcIndex, int destIndex) { // this function finds the shortest path from source index to destination index - if (!waypoints.exists (srcIndex)){ - util.logEntry (false, LL_ERROR, "Pathfinder source path index not valid (%d)", srcIndex); + if (!graph.exists (srcIndex)){ + logger.error ("Pathfinder source path index not valid (%d)", srcIndex); return; } - else if (!waypoints.exists (destIndex)) { - util.logEntry (false, LL_ERROR, "Pathfinder destination path index not valid (%d)", destIndex); + else if (!graph.exists (destIndex)) { + logger.error ("Pathfinder destination path index not valid (%d)", destIndex); return; } clearSearchNodes (); @@ -1201,39 +1175,39 @@ void Bot::searchShortestPath (int srcIndex, int destIndex) { m_chosenGoalIndex = srcIndex; m_goalValue = 0.0f; - m_path.push (srcIndex); + m_pathWalk.push (srcIndex); while (srcIndex != destIndex) { - srcIndex = (waypoints.m_matrix + (srcIndex * waypoints.length ()) + destIndex)->index; + srcIndex = (graph.m_matrix.data () + (srcIndex * graph.length ()) + destIndex)->index; if (srcIndex < 0) { - m_prevGoalIndex = INVALID_WAYPOINT_INDEX; - getTask ()->data = INVALID_WAYPOINT_INDEX; + m_prevGoalIndex = kInvalidNodeIndex; + getTask ()->data = kInvalidNodeIndex; return; } - m_path.push (srcIndex); + m_pathWalk.push (srcIndex); } } -void Bot::searchPath (int srcIndex, int destIndex, SearchPathType pathType /*= SEARCH_PATH_FASTEST */) { +void Bot::findPath (int srcIndex, int destIndex, FindPath pathType /*= FindPath::Fast */) { // 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) { + if (parentIndex == kInvalidNodeIndex) { return 0.0f; } - auto cost = static_cast (waypoints.getDangerDamage (team, currentIndex, currentIndex) + waypoints.getHighestDamageForTeam (team)); - Path ¤t = waypoints[currentIndex]; + auto cost = static_cast (graph.getDangerDamage (team, currentIndex, currentIndex) + graph.getHighestDamageForTeam (team)); + Path ¤t = graph[currentIndex]; - for (auto &neighbour : current.index) { - if (neighbour != INVALID_WAYPOINT_INDEX) { - cost += static_cast (waypoints.getDangerDamage (team, neighbour, neighbour)); + for (auto &neighbour : current.links) { + if (neighbour.index != kInvalidNodeIndex) { + cost += static_cast (graph.getDangerDamage (team, neighbour.index, neighbour.index)); } } - if (current.flags & FLAG_CROUCH) { + if (current.flags & NodeFlag::Crouch) { cost *= 1.5f; } return cost; @@ -1241,12 +1215,12 @@ void Bot::searchPath (int srcIndex, int destIndex, SearchPathType pathType /*= S // 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]; + Path ¤t = graph[currentIndex]; - if (current.flags & FLAG_NOHOSTAGE) { + if (current.flags & NodeFlag::NoHostage) { return 65355.0f; } - else if (current.flags & (FLAG_CROUCH | FLAG_LADDER)) { + else if (current.flags & (NodeFlag::Crouch | NodeFlag::Ladder)) { return gfunctionKillsDist (team, currentIndex, parentIndex) * 500.0f; } return gfunctionKillsDist (team, currentIndex, parentIndex); @@ -1254,16 +1228,16 @@ void Bot::searchPath (int srcIndex, int destIndex, SearchPathType pathType /*= S // 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]; + auto cost = static_cast (graph.getDangerDamage (team, currentIndex, currentIndex)); + Path ¤t = graph[currentIndex]; - for (auto &neighbour : current.index) { - if (neighbour != INVALID_WAYPOINT_INDEX) { - cost += static_cast (waypoints.getDangerDamage (team, neighbour, neighbour)); + for (auto &neighbour : current.links) { + if (neighbour.index != kInvalidNodeIndex) { + cost += static_cast (graph.getDangerDamage (team, neighbour.index, neighbour.index)); } } - if (current.flags & FLAG_CROUCH) { + if (current.flags & NodeFlag::Crouch) { cost *= 1.5f; } return cost + 0.5f; @@ -1271,61 +1245,61 @@ void Bot::searchPath (int srcIndex, int destIndex, SearchPathType pathType /*= S // least kills to goal for a team auto gfunctionKillsCTWithHostage = [&gfunctionKills] (int team, int currentIndex, int parentIndex) -> float { - if (parentIndex == INVALID_WAYPOINT_INDEX) { + if (parentIndex == kInvalidNodeIndex) { return 0.0f; } - Path ¤t = waypoints[currentIndex]; + Path ¤t = graph[currentIndex]; - if (current.flags & FLAG_NOHOSTAGE) { + if (current.flags & NodeFlag::NoHostage) { return 65355.0f; } - else if (current.flags & (FLAG_CROUCH | FLAG_LADDER)) { + else if (current.flags & (NodeFlag::Crouch | NodeFlag::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) { + if (parentIndex == kInvalidNodeIndex) { return 0.0f; } - Path &parent = waypoints[parentIndex]; - Path ¤t = waypoints[currentIndex]; + Path &parent = graph[parentIndex]; + Path ¤t = graph[currentIndex]; - for (int i = 0; i < MAX_PATH_INDEX; i++) { - if (parent.index[i] == currentIndex) { + for (const auto &link : parent.links) { + if (link.index == currentIndex) { // we don't like ladder or crouch point - if (current.flags & (FLAG_CROUCH | FLAG_LADDER)) { - return parent.distances[i] * 1.5f; + if (current.flags & (NodeFlag::Crouch | NodeFlag::Ladder)) { + return link.distance * 1.5f; } - return static_cast (parent.distances[i]); + return static_cast (link.distance); } } return 65355.0f; }; auto gfunctionPathDistWithHostage = [&gfunctionPathDist] (int, int currentIndex, int parentIndex) -> float { - Path ¤t = waypoints[currentIndex]; + Path ¤t = graph[currentIndex]; - if (current.flags & FLAG_NOHOSTAGE) { + if (current.flags & NodeFlag::NoHostage) { return 65355.0f; } - else if (current.flags & (FLAG_CROUCH | FLAG_LADDER)) { - return gfunctionPathDist (TEAM_UNASSIGNED, currentIndex, parentIndex) * 500.0f; + else if (current.flags & (NodeFlag::Crouch | NodeFlag::Ladder)) { + return gfunctionPathDist (Team::Unassigned, currentIndex, parentIndex) * 500.0f; } - return gfunctionPathDist (TEAM_UNASSIGNED, currentIndex, parentIndex); + 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]; + Path &start = graph[index]; + Path &goal = graph[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 ()) { + switch (yb_debug_heuristic_type.int_ ()) { case 0: default: return cr::max (cr::max (x, y), z); // chebyshev distance @@ -1341,7 +1315,7 @@ void Bot::searchPath (int srcIndex, int destIndex, SearchPathType pathType /*= S // 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) { + if (yb_debug_heuristic_type.int_ () == 4) { return 1000.0f *(cr::ceilf (euclidean) - euclidean); } return euclidean; @@ -1350,7 +1324,7 @@ void Bot::searchPath (int srcIndex, int destIndex, SearchPathType pathType /*= S // square distance heuristic with hostages auto hfunctionPathDistWithHostage = [&hfunctionPathDist] (int index, int startIndex, int goalIndex) -> float { - if (waypoints[startIndex].flags & FLAG_NOHOSTAGE) { + if (graph[startIndex].flags & NodeFlag::NoHostage) { return 65355.0f; } return hfunctionPathDist (index, startIndex, goalIndex); @@ -1363,8 +1337,8 @@ void Bot::searchPath (int srcIndex, int destIndex, SearchPathType pathType /*= S // 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 (pathType == FindPath::Optimal) { + if (game.mapIs (MapFlags::HostageRescue) && hasHostage ()) { if (hfun) { return hfunctionPathDistWithHostage (a, b, c); } @@ -1381,8 +1355,8 @@ void Bot::searchPath (int srcIndex, int destIndex, SearchPathType pathType /*= S } } } - else if (pathType == SEARCH_PATH_SAFEST) { - if (game.mapIs (MAP_CS) && hasHostage ()) { + else if (pathType == FindPath::Safe) { + if (game.mapIs (MapFlags::HostageRescue) && hasHostage ()) { if (hfun) { return hfunctionNone (a, b, c); } @@ -1400,7 +1374,7 @@ void Bot::searchPath (int srcIndex, int destIndex, SearchPathType pathType /*= S } } else { - if (game.mapIs (MAP_CS) && hasHostage ()) { + if (game.mapIs (MapFlags::HostageRescue) && hasHostage ()) { if (hfun) { return hfunctionPathDistWithHostage (a, b, c); } @@ -1419,12 +1393,12 @@ void Bot::searchPath (int srcIndex, int destIndex, SearchPathType pathType /*= S } }; - if (!waypoints.exists (srcIndex)) { - util.logEntry (false, LL_ERROR, "Pathfinder source path index not valid (%d)", srcIndex); + if (!graph.exists (srcIndex)) { + logger.error ("Pathfinder source path index not valid (%d)", srcIndex); return; } - else if (!waypoints.exists (destIndex)) { - util.logEntry (false, LL_ERROR, "Pathfinder destination path index not valid (%d)", destIndex); + else if (!graph.exists (destIndex)) { + logger.error ("Pathfinder destination path index not valid (%d)", destIndex); return; } clearSearchNodes (); @@ -1436,23 +1410,23 @@ void Bot::searchPath (int srcIndex, int destIndex, SearchPathType pathType /*= S auto srcRoute = &m_routes[srcIndex]; // put start node into open list - srcRoute->g = calculate (false, m_team, srcIndex, INVALID_WAYPOINT_INDEX); + srcRoute->g = calculate (false, m_team, srcIndex, kInvalidNodeIndex); srcRoute->f = srcRoute->g + calculate (true, srcIndex, srcIndex, destIndex); - srcRoute->state = ROUTE_OPEN; + srcRoute->state = RouteState::Open; m_routeQue.clear (); - m_routeQue.push (srcIndex, srcRoute->g); + m_routeQue.emplace (srcIndex, srcRoute->g); while (!m_routeQue.empty ()) { // remove the first node from the open list - int currentIndex = m_routeQue.pop (); + int currentIndex = m_routeQue.pop ().first; - // safes us from bad waypoints... - if (m_routeQue.length () >= MAX_ROUTE_LENGTH - 1) { - 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 ()); + // safes us from bad graph... + if (m_routeQue.length () >= kMaxRouteLength - 1) { + logger.error ("A* Search for bots \"%s\" has tried to build path with at least %d nodes. Seems to be graph is broken.", STRING (pev->netname), m_routeQue.length ()); // bail out to shortest path - searchShortestPath (srcIndex, destIndex); + findShortestPath (srcIndex, destIndex); return; } @@ -1461,156 +1435,152 @@ void Bot::searchPath (int srcIndex, int destIndex, SearchPathType pathType /*= S // build the complete path do { - m_path.push (currentIndex); + m_pathWalk.push (currentIndex); currentIndex = m_routes[currentIndex].parent; - } while (currentIndex != INVALID_WAYPOINT_INDEX); + } while (currentIndex != kInvalidNodeIndex); - m_path.reverse (); + m_pathWalk.reverse (); return; } auto curRoute = &m_routes[currentIndex]; - if (curRoute->state != ROUTE_OPEN) { + if (curRoute->state != RouteState::Open) { continue; } // put current node into CLOSED list - curRoute->state = ROUTE_CLOSED; + curRoute->state = RouteState::Closed; // now expand the current node - for (auto &child : waypoints[currentIndex].index) { - if (child == INVALID_WAYPOINT_INDEX) { + for (const auto &child : graph[currentIndex].links) { + if (child.index == kInvalidNodeIndex) { continue; } - auto childRoute = &m_routes[child]; + auto childRoute = &m_routes[child.index]; // calculate the F value as F = G + H - float g = curRoute->g + calculate (false, m_team, child, currentIndex); - float h = calculate (true, child, srcIndex, destIndex); + float g = curRoute->g + calculate (false, m_team, child.index, currentIndex); + float h = calculate (true, child.index, srcIndex, destIndex); float f = g + h; - if (childRoute->state == ROUTE_NEW || childRoute->f > f) { + if (childRoute->state == RouteState::New || childRoute->f > f) { // put the current child into open list childRoute->parent = currentIndex; - childRoute->state = ROUTE_OPEN; + childRoute->state = RouteState::Open; childRoute->g = g; childRoute->f = f; - m_routeQue.push (child, g); + m_routeQue.emplace (child.index, g); } } } - searchShortestPath (srcIndex, destIndex); // A* found no path, try floyd pathfinder instead + findShortestPath (srcIndex, destIndex); // A* found no path, try floyd pathfinder instead } -void Bot::clearSearchNodes (void) { - m_path.clear (); - m_chosenGoalIndex = INVALID_WAYPOINT_INDEX; +void Bot::clearSearchNodes () { + m_pathWalk.clear (); + m_chosenGoalIndex = kInvalidNodeIndex; } -void Bot::clearRoute (void) { - int maxWaypoints = waypoints.length (); - - if (m_routes.capacity () < static_cast (waypoints.length ())) { - m_routes.reserve (maxWaypoints); - } +void Bot::clearRoute () { + m_routes.resize (graph.length ()); - for (int i = 0; i < maxWaypoints; i++) { + for (int i = 0; i < graph.length (); ++i) { auto route = &m_routes[i]; route->g = route->f = 0.0f; - route->parent = INVALID_WAYPOINT_INDEX; - route->state = ROUTE_NEW; + route->parent = kInvalidNodeIndex; + route->state = RouteState::New; } m_routes.clear (); } -int Bot::searchAimingPoint (const Vector &to) { - // return the most distant waypoint which is seen from the bot to the target and is within count +int Bot::findAimingNode (const Vector &to) { + // return the most distant node which is seen from the bot to the target and is within count - if (m_currentWaypointIndex == INVALID_WAYPOINT_INDEX) { - m_currentWaypointIndex = changePointIndex (getNearestPoint ()); + if (m_currentNodeIndex == kInvalidNodeIndex) { + m_currentNodeIndex = changePointIndex (findNearestNode ()); } - int destIndex = waypoints.getNearest (to); - int bestIndex = m_currentWaypointIndex; + int destIndex = graph.getNearest (to); + int bestIndex = m_currentNodeIndex; - if (destIndex == INVALID_WAYPOINT_INDEX) { - return INVALID_WAYPOINT_INDEX; + if (destIndex == kInvalidNodeIndex) { + return kInvalidNodeIndex; } - while (destIndex != m_currentWaypointIndex) { - destIndex = (waypoints.m_matrix + (destIndex * waypoints.length ()) + m_currentWaypointIndex)->index; + while (destIndex != m_currentNodeIndex) { + destIndex = (graph.m_matrix.data () + (destIndex * graph.length ()) + m_currentNodeIndex)->index; if (destIndex < 0) { break; } - if (waypoints.isVisible (m_currentWaypointIndex, destIndex) && waypoints.isVisible (destIndex, m_currentWaypointIndex)) { + if (graph.isVisible (m_currentNodeIndex, destIndex) && graph.isVisible (destIndex, m_currentNodeIndex)) { bestIndex = destIndex; break; } } - if (bestIndex == m_currentWaypointIndex) { - return INVALID_WAYPOINT_INDEX; + if (bestIndex == m_currentNodeIndex) { + return kInvalidNodeIndex; } return bestIndex; } -bool Bot::searchOptimalPoint (void) { - // this function find a waypoint in the near of the bot if bot had lost his path of pathfinder needs +bool Bot::findBestNearestNode () { + // this function find a node in the near of the bot if bot had lost his path of pathfinder needs // to be restarted over again. - int busy = INVALID_WAYPOINT_INDEX; + int busy = kInvalidNodeIndex; - float lessDist[3] = { 99999.0f, 99999.0f , 99999.0f }; - int lessIndex[3] = { INVALID_WAYPOINT_INDEX, INVALID_WAYPOINT_INDEX , INVALID_WAYPOINT_INDEX }; + float lessDist[3] = { 9999.0f, 9999.0f , 9999.0f }; + int lessIndex[3] = { kInvalidNodeIndex, kInvalidNodeIndex , kInvalidNodeIndex }; - auto &bucket = waypoints.getWaypointsInBucket (pev->origin); - int numToSkip = cr::clamp (rng.getInt (0, static_cast (bucket.length () - 1)), 0, 5); + auto &bucket = graph.getNodesInBucket (pev->origin); + int numToSkip = cr::clamp (rg.int_ (0, static_cast (bucket.length () - 1)), 0, 5); for (const int at : bucket) { - bool skip = !!(at == m_currentWaypointIndex); + bool skip = !!(at == m_currentNodeIndex); - // skip the current waypoint, if any + // skip the current node, if any if (skip) { continue; } - // skip current and recent previous waypoints - for (int j = 0; j < numToSkip; j++) { + // skip current and recent previous nodes + for (int j = 0; j < numToSkip; ++j) { if (at == m_prevWptIndex[j]) { skip = true; break; } } - // skip waypoint from recent list + // skip node from recent list if (skip) { continue; } - // cts with hostages should not pick waypoints with no hostage flag - if (game.mapIs (MAP_CS) && m_team == TEAM_COUNTER && (waypoints[at].flags & FLAG_NOHOSTAGE) && hasHostage ()) { + // cts with hostages should not pick nodes with no hostage flag + if (game.mapIs (MapFlags::HostageRescue) && m_team == Team::CT && (graph[at].flags & NodeFlag::NoHostage) && hasHostage ()) { continue; } - // ignore non-reacheable waypoints... - if (!waypoints.isReachable (this, at)) { + // ignore non-reacheable nodes... + if (!graph.isReachable (this, at)) { continue; } - // check if waypoint is already used by another bot... + // check if node is already used by another bot... if (bots.getRoundStartTime () + 5.0f > game.timebase () && isOccupiedPoint (at)) { busy = at; continue; } - // if we're still here, find some close waypoints - float distance = (pev->origin - waypoints[at].origin).lengthSq (); + // if we're still here, find some close nodes + float distance = (pev->origin - graph[at].origin).lengthSq (); if (distance < lessDist[0]) { lessDist[2] = lessDist[1]; @@ -1634,56 +1604,54 @@ bool Bot::searchOptimalPoint (void) { lessIndex[2] = at; } } - int selected = INVALID_WAYPOINT_INDEX; + int selected = kInvalidNodeIndex; // now pick random one from choosen int index = 0; // choice from found - if (lessIndex[2] != INVALID_WAYPOINT_INDEX) { - index = rng.getInt (0, 2); + if (lessIndex[2] != kInvalidNodeIndex) { + index = rg.int_ (0, 2); } - else if (lessIndex[1] != INVALID_WAYPOINT_INDEX) { - index = rng.getInt (0, 1); + else if (lessIndex[1] != kInvalidNodeIndex) { + index = rg.int_ (0, 1); } - else if (lessIndex[0] != INVALID_WAYPOINT_INDEX) { + else if (lessIndex[0] != kInvalidNodeIndex) { index = 0; } selected = lessIndex[index]; - // if we're still have no waypoint and have busy one (by other bot) pick it up - if (selected == INVALID_WAYPOINT_INDEX && busy != INVALID_WAYPOINT_INDEX) { + // if we're still have no node and have busy one (by other bot) pick it up + if (selected == kInvalidNodeIndex && busy != kInvalidNodeIndex) { selected = busy; } // worst case... find atleast something - if (selected == INVALID_WAYPOINT_INDEX) { - selected = getNearestPoint (); + if (selected == kInvalidNodeIndex) { + selected = findNearestNode (); } - - ignoreCollision (); changePointIndex (selected); return true; } -float Bot::getReachTime (void) { - auto task = taskId (); +float Bot::getReachTime () { + auto task = getCurrentTaskId (); float estimatedTime = 0.0f; switch (task) { - case TASK_PAUSE: - case TASK_CAMP: - case TASK_HIDE: + case Task::Pause: + case Task::Camp: + case Task::Hide: return 0.0f; default: - estimatedTime = 2.8f; // time to reach next waypoint + estimatedTime = 2.8f; // time to reach next node } - // calculate 'real' time that we need to get from one waypoint to another - if (waypoints.exists (m_currentWaypointIndex) && waypoints.exists (m_prevWptIndex[0])) { - float distance = (waypoints[m_prevWptIndex[0]].origin - waypoints[m_currentWaypointIndex].origin).length (); + // calculate 'real' time that we need to get from one node to another + if (graph.exists (m_currentNodeIndex) && graph.exists (m_prevWptIndex[0])) { + float distance = (graph[m_prevWptIndex[0]].origin - graph[m_currentNodeIndex].origin).length (); // caclulate estimated time if (pev->maxspeed <= 0.0f) { @@ -1692,9 +1660,9 @@ float Bot::getReachTime (void) { else { estimatedTime = 3.0f * distance / pev->maxspeed; } - bool longTermReachability = (m_currentPath->flags & FLAG_CROUCH) || (m_currentPath->flags & FLAG_LADDER) || (pev->button & IN_DUCK) || (m_oldButtons & IN_DUCK); + bool longTermReachability = (m_path->flags & NodeFlag::Crouch) || (m_path->flags & NodeFlag::Ladder) || (pev->button & IN_DUCK) || (m_oldButtons & IN_DUCK); - // check for special waypoints, that can slowdown our movement + // check for special nodes, that can slowdown our movement if (longTermReachability) { estimatedTime *= 2.0f; } @@ -1703,145 +1671,144 @@ float Bot::getReachTime (void) { return estimatedTime; } -void Bot::getValidPoint (void) { - // checks if the last waypoint the bot was heading for is still valid +void Bot::findValidNode () { + // checks if the last node the bot was heading for is still valid - auto rechoiceGoal = [&] (void) { + auto trySelectNewGoal = [&] () { if (m_rechoiceGoalCount > 1) { - int newGoal = searchGoal (); + int newGoal = findBestGoal (); m_prevGoalIndex = newGoal; m_chosenGoalIndex = newGoal; getTask ()->data = newGoal; - // do path finding if it's not the current waypoint - if (newGoal != m_currentWaypointIndex) { - searchPath (m_currentWaypointIndex, newGoal, m_pathType); + // do path finding if it's not the current node + if (newGoal != m_currentNodeIndex) { + findPath (m_currentNodeIndex, newGoal, m_pathType); } m_rechoiceGoalCount = 0; } else { - searchOptimalPoint (); + findBestNearestNode (); m_rechoiceGoalCount++; } }; - // if bot hasn't got a waypoint we need a new one anyway or if time to get there expired get new one as well - if (m_currentWaypointIndex == INVALID_WAYPOINT_INDEX) { + // if bot hasn't got a node we need a new one anyway or if time to get there expired get new one as well + if (m_currentNodeIndex == kInvalidNodeIndex) { clearSearchNodes (); - rechoiceGoal (); + trySelectNewGoal (); - m_waypointOrigin = m_currentPath->origin; + m_pathOrigin = m_path->origin; } else if (m_navTimeset + getReachTime () < game.timebase () && game.isNullEntity (m_enemy)) { - auto experience = waypoints.getRawExperience (); // increase danager for both teams - for (int team = TEAM_TERRORIST; team < MAX_TEAM_COUNT; team++) { + for (int team = Team::Terrorist; team < kGameTeamNum; ++team) { - int damageValue = waypoints.getDangerDamage (team, m_currentWaypointIndex, m_currentWaypointIndex); - damageValue = cr::clamp (damageValue + 100, 0, MAX_DAMAGE_VALUE); + int damageValue = graph.getDangerDamage (team, m_currentNodeIndex, m_currentNodeIndex); + damageValue = cr::clamp (damageValue + 100, 0, kMaxPracticeDamageValue); - // affect nearby connected with victim waypoints - 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); + // affect nearby connected with victim nodes + for (auto &neighbour : m_path->links) { + if (graph.exists (neighbour.index)) { + int neighbourValue = graph.getDangerDamage (team, neighbour.index, neighbour.index); + neighbourValue = cr::clamp (neighbourValue + 100, 0, kMaxPracticeDamageValue); - (experience + (neighbour * waypoints.length ()) + neighbour)->damage[team] = neighbourValue; + graph.setDangerDamage (m_team, neighbour.index, neighbour.index, neighbourValue); } } - (experience + (m_currentWaypointIndex * waypoints.length ()) + m_currentWaypointIndex)->damage[team] = damageValue; + graph.setDangerDamage (m_team, m_currentNodeIndex, m_currentNodeIndex, damageValue); } clearSearchNodes (); - rechoiceGoal (); + trySelectNewGoal (); - m_waypointOrigin = m_currentPath->origin; + m_pathOrigin = m_path->origin; } } int Bot::changePointIndex (int index) { - if (index == INVALID_WAYPOINT_INDEX) { + if (index == kInvalidNodeIndex) { return 0; } m_prevWptIndex[4] = m_prevWptIndex[3]; m_prevWptIndex[3] = m_prevWptIndex[2]; m_prevWptIndex[2] = m_prevWptIndex[1]; - m_prevWptIndex[0] = m_currentWaypointIndex; + m_prevWptIndex[0] = m_currentNodeIndex; - m_currentWaypointIndex = index; + m_currentNodeIndex = index; m_navTimeset = game.timebase (); - m_currentPath = &waypoints[index]; - m_waypointFlags = m_currentPath->flags; + m_path = &graph[index]; + m_pathFlags = m_path->flags; - return m_currentWaypointIndex; // to satisfy static-code analyzers + return m_currentNodeIndex; // to satisfy static-code analyzers } -int Bot::getNearestPoint (void) { - // get the current nearest waypoint to bot with visibility checks +int Bot::findNearestNode () { + // get the current nearest node to bot with visibility checks - int index = INVALID_WAYPOINT_INDEX; + int index = kInvalidNodeIndex; float minimum = cr::square (1024.0f); - auto &bucket = waypoints.getWaypointsInBucket (pev->origin); + auto &bucket = graph.getNodesInBucket (pev->origin); for (const auto at : bucket) { - if (at == m_currentWaypointIndex) { + if (at == m_currentNodeIndex) { continue; } - float distance = (waypoints[at].origin - pev->origin).lengthSq (); + float distance = (graph[at].origin - pev->origin).lengthSq (); if (distance < minimum) { - // if bot doing navigation, make sure waypoint really visible and not too high - if ((m_currentWaypointIndex != INVALID_WAYPOINT_INDEX && waypoints.isVisible (m_currentWaypointIndex, at)) || waypoints.isReachable (this, at)) { + // if bot doing navigation, make sure node really visible and not too high + if ((m_currentNodeIndex != kInvalidNodeIndex && graph.isVisible (m_currentNodeIndex, at)) || graph.isReachable (this, at)) { index = at; minimum = distance; } } } - // worst case, take any waypoint... - if (index == INVALID_WAYPOINT_INDEX) { - index = waypoints.getNearestNoBuckets (pev->origin); + // worst case, take any node... + if (index == kInvalidNodeIndex) { + index = graph.getNearestNoBuckets (pev->origin); } return index; } -int Bot::getBombPoint (void) { - // this function finds the best goal (bomb) waypoint for CTs when searching for a planted bomb. +int Bot::findBombNode () { + // this function finds the best goal (bomb) node for CTs when searching for a planted bomb. - auto &goals = waypoints.m_goalPoints; + auto &goals = graph.m_goalPoints; - auto bomb = waypoints.getBombPos (); + auto bomb = graph.getBombPos (); auto audible = isBombAudible (); if (!audible.empty ()) { m_bombSearchOverridden = true; - return waypoints.getNearest (audible, 240.0f); + return graph.getNearest (audible, 240.0f); } else if (goals.empty ()) { - return waypoints.getNearest (bomb, 240.0f, FLAG_GOAL); // reliability check + return graph.getNearest (bomb, 240.0f, NodeFlag::Goal); // reliability check } - // take the nearest to bomb waypoints instead of goal if close enough + // take the nearest to bomb nodes instead of goal if close enough else if ((pev->origin - bomb).lengthSq () < cr::square (512.0f)) { - int waypoint = waypoints.getNearest (bomb, 240.0f); + int node = graph.getNearest (bomb, 240.0f); m_bombSearchOverridden = true; - if (waypoint != INVALID_WAYPOINT_INDEX) { - return waypoint; + if (node != kInvalidNodeIndex) { + return node; } } int goal = 0, count = 0; float lastDistance = 999999.0f; - // find nearest goal waypoint either to bomb (if "heard" or player) + // find nearest goal node either to bomb (if "heard" or player) for (auto &point : goals) { - float distance = (waypoints[point].origin - bomb).lengthSq (); + float distance = (graph[point].origin - bomb).lengthSq (); // check if we got more close distance if (distance < lastDistance) { @@ -1850,7 +1817,7 @@ int Bot::getBombPoint (void) { } } - while (waypoints.isVisited (goal)) { + while (graph.isVisited (goal)) { goal = goals.random (); if (count++ >= static_cast (goals.length ())) { @@ -1860,118 +1827,111 @@ int Bot::getBombPoint (void) { return goal; } -int Bot::getDefendPoint (const Vector &origin) { +int Bot::findDefendNode (const Vector &origin) { // this function tries to find a good position which has a line of sight to a position, // provides enough cover point, and is far away from the defending position - if (m_currentWaypointIndex == INVALID_WAYPOINT_INDEX) { - m_currentWaypointIndex = changePointIndex (getNearestPoint ()); + if (m_currentNodeIndex == kInvalidNodeIndex) { + m_currentNodeIndex = changePointIndex (findNearestNode ()); } TraceResult tr; - int waypointIndex[MAX_PATH_INDEX]; - int minDistance[MAX_PATH_INDEX]; + int nodeIndex[kMaxNodeLinks]; + int minDistance[kMaxNodeLinks]; - for (int i = 0; i < MAX_PATH_INDEX; i++) { - waypointIndex[i] = INVALID_WAYPOINT_INDEX; + for (int i = 0; i < kMaxNodeLinks; ++i) { + nodeIndex[i] = kInvalidNodeIndex; minDistance[i] = 128; } - int posIndex = waypoints.getNearest (origin); - int srcIndex = m_currentWaypointIndex; + int posIndex = graph.getNearest (origin); + int srcIndex = m_currentNodeIndex; // some of points not found, return random one - if (srcIndex == INVALID_WAYPOINT_INDEX || posIndex == INVALID_WAYPOINT_INDEX) { - return rng.getInt (0, waypoints.length () - 1); + if (srcIndex == kInvalidNodeIndex || posIndex == kInvalidNodeIndex) { + return rg.int_ (0, graph.length () - 1); } - // find the best waypoint now - for (int i = 0; i < waypoints.length (); i++) { - // exclude ladder & current waypoints - if ((waypoints[i].flags & FLAG_LADDER) || i == srcIndex || !waypoints.isVisible (i, posIndex) || isOccupiedPoint (i)) { + // find the best node now + for (int i = 0; i < graph.length (); ++i) { + // exclude ladder & current nodes + if ((graph[i].flags & NodeFlag::Ladder) || i == srcIndex || !graph.isVisible (i, posIndex) || isOccupiedPoint (i)) { continue; } // use the 'real' pathfinding distances - int distance = waypoints.getPathDist (srcIndex, i); + int distance = graph.getPathDist (srcIndex, i); // skip wayponts with distance more than 512 units if (distance > 512) { continue; } - game.testLine (waypoints[i].origin, waypoints[posIndex].origin, TRACE_IGNORE_EVERYTHING, ent (), &tr); + game.testLine (graph[i].origin, graph[posIndex].origin, TraceIgnore::Everything, ent (), &tr); // check if line not hit anything if (tr.flFraction != 1.0f) { continue; } - for (int j = 0; j < MAX_PATH_INDEX; j++) { + for (int j = 0; j < kMaxNodeLinks; ++j) { if (distance > minDistance[j]) { - waypointIndex[j] = i; + nodeIndex[j] = i; minDistance[j] = distance; } } } // use statistic if we have them - for (int i = 0; i < MAX_PATH_INDEX; i++) { - if (waypointIndex[i] != INVALID_WAYPOINT_INDEX) { - int experience = waypoints.getDangerDamage (m_team, waypointIndex[i], waypointIndex[i]); - experience = (experience * 100) / waypoints.getHighestDamageForTeam (m_team); + for (int i = 0; i < kMaxNodeLinks; ++i) { + if (nodeIndex[i] != kInvalidNodeIndex) { + int practice = graph.getDangerDamage (m_team, nodeIndex[i], nodeIndex[i]); + practice = (practice * 100) / graph.getHighestDamageForTeam (m_team); - minDistance[i] = (experience * 100) / 8192; - minDistance[i] += experience; + minDistance[i] = (practice * 100) / 8192; + minDistance[i] += practice; } } bool sorting = false; - // sort results waypoints for farest distance + // sort results nodes for farest distance do { 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++) { - int index = waypointIndex[i]; - - waypointIndex[i] = waypointIndex[i + 1]; - waypointIndex[i + 1] = index; - - index = minDistance[i]; - - minDistance[i] = minDistance[i + 1]; - minDistance[i + 1] = index; + for (int i = 0; i < 3 && nodeIndex[i] != kInvalidNodeIndex && nodeIndex[i + 1] != kInvalidNodeIndex && minDistance[i] > minDistance[i + 1]; ++i) { + cr::swap (nodeIndex[i], nodeIndex[i + 1]); + cr::swap (minDistance[i], minDistance[i + 1]); sorting = true; } } while (sorting); - if (waypointIndex[0] == INVALID_WAYPOINT_INDEX) { + if (nodeIndex[0] == kInvalidNodeIndex) { IntArray found; - for (int i = 0; i < waypoints.length (); i++) { - if ((waypoints[i].origin - origin).lengthSq () <= cr::square (1248.0f) && !waypoints.isVisible (i, posIndex) && !isOccupiedPoint (i)) { + for (int i = 0; i < graph.length (); ++i) { + if ((graph[i].origin - origin).lengthSq () <= cr::square (1248.0f) && !graph.isVisible (i, posIndex) && !isOccupiedPoint (i)) { found.push (i); } } if (found.empty ()) { - return rng.getInt (0, waypoints.length () - 1); // most worst case, since there a evil error in waypoints + return rg.int_ (0, graph.length () - 1); // most worst case, since there a evil error in nodes } return found.random (); } int index = 0; - for (; index < MAX_PATH_INDEX; index++) { - if (waypointIndex[index] == INVALID_WAYPOINT_INDEX) { + for (; index < kMaxNodeLinks; ++index) { + if (nodeIndex[index] == kInvalidNodeIndex) { break; } } - return waypointIndex[rng.getInt (0, static_cast ((index - 1) * 0.5f))]; + return nodeIndex[rg.int_ (0, static_cast ((index - 1) * 0.5f))]; } -int Bot::getCoverPoint (float maxDistance) { - // this function tries to find a good cover waypoint if bot wants to hide +int Bot::findCoverNode (float maxDistance) { + // this function tries to find a good cover node if bot wants to hide const float enemyMax = (m_lastEnemyOrigin - pev->origin).length (); @@ -1984,44 +1944,43 @@ int Bot::getCoverPoint (float maxDistance) { maxDistance = 300.0f; } - int srcIndex = m_currentWaypointIndex; - int enemyIndex = waypoints.getNearest (m_lastEnemyOrigin); + int srcIndex = m_currentNodeIndex; + int enemyIndex = graph.getNearest (m_lastEnemyOrigin); IntArray enemies; - enemies.reserve (MAX_ENGINE_PLAYERS); - int waypointIndex[MAX_PATH_INDEX]; - int minDistance[MAX_PATH_INDEX]; + int nodeIndex[kMaxNodeLinks]; + int minDistance[kMaxNodeLinks]; - for (int i = 0; i < MAX_PATH_INDEX; i++) { - waypointIndex[i] = INVALID_WAYPOINT_INDEX; + for (int i = 0; i < kMaxNodeLinks; ++i) { + nodeIndex[i] = kInvalidNodeIndex; minDistance[i] = static_cast (maxDistance); } - if (enemyIndex == INVALID_WAYPOINT_INDEX) { - return INVALID_WAYPOINT_INDEX; + if (enemyIndex == kInvalidNodeIndex) { + return kInvalidNodeIndex; } // now get enemies neigbouring points - for (auto &index : waypoints[enemyIndex].index) { - if (index != INVALID_WAYPOINT_INDEX) { - enemies.push (index); + for (auto &link : graph[enemyIndex].links) { + if (link.index != kInvalidNodeIndex) { + enemies.push (link.index); } } // ensure we're on valid point changePointIndex (srcIndex); - // find the best waypoint now - for (int i = 0; i < waypoints.length (); i++) { - // exclude ladder, current waypoints and waypoints seen by the enemy - if ((waypoints[i].flags & FLAG_LADDER) || i == srcIndex || waypoints.isVisible (enemyIndex, i)) { + // find the best node now + for (int i = 0; i < graph.length (); ++i) { + // exclude ladder, current node and nodes seen by the enemy + if ((graph[i].flags & NodeFlag::Ladder) || i == srcIndex || graph.isVisible (enemyIndex, i)) { continue; } - bool neighbourVisible = false; // now check neighbour waypoints for visibility + bool neighbourVisible = false; // now check neighbour nodes for visibility for (auto &enemy : enemies) { - if (waypoints.isVisible (enemy, i)) { + if (graph.isVisible (enemy, i)) { neighbourVisible = true; break; } @@ -2033,16 +1992,16 @@ int Bot::getCoverPoint (float maxDistance) { } // use the 'real' pathfinding distances - int distances = waypoints.getPathDist (srcIndex, i); - int enemyDistance = waypoints.getPathDist (enemyIndex, i); + int distances = graph.getPathDist (srcIndex, i); + int enemyDistance = graph.getPathDist (enemyIndex, i); if (distances >= enemyDistance) { continue; } - for (int j = 0; j < MAX_PATH_INDEX; j++) { + for (int j = 0; j < kMaxNodeLinks; ++j) { if (distances < minDistance[j]) { - waypointIndex[j] = i; + nodeIndex[j] = i; minDistance[j] = distances; break; @@ -2051,30 +2010,24 @@ 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) { - int experience = waypoints.getDangerDamage (m_team, waypointIndex[i], waypointIndex[i]); - experience = (experience * 100) / MAX_DAMAGE_VALUE; + for (int i = 0; i < kMaxNodeLinks; ++i) { + if (nodeIndex[i] != kInvalidNodeIndex) { + int practice = graph.getDangerDamage (m_team, nodeIndex[i], nodeIndex[i]); + practice = (practice * 100) / kMaxPracticeDamageValue; - minDistance[i] = (experience * 100) / 8192; - minDistance[i] += experience; + minDistance[i] = (practice * 100) / 8192; + minDistance[i] += practice; } } bool sorting; - // sort resulting waypoints for nearest distance + // sort resulting nodes for nearest distance do { 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]; - - waypointIndex[i] = waypointIndex[i + 1]; - waypointIndex[i + 1] = index; - - index = minDistance[i]; - minDistance[i] = minDistance[i + 1]; - minDistance[i + 1] = index; + for (int i = 0; i < 3 && nodeIndex[i] != kInvalidNodeIndex && nodeIndex[i + 1] != kInvalidNodeIndex && minDistance[i] > minDistance[i + 1]; ++i) { + cr::swap (nodeIndex[i], nodeIndex[i + 1]); + cr::swap (minDistance[i], minDistance[i + 1]); sorting = true; } @@ -2083,9 +2036,9 @@ int Bot::getCoverPoint (float maxDistance) { TraceResult tr; // take the first one which isn't spotted by the enemy - 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); + for (auto &index : nodeIndex) { + if (index != kInvalidNodeIndex) { + game.testLine (m_lastEnemyOrigin + Vector (0.0f, 0.0f, 36.0f), graph[index].origin, TraceIgnore::Everything, ent (), &tr); if (tr.flFraction < 1.0f) { return index; @@ -2094,33 +2047,33 @@ int Bot::getCoverPoint (float maxDistance) { } // if all are seen by the enemy, take the first one - if (waypointIndex[0] != INVALID_WAYPOINT_INDEX) { - return waypointIndex[0]; + if (nodeIndex[0] != kInvalidNodeIndex) { + return nodeIndex[0]; } - return INVALID_WAYPOINT_INDEX; // do not use random points + return kInvalidNodeIndex; // do not use random points } -bool Bot::getNextBestPoint (void) { - // this function does a realtime post processing of waypoints return from the - // pathfinder, to vary paths and find the best waypoint on our way +bool Bot::selectBestNextNode () { + // this function does a realtime post processing of nodes return from the + // pathfinder, to vary paths and find the best node on our way - assert (!m_path.empty ()); - assert (m_path.hasNext ()); + assert (!m_pathWalk.empty ()); + assert (m_pathWalk.hasNext ()); - if (!isOccupiedPoint (m_path.first ())) { + if (!isOccupiedPoint (m_pathWalk.first ())) { return false; } - for (auto &index : m_currentPath->index) { - if (index != INVALID_WAYPOINT_INDEX && waypoints.isConnected (index, m_path.next ()) && waypoints.isConnected (m_currentWaypointIndex, index)) { + for (auto &link : m_path->links) { + if (link.index != kInvalidNodeIndex && graph.isConnected (link.index, m_pathWalk.next ()) && graph.isConnected (m_currentNodeIndex, link.index)) { - // don't use ladder waypoints as alternative - if (waypoints[index].flags & FLAG_LADDER) { + // don't use ladder nodes as alternative + if (graph[link.index].flags & NodeFlag::Ladder) { continue; } - if (!isOccupiedPoint (index)) { - m_path.first () = index; + if (!isOccupiedPoint (link.index)) { + m_pathWalk.first () = link.index; return true; } } @@ -2128,40 +2081,40 @@ bool Bot::getNextBestPoint (void) { return false; } -bool Bot::advanceMovement (void) { +bool Bot::advanceMovement () { // advances in our pathfinding list and sets the appropiate destination origins for this bot - getValidPoint (); // check if old waypoints is still reliable + findValidNode (); // check if old nodes is still reliable - // no waypoints from pathfinding? - if (m_path.empty ()) { + // no nodes from pathfinding? + if (m_pathWalk.empty ()) { return false; } TraceResult tr; - m_path.shift (); // advance in list + m_pathWalk.shift (); // advance in list m_currentTravelFlags = 0; // reset travel flags (jumping etc) // we're not at the end of the list? - if (!m_path.empty ()) { - // if in between a route, postprocess the waypoint (find better alternatives)... - if (m_path.hasNext () && m_path.first () != m_path.back ()) { - getNextBestPoint (); + if (!m_pathWalk.empty ()) { + // if in between a route, postprocess the node (find better alternatives)... + if (m_pathWalk.hasNext () && m_pathWalk.first () != m_pathWalk.last ()) { + selectBestNextNode (); m_minSpeed = pev->maxspeed; - TaskID taskID = taskId (); + Task taskID = getCurrentTaskId (); // only if we in normal task and bomb is not planted - if (taskID == TASK_NORMAL && bots.getRoundMidTime () + 5.0f < game.timebase () && m_timeCamping + 5.0f < game.timebase () && !bots.isBombPlanted () && m_personality != PERSONALITY_RUSHER && !m_hasC4 && !m_isVIP && m_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 == kInvalidNodeIndex && !hasHostage ()) { m_campButtons = 0; - const int nextIndex = m_path.next (); - auto kills = static_cast (waypoints.getDangerDamage (m_team, nextIndex, nextIndex)); + const int nextIndex = m_pathWalk.next (); + auto kills = static_cast (graph.getDangerDamage (m_team, nextIndex, nextIndex)); // if damage done higher than one if (kills > 1.0f && bots.getRoundMidTime () > game.timebase ()) { switch (m_personality) { - case PERSONALITY_NORMAL: + case Personality::Normal: kills *= 0.33f; break; @@ -2171,22 +2124,22 @@ bool Bot::advanceMovement (void) { } if (m_baseAgressionLevel < kills && hasPrimaryWeapon ()) { - 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); + startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, game.timebase () + rg.float_ (m_difficulty * 0.5f, static_cast (m_difficulty)) * 5.0f, true); + startTask (Task::MoveToPosition, TaskPri::MoveToPosition, findDefendNode (graph[nextIndex].origin), game.timebase () + rg.float_ (3.0f, 10.0f), true); } } else if (bots.canPause () && !isOnLadder () && !isInWater () && !m_currentTravelFlags && isOnFloor ()) { if (static_cast (kills) == m_baseAgressionLevel) { m_campButtons |= IN_DUCK; } - else if (rng.chance (m_difficulty * 25)) { + else if (rg.chance (m_difficulty * 25)) { m_minSpeed = getShiftSpeed (); } } // force terrorist bot to plant bomb if (m_inBombZone && !m_hasProgressBar && m_hasC4) { - int newGoal = searchGoal (); + int newGoal = findBestGoal (); m_prevGoalIndex = newGoal; m_chosenGoalIndex = newGoal; @@ -2194,30 +2147,29 @@ bool Bot::advanceMovement (void) { // remember index getTask ()->data = newGoal; - // do path finding if it's not the current waypoint - if (newGoal != m_currentWaypointIndex) { - searchPath (m_currentWaypointIndex, newGoal, m_pathType); + // do path finding if it's not the current node + if (newGoal != m_currentNodeIndex) { + findPath (m_currentNodeIndex, newGoal, m_pathType); } return false; } } } - if (!m_path.empty ()) { - const int destIndex = m_path.first (); + if (!m_pathWalk.empty ()) { + const int destIndex = m_pathWalk.first (); // find out about connection flags - if (m_currentWaypointIndex != INVALID_WAYPOINT_INDEX) { - for (int i = 0; i < MAX_PATH_INDEX; i++) { - if (m_currentPath->index[i] == destIndex) { - m_currentTravelFlags = m_currentPath->connectionFlags[i]; - m_desiredVelocity = m_currentPath->connectionVelocity[i]; + if (m_currentNodeIndex != kInvalidNodeIndex) { + for (const auto &link : m_path->links) { + if (link.index == destIndex) { + m_currentTravelFlags = link.flags; + m_desiredVelocity = link.velocity; m_jumpFinished = false; break; } } - // check if bot is going to jump bool willJump = false; float jumpDistance = 0.0f; @@ -2226,18 +2178,18 @@ bool Bot::advanceMovement (void) { Vector dst; // try to find out about future connection flags - if (m_path.hasNext ()) { - for (int i = 0; i < MAX_PATH_INDEX; i++) { - const int nextIndex = m_path.next (); + if (m_pathWalk.hasNext ()) { + auto nextIndex = m_pathWalk.next (); - Path &path = waypoints[destIndex]; - Path &next = waypoints[nextIndex]; + Path &path = graph[destIndex]; + Path &next = graph[nextIndex]; - if (path.index[i] == nextIndex && (path.connectionFlags[i] & PATHFLAG_JUMP)) { + for (const auto &link : path.links) { + if (link.index == nextIndex && (link.flags & PathFlag::Jump)) { src = path.origin; dst = next.origin; - jumpDistance = (path.origin - next.origin).length (); + jumpDistance = (src - dst).length (); willJump = true; break; @@ -2245,20 +2197,19 @@ bool Bot::advanceMovement (void) { } } - // is there a jump waypoint right ahead and do we need to draw out the light weapon ? - if (willJump && m_currentWeapon != WEAPON_KNIFE && m_currentWeapon != WEAPON_SCOUT && !m_isReloading && !usesPistol () && (jumpDistance > 200.0f || (dst.z - 32.0f > src.z && jumpDistance > 150.0f)) && !(m_states & (STATE_SEEING_ENEMY | STATE_SUSPECT_ENEMY))) { + // is there a jump node right ahead and do we need to draw out the light weapon ? + if (willJump && m_currentWeapon != Weapon::Knife && m_currentWeapon != Weapon::Scout && !m_isReloading && !usesPistol () && (jumpDistance > 200.0f || (dst.z - 32.0f > src.z && jumpDistance > 150.0f)) && !(m_states & (Sense::SeeingEnemy | Sense::SuspectEnemy))) { selectWeaponByName ("weapon_knife"); // draw out the knife if we needed } // 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 < game.maxClients (); c++) { - Bot *otherBot = bots.getBot (c); + if ((graph[destIndex].flags & NodeFlag::Ladder) && !isOnLadder ()) { + // get ladder nodes used by other (first moving) bots + for (const auto &other : bots) { // 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, game.timebase () + 3.0f, false); + if (other.get () != this && other->m_notKilled && other->m_currentNodeIndex == destIndex) { + startTask (Task::Pause, TaskPri::Pause, kInvalidNodeIndex, game.timebase () + 3.0f, false); return true; } } @@ -2267,19 +2218,19 @@ bool Bot::advanceMovement (void) { changePointIndex (destIndex); } } - m_waypointOrigin = m_currentPath->origin; + m_pathOrigin = m_path->origin; // if wayzone radius non zero vary origin a bit depending on the body angles - if (m_currentPath->radius > 0.0f) { - 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 (m_path->radius > 0.0f) { + game.makeVectors (Vector (pev->angles.x, cr::normalizeAngles (pev->angles.y + rg.float_ (-90.0f, 90.0f)), 0.0f)); + m_pathOrigin = m_pathOrigin + game.vec.forward * rg.float_ (0.0f, m_path->radius); } if (isOnLadder ()) { - game.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_pathOrigin, TraceIgnore::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_pathOrigin = m_pathOrigin + (pev->origin - m_pathOrigin) * 0.5f + Vector (0.0f, 0.0f, 32.0f); } } m_navTimeset = game.timebase (); @@ -2299,18 +2250,18 @@ bool Bot::cantMoveForward (const Vector &normal, TraceResult *tr) { game.makeVectors (Vector (0.0f, pev->angles.y, 0.0f)); auto checkDoor = [] (TraceResult *tr) { - if (!game.mapIs (MAP_HAS_DOORS)) { + if (!game.mapIs (MapFlags::HasDoors)) { 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... - game.testLine (src, forward, TRACE_IGNORE_MONSTERS, ent (), tr); + game.testLine (src, forward, TraceIgnore::Monsters, ent (), tr); // check if the trace hit something... if (tr->flFraction < 1.0f) { - if (game.mapIs (MAP_HAS_DOORS) && strncmp ("func_door", STRING (tr->pHit->v.classname), 9) == 0) { + if (game.mapIs (MapFlags::HasDoors) && strncmp ("func_door", STRING (tr->pHit->v.classname), 9) == 0) { return false; } return true; // bot's head will hit something @@ -2321,7 +2272,7 @@ bool Bot::cantMoveForward (const Vector &normal, TraceResult *tr) { 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; - game.testLine (src, forward, TRACE_IGNORE_MONSTERS, ent (), tr); + game.testLine (src, forward, TraceIgnore::Monsters, ent (), tr); // check if the trace hit something... if (checkDoor (tr)) { @@ -2333,7 +2284,7 @@ bool Bot::cantMoveForward (const Vector &normal, TraceResult *tr) { 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; - game.testLine (src, forward, TRACE_IGNORE_MONSTERS, ent (), tr); + game.testLine (src, forward, TraceIgnore::Monsters, ent (), tr); // check if the trace hit something... if (checkDoor (tr)) { @@ -2345,7 +2296,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; - game.testLine (src, forward, TRACE_IGNORE_MONSTERS, ent (), tr); + game.testLine (src, forward, TraceIgnore::Monsters, ent (), tr); // check if the trace hit something... if (checkDoor (tr)) { @@ -2354,7 +2305,7 @@ bool Bot::cantMoveForward (const Vector &normal, TraceResult *tr) { src = pev->origin; forward = src + normal * 24.0f; - game.testLine (src, forward, TRACE_IGNORE_MONSTERS, ent (), tr); + game.testLine (src, forward, TraceIgnore::Monsters, ent (), tr); // check if the trace hit something... if (checkDoor (tr)) { @@ -2367,7 +2318,7 @@ bool Bot::cantMoveForward (const Vector &normal, TraceResult *tr) { 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... - game.testLine (src, forward, TRACE_IGNORE_MONSTERS, ent (), tr); + game.testLine (src, forward, TraceIgnore::Monsters, ent (), tr); // check if the trace hit something... if (checkDoor (tr)) { @@ -2378,7 +2329,7 @@ bool Bot::cantMoveForward (const Vector &normal, TraceResult *tr) { 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; - game.testLine (src, forward, TRACE_IGNORE_MONSTERS, ent (), tr); + game.testLine (src, forward, TraceIgnore::Monsters, ent (), tr); // check if the trace hit something... if (checkDoor (tr)) { @@ -2466,7 +2417,7 @@ bool Bot::canJumpUp (const Vector &normal) { Vector dest = src + normal * 32.0f; // trace a line forward at maximum jump height... - game.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (src, dest, TraceIgnore::Monsters, ent (), &tr); if (tr.flFraction < 1.0f) { return doneCanJumpUp (normal); @@ -2476,7 +2427,7 @@ bool Bot::canJumpUp (const Vector &normal) { src = dest; dest.z = dest.z + 37.0f; - game.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (src, dest, TraceIgnore::Monsters, ent (), &tr); if (tr.flFraction < 1.0f) { return false; @@ -2488,7 +2439,7 @@ bool Bot::canJumpUp (const Vector &normal) { dest = src + normal * 32.0f; // trace a line forward at maximum jump height... - game.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (src, dest, TraceIgnore::Monsters, ent (), &tr); // if trace hit something, return false if (tr.flFraction < 1.0f) { @@ -2499,7 +2450,7 @@ bool Bot::canJumpUp (const Vector &normal) { src = dest; dest.z = dest.z + 37.0f; - game.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (src, dest, TraceIgnore::Monsters, ent (), &tr); // if trace hit something, return false if (tr.flFraction < 1.0f) { @@ -2511,7 +2462,7 @@ bool Bot::canJumpUp (const Vector &normal) { dest = src + normal * 32.0f; // trace a line forward at maximum jump height... - game.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (src, dest, TraceIgnore::Monsters, ent (), &tr); // if trace hit something, return false if (tr.flFraction < 1.0f) { @@ -2522,7 +2473,7 @@ bool Bot::canJumpUp (const Vector &normal) { src = dest; dest.z = dest.z + 37.0f; - game.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (src, dest, TraceIgnore::Monsters, ent (), &tr); // if trace hit something, return false return tr.flFraction > 1.0f; @@ -2536,7 +2487,7 @@ bool Bot::doneCanJumpUp (const Vector &normal) { TraceResult tr; // trace a line forward at maximum jump height... - game.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (src, dest, TraceIgnore::Monsters, ent (), &tr); if (tr.flFraction < 1.0f) { return false; @@ -2546,7 +2497,7 @@ bool Bot::doneCanJumpUp (const Vector &normal) { src = dest; dest.z = dest.z + 37.0f; - game.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (src, dest, TraceIgnore::Monsters, ent (), &tr); // if trace hit something, check duckjump if (tr.flFraction < 1.0f) { @@ -2559,7 +2510,7 @@ bool Bot::doneCanJumpUp (const Vector &normal) { dest = src + normal * 32.0f; // trace a line forward at maximum jump height... - game.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (src, dest, TraceIgnore::Monsters, ent (), &tr); // if trace hit something, return false if (tr.flFraction < 1.0f) { @@ -2570,7 +2521,7 @@ bool Bot::doneCanJumpUp (const Vector &normal) { src = dest; dest.z = dest.z + 37.0f; - game.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (src, dest, TraceIgnore::Monsters, ent (), &tr); // if trace hit something, return false if (tr.flFraction < 1.0f) { @@ -2582,7 +2533,7 @@ bool Bot::doneCanJumpUp (const Vector &normal) { dest = src + normal * 32.0f; // trace a line forward at maximum jump height... - game.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (src, dest, TraceIgnore::Monsters, ent (), &tr); // if trace hit something, return false if (tr.flFraction < 1.0f) { @@ -2593,7 +2544,7 @@ bool Bot::doneCanJumpUp (const Vector &normal) { src = dest; dest.z = dest.z + 37.0f; - game.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (src, dest, TraceIgnore::Monsters, ent (), &tr); // if trace hit something, return false return tr.flFraction > 1.0f; @@ -2620,7 +2571,7 @@ bool Bot::canDuckUnder (const Vector &normal) { Vector dest = src + normal * 32.0f; // trace a line forward at duck height... - game.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (src, dest, TraceIgnore::Monsters, ent (), &tr); // if trace hit something, return false if (tr.flFraction < 1.0f) { @@ -2632,7 +2583,7 @@ bool Bot::canDuckUnder (const Vector &normal) { dest = src + normal * 32.0f; // trace a line forward at duck height... - game.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (src, dest, TraceIgnore::Monsters, ent (), &tr); // if trace hit something, return false if (tr.flFraction < 1.0f) { @@ -2644,7 +2595,7 @@ bool Bot::canDuckUnder (const Vector &normal) { dest = src + normal * 32.0f; // trace a line forward at duck height... - game.testLine (src, dest, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (src, dest, TraceIgnore::Monsters, ent (), &tr); // if trace hit something, return false return tr.flFraction > 1.0f; @@ -2652,7 +2603,7 @@ bool Bot::canDuckUnder (const Vector &normal) { #ifdef DEAD_CODE -bool Bot::isBlockedLeft (void) { +bool Bot::isBlockedLeft () { TraceResult tr; int direction = 48; @@ -2662,16 +2613,16 @@ bool Bot::isBlockedLeft (void) { makeVectors (pev->angles); // do a trace to the left... - game.TestLine (pev->origin, game.vec.forward * direction - game.vec.right * 48.0f, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.TestLine (pev->origin, game.vec.forward * direction - game.vec.right * 48.0f, TraceIgnore::Monsters, ent (), &tr); // check if the trace hit something... - if (game.mapIs (MAP_HAS_DOORS) && tr.flFraction < 1.0f && strncmp ("func_door", STRING (tr.pHit->v.classname), 9) != 0) { + if (game.mapIs (MapFlags::HasDoors) && tr.flFraction < 1.0f && strncmp ("func_door", STRING (tr.pHit->v.classname), 9) != 0) { return true; // bot's body will hit something } return false; } -bool Bot::isBlockedRight (void) { +bool Bot::isBlockedRight () { TraceResult tr; int direction = 48; @@ -2681,10 +2632,10 @@ bool Bot::isBlockedRight (void) { makeVectors (pev->angles); // do a trace to the right... - game.TestLine (pev->origin, pev->origin + game.vec.forward * direction + game.vec.right * 48.0f, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.TestLine (pev->origin, pev->origin + game.vec.forward * direction + game.vec.right * 48.0f, TraceIgnore::Monsters, ent (), &tr); // check if the trace hit something... - if (game.mapIs (MAP_HAS_DOORS) && tr.flFraction < 1.0f && (strncmp ("func_door", STRING (tr.pHit->v.classname), 9) != 0)) { + if (game.mapIs (MapFlags::HasDoors) && tr.flFraction < 1.0f && (strncmp ("func_door", STRING (tr.pHit->v.classname), 9) != 0)) { return true; // bot's body will hit something } return false; @@ -2692,11 +2643,11 @@ bool Bot::isBlockedRight (void) { #endif -bool Bot::checkWallOnLeft (void) { +bool Bot::checkWallOnLeft () { TraceResult tr; game.makeVectors (pev->angles); - game.testLine (pev->origin, pev->origin - game.vec.right * 40.0f, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (pev->origin, pev->origin - game.vec.right * 40.0f, TraceIgnore::Monsters, ent (), &tr); // check if the trace hit something... if (tr.flFraction < 1.0f) { @@ -2705,12 +2656,12 @@ bool Bot::checkWallOnLeft (void) { return false; } -bool Bot::checkWallOnRight (void) { +bool Bot::checkWallOnRight () { TraceResult tr; game.makeVectors (pev->angles); // do a trace to the right... - game.testLine (pev->origin, pev->origin + game.vec.right * 40.0f, TRACE_IGNORE_MONSTERS, ent (), &tr); + game.testLine (pev->origin, pev->origin + game.vec.right * 40.0f, TraceIgnore::Monsters, ent (), &tr); // check if the trace hit something... if (tr.flFraction < 1.0f) { @@ -2725,7 +2676,7 @@ bool Bot::isDeadlyMove (const Vector &to) { Vector botPos = pev->origin; TraceResult tr; - Vector move ((to - botPos).toYaw (), 0.0f, 0.0f); + Vector move ((to - botPos).yaw (), 0.0f, 0.0f); game.makeVectors (move); Vector direction = (to - botPos).normalize (); // 1 unit long @@ -2734,7 +2685,7 @@ bool Bot::isDeadlyMove (const Vector &to) { down.z = down.z - 1000.0f; // straight down 1000 units - game.testHull (check, down, TRACE_IGNORE_MONSTERS, head_hull, ent (), &tr); + game.testHull (check, down, TraceIgnore::Monsters, head_hull, ent (), &tr); if (tr.flFraction > 0.036f) { // we're not on ground anymore? tr.flFraction = 0.036f; @@ -2749,7 +2700,7 @@ bool Bot::isDeadlyMove (const Vector &to) { down = check; down.z = down.z - 1000.0f; // straight down 1000 units - game.testHull (check, down, TRACE_IGNORE_MONSTERS, head_hull, ent (), &tr); + game.testHull (check, down, TraceIgnore::Monsters, head_hull, ent (), &tr); if (tr.fStartSolid) { // Wall blocking? return false; @@ -2830,24 +2781,24 @@ void Bot::changeYaw (float speed) { #endif -int Bot::searchCampDir (void) { - // find a good waypoint to look at when camping +int Bot::findCampingDirection () { + // find a good node to look at when camping - if (m_currentWaypointIndex == INVALID_WAYPOINT_INDEX) { - m_currentWaypointIndex = changePointIndex (getNearestPoint ()); + if (m_currentNodeIndex == kInvalidNodeIndex) { + m_currentNodeIndex = changePointIndex (findNearestNode ()); } int count = 0, indices[3]; float distTab[3]; uint16 visibility[3]; - int currentWaypoint = m_currentWaypointIndex; + int currentNode = m_currentNodeIndex; - for (int i = 0; i < waypoints.length (); i++) { - if (currentWaypoint == i || !waypoints.isVisible (currentWaypoint, i)) { + for (int i = 0; i < graph.length (); ++i) { + if (currentNode == i || !graph.isVisible (currentNode, i)) { continue; } - Path &path = waypoints[i]; + Path &path = graph[i]; if (count < 3) { indices[count] = i; @@ -2861,7 +2812,7 @@ int Bot::searchCampDir (void) { float distance = (pev->origin - path.origin).lengthSq (); uint16 visBits = path.vis.crouch + path.vis.stand; - for (int j = 0; j < 3; j++) { + for (int j = 0; j < 3; ++j) { if (visBits >= visibility[j] && distance > distTab[j]) { indices[j] = i; @@ -2876,12 +2827,12 @@ int Bot::searchCampDir (void) { count--; if (count >= 0) { - return indices[rng.getInt (0, count)]; + return indices[rg.int_ (0, count)]; } - return rng.getInt (0, waypoints.length () - 1); + return rg.int_ (0, graph.length () - 1); } -void Bot::updateBodyAngles (void) { +void Bot::updateBodyAngles () { // 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; @@ -2889,12 +2840,12 @@ void Bot::updateBodyAngles (void) { pev->angles.clampAngles (); } -void Bot::updateLookAngles (void) { - const float delta = cr::clamp (game.timebase () - m_lookUpdateTime, cr::EQEPSILON, 0.05f); +void Bot::updateLookAngles () { + const float delta = cr::clamp (game.timebase () - m_lookUpdateTime, cr::kFloatEqualEpsilon, 0.05f); m_lookUpdateTime = game.timebase (); // adjust all body and view angles to face an absolute vector - Vector direction = (m_lookAt - getEyesPos ()).toAngles (); + Vector direction = (m_lookAt - getEyesPos ()).angles (); direction.x *= -1.0f; // invert for engine direction.clampAngles (); @@ -2907,7 +2858,7 @@ void Bot::updateLookAngles (void) { return; } - if (m_difficulty > 3 && (m_aimFlags & AIM_ENEMY) && (m_wantsToFire || usesSniper ()) && yb_whose_your_daddy.boolean ()) { + if (m_difficulty > 3 && (m_aimFlags & AimFlags::Enemy) && (m_wantsToFire || usesSniper ()) && yb_whose_your_daddy.bool_ ()) { pev->v_angle = direction; updateBodyAngles (); @@ -2918,15 +2869,14 @@ void Bot::updateLookAngles (void) { float stiffness = 200.0f; float damping = 25.0f; - if ((m_aimFlags & (AIM_ENEMY | AIM_ENTITY | AIM_GRENADE)) && m_difficulty > 3) { - accelerate += 800.0f; - stiffness += 320.0f; - damping -= 8.0f; + if ((m_aimFlags & (AimFlags::Enemy | AimFlags::Entity | AimFlags::Grenade)) && m_difficulty > 3) { + stiffness += 100.0f; + damping += 5.0f; } m_idealAngles = pev->v_angle; - float angleDiffYaw = cr::angleDiff (direction.y, m_idealAngles.y); - float angleDiffPitch = cr::angleDiff (direction.x, m_idealAngles.x); + float angleDiffYaw = cr::anglesDifference (direction.y, m_idealAngles.y); + float angleDiffPitch = cr::anglesDifference (direction.x, m_idealAngles.x); if (angleDiffYaw < 1.0f && angleDiffYaw > -1.0f) { m_lookYawVel = 0.0f; @@ -2964,10 +2914,10 @@ void Bot::updateLookAnglesNewbie (const Vector &direction, float delta) { Vector stiffness; Vector randomize; - m_idealAngles = direction.make2D (); + m_idealAngles = direction.get2d (); m_idealAngles.clampAngles (); - if (m_aimFlags & (AIM_ENEMY | AIM_ENTITY)) { + if (m_aimFlags & (AimFlags::Enemy | AimFlags::Entity)) { m_playerTargetTime = game.timebase (); m_randomizedIdealAngles = m_idealAngles; @@ -2983,11 +2933,11 @@ void Bot::updateLookAnglesNewbie (const Vector &direction, float delta) { else { randomize = randomization; } - // randomize targeted location a bit (slightly towards the ground) - m_randomizedIdealAngles = m_idealAngles + Vector (rng.getFloat (-randomize.x * 0.5f, randomize.x * 1.5f), rng.getFloat (-randomize.y, randomize.y), 0.0f); + // randomize targeted location bit (slightly towards the ground) + m_randomizedIdealAngles = m_idealAngles + Vector (rg.float_ (-randomize.x * 0.5f, randomize.x * 1.5f), rg.float_ (-randomize.y, randomize.y), 0.0f); // set next time to do this - m_randomizeAnglesTime = game.timebase () + rng.getFloat (0.4f, offsetDelay); + m_randomizeAnglesTime = game.timebase () + rg.float_ (0.4f, offsetDelay); } float stiffnessMultiplier = noTargetRatio; @@ -3031,8 +2981,8 @@ void Bot::updateLookAnglesNewbie (const Vector &direction, float delta) { void Bot::setStrafeSpeed (const Vector &moveDir, float strafeSpeed) { game.makeVectors (pev->angles); - const Vector &los = (moveDir - pev->origin).normalize2D (); - float dot = los | game.vec.forward.make2D (); + const Vector &los = (moveDir - pev->origin).normalize2d (); + float dot = los | game.vec.forward.get2d (); if (dot > 0.0f && !checkWallOnRight ()) { m_strafeSpeed = strafeSpeed; @@ -3042,11 +2992,11 @@ void Bot::setStrafeSpeed (const Vector &moveDir, float strafeSpeed) { } } -int Bot::getNearestToPlantedBomb (void) { - // this function tries to find planted c4 on the defuse scenario map and returns nearest to it waypoint +int Bot::getNearestToPlantedBomb () { + // this function tries to find planted c4 on the defuse scenario map and returns nearest to it node - 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 + if (m_team != Team::Terrorist || !game.mapIs (MapFlags::Demolition)) { + return kInvalidNodeIndex; // don't search for bomb if the player is CT, or it's not defusing bomb } edict_t *bombEntity = nullptr; // temporaly pointer to bomb @@ -3054,34 +3004,34 @@ int Bot::getNearestToPlantedBomb (void) { // search the bomb on the map while (!game.isNullEntity (bombEntity = engfuncs.pfnFindEntityByString (bombEntity, "classname", "grenade"))) { if (strcmp (STRING (bombEntity->v.model) + 9, "c4.mdl") == 0) { - int nearestIndex = waypoints.getNearest (game.getAbsPos (bombEntity)); + int nearestIndex = graph.getNearest (game.getAbsPos (bombEntity)); - if (waypoints.exists (nearestIndex)) { + if (graph.exists (nearestIndex)) { return nearestIndex; } break; } } - return INVALID_WAYPOINT_INDEX; + return kInvalidNodeIndex; } bool Bot::isOccupiedPoint (int index) { - if (!waypoints.exists (index)) { + if (!graph.exists (index)) { return true; } for (const auto &client : util.getClients ()) { - if (!(client.flags & (CF_USED | CF_ALIVE)) || client.team != m_team || client.ent == ent ()) { + if (!(client.flags & (ClientFlags::Used | ClientFlags::Alive)) || client.team != m_team || client.ent == ent ()) { continue; } - auto bot = bots.getBot (client.ent); + auto bot = bots[client.ent]; if (bot == this) { continue; } if (bot != nullptr) { - int occupyId = util.getShootingCone (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_currentNodeIndex; if (bot != nullptr) { if (index == occupyId) { @@ -3089,9 +3039,9 @@ bool Bot::isOccupiedPoint (int index) { } } } - float length = (waypoints[index].origin - client.origin).lengthSq (); + float length = (graph[index].origin - client.origin).lengthSq (); - if (length < cr::clamp (waypoints[index].radius, cr::square (32.0f), cr::square (90.0f))) { + if (length < cr::clamp (graph[index].radius, cr::square (32.0f), cr::square (90.0f))) { return true; } } diff --git a/source/support.cpp b/source/support.cpp index a2e5b55..21d12e2 100644 --- a/source/support.cpp +++ b/source/support.cpp @@ -10,7 +10,7 @@ ConVar yb_display_welcome_text ("yb_display_welcome_text", "1"); -BotUtils::BotUtils (void) { +BotUtils::BotUtils () { m_needToSendWelcome = false; m_welcomeReceiveTime = 0.0f; @@ -32,47 +32,30 @@ BotUtils::BotUtils (void) { 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_tags.emplace ("[[", "]]"); + m_tags.emplace ("-=", "=-"); + m_tags.emplace ("-[", "]-"); + m_tags.emplace ("-]", "[-"); + m_tags.emplace ("-}", "{-"); + m_tags.emplace ("-{", "}-"); + m_tags.emplace ("<[", "]>"); + m_tags.emplace ("<]", "[>"); + m_tags.emplace ("[-", "-]"); + m_tags.emplace ("]-", "-["); + m_tags.emplace ("{-", "-}"); + m_tags.emplace ("}-", "-{"); + m_tags.emplace ("[", "]"); + m_tags.emplace ("{", "}"); + m_tags.emplace ("<", "["); + m_tags.emplace (">", "<"); + m_tags.emplace ("-", "-"); + m_tags.emplace ("|", "|"); + m_tags.emplace ("=", "="); + m_tags.emplace ("+", "+"); + m_tags.emplace ("(", ")"); + m_tags.emplace (")", "("); - 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; - - if (format == nullptr) { - return strBuffer[rotator]; - } - static char *ptr = strBuffer[rotator ^= 1]; - - va_list ap; - va_start (ap, format); - vsnprintf (ptr, MAX_PRINT_BUFFER - 1, format, ap); - va_end (ap); - - return ptr; + m_clients.resize (kGameMaxPlayers + 1); } bool BotUtils::isAlive (edict_t *ent) { @@ -90,7 +73,7 @@ float BotUtils::getShootingCone (edict_t *ent, const Vector &position) { } 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)); + return getShootingCone (ent, origin) >= cr::cosf (cr::degreesToRadians ((ent->v.fov > 0 ? ent->v.fov : 90.0f) * 0.5f)); } bool BotUtils::isVisible (const Vector &origin, edict_t *ent) { @@ -98,7 +81,7 @@ bool BotUtils::isVisible (const Vector &origin, edict_t *ent) { return false; } TraceResult tr; - game.testLine (ent->v.origin + ent->v.view_ofs, origin, TRACE_IGNORE_EVERYTHING, ent, &tr); + game.testLine (ent->v.origin + ent->v.view_ofs, origin, TraceIgnore::Everything, ent, &tr); if (tr.flFraction != 1.0f) { return false; @@ -109,13 +92,10 @@ bool BotUtils::isVisible (const Vector &origin, edict_t *ent) { void BotUtils::traceDecals (entvars_t *pev, TraceResult *trace, int logotypeIndex) { // this function draw spraypaint depending on the tracing results. - static StringArray logotypes; + auto logo = conf.getRandomLogoName (logotypeIndex); - if (logotypes.empty ()) { - logotypes = String ("{biohaz;{graf003;{graf004;{graf005;{lambda06;{target;{hand1;{spit2;{bloodhand6;{foot_l;{foot_r").split (";"); - } int entityIndex = -1, message = TE_DECAL; - int decalIndex = engfuncs.pfnDecalIndex (logotypes[logotypeIndex].chars ()); + int decalIndex = engfuncs.pfnDecalIndex (logo.chars ()); if (decalIndex < 0) { decalIndex = engfuncs.pfnDecalIndex ("{lambda06"); @@ -151,7 +131,7 @@ void BotUtils::traceDecals (entvars_t *pev, TraceResult *trace, int logotypeInde } } - if (logotypes[logotypeIndex].contains ("{")) { + if (logo.startsWith ("{")) { MessageWriter (MSG_BROADCAST, SVC_TEMPENTITY) .writeByte (TE_PLAYERDECAL) .writeByte (game.indexOfEntity (pev->pContainingEntity)) @@ -187,14 +167,14 @@ bool BotUtils::isPlayer (edict_t *ent) { return false; } - if ((ent->v.flags & (FL_CLIENT | FL_FAKECLIENT)) || bots.getBot (ent) != nullptr) { + if ((ent->v.flags & (FL_CLIENT | FL_FAKECLIENT)) || bots[ent] != nullptr) { return !isEmptyStr (STRING (ent->v.netname)); } return false; } bool BotUtils::isPlayerVIP (edict_t *ent) { - if (!game.mapIs (MAP_AS)) { + if (!game.mapIs (MapFlags::Assassination)) { return false; } @@ -205,14 +185,14 @@ bool BotUtils::isPlayerVIP (edict_t *ent) { } bool BotUtils::isFakeClient (edict_t *ent) { - if (bots.getBot (ent) != nullptr || (!game.isNullEntity (ent) && (ent->v.flags & FL_FAKECLIENT))) { + if (bots[ent] != nullptr || (!game.isNullEntity (ent) && (ent->v.flags & FL_FAKECLIENT))) { return true; } return false; } bool BotUtils::openConfig (const char *fileName, const char *errorIfNotExists, MemFile *outFile, bool languageDependant /*= false*/) { - if (outFile->isValid ()) { + if (*outFile) { outFile->close (); } @@ -225,165 +205,75 @@ bool BotUtils::openConfig (const char *fileName, const char *errorIfNotExists, M if (strcmp (fileName, "lang.cfg") == 0 && strcmp (yb_language.str (), "en") == 0) { return false; } - const char *langConfig = format ("%s/lang/%s_%s", configDir, yb_language.str (), fileName); - - // check file existence - int size = 0; - uint8 *buffer = nullptr; + auto langConfig = strings.format ("%s/lang/%s_%s", configDir, yb_language.str (), fileName); // check is file is exists for this language - if ((buffer = MemoryLoader::ref ().load (langConfig, &size)) != nullptr) { - MemoryLoader::ref ().unload (buffer); - - // unload and reopen file using MemoryFile - outFile->open (langConfig); - } - else { - outFile->open (format ("%s/lang/en_%s", configDir, fileName)); + if (!outFile->open (langConfig)) { + outFile->open (strings.format ("%s/lang/en_%s", configDir, fileName)); } } else { - outFile->open (format ("%s/%s", configDir, fileName)); + outFile->open (strings.format ("%s/%s", configDir, fileName)); } - if (!outFile->isValid ()) { - logEntry (true, LL_ERROR, errorIfNotExists); + if (!*outFile) { + logger.error (errorIfNotExists); return false; } return true; } -void BotUtils::checkWelcome (void) { +void BotUtils::checkWelcome () { // the purpose of this function, is to send quick welcome message, to the listenserver entity. - if (game.isDedicated () || !yb_display_welcome_text.boolean () || !m_needToSendWelcome) { + if (game.isDedicated () || !yb_display_welcome_text.bool_ () || !m_needToSendWelcome) { return; } m_welcomeReceiveTime = 0.0f; - 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) { + bool needToSendMsg = (graph.length () > 0 ? m_needToSendWelcome : true); + auto receiveEntity = game.getLocalEntity (); + + if (isAlive (receiveEntity) && m_welcomeReceiveTime < 1.0 && needToSendMsg) { m_welcomeReceiveTime = game.timebase () + 4.0f; // receive welcome message in four seconds after game has commencing } - if (m_welcomeReceiveTime > 0.0f && needToSendMsg) { - if (!game.is (GAME_MOBILITY | GAME_XASH_ENGINE)) { - game.execCmd ("speak \"%s\"", m_sentences.random ().chars ()); - } - 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 (), game.getLocalEntity ()) + if (m_welcomeReceiveTime > 0.0f && needToSendMsg) { + if (!game.is (GameFlags::Mobility | GameFlags::Xash3D)) { + game.serverCommand ("speak \"%s\"", m_sentences.random ().chars ()); + } + + MessageWriter (MSG_ONE, game.getMessageId (NetMsg::TextMsg), nullvec, receiveEntity) + .writeByte (HUD_PRINTTALK) + .writeString (strings.format ("----- %s v%s (Build: %u), {%s}, (c) %s, by %s (%s)-----", PRODUCT_SHORT_NAME, PRODUCT_VERSION, buildNumber (), PRODUCT_DATE, PRODUCT_END_YEAR, PRODUCT_AUTHOR, PRODUCT_URL)); + + MessageWriter (MSG_ONE, SVC_TEMPENTITY, nullvec, receiveEntity) .writeByte (TE_TEXTMESSAGE) .writeByte (1) - .writeShort (MessageWriter::fs16 (-1, 1 << 13)) - .writeShort (MessageWriter::fs16 (-1, 1 << 13)) + .writeShort (MessageWriter::fs16 (-1.0f, 13.0f)) + .writeShort (MessageWriter::fs16 (-1.0f, 13.0f)) .writeByte (2) - .writeByte (rng.getInt (33, 255)) - .writeByte (rng.getInt (33, 255)) - .writeByte (rng.getInt (33, 255)) + .writeByte (rg.int_ (33, 255)) + .writeByte (rg.int_ (33, 255)) + .writeByte (rg.int_ (33, 255)) .writeByte (0) - .writeByte (rng.getInt (230, 255)) - .writeByte (rng.getInt (230, 255)) - .writeByte (rng.getInt (230, 255)) + .writeByte (rg.int_ (230, 255)) + .writeByte (rg.int_ (230, 255)) + .writeByte (rg.int_ (230, 255)) .writeByte (200) - .writeShort (MessageWriter::fu16 (0.0078125f, 1 << 8)) - .writeShort (MessageWriter::fu16 (2.0f, 1 << 8)) - .writeShort (MessageWriter::fu16 (6.0f, 1 << 8)) - .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 ())); + .writeShort (MessageWriter::fu16 (0.0078125f, 8.0f)) + .writeShort (MessageWriter::fu16 (2.0f, 8.0f)) + .writeShort (MessageWriter::fu16 (6.0f, 8.0f)) + .writeShort (MessageWriter::fu16 (0.1f, 8.0f)) + .writeString (strings.format ("\nServer is running %s v%s (Build: %u)\nDeveloped by %s\n\n%s", PRODUCT_SHORT_NAME, PRODUCT_VERSION, buildNumber (), PRODUCT_AUTHOR, graph.getAuthor ())); m_welcomeReceiveTime = 0.0f; m_needToSendWelcome = false; } } -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; - char buffer[MAX_PRINT_BUFFER] = { 0, }, levelString[32] = { 0, }; - - va_start (ap, format); - vsnprintf (buffer, cr::bufsize (buffer), format, ap); - va_end (ap); - - switch (logLevel) { - case LL_DEFAULT: - strcpy (levelString, "LOG: "); - break; - - case LL_WARNING: - strcpy (levelString, "WARN: "); - break; - - case LL_ERROR: - strcpy (levelString, "ERROR: "); - break; - - case LL_FATAL: - strcpy (levelString, "FATAL: "); - break; - } - - if (outputToConsole) { - game.print ("%s%s", levelString, buffer); - } - - // now check if logging disabled - if (!(logLevel & LL_IGNORE)) { - extern ConVar yb_debug; - - if (logLevel == LL_DEFAULT && yb_debug.integer () < 3) { - return; // no log, default logging is disabled - } - - if (logLevel == LL_WARNING && yb_debug.integer () < 2) { - return; // no log, warning logging is disabled - } - - if (logLevel == LL_ERROR && yb_debug.integer () < 1) { - return; // no log, error logging is disabled - } - } - - // open file in a standard stream - File fp ("yapb.txt", "at"); - - // check if we got a valid handle - if (!fp.isValid ()) { - return; - } - - time_t tickTime = time (&tickTime); - tm *time = localtime (&tickTime); - - fp.writeFormat ("%02d:%02d:%02d --> %s%s\n", time->tm_hour, time->tm_min, time->tm_sec, levelString, buffer); - fp.close (); - - if (logLevel == LL_FATAL) { - bots.kickEveryone (true); - waypoints.init (); - -#if defined(PLATFORM_WIN32) - DestroyWindow (GetForegroundWindow ()); - MessageBoxA (GetActiveWindow (), buffer, "YaPB Error", MB_ICONSTOP); -#else - printf ("%s\n", buffer); -#endif - -#if defined(PLATFORM_WIN32) - _exit (1); -#else - exit (1); -#endif - } -} - 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 @@ -395,11 +285,11 @@ bool BotUtils::findNearestPlayer (void **pvHolder, edict_t *to, float searchDist int toTeam = game.getTeam (to); for (const auto &client : m_clients) { - if (!(client.flags & CF_USED) || client.ent == to) { + if (!(client.flags & ClientFlags::Used) || client.ent == to) { continue; } - 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))) { + if ((sameTeam && client.team != toTeam) || (needAlive && !(client.flags & ClientFlags::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 (); @@ -416,7 +306,7 @@ bool BotUtils::findNearestPlayer (void **pvHolder, edict_t *to, float searchDist // fill the holder if (needBot) { - *pvHolder = reinterpret_cast (bots.getBot (survive)); + *pvHolder = reinterpret_cast (bots[survive]); } else { *pvHolder = reinterpret_cast (survive); @@ -436,16 +326,16 @@ void BotUtils::attachSoundsToClients (edict_t *ent, const char *sample, float vo if (origin.empty ()) { return; } - int index = game.indexOfEntity (ent) - 1; + int index = game.indexOfPlayer (ent); if (index < 0 || index >= game.maxClients ()) { float nearestDistance = 99999.0f; // loop through all players - for (int i = 0; i < game.maxClients (); i++) { + for (int i = 0; i < game.maxClients (); ++i) { const Client &client = m_clients[i]; - if (!(client.flags & CF_USED) || !(client.flags & CF_ALIVE)) { + if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive)) { continue; } float distance = (client.origin - origin).length (); @@ -545,9 +435,9 @@ void BotUtils::simulateSoundUpdates (int playerIndex) { else { extern ConVar mp_footsteps; - if (mp_footsteps.boolean ()) { + if (mp_footsteps.bool_ ()) { // moves fast enough? - hearDistance = 1280.0f * (client.ent->v.velocity.length2D () / 260.0f); + hearDistance = 1280.0f * (client.ent->v.velocity.length2d () / 260.0f); timeSound = game.timebase () + 0.3f; } } @@ -573,36 +463,129 @@ void BotUtils::simulateSoundUpdates (int playerIndex) { } } -void BotUtils::updateClients (void) { +void BotUtils::updateClients () { + // record some stats of all players on the server - for (int i = 0; i < game.maxClients (); i++) { - edict_t *player = game.entityOfIndex (i + 1); + for (int i = 0; i < game.maxClients (); ++i) { + edict_t *player = game.playerOfIndex (i); Client &client = m_clients[i]; if (!game.isNullEntity (player) && (player->v.flags & FL_CLIENT)) { client.ent = player; - client.flags |= CF_USED; + client.flags |= ClientFlags::Used; if (util.isAlive (player)) { - client.flags |= CF_ALIVE; + client.flags |= ClientFlags::Alive; } else { - client.flags &= ~CF_ALIVE; + client.flags &= ~ClientFlags::Alive; } - if (client.flags & CF_ALIVE) { + if (client.flags & ClientFlags::Alive) { client.origin = player->v.origin; simulateSoundUpdates (i); } } else { - client.flags &= ~(CF_USED | CF_ALIVE); + client.flags &= ~(ClientFlags::Used | ClientFlags::Alive); client.ent = nullptr; } } } -int BotUtils::buildNumber (void) { +int BotUtils::getPingBitmask (edict_t *ent, int loss, int ping) { + // this function generats bitmask for SVC_PINGS engine message. See SV_EmitPings from engine for details + + const auto emit = [] (int s0, int s1, int s2) { + return (s0 & (cr::bit (s1) - 1)) << s2; + }; + return emit (loss, 7, 18) | emit (ping, 12, 6) | emit (game.indexOfPlayer (ent), 5, 1) | 1; +} + +void BotUtils::calculatePings () { + if (!game.is (GameFlags::HasFakePings) || yb_show_latency.int_ () != 2) { + return; + } + + Twin average { 0, 0 }; + int numHumans = 0; + + // first get average ping on server, and store real client pings + for (auto &client : m_clients) { + if (!(client.flags & ClientFlags::Used) || isFakeClient (client.ent)) { + continue; + } + int ping, loss; + engfuncs.pfnGetPlayerStats (client.ent, &ping, &loss); + + // store normal client ping + client.ping = getPingBitmask (client.ent, loss, ping > 0 ? ping / 2 : rg.int_ (8, 16)); // getting player ping sometimes fails + client.pingUpdate = true; // force resend ping + + numHumans++; + + average.first += ping; + average.second += loss; + } + + if (numHumans > 0) { + average.first /= numHumans; + average.second /= numHumans; + } + else { + average.first = rg.int_ (30, 40); + average.second = rg.int_ (5, 10); + } + + // now calculate bot ping based on average from players + for (auto &client : m_clients) { + if (!(client.flags & ClientFlags::Used)) { + continue; + } + auto bot = bots[client.ent]; + + // we're only intrested in bots here + if (!bot) { + continue; + } + int part = static_cast (average.first * 0.2f); + + int botPing = bot->m_basePing + rg.int_ (average.first - part, average.first + part) + rg.int_ (bot->m_difficulty / 2, bot->m_difficulty); + int botLoss = rg.int_ (average.second / 2, average.second); + + client.ping = getPingBitmask (client.ent, botLoss, botPing); + client.pingUpdate = true; // force resend ping + } +} + +void BotUtils::sendPings (edict_t *to) { + MessageWriter msg; + + // missing from sdk + constexpr int kGamePingSVC = 17; + + for (auto &client : m_clients) { + if (!(client.flags & ClientFlags::Used)) { + continue; + } + if (!client.pingUpdate) { + continue; + } + client.pingUpdate = false; + + // no ping, no fun + if (!client.ping) { + client.ping = getPingBitmask (client.ent, rg.int_ (5, 10), rg.int_ (15, 40)); + } + + msg.start (MSG_ONE_UNRELIABLE, kGamePingSVC, nullvec, to) + .writeLong (client.ping) + .end (); + } + return; +} + +int BotUtils::buildNumber () { // this function generates build number from the compiler date macros static int buildNumber = 0; @@ -624,7 +607,7 @@ int BotUtils::buildNumber (void) { int i = 0; // go through all months, and calculate, days since year start - for (i = 0; i < 11; i++) { + for (i = 0; i < 11; ++i) { if (strncmp (&date[0], months[i], 3) == 0) { break; // found current month break } @@ -655,38 +638,38 @@ int BotUtils::getWeaponAlias (bool needString, const char *weaponAlias, int weap // weapon enumeration WeaponTab_t weaponTab[] = { - {WEAPON_USP, "usp"}, // HK USP .45 Tactical - {WEAPON_GLOCK, "glock"}, // Glock18 Select Fire - {WEAPON_DEAGLE, "deagle"}, // Desert Eagle .50AE - {WEAPON_P228, "p228"}, // SIG P228 - {WEAPON_ELITE, "elite"}, // Dual Beretta 96G Elite - {WEAPON_FIVESEVEN, "fn57"}, // FN Five-Seven - {WEAPON_M3, "m3"}, // Benelli M3 Super90 - {WEAPON_XM1014, "xm1014"}, // Benelli XM1014 - {WEAPON_MP5, "mp5"}, // HK MP5-Navy - {WEAPON_TMP, "tmp"}, // Steyr Tactical Machine Pistol - {WEAPON_P90, "p90"}, // FN P90 - {WEAPON_MAC10, "mac10"}, // Ingram MAC-10 - {WEAPON_UMP45, "ump45"}, // HK UMP45 - {WEAPON_AK47, "ak47"}, // Automat Kalashnikov AK-47 - {WEAPON_GALIL, "galil"}, // IMI Galil - {WEAPON_FAMAS, "famas"}, // GIAT FAMAS - {WEAPON_SG552, "sg552"}, // Sig SG-552 Commando - {WEAPON_M4A1, "m4a1"}, // Colt M4A1 Carbine - {WEAPON_AUG, "aug"}, // Steyr Aug - {WEAPON_SCOUT, "scout"}, // Steyr Scout - {WEAPON_AWP, "awp"}, // AI Arctic Warfare/Magnum - {WEAPON_G3SG1, "g3sg1"}, // HK G3/SG-1 Sniper Rifle - {WEAPON_SG550, "sg550"}, // Sig SG-550 Sniper - {WEAPON_M249, "m249"}, // FN M249 Para - {WEAPON_FLASHBANG, "flash"}, // Concussion Grenade - {WEAPON_EXPLOSIVE, "hegren"}, // High-Explosive Grenade - {WEAPON_SMOKE, "sgren"}, // Smoke Grenade - {WEAPON_ARMOR, "vest"}, // Kevlar Vest - {WEAPON_ARMORHELM, "vesthelm"}, // Kevlar Vest and Helmet - {WEAPON_DEFUSER, "defuser"}, // Defuser Kit - {WEAPON_SHIELD, "shield"}, // Tactical Shield - {WEAPON_KNIFE, "knife"} // Knife + {Weapon::USP, "usp"}, // HK USP .45 Tactical + {Weapon::Glock18, "glock"}, // Glock18 Select Fire + {Weapon::Deagle, "deagle"}, // Desert Eagle .50AE + {Weapon::P228, "p228"}, // SIG P228 + {Weapon::Elite, "elite"}, // Dual Beretta 96G Elite + {Weapon::FiveSeven, "fn57"}, // FN Five-Seven + {Weapon::M3, "m3"}, // Benelli M3 Super90 + {Weapon::XM1014, "xm1014"}, // Benelli XM1014 + {Weapon::MP5, "mp5"}, // HK MP5-Navy + {Weapon::TMP, "tmp"}, // Steyr Tactical Machine Pistol + {Weapon::P90, "p90"}, // FN P90 + {Weapon::MAC10, "mac10"}, // Ingram MAC-10 + {Weapon::UMP45, "ump45"}, // HK UMP45 + {Weapon::AK47, "ak47"}, // Automat Kalashnikov AK-47 + {Weapon::Galil, "galil"}, // IMI Galil + {Weapon::Famas, "famas"}, // GIAT FAMAS + {Weapon::SG552, "sg552"}, // Sig SG-552 Commando + {Weapon::M4A1, "m4a1"}, // Colt M4A1 Carbine + {Weapon::AUG, "aug"}, // Steyr Aug + {Weapon::Scout, "scout"}, // Steyr Scout + {Weapon::AWP, "awp"}, // AI Arctic Warfare/Magnum + {Weapon::G3SG1, "g3sg1"}, // HK G3/SG-1 Sniper Rifle + {Weapon::SG550, "sg550"}, // Sig SG-550 Sniper + {Weapon::M249, "m249"}, // FN M249 Para + {Weapon::Flashbang, "flash"}, // Concussion Grenade + {Weapon::Explosive, "hegren"}, // High-Explosive Grenade + {Weapon::Smoke, "sgren"}, // Smoke Grenade + {Weapon::Armor, "vest"}, // Kevlar Vest + {Weapon::ArmorHelm, "vesthelm"}, // Kevlar Vest and Helmet + {Weapon::Defuser, "defuser"}, // Defuser Kit + {Weapon::Shield, "shield"}, // Tactical Shield + {Weapon::Knife, "knife"} // Knife }; // if we need to return the string, find by weapon id diff --git a/source/waypoint.cpp b/source/waypoint.cpp deleted file mode 100644 index df0e389..0000000 --- a/source/waypoint.cpp +++ /dev/null @@ -1,2928 +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 - -ConVar yb_wptsubfolder ("yb_wptsubfolder", ""); - -ConVar yb_waypoint_autodl_host ("yb_waypoint_autodl_host", "yapb.ru"); -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 (); - m_lastWaypoint.nullify (); - - 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; -} - -void Waypoint::cleanupPathMemory (void) { - for (int i = 0; i < m_numWaypoints && m_paths[i] != nullptr; i++) { - delete m_paths[i]; - m_paths[i] = nullptr; - } -} - -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)) { - return 0; - } - int numConnectionsFixed = 0; - - if (bots.getBotCount () > 0) { - bots.kickEveryone (true); - } - const int INFINITE_DISTANCE = 99999; - - struct Connection { - int index; - int number; - int distance; - float angles; - - public: - Connection (void) { - reset (); - } - - public: - void reset (void) { - index = INVALID_WAYPOINT_INDEX; - number = INVALID_WAYPOINT_INDEX; - distance = INFINITE_DISTANCE; - angles = 0.0f; - } - }; - - Connection sorted[MAX_PATH_INDEX]; - Connection top; - - for (int i = 0; i < MAX_PATH_INDEX; i++) { - auto &cur = sorted[i]; - - cur.number = i; - cur.index = m_paths[index]->index[i]; - cur.distance = m_paths[index]->distances[i]; - - if (cur.index == INVALID_WAYPOINT_INDEX) { - cur.distance = INFINITE_DISTANCE; - } - - if (cur.distance < top.distance) { - top.distance = m_paths[index]->distances[i]; - top.number = i; - top.index = cur.index; - } - } - - if (top.number == INVALID_WAYPOINT_INDEX) { - ctrl.msg ("Cannot find path to the closest connected waypoint to waypoint number %d!\n", index); - return numConnectionsFixed; - } - bool sorting = false; - - // sort paths from the closest waypoint to the farest away one... - do { - sorting = false; - - for (int i = 0; i < MAX_PATH_INDEX - 1; i++) { - if (sorted[i].distance > sorted[i + 1].distance) { - cr::swap (sorted[i], sorted[i + 1]); - sorting = true; - } - } - } while (sorting); - - // calculate angles related to the angle of the closeset connected waypoint - for (auto &cur : sorted) { - if (cur.index == INVALID_WAYPOINT_INDEX) { - cur.distance = INFINITE_DISTANCE; - cur.angles = 360.0f; - } - else if (exists (cur.index)) { - cur.angles = ((m_paths[cur.index]->origin - m_paths[index]->origin).toAngles () - (m_paths[sorted[0].index]->origin - m_paths[index]->origin).toAngles ()).y; - - if (cur.angles < 0.0f) { - cur.angles += 360.0f; - } - } - } - - // sort the paths from the lowest to the highest angle (related to the vector closest waypoint - checked index)... - do { - sorting = false; - - for (int i = 0; i < MAX_PATH_INDEX - 1; i++) { - if (sorted[i].index != INVALID_WAYPOINT_INDEX && sorted[i].angles > sorted[i + 1].angles) { - cr::swap (sorted[i], sorted[i + 1]); - sorting = true; - } - } - } while (sorting); - - // reset top state - top.reset (); - - auto unassignPath = [&](const int id1, const int id2) { - m_waypointsChanged = true; - - m_paths[id1]->index[id2] = INVALID_WAYPOINT_INDEX; - m_paths[id1]->distances[id2] = 0; - m_paths[id1]->connectionFlags[id2] = 0; - m_paths[id1]->connectionVelocity[id2].nullify (); - - m_waypointsChanged = true; - setEditFlag (WS_EDIT_ENABLED); - - numConnectionsFixed++; - }; - - // check pass 0 - auto inspect_p0 = [&](const int id) -> bool { - if (id < 2) { - return false; - } - auto &cur = sorted[id], &prev = sorted[id - 1], &prev2 = sorted[id - 2]; - - if (cur.index == INVALID_WAYPOINT_INDEX || prev.index == INVALID_WAYPOINT_INDEX || prev2.index == INVALID_WAYPOINT_INDEX) { - return false; - } - - // store the highest index which should be tested later... - top.index = cur.index; - top.distance = cur.distance; - top.angles = cur.angles; - - if (cur.angles - prev2.angles < 80.0f) { - - // leave alone ladder connections and don't remove jump connections.. - if (((m_paths[index]->flags & FLAG_LADDER) && (m_paths[prev.index]->flags & FLAG_LADDER)) || (m_paths[index]->connectionFlags[prev.number] & PATHFLAG_JUMP)) { - return false; - } - - if ((cur.distance + prev2.distance) * 1.1f / 2.0f < static_cast (prev.distance)) { - if (m_paths[index]->index[prev.number] == 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)) { - ctrl.msg ("Removing a useless (P.0.2) connection from index = %d to %d.", prev.index, index); - - // unassign this path - unassignPath (prev.index, j); - } - } - prev.index = INVALID_WAYPOINT_INDEX; - - for (int j = id - 1; j < MAX_PATH_INDEX - 1; j++) { - sorted[j] = cr::move (sorted[j + 1]); - } - sorted[MAX_PATH_INDEX - 1].index = INVALID_WAYPOINT_INDEX; - - // do a second check - return true; - } - else { - ctrl.msg ("Failed to remove a useless (P.0) connection from index = %d to %d.", index, prev.index); - return false; - } - } - } - return false; - }; - - - for (int i = 2; i < MAX_PATH_INDEX; i++) { - while (inspect_p0 (i)) { } - } - - // check pass 1 - if (exists (top.index) && exists (sorted[0].index) && exists (sorted[1].index)) { - 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) { - 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)) { - 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); - } - } - sorted[0].index = INVALID_WAYPOINT_INDEX; - - for (int j = 0; j < MAX_PATH_INDEX - 1; j++) { - sorted[j] = cr::move (sorted[j + 1]); - } - sorted[MAX_PATH_INDEX - 1].index = INVALID_WAYPOINT_INDEX; - } - else { - ctrl.msg ("Failed to remove a useless (P.1) connection from index = %d to %d.", sorted[0].index, index); - } - } - } - } - top.reset (); - - // check pass 2 - auto inspect_p2 = [&](const int id) -> bool { - if (id < 1) { - return false; - } - auto &cur = sorted[id], &prev = sorted[id - 1]; - - if (cur.index == INVALID_WAYPOINT_INDEX || prev.index == INVALID_WAYPOINT_INDEX) { - return false; - } - - if (cur.angles - prev.angles < 40.0f) { - if (prev.distance < static_cast (cur.distance * 1.1f)) { - - // leave alone ladder connections and don't remove jump connections.. - if (((m_paths[index]->flags & FLAG_LADDER) && (m_paths[cur.index]->flags & FLAG_LADDER)) || (m_paths[index]->connectionFlags[cur.number] & PATHFLAG_JUMP)) { - return false; - } - - if (m_paths[index]->index[cur.number] == 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)) { - ctrl.msg ("Removing a useless (P.2.2) connection from index = %d to %d.", cur.index, index); - - // unassign this path - unassignPath (cur.index, j); - } - } - cur.index = INVALID_WAYPOINT_INDEX; - - for (int j = id - 1; j < MAX_PATH_INDEX - 1; j++) { - sorted[j] = cr::move (sorted[j + 1]); - } - sorted[MAX_PATH_INDEX - 1].index = INVALID_WAYPOINT_INDEX; - return true; - } - else { - 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)) { - // leave alone ladder connections and don't remove jump connections.. - if (((m_paths[index]->flags & FLAG_LADDER) && (m_paths[prev.index]->flags & FLAG_LADDER)) || (m_paths[index]->connectionFlags[prev.number] & PATHFLAG_JUMP)) { - return false; - } - - if (m_paths[index]->index[prev.number] == 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)) { - ctrl.msg ("Removing a useless (P.2.4) connection from index = %d to %d.", prev.index, index); - - // unassign this path - unassignPath (prev.index, j); - } - } - prev.index = INVALID_WAYPOINT_INDEX; - - for (int j = id - 1; j < MAX_PATH_INDEX - 1; j++) { - sorted[j] = cr::move (sorted[j + 1]); - } - sorted[MAX_PATH_INDEX - 1].index = INVALID_WAYPOINT_INDEX; - - // do a second check - return true; - } - else { - ctrl.msg ("Failed to remove a useless (P.2) connection from index = %d to %d.", index, prev.index); - } - } - } - else { - top = cur; - } - return false; - }; - - for (int i = 1; i < MAX_PATH_INDEX; i++) { - while (inspect_p2 (i)) { } - } - - // check pass 3 - if (exists (top.index) && exists (sorted[0].index)) { - 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) { - 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)) { - 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); - } - } - sorted[0].index = INVALID_WAYPOINT_INDEX; - - for (int j = 0; j < MAX_PATH_INDEX - 1; j++) { - sorted[j] = cr::move (sorted[j + 1]); - } - sorted[MAX_PATH_INDEX - 1].index = INVALID_WAYPOINT_INDEX; - } - else { - 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) { - 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)) { - 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); - } - } - sorted[0].index = INVALID_WAYPOINT_INDEX; - } - else { - ctrl.msg ("Failed to remove a useless (P.3) connection from index = %d to %d.", sorted[0].index, index); - } - } - } - } - return numConnectionsFixed; -} - -void Waypoint::addPath (int addIndex, int pathIndex, float distance) { - if (!exists (addIndex) || !exists (pathIndex)) { - return; - } - Path *path = m_paths[addIndex]; - - // don't allow paths get connected twice - 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 (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)); - - ctrl.msg ("Path added from %d to %d", addIndex, pathIndex); - return; - } - } - - // there wasn't any free space. try exchanging it with a long-distance path - int maxDistance = -9999; - int slotID = INVALID_WAYPOINT_INDEX; - - for (int i = 0; i < MAX_PATH_INDEX; i++) { - if (path->distances[i] > maxDistance) { - maxDistance = path->distances[i]; - slotID = i; - } - } - - if (slotID != INVALID_WAYPOINT_INDEX) { - ctrl.msg ("Path added from %d to %d", addIndex, pathIndex); - - path->index[slotID] = static_cast (pathIndex); - path->distances[slotID] = cr::abs (static_cast (distance)); - } -} - -int Waypoint::getFarest (const Vector &origin, float maxDistance) { - // find the farest waypoint to that Origin, and return the index to this waypoint - - int index = INVALID_WAYPOINT_INDEX; - maxDistance = cr::square (maxDistance); - - for (int i = 0; i < m_numWaypoints; i++) { - float distance = (m_paths[i]->origin - origin).lengthSq (); - - if (distance > maxDistance) { - index = i; - maxDistance = distance; - } - } - return index; -} - -int Waypoint::getNearestNoBuckets (const Vector &origin, float minDistance, int flags) { - // find the nearest waypoint to that origin and return the index - - // fallback and go thru wall the waypoints... - int index = INVALID_WAYPOINT_INDEX; - minDistance = cr::square (minDistance); - - for (int i = 0; i < m_numWaypoints; i++) { - if (flags != -1 && !(m_paths[i]->flags & flags)) { - continue; // if flag not -1 and waypoint has no this flag, skip waypoint - } - float distance = (m_paths[i]->origin - origin).lengthSq (); - - if (distance < minDistance) { - index = i; - minDistance = distance; - } - } - return index; -} - -int Waypoint::getEditorNeareset (void) { - if (!hasEditFlag (WS_EDIT_ENABLED)) { - return INVALID_WAYPOINT_INDEX; - } - return getNearestNoBuckets (m_editor->v.origin, 50.0f); -} - -int Waypoint::getNearest (const Vector &origin, float minDistance, int flags) { - // find the nearest waypoint to that origin and return the index - - auto &bucket = getWaypointsInBucket (origin); - - if (bucket.empty ()) { - return getNearestNoBuckets (origin, minDistance, flags); - } - int index = INVALID_WAYPOINT_INDEX; - minDistance = cr::square (minDistance); - - for (const auto at : bucket) { - if (flags != -1 && !(m_paths[at]->flags & flags)) { - continue; // if flag not -1 and waypoint has no this flag, skip waypoint - } - float distance = (m_paths[at]->origin - origin).lengthSq (); - - if (distance < minDistance) { - index = at; - minDistance = distance; - } - } - return index; -} - -IntArray Waypoint::searchRadius (float radius, const Vector &origin, int maxCount) { - // returns all waypoints within radius from position - - IntArray result; - auto &bucket = getWaypointsInBucket (origin); - - if (bucket.empty ()) { - result.push (getNearestNoBuckets (origin, radius)); - return cr::move (result); - } - radius = cr::square (radius); - - if (maxCount != -1) { - result.reserve (maxCount); - } - - for (const auto at : bucket) { - if (maxCount != -1 && static_cast (result.length ()) > maxCount) { - break; - } - - if ((m_paths[at]->origin - origin).lengthSq () < radius) { - result.push (at); - } - } - return cr::move (result); -} - -void Waypoint::push (int flags, const Vector &waypointOrigin) { - if (game.isNullEntity (m_editor)) { - return; - } - - int index = INVALID_WAYPOINT_INDEX, i; - float distance; - - Vector forward; - Path *path = nullptr; - - bool placeNew = true; - Vector newOrigin = waypointOrigin; - - if (waypointOrigin.empty ()) { - newOrigin = m_editor->v.origin; - } - - if (bots.getBotCount () > 0) { - bots.kickEveryone (true); - } - m_waypointsChanged = true; - - switch (flags) { - case 6: - index = getEditorNeareset (); - - if (index != INVALID_WAYPOINT_INDEX) { - path = m_paths[index]; - - if (!(path->flags & FLAG_CAMP)) { - ctrl.msg ("This is not Camping Waypoint"); - return; - } - 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... - game.playSound (m_editor, "common/wpn_hudon.wav"); - } - return; - - case 9: - index = getEditorNeareset (); - - if (index != INVALID_WAYPOINT_INDEX && m_paths[index] != nullptr) { - distance = (m_paths[index]->origin - m_editor->v.origin).length (); - - if (distance < 50.0f) { - placeNew = false; - - path = m_paths[index]; - path->origin = (path->origin + m_learnPosition) * 0.5f; - } - } - else { - newOrigin = m_learnPosition; - } - break; - - case 10: - index = getEditorNeareset (); - - if (index != INVALID_WAYPOINT_INDEX && m_paths[index] != nullptr) { - distance = (m_paths[index]->origin - m_editor->v.origin).length (); - - if (distance < 50.0f) { - placeNew = false; - path = m_paths[index]; - - int connectionFlags = 0; - - for (i = 0; i < MAX_PATH_INDEX; i++) { - connectionFlags += path->connectionFlags[i]; - } - if (connectionFlags == 0) { - path->origin = (path->origin + m_editor->v.origin) * 0.5f; - } - } - } - break; - } - - if (placeNew) { - if (m_numWaypoints >= MAX_WAYPOINTS) { - return; - } - index = m_numWaypoints; - - m_paths[index] = new Path; - path = m_paths[index]; - - // increment total number of waypoints - m_numWaypoints++; - path->pathNumber = index; - path->flags = 0; - - // store the origin (location) of this waypoint - path->origin = newOrigin; - addToBucket (newOrigin, index); - - path->campEndX = 0.0f; - path->campEndY = 0.0f; - path->campStartX = 0.0f; - path->campStartY = 0.0f; - - for (i = 0; i < MAX_PATH_INDEX; i++) { - path->index[i] = INVALID_WAYPOINT_INDEX; - path->distances[i] = 0; - - path->connectionFlags[i] = 0; - path->connectionVelocity[i].nullify (); - } - - // store the last used waypoint for the auto waypoint code... - m_lastWaypoint = m_editor->v.origin; - } - - // set the time that this waypoint was originally displayed... - m_waypointDisplayTime[index] = 0; - - if (flags == 9) { - m_lastJumpWaypoint = index; - } - else if (flags == 10) { - distance = (m_paths[m_lastJumpWaypoint]->origin - m_editor->v.origin).length (); - addPath (m_lastJumpWaypoint, index, distance); - - for (i = 0; i < MAX_PATH_INDEX; i++) { - if (m_paths[m_lastJumpWaypoint]->index[i] == index) { - m_paths[m_lastJumpWaypoint]->connectionFlags[i] |= PATHFLAG_JUMP; - m_paths[m_lastJumpWaypoint]->connectionVelocity[i] = m_learnVelocity; - - break; - } - } - - calculatePathRadius (index); - return; - } - - if (path == nullptr) { - return; - } - - if (m_editor->v.flags & FL_DUCKING) { - path->flags |= FLAG_CROUCH; // set a crouch waypoint - } - - if (m_editor->v.movetype == MOVETYPE_FLY) { - path->flags |= FLAG_LADDER; - game.makeVectors (m_editor->v.v_angle); - - forward = m_editor->v.origin + m_editor->v.view_ofs + game.vec.forward * 640.0f; - path->campStartY = forward.y; - } - else if (m_isOnLadder) { - path->flags |= FLAG_LADDER; - } - - switch (flags) { - case 1: - path->flags |= FLAG_CROSSING; - path->flags |= FLAG_TF_ONLY; - break; - - case 2: - path->flags |= FLAG_CROSSING; - path->flags |= FLAG_CF_ONLY; - break; - - case 3: - path->flags |= FLAG_NOHOSTAGE; - break; - - case 4: - path->flags |= FLAG_RESCUE; - break; - - case 5: - path->flags |= FLAG_CROSSING; - path->flags |= FLAG_CAMP; - - 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; - break; - - case 100: - path->flags |= FLAG_GOAL; - break; - } - - // Ladder waypoints need careful connections - if (path->flags & FLAG_LADDER) { - float minDistance = 9999.0f; - int destIndex = INVALID_WAYPOINT_INDEX; - - TraceResult tr; - - // calculate all the paths to this new waypoint - for (i = 0; i < m_numWaypoints; i++) { - if (i == index) { - continue; // skip the waypoint that was just added - } - - // other ladder waypoints should connect to this - if (m_paths[i]->flags & FLAG_LADDER) { - // check if the waypoint is reachable from the new one - 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) < m_autoPathDistance) { - distance = (m_paths[i]->origin - newOrigin).length (); - - addPath (index, i, distance); - addPath (i, index, distance); - } - } - else { - // check if the waypoint is reachable from the new one - if (isNodeReacheable (newOrigin, m_paths[i]->origin) || isNodeReacheable (m_paths[i]->origin, newOrigin)) { - distance = (m_paths[i]->origin - newOrigin).length (); - - if (distance < minDistance) { - destIndex = i; - minDistance = distance; - } - } - } - } - - if (exists (destIndex)) { - // check if the waypoint is reachable from the new one (one-way) - if (isNodeReacheable (newOrigin, m_paths[destIndex]->origin)) { - distance = (m_paths[destIndex]->origin - newOrigin).length (); - addPath (index, destIndex, distance); - } - - // check if the new one is reachable from the waypoint (other way) - if (isNodeReacheable (m_paths[destIndex]->origin, newOrigin)) { - distance = (m_paths[destIndex]->origin - newOrigin).length (); - addPath (destIndex, index, distance); - } - } - } - else { - // calculate all the paths to this new waypoint - for (i = 0; i < m_numWaypoints; i++) { - if (i == index) { - continue; // skip the waypoint that was just added - } - - // check if the waypoint is reachable from the new one (one-way) - if (isNodeReacheable (newOrigin, m_paths[i]->origin)) { - distance = (m_paths[i]->origin - newOrigin).length (); - addPath (index, i, distance); - } - - // check if the new one is reachable from the waypoint (other way) - if (isNodeReacheable (m_paths[i]->origin, newOrigin)) { - distance = (m_paths[i]->origin - newOrigin).length (); - addPath (i, index, distance); - } - } - clearConnections (index); - } - game.playSound (m_editor, "weapons/xbow_hit1.wav"); - calculatePathRadius (index); // calculate the wayzone of this waypoint -} - -void Waypoint::erase (int target) { - m_waypointsChanged = true; - - if (m_numWaypoints < 1) { - return; - } - - if (bots.getBotCount () > 0) { - bots.kickEveryone (true); - } - int index = (target == INVALID_WAYPOINT_INDEX) ? getEditorNeareset () : target; - - if (!exists (index)) { - return; - } - - Path *path = nullptr; - assert (m_paths[index] != nullptr); - - int i, j; - - for (i = 0; i < m_numWaypoints; i++) // delete all references to Node - { - path = m_paths[i]; - - for (j = 0; j < MAX_PATH_INDEX; j++) { - if (path->index[j] == index) { - path->index[j] = INVALID_WAYPOINT_INDEX; // unassign this path - path->connectionFlags[j] = 0; - path->distances[j] = 0; - path->connectionVelocity[j].nullify (); - } - } - } - - for (i = 0; i < m_numWaypoints; i++) { - path = m_paths[i]; - - if (path->pathNumber > index) { // if pathnumber bigger than deleted node... - path->pathNumber--; - } - - for (j = 0; j < MAX_PATH_INDEX; j++) { - if (path->index[j] > index) { - path->index[j]--; - } - } - } - eraseFromBucket (m_paths[index]->origin, index); - - // free deleted node - delete m_paths[index]; - m_paths[index] = nullptr; - - // rotate path array down - for (i = index; i < m_numWaypoints - 1; i++) { - m_paths[i] = m_paths[i + 1]; - } - m_numWaypoints--; - m_waypointDisplayTime[index] = 0; - - game.playSound (m_editor, "weapons/mine_activate.wav"); -} - -void Waypoint::toggleFlags (int toggleFlag) { - // this function allow manually changing flags - - int index = getEditorNeareset (); - - if (index != INVALID_WAYPOINT_INDEX) { - if (m_paths[index]->flags & toggleFlag) { - m_paths[index]->flags &= ~toggleFlag; - } - else if (!(m_paths[index]->flags & toggleFlag)) { - if (toggleFlag == FLAG_SNIPER && !(m_paths[index]->flags & FLAG_CAMP)) { - ctrl.msg ("Cannot assign sniper flag to waypoint #%d. This is not camp waypoint", index); - return; - } - m_paths[index]->flags |= toggleFlag; - } - - // play "done" sound... - game.playSound (m_editor, "common/wpn_hudon.wav"); - } -} - -void Waypoint::setRadius (int index, float radius) { - // this function allow manually setting the zone radius - - int node = exists (index) ? index : getEditorNeareset (); - - if (node != INVALID_WAYPOINT_INDEX) { - m_paths[node]->radius = static_cast (radius); - - // play "done" sound... - 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 (auto &index : m_paths[pointA]->index) { - if (index == pointB) { - return true; - } - } - return false; -} - -int Waypoint::getFacingIndex (void) { - // this function finds waypoint the user is pointing at. - - int indexToPoint = INVALID_WAYPOINT_INDEX; - - Array cones; - float maxCone = 0.0f; - - // find the waypoint the user is pointing at - for (int i = 0; i < m_numWaypoints; i++) { - auto path = m_paths[i]; - - if ((path->origin - m_editor->v.origin).lengthSq () > cr::square (500.0f)) { - continue; - } - cones.clear (); - - // get the current view cones - 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) { - if (cone > 1.000f && cone > maxCone) { - maxCone = cone; - indexToPoint = i; - } - } - } - return indexToPoint; -} - -void Waypoint::pathCreate (char dir) { - // this function allow player to manually create a path from one waypoint to another - - int nodeFrom = getEditorNeareset (); - - if (nodeFrom == INVALID_WAYPOINT_INDEX) { - ctrl.msg ("Unable to find nearest waypoint in 50 units"); - return; - } - int nodeTo = m_facingAtIndex; - - if (!exists (nodeTo)) { - if (exists (m_cacheWaypointIndex)) { - nodeTo = m_cacheWaypointIndex; - } - else { - ctrl.msg ("Unable to find destination waypoint"); - return; - } - } - - if (nodeTo == nodeFrom) { - ctrl.msg ("Unable to connect waypoint with itself"); - return; - } - - float distance = (m_paths[nodeTo]->origin - m_paths[nodeFrom]->origin).length (); - - if (dir == CONNECTION_OUTGOING) { - addPath (nodeFrom, nodeTo, distance); - } - else if (dir == CONNECTION_INCOMING) { - addPath (nodeTo, nodeFrom, distance); - } - else { - addPath (nodeFrom, nodeTo, distance); - addPath (nodeTo, nodeFrom, distance); - } - - game.playSound (m_editor, "common/wpn_hudon.wav"); - m_waypointsChanged = true; -} - -void Waypoint::erasePath (void) { - // this function allow player to manually remove a path from one waypoint to another - - int nodeFrom = getEditorNeareset (); - int index = 0; - - if (nodeFrom == INVALID_WAYPOINT_INDEX) { - ctrl.msg ("Unable to find nearest waypoint in 50 units"); - return; - } - int nodeTo = m_facingAtIndex; - - if (!exists (nodeTo)) { - if (exists (m_cacheWaypointIndex)) { - nodeTo = m_cacheWaypointIndex; - } - else { - ctrl.msg ("Unable to find destination waypoint"); - return; - } - } - - for (index = 0; index < MAX_PATH_INDEX; index++) { - if (m_paths[nodeFrom]->index[index] == nodeTo) { - m_waypointsChanged = true; - - m_paths[nodeFrom]->index[index] = INVALID_WAYPOINT_INDEX; // unassigns this path - m_paths[nodeFrom]->distances[index] = 0; - m_paths[nodeFrom]->connectionFlags[index] = 0; - m_paths[nodeFrom]->connectionVelocity[index].nullify (); - - game.playSound (m_editor, "weapons/mine_activate.wav"); - return; - } - } - - // not found this way ? check for incoming connections then - index = nodeFrom; - nodeFrom = nodeTo; - nodeTo = index; - - for (index = 0; index < MAX_PATH_INDEX; index++) { - if (m_paths[nodeFrom]->index[index] == nodeTo) { - m_waypointsChanged = true; - - m_paths[nodeFrom]->index[index] = INVALID_WAYPOINT_INDEX; // unassign this path - m_paths[nodeFrom]->distances[index] = 0; - - m_paths[nodeFrom]->connectionFlags[index] = 0; - m_paths[nodeFrom]->connectionVelocity[index].nullify (); - - game.playSound (m_editor, "weapons/mine_activate.wav"); - return; - } - } - ctrl.msg ("There is already no path on this waypoint"); -} - -void Waypoint::cachePoint (int index) { - int node = exists (index) ? index : getEditorNeareset (); - - if (node == INVALID_WAYPOINT_INDEX) { - m_cacheWaypointIndex = INVALID_WAYPOINT_INDEX; - ctrl.msg ("Cached waypoint cleared (nearby point not found in 50 units range)"); - - return; - } - m_cacheWaypointIndex = node; - ctrl.msg ("Waypoint #%d has been put into memory", m_cacheWaypointIndex); -} - -void Waypoint::calculatePathRadius (int index) { - // calculate "wayzones" for the nearest waypoint to pentedict (meaning a dynamic distance area to vary waypoint origin) - - Path *path = m_paths[index]; - Vector start, direction; - - if ((path->flags & (FLAG_LADDER | FLAG_GOAL | FLAG_CAMP | FLAG_RESCUE | FLAG_CROUCH)) || m_learnJumpWaypoint) { - path->radius = 0.0f; - return; - } - - for (auto &test : path->index) { - if (test != INVALID_WAYPOINT_INDEX && (m_paths[test]->flags & FLAG_LADDER)) { - path->radius = 0.0f; - return; - } - } - TraceResult tr; - bool wayBlocked = false; - - for (float scanDistance = 32.0f; scanDistance < 128.0f; scanDistance += 16.0f) { - start = path->origin; - game.makeVectors (Vector::null ()); - - direction = game.vec.forward * scanDistance; - direction = direction.toAngles (); - - path->radius = scanDistance; - - for (float circleRadius = 0.0f; circleRadius < 360.0f; circleRadius += 20.0f) { - game.makeVectors (direction); - - Vector radiusStart = start + game.vec.forward * scanDistance; - Vector radiusEnd = start + game.vec.forward * scanDistance; - - game.testHull (radiusStart, radiusEnd, TRACE_IGNORE_MONSTERS, head_hull, nullptr, &tr); - - if (tr.flFraction < 1.0f) { - game.testLine (radiusStart, radiusEnd, TRACE_IGNORE_MONSTERS, nullptr, &tr); - - if (strncmp ("func_door", STRING (tr.pHit->v.classname), 9) == 0) { - path->radius = 0.0f; - wayBlocked = true; - - break; - } - wayBlocked = true; - path->radius -= 16.0f; - - break; - } - - Vector dropStart = start + game.vec.forward * scanDistance; - Vector dropEnd = dropStart - Vector (0.0f, 0.0f, scanDistance + 60.0f); - - game.testHull (dropStart, dropEnd, TRACE_IGNORE_MONSTERS, head_hull, nullptr, &tr); - - if (tr.flFraction >= 1.0f) { - wayBlocked = true; - path->radius -= 16.0f; - - break; - } - dropStart = start - game.vec.forward * scanDistance; - dropEnd = dropStart - Vector (0.0f, 0.0f, scanDistance + 60.0f); - - game.testHull (dropStart, dropEnd, TRACE_IGNORE_MONSTERS, head_hull, nullptr, &tr); - - if (tr.flFraction >= 1.0f) { - wayBlocked = true; - path->radius -= 16.0f; - break; - } - - radiusEnd.z += 34.0f; - game.testHull (radiusStart, radiusEnd, TRACE_IGNORE_MONSTERS, head_hull, nullptr, &tr); - - if (tr.flFraction < 1.0f) { - wayBlocked = true; - path->radius -= 16.0f; - - break; - } - direction.y = cr::angleNorm (direction.y + circleRadius); - } - - if (wayBlocked) { - break; - } - } - path->radius -= 16.0f; - - if (path->radius < 0.0f) { - path->radius = 0.0f; - } -} - -void Waypoint::saveExperience (void) { - if (m_numWaypoints < 1 || m_waypointsChanged) { - return; - } - saveExtFile ("exp", "Experience", FH_EXPERIENCE, FV_EXPERIENCE, reinterpret_cast (m_experience), m_numWaypoints * m_numWaypoints * sizeof (Experience)); -} - -void Waypoint::loadExperience (void) { - delete[] m_experience; - m_experience = nullptr; - - if (m_numWaypoints < 1) { - return; - } - m_experience = new Experience[m_numWaypoints * m_numWaypoints + FastLZ::EXCESS]; - - // 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 (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; - } - } - } - bool isLoaded = loadExtFile ("exp", "Experience", FH_EXPERIENCE, FV_EXPERIENCE, reinterpret_cast (m_experience)); - - // set's the highest damage if loaded ok - if (!isLoaded) { - return; - } - - 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]; - } - } - } - } - } -} - -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 < 1 || m_waypointsChanged) { - return; - } - saveExtFile ("vis", "Visibility", FH_VISTABLE, FV_VISTABLE, reinterpret_cast (m_visLUT), MAX_WAYPOINTS * (MAX_WAYPOINTS / 4) * sizeof (uint8)); -} - -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)); -} - -bool Waypoint::loadPathMatrix (void) { - if (m_numWaypoints <= 0) { - return false; - } - return loadExtFile ("pmx", "Pathmatrix", FH_MATRIX, FV_MATRIX, reinterpret_cast (m_matrix)); -} - -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, cr::bufsize (header.header)); - - 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 \"%s\")", type, util.format ("%slearned/%s.%s", getDataDirectory (), game.getMapName (), ext)); - 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 ()) { - return false; - } - ExtHeader header; - - if (fp.read (&header, sizeof (ExtHeader)) == 0) { - util.logEntry (true, LL_ERROR, "%s data damaged (unable to read header)", type); - fp.close (); - - return false; - } - - if (!!strncmp (header.header, magic, cr::bufsize (header.header))) { - util.logEntry (true, LL_ERROR, "%s data damaged (bad header '%s')", type, header.header); - fp.close (); - - 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) { - // this function get's the light level for each waypoin on the map - - // no waypoints ? no light levels, and only one-time init - if (!m_numWaypoints || !cr::fzero (m_waypointLightLevel[0])) { - return; - } - - // update light levels for all waypoints - for (int i = 0; i < m_numWaypoints; i++) { - m_waypointLightLevel[i] = illum.getLightLevel (m_paths[i]->origin); - } - // disable lightstyle animations on finish (will be auto-enabled on mapchange) - illum.enableAnimation (false); -} - -void Waypoint::initTypes (void) { - m_terrorPoints.clear (); - m_ctPoints.clear (); - m_goalPoints.clear (); - m_campPoints.clear (); - m_rescuePoints.clear (); - m_sniperPoints.clear (); - m_visitedGoals.clear (); - - for (int i = 0; i < m_numWaypoints; i++) { - if (m_paths[i]->flags & FLAG_TF_ONLY) { - m_terrorPoints.push (i); - } - else if (m_paths[i]->flags & FLAG_CF_ONLY) { - m_ctPoints.push (i); - } - else if (m_paths[i]->flags & FLAG_GOAL) { - m_goalPoints.push (i); - } - else if (m_paths[i]->flags & FLAG_CAMP) { - m_campPoints.push (i); - } - else if (m_paths[i]->flags & FLAG_SNIPER) { - m_sniperPoints.push (i); - } - else if (m_paths[i]->flags & FLAG_RESCUE) { - m_rescuePoints.push (i); - } - } -} - -bool Waypoint::load (void) { - initBuckets (); - - if (m_loadTries++ > 3) { - m_loadTries = 0; - - m_tempInfo.assign ("Giving up loading waypoint file (%s). Something went wrong.", game.getMapName ()); - util.logEntry (true, LL_ERROR, m_tempInfo.chars ()); - - return false; - } - MemFile fp (getWaypointFilename (true)); - - WaypointHeader header; - memset (&header, 0, sizeof (header)); - - // save for faster access - 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 (infobuffer, MAX_PRINT_BUFFER - 1, fmt, ap); - va_end (ap); - - util.logEntry (true, LL_ERROR, infobuffer); - ctrl.msg (infobuffer); - - m_tempInfo = infobuffer; - - if (fp.isValid ()) { - fp.close (); - } - m_numWaypoints = 0; - m_waypointPaths = false; - - return false; - }; - - if (fp.isValid ()) { - if (fp.read (&header, sizeof (header)) == 0) { - return throwError ("%s.pwf - damaged waypoint file (unable to read header)", map); - } - - if (strncmp (header.header, FH_WAYPOINT, cr::bufsize (FH_WAYPOINT)) == 0) { - if (header.fileVersion != FV_WAYPOINT) { - return throwError ("%s.pwf - incorrect waypoint file version (expected '%d' found '%ld')", map, FV_WAYPOINT, header.fileVersion); - } - else if (!!stricmp (header.mapName, map)) { - return throwError ("%s.pwf - hacked waypoint file, file name doesn't match waypoint header information (mapname: '%s', header: '%s')", map, map, header.mapName); - } - else { - if (header.pointNumber == 0 || header.pointNumber > MAX_WAYPOINTS) { - return throwError ("%s.pwf - waypoint file contains illegal number of waypoints (mapname: '%s', header: '%s')", map, map, header.mapName); - } - - init (); - m_numWaypoints = header.pointNumber; - - for (int i = 0; i < m_numWaypoints; i++) { - m_paths[i] = new Path; - - if (fp.read (m_paths[i], sizeof (Path)) == 0) { - return throwError ("%s.pwf - truncated waypoint file (count: %d, need: %d)", map, i, m_numWaypoints); - } - - // more checks of waypoint quality - if (m_paths[i]->pathNumber < 0 || m_paths[i]->pathNumber > m_numWaypoints) { - return throwError ("%s.pwf - bad waypoint file (path #%d index is out of bounds)", map, i); - } - addToBucket (m_paths[i]->origin, i); - } - m_waypointPaths = true; - } - } - else { - return throwError ("%s.pwf is not a yapb waypoint file (header found '%s' needed '%s'", map, header.header, FH_WAYPOINT); - } - fp.close (); - } - else { - if (yb_waypoint_autodl_enable.boolean ()) { - util.logEntry (true, LL_DEFAULT, "%s.pwf does not exist, trying to download from waypoint database", map); - - switch (downloadWaypoint ()) { - case WDE_SOCKET_ERROR: - return throwError ("%s.pwf does not exist. Can't autodownload. Socket error.", map); - - case WDE_CONNECT_ERROR: - return throwError ("%s.pwf does not exist. Can't autodownload. Connection problems.", map); - - case WDE_NOTFOUND_ERROR: - return throwError ("%s.pwf does not exist. Can't autodownload. Waypoint not available.", map); - - case WDE_NOERROR: - util.logEntry (true, LL_DEFAULT, "%s.pwf was downloaded from waypoint database. Trying to load...", map); - return load (); - } - } - return throwError ("%s.pwf does not exist", map); - } - - if (strncmp (header.author, "official", 7) == 0) { - m_tempInfo.assign ("Using Official Waypoint File"); - } - else { - m_tempInfo.assign ("Using waypoint file by: %s", header.author); - } - - for (int i = 0; i < m_numWaypoints; i++) { - m_waypointDisplayTime[i] = 0.0f; - m_waypointLightLevel[i] = 0.0f; - } - - initPathMatrix (); - initTypes (); - - m_waypointsChanged = false; - m_pathDisplayTime = 0.0f; - m_arrowDisplayTime = 0.0f; - - loadVisibility (); - loadExperience (); - - extern ConVar yb_debug_goal; - yb_debug_goal.set (INVALID_WAYPOINT_INDEX); - - return true; -} - -void Waypoint::save (void) { - WaypointHeader header; - - memset (header.mapName, 0, sizeof (header.mapName)); - memset (header.author, 0, sizeof (header.author)); - memset (header.header, 0, sizeof (header.header)); - - strcpy (header.header, FH_WAYPOINT); - 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; - header.pointNumber = m_numWaypoints; - - File fp (getWaypointFilename (), "wb"); - - // file was opened - if (fp.isValid ()) { - // write the waypoint header to the file... - fp.write (&header, sizeof (header), 1); - - // save the waypoint paths... - for (int i = 0; i < m_numWaypoints; i++) { - fp.write (m_paths[i], sizeof (Path)); - } - fp.close (); - } - else { - util.logEntry (true, LL_ERROR, "Error writing '%s.pwf' waypoint file", game.getMapName ()); - } -} - -const char *Waypoint::getWaypointFilename (bool isMemoryFile) { - static String buffer; - 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 util.format ("%s%s.pwf", getDataDirectory (isMemoryFile), game.getMapName ()); -} - -float Waypoint::calculateTravelTime (float maxSpeed, const Vector &src, const Vector &origin) { - // this function returns 2D traveltime to a position - - return (origin - src).length2D () / maxSpeed; -} - -bool Waypoint::isReachable (Bot *bot, int index) { - // this function return whether bot able to reach index waypoint or not, depending on several factors. - - if (!bot || !exists (index)) { - return false; - } - - const Vector &src = bot->pev->origin; - const Vector &dst = m_paths[index]->origin; - - // is the destination close enough? - if ((dst - src).lengthSq () >= cr::square (320.0f)) { - return false; - } - float ladderDist = (dst - src).length2D (); - - TraceResult 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) { - - // it's should be not a problem to reach waypoint inside water... - if (bot->pev->waterlevel == 2 || bot->pev->waterlevel == 3) { - return true; - } - - // check for ladder - bool nonLadder = !(m_paths[index]->flags & FLAG_LADDER) || ladderDist > 16.0f; - - // is dest waypoint higher than src? (62 is max jump height) - if (nonLadder && dst.z > src.z + 62.0f) { - return false; // can't reach this one - } - - // is dest waypoint lower than src? - if (nonLadder && dst.z < src.z - 100.0f) { - return false; // can't reach this one - } - return true; - } - return false; -} - -bool Waypoint::isNodeReacheable (const Vector &src, const Vector &destination) { - TraceResult tr; - - float distance = (destination - src).length (); - - // is the destination not close enough? - if (distance > m_autoPathDistance) { - return false; - } - - // check if we go through a func_illusionary, in which case return false - game.testHull (src, destination, TRACE_IGNORE_MONSTERS, head_hull, m_editor, &tr); - - 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"... - 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) { - game.testLine (tr.vecEndPos, destination, TRACE_IGNORE_MONSTERS, tr.pHit, &tr); - - if (tr.flFraction < 1.0f) { - return false; - } - } - - // check for special case of both waypoints being in water... - if (engfuncs.pfnPointContents (src) == CONTENTS_WATER && engfuncs.pfnPointContents (destination) == CONTENTS_WATER) { - return true; // then they're reachable each other - } - - // is dest waypoint higher than src? (45 is max jump height) - if (destination.z > src.z + 45.0f) { - Vector sourceNew = destination; - Vector destinationNew = destination; - destinationNew.z = destinationNew.z - 50.0f; // straight down 50 units - - 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) { - return false; // can't reach this one - } - } - - // check if distance to ground drops more than step height at points between source and destination... - Vector direction = (destination - src).normalize (); // 1 unit long - Vector check = src, down = src; - - down.z = down.z - 1000.0f; // straight down 1000 units - - 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 - - while (distance > 10.0f) { - // move 10 units closer to the goal... - check = check + (direction * 10.0f); - - down = check; - down.z = down.z - 1000.0f; // straight down 1000 units - - game.testLine (check, down, TRACE_IGNORE_MONSTERS, m_editor, &tr); - - float height = tr.flFraction * 1000.0f; // height from ground - - // is the current height greater than the step height? - if (height < lastHeight - 18.0f) { - return false; // can't get there without jumping... - } - lastHeight = height; - distance = (destination - check).length (); // distance from goal - } - return true; - } - return false; -} - -void Waypoint::rebuildVisibility (void) { - if (!m_needsVisRebuild) { - return; - } - - TraceResult tr; - uint8 res, shift; - - for (m_visibilityIndex = 0; m_visibilityIndex < m_numWaypoints; m_visibilityIndex++) { - Vector sourceDuck = m_paths[m_visibilityIndex]->origin; - Vector sourceStand = m_paths[m_visibilityIndex]->origin; - - if (m_paths[m_visibilityIndex]->flags & FLAG_CROUCH) { - sourceDuck.z += 12.0f; - sourceStand.z += 18.0f + 28.0f; - } - else { - sourceDuck.z += -18.0f + 12.0f; - sourceStand.z += 28.0f; - } - uint16 standCount = 0, crouchCount = 0; - - for (int i = 0; i < m_numWaypoints; i++) { - // first check ducked visibility - Vector dest = m_paths[i]->origin; - - 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) { - res = 1; - } - else { - res = 0; - } - res <<= 1; - - 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) { - res |= 1; - } - - if (res != 0) { - dest = m_paths[i]->origin; - - // first check ducked visibility - if (m_paths[i]->flags & FLAG_CROUCH) { - dest.z += 18.0f + 28.0f; - } - else { - dest.z += 28.0f; - } - 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) { - res |= 2; - } - else { - res &= 1; - } - 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) { - res |= 1; - } - else { - res &= 2; - } - } - shift = (i % 4) << 1; - - m_visLUT[m_visibilityIndex][i >> 2] &= ~(3 << shift); - m_visLUT[m_visibilityIndex][i >> 2] |= res << shift; - - if (!(res & 2)) { - crouchCount++; - } - - if (!(res & 1)) { - standCount++; - } - } - m_paths[m_visibilityIndex]->vis.crouch = crouchCount; - m_paths[m_visibilityIndex]->vis.stand = standCount; - } - m_needsVisRebuild = false; -} - -bool Waypoint::isVisible (int srcIndex, int destIndex) { - if (!exists (srcIndex) || !exists (destIndex)) { - return false; - } - - uint8 res = m_visLUT[srcIndex][destIndex >> 2]; - res >>= (destIndex % 4) << 1; - - return !((res & 3) == 3); -} - -bool Waypoint::isDuckVisible (int srcIndex, int destIndex) { - if (!exists (srcIndex) || !exists (destIndex)) { - return false; - } - - uint8 res = m_visLUT[srcIndex][destIndex >> 2]; - res >>= (destIndex % 4) << 1; - - return !((res & 2) == 2); -} - -bool Waypoint::isStandVisible (int srcIndex, int destIndex) { - if (!exists (srcIndex) || !exists (destIndex)) { - return false; - } - - uint8 res = m_visLUT[srcIndex][destIndex >> 2]; - res >>= (destIndex % 4) << 1; - - return !((res & 1) == 1); -} - -void Waypoint::frame (void) { - // this function executes frame of waypoint operation code. - - 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 (m_editor->v.button & IN_JUMP) { - push (9); - - m_timeJumpStarted = game.timebase (); - m_endJumpPoint = true; - } - else { - m_learnVelocity = m_editor->v.velocity; - m_learnPosition = m_editor->v.origin; - } - } - else if (((m_editor->v.flags & FL_ONGROUND) || m_editor->v.movetype == MOVETYPE_FLY) && m_timeJumpStarted + 0.1f < game.timebase () && m_endJumpPoint) { - push (10); - - m_learnJumpWaypoint = false; - m_endJumpPoint = false; - } - } - - // check if it's a autowaypoint mode enabled - 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 - 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 (m_editor->v.origin, m_paths[i]->origin)) { - distance = (m_paths[i]->origin - m_editor->v.origin).lengthSq (); - - if (distance < nearestDistance) { - nearestDistance = distance; - } - } - } - - // make sure nearest waypoint is far enough away... - if (nearestDistance >= 16384.0f) { - push (0); // place a waypoint here - } - } - } - m_facingAtIndex = getFacingIndex (); - - // reset the minimal distance changed before - nearestDistance = 999999.0f; - - // 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 - m_editor->v.origin).length (); - - // check if waypoint is whitin a distance, and is visible - 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 < game.timebase ()) { - float nodeHeight = 0.0f; - - // check the node height - if (m_paths[i]->flags & FLAG_CROUCH) { - nodeHeight = 36.0f; - } - else { - nodeHeight = 72.0f; - } - float nodeHalfHeight = nodeHeight * 0.5f; - - // all waypoints are by default are green - Vector nodeColor; - - // colorize all other waypoints - if (m_paths[i]->flags & FLAG_CAMP) { - nodeColor = Vector (0, 255, 255); - } - else if (m_paths[i]->flags & FLAG_GOAL) { - nodeColor = Vector (128, 0, 255); - } - else if (m_paths[i]->flags & FLAG_LADDER) { - nodeColor = Vector (128, 64, 0); - } - else if (m_paths[i]->flags & FLAG_RESCUE) { - nodeColor = Vector (255, 255, 255); - } - else { - nodeColor = Vector (0, 255, 0); - } - - // colorize additional flags - Vector nodeFlagColor = Vector (-1, -1, -1); - - // check the colors - if (m_paths[i]->flags & FLAG_SNIPER) { - nodeFlagColor = Vector (130, 87, 0); - } - else if (m_paths[i]->flags & FLAG_NOHOSTAGE) { - nodeFlagColor = Vector (255, 255, 255); - } - else if (m_paths[i]->flags & FLAG_TF_ONLY) { - nodeFlagColor = Vector (255, 0, 0); - } - else if (m_paths[i]->flags & FLAG_CF_ONLY) { - nodeFlagColor = Vector (0, 0, 255); - } - int nodeWidth = 14; - - if (exists (m_facingAtIndex) && i == m_facingAtIndex) { - nodeWidth *= 2; - } - - // draw node without additional flags - if (nodeFlagColor.x == -1) { - 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 { - 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] = game.timebase (); - } - } - } - - if (nearestIndex == INVALID_WAYPOINT_INDEX) { - return; - } - - // 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 < game.timebase ()) { - - // finding waypoint - pink arrow - if (m_findWPIndex != INVALID_WAYPOINT_INDEX) { - 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) { - 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) { - 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 = game.timebase (); - } - } - - // create path pointer for faster access - Path *path = m_paths[nearestIndex]; - - // draw a paths, camplines and danger directions for nearest waypoint - if (nearestDistance <= 56.0f && m_pathDisplayTime <= game.timebase ()) { - m_pathDisplayTime = game.timebase () + 1.0f; - - // draw the camplines - if (path->flags & FLAG_CAMP) { - Vector campSourceOrigin = path->origin + Vector (0.0f, 0.0f, 36.0f); - - // check if it's a source - if (path->flags & FLAG_CROUCH) { - campSourceOrigin = path->origin + Vector (0.0f, 0.0f, 18.0f); - } - Vector campStartOrigin = Vector (path->campStartX, path->campStartY, campSourceOrigin.z); // camp start - Vector campEndOrigin = Vector (path->campEndX, path->campEndY, campSourceOrigin.z); // camp end - - // draw it now - 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 - for (int i = 0; i < MAX_PATH_INDEX; i++) { - if (path->index[i] == INVALID_WAYPOINT_INDEX) { - continue; - } - // jump connection - if (path->connectionFlags[i] & PATHFLAG_JUMP) { - 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 - game.drawLine (m_editor, path->origin, m_paths[path->index[i]]->origin, 5, 0, 255, 255, 0, 200, 0, 10); - } - else { // oneway connection - 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)) { - game.drawLine (m_editor, path->origin, m_paths[i]->origin, 5, 0, 0, 192, 96, 200, 0, 10); - } - } - - // draw the radius circle - Vector origin = (path->flags & FLAG_CROUCH) ? path->origin : path->origin - Vector (0.0f, 0.0f, 18.0f); - - // if radius is nonzero, draw a full circle - if (path->radius > 0.0f) { - float sqr = cr::sqrtf (path->radius * path->radius * 0.5f); - - 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); - - 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); - - 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); - - 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); - - 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) { - int dangerIndex = getDangerIndex (game.getTeam (m_editor), nearestIndex, nearestIndex); - - 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 - String waypointMessage; - - // show the information about that point - 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 = getDangerIndex (TEAM_COUNTER, nearestIndex, nearestIndex); - int dangerIndexT = getDangerIndex (TEAM_TERRORIST, nearestIndex, nearestIndex); - - 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) { - 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) { - 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 (), m_editor) - .writeByte (TE_TEXTMESSAGE) - .writeByte (4) // channel - .writeShort (MessageWriter::fs16 (0, 1 << 13)) // x - .writeShort (MessageWriter::fs16 (0, 1 << 13)) // y - .writeByte (0) // effect - .writeByte (255) // r1 - .writeByte (255) // g1 - .writeByte (255) // b1 - .writeByte (1) // a1 - .writeByte (255) // r2 - .writeByte (255) // g2 - .writeByte (255) // b2 - .writeByte (255) // a2 - .writeShort (0) // fadeintime - .writeShort (0) // fadeouttime - .writeShort (MessageWriter::fu16 (1.1f, 1 << 8)) // holdtime - .writeString (waypointMessage.chars ()); - } -} - -bool Waypoint::isConnected (int index) { - for (int i = 0; i < m_numWaypoints; i++) { - if (i == index) { - continue; - } - for (auto &test : m_paths[i]->index) { - if (test == index) { - return true; - } - } - } - return false; -} - -bool Waypoint::checkNodes (void) { - int terrPoints = 0; - int ctPoints = 0; - int goalPoints = 0; - int rescuePoints = 0; - int i, j; - - for (i = 0; i < m_numWaypoints; i++) { - int connections = 0; - - 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) { - util.logEntry (true, LL_WARNING, "Waypoint %d connected with invalid Waypoint #%d!", i, m_paths[i]->index[j]); - return false; - } - connections++; - break; - } - } - - if (connections == 0) { - if (!isConnected (i)) { - util.logEntry (true, LL_WARNING, "Waypoint %d isn't connected with any other Waypoint!", i); - return false; - } - } - - if (m_paths[i]->pathNumber != 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) { - util.logEntry (true, LL_WARNING, "Waypoint %d Camp-Endposition not set!", i); - return false; - } - } - else if (m_paths[i]->flags & FLAG_TF_ONLY) { - terrPoints++; - } - else if (m_paths[i]->flags & FLAG_CF_ONLY) { - ctPoints++; - } - else if (m_paths[i]->flags & FLAG_GOAL) { - goalPoints++; - } - else if (m_paths[i]->flags & FLAG_RESCUE) { - rescuePoints++; - } - - 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])) { - 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) { - util.logEntry (true, LL_WARNING, "Waypoint %d - Pathindex %d points to itself!", i, k); - - engfuncs.pfnSetOrigin (m_editor, m_paths[i]->origin); - setEditFlag (WS_EDIT_ENABLED | WS_EDIT_NOCLIP); - - return false; - } - } - } - } - - if (game.mapIs (MAP_CS)) { - if (rescuePoints == 0) { - util.logEntry (true, LL_WARNING, "You didn't set a Rescue Point!"); - return false; - } - } - if (terrPoints == 0) { - util.logEntry (true, LL_WARNING, "You didn't set any Terrorist Important Point!"); - return false; - } - else if (ctPoints == 0) { - util.logEntry (true, LL_WARNING, "You didn't set any CT Important Point!"); - return false; - } - else if (goalPoints == 0) { - util.logEntry (true, LL_WARNING, "You didn't set any Goal Point!"); - return false; - } - - // perform DFS instead of floyd-warshall, this shit speedup this process in a bit - PathWalk walk; - Array visited; - visited.reserve (m_numWaypoints); - - // first check incoming connectivity, initialize the "visited" table - for (i = 0; i < m_numWaypoints; i++) { - visited[i] = false; - } - walk.push (0); // always check from waypoint number 0 - - while (!walk.empty ()) { - // pop a node from the stack - const int current = walk.first (); - walk.shift (); - - visited[current] = true; - - for (j = 0; j < MAX_PATH_INDEX; j++) { - int index = m_paths[current]->index[j]; - - // skip this waypoint as it's already visited - if (exists (index) && !visited[index]) { - visited[index] = true; - walk.push (index); - } - } - } - - for (i = 0; i < m_numWaypoints; i++) { - if (!visited[i]) { - util.logEntry (true, LL_WARNING, "Path broken from Waypoint #0 to Waypoint #%d!", i); - - engfuncs.pfnSetOrigin (m_editor, m_paths[i]->origin); - setEditFlag (WS_EDIT_ENABLED | WS_EDIT_NOCLIP); - - return false; - } - } - - // then check outgoing connectivity - Array outgoingPaths; // store incoming paths for speedup - outgoingPaths.reserve (m_numWaypoints); - - for (i = 0; i < m_numWaypoints; i++) { - outgoingPaths[i].reserve (m_numWaypoints + 1); - - for (j = 0; j < MAX_PATH_INDEX; j++) { - if (exists (m_paths[i]->index[j])) { - outgoingPaths[m_paths[i]->index[j]].push (i); - } - } - } - - // initialize the "visited" table - for (i = 0; i < m_numWaypoints; i++) { - visited[i] = false; - } - walk.clear (); - walk.push (0); // always check from waypoint number 0 - - while (!walk.empty ()) { - const int current = walk.first (); // pop a node from the stack - walk.shift (); - - for (auto &outgoing : outgoingPaths[current]) { - if (visited[outgoing]) { - continue; // skip this waypoint as it's already visited - } - visited[outgoing] = true; - walk.push (outgoing); - } - } - - for (i = 0; i < m_numWaypoints; i++) { - if (!visited[i]) { - util.logEntry (true, LL_WARNING, "Path broken from Waypoint #%d to Waypoint #0!", i); - - engfuncs.pfnSetOrigin (m_editor, m_paths[i]->origin); - setEditFlag (WS_EDIT_ENABLED | WS_EDIT_NOCLIP); - - return false; - } - } - return true; -} - -void Waypoint::initPathMatrix (void) { - delete[] m_matrix; - m_matrix = nullptr; - - m_matrix = new FloydMatrix[m_numWaypoints * m_numWaypoints + FastLZ::EXCESS]; - - if (loadPathMatrix ()) { - return; // matrix loaded from file - } - const int points = m_numWaypoints; - - 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 (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 (int i = 0; i < points; i++) { - (m_matrix + (i * points) + i)->dist = 0; - } - - 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; - } - } - } - } - - // save path matrix to file for faster access - savePathMatrix (); -} - -int Waypoint::getPathDist (int srcIndex, int destIndex) { - if (!exists (srcIndex) || !exists (destIndex)) { - return 1; - } - 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)) { - m_visitedGoals.push (index); - } -} - -void Waypoint::clearVisited (void) { - m_visitedGoals.clear (); -} - -bool Waypoint::isVisited (int index) { - for (auto &visited : m_visitedGoals) { - if (visited == index) { - return true; - } - } - return false; -} - -void Waypoint::addBasic (void) { - // this function creates basic waypoint types on map - - edict_t *ent = nullptr; - - // first of all, if map contains ladder points, create it - while (!game.isNullEntity (ent = engfuncs.pfnFindEntityByString (ent, "classname", "func_ladder"))) { - Vector ladderLeft = ent->v.absmin; - Vector ladderRight = ent->v.absmax; - ladderLeft.z = ladderRight.z; - - TraceResult tr; - Vector up, down, front, back; - - Vector diff = ((ladderLeft - ladderRight) ^ Vector (0.0f, 0.0f, 0.0f)).normalize () * 15.0f; - front = back = game.getAbsPos (ent); - - front = front + diff; // front - back = back - diff; // back - - up = down = front; - down.z = ent->v.absmax.z; - - game.testHull (down, up, TRACE_IGNORE_MONSTERS, point_hull, nullptr, &tr); - - if (engfuncs.pfnPointContents (up) == CONTENTS_SOLID || tr.flFraction != 1.0f) { - up = down = back; - down.z = ent->v.absmax.z; - } - - 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); - m_isOnLadder = true; - - do { - if (getNearestNoBuckets (point, 50.0f) == INVALID_WAYPOINT_INDEX) { - push (3, point); - } - point.z += 160; - } while (point.z < down.z - 40.0f); - - point = down + Vector (0.0f, 0.0f, 38.0f); - - if (getNearestNoBuckets (point, 50.0f) == INVALID_WAYPOINT_INDEX) { - push (3, point); - } - m_isOnLadder = false; - } - - auto autoCreateForEntity = [](int type, const char *entity) { - edict_t *ent = nullptr; - - 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); - } - } - }; - - autoCreateForEntity (0, "info_player_deathmatch"); // then terrortist spawnpoints - autoCreateForEntity (0, "info_player_start"); // then add ct spawnpoints - autoCreateForEntity (0, "info_vip_start"); // then vip spawnpoint - autoCreateForEntity (0, "armoury_entity"); // weapons on the map ? - - autoCreateForEntity (4, "func_hostage_rescue"); // hostage rescue zone - autoCreateForEntity (4, "info_hostage_rescue"); // hostage rescue zone (same as above) - - autoCreateForEntity (100, "func_bomb_target"); // bombspot zone - autoCreateForEntity (100, "info_bomb_target"); // bombspot zone (same as above) - autoCreateForEntity (100, "hostage_entity"); // hostage entities - autoCreateForEntity (100, "func_vip_safetyzone"); // vip rescue (safety) zone - autoCreateForEntity (100, "func_escapezone"); // terrorist escape zone -} - -void Waypoint::eraseFromDisk (void) { - // this function removes waypoint file from the hard disk - - StringArray forErase; - const char *map = game.getMapName (); - - bots.kickEveryone (true); - - // if we're delete waypoint, delete all corresponding to it files - 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 ()); - util.logEntry (true, LL_DEFAULT, "File %s, has been deleted from the hard disk", item.chars ()); - } - else { - util.logEntry (true, LL_ERROR, "Unable to open %s", item.chars ()); - } - } - init (); // reintialize points -} - -const char *Waypoint::getDataDirectory (bool isMemoryFile) { - static String buffer; - buffer.clear (); - - if (isMemoryFile) { - buffer.assign ("addons/yapb/data/"); - } - else { - buffer.assign ("%s/addons/yapb/data/", game.getModName ()); - } - return buffer.chars (); -} - -void Waypoint::setBombPos (bool reset, const Vector &pos) { - // this function stores the bomb position as a vector - - if (reset) { - m_bombPos.nullify (); - bots.setBombPlanted (false); - - return; - } - - if (!pos.empty ()) { - m_bombPos = pos; - return; - } - edict_t *ent = nullptr; - - while (!game.isNullEntity (ent = engfuncs.pfnFindEntityByString (ent, "classname", "grenade"))) { - if (strcmp (STRING (ent->v.model) + 9, "c4.mdl") == 0) { - m_bombPos = game.getAbsPos (ent); - break; - } - } -} - -void Waypoint::startLearnJump (void) { - m_learnJumpWaypoint = true; -} - -void Waypoint::setSearchIndex (int index) { - m_findWPIndex = index; - - if (exists (m_findWPIndex)) { - ctrl.msg ("Showing Direction to Waypoint #%d", m_findWPIndex); - } - else { - m_findWPIndex = INVALID_WAYPOINT_INDEX; - } -} - -Waypoint::Waypoint (void) { - cleanupPathMemory (); - - memset (m_visLUT, 0, sizeof (m_visLUT)); - memset (m_waypointDisplayTime, 0, sizeof (m_waypointDisplayTime)); - memset (m_waypointLightLevel, 0, sizeof (m_waypointLightLevel)); - - m_waypointPaths = false; - m_endJumpPoint = false; - m_needsVisRebuild = false; - m_learnJumpWaypoint = false; - m_waypointsChanged = false; - m_timeJumpStarted = 0.0f; - - m_lastJumpWaypoint = INVALID_WAYPOINT_INDEX; - m_cacheWaypointIndex = INVALID_WAYPOINT_INDEX; - m_findWPIndex = INVALID_WAYPOINT_INDEX; - m_facingAtIndex = INVALID_WAYPOINT_INDEX; - m_visibilityIndex = 0; - m_loadTries = 0; - m_numWaypoints = 0; - m_isOnLadder = false; - - m_terrorPoints.clear (); - m_ctPoints.clear (); - m_goalPoints.clear (); - m_campPoints.clear (); - m_rescuePoints.clear (); - m_sniperPoints.clear (); - - m_matrix = nullptr; - m_editor = 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 (); -} - -void Waypoint::closeSocket (int sock) { -#if defined(PLATFORM_WIN32) - if (sock != -1) { - closesocket (sock); - } - WSACleanup (); -#else - if (sock != -1) - close (sock); -#endif -} - -WaypointDownloadError Waypoint::downloadWaypoint (void) { -#if defined(PLATFORM_WIN32) - WORD requestedVersion = MAKEWORD (1, 1); - WSADATA wsaData; - - int wsa = WSAStartup (requestedVersion, &wsaData); - - if (wsa != 0) { - return WDE_SOCKET_ERROR; - } -#endif - - hostent *host = gethostbyname (yb_waypoint_autodl_host.str ()); - - if (host == nullptr) { - return WDE_SOCKET_ERROR; - } - auto socketHandle = static_cast (socket (AF_INET, SOCK_STREAM, 0)); - - if (socketHandle < 0) { - closeSocket (socketHandle); - return WDE_SOCKET_ERROR; - } - sockaddr_in dest; - - timeval timeout; - timeout.tv_sec = 5; - timeout.tv_usec = 0; - - 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, reinterpret_cast (&timeout), sizeof (timeout)); - - if (result < 0) { - closeSocket (socketHandle); - return WDE_SOCKET_ERROR; - } - memset (&dest, 0, sizeof (dest)); - - dest.sin_family = AF_INET; - dest.sin_port = htons (80); - dest.sin_addr.s_addr = inet_addr (inet_ntoa (*(reinterpret_cast (host->h_addr)))); - - if (connect (socketHandle, reinterpret_cast (&dest), static_cast (sizeof (dest))) == -1) { - closeSocket (socketHandle); - return WDE_CONNECT_ERROR; - } - - String request; - 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); - return WDE_SOCKET_ERROR; - } - - const int ChunkSize = MAX_PRINT_BUFFER; - char buffer[ChunkSize] = { 0, }; - - bool finished = false; - int recvPosition = 0; - int symbolsInLine = 0; - - // scan for the end of the header - while (!finished && recvPosition < ChunkSize) { - if (recv (socketHandle, &buffer[recvPosition], 1, 0) == 0) { - finished = true; - } - - // ugly, but whatever - if (recvPosition > 2 && buffer[recvPosition - 2] == '4' && buffer[recvPosition - 1] == '0' && buffer[recvPosition] == '4') { - closeSocket (socketHandle); - return WDE_NOTFOUND_ERROR; - } - - switch (buffer[recvPosition]) { - case '\r': - break; - - case '\n': - if (symbolsInLine == 0) { - finished = true; - } - symbolsInLine = 0; - break; - - default: - symbolsInLine++; - break; - } - recvPosition++; - } - - File fp (waypoints.getWaypointFilename (), "wb"); - - if (!fp.isValid ()) { - closeSocket (socketHandle); - return WDE_SOCKET_ERROR; - } - int recvSize = 0; - - do { - recvSize = recv (socketHandle, buffer, ChunkSize, 0); - - if (recvSize > 0) { - fp.write (buffer, recvSize); - fp.flush (); - } - - } while (recvSize != 0); - - fp.close (); - closeSocket (socketHandle); - - return WDE_NOERROR; -} - -void Waypoint::initBuckets (void) { - m_numWaypoints = 0; - - for (int x = 0; x < MAX_WAYPOINT_BUCKET_MAX; x++) { - for (int y = 0; y < MAX_WAYPOINT_BUCKET_MAX; y++) { - for (int z = 0; z < MAX_WAYPOINT_BUCKET_MAX; z++) { - m_buckets[x][y][z].reserve (MAX_WAYPOINT_BUCKET_WPTS); - m_buckets[x][y][z].clear (); - } - } - } -} - -void Waypoint::addToBucket (const Vector &pos, int index) { - const Bucket &bucket = locateBucket (pos); - m_buckets[bucket.x][bucket.y][bucket.z].push (index); -} - -void Waypoint::eraseFromBucket (const Vector &pos, int index) { - const Bucket &bucket = locateBucket (pos); - IntArray &data = m_buckets[bucket.x][bucket.y][bucket.z]; - - for (size_t i = 0; i < data.length (); i++) { - if (data[i] == index) { - data.erase (i, 1); - break; - } - } -} - -Waypoint::Bucket Waypoint::locateBucket (const Vector &pos) { - constexpr float size = 4096.0f; - - return { - cr::abs (static_cast ((pos.x + size) / MAX_WAYPOINT_BUCKET_SIZE)), - cr::abs (static_cast ((pos.y + size) / MAX_WAYPOINT_BUCKET_SIZE)), - cr::abs (static_cast ((pos.z + size) / MAX_WAYPOINT_BUCKET_SIZE)) - }; -} - -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