Many small fixes to combat behaviour, navigation and perfomance.

This commit is contained in:
jeefo 2019-08-18 21:00:00 +03:00
commit f673f5cd0a
26 changed files with 1447 additions and 1330 deletions

View file

@ -219,6 +219,5 @@ public:
} }
}; };
// explose global // explose global
static auto &conf = BotConfig::get (); static auto &conf = BotConfig::get ();

View file

@ -49,6 +49,7 @@ private:
Array <BotMenu> m_menus; Array <BotMenu> m_menus;
edict_t *m_ent; edict_t *m_ent;
Bot *m_djump;
bool m_isFromConsole; bool m_isFromConsole;
bool m_rapidOutput; bool m_rapidOutput;
@ -183,7 +184,7 @@ public:
public: public:
// for the server commands // for the server commands
static void handleEngineCommands (); void handleEngineCommands ();
// for the client commands // for the client commands
bool handleClientCommands (edict_t *ent); bool handleClientCommands (edict_t *ent);

View file

@ -67,7 +67,9 @@ namespace detail {
// basic dictionary // basic dictionary
template <class K, class V, class H = StringHash <K>, size_t HashSize = 36> class Dictionary final : public DenyCopying { template <class K, class V, class H = StringHash <K>, size_t HashSize = 36> class Dictionary final : public DenyCopying {
public: public:
static constexpr size_t kInvalidIndex = static_cast <size_t> (-1); enum : size_t {
InvalidIndex = static_cast <size_t> (-1)
};
private: private:
Array <detail::DictionaryList *> m_table; Array <detail::DictionaryList *> m_table;
@ -105,7 +107,7 @@ private:
return created; return created;
} }
return kInvalidIndex; return InvalidIndex;
} }
size_t findIndex (const K &key) const { size_t findIndex (const K &key) const {
@ -126,7 +128,7 @@ public:
public: public:
bool exists (const K &key) const { bool exists (const K &key) const {
return findIndex (key) != kInvalidIndex; return findIndex (key) != InvalidIndex;
} }
bool empty () const { bool empty () const {
@ -140,7 +142,7 @@ public:
bool find (const K &key, V &value) const { bool find (const K &key, V &value) const {
size_t index = findIndex (key); size_t index = findIndex (key);
if (index == kInvalidIndex) { if (index == InvalidIndex) {
return false; return false;
} }
value = m_buckets[index].value; value = m_buckets[index].value;

View file

@ -178,12 +178,12 @@ namespace detail {
} }
size_t protocol = uri.find ("://"); size_t protocol = uri.find ("://");
if (protocol != String::kInvalidIndex) { if (protocol != String::InvalidIndex) {
result.protocol = uri.substr (0, protocol); result.protocol = uri.substr (0, protocol);
size_t host = uri.find ("/", protocol + 3); size_t host = uri.find ("/", protocol + 3);
if (host != String::kInvalidIndex) { if (host != String::InvalidIndex) {
result.path = uri.substr (host + 1); result.path = uri.substr (host + 1);
result.host = uri.substr (protocol + 3, host - protocol - 3); result.host = uri.substr (protocol + 3, host - protocol - 3);
@ -244,7 +244,7 @@ private:
String response (reinterpret_cast <const char *> (buffer)); String response (reinterpret_cast <const char *> (buffer));
size_t responseCodeStart = response.find ("HTTP/1.1"); size_t responseCodeStart = response.find ("HTTP/1.1");
if (responseCodeStart != String::kInvalidIndex) { if (responseCodeStart != String::InvalidIndex) {
String respCode = response.substr (responseCodeStart + 9, 3).trim (); String respCode = response.substr (responseCodeStart + 9, 3).trim ();
if (respCode == "200") { if (respCode == "200") {
@ -369,7 +369,7 @@ public:
String boundaryName = localPath; String boundaryName = localPath;
size_t boundarySlash = localPath.findLastOf ("\\/"); size_t boundarySlash = localPath.findLastOf ("\\/");
if (boundarySlash != String::kInvalidIndex) { if (boundarySlash != String::InvalidIndex) {
boundaryName = localPath.substr (boundarySlash + 1); boundaryName = localPath.substr (boundarySlash + 1);
} }
const String &kBoundary = "---crlib_upload_boundary_1337"; const String &kBoundary = "---crlib_upload_boundary_1337";

View file

@ -14,10 +14,14 @@
CR_NAMESPACE_BEGIN CR_NAMESPACE_BEGIN
static constexpr uint32 kLambdaSmallBufferSize = sizeof (void *) * 16;
template <typename> class Lambda; template <typename> class Lambda;
template <typename R, typename ...Args> class Lambda <R (Args...)> { template <typename R, typename ...Args> class Lambda <R (Args...)> {
private:
enum : uint32 {
LamdaSmallBufferLength = sizeof (void *) * 16
};
private:
class LambdaFunctorWrapper { class LambdaFunctorWrapper {
public: public:
LambdaFunctorWrapper () = default; LambdaFunctorWrapper () = default;
@ -69,7 +73,7 @@ template <typename R, typename ...Args> class Lambda <R (Args...)> {
union { union {
UniquePtr <LambdaFunctorWrapper> m_functor; UniquePtr <LambdaFunctorWrapper> m_functor;
uint8 m_small[kLambdaSmallBufferSize]; uint8 m_small[LamdaSmallBufferLength];
}; };
bool m_smallObject; bool m_smallObject;
@ -118,7 +122,7 @@ public:
} }
template <typename F> Lambda (F function) { template <typename F> Lambda (F function) {
if (cr::fix (sizeof (function) > kLambdaSmallBufferSize)) { if (cr::fix (sizeof (function) > LamdaSmallBufferLength)) {
m_smallObject = false; m_smallObject = false;
new (m_small) UniquePtr <LambdaFunctorWrapper> (createUniqueBase <LambdaFunctor <F>, LambdaFunctorWrapper> (cr::move (function))); new (m_small) UniquePtr <LambdaFunctorWrapper> (createUniqueBase <LambdaFunctor <F>, LambdaFunctorWrapper> (cr::move (function)));
} }

View file

@ -23,10 +23,15 @@ CR_NAMESPACE_BEGIN
// small-string optimized string class, sso stuff based on: https://github.com/elliotgoodrich/SSO-23/ // small-string optimized string class, sso stuff based on: https://github.com/elliotgoodrich/SSO-23/
class String final { class String final {
public: public:
static constexpr size_t kInvalidIndex = static_cast <size_t> (-1); enum : size_t {
InvalidIndex = static_cast <size_t> (-1)
};
private: private:
static constexpr size_t kExcessSpace = 32; enum : size_t {
ExcessSpace = 32,
CharBit = CHAR_BIT
};
private: private:
using Length = Twin <size_t, size_t>; using Length = Twin <size_t, size_t>;
@ -34,7 +39,7 @@ private:
private: private:
union Data { union Data {
struct Big { struct Big {
char excess[kExcessSpace - sizeof (char *) - 2 * sizeof (size_t)]; char excess[ExcessSpace - sizeof (char *) - 2 * sizeof (size_t)];
char *ptr; char *ptr;
size_t length; size_t length;
size_t capacity; size_t capacity;
@ -47,7 +52,9 @@ private:
} m_data; } m_data;
private: private:
static size_t const kSmallCapacity = sizeof (typename Data::Big) / sizeof (char) - 1; enum : size_t {
SmallCapacity = sizeof (typename Data::Big) / sizeof (char) - 1
};
public: public:
explicit String () { explicit String () {
@ -89,7 +96,7 @@ private:
} }
template <size_t N> static bool getMostSignificantBit (uint8 byte) { template <size_t N> static bool getMostSignificantBit (uint8 byte) {
return byte & cr::bit (CHAR_BIT - N - 1); return byte & cr::bit (CharBit - N - 1);
} }
template <size_t N> static void setLeastSignificantBit (uint8 &byte, bool bit) { template <size_t N> static void setLeastSignificantBit (uint8 &byte, bool bit) {
@ -103,10 +110,10 @@ private:
template <size_t N> static void setMostSignificantBit (uint8 &byte, bool bit) { template <size_t N> static void setMostSignificantBit (uint8 &byte, bool bit) {
if (bit) { if (bit) {
byte |= cr::bit (CHAR_BIT - N - 1); byte |= cr::bit (CharBit - N - 1);
} }
else { else {
byte &= ~cr::bit (CHAR_BIT - N - 1); byte &= ~cr::bit (CharBit - N - 1);
} }
} }
@ -144,7 +151,7 @@ private:
} }
void setLength (size_t amount, size_t capacity) { void setLength (size_t amount, size_t capacity) {
if (amount <= kSmallCapacity) { if (amount <= SmallCapacity) {
endString (m_data.small.str, amount); endString (m_data.small.str, amount);
setSmallLength (static_cast <uint8> (amount)); setSmallLength (static_cast <uint8> (amount));
} }
@ -159,11 +166,11 @@ private:
} }
void setSmallLength (uint8 length) { void setSmallLength (uint8 length) {
m_data.small.length = static_cast <char> (kSmallCapacity - length) << 2; m_data.small.length = static_cast <char> (SmallCapacity - length) << 2;
} }
size_t getSmallLength () const { size_t getSmallLength () const {
return kSmallCapacity - ((m_data.small.length >> 2) & 63u); return SmallCapacity - ((m_data.small.length >> 2) & 63u);
} }
void setDataNonSmall (size_t length, size_t capacity) { void setDataNonSmall (size_t length, size_t capacity) {
@ -208,14 +215,14 @@ public:
String &assign (const char *str, size_t length = 0) { String &assign (const char *str, size_t length = 0) {
length = length > 0 ? length : strlen (str); length = length > 0 ? length : strlen (str);
if (length <= kSmallCapacity) { if (length <= SmallCapacity) {
moveString (m_data.small.str, str, length); moveString (m_data.small.str, str, length);
endString (m_data.small.str, length); endString (m_data.small.str, length);
setSmallLength (static_cast <uint8> (length)); setSmallLength (static_cast <uint8> (length));
} }
else { else {
auto capacity = cr::max (kSmallCapacity * 2, length); auto capacity = cr::max (SmallCapacity * 2, length);
m_data.big.ptr = alloc.allocate <char> (capacity + 1); m_data.big.ptr = alloc.allocate <char> (capacity + 1);
if (m_data.big.ptr) { if (m_data.big.ptr) {
@ -287,7 +294,7 @@ public:
void resize (size_t amount) { void resize (size_t amount) {
size_t oldLength = length (); size_t oldLength = length ();
if (amount <= kSmallCapacity) { if (amount <= SmallCapacity) {
if (!isSmall ()) { if (!isSmall ()) {
auto ptr = m_data.big.ptr; auto ptr = m_data.big.ptr;
@ -300,7 +307,7 @@ public:
size_t newCapacity = 0; size_t newCapacity = 0;
if (isSmall ()) { if (isSmall ()) {
newCapacity = cr::max (amount, kSmallCapacity * 2); newCapacity = cr::max (amount, SmallCapacity * 2);
auto ptr = alloc.allocate <char> (newCapacity + 1); auto ptr = alloc.allocate <char> (newCapacity + 1);
moveString (ptr, m_data.small.str, cr::min (oldLength, amount)); moveString (ptr, m_data.small.str, cr::min (oldLength, amount));
@ -369,7 +376,7 @@ public:
return i; return i;
} }
} }
return kInvalidIndex; return InvalidIndex;
} }
size_t find (const String &pattern, size_t start = 0) const { size_t find (const String &pattern, size_t start = 0) const {
@ -377,7 +384,7 @@ public:
const size_t dataLength = length (); const size_t dataLength = length ();
if (patternLength > dataLength || start > dataLength) { if (patternLength > dataLength || start > dataLength) {
return kInvalidIndex; return InvalidIndex;
} }
for (size_t i = start; i <= dataLength - patternLength; ++i) { for (size_t i = start; i <= dataLength - patternLength; ++i) {
@ -393,7 +400,7 @@ public:
return i; return i;
} }
} }
return kInvalidIndex; return InvalidIndex;
} }
size_t rfind (char pattern) const { size_t rfind (char pattern) const {
@ -402,7 +409,7 @@ public:
return i; return i;
} }
} }
return kInvalidIndex; return InvalidIndex;
} }
size_t rfind (const String &pattern) const { size_t rfind (const String &pattern) const {
@ -410,7 +417,7 @@ public:
const size_t dataLength = length (); const size_t dataLength = length ();
if (patternLength > dataLength) { if (patternLength > dataLength) {
return kInvalidIndex; return InvalidIndex;
} }
bool match = true; bool match = true;
@ -428,7 +435,7 @@ public:
return i; return i;
} }
} }
return kInvalidIndex; return InvalidIndex;
} }
size_t findFirstOf (const String &pattern, size_t start = 0) const { size_t findFirstOf (const String &pattern, size_t start = 0) const {
@ -442,7 +449,7 @@ public:
} }
} }
} }
return kInvalidIndex; return InvalidIndex;
} }
size_t findLastOf (const String &pattern) const { size_t findLastOf (const String &pattern) const {
@ -456,7 +463,7 @@ public:
} }
} }
} }
return kInvalidIndex; return InvalidIndex;
} }
size_t findFirstNotOf (const String &pattern, size_t start = 0) const { size_t findFirstNotOf (const String &pattern, size_t start = 0) const {
@ -479,7 +486,7 @@ public:
return i; return i;
} }
} }
return kInvalidIndex; return InvalidIndex;
} }
size_t findLastNotOf (const String &pattern) const { size_t findLastNotOf (const String &pattern) const {
@ -501,10 +508,9 @@ public:
return i; return i;
} }
} }
return kInvalidIndex; return InvalidIndex;
} }
size_t countChar (char ch) const { size_t countChar (char ch) const {
size_t count = 0; size_t count = 0;
@ -533,10 +539,10 @@ public:
return count; return count;
} }
String substr (size_t start, size_t count = kInvalidIndex) const { String substr (size_t start, size_t count = InvalidIndex) const {
start = cr::min (start, length ()); start = cr::min (start, length ());
if (count == kInvalidIndex) { if (count == InvalidIndex) {
count = length (); count = length ();
} }
return String (data () + start, cr::min (count, length () - start)); return String (data () + start, cr::min (count, length () - start));
@ -551,7 +557,7 @@ public:
while (pos < length ()) { while (pos < length ()) {
pos = find (needle, pos); pos = find (needle, pos);
if (pos == kInvalidIndex) { if (pos == InvalidIndex) {
break; break;
} }
erase (pos, needle.length ()); erase (pos, needle.length ());
@ -581,7 +587,7 @@ public:
Array <String> tokens; Array <String> tokens;
size_t prev = 0, pos = 0; size_t prev = 0, pos = 0;
while ((pos = find (delim, pos)) != kInvalidIndex) { while ((pos = find (delim, pos)) != InvalidIndex) {
tokens.push (substr (prev, pos - prev)); tokens.push (substr (prev, pos - prev));
prev = ++pos; prev = ++pos;
} }
@ -641,7 +647,7 @@ public:
} }
bool contains (const String &rhs) const { bool contains (const String &rhs) const {
return find (rhs) != kInvalidIndex; return find (rhs) != InvalidIndex;
} }
String &lowercase () { String &lowercase () {
@ -670,7 +676,7 @@ public:
size_t begin = length (); size_t begin = length ();
for (size_t i = 0; i < begin; ++i) { for (size_t i = 0; i < begin; ++i) {
if (characters.find (at (i)) == kInvalidIndex) { if (characters.find (at (i)) == InvalidIndex) {
begin = i; begin = i;
break; break;
} }
@ -682,7 +688,7 @@ public:
size_t end = 0; size_t end = 0;
for (size_t i = length (); i > 0; --i) { for (size_t i = length (); i > 0; --i) {
if (characters.find (at (i - 1)) == kInvalidIndex) { if (characters.find (at (i - 1)) == InvalidIndex) {
end = i; end = i;
break; break;
} }

View file

@ -14,67 +14,71 @@
CR_NAMESPACE_BEGIN CR_NAMESPACE_BEGIN
// 3dmath vector // 3dmath vector
class Vector final { template <typename T> class Vec3D {
public: public:
float x = 0.0f, y = 0.0f, z = 0.0f; T x = 0.0f, y = 0.0f, z = 0.0f;
public: public:
Vector (const float scaler = 0.0f) : x (scaler), y (scaler), z (scaler) Vec3D (const T &scaler = 0.0f) : x (scaler), y (scaler), z (scaler)
{ } { }
explicit Vector (const float _x, const float _y, const float _z) : x (_x), y (_y), z (_z) Vec3D (const T &x, const T &y, const T &z) : x (x), y (y), z (z)
{ } { }
Vector (float *rhs) : x (rhs[0]), y (rhs[1]), z (rhs[2]) Vec3D (T *rhs) : x (rhs[0]), y (rhs[1]), z (rhs[2])
{ } { }
Vector (const Vector &) = default; Vec3D (const Vec3D &) = default;
Vec3D (decltype (nullptr)) {
clear ();
}
public: public:
operator float *() { operator T * () {
return &x; return &x;
} }
operator const float * () const { operator const T * () const {
return &x; return &x;
} }
Vector operator + (const Vector &rhs) const { Vec3D operator + (const Vec3D &rhs) const {
return Vector (x + rhs.x, y + rhs.y, z + rhs.z); return { x + rhs.x, y + rhs.y, z + rhs.z };
} }
Vector operator - (const Vector &rhs) const { Vec3D operator - (const Vec3D &rhs) const {
return Vector (x - rhs.x, y - rhs.y, z - rhs.z); return { x - rhs.x, y - rhs.y, z - rhs.z };
} }
Vector operator - () const { Vec3D operator - () const {
return Vector (-x, -y, -z); return { -x, -y, -z };
} }
friend Vector operator * (const float scale, const Vector &rhs) { friend Vec3D operator * (const T &scale, const Vec3D &rhs) {
return Vector (rhs.x * scale, rhs.y * scale, rhs.z * scale); return { rhs.x * scale, rhs.y * scale, rhs.z * scale };
} }
Vector operator * (const float scale) const { Vec3D operator * (const T &scale) const {
return Vector (scale * x, scale * y, scale * z); return { scale * x, scale * y, scale * z };
} }
Vector operator / (const float div) const { Vec3D operator / (const T &rhs) const {
const float inv = 1 / div; const auto inv = 1 / (rhs + kFloatEqualEpsilon);
return Vector (inv * x, inv * y, inv * z); return { inv * x, inv * y, inv * z };
} }
// cross product // cross product
Vector operator ^ (const Vector &rhs) const { Vec3D operator ^ (const Vec3D &rhs) const {
return Vector (y * rhs.z - z * rhs.y, z * rhs.x - x * rhs.z, x * rhs.y - y * rhs.x); return { y * rhs.z - z * rhs.y, z * rhs.x - x * rhs.z, x * rhs.y - y * rhs.x };
} }
// dot product // dot product
float operator | (const Vector &rhs) const { T operator | (const Vec3D &rhs) const {
return x * rhs.x + y * rhs.y + z * rhs.z; return x * rhs.x + y * rhs.y + z * rhs.z;
} }
const Vector &operator += (const Vector &rhs) { const Vec3D &operator += (const Vec3D &rhs) {
x += rhs.x; x += rhs.x;
y += rhs.y; y += rhs.y;
z += rhs.z; z += rhs.z;
@ -82,24 +86,24 @@ public:
return *this; return *this;
} }
const Vector &operator -= (const Vector &right) { const Vec3D &operator -= (const Vec3D &rhs) {
x -= right.x; x -= rhs.x;
y -= right.y; y -= rhs.y;
z -= right.z; z -= rhs.z;
return *this; return *this;
} }
const Vector &operator *= (float scale) { const Vec3D &operator *= (const T &rhs) {
x *= scale; x *= rhs;
y *= scale; y *= rhs;
z *= scale; z *= rhs;
return *this; return *this;
} }
const Vector &operator /= (float div) { const Vec3D &operator /= (const T &rhs) {
const float inv = 1 / div; const auto inv = 1 / (rhs + kFloatEqualEpsilon);
x *= inv; x *= inv;
y *= inv; y *= inv;
@ -108,67 +112,66 @@ public:
return *this; return *this;
} }
bool operator == (const Vector &rhs) const { bool operator == (const Vec3D &rhs) const {
return cr::fequal (x, rhs.x) && cr::fequal (y, rhs.y) && cr::fequal (z, rhs.z); return cr::fequal (x, rhs.x) && cr::fequal (y, rhs.y) && cr::fequal (z, rhs.z);
} }
bool operator != (const Vector &rhs) const { bool operator != (const Vec3D &rhs) const {
return !cr::fequal (x, rhs.x) && !cr::fequal (y, rhs.y) && !cr::fequal (z, rhs.z); return !operator == (rhs);
} }
Vector &operator = (const Vector &) = default; void operator = (decltype (nullptr)) {
clear ();
}
Vec3D &operator = (const Vec3D &) = default;
public: public:
float length () const { T length () const {
return cr::sqrtf (lengthSq ()); return cr::sqrtf (lengthSq ());
} }
float length2d () const { T length2d () const {
return cr::sqrtf (x * x + y * y); return cr::sqrtf (x * x + y * y);
} }
float lengthSq () const { T lengthSq () const {
return x * x + y * y + z * z; return x * x + y * y + z * z;
} }
Vector get2d () const { Vec3D get2d () const {
return Vector (x, y, 0.0f); return { x, y, 0.0f };
} }
Vector normalize () const { Vec3D normalize () const {
float len = length () + cr::kFloatCmpEpsilon; auto len = length () + cr::kFloatCmpEpsilon;
if (cr::fzero (len)) { if (cr::fzero (len)) {
return Vector (0.0f, 0.0f, 1.0f); return { 0.0f, 0.0f, 1.0f };
} }
len = 1.0f / len; len = 1.0f / len;
return Vector (x * len, y * len, z * len); return { x * len, y * len, z * len };
} }
Vector normalize2d () const { Vec3D normalize2d () const {
float len = length2d () + cr::kFloatCmpEpsilon; auto len = length2d () + cr::kFloatCmpEpsilon;
if (cr::fzero (len)) { if (cr::fzero (len)) {
return Vector (0.0f, 1.0f, 0.0f); return { 0.0f, 1.0f, 0.0f };
} }
len = 1.0f / len; len = 1.0f / len;
return Vector (x * len, y * len, 0.0f); return { x * len, y * len, 0.0f };
} }
bool empty () const { bool empty () const {
return cr::fzero (x) && cr::fzero (y) && cr::fzero (z); return cr::fzero (x) && cr::fzero (y) && cr::fzero (z);
} }
static const Vector &null () {
static const Vector &s_null {};
return s_null;
}
void clear () { void clear () {
x = y = z = 0.0f; x = y = z = 0.0f;
} }
Vector clampAngles () { Vec3D clampAngles () {
x = cr::normalizeAngles (x); x = cr::normalizeAngles (x);
y = cr::normalizeAngles (y); y = cr::normalizeAngles (y);
z = 0.0f; z = 0.0f;
@ -176,78 +179,84 @@ public:
return *this; return *this;
} }
float pitch () const { T pitch () const {
if (cr::fzero (x) && cr::fzero (y)) { if (cr::fzero (x) && cr::fzero (y)) {
return 0.0f; return 0.0f;
} }
return cr::degreesToRadians (cr::atan2f (z, length2d ())); return cr::degreesToRadians (cr::atan2f (z, length2d ()));
} }
float yaw () const { T yaw () const {
if (cr::fzero (x) && cr::fzero (y)) { if (cr::fzero (x) && cr::fzero (y)) {
return 0.0f; return 0.0f;
} }
return cr::radiansToDegrees (cr:: atan2f (y, x)); return cr::radiansToDegrees (cr:: atan2f (y, x));
} }
Vector angles () const { Vec3D angles () const {
if (cr::fzero (x) && cr::fzero (y)) { if (cr::fzero (x) && cr::fzero (y)) {
return Vector (z > 0.0f ? 90.0f : 270.0f, 0.0, 0.0f); return { z > 0.0f ? 90.0f : 270.0f, 0.0, 0.0f };
} }
return Vector (cr::radiansToDegrees (cr::atan2f (z, length2d ())), cr::radiansToDegrees (cr::atan2f (y, x)), 0.0f); return { cr::radiansToDegrees (cr::atan2f (z, length2d ())), cr::radiansToDegrees (cr::atan2f (y, x)), 0.0f };
} }
void buildVectors (Vector *forward, Vector *right, Vector *upward) const { void angleVectors (Vec3D *forward, Vec3D *right, Vec3D *upward) const {
enum { pitch, yaw, roll, unused, max }; enum { pitch, yaw, roll, unused, max };
float sines[max] = { 0.0f, 0.0f, 0.0f, 0.0f }; T sines[max] = { 0.0f, 0.0f, 0.0f, 0.0f };
float cosines[max] = { 0.0f, 0.0f, 0.0f, 0.0f }; T cosines[max] = { 0.0f, 0.0f, 0.0f, 0.0f };
// compute the sine and cosine compontents // compute the sine and cosine compontents
cr::sincosf (cr::degreesToRadians (x), cr::degreesToRadians (y), cr::degreesToRadians (z), sines, cosines); cr::sincosf (cr::degreesToRadians (x), cr::degreesToRadians (y), cr::degreesToRadians (z), sines, cosines);
if (forward) { if (forward) {
forward->x = cosines[pitch] * cosines[yaw]; *forward = {
forward->y = cosines[pitch] * sines[yaw]; cosines[pitch] * cosines[yaw],
forward->z = -sines[pitch]; cosines[pitch] * sines[yaw],
-sines[pitch]
};
} }
if (right) { if (right) {
right->x = -sines[roll] * sines[pitch] * cosines[yaw] + cosines[roll] * sines[yaw]; *right = {
right->y = -sines[roll] * sines[pitch] * sines[yaw] - cosines[roll] * cosines[yaw]; -sines[roll] * sines[pitch] * cosines[yaw] + cosines[roll] * sines[yaw],
right->z = -sines[roll] * cosines[pitch]; -sines[roll] * sines[pitch] * sines[yaw] - cosines[roll] * cosines[yaw],
-sines[roll] * cosines[pitch]
};
} }
if (upward) { if (upward) {
upward->x = cosines[roll] * sines[pitch] * cosines[yaw] + sines[roll] * sines[yaw]; *upward = {
upward->y = cosines[roll] * sines[pitch] * sines[yaw] - sines[roll] * cosines[yaw]; cosines[roll] * sines[pitch] * cosines[yaw] + sines[roll] * sines[yaw],
upward->z = cosines[roll] * cosines[pitch]; upward->y = cosines[roll] * sines[pitch] * sines[yaw] - sines[roll] * cosines[yaw],
upward->z = cosines[roll] * cosines[pitch]
};
} }
} }
const Vector &forward () { const Vec3D &forward () {
static Vector s_fwd {}; static Vec3D s_fwd {};
buildVectors (&s_fwd, nullptr, nullptr); angleVectors (&s_fwd, nullptr, nullptr);
return s_fwd; return s_fwd;
} }
const Vector &upward () { const Vec3D &upward () {
static Vector s_up {}; static Vec3D s_up {};
buildVectors (nullptr, nullptr, &s_up); angleVectors (nullptr, nullptr, &s_up);
return s_up; return s_up;
} }
const Vector &right () { const Vec3D &right () {
static Vector s_right {}; static Vec3D s_right {};
buildVectors (nullptr, &s_right, nullptr); angleVectors (nullptr, &s_right, nullptr);
return s_right; return s_right;
} }
}; };
// expose global null vector // default is float
static auto &nullvec = Vector::null (); using Vector = Vec3D <float>;
CR_NAMESPACE_END CR_NAMESPACE_END

View file

@ -57,9 +57,16 @@ CR_DECLARE_SCOPED_ENUM (MapFlags,
Escape = cr::bit (3), Escape = cr::bit (3),
KnifeArena = cr::bit (4), KnifeArena = cr::bit (4),
Fun = cr::bit (5), Fun = cr::bit (5),
HasDoors = cr::bit (10) // additional flags HasDoors = cr::bit (10), // additional flags
HasButtons = cr::bit (11) // map has buttons
) )
// recursive entity search
CR_DECLARE_SCOPED_ENUM (EntitySearchResult,
Continue,
Break
);
// variable reg pair // variable reg pair
struct VarPair { struct VarPair {
Var type; Var type;
@ -74,6 +81,9 @@ using EntityFunction = void (*) (entvars_t *);
// provides utility functions to not call original engine (less call-cost) // provides utility functions to not call original engine (less call-cost)
class Game final : public Singleton <Game> { class Game final : public Singleton <Game> {
public:
using EntitySearch = Lambda <EntitySearchResult (edict_t *)>;
private: private:
int m_drawModels[DrawLine::Count]; int m_drawModels[DrawLine::Count];
int m_spawnCount[Team::Unassigned]; int m_spawnCount[Team::Unassigned];
@ -130,7 +140,7 @@ public:
Vector getAbsPos (edict_t *ent); Vector getAbsPos (edict_t *ent);
// registers a server command // registers a server command
void registerCmd (const char *command, void func_ ()); void registerEngineCommand (const char *command, void func_ ());
// play's sound to client // play's sound to client
void playSound (edict_t *ent, const char *sound); void playSound (edict_t *ent, const char *sound);
@ -159,10 +169,16 @@ public:
// executes stuff every 1 second // executes stuff every 1 second
void slowFrame (); void slowFrame ();
// search entities by variable field
void searchEntities (const String &field, const String &value, EntitySearch functor);
// search entities in sphere
void searchEntities (const Vector &position, const float radius, EntitySearch functor);
// public inlines // public inlines
public: public:
// get the current time on server // get the current time on server
float timebase () const { float time () const {
return globals->time; return globals->time;
} }
@ -361,7 +377,7 @@ private:
public: public:
MessageWriter () = default; MessageWriter () = default;
MessageWriter (int dest, int type, const Vector &pos = nullvec, edict_t *to = nullptr) { MessageWriter (int dest, int type, const Vector &pos = nullptr, edict_t *to = nullptr) {
start (dest, type, pos, to); start (dest, type, pos, to);
m_autoDestruct = true; m_autoDestruct = true;
} }
@ -373,7 +389,7 @@ public:
} }
public: public:
MessageWriter &start (int dest, int type, const Vector &pos = nullvec, edict_t *to = nullptr) { MessageWriter &start (int dest, int type, const Vector &pos = nullptr, edict_t *to = nullptr) {
engfuncs.pfnMessageBegin (dest, type, pos, to); engfuncs.pfnMessageBegin (dest, type, pos, to);
return *this; return *this;
} }

View file

@ -323,7 +323,7 @@ public:
void initNodesTypes (); void initNodesTypes ();
void initLightLevels (); void initLightLevels ();
void addPath (int addIndex, int pathIndex, float distance); void addPath (int addIndex, int pathIndex, float distance);
void add (int type, const Vector &pos = nullvec); void add (int type, const Vector &pos = nullptr);
void erase (int target); void erase (int target);
void toggleFlags (int toggleFlag); void toggleFlags (int toggleFlag);
void setRadius (int index, float radius); void setRadius (int index, float radius);
@ -344,7 +344,7 @@ public:
void initBuckets (); void initBuckets ();
void addToBucket (const Vector &pos, int index); void addToBucket (const Vector &pos, int index);
void eraseFromBucket (const Vector &pos, int index); void eraseFromBucket (const Vector &pos, int index);
void setBombPos (bool reset = false, const Vector &pos = nullvec); void setBombPos (bool reset = false, const Vector &pos = nullptr);
void updateGlobalPractice (); void updateGlobalPractice ();
void unassignPath (int from, int to); void unassignPath (int from, int to);
void setDangerValue (int team, int start, int goal, int value); void setDangerValue (int team, int start, int goal, int value);

View file

@ -81,7 +81,6 @@ public:
float getConnectionTime (int botId); float getConnectionTime (int botId);
void setBombPlanted (bool isPlanted); void setBombPlanted (bool isPlanted);
void slowFrame ();
void frame (); void frame ();
void createKillerEntity (); void createKillerEntity ();
void destroyKillerEntity (); void destroyKillerEntity ();
@ -91,7 +90,6 @@ public:
void addbot (const String &name, const String &difficulty, const String &personality, const String &team, const String &member, bool manual); void 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); void kickEveryone (bool instant = false, bool zeroQuota = true);
bool kickRandom (bool decQuota = true, Team fromTeam = Team::Unassigned);
void kickBot (int index); void kickBot (int index);
void kickFromTeam (Team team, bool removeAll = false); void kickFromTeam (Team team, bool removeAll = false);
void killAllBots (int team = -1); void killAllBots (int team = -1);
@ -117,6 +115,7 @@ public:
void handleDeath (edict_t *killer, edict_t *victim); void handleDeath (edict_t *killer, edict_t *victim);
bool isTeamStacked (int team); bool isTeamStacked (int team);
bool kickRandom (bool decQuota = true, Team fromTeam = Team::Unassigned);
public: public:
Array <edict_t *> &searchActiveGrenades () { Array <edict_t *> &searchActiveGrenades () {

View file

@ -9,6 +9,18 @@
#pragma once #pragma once
// noise types
CR_DECLARE_SCOPED_ENUM (Noise,
NeedHandle = cr::bit (0),
HitFall = cr::bit (1),
Pickup = cr::bit (2),
Zoom = cr::bit (3),
Ammo = cr::bit (4),
Hostage = cr::bit (5),
Broke = cr::bit (6),
Door = cr::bit (7)
)
class BotUtils final : public Singleton <BotUtils> { class BotUtils final : public Singleton <BotUtils> {
private: private:
bool m_needToSendWelcome; bool m_needToSendWelcome;
@ -18,6 +30,7 @@ private:
SmallArray <Client> m_clients; SmallArray <Client> m_clients;
SmallArray <Twin <String, String>> m_tags; SmallArray <Twin <String, String>> m_tags;
Dictionary <String, int32> m_noiseCache;
SimpleHook m_sendToHook; SimpleHook m_sendToHook;
public: public:
@ -65,10 +78,10 @@ public:
void traceDecals (entvars_t *pev, TraceResult *trace, int logotypeIndex); void traceDecals (entvars_t *pev, TraceResult *trace, int logotypeIndex);
// attaches sound to client struct // attaches sound to client struct
void attachSoundsToClients (edict_t *ent, const char *sample, float volume); void listenNoise (edict_t *ent, const String &sample, float volume);
// simulate sound for players // simulate sound for players
void simulateSoundUpdates (int playerIndex); void simulateNoise (int playerIndex);
// update stats on clients // update stats on clients
void updateClients (); void updateClients ();

View file

@ -524,10 +524,10 @@ struct Client {
int radio; // radio orders int radio; // radio orders
int menu; // identifier to openen menu int menu; // identifier to openen menu
int ping; // when bot latency is enabled, client ping stored here int ping; // when bot latency is enabled, client ping stored here
float hearingDistance; // distance this sound is heared
float timeSoundLasting; // time sound is played/heared
int iconFlags[kGameMaxPlayers]; // flag holding chatter icons int iconFlags[kGameMaxPlayers]; // flag holding chatter icons
float iconTimestamp[kGameMaxPlayers]; // timers for chatter icons float iconTimestamp[kGameMaxPlayers]; // timers for chatter icons
float hearingDistance; // distance this sound is heared
float timeSoundLasting; // time sound is played/heared
bool pingUpdate; // update ping ? bool pingUpdate; // update ping ?
}; };
@ -622,7 +622,6 @@ private:
float m_strafeSpeed; // current speed sideways float m_strafeSpeed; // current speed sideways
float m_minSpeed; // minimum speed in normal mode float m_minSpeed; // minimum speed in normal mode
float m_oldCombatDesire; // holds old desire for filtering float m_oldCombatDesire; // holds old desire for filtering
float m_avoidTime; // time to avoid players around
float m_itemCheckTime; // time next search for items needs to be done float m_itemCheckTime; // time next search for items needs to be done
float m_joinServerTime; // time when bot joined the game float m_joinServerTime; // time when bot joined the game
float m_playServerTime; // time bot spent in the game float m_playServerTime; // time bot spent in the game
@ -659,7 +658,6 @@ private:
edict_t *m_breakableEntity; // pointer to breakable entity edict_t *m_breakableEntity; // pointer to breakable entity
edict_t *m_targetEntity; // the entity that the bot is trying to reach edict_t *m_targetEntity; // the entity that the bot is trying to reach
edict_t *m_avoidGrenade; // pointer to grenade entity to avoid edict_t *m_avoidGrenade; // pointer to grenade entity to avoid
edict_t *m_avoid; // avoid players on our way
Vector m_liftTravelPos; // lift travel position Vector m_liftTravelPos; // lift travel position
Vector m_moveAngles; // bot move angles Vector m_moveAngles; // bot move angles
@ -732,7 +730,7 @@ private:
bool hasActiveGoal (); bool hasActiveGoal ();
bool advanceMovement (); bool advanceMovement ();
bool isBombDefusing (const Vector &bombOrigin); bool isBombDefusing (const Vector &bombOrigin);
bool isOccupiedPoint (int index); bool isOccupiedNode (int index);
bool seesItem (const Vector &dest, const char *itemName); bool seesItem (const Vector &dest, const char *itemName);
bool lastEnemyShootable (); bool lastEnemyShootable ();
bool isShootableBreakable (edict_t *ent); bool isShootableBreakable (edict_t *ent);
@ -754,9 +752,11 @@ private:
bool checkChatKeywords (String &reply); bool checkChatKeywords (String &reply);
bool isReplyingToChat (); bool isReplyingToChat ();
bool isReachableNode (int index); bool isReachableNode (int index);
bool updateLiftHandling ();
bool updateLiftStates ();
void instantChatter (int type); void instantChatter (int type);
void runAI (); void update ();
void runMovement (); void runMovement ();
void checkSpawnConditions (); void checkSpawnConditions ();
void buyStuff (); void buyStuff ();
@ -808,6 +808,7 @@ private:
void selectWeaponById (int num); void selectWeaponById (int num);
void completeTask (); void completeTask ();
void executeTasks (); void executeTasks ();
void trackEnemies ();
void normal_ (); void normal_ ();
void spraypaint_ (); void spraypaint_ ();
@ -881,8 +882,10 @@ public:
float m_agressionLevel; // dynamic aggression level (in game) float m_agressionLevel; // dynamic aggression level (in game)
float m_fearLevel; // dynamic fear level (in game) float m_fearLevel; // dynamic fear level (in game)
float m_nextEmotionUpdate; // next time to sanitize emotions float m_nextEmotionUpdate; // next time to sanitize emotions
float m_thinkFps; // skip some frames in bot thinking float m_updateTime; // skip some frames in bot thinking
float m_thinkInterval; // interval between frames float m_updateInterval; // interval between frames
float m_viewFps; // time to update bots vision
float m_viewUpdateInterval; // interval to update bot vision
float m_goalValue; // ranking value for this node float m_goalValue; // ranking value for this node
float m_viewDistance; // current view distance float m_viewDistance; // current view distance
float m_maxViewDistance; // maximum view distance float m_maxViewDistance; // maximum view distance
@ -962,20 +965,18 @@ public:
~Bot () = default; ~Bot () = default;
public: public:
void slowFrame (); // the main Lambda that decides intervals of running bot ai void logic (); /// the things that can be executed while skipping frames
void fastFrame (); /// the things that can be executed while skipping frames void takeBlind (int alpha);
void processBlind (int alpha); void takeDamage (edict_t *inflictor, int damage, int armor, int bits);
void processDamage (edict_t *inflictor, int damage, int armor, int bits);
void showDebugOverlay (); void showDebugOverlay ();
void newRound (); void newRound ();
void processBuyzoneEntering (int buyState); void enteredBuyZone (int buyState);
void pushMsgQueue (int message); void pushMsgQueue (int message);
void prepareChatMessage (const String &message); void prepareChatMessage (const String &message);
void checkForChat (); void checkForChat ();
void showChaterIcon (bool show); void showChaterIcon (bool show);
void clearSearchNodes (); void clearSearchNodes ();
void processBreakables (edict_t *touch); void checkBreakable (edict_t *touch);
void avoidIncomingPlayers (edict_t *touch);
void startTask (Task id, float desire, int data, float time, bool resume); void startTask (Task id, float desire, int data, float time, bool resume);
void clearTask (Task id); void clearTask (Task id);
void filterTasks (); void filterTasks ();
@ -986,7 +987,7 @@ public:
void pushChatMessage (int type, bool isTeamSay = false); void pushChatMessage (int type, bool isTeamSay = false);
void pushRadioMessage (int message); void pushRadioMessage (int message);
void pushChatterMessage (int message); void pushChatterMessage (int message);
void processChatterMessage (const char *tempMessage); void handleChatter (const char *tempMessage);
void tryHeadTowardRadioMessage (); void tryHeadTowardRadioMessage ();
void kill (); void kill ();
void kick (); void kick ();

View file

@ -58,7 +58,7 @@
<ClCompile Include="..\source\manager.cpp" /> <ClCompile Include="..\source\manager.cpp" />
<ClCompile Include="..\source\chatlib.cpp" /> <ClCompile Include="..\source\chatlib.cpp" />
<ClCompile Include="..\source\control.cpp" /> <ClCompile Include="..\source\control.cpp" />
<ClCompile Include="..\source\interface.cpp" /> <ClCompile Include="..\source\linkage.cpp" />
<ClCompile Include="..\source\message.cpp" /> <ClCompile Include="..\source\message.cpp" />
<ClCompile Include="..\source\navigate.cpp" /> <ClCompile Include="..\source\navigate.cpp" />
<ClCompile Include="..\source\support.cpp" /> <ClCompile Include="..\source\support.cpp" />

View file

@ -137,9 +137,6 @@
<ClCompile Include="..\source\chatlib.cpp"> <ClCompile Include="..\source\chatlib.cpp">
<Filter>source</Filter> <Filter>source</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="..\source\interface.cpp">
<Filter>source</Filter>
</ClCompile>
<ClCompile Include="..\source\navigate.cpp"> <ClCompile Include="..\source\navigate.cpp">
<Filter>source</Filter> <Filter>source</Filter>
</ClCompile> </ClCompile>
@ -170,6 +167,9 @@
<ClCompile Include="..\source\message.cpp"> <ClCompile Include="..\source\message.cpp">
<Filter>source</Filter> <Filter>source</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="..\source\linkage.cpp">
<Filter>source</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ResourceCompile Include="yapb.rc"> <ResourceCompile Include="yapb.rc">

View file

@ -22,7 +22,7 @@ LOCAL_SRC_FILES := \
combat.cpp \ combat.cpp \
control.cpp \ control.cpp \
engine.cpp \ engine.cpp \
interface.cpp \ linkage.cpp \
navigate.cpp \ navigate.cpp \
support.cpp \ support.cpp \
graph.cpp \ graph.cpp \

File diff suppressed because it is too large Load diff

View file

@ -19,11 +19,11 @@ void BotUtils::stripTags (String &line) {
for (const auto &tag : m_tags) { for (const auto &tag : m_tags) {
const size_t start = line.find (tag.first, 0); const size_t start = line.find (tag.first, 0);
if (start != String::kInvalidIndex) { if (start != String::InvalidIndex) {
const size_t end = line.find (tag.second, start); const size_t end = line.find (tag.second, start);
const size_t diff = end - start; const size_t diff = end - start;
if (end != String::kInvalidIndex && end > start && diff < 32 && diff > 4) { if (end != String::InvalidIndex && end > start && diff < 32 && diff > 2) {
line.erase (start, diff + tag.second.length ()); line.erase (start, diff + tag.second.length ());
break; break;
} }
@ -84,7 +84,7 @@ bool BotUtils::checkKeywords (const String &line, String &reply) {
for (const auto &keyword : factory.keywords) { for (const auto &keyword : factory.keywords) {
// check is keyword has occurred in message // check is keyword has occurred in message
if (line.find (keyword) != String::kInvalidIndex) { if (line.find (keyword) != String::InvalidIndex) {
StringArray &usedReplies = factory.usedReplies; StringArray &usedReplies = factory.usedReplies;
if (usedReplies.length () >= factory.replies.length () / 4) { if (usedReplies.length () >= factory.replies.length () / 4) {
@ -140,7 +140,7 @@ void Bot::prepareChatMessage (const String &message) {
size_t pos = message.find ('%'); size_t pos = message.find ('%');
// nothing found, bail out // nothing found, bail out
if (pos == String::kInvalidIndex || pos >= message.length ()) { if (pos == String::InvalidIndex || pos >= message.length ()) {
finishPreparation (); finishPreparation ();
return; return;
} }
@ -181,7 +181,7 @@ void Bot::prepareChatMessage (const String &message) {
// get roundtime // get roundtime
auto getRoundTime = [] () -> String { auto getRoundTime = [] () -> String {
auto roundTimeSecs = static_cast <int> (bots.getRoundEndTime () - game.timebase ()); auto roundTimeSecs = static_cast <int> (bots.getRoundEndTime () - game.time ());
String roundTime; String roundTime;
roundTime.assignf ("%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));
@ -235,7 +235,7 @@ void Bot::prepareChatMessage (const String &message) {
}; };
size_t replaceCounter = 0; size_t replaceCounter = 0;
while (replaceCounter < 6 && (pos = m_chatBuffer.find ('%')) != String::kInvalidIndex) { while (replaceCounter < 6 && (pos = m_chatBuffer.find ('%')) != String::InvalidIndex) {
// found one, let's do replace // found one, let's do replace
switch (m_chatBuffer[pos + 1]) { switch (m_chatBuffer[pos + 1]) {
@ -295,7 +295,7 @@ bool Bot::isReplyingToChat () {
if (m_sayTextBuffer.entityIndex != -1 && !m_sayTextBuffer.sayText.empty ()) { if (m_sayTextBuffer.entityIndex != -1 && !m_sayTextBuffer.sayText.empty ()) {
// check is time to chat is good // check is time to chat is good
if (m_sayTextBuffer.timeNextChat < game.timebase () + rg.float_ (m_sayTextBuffer.chatDelay / 2, m_sayTextBuffer.chatDelay)) { if (m_sayTextBuffer.timeNextChat < game.time () + rg.float_ (m_sayTextBuffer.chatDelay / 2, m_sayTextBuffer.chatDelay)) {
String replyText; String replyText;
if (rg.chance (m_sayTextBuffer.chatProbability + rg.int_ (20, 50)) && checkChatKeywords (replyText)) { if (rg.chance (m_sayTextBuffer.chatProbability + rg.int_ (20, 50)) && checkChatKeywords (replyText)) {
@ -303,7 +303,7 @@ bool Bot::isReplyingToChat () {
pushMsgQueue (BotMsg::Say); pushMsgQueue (BotMsg::Say);
m_sayTextBuffer.entityIndex = -1; m_sayTextBuffer.entityIndex = -1;
m_sayTextBuffer.timeNextChat = game.timebase () + m_sayTextBuffer.chatDelay; m_sayTextBuffer.timeNextChat = game.time () + m_sayTextBuffer.chatDelay;
m_sayTextBuffer.sayText.clear (); m_sayTextBuffer.sayText.clear ();
return true; return true;
@ -323,7 +323,7 @@ void Bot::checkForChat () {
} }
// bot chatting turned on? // bot chatting turned on?
if (m_lastChatTime + rg.float_ (6.0f, 10.0f) < game.timebase () && bots.getLastChatTimestamp () + rg.float_ (2.5f, 5.0f) < game.timebase () && !isReplyingToChat ()) { if (m_lastChatTime + rg.float_ (6.0f, 10.0f) < game.time () && bots.getLastChatTimestamp () + rg.float_ (2.5f, 5.0f) < game.time () && !isReplyingToChat ()) {
if (conf.hasChatBank (Chat::Dead)) { if (conf.hasChatBank (Chat::Dead)) {
const auto &phrase = conf.pickRandomFromChatBank (Chat::Dead); const auto &phrase = conf.pickRandomFromChatBank (Chat::Dead);
bool sayBufferExists = false; bool sayBufferExists = false;
@ -340,8 +340,8 @@ void Bot::checkForChat () {
prepareChatMessage (phrase); prepareChatMessage (phrase);
pushMsgQueue (BotMsg::Say); pushMsgQueue (BotMsg::Say);
m_lastChatTime = game.timebase (); m_lastChatTime = game.time ();
bots.setLastChatTimestamp (game.timebase ()); bots.setLastChatTimestamp (game.time ());
// add to ignore list // add to ignore list
m_sayTextBuffer.lastUsedSentences.push (phrase); m_sayTextBuffer.lastUsedSentences.push (phrase);

View file

@ -92,7 +92,7 @@ bool Bot::checkBodyParts (edict_t *target) {
if (isEnemyHidden (target)) { if (isEnemyHidden (target)) {
m_enemyParts = Visibility::None; m_enemyParts = Visibility::None;
m_enemyOrigin = nullvec; m_enemyOrigin = nullptr;
return false; return false;
} }
@ -178,7 +178,7 @@ bool Bot::seesEnemy (edict_t *player, bool ignoreFOV) {
} }
if ((ignoreFOV || isInViewCone (player->v.origin)) && checkBodyParts (player)) { if ((ignoreFOV || isInViewCone (player->v.origin)) && checkBodyParts (player)) {
m_seeEnemyTime = game.timebase (); m_seeEnemyTime = game.time ();
m_lastEnemy = player; m_lastEnemy = player;
m_lastEnemyOrigin = m_enemyOrigin; m_lastEnemyOrigin = m_enemyOrigin;
@ -187,11 +187,21 @@ bool Bot::seesEnemy (edict_t *player, bool ignoreFOV) {
return false; return false;
} }
void Bot::trackEnemies () {
if (lookupEnemies ()) {
m_states |= Sense::SeeingEnemy;
}
else {
m_states &= ~Sense::SeeingEnemy;
m_enemy = nullptr;
}
}
bool Bot::lookupEnemies () { bool Bot::lookupEnemies () {
// this function tries to find the best suitable enemy for the bot // 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 // do not search for enemies while we're blinded, or shooting disabled by user
if (m_enemyIgnoreTimer > game.timebase () || m_blindTime > game.timebase () || yb_ignore_enemies.bool_ ()) { if (m_enemyIgnoreTimer > game.time () || m_blindTime > game.time () || yb_ignore_enemies.bool_ ()) {
return false; return false;
} }
edict_t *player, *newEnemy = nullptr; edict_t *player, *newEnemy = nullptr;
@ -203,18 +213,18 @@ bool Bot::lookupEnemies () {
if (!game.isNullEntity (m_enemy) && (m_states & Sense::SeeingEnemy)) { if (!game.isNullEntity (m_enemy) && (m_states & Sense::SeeingEnemy)) {
m_states &= ~Sense::SuspectEnemy; m_states &= ~Sense::SuspectEnemy;
} }
else if (game.isNullEntity (m_enemy) && m_seeEnemyTime + 1.0f > game.timebase () && util.isAlive (m_lastEnemy)) { else if (game.isNullEntity (m_enemy) && m_seeEnemyTime + 1.0f > game.time () && util.isAlive (m_lastEnemy)) {
m_states |= Sense::SuspectEnemy; m_states |= Sense::SuspectEnemy;
m_aimFlags |= AimFlags::LastEnemy; m_aimFlags |= AimFlags::LastEnemy;
} }
m_enemyParts = Visibility::None; m_enemyParts = Visibility::None;
m_enemyOrigin= nullvec; m_enemyOrigin= nullptr;
if (!game.isNullEntity (m_enemy)) { if (!game.isNullEntity (m_enemy)) {
player = m_enemy; player = m_enemy;
// is player is alive // is player is alive
if (m_enemyUpdateTime > game.timebase () && (m_enemy->v.origin - pev->origin).lengthSq () < nearestDistance && util.isAlive (player) && seesEnemy (player)) { if (m_enemyUpdateTime > game.time () && (m_enemy->v.origin - pev->origin).lengthSq () < nearestDistance && util.isAlive (player) && seesEnemy (player)) {
newEnemy = player; newEnemy = player;
} }
} }
@ -262,7 +272,7 @@ bool Bot::lookupEnemies () {
} }
} }
} }
m_enemyUpdateTime = cr::clamp (game.timebase () + getFrameInterval () * 25.0f, 0.5f, 0.75f); m_enemyUpdateTime = cr::clamp (game.time () + getFrameInterval () * 25.0f, 0.5f, 0.75f);
if (game.isNullEntity (newEnemy) && !game.isNullEntity (shieldEnemy)) { if (game.isNullEntity (newEnemy) && !game.isNullEntity (shieldEnemy)) {
newEnemy = shieldEnemy; newEnemy = shieldEnemy;
@ -277,7 +287,7 @@ bool Bot::lookupEnemies () {
// if enemy is still visible and in field of view, keep it keep track of when we last saw an enemy // if enemy is still visible and in field of view, keep it keep track of when we last saw an enemy
if (newEnemy == m_enemy) { if (newEnemy == m_enemy) {
m_seeEnemyTime = game.timebase (); m_seeEnemyTime = game.time ();
// zero out reaction time // zero out reaction time
m_actualReactionTime = 0.0f; m_actualReactionTime = 0.0f;
@ -287,7 +297,7 @@ bool Bot::lookupEnemies () {
return true; return true;
} }
else { else {
if (m_seeEnemyTime + 3.0f < game.timebase () && (m_hasC4 || hasHostage () || !game.isNullEntity (m_targetEntity))) { if (m_seeEnemyTime + 3.0f < game.time () && (m_hasC4 || hasHostage () || !game.isNullEntity (m_targetEntity))) {
pushRadioMessage (Radio::EnemySpotted); pushRadioMessage (Radio::EnemySpotted);
} }
m_targetEntity = nullptr; // stop following when we see an enemy... m_targetEntity = nullptr; // stop following when we see an enemy...
@ -302,7 +312,7 @@ bool Bot::lookupEnemies () {
if (usesSniper ()) { if (usesSniper ()) {
m_enemySurpriseTime *= 0.5f; m_enemySurpriseTime *= 0.5f;
} }
m_enemySurpriseTime += game.timebase (); m_enemySurpriseTime += game.time ();
// zero out reaction time // zero out reaction time
m_actualReactionTime = 0.0f; m_actualReactionTime = 0.0f;
@ -312,7 +322,7 @@ bool Bot::lookupEnemies () {
m_enemyReachableTimer = 0.0f; m_enemyReachableTimer = 0.0f;
// keep track of when we last saw an enemy // keep track of when we last saw an enemy
m_seeEnemyTime = game.timebase (); m_seeEnemyTime = game.time ();
if (!(m_oldButtons & IN_ATTACK)) { if (!(m_oldButtons & IN_ATTACK)) {
return true; return true;
@ -324,10 +334,10 @@ bool Bot::lookupEnemies () {
continue; continue;
} }
if (other->m_seeEnemyTime + 2.0f < game.timebase () && game.isNullEntity (other->m_lastEnemy) && util.isVisible (pev->origin, other->ent ()) && other->isInViewCone (pev->origin)) { if (other->m_seeEnemyTime + 2.0f < game.time () && game.isNullEntity (other->m_lastEnemy) && util.isVisible (pev->origin, other->ent ()) && other->isInViewCone (pev->origin)) {
other->m_lastEnemy = newEnemy; other->m_lastEnemy = newEnemy;
other->m_lastEnemyOrigin = m_lastEnemyOrigin; other->m_lastEnemyOrigin = m_lastEnemyOrigin;
other->m_seeEnemyTime = game.timebase (); other->m_seeEnemyTime = game.time ();
other->m_states |= (Sense::SuspectEnemy | Sense::HearingEnemy); other->m_states |= (Sense::SuspectEnemy | Sense::HearingEnemy);
other->m_aimFlags |= AimFlags::LastEnemy; other->m_aimFlags |= AimFlags::LastEnemy;
} }
@ -343,9 +353,9 @@ bool Bot::lookupEnemies () {
m_enemy = nullptr; m_enemy = nullptr;
// shoot at dying players if no new enemy to give some more human-like illusion // shoot at dying players if no new enemy to give some more human-like illusion
if (m_seeEnemyTime + 0.3f > game.timebase ()) { if (m_seeEnemyTime + 0.3f > game.time ()) {
if (!usesSniper ()) { if (!usesSniper ()) {
m_shootAtDeadTime = game.timebase () + 0.4f; m_shootAtDeadTime = game.time () + 0.4f;
m_actualReactionTime = 0.0f; m_actualReactionTime = 0.0f;
m_states |= Sense::SuspectEnemy; m_states |= Sense::SuspectEnemy;
@ -353,7 +363,7 @@ bool Bot::lookupEnemies () {
} }
return false; return false;
} }
else if (m_shootAtDeadTime > game.timebase ()) { else if (m_shootAtDeadTime > game.time ()) {
m_actualReactionTime = 0.0f; m_actualReactionTime = 0.0f;
m_states |= Sense::SuspectEnemy; m_states |= Sense::SuspectEnemy;
@ -364,7 +374,7 @@ bool Bot::lookupEnemies () {
// if no enemy visible check if last one shoot able through wall // if no enemy visible check if last one shoot able through wall
if (yb_shoots_thru_walls.bool_ () && m_difficulty >= 2 && isPenetrableObstacle (newEnemy->v.origin)) { if (yb_shoots_thru_walls.bool_ () && m_difficulty >= 2 && isPenetrableObstacle (newEnemy->v.origin)) {
m_seeEnemyTime = game.timebase (); m_seeEnemyTime = game.time ();
m_states |= Sense::SuspectEnemy; m_states |= Sense::SuspectEnemy;
m_aimFlags |= AimFlags::LastEnemy; m_aimFlags |= AimFlags::LastEnemy;
@ -378,14 +388,14 @@ bool Bot::lookupEnemies () {
} }
// check if bots should reload... // check if bots should reload...
if ((m_aimFlags <= AimFlags::PredictPath && m_seeEnemyTime + 3.0f < game.timebase () && !(m_states & (Sense::SeeingEnemy | Sense::HearingEnemy)) && game.isNullEntity (m_lastEnemy) && game.isNullEntity (m_enemy) && getCurrentTaskId () != Task::ShootBreakable && getCurrentTaskId () != Task::PlantBomb && getCurrentTaskId () != Task::DefuseBomb) || bots.isRoundOver ()) { if ((m_aimFlags <= AimFlags::PredictPath && m_seeEnemyTime + 3.0f < game.time () && !(m_states & (Sense::SeeingEnemy | Sense::HearingEnemy)) && game.isNullEntity (m_lastEnemy) && game.isNullEntity (m_enemy) && getCurrentTaskId () != Task::ShootBreakable && getCurrentTaskId () != Task::PlantBomb && getCurrentTaskId () != Task::DefuseBomb) || bots.isRoundOver ()) {
if (!m_reloadState) { if (!m_reloadState) {
m_reloadState = Reload::Primary; m_reloadState = Reload::Primary;
} }
} }
// is the bot using a sniper rifle or a zoomable rifle? // is the bot using a sniper rifle or a zoomable rifle?
if ((usesSniper () || usesZoomableRifle ()) && m_zoomCheckTime + 1.0f < game.timebase ()) { if ((usesSniper () || usesZoomableRifle ()) && m_zoomCheckTime + 1.0f < game.time ()) {
if (pev->fov < 90.0f) { if (pev->fov < 90.0f) {
pev->button |= IN_ATTACK2; pev->button |= IN_ATTACK2;
} }
@ -398,15 +408,15 @@ bool Bot::lookupEnemies () {
Vector Bot::getBodyOffsetError (float distance) { Vector Bot::getBodyOffsetError (float distance) {
if (game.isNullEntity (m_enemy)) { if (game.isNullEntity (m_enemy)) {
return nullvec; return nullptr;
} }
if (m_aimErrorTime < game.timebase ()) { if (m_aimErrorTime < game.time ()) {
const float error = distance / (cr::clamp (m_difficulty, 1, 4) * 1000.0f); const float error = distance / (cr::clamp (m_difficulty, 1, 4) * 1000.0f);
Vector &maxs = m_enemy->v.maxs, &mins = m_enemy->v.mins; Vector &maxs = m_enemy->v.maxs, &mins = m_enemy->v.mins;
m_aimLastError = Vector (rg.float_ (mins.x * error, maxs.x * error), rg.float_ (mins.y * error, maxs.y * error), rg.float_ (mins.z * error, maxs.z * error)); m_aimLastError = Vector (rg.float_ (mins.x * error, maxs.x * error), rg.float_ (mins.y * error, maxs.y * error), rg.float_ (mins.z * error, maxs.z * error));
m_aimErrorTime = game.timebase () + rg.float_ (0.5f, 1.0f); m_aimErrorTime = game.time () + rg.float_ (0.5f, 1.0f);
} }
return m_aimLastError; return m_aimLastError;
} }
@ -434,15 +444,10 @@ const Vector &Bot::getEnemyBodyOffset () {
else if (distance < 800.0f && usesSniper ()) { else if (distance < 800.0f && usesSniper ()) {
m_enemyParts &= ~Visibility::Head; m_enemyParts &= ~Visibility::Head;
} }
// do not aim at head while enemy is soooo close enough to enemy when recoil aims at head automatically
else if (distance < kSprayDistance) {
m_enemyParts &= ~Visibility::Head;
}
Vector aimPos = m_enemy->v.origin; Vector aimPos = m_enemy->v.origin;
if (m_difficulty > 2 && !(m_enemyParts & Visibility::Other)) { if (m_difficulty > 2) {
aimPos = (m_enemy->v.velocity - pev->velocity) * getFrameInterval () + aimPos; aimPos += (m_enemy->v.velocity - pev->velocity) * (getFrameInterval () * 1.75f);
} }
// if we only suspect an enemy behind a wall take the worst skill // if we only suspect an enemy behind a wall take the worst skill
@ -450,16 +455,22 @@ const Vector &Bot::getEnemyBodyOffset () {
aimPos += getBodyOffsetError (distance); aimPos += getBodyOffsetError (distance);
} }
else { else {
bool useBody = !usesPistol () && distance > kSprayDistance && distance < 2048.0f;
// now take in account different parts of enemy body // now take in account different parts of enemy body
if (m_enemyParts & (Visibility::Head | Visibility::Body)) { if (m_enemyParts & (Visibility::Head | Visibility::Body)) {
int headshotFreq[5] = { 20, 40, 60, 80, 100 }; int headshotFreq[5] = { 20, 40, 60, 80, 100 };
// now check is our skill match to aim at head, else aim at enemy body // now check is our skill match to aim at head, else aim at enemy body
if (rg.chance (headshotFreq[m_difficulty]) || usesPistol ()) { if (rg.chance (headshotFreq[m_difficulty]) && !useBody) {
aimPos.z = headOffset (m_enemy) + getEnemyBodyOffsetCorrection (distance); aimPos.z = headOffset (m_enemy) + getEnemyBodyOffsetCorrection (distance);
} }
else { else {
aimPos.z += getEnemyBodyOffsetCorrection (distance); aimPos.z += getEnemyBodyOffsetCorrection (distance);
if (useBody) {
aimPos.z += 4.5f;
}
} }
} }
else if (m_enemyParts & Visibility::Body) { else if (m_enemyParts & Visibility::Body) {
@ -496,7 +507,7 @@ float Bot::getEnemyBodyOffsetCorrection (float distance) {
float result = -2.0f; float result = -2.0f;
if (distance < kSprayDistance) { if (distance < kSprayDistance) {
return -9.0f; return -16.0f;
} }
else if (distance >= kDoubleSprayDistance) { else if (distance >= kDoubleSprayDistance) {
if (sniper) { if (sniper) {
@ -655,7 +666,7 @@ bool Bot::needToPauseFiring (float distance) {
return false; return false;
} }
if (m_firePause > game.timebase ()) { if (m_firePause > game.time ()) {
return true; return true;
} }
@ -678,16 +689,16 @@ bool Bot::needToPauseFiring (float distance) {
const float xPunch = cr::degreesToRadians (pev->punchangle.x); const float xPunch = cr::degreesToRadians (pev->punchangle.x);
const float yPunch = cr::degreesToRadians (pev->punchangle.y); const float yPunch = cr::degreesToRadians (pev->punchangle.y);
float interval = getFrameInterval (); const float interval = getFrameInterval ();
float tolerance = (100.0f - m_difficulty * 25.0f) / 99.0f; const float tolerance = (100.0f - m_difficulty * 25.0f) / 99.0f;
// check if we need to compensate recoil // 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 (cr::tanf (cr::sqrtf (cr::abs (xPunch * xPunch) + cr::abs (yPunch * yPunch))) * distance > offset + 30.0f + tolerance) {
if (m_firePause < game.timebase ()) { if (m_firePause < game.time ()) {
m_firePause = rg.float_ (0.5f, 0.5f + 0.3f * tolerance); m_firePause = rg.float_ (0.65f, 0.65f + 0.3f * tolerance);
} }
m_firePause -= interval; m_firePause -= interval;
m_firePause += game.timebase (); m_firePause += game.time ();
return true; return true;
} }
@ -700,7 +711,7 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) {
// we want to fire weapon, don't reload now // we want to fire weapon, don't reload now
if (!m_isReloading) { if (!m_isReloading) {
m_reloadState = Reload::None; m_reloadState = Reload::None;
m_reloadCheckTime = game.timebase () + 3.0f; m_reloadCheckTime = game.time () + 3.0f;
} }
// select this weapon if it isn't already selected // select this weapon if it isn't already selected
@ -730,7 +741,7 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) {
// if we're have a glock or famas vary burst fire mode // if we're have a glock or famas vary burst fire mode
checkBurstMode (distance); checkBurstMode (distance);
if (hasShield () && m_shieldCheckTime < game.timebase () && getCurrentTaskId () != Task::Camp) // better shield gun usage if (hasShield () && m_shieldCheckTime < game.time () && getCurrentTaskId () != Task::Camp) // better shield gun usage
{ {
if (distance >= 750.0f && !isShieldDrawn ()) { if (distance >= 750.0f && !isShieldDrawn ()) {
pev->button |= IN_ATTACK2; // draw the shield pev->button |= IN_ATTACK2; // draw the shield
@ -738,11 +749,11 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) {
else if (isShieldDrawn () || (!game.isNullEntity (m_enemy) && ((m_enemy->v.button & IN_RELOAD) || !seesEntity (m_enemy->v.origin)))) { else if (isShieldDrawn () || (!game.isNullEntity (m_enemy) && ((m_enemy->v.button & IN_RELOAD) || !seesEntity (m_enemy->v.origin)))) {
pev->button |= IN_ATTACK2; // draw out the shield pev->button |= IN_ATTACK2; // draw out the shield
} }
m_shieldCheckTime = game.timebase () + 1.0f; m_shieldCheckTime = game.time () + 1.0f;
} }
// is the bot holding a sniper rifle? // is the bot holding a sniper rifle?
if (usesSniper () && m_zoomCheckTime < game.timebase ()) { if (usesSniper () && m_zoomCheckTime < game.time ()) {
// should the bot switch to the long-range zoom? // should the bot switch to the long-range zoom?
if (distance > 1500.0f && pev->fov >= 40.0f) { if (distance > 1500.0f && pev->fov >= 40.0f) {
pev->button |= IN_ATTACK2; pev->button |= IN_ATTACK2;
@ -757,11 +768,11 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) {
else if (distance <= 150.0f && pev->fov < 90.0f) { else if (distance <= 150.0f && pev->fov < 90.0f) {
pev->button |= IN_ATTACK2; pev->button |= IN_ATTACK2;
} }
m_zoomCheckTime = game.timebase () + 0.25f; m_zoomCheckTime = game.time () + 0.25f;
} }
// else is the bot holding a zoomable rifle? // else is the bot holding a zoomable rifle?
else if (m_difficulty < 3 && usesZoomableRifle () && m_zoomCheckTime < game.timebase ()) { else if (m_difficulty < 3 && usesZoomableRifle () && m_zoomCheckTime < game.time ()) {
// should the bot switch to zoomed mode? // should the bot switch to zoomed mode?
if (distance > 800.0f && pev->fov >= 90.0f) { if (distance > 800.0f && pev->fov >= 90.0f) {
pev->button |= IN_ATTACK2; pev->button |= IN_ATTACK2;
@ -771,23 +782,23 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) {
else if (distance <= 800.0f && pev->fov < 90.0f) { else if (distance <= 800.0f && pev->fov < 90.0f) {
pev->button |= IN_ATTACK2; pev->button |= IN_ATTACK2;
} }
m_zoomCheckTime = game.timebase () + 0.5f; m_zoomCheckTime = game.time () + 0.5f;
} }
// we're should stand still before firing sniper weapons, else sniping is useless.. // we're should stand still before firing sniper weapons, else sniping is useless..
if (usesSniper () && (m_states & (Sense::SeeingEnemy | Sense::SuspectEnemy)) && !m_isReloading && pev->velocity.lengthSq () > 0.0f) { if (usesSniper () && (m_states & (Sense::SeeingEnemy | Sense::SuspectEnemy)) && !m_isReloading && pev->velocity.lengthSq () > 0.0f) {
m_moveSpeed = 0.0f; m_moveSpeed = 0.0f;
m_strafeSpeed = 0.0f; m_strafeSpeed = 0.0f;
m_navTimeset = game.timebase (); m_navTimeset = game.time ();
if (cr::abs (pev->velocity.x) > 5.0f || cr::abs (pev->velocity.y) > 5.0f || cr::abs (pev->velocity.z) > 5.0f) { if (cr::abs (pev->velocity.x) > 5.0f || cr::abs (pev->velocity.y) > 5.0f || cr::abs (pev->velocity.z) > 5.0f) {
m_sniperStopTime = game.timebase () + 2.5f; m_sniperStopTime = game.time () + 2.5f;
return; return;
} }
} }
// need to care for burst fire? // need to care for burst fire?
if (distance < kSprayDistance || m_blindTime > game.timebase ()) { if (distance < kSprayDistance || m_blindTime > game.time ()) {
if (id == Weapon::Knife) { if (id == Weapon::Knife) {
if (distance < 64.0f) { if (distance < 64.0f) {
if (rg.chance (30) || hasShield ()) { if (rg.chance (30) || hasShield ()) {
@ -813,7 +824,7 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) {
} }
} }
} }
m_shootTime = game.timebase (); m_shootTime = game.time ();
} }
else { else {
if (needToPauseFiring (distance)) { if (needToPauseFiring (distance)) {
@ -822,13 +833,13 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) {
// don't attack with knife over long distance // don't attack with knife over long distance
if (id == Weapon::Knife) { if (id == Weapon::Knife) {
m_shootTime = game.timebase (); m_shootTime = game.time ();
return; return;
} }
if (tab[choosen].primaryFireHold) { if (tab[choosen].primaryFireHold) {
m_shootTime = game.timebase (); m_shootTime = game.time ();
m_zoomCheckTime = game.timebase (); m_zoomCheckTime = game.time ();
pev->button |= IN_ATTACK; // use primary attack pev->button |= IN_ATTACK; // use primary attack
} }
@ -840,21 +851,22 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) {
const int offset = cr::abs <int> (m_difficulty * 25 / 20 - 5); const int offset = cr::abs <int> (m_difficulty * 25 / 20 - 5);
m_shootTime = game.timebase () + 0.1f + rg.float_ (minDelay[offset], maxDelay[offset]); m_shootTime = game.time () + 0.1f + rg.float_ (minDelay[offset], maxDelay[offset]);
m_zoomCheckTime = game.timebase (); m_zoomCheckTime = game.time ();
} }
} }
} }
void Bot::fireWeapons () { void Bot::fireWeapons () {
// this function will return true if weapon was fired, false otherwise // this function will return true if weapon was fired, false otherwise
float distance = (m_lookAt - getEyesPos ()).length (); // how far away is the enemy? float distance = (m_lookAt - getEyesPos ()).length (); // how far away is the enemy?
// or if friend in line of fire, stop this too but do not update shoot time // or if friend in line of fire, stop this too but do not update shoot time
if (!game.isNullEntity (m_enemy)) { if (!game.isNullEntity (m_enemy)) {
if (isFriendInLineOfFire (distance)) { if (isFriendInLineOfFire (distance)) {
m_fightStyle = Fight::Strafe; m_fightStyle = Fight::Strafe;
m_lastFightStyleCheck = game.timebase (); m_lastFightStyleCheck = game.time ();
return; return;
} }
@ -905,10 +917,10 @@ void Bot::fireWeapons () {
if (prop.ammo1 != -1 && prop.ammo1 < 32 && m_ammo[prop.ammo1] >= tab[selectIndex].minPrimaryAmmo) { if (prop.ammo1 != -1 && prop.ammo1 < 32 && m_ammo[prop.ammo1] >= tab[selectIndex].minPrimaryAmmo) {
// available ammo found, reload weapon // available ammo found, reload weapon
if (m_reloadState == Reload::None || m_reloadCheckTime > game.timebase ()) { if (m_reloadState == Reload::None || m_reloadCheckTime > game.time ()) {
m_isReloading = true; m_isReloading = true;
m_reloadState = Reload::Primary; m_reloadState = Reload::Primary;
m_reloadCheckTime = game.timebase (); m_reloadCheckTime = game.time ();
if (rg.chance (cr::abs (m_difficulty * 25 - 100))) { if (rg.chance (cr::abs (m_difficulty * 25 - 100))) {
pushRadioMessage (Radio::NeedBackup); pushRadioMessage (Radio::NeedBackup);
@ -957,10 +969,14 @@ bool Bot::isWeaponBadAtDistance (int weaponIndex, float distance) {
} }
void Bot::focusEnemy () { void Bot::focusEnemy () {
if (game.isNullEntity (m_enemy)) {
return;
}
// aim for the head and/or body // aim for the head and/or body
m_lookAt = getEnemyBodyOffset (); m_lookAt = getEnemyBodyOffset ();
if (m_enemySurpriseTime > game.timebase () || game.isNullEntity (m_enemy)) { if (m_enemySurpriseTime > game.time ()) {
return; 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?
@ -1010,7 +1026,7 @@ void Bot::attackMovement () {
} }
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_lastUsedNodesTime + getFrameInterval () + 0.5f < game.timebase ()) { if (m_lastUsedNodesTime + getFrameInterval () + 0.5f < game.time ()) {
int approach; int approach;
if (m_currentWeapon == Weapon::Knife) { if (m_currentWeapon == Weapon::Knife) {
@ -1048,10 +1064,10 @@ void Bot::attackMovement () {
if (usesSniper () || !(m_enemyParts & (Visibility::Body | Visibility::Head))) { if (usesSniper () || !(m_enemyParts & (Visibility::Body | Visibility::Head))) {
m_fightStyle = Fight::Stay; m_fightStyle = Fight::Stay;
m_lastFightStyleCheck = game.timebase (); m_lastFightStyleCheck = game.time ();
} }
else if (usesRifle () || usesSubmachine ()) { else if (usesRifle () || usesSubmachine ()) {
if (m_lastFightStyleCheck + 3.0f < game.timebase ()) { if (m_lastFightStyleCheck + 3.0f < game.time ()) {
int rand = rg.int_ (1, 100); int rand = rg.int_ (1, 100);
if (distance < 450.0f) { if (distance < 450.0f) {
@ -1073,23 +1089,15 @@ void Bot::attackMovement () {
m_fightStyle = Fight::Strafe; m_fightStyle = Fight::Strafe;
} }
} }
m_lastFightStyleCheck = game.timebase (); m_lastFightStyleCheck = game.time ();
} }
} }
else { else {
if (m_lastFightStyleCheck + 3.0f < game.timebase ()) {
if (rg.chance (50)) {
m_fightStyle = Fight::Strafe; m_fightStyle = Fight::Strafe;
} }
else {
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 ()) { if (m_strafeSetTime < game.time ()) {
// to start strafing, we have to first figure out if the target is on the left side or right side // to start strafing, we have to first figure out if the target is on the left side or right side
const auto &dirToPoint = (pev->origin - m_enemy->v.origin).normalize2d (); const auto &dirToPoint = (pev->origin - m_enemy->v.origin).normalize2d ();
@ -1105,7 +1113,7 @@ void Bot::attackMovement () {
if (rg.chance (30)) { if (rg.chance (30)) {
m_combatStrafeDir = (m_combatStrafeDir == Dodge::Left ? Dodge::Right : Dodge::Left); m_combatStrafeDir = (m_combatStrafeDir == Dodge::Left ? Dodge::Right : Dodge::Left);
} }
m_strafeSetTime = game.timebase () + rg.float_ (0.5f, 3.0f); m_strafeSetTime = game.time () + rg.float_ (0.5f, 3.0f);
} }
if (m_combatStrafeDir == Dodge::Right) { if (m_combatStrafeDir == Dodge::Right) {
@ -1114,7 +1122,7 @@ void Bot::attackMovement () {
} }
else { else {
m_combatStrafeDir = Dodge::Left; m_combatStrafeDir = Dodge::Left;
m_strafeSetTime = game.timebase () + rg.float_ (0.8f, 1.3f); m_strafeSetTime = game.time () + rg.float_ (0.8f, 1.1f);
} }
} }
else { else {
@ -1123,11 +1131,11 @@ void Bot::attackMovement () {
} }
else { else {
m_combatStrafeDir = Dodge::Right; m_combatStrafeDir = Dodge::Right;
m_strafeSetTime = game.timebase () + rg.float_ (0.8f, 1.3f); m_strafeSetTime = game.time () + rg.float_ (0.8f, 1.1f);
} }
} }
if (m_difficulty >= 3 && (m_jumpTime + 5.0f < game.timebase () && isOnFloor () && rg.int_ (0, 1000) < (m_isReloading ? 8 : 2) && pev->velocity.length2d () > 120.0f) && !usesSniper ()) { if (m_difficulty >= 3 && (m_jumpTime + 5.0f < game.time () && isOnFloor () && rg.int_ (0, 1000) < (m_isReloading ? 8 : 2) && pev->velocity.length2d () > 120.0f) && !usesSniper ()) {
pev->button |= IN_JUMP; pev->button |= IN_JUMP;
} }
@ -1144,32 +1152,24 @@ void Bot::attackMovement () {
int enemyNearestIndex = graph.getNearest (m_enemy->v.origin); int enemyNearestIndex = graph.getNearest (m_enemy->v.origin);
if (graph.isDuckVisible (m_currentNodeIndex, enemyNearestIndex) && graph.isDuckVisible (enemyNearestIndex, m_currentNodeIndex)) { if (graph.isDuckVisible (m_currentNodeIndex, enemyNearestIndex) && graph.isDuckVisible (enemyNearestIndex, m_currentNodeIndex)) {
m_duckTime = game.timebase () + 0.5f; m_duckTime = game.time () + 0.64f;
} }
} }
m_moveSpeed = 0.0f; m_moveSpeed = 0.0f;
m_strafeSpeed = 0.0f; m_strafeSpeed = 0.0f;
m_navTimeset = game.timebase (); m_navTimeset = game.time ();
} }
} }
if (m_duckTime > game.timebase ()) { if (m_fightStyle == Fight::Stay || (m_duckTime > game.time () || m_sniperStopTime > game.time ())) {
if (m_moveSpeed > 0.0f) {
m_moveSpeed = 0.0f; m_moveSpeed = 0.0f;
m_strafeSpeed = 0.0f;
} }
if (m_moveSpeed > 0.0f && m_currentWeapon != Weapon::Knife) {
m_moveSpeed = getShiftSpeed ();
}
if (m_isReloading) {
m_moveSpeed = -pev->maxspeed;
m_duckTime = game.timebase () - 1.0f;
} }
if (!isInWater () && !isOnLadder () && (m_moveSpeed > 0.0f || m_strafeSpeed >= 0.0f)) { if (!isInWater () && !isOnLadder () && (m_moveSpeed > 0.0f || m_strafeSpeed >= 0.0f)) {
Vector right, forward; Vector right, forward;
pev->v_angle.buildVectors (&forward, &right, nullptr); pev->v_angle.angleVectors (&forward, &right, nullptr);
if (isDeadlyMove (pev->origin + (forward * m_moveSpeed * 0.2f) + (right * m_strafeSpeed * 0.2f) + (pev->velocity * getFrameInterval ()))) { if (isDeadlyMove (pev->origin + (forward * m_moveSpeed * 0.2f) + (right * m_strafeSpeed * 0.2f) + (pev->velocity * getFrameInterval ()))) {
m_strafeSpeed = -m_strafeSpeed; m_strafeSpeed = -m_strafeSpeed;
@ -1499,7 +1499,7 @@ void Bot::decideFollowUser () {
void Bot::updateTeamCommands () { void Bot::updateTeamCommands () {
// prevent spamming // prevent spamming
if (m_timeTeamOrder > game.timebase () + 2.0f || game.is (GameFlags::FreeForAll) || !yb_radio_mode.int_ ()) { if (m_timeTeamOrder > game.time () + 2.0f || game.is (GameFlags::FreeForAll) || !yb_radio_mode.int_ ()) {
return; return;
} }
@ -1534,7 +1534,7 @@ void Bot::updateTeamCommands () {
else if (memberExists && yb_radio_mode.int_ () == 2) { else if (memberExists && yb_radio_mode.int_ () == 2) {
pushChatterMessage (Chatter::ScaredEmotion); pushChatterMessage (Chatter::ScaredEmotion);
} }
m_timeTeamOrder = game.timebase () + rg.float_ (15.0f, 30.0f); m_timeTeamOrder = game.time () + rg.float_ (15.0f, 30.0f);
} }
bool Bot::isGroupOfEnemies (const Vector &location, int numEnemies, float radius) { bool Bot::isGroupOfEnemies (const Vector &location, int numEnemies, float radius) {
@ -1562,13 +1562,19 @@ bool Bot::isGroupOfEnemies (const Vector &location, int numEnemies, float radius
void Bot::checkReload () { void Bot::checkReload () {
// check the reload state // check the reload state
if (getCurrentTaskId () == Task::PlantBomb || getCurrentTaskId () == Task::DefuseBomb || getCurrentTaskId () == Task::PickupItem || getCurrentTaskId () == Task::ThrowFlashbang || getCurrentTaskId () == Task::ThrowSmoke || m_isUsingGrenade) { auto task = getCurrentTaskId ();
// we're should not reload, while doing next tasks
bool uninterruptibleTask = (task == Task::PlantBomb || task == Task::DefuseBomb || task == Task::PickupItem || task == Task::ThrowExplosive || task == Task::ThrowFlashbang || task == Task::ThrowSmoke);
// do not check for reload
if (uninterruptibleTask || m_isUsingGrenade) {
m_reloadState = Reload::None; m_reloadState = Reload::None;
return; return;
} }
m_isReloading = false; // update reloading status m_isReloading = false; // update reloading status
m_reloadCheckTime = game.timebase () + 3.0f; m_reloadCheckTime = game.time () + 3.0f;
if (m_reloadState != Reload::None) { if (m_reloadState != Reload::None) {
int weaponIndex = 0; int weaponIndex = 0;
@ -1611,7 +1617,7 @@ void Bot::checkReload () {
} }
else { else {
// if we have enemy don't reload next weapon // if we have enemy don't reload next weapon
if ((m_states & (Sense::SeeingEnemy | Sense::HearingEnemy)) || m_seeEnemyTime + 5.0f > game.timebase ()) { if ((m_states & (Sense::SeeingEnemy | Sense::HearingEnemy)) || m_seeEnemyTime + 5.0f > game.time ()) {
m_reloadState = Reload::None; m_reloadState = Reload::None;
return; return;
} }

View file

@ -26,15 +26,15 @@ int BotControl::cmdAddBot () {
} }
// if team is specified, modify args to set team // if team is specified, modify args to set team
if (m_args[alias].find ("_ct", 0) != String::kInvalidIndex) { if (m_args[alias].find ("_ct", 0) != String::InvalidIndex) {
m_args.set (team, "2"); m_args.set (team, "2");
} }
else if (m_args[alias].find ("_t", 0) != String::kInvalidIndex) { else if (m_args[alias].find ("_t", 0) != String::InvalidIndex) {
m_args.set (team, "1"); m_args.set (team, "1");
} }
// if highskilled bot is requsted set personality to rusher and maxout difficulty // if highskilled bot is requsted set personality to rusher and maxout difficulty
if (m_args[alias].find ("hs", 0) != String::kInvalidIndex) { if (m_args[alias].find ("hs", 0) != String::InvalidIndex) {
m_args.set (difficulty, "4"); m_args.set (difficulty, "4");
m_args.set (personality, "1"); m_args.set (personality, "1");
} }
@ -50,10 +50,10 @@ int BotControl::cmdKickBot () {
fixMissingArgs (max); fixMissingArgs (max);
// if team is specified, kick from specified tram // if team is specified, kick from specified tram
if (m_args[alias].find ("_ct", 0) != String::kInvalidIndex || getInt (team) == 2 || getStr (team) == "ct") { if (m_args[alias].find ("_ct", 0) != String::InvalidIndex || getInt (team) == 2 || getStr (team) == "ct") {
bots.kickFromTeam (Team::CT); bots.kickFromTeam (Team::CT);
} }
else if (m_args[alias].find ("_t", 0) != String::kInvalidIndex || getInt (team) == 1 || getStr (team) == "t") { else if (m_args[alias].find ("_t", 0) != String::InvalidIndex || getInt (team) == 1 || getStr (team) == "t") {
bots.kickFromTeam (Team::Terrorist); bots.kickFromTeam (Team::Terrorist);
} }
else { else {
@ -84,10 +84,10 @@ int BotControl::cmdKillBots () {
fixMissingArgs (max); fixMissingArgs (max);
// if team is specified, kick from specified tram // if team is specified, kick from specified tram
if (m_args[alias].find ("_ct", 0) != String::kInvalidIndex || getInt (team) == 2 || getStr (team) == "ct") { if (m_args[alias].find ("_ct", 0) != String::InvalidIndex || getInt (team) == 2 || getStr (team) == "ct") {
bots.killAllBots (Team::CT); bots.killAllBots (Team::CT);
} }
else if (m_args[alias].find ("_t", 0) != String::kInvalidIndex || getInt (team) == 1 || getStr (team) == "t") { else if (m_args[alias].find ("_t", 0) != String::InvalidIndex || getInt (team) == 1 || getStr (team) == "t") {
bots.killAllBots (Team::Terrorist); bots.killAllBots (Team::Terrorist);
} }
else { else {
@ -638,13 +638,13 @@ int BotControl::cmdNodePathCreate () {
graph.setEditFlag (GraphEdit::On); graph.setEditFlag (GraphEdit::On);
// choose the direction for path creation // choose the direction for path creation
if (m_args[cmd].find ("_both", 0) != String::kInvalidIndex) { if (m_args[cmd].find ("_both", 0) != String::InvalidIndex) {
graph.pathCreate (PathConnection::Bidirectional); graph.pathCreate (PathConnection::Bidirectional);
} }
else if (m_args[cmd].find ("_in", 0) != String::kInvalidIndex) { else if (m_args[cmd].find ("_in", 0) != String::InvalidIndex) {
graph.pathCreate (PathConnection::Incoming); graph.pathCreate (PathConnection::Incoming);
} }
else if (m_args[cmd].find ("_out", 0) != String::kInvalidIndex) { else if (m_args[cmd].find ("_out", 0) != String::InvalidIndex) {
graph.pathCreate (PathConnection::Outgoing); graph.pathCreate (PathConnection::Outgoing);
} }
else { else {
@ -1052,17 +1052,20 @@ int BotControl::menuClassSelect (int item) {
int BotControl::menuCommands (int item) { int BotControl::menuCommands (int item) {
showMenu (Menu::None); // reset menu display showMenu (Menu::None); // reset menu display
Bot *bot = nullptr; Bot *nearest = nullptr;
switch (item) { switch (item) {
case 1: case 1:
case 2: case 2:
if (util.findNearestPlayer (reinterpret_cast <void **> (&bot), m_ent, 600.0f, true, true, true) && bot->m_hasC4 && !bot->hasHostage ()) { if (util.findNearestPlayer (reinterpret_cast <void **> (&m_djump), m_ent, 600.0f, true, true, true, true, false) && !m_djump->m_hasC4 && !m_djump->hasHostage ()) {
if (item == 1) { if (item == 1) {
bot->startDoubleJump (m_ent); m_djump->startDoubleJump (m_ent);
} }
else { else {
bot->resetDoubleJump (); if (m_djump) {
m_djump->resetDoubleJump ();
m_djump = nullptr;
}
} }
} }
showMenu (Menu::Commands); showMenu (Menu::Commands);
@ -1070,8 +1073,8 @@ int BotControl::menuCommands (int item) {
case 3: case 3:
case 4: case 4:
if (util.findNearestPlayer (reinterpret_cast <void **> (&bot), m_ent, 600.0f, true, true, true, true, item == 4 ? false : true)) { if (util.findNearestPlayer (reinterpret_cast <void **> (&nearest), m_ent, 600.0f, true, true, true, true, item == 4 ? false : true)) {
bot->dropWeaponForUser (m_ent, item == 4 ? false : true); nearest->dropWeaponForUser (m_ent, item == 4 ? false : true);
} }
showMenu (Menu::Commands); showMenu (Menu::Commands);
break; break;
@ -1641,7 +1644,7 @@ void BotControl::showMenu (int id) {
Client &client = util.getClient (game.indexOfPlayer (m_ent)); Client &client = util.getClient (game.indexOfPlayer (m_ent));
if (id == Menu::None) { if (id == Menu::None) {
MessageWriter (MSG_ONE_UNRELIABLE, msgs.id (NetMsg::ShowMenu), nullvec, m_ent) MessageWriter (MSG_ONE_UNRELIABLE, msgs.id (NetMsg::ShowMenu), nullptr, m_ent)
.writeShort (0) .writeShort (0)
.writeChar (0) .writeChar (0)
.writeByte (0) .writeByte (0)
@ -1657,7 +1660,7 @@ void BotControl::showMenu (int id) {
MessageWriter msg; MessageWriter msg;
while (strlen (text) >= 64) { while (strlen (text) >= 64) {
msg.start (MSG_ONE_UNRELIABLE, msgs.id (NetMsg::ShowMenu), nullvec, m_ent) msg.start (MSG_ONE_UNRELIABLE, msgs.id (NetMsg::ShowMenu), nullptr, m_ent)
.writeShort (display.slots) .writeShort (display.slots)
.writeChar (-1) .writeChar (-1)
.writeByte (1); .writeByte (1);
@ -1669,7 +1672,7 @@ void BotControl::showMenu (int id) {
text += 64; text += 64;
} }
MessageWriter (MSG_ONE_UNRELIABLE, msgs.id (NetMsg::ShowMenu), nullvec, m_ent) MessageWriter (MSG_ONE_UNRELIABLE, msgs.id (NetMsg::ShowMenu), nullptr, m_ent)
.writeShort (display.slots) .writeShort (display.slots)
.writeChar (-1) .writeChar (-1)
.writeByte (0) .writeByte (0)
@ -1773,6 +1776,8 @@ void BotControl::maintainAdminRights () {
BotControl::BotControl () { BotControl::BotControl () {
m_ent = nullptr; m_ent = nullptr;
m_djump = nullptr;
m_isFromConsole = false; m_isFromConsole = false;
m_isMenuFillCommand = false; m_isMenuFillCommand = false;
m_rapidOutput = false; m_rapidOutput = false;
@ -1789,22 +1794,22 @@ BotControl::BotControl () {
m_cmds.emplace ("version/ver/about", "version [no arguments]", "Displays version information about bot build.", &BotControl::cmdVersion); m_cmds.emplace ("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 ("graphmenu/wpmenu/wptmenu", "graphmenu [noarguments]", "Opens and displays bots graph edtior.", &BotControl::cmdNodeMenu);
m_cmds.emplace ("list/listbots", "list [noarguments]", "Lists the bots currently playing on server.", &BotControl::cmdList); m_cmds.emplace ("list/listbots", "list [noarguments]", "Lists the bots currently playing on server.", &BotControl::cmdList);
m_cmds.emplace ("graph/wp/wpt/waypoint", "graph [help]", "Handles graph operations.", &BotControl::cmdNode); m_cmds.emplace ("graph/g/wp/wpt/waypoint", "graph [help]", "Handles graph operations.", &BotControl::cmdNode);
// declare the menus // declare the menus
createMenus (); createMenus ();
} }
void BotControl::handleEngineCommands () { void BotControl::handleEngineCommands () {
ctrl.collectArgs (); collectArgs ();
ctrl.setIssuer (game.getLocalEntity ()); setIssuer (game.getLocalEntity ());
ctrl.setFromConsole (true); setFromConsole (true);
ctrl.executeCommands (); executeCommands ();
} }
bool BotControl::handleClientCommands (edict_t *ent) { bool BotControl::handleClientCommands (edict_t *ent) {
ctrl.collectArgs (); collectArgs ();
setIssuer (ent); setIssuer (ent);
setFromConsole (true); setFromConsole (true);
@ -1812,7 +1817,7 @@ bool BotControl::handleClientCommands (edict_t *ent) {
} }
bool BotControl::handleMenuCommands (edict_t *ent) { bool BotControl::handleMenuCommands (edict_t *ent) {
ctrl.collectArgs (); collectArgs ();
setIssuer (ent); setIssuer (ent);
setFromConsole (false); setFromConsole (false);
@ -1827,16 +1832,15 @@ void BotControl::enableDrawModels (bool enable) {
entities.push ("info_vip_start"); entities.push ("info_vip_start");
for (auto &entity : entities) { for (auto &entity : entities) {
edict_t *ent = nullptr; game.searchEntities ("classname", entity, [&enable] (edict_t *ent) {
while (!game.isNullEntity (ent = engfuncs.pfnFindEntityByString (ent, "classname", entity.chars ()))) {
if (enable) { if (enable) {
ent->v.effects &= ~EF_NODRAW; ent->v.effects &= ~EF_NODRAW;
} }
else { else {
ent->v.effects |= EF_NODRAW; ent->v.effects |= EF_NODRAW;
} }
} return EntitySearchResult::Continue;
});
} }
} }

View file

@ -129,6 +129,9 @@ void Game::levelInitialize (edict_t *entities, int max) {
else if (strncmp (classname, "func_door", 9) == 0) { else if (strncmp (classname, "func_door", 9) == 0) {
m_mapFlags |= MapFlags::HasDoors; m_mapFlags |= MapFlags::HasDoors;
} }
else if (strncmp (classname, "func_button", 11) == 0) {
m_mapFlags |= MapFlags::HasButtons;
}
} }
// next maps doesn't have map-specific entities, so determine it by name // next maps doesn't have map-specific entities, so determine it by name
@ -152,7 +155,7 @@ void Game::drawLine (edict_t *ent, const Vector &start, const Vector &end, int w
return; // reliability check return; // reliability check
} }
MessageWriter (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, nullvec, ent) MessageWriter (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, nullptr, ent)
.writeByte (TE_BEAMPOINTS) .writeByte (TE_BEAMPOINTS)
.writeCoord (end.x) .writeCoord (end.x)
.writeCoord (end.y) .writeCoord (end.y)
@ -285,7 +288,7 @@ const char *Game::getModName () {
name = engineModName; name = engineModName;
size_t slash = name.findLastOf ("\\/"); size_t slash = name.findLastOf ("\\/");
if (slash != String::kInvalidIndex) { if (slash != String::InvalidIndex) {
name = name.substr (slash + 1); name = name.substr (slash + 1);
} }
name = name.trim (" \\/"); name = name.trim (" \\/");
@ -303,7 +306,7 @@ Vector Game::getAbsPos (edict_t *ent) {
// entity that has a bounding box has its center at the center of the bounding box itself. // entity that has a bounding box has its center at the center of the bounding box itself.
if (isNullEntity (ent)) { if (isNullEntity (ent)) {
return nullvec; return nullptr;
} }
if (ent->v.origin.empty ()) { if (ent->v.origin.empty ()) {
@ -312,7 +315,7 @@ Vector Game::getAbsPos (edict_t *ent) {
return ent->v.origin; return ent->v.origin;
} }
void Game::registerCmd (const char *command, void func ()) { void Game::registerEngineCommand (const char *command, void func ()) {
// this function tells the engine that a new server command is being declared, in addition // 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 // 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 // that for every "command_name" server command it receives, it should call the function
@ -378,7 +381,7 @@ uint8 *Game::getVisibilitySet (Bot *bot, bool pvs) {
void Game::sendClientMessage (bool console, edict_t *ent, const char *message) { void Game::sendClientMessage (bool console, edict_t *ent, const char *message) {
// helper to sending the client message // helper to sending the client message
MessageWriter (MSG_ONE, msgs.id (NetMsg::TextMsg), nullvec, ent) MessageWriter (MSG_ONE, msgs.id (NetMsg::TextMsg), nullptr, ent)
.writeByte (console ? HUD_PRINTCONSOLE : HUD_PRINTCENTER) .writeByte (console ? HUD_PRINTCONSOLE : HUD_PRINTCENTER)
.writeString (message); .writeString (message);
} }
@ -409,7 +412,7 @@ void Game::prepareBotArgs (edict_t *ent, String str) {
const size_t space = args.find (' ', 0); const size_t space = args.find (' ', 0);
// if found space // if found space
if (space != String::kInvalidIndex) { if (space != String::InvalidIndex) {
const auto quote = space + 1; // check for quote next to space const auto quote = space + 1; // check for quote next to space
// check if we're got a quoted string // check if we're got a quoted string
@ -430,7 +433,7 @@ void Game::prepareBotArgs (edict_t *ent, String str) {
m_botArgs.clear (); // clear space for next cmd m_botArgs.clear (); // clear space for next cmd
}; };
if (str.find (';', 0) != String::kInvalidIndex) { if (str.find (';', 0) != String::InvalidIndex) {
for (auto &part : str.split (";")) { for (auto &part : str.split (";")) {
parsePartArgs (part); parsePartArgs (part);
} }
@ -629,20 +632,40 @@ bool Game::loadCSBinary () {
} }
bool Game::postload () { bool Game::postload () {
// ensure we're have all needed directories
const char *mod = getModName ();
// create the needed paths // ensure we're have all needed directories
File::createPath (strings.format ("%s/addons/yapb/conf/lang", mod)); for (const auto &dir : StringArray { "conf/lang", "data/learned", "data/graph", "data/logs" }) {
File::createPath (strings.format ("%s/addons/yapb/data/learned", mod)); File::createPath (strings.format ("%s/addons/yapb/%s", getModName (), dir.chars ()));
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 // set out user agent for http stuff
http.setUserAgent (strings.format ("%s/%s", PRODUCT_SHORT_NAME, PRODUCT_VERSION)); http.setUserAgent (strings.format ("%s/%s", PRODUCT_SHORT_NAME, PRODUCT_VERSION));
// register bot cvars
game.registerCvars ();
// register server command(s)
registerEngineCommand ("yapb", [] () {
ctrl.handleEngineCommands ();
});
registerEngineCommand ("yb", [] () {
ctrl.handleEngineCommands ();
});
// register fake metamod command handler if we not! under mm
if (!(game.is (GameFlags::Metamod))) {
game.registerEngineCommand ("meta", [] () {
game.print ("You're launched standalone version of %s. Metamod is not installed or not enabled!", PRODUCT_SHORT_NAME);
});
}
// initialize weapons
conf.initWeapons ();
// print game detection info // print game detection info
auto printGame = [&] () { auto displayCSVersion = [&] () {
String gameVersionStr; String gameVersionStr;
StringArray gameVersionFlags; StringArray gameVersionFlags;
@ -694,7 +717,7 @@ bool Game::postload () {
if (!m_gameLib.load (gamedll)) { if (!m_gameLib.load (gamedll)) {
logger.fatal ("Unable to load gamedll \"%s\". Exiting... (gamedir: %s)", gamedll, getModName ()); logger.fatal ("Unable to load gamedll \"%s\". Exiting... (gamedir: %s)", gamedll, getModName ());
} }
printGame (); displayCSVersion ();
} }
else { else {
@ -703,7 +726,7 @@ bool Game::postload () {
if (!binaryLoaded && !is (GameFlags::Metamod)) { if (!binaryLoaded && !is (GameFlags::Metamod)) {
logger.fatal ("Mod that you has started, not supported by this bot (gamedir: %s)", getModName ()); logger.fatal ("Mod that you has started, not supported by this bot (gamedir: %s)", getModName ());
} }
printGame (); displayCSVersion ();
if (is (GameFlags::Metamod)) { if (is (GameFlags::Metamod)) {
m_gameLib.unload (); m_gameLib.unload ();
@ -742,7 +765,7 @@ void Game::detectDeathmatch () {
} }
void Game::slowFrame () { void Game::slowFrame () {
if (m_slowFrame > timebase ()) { if (m_slowFrame > time ()) {
return; return;
} }
ctrl.maintainAdminRights (); ctrl.maintainAdminRights ();
@ -761,7 +784,36 @@ void Game::slowFrame () {
// display welcome message // display welcome message
util.checkWelcome (); util.checkWelcome ();
m_slowFrame = timebase () + 1.0f; m_slowFrame = time () + 1.0f;
}
void Game::searchEntities (const String &field, const String &value, EntitySearch functor) {
edict_t *ent = nullptr;
while (!game.isNullEntity (ent = engfuncs.pfnFindEntityByString (ent, field.chars (), value.chars ()))) {
if ((ent->v.flags & EF_NODRAW) || (ent->v.flags & FL_CLIENT)) {
continue;
}
if (functor (ent) == EntitySearchResult::Break) {
break;
}
}
}
void Game::searchEntities (const Vector &position, const float radius, EntitySearch functor) {
edict_t *ent = nullptr;
const Vector &pos = position.empty () ? m_startEntity->v.origin : position;
while (!game.isNullEntity (ent = engfuncs.pfnFindEntityInSphere (ent, pos, radius))) {
if ((ent->v.flags & EF_NODRAW) || (ent->v.flags & FL_CLIENT)) {
continue;
}
if (functor (ent) == EntitySearchResult::Break) {
break;
}
}
} }
void LightMeasure::initializeLightstyles () { void LightMeasure::initializeLightstyles () {
@ -786,7 +838,7 @@ void LightMeasure::animateLight () {
} }
// 'm' is normal light, 'a' is no light, 'z' is double bright // 'm' is normal light, 'a' is no light, 'z' is double bright
const int index = static_cast <int> (game.timebase () * 10.0f); const int index = static_cast <int> (game.time () * 10.0f);
for (int j = 0; j < MAX_LIGHTSTYLES; ++j) { for (int j = 0; j < MAX_LIGHTSTYLES; ++j) {
if (!m_lightstyle[j].length) { if (!m_lightstyle[j].length) {

View file

@ -19,9 +19,9 @@ void BotGraph::initGraph () {
m_loadAttempts = 0; m_loadAttempts = 0;
m_editFlags = 0; m_editFlags = 0;
m_learnVelocity= nullvec; m_learnVelocity= nullptr;
m_learnPosition= nullvec; m_learnPosition= nullptr;
m_lastNode= nullvec; m_lastNode= nullptr;
m_pathDisplayTime = 0.0f; m_pathDisplayTime = 0.0f;
m_arrowDisplayTime = 0.0f; m_arrowDisplayTime = 0.0f;
@ -540,7 +540,7 @@ void BotGraph::add (int type, const Vector &pos) {
Path *path = nullptr; Path *path = nullptr;
bool addNewNode = true; bool addNewNode = true;
Vector newOrigin = pos == nullvec ? m_editor->v.origin : pos; Vector newOrigin = pos.empty () ? m_editor->v.origin : pos;
if (bots.hasBotsOnline ()) { if (bots.hasBotsOnline ()) {
bots.kickEveryone (true); bots.kickEveryone (true);
@ -624,8 +624,8 @@ void BotGraph::add (int type, const Vector &pos) {
path->origin = newOrigin; path->origin = newOrigin;
addToBucket (newOrigin, index); addToBucket (newOrigin, index);
path->start = nullvec; path->start = nullptr;
path->end = nullvec; path->end = nullptr;
path->display = 0.0f; path->display = 0.0f;
path->light = 0.0f; path->light = 0.0f;
@ -634,7 +634,7 @@ void BotGraph::add (int type, const Vector &pos) {
link.index = kInvalidNodeIndex; link.index = kInvalidNodeIndex;
link.distance = 0; link.distance = 0;
link.flags = 0; link.flags = 0;
link.velocity = nullvec; link.velocity = nullptr;
} }
@ -804,7 +804,7 @@ void BotGraph::erase (int target) {
link.index = kInvalidNodeIndex; link.index = kInvalidNodeIndex;
link.flags = 0; link.flags = 0;
link.distance = 0; link.distance = 0;
link.velocity = nullvec; link.velocity = nullptr;
} }
} }
} }
@ -1037,9 +1037,8 @@ void BotGraph::calculatePathRadius (int index) {
for (float scanDistance = 32.0f; scanDistance < 128.0f; scanDistance += 16.0f) { for (float scanDistance = 32.0f; scanDistance < 128.0f; scanDistance += 16.0f) {
start = path.origin; start = path.origin;
auto null = nullvec;
direction = null.forward () * scanDistance; direction = Vector (0.0f, 0.0f, 0.0f).forward () * scanDistance;
direction = direction.angles (); direction = direction.angles ();
path.radius = scanDistance; path.radius = scanDistance;
@ -1925,7 +1924,7 @@ void BotGraph::frame () {
if (m_editor->v.button & IN_JUMP) { if (m_editor->v.button & IN_JUMP) {
add (9); add (9);
m_timeJumpStarted = game.timebase (); m_timeJumpStarted = game.time ();
m_endJumpPoint = true; m_endJumpPoint = true;
} }
else { else {
@ -1933,7 +1932,7 @@ void BotGraph::frame () {
m_learnPosition = m_editor->v.origin; m_learnPosition = m_editor->v.origin;
} }
} }
else if (((m_editor->v.flags & FL_ONGROUND) || m_editor->v.movetype == MOVETYPE_FLY) && m_timeJumpStarted + 0.1f < game.timebase () && m_endJumpPoint) { else if (((m_editor->v.flags & FL_ONGROUND) || m_editor->v.movetype == MOVETYPE_FLY) && m_timeJumpStarted + 0.1f < game.time () && m_endJumpPoint) {
add (10); add (10);
m_jumpLearnNode = false; m_jumpLearnNode = false;
@ -1946,7 +1945,7 @@ void BotGraph::frame () {
// find the distance from the last used node // find the distance from the last used node
float distance = (m_lastNode - m_editor->v.origin).lengthSq (); float distance = (m_lastNode - m_editor->v.origin).lengthSq ();
if (distance > 16384.0f) { if (distance > cr::square (128.0f)) {
// check that no other reachable nodes are nearby... // check that no other reachable nodes are nearby...
for (const auto &path : m_paths) { for (const auto &path : m_paths) {
if (isNodeReacheable (m_editor->v.origin, path.origin)) { if (isNodeReacheable (m_editor->v.origin, path.origin)) {
@ -1981,7 +1980,7 @@ void BotGraph::frame () {
nearestDistance = distance; nearestDistance = distance;
} }
if (path.display + 0.8f < game.timebase ()) { if (path.display + 0.8f < game.time ()) {
float nodeHeight = 0.0f; float nodeHeight = 0.0f;
// check the node height // check the node height
@ -2045,7 +2044,7 @@ void BotGraph::frame () {
game.drawLine (m_editor, path.origin - Vector (0, 0, nodeHalfHeight), path.origin - Vector (0, 0, nodeHalfHeight - nodeHeight * 0.75f), nodeWidth, 0, nodeColor, 250, 0, 10); // draw basic path game.drawLine (m_editor, path.origin - Vector (0, 0, nodeHalfHeight), 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 game.drawLine (m_editor, path.origin - Vector (0, 0, nodeHalfHeight - nodeHeight * 0.75f), path.origin + Vector (0, 0, nodeHalfHeight), nodeWidth, 0, nodeFlagColor, 250, 0, 10); // draw additional path
} }
path.display = game.timebase (); path.display = game.time ();
} }
} }
} }
@ -2057,7 +2056,7 @@ void BotGraph::frame () {
// draw arrow to a some importaint nodes // draw arrow to a some importaint nodes
if (exists (m_findWPIndex) || exists (m_cacheNodeIndex) || exists (m_facingAtIndex)) { if (exists (m_findWPIndex) || exists (m_cacheNodeIndex) || exists (m_facingAtIndex)) {
// check for drawing code // check for drawing code
if (m_arrowDisplayTime + 0.5f < game.timebase ()) { if (m_arrowDisplayTime + 0.5f < game.time ()) {
// finding node - pink arrow // finding node - pink arrow
if (m_findWPIndex != kInvalidNodeIndex) { if (m_findWPIndex != kInvalidNodeIndex) {
@ -2073,7 +2072,7 @@ void BotGraph::frame () {
if (m_facingAtIndex != kInvalidNodeIndex) { 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); game.drawLine (m_editor, m_editor->v.origin, m_paths[m_facingAtIndex].origin, 10, 0, Color (255, 255, 255), 200, 0, 5, DrawLine::Arrow);
} }
m_arrowDisplayTime = game.timebase (); m_arrowDisplayTime = game.time ();
} }
} }
@ -2081,8 +2080,8 @@ void BotGraph::frame () {
auto &path = m_paths[nearestIndex]; auto &path = m_paths[nearestIndex];
// draw a paths, camplines and danger directions for nearest node // draw a paths, camplines and danger directions for nearest node
if (nearestDistance <= 56.0f && m_pathDisplayTime <= game.timebase ()) { if (nearestDistance <= 56.0f && m_pathDisplayTime <= game.time ()) {
m_pathDisplayTime = game.timebase () + 1.0f; m_pathDisplayTime = game.time () + 1.0f;
// draw the camplines // draw the camplines
if (path.flags & NodeFlag::Camp) { if (path.flags & NodeFlag::Camp) {
@ -2214,7 +2213,7 @@ void BotGraph::frame () {
} }
// draw entire message // draw entire message
MessageWriter (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, nullvec, m_editor) MessageWriter (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, nullptr, m_editor)
.writeByte (TE_TEXTMESSAGE) .writeByte (TE_TEXTMESSAGE)
.writeByte (4) // channel .writeByte (4) // channel
.writeShort (MessageWriter::fs16 (0.0f, 13.0f)) // x .writeShort (MessageWriter::fs16 (0.0f, 13.0f)) // x
@ -2293,7 +2292,7 @@ bool BotGraph::checkNodes (bool teleportPlayer) {
} }
if (path.flags & NodeFlag::Camp) { if (path.flags & NodeFlag::Camp) {
if (path.end == nullvec) { if (path.end.empty ()) {
ctrl.msg ("Node %d Camp-Endposition not set!", path.number); ctrl.msg ("Node %d Camp-Endposition not set!", path.number);
return false; return false;
} }
@ -2463,10 +2462,8 @@ bool BotGraph::isVisited (int index) {
void BotGraph::addBasic () { void BotGraph::addBasic () {
// this function creates basic node types on map // this function creates basic node types on map
edict_t *ent = nullptr;
// first of all, if map contains ladder points, create it // first of all, if map contains ladder points, create it
while (!game.isNullEntity (ent = engfuncs.pfnFindEntityByString (ent, "classname", "func_ladder"))) { game.searchEntities ("classname", "func_ladder", [&] (edict_t *ent) {
Vector ladderLeft = ent->v.absmin; Vector ladderLeft = ent->v.absmin;
Vector ladderRight = ent->v.absmax; Vector ladderRight = ent->v.absmax;
ladderLeft.z = ladderRight.z; ladderLeft.z = ladderRight.z;
@ -2474,7 +2471,7 @@ void BotGraph::addBasic () {
TraceResult tr; TraceResult tr;
Vector up, down, front, back; Vector up, down, front, back;
Vector diff = ((ladderLeft - ladderRight) ^ Vector (0.0f, 0.0f, 0.0f)).normalize () * 15.0f; const Vector &diff = ((ladderLeft - ladderRight) ^ Vector (0.0f, 0.0f, 0.0f)).normalize () * 15.0f;
front = back = game.getAbsPos (ent); front = back = game.getAbsPos (ent);
front = front + diff; // front front = front + diff; // front
@ -2509,18 +2506,19 @@ void BotGraph::addBasic () {
add (3, point); add (3, point);
} }
m_isOnLadder = false; m_isOnLadder = false;
}
return EntitySearchResult::Continue;
});
auto autoCreateForEntity = [] (int type, const char *entity) { auto autoCreateForEntity = [] (int type, const char *entity) {
edict_t *ent = nullptr; game.searchEntities ("classname", entity, [&] (edict_t *ent) {
while (!game.isNullEntity (ent = engfuncs.pfnFindEntityByString (ent, "classname", entity))) {
const Vector &pos = game.getAbsPos (ent); const Vector &pos = game.getAbsPos (ent);
if (graph.getNearestNoBuckets (pos, 50.0f) == kInvalidNodeIndex) { if (graph.getNearestNoBuckets (pos, 50.0f) == kInvalidNodeIndex) {
graph.add (type, pos); graph.add (type, pos);
} }
} return EntitySearchResult::Continue;
});
}; };
autoCreateForEntity (0, "info_player_deathmatch"); // then terrortist spawnpoints autoCreateForEntity (0, "info_player_deathmatch"); // then terrortist spawnpoints
@ -2583,7 +2581,7 @@ void BotGraph::setBombPos (bool reset, const Vector &pos) {
// this function stores the bomb position as a vector // this function stores the bomb position as a vector
if (reset) { if (reset) {
m_bombPos= nullvec; m_bombPos= nullptr;
bots.setBombPlanted (false); bots.setBombPlanted (false);
return; return;
@ -2593,14 +2591,14 @@ void BotGraph::setBombPos (bool reset, const Vector &pos) {
m_bombPos = pos; m_bombPos = pos;
return; return;
} }
edict_t *ent = nullptr;
while (!game.isNullEntity (ent = engfuncs.pfnFindEntityByString (ent, "classname", "grenade"))) { game.searchEntities ("classname", "grenade", [&] (edict_t *ent) {
if (strcmp (STRING (ent->v.model) + 9, "c4.mdl") == 0) { if (strcmp (STRING (ent->v.model) + 9, "c4.mdl") == 0) {
m_bombPos = game.getAbsPos (ent); m_bombPos = game.getAbsPos (ent);
break; return EntitySearchResult::Break;
}
} }
return EntitySearchResult::Continue;
});
} }
void BotGraph::startLearnJump () { void BotGraph::startLearnJump () {
@ -2760,7 +2758,7 @@ void BotGraph::unassignPath (int from, int to) {
link.index = kInvalidNodeIndex; link.index = kInvalidNodeIndex;
link.distance = 0; link.distance = 0;
link.flags = 0; link.flags = 0;
link.velocity = nullvec; link.velocity = nullptr;
setEditFlag (GraphEdit::On); setEditFlag (GraphEdit::On);
m_hasChanged = true; m_hasChanged = true;

View file

@ -109,32 +109,16 @@ CR_EXPORT int GetEntityAPI2 (gamefuncs_t *functionTable, int *) {
// server is enabled. Here is a good place to do our own game session initialization, and // 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. // to register by the engine side the server commands we need to administrate our bots.
// register bot cvars
game.registerCvars ();
// register logger // register logger
logger.initialize (strings.format ("%slogs/yapb.log", graph.getDataDirectory (false)), [] (const char *msg) { logger.initialize (strings.format ("%slogs/yapb.log", graph.getDataDirectory (false)), [] (const char *msg) {
game.print (msg); game.print (msg);
}); });
conf.initWeapons ();
// register server command(s)
game.registerCmd ("yapb", BotControl::handleEngineCommands);
game.registerCmd ("yb", BotControl::handleEngineCommands);
// set correct version string // set correct version string
yb_version.set (strings.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 // execute main config
conf.loadMainConfig (); conf.loadMainConfig ();
// register fake metamod command handler if we not! under mm
if (!(game.is (GameFlags::Metamod))) {
game.registerCmd ("meta", [] () {
game.print ("You're launched standalone version of yapb. Metamod is not installed or not enabled!");
});
}
conf.adjustWeaponPrices (); conf.adjustWeaponPrices ();
if (game.is (GameFlags::Metamod)) { if (game.is (GameFlags::Metamod)) {
@ -177,14 +161,8 @@ CR_EXPORT int GetEntityAPI2 (gamefuncs_t *functionTable, int *) {
if (!game.isNullEntity (pentTouched) && pentOther != game.getStartEntity ()) { if (!game.isNullEntity (pentTouched) && pentOther != game.getStartEntity ()) {
auto bot = bots[pentTouched]; auto bot = bots[pentTouched];
if (bot != nullptr && pentOther != bot->ent ()) { if (bot && pentOther != bot->ent () && !util.isPlayer (pentOther)) {
bot->checkBreakable (pentOther);
if (util.isPlayer (pentOther)) {
bot->avoidIncomingPlayers (pentOther);
}
else {
bot->processBreakables (pentOther);
}
} }
} }
@ -397,9 +375,6 @@ CR_EXPORT int GetEntityAPI2 (gamefuncs_t *functionTable, int *) {
// for example if a new player joins the server, we should disconnect a bot, and if the // for example if a new player joins the server, we should disconnect a bot, and if the
// player population decreases, we should fill the server with other bots. // player population decreases, we should fill the server with other bots.
// run periodic update of bot states
bots.frame ();
// update lightstyle animations // update lightstyle animations
illum.animateLight (); illum.animateLight ();
@ -430,7 +405,7 @@ CR_EXPORT int GetEntityAPI2 (gamefuncs_t *functionTable, int *) {
dllapi.pfnStartFrame (); dllapi.pfnStartFrame ();
// run the bot ai // run the bot ai
bots.slowFrame (); bots.frame ();
}; };
functionTable->pfnCmdStart = [] (const edict_t *player, usercmd_t *cmd, unsigned int random_seed) { functionTable->pfnCmdStart = [] (const edict_t *player, usercmd_t *cmd, unsigned int random_seed) {
@ -504,7 +479,8 @@ CR_EXPORT int GetEntityAPI2_Post (gamefuncs_t *table, int *) {
// for the bots by the MOD side, remember). Post version called only by metamod. // for the bots by the MOD side, remember). Post version called only by metamod.
// run the bot ai // run the bot ai
bots.slowFrame (); bots.frame ();
RETURN_META (MRES_IGNORED); RETURN_META (MRES_IGNORED);
}; };
@ -578,9 +554,10 @@ CR_EXPORT int GetEngineFunctions (enginefuncs_t *functionTable, int *) {
engfuncs.pfnLightStyle (style, val); engfuncs.pfnLightStyle (style, val);
}; };
if (game.is (GameFlags::Legacy)) {
functionTable->pfnFindEntityByString = [] (edict_t *edictStartSearchAfter, const char *field, const char *value) { functionTable->pfnFindEntityByString = [] (edict_t *edictStartSearchAfter, const char *field, const char *value) {
// round starts in counter-strike 1.5 // round starts in counter-strike 1.5
if ((game.is (GameFlags::Legacy)) && strcmp (value, "info_map_parameters") == 0) { if (strcmp (value, "info_map_parameters") == 0) {
bots.initRound (); bots.initRound ();
} }
@ -589,6 +566,7 @@ CR_EXPORT int GetEngineFunctions (enginefuncs_t *functionTable, int *) {
} }
return engfuncs.pfnFindEntityByString (edictStartSearchAfter, field, value); return engfuncs.pfnFindEntityByString (edictStartSearchAfter, field, value);
}; };
}
functionTable->pfnEmitSound = [] (edict_t *entity, int channel, const char *sample, float volume, float attenuation, int flags, int pitch) { functionTable->pfnEmitSound = [] (edict_t *entity, int channel, const char *sample, float volume, float attenuation, int flags, int pitch) {
// this function tells the engine that the entity pointed to by "entity", is emitting a sound // this function tells the engine that the entity pointed to by "entity", is emitting a sound
@ -601,7 +579,7 @@ CR_EXPORT int GetEngineFunctions (enginefuncs_t *functionTable, int *) {
// SoundAttachToThreat() to bring the sound to the ears of the bots. Since bots have no client DLL // SoundAttachToThreat() to bring the sound to the ears of the bots. Since bots have no client DLL
// to handle this for them, such a job has to be done manually. // to handle this for them, such a job has to be done manually.
util.attachSoundsToClients (entity, sample, volume); util.listenNoise (entity, sample, volume);
if (game.is (GameFlags::Metamod)) { if (game.is (GameFlags::Metamod)) {
RETURN_META (MRES_IGNORED); RETURN_META (MRES_IGNORED);
@ -863,7 +841,7 @@ CR_EXPORT int Meta_Query (char *, plugin_info_t **pPlugInfo, mutil_funcs_t *pMet
return TRUE; // tell metamod this plugin looks safe return TRUE; // tell metamod this plugin looks safe
} }
CR_EXPORT int Meta_Attach (PLUG_LOADTIME, metamod_funcs_t *functionTable, meta_globals_t *pMGlobals, gamedll_funcs_t *pGamedllFuncs) { CR_EXPORT int Meta_Attach (PLUG_LOADTIME now, metamod_funcs_t *functionTable, meta_globals_t *pMGlobals, gamedll_funcs_t *pGamedllFuncs) {
// this function is called when metamod attempts to load the plugin. Since it's the place // 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 // 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. // our initialization stuff, like registering CVARs and dedicated server commands.
@ -880,6 +858,11 @@ CR_EXPORT int Meta_Attach (PLUG_LOADTIME, metamod_funcs_t *functionTable, meta_g
nullptr, // pfnGetEngineFunctions_Post () nullptr, // pfnGetEngineFunctions_Post ()
}; };
if (now > Plugin_info.loadable) {
logger.error ("%s: plugin NOT attaching (can't load plugin right now)", Plugin_info.name);
return FALSE; // returning FALSE prevents metamod from attaching this plugin
}
// keep track of the pointers to engine function tables metamod gives us // keep track of the pointers to engine function tables metamod gives us
gpMetaGlobals = pMGlobals; gpMetaGlobals = pMGlobals;
memcpy (functionTable, &metamodFunctionTable, sizeof (metamod_funcs_t)); memcpy (functionTable, &metamodFunctionTable, sizeof (metamod_funcs_t));
@ -888,10 +871,14 @@ CR_EXPORT int Meta_Attach (PLUG_LOADTIME, metamod_funcs_t *functionTable, meta_g
return TRUE; // returning true enables metamod to attach this plugin return TRUE; // returning true enables metamod to attach this plugin
} }
CR_EXPORT int Meta_Detach (PLUG_LOADTIME, PL_UNLOAD_REASON) { CR_EXPORT int Meta_Detach (PLUG_LOADTIME now, PL_UNLOAD_REASON reason) {
// this function is called when metamod unloads the plugin. A basic check is made in order // 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. // to prevent unloading the plugin if its processing should not be interrupted.
if (now > Plugin_info.unloadable && reason != PNL_CMD_FORCED) {
logger.error ("%s: plugin NOT detaching (can't unload plugin right now)", Plugin_info.name);
return FALSE; // returning FALSE prevents metamod from unloading this plugin
}
bots.kickEveryone (true); // kick all bots off this server bots.kickEveryone (true); // kick all bots off this server
// save collected experience on shutdown // save collected experience on shutdown

View file

@ -250,23 +250,15 @@ Bot *BotManager::findAliveBot () {
return nullptr; return nullptr;
} }
void BotManager::slowFrame () {
// this function calls showframe function for all available at call moment bots
for (const auto &bot : m_bots) {
bot->slowFrame ();
}
}
void BotManager::frame () { void BotManager::frame () {
// this function calls periodic frame function for all available at call moment bots // this function calls showframe function for all available at call moment bots
for (const auto &bot : m_bots) { for (const auto &bot : m_bots) {
bot->frame (); bot->frame ();
} }
// select leader each team somewhere in round start // select leader each team somewhere in round start
if (m_timeRoundStart + 5.0f > game.timebase () && m_timeRoundStart + 10.0f < game.timebase ()) { if (m_timeRoundStart + 5.0f > game.time () && m_timeRoundStart + 10.0f < game.time ()) {
for (int team = 0; team < kGameTeamNum; ++team) { for (int team = 0; team < kGameTeamNum; ++team) {
selectLeaders (team, false); selectLeaders (team, false);
} }
@ -319,7 +311,7 @@ void BotManager::maintainQuota () {
} }
// bot's creation update // bot's creation update
if (!m_creationTab.empty () && m_maintainTime < game.timebase ()) { if (!m_creationTab.empty () && m_maintainTime < game.time ()) {
const CreateQueue &last = m_creationTab.pop (); const CreateQueue &last = m_creationTab.pop ();
const BotCreateResult 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);
@ -342,11 +334,11 @@ void BotManager::maintainQuota () {
m_creationTab.clear (); m_creationTab.clear ();
yb_quota.set (getBotCount ()); yb_quota.set (getBotCount ());
} }
m_maintainTime = game.timebase () + 0.10f; m_maintainTime = game.time () + 0.10f;
} }
// now keep bot number up to date // now keep bot number up to date
if (m_quotaMaintainTime > game.timebase ()) { if (m_quotaMaintainTime > game.time ()) {
return; return;
} }
yb_quota.set (cr::clamp <int> (yb_quota.int_ (), 0, game.maxClients ())); yb_quota.set (cr::clamp <int> (yb_quota.int_ (), 0, game.maxClients ()));
@ -410,7 +402,7 @@ void BotManager::maintainQuota () {
kickRandom (false, Team::Unassigned); kickRandom (false, Team::Unassigned);
} }
} }
m_quotaMaintainTime = game.timebase () + 0.40f; m_quotaMaintainTime = game.time () + 0.40f;
} }
void BotManager::reset () { void BotManager::reset () {
@ -467,8 +459,8 @@ void BotManager::decrementQuota (int by) {
} }
void BotManager::initQuota () { void BotManager::initQuota () {
m_maintainTime = game.timebase () + yb_join_delay.float_ (); m_maintainTime = game.time () + yb_join_delay.float_ ();
m_quotaMaintainTime = game.timebase () + yb_join_delay.float_ (); m_quotaMaintainTime = game.time () + yb_join_delay.float_ ();
m_creationTab.clear (); m_creationTab.clear ();
} }
@ -855,8 +847,8 @@ Bot::Bot (edict_t *bot, int difficulty, int personality, int team, int member) {
m_difficulty = cr::clamp (difficulty, 0, 4); m_difficulty = cr::clamp (difficulty, 0, 4);
m_basePing = rg.int_ (7, 14); m_basePing = rg.int_ (7, 14);
m_lastCommandTime = game.timebase () - 0.1f; m_lastCommandTime = game.time () - 0.1f;
m_frameInterval = game.timebase (); m_frameInterval = game.time ();
m_slowFrameTimestamp = 0.0f; m_slowFrameTimestamp = 0.0f;
// stuff from jk_botti // stuff from jk_botti
@ -892,7 +884,7 @@ Bot::Bot (edict_t *bot, int difficulty, int personality, int team, int member) {
// copy them over to the temp level variables // copy them over to the temp level variables
m_agressionLevel = m_baseAgressionLevel; m_agressionLevel = m_baseAgressionLevel;
m_fearLevel = m_baseFearLevel; m_fearLevel = m_baseFearLevel;
m_nextEmotionUpdate = game.timebase () + 0.5f; m_nextEmotionUpdate = game.time () + 0.5f;
// just to be sure // just to be sure
m_actMessageIndex = 0; m_actMessageIndex = 0;
@ -906,7 +898,7 @@ Bot::Bot (edict_t *bot, int difficulty, int personality, int team, int member) {
} }
float Bot::getFrameInterval () { float Bot::getFrameInterval () {
return cr::fzero (m_thinkInterval) ? m_frameInterval : m_thinkInterval; return m_frameInterval;
} }
int BotManager::getHumansCount (bool ignoreSpectators) { int BotManager::getHumansCount (bool ignoreSpectators) {
@ -977,10 +969,10 @@ void BotManager::handleDeath (edict_t *killer, edict_t *victim) {
for (const auto &notify : bots) { for (const auto &notify : bots) {
if (notify->m_notKilled && killerTeam == notify->m_team && killerTeam != victimTeam && killer != notify->ent () && notify->seesEntity (victim->v.origin)) { if (notify->m_notKilled && killerTeam == notify->m_team && killerTeam != victimTeam && killer != notify->ent () && notify->seesEntity (victim->v.origin)) {
if (!(killer->v.flags & FL_FAKECLIENT)) { if (!(killer->v.flags & FL_FAKECLIENT)) {
notify->processChatterMessage ("#Bot_NiceShotCommander"); notify->handleChatter ("#Bot_NiceShotCommander");
} }
else { else {
notify->processChatterMessage ("#Bot_NiceShotPall"); notify->handleChatter ("#Bot_NiceShotPall");
} }
break; break;
} }
@ -991,9 +983,9 @@ void BotManager::handleDeath (edict_t *killer, edict_t *victim) {
// notice nearby to victim teammates, that attacker is near // notice nearby to victim teammates, that attacker is near
for (const auto &notify : bots) { for (const auto &notify : bots) {
if (notify->m_seeEnemyTime + 2.0f < game.timebase () && notify->m_notKilled && notify->m_team == victimTeam && game.isNullEntity (notify->m_enemy) && killerTeam != victimTeam && util.isVisible (killer->v.origin, notify->ent ())) { if (notify->m_seeEnemyTime + 2.0f < game.time () && notify->m_notKilled && notify->m_team == victimTeam && game.isNullEntity (notify->m_enemy) && killerTeam != victimTeam && util.isVisible (killer->v.origin, notify->ent ())) {
notify->m_actualReactionTime = 0.0f; notify->m_actualReactionTime = 0.0f;
notify->m_seeEnemyTime = game.timebase (); notify->m_seeEnemyTime = game.time ();
notify->m_enemy = killer; notify->m_enemy = killer;
notify->m_lastEnemy = killer; notify->m_lastEnemy = killer;
notify->m_lastEnemyOrigin = killer->v.origin; notify->m_lastEnemyOrigin = killer->v.origin;
@ -1032,11 +1024,11 @@ void Bot::newRound () {
clearSearchNodes (); clearSearchNodes ();
clearRoute (); clearRoute ();
m_pathOrigin= nullvec; m_pathOrigin= nullptr;
m_destOrigin= nullvec; m_destOrigin= nullptr;
m_path = nullptr; m_path = nullptr;
m_currentTravelFlags = 0; m_currentTravelFlags = 0;
m_desiredVelocity= nullvec; m_desiredVelocity= nullptr;
m_currentNodeIndex = kInvalidNodeIndex; m_currentNodeIndex = kInvalidNodeIndex;
m_prevGoalIndex = kInvalidNodeIndex; m_prevGoalIndex = kInvalidNodeIndex;
m_chosenGoalIndex = kInvalidNodeIndex; m_chosenGoalIndex = kInvalidNodeIndex;
@ -1053,13 +1045,10 @@ void Bot::newRound () {
m_oldButtons = pev->button; m_oldButtons = pev->button;
m_rechoiceGoalCount = 0; m_rechoiceGoalCount = 0;
m_avoid = nullptr;
m_avoidTime = 0.0f;
for (i = 0; i < 5; ++i) { for (i = 0; i < 5; ++i) {
m_previousNodes[i] = kInvalidNodeIndex; m_previousNodes[i] = kInvalidNodeIndex;
} }
m_navTimeset = game.timebase (); m_navTimeset = game.time ();
m_team = game.getTeam (ent ()); m_team = game.getTeam (ent ());
m_isVIP = false; m_isVIP = false;
@ -1093,9 +1082,9 @@ void Bot::newRound () {
m_minSpeed = 260.0f; m_minSpeed = 260.0f;
m_prevSpeed = 0.0f; m_prevSpeed = 0.0f;
m_prevOrigin = Vector (kInfiniteDistance, kInfiniteDistance, kInfiniteDistance); m_prevOrigin = Vector (kInfiniteDistance, kInfiniteDistance, kInfiniteDistance);
m_prevTime = game.timebase (); m_prevTime = game.time ();
m_lookUpdateTime = game.timebase (); m_lookUpdateTime = game.time ();
m_aimErrorTime = game.timebase (); m_aimErrorTime = game.time ();
m_viewDistance = 4096.0f; m_viewDistance = 4096.0f;
m_maxViewDistance = 4096.0f; m_maxViewDistance = 4096.0f;
@ -1106,7 +1095,7 @@ void Bot::newRound () {
m_itemCheckTime = 0.0f; m_itemCheckTime = 0.0f;
m_breakableEntity = nullptr; m_breakableEntity = nullptr;
m_breakableOrigin= nullvec; m_breakableOrigin= nullptr;
m_timeDoorOpen = 0.0f; m_timeDoorOpen = 0.0f;
resetCollision (); resetCollision ();
@ -1115,7 +1104,7 @@ void Bot::newRound () {
m_enemy = nullptr; m_enemy = nullptr;
m_lastVictim = nullptr; m_lastVictim = nullptr;
m_lastEnemy = nullptr; m_lastEnemy = nullptr;
m_lastEnemyOrigin= nullvec; m_lastEnemyOrigin= nullptr;
m_trackingEdict = nullptr; m_trackingEdict = nullptr;
m_timeNextTracking = 0.0f; m_timeNextTracking = 0.0f;
@ -1139,9 +1128,9 @@ void Bot::newRound () {
m_aimFlags = 0; m_aimFlags = 0;
m_liftState = 0; m_liftState = 0;
m_aimLastError= nullvec; m_aimLastError= nullptr;
m_position= nullvec; m_position= nullptr;
m_liftTravelPos= nullvec; m_liftTravelPos= nullptr;
setIdealReactionTimers (true); setIdealReactionTimers (true);
@ -1157,8 +1146,8 @@ void Bot::newRound () {
m_reloadState = Reload::None; m_reloadState = Reload::None;
m_reloadCheckTime = 0.0f; m_reloadCheckTime = 0.0f;
m_shootTime = game.timebase (); m_shootTime = game.time ();
m_playerTargetTime = game.timebase (); m_playerTargetTime = game.time ();
m_firePause = 0.0f; m_firePause = 0.0f;
m_timeLastFired = 0.0f; m_timeLastFired = 0.0f;
@ -1174,7 +1163,7 @@ void Bot::newRound () {
m_jumpFinished = false; m_jumpFinished = false;
m_isStuck = false; m_isStuck = false;
m_sayTextBuffer.timeNextChat = game.timebase (); m_sayTextBuffer.timeNextChat = game.time ();
m_sayTextBuffer.entityIndex = -1; m_sayTextBuffer.entityIndex = -1;
m_sayTextBuffer.sayText.clear (); m_sayTextBuffer.sayText.clear ();
@ -1189,10 +1178,10 @@ void Bot::newRound () {
m_currentWeapon = 0; m_currentWeapon = 0;
} }
m_flashLevel = 100.0f; m_flashLevel = 100.0f;
m_checkDarkTime = game.timebase (); m_checkDarkTime = game.time ();
m_knifeAttackTime = game.timebase () + rg.float_ (1.3f, 2.6f); m_knifeAttackTime = game.time () + rg.float_ (1.3f, 2.6f);
m_nextBuyTime = game.timebase () + rg.float_ (0.6f, 2.0f); m_nextBuyTime = game.time () + rg.float_ (0.6f, 2.0f);
m_buyPending = false; m_buyPending = false;
m_inBombZone = false; m_inBombZone = false;
@ -1217,9 +1206,9 @@ void Bot::newRound () {
m_defendHostage = false; m_defendHostage = false;
m_headedTime = 0.0f; m_headedTime = 0.0f;
m_timeLogoSpray = game.timebase () + rg.float_ (5.0f, 30.0f); m_timeLogoSpray = game.time () + rg.float_ (5.0f, 30.0f);
m_spawnTime = game.timebase (); m_spawnTime = game.time ();
m_lastChatTime = game.timebase (); m_lastChatTime = game.time ();
m_timeCamping = 0.0f; m_timeCamping = 0.0f;
m_campDirection = 0; m_campDirection = 0;
@ -1227,7 +1216,7 @@ void Bot::newRound () {
m_campButtons = 0; m_campButtons = 0;
m_soundUpdateTime = 0.0f; m_soundUpdateTime = 0.0f;
m_heardSoundTime = game.timebase (); m_heardSoundTime = game.time ();
// clear its message queue // clear its message queue
for (i = 0; i < 32; ++i) { for (i = 0; i < 32; ++i) {
@ -1243,7 +1232,8 @@ void Bot::newRound () {
if (rg.chance (50)) { if (rg.chance (50)) {
pushChatterMessage (Chatter::NewRound); pushChatterMessage (Chatter::NewRound);
} }
m_thinkInterval = game.is (GameFlags::Legacy | GameFlags::Xash3D) ? 0.0f : (1.0f / cr::clamp (yb_think_fps.float_ (), 30.0f, 90.0f)) * rg.float_ (0.95f, 1.05f); m_updateInterval = game.is (GameFlags::Legacy | GameFlags::Xash3D) ? 0.0f : (1.0f / cr::clamp (yb_think_fps.float_ (), 30.0f, 60.0f));
m_viewUpdateInterval = 1.0f / 30.0f;
} }
void Bot::kill () { void Bot::kill () {
@ -1344,15 +1334,6 @@ void BotManager::captureChatRadio (const char *cmd, const char *arg, edict_t *en
} }
if (plat.caseStrMatch (cmd, "say") || plat.caseStrMatch (cmd, "say_team")) { if (plat.caseStrMatch (cmd, "say") || plat.caseStrMatch (cmd, "say_team")) {
if (strcmp (arg, "dropme") == 0 || strcmp (arg, "dropc4") == 0) {
Bot *bot = nullptr;
if (util.findNearestPlayer (reinterpret_cast <void **> (&bot), ent, 300.0f, true, true, true)) {
bot->dropWeaponForUser (ent, strings.isEmpty (strstr (arg, "c4")) ? false : true);
}
return;
}
bool alive = util.isAlive (ent); bool alive = util.isAlive (ent);
int team = -1; int team = -1;
@ -1373,7 +1354,7 @@ void BotManager::captureChatRadio (const char *cmd, const char *arg, edict_t *en
continue; continue;
} }
target->m_sayTextBuffer.sayText = engfuncs.pfnCmd_Args (); target->m_sayTextBuffer.sayText = engfuncs.pfnCmd_Args ();
target->m_sayTextBuffer.timeNextChat = game.timebase () + target->m_sayTextBuffer.chatDelay; target->m_sayTextBuffer.timeNextChat = game.time () + target->m_sayTextBuffer.chatDelay;
} }
} }
} }
@ -1396,7 +1377,7 @@ void BotManager::captureChatRadio (const char *cmd, const char *arg, edict_t *en
} }
} }
} }
bots.setLastRadioTimestamp (target.team, game.timebase ()); bots.setLastRadioTimestamp (target.team, game.time ());
} }
target.radio = 0; target.radio = 0;
} }
@ -1419,59 +1400,61 @@ void BotManager::notifyBombDefuse () {
} }
void BotManager::updateActiveGrenade () { void BotManager::updateActiveGrenade () {
if (m_grenadeUpdateTime > game.timebase ()) { if (m_grenadeUpdateTime > game.time ()) {
return; return;
} }
edict_t *grenade = nullptr; m_activeGrenades.clear (); // clear previously stored grenades
// clear previously stored grenades
m_activeGrenades.clear ();
// search the map for any type of grenade // search the map for any type of grenade
while (!game.isNullEntity (grenade = engfuncs.pfnFindEntityByString (grenade, "classname", "grenade"))) { game.searchEntities ("classname", "grenade", [&] (edict_t *e) {
// do not count c4 as a grenade // do not count c4 as a grenade
if (strcmp (STRING (grenade->v.model) + 9, "c4.mdl") == 0) { if (strcmp (STRING (e->v.model) + 9, "c4.mdl") == 0) {
continue; return EntitySearchResult::Continue;
} }
m_activeGrenades.push (grenade); m_activeGrenades.push (e);
}
m_grenadeUpdateTime = game.timebase () + 0.213f; // continue iteration
return EntitySearchResult::Continue;
});
m_grenadeUpdateTime = game.time () + 0.25f;
} }
void BotManager::updateIntrestingEntities () { void BotManager::updateIntrestingEntities () {
if (m_entityUpdateTime > game.timebase ()) { if (m_entityUpdateTime > game.time ()) {
return; return;
} }
// clear previously stored entities // clear previously stored entities
m_intrestingEntities.clear (); m_intrestingEntities.clear ();
// search the map for entities // search the map for any type of grenade
for (int i = kGameMaxPlayers - 1; i < globals->maxEntities; ++i) { game.searchEntities (nullptr, kInfiniteDistance, [&] (edict_t *e) {
auto ent = game.entityOfIndex (i); auto classname = STRING (e->v.classname);
// only valid drawn entities
if (game.isNullEntity (ent) || ent->free || ent->v.classname == 0 || (ent->v.effects & EF_NODRAW)) {
continue;
}
auto classname = STRING (ent->v.classname);
// search for grenades, weaponboxes, weapons, items and armoury entities // 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) { if (strncmp ("weapon", classname, 6) == 0 || strncmp ("grenade", classname, 7) == 0 || strncmp ("item", classname, 4) == 0 || strncmp ("armoury", classname, 7) == 0) {
m_intrestingEntities.push (ent); m_intrestingEntities.push (e);
} }
// pickup some csdm stuff if we're running csdm // pickup some csdm stuff if we're running csdm
if (game.mapIs (MapFlags::HostageRescue) && strncmp ("hostage", classname, 7) == 0) { if (game.mapIs (MapFlags::HostageRescue) && strncmp ("hostage", classname, 7) == 0) {
m_intrestingEntities.push (ent); m_intrestingEntities.push (e);
}
// add buttons
if (game.mapIs (MapFlags::HasButtons) && strncmp ("func_button", classname, 11) == 0) {
m_intrestingEntities.push (e);
} }
// pickup some csdm stuff if we're running csdm // pickup some csdm stuff if we're running csdm
if (game.is (GameFlags::CSDM) && strncmp ("csdm", classname, 4) == 0) { if (game.is (GameFlags::CSDM) && strncmp ("csdm", classname, 4) == 0) {
m_intrestingEntities.push (ent); m_intrestingEntities.push (e);
} }
}
m_entityUpdateTime = game.timebase () + 0.5f; // continue iteration
return EntitySearchResult::Continue;
});
m_entityUpdateTime = game.time () + 0.5f;
} }
void BotManager::selectLeaders (int team, bool reset) { void BotManager::selectLeaders (int team, bool reset) {
@ -1606,14 +1589,14 @@ void BotManager::initRound () {
graph.updateGlobalPractice (); // update experience data on round start graph.updateGlobalPractice (); // update experience data on round start
// calculate the round mid/end in world time // calculate the round mid/end in world time
m_timeRoundStart = game.timebase () + mp_freezetime.float_ (); m_timeRoundStart = game.time () + mp_freezetime.float_ ();
m_timeRoundMid = m_timeRoundStart + mp_roundtime.float_ () * 60.0f * 0.5f; m_timeRoundMid = m_timeRoundStart + mp_roundtime.float_ () * 60.0f * 0.5f;
m_timeRoundEnd = m_timeRoundStart + mp_roundtime.float_ () * 60.0f; m_timeRoundEnd = m_timeRoundStart + mp_roundtime.float_ () * 60.0f;
} }
void BotManager::setBombPlanted (bool isPlanted) { void BotManager::setBombPlanted (bool isPlanted) {
if (isPlanted) { if (isPlanted) {
m_timeBombPlanted = game.timebase (); m_timeBombPlanted = game.time ();
} }
m_bombPlanted = isPlanted; m_bombPlanted = isPlanted;
} }
@ -1680,6 +1663,12 @@ void BotConfig::loadMainConfig () {
auto value = const_cast <char *> (keyval[1].trim ().trim ("\"").trim ().chars ()); auto value = const_cast <char *> (keyval[1].trim ().trim ("\"").trim ().chars ());
if (needsToIgnoreVar (ignore, key) && !plat.caseStrMatch (value, cvar->string)) { if (needsToIgnoreVar (ignore, key) && !plat.caseStrMatch (value, cvar->string)) {
// preserve quota number if it's zero
if (plat.caseStrMatch (cvar->name, "yb_quota") && yb_quota.int_ () <= 0) {
engfuncs.pfnCvar_DirectSet (cvar, value);
continue;
}
game.print ("Bot CVAR '%s' differs from the stored in the config (%s/%s). Ignoring.", cvar->name, cvar->string, value); 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 // ensure cvar will have old value
@ -2183,6 +2172,8 @@ void BotConfig::clearUsedName (Bot *bot) {
} }
void BotConfig::initWeapons () { void BotConfig::initWeapons () {
m_weapons.clear ();
// fill array with available weapons // fill array with available weapons
m_weapons.emplace (Weapon::Knife, "weapon_knife", "knife.mdl", 0, 0, -1, -1, 0, 0, 0, 0, 0, 0, true ); m_weapons.emplace (Weapon::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::USP, "weapon_usp", "usp.mdl", 500, 1, -1, -1, 1, 1, 2, 2, 0, 12, false );

View file

@ -23,7 +23,7 @@ void MessageDispatcher::netMsgTextMsg () {
auto notify = bots.findAliveBot (); auto notify = bots.findAliveBot ();
if (notify && notify->m_notKilled) { if (notify && notify->m_notKilled) {
notify->processChatterMessage (m_args[msg].chars_); notify->handleChatter (m_args[msg].chars_);
} }
} }
@ -52,6 +52,14 @@ void MessageDispatcher::netMsgTextMsg () {
else if (cached & TextMsgCache::RestartRound) { else if (cached & TextMsgCache::RestartRound) {
bots.updateTeamEconomics (Team::CT, true); bots.updateTeamEconomics (Team::CT, true);
bots.updateTeamEconomics (Team::Terrorist, true); bots.updateTeamEconomics (Team::Terrorist, true);
extern ConVar mp_startmoney;
// set balance for all players
bots.forEach ([] (Bot *bot) {
bot->m_moneyAmount = mp_startmoney.int_ ();
return false;
});
} }
else if (cached & TextMsgCache::TerroristWin) { else if (cached & TextMsgCache::TerroristWin) {
bots.setLastWinner (Team::Terrorist); // update last winner for economics bots.setLastWinner (Team::Terrorist); // update last winner for economics
@ -157,7 +165,7 @@ void MessageDispatcher::netMsgCurWeapon () {
// ammo amount decreased ? must have fired a bullet... // ammo amount decreased ? must have fired a bullet...
if (m_args[id].long_ == m_bot->m_currentWeapon && m_bot->m_ammoInClip[m_args[id].long_] > m_args[clip].long_) { if (m_args[id].long_ == m_bot->m_currentWeapon && m_bot->m_ammoInClip[m_args[id].long_] > m_args[clip].long_) {
m_bot->m_timeLastFired = game.timebase (); // remember the last bullet time m_bot->m_timeLastFired = game.time (); // remember the last bullet time
} }
m_bot->m_ammoInClip[m_args[id].long_] = m_args[clip].long_; m_bot->m_ammoInClip[m_args[id].long_] = m_args[clip].long_;
} }
@ -201,7 +209,7 @@ void MessageDispatcher::netMsgDamage () {
// handle damage if any // handle damage if any
if (m_args[armor].long_ > 0 || m_args[health].long_) { if (m_args[armor].long_ > 0 || m_args[health].long_) {
m_bot->processDamage (m_bot->pev->dmg_inflictor, m_args[health].long_, m_args[armor].long_, m_args[bits].long_); m_bot->takeDamage (m_bot->pev->dmg_inflictor, m_args[health].long_, m_args[armor].long_, m_args[bits].long_);
} }
} }
@ -237,7 +245,7 @@ void MessageDispatcher::netMsgStatusIcon () {
m_bot->m_inBuyZone = (m_args[enabled].long_ != 0); m_bot->m_inBuyZone = (m_args[enabled].long_ != 0);
// try to equip in buyzone // try to equip in buyzone
m_bot->processBuyzoneEntering (BuyState::PrimaryWeapon); m_bot->enteredBuyZone (BuyState::PrimaryWeapon);
} }
else if (cached & StatusIconCache::VipSafety) { else if (cached & StatusIconCache::VipSafety) {
m_bot->m_inVIPZone = (m_args[enabled].long_ != 0); m_bot->m_inVIPZone = (m_args[enabled].long_ != 0);
@ -278,7 +286,7 @@ void MessageDispatcher::netMsgScreenFade () {
// screen completely faded ? // screen completely faded ?
if (m_args[r].long_ >= 255 && m_args[g].long_ >= 255 && m_args[b].long_ >= 255 && m_args[alpha].long_ > 170) { if (m_args[r].long_ >= 255 && m_args[g].long_ >= 255 && m_args[b].long_ >= 255 && m_args[alpha].long_ > 170) {
m_bot->processBlind (m_args[alpha].long_); m_bot->takeBlind (m_args[alpha].long_);
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -56,6 +56,17 @@ BotUtils::BotUtils () {
m_tags.emplace ("(", ")"); m_tags.emplace ("(", ")");
m_tags.emplace (")", "("); m_tags.emplace (")", "(");
// register noise cache
m_noiseCache["player/bhit"] = Noise::NeedHandle | Noise::HitFall;
m_noiseCache["player/head"] = Noise::NeedHandle | Noise::HitFall;
m_noiseCache["items/gunpi"] = Noise::NeedHandle | Noise::Pickup;
m_noiseCache["items/9mmcl"] = Noise::NeedHandle | Noise::Ammo;
m_noiseCache["weapons/zoo"] = Noise::NeedHandle | Noise::Zoom;
m_noiseCache["hostage/hos"] = Noise::NeedHandle | Noise::Hostage;
m_noiseCache["debris/bust"] = Noise::NeedHandle | Noise::Broke;
m_noiseCache["debris/bust"] = Noise::NeedHandle | Noise::Broke;
m_noiseCache["doors/doorm"] = Noise::NeedHandle | Noise::Door;
m_clients.resize (kGameMaxPlayers + 1); m_clients.resize (kGameMaxPlayers + 1);
} }
@ -234,7 +245,7 @@ void BotUtils::checkWelcome () {
auto receiveEntity = game.getLocalEntity (); auto receiveEntity = game.getLocalEntity ();
if (isAlive (receiveEntity) && m_welcomeReceiveTime < 1.0 && needToSendMsg) { if (isAlive (receiveEntity) && m_welcomeReceiveTime < 1.0 && needToSendMsg) {
m_welcomeReceiveTime = game.timebase () + 4.0f; // receive welcome message in four seconds after game has commencing m_welcomeReceiveTime = game.time () + 4.0f; // receive welcome message in four seconds after game has commencing
} }
@ -243,11 +254,11 @@ void BotUtils::checkWelcome () {
game.serverCommand ("speak \"%s\"", m_sentences.random ().chars ()); game.serverCommand ("speak \"%s\"", m_sentences.random ().chars ());
} }
MessageWriter (MSG_ONE, msgs.id (NetMsg::TextMsg), nullvec, receiveEntity) MessageWriter (MSG_ONE, msgs.id (NetMsg::TextMsg), nullptr, receiveEntity)
.writeByte (HUD_PRINTTALK) .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)); .writeString (strings.format ("----- %s v%s (Build: %u), {%s}, (c) %s, by %s (%s)-----", PRODUCT_SHORT_NAME, PRODUCT_VERSION, buildNumber (), PRODUCT_DATE, PRODUCT_END_YEAR, PRODUCT_AUTHOR, PRODUCT_URL));
MessageWriter (MSG_ONE, SVC_TEMPENTITY, nullvec, receiveEntity) MessageWriter (MSG_ONE, SVC_TEMPENTITY, nullptr, receiveEntity)
.writeByte (TE_TEXTMESSAGE) .writeByte (TE_TEXTMESSAGE)
.writeByte (1) .writeByte (1)
.writeShort (MessageWriter::fs16 (-1.0f, 13.0f)) .writeShort (MessageWriter::fs16 (-1.0f, 13.0f))
@ -312,91 +323,96 @@ bool BotUtils::findNearestPlayer (void **pvHolder, edict_t *to, float searchDist
return true; return true;
} }
void BotUtils::attachSoundsToClients (edict_t *ent, const char *sample, float volume) { void BotUtils::listenNoise (edict_t *ent, const String &sample, float volume) {
// this function called by the sound hooking code (in emit_sound) enters the played sound into // this function called by the sound hooking code (in emit_sound) enters the played sound into the array associated with the entity
// the array associated with the entity
if (game.isNullEntity (ent) || strings.isEmpty (sample)) { if (game.isNullEntity (ent) || sample.empty ()) {
return; return;
} }
const Vector &origin = game.getAbsPos (ent); const Vector &origin = game.getAbsPos (ent);
// something wrong with sound...
if (origin.empty ()) { if (origin.empty ()) {
return; return;
} }
int index = game.indexOfPlayer (ent); auto noise = m_noiseCache[sample.substr (0, 11)];
if (index < 0 || index >= game.maxClients ()) { // we're not handling theese
float nearestDistance = kInfiniteDistance; if (!(noise & Noise::NeedHandle)) {
return;
}
// find nearest player to sound origin
auto findNearbyClient = [&origin] () {
float nearest = kInfiniteDistance;
Client *result = nullptr;
// loop through all players // loop through all players
for (int i = 0; i < game.maxClients (); ++i) { for (auto &client : util.getClients ()) {
const Client &client = m_clients[i];
if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive)) { if (!(client.flags & ClientFlags::Used) || !(client.flags & ClientFlags::Alive)) {
continue; continue;
} }
float distance = (client.origin - origin).length (); auto distance = (client.origin - origin).lengthSq ();
// now find nearest player // now find nearest player
if (distance < nearestDistance) { if (distance < nearest) {
index = i; result = &client;
nearestDistance = distance; nearest = distance;
}
} }
} }
return result;
};
auto client = findNearbyClient ();
// in case of worst case // update noise stats
if (index < 0 || index >= game.maxClients ()) { auto registerNoise = [&origin, &client, &volume] (float distance, float lasting) {
client->hearingDistance = distance * volume;
client->timeSoundLasting = game.time () + lasting;
client->sound = origin;
};
// client wasn't found
if (!client) {
return; return;
} }
Client &client = m_clients[index];
if (strncmp ("player/bhit_flesh", sample, 17) == 0 || strncmp ("player/headshot", sample, 15) == 0) {
// hit/fall sound? // hit/fall sound?
client.hearingDistance = 768.0f * volume; if (noise & Noise::HitFall) {
client.timeSoundLasting = game.timebase () + 0.5f; registerNoise (768.0f, 0.52f);
client.sound = origin;
} }
else if (strncmp ("items/gunpickup", sample, 15) == 0) {
// weapon pickup? // weapon pickup?
client.hearingDistance = 768.0f * volume; else if (noise & Noise::Pickup) {
client.timeSoundLasting = game.timebase () + 0.5f; registerNoise (768.0f, 0.45f);
client.sound = origin;
} }
else if (strncmp ("weapons/zoom", sample, 12) == 0) {
// sniper zooming? // sniper zooming?
client.hearingDistance = 512.0f * volume; else if (noise & Noise::Zoom) {
client.timeSoundLasting = game.timebase () + 0.1f; registerNoise (512.0f, 0.10f);
client.sound = origin;
} }
else if (strncmp ("items/9mmclip", sample, 13) == 0) {
// ammo pickup? // ammo pickup?
client.hearingDistance = 512.0f * volume; else if (noise & Noise::Ammo) {
client.timeSoundLasting = game.timebase () + 0.1f; registerNoise (512.0f, 0.25f);
client.sound = origin;
} }
else if (strncmp ("hostage/hos", sample, 11) == 0) {
// CT used hostage? // ct used hostage?
client.hearingDistance = 1024.0f * volume; else if (noise & Noise::Hostage) {
client.timeSoundLasting = game.timebase () + 5.0f; registerNoise (1024.0f, 5.00f);
client.sound = origin;
} }
else if (strncmp ("debris/bustmetal", sample, 16) == 0 || strncmp ("debris/bustglass", sample, 16) == 0) {
// broke something? // broke something?
client.hearingDistance = 1024.0f * volume; else if (noise & Noise::Broke) {
client.timeSoundLasting = game.timebase () + 2.0f; registerNoise (1024.0f, 2.00f);
client.sound = origin;
} }
else if (strncmp ("doors/doormove", sample, 14) == 0) {
// someone opened a door // someone opened a door
client.hearingDistance = 1024.0f * volume; else if (noise & Noise::Door) {
client.timeSoundLasting = game.timebase () + 3.0f; registerNoise (1024.0f, 3.00f);
client.sound = origin;
} }
} }
void BotUtils::simulateSoundUpdates (int playerIndex) { void BotUtils::simulateNoise (int playerIndex) {
// this function tries to simulate playing of sounds to let the bots hear sounds which aren't // this function tries to simulate playing of sounds to let the bots hear sounds which aren't
// captured through server sound hooking // captured through server sound hooking
@ -407,27 +423,28 @@ void BotUtils::simulateSoundUpdates (int playerIndex) {
float hearDistance = 0.0f; float hearDistance = 0.0f;
float timeSound = 0.0f; float timeSound = 0.0f;
auto buttons = client.ent->v.button | client.ent->v.oldbuttons;
if (client.ent->v.oldbuttons & IN_ATTACK) // pressed attack button? if (buttons & IN_ATTACK) // pressed attack button?
{ {
hearDistance = 2048.0f; hearDistance = 2048.0f;
timeSound = game.timebase () + 0.3f; timeSound = game.time () + 0.3f;
} }
else if (client.ent->v.oldbuttons & IN_USE) // pressed used button? else if (buttons & IN_USE) // pressed used button?
{ {
hearDistance = 512.0f; hearDistance = 512.0f;
timeSound = game.timebase () + 0.5f; timeSound = game.time () + 0.5f;
} }
else if (client.ent->v.oldbuttons & IN_RELOAD) // pressed reload button? else if (buttons & IN_RELOAD) // pressed reload button?
{ {
hearDistance = 512.0f; hearDistance = 512.0f;
timeSound = game.timebase () + 0.5f; timeSound = game.time () + 0.5f;
} }
else if (client.ent->v.movetype == MOVETYPE_FLY) // uses ladder? else if (client.ent->v.movetype == MOVETYPE_FLY) // uses ladder?
{ {
if (cr::abs (client.ent->v.velocity.z) > 50.0f) { if (cr::abs (client.ent->v.velocity.z) > 50.0f) {
hearDistance = 1024.0f; hearDistance = 1024.0f;
timeSound = game.timebase () + 0.3f; timeSound = game.time () + 0.3f;
} }
} }
else { else {
@ -436,7 +453,7 @@ void BotUtils::simulateSoundUpdates (int playerIndex) {
if (mp_footsteps.bool_ ()) { if (mp_footsteps.bool_ ()) {
// moves fast enough? // moves fast enough?
hearDistance = 1280.0f * (client.ent->v.velocity.length2d () / 260.0f); hearDistance = 1280.0f * (client.ent->v.velocity.length2d () / 260.0f);
timeSound = game.timebase () + 0.3f; timeSound = game.time () + 0.3f;
} }
} }
@ -445,7 +462,7 @@ void BotUtils::simulateSoundUpdates (int playerIndex) {
} }
// some sound already associated // some sound already associated
if (client.timeSoundLasting > game.timebase ()) { if (client.timeSoundLasting > game.time ()) {
if (client.hearingDistance <= hearDistance) { if (client.hearingDistance <= hearDistance) {
// override it with new // override it with new
client.hearingDistance = hearDistance; client.hearingDistance = hearDistance;
@ -481,7 +498,7 @@ void BotUtils::updateClients () {
if (client.flags & ClientFlags::Alive) { if (client.flags & ClientFlags::Alive) {
client.origin = player->v.origin; client.origin = player->v.origin;
simulateSoundUpdates (i); simulateNoise (i);
} }
} }
else { else {
@ -576,7 +593,7 @@ void BotUtils::sendPings (edict_t *to) {
client.ping = getPingBitmask (client.ent, rg.int_ (5, 10), rg.int_ (15, 40)); client.ping = getPingBitmask (client.ent, rg.int_ (5, 10), rg.int_ (15, 40));
} }
msg.start (MSG_ONE_UNRELIABLE, kGamePingSVC, nullvec, to) msg.start (MSG_ONE_UNRELIABLE, kGamePingSVC, nullptr, to)
.writeLong (client.ping) .writeLong (client.ping)
.end (); .end ();
} }